Basic functionality.
>>> import pylibmc, _pylibmc
>>> from tests import make_test_client
>>> from hashlib import md5
>>> _pylibmc.__version__ == pylibmc.__version__
True
>>> c = make_test_client(_pylibmc.client)
>>> c.get("hello")
>>> c.set("test_key", 123)
True
>>> c.get("test_key") == 123
True
>>> c.get("test_key_2")
>>> c.delete("test_key")
True
>>> c.get("test_key")

The invariant mc.get_multi([k])[k] == mc.get(k) should hold for string and byte
types. Mixed key types should work too.

Work around the lack of the 'u' string prefix in Python 3.2: get a text
string containing "hello" by decoding a byte string
>>> hello_key = b'hello'.decode()
>>> c.set_multi({hello_key: 'world', b'foo': 'bar'})
[]
>>> c.get_multi([hello_key, b'foo'])[hello_key]
'world'
>>> c.get_multi([hello_key, b'foo'])[b'foo']
'bar'

We should handle empty values just nicely.
>>> c.set("foo", "")
True
>>> c.get("foo")
''
>>> c.set("foo", b"")
True
>>> c.get("foo") == b''
True

Some sort of idempotency seems meaningful
>>> c.set("foo", "bar")
True
>>> c.get("foo")
'bar'

Important that we know how to deal with bytes and unicode data
>>> c.set("foo", b"bar")
True
>>> isinstance(c.get("foo"), bytes)
True
>>> c.set("foo", u"bar")
True
>>> isinstance(c.get("foo"), bytes)
False

Now this section is because most other implementations ignore zero-keys.
>>> c.get("")
>>> c.set("", "hi")
False
>>> c.delete("")
False
>>>

Multi functionality.
>>> c.set_multi({"a": 1, "b": 2, "c": 3})
[]
>>> sorted(c.get_multi("abc"))
['a', 'b', 'c']
>>> c.delete_multi("abc")
True
>>> not(c.get_multi("abc").keys())
True
>>> c.set_multi(dict(zip("abc", "def")), key_prefix="test_")
[]
>>> sorted(c.get_multi("abc", key_prefix="test_").items())
[('a', 'd'), ('b', 'e'), ('c', 'f')]
>>> c.get("test_a")
'd'
>>> c.delete_multi("abc", key_prefix="test_")
True
>>> bool(c.get_multi("abc", key_prefix="test_"))
False

Zero-key-test-time!
>>> c.get_multi([""])
{}
>>> c.delete_multi([""])
False
>>> c.set_multi({"": "hi"})
['']
>>> c.delete_multi({"a": "b"})
Traceback (most recent call last):
  ...
TypeError: keys must be a sequence, not a mapping

Timed stuff. The reason we, at UNIX times, set it two seconds in the future and
then sleep for >3 is that memcached might round the time up and down and left
and yeah, so you know...
>>> from time import sleep, time
>>> c.set("hi", "steven", 1)
True
>>> c.get("hi")
'steven'
>>> sleep(2.1)
>>> c.get("hi")
>>> c.set("hi", "loretta", int(time()) + 2)
True
>>> c.set_multi({"hi2": "charlotta"}, 1)
[]
>>> c.get("hi")
'loretta'
>>> c.get("hi2")
'charlotta'
>>> sleep(3.1)
>>> c.get("hi"), c.get("hi2")
(None, None)

See issue #1 ``http://github.com/lericson/pylibmc/issues/#issue/1`` -- delete
should not accept a time argument.
>>> c.delete("foo", 123)
Traceback (most recent call last):
  ...
TypeError: delete() takes exactly 1 argument (2 given)
>>> try:
...   c.delete_multi(["foo"], time=123)
... except TypeError as e:
...   print('nope')
nope

Now for keys with funny types.
# moved to unit test; restore with doctest IGNORE_EXCEPTION_DETAIL directive
# after dropping 2.6 support:
>>> # c.set(1, "hi")
>>> c.get(1)
Traceback (most recent call last):
  ...
TypeError: key must be bytes
>>> c.set_multi({1: True})
Traceback (most recent call last):
  ...
TypeError: key must be bytes
>>> c.get_multi([1, 2])
Traceback (most recent call last):
  ...
TypeError: key must be bytes

This didn't use to work, but now it does.
>>> c.get_multi([])
{}

Getting stats is fun!
>>> for (svr, stats) in c.get_stats():
...     ks = sorted(stats.keys())
...     while ks:
...         cks, ks = ks[:6], ks[6:]
...         print(", ".join(cks))
bytes, bytes_read, bytes_written, cmd_get, cmd_set, connection_structures
curr_connections, curr_items, evictions, get_hits, get_misses, limit_maxbytes
pid, pointer_size, rusage_system, rusage_user, threads, time
total_connections, total_items, uptime, version

Also test some flush all.
>>> c.set("hi", "guys")
True
>>> c.get("hi")
'guys'
>>> c.flush_all()
True
>>> c.get("hi")
>>>

Get and set booleans.
>>> c.set("greta", True)
True
>>> int(c.get("greta"))
1
>>> c.set("greta", False)
True
>>> int(c.get("greta"))
0
>>> c.delete("greta")
True

Complex data types!
>>> v = ValueError("what?")
>>> v.args
('what?',)
>>> c.set("tengil", v)
True
>>> c.get("tengil").args == v.args
True

Cloning (ethically, I don't know about it.)
>>> c is not c.clone()
True
>>> c2 = c.clone()
>>> c.set("test", "hello")
True
>>> c2.get("test")
'hello'
>>> c2.delete("test")
True
>>> del c2

Per-error exceptions
>>> try:
...   c.incr("test")
... except pylibmc.NotFound:
...   print("ok")
ok
>>> try:
...   c.incr(chr(0))
... except (pylibmc.ProtocolError, pylibmc.SocketCreateError):
...   print("ok")
ok

Behaviors.
>>> c.set_behaviors({"tcp_nodelay": True, "hash": 6})
>>> behaviors_items = [('hash', 6), ('tcp_nodelay', 1)]
>>> behaviors_items == sorted((k, v) for (k, v) in c.get_behaviors().items()
...             if k in ("tcp_nodelay", "hash"))
True

Binary protocol!
>>> c = make_test_client(_pylibmc.client, binary=True)
>>> c.set("hello", "world")
True
>>> c.get("hello")
'world'
>>> c.delete("hello")
True

incr_multi
>>> c.add_multi({'a': 1, 'b': 0, 'c': 4})
[]
>>> c.incr_multi(('a', 'b', 'c'), delta=1)
>>> sorted(c.get_multi(('a', 'b', 'c')).items()) == [('a', 2), ('b', 1), ('c', 5)]
True
>>> c.delete_multi(('a', 'b', 'c'))
True
>>> c.add_multi({'a': 1, 'b': 0, 'c': 4}, key_prefix='x')
[]
>>> c.incr_multi(('a', 'b', 'c'), key_prefix='x', delta=5)
>>> test_items = [('a', 6), ('b', 5), ('c', 9)]
>>> sorted(c.get_multi(('a', 'b', 'c'), key_prefix='x').items()) == test_items
True
>>> c.delete_multi(('a', 'b', 'c'), key_prefix='x')
True
>>> c.add('a', 1)
True

# moved to unit test; restore with doctest IGNORE_EXCEPTION_DETAIL directive
# after dropping 2.6 support:
>>> # c.incr_multi(('a', 'b', 'c'), key_prefix='x', delta=1)
>>> c.delete('xa')
False

Empty server lists are bad for your health.
# moved to unit test; restore with doctest IGNORE_EXCEPTION_DETAIL directive
# after dropping 2.6 support:
>>> # c = _pylibmc.client([])

Python-wrapped behaviors dict
>>> pc = make_test_client()
>>> (pc.behaviors["hash"], pc.behaviors["distribution"])
('default', 'modula')
>>> pc.behaviors.update({"hash": "fnv1a_32", "distribution": "consistent"})
>>> (pc.behaviors["hash"], pc.behaviors["distribution"])
('fnv1a_32', 'consistent')

Note: `remove_failed` came about in libmemcached 0.49.
>>> pc = make_test_client()
>>> b = pc.behaviors
>>> b["hash"]
'default'
>>> b["hash"] = 'fnv1a_32'
>>> pc.behaviors["hash"]
'fnv1a_32'
>>> super(pylibmc.Client, pc).get_behaviors()["hash"] == 6
True
>>> pc.set_behaviors({"_retry_timeout": 123})

Ormod's Zero-byte Adventure Story
>>> bc = make_test_client(_pylibmc.client, binary=True)
>>> bc.set(b"\x00\x00\x01", "ORMOD")
True
>>> bc.get_multi([b"\x00\x00\x01"]) == {b'\x00\x00\x01': 'ORMOD'}
True

Test server/client max length
>>> mc = make_test_client()
>>> mc.get('x'*250)
>>> mc.get('x'*251)
Traceback (most recent call last):
  ...
ValueError: key length 251 too long, max is 250

Make sure ServerDown exception exists
>>> hasattr(pylibmc, 'ServerDown') or _pylibmc.libmemcached_version_hex < 0x01000002
True

Gets should return (None, None) for non-existing keys
>>> c = make_test_client(_pylibmc.client, binary=True)
>>> c.set_behaviors({'cas': True})
>>> c.gets('')
(None, None)
>>> c.gets('non_existing_key')
(None, None)

Whoop, pickle protocol
>>> int(mc.behaviors['pickle_protocol'])
-1
>>> mc.behaviors['pickle_protocol'] = 2
>>> mc.set('foo', ('bar', 'quux'))
True
>>> mc.behaviors['pickle_protocol'] = 1
>>> mc.set('foo', ('bar', 'quuz'))
True
>>> mc.behaviors['pickle_protocol'] = -1
>>> mc.get('foo')
('bar', 'quuz')

With reference to bug #252
>>> c = make_test_client(behaviors={'pickle_protocol': 2})
>>> int(c.behaviors['pickle_protocol'])
2

Compression shouldn't break anything
>>> c = make_test_client(_pylibmc.client, binary=True)

Try to go over the 1mb object size limit, but compress so that we don't
>>> c.set('foo', b'a'*(1024*1024+1), min_compress_len=1)
True
>>> md5(c.get('foo')).hexdigest() == md5(b'a'*(1024*1024+1)).hexdigest()
True
>>> c.set('foo', 'a'*(1024*1024+1), min_compress_len=1)
True
>>> c.get('foo') == 'a'*(1024*1024+1)
True
>>> c.set('foo', 'a'*(1024*1024+1), min_compress_len=1, compress_level=1)
True
>>> c.get('foo') == 'a'*(1024*1024+1)
True
>>> c.set('foo', 'a'*(1024*1024+1), min_compress_len=1, compress_level=9)
True
>>> c.get('foo') == 'a'*(1024*1024+1)
True
>>> c.set('foo', 'abc', min_compress_len=1, compress_level=9)
True
>>> c.get('foo')
'abc'
>>> c.set_multi({'a': 20, 'b': '*'*500}, min_compress_len=1)
[]
>>> c.set_multi({'a': 20, 'b': '*'*500}, min_compress_len=1, compress_level=1)
[]
>>> c.set_multi({'a': 20, 'b': '*'*500}, min_compress_len=1, compress_level=9)
[]
