# Tests for nybitset

# Note: uses assert statements for brevity,
# so wouldn't check so much with python -O.

from guppy.sets import *
import pickle
from time import process_time as clock
import gc
import random
import sys
try:
    import numpy.random
except ImportError:
    has_numpy = 0
else:
    has_numpy = 1

if has_numpy:
    def random_integers_list(low, high, length):
        return list(map(int, numpy.random.random_integers(low, high, [length])))
else:
    def random_integers_list(low, high, length):
        return [random.randint(low, high) for i in range(length)]


Empty = immbitset()
Omega = ~Empty
bitsmut = mutbitset
bitset = immbitset
bitrange = immbitrange
bitsingle = immbit


def absorption(a, b):
    assert a & (a | b) == a
    assert a | (a & b) == a


def associative(a, b, c):
    assert (a & b) & c == a & (b & c)
    assert (a | b) | c == a | (b | c)


def commutative(a, b):
    assert a & b == b & a
    assert a | b == b | a


def deMorgan(a, b, c=None):
    if c is None:
        assert ~(a & b) == ~a | ~b
        assert ~(a | b) == ~a & ~b
    else:
        assert c - (a & b) == (c - a) | (c - b)
        assert c - (a | b) == (c - a) & (c - b)


def idempotence(a):
    assert a & a == a
    assert a | a == a


def inclusion(a, b):
    assert a & b <= a
    assert a & b <= b
    assert a | b >= a
    assert a | b >= b


def distributive(a, b, c):
    assert a | (b & c) == (a | b) & (a | c)
    assert a & (b | c) == (a & b) | (a & c)
    assert (a & b) | (b & c) | (c & a) == (a | b) & (b | c) & (c | a)
    assert not (a & b == a & c and a | b == a | c) or (b == c)


def test_set_operations(as_, bs, cs):
    for a in as_:
        idempotence(a)
        for b in bs:
            inclusion(a, b)
            commutative(a, b)
            absorption(a, b)
            for c in cs:
                associative(a, b, c)
                distributive(a, b, c)
                deMorgan(a, b, c)


def test_set_sub(as_, bs):
    def imp(a, b):
        assert not a or b
    for a in as_:
        for b in bs:
            imp(len(a) != len(b), a != b)
            imp(a < b, b > a and (not b < a))
            imp(a <= b, b >= a and (a < b or a == b) and not a > b)
            imp(a == b, a <= b and a >= b and not a != b and not b != a)
            imp(a != b, not a == b and not b == a)
            imp(a > b, b < a and not b > a)
            imp(a >= b, b <= a and (b < a or a == b) and not a < b)


def test_set_len(as_, bs):
    # If a set can provide a len(), it should be convertible to a list
    for a in as_:
        assert len(a) == len(list(a))
        assert len(a & a) == len(a)
        assert len(a | a) == len(a)
        for b in bs:

            # Test len of binary ops

            assert len(a | b) == len(list(a | b))
            assert len(a & b) == len(list(a & b))
            assert len(a - b) == len(list(a - b))
            assert len(a ^ b) == len(list(a ^ b))


def test_set_convert(as_, bs):
    for a in as_:
        for b in bs:
            # Conversions

            assert a | list(b) == a | b
            assert a - tuple(b) == a - b
            assert a & list(b) == a & b
            assert a ^ tuple(b) == a ^ b


def eltime(f, args=(), N=1, retx=0):
    r = list(range(N))
    starttime = clock()
    for i in r:
        x = f(*args)
    endtime = clock()
    elapsed = endtime - starttime
    if retx:
        return elapsed, x
    else:
        return elapsed


'.nython on'


class IdSet(bitsmut):
    def append(self, x):
        bitsmut.append(self, id(x) // 12)

    def remove(self, x):
        bitsmut.remove(self, id(x) // 12)

    def __contains__(self, x):
        return bitsmut.__contains__(self, id(x) // 12)


'.nython off'


def add(a, b):
    c = b
    while c:
        a, c = a ^ c, (a & c) << 1
        print(a, c)
    return a


def randint(lim=1 << 30):
    # Return a random signed int
    return int(random.randrange(-lim, lim))


def randlong():
    a = randint()
    b = randint()
    ash = randint() & 255
    c = randint()
    d = randint()
    bsh = randint() & 255
    r = (a * b << ash) + (c * d << bsh)
    return r


def dictset(l):
    ds = {}
    for e in l:
        if e not in ds:
            ds[e] = 1
    return ds


def dslist(l):
    ds = dictset(l)
    ks = list(ds.keys())
    ks.sort()
    return ks


def randlist(n, amp):
    ' randlist(n, amp) -> list of n unique random ints in [-amp,amp]'
    ds = {}
    rng = []  # To become a non-sorted list of unique random ints
    for i in range(10000):
        while 1:
            b = randint(50000)
            if b not in ds:
                rng.append(b)
                ds[b] = 1
                break
    return rng


'.nython on'


def t_append(a, b):
    ap = a.append
    for bit in b:
        ap(bit)


def t_append_id(a, b):
    ap = a.append
    for bit in b:
        ap(id(bit) // 12)


'.nython off'


class Test:
    # Set to 1 if test should be faster (less exhaustive) than normally
    faster = 1

    def test0(self):
        pass

    def test1(self):
        import io
        f = io.StringIO()

        bitset([1, 3, 4]) | []
        bitset([1, 3, 4]) & []
        #bitset([1,3,4]) | {}
        # bitset([1,3,4]) & {}
        bitset([1, 3, 4]) | [5]
        bitset([1, 3, 4]) | list(range(100))
        bitset([1, 3, 4]) | list(range(100, -1, -1))

        empties = (
            bitset(),
            bitset([]),
            bitset(()),
            bitset(0),
            bitset(0),
            bitset(bitset())
        )
        print(empties, file=f)
        for e in empties:
            assert e is Empty

        bitset(0x1 << 30)
        bitset(0x1 << 32)

        print(bitset(0x8000), file=f)
        print(bitset((4,)), file=f)
        print(~bitset(0x8000), file=f)
        print(bitset([1]) | bitset(3), file=f)
        print(int(bitset([1])), file=f)
        print(int(bitset([1])), file=f)

        ms = bitset(0).mutcopy()
        msa = ms
        ms |= 1
        print(list(ms), file=f)
        ms |= 0x4000
        print(list(ms), file=f)
        ms |= [3, 4]
        print(list(ms), file=f)
        ms |= (6, 8)
        print(list(ms), file=f)
        ms |= bitset([7])
        print(list(ms), ms, file=f)
        ms |= bitset([37])
        ts = bitset(ms)
        print(ts, file=f)
        ms &= ts
        print(ms, file=f)

        ms &= 1
        print(ms, file=f)
        ms |= ts
        ms &= 0x4000
        print(list(ms), file=f)
        ms |= ts
        ms &= [3, 4]
        print(list(ms), file=f)
        ms |= ts
        ms &= (6, 8)
        print(list(ms), file=f)
        ms |= ts
        ms &= bitset([7])
        print(ms, file=f)

        ms |= ts
        ms &= ~bitset([6])
        print(ms, 'ts&.', ts & ~bitset([6]), file=f)

        ms ^= 1
        print(ms, file=f)
        ms ^= 0x4000
        print(list(ms), file=f)
        ms ^= [3, 4]
        print(list(ms), file=f)
        ms ^= (6, 8)
        print(list(ms), file=f)
        ms ^= bitset([7])

        print(ms, file=f)

        ms &= 0
        ms |= ts

        ms |= ~ts
        print(ms, 'mt', ms | ~ ts, ts | ~ts, ~bitset([]) | ~ts, file=f)

        xs = bitset(ms)

        ms |= 1
        print(ms, xs | 1, int(xs), int(xs), file=f)

        ms ^= ms
        print(ms, file=f)

        ms &= ~ms
        print(ms, int(ms), int(ms), file=f)

        ms |= -1
        print(ms, int(ms), file=f)
        ms &= -2
        print(ms, int(ms), file=f)
        ms ^= -4
        print(ms, int(ms), file=f)

        ms |= -1
        print(ms, int(ms), file=f)
        ms &= -2
        print(ms, int(ms), file=f)
        ms ^= -4
        print(ms, int(ms), file=f)

        ms |= bitset(-1)
        print(ms, int(ms), file=f)
        ms &= bitset(-2)
        print(ms, int(ms), file=f)

        assert ms is msa

        print(bitset(-1), file=f)
        print(bitset([-1]), file=f)
        print(bitset([-1]) | bitset([4]), file=f)
        #print >>f,long(bitset([-1]))

        # LP: #770882
        if sys.hexversion < 0x2070000:
            assert f.getvalue() == """\
(ImmBitSet([]), ImmBitSet([]), ImmBitSet([]), ImmBitSet([]), ImmBitSet([]), ImmBitSet([]))
ImmBitSet([15])
ImmBitSet([4])
(~ImmBitSet([15]))
ImmBitSet([0, 1])
2
2
[0]
[0, 14]
[0, 3, 4, 14]
[0, 3, 4, 6, 8, 14]
[0, 3, 4, 6, 7, 8, 14] MutBitSet([0, 3, 4, 6, 7, 8, 14])
ImmBitSet([0, 3, 4, 6, 7, 8, 14, 37])
MutBitSet([0, 3, 4, 6, 7, 8, 14, 37])
MutBitSet([0])
[14]
[3, 4]
[6, 8]
MutBitSet([7])
MutBitSet([0, 3, 4, 7, 8, 14, 37]) ts&. ImmBitSet([0, 3, 4, 7, 8, 14, 37])
MutBitSet([3, 4, 7, 8, 14, 37])
[3, 4, 7, 8, 37]
[7, 8, 37]
[6, 7, 37]
MutBitSet([6, 37])
MutBitSet(~ImmBitSet([])) mt (~ImmBitSet([])) (~ImmBitSet([])) (~ImmBitSet([]))
MutBitSet(~ImmBitSet([])) (~ImmBitSet([])) -1 -1
MutBitSet([])
MutBitSet([]) 0 0
MutBitSet(~ImmBitSet([])) -1
MutBitSet(~ImmBitSet([0])) -2
MutBitSet([1]) 2
MutBitSet(~ImmBitSet([])) -1
MutBitSet(~ImmBitSet([0])) -2
MutBitSet([1]) 2
MutBitSet(~ImmBitSet([])) -1
MutBitSet(~ImmBitSet([0])) -2
(~ImmBitSet([]))
ImmBitSet([-1])
ImmBitSet([-1, 4])
"""

    def test2(self):
        # Test standard operators (not-inplace)
        for a in [randlong() for i in range(10)]:
            for b in [randlong() for j in range(10)]:
                ts = []
                for ta in (a, bitset(a), bitsmut(a)):
                    for tb in (b, bitset(b), bitsmut(b)):
                        tr = []
                        tr.append(ta | tb)
                        tr.append(ta & tb)
                        tr.append(ta ^ tb)

                        tr.append(ta | ~tb)
                        tr.append(ta & ~tb)
                        tr.append(ta ^ ~tb)

                        tr.append(~ta | tb)
                        tr.append(~ta & tb)
                        tr.append(~ta ^ tb)

                        tr.append(~ta | ~tb)
                        tr.append(~ta & ~tb)
                        tr.append(~ta ^ ~tb)
                        ts.append(tr)

                # LP: #770882
                if sys.hexversion >= 0x2070000:
                    continue
                for tr in ts[1:]:
                    for r, x in zip(tr, ts[0]):
                        assert int(r) == x

    def test3(self):
        # Test in-place operators
        p = randlong()
        op = randint()
        a = randlong()
        b = randlong()
        ts = []
        for tp in (p, bitset(p), bitsmut(p)):
            for ta in (a, bitset(a), bitsmut(a)):
                if op & 1:
                    ta |= tp
                elif op & 2:
                    ta &= tp
                elif op & 4:
                    ta ^= tp
                for tb in (b, bitset(b), bitsmut(b)):
                    tr = []
                    tb |= ta
                    tr.append(int(tb))
                    tb &= ta
                    tr.append(int(tb))
                    tb ^= ta
                    tr.append(int(tb))

                    tb |= ~ta
                    tr.append(int(tb))
                    tb &= ~ta
                    tr.append(int(tb))
                    tb ^= ~ta
                    tr.append(int(tb))
                    ts.append(tr)

        # LP: #770882
        if sys.hexversion >= 0x2070000:
            return
        for tr in ts[1:]:
            # print tr
            for r, x in zip(tr, ts[0]):
                assert int(r) == x

    def test4(self):
        # Some performance test
        def f1(n, x, y):
            while n > 0:
                x |= y
                x |= y
                x |= y
                x |= y
                x |= y
                n -= 1

        x = 0
        for exp in range(0, 1024*32, 16*32*(1+self.faster*31)):
            y = 1 << exp
            print(exp, eltime(f1, (1000, x, y)),
                  eltime(f1, (1000, bitset(x), y)),
                  eltime(f1, (1000, bitset(x), bitset(y))),
                  eltime(f1, (1000, bitsmut(x), y)),
                  eltime(f1, (1000, bitsmut(x), bitsmut(y))),
                  eltime(f1, (1000, bitsmut(x), bitset(y))))

    def test5(self):
        # Bitset from sequences in different ways

        bits = {}
        for i in range(50):
            bit = randint()
            bits[bit] = 1
            bits[bit+randint() % 15] = 1
            bits[bit+randint() % 15] = 1
            bits[bit-randint() % 15] = 1
            bits[bit-randint() % 15] = 1
        bits = list(bits)
        sbits = list(bits)
        sbits.sort()

        def dictset(bits):
            return dict([(bit, 1) for bit in bits])

        seqs = [bits, tuple(bits), dictset(bits)]
        for seq in seqs:
            assert list(bitset(seq)) == sbits

            bs = Empty
            bs = bs | seq
            assert list(bs) == sbits
            bs = Empty
            bs = seq | bs
            assert list(bs) == sbits
            bs = Empty
            bs |= seq
            assert list(bs) == sbits
            bs = bitsmut(Empty)
            bs |= seq
            assert list(bs) == sbits

            bs = Empty
            bs = bs ^ seq
            assert list(bs) == sbits
            bs = Empty
            bs = seq ^ bs
            assert list(bs) == sbits
            bs = Empty
            bs ^= seq
            assert list(bs) == sbits
            bs = bitsmut(Empty)
            bs ^= seq
            assert list(bs) == sbits

            bs = Omega
            bs = bs & seq
            assert list(bs) == sbits
            bs = Omega
            bs = seq & bs
            assert list(bs) == sbits
            bs = Omega
            bs &= seq
            assert list(bs) == sbits
            bs = bitsmut(Omega)
            bs &= seq
            assert list(bs) == sbits

            bs = Omega
            bs = bs ^ seq
            bs = ~bs
            assert list(bs) == sbits
            bs = Omega
            bs = seq ^ bs
            bs = ~bs
            assert list(bs) == sbits
            bs = Omega
            bs ^= seq
            bs = ~bs
            assert list(bs) == sbits
            bs = bitsmut(Omega)
            bs ^= seq
            bs = ~bs
            assert list(bs) == sbits

    def test6(self):
        # Comparisons
        # LP: #770882
        if sys.hexversion >= 0x2070000:
            return
        for a in (randlong(),):
            for b in (a, ~a, randlong()):
                assert ((bitset(a) == bitset(b)) == (a == b))
                assert ((bitset(a) != bitset(b)) == (a != b))
                assert ((bitset(a) == ~bitset(b)) == (a == ~b))
                assert ((bitset(a) != ~bitset(b)) == (a != ~b))
                assert ((~bitset(a) == bitset(b)) == (~a == b))
                assert ((~bitset(a) != bitset(b)) == (~a != b))
                assert ((~bitset(a) == ~bitset(b)) == (~a == ~b))
                assert ((~bitset(a) != ~bitset(b)) == (~a != ~b))

                assert ((bitsmut(a) == bitsmut(b)) == (a == b))
                assert ((bitsmut(a) != bitsmut(b)) == (a != b))

                assert ((bitsmut(a) == bitset(b)) == (a == b))
                assert ((bitsmut(a) != bitset(b)) == (a != b))

                assert ((bitset(a) == bitsmut(b)) == (a == b))
                assert ((bitset(a) != bitsmut(b)) == (a != b))

    def test7(self):
        # Bitsmut gymnastics
        import io
        f = io.StringIO()

        a = bitsmut(0)
        print(str(a), file=f)
        a.append(1)
        print(str(a), a.pop(), str(a), file=f)
        a.append(1)
        print(str(a), a.pop(-1), str(a), file=f)
        a.append(1)
        print(str(a), a.pop(0), str(a), file=f)
        a.append(1)
        a.append(2)
        a.append(3)
        print(str(a), a.pop(), str(a), file=f)
        print(str(a), a.pop(0), str(a), file=f)
        a.remove(2)
        print(str(a), file=f)

        print(f.getvalue())
        assert f.getvalue() == """\
MutBitSet([])
MutBitSet([1]) 1 MutBitSet([])
MutBitSet([1]) 1 MutBitSet([])
MutBitSet([1]) 1 MutBitSet([])
MutBitSet([1, 2, 3]) 3 MutBitSet([1, 2])
MutBitSet([1, 2]) 1 MutBitSet([2])
MutBitSet([])
"""

        def f(a, b):
            ap = a.append
            for bit in b:
                ap(bit)

        def flu(a, b):
            s = 0
            for bit in b:
                if bit in a:
                    s += 1
            return s

        def g(a, b):
            for bit in b:
                a[bit] = 1

        def h(a, b):
            for bit in b:
                a |= bitsingle(bit)

        def tms(rng, f=f):
            ms = bitsmut(0)
            t = eltime(f, (ms, rng))
            srng = list(rng)
            srng.sort()
            assert ms == bitset(srng)
            return t

        def tmslu(rng, n=None):
            if n is None:
                n = len(rng)
            ms = bitsmut(rng[:n])
            elt, s = eltime(flu, (ms, rng), retx=1)
            assert s == n
            return elt

        def tbslu(rng, n=None):
            if n is None:
                n = len(rng)
            ms = bitset(rng[:n])
            elt, s = eltime(flu, (ms, rng), retx=1)
            assert s == n
            return elt

        def tlo(rng):
            lo = 0

            def f(a, b):
                for bit in b:
                    a |= 1 << b
            return eltime(h, (lo, rng))

        def tbs(rng):
            lo = bitset()

            def f(a, b):
                for bit in b:
                    a |= bitsingle(b)
            return eltime(h, (lo, rng))

        def tls(rng):
            ls = []
            return eltime(f, (ls, rng))

        def tds(rng):
            ds = {}
            return eltime(g, (ds, rng))

        def tdslu(rng, n=None):
            if n is None:
                n = len(rng)
            ds = dict([(x, 1) for x in rng[:n]])
            elt, s = eltime(flu, (ds, rng), retx=1)
            assert s == n
            return elt

        step = (1 + self.faster*5)

        for rng in (list(range(0, 10000, step)),
                    list(range(0, 100000, step)),
                    list(range(10000, -1, -1*step)),
                    randlist(10000, 50000-self.faster*40000)):
            print(tms(rng), tds(rng), tls(rng), tms(rng, h),
                  tmslu(rng), tbslu(rng), tdslu(rng),
                  tmslu(rng, 100), tbslu(rng, 100), tdslu(rng, 100))

        rng = list(range(10000))
        print(tlo(rng), tbs(rng))

    def test8(self):
        # Subclassing a bitsmut
        BS = IdSet
        for bs in (BS(), BS([]), BS([0])):
            os = ((), [], {})
            for o in os:
                bs.append(o)
            for o in os:
                assert o in bs
            for o in os:
                bs.remove(o)
            for o in os:
                assert o not in bs

        try:
            from snidioms import ListLikeDictSet
        except ImportError:
            print('can not import snidioms, skipping a performance comparison')
        else:
            rng = list(range(10000))
            for s in [], BS(), ListLikeDictSet():
                print(eltime(t_append, (s, rng)))

            for s in [], bitsmut([]), :
                print(eltime(t_append_id, (s, rng)))

    def test9(self):
        # Making bigger bitsmuts - testing the split
        for i in (1000, 10000, 100000):
            r = list(range(i))
            m = bitsmut(r)
            assert list(m) == r

            la = random_integers_list(-i, i, i)
            m = bitsmut(la)
            las = dslist(la)
            bs = bitset(m)
            assert list(bs) == las

    def test10(self):

        # Performance test

        def tests(la):
            for i in (1000, 10000, 100000, 400000):
                print('eltime(bitset, (la[:%d],))' % i)
                print(eltime(bitset, (la[:i],)))
        la = list(range(400000))
        print('la = range(400000)')
        tests(la)
        la.reverse()
        print('la.reverse()')
        tests(la)
        la = random_integers_list(-400000, 400000, 400000)
        print('la=random_integers_list(-400000,400000,400000))')
        tests(la)

    def test11(self, n=1):
        # A specific bug showed when setting splitting_size
        la = random_integers_list(-400000, 400000, 400000)
        while n > 0:
            ms = bitsmut([])
            ms._splitting_size = 100
            ms |= la
            print('test11', n, ms._indisize, ms._num_seg)
            n -= 1

    def test12(self):
        # append should be able to reuse space that was pop()'d
        # even for other bit ranges
        # Due to allocation strategy, the size may differ an
        # initial round but should then be stable.

        for N in (32, 64, 128, 256, 31, 33, 63, 65, 255, 257):
            ms = bitsmut()

            # Train it
            rng = list(range(N))
            ms |= rng
            for popix in (-1, 0):
                for j in range(N):
                    ms.pop(popix)
                ms |= rng
            # Now should be stable..
            indisize = ms._indisize
            for popix in (-1, 0):
                for i in range(0, N*10, N):
                    pops = []
                    for j in range(N):
                        pops.append(ms.pop(popix))
                    assert list(ms) == []
                    if popix == -1:
                        pops.reverse()
                    assert pops == rng
                    rng = list(range(i, i+N))
                    ms |= rng
                    assert indisize == ms._indisize
                    assert list(ms) == rng

    def test13(self):
        # append, remove for inverted bitsmuts,
        # have inverted sense. 'nonzero' is always true.
        # (pop is not supported - it seems it conceptually should give infite range of bits)

        ms = bitsmut()
        assert not ms
        ms ^= ~0        # Make it inverted - contains 'all bits'
        assert ms
        ms.remove(0)
        assert ms
        assert list(~ms) == [0]
        try:
            ms.remove(0)
        except ValueError:
            pass
        else:
            raise AssertionError('expected ValueError for remove')
        ms.append(0)
        assert list(~ms) == []
        try:
            ms.append(0)
        except ValueError:
            pass
        else:
            raise AssertionError('expected ValueError for append')

        ms.remove(0)
        try:
            ms.pop()
        except ValueError:
            pass
        else:
            raise AssertionError('expected ValueError for pop')

    def test14(self):
        # Test the bitrange() constructor
        xs = (-1000, -100, -33, -32, -31, -10, -
              1, 0, 1, 10, 31, 32, 33, 100, 1000)
        for lo in xs:
            assert list(bitrange(lo)) == list(range(lo))
            for hi in xs:
                assert list(bitrange(lo, hi)) == list(range(lo, hi))
                for step in (1, 2, 3, 4, 5, 6, 7, 31, 32, 33):
                    r = list(range(lo, hi, step))
                    assert list(bitrange(lo, hi, step)) == r

    def test15(self):
        # Test the indexing
        # Only index 0 or -1 is currently supported, for first or last bit -
        # the others would take more work and might appear surprisingly slow.

        for a in range(-33, 34):
            for b in range(a+1, a+35):
                rng = list(range(a, b))
                bs = bitrange(a, b)
                assert bs[0] == a
                assert bs[-1] == b-1
                ms = bitsmut(bs)
                assert ms[0] == a
                assert ms[-1] == b-1
                i = 0
                while ms:
                    x = ms[i]
                    assert x == ms.pop(i)
                    assert x == rng.pop(i)
                    i = -1 - i

    def test16(self):
        # Test shifting
        for sh in range(64):
            for v in range(64):
                assert int(bitset(v) << sh) == int(v) << sh

        maxint = sys.maxsize
        minint = -maxint - 1

        b = bitset([0])

        for sh in (maxint, -maxint, minint):
            assert b << sh == bitset([sh])

        def tsv(bs, sh):
            try:
                bs << sh
            except OverflowError:
                pass
            else:
                raise AssertionError('expected OverflowError')

        tsv(bitset([maxint]), 1)
        tsv(bitset([minint]), -1)
        tsv(bitset([-maxint]) << (-1), -1)

        for a, b in ((0, 10), (0, 10000), (-1000, 1000)):
            for sh in (-257, -256, -255, -1, 0, 1, 255, 256, 257):
                for step in (1, 2, 3):
                    assert bitrange(a, b, step) << sh == bitrange(
                        a+sh, b+sh, step)

    def test17(self):
        # Comparisons: inclusion tests

        for a in (0, 1, 2, list(range(31)), list(range(32)), list(range(33)), randlong()):
            for b in (0, 1, 2, list(range(31)), list(range(32)), list(range(33)), randlong()):
                for as_ in (bitset(a), ~bitset(a), bitsmut(a), bitsmut(~bitset(a))):
                    for bs in (as_, ~as_, bitset(b), ~bitset(b), bitsmut(b), bitsmut(~bitset(b))):
                        t = as_ <= bs
                        assert t == (bs >= as_)
                        assert t == ((as_ & bs) == as_)
                        assert t == ((int(as_) & int(bs)) == int(as_))

                        t = as_ < bs
                        assert t == (bs > as_)
                        assert t == ((as_ <= bs) and (as_ != bs))
                        assert t == ((as_ <= bs) and (int(as_) != int(bs)))

    def test18(self):
        # Testing internal consistency, with test values
        # that may not be practical to convert to longs.
        # Using Properties of Boolean algebras
        # (from 'Mathematichal Handbook'... tables p.30, p.15)
        # Some tests should be quite redundant given others passed,
        # but are kept anyway for reference & doublechecking.

        any = [bitset(abs(randlong())) << randint(),
               bitset(abs(randlong())) << randint(),
               bitset(abs(randlong())) << randint() | bitset(
                   abs(randlong())) << randint(),
               bitset(abs(randlong())) << randint() | bitset(
                   abs(randlong())) << randint(),
               ]

        any = [Empty, Omega, bitset([0]),
               bitset(randlong()),
               bitset(randlong())] + [a ^ randlong() for a in any]
        any = any + [bitsmut(a) for a in any]
        for a in any:
            # Empty and Omega are the least and greatest elements
            assert Empty <= a <= Omega
            assert a & Empty == Empty
            assert a | Omega == Omega
            # Identity elements for & and |
            assert a & Omega == a
            assert a | Empty == a
            # Complement laws
            assert a & ~a == Empty
            assert a | ~a == Omega
            assert ~Empty == Omega
            assert ~Omega == Empty
            assert ~(~a) == a

            idempotence(a)
            for b in any:
                # Relative complement, definition
                assert a & ~b == a - b
                # ...
                absorption(a, b)
                commutative(a, b)
                deMorgan(a, b)
                inclusion(a, b)
                for c in any:
                    associative(a, b, c)
                    distributive(a, b, c)

                # ...
                assert ((a <= b) == (a & b == a) == (a | b == b) ==
                        (a & ~b == Empty) == (~b <= ~a) == (~a | b == Omega))

                # Symmetric difference
                # From p. 15
                assert a ^ b == b ^ a
                for c in any:
                    assert (a ^ b) ^ c == a ^ (b ^ c)
                    deMorgan(a, b, c)
                assert a ^ Empty == a
                assert a ^ a == Empty
                assert a ^ b == (a & ~b) | (b & ~a)

    def test19(self):
        # Finding prime numbers using the Sieve of Eratosthenes
        # - an excercise for eg bitrange().

        N = 4000

        primes = ([2] | bitrange(3, N, 2)).mutcopy()
        for i in bitrange(3, N // 2, 2):
            primes &= ~bitrange(2 * i, N, i)

        # print primes._indisize, primes._num_seg

        primes = list(primes)
        assert len(primes) == 550
        assert primes[:10] == [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
        assert primes[399] == 2741
        assert primes[549] == 3989
        return primes

    def test20(self):
        # Some bitrange arguments used when debugging its optimized version.
        # Entered here, in case some wasn't covered by previous tests.
        maxint = sys.maxsize
        minint = -maxint - 1
        for a in (
            (32,),
            (31,),
            (33,),
            (13,),
            (1, 33),
            (1, 33, 2),
            (1, 63, 2),
            (0, 64, 32),
            (0, 64+17, 32),
            (0, 32*3, 32),
            (0, 32*3+1, 32),
            (0, 32*4, 32),
            (0, 32*4, 16),
            (0, 32*2, 16),
            (0, 32*3, 16),
            (maxint-32, maxint),
            (maxint-32, maxint, 2),
            (maxint-32, maxint, 4),
            (maxint-32, maxint, 16),
            (maxint-32, maxint, 20),
            (maxint-320, maxint),
            (maxint-320, maxint, 2),
            (maxint-320, maxint, 4),
            (maxint-320, maxint, 16),
            (maxint-320, maxint, 20),
            (-1, maxint, maxint),
            (0, maxint, maxint),
            (1, maxint, maxint),
            (minint, maxint, maxint),
            (minint, maxint, maxint//32),
            (minint, maxint, maxint//320),
            (minint, maxint, -(minint//32)),
            (minint, maxint, -(minint//320)),
        ):
            br = bitrange(*a)
            # print br
            assert list(br) == list(range(*a))

        try:
            bitrange(minint, maxint, 1)
        except OverflowError:
            pass
        else:
            raise AssertionError('expected OverflowError')

        # a more exhaustive check,
        # it tests some > 70000 combinations if not self.faster
        if not self.faster:
            print('bitrange testing many combinations, this may take some time...')
        for a in range(0, 34, 1 + 8*self.faster):
            print('a', a, end=' ')
            sys.stdout.flush()
            for l in range(1000, 1034, 1 + 8*self.faster):
                for st in range(1, 34, 1 + 8*self.faster):
                    for arg in ((maxint - l, maxint - a, st),
                                (minint + a, minint + l, st)):
                        br = bitrange(*arg)
                        assert list(br) == list(range(*arg))
        print('done')

    def test21(self):
        # Test bitset as dict key - i.e. hashing, equality
        D = {}
        a = bitrange(1)
        b = bitrange(1)
        c = ~a
        d = ~b
        D[a] = 1
        D[c] = -1
        assert D[b] == D[a] == 1
        assert D[c] == D[d] == -1

    def test22(self):
        # Test pickling
        any = [bitset() for x in range(10)]
        any = any + [bitrange(x, y, z)
                     for x in (-1000, 0, 1000)
                     for y in (2000,)
                     for z in (1, 3, 300)]
        any = any + [~x for x in any]
        any = any + [bitsmut(x) for x in any]
        for a in any:
            for bin in (0, 1):
                da = pickle.dumps(a, bin)
                # print len(da), len(bitset(a))
                aa = pickle.loads(da)
                assert aa == a
                assert type(aa) is type(a)

    def test23(self):
        # bitset from general sequence with iterator
        # We already special-cased list, tuple & dict

        class T:
            def __init__(self, data):
                self.data = data

            def __iter__(self):
                return iter(self.data)

        l = list(range(10))
        t = T(l)
        b = bitset(t)
        assert list(b) == l

        bo100 = b | T([100])
        assert list(bo100) == l + [100]

        ms = bitsmut(t)
        assert ms == b

        ms |= T([100])
        assert ms == bo100

    def test24(self):
        # tests to do with the copy-on-write optimizations
        # this should show in improved timing for some operation sequences

        def f1(n):
            return bitrange(n).mutcopy()[0]

        t, v = eltime(f1, (10000000,), retx=1)
        print(t)
        assert v == 0

        bs = bitrange(10000000)

        def f2(bs):
            ms = bs.mutcopy()
            ms &= ~1
            return ms[0], bs[0]

        t, v = eltime(f2, (bs,), retx=1)
        print(t)
        assert v == (1, 0)

        ms = bs.mutcopy()

        # Test that a temporary immutable copy can be fast

        def f3(ms):
            bs = bitset(ms)
            return ms[0], bs[0],

        t, v = eltime(f3, (ms,), retx=1)
        print(t)
        assert v == (0, 0)

        def f4(ms):
            bs = bitset(ms)
            ms &= ~1
            return ms[0], bs[0],

        def f4b(ms):
            # make sure cur_field is cleared when bitset is made
            ms |= 1
            bs = bitset(ms)
            ms ^= 1
            return ms[0], bs[0],

        for f in (f4, f4b):
            ms = bs.mutcopy()

            t, v = eltime(f, (ms,), retx=1)
            print(t)
            assert v == (1, 0)

        ms = bs.mutcopy()

        # Test that a temporary mutable copy of a bitsmut can be fast

        def f5(ms):
            mc = ms.mutcopy()
            return mc[0], ms[0],

        t, v = eltime(f5, (ms,), retx=1)
        print(t)
        assert v == (0, 0)

        # Test that a temporary mutable copy of a bitsmut can be fast
        # and still be separately updated

        def f6(ms):
            ms &= ~bitrange(15)
            mc = ms.mutcopy()
            mc |= [2]
            ms |= [4]
            return mc[0], ms[0],

        def f6a(ms):
            # as f6 but updating in the other order - tried to induce a bug
            ms &= ~bitrange(15)
            mc = ms.mutcopy()
            ms |= [4]
            mc |= [2]
            return mc[0], ms[0],

        def f6b(ms):
            # working harder and managed to provoke test of a noticed copy-on-write
            # requirement (cur_field had to be cleared when the set was borrowed)
            ms &= ~bitrange(15)
            ms |= [8]
            mc = ms.mutcopy()
            ms |= [1, 4]
            mc |= [2]
            ms &= ~bitsingle(1)
            return mc[0], ms[0],

        for f in (f6, f6a, f6b):
            t, v = eltime(f, (ms,), retx=1)
            print(t)
            assert v == (2, 4)

        # Temporary mutable copy of splitted bitsmut

        for f in (f6, f6a, f6b):
            bs = bitrange(100000) | bitrange(200000, 300000)
            ms = bs.mutcopy()

            ms |= bitsingle(150000)     # Force a split

            assert ms._num_seg > 1
            print('num_seg', ms._num_seg)

            t, v = eltime(f, (ms,), retx=1)
            print(t)
            assert v == (2, 4)

    def test25(self):
        # Thing that came up
        # converting to int should fail here, not become negative.
        # (Assuming 'standard' 2-complement int representation)

        bs = bitset(int(sys.maxsize)+1)
        # try:
        #     a = int(bs)
        # except OverflowError:
        #     pass
        # else:
        #     raise AssertionError('expected OverflowError')

        assert int(bs) == int(sys.maxsize)+1

        # These border cases should pass
        assert int(bitset(sys.maxsize)) == sys.maxsize
        assert int(bitset(-sys.maxsize - 1)) == - sys.maxsize - 1

        if 0:
            # This was added without implementing & testing
            # I have not implemented it yet.
            # It is possible but I don't need to right now. / Also Notes May 19 2005
            #

            # Relation operation with iterable right argument,
            # apparently not tested before. (Nov. 10 2004)

            assert not immbitset([1, 2, 3]) <= [1, 2]
            assert not mutbitset([1, 2, 3]) <= [1, 2]
            assert not mutnodeset([1, 2, 3]) <= [1, 2]
            assert not immnodeset([1, 2, 3]) <= [1, 2]
            assert immbitset([1, 2, 3]) <= [1, 2, 3]
            assert mutbitset([1, 2, 3]) <= [1, 2, 3]
            assert immnodeset([1, 2, 3]) <= [1, 2, 3]
            assert mutnodeset([1, 2, 3]) <= [1, 2, 3]
            assert [1, 2] <= immbitset([1, 2, 3])
            assert [1, 2] <= mutbitset([1, 2, 3])
            assert [1, 2] <= immnodeset([1, 2, 3])
            assert [1, 2] <= mutnodeset([1, 2, 3])
            assert not [1, 2, 3] <= immbitset([1, 2])
            assert not [1, 2, 3] <= mutbitset([1, 2])
            assert not [1, 2, 3] <= immnodeset([1, 2])
            assert not [1, 2, 3] <= mutnodeset([1, 2])

    def test26(self):
        # len() tests

        for thelen in [0, 15, 17, 31, 33, 1023, 1024, 1025, int(1e7)]:
            for args in [(thelen,), (0, thelen * 3, 3)]:
                bs = bitrange(*args)
                t, v = eltime(len, (bs,), retx=1)
                if t > 0.01:
                    print(t, v)
                assert v == thelen

                bs = bitsmut(bs)

                t, v = eltime(len, (bs,), retx=1)
                if t > 0.01:
                    print(t, v)
                assert v == thelen

    def test27(self):
        # slices
        for b in (bitset(64), bitrange(64), bitset(abs(randlong()))):
            for st in (b, b.mutcopy()):
                for i in (1, 2, 3, 30, 31, 32, 33, 34, 63, 64, 65):
                    assert b[:i] == bitset(list(b)[:i])
                    assert b[-i:] == bitset(list(b)[-i:])

    def test28(self):
        # test & set; test & clr
        for s in (bitsmut(), bitsmut(~bitset() & ~bitset([14]))):
            assert s.tas(14) == 0
            assert s.tas(14) == 1
            assert s.tac(14) == 1
            assert s.tac(14) == 0

    def test29(self):
        # Compatibility functions added:
        # add, discard, -, -=
        # Also tests S.mutcopy() where S is mutable with 1 or 2 segments

        def t(p):
            # print p._num_seg
            q = p.mutcopy()
            p.add(17)
            assert p != q
            q.append(17)
            assert p == q

            p.discard(-1)
            assert p == q
            p.discard(17)
            assert p != q
            q.remove(17)
            assert p == q

            r = p - q
            assert r == bitsmut([])

        ms = bitsmut(12345)
        t(ms)

        bs = bitrange(20, 100000) | bitrange(200000, 300000)
        ms = bs.mutcopy()

        ms |= bitsingle(150000)  # Force a split
        assert ms._num_seg > 1

        t(ms)

        all = 0, -1, 1, -2, 2, randlong(), -randlong()
        all = [bitsmut(a) for a in all]
        all = all + [bitsmut(a) for a in all]
        for a in all:
            a = a.mutcopy()
            aa = a.mutcopy()
            for b in all:
                a -= b
                aa &= ~b
                assert a == aa

    def test30(self):
        # Test nodeset

        nodeset = immnodeset
        ns = mutnodeset()
        ns0 = ns
        a = []
        b = ()
        c = {}
        d = 0
        e = ''

        # Test 5 ways to add elements

        ns.add(a)
        ns.append(b)
        ns |= nodeset([c])
        assert not ns.tas(d)
        ns ^= [e]

        assert ns == nodeset([a, b, c, d, e])

        # Test 5 ways to remove elements

        ns ^= [e]
        assert ns == nodeset([a, b, c, d])
        assert ns.tac(d)
        assert ns == nodeset([a, b, c])
        ns -= nodeset([c])
        assert ns == nodeset([a, b])
        ns.remove(b)
        assert ns == nodeset([a])
        ns.discard(a)
        assert ns == nodeset([])

        # Test pop
        ns.add(a)
        assert ns.pop() is a
        try:
            ns.pop()
        except ValueError:
            pass
        else:
            raise AssertionError('expected ValueError')

        assert ns0 is ns

        ns = immnodeset(ns)

        ns |= nodeset([a])
        assert ns == nodeset([a])
        assert ns is not ns0

        # ns is now immutable
        # this is like bitset
        # see note per Wed Jan 21 16:13:55 MET 2004
        # The change was made after that.

        ns1 = ns

        ns -= nodeset([a])

        # See note above. The following check
        # applies since mutability behaviour is as for bitset

        assert ns is not ns1

        assert ns == nodeset([])

        # Test clear

        ns = mutnodeset([1, 2, 3])
        assert len(ns) == 3
        ns.clear()
        assert len(ns) == 0
        assert list(ns) == []

    def test31(self):
        # Test nodeset, element-wise operations & object deallocation w. gc

        H = mutnodeset
        from sys import getrefcount as grc

        if 0:
            print(H.add.__doc__)
            print(H.append.__doc__)
            print(H.discard.__doc__)
            print(H.remove.__doc__)
            print(H.tas.__doc__)
            print(H.tac.__doc__)

        e1 = []
        e2 = []
        e3 = []
        r1 = grc(e1)
        r2 = grc(e2)
        r3 = grc(e3)

        s = H()
        s.add(e1)
        assert e1 in s
        assert e2 not in s
        s.append(e2)
        assert e2 in s
        assert s.tas(e3) == 0

        assert e3 in s

        assert r1 + 1 == grc(e1)
        assert r2 + 1 == grc(e2)
        assert r3 + 1 == grc(e3)

        assert s.tas(e3) == 1
        assert s.tac(e3) == 1
        assert s.tac(e3) == 0
        s.discard(e3)
        s.remove(e2)

        try:
            s.append(e1)
        except ValueError:
            pass
        else:
            raise AssertionError('no exception from append')

        s.remove(e1)

        try:
            s.remove(e1)
        except ValueError:
            pass
        else:
            raise AssertionError('no exception from remove')

        assert r1 == grc(e1)
        assert r2 == grc(e2)
        assert r3 == grc(e3)

        s.add(e1)
        s.add(e2)
        s.add(e3)

        s = None

        assert r1 == grc(e1)
        assert r2 == grc(e2)
        assert r3 == grc(e3)

        # Test gc support

        import gc

        s = H()
        s.append(e1)
        s.append(s)     # Make it cyclic
        assert s in s
        s = None
        gc.collect()
        #assert r1 == grc(e1)

        s = H()
        s.append(e1)
        s.append(e2)
        e2.append(s)    # Make it cyclic
        s = None
        e2 = None
        gc.collect()
        assert r1 == grc(e1)

    def test32(self):
        # Test extended NodeSet functionality

        H = immnodeset
        import gc
        from sys import getrefcount as grc

        gc.collect()
        e1 = []
        e2 = []
        e3 = []
        r1 = grc(e1)
        r2 = grc(e2)
        r3 = grc(e3)

        s = H([e1, e2])

        assert e1 in s and e2 in s and not e3 in s

        s3 = H([e1, e3])

        s |= s3
        assert e3 in s
        assert e2 in s
        s &= s3
        assert e2 not in s
        assert e1 in s

        la = [], [e1], [e1, e2], [e1, e2, e3], [e2], [e2, e3], [e3], [e1, e3, e3, e1]

        ss = [H(x) for x in la]

        test_set_operations(ss, ss, ss)
        test_set_len(ss, ss)
        test_set_sub(ss, ss)
        test_set_convert(ss, ss)

        for a in ss:
            for b in ss:

                # Not supported...yet..
                for x in (
                    'assert list(b) | a == a | b',
                    'assert list(b) & a == a & b',
                ):
                    try:
                        exec(x)
                    except TypeError:
                        pass
                    else:
                        raise Exception('Expected TypeError')

        ss = s = s3 = la = a = b = c = x = None
        locals().clear()
        gc.collect()
        gc.collect()

        assert r1 == grc(e1)
        assert r2 == grc(e2)
        assert r3 == grc(e3)

    def test33(self):
        # Test with multiple segments - so that code
        # in union_realloc is covered
        # I am unsure if any of the other tests used more segments than 2
        # It is a bit tricky (and implementation-dependent)
        # to make it make a specific number of segments.

        # The testing with 20 segments will make 3 reallocations:
        # to make place for 8, 16 and 24 segments.

        numseg = 20

        bs = bitset()

        for i in range(numseg):
            bs |= bitrange(i*2*100000+20, (i*2+1)*100000)

        ms = bs.mutcopy()
        mss = []

        assert ms._num_seg == 1

        for i in range(numseg-1):
            mss.append(ms.mutcopy())
            ms |= bitsingle((i*2+1)*100000+50000)
            assert ms._num_seg == i+2

        # Test that the copies were separate copies (Testing copy-on-write)

        for i in range(numseg-1):
            assert mss[i] == bs
            bs |= bitsingle((i*2+1)*100000+50000)

    def test34(self):
        # Test nodeset inheritance
        # This leaks in Python 2.3.3; whether or not H is MutNodeSet or list.
        H = MutNodeSet
        e1 = []

        class X(H):
            def extend(self, y):
                for e in y:
                    self.append(e)

        s = X()
        assert e1 not in s
        s.extend([e1])
        assert e1 in s

    def test35(self):
        # Test bitset inheritance

        for i in range(2):
            # An error didn't show until second time around

            for H in ImmBitSet, MutBitSet:
                class X(H):
                    bitnames = ['red', 'green', 'blue']

                    def __new__(clas, *args):
                        return H.__new__(clas, [clas.bitnames.index(x) for x in args])

                    def __iter__(self):
                        for bit in H.__iter__(self):
                            yield self.bitnames[bit]

                    def __str__(self):
                        return '{%s}' % (', '.join(self))

                    def __eq__(self, other):
                        return str(self) == str(other)

                x = X()
                x = X('red', 'blue')
                assert list(x) == ['red', 'blue']

                # Test different kinds of construction args

                assert (H.__new__(X, )) == '{}'
                assert (H.__new__(X, immbitset(1))) == '{red}'
                assert (H.__new__(X, mutbitset(2))) == '{green}'
                assert (H.__new__(X, 3)) == '{red, green}'
                assert (H.__new__(X, 4)) == '{blue}'

                if H is ImmBitSet:
                    x = X('red', 'blue')
                    import guppy.sets.setsc
                    # See that we can pass a subtype to CplBitSet
                    assert(str(guppy.sets.setsc.CplBitSet(x))
                           == "(~ImmBitSet(['red', 'blue']))")


class MemStat:
    def __init__(self):
        self.nrefs = {}
        from guppy import Root
        self.R = R = Root()
        self.V = R.guppy.heapy.View
        self.P = R.guppy.heapy.Path
        self.xmemstats = R.guppy.heapy.heapyc.xmemstats
        #self.alset = R.guppy.heapy.heapyc.set_alset()

        # self.mark()

    def mark(self):
        self.R.gc.collect()
        h = self.V.horizon()
        h.update(gc.get_objects())
        self.h = h

    def dump(self):
        gc.collect()
        self.xmemstats()
        # print 'len alset', len(self.alset)

        V = self.V
        R = self.R
        P = self.P
        nrefs = self.nrefs
        if 0:
            h = self.h

            n = h.news(gc.get_objects())
            print(V.retset(n))
            if len(n) <= 12:
                l = list(n)
                for i in range(len(n)):
                    V.enter(
                        lambda: P.shpaths((), l[i]).pp())
        try:
            co = sys.getcounts()
        except AttributeError:
            pass
        else:
            for (name, allo, free, max) in co:
                nref = allo - free
                if name not in nrefs or nref != nrefs[name]:
                    print((name, nref), end=' ', file=sys.stderr)
                    nrefs[name] = nref
            print(file=sys.stderr)
        h = self.h = n = co = name = allo = free = max = l = i = None
        # self.mark()
        #self.alset = None
        # R.guppy.heapy.heapyc.clr_alset()
        gc.collect()
        #self.alset = R.guppy.heapy.heapyc.set_alset()


def test_nums(numbers, dump=None):
    enufuncs = []
    for n in numbers:
        enufuncs.append((n, getattr(t, 'test%d' % n)))
    for n, f in enufuncs:
        print('Test #%d' % n)
        f()
        if dump is not None:
            dump()


def test_leak():
    import gc
    # Test 34 is known to leak in Python 2.3.3.
    nums = list(range(36))
    nums.remove(34)
    ms = MemStat()
    if 0:
        clr_alset = ms.R.guppy.heapy.heapyc.clr_alset
        dump_alset = ms.R.guppy.heapy.heapyc.dump_alset
    # dump_alset()
    i = 0
    while 1:
        test_nums(nums, ms.dump)
        gc.collect()
        if 0 and i >= 2:
            dump_alset()
        i += 1
        # ms.dump()


def test_main():
    test_nums(list(range(36)))


t = Test()

if __name__ == '__main__':
    # test_leak()
    # t.test25()
    # t.test30()
    test_main()
    #test_nums(range(30, 36))
    # test_nums(range(13,35))
