From ecfc3ba4143115d03622dc947cd7bbf834f76f23 Mon Sep 17 00:00:00 2001 From: SVN-Git Migration Date: Thu, 8 Oct 2015 08:34:01 -0700 Subject: Imported Upstream version 1.3.6 --- LICENSE | 9 +- MANIFEST.in | 16 +- PKG-INFO | 26 +- README.rst | 6 +- _blist.c | 7684 --------------------------------------- _btuple.py | 92 - _sorteddict.py | 142 - _sortedlist.py | 647 ---- blist.egg-info/PKG-INFO | 26 +- blist.egg-info/SOURCES.txt | 38 +- blist.egg-info/not-zip-safe | 2 +- blist.egg-info/top_level.txt | 4 - blist.h | 248 -- blist.py | 8 - blist/__init__.py | 10 + blist/_blist.c | 7741 ++++++++++++++++++++++++++++++++++++++++ blist/_btuple.py | 86 + blist/_sorteddict.py | 147 + blist/_sortedlist.py | 647 ++++ blist/blist.h | 248 ++ blist/test/__init__.py | 0 blist/test/btuple_tests.py | 159 + blist/test/list_tests.py | 636 ++++ blist/test/mapping_tests.py | 679 ++++ blist/test/seq_tests.py | 332 ++ blist/test/sorteddict_tests.py | 160 + blist/test/sortedlist_tests.py | 615 ++++ blist/test/test_list.py | 39 + blist/test/test_set.py | 1537 ++++++++ blist/test/test_support.py | 415 +++ blist/test/unittest.py | 848 +++++ distribute_setup.py | 481 --- ez_setup.py | 370 ++ setup.py | 17 +- test/__init__.py | 0 test/btuple_tests.py | 161 - test/list_tests.py | 558 --- test/mapping_tests.py | 678 ---- test/seq_tests.py | 332 -- test/sorteddict_tests.py | 81 - test/sortedlist_tests.py | 614 ---- test/test_list.py | 39 - test/test_set.py | 1536 -------- test/test_support.py | 415 --- test/unittest.py | 840 ----- test_blist.py | 7 +- 46 files changed, 14748 insertions(+), 14628 deletions(-) delete mode 100644 _blist.c delete mode 100644 _btuple.py delete mode 100644 _sorteddict.py delete mode 100644 _sortedlist.py delete mode 100644 blist.h delete mode 100644 blist.py create mode 100644 blist/__init__.py create mode 100644 blist/_blist.c create mode 100644 blist/_btuple.py create mode 100644 blist/_sorteddict.py create mode 100644 blist/_sortedlist.py create mode 100644 blist/blist.h create mode 100644 blist/test/__init__.py create mode 100644 blist/test/btuple_tests.py create mode 100644 blist/test/list_tests.py create mode 100644 blist/test/mapping_tests.py create mode 100644 blist/test/seq_tests.py create mode 100644 blist/test/sorteddict_tests.py create mode 100644 blist/test/sortedlist_tests.py create mode 100644 blist/test/test_list.py create mode 100644 blist/test/test_set.py create mode 100644 blist/test/test_support.py create mode 100644 blist/test/unittest.py delete mode 100644 distribute_setup.py create mode 100644 ez_setup.py delete mode 100644 test/__init__.py delete mode 100644 test/btuple_tests.py delete mode 100644 test/list_tests.py delete mode 100644 test/mapping_tests.py delete mode 100644 test/seq_tests.py delete mode 100644 test/sorteddict_tests.py delete mode 100644 test/sortedlist_tests.py delete mode 100644 test/test_list.py delete mode 100644 test/test_set.py delete mode 100644 test/test_support.py delete mode 100644 test/unittest.py diff --git a/LICENSE b/LICENSE index b51c68f..ed58843 100644 --- a/LICENSE +++ b/LICENSE @@ -1,19 +1,22 @@ Copyright 2007-2010 Stutzbach Enterprises, LLC (daniel@stutzbachenterprises.com) +Copyright 2012 Google, Inc. All Rights Reserved. +(stutzbach@google.com) + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. + notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided - with the distribution. + with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written - permission. + permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED diff --git a/MANIFEST.in b/MANIFEST.in index ae9099c..e41c75c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,15 +1,15 @@ -include _blist.c -include blist.py -include _sorteddict.py -include _sortedlist.py -include _btuple.py +include blist/_blist.c +include blist/__init__.py +include blist/_sorteddict.py +include blist/_sortedlist.py +include blist/_btuple.py include setup.py include test_blist.py -include test/*.py +include blist/test/*.py include README.rst include LICENSE include prototype/blist.py -include distribute_setup.py +include ez_setup.py include speed_test.py include blist.rst -include blist.h +include blist/blist.h diff --git a/PKG-INFO b/PKG-INFO index a43d00d..2f27158 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: blist -Version: 1.3.4 +Version: 1.3.6 Summary: a list-like type with better asymptotic performance and similar performance on small lists Home-page: http://stutzbachenterprises.com/blist/ Author: Stutzbach Enterprises, LLC @@ -9,7 +9,7 @@ License: BSD Description: blist: a list-like type with better performance =============================================== - The ``blist`` is a drop-in replacement for the Python list the provides + The ``blist`` is a drop-in replacement for the Python list that provides better performance when modifying large lists. The blist package also provides ``sortedlist``, ``sortedset``, ``weaksortedlist``, ``weaksortedset``, ``sorteddict``, and ``btuple`` types. @@ -19,7 +19,7 @@ Description: blist: a list-like type with better performance http://stutzbachenterprises.com/blist-doc/ Python's built-in list is a dynamically-sized array; to insert or - removal an item from the beginning or middle of the list, it has to + remove an item from the beginning or middle of the list, it has to move most of the list in memory, i.e., O(n) operations. The blist uses a flexible, hybrid array/tree structure and only needs to move a small portion of items in memory, specifically using O(log n) @@ -74,7 +74,7 @@ Description: blist: a list-like type with better performance - sortedlist - sortedset - weaksortedlist - - weaksorteset + - weaksortedset - sorteddict - btuple @@ -130,7 +130,7 @@ Description: blist: a list-like type with better performance - Constructing a btuple from a blist takes O(1) time. - Taking a slice of a btuple takes O(n) time, where n is the size of - the original tuple. The size of the slice does not matter. + the original tuple. The size of the slice does not matter. >>> from blist import blist, btuple >>> x = blist([0]) # x is a blist with one element @@ -144,7 +144,7 @@ Description: blist: a list-like type with better performance distribution, the Python header files are also required. In either case, just run: - python setup.py install + python setup.py install If you're running Linux and see a bunch of compilation errors from GCC, you probably do not have the Python header files installed. @@ -159,7 +159,7 @@ Description: blist: a list-like type with better performance If you downloaded the source distribution and wish to run the associated test suite, you can also run: - python setup.py test + python setup.py test which will verify the correct installation and functioning of the package. The tests require Python 2.6 or higher. @@ -178,13 +178,13 @@ Description: blist: a list-like type with better performance In addition to the tests include in the source distribution, we perform the following to add extra rigor to our testing process: - 1. We use a "fuzzer": a program that randomly generates list - operations, performs them using both the blist and the built-in - list, and compares the results. + 1. We use a "fuzzer": a program that randomly generates list + operations, performs them using both the blist and the built-in + list, and compares the results. - 2. We use a modified Python interpreter where we have replaced the - array-based built-in list with the blist. Then, we run all of - the regular Python unit tests. + 2. We use a modified Python interpreter where we have replaced the + array-based built-in list with the blist. Then, we run all of + the regular Python unit tests. Keywords: blist list b+tree btree fast copy-on-write sparse array sortedlist sorted sortedset weak weaksortedlist weaksortedset sorteddict btuple Platform: UNKNOWN diff --git a/README.rst b/README.rst index 32e569c..cc60384 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ blist: a list-like type with better performance =============================================== -The ``blist`` is a drop-in replacement for the Python list the provides +The ``blist`` is a drop-in replacement for the Python list that provides better performance when modifying large lists. The blist package also provides ``sortedlist``, ``sortedset``, ``weaksortedlist``, ``weaksortedset``, ``sorteddict``, and ``btuple`` types. @@ -11,7 +11,7 @@ Full documentation is at the link below: http://stutzbachenterprises.com/blist-doc/ Python's built-in list is a dynamically-sized array; to insert or -removal an item from the beginning or middle of the list, it has to +remove an item from the beginning or middle of the list, it has to move most of the list in memory, i.e., O(n) operations. The blist uses a flexible, hybrid array/tree structure and only needs to move a small portion of items in memory, specifically using O(log n) @@ -66,7 +66,7 @@ The blist package provides other data structures based on the blist: - sortedlist - sortedset - weaksortedlist -- weaksorteset +- weaksortedset - sorteddict - btuple diff --git a/_blist.c b/_blist.c deleted file mode 100644 index 7efb681..0000000 --- a/_blist.c +++ /dev/null @@ -1,7684 +0,0 @@ -/* Copyright 2007-2010 Stutzbach Enterprises, LLC - * daniel@stutzbachenterprises.com - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * 3. The name of the author may not be used to endorse or promote - * products derived from this software without specific prior written - * permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING - * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -/********************************************************************** - * * - * PLEASE READ blist.rst BEFORE MODIFYING THIS CODE * - * * - **********************************************************************/ - - -#include -#include - -#if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L -#define restrict -#endif - -#if PY_MAJOR_VERSION == 2 - -#ifndef PY_UINT32_T -#if (defined UINT32_MAX || defined uint32_t) -#define HAVE_UINT32_T 1 -#define PY_UINT32_T uint32_t -#elif defined(MS_WINDOWS) -#if SIZEOF_INT == 4 -#define HAVE_UINT32_T 1 -#define PY_UINT32_T unsigned int -#elif SIZEOF_LONG == 4 -#define HAVE_UINT32_T 1 -#define PY_UINT32_T unsigned long -#endif -#endif -#endif - -#ifndef PY_UINT64_T -#if (defined UINT64_MAX || defined uint64_t) -#define HAVE_UINT64_T 1 -#define PY_UINT64_T uint64_t -#elif defined(MS_WINDOWS) -#if SIZEOF_LONG_LONG == 8 -#define HAVE_UINT64_T 1 -#define PY_UINT64_T unsigned PY_LONG_LONG -#endif -#endif -#endif - -#ifndef PY_INT32_T -#if (defined INT32_MAX || defined int32_t) -#define HAVE_INT32_T 1 -#define PY_INT32_T int32_t -#elif defined(MS_WINDOWS) -#if SIZEOF_INT == 4 -#define HAVE_INT32_T 1 -#define PY_INT32_T int -#elif SIZEOF_LONG == 4 -#define HAVE_INT32_T 1 -#define PY_INT32_T long -#endif -#endif -#endif - -#ifndef PY_INT64_T -#if (defined INT64_MAX || defined int64_t) -#define HAVE_INT64_T 1 -#define PY_INT64_T int64_t -#elif defined(MS_WINDOWS) -#if SIZEOF_LONG_LONG == 8 -#define HAVE_INT64_T 1 -#define PY_INT64_T PY_LONG_LONG -#endif -#endif -#endif - -/* This macro is defined in Python 3. We need it since calling - * PyObject_GC_UnTrack twice is unsafe. */ -/* True if the object is currently tracked by the GC. */ -#define _PyObject_GC_IS_TRACKED(o) \ - ((_Py_AS_GC(o))->gc.gc_refs != _PyGC_REFS_UNTRACKED) - -#if PY_MINOR_VERSION < 6 -/* Backward compatibility with Python 2.5 */ -#define PyUnicode_FromString PyString_FromString -#define Py_REFCNT(ob) (((PyObject*)(ob))->ob_refcnt) -#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type) -#define Py_SIZE(ob) (((PyVarObject*)(ob))->ob_size) -#define PyVarObject_HEAD_INIT(type, size) \ - PyObject_HEAD_INIT(type) size, -#define PyUnicode_FromFormat PyString_FromFormat -#endif - -#elif PY_MAJOR_VERSION == 3 -/* Backward compatibility with Python 3 */ -#define PyInt_FromSsize_t PyLong_FromSsize_t -#define PyInt_CheckExact PyLong_CheckExact -#define PyInt_AsLong PyLong_AsLong -#define PyInt_AsSsize_t PyLong_AsSsize_t -#define PyInt_FromLong PyLong_FromLong -#endif - -#ifndef BLIST_IN_PYTHON -#include "blist.h" -#endif - -#define BLIST_PYAPI(type) static type - -typedef struct { - PyBList *lst; - int i; -} point_t; - -typedef struct { - int depth; - PyBList *leaf; - int i; - point_t stack[MAX_HEIGHT]; -} iter_t; - -typedef struct { - PyObject_HEAD - iter_t iter; -} blistiterobject; - -/* Empty BList reuse scheme to save calls to malloc and free */ -#define MAXFREELISTS 80 -static PyBList *free_lists[MAXFREELISTS]; -static int num_free_lists = 0; - -static PyBList *free_ulists[MAXFREELISTS]; -static int num_free_ulists = 0; - -static blistiterobject *free_iters[MAXFREELISTS]; -static int num_free_iters = 0; - -typedef struct sortwrapperobject -{ - union { - unsigned long k_ulong; -#ifdef BLIST_FLOAT_RADIX_SORT - PY_UINT64_T k_uint64; -#endif - } fkey; - PyObject *key; - PyObject *value; -} sortwrapperobject; - -#define PyBList_Check(op) (PyObject_TypeCheck((op), &PyBList_Type) || (PyObject_TypeCheck((op), &PyRootBList_Type))) -#define PyRootBList_Check(op) (PyObject_TypeCheck((op), &PyRootBList_Type)) -#define PyRootBList_CheckExact(op) (Py_TYPE((op)) == &PyRootBList_Type) -#define PyBList_CheckExact(op) ((op)->ob_type == &PyBList_Type || (op)->ob_type == &PyRootBList_Type) -#define PyBListIter_Check(op) (PyObject_TypeCheck((op), &PyBListIter_Type) || (PyObject_TypeCheck((op), &PyBListReverseIter_Type))) - -#define INDEX_LENGTH(self) (((self)->n-1) / INDEX_FACTOR + 1) - -/************************************************************************ - * Utility functions for copying and moving children. - */ - -/* copy n children from index k2 of other to index k of self */ -BLIST_LOCAL(void) -copy(PyBList *self, int k, PyBList *other, int k2, int n) -{ - PyObject **restrict src = &other->children[k2]; - PyObject **restrict dst = &self->children[k]; - PyObject **stop = &other->children[k2+n]; - - assert(self != other); - - while (src < stop) - *dst++ = *src++; -} - -/* like copy(), but incrementing references */ -BLIST_LOCAL(void) -copyref(PyBList *self, int k, PyBList *other, int k2,int n) { - PyObject **restrict src = &other->children[k2]; - PyObject **restrict dst = &self->children[k]; - PyObject **stop = &src[n]; - - while (src < stop) { - Py_INCREF(*src); - *dst++ = *src++; - } -} - -/* like copy(), but incrementing references but check for NULL */ -BLIST_LOCAL(void) -xcopyref(PyBList *self, int k, PyBList *other,int k2,int n) { - PyObject **restrict src = &other->children[k2]; - PyObject **restrict dst = &self->children[k]; - PyObject **stop = &src[n]; - - while (src < stop) { - Py_XINCREF(*src); - *dst++ = *src++; - } -} - -/* Move children starting at k to the right by n */ -BLIST_LOCAL(void) -shift_right(PyBList *self, int k, int n) -{ - PyObject **src = &self->children[self->num_children-1]; - PyObject **dst = &self->children[self->num_children-1 + n]; - PyObject **stop = &self->children[k]; - - if (self->num_children == 0) - return; - - assert(k >= 0); - assert(k <= LIMIT); - assert(n + self->num_children <= LIMIT); - - while (src >= stop) - *dst-- = *src--; -} - -/* Move children starting at k to the left by n */ -BLIST_LOCAL(void) -shift_left(PyBList *self, int k, int n) -{ - PyObject **src = &self->children[k]; - PyObject **dst = &self->children[k - n]; - PyObject **stop = &self->children[self->num_children]; - - assert(k - n >= 0); - assert(k >= 0); - assert(k <= LIMIT); - assert(self->num_children -n >= 0); - - while (src < stop) - *dst++ = *src++; - -#ifdef Py_DEBUG - while (dst < stop) - *dst++ = NULL; -#endif -} - -BLIST_LOCAL(void) -balance_leafs(PyBList *restrict leaf1, PyBList *restrict leaf2) -{ - assert(leaf1->leaf); - assert(leaf2->leaf); - if (leaf1->num_children + leaf2->num_children <= LIMIT) { - copy(leaf1, leaf1->num_children, leaf2, 0,leaf2->num_children); - leaf1->num_children += leaf2->num_children; - leaf1->n += leaf2->num_children; - leaf2->num_children = 0; - leaf2->n = 0; - } else if (leaf1->num_children < HALF) { - int needed = HALF - leaf1->num_children; - - copy(leaf1, leaf1->num_children, leaf2, 0, needed); - leaf1->num_children += needed; - leaf1->n += needed; - shift_left(leaf2, needed, needed); - leaf2->num_children -= needed; - leaf2->n -= needed; - } else if (leaf2->num_children < HALF) { - int needed = HALF - leaf2->num_children; - - shift_right(leaf2, 0, needed); - copy(leaf2, 0, leaf1, leaf1->num_children-needed, needed); - leaf1->num_children -= needed; - leaf1->n -= needed; - leaf2->num_children += needed; - leaf2->n += needed; - } -} - -/************************************************************************ - * Macros for O(1) iteration over a BList via Depth-First-Search. If - * the root is also a leaf, it will skip the memory allocation and - * just use a for loop. - */ - -/* Iteration over part of the list */ -#define ITER2(lst, item, start, stop, block) {\ - iter_t _it; int _use_iter; \ - if (lst->leaf) { \ - Py_ssize_t _i; _use_iter = 0; \ - for (_i = (start); _i < lst->num_children && _i < (stop); _i++) { \ - item = lst->children[_i]; \ - block; \ - } \ - } else { \ - Py_ssize_t _remaining = (stop) - (start);\ - PyBList *_p; _use_iter = 1;\ - iter_init2(&_it, (lst), (start)); \ - _p = _it.leaf; \ - while (_p != NULL && _remaining--) { \ - if (_it.i < _p->num_children) { \ - item = _p->children[_it.i++]; \ - } else { \ - item = iter_next(&_it); \ - _p = _it.leaf; \ - if (item == NULL) break; \ - } \ - block; \ - } \ - iter_cleanup(&_it); \ - } \ -} - -/* Iteration over the whole list */ -#define ITER(lst, item, block) {\ - if ((lst)->leaf) { \ - iter_t _it; \ - Py_ssize_t _i; const int _use_iter = 0;\ - for (_i = 0; _i < (lst)->num_children; _i++) { \ - item = (lst)->children[_i]; \ - block; \ - } ITER_CLEANUP(); \ - } else { \ - iter_t _it; \ - PyBList *_p; \ - const int _use_iter = 1; \ - iter_init(&_it, (lst)); \ - _p = _it.leaf; \ - while (_p) { \ - if (_it.i < _p->num_children) { \ - item = _p->children[_it.i++]; \ - } else { \ - item = iter_next(&_it); \ - _p = _it.leaf; \ - if (item == NULL) break; \ - } \ - block; \ - } \ - ITER_CLEANUP(); \ - } \ -} - -/* Call this before when leaving the ITER via return or goto. */ -#define ITER_CLEANUP() if (_use_iter) iter_cleanup(&_it) - -/* Forward declarations */ -PyTypeObject PyBList_Type; -PyTypeObject PyRootBList_Type; -PyTypeObject PyBListIter_Type; -PyTypeObject PyBListReverseIter_Type; -static void ext_init(PyBListRoot *root); -static void ext_mark(PyBList *broot, Py_ssize_t offset, int value); -static void ext_mark_set_dirty(PyBList *broot, Py_ssize_t i, Py_ssize_t j); -static void ext_mark_set_dirty_all(PyBList *broot); - -/* also hard-coded in blist.h */ -#define DIRTY (-1) -#define CLEAN (-2) -#define CLEAN_RW (-3) /* Only valid for dirty_root */ - -static PyObject *_indexerr = NULL; -void set_index_error(void) -{ - if (_indexerr == NULL) - _indexerr = PyUnicode_FromString("list index out of range"); - PyErr_SetObject(PyExc_IndexError, _indexerr); -} - -/************************************************************************ - * Debugging forward declarations - */ - -#ifdef Py_DEBUG - -static int blist_unstable = 0; -static int blist_in_code = 0; -static int blist_danger = 0; -#define DANGER_GC_BEGIN { int _blist_unstable = blist_unstable, _blist_in_code = blist_in_code; blist_unstable = 0; blist_in_code = 0; blist_danger++ -#define DANGER_GC_END assert(!blist_unstable); assert(!blist_in_code); blist_unstable = _blist_unstable; blist_in_code = _blist_in_code; assert(blist_danger); blist_danger--; } while (0) -#define DANGER_BEGIN DANGER_GC_BEGIN; assert(!gc_pause_count) -#define DANGER_END assert(!gc_pause_count); DANGER_GC_END - -#else - -#define DANGER_BEGIN while(0) -#define DANGER_END while(0) -#define DANGER_GC_BEGIN while(0) -#define DANGER_GC_END while(0) - -#endif - -/************************************************************************ - * Functions we wish CPython's API provided :-) - */ - -#ifdef Py_DEBUG -static int gc_pause_count = 0; -#endif - -#ifdef BLIST_IN_PYTHON -#define gc_pause() (0) -#define gc_unpause(previous) do {} while (0) -#else -static PyObject * (*pgc_disable)(PyObject *self, PyObject *noargs); -static PyObject * (*pgc_enable)(PyObject *self, PyObject *noargs); -static PyObject * (*pgc_isenabled)(PyObject *self, PyObject *noargs); - -BLIST_LOCAL(void) -gc_unpause(int previous) -{ -#ifdef Py_DEBUG - assert(gc_pause_count > 0); - gc_pause_count--; -#endif - if (previous) { - PyObject *rv = pgc_enable(NULL, NULL); - Py_DECREF(rv); - } -} - -BLIST_LOCAL(int) -gc_pause(void) -{ - int rv; - PyObject *enabled = pgc_isenabled(NULL, NULL); - rv = (enabled == Py_True); - Py_DECREF(enabled); - if (rv) { - PyObject *none = pgc_disable(NULL, NULL); - Py_DECREF(none); - } -#ifdef Py_DEBUG - gc_pause_count++; -#endif - return rv; -} -#endif - -BLIST_LOCAL(int) -do_eq(PyObject *v, PyObject *w) -{ - richcmpfunc f; - PyObject *res; - int rv; - - if (Py_EnterRecursiveCall(" in cmp")) - return -1; - - if (v->ob_type != w->ob_type && - PyType_IsSubtype(w->ob_type, v->ob_type) && - (f = w->ob_type->tp_richcompare) != NULL) { - res = (*f)(w, v, Py_EQ); - if (res != Py_NotImplemented) { - ob_to_int: - if (res == Py_False) - rv = 0; - else if (res == Py_True) - rv = 1; - else if (res == NULL) { - Py_LeaveRecursiveCall(); - return -1; - } else - rv = PyObject_IsTrue(res); - Py_DECREF(res); - Py_LeaveRecursiveCall(); - return rv; - } - Py_DECREF(res); - } - if ((f = v->ob_type->tp_richcompare) != NULL) { - res = (*f)(v, w, Py_EQ); - if (res != Py_NotImplemented) - goto ob_to_int; - Py_DECREF(res); - } - if ((f = w->ob_type->tp_richcompare) != NULL) { - res = (*f)(w, v, Py_EQ); - if (res != Py_NotImplemented) - goto ob_to_int; - Py_DECREF(res); - } - - Py_LeaveRecursiveCall(); -#if PY_MAJOR_VERSION < 3 - rv = PyObject_Compare(v, w); - if (PyErr_Occurred()) - return -1; - if (rv == 0) - return 1; -#endif - return 0; -} - -/* If fast_type == v->ob_type == w->ob_type, then we can assume: - * 1) tp_richcompare != NULL - * 2) tp_richcompare will not recurse - * 3) tp_richcompare(v, w, op) == tp_richcompare(w, v, symmetric_op) - * 4) tp_richcompare(v, w, op) can return only Py_True or Py_False - * - * These assumptions hold for built-in, immutable, non-container types. - */ -static int -fast_eq_richcompare(PyObject *v, PyObject *w, PyTypeObject *fast_type) -{ - if (v == w) return 1; - if (v->ob_type == fast_type && w->ob_type == fast_type) { - PyObject *res = v->ob_type->tp_richcompare(v, w, Py_EQ); - Py_DECREF(res); - - return res == Py_True; - } else { - int rv; - DANGER_BEGIN; - rv = do_eq(v, w); - DANGER_END; - return rv; - } -} - -#if PY_MAJOR_VERSION < 3 -static int -fast_eq_compare(PyObject *v, PyObject *w, PyTypeObject *fast_type) -{ - if (v == w) return 1; - if (v->ob_type == w->ob_type && v->ob_type == fast_type) - return v->ob_type->tp_compare(v, w) == 0; - else { - int rv; - DANGER_BEGIN; - rv = PyObject_RichCompareBool(v, w, Py_EQ); - DANGER_END; - return rv; - } -} -#endif - -static int -fast_lt_richcompare(PyObject *v, PyObject *w, PyTypeObject *fast_type) -{ - if (v->ob_type == w->ob_type && v->ob_type == fast_type) { - PyObject *res = v->ob_type->tp_richcompare(v, w, Py_LT); - Py_DECREF(res); - - return res == Py_True; - } else { - int rv; - DANGER_BEGIN; - rv = PyObject_RichCompareBool(v, w, Py_LT); - DANGER_END; - return rv; - } -} - -#if PY_MAJOR_VERSION < 3 - -static int -fast_lt_compare(PyObject *v, PyObject *w, PyTypeObject *fast_type) -{ - if (v->ob_type == w->ob_type && v->ob_type == fast_type) - return v->ob_type->tp_compare(v, w) < 0; - else { - int rv; - DANGER_BEGIN; - rv = PyObject_RichCompareBool(v, w, Py_LT); - DANGER_END; - return rv; - } -} - -typedef int fast_compare_t(PyObject *v, PyObject *w, PyTypeObject *fast_type); -typedef struct fast_compare_data -{ - PyTypeObject *fast_type; - fast_compare_t *comparer; -} fast_compare_data_t; - -BLIST_LOCAL(fast_compare_data_t) -_check_fast_cmp_type(PyObject *ob, int op) -{ - fast_compare_data_t rv = { NULL, NULL }; - - if (ob->ob_type == &PyInt_Type - || ob->ob_type == &PyLong_Type) { - rv.fast_type = ob->ob_type; - if (op == Py_EQ) - rv.comparer = fast_eq_compare; - else if (op == Py_LT) - rv.comparer = fast_lt_compare; - else - rv.fast_type = NULL; - } else { - if (op == Py_EQ) - rv.comparer = fast_eq_richcompare; - else if (op == Py_LT) - rv.comparer = fast_lt_richcompare; - else - return rv; - - if ((ob->ob_type == &PyComplex_Type && (op == Py_EQ || op == Py_NE)) - || ob->ob_type == &PyFloat_Type - || ob->ob_type == &PyLong_Type - || ob->ob_type == &PyUnicode_Type - || ob->ob_type == &PyString_Type) { - rv.fast_type = ob->ob_type; - } - } - - return rv; -} - -#define check_fast_cmp_type(ob, op) \ - (_check_fast_cmp_type((ob), (op))) -#define fast_eq(v, w, name) \ - (((name).comparer == fast_eq_compare) \ - ? fast_eq_compare((v), (w), (name).fast_type) \ - : fast_eq_richcompare((v), (w), (name).fast_type)) -#define fast_lt(v, w, name) \ - (((name).comparer == fast_lt_compare) \ - ? fast_lt_compare((v), (w), (name).fast_type) \ - : fast_lt_richcompare((v), (w), (name).fast_type)) - -static const fast_compare_data_t no_fast_lt = { NULL, fast_lt_richcompare }; -static const fast_compare_data_t no_fast_eq = { NULL, fast_eq_richcompare }; - -#else - -typedef PyTypeObject *fast_compare_data_t; - -BLIST_LOCAL(fast_compare_data_t) -_check_fast_cmp_type(PyObject *ob, int op) -{ - if ((ob->ob_type == &PyComplex_Type && (op == Py_EQ || op == Py_NE)) - || ob->ob_type == &PyFloat_Type - || ob->ob_type == &PyLong_Type - || ob->ob_type == &PyUnicode_Type - || ob->ob_type == &PyBytes_Type - || ob->ob_type == &PyLong_Type) - return ob->ob_type; - return NULL; -} - -#define check_fast_cmp_type(ob, op) (_check_fast_cmp_type((ob), (op))) - -#define fast_eq(v, w, name) (fast_eq_richcompare((v), (w), (name))) -#define fast_lt(v, w, name) (fast_lt_richcompare((v), (w), (name))) - -#define no_fast_lt (NULL) -#define no_fast_eq (NULL) - -#endif - -/************************************************************************ - * Utility functions for removal items from a BList - * - * Objects in Python can execute arbitrary code when garbage - * collected, which means they may make calls that modify the BList - * that we're deleting items from. Yuck. - * - * To avoid this in the general case, any function that removes items - * from a BList calls decref_later() on the object instead of - * Py_DECREF(). The objects are accumulated in a global list of - * objects pending for deletion. Just before the function returns to - * the interpreter, decref_flush() is called to actually decrement the - * reference counters. - * - * decref_later() can be passed PyBList objects to delete whole - * subtrees. - */ - -static PyObject **decref_list = NULL; -static Py_ssize_t decref_max = 0; -static Py_ssize_t decref_num = 0; - -#define DECREF_BASE (2*128) - -int decref_init(void) -{ - decref_max = DECREF_BASE; - decref_list = (PyObject **) PyMem_New(PyObject *, decref_max); - if (decref_list == NULL) - return -1; - return 0; -} - -static void _decref_later(PyObject *ob) -{ - if (decref_num == decref_max) { - PyObject **tmp = decref_list; - decref_max *= 2; - - PyMem_Resize(decref_list, PyObject *, decref_max); - if (decref_list == NULL) { - PyErr_NoMemory(); - decref_list = tmp; - decref_max /= 2; - return; - } - } - - decref_list[decref_num++] = ob; -} -#define decref_later(ob) do { if (Py_REFCNT((ob)) > 1) { Py_DECREF((ob)); } else { _decref_later((ob)); } } while (0) - -static void xdecref_later(PyObject *ob) -{ - if (ob == NULL) - return; - - decref_later(ob); -} - -/* Like shift_left(), adding overwritten entries to the decref_later list */ -static void shift_left_decref(PyBList *self, int k, int n) -{ - register PyObject **src = &self->children[k]; - register PyObject **dst = &self->children[k - n]; - register PyObject **stop = &self->children[self->num_children]; - register PyObject **dec; - register PyObject **dst_stop = &self->children[k]; - - if (decref_num + n > decref_max) { - while (decref_num + n > decref_max) - decref_max *= 2; - /* XXX Out of memory not handled */ - PyMem_Resize(decref_list, PyObject *, decref_max); - } - - dec = &decref_list[decref_num]; - - assert(n >= 0); - assert(k - n >= 0); - assert(k >= 0); - assert(k <= LIMIT); - assert(self->num_children - n >= 0); - - while (src < stop && dst < dst_stop) { - if (*dst != NULL) { - if (Py_REFCNT(*dst) > 1) { - Py_DECREF(*dst); - } else { - *dec++ = *dst; - } - } - *dst++ = *src++; - } - - while (src < stop) { - *dst++ = *src++; - } - - while (dst < dst_stop) { - if (*dst != NULL) { - if (Py_REFCNT(*dst) > 1) { - Py_DECREF(*dst); - } else { - *dec++ = *dst; - } - } - dst++; - } - -#ifdef Py_DEBUG - src = &self->children[self->num_children - n]; - while (src < stop) - *src++ = NULL; -#endif - - decref_num += dec - &decref_list[decref_num]; -} - -static void _decref_flush(void) -{ - while (decref_num) { - /* Py_DECREF() can cause arbitrary other oerations on - * BList, potentially even resulting in additional - * calls to decref_later() and decref_flush()! - * - * Any changes to this function must be made VERY - * CAREFULLY to handle this case. - * - * Invariant: whenever we call Py_DECREF, the - * decref_list is in a coherent state. It contains - * only items that still need to be decrefed. - * Furthermore, we can't cache anything about the - * global variables. - */ - - decref_num--; - DANGER_BEGIN; - Py_DECREF(decref_list[decref_num]); - DANGER_END; - } - - if (decref_max > DECREF_BASE) { - /* Return memory to the system now. - * We may never get another chance - */ - - decref_max = DECREF_BASE; - PyMem_Resize(decref_list, PyObject *, decref_max); - } -} - -/* Redefined in debug mode */ -#define decref_flush() (_decref_flush()) -#define SAFE_DECREF(x) Py_DECREF((x)) -#define SAFE_XDECREF(x) Py_XDECREF((x)) - -/************************************************************************ - * Debug functions - */ - -#ifdef Py_DEBUG - -static void check_invariants(PyBList *self) -{ - if (self->leaf) { - assert(self->n == self->num_children); - int i; - - for (i = 0; i < self->num_children; i++) { - PyObject *child = self->children[i]; - if (child != NULL) - assert(Py_REFCNT(child) > 0); - } - } else { - int i; - Py_ssize_t total = 0; - - assert(self->num_children > 0); - - for (i = 0; i < self->num_children; i++) { - assert(PyBList_Check(self->children[i])); - assert(!PyRootBList_Check(self->children[i])); - - PyBList *child = (PyBList *) self->children[i]; - assert(child != self); - total += child->n; - assert(child->num_children <= LIMIT); - assert(HALF <= child->num_children); - /* check_invariants(child); */ - } - assert(self->n == total); - assert(self->num_children > 1 || self->num_children == 0); - - } - - assert (Py_REFCNT(self) >= 1 || Py_TYPE(self) == &PyRootBList_Type - || (Py_REFCNT(self) == 0 && self->num_children == 0)); -} - -#define VALID_RW 1 -#define VALID_PARENT 2 -#define VALID_USER 4 -#define VALID_OVERFLOW 8 -#define VALID_COLLAPSE 16 -#define VALID_DECREF 32 -#define VALID_ROOT 64 - -typedef struct -{ - PyBList *self; - int options; -} debug_t; - -static void debug_setup(debug_t *debug) -{ - Py_INCREF(Py_None); - blist_in_code++; - - assert(PyBList_Check(debug->self)); - - if (debug->options & VALID_DECREF) { - assert(blist_in_code == 1); - assert(debug->options & VALID_USER); - } - -#if 0 - /* Comment this test out since users can get references via - * the gc module */ - if (debug->options & VALID_RW) { - assert(Py_REFCNT(debug->self) == 1 - || PyRootBList_Check(debug->self)); - } -#endif - - if (debug->options & VALID_USER) { - debug->options |= VALID_ROOT; - assert(PyRootBList_Check(debug->self)); - if (!debug->self->leaf) - assert(((PyBListRoot *)debug->self)->last_n - == debug->self->n); - assert(((PyBListRoot *)debug->self)->dirty_length - || ((PyBListRoot *)debug->self)->dirty_root); - - if (!blist_danger) - assert(decref_num == 0); - } - - if (debug->options & VALID_ROOT) { - debug->options |= VALID_PARENT; - - assert(Py_REFCNT(debug->self) >= 1); - } - - if (debug->options & (VALID_USER | VALID_PARENT)) { - check_invariants(debug->self); - } - - if ((debug->options & VALID_USER) && (debug->options & VALID_RW)) { - assert(!blist_unstable); - blist_unstable = 1; - } -} - -static void debug_return(debug_t *debug) -{ - Py_DECREF(Py_None); - assert(blist_in_code); - blist_in_code--; - -#if 0 - /* Comment this test out since users can get references via - * the gc module */ - if (debug->options & VALID_RW) { - assert(Py_REFCNT(debug->self) == 1 - || PyRootBList_Check(debug->self)); - } -#endif - - if (debug->options - & (VALID_PARENT|VALID_USER|VALID_OVERFLOW|VALID_COLLAPSE|VALID_ROOT)) - check_invariants(debug->self); - - if (debug->options & VALID_USER) { - if (!blist_danger) - assert(decref_num == 0); - if (!debug->self->leaf) - assert(((PyBListRoot *)debug->self)->last_n - == debug->self->n); - assert(((PyBListRoot *)debug->self)->dirty_length - || ((PyBListRoot *)debug->self)->dirty_root); - } - - if ((debug->options & VALID_USER) && (debug->options & VALID_RW)) { - assert(blist_unstable); - blist_unstable = 0; - } -} - -static PyObject *debug_return_overflow(debug_t *debug, PyObject *ret) -{ - if (debug->options & VALID_OVERFLOW) { - if (ret == NULL) { - debug_return(debug); - return ret; - } - - assert(PyBList_Check((PyObject *) ret)); - check_invariants((PyBList *) ret); - } - - assert(!(debug->options & VALID_COLLAPSE)); - - debug_return(debug); - - return ret; -} - -static Py_ssize_t debug_return_collapse(debug_t *debug, Py_ssize_t ret) -{ - if (debug->options & VALID_COLLAPSE) - assert (((Py_ssize_t) ret) >= 0); - - assert(!(debug->options & VALID_OVERFLOW)); - - debug_return(debug); - - return ret; -} - -#define invariants(self, options) debug_t _debug = { (PyBList *) (self), (options) }; \ - debug_setup(&_debug) - -#define _blist(ret) (PyBList *) debug_return_overflow(&_debug, (PyObject *) (ret)) -#define _ob(ret) debug_return_overflow(&_debug, (ret)) -#define _int(ret) debug_return_collapse(&_debug, (ret)) -#define _void() do {assert(!(_debug.options & (VALID_OVERFLOW|VALID_COLLAPSE))); debug_return(&_debug);} while (0) -#define _redir(ret) ((debug_return(&_debug), 1) ? (ret) : (ret)) - -#undef Py_RETURN_NONE -#define Py_RETURN_NONE return Py_INCREF(Py_None), _ob(Py_None) - -#undef decref_flush -#define decref_flush() do { assert(_debug.options & VALID_DECREF); _decref_flush(); } while (0) - -static void safe_decref_check(PyBList *self) -{ - int i; - - assert(PyBList_Check((PyObject *) self)); - - if (Py_REFCNT(self) > 1) - return; - - if (self->leaf) { - for (i = 0; i < self->num_children; i++) - assert(self->children[i] == NULL - || Py_REFCNT(self->children[i]) > 1); - return; - } - - for (i = 0; i < self->num_children; i++) - safe_decref_check((PyBList *)self->children[i]); -} - -static void safe_decref(PyBList *self) -{ - assert(PyBList_Check((PyObject *) self)); - safe_decref_check(self); - - DANGER_GC_BEGIN; - Py_DECREF(self); - DANGER_GC_END; -} - -#undef SAFE_DECREF -#undef SAFE_XDECREF -#define SAFE_DECREF(self) (safe_decref((PyBList *)(self))) -#define SAFE_XDECREF(self) if ((self) == NULL) ; else SAFE_DECREF((self)) - -#else /* !Py_DEBUG */ - -#define check_invariants(self) -#define invariants(self, options) -#define _blist(ret) (ret) -#define _ob(ret) (ret) -#define _int(ret) (ret) -#define _void() -#define _redir(ret) (ret) - -#endif - -BLIST_LOCAL(int) -append_and_squish(PyBList **out, int n, PyBList *leaf) -{ - if (n >= 1) { - PyBList *last = out[n-1]; - if (last->num_children + leaf->num_children <= LIMIT) { - copy(last, last->num_children, leaf, 0, - leaf->num_children); - last->num_children += leaf->num_children; - last->n += leaf->num_children; - leaf->num_children = 0; - leaf->n = 0; - } else { - int moved = LIMIT - last->num_children; - copy(last, last->num_children, leaf, 0, moved); - shift_left(leaf, moved, moved); - last->num_children = LIMIT; - last->n = LIMIT; - leaf->num_children -= moved; - leaf->n -= moved; - } - } - if (!leaf->num_children) - SAFE_DECREF(leaf); - else - out[n++] = leaf; - return n; -} - -BLIST_LOCAL(int) -balance_last_2(PyBList **out, int n) -{ - PyBList *last; - - if (n >= 2) - balance_leafs(out[n-2], out[n-1]); - if (n >= 1) { - last = out[n-1]; - if (!last->num_children) { - SAFE_DECREF(last); - n--; - } - } - return n; -} - -/************************************************************************ - * Back to BLists proper. - */ - -/* Creates a new blist for internal use only */ -static PyBList *blist_new(void) -{ - PyBList *self; - - if (num_free_lists) { - self = free_lists[--num_free_lists]; - _Py_NewReference((PyObject *) self); - } else { - DANGER_GC_BEGIN; - self = PyObject_GC_New(PyBList, &PyBList_Type); - DANGER_GC_END; - if (self == NULL) - return NULL; - self->children = PyMem_New(PyObject *, LIMIT); - if (self->children == NULL) { - PyObject_GC_Del(self); - PyErr_NoMemory(); - return NULL; - } - } - - self->leaf = 1; /* True */ - self->num_children = 0; - self->n = 0; - - PyObject_GC_Track(self); - - return self; -} - -static PyBList *blist_new_no_GC(void) -{ - PyBList *self = blist_new(); - PyObject_GC_UnTrack(self); - return self; -} - -/* Creates a blist for user use */ -static PyBList *blist_root_new(void) -{ - PyBList *self; - - if (num_free_ulists) { - self = free_ulists[--num_free_ulists]; - _Py_NewReference((PyObject *) self); - } else { - DANGER_GC_BEGIN; - self = (PyBList *) PyObject_GC_New(PyBListRoot, &PyRootBList_Type); - DANGER_GC_END; - if (self == NULL) - return NULL; - self->children = PyMem_New(PyObject *, LIMIT); - if (self->children == NULL) { - PyObject_GC_Del(self); - PyErr_NoMemory(); - return NULL; - } - } - - self->leaf = 1; /* True */ - self->n = 0; - self->num_children = 0; - - ext_init((PyBListRoot *) self); - - PyObject_GC_Track(self); - - return self; -} - -/* Remove links to some of our children, decrementing their refcounts */ -static void blist_forget_children2(PyBList *self, int i, int j) -{ - int delta = j - i; - - invariants(self, VALID_RW); - - shift_left_decref(self, j, delta); - self->num_children -= delta; - - _void(); -} - -/* Remove links to all children */ -#define blist_forget_children(self) \ - (blist_forget_children2((self), 0, (self)->num_children)) - -/* Remove link to one child */ -#define blist_forget_child(self, i) \ - (blist_forget_children2((self), (i), (i)+1)) - -/* Version for internal use defers Py_DECREF calls */ -static int blist_CLEAR(PyBList *self) -{ - invariants(self, VALID_RW|VALID_PARENT); - - blist_forget_children(self); - self->n = 0; - self->leaf = 1; - - return _int(0); -} - -/* Make self into a copy of other */ -BLIST_LOCAL(void) -blist_become(PyBList *restrict self, PyBList *restrict other) -{ - invariants(self, VALID_RW); - assert(self != other); - - Py_INCREF(other); /* "other" may be one of self's children */ - blist_forget_children(self); - self->n = other->n; - xcopyref(self, 0, other, 0, other->num_children); - self->num_children = other->num_children; - self->leaf = other->leaf; - - SAFE_DECREF(other); - _void(); -} - -/* Make self into a copy of other and empty other */ -BLIST_LOCAL(void) -blist_become_and_consume(PyBList *restrict self, PyBList *restrict other) -{ - PyObject **tmp; - - invariants(self, VALID_RW); - assert(self != other); - assert(Py_REFCNT(other) == 1 || PyRootBList_Check(other)); - - Py_INCREF(other); - blist_forget_children(self); - tmp = self->children; - self->children = other->children; - self->n = other->n; - self->num_children = other->num_children; - self->leaf = other->leaf; - - other->children = tmp; - other->n = 0; - other->num_children = 0; - other->leaf = 1; - - SAFE_DECREF(other); - _void(); -} - -/* Create a copy of self */ -static PyBList *blist_copy(PyBList *restrict self) -{ - PyBList *copy; - - copy = blist_new(); - if (!copy) return NULL; - blist_become(copy, self); - return copy; -} - -/* Create a copy of self, which is a root node */ -static PyBList *blist_root_copy(PyBList *restrict self) -{ - PyBList *copy; - - copy = blist_root_new(); - if (!copy) return NULL; - blist_become(copy, self); - ext_mark(copy, 0, DIRTY); - ext_mark_set_dirty_all(self); - return copy; -} - -/************************************************************************ - * Useful internal utility functions - */ - -/* We are searching for the child that contains leaf element i. - * - * Returns a 3-tuple: (the child object, our index of the child, - * the number of leaf elements before the child) - */ -static void blist_locate(PyBList *self, Py_ssize_t i, - PyObject **child, int *idx, Py_ssize_t *before) -{ - invariants(self, VALID_PARENT); - assert (!self->leaf); - - if (i <= self->n/2) { - /* Search from the left */ - Py_ssize_t so_far = 0; - int k; - for (k = 0; k < self->num_children; k++) { - PyBList *p = (PyBList *) self->children[k]; - if (i < so_far + p->n) { - *child = (PyObject *) p; - *idx = k; - *before = so_far; - _void(); - return; - } - so_far += p->n; - } - } else { - /* Search from the right */ - Py_ssize_t so_far = self->n; - int k; - for (k = self->num_children-1; k >= 0; k--) { - PyBList *p = (PyBList *) self->children[k]; - so_far -= p->n; - if (i >= so_far) { - *child = (PyObject *) p; - *idx = k; - *before = so_far; - _void(); - return; - } - } - } - - /* Just append */ - *child = self->children[self->num_children-1]; - *idx = self->num_children-1; - *before = self->n - ((PyBList *)(*child))->n; - - _void(); -} - -/* Find the current height of the tree. - * - * We could keep an extra few bytes in each node rather than - * figuring this out dynamically, which would reduce the - * asymptotic complexitiy of a few operations. However, I - * suspect it's not worth the extra overhead of updating it all - * over the place. - */ -BLIST_LOCAL(int) -blist_get_height(PyBList *self) -{ - invariants(self, VALID_PARENT); - if (self->leaf) - return _int(1); - return _int(blist_get_height((PyBList *) - self->children[self->num_children - 1]) - + 1); -} - -BLIST_LOCAL(PyBList *) -blist_prepare_write(PyBList *self, int pt) -{ - /* We are about to modify the child at index pt. Prepare it. - * - * This function returns the child object. If the caller has - * other references to the child, they must be discarded as they - * may no longer be valid. - * - * If the child's .refcount is 1, we simply return the - * child object. - * - * If the child's .refcount is greater than 1, we: - * - * - copy the child object - * - decrement the child's .refcount - * - replace self.children[pt] with the copy - * - return the copy - */ - - invariants(self, VALID_RW); - assert(!self->leaf); - - if (pt < 0) - pt += self->num_children; - if (Py_REFCNT(self->children[pt]) > 1) { - PyBList *new_copy = blist_new(); - if (!new_copy) return NULL; - blist_become(new_copy, (PyBList *) self->children[pt]); - SAFE_DECREF(self->children[pt]); - self->children[pt] = (PyObject *) new_copy; - } - - return (PyBList *) _ob(self->children[pt]); -} - -/* Macro version assumes that pt is non-negative */ -#define blist_PREPARE_WRITE(self, pt) (Py_REFCNT((self)->children[(pt)]) > 1 ? blist_prepare_write((self), (pt)) : (PyBList *) (self)->children[(pt)]) - -/* Recompute self->n */ -BLIST_LOCAL(void) -blist_adjust_n(PyBList *restrict self) -{ - int i; - - invariants(self, VALID_RW); - - if (self->leaf) { - self->n = self->num_children; - _void(); - return; - } - self->n = 0; - for (i = 0; i < self->num_children; i++) - self->n += ((PyBList *)self->children[i])->n; - - _void(); -} - -/* Non-default constructor. Create a node with specific children. - * - * We steal the reference counters from the caller. - */ -static PyBList *blist_new_sibling(PyBList *sibling) -{ - PyBList *restrict self = blist_new(); - if (!self) return NULL; - assert(sibling->num_children == LIMIT); - copy(self, 0, sibling, HALF, HALF); - self->leaf = sibling->leaf; - self->num_children = HALF; - sibling->num_children = HALF; - blist_adjust_n(self); - return self; -} - -/************************************************************************ - * Bit twiddling utility function - */ - -/* Return the highest set bit. E.g., for 0x00845894 return 0x00800000 */ -static unsigned highest_set_bit_slow(unsigned x) -{ - unsigned rv = 0; - unsigned mask; - for (mask = 0x1; mask; mask <<= 1) - if (mask & x) rv = mask; - return rv; -} - -#if SIZEOF_INT == 4 -static unsigned highest_set_bit_table[256]; - -static void highest_set_bit_init(void) -{ - unsigned i; - for (i = 0; i < 256; i++) - highest_set_bit_table[i] = highest_set_bit_slow(i); -} - -static unsigned highest_set_bit(unsigned v) -{ - register unsigned tt, t; - - if ((tt = v >> 16)) - return (t = tt >> 8) ? highest_set_bit_table[t] << 24 - : highest_set_bit_table[tt] << 16; - else - return (t = v >> 8) ? highest_set_bit_table[t] << 8 - : highest_set_bit_table[v]; -} -#else -#define highest_set_bit_init() () -#define highest_set_bit(v) (highest_set_bit_slow((v))) -#endif - -/************************************************************************ - * Functions for the index extension used by the root node - */ - -/* Initialize the index extension. Does not allocate any memory */ -static void ext_init(PyBListRoot *root) -{ - root->index_list = NULL; - root->offset_list = NULL; - root->setclean_list = NULL; - root->index_allocated = 0; - root->dirty = NULL; - root->dirty_length = 0; - root->dirty_root = DIRTY; - root->free_root = -1; - -#ifdef Py_DEBUG - root->last_n = root->n; -#endif -} - -/* Deallocate any memory used by the index extension */ -static void ext_dealloc(PyBListRoot *root) -{ - if (root->index_list) PyMem_Free(root->index_list); - if (root->offset_list) PyMem_Free(root->offset_list); - if (root->setclean_list) PyMem_Free(root->setclean_list); - if (root->dirty) PyMem_Free(root->dirty); - ext_init(root); -} - -/* Find or create a new free node in "dirty" and return an index to it. - * amortized O(1) */ -static Py_ssize_t ext_alloc(PyBListRoot *root) -{ - Py_ssize_t i, parent; - - if (root->free_root < 0) { - int newl; - int i; - - if (!root->dirty) { - newl = 32; - root->dirty = PyMem_New(Py_ssize_t, newl); - root->dirty_root = DIRTY; - if (!root->dirty) return -1; - } else { - void *tmp; - - assert(root->dirty_length > 0); - newl = root->dirty_length*2; - tmp = root->dirty; - PyMem_Resize(tmp, Py_ssize_t, newl); - if (!tmp) { - PyMem_Free(root->dirty); - root->dirty = NULL; - root->dirty_root = DIRTY; - return -1; - } - root->dirty = tmp; - } - - for (i = root->dirty_length; i < newl; i += 2) { - root->dirty[i] = i+2; - root->dirty[i+1] = -1; - } - root->dirty[newl-2] = -1; - root->free_root = root->dirty_length; - root->dirty_length = newl; - assert(root->free_root >= 0); - assert(root->free_root+1 < root->dirty_length); - } - - /* Depth-first search for a node with fewer than 2 children. - * Guaranteed to terminate in O(log n) since any leaf node - * will suffice. - */ - - i = root->free_root; - parent = -1; - assert(i >= 0); - assert(i+1 < root->dirty_length); - while (root->dirty[i] >= 0 && root->dirty[i+1] >= 0) { - assert(0); - assert(i >= 0); - assert(i+1 < root->dirty_length); - parent = i; - i = root->dirty[i]; - } - - /* At this point, "i" is the node to be alloced. "parent" is - * the node containing a pointer to "i" or -1 if free_root - * points to "i" - * - * parent's pointer to i is always the left-hand pointer - * - * i has at most one child - */ - - if (parent < 0) { - if (root->dirty[i] >= 0) - root->free_root = root->dirty[i]; - else - root->free_root = root->dirty[i+1]; - } else { - if (root->dirty[i] >= 0) - root->dirty[parent] = root->dirty[i]; - else - root->dirty[parent] = root->dirty[i+1]; - } - - assert(i >= 0); - assert(i+1 < root->dirty_length); - return i; -} - -/* Add each node in the tree rooted at loc to the free tree */ -/* Amortized O(1), since each node to be freed corresponds with - * an earlier lookup. Unamortized worst-case O(n)*/ -static void ext_free(PyBListRoot *root, Py_ssize_t loc) -{ - assert(loc >= 0); - assert(loc+1 < root->dirty_length); - if (root->dirty[loc] >= 0) - ext_free(root, root->dirty[loc]); - if (root->dirty[loc+1] >= 0) - ext_free(root, root->dirty[loc+1]); - - root->dirty[loc] = root->free_root; - root->dirty[loc+1] = -1; - root->free_root = loc; - assert(root->free_root >= 0); - assert(root->free_root+1 < root->dirty_length); -} - -BLIST_LOCAL(void) -ext_mark_r(PyBListRoot *root, Py_ssize_t offset, Py_ssize_t i, - int bit, int value) -{ - Py_ssize_t j, next; - - if (!(offset & bit)) { - /* Take left fork */ - - if (value == DIRTY) { - /* Mark right fork dirty */ - assert(i >= 0 && i+1 < root->dirty_length); - if (root->dirty[i+1] >= 0) - ext_free(root, root->dirty[i+1]); - root->dirty[i+1] = DIRTY; - } - next = i; - } else { - /* Take right fork */ - next = i+1; - } - - assert(next >= 0 && next < root->dirty_length); - - j = root->dirty[next]; - - if (j == value) - return; - - if (bit == 1) { - root->dirty[next] = value; - return; - } - - if (j < 0) { - Py_ssize_t nvalue = j; - Py_ssize_t tmp; - tmp = ext_alloc(root); - if (tmp < 0) { - ext_dealloc(root); - return; - } - root->dirty[next] = tmp; - j = root->dirty[next]; - assert(j >= 0); - assert(j+1 < root->dirty_length); - root->dirty[j] = nvalue; - root->dirty[j+1] = nvalue; - } - - ext_mark_r(root, offset, j, bit >> 1, value); - - if (root->dirty - && (root->dirty[j] == root->dirty[j+1] - || (root->dirty[j] < 0 - && (((offset | (bit>>1)) & ~((bit>>1)-1)) - > (root->n-1) /INDEX_FACTOR)))) { - /* Both the same? Consolidate */ - ext_free(root, j); - root->dirty[next] = value; - } -} - -/* If "value" is CLEAN, mark the list clean at exactly "offset". - * If "value" is DIRTY, mark the list dirty for all >= "offset". - */ -static void ext_mark(PyBList *broot, Py_ssize_t offset, int value) -{ - int bit; - - PyBListRoot *root = (PyBListRoot*) broot; - if (!root->n) { -#ifdef Py_DEBUG - root->last_n = root->n; -#endif - return; - } - if ((!offset && value == DIRTY) || root->n <= INDEX_FACTOR) { - if (root->dirty_root >= 0) - ext_free(root, root->dirty_root); - root->dirty_root = DIRTY; -#ifdef Py_DEBUG - root->last_n = root->n; -#endif - return; - } - -#ifdef Py_DEBUG - assert(root->last_n == root->n); -#endif - - if (root->dirty_root == value) return; - - if (root->dirty_root < 0) { - Py_ssize_t nvalue = root->dirty_root; - root->dirty_root = ext_alloc(root); - if (root->dirty_root < 0) { - ext_dealloc(root); - return; - } - assert(root->dirty_root >= 0); - assert(root->dirty_root+1 < root->dirty_length); - root->dirty[root->dirty_root] = nvalue; - root->dirty[root->dirty_root+1] = nvalue; - } - offset /= INDEX_FACTOR; - - bit = highest_set_bit((root->n-1) / INDEX_FACTOR); - ext_mark_r(root, offset, root->dirty_root, bit, value); - if (root->dirty && - (root->dirty[root->dirty_root] ==root->dirty[root->dirty_root+1])){ - ext_free(root, root->dirty_root); - root->dirty_root = value; - } -} - -/* Mark a section of the list dirty for set operations */ -static void ext_mark_set_dirty(PyBList *broot, Py_ssize_t i, Py_ssize_t j) -{ - /* XXX We could set only the values in the setclean_list, but - * that takes O(n) time. Marking the nodes dirty for reading - * takes O(log n) time but will slow down future reads. - * Ideally there'd be a separate index_list for set - * operations */ - ext_mark(broot, i, DIRTY); -} - -/* Mark an entire list dirty for set operations */ -static void ext_mark_set_dirty_all(PyBList *broot) -{ - ext_mark_set_dirty(broot, 0, broot->n); -} - -#if 0 -/* These functions are unused, but useful for debugging. Do not remove. */ - -static void ext_print_r(PyBListRoot *root, Py_ssize_t i) -{ - printf("("); - if (root->dirty[i] < 0) - printf("%d", root->dirty[i]); - else - ext_print_r(root, root->dirty[i]); - printf(","); - if (root->dirty[i+1] < 0) - printf("%d", root->dirty[i+1]); - else - ext_print_r(root, root->dirty[i+1]); - printf(")"); -} - -static void ext_print(PyBListRoot *root) -{ - if (root->dirty_root < 0) - printf("%d", root->dirty_root); - else - ext_print_r(root, root->dirty_root); - printf("\n"); -} -#endif - -/* Find an arbitrary DIRTY node in "dirty" at or below node "i" which - * corresponds with "offset" into the list. "bit" corresponds with - * the bit tested to determine the right or left child of "i". - * Returns the offset corresponding with the DIRTY node - */ -static Py_ssize_t -ext_find_dirty(PyBListRoot *root, Py_ssize_t offset, int bit, Py_ssize_t i) -{ - assert(root->dirty); - assert(i >= 0); - assert(bit); - - if (root->dirty[i] == DIRTY) - return offset; - if (root->dirty[i] >= 0) - return ext_find_dirty(root, offset, bit >> 1, root->dirty[i]); - - if (root->dirty[i+1] == DIRTY) - return offset | bit; - assert(root->dirty[i+1] >= 0); - return ext_find_dirty(root, offset | bit, bit >> 1, root->dirty[i+1]); -} - -/* Determine if "offset" is DIRTY, returning a Boolean value. Sets - * "dirty_offset" to an arbitrary DIRTY node located along the way, - * even if the requested offset is CLEAN. "dirty_offset" will be set - * to -1 if there are no DIRTY nodes. Worst-case O(log n) - */ -static int -ext_is_dirty(PyBListRoot *root, Py_ssize_t offset, Py_ssize_t *dirty_offset) -{ - Py_ssize_t i, parent; - int bit; - - if (root->dirty == NULL || root->dirty_root < 0) { - *dirty_offset = -1; - return root->dirty_root == DIRTY; - } - i = root->dirty_root; - parent = -1; - offset /= INDEX_FACTOR; - bit = highest_set_bit((root->n-1) / INDEX_FACTOR); - -#ifdef Py_DEBUG - assert(root->last_n == root->n); -#endif - - do { - assert(bit); - parent = i; - if (!(offset & bit)) { - assert (i >= 0 && i < root->dirty_length); - i = root->dirty[i]; - } else { - assert (i >= 0 && i+1 < root->dirty_length); - i = root->dirty[i+1]; - } - bit >>= 1; - } while (i >= 0); - - if (i != DIRTY) { - if (!bit) bit = 1; else bit <<= 1; - *dirty_offset = INDEX_FACTOR * - ext_find_dirty(root, (offset ^ bit) & ~(bit-1), bit, - parent); - assert(*dirty_offset >= 0); - assert(*dirty_offset < root->n); - } - - return i == DIRTY; -} - -/* The length of the BList may have changed. Adjust the lengths of - * the extension data structures as needed */ -static int -ext_grow_index(PyBListRoot *root) -{ - Py_ssize_t oldl = root->index_allocated; - if (!root->index_allocated) { - if (root->index_list) PyMem_Free(root->index_list); - if (root->offset_list) PyMem_Free(root->offset_list); - if (root->setclean_list) PyMem_Free(root->setclean_list); - - root->index_list = NULL; - root->offset_list = NULL; - root->setclean_list = NULL; - - root->index_allocated = (root->n-1) / INDEX_FACTOR + 1; - root->index_list = PyMem_New(PyBList *, root->index_allocated); - if (!root->index_list) { - fail: - root->index_allocated = oldl; - return -1; - } - root->offset_list = PyMem_New(Py_ssize_t, root->index_allocated); - if (!root->offset_list) goto fail; - root->setclean_list - = PyMem_New(unsigned,SETCLEAN_LEN(root->index_allocated)); - if (!root->setclean_list) goto fail; - } else { - void *tmp; - - do { - root->index_allocated *= 2; - } while ((root->n-1) / INDEX_FACTOR + 1 > root->index_allocated); - tmp = root->index_list; - PyMem_Resize(tmp, PyBList *, root->index_allocated); - if (!tmp) goto fail; - root->index_list = tmp; - - tmp = root->offset_list; - PyMem_Resize(tmp, Py_ssize_t, root->index_allocated); - if (!tmp) goto fail; - root->offset_list = tmp; - - tmp = root->setclean_list; - PyMem_Resize(tmp, unsigned, SETCLEAN_LEN(root->index_allocated)); - if (!tmp) goto fail; - root->setclean_list = tmp; - } - return 0; -} - -#define SET_OK_NO 0 -#define SET_OK_YES 1 -#define SET_OK_ALL 2 - -BLIST_LOCAL(void) -ext_index_r(PyBListRoot *root, PyBList *self, Py_ssize_t i, int set_ok) -{ - int j; - if (self != (PyBList *)root) { - assert(!(set_ok == SET_OK_ALL && Py_REFCNT(self) != 1)); - set_ok = set_ok && (Py_REFCNT(self) == 1); - } - - if (self->leaf) { - Py_ssize_t ioffset = i / INDEX_FACTOR; - if (ioffset * INDEX_FACTOR < i) ioffset++; - do { - assert(ioffset < root->index_allocated); - root->index_list[ioffset] = self; - root->offset_list[ioffset] = i; - if (set_ok != SET_OK_ALL) { - if (Py_REFCNT(self) > 1 || !set_ok) - CLEAR_BIT(root->setclean_list,ioffset); - else - SET_BIT(root->setclean_list, ioffset); - } - } while (++ioffset * INDEX_FACTOR < i + self->n); - i += self->n; - } else { - for (j = 0; j < self->num_children; j++) { - PyBList *child = (PyBList *) self->children[j]; - ext_index_r(root, child, i, set_ok); - i += child->n; - } - } -} - -BLIST_LOCAL(void) -ext_index_all_dirty_r(PyBListRoot *root, PyBList *self, Py_ssize_t end, - Py_ssize_t child_index, Py_ssize_t child_n, int set_ok) -{ - Py_ssize_t i; - for (i = child_index; i < self->num_children && child_n < end; i++) { - PyBList *child = (PyBList *) self->children[i]; - ext_index_r(root, child, child_n, set_ok); - child_n += child->n; - } -} - -BLIST_LOCAL(void) -ext_index_all_r(PyBListRoot *root, - Py_ssize_t dirty_index, Py_ssize_t dirty_offset, Py_ssize_t dirty_length, - PyBList *self, Py_ssize_t child_index, Py_ssize_t child_n, - int set_ok) -{ - if (dirty_index <= CLEAN) { - return; - } else if (dirty_index == DIRTY) { - ext_index_all_dirty_r(root, self, dirty_offset + dirty_length, - child_index, child_n, set_ok); - return; - } - - if (!self->leaf) { - while (child_index < self->num_children) { - PyBList *child = (PyBList *) self->children[child_index]; - if (child_n + child->n > dirty_offset) - break; - child_n += child->n; - child_index++; - } - - if (child_index+1 == self->num_children - || (((PyBList *)self->children[child_index])->n + child_n - <= dirty_offset + dirty_length)) { - self = (PyBList *) self->children[child_index]; - child_index = 0; - } - } - - dirty_length /= 2; - ext_index_all_r(root, - root->dirty[dirty_index], dirty_offset, dirty_length, - self, child_index, child_n, set_ok); - dirty_offset += dirty_length; - ext_index_all_r(root, - root->dirty[dirty_index+1], dirty_offset, dirty_length, - self, child_index, child_n, set_ok); -} - -/* Make everything clean in O(n) time. Any operation that alters the - * list and already takes Omega(n) time should call one of these functions. - */ -BLIST_LOCAL(void) -_ext_index_all(PyBListRoot *root, int set_ok_all) -{ - Py_ssize_t ioffset_max = (root->n-1) / INDEX_FACTOR + 1; - int set_ok; - - if (root->index_allocated < ioffset_max) - ext_grow_index(root); - if (set_ok_all) { - set_ok = SET_OK_ALL; - memset(root->setclean_list, 255, - SETCLEAN_LEN(root->index_allocated) * sizeof(unsigned)); - } else - set_ok = SET_OK_YES; - - ext_index_all_r(root, root->dirty_root, 0, - highest_set_bit((root->n-1)) * 2, - (PyBList*)root, 0, 0, set_ok); - -#ifdef Py_DEBUG - root->last_n = root->n; -#endif - if (root->dirty_root >= 0) - ext_free(root, root->dirty_root); - root->dirty_root = set_ok_all ? CLEAN_RW : CLEAN; -} -#define ext_index_all(root) do { if (!(root)->leaf) _ext_index_all((root), 0); } while (0) -#define ext_index_set_all(root) do { if (!(root)->leaf) _ext_index_all((root), 1); } while (0) - -BLIST_LOCAL_INLINE(void) -_ext_reindex_all(PyBListRoot *root, int set_ok_all) -{ - if (root->dirty_root >= 0) - ext_free(root, root->dirty_root); - root->dirty_root = DIRTY; - - _ext_index_all(root, set_ok_all); -} - -#define ext_reindex_all(root) do { if (!(root)->leaf) _ext_reindex_all((root), 0); } while (0) -#define ext_reindex_set_all(root) do { if (!(root)->leaf) _ext_reindex_all((root), 1); } while (0) - -/* We found a particular node at a certain offset. Add it to the - * index and mark it clean. */ -BLIST_LOCAL(void) -ext_mark_clean(PyBListRoot *root, Py_ssize_t offset, PyBList *p, int setclean) -{ - Py_ssize_t ioffset = offset / INDEX_FACTOR; - - assert(offset < root->n); - - while (ioffset * INDEX_FACTOR < offset) - ioffset++; - for (;ioffset * INDEX_FACTOR < offset + p->n; ioffset++) { - ext_mark((PyBList*)root, ioffset * INDEX_FACTOR, CLEAN); - - if (ioffset >= root->index_allocated) { - int err = ext_grow_index(root); - if (err < -1) { - ext_dealloc(root); - return; - } - } - - assert(ioffset >= 0); - assert(ioffset < root->index_allocated); - root->index_list[ioffset] = p; - root->offset_list[ioffset] = offset; - - if (setclean) - SET_BIT(root->setclean_list, ioffset); - else - CLEAR_BIT(root->setclean_list, ioffset); - } -} - -/* Lookup the node at offset i and mark it clean */ -static PyObject *ext_make_clean(PyBListRoot *root, Py_ssize_t i) -{ - PyObject *rv; - Py_ssize_t so_far; - Py_ssize_t offset = 0; - PyBList *p = (PyBList *)root; - Py_ssize_t j = i; - int k; - int setclean = 1; - do { - blist_locate(p, j, (PyObject **) &p, &k, &so_far); - if (Py_REFCNT(p) > 1) - setclean = 0; - offset += so_far; - j -= so_far; - } while (!p->leaf); - - rv = p->children[j]; - ext_mark_clean(root, offset, p, setclean); - return rv; -} - -/************************************************************************ - * Functions for manipulating the tree - */ - -/* Child k has underflowed. Borrow from k+1 */ -static void blist_borrow_right(PyBList *self, int k) -{ - PyBList *restrict p = (PyBList *) self->children[k]; - PyBList *restrict right; - unsigned total; - unsigned split; - unsigned migrate; - - invariants(self, VALID_RW); - - right = blist_prepare_write(self, k+1); - total = p->num_children + right->num_children; - split = total / 2; - migrate = split - p->num_children; - - assert(split >= HALF); - assert(total-split >= HALF); - - copy(p, p->num_children, right, 0, migrate); - p->num_children += migrate; - shift_left(right, migrate, migrate); - right->num_children -= migrate; - blist_adjust_n(right); - blist_adjust_n(p); - - _void(); -} - -/* Child k has underflowed. Borrow from k-1 */ -static void blist_borrow_left(PyBList *self, int k) -{ - PyBList *restrict p = (PyBList *) self->children[k]; - PyBList *restrict left; - unsigned total; - unsigned split; - unsigned migrate; - - invariants(self, VALID_RW); - - left = blist_prepare_write(self, k-1); - total = p->num_children + left->num_children; - split = total / 2; - migrate = split - p->num_children; - - assert(split >= HALF); - assert(total-split >= HALF); - - shift_right(p, 0, migrate); - copy(p, 0, left, left->num_children - migrate, migrate); - p->num_children += migrate; - left->num_children -= migrate; - blist_adjust_n(left); - blist_adjust_n(p); - - _void(); -} - -/* Child k has underflowed. Merge with k+1 */ -static void blist_merge_right(PyBList *self, int k) -{ - int i; - PyBList *restrict p = (PyBList *) self->children[k]; - PyBList *restrict p2 = (PyBList *) self->children[k+1]; - - invariants(self, VALID_RW); - - copy(p, p->num_children, p2, 0, p2->num_children); - for (i = 0; i < p2->num_children; i++) - Py_INCREF(p2->children[i]); - p->num_children += p2->num_children; - blist_forget_child(self, k+1); - blist_adjust_n(p); - - _void(); -} - -/* Child k has underflowed. Merge with k-1 */ -static void blist_merge_left(PyBList *self, int k) -{ - int i; - PyBList *restrict p = (PyBList *) self->children[k]; - PyBList *restrict p2 = (PyBList *) self->children[k-1]; - - invariants(self, VALID_RW); - - shift_right(p, 0, p2->num_children); - p->num_children += p2->num_children; - copy(p, 0, p2, 0, p2->num_children); - for (i = 0; i < p2->num_children; i++) - Py_INCREF(p2->children[i]); - blist_forget_child(self, k-1); - blist_adjust_n(p); - - _void(); -} - -/* Collapse the tree, if possible */ -BLIST_LOCAL(int) -blist_collapse(PyBList *self) -{ - PyBList *p; - invariants(self, VALID_RW|VALID_COLLAPSE); - - if (self->num_children != 1 || self->leaf) { - blist_adjust_n(self); - return _int(0); - } - - p = blist_PREPARE_WRITE(self, 0); - blist_become_and_consume(self, p); - check_invariants(self); - return _int(1); -} - -/* Check if children k-1, k, or k+1 have underflowed. - * - * If so, move things around until self is the root of a valid - * subtree again, possibly requiring collapsing the tree. - * - * Always calls self._adjust_n() (often via self.__collapse()). - */ -BLIST_LOCAL(int) -blist_underflow(PyBList *self, int k) -{ - invariants(self, VALID_RW|VALID_COLLAPSE); - - if (self->leaf) { - blist_adjust_n(self); - return _int(0); - } - - if (k < self->num_children) { - PyBList *restrict p = blist_prepare_write(self, k); - int shrt = HALF - p->num_children; - - while (shrt > 0) { - if (k+1 < self->num_children - && ((PyBList *)self->children[k+1])->num_children >= HALF + shrt) - blist_borrow_right(self, k); - else if (k > 0 - && (((PyBList *)self->children[k-1])->num_children - >= HALF + shrt)) - blist_borrow_left(self, k); - else if (k+1 < self->num_children) - blist_merge_right(self, k); - else if (k > 0) - blist_merge_left(self, k--); - else /* No siblings for p */ - return _int(blist_collapse(self)); - - p = blist_prepare_write(self, k); - shrt = HALF - p->num_children; - } - } - - if (k > 0 && ((PyBList *)self->children[k-1])->num_children < HALF) { - int collapse = blist_underflow(self, k-1); - if (collapse) return _int(collapse); - } - - if (k+1 < self->num_children - && ((PyBList *)self->children[k+1])->num_children < HALF) { - int collapse = blist_underflow(self, k+1); - if (collapse) return _int(collapse); - } - - return _int(blist_collapse(self)); -} - -/* Insert 'item', which may be a subtree, at index k. */ -BLIST_LOCAL(PyBList *) -blist_insert_here(PyBList *self, int k, PyObject *item) -{ - /* Since the subtree may have fewer than half elements, we may - * need to merge it after insertion. - * - * This function may cause self to overflow. If it does, it will - * take the upper half of its children and put them in a new - * subtree and return the subtree. The caller is responsible for - * inserting this new subtree just to the right of self. - * - * Otherwise, it returns None. - */ - - PyBList *restrict sibling; - - invariants(self, VALID_RW|VALID_OVERFLOW); - assert(k >= 0); - - if (self->num_children < LIMIT) { - int collapse; - - shift_right(self, k, 1); - self->num_children++; - self->children[k] = item; - collapse = blist_underflow(self, k); - assert(!collapse); (void) collapse; - return _blist(NULL); - } - - sibling = blist_new_sibling(self); - - if (k < HALF) { - int collapse; - - shift_right(self, k, 1); - self->num_children++; - self->children[k] = item; - collapse = blist_underflow(self, k); - assert(!collapse); (void) collapse; - } else { - int collapse; - - shift_right(sibling, k - HALF, 1); - sibling->num_children++; - sibling->children[k - HALF] = item; - collapse = blist_underflow(sibling, k - HALF); - assert(!collapse); (void) collapse; - blist_adjust_n(sibling); - } - - blist_adjust_n(self); - check_invariants(self); - return _blist(sibling); -} - -/* Recurse depth layers, then insert subtree on the left or right */ -BLIST_LOCAL(PyBList *) -blist_insert_subtree(PyBList *self, int side, PyBList *subtree, int depth) -{ - /* This function may cause an overflow. - * - * depth == 0 means insert the subtree as a child of self. - * depth == 1 means insert the subtree as a grandchild, etc. - */ - - PyBList *sibling; - invariants(self, VALID_RW|VALID_OVERFLOW); - assert(side == 0 || side == -1); - - self->n += subtree->n; - - if (depth) { - PyBList *restrict p = blist_prepare_write(self, side); - PyBList *overflow = blist_insert_subtree(p, side, - subtree, depth-1); - if (!overflow) return _blist(NULL); - if (side == 0) - side = 1; - subtree = overflow; - } - - if (side < 0) - side = self->num_children; - - sibling = blist_insert_here(self, side, (PyObject *) subtree); - - return _blist(sibling); -} - -/* Handle the case where a user-visible node overflowed */ -BLIST_LOCAL(int) -blist_overflow_root(PyBList *self, PyBList *overflow) -{ - PyBList *child; - - invariants(self, VALID_RW); - - if (!overflow) return _int(0); - child = blist_new(); - if (!child) { - decref_later((PyObject*)overflow); - return _int(0); - } - blist_become_and_consume(child, self); - self->children[0] = (PyObject *)child; - self->children[1] = (PyObject *)overflow; - self->num_children = 2; - self->leaf = 0; - blist_adjust_n(self); - return _int(-1); -} - -/* Concatenate two trees of potentially different heights. */ -BLIST_LOCAL(PyBList *) -blist_concat_blist(PyBList *left_subtree, PyBList *right_subtree, - int height_diff, int *padj) -{ - /* The parameters are the two trees, and the difference in their - * heights expressed as left_height - right_height. - * - * Returns a tuple of the new, combined tree, and an integer. - * The integer expresses the height difference between the new - * tree and the taller of the left and right subtrees. It will - * be 0 if there was no change, and 1 if the new tree is taller - * by 1. - */ - - int adj = 0; - PyBList *overflow; - PyBList *root; - - assert(Py_REFCNT(left_subtree) == 1); - assert(Py_REFCNT(right_subtree) == 1); - - if (height_diff == 0) { - int collapse; - - root = blist_new(); - if (!root) { - decref_later((PyObject*)left_subtree); - decref_later((PyObject*)right_subtree); - return NULL; - } - root->children[0] = (PyObject *) left_subtree; - root->children[1] = (PyObject *) right_subtree; - root->leaf = 0; - root->num_children = 2; - collapse = blist_underflow(root, 0); - if (!collapse) - collapse = blist_underflow(root, 1); - if (!collapse) - adj = 1; - overflow = NULL; - } else if (height_diff > 0) { /* Left is larger */ - root = left_subtree; - overflow = blist_insert_subtree(root, -1, right_subtree, - height_diff - 1); - } else { /* Right is larger */ - root = right_subtree; - overflow = blist_insert_subtree(root, 0, left_subtree, - -height_diff - 1); - } - - adj += -blist_overflow_root(root, overflow); - if (padj) *padj = adj; - - return root; -} - -/* Concatenate two subtrees of potentially different heights. */ -BLIST_LOCAL(PyBList *) -blist_concat_subtrees(PyBList *left_subtree, int left_depth, - PyBList *right_subtree, int right_depth, int *pdepth) -{ - /* Returns a tuple of the new, combined subtree and its depth. - * - * Depths are the depth in the parent, not their height. - */ - - int deepest = left_depth > right_depth ? - left_depth : right_depth; - PyBList *root = blist_concat_blist(left_subtree, right_subtree, - -(left_depth - right_depth), pdepth); - if (pdepth) *pdepth = deepest - *pdepth; - return root; -} - -/* Concatenate two roots of potentially different heights. */ -BLIST_LOCAL(PyBList *) -blist_concat_roots(PyBList *left_root, int left_height, - PyBList *right_root, int right_height, int *pheight) -{ - /* Returns a tuple of the new, combined root and its height. - * - * Heights are the height from the root to its leaf nodes. - */ - - PyBList *root = blist_concat_blist(left_root, right_root, - left_height - right_height, pheight); - int highest = left_height > right_height ? - left_height : right_height; - - if (pheight) *pheight = highest + *pheight; - - return root; -} - -BLIST_LOCAL(PyBList *) -blist_concat_unknown_roots(PyBList *left_root, PyBList *right_root) -{ - return blist_concat_roots(left_root, blist_get_height(left_root), - right_root, blist_get_height(right_root), - NULL); -} - -/* Child at position k is too short by "depth". Fix it */ -BLIST_LOCAL(int) -blist_reinsert_subtree(PyBList *self, int k, int depth) -{ - PyBList *subtree; - - invariants(self, VALID_RW); - - assert(Py_REFCNT(self->children[k]) == 1); - subtree = (PyBList *) self->children[k]; - shift_left(self, k+1, 1); - self->num_children--; - - if (self->num_children > k) { - /* Merge right */ - PyBList *p = blist_prepare_write(self, k); - PyBList *overflow = blist_insert_subtree(p, 0, - subtree, depth-1); - if (overflow) { - shift_right(self, k+1, 1); - self->num_children++; - self->children[k+1] = (PyObject *) overflow; - } - } else { - /* Merge left */ - PyBList *p = blist_prepare_write(self, k-1); - PyBList *overflow = blist_insert_subtree(p, -1, - subtree, depth-1); - if (overflow) { - shift_right(self, k, 1); - self->num_children++; - self->children[k] = (PyObject *) overflow; - } - } - - return _int(blist_underflow(self, k)); -} - -/************************************************************************ - * The main insert and deletion operations - */ - -/* Recursive to find position i, and insert item just there. */ -BLIST_LOCAL(PyBList *) -ins1(PyBList *self, Py_ssize_t i, PyObject *item) -{ - PyBList *ret; - PyBList *restrict p; - int k; - Py_ssize_t so_far; - PyBList *overflow; - - invariants(self, VALID_RW|VALID_OVERFLOW); - - if (self->leaf) { - Py_INCREF(item); - - /* Speed up the common case */ - if (self->num_children < LIMIT) { - shift_right(self, i, 1); - self->num_children++; - self->n++; - self->children[i] = item; - return _blist(NULL); - } - - return _blist(blist_insert_here(self, i, item)); - } - - blist_locate(self, i, (PyObject **) &p, &k, &so_far); - - self->n += 1; - p = blist_prepare_write(self, k); - overflow = ins1(p, i - so_far, item); - - if (!overflow) ret = NULL; - else ret = blist_insert_here(self, k+1, (PyObject *) overflow); - - return _blist(ret); -} - -BLIST_LOCAL(int) -blist_extend_blist(PyBList *self, PyBList *other) -{ - PyBList *right, *left, *root; - - invariants(self, VALID_RW); - - /* Special case for speed */ - if (self->leaf && other->leaf && self->n + other->n <= LIMIT) { - copyref(self, self->n, other, 0, other->n); - self->n += other->n; - self->num_children = self->n; - return _int(0); - } - - /* Make not-user-visible roots for the subtrees */ - right = blist_copy(other); /* XXX not checking return values */ - left = blist_new(); - if (left == NULL) - return _int(-1); - blist_become_and_consume(left, self); - - if (left->leaf && right->leaf) { - balance_leafs(left, right); - self->children[0] = (PyObject *) left; - self->children[1] = (PyObject *) right; - self->num_children = 2; - self->leaf = 0; - blist_adjust_n(self); - return _int(0); - } - - root = blist_concat_unknown_roots(left, right); - blist_become_and_consume(self, root); - SAFE_DECREF(root); - return _int(0); -} - -/* Recursive version of __delslice__ */ -static int blist_delslice(PyBList *self, Py_ssize_t i, Py_ssize_t j) -{ - /* This may cause self to collapse. It returns 0 if it did - * not. If a collapse occured, it returns a positive integer - * indicating how much shorter this subtree is compared to when - * _delslice() was entered. - * - * As a special exception, it may return 0 if the entire subtree - * is deleted. - * - * Additionally, this function may cause an underflow. - */ - - PyBList *restrict p, *restrict p2; - int k, k2, depth; - Py_ssize_t so_far, so_far2, low; - int collapse_left, collapse_right, deleted_k, deleted_k2; - - invariants(self, VALID_RW | VALID_PARENT | VALID_COLLAPSE); - check_invariants(self); - - if (j > self->n) - j = self->n; - - if (i == j) - return _int(0); - - if (self->leaf) { - blist_forget_children2(self, i, j); - self->n = self->num_children; - return _int(0); - } - - if (i == 0 && j >= self->n) { - /* Delete everything. */ - blist_CLEAR(self); - return _int(0); - } - - blist_locate(self, i, (PyObject **) &p, &k, &so_far); - blist_locate(self, j-1, (PyObject **) &p2, &k2, &so_far2); - - if (k == k2) { - /* All of the deleted elements are contained under a single - * child of this node. Recurse and check for a short - * subtree and/or underflow - */ - - assert(so_far == so_far2); - p = blist_prepare_write(self, k); - depth = blist_delslice(p, i - so_far, j - so_far); - if (p->n == 0) { - SAFE_DECREF(p); - shift_left(self, k+1, 1); - self->num_children--; - return _int(blist_collapse(self)); - } - if (!depth) - return _int(blist_underflow(self, k)); - return _int(blist_reinsert_subtree(self, k, depth)); - } - - /* Deleted elements are in a range of child elements. There - * will be: - * - a left child (k) where we delete some (or all) of its children - * - a right child (k2) where we delete some (or all) of it children - * - children in between who are deleted entirely - */ - - /* Call _delslice recursively on the left and right */ - p = blist_prepare_write(self, k); - collapse_left = blist_delslice(p, i - so_far, j - so_far); - p2 = blist_prepare_write(self, k2); - low = i-so_far2 > 0 ? i-so_far2 : 0; - collapse_right = blist_delslice(p2, low, j - so_far2); - - deleted_k = 0; /* False */ - deleted_k2 = 0; /* False */ - - /* Delete [k+1:k2] */ - blist_forget_children2(self, k+1, k2); - k2 = k+1; - - /* Delete k1 and k2 if they are empty */ - if (!((PyBList *)self->children[k2])->n) { - decref_later((PyObject *) self->children[k2]); - shift_left(self, k2+1, 1); - self->num_children--; - deleted_k2 = 1; /* True */ - } - if (!((PyBList *)self->children[k])->n) { - decref_later(self->children[k]); - shift_left(self, k+1, 1); - self->num_children--; - deleted_k = 1; /* True */ - } - - if (deleted_k && deleted_k2) /* # No messy subtrees. Good. */ - return _int(blist_collapse(self)); - - /* The left and right may have collapsed and/or be in an - * underflow state. Clean them up. Work on fixing collapsed - * trees first, then worry about underflows. - */ - - if (!deleted_k && !deleted_k2 && collapse_left && collapse_right) { - /* Both exist and collapsed. Merge them into one subtree. */ - PyBList *left, *right, *subtree; - - left = (PyBList *) self->children[k]; - right = (PyBList *) self->children[k+1]; - shift_left(self, k+1, 1); - self->num_children--; - subtree = blist_concat_subtrees(left, collapse_left, - right, collapse_right, - &depth); - self->children[k] = (PyObject *) subtree; - } else if (deleted_k) { - /* Only the right potentially collapsed, point there. */ - depth = collapse_right; - /* k already points to the old k2, since k was deleted */ - } else if (!deleted_k2 && !collapse_left) { - /* Only the right potentially collapsed, point there. */ - k = k + 1; - depth = collapse_right; - } else { - depth = collapse_left; - } - - /* At this point, we have a potentially short subtree at k, - * with depth "depth". - */ - - if (!depth || self->num_children == 1) { - /* Doesn't need merging, or no siblings to merge with */ - return _int(depth + blist_underflow(self, k)); - } - - /* We have a short subtree at k, and we have other children */ - return _int(blist_reinsert_subtree(self, k, depth)); -} - -BLIST_LOCAL(PyObject *) -blist_get1(PyBList *self, Py_ssize_t i) -{ - PyBList *p; - int k; - Py_ssize_t so_far; - - invariants(self, VALID_PARENT); - - if (self->leaf) - return _ob(self->children[i]); - - blist_locate(self, i, (PyObject **) &p, &k, &so_far); - assert(i >= so_far); - return _ob(blist_get1(p, i - so_far)); -} - -BLIST_LOCAL(PyObject *) -blist_pop_last_fast(PyBList *self) -{ - PyBList *p; - - invariants(self, VALID_ROOT|VALID_RW); - - for (p = self; !p->leaf; - p = (PyBList*)p->children[p->num_children-1]) { - if (p != self && Py_REFCNT(p) > 1) - goto cleanup_and_slow; - p->n--; - } - - if ((Py_REFCNT(p) > 1 || p->num_children == HALF) - && self != p) { - PyBList *p2; - cleanup_and_slow: - for (p2 = self; p != p2; - p2 = (PyBList*)p2->children[p2->num_children-1]) - p2->n++; - return _ob(NULL); - } - p->n--; - p->num_children--; - - if ((self->n) % INDEX_FACTOR == 0) - ext_mark(self, 0, DIRTY); -#ifdef Py_DEBUG - else - ((PyBListRoot*)self)->last_n--; -#endif - return _ob(p->children[p->num_children]); -} - -static void blist_delitem(PyBList *self, Py_ssize_t i) -{ - invariants(self, VALID_ROOT|VALID_RW); - if (i == self->n-1) { - PyObject *v = blist_pop_last_fast(self); - if (v) { - decref_later(v); - _void(); - return; - } - } - - blist_delslice(self, i, i+1); - _void(); -} - -static PyObject *blist_delitem_return(PyBList *self, Py_ssize_t i) -{ - PyObject *rv; - - rv = blist_get1(self, i); - Py_INCREF(rv); - blist_delitem(self, i); - return rv; -} - -/************************************************************************ - * BList iterator - */ - -static iter_t *iter_init(iter_t *iter, PyBList *lst) -{ - iter->depth = 0; - - while(!lst->leaf) { - iter->stack[iter->depth].lst = lst; - iter->stack[iter->depth++].i = 1; - Py_INCREF(lst); - lst = (PyBList *) lst->children[0]; - } - - iter->leaf = lst; - iter->i = 0; - iter->depth++; - Py_INCREF(lst); - - return iter; -} - -static iter_t *iter_init2(iter_t *iter, PyBList *lst, Py_ssize_t start) -{ - iter->depth = 0; - - assert(start >= 0); - while (!lst->leaf) { - PyBList *p; - int k; - Py_ssize_t so_far; - - blist_locate(lst, start, (PyObject **) &p, &k, &so_far); - iter->stack[iter->depth].lst = lst; - iter->stack[iter->depth++].i = k + 1; - Py_INCREF(lst); - lst = p; - start -= so_far; - } - - iter->leaf = lst; - iter->i = start; - iter->depth++; - Py_INCREF(lst); - - return iter; -} - -static PyObject *iter_next(iter_t *iter) -{ - PyBList *p; - int i; - - p = iter->leaf; - if (p == NULL) - return NULL; - - if (!p->leaf) { - /* If p is the root, it may have been a leaf when we began - * iterating, but turned into a non-leaf during iteration. - * Modifying the list during iteration results in undefined - * behavior, so just throw in the towel. - */ - - return NULL; - } - - if (iter->i < p->num_children) - return p->children[iter->i++]; - - iter->depth--; - do { - decref_later((PyObject *) p); - if (!iter->depth) { - iter->leaf = NULL; - return NULL; - } - p = iter->stack[--iter->depth].lst; - i = iter->stack[iter->depth].i; - } while (i >= p->num_children); - - assert(iter->stack[iter->depth].lst == p); - iter->stack[iter->depth++].i = i+1; - - while (!p->leaf) { - p = (PyBList *) p->children[i]; - Py_INCREF(p); - i = 0; - iter->stack[iter->depth].lst = p; - iter->stack[iter->depth++].i = i+1; - } - - iter->leaf = iter->stack[iter->depth-1].lst; - iter->i = iter->stack[iter->depth-1].i; - - return p->children[i]; -} - -static void iter_cleanup(iter_t *iter) -{ - int i; - for (i = 0; i < iter->depth-1; i++) - decref_later((PyObject *) iter->stack[i].lst); - if (iter->depth) - decref_later((PyObject *) iter->leaf); -} - -BLIST_PYAPI(PyObject *) -py_blist_iter(PyObject *oseq) -{ - PyBList *seq; - blistiterobject *it; - - if (!PyRootBList_Check(oseq)) { - PyErr_BadInternalCall(); - return NULL; - } - - seq = (PyBList *) oseq; - - invariants(seq, VALID_USER); - - if (num_free_iters) { - it = free_iters[--num_free_iters]; - _Py_NewReference((PyObject *) it); - } else { - DANGER_BEGIN; - it = PyObject_GC_New(blistiterobject, &PyBListIter_Type); - DANGER_END; - if (it == NULL) - return _ob(NULL); - } - - if (seq->leaf) { - /* Speed up common case */ - it->iter.leaf = seq; - it->iter.i = 0; - it->iter.depth = 1; - Py_INCREF(seq); - } else - iter_init(&it->iter, seq); - - PyObject_GC_Track(it); - return _ob((PyObject *) it); -} - -static void blistiter_dealloc(PyObject *oit) -{ - blistiterobject *it; - - assert(PyBListIter_Check(oit)); - it = (blistiterobject *) oit; - - PyObject_GC_UnTrack(it); - iter_cleanup(&it->iter); - if (num_free_iters < MAXFREELISTS - && (Py_TYPE(it) == &PyBListIter_Type)) - free_iters[num_free_iters++] = it; - else - PyObject_GC_Del(it); - _decref_flush(); -} - -static int blistiter_traverse(PyObject *oit, visitproc visit, void *arg) -{ - blistiterobject *it; - int i; - - assert(PyBListIter_Check(oit)); - it = (blistiterobject *) oit; - - for (i = 0; i < it->iter.depth-1; i++) - Py_VISIT(it->iter.stack[i].lst); - if (it->iter.depth) - Py_VISIT(it->iter.leaf); - return 0; -} - -static PyObject *blistiter_next(PyObject *oit) -{ - blistiterobject *it = (blistiterobject *) oit; - PyObject *obj; - - /* Speed up common case */ - PyBList *p; - p = it->iter.leaf; - if (p == NULL) - return NULL; - if (p->leaf && it->iter.i < p->num_children) { - obj = p->children[it->iter.i++]; - Py_INCREF(obj); - return obj; - } - - obj = iter_next(&it->iter); - if (obj != NULL) - Py_INCREF(obj); - - _decref_flush(); - return obj; -} - -BLIST_PYAPI(PyObject *) -blistiter_len(blistiterobject *it) -{ - iter_t *iter = &it->iter; - int depth; - Py_ssize_t total = 0; - - if (!iter->leaf) - return PyInt_FromLong(0); - - total += iter->leaf->n - iter->i; - - for (depth = iter->depth-2; depth >= 0; depth--) { - point_t point = iter->stack[depth]; - int j; - if (point.lst->leaf) continue; - assert(point.i > 0); - for (j = point.i; j < point.lst->num_children; j++) { - PyBList *child = (PyBList *) point.lst->children[j]; - total += child->n; - } - } - if (iter->depth > 1 && iter->stack[0].lst->leaf) { - int extra = iter->stack[0].lst->n - iter->stack[0].i; - if (extra > 0) total += extra; - } - return PyInt_FromLong(total); -} - -PyDoc_STRVAR(length_hint_doc, "Private method returning an estimate of len(list(it))."); - -static PyMethodDef blistiter_methods[] = { - {"__length_hint__", (PyCFunction)blistiter_len, METH_NOARGS, length_hint_doc}, - {NULL, NULL} /* sentinel */ -}; - -PyTypeObject PyBListIter_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "blistiterator", /* tp_name */ - sizeof(blistiterobject), /* tp_basicsize */ - 0, /* tp_itemsize */ - /* methods */ - blistiter_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - PyObject_GenericGetAttr, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */ - 0, /* tp_doc */ - blistiter_traverse, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - PyObject_SelfIter, /* tp_iter */ - blistiter_next, /* tp_iternext */ - blistiter_methods, /* tp_methods */ - 0, /* tp_members */ -}; - -/************************************************************************ - * BList reverse iterator - */ - -BLIST_LOCAL(iter_t *) -riter_init2(iter_t *iter, PyBList *lst, Py_ssize_t start, Py_ssize_t stop) -{ - iter->depth = 0; - - assert(stop >= 0); - assert(start >= 0); - assert(start >= stop); - while (!lst->leaf) { - PyBList *p; - int k; - Py_ssize_t so_far; - - blist_locate(lst, start-1, (PyObject **) &p, &k, &so_far); - iter->stack[iter->depth].lst = lst; - iter->stack[iter->depth++].i = k - 1; - Py_INCREF(lst); - lst = p; - start -= so_far; - } - - iter->leaf = lst; - iter->i = start-1; - iter->depth++; - Py_INCREF(lst); - - return iter; -} -#define riter_init(iter, lst) (riter_init2((iter), (lst), (lst)->n, 0)) - -BLIST_LOCAL(PyObject *) -iter_prev(iter_t *iter) -{ - PyBList *p; - int i; - - p = iter->leaf; - if (p == NULL) - return NULL; - - if (!p->leaf) { - /* If p is the root, it may have been a leaf when we began - * iterating, but turned into a non-leaf during iteration. - * Modifying the list during iteration results in undefined - * behavior, so just throw in the towel. - */ - - return NULL; - } - - if (iter->i >= p->num_children && iter->i >= 0) - iter->i = p->num_children - 1; - - if (iter->i >= 0) - return p->children[iter->i--]; - - iter->depth--; - do { - decref_later((PyObject *) p); - if (!iter->depth) { - iter->leaf = NULL; - return NULL; - } - p = iter->stack[--iter->depth].lst; - i = iter->stack[iter->depth].i; - - if (i >= p->num_children && i >= 0) - i = p->num_children - 1; - } while (i < 0); - - assert(iter->stack[iter->depth].lst == p); - iter->stack[iter->depth++].i = i-1; - - while (!p->leaf) { - p = (PyBList *) p->children[i]; - Py_INCREF(p); - i = p->num_children-1; - iter->stack[iter->depth].lst = p; - iter->stack[iter->depth++].i = i-1; - } - - iter->leaf = iter->stack[iter->depth-1].lst; - iter->i = iter->stack[iter->depth-1].i; - - return p->children[i]; -} - -BLIST_PYAPI(PyObject *) -py_blist_reversed(PyBList *seq) -{ - blistiterobject *it; - - invariants(seq, VALID_USER); - - DANGER_BEGIN; - it = PyObject_GC_New(blistiterobject, - &PyBListReverseIter_Type); - DANGER_END; - if (it == NULL) - return _ob(NULL); - - if (seq->leaf) { - /* Speed up common case */ - it->iter.leaf = seq; - it->iter.i = seq->n-1; - it->iter.depth = 1; - Py_INCREF(seq); - } else - riter_init(&it->iter, seq); - - PyObject_GC_Track(it); - return _ob((PyObject *) it); -} - -static PyObject *blistiter_prev(PyObject *oit) -{ - blistiterobject *it = (blistiterobject *) oit; - PyObject *obj; - - /* Speed up common case */ - PyBList *p; - p = it->iter.leaf; - if (p == NULL) - return NULL; - - if (it->iter.i >= p->num_children && it->iter.i >= 0) - it->iter.i = p->num_children - 1; - - if (p->leaf && it->iter.i >= 0) { - obj = p->children[it->iter.i--]; - Py_INCREF(obj); - return obj; - } - - obj = iter_prev(&it->iter); - if (obj != NULL) - Py_INCREF(obj); - - _decref_flush(); - return obj; -} - -BLIST_PYAPI(PyObject *) -blistriter_len(blistiterobject *it) -{ - iter_t *iter = &it->iter; - int depth; - Py_ssize_t total = 0; - - total += iter->i + 1; - - for (depth = iter->depth-2; depth >= 0; depth--) { - point_t point = iter->stack[depth]; - int j; - if (point.lst->leaf) continue; - for (j = 0; j <= point.i; j++) { - PyBList *child = (PyBList *) point.lst->children[j]; - total += child->n; - } - } - if (iter->depth > 1 && iter->stack[0].lst->leaf) { - int extra = iter->stack[0].i + 1; - if (extra > 0) total += extra; - } - return PyInt_FromLong(total); -} - -static PyMethodDef blistriter_methods[] = { - {"__length_hint__", (PyCFunction)blistriter_len, METH_NOARGS, length_hint_doc}, - {NULL, NULL} /* sentinel */ -}; - -PyTypeObject PyBListReverseIter_Type = { - PyVarObject_HEAD_INIT(NULL, 0) - "blistreverseiterator", /* tp_name */ - sizeof(blistiterobject), /* tp_basicsize */ - 0, /* tp_itemsize */ - /* methods */ - blistiter_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - PyObject_GenericGetAttr, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */ - 0, /* tp_doc */ - blistiter_traverse, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - PyObject_SelfIter, /* tp_iter */ - blistiter_prev, /* tp_iternext */ - blistriter_methods, /* tp_methods */ - 0, /* tp_members */ -}; - -/************************************************************************ - * A forest is an array of BList tree structures, which may be of - * different heights. It's a temporary utility structure for certain - * operations. Specifically, it allows us to compose or decompose a - * BList in O(n) time. - * - * The BList trees in the forest are stored in position order, left-to-right. - * - */ - -typedef struct Forest -{ - Py_ssize_t num_leafs; - Py_ssize_t num_trees; - Py_ssize_t max_trees; - PyBList **list; -} Forest; - -#if 0 -/* Remove the right-most element. If it's a leaf, return it. - * Otherwise, add all of its childrent to the forest *in reverse - * order* and try again. Assuming only one BList was added to the - * forest, the effect is to return all of the leafs one-at-a-time - * left-to-right. */ -BLIST_LOCAL(PyBList *) -forest_get_leaf(Forest *forest) -{ - PyBList *node = forest->list[--forest->num_trees]; - PyBList **list; - while (!node->leaf) { - int i; - while (forest->num_trees + node->num_children - > forest->max_trees) { - list = forest->list; - forest->max_trees *= 2; - PyMem_Resize(list, PyBList*,forest->max_trees); - if (list == NULL) { - PyErr_NoMemory(); - return NULL; - } - forest->list = list; - } - - for (i = node->num_children - 1; i >= 0; i--) - forest->list[forest->num_trees++] - = blist_PREPARE_WRITE(node, i); - - node->num_children = 0; - SAFE_DECREF(node); - node = forest->list[--forest->num_trees]; - } - - return node; -} -#endif - -#define MAX_FREE_FORESTS 20 -static PyBList **forest_saved[MAX_FREE_FORESTS]; -static unsigned forest_max_trees[MAX_FREE_FORESTS]; -static unsigned num_free_forests = 0; - -BLIST_LOCAL(Forest *) -forest_init(Forest *forest) -{ - forest->num_trees = 0; - forest->num_leafs = 0; - if (num_free_forests) { - forest->list = forest_saved[--num_free_forests]; - forest->max_trees = forest_max_trees[num_free_forests]; - } else { - forest->max_trees = LIMIT; /* enough for O(LIMIT**2) items */ - forest->list = PyMem_New(PyBList *, forest->max_trees); - if (forest->list == NULL) - return (Forest *) PyErr_NoMemory(); - } - return forest; -} - -#if 0 -BLIST_LOCAL(Forest *) -forest_new(void) -{ - Forest *forest = PyMem_New(Forest, 1); - Forest *rv; - if (forest == NULL) - return (Forest *) PyErr_NoMemory(); - rv = forest_init(forest); - if (rv == NULL) - PyMem_Free(forest); - return rv; -} - -BLIST_LOCAL(void) -forest_grow(Forest *forest, Py_ssize_t new_max) -{ - if (forest->max_trees > new_max) return; - /* XXX Check return value */ - PyMem_Resize(forest->list, PyBList *, new_max); - forest->max_trees = new_max; -} -#endif - -/* Append a tree to the forest. Steals the reference to "leaf" */ -BLIST_LOCAL(int) -forest_append(Forest *forest, PyBList *leaf) -{ - Py_ssize_t power = LIMIT; - - if (!leaf->num_children) { /* Don't bother adding empty leaf nodes */ - SAFE_DECREF(leaf); - return 0; - } - - leaf->n = leaf->num_children; - - if (forest->num_trees == forest->max_trees) { - PyBList **list = forest->list; - - forest->max_trees <<= 1; - PyMem_Resize(list, PyBList *, forest->max_trees); - if (list == NULL) { - PyErr_NoMemory(); - return -1; - } - forest->list = list; - } - - forest->list[forest->num_trees++] = leaf; - forest->num_leafs++; - - while (forest->num_leafs % power == 0) { - struct PyBList *parent = blist_new(); - int x; - - if (parent == NULL) { - PyErr_NoMemory(); - return -1; - } - parent->leaf = 0; - memcpy(parent->children, - &forest->list[forest->num_trees - LIMIT], - sizeof (PyBList *) * LIMIT); - parent->num_children = LIMIT; - forest->num_trees -= LIMIT; - x = blist_underflow(parent, LIMIT - 1); - assert(!x); (void) x; - - forest->list[forest->num_trees++] = parent; - power *= LIMIT; - } - - return 0; -} - -BLIST_LOCAL(int) -blist_init_from_child_array(PyBList **children, int num_children) -{ - int i, j, k; - - assert(num_children); - if (num_children == 1) - return 1; - - for (k = i = 0; i < num_children; i += LIMIT) { - PyBList *parent = blist_new(); - int stop = (LIMIT < (num_children - i)) - ? LIMIT : (num_children - i); - if (parent == NULL) - return -1; - parent->leaf = 0; - for (j = 0; j < stop; j++) { - parent->children[j] = (PyObject *) children[i+j]; - assert(children[i+j]->num_children >= HALF); - children[i+j] = NULL; - } - parent->num_children = j; - blist_adjust_n(parent); - children[k++] = parent; - } - - if (k <= 1) - return k; - - if (children[k-1]->num_children < HALF) { - PyBList *left = children[k-2]; - PyBList *right = children[k-1]; - int needed = HALF - right->num_children; - - shift_right(right, 0, needed); - copy(right, 0, left, LIMIT-needed, needed); - left->num_children -= needed; - right->num_children += needed; - blist_adjust_n(left); - blist_adjust_n(right); - } - - return blist_init_from_child_array(children, k); -} - -#if 0 -/* Like forest_append(), but handles the case where the previously - * added leaf is in an underflow state. */ -BLIST_LOCAL(int) -forest_append_safe(Forest *forest, PyBList *leaf) -{ - PyBList *last; - - if (forest->num_trees == 0) - goto append; - - last = forest->list[forest->num_trees-1]; - - if (!last->leaf || last->num_children >= HALF) - goto append; - - if (last->num_children + leaf->num_children <= LIMIT) { - copy(last, last->num_children, leaf, 0, leaf->num_children); - last->num_children += leaf->num_children; - last->n += leaf->num_children; - leaf->num_children = 0; - } else { - int needed = HALF - last->num_children; - - copy(last, last->num_children, leaf, 0, needed); - last->num_children += needed; - last->n += needed; - shift_left(leaf, needed, needed); - leaf->num_children -= needed; - } - - append: - return forest_append(forest, leaf); -} -#endif - -BLIST_LOCAL(void) -forest_uninit(Forest *forest) -{ - Py_ssize_t i; - for (i = 0; i < forest->num_trees; i++) - decref_later((PyObject *) forest->list[i]); - if (num_free_forests < MAX_FREE_FORESTS && forest->max_trees == LIMIT){ - forest_saved[num_free_forests] = forest->list; - forest_max_trees[num_free_forests++] = forest->max_trees; - } else - PyMem_Free(forest->list); -} - -BLIST_LOCAL(void) -forest_uninit_now(Forest *forest) -{ - Py_ssize_t i; - for (i = 0; i < forest->num_trees; i++) - Py_DECREF((PyObject *) forest->list[i]); - if (num_free_forests < MAX_FREE_FORESTS && forest->max_trees == LIMIT){ - forest_saved[num_free_forests] = forest->list; - forest_max_trees[num_free_forests++] = forest->max_trees; - } else - PyMem_Free(forest->list); -} - -#if 0 -BLIST_LOCAL(void) -forest_delete(Forest *forest) -{ - forest_uninit(forest); - PyMem_Free(forest); -} -#endif - -/* Combine the forest into a final BList and delete the forest. - * - * forest_finish() assumes that only leaf nodes were passed to forest_append() - */ -static PyBList *forest_finish(Forest *forest) -{ - PyBList *out_tree = NULL; /* The final BList we are building */ - int out_height = 0; /* It's height */ - int group_height = 1; /* height of the next group from forest */ - - while(forest->num_trees) { - int n = forest->num_leafs % LIMIT; - PyBList *group; - int adj; - - forest->num_leafs /= LIMIT; - group_height++; - - if (!n) continue; /* No nodes at this height */ - - /* Merge nodes of the same height into 1 node, and - * merge it into our output BList. - */ - group = blist_new(); - if (group == NULL) { - forest_uninit(forest); - xdecref_later((PyObject *) out_tree); - return NULL; - } - group->leaf = 0; - memcpy(group->children, - &forest->list[forest->num_trees - n], - sizeof (PyBList *) * n); - group->num_children = n; - forest->num_trees -= n; - adj = blist_underflow(group, n - 1); - if (out_tree == NULL) { - out_tree = group; - out_height = group_height - adj; - } else { - out_tree = blist_concat_roots(group, group_height- adj, - out_tree, out_height, - &out_height); - } - } - - forest_uninit(forest); - - return out_tree; -} - -/************************************************************************ - * Functions that rely on forests. - */ - -/* Initialize an empty BList from an array of PyObjects in O(n) time */ -BLIST_LOCAL(int) -blist_init_from_array(PyBList *self, PyObject **restrict src, Py_ssize_t n) -{ - int i; - PyBList *final, *cur; - PyObject **dst; - PyObject **stop = &src[n]; - PyObject **next; - Forest forest; - int gc_previous; - - invariants(self, VALID_ROOT|VALID_RW); - - if (n <= LIMIT) { - dst = self->children; - while (src < stop) { - Py_INCREF(*src); - *dst++ = *src++; - } - self->num_children = n; - self->n = n; - return _int(0); - } - - if (forest_init(&forest) == NULL) - return _int(-1); - - gc_previous = gc_pause(); - - cur = blist_new(); - if (cur == NULL) - goto error2; - dst = cur->children; - - while (src < stop) { - next = &src[LIMIT]; - if (next > stop) next = stop; - while (src < next) { - Py_INCREF(*src); - *dst++ = *src++; - } - if (src == stop) break; - - cur->num_children = LIMIT; - if (forest_append(&forest, cur) < 0) - goto error; - cur = blist_new(); - if (cur == NULL) - goto error2; - dst = cur->children; - } - - i = dst - cur->children; - - if (i) { - cur->num_children = i; - if (forest_append(&forest, cur) < 0) { - error: - Py_DECREF(cur); - error2: - forest_uninit(&forest); - gc_unpause(gc_previous); - return _int(-1); - } - } else { - Py_DECREF(cur); - } - - final = forest_finish(&forest); - blist_become_and_consume(self, final); - - ext_reindex_set_all((PyBListRoot *) self); - SAFE_DECREF(final); - - gc_unpause(gc_previous); - - return _int(0); -} - -/* Initialize an empty BList from a Python sequence in O(n) time */ -BLIST_LOCAL(int) -blist_init_from_seq(PyBList *self, PyObject *b) -{ - PyObject *it; - PyObject *(*iternext)(PyObject *); - PyBList *cur, *final; - Forest forest; - - invariants(self, VALID_ROOT | VALID_RW); - - if (PyBList_Check(b)) { - /* We can copy other BLists in O(1) time :-) */ - blist_become(self, (PyBList *) b); - ext_mark(self, 0, DIRTY); - ext_mark_set_dirty_all((PyBList *) b); - return _int(0); - } - - if (PyTuple_CheckExact(b)) { - PyTupleObject *t = (PyTupleObject *) b; - return _int(blist_init_from_array(self, t->ob_item, - PyTuple_GET_SIZE(t))); - } -#ifndef Py_BUILD_CORE - if (PyList_CheckExact(b)) { - PyListObject *l = (PyListObject *) b; - return _int(blist_init_from_array(self, l->ob_item, - PyList_GET_SIZE(l))); - } -#endif - - DANGER_BEGIN; - it = PyObject_GetIter(b); - DANGER_END; - if (it == NULL) - return _int(-1); - iternext = *Py_TYPE(it)->tp_iternext; - - /* Try common case of len(sequence) <= LIMIT */ - for (self->num_children = 0; self->num_children < LIMIT; - self->num_children++) { - PyObject *item; - - DANGER_BEGIN; - item = iternext(it); - DANGER_END; - - if (item == NULL) { - self->n = self->num_children; - if (PyErr_Occurred()) { - if (PyErr_ExceptionMatches(PyExc_StopIteration)) - PyErr_Clear(); - else - goto error; - } - goto done; - } - - self->children[self->num_children] = item; - } - - /* No such luck, build bottom-up instead. The sequence data - * so far goes in a leaf node. */ - - cur = blist_new(); - if (cur == NULL) - goto error; - blist_become_and_consume(cur, self); - - if (forest_init(&forest) == NULL) { - decref_later(it); - decref_later((PyObject *) cur); - return _int(-1); - } - - if (0 > forest_append(&forest, cur)) - goto error2; - - cur = blist_new(); - if (cur == NULL) - goto error2; - - while (1) { - PyObject *item; - DANGER_BEGIN; - item = iternext(it); - DANGER_END; - if (item == NULL) { - if (PyErr_Occurred()) { - if (PyErr_ExceptionMatches(PyExc_StopIteration)) - PyErr_Clear(); - else - goto error2; - } - break; - } - - if (cur->num_children == LIMIT) { - if (forest_append(&forest, cur) < 0) goto error2; - cur = blist_new(); - if (cur == NULL) - goto error2; - } - - cur->children[cur->num_children++] = item; - } - - if (cur->num_children) { - if (forest_append(&forest, cur) < 0) goto error2; - cur->n = cur->num_children; - } else { - SAFE_DECREF(cur); - } - - final = forest_finish(&forest); - blist_become_and_consume(self, final); - SAFE_DECREF(final); - - done: - ext_reindex_set_all((PyBListRoot*)self); - decref_later(it); - return _int(0); - - error2: - DANGER_BEGIN; - Py_XDECREF((PyObject *) cur); - forest_uninit_now(&forest); - DANGER_END; - error: - DANGER_BEGIN; - Py_DECREF(it); - DANGER_END; - blist_CLEAR(self); - return _int(-1); -} - -/* Utility function for performing repr() */ -BLIST_LOCAL(int) -blist_repr_r(PyBList *self) -{ - int i; - PyObject *s; - - invariants(self, VALID_RW|VALID_PARENT); - - if (self->leaf) { - for (i = 0; i < self->num_children; i++) { - if (Py_EnterRecursiveCall(" while getting the repr of a list")) - return _int(-1); - DANGER_BEGIN; - s = PyObject_Repr(self->children[i]); - DANGER_END; - Py_LeaveRecursiveCall(); - if (s == NULL) - return _int(-1); - Py_DECREF(self->children[i]); - self->children[i] = s; - } - } else { - for (i = 0; i < self->num_children; i++) { - PyBList *child = blist_PREPARE_WRITE(self, i); - int status = blist_repr_r(child); - if (status < 0) - return _int(status); - } - } - - return _int(0); -} - -PyObject * -ext_make_clean_set(PyBListRoot *root, Py_ssize_t i, PyObject *v) -{ - PyBList *p = (PyBList *) root; - PyBList *next; - int k; - Py_ssize_t so_far, offset = 0; - PyObject *old_value; - int did_mark = 0; - - while (!p->leaf) { - blist_locate(p, i, (PyObject **) &next, &k, &so_far); - if (Py_REFCNT(next) <= 1) - p = next; - else { - p = blist_PREPARE_WRITE(p, k); - if (!did_mark) { - ext_mark((PyBList *) root, offset, DIRTY); - did_mark = 1; - } - } - assert(i >= so_far); - i -= so_far; - offset += so_far; - } - - if (!root->leaf) - ext_mark_clean(root, offset, p, 1); - - old_value = p->children[i]; - p->children[i] = v; - return old_value; -} - -PyObject * -blist_ass_item_return_slow(PyBListRoot *root, Py_ssize_t i, PyObject *v) -{ - Py_ssize_t dirty_offset, ioffset; - PyObject *rv; - assert(i >= 0); - assert(i < root->n); - invariants(root, VALID_RW); - ioffset = i / INDEX_FACTOR; - - if (root->leaf || ext_is_dirty(root, i, &dirty_offset) - || !GET_BIT(root->setclean_list, ioffset)) { - rv = ext_make_clean_set(root, i, v); - } else { - Py_ssize_t offset = root->offset_list[ioffset]; - PyBList *p = root->index_list[ioffset]; - assert(i >= offset); - assert(p); - assert(p->leaf); - if (i < offset + p->n) { - good: - /* If we're here, Py_REFCNT(p) == 1, most likely. - * However, we can't assert() it since there are two - * exceptions: - * 1) The user may have acquired a ref via gc, or - * 2) an iterator may have a reference - * - * If it's an iterator, we can go ahead and make the - * change anyway since we're not changing the length - * of the list. - */ - rv = p->children[i - offset]; - p->children[i - offset] = v; - if (dirty_offset >= 0) - ext_make_clean(root, dirty_offset); - } else if (ext_is_dirty(root,i + INDEX_FACTOR,&dirty_offset) - || !GET_BIT(root->setclean_list, ioffset+1)) { - rv = ext_make_clean_set(root, i, v); - } else { - ioffset++; - assert(ioffset < root->index_allocated); - offset = root->offset_list[ioffset]; - p = root->index_list[ioffset]; - assert(p); - assert(p->leaf); - assert(i < offset + p->n); - - goto good; - } - } - - return _ob(rv); -} - -BLIST_LOCAL_INLINE(PyObject *) -blist_ass_item_return(PyBList *self, Py_ssize_t i, PyObject *v) -{ - Py_INCREF(v); - if (self->leaf) { - PyObject *rv = self->children[i]; - self->children[i] = v; - return rv; - } - - return blist_ass_item_return2((PyBListRoot*)self, i, v); -} - -#ifndef Py_BUILD_CORE -BLIST_LOCAL(PyObject *) -blist_richcompare_list(PyBList *v, PyListObject *w, int op) -{ - int cmp; - PyObject *ret, *item; - Py_ssize_t i; - int v_stopped = 0; - int w_stopped = 0; - fast_compare_data_t fast_cmp_type = no_fast_eq; - - invariants(v, VALID_RW); - - if (v->n != PyList_GET_SIZE(w) && (op == Py_EQ || op == Py_NE)) { - /* Shortcut: if the lengths differ, the lists differ */ - PyObject *res; - if (op == Py_EQ) { - false: - res = Py_False; - } else { - true: - res = Py_True; - } - Py_INCREF(res); - return _ob(res); - } - - /* Search for the first index where items are different */ - i = 0; - ITER(v, item, { - if (i >= PyList_GET_SIZE(w)) { - w_stopped = 1; - break; - } - if (i == 0) - fast_cmp_type = check_fast_cmp_type(item, Py_EQ); - - cmp = fast_eq(item, w->ob_item[i], fast_cmp_type); - - if (cmp < 0) { - ITER_CLEANUP(); - return _ob(NULL); - } else if (!cmp) { - if (op == Py_EQ) { ITER_CLEANUP(); goto false; } - if (op == Py_NE) { ITER_CLEANUP(); goto true; } - - /* Last RichComparebool may have modified the list */ - if (i >= PyList_GET_SIZE(w)) { - w_stopped = 1; - break; - } - - DANGER_BEGIN; - ret = PyObject_RichCompare(item, w->ob_item[i], op); - DANGER_END; - ITER_CLEANUP(); - return ret; - } - i++; - }); - - if (!w_stopped) { - v_stopped = 1; - if (i >= PyList_GET_SIZE(w)) - w_stopped = 1; - } - - /* No more items to compare -- compare sizes */ - switch (op) { - case Py_LT: cmp = v_stopped && !w_stopped; break; - case Py_LE: cmp = v_stopped; break; - case Py_EQ: cmp = v_stopped == w_stopped; break; - case Py_NE: cmp = v_stopped != w_stopped; break; - case Py_GT: cmp = !v_stopped && w_stopped; break; - case Py_GE: cmp = w_stopped; break; - default: - /* cannot happen */ - PyErr_BadInternalCall(); - return _ob(NULL); - } - - if (cmp) goto true; - else goto false; -} -#endif - -BLIST_LOCAL(PyObject *) -blist_richcompare_item(int c, int op, PyObject *item1, PyObject *item2) -{ - PyObject *ret; - - if (c < 0) - return NULL; - if (!c) { - if (op == Py_EQ) - Py_RETURN_FALSE; - if (op == Py_NE) - Py_RETURN_TRUE; - DANGER_BEGIN; - ret = PyObject_RichCompare(item1, item2, op); - DANGER_END; - return ret; - } - - /* Impossible to get here */ - assert(0); - return NULL; -} - -static PyObject *blist_richcompare_len(PyBList *v, PyBList *w, int op) -{ - /* No more items to compare -- compare sizes */ - switch (op) { - case Py_LT: if (v->n < w->n) Py_RETURN_TRUE; else Py_RETURN_FALSE; - case Py_LE: if (v->n <= w->n) Py_RETURN_TRUE; else Py_RETURN_FALSE; - case Py_EQ: if (v->n == w->n) Py_RETURN_TRUE; else Py_RETURN_FALSE; - case Py_NE: if (v->n != w->n) Py_RETURN_TRUE; else Py_RETURN_FALSE; - case Py_GT: if (v->n > w->n) Py_RETURN_TRUE; else Py_RETURN_FALSE; - case Py_GE: if (v->n >= w->n) Py_RETURN_TRUE; else Py_RETURN_FALSE; - default: return NULL; /* cannot happen */ - } -} - -BLIST_LOCAL(PyObject *) -blist_richcompare_slow(PyBList *v, PyBList *w, int op) -{ - /* Search for the first index where items are different */ - PyObject *item1, *item2; - iter_t it1, it2; - int c; - PyBList *leaf1, *leaf2; - fast_compare_data_t fast_cmp_type; - - iter_init(&it1, v); - iter_init(&it2, w); - - leaf1 = it1.leaf; - leaf2 = it2.leaf; - fast_cmp_type = check_fast_cmp_type(it1.leaf->children[0], Py_EQ); - do { - if (it1.i < leaf1->num_children) { - item1 = leaf1->children[it1.i++]; - } else { - item1 = iter_next(&it1); - leaf1 = it1.leaf; - if (item1 == NULL) { - compare_len: - iter_cleanup(&it1); - iter_cleanup(&it2); - return blist_richcompare_len(v, w, op); - } - } - - if (it2.i < leaf2->num_children) { - item2 = leaf2->children[it2.i++]; - } else { - item2 = iter_next(&it2); - leaf2 = it2.leaf; - if (item2 == NULL) - goto compare_len; - } - - c = fast_eq(item1, item2, fast_cmp_type); - } while (c >= 1); - - iter_cleanup(&it1); - iter_cleanup(&it2); - return blist_richcompare_item(c, op, item1, item2); -} - -BLIST_LOCAL(PyObject *) -blist_richcompare_blist(PyBList *v, PyBList *w, int op) -{ - int i, c; - fast_compare_data_t fast_cmp_type; - - if (v->n != w->n) { - /* Shortcut: if the lengths differ, the lists differ */ - if (op == Py_EQ) { - Py_RETURN_FALSE; - } else if (op == Py_NE) { - Py_RETURN_TRUE; - } - - if (!v->n) { - /* Shortcut: first list empty, second un-empty. */ - switch (op) { - case Py_LT: case Py_LE: Py_RETURN_TRUE; - case Py_GT: case Py_GE: Py_RETURN_FALSE; - default: return NULL; /* cannot happen */ - } - } - } else if (!v->n) { - /* Shortcut: two empty lists */ - switch (op) { - case Py_NE: case Py_GT: case Py_LT: Py_RETURN_FALSE; - case Py_LE: case Py_EQ: case Py_GE: Py_RETURN_TRUE; - default: return NULL; /* cannot happen */ - } - } - - if (!v->leaf || !w->leaf) - return blist_richcompare_slow(v, w, op); - - /* Due to the shortcuts above, we know that v->n > 0 */ - fast_cmp_type = check_fast_cmp_type(v->children[0], Py_EQ); - - for (i = 0; i < v->num_children && i < w->num_children; i++) { - c = fast_eq(v->children[i], w->children[i], fast_cmp_type); - if (c < 1) - return blist_richcompare_item(c, op, v->children[i], - w->children[i]); - } - return blist_richcompare_len(v, w, op); - -} - -/* Reverse a slice of a list in place, from lo up to (exclusive) hi. */ -BLIST_LOCAL_INLINE(void) -reverse_slice(register PyObject **restrict lo, register PyObject **restrict hi) -{ - register PyObject *t; - assert(lo && hi); - - /* slice of length 0 */ - if (hi == lo) return; - - /* Use Duff's Device - * http://en.wikipedia.org/wiki/Duff%27s_device - */ - - --hi; - - switch ((hi - lo) & 31) { - case 31: do { t = *lo; *lo++ = *hi; *hi-- = t; - case 30: case 29: t = *lo; *lo++ = *hi; *hi-- = t; - case 28: case 27: t = *lo; *lo++ = *hi; *hi-- = t; - case 26: case 25: t = *lo; *lo++ = *hi; *hi-- = t; - case 24: case 23: t = *lo; *lo++ = *hi; *hi-- = t; - case 22: case 21: t = *lo; *lo++ = *hi; *hi-- = t; - case 20: case 19: t = *lo; *lo++ = *hi; *hi-- = t; - case 18: case 17: t = *lo; *lo++ = *hi; *hi-- = t; - case 16: case 15: t = *lo; *lo++ = *hi; *hi-- = t; - case 14: case 13: t = *lo; *lo++ = *hi; *hi-- = t; - case 12: case 11: t = *lo; *lo++ = *hi; *hi-- = t; - case 10: case 9: t = *lo; *lo++ = *hi; *hi-- = t; - case 8: case 7: t = *lo; *lo++ = *hi; *hi-- = t; - case 6: case 5: t = *lo; *lo++ = *hi; *hi-- = t; - case 4: case 3: t = *lo; *lo++ = *hi; *hi-- = t; - case 2: case 1: t = *lo; *lo++ = *hi; *hi-- = t; - case 0: ; - } while (lo < hi); - } -} - -/* self *= 2 */ -static void blist_double(PyBList *self) -{ - if (self->num_children > HALF) { - blist_extend_blist(self, self); - return; - } - - copyref(self, self->num_children, self, 0, self->num_children); - self->num_children *= 2; - self->n *= 2; -} - -BLIST_LOCAL(int) -blist_extend(PyBList *self, PyObject *other) -{ - int err; - PyBList *bother = NULL; - - invariants(self, VALID_PARENT|VALID_RW); - - if (PyBList_Check(other)) { - err = blist_extend_blist(self, (PyBList *) other); - goto done; - } - - bother = blist_root_new(); - err = blist_init_from_seq(bother, other); - if (err < 0) - goto done; - err = blist_extend_blist(self, bother); - ext_mark(self, 0, DIRTY); - - done: - SAFE_XDECREF(bother); - return _int(err); -} - -BLIST_LOCAL(PyObject *) -blist_repeat(PyBList *self, Py_ssize_t n) -{ - Py_ssize_t mask; - PyBList *power = NULL, *rv, *remainder = NULL; - Py_ssize_t remainder_n = 0; - - invariants(self, VALID_PARENT); - - if (n <= 0 || self->n == 0) - return _ob((PyObject *) blist_root_new()); - - if ((self->n * n) / n != self->n) - return _ob(PyErr_NoMemory()); - - rv = blist_root_new(); - if (rv == NULL) - return _ob(NULL); - - if (n == 1) { - blist_become(rv, self); - ext_mark(rv, 0, DIRTY); - return _ob((PyObject *) rv); - } - - if (self->num_children > HALF) - blist_become(rv, self); - else { - Py_ssize_t fit, fitn, so_far; - - rv->leaf = self->leaf; - fit = LIMIT / self->num_children; - if (fit > n) fit = n; - fitn = fit * self->num_children; - xcopyref(rv, 0, self, 0, self->num_children); - so_far = self->num_children; - while (so_far*2 < fitn) { - xcopyref(rv, so_far, rv, 0, so_far); - so_far *= 2; - } - xcopyref(rv, so_far, rv, 0, (fitn - so_far)); - - rv->num_children = fitn; - rv->n = self->n * fit; - check_invariants(rv); - - if (fit == n) { - ext_mark(rv, 0, DIRTY); - return _ob((PyObject *) rv); - } - - remainder_n = n % fit; - n /= fit; - - if (remainder_n) { - remainder = blist_root_new(); - if (remainder == NULL) - goto error; - remainder->n = self->n * remainder_n; - remainder_n *= self->num_children; - remainder->leaf = self->leaf; - xcopyref(remainder, 0, rv, 0, remainder_n); - remainder->num_children = remainder_n; - check_invariants(remainder); - } - } - - if (n == 0) - goto do_remainder; - - power = rv; - rv = blist_root_new(); - if (rv == NULL) { - SAFE_XDECREF(remainder); - error: - SAFE_DECREF(power); - return _ob(NULL); - } - - if (n & 1) - blist_become(rv, power); - - for (mask = 2; mask <= n; mask <<= 1) { - blist_double(power); - if (mask & n) - blist_extend_blist(rv, power); - } - SAFE_DECREF(power); - - do_remainder: - - if (remainder) { - blist_extend_blist(rv, remainder); - SAFE_DECREF(remainder); - } - - check_invariants(rv); - ext_mark(rv, 0, DIRTY); - return _ob((PyObject *) rv); -} - -BLIST_LOCAL(void) -linearize_rw_r(PyBList *self) -{ - int i; - - invariants(self, VALID_PARENT|VALID_RW); - - for (i = 0; i < self->num_children; i++) { - PyBList *restrict p = blist_PREPARE_WRITE(self, i); - if (!p->leaf) - linearize_rw_r(p); - } - - _void(); - return; -} - -BLIST_LOCAL(void) -linearize_rw(PyBListRoot *self) -{ - int i; - - if (self->leaf || self->dirty_root == CLEAN_RW) - return; - - if (self->dirty_root == CLEAN) { - Py_ssize_t n = SETCLEAN_LEN(INDEX_LENGTH(self)); - for (i = 0; i < n; i++) - if (self->setclean_list[i] != (unsigned) -1) - goto slow; - memset(self->setclean_list, 255, - SETCLEAN_LEN(INDEX_LENGTH(self)) * sizeof(unsigned)); - self->dirty_root = CLEAN_RW; - return; - } - -slow: - linearize_rw_r((PyBList *)self); - ext_reindex_set_all(self); -} - -BLIST_LOCAL(void) -blist_reverse(PyBListRoot *restrict self) -{ - int idx, ridx; - PyBList *restrict left, *restrict right; - register PyObject **restrict slice1; - register PyObject **restrict slice2; - int n1, n2; - - invariants(self, VALID_ROOT|VALID_RW); - - if (self->leaf) { - reverse_slice(self->children, - &self->children[self->num_children]); - _void(); - return; - } - - linearize_rw(self); - - idx = 0; - left = self->index_list[idx]; - if (left == self->index_list[idx+1]) - idx++; - slice1 = &left->children[0]; - n1 = left->num_children; - - ridx = INDEX_LENGTH(self)-1; - right = self->index_list[ridx]; - if (right == self->index_list[ridx-1]) - ridx--; - slice2 = &right->children[right->num_children-1]; - n2 = right->num_children; - - while (idx < ridx) { - int n = (n1 < n2) ? n1 : n2; - int count = (n+31) / 32; - switch (n & 31) { - register PyObject *t; - case 0: do { t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 31: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 30: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 29: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 28: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 27: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 26: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 25: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 24: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 23: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 22: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 21: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 20: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 19: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 18: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 17: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 16: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 15: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 14: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 13: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 12: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 11: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 10: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 9: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 8: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 7: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 6: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 5: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 4: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 3: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 2: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - case 1: t = *slice1; *slice1++ = *slice2; *slice2-- = t; - } while (--count > 0); - } - - n1 -= n; - if (!n1) { - idx++; - left = self->index_list[idx]; - if (left == self->index_list[idx+1]) - idx++; - slice1 = &left->children[0]; - n1 = left->num_children; - } - - n2 -= n; - if (!n2) { - ridx--; - right = self->index_list[ridx]; - if (right == self->index_list[ridx-1]) - ridx--; - slice2 = &right->children[right->num_children-1]; - n2 = right->num_children; - } - } - - if (left == right && slice1 < slice2) - reverse_slice(slice1, slice2 + 1); - - _void(); - return; -} - -BLIST_LOCAL(int) -blist_append(PyBList *self, PyObject *v) -{ - PyBList *overflow; - PyBList *p; - - invariants(self, VALID_ROOT|VALID_RW); - - if (self->n == PY_SSIZE_T_MAX) { - PyErr_SetString(PyExc_OverflowError, - "cannot add more objects to list"); - return _int(-1); - } - - for (p = self; !p->leaf; p= (PyBList*)p->children[p->num_children-1]) { - if (p != self && Py_REFCNT(p) > 1) - goto cleanup_and_slow; - p->n++; - } - - if (p->num_children == LIMIT || (p != self && Py_REFCNT(p) > 1)) { - PyBList *p2; - cleanup_and_slow: - for (p2 = self; p2 != p; - p2 = (PyBList*)p2->children[p2->num_children-1]) - p2->n--; - goto slow; - } - - p->children[p->num_children++] = v; - p->n++; - Py_INCREF(v); - - if ((self->n-1) % INDEX_FACTOR == 0) - ext_mark(self, 0, DIRTY); -#ifdef Py_DEBUG - else - ((PyBListRoot*)self)->last_n++; -#endif - check_invariants(self); - return _int(0); - - slow: - overflow = ins1(self, self->n, v); - if (overflow) - blist_overflow_root(self, overflow); - ext_mark(self, 0, DIRTY); - - return _int(0); -} - -/************************************************************************ - * Sorting code - * - * Bits and pieces swiped from Python's listobject.c - * - * Invariant: In case of an error, any sort function returns the list - * to a valid state before returning. "Valid" means that each item - * originally in the list is still in the list. No removals, no - * additions, and no changes to the reference counters. - * - ************************************************************************/ - -static void -unwrap_leaf_array(PyBList **leafs, int leafs_n, int n, - sortwrapperobject *array) -{ - int i, j, k = 0; - - for (i = 0; i < leafs_n; i++) { - PyBList *leaf = leafs[i]; - if (leafs_n > 1 && !_PyObject_GC_IS_TRACKED(leafs[i])) - PyObject_GC_Track(leafs[i]); - for (j = 0; j < leaf->num_children && k < n; j++, k++) { - sortwrapperobject *wrapper; - wrapper = (sortwrapperobject *) leaf->children[j]; - leaf->children[j] = wrapper->value; - DANGER_BEGIN; - Py_DECREF(wrapper->key); - DANGER_END; - } - } -} - -#define KEY_ALL_DOUBLE 1 -#define KEY_ALL_LONG 2 - -static int -wrap_leaf_array(sortwrapperobject *restrict array, - PyBList **leafs, int leafs_n, int n, - PyObject *restrict keyfunc, - int *restrict pkey_flags) -{ - int i, j, k; - int key_flags; - - key_flags = KEY_ALL_DOUBLE | KEY_ALL_LONG; - - for (k = i = 0; i < leafs_n; i++) { - PyBList *restrict leaf = leafs[i]; - if (leafs_n > 1) - PyObject_GC_UnTrack(leaf); - - for (j = 0; j < leaf->num_children; j++) { - sortwrapperobject *restrict pair = &array[k]; - PyObject *restrict key, *value = leaf->children[j]; - PyTypeObject *type; - if (keyfunc == NULL) { - key = value; - Py_INCREF(key); - } else { - DANGER_BEGIN; - key = PyObject_CallFunctionObjArgs( - keyfunc, value, NULL); - DANGER_END; - if (key == NULL) { - unwrap_leaf_array(leafs, leafs_n, k, array); - return -1; - } - } - type = key->ob_type; -#ifdef BLIST_FLOAT_RADIX_SORT - if (type == &PyFloat_Type) { - double d = PyFloat_AS_DOUBLE(key); - PY_UINT64_T di, mask; - memcpy(&di, &d, 8); - mask = (-(PY_INT64_T) (di >> 63)) - | (1ull << 63ull); - pair->fkey.k_uint64 = di ^ mask; - key_flags &= KEY_ALL_DOUBLE; - } else -#endif -#if PY_MAJOR_VERSION < 3 - if (type == &PyInt_Type) { - long i = PyInt_AS_LONG(key); - unsigned long u = i; - const unsigned long mask = 1ul << (sizeof(long)*8-1); - pair->fkey.k_ulong = u ^ mask; - key_flags &= KEY_ALL_LONG; - } else -#endif - if (type == &PyLong_Type) { - unsigned long x = PyLong_AsLong(key); - if (x == (unsigned long) (long) -1 - && PyErr_Occurred()) { - PyErr_Clear(); - key_flags = 0; - } else { - const unsigned long mask = 1ul << (sizeof(long)*8-1); - pair->fkey.k_ulong = x ^ mask; - key_flags &= KEY_ALL_LONG; - } - } else - key_flags = 0; - pair->key = key; - pair->value = value; - leaf->children[j] = (PyObject*) pair; - k++; - } - } - - assert(k == n); - - *pkey_flags = key_flags; - return 0; -} - -/* If COMPARE is NULL, calls PyObject_RichCompareBool with Py_LT, else calls - * islt. This avoids a layer of function call in the usual case, and - * sorting does many comparisons. - * Returns -1 on error, 1 if x < y, 0 if x >= y. - * - * In Python 3, COMPARE is always NULL. - */ - -#define FAST_ISLT(X, Y, fast_cmp_type) \ - (fast_lt(((sortwrapperobject *)(X))->key, \ - ((sortwrapperobject *)(Y))->key, \ - (fast_cmp_type))) - -#if PY_MAJOR_VERSION < 3 -#define ISLT(X, Y, COMPARE, fast_cmp_type) \ - ((COMPARE) == NULL ? \ - FAST_ISLT(X, Y, fast_cmp_type) : \ - islt(X, Y, COMPARE)) -#else -#define ISLT(X, Y, COMPARE, fast_cmp_type) \ - (FAST_ISLT((X), (Y), (fast_cmp_type))) -#endif - -#if PY_MAJOR_VERSION < 3 -/* XXX - - Efficiency improvement: - Keep one PyTuple in a global spot and just change what it points to. - We can also skip all the INCREF/DECREF stuff then and just borrow - references -*/ -static int islt(PyObject *x, PyObject *y, PyObject *compare) -{ - PyObject *res; - PyObject *args; - Py_ssize_t i; - - Py_INCREF(x); - Py_INCREF(y); - - DANGER_BEGIN; - args = PyTuple_New(2); - DANGER_END; - - if (args == NULL) { - DANGER_BEGIN; - Py_DECREF(x); - Py_DECREF(y); - DANGER_END; - return -1; - } - - PyTuple_SET_ITEM(args, 0, x); - PyTuple_SET_ITEM(args, 1, y); - DANGER_BEGIN; - res = PyObject_Call(compare, args, NULL); - Py_DECREF(args); - DANGER_END; - if (res == NULL) - return -1; - if (!PyInt_CheckExact(res)) { - PyErr_Format(PyExc_TypeError, - "comparison function must return int, not %.200s", - Py_TYPE(res)->tp_name); - Py_DECREF(res); - return -1; - } - i = PyInt_AsLong(res); - Py_DECREF(res); - return i < 0; -} -#endif - -#define INSERTION_THRESH 0 -#define BINARY_THRESH 10 - -#define TESTSWAP(i, j) { \ - if (fast_lt(sortarray[j], sortarray[i], fast_cmp_type)) { \ - PyObject *t = sortarray[j]; \ - sortarray[j] = sortarray[i]; \ - sortarray[i] = t; \ - } \ - } - -#if 0 -BLIST_LOCAL(int) -network_sort(PyObject **sortarray, Py_ssize_t n) -{ - fast_compare_data_t fast_cmp_type; - fast_cmp_type = check_fast_cmp_type(sortarray[0], Py_LT); - - switch(n) { - case 0: - case 1: - assert(0); - case 2: - TESTSWAP(0, 1); - return 0; - case 3: - TESTSWAP(0, 1); - TESTSWAP(0, 2); - TESTSWAP(1, 2); - return 0; - case 4: - TESTSWAP(0, 1); - TESTSWAP(2, 3); - TESTSWAP(0, 2); - TESTSWAP(1, 3); - TESTSWAP(1, 2); - return 0; - case 5: - TESTSWAP(0, 1); - TESTSWAP(3, 4); - TESTSWAP(2, 4); - TESTSWAP(2, 3); - TESTSWAP(1, 4); - TESTSWAP(0, 3); - TESTSWAP(0, 2); - TESTSWAP(1, 3); - TESTSWAP(1, 2); - return 0; - case 6: - TESTSWAP(1, 2); - TESTSWAP(4, 5); - - TESTSWAP(0, 2); - TESTSWAP(3, 5); - - TESTSWAP(0, 1); - TESTSWAP(3, 4); - TESTSWAP(2, 5); - - TESTSWAP(0, 3); - TESTSWAP(1, 4); - - TESTSWAP(2, 4); - TESTSWAP(1, 3); - - TESTSWAP(2, 3); - return 0; - default: - /* Should not be possible */ - assert (0); - abort(); - } -} -#endif - -#ifdef BLIST_FLOAT_RADIX_SORT -BLIST_LOCAL_INLINE(int) -insertion_sort_uint64(sortwrapperobject *array, Py_ssize_t n) -{ - int i, j; - PY_UINT64_T tmp_key; - PyObject *tmp_value; - for (i = 1; i < n; i++) { - tmp_key = array[i].fkey.k_uint64; - tmp_value = array[i].value; - for (j = i; j >= 1; j--) { - if (tmp_key >= array[j-1].fkey.k_uint64) - break; - array[j].fkey.k_uint64 = array[j-1].fkey.k_uint64; - array[j].value = array[j-1].value; - } - array[j].fkey.k_uint64 = tmp_key; - array[j].value = tmp_value; - } - - return 0; -} -#endif - -BLIST_LOCAL_INLINE(int) -insertion_sort_ulong(sortwrapperobject *restrict array, Py_ssize_t n) -{ - int i, j; - unsigned long tmp_key; - PyObject *tmp_value; - - for (i = 1; i < n; i++) { - tmp_key = array[i].fkey.k_ulong; - tmp_value = array[i].value; - for (j = i; j >= 1; j--) { - if (tmp_key >= array[j-1].fkey.k_ulong) - break; - array[j].fkey.k_ulong = array[j-1].fkey.k_ulong; - array[j].value = array[j-1].value; - } - array[j].fkey.k_ulong = tmp_key; - array[j].value = tmp_value; - } - - return 0; -} - -#if 0 -static int binary_sort_int64(sortwrapperobject *array, int n) -{ - int i, j, low, high, mid, c; - sortwrapperobject tmp; - - for (i = 1; i < n; i++) { - tmp = array[i]; - - c = INT64_LT(tmp, array[i-1]); - if (c < 0) - return -1; - if (c == 0) - continue; - - low = 0; - high = i-1; - - while (low < high) { - mid = low + (high - low)/2; - c = INT64_LT(tmp, array[mid]); - if (c < 0) - return -1; - if (c == 0) - low = mid+1; - else - high = mid; - } - - for (j = i; j >= low; j--) - array[j] = array[j-1]; - - array[low] = tmp; - } - - return 0; -} -#endif - -BLIST_LOCAL(int) -mini_merge(PyObject **array, int middle, int n, PyObject *compare) -{ - int c, ret = 0; - - PyObject *copy[LIMIT]; - PyObject **left; - PyObject **right = &array[middle]; - PyObject **rend = &array[n]; - PyObject **lend = ©[middle]; - PyObject **src; - PyObject **dst; - fast_compare_data_t fast_cmp_type; - - assert (middle <= LIMIT); - - fast_cmp_type = check_fast_cmp_type(array[0], Py_LT); - - for (left = array; left < right; left++) { - c = ISLT(*right, *left, compare, fast_cmp_type); - if (c < 0) - return -1; - if (c) - goto normal; - } - - return 0; - - normal: - src = left; - dst = left; - - for (left = copy; src < right; left++) - *left = *src++; - - lend = left; - - *dst++ = *right++; - - for (left = copy; left < lend && right < rend; dst++) { - c = ISLT(*right, *left, compare, fast_cmp_type); - if (c < 0) { - ret = -1; - goto done; - } - if (c == 0) - *dst = *left++; - else - *dst = *right++; - } - - done: - while (left < lend) - *dst++ = *left++; - - return ret; -} - -#define RUN_THRESH 5 - -BLIST_LOCAL(int) -gallop_sort(PyObject **array, int n, PyObject *compare) -{ - int i; - int run_start = 0, run_dir = 2; - PyObject **runs[LIMIT/RUN_THRESH+2]; - int ns[LIMIT/RUN_THRESH+2]; - int num_runs = 0; - PyObject **run = array; - fast_compare_data_t fast_cmp_type; - - if (n < 2) return 0; - - fast_cmp_type = check_fast_cmp_type(array[0], Py_LT); - - for (i = 1; i < n; i++) { - int c = ISLT(array[i], array[i-1], compare, fast_cmp_type); - assert(c < 0 || c == 0 || c == 1); - if (c == run_dir) - continue; - if (c < 0) - return -1; - if (run_start == i-1) - run_dir = c; - else if (i - run_start >= RUN_THRESH) { - if (run_dir > 0) - reverse_slice(run, &array[i]); - runs[num_runs] = run; - ns[num_runs++] = i - run_start; - run = &array[i]; - run_start = i; - run_dir = 2; - } else { - int j; - int low = run - array; - int high = i-1; - int mid; - PyObject *tmp = array[i]; - - /* XXX: Is this a stable sort? */ - /* XXX: In both directions? */ - - while (low < high) { - mid = low + (high - low)/2; - c = ISLT(tmp, array[mid], compare, - fast_cmp_type); - assert(c < 0 || c == 0 || c == 1); - if (c == run_dir) - low = mid+1; - else if (c < 0) - return -1; - else - high = mid; - } - - for (j = i; j > low; j--) - array[j] = array[j-1]; - - array[low] = tmp; - } - } - - if (run_dir > 0) - reverse_slice(run, &array[n]); - runs[num_runs] = run; - ns[num_runs++] = n - run_start; - - while(num_runs > 1) { - for (i = 0; i < num_runs/2; i++) { - int total = ns[2*i] + ns[2*i+1]; - if (0 > mini_merge(runs[2*i], ns[2*i], total, - compare)) { - /* List valid due to invariants */ - return -1; - } - - runs[i] = runs[2*i]; - ns[i] = total; - } - - if (num_runs & 1) { - runs[i] = runs[num_runs - 1]; - ns[i] = ns[num_runs - 1]; - } - num_runs = (num_runs+1)/2; - } - - assert(ns[0] == n); - - return 0; - -} - -#if 0 -BLIST_LOCAL(int) -mini_merge_sort(PyObject **array, int n, PyObject *compare) -{ - int i, run_size = BINARY_THRESH; - - for (i = 0; i < n; i += run_size) { - int len = run_size; - if (n - i < len) - len = n - i; - if (binary_sort(&array[i], len, compare) < 0) - return -1; - } - - run_size *= 2; - while (run_size < n) { - for (i = 0; i < n; i += run_size) { - int len = run_size; - if (n - i < len) - len = n - i; - if (len <= run_size/2) - continue; - if (mini_merge(&array[i], run_size/2, len, compare) < 0) - return -1; - } - run_size *= 2; - } - - return 0; -} -#endif - -#if PY_MAJOR_VERSION < 3 -BLIST_LOCAL(int) -is_default_cmp(PyObject *cmpfunc) -{ - PyCFunctionObject *f; - if (cmpfunc == NULL || cmpfunc == Py_None) - return 1; - if (!PyCFunction_Check(cmpfunc)) - return 0; - f = (PyCFunctionObject *)cmpfunc; - if (f->m_self != NULL) - return 0; - if (!PyString_Check(f->m_module)) - return 0; - if (strcmp(PyString_AS_STRING(f->m_module), "__builtin__") != 0) - return 0; - if (strcmp(f->m_ml->ml_name, "cmp") != 0) - return 0; - return 1; -} -#endif - -BLIST_LOCAL(void) -do_fast_merge(PyBList **restrict out, PyBList **in1, PyBList **in2, - int n1, int n2) -{ - memcpy(out, in1, sizeof(PyBList *) * n1); - memcpy(&out[n1], in2, sizeof(PyBList *) * n2); -} - -BLIST_LOCAL(int) -try_fast_merge(PyBList **restrict out, PyBList **in1, PyBList **in2, - Py_ssize_t n1, Py_ssize_t n2, - PyObject *compare, int *err) -{ - int c; - PyBList *end; - - end = in1[n1-1]; - - c = ISLT(end->children[end->num_children-1], - in2[0]->children[0], compare, no_fast_lt); - - if (c < 0) { - error: - *err = -1; - do_fast_merge(out, in1, in2, n1, n2); - return 1; - } else if (c) { - do_fast_merge(out, in1, in2, n1, n2); - return 1; - } - - end = in2[n2-1]; - - c = ISLT(end->children[end->num_children-1], - in1[0]->children[0], compare, no_fast_lt); - if (c < 0) - goto error; - else if (c) { - do_fast_merge(out, in2, in1, n2, n1); - return 1; - } - - return 0; -} - -/* Merge two arrays of leaf nodes. */ -BLIST_LOCAL(int) -sub_merge(PyBList **restrict out, PyBList **in1, PyBList **in2, - Py_ssize_t n1, Py_ssize_t n2, - PyObject *compare, int *err) -{ - int c; - Py_ssize_t i, j; - PyBList *restrict leaf1, *restrict leaf2, *restrict output; - int leaf1_i = 0, leaf2_i = 0; - Py_ssize_t nout = 0; - fast_compare_data_t fast_cmp_type; - - if (try_fast_merge(out, in1, in2, n1, n2, compare, err)) - return n1 + n2; - - leaf1 = in1[leaf1_i++]; - leaf2 = in2[leaf2_i++]; - - i = 0; /* Index into leaf 1 */ - j = 0; /* Index into leaf 2 */ - - output = blist_new_no_GC(); - - fast_cmp_type = check_fast_cmp_type(leaf1->children[0], Py_LT); - - while ((leaf1_i < n1 || i < leaf1->num_children) - && (leaf2_i < n2 || j < leaf2->num_children)) { - - /* Check if we need to get a new input leaf node */ - if (i == leaf1->num_children) { - leaf1->num_children = 0; - SAFE_DECREF(leaf1); - leaf1 = in1[leaf1_i++]; - i = 0; - } - - if (j == leaf2->num_children) { - leaf2->num_children = 0; - SAFE_DECREF(leaf2); - leaf2 = in2[leaf2_i++]; - j = 0; - } - - assert (i < leaf1->num_children); - assert (j < leaf2->num_children); - - /* Check if we have filled up an output leaf node */ - if (output->n == LIMIT) { - out[nout++] = output; - output = blist_new_no_GC(); - } - - /* Figure out which input leaf has the lower element */ - c = ISLT(leaf2->children[j], leaf1->children[i], compare, - fast_cmp_type); - if (c < 0) { - *err = -1; - goto done; - } - if (c == 0) { - output->children[output->num_children++] - = leaf1->children[i++]; - } else { - output->children[output->num_children++] - = leaf2->children[j++]; - } - - output->n++; - } - - done: - /* Append our partially-complete output leaf node */ - nout = append_and_squish(out, nout, output); - - /* Append a partially-consumed input leaf node, if one exists */ - if (i < leaf1->num_children) { - shift_left(leaf1, i, i); - leaf1->num_children -= i; - leaf1->n -= i; - nout = append_and_squish(out, nout, leaf1); - } else { - leaf1->num_children = 0; - SAFE_DECREF(leaf1); - } - - if (j < leaf2->num_children) { - shift_left(leaf2, j, j); - leaf2->num_children -= j; - leaf2->n -= j; - nout = append_and_squish(out, nout, leaf2); - } else { - leaf2->num_children = 0; - SAFE_DECREF(leaf2); - } - - nout = balance_last_2(out, nout); - - /* Append the rest of any input that still has nodes. */ - if (leaf1_i < n1) { - memcpy(&out[nout], &in1[leaf1_i], - sizeof(PyBList *) * (n1 - leaf1_i)); - nout += n1 - leaf1_i; - } - - if (leaf2_i < n2) { - memcpy(&out[nout], &in2[leaf2_i], - sizeof(PyBList *) * (n2 - leaf2_i)); - nout += n2 - leaf2_i; - } - - for (i = nout; i < n1+n2; i++) - out[i] = NULL; - - assert(nout <= n1+n2); - - return nout; -} - -/* If swap is true, place the output in scratch. - * Otherwise, place the output in "in" */ -BLIST_LOCAL(Py_ssize_t) -sub_sort(PyBList **restrict scratch, PyBList **in, PyObject *compare, - Py_ssize_t n, int *err, int swap) -{ - Py_ssize_t half, n1, n2; - - if (!n) return n; - if (*err) { - if (swap) - memcpy(scratch, in, n * sizeof(PyBList *)); - return n; - } - if (n == 1) { - *err |= gallop_sort(in[0]->children, in[0]->num_children, - compare); - *scratch = *in; - return 1; - } - - half = n / 2; - - n1 = sub_sort(scratch, in, compare, half, err, !swap); - n2 = sub_sort(&scratch[half], &in[half], compare, n-half, err, !swap); - - /* If swap is true, the output is currently in "in". - * Otherwise, the output is currently in scratch. - * - * sub_merge() will reverse it. - */ - - if (!*err) { - if (swap) - n = sub_merge(scratch, in, &in[half], n1, n2, compare, err); - else - n = sub_merge(in, scratch, &scratch[half], n1, n2, compare, err); - } else { - if (swap) { - memcpy(scratch, in, n1 * sizeof(PyBList *)); - memcpy(&scratch[n1], &in[half], n2 * sizeof(PyBList *)); - } else { - memcpy(in, scratch, n1 * sizeof(PyBList *)); - memcpy(&in[n1], &scratch[half], n2 * sizeof(PyBList *)); - } - n = n1 + n2; - } - return n; -} - -#if 0 -BLIST_LOCAL_INLINE(void) -array_disable_GC(PyBList **leafs, Py_ssize_t num_leafs) -{ - Py_ssize_t i; - for (i = 0; i < num_leafs; i++) - PyObject_GC_UnTrack(leafs[i]); -} -#endif - -BLIST_LOCAL_INLINE(void) -array_enable_GC(PyBList **leafs, Py_ssize_t num_leafs) -{ - Py_ssize_t i; - for (i = 0; i < num_leafs; i++) - PyObject_GC_Track(leafs[i]); -} - -#define BITS_PER_PASS 8 -#define HISTOGRAM_SIZE (((Py_ssize_t) 1) << BITS_PER_PASS) -#define MASK (HISTOGRAM_SIZE - 1) -#define NUM_PASSES (((sizeof(unsigned long)*8-1) / BITS_PER_PASS)+1) - -BLIST_LOCAL_INLINE(int) -sort_ulong(sortwrapperobject *restrict sortarray, Py_ssize_t n) -{ - sortwrapperobject *restrict scratch, *from, *to, *tmp; - Py_ssize_t histograms[HISTOGRAM_SIZE][NUM_PASSES]; - Py_ssize_t i, j, sums[NUM_PASSES], count[NUM_PASSES], tsum; - - memset(sums, 0, sizeof sums); - memset(count, 0, sizeof count); - - scratch = PyMem_New(sortwrapperobject, n); - if (scratch == NULL) - return -1; - - memset(histograms, 0, sizeof histograms); - for (i = 0; i < n; i++) { - unsigned long v = sortarray[i].fkey.k_ulong; - for (j = 0; j < NUM_PASSES; j++) { - histograms[(v >> (BITS_PER_PASS * j)) & MASK][j]++; - } - } - - for (i = 0; i < HISTOGRAM_SIZE; i++) { - for (j = 0; j < NUM_PASSES; j++) { - count[j] += !!histograms[i][j]; - tsum = histograms[i][j] + sums[j]; - histograms[i][j] = sums[j] - 1; - sums[j] = tsum; - } - } - - from = sortarray; - to = scratch; - for (j = 0; j < NUM_PASSES; j++) { - sortwrapperobject *restrict f = from; - sortwrapperobject *restrict t = to; - if (count[j] == 1) continue; - for (i = 0; i < n; i++) { - unsigned long fi = f[i].fkey.k_ulong; - Py_ssize_t pos = (fi >> (BITS_PER_PASS * j)) & MASK; - pos = ++histograms[pos][j]; - t[pos].fkey.k_ulong = fi; - t[pos].value = f[i].value; - } - - tmp = from; - from = to; - to = tmp; - } - - if (from != sortarray) - for (i = 0; i < n; i++) - sortarray[i].value = scratch[i].value; - - PyMem_Free(scratch); - return 0; -} - -#undef NUM_PASSES - -#ifdef BLIST_FLOAT_RADIX_SORT -#if SIZEOF_LONG == 8 -#define sort_uint64 sort_ulong -#else -#define NUM_PASSES (((64-1) / BITS_PER_PASS)+1) - -BLIST_LOCAL_INLINE(int) -sort_uint64(sortwrapperobject *restrict sortarray, Py_ssize_t n) -{ - sortwrapperobject *restrict scratch, *from, *to, *tmp; - Py_ssize_t histograms[HISTOGRAM_SIZE][NUM_PASSES]; - Py_ssize_t i, j, sums[NUM_PASSES], count[NUM_PASSES], tsum; - - memset(sums, 0, sizeof sums); - memset(count, 0, sizeof count); - - scratch = PyMem_New(sortwrapperobject, n); - if (scratch == NULL) - return -1; - - memset(histograms, 0, sizeof histograms); - for (i = 0; i < n; i++) { - PY_UINT64_T v = sortarray[i].fkey.k_uint64; - for (j = 0; j < NUM_PASSES; j++) { - histograms[(v >> (BITS_PER_PASS * j)) & MASK][j]++; - } - } - - for (i = 0; i < HISTOGRAM_SIZE; i++) { - for (j = 0; j < NUM_PASSES; j++) { - count[j] += !!histograms[i][j]; - tsum = histograms[i][j] + sums[j]; - histograms[i][j] = sums[j] - 1; - sums[j] = tsum; - } - } - - from = sortarray; - to = scratch; - for (j = 0; j < NUM_PASSES; j++) { - if (count[j] == 1) continue; - for (i = 0; i < n; i++) { - PY_UINT64_T fi = from[i].fkey.k_uint64; - Py_ssize_t pos = (fi >> (BITS_PER_PASS * j)) & MASK; - pos = ++histograms[pos][j]; - to[pos].fkey.k_uint64 = fi; - to[pos].value = from[i].value; - } - - tmp = from; - from = to; - to = tmp; - } - - if (from != sortarray) - for (i = 0; i < n; i++) - sortarray[i].value = scratch[i].value; - - PyMem_Free(scratch); - return 0; -} - -#undef NUM_PASSES - -#endif -#endif - -BLIST_LOCAL(Py_ssize_t) -sort(PyBListRoot *restrict self, PyObject *compare, PyObject *keyfunc) -{ - PyBList *leaf; - PyBList **leafs; - int err=0; - Py_ssize_t i, leafs_n = 0; - sortwrapperobject sortarraystack[10]; - sortwrapperobject *sortarray = sortarraystack; - int key_flags; - - if (self->leaf) - leafs = &leaf; - else { - leafs = PyMem_New(PyBList *, self->n / HALF + 1); - if (!leafs) - return -1; - } - - if (self->leaf) { - leaf = (PyBList *) self; - leafs_n = 1; - } else { - linearize_rw(self); - - assert(INDEX_LENGTH(self) <= self->index_allocated); - for (i = 0; i < INDEX_LENGTH(self)-1; i++) { - leaf = self->index_list[i]; - if (leaf == self->index_list[i+1]) - continue; - leafs[leafs_n++] = leaf; - Py_INCREF(leaf); - } - leaf = self->index_list[i]; - leafs[leafs_n++] = leaf; - Py_INCREF(leaf); - } - - if (self->n > 10) { - sortarray = PyMem_New(sortwrapperobject, self->n); - if (sortarray == NULL) { - sortarray = sortarraystack; - goto error; - } - } - - err = wrap_leaf_array(sortarray, leafs, leafs_n, self->n, keyfunc, - &key_flags); - if (err < 0) { - error: - if (!self->leaf) { - for (i = 0; i < leafs_n; i++) - SAFE_DECREF(leafs[i]); - PyMem_Free(leafs); - } - if (sortarray != sortarraystack) - PyMem_Free(sortarray); - return -1; - } - - if (key_flags && compare == NULL) { -#ifdef BLIST_FLOAT_RADIX_SORT - if (key_flags & KEY_ALL_DOUBLE) { - if (self->n < 40 && self->leaf) - err = insertion_sort_uint64(sortarray,self->n); - else - err = sort_uint64(sortarray, self->n); - } else -#endif - if (key_flags & KEY_ALL_LONG) { - if (self->n < 40 && self->leaf) - err = insertion_sort_ulong(sortarray, self->n); - else - err = sort_ulong(sortarray, self->n); - } - else - assert(0); /* Should not be possible */ - unwrap_leaf_array(leafs, leafs_n, self->n, sortarray); - if (!self->leaf) { - for (i = 0; i < leafs_n; i++) - SAFE_DECREF(leafs[i]); - PyMem_Free(leafs); - } - } else if (self->leaf) { - err = gallop_sort(self->children, self->num_children, compare); - unwrap_leaf_array(leafs, 1, self->n, sortarray); - } else { - PyBList **scratch = PyMem_New(PyBList *, self->n / HALF + 1); - if (!scratch) { - PyMem_Free(leafs); - PyMem_Free(sortarray); - return -1; - } - leafs_n = sub_sort(scratch, leafs, compare, leafs_n, &err, 0); - array_enable_GC(leafs, leafs_n); - PyMem_Free(scratch); - unwrap_leaf_array(leafs, leafs_n, self->n, sortarray); - i = blist_init_from_child_array(leafs, leafs_n); - - if (i < 0) { - /* XXX leaking memory here when out of memory */ - PyMem_Free(sortarray); - return -1; - } else { - assert(i == 1); - blist_become_and_consume((PyBList *) self, leafs[0]); - } - SAFE_DECREF(leafs[0]); - PyMem_Free(leafs); - } - - if (sortarray != sortarraystack) - PyMem_Free(sortarray); - return err; -} - -/************************************************************************ - * Section for functions callable directly by the interpreter. - * - * Each of these functions are marked with VALID_USER for debug mode. - * - * If they, or any function they call, makes calls to decref_later, - * they must call decref_flush() just before returning. - * - * These functions must not be called directly by other blist - * functions. They should *only* be called by the interpreter, to - * ensure that decref_flush() is the last thing called before - * returning to the interpreter. - */ - -BLIST_PYAPI(PyObject *) -py_blist_root_tp_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds) -{ - PyBList *self; - - if (subtype == &PyRootBList_Type) - return (PyObject *) blist_root_new(); - - self = (PyBList *) subtype->tp_alloc(subtype, 0); - if (self == NULL) - return NULL; - self->children = PyMem_New(PyObject *, LIMIT); - if (self->children == NULL) { - subtype->tp_free(self); - return NULL; - } - - self->leaf = 1; - ext_init((PyBListRoot *)self); - - return (PyObject *) self; -} - -/* Should only be used by the unpickler */ -BLIST_PYAPI(PyObject *) -py_blist_internal_tp_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds) -{ - assert (subtype == &PyBList_Type); - return (PyObject *) blist_new(); -} - -/* Should only be used by the unpickler */ -BLIST_PYAPI(int) -py_blist_internal_init(PyObject *oself, PyObject *args, PyObject *kw) -{ - return 0; -} - -BLIST_PYAPI(int) -py_blist_init(PyObject *oself, PyObject *args, PyObject *kw) -{ - int ret; - PyObject *arg = NULL; - static char *kwlist[] = {"sequence", 0}; - int err; - PyBList *self; - - invariants(oself, VALID_USER|VALID_DECREF); - self = (PyBList *) oself; - - DANGER_BEGIN; - err = PyArg_ParseTupleAndKeywords(args, kw, "|O:list", kwlist, &arg); - DANGER_END; - if (!err) - return _int(-1); - - if (self->n) { - blist_CLEAR(self); - ext_dealloc((PyBListRoot *) self); - } - - if (arg == NULL) - return _int(0); - - ret = blist_init_from_seq(self, arg); - - decref_flush(); /* Needed due to blist_CLEAR() call */ - return _int(ret); -} - -BLIST_PYAPI(PyObject *) -py_blist_richcompare(PyObject *v, PyObject *w, int op) -{ - PyObject *rv; - - if (!PyRootBList_Check(v)) { - not_implemented: - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; - } - - invariants((PyBList *) v, VALID_USER|VALID_DECREF); - if (PyRootBList_Check(w)) { - rv = blist_richcompare_blist((PyBList *)v, (PyBList *)w, op); - decref_flush(); - return _ob(rv); - } -#ifndef Py_BUILD_CORE - if (PyList_Check(w)) { - rv = blist_richcompare_list((PyBList*)v, (PyListObject*)w, op); - decref_flush(); - return _ob(rv); - } -#endif - _void(); - goto not_implemented; -} - -BLIST_PYAPI(int) -py_blist_traverse(PyObject *oself, visitproc visit, void *arg) -{ - PyBList *self; - int i; - - assert(PyBList_Check(oself)); - self = (PyBList *) oself; - - for (i = 0; i < self->num_children; i++) { - if (self->children[i] != NULL) - Py_VISIT(self->children[i]); - } - return 0; -} - -BLIST_PYAPI(int) -py_blist_clear(PyObject *oself) -{ - PyBList *self; - - invariants(oself, VALID_USER|VALID_RW|VALID_DECREF); - self = (PyBList *) oself; - - blist_forget_children(self); - self->n = 0; - self->leaf = 1; - ext_dealloc((PyBListRoot *) self); - - decref_flush(); - return _int(0); -} - -#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6 || PY_MAJOR_VERSION >= 3 -#define py_blist_nohash PyObject_HashNotImplemented -#else -BLIST_PYAPI(long) -py_blist_nohash(PyObject *self) -{ - PyErr_SetString(PyExc_TypeError, "list objects are unhashable"); - return -1; -} -#endif - -BLIST_PYAPI(void) -py_blist_dealloc(PyObject *oself) -{ - int i; - PyBList *self; - - assert(PyBList_Check(oself)); - self = (PyBList *) oself; - - if (_PyObject_GC_IS_TRACKED(self)) - PyObject_GC_UnTrack(self); - - Py_TRASHCAN_SAFE_BEGIN(self) - - /* Py_XDECREF() is needed here because the Python C API allows list - * items to be NULL. */ - for (i = 0; i < self->num_children; i++) - Py_XDECREF(self->children[i]); - - if (PyRootBList_Check(self)) { - ext_dealloc((PyBListRoot *) self); - if (PyRootBList_CheckExact(self) - && num_free_ulists < MAXFREELISTS) - free_ulists[num_free_ulists++] = self; - else - goto free_blist; - } else if (Py_TYPE(self) == &PyBList_Type - && num_free_lists < MAXFREELISTS) - free_lists[num_free_lists++] = self; - else { - free_blist: - PyMem_Free(self->children); - Py_TYPE(self)->tp_free((PyObject *)self); - } - - Py_TRASHCAN_SAFE_END(self); -} - -BLIST_PYAPI(int) -py_blist_ass_item(PyObject *oself, Py_ssize_t i, PyObject *v) -{ - PyObject *old_value; - PyBList *self; - - invariants(oself, VALID_USER|VALID_RW|VALID_DECREF); - - self = (PyBList *) oself; - - if (i >= self->n || i < 0) { - set_index_error(); - return _int(-1); - } - - if (v == NULL) { - blist_delitem(self, i); - ext_mark(self, 0, DIRTY); - decref_flush(); - return _int(0); - } - - old_value = blist_ass_item_return(self, i, v); - Py_XDECREF(old_value); - return _int(0); -} - -BLIST_PYAPI(int) -py_blist_ass_slice(PyObject *oself, Py_ssize_t ilow, Py_ssize_t ihigh, - PyObject *v) -{ - Py_ssize_t net; - PyBList *other, *left, *right, *self; - - invariants(oself, VALID_RW|VALID_USER|VALID_DECREF); - - self = (PyBList *) oself; - - if (ilow < 0) ilow = 0; - else if (ilow > self->n) ilow = self->n; - if (ihigh < ilow) ihigh = ilow; - else if (ihigh > self->n) ihigh = self->n; - - if (!v) { - blist_delslice(self, ilow, ihigh); - ext_mark(self, 0, DIRTY); - decref_flush(); - return _int(0); - } - - if (PyRootBList_Check(v) && (PyObject *) self != v) { - other = (PyBList *) v; - Py_INCREF(other); - ext_mark_set_dirty_all(other); - } else { - other = blist_root_new(); - if (v) { - int err = blist_init_from_seq(other, v); - if (err < 0) { - decref_later((PyObject *) other); - decref_flush(); - return _int(-1); - } - } - } - - net = other->n - (ihigh - ilow); - - /* Special case small lists */ - if (self->leaf && other->leaf && (self->n + net <= LIMIT)) - { - Py_ssize_t i; - - for (i = ilow; i < ihigh; i++) - decref_later(self->children[i]); - - if (net >= 0) - shift_right(self, ihigh, net); - else - shift_left(self, ihigh, -net); - self->num_children += net; - copyref(self, ilow, other, 0, other->n); - SAFE_DECREF(other); - blist_adjust_n(self); - decref_flush(); - return _int(0); - } - - left = self; - right = blist_root_copy(self); - blist_delslice(left, ilow, left->n); - blist_delslice(right, 0, ihigh); - blist_extend_blist(left, other); /* XXX check return values */ - blist_extend_blist(left, right); - - ext_mark(self, 0, DIRTY); - - SAFE_DECREF(other); - SAFE_DECREF(right); - - decref_flush(); - - return _int(0); -} - -BLIST_PYAPI(int) -py_blist_ass_subscript(PyObject *oself, PyObject *item, PyObject *value) -{ - PyBList *self; - - invariants(oself, VALID_USER|VALID_RW|VALID_DECREF); - - self = (PyBList *) oself; - - if (PyIndex_Check(item)) { - Py_ssize_t i; - PyObject *old_value; - - if (PyLong_CheckExact(item)) { - i = PyInt_AsSsize_t(item); - if (i == -1 && PyErr_Occurred()) { - PyErr_Clear(); - goto number; - } - } else { - number: - i = PyNumber_AsSsize_t(item, PyExc_IndexError); - if (i == -1 && PyErr_Occurred()) - return _int(-1); - } - if (i < 0) - i += self->n; - - if (i >= self->n || i < 0) { - set_index_error(); - return _int(-1); - } - - if (self->leaf) { - /* Speed up common cases */ - - old_value = self->children[i]; - if (value == NULL) { - shift_left(self, i+1, 1); - self->num_children--; - self->n--; - } else { - self->children[i] = value; - Py_INCREF(value); - } - DANGER_BEGIN; - Py_DECREF(old_value); - DANGER_END; - return _int(0); - } - - if (value == NULL) { - blist_delitem(self, i); - ext_mark(self, 0, DIRTY); - decref_flush(); - return _int(0); - } - - Py_INCREF(value); - old_value = blist_ass_item_return2((PyBListRoot*)self,i,value); - DANGER_BEGIN; - Py_DECREF(old_value); - DANGER_END; - return _int(0); - } else if (PySlice_Check(item)) { - Py_ssize_t start, stop, step, slicelength; - - ext_mark(self, 0, DIRTY); - - if (PySlice_GetIndicesEx((PySliceObject*)item, self->n, - &start, &stop,&step,&slicelength)<0) - return _int(-1); - - /* treat L[slice(a,b)] = v _exactly_ like L[a:b] = v */ - if (step == 1 && ((PySliceObject*)item)->step == Py_None) - return _redir(py_blist_ass_slice(oself,start,stop,value)); - - if (value == NULL) { - /* Delete back-to-front */ - Py_ssize_t i, cur; - - if (slicelength <= 0) - return _int(0); - - if (step > 0) { - stop = start - 1; - start = start + step*(slicelength-1); - step = -step; - } - - for (cur = start, i = 0; i < slicelength; - cur += step, i++) { - PyObject *ob = blist_delitem_return(self, cur); - decref_later(ob); - } - - decref_flush(); - ext_mark(self, 0, DIRTY); - - return _int(0); - } else { /* assign slice */ - PyObject *ins, *seq; - Py_ssize_t cur, i; - - DANGER_BEGIN; - seq = PySequence_Fast(value, - "Must assign iterable to extended slice"); - DANGER_END; - if (!seq) - return _int(-1); - - if (seq == (PyObject *) self) { - Py_DECREF(seq); - seq = (PyObject *) blist_root_copy(self); - } - - if (PySequence_Fast_GET_SIZE(seq) != slicelength) { - PyErr_Format(PyExc_ValueError, - "attempt to assign sequence of size %zd to extended slice of size %zd", - PySequence_Fast_GET_SIZE(seq), - slicelength); - Py_DECREF(seq); - return _int(-1); - } - - if (!slicelength) { - Py_DECREF(seq); - return _int(0); - } - - for (cur = start, i = 0; i < slicelength; - cur += step, i++) { - PyObject *ob; - ins = PySequence_Fast_GET_ITEM(seq, i); - ob = blist_ass_item_return(self, cur, ins); - decref_later(ob); - } - - Py_DECREF(seq); - - decref_flush(); - - return _int(0); - } - } else { - PyErr_SetString(PyExc_TypeError, - "list indices must be integers"); - return _int(-1); - } -} - -BLIST_PYAPI(Py_ssize_t) -py_blist_length(PyObject *ob) -{ - assert(PyRootBList_Check(ob)); - return ((PyBList *) ob)->n; -} - -BLIST_PYAPI(PyObject *) -py_blist_repeat(PyObject *oself, Py_ssize_t n) -{ - PyObject *ret; - PyBList *self; - - invariants(oself, VALID_USER|VALID_DECREF); - - self = (PyBList *) oself; - - ret = blist_repeat(self, n); - decref_flush(); - ext_mark_set_dirty_all(self); - - return _ob(ret); -} - -BLIST_PYAPI(PyObject *) -py_blist_inplace_repeat(PyObject *oself, Py_ssize_t n) -{ - PyBList *tmp, *self; - - invariants(oself, VALID_USER|VALID_RW|VALID_DECREF); - - self = (PyBList *) oself; - - tmp = (PyBList *) blist_repeat(self, n); - if (tmp == NULL) - return (PyObject *) _blist(NULL); - blist_become_and_consume(self, tmp); - Py_INCREF(self); - SAFE_DECREF(tmp); - - decref_flush(); - - ext_mark(self, 0, DIRTY); - - return (PyObject *) _blist(self); -} - -BLIST_PYAPI(PyObject *) -py_blist_extend(PyBList *self, PyObject *other) -{ - int err; - - invariants(self, VALID_USER|VALID_RW|VALID_DECREF); - - err = blist_extend(self, other); - decref_flush(); - ext_mark(self, 0, DIRTY); - if (PyBList_Check(other)) - ext_mark_set_dirty_all((PyBList *) other); - - if (err < 0) - return _ob(NULL); - Py_RETURN_NONE; -} - -BLIST_PYAPI(PyObject *) -py_blist_inplace_concat(PyObject *oself, PyObject *other) -{ - int err; - PyBList *self; - - invariants(oself, VALID_RW|VALID_USER|VALID_DECREF); - - self = (PyBList *) oself; - - err = blist_extend(self, other); - decref_flush(); - ext_mark(self, 0, DIRTY); - if (PyBList_Check(other)) - ext_mark_set_dirty_all((PyBList*) other); - - if (err < 0) - return _ob(NULL); - - Py_INCREF(self); - return _ob((PyObject *)self); -} - -BLIST_PYAPI(int) -py_blist_contains(PyObject *oself, PyObject *el) -{ - int c, ret = 0; - PyObject *item; - PyBList *self; - fast_compare_data_t fast_cmp_type; - - invariants(oself, VALID_USER | VALID_DECREF); - - self = (PyBList *) oself; - fast_cmp_type = check_fast_cmp_type(el, Py_EQ); - - ITER(self, item, { - c = fast_eq(el, item, fast_cmp_type); - if (c < 0) { - ret = -1; - break; - } - if (c > 0) { - ret = 1; - break; - } - }); - - decref_flush(); - return _int(ret); -} - -BLIST_PYAPI(PyObject *) -py_blist_get_slice(PyObject *oself, Py_ssize_t ilow, Py_ssize_t ihigh) -{ - PyBList *rv, *self; - - invariants(oself, VALID_USER | VALID_DECREF); - - self = (PyBList *) oself; - - if (ilow < 0) ilow = 0; - else if (ilow > self->n) ilow = self->n; - if (ihigh < ilow) ihigh = ilow; - else if (ihigh > self->n) ihigh = self->n; - - rv = blist_root_new(); - if (rv == NULL) - return (PyObject *) _blist(NULL); - - if (ihigh <= ilow || ilow >= self->n) - return (PyObject *) _blist(rv); - - if (self->leaf) { - Py_ssize_t delta = ihigh - ilow; - - copyref(rv, 0, self, ilow, delta); - rv->num_children = delta; - rv->n = delta; - return (PyObject *) _blist(rv); - } - - blist_become(rv, self); - blist_delslice(rv, ihigh, self->n); - blist_delslice(rv, 0, ilow); - - ext_mark(rv, 0, DIRTY); - ext_mark_set_dirty(self, ilow, ihigh); - decref_flush(); - - return (PyObject *) _blist(rv); -} - -/* This should only be called by _PyBList_GET_ITEM_FAST2() */ -PyObject *_PyBList_GetItemFast3(PyBListRoot *root, Py_ssize_t i) -{ - PyObject *rv; - Py_ssize_t dirty_offset = -1; - - invariants(root, VALID_PARENT); - assert(!root->leaf); - assert(root->dirty_root != CLEAN); - assert(i >= 0); - assert(i < root->n); - - if (ext_is_dirty(root, i, &dirty_offset)){ - rv = ext_make_clean(root, i); - } else { - Py_ssize_t ioffset = i / INDEX_FACTOR; - Py_ssize_t offset = root->offset_list[ioffset]; - PyBList *p = root->index_list[ioffset]; - assert(i >= offset); - assert(p); - assert(p->leaf); - if (i < offset + p->n) { - rv = p->children[i - offset]; - if (dirty_offset >= 0) - ext_make_clean(root, dirty_offset); - } else if (ext_is_dirty(root,i + INDEX_FACTOR,&dirty_offset)){ - rv = ext_make_clean(root, i); - } else { - ioffset++; - assert(ioffset < root->index_allocated); - offset = root->offset_list[ioffset]; - p = root->index_list[ioffset]; - rv = p->children[i - offset]; - assert(p); - assert(p->leaf); - assert(i < offset + p->n); - if (dirty_offset >= 0) - ext_make_clean(root, dirty_offset); - } - } - - assert(rv == blist_get1((PyBList *)root, i)); - - return _ob(rv); -} - -BLIST_PYAPI(PyObject *) -py_blist_get_item(PyObject *oself, Py_ssize_t i) -{ - PyBList *self = (PyBList *) oself; - PyObject *ret; - - invariants(self, VALID_USER); - - if (i < 0 || i >= self->n) { - set_index_error(); - return _ob(NULL); - } - - if (self->leaf) - ret = self->children[i]; - else - ret = _PyBList_GET_ITEM_FAST2((PyBListRoot*)self, i); - Py_INCREF(ret); - return _ob(ret); -} - -/* Note: this may be called as __radd__, which means the arguments may - * be reversed. */ -BLIST_PYAPI(PyObject *) -py_blist_concat(PyObject *ob1, PyObject *ob2) -{ - PyBList *rv; - int err; - - int is_blist1 = PyRootBList_Check(ob1); - int is_blist2 = PyRootBList_Check(ob2); - - if ((!is_blist1 && !PyList_Check(ob1)) - || (!is_blist2 && !PyList_Check(ob2))) { - Py_INCREF(Py_NotImplemented); - return Py_NotImplemented; - } - - if (is_blist1 && is_blist2) { - PyBList *blist1 = (PyBList *) ob1; - PyBList *blist2 = (PyBList *) ob2; - if (blist1->n < LIMIT && blist2->n < LIMIT - && blist1->n + blist2->n < LIMIT) { - rv = blist_root_new(); - copyref(rv, 0, blist1, 0, blist1->n); - copyref(rv, blist1->n, blist2, 0, blist2->n); - rv->n = rv->num_children = blist1->n + blist2->n; - goto done; - } - - rv = blist_root_copy(blist1); - blist_extend_blist(rv, blist2); - ext_mark(rv, 0, DIRTY); - ext_mark_set_dirty_all(blist2); - goto done; - } - - rv = blist_root_new(); - err = blist_init_from_seq(rv, ob1); - if (err < 0) { - decref_later((PyObject *) rv); - rv = NULL; - goto done; - } - err = blist_extend(rv, ob2); - if (err < 0) { - decref_later((PyObject *) rv); - rv = NULL; - goto done; - } - ext_mark(rv, 0, DIRTY); - if (PyBList_Check(ob1)) - ext_mark_set_dirty_all((PyBList *) ob1); - if (PyBList_Check(ob2)) - ext_mark_set_dirty_all((PyBList *) ob2); - -done: - _decref_flush(); - return (PyObject *) rv; -} - -/* User-visible repr() */ -BLIST_PYAPI(PyObject *) -py_blist_repr(PyObject *oself) -{ - /* Basic approach: Clone self in O(1) time, then walk through - * the clone, changing each element to repr() of the element, - * in O(n) time. Finally, enclose it in square brackets and - * call join. - */ - - Py_ssize_t i; - PyBList *pieces = NULL, *self; - PyObject *result = NULL; - PyObject *s, *tmp, *tmp2; - - invariants(oself, VALID_USER); - self = (PyBList *) oself; - - DANGER_BEGIN; - i = Py_ReprEnter((PyObject *) self); - DANGER_END; - if (i) { - return i > 0 ? _ob(PyUnicode_FromString("[...]")) : _ob(NULL); - } - - if (self->n == 0) { -#ifdef Py_BUILD_CORE - result = PyUnicode_FromString("[]"); -#else - result = PyUnicode_FromString("blist([])"); -#endif - goto Done; - } - - pieces = blist_root_copy(self); - if (pieces == NULL) - goto Done; - - if (blist_repr_r(pieces) < 0) - goto Done; - -#ifdef Py_BUILD_CORE - s = PyUnicode_FromString("["); -#else - s = PyUnicode_FromString("blist(["); -#endif - if (s == NULL) - goto Done; - tmp = blist_get1(pieces, 0); - tmp2 = PyUnicode_Concat(s, tmp); - Py_DECREF(s); - s = tmp2; - DANGER_BEGIN; - py_blist_ass_item((PyObject *) pieces, 0, s); - DANGER_END; - Py_DECREF(s); - -#ifdef Py_BUILD_CORE - s = PyUnicode_FromString("]"); -#else - s = PyUnicode_FromString("])"); -#endif - if (s == NULL) - goto Done; - tmp = blist_get1(pieces, pieces->n-1); - tmp2 = PyUnicode_Concat(tmp, s); - Py_DECREF(s); - tmp = tmp2; - DANGER_BEGIN; - py_blist_ass_item((PyObject *) pieces, pieces->n-1, tmp); - DANGER_END; - Py_DECREF(tmp); - - s = PyUnicode_FromString(", "); - if (s == NULL) - goto Done; - result = PyUnicode_Join(s, (PyObject *) pieces); - Py_DECREF(s); - - Done: - DANGER_BEGIN; - /* Only deallocating strings, so this is safe */ - Py_XDECREF(pieces); - DANGER_END; - - DANGER_BEGIN; - Py_ReprLeave((PyObject *) self); - DANGER_END; - return _ob(result); -} - -#if defined(Py_DEBUG) && !defined(BLIST_IN_PYTHON) -/* Return a string that shows the internal structure of the BList */ -BLIST_PYAPI(PyObject *) -blist_debug(PyBList *self, PyObject *indent) -{ - PyObject *result, *s, *nl_indent, *comma, *indent2, *tmp, *tmp2; - - invariants(self, VALID_PARENT); - - comma = PyUnicode_FromString(", "); - - if (indent == NULL) - indent = PyUnicode_FromString(""); - else - Py_INCREF(indent); - - tmp = PyUnicode_FromString(" "); - indent2 = PyUnicode_Concat(indent, tmp); - Py_DECREF(tmp); - - if (!self->leaf) { - int i; - - nl_indent = indent2; - tmp = PyUnicode_FromString("\n"); - nl_indent = PyUnicode_Concat(indent2, tmp); - Py_DECREF(tmp); - - result = PyUnicode_FromFormat("blist(leaf=%d, n=%d, r=%d, ", - self->leaf, self->n, - Py_REFCNT(self)); - /* PyUnicode_Concat(&result, nl_indent); */ - - for (i = 0; i < self->num_children; i++) { - s = blist_debug((PyBList *)self->children[i], indent2); - tmp = PyUnicode_Concat(result, nl_indent); - Py_DECREF(result); - result = tmp; - tmp = PyUnicode_Concat(result, s); - Py_DECREF(result); - result = tmp; - Py_DECREF(s); - } - - tmp = PyUnicode_FromString(")"); - tmp2 = PyUnicode_Concat(result, tmp); - Py_DECREF(result); - Py_DECREF(tmp); - result = tmp2; - } else { - int i; - - result = PyUnicode_FromFormat("blist(leaf=%d, n=%d, r=%d, ", - self->leaf, self->n, - Py_REFCNT(self)); - for (i = 0; i < self->num_children; i++) { - s = PyObject_Str(self->children[i]); - tmp = PyUnicode_Concat(result, s); - Py_DECREF(result); - result = tmp; - Py_DECREF(s); - tmp = PyUnicode_Concat(result, comma); - Py_DECREF(result); - result = tmp; - } - } - - s = indent; - tmp = PyUnicode_Concat(s, result); - Py_DECREF(result); - result = tmp; - - Py_DECREF(comma); - Py_DECREF(indent); - check_invariants(self); - return _ob(result); -} - -BLIST_PYAPI(PyObject *) -py_blist_debug(PyBList *self) -{ - invariants(self, VALID_USER); - return _ob(blist_debug(self, NULL)); -} -#endif - -BLIST_PYAPI(PyObject *) -py_blist_sort(PyBListRoot *self, PyObject *args, PyObject *kwds) -{ -#if PY_MAJOR_VERSION < 3 - static char *kwlist[] = {"cmp", "key", "reverse", 0}; -#else - static char *kwlist[] = {"key", "reverse", 0}; -#endif - int reverse = 0; - int ret = -1; - PyBListRoot saved; - PyObject *result = NULL; - PyObject *compare = NULL, *keyfunc = NULL; - static PyObject **extra_list = NULL; - - invariants(self, VALID_USER|VALID_RW | VALID_DECREF); - - if (args != NULL) { - int err; - DANGER_BEGIN; -#if PY_MAJOR_VERSION < 3 - err = PyArg_ParseTupleAndKeywords(args, kwds, "|OOi:sort", - kwlist, &compare, &keyfunc, - &reverse); - DANGER_END; - if (!err) - return _ob(NULL); -#else - err = PyArg_ParseTupleAndKeywords(args, kwds, "|Oi:sort", - kwlist, &keyfunc, &reverse); - DANGER_END; - if (!err) - return _ob(NULL); - if (Py_SIZE(args) > 0) { - PyErr_SetString(PyExc_TypeError, - "must use keyword argument for key function"); - return _ob(NULL); - } -#endif - } - - if (self->n < 2) - Py_RETURN_NONE; - -#if PY_MAJOR_VERSION < 3 - if (is_default_cmp(compare)) - compare = NULL; -#endif - if (keyfunc == Py_None) - keyfunc = NULL; - - memset(&saved, 0, offsetof(PyBListRoot, BLIST_FIRST_FIELD)); - memcpy(&saved.BLIST_FIRST_FIELD, &self->BLIST_FIRST_FIELD, - sizeof(*self) - offsetof(PyBListRoot, BLIST_FIRST_FIELD)); - Py_TYPE(&saved) = &PyRootBList_Type; - Py_REFCNT(&saved) = 1; - - if (extra_list != NULL) { - self->children = extra_list; - extra_list = NULL; - } else { - self->children = PyMem_New(PyObject *, LIMIT); - if (self->children == NULL) { - PyErr_NoMemory(); - goto err; - } - } - self->n = 0; - self->num_children = 0; - self->leaf = 1; - ext_init(self); - - /* Reverse sort stability achieved by initially reversing the list, - applying a stable forward sort, then reversing the final result. */ - if (reverse) - blist_reverse(&saved); - - ret = sort(&saved, compare, keyfunc); - - if (ret >= 0) { - result = Py_None; - if (reverse) { - ext_mark((PyBList*)&saved, 0, DIRTY); - blist_reverse(&saved); - } - } else - ext_mark((PyBList*)&saved, 0, DIRTY); - - if (self->n && saved.n) { - DANGER_BEGIN; - /* An error may also have been raised by a comparison - * function. Since this may decref that traceback, it can - * execute arbitrary python code. */ - PyErr_SetString(PyExc_ValueError, "list modified during sort"); - DANGER_END; - result = NULL; - blist_CLEAR((PyBList*) self); - } - - if (extra_list == NULL) - extra_list = self->children; - else - PyMem_Free(self->children); - - ext_dealloc(self); - assert(!self->n); - err: - memcpy(&self->BLIST_FIRST_FIELD, &saved.BLIST_FIRST_FIELD, - sizeof(*self) - offsetof(PyBListRoot, BLIST_FIRST_FIELD)); - - Py_XINCREF(result); - - decref_flush(); - - /* This must come after the decref_flush(); otherwise, we may have - * extra temporary references to internal nodes, which throws off the - * debug-mode sanity checking. */ - if (ret >= 0) - ext_reindex_set_all(&saved); - - return _ob(result); -} - -BLIST_PYAPI(PyObject *) -py_blist_reverse(PyBList *restrict self) -{ - invariants(self, VALID_USER|VALID_RW); - - if (self->leaf) - reverse_slice(self->children, - &self->children[self->num_children]); - else { - blist_reverse((PyBListRoot*) self); - } - - Py_RETURN_NONE; -} - -BLIST_PYAPI(PyObject *) -py_blist_count(PyBList *self, PyObject *v) -{ - Py_ssize_t count = 0; - PyObject *item; - int c; - fast_compare_data_t fast_cmp_type; - - invariants(self, VALID_USER | VALID_DECREF); - - fast_cmp_type = check_fast_cmp_type(v, Py_EQ); - - ITER(self, item, { - c = fast_eq(item, v, fast_cmp_type); - if (c > 0) - count++; - else if (c < 0) { - ITER_CLEANUP(); - decref_flush(); - return _ob(NULL); - } - }) - - decref_flush(); - return _ob(PyInt_FromSsize_t(count)); -} - -BLIST_PYAPI(PyObject *) -py_blist_index(PyBList *self, PyObject *args) -{ - Py_ssize_t i, start=0, stop=self->n; - PyObject *v; - int c, err; - PyObject *item; - fast_compare_data_t fast_cmp_type; - - invariants(self, VALID_USER|VALID_DECREF); - - DANGER_BEGIN; - err = PyArg_ParseTuple(args, "O|O&O&:index", &v, - _PyEval_SliceIndex, &start, - _PyEval_SliceIndex, &stop); - DANGER_END; - if (!err) - return _ob(NULL); - if (start < 0) { - start += self->n; - if (start < 0) - start = 0; - } else if (start > self->n) - start = self->n; - if (stop < 0) { - stop += self->n; - if (stop < 0) - stop = 0; - } else if (stop > self->n) - stop = self->n; - - fast_cmp_type = check_fast_cmp_type(v, Py_EQ); - i = start; - ITER2(self, item, start, stop, { - c = fast_eq(item, v, fast_cmp_type); - if (c > 0) { - ITER_CLEANUP(); - decref_flush(); - return _ob(PyInt_FromSsize_t(i)); - } else if (c < 0) { - ITER_CLEANUP(); - decref_flush(); - return _ob(NULL); - } - i++; - }) - - decref_flush(); - PyErr_SetString(PyExc_ValueError, "list.index(x): x not in list"); - return _ob(NULL); -} - -BLIST_PYAPI(PyObject *) -py_blist_remove(PyBList *self, PyObject *v) -{ - Py_ssize_t i; - int c; - PyObject *item; - fast_compare_data_t fast_cmp_type; - - invariants(self, VALID_USER|VALID_RW|VALID_DECREF); - - fast_cmp_type = check_fast_cmp_type(v, Py_EQ); - i = 0; - ITER(self, item, { - c = fast_eq(item, v, fast_cmp_type); - if (c > 0) { - ITER_CLEANUP(); - blist_delitem(self, i); - decref_flush(); - ext_mark(self, 0, DIRTY); - Py_RETURN_NONE; - } else if (c < 0) { - ITER_CLEANUP(); - decref_flush(); - return _ob(NULL); - } - i++; - }) - - decref_flush(); - PyErr_SetString(PyExc_ValueError, "list.remove(x): x not in list"); - return _ob(NULL); -} - -BLIST_PYAPI(PyObject *) -py_blist_pop(PyBList *self, PyObject *args) -{ - Py_ssize_t i = -1; - PyObject *v; - int err; - - invariants(self, VALID_USER|VALID_RW|VALID_DECREF); - - DANGER_BEGIN; - err = PyArg_ParseTuple(args, "|n:pop", &i); - DANGER_END; - if (!err) - return _ob(NULL); - - if (self->n == 0) { - /* Special-case most common failure cause */ - PyErr_SetString(PyExc_IndexError, "pop from empty list"); - return _ob(NULL); - } - - if (i == -1 || i == self->n-1) { - v = blist_pop_last_fast(self); - if (v) - return _ob(v); - } - - if (i < 0) - i += self->n; - if (i < 0 || i >= self->n) { - PyErr_SetString(PyExc_IndexError, "pop index out of range"); - return _ob(NULL); - } - - v = blist_delitem_return(self, i); - ext_mark(self, 0, DIRTY); - - decref_flush(); /* Remove any deleted BList nodes */ - - return _ob(v); /* the caller now owns the reference the list had */ -} - -BLIST_PYAPI(PyObject *) -py_blist_insert(PyBList *self, PyObject *args) -{ - Py_ssize_t i; - PyObject *v; - PyBList *overflow; - int err; - - invariants(self, VALID_USER|VALID_RW); - - DANGER_BEGIN; - err = PyArg_ParseTuple(args, "nO:insert", &i, &v); - DANGER_END; - if (!err) - return _ob(NULL); - - if (self->n == PY_SSIZE_T_MAX) { - PyErr_SetString(PyExc_OverflowError, - "cannot add more objects to list"); - return _ob(NULL); - } - - if (i < 0) { - i += self->n; - if (i < 0) - i = 0; - } else if (i > self->n) - i = self->n; - - /* Speed up the common case */ - if (self->leaf && self->num_children < LIMIT) { - Py_INCREF(v); - - shift_right(self, i, 1); - self->num_children++; - self->n++; - self->children[i] = v; - Py_RETURN_NONE; - } - - overflow = ins1(self, i, v); - if (overflow) - blist_overflow_root(self, overflow); - ext_mark(self, 0, DIRTY); - Py_RETURN_NONE; -} - -BLIST_PYAPI(PyObject *) -py_blist_append(PyBList *self, PyObject *v) -{ - int err; - - invariants(self, VALID_USER|VALID_RW); - - err = blist_append(self, v); - - if (err < 0) - return _ob(NULL); - - Py_RETURN_NONE; -} - -BLIST_PYAPI(PyObject *) -py_blist_subscript(PyObject *oself, PyObject *item) -{ - PyBList *self; - - invariants(oself, VALID_USER); - - self = (PyBList *) oself; - - if (PyIndex_Check(item)) { - Py_ssize_t i; - PyObject *ret; - - if (PyLong_CheckExact(item)) { - i = PyInt_AsSsize_t(item); - if (i == -1 && PyErr_Occurred()) { - PyErr_Clear(); - goto number; - } - } else { - number: - i = PyNumber_AsSsize_t(item, PyExc_IndexError); - if (i == -1 && PyErr_Occurred()) - return _ob(NULL); - } - - if (i < 0) - i += self->n; - - if (i < 0 || i >= self->n) { - set_index_error(); - return _ob(NULL); - } - - if (self->leaf) - ret = self->children[i]; - else - ret = _PyBList_GET_ITEM_FAST2((PyBListRoot*)self, i); - Py_INCREF(ret); - - return _ob(ret); - } else if (PySlice_Check(item)) { - Py_ssize_t start, stop, step, slicelength, cur, i; - PyBList* result; - PyObject* it; - - if (PySlice_GetIndicesEx((PySliceObject*)item, self->n, - &start, &stop,&step,&slicelength)<0) { - return _ob(NULL); - } - - if (step == 1) - return _redir((PyObject *) - py_blist_get_slice((PyObject *) self, start, stop)); - - result = blist_root_new(); - - if (slicelength <= 0) - return _ob((PyObject *) result); - - /* This could be made slightly faster by using forests */ - /* Also, by special-casing small trees */ - for (cur = start, i = 0; i < slicelength; cur += step, i++) { - int err; - - it = blist_get1(self, cur); - err = blist_append(result, it); - if (err < 0) { - Py_DECREF(result); - return _ob(NULL); - } - } - - ext_mark(result, 0, DIRTY); - return _ob((PyObject *) result); - } else { - PyErr_SetString(PyExc_TypeError, - "list indices must be integers"); - return _ob(NULL); - } -} - -#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6 || PY_MAJOR_VERSION >= 3 -static PyObject * -py_blist_root_sizeof(PyBListRoot *root) -{ - Py_ssize_t res; - res = sizeof(PyBListRoot) - + LIMIT * sizeof(PyObject *) - + root->index_allocated * (sizeof (PyBList *) +sizeof(Py_ssize_t)) - + root->dirty_length * sizeof(Py_ssize_t) - + (root->index_allocated ? - SETCLEAN_LEN(root->index_allocated) * sizeof(unsigned): 0); - return PyLong_FromSsize_t(res); -} - -static PyObject * -py_blist_sizeof(PyBList *self) -{ - Py_ssize_t res; - res = sizeof(PyBList) - + LIMIT * sizeof(PyObject *); - return PyLong_FromSsize_t(res); -} - -PyDoc_STRVAR(sizeof_doc, -"L.__sizeof__() -- size of L in memory, in bytes"); -#endif - -/************************************************************************ - * Routines for supporting pickling - */ - -#ifndef BLIST_IN_PYTHON -BLIST_PYAPI(PyObject *) -blist_getstate(PyBList *self) -{ - PyObject *lst; - int i; - - invariants(self, VALID_PARENT); - - lst = PyList_New(self->num_children); - for (i = 0; i < self->num_children; i++) { - PyList_SET_ITEM(lst, i, self->children[i]); - Py_INCREF(PyList_GET_ITEM(lst, i)); - } - - if (PyRootBList_CheckExact(self)) - ext_mark_set_dirty_all(self); - - return _ob(lst); -} - -BLIST_PYAPI(PyObject *) -py_blist_setstate(PyBList *self, PyObject *state) -{ - Py_ssize_t i; - - invariants(self, VALID_PARENT); - - if (!PyList_CheckExact(state) || PyList_GET_SIZE(state) > LIMIT) { - PyErr_SetString(PyExc_TypeError, "invalid state"); - return _ob(NULL); - } - - for (self->n = i = 0; i < PyList_GET_SIZE(state); i++) { - PyObject *child = PyList_GET_ITEM(state, i); - if (Py_TYPE(child) == &PyBList_Type) { - self->leaf = 0; - self->n += ((PyBList*)child)->n; - } else - self->n++; - self->children[i] = child; - Py_INCREF(child); - } - - self->num_children = PyList_GET_SIZE(state); - - if (PyRootBList_CheckExact(self)) - ext_reindex_all((PyBListRoot*)self); - - Py_RETURN_NONE; -} - -BLIST_PYAPI(PyObject *) -py_blist_reduce(PyBList *self) -{ - PyObject *rv, *args, *type; - - invariants(self, VALID_PARENT); - - type = (PyObject *) Py_TYPE(self); - args = PyTuple_New(0); - rv = PyTuple_New(3); - PyTuple_SET_ITEM(rv, 0, type); - Py_INCREF(type); - PyTuple_SET_ITEM(rv, 1, args); - PyTuple_SET_ITEM(rv, 2, blist_getstate(self)); - - return _ob(rv); -} -#endif - -PyDoc_STRVAR(getitem_doc, - "x.__getitem__(y) <==> x[y]"); -PyDoc_STRVAR(reversed_doc, - "L.__reversed__() -- return a reverse iterator over the list"); -PyDoc_STRVAR(append_doc, -"L.append(object) -- append object to end"); -PyDoc_STRVAR(extend_doc, -"L.extend(iterable) -- extend list by appending elements from the iterable"); -PyDoc_STRVAR(insert_doc, -"L.insert(index, object) -- insert object before index"); -PyDoc_STRVAR(pop_doc, -"L.pop([index]) -> item -- remove and return item at index (default last)"); -PyDoc_STRVAR(remove_doc, -"L.remove(value) -- remove first occurrence of value"); -PyDoc_STRVAR(index_doc, -"L.index(value, [start, [stop]]) -> integer -- return first index of value"); -PyDoc_STRVAR(count_doc, -"L.count(value) -> integer -- return number of occurrences of value"); -PyDoc_STRVAR(reverse_doc, -"L.reverse() -- reverse *IN PLACE*"); -PyDoc_STRVAR(sort_doc, -"L.sort(cmp=None, key=None, reverse=False) -- stable sort *IN PLACE*;\n\ -cmp(x, y) -> -1, 0, 1"); - -static PyMethodDef blist_methods[] = { - {"__getitem__", (PyCFunction)py_blist_subscript, METH_O|METH_COEXIST, getitem_doc}, - {"__reversed__",(PyCFunction)py_blist_reversed, METH_NOARGS, reversed_doc}, -#ifndef BLIST_IN_PYTHON - {"__reduce__", (PyCFunction)py_blist_reduce, METH_NOARGS, NULL}, - {"__setstate__",(PyCFunction)py_blist_setstate, METH_O, NULL}, -#endif - {"append", (PyCFunction)py_blist_append, METH_O, append_doc}, - {"insert", (PyCFunction)py_blist_insert, METH_VARARGS, insert_doc}, - {"extend", (PyCFunction)py_blist_extend, METH_O, extend_doc}, - {"pop", (PyCFunction)py_blist_pop, METH_VARARGS, pop_doc}, - {"remove", (PyCFunction)py_blist_remove, METH_O, remove_doc}, - {"index", (PyCFunction)py_blist_index, METH_VARARGS, index_doc}, - {"count", (PyCFunction)py_blist_count, METH_O, count_doc}, - {"reverse", (PyCFunction)py_blist_reverse, METH_NOARGS, reverse_doc}, - {"sort", (PyCFunction)py_blist_sort, METH_VARARGS | METH_KEYWORDS, sort_doc}, -#if defined(Py_DEBUG) && !defined(BLIST_IN_PYTHON) - {"debug", (PyCFunction)py_blist_debug, METH_NOARGS, NULL}, -#endif -#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6 || PY_MAJOR_VERSION >= 3 - {"__sizeof__", (PyCFunction)py_blist_root_sizeof, METH_NOARGS, sizeof_doc}, -#endif - {NULL, NULL} /* sentinel */ -}; - -static PyMethodDef blist_internal_methods[] = { -#ifndef BLIST_IN_PYTHON - {"__reduce__", (PyCFunction)py_blist_reduce, METH_NOARGS, NULL}, - {"__setstate__",(PyCFunction)py_blist_setstate, METH_O, NULL}, -#endif -#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6 || PY_MAJOR_VERSION >= 3 - {"__sizeof__", (PyCFunction)py_blist_sizeof, METH_NOARGS, sizeof_doc}, -#endif - {NULL, NULL} /* sentinel */ -}; - -static PySequenceMethods blist_as_sequence = { - py_blist_length, /* sq_length */ - 0, /* sq_concat */ - py_blist_repeat, /* sq_repeat */ - py_blist_get_item, /* sq_item */ - py_blist_get_slice, /* sq_slice */ - py_blist_ass_item, /* sq_ass_item */ - py_blist_ass_slice, /* sq_ass_slice */ - py_blist_contains, /* sq_contains */ - 0, /* sq_inplace_concat */ - py_blist_inplace_repeat, /* sq_inplace_repeat */ -}; - -PyDoc_STRVAR(blist_doc, -"blist() -> new list\n" -"blist(iterable) -> new list initialized from iterable's items"); - -static PyMappingMethods blist_as_mapping = { - py_blist_length, - py_blist_subscript, - py_blist_ass_subscript -}; - -/* All of this, just to get __radd__ to work */ -#if PY_MAJOR_VERSION < 3 -static PyNumberMethods blist_as_number = { - py_blist_concat, /* nb_add */ - 0, /* nb_subtract */ - 0, /* nb_multiply */ - 0, /* nb_divide */ - 0, /* nb_remainder */ - 0, /* nb_divmod */ - 0, /* nb_power */ - 0, /* nb_negative */ - 0, /* tp_positive */ - 0, /* tp_absolute */ - 0, /* tp_nonzero */ - 0, /* nb_invert */ - 0, /* nb_lshift */ - 0, /* nb_rshift */ - 0, /* nb_and */ - 0, /* nb_xor */ - 0, /* nb_or */ - 0, /* nb_coerce */ - 0, /* nb_int */ - 0, /* nb_long */ - 0, /* nb_float */ - 0, /* nb_oct */ - 0, /* nb_hex */ - py_blist_inplace_concat, /* nb_inplace_add */ - 0, /* nb_inplace_subtract */ - 0, /* nb_inplace_multiply */ - 0, /* nb_inplace_divide */ - 0, /* nb_inplace_remainder */ - 0, /* nb_inplace_power */ - 0, /* nb_inplace_lshift */ - 0, /* nb_inplace_rshift */ - 0, /* nb_inplace_and */ - 0, /* nb_inplace_xor */ - 0, /* nb_inplace_or */ - 0, /* nb_floor_divide */ - 0, /* nb_true_divide */ - 0, /* nb_inplace_floor_divide */ - 0, /* nb_inplace_true_divide */ - 0, /* nb_index */ -}; -#else -static PyNumberMethods blist_as_number = { - (binaryfunc) py_blist_concat, /* nb_add */ - 0, /* nb_subtract */ - 0, /* nb_multiply */ - 0, /* nb_remainder */ - 0, /* nb_divmod */ - 0, /* nb_power */ - 0, /* nb_negative */ - 0, /* tp_positive */ - 0, /* tp_absolute */ - 0, /* tp_bool */ - 0, /* nb_invert */ - 0, /* nb_lshift */ - 0, /* nb_rshift */ - 0, /* nb_and */ - 0, /* nb_xor */ - 0, /* nb_or */ - 0, /* nb_int */ - 0, /* nb_reserved */ - 0, /* nb_float */ - py_blist_inplace_concat, /* nb_inplace_add */ - 0, /* nb_inplace_subtract */ - 0, /* nb_inplace_multiply */ - 0, /* nb_inplace_remainder */ - 0, /* nb_inplace_power */ - 0, /* nb_inplace_lshift */ - 0, /* nb_inplace_rshift */ - 0, /* nb_inplace_and */ - 0, /* nb_inplace_xor */ - 0, /* nb_inplace_or */ - 0, /* nb_floor_divide */ - 0, /* nb_true_divide */ - 0, /* nb_inplace_floor_divide */ - 0, /* nb_inplace_true_divide */ - 0, /* nb_index */ -}; -#endif - -PyTypeObject PyBList_Type = { - PyVarObject_HEAD_INIT(NULL, 0) -#ifdef BLIST_IN_PYTHON - "__internal_blist", -#else - "_blist.__internal_blist", -#endif - sizeof(PyBList), - 0, - py_blist_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - py_blist_nohash, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - PyObject_GenericGetAttr, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | - Py_TPFLAGS_BASETYPE, /* tp_flags */ - blist_doc, /* tp_doc */ - py_blist_traverse, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - blist_internal_methods, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - py_blist_internal_init, /* tp_init */ - 0, /* tp_alloc */ - py_blist_internal_tp_new, /* tp_new */ - PyObject_GC_Del, /* tp_free */ -}; - -PyTypeObject PyRootBList_Type = { - PyVarObject_HEAD_INIT(NULL, 0) -#ifdef BLIST_IN_PYTHON - "list", -#else - "_blist.blist", -#endif - sizeof(PyBListRoot), - 0, - py_blist_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - py_blist_repr, /* tp_repr */ - &blist_as_number, /* tp_as_number */ - &blist_as_sequence, /* tp_as_sequence */ - &blist_as_mapping, /* tp_as_mapping */ - py_blist_nohash, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - PyObject_GenericGetAttr, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | - Py_TPFLAGS_BASETYPE /* tp_flags */ -#if (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6 || PY_MAJOR_VERSION >= 3) && defined(BLIST_IN_PYTHON) - | Py_TPFLAGS_LIST_SUBCLASS -#endif - , - blist_doc, /* tp_doc */ - py_blist_traverse, /* tp_traverse */ - py_blist_clear, /* tp_clear */ - py_blist_richcompare, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - py_blist_iter, /* tp_iter */ - 0, /* tp_iternext */ - blist_methods, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - py_blist_init, /* tp_init */ - PyType_GenericAlloc, /* tp_alloc */ - py_blist_root_tp_new, /* tp_new */ - PyObject_GC_Del, /* tp_free */ -}; - -static PyMethodDef module_methods[] = { { NULL } }; - -BLIST_LOCAL(int) -init_blist_types1(void) -{ - decref_init(); - highest_set_bit_init(); - - Py_TYPE(&PyBList_Type) = &PyType_Type; - Py_TYPE(&PyRootBList_Type) = &PyType_Type; - Py_TYPE(&PyBListIter_Type) = &PyType_Type; - Py_TYPE(&PyBListReverseIter_Type) = &PyType_Type; - - Py_INCREF(&PyBList_Type); - Py_INCREF(&PyRootBList_Type); - Py_INCREF(&PyBListIter_Type); - Py_INCREF(&PyBListReverseIter_Type); - - return 0; -} - -BLIST_LOCAL(int) -init_blist_types2(void) -{ - if (PyType_Ready(&PyRootBList_Type) < 0) return -1; - if (PyType_Ready(&PyBList_Type) < 0) return -1; - if (PyType_Ready(&PyBListIter_Type) < 0) return -1; - if (PyType_Ready(&PyBListReverseIter_Type) < 0) return -1; - - return 0; -} - -#if PY_MAJOR_VERSION < 3 -PyMODINIT_FUNC -init_blist(void) -{ -#ifndef BLIST_IN_PYTHON - PyCFunctionObject *meth; - PyObject *gc_module; -#endif - - PyObject *m; - PyObject *limit = PyInt_FromLong(LIMIT); - - init_blist_types1(); - init_blist_types2(); - - m = Py_InitModule3("_blist", module_methods, "_blist"); - - PyModule_AddObject(m, "blist", (PyObject *) &PyRootBList_Type); - PyModule_AddObject(m, "_limit", limit); - PyModule_AddObject(m, "__internal_blist", (PyObject *) - &PyBList_Type); - -#ifndef BLIST_IN_PYTHON - gc_module = PyImport_ImportModule("gc"); - - meth = (PyCFunctionObject*)PyObject_GetAttrString(gc_module, "enable"); - pgc_enable = meth->m_ml->ml_meth; - - meth = (PyCFunctionObject*)PyObject_GetAttrString(gc_module,"disable"); - pgc_disable = meth->m_ml->ml_meth; - - meth = (PyCFunctionObject*)PyObject_GetAttrString(gc_module, - "isenabled"); - pgc_isenabled = meth->m_ml->ml_meth; -#endif -} -#else - -static struct PyModuleDef blist_module = { - PyModuleDef_HEAD_INIT, - "_blist", - NULL, - -1, - module_methods, - NULL, - NULL, - NULL, - NULL, -}; - -PyMODINIT_FUNC -PyInit__blist(void) -{ -#ifndef BLIST_IN_PYTHON - PyModuleDef *gc_module_def; - PyMethodDef *gc_methods; - PyObject *gc_module; -#endif - PyObject *m; - PyObject *limit = PyInt_FromLong(LIMIT); - - if (init_blist_types1() < 0) - return NULL; - if (init_blist_types2() < 0) - return NULL; - - m = PyModule_Create(&blist_module); - - PyModule_AddObject(m, "blist", (PyObject *) &PyRootBList_Type); - PyModule_AddObject(m, "_limit", limit); - PyModule_AddObject(m, "__internal_blist", (PyObject *) - &PyBList_Type); - -#ifndef BLIST_IN_PYTHON - gc_module = PyImport_ImportModule("gc"); - gc_module_def = PyModule_GetDef(gc_module); - gc_methods = gc_module_def->m_methods; - while (gc_methods->ml_name != NULL) { - if (0 == strcmp(gc_methods->ml_name, "enable")) - pgc_enable = gc_methods->ml_meth; - else if (0 == strcmp(gc_methods->ml_name, "disable")) - pgc_disable = gc_methods->ml_meth; - else if (0 == strcmp(gc_methods->ml_name, "isenabled")) - pgc_isenabled = gc_methods->ml_meth; - gc_methods++; - } -#endif - return m; -} -#endif - -/************************************************************************ - * The List C API, for building BList into the Python core - */ - -#ifdef Py_BUILD_CORE -int -PyList_Init1(void) -{ - return init_blist_types1(); -} - -int -PyList_Init2(void) -{ - return init_blist_types2(); -} - -PyObject *PyList_New(Py_ssize_t size) -{ - PyBList *self = blist_root_new(); - PyObject *tmp; - - if (self == NULL) - return NULL; - - if (size <= LIMIT) { - self->n = size; - self->num_children = size; - memset(self->children, 0, sizeof(PyObject *) * size); - check_invariants(self); - return (PyObject *) self; - } - - self->n = 1; - self->num_children = 1; - self->children[0] = NULL; - - tmp = blist_repeat(self, size); - check_invariants((PyBList *) tmp); - SAFE_DECREF(self); - _decref_flush(); - check_invariants((PyBList *) tmp); - assert(((PyBList *)tmp)->n == size); - ext_dealloc((PyBListRoot *) tmp); - return tmp; -} - -Py_ssize_t PyList_Size(PyObject *ob) -{ - if (ob == NULL || !PyList_Check(ob)) { - PyErr_BadInternalCall(); - return -1; - } - - return py_blist_length(ob); -} - -PyObject *PyList_GetItem(PyObject *ob, Py_ssize_t i) -{ - PyBList *self = (PyBList *) ob; - PyObject *ret; - - if (ob == NULL || !PyList_Check(ob)) { - PyErr_BadInternalCall(); - return NULL; - } - - assert(i >= 0 && i < self->n); /* XXX Remove */ - - if (i < 0 || i >= self->n) { - set_index_error(); - return NULL; - } - - ret = blist_get1((PyBList *) ob, i); - assert(ret != NULL); - return ret; -} - -int PyList_SetItem(PyObject *ob, Py_ssize_t i, PyObject *item) -{ - int ret; - - if (ob == NULL || !PyList_Check(ob)) { - PyErr_BadInternalCall(); - Py_XDECREF(item); - return -1; - } - - assert(i >= 0 && i < ((PyBList *)ob)->n); /* XXX Remove */ - ret = py_blist_ass_item(ob, i, item); - assert(Py_REFCNT(item) > 1); - Py_XDECREF(item); - return ret; -} - -int PyList_Insert(PyObject *ob, Py_ssize_t i, PyObject *v) -{ - PyBList *overflow; - PyBList *self = (PyBList *) ob; - - if (ob == NULL || !PyList_Check(ob)) { - PyErr_BadInternalCall(); - return -1; - } - - invariants(self, VALID_USER|VALID_RW); - - if (self->n == PY_SSIZE_T_MAX) { - PyErr_SetString(PyExc_OverflowError, - "cannot add more objects to list"); - return _int(0); - } - - if (i < 0) { - i += self->n; - if (i < 0) - i = 0; - } else if (i > self->n) - i = self->n; - - if (self->leaf && self->num_children < LIMIT) { - Py_INCREF(v); - - shift_right(self, i, 1); - self->num_children++; - self->n++; - self->children[i] = v; - return _int(0); - } - - overflow = ins1(self, i, v); - if (overflow) - blist_overflow_root(self, overflow); - ext_mark(self, 0, DIRTY); - - return _int(0); -} - -int PyList_Append(PyObject *ob, PyObject *item) -{ - if (ob == NULL || !PyList_Check(ob)) { - PyErr_BadInternalCall(); - return -1; - } - - return blist_append((PyBList *) ob, item); -} - -PyObject *PyList_GetSlice(PyObject *ob, Py_ssize_t i, Py_ssize_t j) -{ - if (ob == NULL || !PyList_Check(ob)) { - PyErr_BadInternalCall(); - return NULL; - } - - return py_blist_get_slice(ob, i, j); -} - -int PyList_SetSlice(PyObject *ob, Py_ssize_t i, Py_ssize_t j, PyObject *lst) -{ - if (ob == NULL || !PyList_Check(ob)) { - PyErr_BadInternalCall(); - return -1; - } - - return py_blist_ass_slice(ob, i, j, lst); -} - -int PyList_Sort(PyObject *ob) -{ - PyObject *ret; - - if (ob == NULL || !PyList_Check(ob)) { - PyErr_BadInternalCall(); - return -1; - } - - ret = py_blist_sort((PyBListRoot *) ob, NULL, NULL); - if (ret == NULL) - return -1; - - Py_DECREF(ret); - return 0; -} - -int PyList_Reverse(PyObject *ob) -{ - if (ob == NULL || !PyList_Check(ob)) { - PyErr_BadInternalCall(); - return -1; - } - - invariants((PyBList *) ob, VALID_USER|VALID_RW); - - blist_reverse((PyBListRoot *) ob); - - return _int(0); -} - -PyObject *PyList_AsTuple(PyObject *ob) -{ - PyBList *self = (PyBList *) ob; - PyObject *item; - PyTupleObject *tuple; - int i; - - if (ob == NULL || !PyList_Check(ob)) { - PyErr_BadInternalCall(); - return NULL; - } - - invariants(self, VALID_USER | VALID_DECREF); - - DANGER_BEGIN; - tuple = (PyTupleObject *) PyTuple_New(self->n); - DANGER_END; - if (tuple == NULL) - return _ob(NULL); - - i = 0; - ITER(self, item, { - tuple->ob_item[i++] = item; - Py_INCREF(item); - }) - - assert(i == self->n); - - decref_flush(); - - return _ob((PyObject *) tuple); - -} - -PyObject * -_PyList_Extend(PyBListRoot *ob, PyObject *b) -{ - return py_blist_extend((PyBList *) ob, b); -} - -#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6 || PY_MAJOR_VERSION >= 3 -void -PyList_Fini(void) -{ - /* XXX free statically allocated memory here */ -} -#endif - -#endif diff --git a/_btuple.py b/_btuple.py deleted file mode 100644 index 76e1d03..0000000 --- a/_btuple.py +++ /dev/null @@ -1,92 +0,0 @@ -from _blist import blist -from ctypes import c_int -import collections -class btuple(collections.Sequence): - def __init__(self, seq=None): - if isinstance(seq, btuple): - self._blist = seq._blist - elif seq is not None: - self._blist = blist(seq) - else: - self._blist = blist() - self._hash = -1 - - def _btuple_or_tuple(self, other, f): - if isinstance(other, btuple): - rv = f(self._blist, other._blist) - elif isinstance(other, tuple): - rv = f(self._blist, blist(other)) - else: - return NotImplemented - if isinstance(rv, blist): - rv = btuple(rv) - return rv - - def __hash__(self): - # Based on tuplehash from tupleobject.c - if self._hash != -1: - return self._hash - - n = len(self) - mult = c_int(1000003) - x = c_int(0x345678) - for ob in self: - n -= 1 - y = c_int(hash(ob)) - x = (x ^ y) * mult - mult += c_int(82520) + n + n - x += c_int(97531) - if x == -1: - x = -2; - self._hash = x.value - return self._hash - - def __add__(self, other): - rv = self._btuple_or_tuple(other, blist.__add__) - if rv is NotImplemented: - raise TypeError - return rv - def __radd__(self, other): - rv = self._btuple_or_tuple(other, blist.__radd__) - if rv is NotImplemented: - raise TypeError - return rv - def __contains__(self, item): - return item in self._blist - def __eq__(self, other): - return self._btuple_or_tuple(other, blist.__eq__) - def __ge__(self, other): - return self._btuple_or_tuple(other, blist.__ge__) - def __gt__(self, other): - return self._btuple_or_tuple(other, blist.__gt__) - def __le__(self, other): - return self._btuple_or_tuple(other, blist.__le__) - def __lt__(self, other): - return self._btuple_or_tuple(other, blist.__lt__) - def __ne__(self, other): - return self._btuple_or_tuple(other, blist.__ne__) - def __iter__(self): - return iter(self._blist) - def __len__(self): - return len(self._blist) - def __getitem__(self, key): - if isinstance(key, slice): - return btuple(self._blist[key]) - return self._blist[key] - def __getslice__(self, i, j): - return btuple(self._blist[i:j]) - def __repr__(self): - return 'btuple((' + repr(self._blist)[7:-2] + '))' - def __str__(self): - return repr(self) - def __mul__(self, i): - return btuple(self._blist * i) - def __rmul__(self, i): - return btuple(i * self._blist) - def count(self, item): - return self._blist.count(item) - def index(self, item): - return self._blist.index(item) - -del c_int -del collections diff --git a/_sorteddict.py b/_sorteddict.py deleted file mode 100644 index 225b806..0000000 --- a/_sorteddict.py +++ /dev/null @@ -1,142 +0,0 @@ -from _sortedlist import sortedset -import collections, sys -from _blist import blist - -class missingdict(dict): - def __missing__(self, key): - return self._missing(key) - -class KeysView(collections.KeysView, collections.Sequence): - def __getitem__(self, index): - return self._mapping._sortedkeys[index] - def __reversed__(self): - return reversed(self._mapping._sortedkeys) - def index(self, key): - return self._mapping._sortedkeys.index(key) - def count(self, key): - return self._mapping.count(key) - def _from_iterable(cls, it): - return sortedset(key=self._mapping._sortedkeys.key) - def bisect_left(self, key): - return self._mapping._sortedkeys.bisect_left(key) - def bisect_right(self, key): - return self._mapping._sortedkeys.bisect_right(key) - bisect = bisect_right - -class ItemsView(collections.ItemsView, collections.Sequence): - def __getitem__(self, index): - if isinstance(index, slice): - keys = self._mapping._sortedkeys[index] - return self._from_iterable((key, self._mapping[key]) - for key in keys) - key = self._mapping.sortedkeys[index] - return (key, self._mapping[key]) - def index(self, item): - key, value = item - i = self._mapping._sortedkeys.index(key) - if self._mapping[key] == value: - return i - raise ValueError - def count(self, item): - return 1 if item in self else 0 - def _from_iterable(cls, it): - return sortedset(key=lambda item: - self._mapping._sortedkeys.key(item[0])) - -class ValuesView(collections.ValuesView, collections.Sequence): - def __getitem__(self, index): - if isinstance(index, slice): - keys = self._mapping._sortedkeys[index] - rv = sortedset(key=self._mapping._sortedkeys.key) - rv.update(self._mapping[key] for key in keys) - return rv - key = self._mapping.sortedkeys[index] - return self._mapping[key] - -class sorteddict(collections.MutableMapping): - def __init__(self, *args, **kw): - if hasattr(self, '__missing__'): - self._map = missingdict() - self._map._missing = self.__missing__ - else: - self._map = dict() - key = None - if len(args) > 0: - if hasattr(args[0], '__call__'): - key = args[0] - args = args[1:] - elif len(args) > 1: - raise TypeError("'%s' object is not callable" % - args[0].__class__.__name__) - if len(args) > 1: - raise TypeError('sorteddict expected at most 2 arguments, got %d' - % len(args)) - if len(args) == 1 and isinstance(args[0], sorteddict) and key is None: - key = args[0]._sortedkeys._key - self._sortedkeys = sortedset(key=key) - self.update(*args, **kw) - - if sys.version_info[0] < 3: - def keys(self): - return self._sortedkeys.copy() - def items(self): - return blist((key, self[key]) for key in self) - def values(self): - return blist(self[key] for key in self) - def viewkeys(self): - return KeysView(self) - def viewitems(self): - return ItemsView(self) - def viewvalues(self): - return ValuesView(self) - else: - def keys(self): - return KeysView(self) - def items(self): - return ItemsView(self) - def values(self): - return ValuesView(self) - - def __setitem__(self, key, value): - try: - if key not in self._map: - self._sortedkeys.add(key) - self._map[key] = value - except: - if key not in self._map: - self._sortedkeys.discard(key) - raise - - def __delitem__(self, key): - self._sortedkeys.discard(key) - del self._map[key] - - def __getitem__(self, key): - return self._map[key] - - def __iter__(self): - return iter(self._sortedkeys) - - def __len__(self): - return len(self._sortedkeys) - - def copy(self): - return sorteddict(self) - - @classmethod - def fromkeys(cls, keys, value=None, key=None): - if key is not None: - rv = cls(key) - else: - rv = cls() - for key in keys: - rv[key] = value - return rv - - def __repr__(self): - return 'sorteddict(%s)' % repr(self._map) - - def __eq__(self, other): - if not isinstance(other, sorteddict): - return False - return self._map == other._map diff --git a/_sortedlist.py b/_sortedlist.py deleted file mode 100644 index 762d20b..0000000 --- a/_sortedlist.py +++ /dev/null @@ -1,647 +0,0 @@ -from _blist import blist -import collections, bisect, weakref, operator, itertools, sys, threading -try: # pragma: no cover - izip = itertools.izip -except AttributeError: # pragma: no cover - izip = zip - -__all__ = ['sortedlist', 'weaksortedlist', 'sortedset', 'weaksortedset'] - -class ReprRecursion(object): - local = threading.local() - def __init__(self, ob): - if not hasattr(self.local, 'repr_count'): - self.local.repr_count = collections.defaultdict(int) - self.ob_id = id(ob) - self.value = self.ob_id in self.local.repr_count - - def __enter__(self): - self.local.repr_count[self.ob_id] += 1 - return self.value - - def __exit__(self, exc_type, exc_val, exc_tb): - self.local.repr_count[self.ob_id] -= 1 - if self.local.repr_count[self.ob_id] == 0: - del self.local.repr_count[self.ob_id] - return False - -class _sortedbase(collections.Sequence): - def __init__(self, iterable=(), key=None): - self._key = key - if key is not None and not hasattr(key, '__call__'): - raise TypeError("'%s' object is not callable" % str(type(key))) - if ((isinstance(iterable,type(self)) - or isinstance(self,type(iterable))) - and iterable._key is key): - self._blist = blist(iterable._blist) - else: - self._blist = blist() - for v in iterable: - self.add(v) - - def _from_iterable(self, iterable): - return self.__class__(iterable, self._key) - - def _u2key(self, value): - "Convert a user-object to the key" - if self._key is None: - return value - else: - return self._key(value) - - def _u2i(self, value): - "Convert a user-object to the internal representation" - if self._key is None: - return value - else: - return (self._key(value), value) - - def _i2u(self, value): - "Convert an internal object to a user-object" - if self._key is None: - return value - else: - return value[1] - - def _i2key(self, value): - "Convert an internal object to the key" - if self._key is None: - return value - else: - return value[0] - - def _bisect_left(self, v): - """Locate the point in the list where v would be inserted. - - Returns an (i, value) tuple: - - i is the position where v would be inserted - - value is the current user-object at position i - - This is the key function to override in subclasses. They must - accept a user-object v and return a user-object value. - """ - - key = self._u2key(v) - lo = 0 - hi = len(self._blist) - while lo < hi: - mid = (lo+hi)//2 - v = self._i2key(self._blist[mid]) - if v < key: lo = mid + 1 - else: hi = mid - if lo < len(self._blist): - return lo, self._i2u(self._blist[lo]) - return lo, None - - def _bisect_right(self, v): - """Same as _bisect_left, but go to the right of equal values""" - - key = self._u2key(v) - lo = 0 - hi = len(self._blist) - while lo < hi: - mid = (lo+hi)//2 - v = self._i2key(self._blist[mid]) - if key < v: hi = mid - else: lo = mid + 1 - if lo < len(self._blist): - return lo, self._i2u(self._blist[lo]) - return lo, None - - def bisect_left(self, v): - """L.bisect_left(v) -> index - - The return value i is such that all e in L[:i] have e < v, and - all e in a[i:] have e >= v. So if v already appears in the - list, i points just before the leftmost v already there. - """ - return self._bisect_left(v)[0] - - def bisect_right(self, v): - """L.bisect_right(v) -> index - - Return the index where to insert item v in the list. - - The return value i is such that all e in a[:i] have e <= v, - and all e in a[i:] have e > v. So if v already appears in the - list, i points just beyond the rightmost v already there. - """ - return self._bisect_right(v)[0] - - bisect = bisect_right - - def add(self, value): - """Add an element.""" - # Will throw a TypeError when trying to add an object that - # cannot be compared to objects already in the list. - i, _ = self._bisect_right(value) - self._blist.insert(i, self._u2i(value)) - - def discard(self, value): - """Remove an element if it is a member. - - If the element is not a member, do nothing. - - """ - - try: - i, v = self._bisect_left(value) - except TypeError: - # Value cannot be compared with values already in the list. - # Ergo, value isn't in the list. - return - i = self._advance(i, value) - if i >= 0: - del self._blist[i] - - def __contains__(self, value): - """x.__contains__(y) <==> y in x""" - try: - i, v = self._bisect_left(value) - except TypeError: - # Value cannot be compared with values already in the list. - # Ergo, value isn't in the list. - return False - i = self._advance(i, value) - return i >= 0 - - def __len__(self): - """x.__len__() <==> len(x)""" - return len(self._blist) - - def __iter__(self): - """ x.__iter__() <==> iter(x)""" - return (self._i2u(v) for v in self._blist) - - def __getitem__(self, index): - """x.__getitem__(y) <==> x[y]""" - if isinstance(index, slice): - rv = self.__class__() - rv._blist = self._blist[index] - rv._key = self._key - return rv - return self._i2u(self._blist[index]) - - def _advance(self, i, value): - "Do a linear search through all items with the same key" - key = self._u2key(value) - while i < len(self._blist): - if self._i2u(self._blist[i]) == value: - return i - elif key < self._i2key(self._blist[i]): - break - i += 1 - return -1 - - def __reversed__(self): - """L.__reversed__() -- return a reverse iterator over the list""" - return (self._i2u(v) for v in reversed(self._blist)) - - def index(self, value): - """L.index(value) -> integer -- return first index of value. - - Raises ValueError if the value is not present. - - """ - - try: - i, v = self._bisect_left(value) - except TypeError: - raise ValueError - i = self._advance(i, value) - if i >= 0: - return i - raise ValueError - - def count(self, value): - """L.count(value) -> integer -- return number of occurrences of value""" - try: - i, _ = self._bisect_left(value) - except TypeError: - return 0 - key = self._u2key(value) - count = 0 - while True: - i = self._advance(i, value) - if i == -1: - return count - count += 1 - i += 1 - - def pop(self, index=-1): - """L.pop([index]) -> item -- remove and return item at index (default last). - - Raises IndexError if list is empty or index is out of range. - - """ - - rv = self[index] - del self[index] - return rv - - def __delslice__(self, i, j): - """x.__delslice__(i, j) <==> del x[i:j] - - Use of negative indices is not supported. - - """ - del self._blist[i:j] - - def __delitem__(self, i): - """x.__delitem__(y) <==> del x[y]""" - del self._blist[i] - -class _weaksortedbase(_sortedbase): - def _bisect_left(self, value): - key = self._u2key(value) - lo = 0 - hi = len(self._blist) - while lo < hi: - mid = (lo+hi)//2 - n, v = self._squeeze(mid) - hi -= n - if n and hi == len(self._blist): - continue - if self._i2key(self._blist[mid]) < key: lo = mid+1 - else: hi = mid - n, v = self._squeeze(lo) - return lo, v - - def _bisect_right(self, value): - key = self._u2key(value) - lo = 0 - hi = len(self._blist) - while lo < hi: - mid = (lo+hi)//2 - n, v = self._squeeze(mid) - hi -= n - if n and hi == len(self._blist): - continue - if key < self._i2key(self._blist[mid]): hi = mid - else: lo = mid+1 - n, v = self._squeeze(lo) - return lo, v - - _bisect = _bisect_right - - def _u2i(self, value): - if self._key is None: - return weakref.ref(value) - else: - return (self._key(value), weakref.ref(value)) - - def _i2u(self, value): - if self._key is None: - return value() - else: - return value[1]() - - def _i2key(self, value): - if self._key is None: - return value() - else: - return value[0] - - def __iter__(self): - """ x.__iter__() <==> iter(x)""" - i = 0 - while i < len(self._blist): - n, v = self._squeeze(i) - if v is None: break - yield v - i += 1 - - def _squeeze(self, i): - n = 0 - while i < len(self._blist): - v = self._i2u(self._blist[i]) - if v is None: - del self._blist[i] - n += 1 - else: - return n, v - return n, None - - def __getitem__(self, index): - """x.__getitem__(y) <==> x[y]""" - if isinstance(index, slice): - return _sortedbase.__getitem__(self, index) - n, v = self._squeeze(index) - if v is None: - raise IndexError('list index out of range') - return v - - def __reversed__(self): - """L.__reversed__() -- return a reverse iterator over the list""" - i = len(self._blist)-1 - while i >= 0: - n, v = self._squeeze(i) - if not n: - yield v - i -= 1 - - def _advance(self, i, value): - "Do a linear search through all items with the same key" - key = self._u2key(value) - while i < len(self._blist): - n, v = self._squeeze(i) - if v is None: - break - if v == value: - return i - elif key < self._i2key(self._blist[i]): - break - i += 1 - return -1 - -class _listmixin(object): - def remove(self, value): - """L.remove(value) -- remove first occurrence of value. - - Raises ValueError if the value is not present. - """ - - del self[self.index(value)] - - def update(self, iterable): - """L.update(iterable) -- add all elements from iterable into the list""" - - for item in iterable: - self.add(item) - - def __mul__(self, k): - if not isinstance(k, int): - raise TypeError("can't multiply sequence by non-int of type '%s'" - % str(type(int))) - rv = self.__class__() - rv._key = self._key - rv._blist = sum((blist([x])*k for x in self._blist), blist()) - return rv - __rmul__ = __mul__ - - def __imul__(self, k): - if not isinstance(k, int): - raise TypeError("can't multiply sequence by non-int of type '%s'" - % str(type(int))) - self._blist = sum((blist([x])*k for x in self._blist), blist()) - return self - - def __eq__(self, other): - """x.__eq__(y) <==> x==y""" - return self._cmp_op(other, operator.eq) - def __ne__(self, other): - """x.__ne__(y) <==> x!=y""" - return self._cmp_op(other, operator.ne) - def __lt__(self, other): - """x.__lt__(y) <==> x x>y""" - return self._cmp_op(other, operator.gt) - def __le__(self, other): - """x.__le__(y) <==> x<=y""" - return self._cmp_op(other, operator.le) - def __ge__(self, other): - """x.__ge__(y) <==> x>=y""" - return self._cmp_op(other, operator.ge) - -class _setmixin(object): - "Methods that override our base class" - - def add(self, value): - """Add an element to the set. - - This has no effect if the element is already present. - - """ - if value in self: return - super(_setmixin, self).add(value) - - def __iter__(self): - it = super(_setmixin, self).__iter__() - while True: - item = next(it) - n = len(self) - yield item - if n != len(self): - raise RuntimeError('Set changed size during iteration') - -def safe_cmp(f): - def g(self, other): - if not isinstance(other, collections.Set): - raise TypeError("can only compare to a set") - return f(self, other) - return g - -class _setmixin2(collections.MutableSet): - "methods that override or supplement the collections.MutableSet methods" - - __ror__ = collections.MutableSet.__or__ - __rand__ = collections.MutableSet.__and__ - __rxor__ = collections.MutableSet.__xor__ - - if sys.version_info[0] < 3: # pragma: no cover - __lt__ = safe_cmp(collections.MutableSet.__lt__) - __gt__ = safe_cmp(collections.MutableSet.__gt__) - __le__ = safe_cmp(collections.MutableSet.__le__) - __ge__ = safe_cmp(collections.MutableSet.__ge__) - - def __ior__(self, it): - if self is it: - return self - for value in it: - self.add(value) - return self - - def __isub__(self, it): - if self is it: - self.clear() - return self - for value in it: - self.discard(value) - return self - - def __ixor__(self, it): - if self is it: - self.clear() - return self - for value in it: - if value in self: - self.discard(value) - else: - self.add(value) - return self - - def __rsub__(self, other): - return self._from_iterable(other) - self - - def _make_set(self, iterable): - if isinstance(iterable, collections.Set): - return iterable - return self._from_iterable(iterable) - - def difference(self, *args): - """Return a new set with elements in the set that are not in the others.""" - rv = self.copy() - rv.difference_update(*args) - return rv - - def intersection(self, *args): - """Return a new set with elements common to the set and all others.""" - rv = self.copy() - rv.intersection_update(*args) - return rv - - def issubset(self, other): - """Test whether every element in the set is in *other*.""" - return self <= self._make_set(other) - - def issuperset(self, other): - """Test whether every element in *other* is in the set.""" - return self >= self._make_set(other) - - def symmetric_difference(self, other): - """Return a new set with elements in either the set or *other* - but not both.""" - - return self ^ self._make_set(other) - - def union(self, *args): - """Return the union of sets as a new set. - - (i.e. all elements that are in either set.) - - """ - rv = self.copy() - for arg in args: - rv |= self._make_set(arg) - return rv - - def update(self, *args): - """Update the set, adding elements from all others.""" - for arg in args: - self |= self._make_set(arg) - - def difference_update(self, *args): - """Update the set, removing elements found in others.""" - for arg in args: - self -= self._make_set(arg) - - def intersection_update(self, *args): - """Update the set, keeping only elements found in it and all others.""" - for arg in args: - self &= self._make_set(arg) - - def symmetric_difference_update(self, other): - """Update the set, keeping only elements found in either set, - but not in both.""" - - self ^= self._make_set(other) - - def clear(self): - """Remove all elements""" - del self._blist[:] - - def copy(self): - return self[:] - -class sortedlist(_sortedbase, _listmixin): - """sortedlist(iterable=(), key=None) -> new sorted list - - Keyword arguments: - iterable -- items used to initially populate the sorted list - key -- a function to return the sort key of an item - - A sortedlist is indexable like a list, but always keeps its - members in sorted order. - - """ - - def __repr__(self): - """x.__repr__() <==> repr(x)""" - if not self: return 'sortedlist()' - with ReprRecursion(self) as r: - if r: return 'sortedlist(...)' - return ('sortedlist(%s)' % repr(list(self))) - - def _cmp_op(self, other, op): - if not (isinstance(other,type(self)) or isinstance(self,type(other))): - return NotImplemented - if len(self) != len(other): - if op is operator.eq: - return False - if op is operator.ne: - return True - for x, y in izip(self, other): - if x != y: - return op(x, y) - return op in (operator.eq, operator.le, operator.ge) - -class weaksortedlist(_listmixin, _weaksortedbase): - """weaksortedlist(iterable=(), key=None) -> new sorted weak list - - Keyword arguments: - iterable -- items used to initially populate the sorted list - key -- a function to return the sort key of an item - - A weaksortedlist is indexable like a list, but always keeps its - items in sorted order. The weaksortedlist weakly references its - members, so items will be discarded after there is no longer a - strong reference to the item. - - """ - - def __repr__(self): - """x.__repr__() <==> repr(x)""" - if not self: return 'weaksortedlist()' - with ReprRecursion(self) as r: - if r: return 'weaksortedlist(...)' - return 'weaksortedlist(%s)' % repr(list(self)) - - def _cmp_op(self, other, op): - if not (isinstance(other,type(self)) or isinstance(self,type(other))): - return NotImplemented - for x, y in izip(self, other): - if x != y: - return op(x, y) - return op in (operator.eq, operator.le, operator.ge) - -class sortedset(_setmixin, _sortedbase, _setmixin2): - """sortedset(iterable=(), key=None) -> new sorted set - - Keyword arguments: - iterable -- items used to initially populate the sorted set - key -- a function to return the sort key of an item - - A sortedset is similar to a set but is also indexable like a list. - Items are maintained in sorted order. - - """ - - def __repr__(self): - """x.__repr__() <==> repr(x)""" - if not self: return 'sortedset()' - with ReprRecursion(self) as r: - if r: return 'sortedset(...)' - return ('sortedset(%s)' % repr(list(self))) - -class weaksortedset(_setmixin, _weaksortedbase, _setmixin2): - """weaksortedset(iterable=(), key=None) -> new sorted weak set - - Keyword arguments: - iterable -- items used to initially populate the sorted set - key -- a function to return the sort key of an item - - A weaksortedset is similar to a set but is also indexable like a - list. Items are maintained in sorted order. The weaksortedset - weakly references its members, so items will be discarded after - there is no longer a strong reference to the item. - - """ - - def __repr__(self): - """x.__repr__() <==> repr(x)""" - if not self: return 'weaksortedset()' - with ReprRecursion(self) as r: - if r: return 'weaksortedset(...)' - return 'weaksortedset(%s)' % repr(list(self)) diff --git a/blist.egg-info/PKG-INFO b/blist.egg-info/PKG-INFO index a43d00d..2f27158 100644 --- a/blist.egg-info/PKG-INFO +++ b/blist.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: blist -Version: 1.3.4 +Version: 1.3.6 Summary: a list-like type with better asymptotic performance and similar performance on small lists Home-page: http://stutzbachenterprises.com/blist/ Author: Stutzbach Enterprises, LLC @@ -9,7 +9,7 @@ License: BSD Description: blist: a list-like type with better performance =============================================== - The ``blist`` is a drop-in replacement for the Python list the provides + The ``blist`` is a drop-in replacement for the Python list that provides better performance when modifying large lists. The blist package also provides ``sortedlist``, ``sortedset``, ``weaksortedlist``, ``weaksortedset``, ``sorteddict``, and ``btuple`` types. @@ -19,7 +19,7 @@ Description: blist: a list-like type with better performance http://stutzbachenterprises.com/blist-doc/ Python's built-in list is a dynamically-sized array; to insert or - removal an item from the beginning or middle of the list, it has to + remove an item from the beginning or middle of the list, it has to move most of the list in memory, i.e., O(n) operations. The blist uses a flexible, hybrid array/tree structure and only needs to move a small portion of items in memory, specifically using O(log n) @@ -74,7 +74,7 @@ Description: blist: a list-like type with better performance - sortedlist - sortedset - weaksortedlist - - weaksorteset + - weaksortedset - sorteddict - btuple @@ -130,7 +130,7 @@ Description: blist: a list-like type with better performance - Constructing a btuple from a blist takes O(1) time. - Taking a slice of a btuple takes O(n) time, where n is the size of - the original tuple. The size of the slice does not matter. + the original tuple. The size of the slice does not matter. >>> from blist import blist, btuple >>> x = blist([0]) # x is a blist with one element @@ -144,7 +144,7 @@ Description: blist: a list-like type with better performance distribution, the Python header files are also required. In either case, just run: - python setup.py install + python setup.py install If you're running Linux and see a bunch of compilation errors from GCC, you probably do not have the Python header files installed. @@ -159,7 +159,7 @@ Description: blist: a list-like type with better performance If you downloaded the source distribution and wish to run the associated test suite, you can also run: - python setup.py test + python setup.py test which will verify the correct installation and functioning of the package. The tests require Python 2.6 or higher. @@ -178,13 +178,13 @@ Description: blist: a list-like type with better performance In addition to the tests include in the source distribution, we perform the following to add extra rigor to our testing process: - 1. We use a "fuzzer": a program that randomly generates list - operations, performs them using both the blist and the built-in - list, and compares the results. + 1. We use a "fuzzer": a program that randomly generates list + operations, performs them using both the blist and the built-in + list, and compares the results. - 2. We use a modified Python interpreter where we have replaced the - array-based built-in list with the blist. Then, we run all of - the regular Python unit tests. + 2. We use a modified Python interpreter where we have replaced the + array-based built-in list with the blist. Then, we run all of + the regular Python unit tests. Keywords: blist list b+tree btree fast copy-on-write sparse array sortedlist sorted sortedset weak weaksortedlist weaksortedset sorteddict btuple Platform: UNKNOWN diff --git a/blist.egg-info/SOURCES.txt b/blist.egg-info/SOURCES.txt index 0dc5784..b2924a3 100644 --- a/blist.egg-info/SOURCES.txt +++ b/blist.egg-info/SOURCES.txt @@ -1,30 +1,30 @@ LICENSE MANIFEST.in README.rst -_blist.c -_btuple.py -_sorteddict.py -_sortedlist.py -blist.h -blist.py -distribute_setup.py +ez_setup.py setup.py speed_test.py test_blist.py +blist/__init__.py +blist/_blist.c +blist/_btuple.py +blist/_sorteddict.py +blist/_sortedlist.py +blist/blist.h blist.egg-info/PKG-INFO blist.egg-info/SOURCES.txt blist.egg-info/dependency_links.txt blist.egg-info/not-zip-safe blist.egg-info/top_level.txt -prototype/blist.py -test/__init__.py -test/btuple_tests.py -test/list_tests.py -test/mapping_tests.py -test/seq_tests.py -test/sorteddict_tests.py -test/sortedlist_tests.py -test/test_list.py -test/test_set.py -test/test_support.py -test/unittest.py \ No newline at end of file +blist/test/__init__.py +blist/test/btuple_tests.py +blist/test/list_tests.py +blist/test/mapping_tests.py +blist/test/seq_tests.py +blist/test/sorteddict_tests.py +blist/test/sortedlist_tests.py +blist/test/test_list.py +blist/test/test_set.py +blist/test/test_support.py +blist/test/unittest.py +prototype/blist.py \ No newline at end of file diff --git a/blist.egg-info/not-zip-safe b/blist.egg-info/not-zip-safe index d3f5a12..8b13789 100644 --- a/blist.egg-info/not-zip-safe +++ b/blist.egg-info/not-zip-safe @@ -1 +1 @@ - + diff --git a/blist.egg-info/top_level.txt b/blist.egg-info/top_level.txt index 48b0d1f..5d8f43a 100644 --- a/blist.egg-info/top_level.txt +++ b/blist.egg-info/top_level.txt @@ -1,5 +1 @@ -_btuple blist -_sorteddict -_sortedlist -_blist diff --git a/blist.h b/blist.h deleted file mode 100644 index 7476fe1..0000000 --- a/blist.h +++ /dev/null @@ -1,248 +0,0 @@ - -/* List object interface */ - -/* -Another generally useful object type is an list of object pointers. -This is a mutable type: the list items can be changed, and items can be -added or removed. Out-of-range indices or non-list objects are ignored. - -*** WARNING *** PyList_SetItem does not increment the new item's reference -count, but does decrement the reference count of the item it replaces, -if not nil. It does *decrement* the reference count if it is *not* -inserted in the list. Similarly, PyList_GetItem does not increment the -returned item's reference count. -*/ - -/********************************************************************** - * * - * PLEASE READ blist.rst BEFORE MODIFYING THIS CODE * - * * - **********************************************************************/ - -#ifndef Py_BLISTOBJECT_H -#define Py_BLISTOBJECT_H -#ifdef __cplusplus -extern "C" { -#endif - -#if 0 -#define BLIST_IN_PYTHON /* Define if building BList into Python */ -#endif - -/* pyport.h includes similar defines, but they're broken and never use - * "inline" except on Windows :-( */ -#if defined(_MSC_VER) -/* ignore warnings if the compiler decides not to inline a function */ -#pragma warning(disable: 4710) -/* fastest possible local call under MSVC */ -#define BLIST_LOCAL(type) static type __fastcall -#define BLIST_LOCAL_INLINE(type) static __inline type __fastcall -#elif defined(__GNUC__) -#if defined(__i386__) -#define BLIST_LOCAL(type) static type __attribute__((fastcall)) -#define BLIST_LOCAL_INLINE(type) static inline __attribute__((fastcall)) type -#else -#define BLIST_LOCAL(type) static type -#define BLIST_LOCAL_INLINE(type) static inline type -#endif -#else -#define BLIST_LOCAL(type) static type -#define BLIST_LOCAL_INLINE(type) static type -#endif - -#ifndef LIMIT -#define LIMIT (128) /* Good performance value */ -#if 0 -#define LIMIT (8) /* Maximum size, currently low (for test purposes) */ -#endif -#endif -#define HALF (LIMIT/2) /* Minimum size */ -#define MAX_HEIGHT (16) /* ceil(log(PY_SSIZE_T_MAX)/log(HALF)); */ -#if LIMIT & 1 -#error LIMIT must be divisible by 2 -#endif -#if LIMIT < 8 -#error LIMIT must be at least 8 -#endif -#define INDEX_FACTOR (HALF) - -typedef struct PyBList { - PyObject_HEAD - Py_ssize_t n; /* Total # of user-object descendents */ - int num_children; /* Number of immediate children */ - int leaf; /* Boolean value */ - PyObject **children; /* Immediate children */ -} PyBList; - -typedef struct PyBListRoot { - PyObject_HEAD -#define BLIST_FIRST_FIELD n - Py_ssize_t n; /* Total # of user-object descendents */ - int num_children; /* Number of immediate children */ - int leaf; /* Boolean value */ - PyObject **children; /* Immediate children */ - - PyBList **index_list; - Py_ssize_t *offset_list; - unsigned *setclean_list; /* contains index_allocated _bits_ */ - Py_ssize_t index_allocated; - Py_ssize_t *dirty; - Py_ssize_t dirty_length; - Py_ssize_t dirty_root; - Py_ssize_t free_root; - -#ifdef Py_DEBUG - Py_ssize_t last_n; /* For debug */ -#endif -} PyBListRoot; - -#define PyBList_GET_ITEM(op, i) (((PyBList *)(op))->leaf ? (((PyBList *)(op))->children[(i)]) : _PyBList_GET_ITEM_FAST2((PyBListRoot*) (op), (i))) - -/************************************************************************ - * Code used when building BList into the interpreter - */ - -#ifdef BLIST_IN_PYTHON -int PyList_Init1(void); -int PyList_Init2(void); -typedef PyBListRoot PyListObject; - -//PyAPI_DATA(PyTypeObject) PyList_Type; - -PyAPI_DATA(PyTypeObject) PyBList_Type; -PyAPI_DATA(PyTypeObject) PyRootBList_Type; -#define PyList_Type PyRootBList_Type - -#define PyList_Check(op) \ - PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_LIST_SUBCLASS) -#define PyList_CheckExact(op) ((op)->ob_type == &PyRootBList_Type) - -PyAPI_FUNC(PyObject *) PyList_New(Py_ssize_t size); -PyAPI_FUNC(Py_ssize_t) PyList_Size(PyObject *); -PyAPI_FUNC(PyObject *) PyList_GetItem(PyObject *, Py_ssize_t); -PyAPI_FUNC(int) PyList_SetItem(PyObject *, Py_ssize_t, PyObject *); -PyAPI_FUNC(int) PyList_Insert(PyObject *, Py_ssize_t, PyObject *); -PyAPI_FUNC(int) PyList_Append(PyObject *, PyObject *); -PyAPI_FUNC(PyObject *) PyList_GetSlice(PyObject *, Py_ssize_t, Py_ssize_t); -PyAPI_FUNC(int) PyList_SetSlice(PyObject *, Py_ssize_t, Py_ssize_t, PyObject *); -PyAPI_FUNC(int) PyList_Sort(PyObject *); -PyAPI_FUNC(int) PyList_Reverse(PyObject *); -PyAPI_FUNC(PyObject *) PyList_AsTuple(PyObject *); -PyAPI_FUNC(PyObject *) _PyList_Extend(PyBListRoot *, PyObject *); - -PyAPI_FUNC(void) _PyList_SetItemFast(PyObject *, Py_ssize_t, PyObject *); - -/* Macro, trading safety for speed */ -#define PyList_GET_ITEM(op, i) (PyBList_GET_ITEM((op), (i))) -//#define PyList_SET_ITEM(op, i, v) (((PyBList *)(op))->leaf ? (void) (((PyBList *)(op))->children[(i)] = (v)) : (void) _PyList_SetItemFast((PyObject *) (op), (i), (v))) -#define PyList_SET_ITEM(self, i, v) (((PyBList *)self)->leaf ? (void) (((PyBList*)self)->children[(i)] = (v)) : (void) blist_ass_item_return2((PyBListRoot*)(self), (i), (v))) - -//#define PyList_GET_ITEM(op, i) PyList_GetItem((PyObject*) (op), (i)) -//#define PyList_SET_ITEM(op, i, v) _PyList_SetItemFast((PyObject *) (op), (i), (v)) -#define PyList_GET_SIZE(op) ({ assert(PyList_Check(op)); (((PyBList *)(op))->n); }) - -#define PyList_IS_LEAF(op) ({ assert(PyList_Check(op)); (((PyBList *) (op))->leaf); }) - -PyAPI_FUNC(PyObject *) _PyBList_GetItemFast3(PyBListRoot *, Py_ssize_t); - -PyAPI_FUNC(PyObject *) blist_ass_item_return_slow(PyBListRoot *root, Py_ssize_t i, PyObject *v); -PyAPI_FUNC(PyObject *) ext_make_clean_set(PyBListRoot *root, Py_ssize_t i, PyObject *v); -#else -PyObject *_PyBList_GetItemFast3(PyBListRoot *, Py_ssize_t); -PyObject *blist_ass_item_return_slow(PyBListRoot *root, Py_ssize_t i, PyObject *v); -PyObject *ext_make_clean_set(PyBListRoot *root, Py_ssize_t i, PyObject *v); -#endif - -#define INDEX_FACTOR (HALF) - -/* This should only be called if we know the root is not a leaf */ -/* inlining a common case for speed */ -BLIST_LOCAL_INLINE(PyObject *) -_PyBList_GET_ITEM_FAST2(PyBListRoot *root, Py_ssize_t i) -{ - Py_ssize_t ioffset; - Py_ssize_t offset; - PyBList *p; - - assert(!root->leaf); - assert(i >= 0); - assert(i < root->n); - - if (root->dirty_root >= -1 /* DIRTY */) - return _PyBList_GetItemFast3(root, i); - - ioffset = i / INDEX_FACTOR; - offset = root->offset_list[ioffset]; - p = root->index_list[ioffset]; - - if (i < offset + p->n) - return p->children[i - offset]; - ioffset++; - offset = root->offset_list[ioffset]; - p = root->index_list[ioffset]; - return p->children[i - offset]; -} - -#define SETCLEAN_LEN(index_allocated) ((((index_allocated)-1) >> SETCLEAN_SHIFT)+1) -#if SIZEOF_INT == 4 -#define SETCLEAN_SHIFT (5u) -#define SETCLEAN_MASK (0x1fu) -#elif SIZEOF_INT == 8 -#define SETCLEAN_SHIFT (6u) -#define SETCLEAN_MASK (0x3fu) -#else -#error Unknown sizeof(unsigned) -#endif - -#define SET_BIT(setclean_list, i) (setclean_list[(i) >> SETCLEAN_SHIFT] |= (1u << ((i) & SETCLEAN_MASK))) -#define CLEAR_BIT(setclean_list, i) (setclean_list[(i) >> SETCLEAN_SHIFT] &= ~(1u << ((i) & SETCLEAN_MASK))) -#define GET_BIT(setclean_list, i) (setclean_list[(i) >> SETCLEAN_SHIFT] & (1u << ((i) & SETCLEAN_MASK))) - -BLIST_LOCAL_INLINE(PyObject *) -blist_ass_item_return2(PyBListRoot *root, Py_ssize_t i, PyObject *v) -{ - PyObject *rv; - Py_ssize_t offset; - PyBList *p; - Py_ssize_t ioffset = i / INDEX_FACTOR; - - assert(i >= 0); - assert(i < root->n); - assert(!root->leaf); - - if (root->dirty_root >= -1 /* DIRTY */ - || !GET_BIT(root->setclean_list, ioffset)) - return blist_ass_item_return_slow(root, i, v); - - offset = root->offset_list[ioffset]; - p = root->index_list[ioffset]; - assert(i >= offset); - assert(p); - assert(p->leaf); - if (i < offset + p->n) { - good: - /* Py_REFCNT(p) == 1, generally, but see comment in - * blist_ass_item_return_slow for caveats */ - rv = p->children[i - offset]; - p->children[i - offset] = v; - } else if (!GET_BIT(root->setclean_list, ioffset+1)) { - return ext_make_clean_set(root, i, v); - } else { - ioffset++; - assert(ioffset < root->index_allocated); - offset = root->offset_list[ioffset]; - p = root->index_list[ioffset]; - assert(p); - assert(p->leaf); - assert(i < offset + p->n); - - goto good; - } - - return rv; -} - -#ifdef __cplusplus -} -#endif -#endif /* !Py_BLISTOBJECT_H */ diff --git a/blist.py b/blist.py deleted file mode 100644 index efe3ff2..0000000 --- a/blist.py +++ /dev/null @@ -1,8 +0,0 @@ -from _blist import * -import collections -if hasattr(collections, 'MutableSet'): # Only supported in Python 2.6+ - from _sortedlist import sortedlist, sortedset, weaksortedlist, weaksortedset - from _sorteddict import sorteddict - from _btuple import btuple - collections.MutableSequence.register(blist) -del collections diff --git a/blist/__init__.py b/blist/__init__.py new file mode 100644 index 0000000..931aaac --- /dev/null +++ b/blist/__init__.py @@ -0,0 +1,10 @@ +__version__ = '1.3.6' +from blist._blist import * +import collections +if hasattr(collections, 'MutableSet'): # Only supported in Python 2.6+ + from blist._sortedlist import sortedlist, sortedset, weaksortedlist, weaksortedset + from blist._sorteddict import sorteddict + from blist._btuple import btuple + collections.MutableSequence.register(blist) + del _sortedlist, _sorteddict, _btuple +del collections diff --git a/blist/_blist.c b/blist/_blist.c new file mode 100644 index 0000000..f7fcb60 --- /dev/null +++ b/blist/_blist.c @@ -0,0 +1,7741 @@ +/* Copyright 2007-2010 Stutzbach Enterprises, LLC + * daniel@stutzbachenterprises.com + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/********************************************************************** + * * + * PLEASE READ blist.rst BEFORE MODIFYING THIS CODE * + * * + **********************************************************************/ + + +#include +#include + +#if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L +#define restrict +#endif + +#if PY_MAJOR_VERSION == 2 + +#ifndef PY_UINT32_T +#if (defined UINT32_MAX || defined uint32_t) +#define HAVE_UINT32_T 1 +#define PY_UINT32_T uint32_t +#elif defined(MS_WINDOWS) +#if SIZEOF_INT == 4 +#define HAVE_UINT32_T 1 +#define PY_UINT32_T unsigned int +#elif SIZEOF_LONG == 4 +#define HAVE_UINT32_T 1 +#define PY_UINT32_T unsigned long +#endif +#endif +#endif + +#ifndef PY_UINT64_T +#if (defined UINT64_MAX || defined uint64_t) +#define HAVE_UINT64_T 1 +#define PY_UINT64_T uint64_t +#elif defined(MS_WINDOWS) +#if SIZEOF_LONG_LONG == 8 +#define HAVE_UINT64_T 1 +#define PY_UINT64_T unsigned PY_LONG_LONG +#endif +#endif +#endif + +#ifndef PY_INT32_T +#if (defined INT32_MAX || defined int32_t) +#define HAVE_INT32_T 1 +#define PY_INT32_T int32_t +#elif defined(MS_WINDOWS) +#if SIZEOF_INT == 4 +#define HAVE_INT32_T 1 +#define PY_INT32_T int +#elif SIZEOF_LONG == 4 +#define HAVE_INT32_T 1 +#define PY_INT32_T long +#endif +#endif +#endif + +#ifndef PY_INT64_T +#if (defined INT64_MAX || defined int64_t) +#define HAVE_INT64_T 1 +#define PY_INT64_T int64_t +#elif defined(MS_WINDOWS) +#if SIZEOF_LONG_LONG == 8 +#define HAVE_INT64_T 1 +#define PY_INT64_T PY_LONG_LONG +#endif +#endif +#endif + +/* This macro is defined in Python 3. We need it since calling + * PyObject_GC_UnTrack twice is unsafe. */ +/* True if the object is currently tracked by the GC. */ +#define _PyObject_GC_IS_TRACKED(o) \ + ((_Py_AS_GC(o))->gc.gc_refs != _PyGC_REFS_UNTRACKED) + +#if PY_MINOR_VERSION < 6 +/* Backward compatibility with Python 2.5 */ +#define PyUnicode_FromString PyString_FromString +#define Py_REFCNT(ob) (((PyObject*)(ob))->ob_refcnt) +#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type) +#define Py_SIZE(ob) (((PyVarObject*)(ob))->ob_size) +#define PyVarObject_HEAD_INIT(type, size) \ + PyObject_HEAD_INIT(type) size, +#define PyUnicode_FromFormat PyString_FromFormat +#endif + +#elif PY_MAJOR_VERSION == 3 +/* Backward compatibility with Python 3 */ +#define PyInt_FromSsize_t PyLong_FromSsize_t +#define PyInt_CheckExact PyLong_CheckExact +#define PyInt_AsLong PyLong_AsLong +#define PyInt_AsSsize_t PyLong_AsSsize_t +#define PyInt_FromLong PyLong_FromLong +#endif + +#ifndef BLIST_IN_PYTHON +#include "blist.h" +#endif + +#define BLIST_PYAPI(type) static type + +typedef struct { + PyBList *lst; + int i; +} point_t; + +typedef struct { + int depth; + PyBList *leaf; + int i; + point_t stack[MAX_HEIGHT]; +} iter_t; + +typedef struct { + PyObject_HEAD + iter_t iter; +} blistiterobject; + +/* Empty BList reuse scheme to save calls to malloc and free */ +#define MAXFREELISTS 80 +static PyBList *free_lists[MAXFREELISTS]; +static int num_free_lists = 0; + +static PyBList *free_ulists[MAXFREELISTS]; +static int num_free_ulists = 0; + +static blistiterobject *free_iters[MAXFREELISTS]; +static int num_free_iters = 0; + +typedef struct sortwrapperobject +{ + union { + unsigned long k_ulong; +#ifdef BLIST_FLOAT_RADIX_SORT + PY_UINT64_T k_uint64; +#endif + } fkey; + PyObject *key; + PyObject *value; +} sortwrapperobject; + +#define PyBList_Check(op) (PyObject_TypeCheck((op), &PyBList_Type) || (PyObject_TypeCheck((op), &PyRootBList_Type))) +#define PyRootBList_Check(op) (PyObject_TypeCheck((op), &PyRootBList_Type)) +#define PyRootBList_CheckExact(op) (Py_TYPE((op)) == &PyRootBList_Type) +#define PyBList_CheckExact(op) ((op)->ob_type == &PyBList_Type || (op)->ob_type == &PyRootBList_Type) +#define PyBListIter_Check(op) (PyObject_TypeCheck((op), &PyBListIter_Type) || (PyObject_TypeCheck((op), &PyBListReverseIter_Type))) + +#define INDEX_LENGTH(self) (((self)->n-1) / INDEX_FACTOR + 1) + +/************************************************************************ + * Utility functions for copying and moving children. + */ + +/* copy n children from index k2 of other to index k of self */ +BLIST_LOCAL(void) +copy(PyBList *self, int k, PyBList *other, int k2, int n) +{ + PyObject **restrict src = &other->children[k2]; + PyObject **restrict dst = &self->children[k]; + PyObject **stop = &other->children[k2+n]; + + assert(self != other); + + while (src < stop) + *dst++ = *src++; +} + +/* like copy(), but incrementing references */ +BLIST_LOCAL(void) +copyref(PyBList *self, int k, PyBList *other, int k2,int n) { + PyObject **restrict src = &other->children[k2]; + PyObject **restrict dst = &self->children[k]; + PyObject **stop = &src[n]; + + while (src < stop) { + Py_INCREF(*src); + *dst++ = *src++; + } +} + +/* like copy(), but incrementing references but check for NULL */ +BLIST_LOCAL(void) +xcopyref(PyBList *self, int k, PyBList *other,int k2,int n) { + PyObject **restrict src = &other->children[k2]; + PyObject **restrict dst = &self->children[k]; + PyObject **stop = &src[n]; + + while (src < stop) { + Py_XINCREF(*src); + *dst++ = *src++; + } +} + +/* Move children starting at k to the right by n */ +BLIST_LOCAL(void) +shift_right(PyBList *self, int k, int n) +{ + PyObject **src = &self->children[self->num_children-1]; + PyObject **dst = &self->children[self->num_children-1 + n]; + PyObject **stop = &self->children[k]; + + if (self->num_children == 0) + return; + + assert(k >= 0); + assert(k <= LIMIT); + assert(n + self->num_children <= LIMIT); + + while (src >= stop) + *dst-- = *src--; +} + +/* Move children starting at k to the left by n */ +BLIST_LOCAL(void) +shift_left(PyBList *self, int k, int n) +{ + PyObject **src = &self->children[k]; + PyObject **dst = &self->children[k - n]; + PyObject **stop = &self->children[self->num_children]; + + assert(k - n >= 0); + assert(k >= 0); + assert(k <= LIMIT); + assert(self->num_children -n >= 0); + + while (src < stop) + *dst++ = *src++; + +#ifdef Py_DEBUG + while (dst < stop) + *dst++ = NULL; +#endif +} + +BLIST_LOCAL(void) +balance_leafs(PyBList *restrict leaf1, PyBList *restrict leaf2) +{ + assert(leaf1->leaf); + assert(leaf2->leaf); + if (leaf1->num_children + leaf2->num_children <= LIMIT) { + copy(leaf1, leaf1->num_children, leaf2, 0,leaf2->num_children); + leaf1->num_children += leaf2->num_children; + leaf1->n += leaf2->num_children; + leaf2->num_children = 0; + leaf2->n = 0; + } else if (leaf1->num_children < HALF) { + int needed = HALF - leaf1->num_children; + + copy(leaf1, leaf1->num_children, leaf2, 0, needed); + leaf1->num_children += needed; + leaf1->n += needed; + shift_left(leaf2, needed, needed); + leaf2->num_children -= needed; + leaf2->n -= needed; + } else if (leaf2->num_children < HALF) { + int needed = HALF - leaf2->num_children; + + shift_right(leaf2, 0, needed); + copy(leaf2, 0, leaf1, leaf1->num_children-needed, needed); + leaf1->num_children -= needed; + leaf1->n -= needed; + leaf2->num_children += needed; + leaf2->n += needed; + } +} + +/************************************************************************ + * Macros for O(1) iteration over a BList via Depth-First-Search. If + * the root is also a leaf, it will skip the memory allocation and + * just use a for loop. + */ + +/* Iteration over part of the list */ +#define ITER2(lst, item, start, stop, block) {\ + iter_t _it; int _use_iter; \ + if (lst->leaf) { \ + Py_ssize_t _i; _use_iter = 0; \ + for (_i = (start); _i < lst->num_children && _i < (stop); _i++) { \ + item = lst->children[_i]; \ + block; \ + } \ + } else { \ + Py_ssize_t _remaining = (stop) - (start);\ + PyBList *_p; _use_iter = 1;\ + iter_init2(&_it, (lst), (start)); \ + _p = _it.leaf; \ + while (_p != NULL && _remaining--) { \ + if (_it.i < _p->num_children) { \ + item = _p->children[_it.i++]; \ + } else { \ + item = iter_next(&_it); \ + _p = _it.leaf; \ + if (item == NULL) break; \ + } \ + block; \ + } \ + iter_cleanup(&_it); \ + } \ +} + +/* Iteration over the whole list */ +#define ITER(lst, item, block) {\ + if ((lst)->leaf) { \ + iter_t _it; \ + Py_ssize_t _i; const int _use_iter = 0;\ + for (_i = 0; _i < (lst)->num_children; _i++) { \ + item = (lst)->children[_i]; \ + block; \ + } ITER_CLEANUP(); \ + } else { \ + iter_t _it; \ + PyBList *_p; \ + const int _use_iter = 1; \ + iter_init(&_it, (lst)); \ + _p = _it.leaf; \ + while (_p) { \ + if (_it.i < _p->num_children) { \ + item = _p->children[_it.i++]; \ + } else { \ + item = iter_next(&_it); \ + _p = _it.leaf; \ + if (item == NULL) break; \ + } \ + block; \ + } \ + ITER_CLEANUP(); \ + } \ +} + +/* Call this before when leaving the ITER via return or goto. */ +#define ITER_CLEANUP() if (_use_iter) iter_cleanup(&_it) + +/* Forward declarations */ +PyTypeObject PyBList_Type; +PyTypeObject PyRootBList_Type; +PyTypeObject PyBListIter_Type; +PyTypeObject PyBListReverseIter_Type; +static void ext_init(PyBListRoot *root); +static void ext_mark(PyBList *broot, Py_ssize_t offset, int value); +static void ext_mark_set_dirty(PyBList *broot, Py_ssize_t i, Py_ssize_t j); +static void ext_mark_set_dirty_all(PyBList *broot); + +/* also hard-coded in blist.h */ +#define DIRTY (-1) +#define CLEAN (-2) +#define CLEAN_RW (-3) /* Only valid for dirty_root */ + +static PyObject *_indexerr = NULL; +void set_index_error(void) +{ + if (_indexerr == NULL) + _indexerr = PyUnicode_FromString("list index out of range"); + PyErr_SetObject(PyExc_IndexError, _indexerr); +} + +/************************************************************************ + * Debugging forward declarations + */ + +#ifdef Py_DEBUG + +static int blist_unstable = 0; +static int blist_in_code = 0; +static int blist_danger = 0; +#define DANGER_GC_BEGIN { int _blist_unstable = blist_unstable, _blist_in_code = blist_in_code; blist_unstable = 0; blist_in_code = 0; blist_danger++ +#define DANGER_GC_END assert(!blist_unstable); assert(!blist_in_code); blist_unstable = _blist_unstable; blist_in_code = _blist_in_code; assert(blist_danger); blist_danger--; } while (0) +#define DANGER_BEGIN DANGER_GC_BEGIN; assert(!gc_pause_count) +#define DANGER_END assert(!gc_pause_count); DANGER_GC_END + +#else + +#define DANGER_BEGIN while(0) +#define DANGER_END while(0) +#define DANGER_GC_BEGIN while(0) +#define DANGER_GC_END while(0) + +#endif + +/************************************************************************ + * Functions we wish CPython's API provided :-) + */ + +#ifdef Py_DEBUG +static int gc_pause_count = 0; +#endif + +#ifdef BLIST_IN_PYTHON +#define gc_pause() (0) +#define gc_unpause(previous) do {} while (0) +#else +static PyObject * (*pgc_disable)(PyObject *self, PyObject *noargs); +static PyObject * (*pgc_enable)(PyObject *self, PyObject *noargs); +static PyObject * (*pgc_isenabled)(PyObject *self, PyObject *noargs); + +BLIST_LOCAL(void) +gc_unpause(int previous) +{ +#ifdef Py_DEBUG + assert(gc_pause_count > 0); + gc_pause_count--; +#endif + if (previous) { + PyObject *rv = pgc_enable(NULL, NULL); + Py_DECREF(rv); + } +} + +BLIST_LOCAL(int) +gc_pause(void) +{ + int rv; + PyObject *enabled = pgc_isenabled(NULL, NULL); + rv = (enabled == Py_True); + Py_DECREF(enabled); + if (rv) { + PyObject *none = pgc_disable(NULL, NULL); + Py_DECREF(none); + } +#ifdef Py_DEBUG + gc_pause_count++; +#endif + return rv; +} +#endif + +BLIST_LOCAL(int) +do_eq(PyObject *v, PyObject *w) +{ + richcmpfunc f; + PyObject *res; + int rv; + + if (Py_EnterRecursiveCall(" in cmp")) + return -1; + + if (v->ob_type != w->ob_type && + PyType_IsSubtype(w->ob_type, v->ob_type) && + (f = w->ob_type->tp_richcompare) != NULL) { + res = (*f)(w, v, Py_EQ); + if (res != Py_NotImplemented) { + ob_to_int: + if (res == Py_False) + rv = 0; + else if (res == Py_True) + rv = 1; + else if (res == NULL) { + Py_LeaveRecursiveCall(); + return -1; + } else + rv = PyObject_IsTrue(res); + Py_DECREF(res); + Py_LeaveRecursiveCall(); + return rv; + } + Py_DECREF(res); + } + if ((f = v->ob_type->tp_richcompare) != NULL) { + res = (*f)(v, w, Py_EQ); + if (res != Py_NotImplemented) + goto ob_to_int; + Py_DECREF(res); + } + if ((f = w->ob_type->tp_richcompare) != NULL) { + res = (*f)(w, v, Py_EQ); + if (res != Py_NotImplemented) + goto ob_to_int; + Py_DECREF(res); + } + + Py_LeaveRecursiveCall(); +#if PY_MAJOR_VERSION < 3 + rv = PyObject_Compare(v, w); + if (PyErr_Occurred()) + return -1; + if (rv == 0) + return 1; +#endif + return 0; +} + +/* If fast_type == v->ob_type == w->ob_type, then we can assume: + * 1) tp_richcompare != NULL + * 2) tp_richcompare will not recurse + * 3) tp_richcompare(v, w, op) == tp_richcompare(w, v, symmetric_op) + * 4) tp_richcompare(v, w, op) can return only Py_True or Py_False + * + * These assumptions hold for built-in, immutable, non-container types. + */ +static int +fast_eq_richcompare(PyObject *v, PyObject *w, PyTypeObject *fast_type) +{ + if (v == w) return 1; + if (v->ob_type == fast_type && w->ob_type == fast_type) { + PyObject *res = v->ob_type->tp_richcompare(v, w, Py_EQ); + Py_DECREF(res); + + return res == Py_True; + } else { + int rv; + DANGER_BEGIN; + rv = do_eq(v, w); + DANGER_END; + return rv; + } +} + +#if PY_MAJOR_VERSION < 3 +static int +fast_eq_compare(PyObject *v, PyObject *w, PyTypeObject *fast_type) +{ + if (v == w) return 1; + if (v->ob_type == w->ob_type && v->ob_type == fast_type) + return v->ob_type->tp_compare(v, w) == 0; + else { + int rv; + DANGER_BEGIN; + rv = PyObject_RichCompareBool(v, w, Py_EQ); + DANGER_END; + return rv; + } +} +#endif + +static int +fast_lt_richcompare(PyObject *v, PyObject *w, PyTypeObject *fast_type) +{ + if (v->ob_type == w->ob_type && v->ob_type == fast_type) { + PyObject *res = v->ob_type->tp_richcompare(v, w, Py_LT); + Py_DECREF(res); + + return res == Py_True; + } else { + int rv; + DANGER_BEGIN; + rv = PyObject_RichCompareBool(v, w, Py_LT); + DANGER_END; + return rv; + } +} + +#if PY_MAJOR_VERSION < 3 + +static int +fast_lt_compare(PyObject *v, PyObject *w, PyTypeObject *fast_type) +{ + if (v->ob_type == w->ob_type && v->ob_type == fast_type) + return v->ob_type->tp_compare(v, w) < 0; + else { + int rv; + DANGER_BEGIN; + rv = PyObject_RichCompareBool(v, w, Py_LT); + DANGER_END; + return rv; + } +} + +typedef int fast_compare_t(PyObject *v, PyObject *w, PyTypeObject *fast_type); +typedef struct fast_compare_data +{ + PyTypeObject *fast_type; + fast_compare_t *comparer; +} fast_compare_data_t; + +BLIST_LOCAL(fast_compare_data_t) +_check_fast_cmp_type(PyObject *ob, int op) +{ + fast_compare_data_t rv = { NULL, NULL }; + + if (ob->ob_type == &PyInt_Type + || ob->ob_type == &PyLong_Type) { + rv.fast_type = ob->ob_type; + if (op == Py_EQ) + rv.comparer = fast_eq_compare; + else if (op == Py_LT) + rv.comparer = fast_lt_compare; + else + rv.fast_type = NULL; + } else { + if (op == Py_EQ) + rv.comparer = fast_eq_richcompare; + else if (op == Py_LT) + rv.comparer = fast_lt_richcompare; + else + return rv; + + if ((ob->ob_type == &PyComplex_Type && (op == Py_EQ || op == Py_NE)) + || ob->ob_type == &PyFloat_Type + || ob->ob_type == &PyLong_Type + || ob->ob_type == &PyUnicode_Type + || ob->ob_type == &PyString_Type) { + rv.fast_type = ob->ob_type; + } + } + + return rv; +} + +#define check_fast_cmp_type(ob, op) \ + (_check_fast_cmp_type((ob), (op))) +#define fast_eq(v, w, name) \ + (((name).comparer == fast_eq_compare) \ + ? fast_eq_compare((v), (w), (name).fast_type) \ + : fast_eq_richcompare((v), (w), (name).fast_type)) +#define fast_lt(v, w, name) \ + (((name).comparer == fast_lt_compare) \ + ? fast_lt_compare((v), (w), (name).fast_type) \ + : fast_lt_richcompare((v), (w), (name).fast_type)) + +static const fast_compare_data_t no_fast_lt = { NULL, fast_lt_richcompare }; +static const fast_compare_data_t no_fast_eq = { NULL, fast_eq_richcompare }; + +#else + +typedef PyTypeObject *fast_compare_data_t; + +BLIST_LOCAL(fast_compare_data_t) +_check_fast_cmp_type(PyObject *ob, int op) +{ + if ((ob->ob_type == &PyComplex_Type && (op == Py_EQ || op == Py_NE)) + || ob->ob_type == &PyFloat_Type + || ob->ob_type == &PyLong_Type + || ob->ob_type == &PyUnicode_Type + || ob->ob_type == &PyBytes_Type + || ob->ob_type == &PyLong_Type) + return ob->ob_type; + return NULL; +} + +#define check_fast_cmp_type(ob, op) (_check_fast_cmp_type((ob), (op))) + +#define fast_eq(v, w, name) (fast_eq_richcompare((v), (w), (name))) +#define fast_lt(v, w, name) (fast_lt_richcompare((v), (w), (name))) + +#define no_fast_lt (NULL) +#define no_fast_eq (NULL) + +#endif + +/************************************************************************ + * Utility functions for removal of items from a BList + * + * Objects in Python can execute arbitrary code when garbage + * collected, which means they may make calls that modify the BList + * that we're deleting items from. Yuck. + * + * To avoid this in the general case, any function that removes items + * from a BList calls decref_later() on the object instead of + * Py_DECREF(). The objects are accumulated in a global list of + * objects pending for deletion. Just before the function returns to + * the interpreter, decref_flush() is called to actually decrement the + * reference counters. + * + * decref_later() can be passed PyBList objects to delete whole + * subtrees. + */ + +static PyObject **decref_list = NULL; +static Py_ssize_t decref_max = 0; +static Py_ssize_t decref_num = 0; + +#define DECREF_BASE (2*128) + +int decref_init(void) +{ + decref_max = DECREF_BASE; + decref_list = (PyObject **) PyMem_New(PyObject *, decref_max); + if (decref_list == NULL) + return -1; + return 0; +} + +static void _decref_later(PyObject *ob) +{ + if (decref_num == decref_max) { + PyObject **tmp = decref_list; + decref_max *= 2; + + PyMem_Resize(decref_list, PyObject *, decref_max); + if (decref_list == NULL) { + PyErr_NoMemory(); + decref_list = tmp; + decref_max /= 2; + return; + } + } + + decref_list[decref_num++] = ob; +} +#define decref_later(ob) do { if (Py_REFCNT((ob)) > 1) { Py_DECREF((ob)); } else { _decref_later((ob)); } } while (0) + +static void xdecref_later(PyObject *ob) +{ + if (ob == NULL) + return; + + decref_later(ob); +} + +/* Like shift_left(), adding overwritten entries to the decref_later list */ +static void shift_left_decref(PyBList *self, int k, int n) +{ + register PyObject **src = &self->children[k]; + register PyObject **dst = &self->children[k - n]; + register PyObject **stop = &self->children[self->num_children]; + register PyObject **dec; + register PyObject **dst_stop = &self->children[k]; + + if (decref_num + n > decref_max) { + while (decref_num + n > decref_max) + decref_max *= 2; + /* XXX Out of memory not handled */ + PyMem_Resize(decref_list, PyObject *, decref_max); + } + + dec = &decref_list[decref_num]; + + assert(n >= 0); + assert(k - n >= 0); + assert(k >= 0); + assert(k <= LIMIT); + assert(self->num_children - n >= 0); + + while (src < stop && dst < dst_stop) { + if (*dst != NULL) { + if (Py_REFCNT(*dst) > 1) { + Py_DECREF(*dst); + } else { + *dec++ = *dst; + } + } + *dst++ = *src++; + } + + while (src < stop) { + *dst++ = *src++; + } + + while (dst < dst_stop) { + if (*dst != NULL) { + if (Py_REFCNT(*dst) > 1) { + Py_DECREF(*dst); + } else { + *dec++ = *dst; + } + } + dst++; + } + +#ifdef Py_DEBUG + src = &self->children[self->num_children - n]; + while (src < stop) + *src++ = NULL; +#endif + + decref_num += dec - &decref_list[decref_num]; +} + +static void _decref_flush(void) +{ + while (decref_num) { + /* Py_DECREF() can cause arbitrary other oerations on + * BList, potentially even resulting in additional + * calls to decref_later() and decref_flush()! + * + * Any changes to this function must be made VERY + * CAREFULLY to handle this case. + * + * Invariant: whenever we call Py_DECREF, the + * decref_list is in a coherent state. It contains + * only items that still need to be decrefed. + * Furthermore, we can't cache anything about the + * global variables. + */ + + decref_num--; + DANGER_BEGIN; + Py_DECREF(decref_list[decref_num]); + DANGER_END; + } + + if (decref_max > DECREF_BASE) { + /* Return memory to the system now. + * We may never get another chance + */ + + decref_max = DECREF_BASE; + PyMem_Resize(decref_list, PyObject *, decref_max); + } +} + +/* Redefined in debug mode */ +#define decref_flush() (_decref_flush()) +#define SAFE_DECREF(x) Py_DECREF((x)) +#define SAFE_XDECREF(x) Py_XDECREF((x)) + +/************************************************************************ + * Debug functions + */ + +#ifdef Py_DEBUG + +static void check_invariants(PyBList *self) +{ + if (self->leaf) { + assert(self->n == self->num_children); + int i; + + for (i = 0; i < self->num_children; i++) { + PyObject *child = self->children[i]; + if (child != NULL) + assert(Py_REFCNT(child) > 0); + } + } else { + int i; + Py_ssize_t total = 0; + + assert(self->num_children > 0); + + for (i = 0; i < self->num_children; i++) { + assert(PyBList_Check(self->children[i])); + assert(!PyRootBList_Check(self->children[i])); + + PyBList *child = (PyBList *) self->children[i]; + assert(child != self); + total += child->n; + assert(child->num_children <= LIMIT); + assert(HALF <= child->num_children); + /* check_invariants(child); */ + } + assert(self->n == total); + assert(self->num_children > 1 || self->num_children == 0); + + } + + assert (Py_REFCNT(self) >= 1 || Py_TYPE(self) == &PyRootBList_Type + || (Py_REFCNT(self) == 0 && self->num_children == 0)); +} + +#define VALID_RW 1 +#define VALID_PARENT 2 +#define VALID_USER 4 +#define VALID_OVERFLOW 8 +#define VALID_COLLAPSE 16 +#define VALID_DECREF 32 +#define VALID_ROOT 64 + +typedef struct +{ + PyBList *self; + int options; +} debug_t; + +static void debug_setup(debug_t *debug) +{ + Py_INCREF(Py_None); + blist_in_code++; + + assert(PyBList_Check(debug->self)); + + if (debug->options & VALID_DECREF) { + assert(blist_in_code == 1); + assert(debug->options & VALID_USER); + } + +#if 0 + /* Comment this test out since users can get references via + * the gc module */ + if (debug->options & VALID_RW) { + assert(Py_REFCNT(debug->self) == 1 + || PyRootBList_Check(debug->self)); + } +#endif + + if (debug->options & VALID_USER) { + debug->options |= VALID_ROOT; + assert(PyRootBList_Check(debug->self)); + if (!debug->self->leaf) + assert(((PyBListRoot *)debug->self)->last_n + == debug->self->n); + assert(((PyBListRoot *)debug->self)->dirty_length + || ((PyBListRoot *)debug->self)->dirty_root); + + if (!blist_danger) + assert(decref_num == 0); + } + + if (debug->options & VALID_ROOT) { + debug->options |= VALID_PARENT; + + assert(Py_REFCNT(debug->self) >= 1); + } + + if (debug->options & (VALID_USER | VALID_PARENT)) { + check_invariants(debug->self); + } + + if ((debug->options & VALID_USER) && (debug->options & VALID_RW)) { + assert(!blist_unstable); + blist_unstable = 1; + } +} + +static void debug_return(debug_t *debug) +{ + Py_DECREF(Py_None); + assert(blist_in_code); + blist_in_code--; + +#if 0 + /* Comment this test out since users can get references via + * the gc module */ + if (debug->options & VALID_RW) { + assert(Py_REFCNT(debug->self) == 1 + || PyRootBList_Check(debug->self)); + } +#endif + + if (debug->options + & (VALID_PARENT|VALID_USER|VALID_OVERFLOW|VALID_COLLAPSE|VALID_ROOT)) + check_invariants(debug->self); + + if (debug->options & VALID_USER) { + if (!blist_danger) + assert(decref_num == 0); + if (!debug->self->leaf) + assert(((PyBListRoot *)debug->self)->last_n + == debug->self->n); + assert(((PyBListRoot *)debug->self)->dirty_length + || ((PyBListRoot *)debug->self)->dirty_root); + } + + if ((debug->options & VALID_USER) && (debug->options & VALID_RW)) { + assert(blist_unstable); + blist_unstable = 0; + } +} + +static PyObject *debug_return_overflow(debug_t *debug, PyObject *ret) +{ + if (debug->options & VALID_OVERFLOW) { + if (ret == NULL) { + debug_return(debug); + return ret; + } + + assert(PyBList_Check((PyObject *) ret)); + check_invariants((PyBList *) ret); + } + + assert(!(debug->options & VALID_COLLAPSE)); + + debug_return(debug); + + return ret; +} + +static Py_ssize_t debug_return_collapse(debug_t *debug, Py_ssize_t ret) +{ + if (debug->options & VALID_COLLAPSE) + assert (((Py_ssize_t) ret) >= 0); + + assert(!(debug->options & VALID_OVERFLOW)); + + debug_return(debug); + + return ret; +} + +#define invariants(self, options) debug_t _debug = { (PyBList *) (self), (options) }; \ + debug_setup(&_debug) + +#define _blist(ret) (PyBList *) debug_return_overflow(&_debug, (PyObject *) (ret)) +#define _ob(ret) debug_return_overflow(&_debug, (ret)) +#define _int(ret) debug_return_collapse(&_debug, (ret)) +#define _void() do {assert(!(_debug.options & (VALID_OVERFLOW|VALID_COLLAPSE))); debug_return(&_debug);} while (0) +#define _redir(ret) ((debug_return(&_debug), 1) ? (ret) : (ret)) + +#undef Py_RETURN_NONE +#define Py_RETURN_NONE return Py_INCREF(Py_None), _ob(Py_None) + +#undef decref_flush +#define decref_flush() do { assert(_debug.options & VALID_DECREF); _decref_flush(); } while (0) + +static void safe_decref_check(PyBList *self) +{ + int i; + + assert(PyBList_Check((PyObject *) self)); + + if (Py_REFCNT(self) > 1) + return; + + if (self->leaf) { + for (i = 0; i < self->num_children; i++) + assert(self->children[i] == NULL + || Py_REFCNT(self->children[i]) > 1); + return; + } + + for (i = 0; i < self->num_children; i++) + safe_decref_check((PyBList *)self->children[i]); +} + +static void safe_decref(PyBList *self) +{ + assert(PyBList_Check((PyObject *) self)); + safe_decref_check(self); + + DANGER_GC_BEGIN; + Py_DECREF(self); + DANGER_GC_END; +} + +#undef SAFE_DECREF +#undef SAFE_XDECREF +#define SAFE_DECREF(self) (safe_decref((PyBList *)(self))) +#define SAFE_XDECREF(self) if ((self) == NULL) ; else SAFE_DECREF((self)) + +#else /* !Py_DEBUG */ + +#define check_invariants(self) +#define invariants(self, options) +#define _blist(ret) (ret) +#define _ob(ret) (ret) +#define _int(ret) (ret) +#define _void() +#define _redir(ret) (ret) + +#endif + +BLIST_LOCAL(int) +append_and_squish(PyBList **out, int n, PyBList *leaf) +{ + if (n >= 1) { + PyBList *last = out[n-1]; + if (last->num_children + leaf->num_children <= LIMIT) { + copy(last, last->num_children, leaf, 0, + leaf->num_children); + last->num_children += leaf->num_children; + last->n += leaf->num_children; + leaf->num_children = 0; + leaf->n = 0; + } else { + int moved = LIMIT - last->num_children; + copy(last, last->num_children, leaf, 0, moved); + shift_left(leaf, moved, moved); + last->num_children = LIMIT; + last->n = LIMIT; + leaf->num_children -= moved; + leaf->n -= moved; + } + } + if (!leaf->num_children) + SAFE_DECREF(leaf); + else + out[n++] = leaf; + return n; +} + +BLIST_LOCAL(int) +balance_last_2(PyBList **out, int n) +{ + PyBList *last; + + if (n >= 2) + balance_leafs(out[n-2], out[n-1]); + if (n >= 1) { + last = out[n-1]; + if (!last->num_children) { + SAFE_DECREF(last); + n--; + } + } + return n; +} + +/************************************************************************ + * Back to BLists proper. + */ + +/* Creates a new blist for internal use only */ +static PyBList *blist_new(void) +{ + PyBList *self; + + if (num_free_lists) { + self = free_lists[--num_free_lists]; + _Py_NewReference((PyObject *) self); + } else { + DANGER_GC_BEGIN; + self = PyObject_GC_New(PyBList, &PyBList_Type); + DANGER_GC_END; + if (self == NULL) + return NULL; + self->children = PyMem_New(PyObject *, LIMIT); + if (self->children == NULL) { + PyObject_GC_Del(self); + PyErr_NoMemory(); + return NULL; + } + } + + self->leaf = 1; /* True */ + self->num_children = 0; + self->n = 0; + + PyObject_GC_Track(self); + + return self; +} + +static PyBList *blist_new_no_GC(void) +{ + PyBList *self = blist_new(); + PyObject_GC_UnTrack(self); + return self; +} + +/* Creates a blist for user use */ +static PyBList *blist_root_new(void) +{ + PyBList *self; + + if (num_free_ulists) { + self = free_ulists[--num_free_ulists]; + _Py_NewReference((PyObject *) self); + } else { + DANGER_GC_BEGIN; + self = (PyBList *) PyObject_GC_New(PyBListRoot, &PyRootBList_Type); + DANGER_GC_END; + if (self == NULL) + return NULL; + self->children = PyMem_New(PyObject *, LIMIT); + if (self->children == NULL) { + PyObject_GC_Del(self); + PyErr_NoMemory(); + return NULL; + } + } + + self->leaf = 1; /* True */ + self->n = 0; + self->num_children = 0; + + ext_init((PyBListRoot *) self); + + PyObject_GC_Track(self); + + return self; +} + +/* Remove links to some of our children, decrementing their refcounts */ +static void blist_forget_children2(PyBList *self, int i, int j) +{ + int delta = j - i; + + invariants(self, VALID_RW); + + shift_left_decref(self, j, delta); + self->num_children -= delta; + + _void(); +} + +/* Remove links to all children */ +#define blist_forget_children(self) \ + (blist_forget_children2((self), 0, (self)->num_children)) + +/* Remove link to one child */ +#define blist_forget_child(self, i) \ + (blist_forget_children2((self), (i), (i)+1)) + +/* Version for internal use defers Py_DECREF calls */ +static int blist_CLEAR(PyBList *self) +{ + invariants(self, VALID_RW|VALID_PARENT); + + blist_forget_children(self); + self->n = 0; + self->leaf = 1; + + return _int(0); +} + +/* Make self into a copy of other */ +BLIST_LOCAL(void) +blist_become(PyBList *restrict self, PyBList *restrict other) +{ + invariants(self, VALID_RW); + assert(self != other); + + Py_INCREF(other); /* "other" may be one of self's children */ + blist_forget_children(self); + self->n = other->n; + xcopyref(self, 0, other, 0, other->num_children); + self->num_children = other->num_children; + self->leaf = other->leaf; + + SAFE_DECREF(other); + _void(); +} + +/* Make self into a copy of other and empty other */ +BLIST_LOCAL(void) +blist_become_and_consume(PyBList *restrict self, PyBList *restrict other) +{ + PyObject **tmp; + + invariants(self, VALID_RW); + assert(self != other); + assert(Py_REFCNT(other) == 1 || PyRootBList_Check(other)); + + Py_INCREF(other); + blist_forget_children(self); + tmp = self->children; + self->children = other->children; + self->n = other->n; + self->num_children = other->num_children; + self->leaf = other->leaf; + + other->children = tmp; + other->n = 0; + other->num_children = 0; + other->leaf = 1; + + SAFE_DECREF(other); + _void(); +} + +/* Create a copy of self */ +static PyBList *blist_copy(PyBList *restrict self) +{ + PyBList *copy; + + copy = blist_new(); + if (!copy) return NULL; + blist_become(copy, self); + return copy; +} + +/* Create a copy of self, which is a root node */ +static PyBList *blist_root_copy(PyBList *restrict self) +{ + PyBList *copy; + + copy = blist_root_new(); + if (!copy) return NULL; + blist_become(copy, self); + ext_mark(copy, 0, DIRTY); + ext_mark_set_dirty_all(self); + return copy; +} + +/************************************************************************ + * Useful internal utility functions + */ + +/* We are searching for the child that contains leaf element i. + * + * Returns a 3-tuple: (the child object, our index of the child, + * the number of leaf elements before the child) + */ +static void blist_locate(PyBList *self, Py_ssize_t i, + PyObject **child, int *idx, Py_ssize_t *before) +{ + invariants(self, VALID_PARENT); + assert (!self->leaf); + + if (i <= self->n/2) { + /* Search from the left */ + Py_ssize_t so_far = 0; + int k; + for (k = 0; k < self->num_children; k++) { + PyBList *p = (PyBList *) self->children[k]; + if (i < so_far + p->n) { + *child = (PyObject *) p; + *idx = k; + *before = so_far; + _void(); + return; + } + so_far += p->n; + } + } else { + /* Search from the right */ + Py_ssize_t so_far = self->n; + int k; + for (k = self->num_children-1; k >= 0; k--) { + PyBList *p = (PyBList *) self->children[k]; + so_far -= p->n; + if (i >= so_far) { + *child = (PyObject *) p; + *idx = k; + *before = so_far; + _void(); + return; + } + } + } + + /* Just append */ + *child = self->children[self->num_children-1]; + *idx = self->num_children-1; + *before = self->n - ((PyBList *)(*child))->n; + + _void(); +} + +/* Find the current height of the tree. + * + * We could keep an extra few bytes in each node rather than + * figuring this out dynamically, which would reduce the + * asymptotic complexitiy of a few operations. However, I + * suspect it's not worth the extra overhead of updating it all + * over the place. + */ +BLIST_LOCAL(int) +blist_get_height(PyBList *self) +{ + invariants(self, VALID_PARENT); + if (self->leaf) + return _int(1); + return _int(blist_get_height((PyBList *) + self->children[self->num_children - 1]) + + 1); +} + +BLIST_LOCAL(PyBList *) +blist_prepare_write(PyBList *self, int pt) +{ + /* We are about to modify the child at index pt. Prepare it. + * + * This function returns the child object. If the caller has + * other references to the child, they must be discarded as they + * may no longer be valid. + * + * If the child's .refcount is 1, we simply return the + * child object. + * + * If the child's .refcount is greater than 1, we: + * + * - copy the child object + * - decrement the child's .refcount + * - replace self.children[pt] with the copy + * - return the copy + */ + + invariants(self, VALID_RW); + assert(!self->leaf); + + if (pt < 0) + pt += self->num_children; + if (Py_REFCNT(self->children[pt]) > 1) { + PyBList *new_copy = blist_new(); + if (!new_copy) return NULL; + blist_become(new_copy, (PyBList *) self->children[pt]); + SAFE_DECREF(self->children[pt]); + self->children[pt] = (PyObject *) new_copy; + } + + return (PyBList *) _ob(self->children[pt]); +} + +/* Macro version assumes that pt is non-negative */ +#define blist_PREPARE_WRITE(self, pt) (Py_REFCNT((self)->children[(pt)]) > 1 ? blist_prepare_write((self), (pt)) : (PyBList *) (self)->children[(pt)]) + +/* Recompute self->n */ +BLIST_LOCAL(void) +blist_adjust_n(PyBList *restrict self) +{ + int i; + + invariants(self, VALID_RW); + + if (self->leaf) { + self->n = self->num_children; + _void(); + return; + } + self->n = 0; + for (i = 0; i < self->num_children; i++) + self->n += ((PyBList *)self->children[i])->n; + + _void(); +} + +/* Non-default constructor. Create a node with specific children. + * + * We steal the reference counters from the caller. + */ +static PyBList *blist_new_sibling(PyBList *sibling) +{ + PyBList *restrict self = blist_new(); + if (!self) return NULL; + assert(sibling->num_children == LIMIT); + copy(self, 0, sibling, HALF, HALF); + self->leaf = sibling->leaf; + self->num_children = HALF; + sibling->num_children = HALF; + blist_adjust_n(self); + return self; +} + +/************************************************************************ + * Bit twiddling utility function + */ + +/* Return the highest set bit. E.g., for 0x00845894 return 0x00800000 */ +static unsigned highest_set_bit_slow(unsigned x) +{ + unsigned rv = 0; + unsigned mask; + for (mask = 0x1; mask; mask <<= 1) + if (mask & x) rv = mask; + return rv; +} + +#if SIZEOF_INT == 4 +static unsigned highest_set_bit_table[256]; + +static void highest_set_bit_init(void) +{ + unsigned i; + for (i = 0; i < 256; i++) + highest_set_bit_table[i] = highest_set_bit_slow(i); +} + +static unsigned highest_set_bit(unsigned v) +{ + register unsigned tt, t; + + if ((tt = v >> 16)) + return (t = tt >> 8) ? highest_set_bit_table[t] << 24 + : highest_set_bit_table[tt] << 16; + else + return (t = v >> 8) ? highest_set_bit_table[t] << 8 + : highest_set_bit_table[v]; +} +#else +#define highest_set_bit_init() () +#define highest_set_bit(v) (highest_set_bit_slow((v))) +#endif + +/************************************************************************ + * Functions for the index extension used by the root node + */ + +/* Initialize the index extension. Does not allocate any memory */ +static void ext_init(PyBListRoot *root) +{ + root->index_list = NULL; + root->offset_list = NULL; + root->setclean_list = NULL; + root->index_allocated = 0; + root->dirty = NULL; + root->dirty_length = 0; + root->dirty_root = DIRTY; + root->free_root = -1; + +#ifdef Py_DEBUG + root->last_n = root->n; +#endif +} + +/* Deallocate any memory used by the index extension */ +static void ext_dealloc(PyBListRoot *root) +{ + if (root->index_list) PyMem_Free(root->index_list); + if (root->offset_list) PyMem_Free(root->offset_list); + if (root->setclean_list) PyMem_Free(root->setclean_list); + if (root->dirty) PyMem_Free(root->dirty); + ext_init(root); +} + +/* Find or create a new free node in "dirty" and return an index to it. + * amortized O(1) */ +static Py_ssize_t ext_alloc(PyBListRoot *root) +{ + Py_ssize_t i, parent; + + if (root->free_root < 0) { + int newl; + int i; + + if (!root->dirty) { + newl = 32; + root->dirty = PyMem_New(Py_ssize_t, newl); + root->dirty_root = DIRTY; + if (!root->dirty) return -1; + } else { + void *tmp; + + assert(root->dirty_length > 0); + newl = root->dirty_length*2; + tmp = root->dirty; + PyMem_Resize(tmp, Py_ssize_t, newl); + if (!tmp) { + PyMem_Free(root->dirty); + root->dirty = NULL; + root->dirty_root = DIRTY; + return -1; + } + root->dirty = tmp; + } + + for (i = root->dirty_length; i < newl; i += 2) { + root->dirty[i] = i+2; + root->dirty[i+1] = -1; + } + root->dirty[newl-2] = -1; + root->free_root = root->dirty_length; + root->dirty_length = newl; + assert(root->free_root >= 0); + assert(root->free_root+1 < root->dirty_length); + } + + /* Depth-first search for a node with fewer than 2 children. + * Guaranteed to terminate in O(log n) since any leaf node + * will suffice. + */ + + i = root->free_root; + parent = -1; + assert(i >= 0); + assert(i+1 < root->dirty_length); + while (root->dirty[i] >= 0 && root->dirty[i+1] >= 0) { + assert(0); + assert(i >= 0); + assert(i+1 < root->dirty_length); + parent = i; + i = root->dirty[i]; + } + + /* At this point, "i" is the node to be alloced. "parent" is + * the node containing a pointer to "i" or -1 if free_root + * points to "i" + * + * parent's pointer to i is always the left-hand pointer + * + * i has at most one child + */ + + if (parent < 0) { + if (root->dirty[i] >= 0) + root->free_root = root->dirty[i]; + else + root->free_root = root->dirty[i+1]; + } else { + if (root->dirty[i] >= 0) + root->dirty[parent] = root->dirty[i]; + else + root->dirty[parent] = root->dirty[i+1]; + } + + assert(i >= 0); + assert(i+1 < root->dirty_length); + return i; +} + +/* Add each node in the tree rooted at loc to the free tree */ +/* Amortized O(1), since each node to be freed corresponds with + * an earlier lookup. Unamortized worst-case O(n)*/ +static void ext_free(PyBListRoot *root, Py_ssize_t loc) +{ + assert(loc >= 0); + assert(loc+1 < root->dirty_length); + if (root->dirty[loc] >= 0) + ext_free(root, root->dirty[loc]); + if (root->dirty[loc+1] >= 0) + ext_free(root, root->dirty[loc+1]); + + root->dirty[loc] = root->free_root; + root->dirty[loc+1] = -1; + root->free_root = loc; + assert(root->free_root >= 0); + assert(root->free_root+1 < root->dirty_length); +} + +BLIST_LOCAL(void) +ext_mark_r(PyBListRoot *root, Py_ssize_t offset, Py_ssize_t i, + int bit, int value) +{ + Py_ssize_t j, next; + + if (!(offset & bit)) { + /* Take left fork */ + + if (value == DIRTY) { + /* Mark right fork dirty */ + assert(i >= 0 && i+1 < root->dirty_length); + if (root->dirty[i+1] >= 0) + ext_free(root, root->dirty[i+1]); + root->dirty[i+1] = DIRTY; + } + next = i; + } else { + /* Take right fork */ + next = i+1; + } + + assert(next >= 0 && next < root->dirty_length); + + j = root->dirty[next]; + + if (j == value) + return; + + if (bit == 1) { + root->dirty[next] = value; + return; + } + + if (j < 0) { + Py_ssize_t nvalue = j; + Py_ssize_t tmp; + tmp = ext_alloc(root); + if (tmp < 0) { + ext_dealloc(root); + return; + } + root->dirty[next] = tmp; + j = root->dirty[next]; + assert(j >= 0); + assert(j+1 < root->dirty_length); + root->dirty[j] = nvalue; + root->dirty[j+1] = nvalue; + } + + ext_mark_r(root, offset, j, bit >> 1, value); + + if (root->dirty + && (root->dirty[j] == root->dirty[j+1] + || (root->dirty[j] < 0 + && (((offset | (bit>>1)) & ~((bit>>1)-1)) + > (root->n-1) /INDEX_FACTOR)))) { + /* Both the same? Consolidate */ + ext_free(root, j); + root->dirty[next] = value; + } +} + +/* If "value" is CLEAN, mark the list clean at exactly "offset". + * If "value" is DIRTY, mark the list dirty for all >= "offset". + */ +static void ext_mark(PyBList *broot, Py_ssize_t offset, int value) +{ + int bit; + + PyBListRoot *root = (PyBListRoot*) broot; + if (!root->n) { +#ifdef Py_DEBUG + root->last_n = root->n; +#endif + return; + } + if ((!offset && value == DIRTY) || root->n <= INDEX_FACTOR) { + if (root->dirty_root >= 0) + ext_free(root, root->dirty_root); + root->dirty_root = DIRTY; +#ifdef Py_DEBUG + root->last_n = root->n; +#endif + return; + } + +#ifdef Py_DEBUG + assert(root->last_n == root->n); +#endif + + if (root->dirty_root == value) return; + + if (root->dirty_root < 0) { + Py_ssize_t nvalue = root->dirty_root; + root->dirty_root = ext_alloc(root); + if (root->dirty_root < 0) { + ext_dealloc(root); + return; + } + assert(root->dirty_root >= 0); + assert(root->dirty_root+1 < root->dirty_length); + root->dirty[root->dirty_root] = nvalue; + root->dirty[root->dirty_root+1] = nvalue; + } + offset /= INDEX_FACTOR; + + bit = highest_set_bit((root->n-1) / INDEX_FACTOR); + ext_mark_r(root, offset, root->dirty_root, bit, value); + if (root->dirty && + (root->dirty[root->dirty_root] ==root->dirty[root->dirty_root+1])){ + ext_free(root, root->dirty_root); + root->dirty_root = value; + } +} + +/* Mark a section of the list dirty for set operations */ +static void ext_mark_set_dirty(PyBList *broot, Py_ssize_t i, Py_ssize_t j) +{ + /* XXX We could set only the values in the setclean_list, but + * that takes O(n) time. Marking the nodes dirty for reading + * takes O(log n) time but will slow down future reads. + * Ideally there'd be a separate index_list for set + * operations */ + ext_mark(broot, i, DIRTY); +} + +/* Mark an entire list dirty for set operations */ +static void ext_mark_set_dirty_all(PyBList *broot) +{ + ext_mark_set_dirty(broot, 0, broot->n); +} + +#if 0 +/* These functions are unused, but useful for debugging. Do not remove. */ + +static void ext_print_r(PyBListRoot *root, Py_ssize_t i) +{ + printf("("); + if (root->dirty[i] < 0) + printf("%d", root->dirty[i]); + else + ext_print_r(root, root->dirty[i]); + printf(","); + if (root->dirty[i+1] < 0) + printf("%d", root->dirty[i+1]); + else + ext_print_r(root, root->dirty[i+1]); + printf(")"); +} + +static void ext_print(PyBListRoot *root) +{ + if (root->dirty_root < 0) + printf("%d", root->dirty_root); + else + ext_print_r(root, root->dirty_root); + printf("\n"); +} +#endif + +/* Find an arbitrary DIRTY node in "dirty" at or below node "i" which + * corresponds with "offset" into the list. "bit" corresponds with + * the bit tested to determine the right or left child of "i". + * Returns the offset corresponding with the DIRTY node + */ +static Py_ssize_t +ext_find_dirty(PyBListRoot *root, Py_ssize_t offset, int bit, Py_ssize_t i) +{ + assert(root->dirty); + assert(i >= 0); + assert(bit); + + if (root->dirty[i] == DIRTY) + return offset; + if (root->dirty[i] >= 0) + return ext_find_dirty(root, offset, bit >> 1, root->dirty[i]); + + if (root->dirty[i+1] == DIRTY) + return offset | bit; + assert(root->dirty[i+1] >= 0); + return ext_find_dirty(root, offset | bit, bit >> 1, root->dirty[i+1]); +} + +/* Determine if "offset" is DIRTY, returning a Boolean value. Sets + * "dirty_offset" to an arbitrary DIRTY node located along the way, + * even if the requested offset is CLEAN. "dirty_offset" will be set + * to -1 if there are no DIRTY nodes. Worst-case O(log n) + */ +static int +ext_is_dirty(PyBListRoot *root, Py_ssize_t offset, Py_ssize_t *dirty_offset) +{ + Py_ssize_t i, parent; + int bit; + + if (root->dirty == NULL || root->dirty_root < 0) { + *dirty_offset = -1; + return root->dirty_root == DIRTY; + } + i = root->dirty_root; + parent = -1; + offset /= INDEX_FACTOR; + bit = highest_set_bit((root->n-1) / INDEX_FACTOR); + +#ifdef Py_DEBUG + assert(root->last_n == root->n); +#endif + + do { + assert(bit); + parent = i; + if (!(offset & bit)) { + assert (i >= 0 && i < root->dirty_length); + i = root->dirty[i]; + } else { + assert (i >= 0 && i+1 < root->dirty_length); + i = root->dirty[i+1]; + } + bit >>= 1; + } while (i >= 0); + + if (i != DIRTY) { + if (!bit) bit = 1; else bit <<= 1; + *dirty_offset = INDEX_FACTOR * + ext_find_dirty(root, (offset ^ bit) & ~(bit-1), bit, + parent); + assert(*dirty_offset >= 0); + assert(*dirty_offset < root->n); + } + + return i == DIRTY; +} + +/* The length of the BList may have changed. Adjust the lengths of + * the extension data structures as needed */ +static int +ext_grow_index(PyBListRoot *root) +{ + Py_ssize_t oldl = root->index_allocated; + if (!root->index_allocated) { + if (root->index_list) PyMem_Free(root->index_list); + if (root->offset_list) PyMem_Free(root->offset_list); + if (root->setclean_list) PyMem_Free(root->setclean_list); + + root->index_list = NULL; + root->offset_list = NULL; + root->setclean_list = NULL; + + root->index_allocated = (root->n-1) / INDEX_FACTOR + 1; + root->index_list = PyMem_New(PyBList *, root->index_allocated); + if (!root->index_list) { + fail: + root->index_allocated = oldl; + return -1; + } + root->offset_list = PyMem_New(Py_ssize_t, root->index_allocated); + if (!root->offset_list) goto fail; + root->setclean_list + = PyMem_New(unsigned,SETCLEAN_LEN(root->index_allocated)); + if (!root->setclean_list) goto fail; + } else { + void *tmp; + + do { + root->index_allocated *= 2; + } while ((root->n-1) / INDEX_FACTOR + 1 > root->index_allocated); + tmp = root->index_list; + PyMem_Resize(tmp, PyBList *, root->index_allocated); + if (!tmp) goto fail; + root->index_list = tmp; + + tmp = root->offset_list; + PyMem_Resize(tmp, Py_ssize_t, root->index_allocated); + if (!tmp) goto fail; + root->offset_list = tmp; + + tmp = root->setclean_list; + PyMem_Resize(tmp, unsigned, SETCLEAN_LEN(root->index_allocated)); + if (!tmp) goto fail; + root->setclean_list = tmp; + } + return 0; +} + +#define SET_OK_NO 0 +#define SET_OK_YES 1 +#define SET_OK_ALL 2 + +BLIST_LOCAL(void) +ext_index_r(PyBListRoot *root, PyBList *self, Py_ssize_t i, int set_ok) +{ + int j; + if (self != (PyBList *)root) { + assert(!(set_ok == SET_OK_ALL && Py_REFCNT(self) != 1)); + set_ok = set_ok && (Py_REFCNT(self) == 1); + } + + if (self->leaf) { + Py_ssize_t ioffset = i / INDEX_FACTOR; + if (ioffset * INDEX_FACTOR < i) ioffset++; + do { + assert(ioffset < root->index_allocated); + root->index_list[ioffset] = self; + root->offset_list[ioffset] = i; + if (set_ok != SET_OK_ALL) { + if (Py_REFCNT(self) > 1 || !set_ok) + CLEAR_BIT(root->setclean_list,ioffset); + else + SET_BIT(root->setclean_list, ioffset); + } + } while (++ioffset * INDEX_FACTOR < i + self->n); + i += self->n; + } else { + for (j = 0; j < self->num_children; j++) { + PyBList *child = (PyBList *) self->children[j]; + ext_index_r(root, child, i, set_ok); + i += child->n; + } + } +} + +BLIST_LOCAL(void) +ext_index_all_dirty_r(PyBListRoot *root, PyBList *self, Py_ssize_t end, + Py_ssize_t child_index, Py_ssize_t child_n, int set_ok) +{ + Py_ssize_t i; + for (i = child_index; i < self->num_children && child_n < end; i++) { + PyBList *child = (PyBList *) self->children[i]; + ext_index_r(root, child, child_n, set_ok); + child_n += child->n; + } +} + +BLIST_LOCAL(void) +ext_index_all_r(PyBListRoot *root, + Py_ssize_t dirty_index, Py_ssize_t dirty_offset, Py_ssize_t dirty_length, + PyBList *self, Py_ssize_t child_index, Py_ssize_t child_n, + int set_ok) +{ + if (dirty_index <= CLEAN) { + return; + } else if (dirty_index == DIRTY) { + ext_index_all_dirty_r(root, self, dirty_offset + dirty_length, + child_index, child_n, set_ok); + return; + } + + if (!self->leaf) { + while (child_index < self->num_children) { + PyBList *child = (PyBList *) self->children[child_index]; + if (child_n + child->n > dirty_offset) + break; + child_n += child->n; + child_index++; + } + + if (child_index+1 == self->num_children + || (((PyBList *)self->children[child_index])->n + child_n + <= dirty_offset + dirty_length)) { + self = (PyBList *) self->children[child_index]; + child_index = 0; + } + } + + dirty_length /= 2; + ext_index_all_r(root, + root->dirty[dirty_index], dirty_offset, dirty_length, + self, child_index, child_n, set_ok); + dirty_offset += dirty_length; + ext_index_all_r(root, + root->dirty[dirty_index+1], dirty_offset, dirty_length, + self, child_index, child_n, set_ok); +} + +/* Make everything clean in O(n) time. Any operation that alters the + * list and already takes Omega(n) time should call one of these functions. + */ +BLIST_LOCAL(void) +_ext_index_all(PyBListRoot *root, int set_ok_all) +{ + Py_ssize_t ioffset_max = (root->n-1) / INDEX_FACTOR + 1; + int set_ok; + + if (root->index_allocated < ioffset_max) + ext_grow_index(root); + if (set_ok_all) { + set_ok = SET_OK_ALL; + memset(root->setclean_list, 255, + SETCLEAN_LEN(root->index_allocated) * sizeof(unsigned)); + } else + set_ok = SET_OK_YES; + + ext_index_all_r(root, root->dirty_root, 0, + highest_set_bit((root->n-1)) * 2, + (PyBList*)root, 0, 0, set_ok); + +#ifdef Py_DEBUG + root->last_n = root->n; +#endif + if (root->dirty_root >= 0) + ext_free(root, root->dirty_root); + root->dirty_root = set_ok_all ? CLEAN_RW : CLEAN; +} +#define ext_index_all(root) do { if (!(root)->leaf) _ext_index_all((root), 0); } while (0) +#define ext_index_set_all(root) do { if (!(root)->leaf) _ext_index_all((root), 1); } while (0) + +BLIST_LOCAL_INLINE(void) +_ext_reindex_all(PyBListRoot *root, int set_ok_all) +{ + if (root->dirty_root >= 0) + ext_free(root, root->dirty_root); + root->dirty_root = DIRTY; + + _ext_index_all(root, set_ok_all); +} + +#define ext_reindex_all(root) do { if (!(root)->leaf) _ext_reindex_all((root), 0); } while (0) +#define ext_reindex_set_all(root) do { if (!(root)->leaf) _ext_reindex_all((root), 1); } while (0) + +/* We found a particular node at a certain offset. Add it to the + * index and mark it clean. */ +BLIST_LOCAL(void) +ext_mark_clean(PyBListRoot *root, Py_ssize_t offset, PyBList *p, int setclean) +{ + Py_ssize_t ioffset = offset / INDEX_FACTOR; + + assert(offset < root->n); + + while (ioffset * INDEX_FACTOR < offset) + ioffset++; + for (;ioffset * INDEX_FACTOR < offset + p->n; ioffset++) { + ext_mark((PyBList*)root, ioffset * INDEX_FACTOR, CLEAN); + + if (ioffset >= root->index_allocated) { + int err = ext_grow_index(root); + if (err < -1) { + ext_dealloc(root); + return; + } + } + + assert(ioffset >= 0); + assert(ioffset < root->index_allocated); + root->index_list[ioffset] = p; + root->offset_list[ioffset] = offset; + + if (setclean) + SET_BIT(root->setclean_list, ioffset); + else + CLEAR_BIT(root->setclean_list, ioffset); + } +} + +/* Lookup the node at offset i and mark it clean */ +static PyObject *ext_make_clean(PyBListRoot *root, Py_ssize_t i) +{ + PyObject *rv; + Py_ssize_t so_far; + Py_ssize_t offset = 0; + PyBList *p = (PyBList *)root; + Py_ssize_t j = i; + int k; + int setclean = 1; + do { + blist_locate(p, j, (PyObject **) &p, &k, &so_far); + if (Py_REFCNT(p) > 1) + setclean = 0; + offset += so_far; + j -= so_far; + } while (!p->leaf); + + rv = p->children[j]; + ext_mark_clean(root, offset, p, setclean); + return rv; +} + +/************************************************************************ + * Functions for manipulating the tree + */ + +/* Child k has underflowed. Borrow from k+1 */ +static void blist_borrow_right(PyBList *self, int k) +{ + PyBList *restrict p = (PyBList *) self->children[k]; + PyBList *restrict right; + unsigned total; + unsigned split; + unsigned migrate; + + invariants(self, VALID_RW); + + right = blist_prepare_write(self, k+1); + total = p->num_children + right->num_children; + split = total / 2; + migrate = split - p->num_children; + + assert(split >= HALF); + assert(total-split >= HALF); + + copy(p, p->num_children, right, 0, migrate); + p->num_children += migrate; + shift_left(right, migrate, migrate); + right->num_children -= migrate; + blist_adjust_n(right); + blist_adjust_n(p); + + _void(); +} + +/* Child k has underflowed. Borrow from k-1 */ +static void blist_borrow_left(PyBList *self, int k) +{ + PyBList *restrict p = (PyBList *) self->children[k]; + PyBList *restrict left; + unsigned total; + unsigned split; + unsigned migrate; + + invariants(self, VALID_RW); + + left = blist_prepare_write(self, k-1); + total = p->num_children + left->num_children; + split = total / 2; + migrate = split - p->num_children; + + assert(split >= HALF); + assert(total-split >= HALF); + + shift_right(p, 0, migrate); + copy(p, 0, left, left->num_children - migrate, migrate); + p->num_children += migrate; + left->num_children -= migrate; + blist_adjust_n(left); + blist_adjust_n(p); + + _void(); +} + +/* Child k has underflowed. Merge with k+1 */ +static void blist_merge_right(PyBList *self, int k) +{ + int i; + PyBList *restrict p = (PyBList *) self->children[k]; + PyBList *restrict p2 = (PyBList *) self->children[k+1]; + + invariants(self, VALID_RW); + + copy(p, p->num_children, p2, 0, p2->num_children); + for (i = 0; i < p2->num_children; i++) + Py_INCREF(p2->children[i]); + p->num_children += p2->num_children; + blist_forget_child(self, k+1); + blist_adjust_n(p); + + _void(); +} + +/* Child k has underflowed. Merge with k-1 */ +static void blist_merge_left(PyBList *self, int k) +{ + int i; + PyBList *restrict p = (PyBList *) self->children[k]; + PyBList *restrict p2 = (PyBList *) self->children[k-1]; + + invariants(self, VALID_RW); + + shift_right(p, 0, p2->num_children); + p->num_children += p2->num_children; + copy(p, 0, p2, 0, p2->num_children); + for (i = 0; i < p2->num_children; i++) + Py_INCREF(p2->children[i]); + blist_forget_child(self, k-1); + blist_adjust_n(p); + + _void(); +} + +/* Collapse the tree, if possible */ +BLIST_LOCAL(int) +blist_collapse(PyBList *self) +{ + PyBList *p; + invariants(self, VALID_RW|VALID_COLLAPSE); + + if (self->num_children != 1 || self->leaf) { + blist_adjust_n(self); + return _int(0); + } + + p = blist_PREPARE_WRITE(self, 0); + blist_become_and_consume(self, p); + check_invariants(self); + return _int(1); +} + +/* Check if children k-1, k, or k+1 have underflowed. + * + * If so, move things around until self is the root of a valid + * subtree again, possibly requiring collapsing the tree. + * + * Always calls self._adjust_n() (often via self.__collapse()). + */ +BLIST_LOCAL(int) +blist_underflow(PyBList *self, int k) +{ + invariants(self, VALID_RW|VALID_COLLAPSE); + + if (self->leaf) { + blist_adjust_n(self); + return _int(0); + } + + if (k < self->num_children) { + PyBList *restrict p = blist_prepare_write(self, k); + int shrt = HALF - p->num_children; + + while (shrt > 0) { + if (k+1 < self->num_children + && ((PyBList *)self->children[k+1])->num_children >= HALF + shrt) + blist_borrow_right(self, k); + else if (k > 0 + && (((PyBList *)self->children[k-1])->num_children + >= HALF + shrt)) + blist_borrow_left(self, k); + else if (k+1 < self->num_children) + blist_merge_right(self, k); + else if (k > 0) + blist_merge_left(self, k--); + else /* No siblings for p */ + return _int(blist_collapse(self)); + + p = blist_prepare_write(self, k); + shrt = HALF - p->num_children; + } + } + + if (k > 0 && ((PyBList *)self->children[k-1])->num_children < HALF) { + int collapse = blist_underflow(self, k-1); + if (collapse) return _int(collapse); + } + + if (k+1 < self->num_children + && ((PyBList *)self->children[k+1])->num_children < HALF) { + int collapse = blist_underflow(self, k+1); + if (collapse) return _int(collapse); + } + + return _int(blist_collapse(self)); +} + +/* Insert 'item', which may be a subtree, at index k. */ +BLIST_LOCAL(PyBList *) +blist_insert_here(PyBList *self, int k, PyObject *item) +{ + /* Since the subtree may have fewer than half elements, we may + * need to merge it after insertion. + * + * This function may cause self to overflow. If it does, it will + * take the upper half of its children and put them in a new + * subtree and return the subtree. The caller is responsible for + * inserting this new subtree just to the right of self. + * + * Otherwise, it returns None. + */ + + PyBList *restrict sibling; + + invariants(self, VALID_RW|VALID_OVERFLOW); + assert(k >= 0); + + if (self->num_children < LIMIT) { + int collapse; + + shift_right(self, k, 1); + self->num_children++; + self->children[k] = item; + collapse = blist_underflow(self, k); + assert(!collapse); (void) collapse; + return _blist(NULL); + } + + sibling = blist_new_sibling(self); + + if (k < HALF) { + int collapse; + + shift_right(self, k, 1); + self->num_children++; + self->children[k] = item; + collapse = blist_underflow(self, k); + assert(!collapse); (void) collapse; + } else { + int collapse; + + shift_right(sibling, k - HALF, 1); + sibling->num_children++; + sibling->children[k - HALF] = item; + collapse = blist_underflow(sibling, k - HALF); + assert(!collapse); (void) collapse; + blist_adjust_n(sibling); + } + + blist_adjust_n(self); + check_invariants(self); + return _blist(sibling); +} + +/* Recurse depth layers, then insert subtree on the left or right */ +BLIST_LOCAL(PyBList *) +blist_insert_subtree(PyBList *self, int side, PyBList *subtree, int depth) +{ + /* This function may cause an overflow. + * + * depth == 0 means insert the subtree as a child of self. + * depth == 1 means insert the subtree as a grandchild, etc. + */ + + PyBList *sibling; + invariants(self, VALID_RW|VALID_OVERFLOW); + assert(side == 0 || side == -1); + + self->n += subtree->n; + + if (depth) { + PyBList *restrict p = blist_prepare_write(self, side); + PyBList *overflow = blist_insert_subtree(p, side, + subtree, depth-1); + if (!overflow) return _blist(NULL); + if (side == 0) + side = 1; + subtree = overflow; + } + + if (side < 0) + side = self->num_children; + + sibling = blist_insert_here(self, side, (PyObject *) subtree); + + return _blist(sibling); +} + +/* Handle the case where a user-visible node overflowed */ +BLIST_LOCAL(int) +blist_overflow_root(PyBList *self, PyBList *overflow) +{ + PyBList *child; + + invariants(self, VALID_RW); + + if (!overflow) return _int(0); + child = blist_new(); + if (!child) { + decref_later((PyObject*)overflow); + return _int(0); + } + blist_become_and_consume(child, self); + self->children[0] = (PyObject *)child; + self->children[1] = (PyObject *)overflow; + self->num_children = 2; + self->leaf = 0; + blist_adjust_n(self); + return _int(-1); +} + +/* Concatenate two trees of potentially different heights. */ +BLIST_LOCAL(PyBList *) +blist_concat_blist(PyBList *left_subtree, PyBList *right_subtree, + int height_diff, int *padj) +{ + /* The parameters are the two trees, and the difference in their + * heights expressed as left_height - right_height. + * + * Returns a tuple of the new, combined tree, and an integer. + * The integer expresses the height difference between the new + * tree and the taller of the left and right subtrees. It will + * be 0 if there was no change, and 1 if the new tree is taller + * by 1. + */ + + int adj = 0; + PyBList *overflow; + PyBList *root; + + assert(Py_REFCNT(left_subtree) == 1); + assert(Py_REFCNT(right_subtree) == 1); + + if (height_diff == 0) { + int collapse; + + root = blist_new(); + if (!root) { + decref_later((PyObject*)left_subtree); + decref_later((PyObject*)right_subtree); + return NULL; + } + root->children[0] = (PyObject *) left_subtree; + root->children[1] = (PyObject *) right_subtree; + root->leaf = 0; + root->num_children = 2; + collapse = blist_underflow(root, 0); + if (!collapse) + collapse = blist_underflow(root, 1); + if (!collapse) + adj = 1; + overflow = NULL; + } else if (height_diff > 0) { /* Left is larger */ + root = left_subtree; + overflow = blist_insert_subtree(root, -1, right_subtree, + height_diff - 1); + } else { /* Right is larger */ + root = right_subtree; + overflow = blist_insert_subtree(root, 0, left_subtree, + -height_diff - 1); + } + + adj += -blist_overflow_root(root, overflow); + if (padj) *padj = adj; + + return root; +} + +/* Concatenate two subtrees of potentially different heights. */ +BLIST_LOCAL(PyBList *) +blist_concat_subtrees(PyBList *left_subtree, int left_depth, + PyBList *right_subtree, int right_depth, int *pdepth) +{ + /* Returns a tuple of the new, combined subtree and its depth. + * + * Depths are the depth in the parent, not their height. + */ + + int deepest = left_depth > right_depth ? + left_depth : right_depth; + PyBList *root = blist_concat_blist(left_subtree, right_subtree, + -(left_depth - right_depth), pdepth); + if (pdepth) *pdepth = deepest - *pdepth; + return root; +} + +/* Concatenate two roots of potentially different heights. */ +BLIST_LOCAL(PyBList *) +blist_concat_roots(PyBList *left_root, int left_height, + PyBList *right_root, int right_height, int *pheight) +{ + /* Returns a tuple of the new, combined root and its height. + * + * Heights are the height from the root to its leaf nodes. + */ + + PyBList *root = blist_concat_blist(left_root, right_root, + left_height - right_height, pheight); + int highest = left_height > right_height ? + left_height : right_height; + + if (pheight) *pheight = highest + *pheight; + + return root; +} + +BLIST_LOCAL(PyBList *) +blist_concat_unknown_roots(PyBList *left_root, PyBList *right_root) +{ + return blist_concat_roots(left_root, blist_get_height(left_root), + right_root, blist_get_height(right_root), + NULL); +} + +/* Child at position k is too short by "depth". Fix it */ +BLIST_LOCAL(int) +blist_reinsert_subtree(PyBList *self, int k, int depth) +{ + PyBList *subtree; + + invariants(self, VALID_RW); + + assert(Py_REFCNT(self->children[k]) == 1); + subtree = (PyBList *) self->children[k]; + shift_left(self, k+1, 1); + self->num_children--; + + if (self->num_children > k) { + /* Merge right */ + PyBList *p = blist_prepare_write(self, k); + PyBList *overflow = blist_insert_subtree(p, 0, + subtree, depth-1); + if (overflow) { + shift_right(self, k+1, 1); + self->num_children++; + self->children[k+1] = (PyObject *) overflow; + } + } else { + /* Merge left */ + PyBList *p = blist_prepare_write(self, k-1); + PyBList *overflow = blist_insert_subtree(p, -1, + subtree, depth-1); + if (overflow) { + shift_right(self, k, 1); + self->num_children++; + self->children[k] = (PyObject *) overflow; + } + } + + return _int(blist_underflow(self, k)); +} + +/************************************************************************ + * The main insert and deletion operations + */ + +/* Recursive to find position i, and insert item just there. */ +BLIST_LOCAL(PyBList *) +ins1(PyBList *self, Py_ssize_t i, PyObject *item) +{ + PyBList *ret; + PyBList *restrict p; + int k; + Py_ssize_t so_far; + PyBList *overflow; + + invariants(self, VALID_RW|VALID_OVERFLOW); + + if (self->leaf) { + Py_INCREF(item); + + /* Speed up the common case */ + if (self->num_children < LIMIT) { + shift_right(self, i, 1); + self->num_children++; + self->n++; + self->children[i] = item; + return _blist(NULL); + } + + return _blist(blist_insert_here(self, i, item)); + } + + blist_locate(self, i, (PyObject **) &p, &k, &so_far); + + self->n += 1; + p = blist_prepare_write(self, k); + overflow = ins1(p, i - so_far, item); + + if (!overflow) ret = NULL; + else ret = blist_insert_here(self, k+1, (PyObject *) overflow); + + return _blist(ret); +} + +BLIST_LOCAL(int) +blist_extend_blist(PyBList *self, PyBList *other) +{ + PyBList *right, *left, *root; + + invariants(self, VALID_RW); + + /* Special case for speed */ + if (self->leaf && other->leaf && self->n + other->n <= LIMIT) { + copyref(self, self->n, other, 0, other->n); + self->n += other->n; + self->num_children = self->n; + return _int(0); + } + + /* Make not-user-visible roots for the subtrees */ + right = blist_copy(other); /* XXX not checking return values */ + left = blist_new(); + if (left == NULL) + return _int(-1); + blist_become_and_consume(left, self); + + if (left->leaf && right->leaf) { + balance_leafs(left, right); + self->children[0] = (PyObject *) left; + self->children[1] = (PyObject *) right; + self->num_children = 2; + self->leaf = 0; + blist_adjust_n(self); + return _int(0); + } + + root = blist_concat_unknown_roots(left, right); + blist_become_and_consume(self, root); + SAFE_DECREF(root); + return _int(0); +} + +/* Recursive version of __delslice__ */ +static int blist_delslice(PyBList *self, Py_ssize_t i, Py_ssize_t j) +{ + /* This may cause self to collapse. It returns 0 if it did + * not. If a collapse occured, it returns a positive integer + * indicating how much shorter this subtree is compared to when + * _delslice() was entered. + * + * As a special exception, it may return 0 if the entire subtree + * is deleted. + * + * Additionally, this function may cause an underflow. + */ + + PyBList *restrict p, *restrict p2; + int k, k2, depth; + Py_ssize_t so_far, so_far2, low; + int collapse_left, collapse_right, deleted_k, deleted_k2; + + invariants(self, VALID_RW | VALID_PARENT | VALID_COLLAPSE); + check_invariants(self); + + if (j > self->n) + j = self->n; + + if (i == j) + return _int(0); + + if (self->leaf) { + blist_forget_children2(self, i, j); + self->n = self->num_children; + return _int(0); + } + + if (i == 0 && j >= self->n) { + /* Delete everything. */ + blist_CLEAR(self); + return _int(0); + } + + blist_locate(self, i, (PyObject **) &p, &k, &so_far); + blist_locate(self, j-1, (PyObject **) &p2, &k2, &so_far2); + + if (k == k2) { + /* All of the deleted elements are contained under a single + * child of this node. Recurse and check for a short + * subtree and/or underflow + */ + + assert(so_far == so_far2); + p = blist_prepare_write(self, k); + depth = blist_delslice(p, i - so_far, j - so_far); + if (p->n == 0) { + SAFE_DECREF(p); + shift_left(self, k+1, 1); + self->num_children--; + return _int(blist_collapse(self)); + } + if (!depth) + return _int(blist_underflow(self, k)); + return _int(blist_reinsert_subtree(self, k, depth)); + } + + /* Deleted elements are in a range of child elements. There + * will be: + * - a left child (k) where we delete some (or all) of its children + * - a right child (k2) where we delete some (or all) of it children + * - children in between who are deleted entirely + */ + + /* Call _delslice recursively on the left and right */ + p = blist_prepare_write(self, k); + collapse_left = blist_delslice(p, i - so_far, j - so_far); + p2 = blist_prepare_write(self, k2); + low = i-so_far2 > 0 ? i-so_far2 : 0; + collapse_right = blist_delslice(p2, low, j - so_far2); + + deleted_k = 0; /* False */ + deleted_k2 = 0; /* False */ + + /* Delete [k+1:k2] */ + blist_forget_children2(self, k+1, k2); + k2 = k+1; + + /* Delete k1 and k2 if they are empty */ + if (!((PyBList *)self->children[k2])->n) { + decref_later((PyObject *) self->children[k2]); + shift_left(self, k2+1, 1); + self->num_children--; + deleted_k2 = 1; /* True */ + } + if (!((PyBList *)self->children[k])->n) { + decref_later(self->children[k]); + shift_left(self, k+1, 1); + self->num_children--; + deleted_k = 1; /* True */ + } + + if (deleted_k && deleted_k2) /* # No messy subtrees. Good. */ + return _int(blist_collapse(self)); + + /* The left and right may have collapsed and/or be in an + * underflow state. Clean them up. Work on fixing collapsed + * trees first, then worry about underflows. + */ + + if (!deleted_k && !deleted_k2 && collapse_left && collapse_right) { + /* Both exist and collapsed. Merge them into one subtree. */ + PyBList *left, *right, *subtree; + + left = (PyBList *) self->children[k]; + right = (PyBList *) self->children[k+1]; + shift_left(self, k+1, 1); + self->num_children--; + subtree = blist_concat_subtrees(left, collapse_left, + right, collapse_right, + &depth); + self->children[k] = (PyObject *) subtree; + } else if (deleted_k) { + /* Only the right potentially collapsed, point there. */ + depth = collapse_right; + /* k already points to the old k2, since k was deleted */ + } else if (!deleted_k2 && !collapse_left) { + /* Only the right potentially collapsed, point there. */ + k = k + 1; + depth = collapse_right; + } else { + depth = collapse_left; + } + + /* At this point, we have a potentially short subtree at k, + * with depth "depth". + */ + + if (!depth || self->num_children == 1) { + /* Doesn't need merging, or no siblings to merge with */ + return _int(depth + blist_underflow(self, k)); + } + + /* We have a short subtree at k, and we have other children */ + return _int(blist_reinsert_subtree(self, k, depth)); +} + +BLIST_LOCAL(PyObject *) +blist_get1(PyBList *self, Py_ssize_t i) +{ + PyBList *p; + int k; + Py_ssize_t so_far; + + invariants(self, VALID_PARENT); + + if (self->leaf) + return _ob(self->children[i]); + + blist_locate(self, i, (PyObject **) &p, &k, &so_far); + assert(i >= so_far); + return _ob(blist_get1(p, i - so_far)); +} + +BLIST_LOCAL(PyObject *) +blist_pop_last_fast(PyBList *self) +{ + PyBList *p; + + invariants(self, VALID_ROOT|VALID_RW); + + for (p = self; !p->leaf; + p = (PyBList*)p->children[p->num_children-1]) { + if (p != self && Py_REFCNT(p) > 1) + goto cleanup_and_slow; + p->n--; + } + + if ((Py_REFCNT(p) > 1 || p->num_children == HALF) + && self != p) { + PyBList *p2; + cleanup_and_slow: + for (p2 = self; p != p2; + p2 = (PyBList*)p2->children[p2->num_children-1]) + p2->n++; + return _ob(NULL); + } + p->n--; + p->num_children--; + + if ((self->n) % INDEX_FACTOR == 0) + ext_mark(self, 0, DIRTY); +#ifdef Py_DEBUG + else + ((PyBListRoot*)self)->last_n--; +#endif + return _ob(p->children[p->num_children]); +} + +static void blist_delitem(PyBList *self, Py_ssize_t i) +{ + invariants(self, VALID_ROOT|VALID_RW); + if (i == self->n-1) { + PyObject *v = blist_pop_last_fast(self); + if (v) { + decref_later(v); + _void(); + return; + } + } + + blist_delslice(self, i, i+1); + _void(); +} + +static PyObject *blist_delitem_return(PyBList *self, Py_ssize_t i) +{ + PyObject *rv; + + rv = blist_get1(self, i); + Py_INCREF(rv); + blist_delitem(self, i); + return rv; +} + +/************************************************************************ + * BList iterator + */ + +static iter_t *iter_init(iter_t *iter, PyBList *lst) +{ + iter->depth = 0; + + while(!lst->leaf) { + iter->stack[iter->depth].lst = lst; + iter->stack[iter->depth++].i = 1; + Py_INCREF(lst); + lst = (PyBList *) lst->children[0]; + } + + iter->leaf = lst; + iter->i = 0; + iter->depth++; + Py_INCREF(lst); + + return iter; +} + +static iter_t *iter_init2(iter_t *iter, PyBList *lst, Py_ssize_t start) +{ + iter->depth = 0; + + assert(start >= 0); + while (!lst->leaf) { + PyBList *p; + int k; + Py_ssize_t so_far; + + blist_locate(lst, start, (PyObject **) &p, &k, &so_far); + iter->stack[iter->depth].lst = lst; + iter->stack[iter->depth++].i = k + 1; + Py_INCREF(lst); + lst = p; + start -= so_far; + } + + iter->leaf = lst; + iter->i = start; + iter->depth++; + Py_INCREF(lst); + + return iter; +} + +static PyObject *iter_next(iter_t *iter) +{ + PyBList *p; + int i; + + p = iter->leaf; + if (p == NULL) + return NULL; + + if (!p->leaf) { + /* If p is the root, it may have been a leaf when we began + * iterating, but turned into a non-leaf during iteration. + * Modifying the list during iteration results in undefined + * behavior, so just throw in the towel. + */ + + return NULL; + } + + if (iter->i < p->num_children) + return p->children[iter->i++]; + + iter->depth--; + do { + decref_later((PyObject *) p); + if (!iter->depth) { + iter->leaf = NULL; + return NULL; + } + p = iter->stack[--iter->depth].lst; + i = iter->stack[iter->depth].i; + } while (i >= p->num_children); + + assert(iter->stack[iter->depth].lst == p); + iter->stack[iter->depth++].i = i+1; + + while (!p->leaf) { + p = (PyBList *) p->children[i]; + Py_INCREF(p); + i = 0; + iter->stack[iter->depth].lst = p; + iter->stack[iter->depth++].i = i+1; + } + + iter->leaf = iter->stack[iter->depth-1].lst; + iter->i = iter->stack[iter->depth-1].i; + + return p->children[i]; +} + +static void iter_cleanup(iter_t *iter) +{ + int i; + for (i = 0; i < iter->depth-1; i++) + decref_later((PyObject *) iter->stack[i].lst); + if (iter->depth) + decref_later((PyObject *) iter->leaf); +} + +BLIST_PYAPI(PyObject *) +py_blist_iter(PyObject *oseq) +{ + PyBList *seq; + blistiterobject *it; + + if (!PyRootBList_Check(oseq)) { + PyErr_BadInternalCall(); + return NULL; + } + + seq = (PyBList *) oseq; + + invariants(seq, VALID_USER); + + if (num_free_iters) { + it = free_iters[--num_free_iters]; + _Py_NewReference((PyObject *) it); + } else { + DANGER_BEGIN; + it = PyObject_GC_New(blistiterobject, &PyBListIter_Type); + DANGER_END; + if (it == NULL) + return _ob(NULL); + } + + if (seq->leaf) { + /* Speed up common case */ + it->iter.leaf = seq; + it->iter.i = 0; + it->iter.depth = 1; + Py_INCREF(seq); + } else + iter_init(&it->iter, seq); + + PyObject_GC_Track(it); + return _ob((PyObject *) it); +} + +static void blistiter_dealloc(PyObject *oit) +{ + blistiterobject *it; + + assert(PyBListIter_Check(oit)); + it = (blistiterobject *) oit; + + PyObject_GC_UnTrack(it); + iter_cleanup(&it->iter); + if (num_free_iters < MAXFREELISTS + && (Py_TYPE(it) == &PyBListIter_Type)) + free_iters[num_free_iters++] = it; + else + PyObject_GC_Del(it); + _decref_flush(); +} + +static int blistiter_traverse(PyObject *oit, visitproc visit, void *arg) +{ + blistiterobject *it; + int i; + + assert(PyBListIter_Check(oit)); + it = (blistiterobject *) oit; + + for (i = 0; i < it->iter.depth-1; i++) + Py_VISIT(it->iter.stack[i].lst); + if (it->iter.depth) + Py_VISIT(it->iter.leaf); + return 0; +} + +static PyObject *blistiter_next(PyObject *oit) +{ + blistiterobject *it = (blistiterobject *) oit; + PyObject *obj; + + /* Speed up common case */ + PyBList *p; + p = it->iter.leaf; + if (p == NULL) + return NULL; + if (p->leaf && it->iter.i < p->num_children) { + obj = p->children[it->iter.i++]; + Py_INCREF(obj); + return obj; + } + + obj = iter_next(&it->iter); + if (obj != NULL) + Py_INCREF(obj); + + _decref_flush(); + return obj; +} + +BLIST_PYAPI(PyObject *) +blistiter_len(blistiterobject *it) +{ + iter_t *iter = &it->iter; + int depth; + Py_ssize_t total = 0; + + if (!iter->leaf) + return PyInt_FromLong(0); + + total += iter->leaf->n - iter->i; + + for (depth = iter->depth-2; depth >= 0; depth--) { + point_t point = iter->stack[depth]; + int j; + if (point.lst->leaf) continue; + assert(point.i > 0); + for (j = point.i; j < point.lst->num_children; j++) { + PyBList *child = (PyBList *) point.lst->children[j]; + total += child->n; + } + } + if (iter->depth > 1 && iter->stack[0].lst->leaf) { + int extra = iter->stack[0].lst->n - iter->stack[0].i; + if (extra > 0) total += extra; + } + return PyInt_FromLong(total); +} + +PyDoc_STRVAR(length_hint_doc, "Private method returning an estimate of len(list(it))."); + +static PyMethodDef blistiter_methods[] = { + {"__length_hint__", (PyCFunction)blistiter_len, METH_NOARGS, length_hint_doc}, + {NULL, NULL} /* sentinel */ +}; + +PyTypeObject PyBListIter_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "blistiterator", /* tp_name */ + sizeof(blistiterobject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + blistiter_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */ + 0, /* tp_doc */ + blistiter_traverse, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + PyObject_SelfIter, /* tp_iter */ + blistiter_next, /* tp_iternext */ + blistiter_methods, /* tp_methods */ + 0, /* tp_members */ +}; + +/************************************************************************ + * BList reverse iterator + */ + +BLIST_LOCAL(iter_t *) +riter_init2(iter_t *iter, PyBList *lst, Py_ssize_t start, Py_ssize_t stop) +{ + iter->depth = 0; + + assert(stop >= 0); + assert(start >= 0); + assert(start >= stop); + while (!lst->leaf) { + PyBList *p; + int k; + Py_ssize_t so_far; + + blist_locate(lst, start-1, (PyObject **) &p, &k, &so_far); + iter->stack[iter->depth].lst = lst; + iter->stack[iter->depth++].i = k - 1; + Py_INCREF(lst); + lst = p; + start -= so_far; + } + + iter->leaf = lst; + iter->i = start-1; + iter->depth++; + Py_INCREF(lst); + + return iter; +} +#define riter_init(iter, lst) (riter_init2((iter), (lst), (lst)->n, 0)) + +BLIST_LOCAL(PyObject *) +iter_prev(iter_t *iter) +{ + PyBList *p; + int i; + + p = iter->leaf; + if (p == NULL) + return NULL; + + if (!p->leaf) { + /* If p is the root, it may have been a leaf when we began + * iterating, but turned into a non-leaf during iteration. + * Modifying the list during iteration results in undefined + * behavior, so just throw in the towel. + */ + + return NULL; + } + + if (iter->i >= p->num_children && iter->i >= 0) + iter->i = p->num_children - 1; + + if (iter->i >= 0) + return p->children[iter->i--]; + + iter->depth--; + do { + decref_later((PyObject *) p); + if (!iter->depth) { + iter->leaf = NULL; + return NULL; + } + p = iter->stack[--iter->depth].lst; + i = iter->stack[iter->depth].i; + + if (i >= p->num_children && i >= 0) + i = p->num_children - 1; + } while (i < 0); + + assert(iter->stack[iter->depth].lst == p); + iter->stack[iter->depth++].i = i-1; + + while (!p->leaf) { + p = (PyBList *) p->children[i]; + Py_INCREF(p); + i = p->num_children-1; + iter->stack[iter->depth].lst = p; + iter->stack[iter->depth++].i = i-1; + } + + iter->leaf = iter->stack[iter->depth-1].lst; + iter->i = iter->stack[iter->depth-1].i; + + return p->children[i]; +} + +BLIST_PYAPI(PyObject *) +py_blist_reversed(PyBList *seq) +{ + blistiterobject *it; + + invariants(seq, VALID_USER); + + DANGER_BEGIN; + it = PyObject_GC_New(blistiterobject, + &PyBListReverseIter_Type); + DANGER_END; + if (it == NULL) + return _ob(NULL); + + if (seq->leaf) { + /* Speed up common case */ + it->iter.leaf = seq; + it->iter.i = seq->n-1; + it->iter.depth = 1; + Py_INCREF(seq); + } else + riter_init(&it->iter, seq); + + PyObject_GC_Track(it); + return _ob((PyObject *) it); +} + +static PyObject *blistiter_prev(PyObject *oit) +{ + blistiterobject *it = (blistiterobject *) oit; + PyObject *obj; + + /* Speed up common case */ + PyBList *p; + p = it->iter.leaf; + if (p == NULL) + return NULL; + + if (it->iter.i >= p->num_children && it->iter.i >= 0) + it->iter.i = p->num_children - 1; + + if (p->leaf && it->iter.i >= 0) { + obj = p->children[it->iter.i--]; + Py_INCREF(obj); + return obj; + } + + obj = iter_prev(&it->iter); + if (obj != NULL) + Py_INCREF(obj); + + _decref_flush(); + return obj; +} + +BLIST_PYAPI(PyObject *) +blistriter_len(blistiterobject *it) +{ + iter_t *iter = &it->iter; + int depth; + Py_ssize_t total = 0; + + total += iter->i + 1; + + for (depth = iter->depth-2; depth >= 0; depth--) { + point_t point = iter->stack[depth]; + int j; + if (point.lst->leaf) continue; + for (j = 0; j <= point.i; j++) { + PyBList *child = (PyBList *) point.lst->children[j]; + total += child->n; + } + } + if (iter->depth > 1 && iter->stack[0].lst->leaf) { + int extra = iter->stack[0].i + 1; + if (extra > 0) total += extra; + } + return PyInt_FromLong(total); +} + +static PyMethodDef blistriter_methods[] = { + {"__length_hint__", (PyCFunction)blistriter_len, METH_NOARGS, length_hint_doc}, + {NULL, NULL} /* sentinel */ +}; + +PyTypeObject PyBListReverseIter_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "blistreverseiterator", /* tp_name */ + sizeof(blistiterobject), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + blistiter_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */ + 0, /* tp_doc */ + blistiter_traverse, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + PyObject_SelfIter, /* tp_iter */ + blistiter_prev, /* tp_iternext */ + blistriter_methods, /* tp_methods */ + 0, /* tp_members */ +}; + +/************************************************************************ + * A forest is an array of BList tree structures, which may be of + * different heights. It's a temporary utility structure for certain + * operations. Specifically, it allows us to compose or decompose a + * BList in O(n) time. + * + * The BList trees in the forest are stored in position order, left-to-right. + * + */ + +typedef struct Forest +{ + Py_ssize_t num_leafs; + Py_ssize_t num_trees; + Py_ssize_t max_trees; + PyBList **list; +} Forest; + +#if 0 +/* Remove the right-most element. If it's a leaf, return it. + * Otherwise, add all of its childrent to the forest *in reverse + * order* and try again. Assuming only one BList was added to the + * forest, the effect is to return all of the leafs one-at-a-time + * left-to-right. */ +BLIST_LOCAL(PyBList *) +forest_get_leaf(Forest *forest) +{ + PyBList *node = forest->list[--forest->num_trees]; + PyBList **list; + while (!node->leaf) { + int i; + while (forest->num_trees + node->num_children + > forest->max_trees) { + list = forest->list; + forest->max_trees *= 2; + PyMem_Resize(list, PyBList*,forest->max_trees); + if (list == NULL) { + PyErr_NoMemory(); + return NULL; + } + forest->list = list; + } + + for (i = node->num_children - 1; i >= 0; i--) + forest->list[forest->num_trees++] + = blist_PREPARE_WRITE(node, i); + + node->num_children = 0; + SAFE_DECREF(node); + node = forest->list[--forest->num_trees]; + } + + return node; +} +#endif + +#define MAX_FREE_FORESTS 20 +static PyBList **forest_saved[MAX_FREE_FORESTS]; +static unsigned forest_max_trees[MAX_FREE_FORESTS]; +static unsigned num_free_forests = 0; + +BLIST_LOCAL(Forest *) +forest_init(Forest *forest) +{ + forest->num_trees = 0; + forest->num_leafs = 0; + if (num_free_forests) { + forest->list = forest_saved[--num_free_forests]; + forest->max_trees = forest_max_trees[num_free_forests]; + } else { + forest->max_trees = LIMIT; /* enough for O(LIMIT**2) items */ + forest->list = PyMem_New(PyBList *, forest->max_trees); + if (forest->list == NULL) + return (Forest *) PyErr_NoMemory(); + } + return forest; +} + +#if 0 +BLIST_LOCAL(Forest *) +forest_new(void) +{ + Forest *forest = PyMem_New(Forest, 1); + Forest *rv; + if (forest == NULL) + return (Forest *) PyErr_NoMemory(); + rv = forest_init(forest); + if (rv == NULL) + PyMem_Free(forest); + return rv; +} + +BLIST_LOCAL(void) +forest_grow(Forest *forest, Py_ssize_t new_max) +{ + if (forest->max_trees > new_max) return; + /* XXX Check return value */ + PyMem_Resize(forest->list, PyBList *, new_max); + forest->max_trees = new_max; +} +#endif + +/* Append a tree to the forest. Steals the reference to "leaf" */ +BLIST_LOCAL(int) +forest_append(Forest *forest, PyBList *leaf) +{ + Py_ssize_t power = LIMIT; + + if (!leaf->num_children) { /* Don't bother adding empty leaf nodes */ + SAFE_DECREF(leaf); + return 0; + } + + leaf->n = leaf->num_children; + + if (forest->num_trees == forest->max_trees) { + PyBList **list = forest->list; + + forest->max_trees <<= 1; + PyMem_Resize(list, PyBList *, forest->max_trees); + if (list == NULL) { + PyErr_NoMemory(); + return -1; + } + forest->list = list; + } + + forest->list[forest->num_trees++] = leaf; + forest->num_leafs++; + + while (forest->num_leafs % power == 0) { + struct PyBList *parent = blist_new(); + int x; + + if (parent == NULL) { + PyErr_NoMemory(); + return -1; + } + parent->leaf = 0; + memcpy(parent->children, + &forest->list[forest->num_trees - LIMIT], + sizeof (PyBList *) * LIMIT); + parent->num_children = LIMIT; + forest->num_trees -= LIMIT; + x = blist_underflow(parent, LIMIT - 1); + assert(!x); (void) x; + + forest->list[forest->num_trees++] = parent; + power *= LIMIT; + } + + return 0; +} + +BLIST_LOCAL(int) +blist_init_from_child_array(PyBList **children, int num_children) +{ + int i, j, k; + + assert(num_children); + if (num_children == 1) + return 1; + + for (k = i = 0; i < num_children; i += LIMIT) { + PyBList *parent = blist_new(); + int stop = (LIMIT < (num_children - i)) + ? LIMIT : (num_children - i); + if (parent == NULL) + return -1; + parent->leaf = 0; + for (j = 0; j < stop; j++) { + parent->children[j] = (PyObject *) children[i+j]; + assert(children[i+j]->num_children >= HALF); + children[i+j] = NULL; + } + parent->num_children = j; + blist_adjust_n(parent); + children[k++] = parent; + } + + if (k <= 1) + return k; + + if (children[k-1]->num_children < HALF) { + PyBList *left = children[k-2]; + PyBList *right = children[k-1]; + int needed = HALF - right->num_children; + + shift_right(right, 0, needed); + copy(right, 0, left, LIMIT-needed, needed); + left->num_children -= needed; + right->num_children += needed; + blist_adjust_n(left); + blist_adjust_n(right); + } + + return blist_init_from_child_array(children, k); +} + +#if 0 +/* Like forest_append(), but handles the case where the previously + * added leaf is in an underflow state. */ +BLIST_LOCAL(int) +forest_append_safe(Forest *forest, PyBList *leaf) +{ + PyBList *last; + + if (forest->num_trees == 0) + goto append; + + last = forest->list[forest->num_trees-1]; + + if (!last->leaf || last->num_children >= HALF) + goto append; + + if (last->num_children + leaf->num_children <= LIMIT) { + copy(last, last->num_children, leaf, 0, leaf->num_children); + last->num_children += leaf->num_children; + last->n += leaf->num_children; + leaf->num_children = 0; + } else { + int needed = HALF - last->num_children; + + copy(last, last->num_children, leaf, 0, needed); + last->num_children += needed; + last->n += needed; + shift_left(leaf, needed, needed); + leaf->num_children -= needed; + } + + append: + return forest_append(forest, leaf); +} +#endif + +BLIST_LOCAL(void) +forest_uninit(Forest *forest) +{ + Py_ssize_t i; + for (i = 0; i < forest->num_trees; i++) + decref_later((PyObject *) forest->list[i]); + if (num_free_forests < MAX_FREE_FORESTS && forest->max_trees == LIMIT){ + forest_saved[num_free_forests] = forest->list; + forest_max_trees[num_free_forests++] = forest->max_trees; + } else + PyMem_Free(forest->list); +} + +BLIST_LOCAL(void) +forest_uninit_now(Forest *forest) +{ + Py_ssize_t i; + for (i = 0; i < forest->num_trees; i++) + Py_DECREF((PyObject *) forest->list[i]); + if (num_free_forests < MAX_FREE_FORESTS && forest->max_trees == LIMIT){ + forest_saved[num_free_forests] = forest->list; + forest_max_trees[num_free_forests++] = forest->max_trees; + } else + PyMem_Free(forest->list); +} + +#if 0 +BLIST_LOCAL(void) +forest_delete(Forest *forest) +{ + forest_uninit(forest); + PyMem_Free(forest); +} +#endif + +/* Combine the forest into a final BList and delete the forest. + * + * forest_finish() assumes that only leaf nodes were passed to forest_append() + */ +static PyBList *forest_finish(Forest *forest) +{ + PyBList *out_tree = NULL; /* The final BList we are building */ + int out_height = 0; /* It's height */ + int group_height = 1; /* height of the next group from forest */ + + while(forest->num_trees) { + int n = forest->num_leafs % LIMIT; + PyBList *group; + int adj; + + forest->num_leafs /= LIMIT; + group_height++; + + if (!n) continue; /* No nodes at this height */ + + /* Merge nodes of the same height into 1 node, and + * merge it into our output BList. + */ + group = blist_new(); + if (group == NULL) { + forest_uninit(forest); + xdecref_later((PyObject *) out_tree); + return NULL; + } + group->leaf = 0; + memcpy(group->children, + &forest->list[forest->num_trees - n], + sizeof (PyBList *) * n); + group->num_children = n; + forest->num_trees -= n; + adj = blist_underflow(group, n - 1); + if (out_tree == NULL) { + out_tree = group; + out_height = group_height - adj; + } else { + out_tree = blist_concat_roots(group, group_height- adj, + out_tree, out_height, + &out_height); + } + } + + forest_uninit(forest); + + return out_tree; +} + +/************************************************************************ + * Functions that rely on forests. + */ + +/* Initialize an empty BList from an array of PyObjects in O(n) time */ +BLIST_LOCAL(int) +blist_init_from_array(PyBList *self, PyObject **restrict src, Py_ssize_t n) +{ + int i; + PyBList *final, *cur; + PyObject **dst; + PyObject **stop = &src[n]; + PyObject **next; + Forest forest; + int gc_previous; + + invariants(self, VALID_ROOT|VALID_RW); + + if (n <= LIMIT) { + dst = self->children; + while (src < stop) { + Py_INCREF(*src); + *dst++ = *src++; + } + self->num_children = n; + self->n = n; + return _int(0); + } + + if (forest_init(&forest) == NULL) + return _int(-1); + + gc_previous = gc_pause(); + + cur = blist_new(); + if (cur == NULL) + goto error2; + dst = cur->children; + + while (src < stop) { + next = &src[LIMIT]; + if (next > stop) next = stop; + while (src < next) { + Py_INCREF(*src); + *dst++ = *src++; + } + if (src == stop) break; + + cur->num_children = LIMIT; + if (forest_append(&forest, cur) < 0) + goto error; + cur = blist_new(); + if (cur == NULL) + goto error2; + dst = cur->children; + } + + i = dst - cur->children; + + if (i) { + cur->num_children = i; + if (forest_append(&forest, cur) < 0) { + error: + Py_DECREF(cur); + error2: + forest_uninit(&forest); + gc_unpause(gc_previous); + return _int(-1); + } + } else { + Py_DECREF(cur); + } + + final = forest_finish(&forest); + blist_become_and_consume(self, final); + + ext_reindex_set_all((PyBListRoot *) self); + SAFE_DECREF(final); + + gc_unpause(gc_previous); + + return _int(0); +} + +/* Initialize an empty BList from a Python sequence in O(n) time */ +BLIST_LOCAL(int) +blist_init_from_seq(PyBList *self, PyObject *b) +{ + PyObject *it; + PyObject *(*iternext)(PyObject *); + PyBList *cur, *final; + Forest forest; + + invariants(self, VALID_ROOT | VALID_RW); + + if (PyBList_Check(b)) { + /* We can copy other BLists in O(1) time :-) */ + blist_become(self, (PyBList *) b); + ext_mark(self, 0, DIRTY); + ext_mark_set_dirty_all((PyBList *) b); + return _int(0); + } + + if (PyTuple_CheckExact(b)) { + PyTupleObject *t = (PyTupleObject *) b; + return _int(blist_init_from_array(self, t->ob_item, + PyTuple_GET_SIZE(t))); + } +#ifndef Py_BUILD_CORE + if (PyList_CheckExact(b)) { + PyListObject *l = (PyListObject *) b; + return _int(blist_init_from_array(self, l->ob_item, + PyList_GET_SIZE(l))); + } +#endif + + DANGER_BEGIN; + it = PyObject_GetIter(b); + DANGER_END; + if (it == NULL) + return _int(-1); + iternext = *Py_TYPE(it)->tp_iternext; + + /* Try common case of len(sequence) <= LIMIT */ + for (self->num_children = 0; self->num_children < LIMIT; + self->num_children++) { + PyObject *item; + + DANGER_BEGIN; + item = iternext(it); + DANGER_END; + + if (item == NULL) { + self->n = self->num_children; + if (PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_StopIteration)) + PyErr_Clear(); + else + goto error; + } + goto done; + } + + self->children[self->num_children] = item; + } + + /* No such luck, build bottom-up instead. The sequence data + * so far goes in a leaf node. */ + + cur = blist_new(); + if (cur == NULL) + goto error; + blist_become_and_consume(cur, self); + + if (forest_init(&forest) == NULL) { + decref_later(it); + decref_later((PyObject *) cur); + return _int(-1); + } + + if (0 > forest_append(&forest, cur)) + goto error2; + + cur = blist_new(); + if (cur == NULL) + goto error2; + + while (1) { + PyObject *item; + DANGER_BEGIN; + item = iternext(it); + DANGER_END; + if (item == NULL) { + if (PyErr_Occurred()) { + if (PyErr_ExceptionMatches(PyExc_StopIteration)) + PyErr_Clear(); + else + goto error2; + } + break; + } + + if (cur->num_children == LIMIT) { + if (forest_append(&forest, cur) < 0) goto error2; + cur = blist_new(); + if (cur == NULL) + goto error2; + } + + cur->children[cur->num_children++] = item; + } + + if (cur->num_children) { + if (forest_append(&forest, cur) < 0) goto error2; + cur->n = cur->num_children; + } else { + SAFE_DECREF(cur); + } + + final = forest_finish(&forest); + blist_become_and_consume(self, final); + SAFE_DECREF(final); + + done: + ext_reindex_set_all((PyBListRoot*)self); + decref_later(it); + return _int(0); + + error2: + DANGER_BEGIN; + Py_XDECREF((PyObject *) cur); + forest_uninit_now(&forest); + DANGER_END; + error: + DANGER_BEGIN; + Py_DECREF(it); + DANGER_END; + blist_CLEAR(self); + return _int(-1); +} + +/* Utility function for performing repr() */ +BLIST_LOCAL(int) +blist_repr_r(PyBList *self) +{ + int i; + PyObject *s; + + invariants(self, VALID_RW|VALID_PARENT); + + if (self->leaf) { + for (i = 0; i < self->num_children; i++) { + if (Py_EnterRecursiveCall(" while getting the repr of a list")) + return _int(-1); + DANGER_BEGIN; + s = PyObject_Repr(self->children[i]); + DANGER_END; + Py_LeaveRecursiveCall(); + if (s == NULL) + return _int(-1); + Py_DECREF(self->children[i]); + self->children[i] = s; + } + } else { + for (i = 0; i < self->num_children; i++) { + PyBList *child = blist_PREPARE_WRITE(self, i); + int status = blist_repr_r(child); + if (status < 0) + return _int(status); + } + } + + return _int(0); +} + +PyObject * +ext_make_clean_set(PyBListRoot *root, Py_ssize_t i, PyObject *v) +{ + PyBList *p = (PyBList *) root; + PyBList *next; + int k; + Py_ssize_t so_far, offset = 0; + PyObject *old_value; + int did_mark = 0; + + while (!p->leaf) { + blist_locate(p, i, (PyObject **) &next, &k, &so_far); + if (Py_REFCNT(next) <= 1) + p = next; + else { + p = blist_PREPARE_WRITE(p, k); + if (!did_mark) { + ext_mark((PyBList *) root, offset, DIRTY); + did_mark = 1; + } + } + assert(i >= so_far); + i -= so_far; + offset += so_far; + } + + if (!root->leaf) + ext_mark_clean(root, offset, p, 1); + + old_value = p->children[i]; + p->children[i] = v; + return old_value; +} + +PyObject * +blist_ass_item_return_slow(PyBListRoot *root, Py_ssize_t i, PyObject *v) +{ + Py_ssize_t dirty_offset, ioffset; + PyObject *rv; + assert(i >= 0); + assert(i < root->n); + invariants(root, VALID_RW); + ioffset = i / INDEX_FACTOR; + + if (root->leaf || ext_is_dirty(root, i, &dirty_offset) + || !GET_BIT(root->setclean_list, ioffset)) { + rv = ext_make_clean_set(root, i, v); + } else { + Py_ssize_t offset = root->offset_list[ioffset]; + PyBList *p = root->index_list[ioffset]; + assert(i >= offset); + assert(p); + assert(p->leaf); + if (i < offset + p->n) { + good: + /* If we're here, Py_REFCNT(p) == 1, most likely. + * However, we can't assert() it since there are two + * exceptions: + * 1) The user may have acquired a ref via gc, or + * 2) an iterator may have a reference + * + * If it's an iterator, we can go ahead and make the + * change anyway since we're not changing the length + * of the list. + */ + rv = p->children[i - offset]; + p->children[i - offset] = v; + if (dirty_offset >= 0) + ext_make_clean(root, dirty_offset); + } else if (ext_is_dirty(root,i + INDEX_FACTOR,&dirty_offset) + || !GET_BIT(root->setclean_list, ioffset+1)) { + rv = ext_make_clean_set(root, i, v); + } else { + ioffset++; + assert(ioffset < root->index_allocated); + offset = root->offset_list[ioffset]; + p = root->index_list[ioffset]; + assert(p); + assert(p->leaf); + assert(i < offset + p->n); + + goto good; + } + } + + return _ob(rv); +} + +BLIST_LOCAL_INLINE(PyObject *) +blist_ass_item_return(PyBList *self, Py_ssize_t i, PyObject *v) +{ + Py_INCREF(v); + if (self->leaf) { + PyObject *rv = self->children[i]; + self->children[i] = v; + return rv; + } + + return blist_ass_item_return2((PyBListRoot*)self, i, v); +} + +#ifndef Py_BUILD_CORE +BLIST_LOCAL(PyObject *) +blist_richcompare_list(PyBList *v, PyListObject *w, int op) +{ + int cmp; + PyObject *ret, *item; + Py_ssize_t i; + int v_stopped = 0; + int w_stopped = 0; + fast_compare_data_t fast_cmp_type = no_fast_eq; + + invariants(v, VALID_RW); + + if (v->n != PyList_GET_SIZE(w) && (op == Py_EQ || op == Py_NE)) { + /* Shortcut: if the lengths differ, the lists differ */ + PyObject *res; + if (op == Py_EQ) { + false: + res = Py_False; + } else { + true: + res = Py_True; + } + Py_INCREF(res); + return _ob(res); + } + + /* Search for the first index where items are different */ + i = 0; + ITER(v, item, { + if (i >= PyList_GET_SIZE(w)) { + w_stopped = 1; + break; + } + if (i == 0) + fast_cmp_type = check_fast_cmp_type(item, Py_EQ); + + cmp = fast_eq(item, w->ob_item[i], fast_cmp_type); + + if (cmp < 0) { + ITER_CLEANUP(); + return _ob(NULL); + } else if (!cmp) { + if (op == Py_EQ) { ITER_CLEANUP(); goto false; } + if (op == Py_NE) { ITER_CLEANUP(); goto true; } + + /* Last RichComparebool may have modified the list */ + if (i >= PyList_GET_SIZE(w)) { + w_stopped = 1; + break; + } + + DANGER_BEGIN; + ret = PyObject_RichCompare(item, w->ob_item[i], op); + DANGER_END; + ITER_CLEANUP(); + return ret; + } + i++; + }); + + if (!w_stopped) { + v_stopped = 1; + if (i >= PyList_GET_SIZE(w)) + w_stopped = 1; + } + + /* No more items to compare -- compare sizes */ + switch (op) { + case Py_LT: cmp = v_stopped && !w_stopped; break; + case Py_LE: cmp = v_stopped; break; + case Py_EQ: cmp = v_stopped == w_stopped; break; + case Py_NE: cmp = v_stopped != w_stopped; break; + case Py_GT: cmp = !v_stopped && w_stopped; break; + case Py_GE: cmp = w_stopped; break; + default: + /* cannot happen */ + PyErr_BadInternalCall(); + return _ob(NULL); + } + + if (cmp) goto true; + else goto false; +} +#endif + +BLIST_LOCAL(PyObject *) +blist_richcompare_item(int c, int op, PyObject *item1, PyObject *item2) +{ + PyObject *ret; + + if (c < 0) + return NULL; + if (!c) { + if (op == Py_EQ) + Py_RETURN_FALSE; + if (op == Py_NE) + Py_RETURN_TRUE; + DANGER_BEGIN; + ret = PyObject_RichCompare(item1, item2, op); + DANGER_END; + return ret; + } + + /* Impossible to get here */ + assert(0); + return NULL; +} + +static PyObject *blist_richcompare_len(PyBList *v, PyBList *w, int op) +{ + /* No more items to compare -- compare sizes */ + switch (op) { + case Py_LT: if (v->n < w->n) Py_RETURN_TRUE; else Py_RETURN_FALSE; + case Py_LE: if (v->n <= w->n) Py_RETURN_TRUE; else Py_RETURN_FALSE; + case Py_EQ: if (v->n == w->n) Py_RETURN_TRUE; else Py_RETURN_FALSE; + case Py_NE: if (v->n != w->n) Py_RETURN_TRUE; else Py_RETURN_FALSE; + case Py_GT: if (v->n > w->n) Py_RETURN_TRUE; else Py_RETURN_FALSE; + case Py_GE: if (v->n >= w->n) Py_RETURN_TRUE; else Py_RETURN_FALSE; + default: return NULL; /* cannot happen */ + } +} + +BLIST_LOCAL(PyObject *) +blist_richcompare_slow(PyBList *v, PyBList *w, int op) +{ + /* Search for the first index where items are different */ + PyObject *item1, *item2; + iter_t it1, it2; + int c; + PyBList *leaf1, *leaf2; + fast_compare_data_t fast_cmp_type; + + iter_init(&it1, v); + iter_init(&it2, w); + + leaf1 = it1.leaf; + leaf2 = it2.leaf; + fast_cmp_type = check_fast_cmp_type(it1.leaf->children[0], Py_EQ); + do { + if (it1.i < leaf1->num_children) { + item1 = leaf1->children[it1.i++]; + } else { + item1 = iter_next(&it1); + leaf1 = it1.leaf; + if (item1 == NULL) { + compare_len: + iter_cleanup(&it1); + iter_cleanup(&it2); + return blist_richcompare_len(v, w, op); + } + } + + if (it2.i < leaf2->num_children) { + item2 = leaf2->children[it2.i++]; + } else { + item2 = iter_next(&it2); + leaf2 = it2.leaf; + if (item2 == NULL) + goto compare_len; + } + + c = fast_eq(item1, item2, fast_cmp_type); + } while (c >= 1); + + iter_cleanup(&it1); + iter_cleanup(&it2); + return blist_richcompare_item(c, op, item1, item2); +} + +BLIST_LOCAL(PyObject *) +blist_richcompare_blist(PyBList *v, PyBList *w, int op) +{ + int i, c; + fast_compare_data_t fast_cmp_type; + + if (v->n != w->n) { + /* Shortcut: if the lengths differ, the lists differ */ + if (op == Py_EQ) { + Py_RETURN_FALSE; + } else if (op == Py_NE) { + Py_RETURN_TRUE; + } + + if (!v->n) { + /* Shortcut: first list empty, second un-empty. */ + switch (op) { + case Py_LT: case Py_LE: Py_RETURN_TRUE; + case Py_GT: case Py_GE: Py_RETURN_FALSE; + default: return NULL; /* cannot happen */ + } + } + } else if (!v->n) { + /* Shortcut: two empty lists */ + switch (op) { + case Py_NE: case Py_GT: case Py_LT: Py_RETURN_FALSE; + case Py_LE: case Py_EQ: case Py_GE: Py_RETURN_TRUE; + default: return NULL; /* cannot happen */ + } + } + + if (!v->leaf || !w->leaf) + return blist_richcompare_slow(v, w, op); + + /* Due to the shortcuts above, we know that v->n > 0 */ + fast_cmp_type = check_fast_cmp_type(v->children[0], Py_EQ); + + for (i = 0; i < v->num_children && i < w->num_children; i++) { + c = fast_eq(v->children[i], w->children[i], fast_cmp_type); + if (c < 1) + return blist_richcompare_item(c, op, v->children[i], + w->children[i]); + } + return blist_richcompare_len(v, w, op); + +} + +/* Reverse a slice of a list in place, from lo up to (exclusive) hi. */ +BLIST_LOCAL_INLINE(void) +reverse_slice(register PyObject **restrict lo, register PyObject **restrict hi) +{ + register PyObject *t; + assert(lo && hi); + + /* slice of length 0 */ + if (hi == lo) return; + + /* Use Duff's Device + * http://en.wikipedia.org/wiki/Duff%27s_device + */ + + --hi; + + switch ((hi - lo) & 31) { + case 31: do { t = *lo; *lo++ = *hi; *hi-- = t; + case 30: case 29: t = *lo; *lo++ = *hi; *hi-- = t; + case 28: case 27: t = *lo; *lo++ = *hi; *hi-- = t; + case 26: case 25: t = *lo; *lo++ = *hi; *hi-- = t; + case 24: case 23: t = *lo; *lo++ = *hi; *hi-- = t; + case 22: case 21: t = *lo; *lo++ = *hi; *hi-- = t; + case 20: case 19: t = *lo; *lo++ = *hi; *hi-- = t; + case 18: case 17: t = *lo; *lo++ = *hi; *hi-- = t; + case 16: case 15: t = *lo; *lo++ = *hi; *hi-- = t; + case 14: case 13: t = *lo; *lo++ = *hi; *hi-- = t; + case 12: case 11: t = *lo; *lo++ = *hi; *hi-- = t; + case 10: case 9: t = *lo; *lo++ = *hi; *hi-- = t; + case 8: case 7: t = *lo; *lo++ = *hi; *hi-- = t; + case 6: case 5: t = *lo; *lo++ = *hi; *hi-- = t; + case 4: case 3: t = *lo; *lo++ = *hi; *hi-- = t; + case 2: case 1: t = *lo; *lo++ = *hi; *hi-- = t; + case 0: ; + } while (lo < hi); + } +} + +/* self *= 2 */ +static void blist_double(PyBList *self) +{ + if (self->num_children > HALF) { + blist_extend_blist(self, self); + return; + } + + copyref(self, self->num_children, self, 0, self->num_children); + self->num_children *= 2; + self->n *= 2; +} + +BLIST_LOCAL(int) +blist_extend(PyBList *self, PyObject *other) +{ + int err; + PyBList *bother = NULL; + + invariants(self, VALID_PARENT|VALID_RW); + + if (PyBList_Check(other)) { + err = blist_extend_blist(self, (PyBList *) other); + goto done; + } + + bother = blist_root_new(); + err = blist_init_from_seq(bother, other); + if (err < 0) + goto done; + err = blist_extend_blist(self, bother); + ext_mark(self, 0, DIRTY); + + done: + SAFE_XDECREF(bother); + return _int(err); +} + +BLIST_LOCAL(PyObject *) +blist_repeat(PyBList *self, Py_ssize_t n) +{ + Py_ssize_t mask; + PyBList *power = NULL, *rv, *remainder = NULL; + Py_ssize_t remainder_n = 0; + + invariants(self, VALID_PARENT); + + if (n <= 0 || self->n == 0) + return _ob((PyObject *) blist_root_new()); + + if ((self->n * n) / n != self->n) + return _ob(PyErr_NoMemory()); + + rv = blist_root_new(); + if (rv == NULL) + return _ob(NULL); + + if (n == 1) { + blist_become(rv, self); + ext_mark(rv, 0, DIRTY); + return _ob((PyObject *) rv); + } + + if (self->num_children > HALF) + blist_become(rv, self); + else { + Py_ssize_t fit, fitn, so_far; + + rv->leaf = self->leaf; + fit = LIMIT / self->num_children; + if (fit > n) fit = n; + fitn = fit * self->num_children; + xcopyref(rv, 0, self, 0, self->num_children); + so_far = self->num_children; + while (so_far*2 < fitn) { + xcopyref(rv, so_far, rv, 0, so_far); + so_far *= 2; + } + xcopyref(rv, so_far, rv, 0, (fitn - so_far)); + + rv->num_children = fitn; + rv->n = self->n * fit; + check_invariants(rv); + + if (fit == n) { + ext_mark(rv, 0, DIRTY); + return _ob((PyObject *) rv); + } + + remainder_n = n % fit; + n /= fit; + + if (remainder_n) { + remainder = blist_root_new(); + if (remainder == NULL) + goto error; + remainder->n = self->n * remainder_n; + remainder_n *= self->num_children; + remainder->leaf = self->leaf; + xcopyref(remainder, 0, rv, 0, remainder_n); + remainder->num_children = remainder_n; + check_invariants(remainder); + } + } + + if (n == 0) + goto do_remainder; + + power = rv; + rv = blist_root_new(); + if (rv == NULL) { + SAFE_XDECREF(remainder); + error: + SAFE_DECREF(power); + return _ob(NULL); + } + + if (n & 1) + blist_become(rv, power); + + for (mask = 2; mask <= n; mask <<= 1) { + blist_double(power); + if (mask & n) + blist_extend_blist(rv, power); + } + SAFE_DECREF(power); + + do_remainder: + + if (remainder) { + blist_extend_blist(rv, remainder); + SAFE_DECREF(remainder); + } + + check_invariants(rv); + ext_mark(rv, 0, DIRTY); + return _ob((PyObject *) rv); +} + +BLIST_LOCAL(void) +linearize_rw_r(PyBList *self) +{ + int i; + + invariants(self, VALID_PARENT|VALID_RW); + + for (i = 0; i < self->num_children; i++) { + PyBList *restrict p = blist_PREPARE_WRITE(self, i); + if (!p->leaf) + linearize_rw_r(p); + } + + _void(); + return; +} + +BLIST_LOCAL(void) +linearize_rw(PyBListRoot *self) +{ + int i; + + if (self->leaf || self->dirty_root == CLEAN_RW) + return; + + if (self->dirty_root == CLEAN) { + Py_ssize_t n = SETCLEAN_LEN(INDEX_LENGTH(self)); + for (i = 0; i < n; i++) + if (self->setclean_list[i] != (unsigned) -1) + goto slow; + memset(self->setclean_list, 255, + SETCLEAN_LEN(INDEX_LENGTH(self)) * sizeof(unsigned)); + self->dirty_root = CLEAN_RW; + return; + } + +slow: + linearize_rw_r((PyBList *)self); + ext_reindex_set_all(self); +} + +BLIST_LOCAL(void) +blist_reverse(PyBListRoot *restrict self) +{ + int idx, ridx; + PyBList *restrict left, *restrict right; + register PyObject **restrict slice1; + register PyObject **restrict slice2; + int n1, n2; + + invariants(self, VALID_ROOT|VALID_RW); + + if (self->leaf) { + reverse_slice(self->children, + &self->children[self->num_children]); + _void(); + return; + } + + linearize_rw(self); + + idx = 0; + left = self->index_list[idx]; + if (left == self->index_list[idx+1]) + idx++; + slice1 = &left->children[0]; + n1 = left->num_children; + + ridx = INDEX_LENGTH(self)-1; + right = self->index_list[ridx]; + if (right == self->index_list[ridx-1]) + ridx--; + slice2 = &right->children[right->num_children-1]; + n2 = right->num_children; + + while (idx < ridx) { + int n = (n1 < n2) ? n1 : n2; + int count = (n+31) / 32; + switch (n & 31) { + register PyObject *t; + case 0: do { t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 31: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 30: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 29: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 28: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 27: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 26: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 25: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 24: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 23: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 22: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 21: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 20: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 19: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 18: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 17: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 16: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 15: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 14: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 13: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 12: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 11: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 10: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 9: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 8: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 7: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 6: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 5: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 4: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 3: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 2: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + case 1: t = *slice1; *slice1++ = *slice2; *slice2-- = t; + } while (--count > 0); + } + + n1 -= n; + if (!n1) { + idx++; + left = self->index_list[idx]; + if (left == self->index_list[idx+1]) + idx++; + slice1 = &left->children[0]; + n1 = left->num_children; + } + + n2 -= n; + if (!n2) { + ridx--; + right = self->index_list[ridx]; + if (right == self->index_list[ridx-1]) + ridx--; + slice2 = &right->children[right->num_children-1]; + n2 = right->num_children; + } + } + + if (left == right && slice1 < slice2) + reverse_slice(slice1, slice2 + 1); + + _void(); + return; +} + +BLIST_LOCAL(int) +blist_append(PyBList *self, PyObject *v) +{ + PyBList *overflow; + PyBList *p; + + invariants(self, VALID_ROOT|VALID_RW); + + if (self->n == PY_SSIZE_T_MAX) { + PyErr_SetString(PyExc_OverflowError, + "cannot add more objects to list"); + return _int(-1); + } + + for (p = self; !p->leaf; p= (PyBList*)p->children[p->num_children-1]) { + if (p != self && Py_REFCNT(p) > 1) + goto cleanup_and_slow; + p->n++; + } + + if (p->num_children == LIMIT || (p != self && Py_REFCNT(p) > 1)) { + PyBList *p2; + cleanup_and_slow: + for (p2 = self; p2 != p; + p2 = (PyBList*)p2->children[p2->num_children-1]) + p2->n--; + goto slow; + } + + p->children[p->num_children++] = v; + p->n++; + Py_INCREF(v); + + if ((self->n-1) % INDEX_FACTOR == 0) + ext_mark(self, 0, DIRTY); +#ifdef Py_DEBUG + else + ((PyBListRoot*)self)->last_n++; +#endif + check_invariants(self); + return _int(0); + + slow: + overflow = ins1(self, self->n, v); + if (overflow) + blist_overflow_root(self, overflow); + ext_mark(self, 0, DIRTY); + + return _int(0); +} + +/************************************************************************ + * Sorting code + * + * Bits and pieces swiped from Python's listobject.c + * + * Invariant: In case of an error, any sort function returns the list + * to a valid state before returning. "Valid" means that each item + * originally in the list is still in the list. No removals, no + * additions, and no changes to the reference counters. + * + ************************************************************************/ + +static void +unwrap_leaf_array(PyBList **leafs, int leafs_n, int n, + sortwrapperobject *array) +{ + int i, j, k = 0; + + for (i = 0; i < leafs_n; i++) { + PyBList *leaf = leafs[i]; + if (leafs_n > 1 && !_PyObject_GC_IS_TRACKED(leafs[i])) + PyObject_GC_Track(leafs[i]); + for (j = 0; j < leaf->num_children && k < n; j++, k++) { + sortwrapperobject *wrapper; + wrapper = (sortwrapperobject *) leaf->children[j]; + leaf->children[j] = wrapper->value; + DANGER_BEGIN; + Py_DECREF(wrapper->key); + DANGER_END; + } + } +} + +#define KEY_ALL_DOUBLE 1 +#define KEY_ALL_LONG 2 + +static int +wrap_leaf_array(sortwrapperobject *restrict array, + PyBList **leafs, int leafs_n, int n, + PyObject *restrict keyfunc, + int *restrict pkey_flags) +{ + int i, j, k; + int key_flags; + + key_flags = KEY_ALL_DOUBLE | KEY_ALL_LONG; + + for (k = i = 0; i < leafs_n; i++) { + PyBList *restrict leaf = leafs[i]; + if (leafs_n > 1) + PyObject_GC_UnTrack(leaf); + + for (j = 0; j < leaf->num_children; j++) { + sortwrapperobject *restrict pair = &array[k]; + PyObject *restrict key, *value = leaf->children[j]; + PyTypeObject *type; + if (keyfunc == NULL) { + key = value; + Py_INCREF(key); + } else { + DANGER_BEGIN; + key = PyObject_CallFunctionObjArgs( + keyfunc, value, NULL); + DANGER_END; + if (key == NULL) { + unwrap_leaf_array(leafs, leafs_n, k, array); + return -1; + } + } + type = key->ob_type; +#ifdef BLIST_FLOAT_RADIX_SORT + if (type == &PyFloat_Type) { + double d = PyFloat_AS_DOUBLE(key); + PY_UINT64_T di, mask; + memcpy(&di, &d, 8); + mask = (-(PY_INT64_T) (di >> 63)) + | (1ull << 63ull); + pair->fkey.k_uint64 = di ^ mask; + key_flags &= KEY_ALL_DOUBLE; + } else +#endif +#if PY_MAJOR_VERSION < 3 + if (type == &PyInt_Type) { + long i = PyInt_AS_LONG(key); + unsigned long u = i; + const unsigned long mask = 1ul << (sizeof(long)*8-1); + pair->fkey.k_ulong = u ^ mask; + key_flags &= KEY_ALL_LONG; + } else +#endif + if (type == &PyLong_Type) { + unsigned long x = PyLong_AsLong(key); + if (x == (unsigned long) (long) -1 + && PyErr_Occurred()) { + PyErr_Clear(); + key_flags = 0; + } else { + const unsigned long mask = 1ul << (sizeof(long)*8-1); + pair->fkey.k_ulong = x ^ mask; + key_flags &= KEY_ALL_LONG; + } + } else + key_flags = 0; + pair->key = key; + pair->value = value; + leaf->children[j] = (PyObject*) pair; + k++; + } + } + + assert(k == n); + + *pkey_flags = key_flags; + return 0; +} + +/* If COMPARE is NULL, calls PyObject_RichCompareBool with Py_LT, else calls + * islt. This avoids a layer of function call in the usual case, and + * sorting does many comparisons. + * Returns -1 on error, 1 if x < y, 0 if x >= y. + * + * In Python 3, COMPARE is always NULL. + */ + +#define FAST_ISLT(X, Y, fast_cmp_type) \ + (fast_lt(((sortwrapperobject *)(X))->key, \ + ((sortwrapperobject *)(Y))->key, \ + (fast_cmp_type))) + +#if PY_MAJOR_VERSION < 3 +#define ISLT(X, Y, COMPARE, fast_cmp_type) \ + ((COMPARE) == NULL ? \ + FAST_ISLT(X, Y, fast_cmp_type) : \ + islt(((sortwrapperobject *)(X))->key, \ + ((sortwrapperobject *)(Y))->key, COMPARE)) +#else +#define ISLT(X, Y, COMPARE, fast_cmp_type) \ + (FAST_ISLT((X), (Y), (fast_cmp_type))) +#endif + +#if PY_MAJOR_VERSION < 3 +/* XXX + + Efficiency improvement: + Keep one PyTuple in a global spot and just change what it points to. + We can also skip all the INCREF/DECREF stuff then and just borrow + references +*/ +static int islt(PyObject *x, PyObject *y, PyObject *compare) +{ + PyObject *res; + PyObject *args; + Py_ssize_t i; + + Py_INCREF(x); + Py_INCREF(y); + + DANGER_BEGIN; + args = PyTuple_New(2); + DANGER_END; + + if (args == NULL) { + DANGER_BEGIN; + Py_DECREF(x); + Py_DECREF(y); + DANGER_END; + return -1; + } + + PyTuple_SET_ITEM(args, 0, x); + PyTuple_SET_ITEM(args, 1, y); + DANGER_BEGIN; + res = PyObject_Call(compare, args, NULL); + Py_DECREF(args); + DANGER_END; + if (res == NULL) + return -1; + if (!PyInt_CheckExact(res)) { + PyErr_Format(PyExc_TypeError, + "comparison function must return int, not %.200s", + Py_TYPE(res)->tp_name); + Py_DECREF(res); + return -1; + } + i = PyInt_AsLong(res); + Py_DECREF(res); + return i < 0; +} +#endif + +#define INSERTION_THRESH 0 +#define BINARY_THRESH 10 + +#define TESTSWAP(i, j) { \ + if (fast_lt(sortarray[j], sortarray[i], fast_cmp_type)) { \ + PyObject *t = sortarray[j]; \ + sortarray[j] = sortarray[i]; \ + sortarray[i] = t; \ + } \ + } + +#if 0 +BLIST_LOCAL(int) +network_sort(PyObject **sortarray, Py_ssize_t n) +{ + fast_compare_data_t fast_cmp_type; + fast_cmp_type = check_fast_cmp_type(sortarray[0], Py_LT); + + switch(n) { + case 0: + case 1: + assert(0); + case 2: + TESTSWAP(0, 1); + return 0; + case 3: + TESTSWAP(0, 1); + TESTSWAP(0, 2); + TESTSWAP(1, 2); + return 0; + case 4: + TESTSWAP(0, 1); + TESTSWAP(2, 3); + TESTSWAP(0, 2); + TESTSWAP(1, 3); + TESTSWAP(1, 2); + return 0; + case 5: + TESTSWAP(0, 1); + TESTSWAP(3, 4); + TESTSWAP(2, 4); + TESTSWAP(2, 3); + TESTSWAP(1, 4); + TESTSWAP(0, 3); + TESTSWAP(0, 2); + TESTSWAP(1, 3); + TESTSWAP(1, 2); + return 0; + case 6: + TESTSWAP(1, 2); + TESTSWAP(4, 5); + + TESTSWAP(0, 2); + TESTSWAP(3, 5); + + TESTSWAP(0, 1); + TESTSWAP(3, 4); + TESTSWAP(2, 5); + + TESTSWAP(0, 3); + TESTSWAP(1, 4); + + TESTSWAP(2, 4); + TESTSWAP(1, 3); + + TESTSWAP(2, 3); + return 0; + default: + /* Should not be possible */ + assert (0); + abort(); + } +} +#endif + +#ifdef BLIST_FLOAT_RADIX_SORT +BLIST_LOCAL_INLINE(int) +insertion_sort_uint64(sortwrapperobject *array, Py_ssize_t n) +{ + int i, j; + PY_UINT64_T tmp_key; + PyObject *tmp_value; + for (i = 1; i < n; i++) { + tmp_key = array[i].fkey.k_uint64; + tmp_value = array[i].value; + for (j = i; j >= 1; j--) { + if (tmp_key >= array[j-1].fkey.k_uint64) + break; + array[j].fkey.k_uint64 = array[j-1].fkey.k_uint64; + array[j].value = array[j-1].value; + } + array[j].fkey.k_uint64 = tmp_key; + array[j].value = tmp_value; + } + + return 0; +} +#endif + +BLIST_LOCAL_INLINE(int) +insertion_sort_ulong(sortwrapperobject *restrict array, Py_ssize_t n) +{ + int i, j; + unsigned long tmp_key; + PyObject *tmp_value; + + for (i = 1; i < n; i++) { + tmp_key = array[i].fkey.k_ulong; + tmp_value = array[i].value; + for (j = i; j >= 1; j--) { + if (tmp_key >= array[j-1].fkey.k_ulong) + break; + array[j].fkey.k_ulong = array[j-1].fkey.k_ulong; + array[j].value = array[j-1].value; + } + array[j].fkey.k_ulong = tmp_key; + array[j].value = tmp_value; + } + + return 0; +} + +#if 0 +static int binary_sort_int64(sortwrapperobject *array, int n) +{ + int i, j, low, high, mid, c; + sortwrapperobject tmp; + + for (i = 1; i < n; i++) { + tmp = array[i]; + + c = INT64_LT(tmp, array[i-1]); + if (c < 0) + return -1; + if (c == 0) + continue; + + low = 0; + high = i-1; + + while (low < high) { + mid = low + (high - low)/2; + c = INT64_LT(tmp, array[mid]); + if (c < 0) + return -1; + if (c == 0) + low = mid+1; + else + high = mid; + } + + for (j = i; j >= low; j--) + array[j] = array[j-1]; + + array[low] = tmp; + } + + return 0; +} +#endif + +BLIST_LOCAL(int) +mini_merge(PyObject **array, int middle, int n, PyObject *compare) +{ + int c, ret = 0; + + PyObject *copy[LIMIT]; + PyObject **left; + PyObject **right = &array[middle]; + PyObject **rend = &array[n]; + PyObject **lend = ©[middle]; + PyObject **src; + PyObject **dst; + fast_compare_data_t fast_cmp_type; + + assert (middle <= LIMIT); + + fast_cmp_type = check_fast_cmp_type(array[0], Py_LT); + + for (left = array; left < right; left++) { + c = ISLT(*right, *left, compare, fast_cmp_type); + if (c < 0) + return -1; + if (c) + goto normal; + } + + return 0; + + normal: + src = left; + dst = left; + + for (left = copy; src < right; left++) + *left = *src++; + + lend = left; + + *dst++ = *right++; + + for (left = copy; left < lend && right < rend; dst++) { + c = ISLT(*right, *left, compare, fast_cmp_type); + if (c < 0) { + ret = -1; + goto done; + } + if (c == 0) + *dst = *left++; + else + *dst = *right++; + } + + done: + while (left < lend) + *dst++ = *left++; + + return ret; +} + +#define RUN_THRESH 5 + +BLIST_LOCAL(int) +gallop_sort(PyObject **array, int n, PyObject *compare) +{ + int i; + int run_start = 0, run_dir = 2; + PyObject **runs[LIMIT/RUN_THRESH+2]; + int ns[LIMIT/RUN_THRESH+2]; + int num_runs = 0; + PyObject **run = array; + fast_compare_data_t fast_cmp_type; + + if (n < 2) return 0; + + fast_cmp_type = check_fast_cmp_type(array[0], Py_LT); + + for (i = 1; i < n; i++) { + int c = ISLT(array[i], array[i-1], compare, fast_cmp_type); + assert(c < 0 || c == 0 || c == 1); + if (c == run_dir) + continue; + if (c < 0) + return -1; + if (run_start == i-1) + run_dir = c; + else if (i - run_start >= RUN_THRESH) { + if (run_dir > 0) + reverse_slice(run, &array[i]); + runs[num_runs] = run; + ns[num_runs++] = i - run_start; + run = &array[i]; + run_start = i; + run_dir = 2; + } else { + int j; + int low = run - array; + int high = i-1; + int mid; + PyObject *tmp = array[i]; + + /* XXX: Is this a stable sort? */ + /* XXX: In both directions? */ + + while (low < high) { + mid = low + (high - low)/2; + c = ISLT(tmp, array[mid], compare, + fast_cmp_type); + assert(c < 0 || c == 0 || c == 1); + if (c == run_dir) + low = mid+1; + else if (c < 0) + return -1; + else + high = mid; + } + + for (j = i; j > low; j--) + array[j] = array[j-1]; + + array[low] = tmp; + } + } + + if (run_dir > 0) + reverse_slice(run, &array[n]); + runs[num_runs] = run; + ns[num_runs++] = n - run_start; + + while(num_runs > 1) { + for (i = 0; i < num_runs/2; i++) { + int total = ns[2*i] + ns[2*i+1]; + if (0 > mini_merge(runs[2*i], ns[2*i], total, + compare)) { + /* List valid due to invariants */ + return -1; + } + + runs[i] = runs[2*i]; + ns[i] = total; + } + + if (num_runs & 1) { + runs[i] = runs[num_runs - 1]; + ns[i] = ns[num_runs - 1]; + } + num_runs = (num_runs+1)/2; + } + + assert(ns[0] == n); + + return 0; + +} + +#if 0 +BLIST_LOCAL(int) +mini_merge_sort(PyObject **array, int n, PyObject *compare) +{ + int i, run_size = BINARY_THRESH; + + for (i = 0; i < n; i += run_size) { + int len = run_size; + if (n - i < len) + len = n - i; + if (binary_sort(&array[i], len, compare) < 0) + return -1; + } + + run_size *= 2; + while (run_size < n) { + for (i = 0; i < n; i += run_size) { + int len = run_size; + if (n - i < len) + len = n - i; + if (len <= run_size/2) + continue; + if (mini_merge(&array[i], run_size/2, len, compare) < 0) + return -1; + } + run_size *= 2; + } + + return 0; +} +#endif + +#if PY_MAJOR_VERSION < 3 +BLIST_LOCAL(int) +is_default_cmp(PyObject *cmpfunc) +{ + PyCFunctionObject *f; + if (cmpfunc == NULL || cmpfunc == Py_None) + return 1; + if (!PyCFunction_Check(cmpfunc)) + return 0; + f = (PyCFunctionObject *)cmpfunc; + if (f->m_self != NULL) + return 0; + if (!PyString_Check(f->m_module)) + return 0; + if (strcmp(PyString_AS_STRING(f->m_module), "__builtin__") != 0) + return 0; + if (strcmp(f->m_ml->ml_name, "cmp") != 0) + return 0; + return 1; +} +#endif + +BLIST_LOCAL(void) +do_fast_merge(PyBList **restrict out, PyBList **in1, PyBList **in2, + int n1, int n2) +{ + memcpy(out, in1, sizeof(PyBList *) * n1); + memcpy(&out[n1], in2, sizeof(PyBList *) * n2); +} + +BLIST_LOCAL(int) +try_fast_merge(PyBList **restrict out, PyBList **in1, PyBList **in2, + Py_ssize_t n1, Py_ssize_t n2, + PyObject *compare, int *err) +{ + int c; + PyBList *end; + + end = in1[n1-1]; + + c = ISLT(end->children[end->num_children-1], + in2[0]->children[0], compare, no_fast_lt); + + if (c < 0) { + error: + *err = -1; + do_fast_merge(out, in1, in2, n1, n2); + return 1; + } else if (c) { + do_fast_merge(out, in1, in2, n1, n2); + return 1; + } + + end = in2[n2-1]; + + c = ISLT(end->children[end->num_children-1], + in1[0]->children[0], compare, no_fast_lt); + if (c < 0) + goto error; + else if (c) { + do_fast_merge(out, in2, in1, n2, n1); + return 1; + } + + return 0; +} + +/* Merge two arrays of leaf nodes. */ +BLIST_LOCAL(int) +sub_merge(PyBList **restrict out, PyBList **in1, PyBList **in2, + Py_ssize_t n1, Py_ssize_t n2, + PyObject *compare, int *err) +{ + int c; + Py_ssize_t i, j; + PyBList *restrict leaf1, *restrict leaf2, *restrict output; + int leaf1_i = 0, leaf2_i = 0; + Py_ssize_t nout = 0; + fast_compare_data_t fast_cmp_type; + + if (try_fast_merge(out, in1, in2, n1, n2, compare, err)) + return n1 + n2; + + leaf1 = in1[leaf1_i++]; + leaf2 = in2[leaf2_i++]; + + i = 0; /* Index into leaf 1 */ + j = 0; /* Index into leaf 2 */ + + output = blist_new_no_GC(); + + fast_cmp_type = check_fast_cmp_type(leaf1->children[0], Py_LT); + + while ((leaf1_i < n1 || i < leaf1->num_children) + && (leaf2_i < n2 || j < leaf2->num_children)) { + + /* Check if we need to get a new input leaf node */ + if (i == leaf1->num_children) { + leaf1->num_children = 0; + SAFE_DECREF(leaf1); + leaf1 = in1[leaf1_i++]; + i = 0; + } + + if (j == leaf2->num_children) { + leaf2->num_children = 0; + SAFE_DECREF(leaf2); + leaf2 = in2[leaf2_i++]; + j = 0; + } + + assert (i < leaf1->num_children); + assert (j < leaf2->num_children); + + /* Check if we have filled up an output leaf node */ + if (output->n == LIMIT) { + out[nout++] = output; + output = blist_new_no_GC(); + } + + /* Figure out which input leaf has the lower element */ + c = ISLT(leaf2->children[j], leaf1->children[i], compare, + fast_cmp_type); + if (c < 0) { + *err = -1; + goto done; + } + if (c == 0) { + output->children[output->num_children++] + = leaf1->children[i++]; + } else { + output->children[output->num_children++] + = leaf2->children[j++]; + } + + output->n++; + } + + done: + /* Append our partially-complete output leaf node */ + nout = append_and_squish(out, nout, output); + + /* Append a partially-consumed input leaf node, if one exists */ + if (i < leaf1->num_children) { + shift_left(leaf1, i, i); + leaf1->num_children -= i; + leaf1->n -= i; + nout = append_and_squish(out, nout, leaf1); + } else { + leaf1->num_children = 0; + SAFE_DECREF(leaf1); + } + + if (j < leaf2->num_children) { + shift_left(leaf2, j, j); + leaf2->num_children -= j; + leaf2->n -= j; + nout = append_and_squish(out, nout, leaf2); + } else { + leaf2->num_children = 0; + SAFE_DECREF(leaf2); + } + + nout = balance_last_2(out, nout); + + /* Append the rest of any input that still has nodes. */ + if (leaf1_i < n1) { + memcpy(&out[nout], &in1[leaf1_i], + sizeof(PyBList *) * (n1 - leaf1_i)); + nout += n1 - leaf1_i; + } + + if (leaf2_i < n2) { + memcpy(&out[nout], &in2[leaf2_i], + sizeof(PyBList *) * (n2 - leaf2_i)); + nout += n2 - leaf2_i; + } + + for (i = nout; i < n1+n2; i++) + out[i] = NULL; + + assert(nout <= n1+n2); + + return nout; +} + +/* If swap is true, place the output in scratch. + * Otherwise, place the output in "in" */ +BLIST_LOCAL(Py_ssize_t) +sub_sort(PyBList **restrict scratch, PyBList **in, PyObject *compare, + Py_ssize_t n, int *err, int swap) +{ + Py_ssize_t half, n1, n2; + + if (!n) return n; + if (*err) { + if (swap) + memcpy(scratch, in, n * sizeof(PyBList *)); + return n; + } + if (n == 1) { + *err |= gallop_sort(in[0]->children, in[0]->num_children, + compare); + *scratch = *in; + return 1; + } + + half = n / 2; + + n1 = sub_sort(scratch, in, compare, half, err, !swap); + n2 = sub_sort(&scratch[half], &in[half], compare, n-half, err, !swap); + + /* If swap is true, the output is currently in "in". + * Otherwise, the output is currently in scratch. + * + * sub_merge() will reverse it. + */ + + if (!*err) { + if (swap) + n = sub_merge(scratch, in, &in[half], n1, n2, compare, err); + else + n = sub_merge(in, scratch, &scratch[half], n1, n2, compare, err); + } else { + if (swap) { + memcpy(scratch, in, n1 * sizeof(PyBList *)); + memcpy(&scratch[n1], &in[half], n2 * sizeof(PyBList *)); + } else { + memcpy(in, scratch, n1 * sizeof(PyBList *)); + memcpy(&in[n1], &scratch[half], n2 * sizeof(PyBList *)); + } + n = n1 + n2; + } + return n; +} + +#if 0 +BLIST_LOCAL_INLINE(void) +array_disable_GC(PyBList **leafs, Py_ssize_t num_leafs) +{ + Py_ssize_t i; + for (i = 0; i < num_leafs; i++) + PyObject_GC_UnTrack(leafs[i]); +} +#endif + +BLIST_LOCAL_INLINE(void) +array_enable_GC(PyBList **leafs, Py_ssize_t num_leafs) +{ + Py_ssize_t i; + for (i = 0; i < num_leafs; i++) + PyObject_GC_Track(leafs[i]); +} + +#define BITS_PER_PASS 8 +#define HISTOGRAM_SIZE (((Py_ssize_t) 1) << BITS_PER_PASS) +#define MASK (HISTOGRAM_SIZE - 1) +#define NUM_PASSES (((sizeof(unsigned long)*8-1) / BITS_PER_PASS)+1) + +/* The histogram arrays are two-dimension arrays of + * [HISTOGRAM_SIZE][NUM_PASSES]. Since that can end up somewhat large (16k on + * a 64-bit build), we allocate them on the heap instead of on the stack. + */ +typedef Py_ssize_t histogram_array_t[NUM_PASSES]; + +BLIST_LOCAL_INLINE(int) +sort_ulong(sortwrapperobject *restrict sortarray, Py_ssize_t n) +{ + sortwrapperobject *restrict scratch, *from, *to, *tmp; + Py_ssize_t i, j, sums[NUM_PASSES], count[NUM_PASSES], tsum; + histogram_array_t *histograms; + + memset(sums, 0, sizeof sums); + memset(count, 0, sizeof count); + + scratch = PyMem_New(sortwrapperobject, n); + if (scratch == NULL) + return -1; + + histograms = PyMem_New(histogram_array_t, HISTOGRAM_SIZE); + if (histograms == NULL) { + PyMem_Free(scratch); + return -1; + } + memset(histograms, 0, sizeof(histogram_array_t) * HISTOGRAM_SIZE); + + for (i = 0; i < n; i++) { + unsigned long v = sortarray[i].fkey.k_ulong; + for (j = 0; j < NUM_PASSES; j++) { + histograms[(v >> (BITS_PER_PASS * j)) & MASK][j]++; + } + } + + for (i = 0; i < HISTOGRAM_SIZE; i++) { + for (j = 0; j < NUM_PASSES; j++) { + count[j] += !!histograms[i][j]; + tsum = histograms[i][j] + sums[j]; + histograms[i][j] = sums[j] - 1; + sums[j] = tsum; + } + } + + from = sortarray; + to = scratch; + for (j = 0; j < NUM_PASSES; j++) { + sortwrapperobject *restrict f = from; + sortwrapperobject *restrict t = to; + if (count[j] == 1) continue; + for (i = 0; i < n; i++) { + unsigned long fi = f[i].fkey.k_ulong; + Py_ssize_t pos = (fi >> (BITS_PER_PASS * j)) & MASK; + pos = ++histograms[pos][j]; + t[pos].fkey.k_ulong = fi; + t[pos].value = f[i].value; + } + + tmp = from; + from = to; + to = tmp; + } + + if (from != sortarray) + for (i = 0; i < n; i++) + sortarray[i].value = scratch[i].value; + + PyMem_Free(histograms); + PyMem_Free(scratch); + return 0; +} + +#undef NUM_PASSES + +#ifdef BLIST_FLOAT_RADIX_SORT +#if SIZEOF_LONG == 8 +#define sort_uint64 sort_ulong +#else +#define NUM_PASSES (((64-1) / BITS_PER_PASS)+1) + +BLIST_LOCAL_INLINE(int) +sort_uint64(sortwrapperobject *restrict sortarray, Py_ssize_t n) +{ + sortwrapperobject *restrict scratch, *from, *to, *tmp; + Py_ssize_t i, j, sums[NUM_PASSES], count[NUM_PASSES], tsum; + histogram_array_t *histograms; + + memset(sums, 0, sizeof sums); + memset(count, 0, sizeof count); + + scratch = PyMem_New(sortwrapperobject, n); + if (scratch == NULL) + return -1; + + histograms = PyMem_New(histogram_array_t, HISTOGRAM_SIZE); + if (histograms == NULL) { + PyMem_Free(scratch); + return -1; + } + memset(histograms, 0, sizeof(histogram_array_t) * HISTOGRAM_SIZE); + + for (i = 0; i < n; i++) { + PY_UINT64_T v = sortarray[i].fkey.k_uint64; + for (j = 0; j < NUM_PASSES; j++) { + histograms[(v >> (BITS_PER_PASS * j)) & MASK][j]++; + } + } + + for (i = 0; i < HISTOGRAM_SIZE; i++) { + for (j = 0; j < NUM_PASSES; j++) { + count[j] += !!histograms[i][j]; + tsum = histograms[i][j] + sums[j]; + histograms[i][j] = sums[j] - 1; + sums[j] = tsum; + } + } + + from = sortarray; + to = scratch; + for (j = 0; j < NUM_PASSES; j++) { + if (count[j] == 1) continue; + for (i = 0; i < n; i++) { + PY_UINT64_T fi = from[i].fkey.k_uint64; + Py_ssize_t pos = (fi >> (BITS_PER_PASS * j)) & MASK; + pos = ++histograms[pos][j]; + to[pos].fkey.k_uint64 = fi; + to[pos].value = from[i].value; + } + + tmp = from; + from = to; + to = tmp; + } + + if (from != sortarray) + for (i = 0; i < n; i++) + sortarray[i].value = scratch[i].value; + + PyMem_Free(histograms); + PyMem_Free(scratch); + return 0; +} + +#undef NUM_PASSES + +#endif +#endif + +BLIST_LOCAL(Py_ssize_t) +sort(PyBListRoot *restrict self, PyObject *compare, PyObject *keyfunc) +{ + PyBList *leaf; + PyBList **leafs; + int err=0; + Py_ssize_t i, leafs_n = 0; + sortwrapperobject sortarraystack[10]; + sortwrapperobject *sortarray = sortarraystack; + int key_flags; + + if (self->leaf) + leafs = &leaf; + else { + leafs = PyMem_New(PyBList *, self->n / HALF + 1); + if (!leafs) + return -1; + } + + if (self->leaf) { + leaf = (PyBList *) self; + leafs_n = 1; + } else { + linearize_rw(self); + + assert(INDEX_LENGTH(self) <= self->index_allocated); + for (i = 0; i < INDEX_LENGTH(self)-1; i++) { + leaf = self->index_list[i]; + if (leaf == self->index_list[i+1]) + continue; + leafs[leafs_n++] = leaf; + Py_INCREF(leaf); + } + leaf = self->index_list[i]; + leafs[leafs_n++] = leaf; + Py_INCREF(leaf); + } + + if (self->n > 10) { + sortarray = PyMem_New(sortwrapperobject, self->n); + if (sortarray == NULL) { + sortarray = sortarraystack; + goto error; + } + } + + err = wrap_leaf_array(sortarray, leafs, leafs_n, self->n, keyfunc, + &key_flags); + if (err < 0) { + error: + if (!self->leaf) { + for (i = 0; i < leafs_n; i++) + SAFE_DECREF(leafs[i]); + PyMem_Free(leafs); + } + if (sortarray != sortarraystack) + PyMem_Free(sortarray); + return -1; + } + + if (key_flags && compare == NULL) { +#ifdef BLIST_FLOAT_RADIX_SORT + if (key_flags & KEY_ALL_DOUBLE) { + if (self->n < 40 && self->leaf) + err = insertion_sort_uint64(sortarray,self->n); + else + err = sort_uint64(sortarray, self->n); + } else +#endif + if (key_flags & KEY_ALL_LONG) { + if (self->n < 40 && self->leaf) + err = insertion_sort_ulong(sortarray, self->n); + else + err = sort_ulong(sortarray, self->n); + } + else + assert(0); /* Should not be possible */ + unwrap_leaf_array(leafs, leafs_n, self->n, sortarray); + if (!self->leaf) { + for (i = 0; i < leafs_n; i++) + SAFE_DECREF(leafs[i]); + PyMem_Free(leafs); + } + } else if (self->leaf) { + err = gallop_sort(self->children, self->num_children, compare); + unwrap_leaf_array(leafs, 1, self->n, sortarray); + } else { + PyBList **scratch = PyMem_New(PyBList *, self->n / HALF + 1); + if (!scratch) { + PyMem_Free(leafs); + PyMem_Free(sortarray); + return -1; + } + leafs_n = sub_sort(scratch, leafs, compare, leafs_n, &err, 0); + array_enable_GC(leafs, leafs_n); + PyMem_Free(scratch); + unwrap_leaf_array(leafs, leafs_n, self->n, sortarray); + i = blist_init_from_child_array(leafs, leafs_n); + + if (i < 0) { + /* XXX leaking memory here when out of memory */ + PyMem_Free(sortarray); + return -1; + } else { + assert(i == 1); + blist_become_and_consume((PyBList *) self, leafs[0]); + } + SAFE_DECREF(leafs[0]); + PyMem_Free(leafs); + } + + if (sortarray != sortarraystack) + PyMem_Free(sortarray); + return err; +} + +/************************************************************************ + * Section for functions callable directly by the interpreter. + * + * Each of these functions are marked with VALID_USER for debug mode. + * + * If they, or any function they call, makes calls to decref_later, + * they must call decref_flush() just before returning. + * + * These functions must not be called directly by other blist + * functions. They should *only* be called by the interpreter, to + * ensure that decref_flush() is the last thing called before + * returning to the interpreter. + */ + +BLIST_PYAPI(PyObject *) +py_blist_root_tp_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds) +{ + PyBList *self; + + if (subtype == &PyRootBList_Type) + return (PyObject *) blist_root_new(); + + self = (PyBList *) subtype->tp_alloc(subtype, 0); + if (self == NULL) + return NULL; + self->children = PyMem_New(PyObject *, LIMIT); + if (self->children == NULL) { + subtype->tp_free(self); + return NULL; + } + + self->leaf = 1; + ext_init((PyBListRoot *)self); + + return (PyObject *) self; +} + +/* Should only be used by the unpickler */ +BLIST_PYAPI(PyObject *) +py_blist_internal_tp_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds) +{ + assert (subtype == &PyBList_Type); + return (PyObject *) blist_new(); +} + +/* Should only be used by the unpickler */ +BLIST_PYAPI(int) +py_blist_internal_init(PyObject *oself, PyObject *args, PyObject *kw) +{ + return 0; +} + +BLIST_PYAPI(int) +py_blist_init(PyObject *oself, PyObject *args, PyObject *kw) +{ + int ret; + PyObject *arg = NULL; + static char *kwlist[] = {"sequence", 0}; + int err; + PyBList *self; + + invariants(oself, VALID_USER|VALID_DECREF); + self = (PyBList *) oself; + + DANGER_BEGIN; + err = PyArg_ParseTupleAndKeywords(args, kw, "|O:list", kwlist, &arg); + DANGER_END; + if (!err) + return _int(-1); + + if (self->n) { + blist_CLEAR(self); + ext_dealloc((PyBListRoot *) self); + } + + if (arg == NULL) + return _int(0); + + ret = blist_init_from_seq(self, arg); + + decref_flush(); /* Needed due to blist_CLEAR() call */ + return _int(ret); +} + +BLIST_PYAPI(PyObject *) +py_blist_richcompare(PyObject *v, PyObject *w, int op) +{ + PyObject *rv; + + if (!PyRootBList_Check(v)) { + not_implemented: + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + + invariants((PyBList *) v, VALID_USER|VALID_DECREF); + if (PyRootBList_Check(w)) { + rv = blist_richcompare_blist((PyBList *)v, (PyBList *)w, op); + decref_flush(); + return _ob(rv); + } +#ifndef Py_BUILD_CORE + if (PyList_Check(w)) { + rv = blist_richcompare_list((PyBList*)v, (PyListObject*)w, op); + decref_flush(); + return _ob(rv); + } +#endif + _void(); + goto not_implemented; +} + +BLIST_PYAPI(int) +py_blist_traverse(PyObject *oself, visitproc visit, void *arg) +{ + PyBList *self; + int i; + + assert(PyBList_Check(oself)); + self = (PyBList *) oself; + + for (i = 0; i < self->num_children; i++) { + if (self->children[i] != NULL) + Py_VISIT(self->children[i]); + } + return 0; +} + +BLIST_PYAPI(int) +py_blist_tp_clear(PyObject *oself) +{ + PyBList *self; + + invariants(oself, VALID_USER|VALID_RW|VALID_DECREF); + self = (PyBList *) oself; + + blist_forget_children(self); + self->n = 0; + self->leaf = 1; + ext_dealloc((PyBListRoot *) self); + + decref_flush(); + return _int(0); +} + +#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6 || PY_MAJOR_VERSION >= 3 +#define py_blist_nohash PyObject_HashNotImplemented +#else +BLIST_PYAPI(long) +py_blist_nohash(PyObject *self) +{ + PyErr_SetString(PyExc_TypeError, "list objects are unhashable"); + return -1; +} +#endif + +BLIST_PYAPI(void) +py_blist_dealloc(PyObject *oself) +{ + int i; + PyBList *self; + + assert(PyBList_Check(oself)); + self = (PyBList *) oself; + + if (_PyObject_GC_IS_TRACKED(self)) + PyObject_GC_UnTrack(self); + + Py_TRASHCAN_SAFE_BEGIN(self) + + /* Py_XDECREF() is needed here because the Python C API allows list + * items to be NULL. */ + for (i = 0; i < self->num_children; i++) + Py_XDECREF(self->children[i]); + + if (PyRootBList_Check(self)) { + ext_dealloc((PyBListRoot *) self); + if (PyRootBList_CheckExact(self) + && num_free_ulists < MAXFREELISTS) + free_ulists[num_free_ulists++] = self; + else + goto free_blist; + } else if (Py_TYPE(self) == &PyBList_Type + && num_free_lists < MAXFREELISTS) + free_lists[num_free_lists++] = self; + else { + free_blist: + PyMem_Free(self->children); + Py_TYPE(self)->tp_free((PyObject *)self); + } + + Py_TRASHCAN_SAFE_END(self); +} + +BLIST_PYAPI(int) +py_blist_ass_item(PyObject *oself, Py_ssize_t i, PyObject *v) +{ + PyObject *old_value; + PyBList *self; + + invariants(oself, VALID_USER|VALID_RW|VALID_DECREF); + + self = (PyBList *) oself; + + if (i >= self->n || i < 0) { + set_index_error(); + return _int(-1); + } + + if (v == NULL) { + blist_delitem(self, i); + ext_mark(self, 0, DIRTY); + decref_flush(); + return _int(0); + } + + old_value = blist_ass_item_return(self, i, v); + Py_XDECREF(old_value); + return _int(0); +} + +BLIST_PYAPI(int) +py_blist_ass_slice(PyObject *oself, Py_ssize_t ilow, Py_ssize_t ihigh, + PyObject *v) +{ + Py_ssize_t net; + PyBList *other, *left, *right, *self; + + invariants(oself, VALID_RW|VALID_USER|VALID_DECREF); + + self = (PyBList *) oself; + + if (ilow < 0) ilow = 0; + else if (ilow > self->n) ilow = self->n; + if (ihigh < ilow) ihigh = ilow; + else if (ihigh > self->n) ihigh = self->n; + + if (!v) { + blist_delslice(self, ilow, ihigh); + ext_mark(self, 0, DIRTY); + decref_flush(); + return _int(0); + } + + if (PyRootBList_Check(v) && (PyObject *) self != v) { + other = (PyBList *) v; + Py_INCREF(other); + ext_mark_set_dirty_all(other); + } else { + other = blist_root_new(); + if (v) { + int err = blist_init_from_seq(other, v); + if (err < 0) { + decref_later((PyObject *) other); + decref_flush(); + return _int(-1); + } + } + } + + net = other->n - (ihigh - ilow); + + /* Special case small lists */ + if (self->leaf && other->leaf && (self->n + net <= LIMIT)) + { + Py_ssize_t i; + + for (i = ilow; i < ihigh; i++) + decref_later(self->children[i]); + + if (net >= 0) + shift_right(self, ihigh, net); + else + shift_left(self, ihigh, -net); + self->num_children += net; + copyref(self, ilow, other, 0, other->n); + SAFE_DECREF(other); + blist_adjust_n(self); + decref_flush(); + return _int(0); + } + + left = self; + right = blist_root_copy(self); + blist_delslice(left, ilow, left->n); + blist_delslice(right, 0, ihigh); + blist_extend_blist(left, other); /* XXX check return values */ + blist_extend_blist(left, right); + + ext_mark(self, 0, DIRTY); + + SAFE_DECREF(other); + SAFE_DECREF(right); + + decref_flush(); + + return _int(0); +} + +BLIST_PYAPI(int) +py_blist_ass_subscript(PyObject *oself, PyObject *item, PyObject *value) +{ + PyBList *self; + + invariants(oself, VALID_USER|VALID_RW|VALID_DECREF); + + self = (PyBList *) oself; + + if (PyIndex_Check(item)) { + Py_ssize_t i; + PyObject *old_value; + + if (PyLong_CheckExact(item)) { + i = PyInt_AsSsize_t(item); + if (i == -1 && PyErr_Occurred()) { + PyErr_Clear(); + goto number; + } + } else { + number: + i = PyNumber_AsSsize_t(item, PyExc_IndexError); + if (i == -1 && PyErr_Occurred()) + return _int(-1); + } + if (i < 0) + i += self->n; + + if (i >= self->n || i < 0) { + set_index_error(); + return _int(-1); + } + + if (self->leaf) { + /* Speed up common cases */ + + old_value = self->children[i]; + if (value == NULL) { + shift_left(self, i+1, 1); + self->num_children--; + self->n--; + } else { + self->children[i] = value; + Py_INCREF(value); + } + DANGER_BEGIN; + Py_DECREF(old_value); + DANGER_END; + return _int(0); + } + + if (value == NULL) { + blist_delitem(self, i); + ext_mark(self, 0, DIRTY); + decref_flush(); + return _int(0); + } + + Py_INCREF(value); + old_value = blist_ass_item_return2((PyBListRoot*)self,i,value); + DANGER_BEGIN; + Py_DECREF(old_value); + DANGER_END; + return _int(0); + } else if (PySlice_Check(item)) { + Py_ssize_t start, stop, step, slicelength; + + ext_mark(self, 0, DIRTY); + +#if PY_MAJOR_VERSION < 3 || PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION < 2 + if (PySlice_GetIndicesEx((PySliceObject*)item, self->n, +#else + if (PySlice_GetIndicesEx(item, self->n, +#endif + &start, &stop,&step,&slicelength)<0) + return _int(-1); + + /* treat L[slice(a,b)] = v _exactly_ like L[a:b] = v */ + if (step == 1 && ((PySliceObject*)item)->step == Py_None) + return _redir(py_blist_ass_slice(oself,start,stop,value)); + + if (value == NULL) { + /* Delete back-to-front */ + Py_ssize_t i, cur; + + if (slicelength <= 0) + return _int(0); + + if (step > 0) { + stop = start - 1; + start = start + step*(slicelength-1); + step = -step; + } + + for (cur = start, i = 0; i < slicelength; + cur += step, i++) { + PyObject *ob = blist_delitem_return(self, cur); + decref_later(ob); + } + + decref_flush(); + ext_mark(self, 0, DIRTY); + + return _int(0); + } else { /* assign slice */ + PyObject *ins, *seq; + Py_ssize_t cur, i; + + DANGER_BEGIN; + seq = PySequence_Fast(value, + "Must assign iterable to extended slice"); + DANGER_END; + if (!seq) + return _int(-1); + + if (seq == (PyObject *) self) { + Py_DECREF(seq); + seq = (PyObject *) blist_root_copy(self); + } + + if (PySequence_Fast_GET_SIZE(seq) != slicelength) { + PyErr_Format(PyExc_ValueError, + "attempt to assign sequence of size %zd to extended slice of size %zd", + PySequence_Fast_GET_SIZE(seq), + slicelength); + Py_DECREF(seq); + return _int(-1); + } + + if (!slicelength) { + Py_DECREF(seq); + return _int(0); + } + + for (cur = start, i = 0; i < slicelength; + cur += step, i++) { + PyObject *ob; + ins = PySequence_Fast_GET_ITEM(seq, i); + ob = blist_ass_item_return(self, cur, ins); + decref_later(ob); + } + + Py_DECREF(seq); + + decref_flush(); + + return _int(0); + } + } else { + PyErr_SetString(PyExc_TypeError, + "list indices must be integers"); + return _int(-1); + } +} + +BLIST_PYAPI(Py_ssize_t) +py_blist_length(PyObject *ob) +{ + assert(PyRootBList_Check(ob)); + return ((PyBList *) ob)->n; +} + +BLIST_PYAPI(PyObject *) +py_blist_repeat(PyObject *oself, Py_ssize_t n) +{ + PyObject *ret; + PyBList *self; + + invariants(oself, VALID_USER|VALID_DECREF); + + self = (PyBList *) oself; + + ret = blist_repeat(self, n); + decref_flush(); + ext_mark_set_dirty_all(self); + + return _ob(ret); +} + +BLIST_PYAPI(PyObject *) +py_blist_inplace_repeat(PyObject *oself, Py_ssize_t n) +{ + PyBList *tmp, *self; + + invariants(oself, VALID_USER|VALID_RW|VALID_DECREF); + + self = (PyBList *) oself; + + tmp = (PyBList *) blist_repeat(self, n); + if (tmp == NULL) + return (PyObject *) _blist(NULL); + blist_become_and_consume(self, tmp); + Py_INCREF(self); + SAFE_DECREF(tmp); + + decref_flush(); + + ext_mark(self, 0, DIRTY); + + return (PyObject *) _blist(self); +} + +BLIST_PYAPI(PyObject *) +py_blist_extend(PyBList *self, PyObject *other) +{ + int err; + + invariants(self, VALID_USER|VALID_RW|VALID_DECREF); + + err = blist_extend(self, other); + decref_flush(); + ext_mark(self, 0, DIRTY); + if (PyBList_Check(other)) + ext_mark_set_dirty_all((PyBList *) other); + + if (err < 0) + return _ob(NULL); + Py_RETURN_NONE; +} + +BLIST_PYAPI(PyObject *) +py_blist_inplace_concat(PyObject *oself, PyObject *other) +{ + int err; + PyBList *self; + + invariants(oself, VALID_RW|VALID_USER|VALID_DECREF); + + self = (PyBList *) oself; + + err = blist_extend(self, other); + decref_flush(); + ext_mark(self, 0, DIRTY); + if (PyBList_Check(other)) + ext_mark_set_dirty_all((PyBList*) other); + + if (err < 0) + return _ob(NULL); + + Py_INCREF(self); + return _ob((PyObject *)self); +} + +BLIST_PYAPI(int) +py_blist_contains(PyObject *oself, PyObject *el) +{ + int c, ret = 0; + PyObject *item; + PyBList *self; + fast_compare_data_t fast_cmp_type; + + invariants(oself, VALID_USER | VALID_DECREF); + + self = (PyBList *) oself; + fast_cmp_type = check_fast_cmp_type(el, Py_EQ); + + ITER(self, item, { + c = fast_eq(el, item, fast_cmp_type); + if (c < 0) { + ret = -1; + break; + } + if (c > 0) { + ret = 1; + break; + } + }); + + decref_flush(); + return _int(ret); +} + +BLIST_PYAPI(PyObject *) +py_blist_get_slice(PyObject *oself, Py_ssize_t ilow, Py_ssize_t ihigh) +{ + PyBList *rv, *self; + + invariants(oself, VALID_USER | VALID_DECREF); + + self = (PyBList *) oself; + + if (ilow < 0) ilow = 0; + else if (ilow > self->n) ilow = self->n; + if (ihigh < ilow) ihigh = ilow; + else if (ihigh > self->n) ihigh = self->n; + + rv = blist_root_new(); + if (rv == NULL) + return (PyObject *) _blist(NULL); + + if (ihigh <= ilow || ilow >= self->n) + return (PyObject *) _blist(rv); + + if (self->leaf) { + Py_ssize_t delta = ihigh - ilow; + + copyref(rv, 0, self, ilow, delta); + rv->num_children = delta; + rv->n = delta; + return (PyObject *) _blist(rv); + } + + blist_become(rv, self); + blist_delslice(rv, ihigh, self->n); + blist_delslice(rv, 0, ilow); + + ext_mark(rv, 0, DIRTY); + ext_mark_set_dirty(self, ilow, ihigh); + decref_flush(); + + return (PyObject *) _blist(rv); +} + +/* This should only be called by _PyBList_GET_ITEM_FAST2() */ +PyObject *_PyBList_GetItemFast3(PyBListRoot *root, Py_ssize_t i) +{ + PyObject *rv; + Py_ssize_t dirty_offset = -1; + + invariants(root, VALID_PARENT); + assert(!root->leaf); + assert(root->dirty_root != CLEAN); + assert(i >= 0); + assert(i < root->n); + + if (ext_is_dirty(root, i, &dirty_offset)){ + rv = ext_make_clean(root, i); + } else { + Py_ssize_t ioffset = i / INDEX_FACTOR; + Py_ssize_t offset = root->offset_list[ioffset]; + PyBList *p = root->index_list[ioffset]; + assert(i >= offset); + assert(p); + assert(p->leaf); + if (i < offset + p->n) { + rv = p->children[i - offset]; + if (dirty_offset >= 0) + ext_make_clean(root, dirty_offset); + } else if (ext_is_dirty(root,i + INDEX_FACTOR,&dirty_offset)){ + rv = ext_make_clean(root, i); + } else { + ioffset++; + assert(ioffset < root->index_allocated); + offset = root->offset_list[ioffset]; + p = root->index_list[ioffset]; + rv = p->children[i - offset]; + assert(p); + assert(p->leaf); + assert(i < offset + p->n); + if (dirty_offset >= 0) + ext_make_clean(root, dirty_offset); + } + } + + assert(rv == blist_get1((PyBList *)root, i)); + + return _ob(rv); +} + +BLIST_PYAPI(PyObject *) +py_blist_get_item(PyObject *oself, Py_ssize_t i) +{ + PyBList *self = (PyBList *) oself; + PyObject *ret; + + invariants(self, VALID_USER); + + if (i < 0 || i >= self->n) { + set_index_error(); + return _ob(NULL); + } + + if (self->leaf) + ret = self->children[i]; + else + ret = _PyBList_GET_ITEM_FAST2((PyBListRoot*)self, i); + Py_INCREF(ret); + return _ob(ret); +} + +/* Note: this may be called as __radd__, which means the arguments may + * be reversed. */ +BLIST_PYAPI(PyObject *) +py_blist_concat(PyObject *ob1, PyObject *ob2) +{ + PyBList *rv; + int err; + + int is_blist1 = PyRootBList_Check(ob1); + int is_blist2 = PyRootBList_Check(ob2); + + if ((!is_blist1 && !PyList_Check(ob1)) + || (!is_blist2 && !PyList_Check(ob2))) { + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + + if (is_blist1 && is_blist2) { + PyBList *blist1 = (PyBList *) ob1; + PyBList *blist2 = (PyBList *) ob2; + if (blist1->n < LIMIT && blist2->n < LIMIT + && blist1->n + blist2->n < LIMIT) { + rv = blist_root_new(); + copyref(rv, 0, blist1, 0, blist1->n); + copyref(rv, blist1->n, blist2, 0, blist2->n); + rv->n = rv->num_children = blist1->n + blist2->n; + goto done; + } + + rv = blist_root_copy(blist1); + blist_extend_blist(rv, blist2); + ext_mark(rv, 0, DIRTY); + ext_mark_set_dirty_all(blist2); + goto done; + } + + rv = blist_root_new(); + err = blist_init_from_seq(rv, ob1); + if (err < 0) { + decref_later((PyObject *) rv); + rv = NULL; + goto done; + } + err = blist_extend(rv, ob2); + if (err < 0) { + decref_later((PyObject *) rv); + rv = NULL; + goto done; + } + ext_mark(rv, 0, DIRTY); + if (PyBList_Check(ob1)) + ext_mark_set_dirty_all((PyBList *) ob1); + if (PyBList_Check(ob2)) + ext_mark_set_dirty_all((PyBList *) ob2); + +done: + _decref_flush(); + return (PyObject *) rv; +} + +/* User-visible repr() */ +BLIST_PYAPI(PyObject *) +py_blist_repr(PyObject *oself) +{ + /* Basic approach: Clone self in O(1) time, then walk through + * the clone, changing each element to repr() of the element, + * in O(n) time. Finally, enclose it in square brackets and + * call join. + */ + + Py_ssize_t i; + PyBList *pieces = NULL, *self; + PyObject *result = NULL; + PyObject *s, *tmp, *tmp2; + + invariants(oself, VALID_USER); + self = (PyBList *) oself; + + DANGER_BEGIN; + i = Py_ReprEnter((PyObject *) self); + DANGER_END; + if (i) { + return i > 0 ? _ob(PyUnicode_FromString("[...]")) : _ob(NULL); + } + + if (self->n == 0) { +#ifdef Py_BUILD_CORE + result = PyUnicode_FromString("[]"); +#else + result = PyUnicode_FromString("blist([])"); +#endif + goto Done; + } + + pieces = blist_root_copy(self); + if (pieces == NULL) + goto Done; + + if (blist_repr_r(pieces) < 0) + goto Done; + +#ifdef Py_BUILD_CORE + s = PyUnicode_FromString("["); +#else + s = PyUnicode_FromString("blist(["); +#endif + if (s == NULL) + goto Done; + tmp = blist_get1(pieces, 0); + tmp2 = PyUnicode_Concat(s, tmp); + Py_DECREF(s); + s = tmp2; + DANGER_BEGIN; + py_blist_ass_item((PyObject *) pieces, 0, s); + DANGER_END; + Py_DECREF(s); + +#ifdef Py_BUILD_CORE + s = PyUnicode_FromString("]"); +#else + s = PyUnicode_FromString("])"); +#endif + if (s == NULL) + goto Done; + tmp = blist_get1(pieces, pieces->n-1); + tmp2 = PyUnicode_Concat(tmp, s); + Py_DECREF(s); + tmp = tmp2; + DANGER_BEGIN; + py_blist_ass_item((PyObject *) pieces, pieces->n-1, tmp); + DANGER_END; + Py_DECREF(tmp); + + s = PyUnicode_FromString(", "); + if (s == NULL) + goto Done; + result = PyUnicode_Join(s, (PyObject *) pieces); + Py_DECREF(s); + + Done: + DANGER_BEGIN; + /* Only deallocating strings, so this is safe */ + Py_XDECREF(pieces); + DANGER_END; + + DANGER_BEGIN; + Py_ReprLeave((PyObject *) self); + DANGER_END; + return _ob(result); +} + +#if defined(Py_DEBUG) && !defined(BLIST_IN_PYTHON) +/* Return a string that shows the internal structure of the BList */ +BLIST_PYAPI(PyObject *) +blist_debug(PyBList *self, PyObject *indent) +{ + PyObject *result, *s, *nl_indent, *comma, *indent2, *tmp, *tmp2; + + invariants(self, VALID_PARENT); + + comma = PyUnicode_FromString(", "); + + if (indent == NULL) + indent = PyUnicode_FromString(""); + else + Py_INCREF(indent); + + tmp = PyUnicode_FromString(" "); + indent2 = PyUnicode_Concat(indent, tmp); + Py_DECREF(tmp); + + if (!self->leaf) { + int i; + + nl_indent = indent2; + tmp = PyUnicode_FromString("\n"); + nl_indent = PyUnicode_Concat(indent2, tmp); + Py_DECREF(tmp); + + result = PyUnicode_FromFormat("blist(leaf=%d, n=%d, r=%d, ", + self->leaf, self->n, + Py_REFCNT(self)); + /* PyUnicode_Concat(&result, nl_indent); */ + + for (i = 0; i < self->num_children; i++) { + s = blist_debug((PyBList *)self->children[i], indent2); + tmp = PyUnicode_Concat(result, nl_indent); + Py_DECREF(result); + result = tmp; + tmp = PyUnicode_Concat(result, s); + Py_DECREF(result); + result = tmp; + Py_DECREF(s); + } + + tmp = PyUnicode_FromString(")"); + tmp2 = PyUnicode_Concat(result, tmp); + Py_DECREF(result); + Py_DECREF(tmp); + result = tmp2; + } else { + int i; + + result = PyUnicode_FromFormat("blist(leaf=%d, n=%d, r=%d, ", + self->leaf, self->n, + Py_REFCNT(self)); + for (i = 0; i < self->num_children; i++) { + s = PyObject_Str(self->children[i]); + tmp = PyUnicode_Concat(result, s); + Py_DECREF(result); + result = tmp; + Py_DECREF(s); + tmp = PyUnicode_Concat(result, comma); + Py_DECREF(result); + result = tmp; + } + } + + s = indent; + tmp = PyUnicode_Concat(s, result); + Py_DECREF(result); + result = tmp; + + Py_DECREF(comma); + Py_DECREF(indent); + check_invariants(self); + return _ob(result); +} + +BLIST_PYAPI(PyObject *) +py_blist_debug(PyBList *self) +{ + invariants(self, VALID_USER); + return _ob(blist_debug(self, NULL)); +} +#endif + +BLIST_PYAPI(PyObject *) +py_blist_sort(PyBListRoot *self, PyObject *args, PyObject *kwds) +{ +#if PY_MAJOR_VERSION < 3 + static char *kwlist[] = {"cmp", "key", "reverse", 0}; +#else + static char *kwlist[] = {"key", "reverse", 0}; +#endif + int reverse = 0; + int ret = -1; + PyBListRoot saved; + PyObject *result = NULL; + PyObject *compare = NULL, *keyfunc = NULL; + static PyObject **extra_list = NULL; + + invariants(self, VALID_USER|VALID_RW | VALID_DECREF); + + if (args != NULL) { + int err; + DANGER_BEGIN; +#if PY_MAJOR_VERSION < 3 + err = PyArg_ParseTupleAndKeywords(args, kwds, "|OOi:sort", + kwlist, &compare, &keyfunc, + &reverse); + DANGER_END; + if (!err) + return _ob(NULL); +#else + err = PyArg_ParseTupleAndKeywords(args, kwds, "|Oi:sort", + kwlist, &keyfunc, &reverse); + DANGER_END; + if (!err) + return _ob(NULL); + if (Py_SIZE(args) > 0) { + PyErr_SetString(PyExc_TypeError, + "must use keyword argument for key function"); + return _ob(NULL); + } +#endif + } + + if (self->n < 2) + Py_RETURN_NONE; + +#if PY_MAJOR_VERSION < 3 + if (is_default_cmp(compare)) + compare = NULL; +#endif + if (keyfunc == Py_None) + keyfunc = NULL; + + memset(&saved, 0, offsetof(PyBListRoot, BLIST_FIRST_FIELD)); + memcpy(&saved.BLIST_FIRST_FIELD, &self->BLIST_FIRST_FIELD, + sizeof(*self) - offsetof(PyBListRoot, BLIST_FIRST_FIELD)); + Py_TYPE(&saved) = &PyRootBList_Type; + Py_REFCNT(&saved) = 1; + + if (extra_list != NULL) { + self->children = extra_list; + extra_list = NULL; + } else { + self->children = PyMem_New(PyObject *, LIMIT); + if (self->children == NULL) { + PyErr_NoMemory(); + goto err; + } + } + self->n = 0; + self->num_children = 0; + self->leaf = 1; + ext_init(self); + + /* Reverse sort stability achieved by initially reversing the list, + applying a stable forward sort, then reversing the final result. */ + if (reverse) + blist_reverse(&saved); + + ret = sort(&saved, compare, keyfunc); + + if (ret >= 0) { + result = Py_None; + if (reverse) { + ext_mark((PyBList*)&saved, 0, DIRTY); + blist_reverse(&saved); + } + } else + ext_mark((PyBList*)&saved, 0, DIRTY); + + if (self->n && saved.n) { + DANGER_BEGIN; + /* An error may also have been raised by a comparison + * function. Since this may decref that traceback, it can + * execute arbitrary python code. */ + PyErr_SetString(PyExc_ValueError, "list modified during sort"); + DANGER_END; + result = NULL; + blist_CLEAR((PyBList*) self); + } + + if (extra_list == NULL) + extra_list = self->children; + else + PyMem_Free(self->children); + + ext_dealloc(self); + assert(!self->n); + err: + memcpy(&self->BLIST_FIRST_FIELD, &saved.BLIST_FIRST_FIELD, + sizeof(*self) - offsetof(PyBListRoot, BLIST_FIRST_FIELD)); + + Py_XINCREF(result); + + decref_flush(); + + /* This must come after the decref_flush(); otherwise, we may have + * extra temporary references to internal nodes, which throws off the + * debug-mode sanity checking. */ + if (ret >= 0) + ext_reindex_set_all(&saved); + + return _ob(result); +} + +BLIST_PYAPI(PyObject *) +py_blist_reverse(PyBList *restrict self) +{ + invariants(self, VALID_USER|VALID_RW); + + if (self->leaf) + reverse_slice(self->children, + &self->children[self->num_children]); + else { + blist_reverse((PyBListRoot*) self); + } + + Py_RETURN_NONE; +} + +BLIST_PYAPI(PyObject *) +py_blist_count(PyBList *self, PyObject *v) +{ + Py_ssize_t count = 0; + PyObject *item; + int c; + fast_compare_data_t fast_cmp_type; + + invariants(self, VALID_USER | VALID_DECREF); + + fast_cmp_type = check_fast_cmp_type(v, Py_EQ); + + ITER(self, item, { + c = fast_eq(item, v, fast_cmp_type); + if (c > 0) + count++; + else if (c < 0) { + ITER_CLEANUP(); + decref_flush(); + return _ob(NULL); + } + }) + + decref_flush(); + return _ob(PyInt_FromSsize_t(count)); +} + +BLIST_PYAPI(PyObject *) +py_blist_index(PyBList *self, PyObject *args) +{ + Py_ssize_t i, start=0, stop=self->n; + PyObject *v; + int c, err; + PyObject *item; + fast_compare_data_t fast_cmp_type; + + invariants(self, VALID_USER|VALID_DECREF); + + DANGER_BEGIN; + err = PyArg_ParseTuple(args, "O|O&O&:index", &v, + _PyEval_SliceIndex, &start, + _PyEval_SliceIndex, &stop); + DANGER_END; + if (!err) + return _ob(NULL); + if (start < 0) { + start += self->n; + if (start < 0) + start = 0; + } else if (start > self->n) + start = self->n; + if (stop < 0) { + stop += self->n; + if (stop < 0) + stop = 0; + } else if (stop > self->n) + stop = self->n; + + fast_cmp_type = check_fast_cmp_type(v, Py_EQ); + i = start; + ITER2(self, item, start, stop, { + c = fast_eq(item, v, fast_cmp_type); + if (c > 0) { + ITER_CLEANUP(); + decref_flush(); + return _ob(PyInt_FromSsize_t(i)); + } else if (c < 0) { + ITER_CLEANUP(); + decref_flush(); + return _ob(NULL); + } + i++; + }) + + decref_flush(); + PyErr_SetString(PyExc_ValueError, "list.index(x): x not in list"); + return _ob(NULL); +} + +BLIST_PYAPI(PyObject *) +py_blist_remove(PyBList *self, PyObject *v) +{ + Py_ssize_t i; + int c; + PyObject *item; + fast_compare_data_t fast_cmp_type; + + invariants(self, VALID_USER|VALID_RW|VALID_DECREF); + + fast_cmp_type = check_fast_cmp_type(v, Py_EQ); + i = 0; + ITER(self, item, { + c = fast_eq(item, v, fast_cmp_type); + if (c > 0) { + ITER_CLEANUP(); + blist_delitem(self, i); + decref_flush(); + ext_mark(self, 0, DIRTY); + Py_RETURN_NONE; + } else if (c < 0) { + ITER_CLEANUP(); + decref_flush(); + return _ob(NULL); + } + i++; + }) + + decref_flush(); + PyErr_SetString(PyExc_ValueError, "list.remove(x): x not in list"); + return _ob(NULL); +} + +BLIST_PYAPI(PyObject *) +py_blist_pop(PyBList *self, PyObject *args) +{ + Py_ssize_t i = -1; + PyObject *v; + int err; + + invariants(self, VALID_USER|VALID_RW|VALID_DECREF); + + DANGER_BEGIN; + err = PyArg_ParseTuple(args, "|n:pop", &i); + DANGER_END; + if (!err) + return _ob(NULL); + + if (self->n == 0) { + /* Special-case most common failure cause */ + PyErr_SetString(PyExc_IndexError, "pop from empty list"); + return _ob(NULL); + } + + if (i == -1 || i == self->n-1) { + v = blist_pop_last_fast(self); + if (v) + return _ob(v); + } + + if (i < 0) + i += self->n; + if (i < 0 || i >= self->n) { + PyErr_SetString(PyExc_IndexError, "pop index out of range"); + return _ob(NULL); + } + + v = blist_delitem_return(self, i); + ext_mark(self, 0, DIRTY); + + decref_flush(); /* Remove any deleted BList nodes */ + + return _ob(v); /* the caller now owns the reference the list had */ +} + +BLIST_PYAPI(PyObject *) +py_blist_clear(PyBList *self) +{ + invariants(self, VALID_USER|VALID_RW|VALID_DECREF); + + blist_forget_children(self); + self->n = 0; + self->leaf = 1; + ext_dealloc((PyBListRoot *) self); + + decref_flush(); + Py_RETURN_NONE; +} + +BLIST_PYAPI(PyObject *) +py_blist_copy(PyBList *self) +{ + invariants(self, VALID_USER); + return (PyObject *) _blist(blist_root_copy(self)); +} + +BLIST_PYAPI(PyObject *) +py_blist_insert(PyBList *self, PyObject *args) +{ + Py_ssize_t i; + PyObject *v; + PyBList *overflow; + int err; + + invariants(self, VALID_USER|VALID_RW); + + DANGER_BEGIN; + err = PyArg_ParseTuple(args, "nO:insert", &i, &v); + DANGER_END; + if (!err) + return _ob(NULL); + + if (self->n == PY_SSIZE_T_MAX) { + PyErr_SetString(PyExc_OverflowError, + "cannot add more objects to list"); + return _ob(NULL); + } + + if (i < 0) { + i += self->n; + if (i < 0) + i = 0; + } else if (i > self->n) + i = self->n; + + /* Speed up the common case */ + if (self->leaf && self->num_children < LIMIT) { + Py_INCREF(v); + + shift_right(self, i, 1); + self->num_children++; + self->n++; + self->children[i] = v; + Py_RETURN_NONE; + } + + overflow = ins1(self, i, v); + if (overflow) + blist_overflow_root(self, overflow); + ext_mark(self, 0, DIRTY); + Py_RETURN_NONE; +} + +BLIST_PYAPI(PyObject *) +py_blist_append(PyBList *self, PyObject *v) +{ + int err; + + invariants(self, VALID_USER|VALID_RW); + + err = blist_append(self, v); + + if (err < 0) + return _ob(NULL); + + Py_RETURN_NONE; +} + +BLIST_PYAPI(PyObject *) +py_blist_subscript(PyObject *oself, PyObject *item) +{ + PyBList *self; + + invariants(oself, VALID_USER); + + self = (PyBList *) oself; + + if (PyIndex_Check(item)) { + Py_ssize_t i; + PyObject *ret; + + if (PyLong_CheckExact(item)) { + i = PyInt_AsSsize_t(item); + if (i == -1 && PyErr_Occurred()) { + PyErr_Clear(); + goto number; + } + } else { + number: + i = PyNumber_AsSsize_t(item, PyExc_IndexError); + if (i == -1 && PyErr_Occurred()) + return _ob(NULL); + } + + if (i < 0) + i += self->n; + + if (i < 0 || i >= self->n) { + set_index_error(); + return _ob(NULL); + } + + if (self->leaf) + ret = self->children[i]; + else + ret = _PyBList_GET_ITEM_FAST2((PyBListRoot*)self, i); + Py_INCREF(ret); + + return _ob(ret); + } else if (PySlice_Check(item)) { + Py_ssize_t start, stop, step, slicelength, cur, i; + PyBList* result; + PyObject* it; + +#if PY_MAJOR_VERSION < 3 || PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION < 2 + if (PySlice_GetIndicesEx((PySliceObject*)item, self->n, +#else + if (PySlice_GetIndicesEx(item, self->n, +#endif + &start, &stop,&step,&slicelength)<0) { + return _ob(NULL); + } + + if (step == 1) + return _redir((PyObject *) + py_blist_get_slice((PyObject *) self, start, stop)); + + result = blist_root_new(); + + if (slicelength <= 0) + return _ob((PyObject *) result); + + /* This could be made slightly faster by using forests */ + /* Also, by special-casing small trees */ + for (cur = start, i = 0; i < slicelength; cur += step, i++) { + int err; + + it = blist_get1(self, cur); + err = blist_append(result, it); + if (err < 0) { + Py_DECREF(result); + return _ob(NULL); + } + } + + ext_mark(result, 0, DIRTY); + return _ob((PyObject *) result); + } else { + PyErr_SetString(PyExc_TypeError, + "list indices must be integers"); + return _ob(NULL); + } +} + +#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6 || PY_MAJOR_VERSION >= 3 +static PyObject * +py_blist_root_sizeof(PyBListRoot *root) +{ + Py_ssize_t res; + res = sizeof(PyBListRoot) + + LIMIT * sizeof(PyObject *) + + root->index_allocated * (sizeof (PyBList *) +sizeof(Py_ssize_t)) + + root->dirty_length * sizeof(Py_ssize_t) + + (root->index_allocated ? + SETCLEAN_LEN(root->index_allocated) * sizeof(unsigned): 0); + return PyLong_FromSsize_t(res); +} + +static PyObject * +py_blist_sizeof(PyBList *self) +{ + Py_ssize_t res; + res = sizeof(PyBList) + + LIMIT * sizeof(PyObject *); + return PyLong_FromSsize_t(res); +} + +PyDoc_STRVAR(sizeof_doc, +"L.__sizeof__() -- size of L in memory, in bytes"); +#endif + +/************************************************************************ + * Routines for supporting pickling + */ + +#ifndef BLIST_IN_PYTHON +BLIST_PYAPI(PyObject *) +blist_getstate(PyBList *self) +{ + PyObject *lst; + int i; + + invariants(self, VALID_PARENT); + + lst = PyList_New(self->num_children); + for (i = 0; i < self->num_children; i++) { + PyList_SET_ITEM(lst, i, self->children[i]); + Py_INCREF(PyList_GET_ITEM(lst, i)); + } + + if (PyRootBList_CheckExact(self)) + ext_mark_set_dirty_all(self); + + return _ob(lst); +} + +BLIST_PYAPI(PyObject *) +py_blist_setstate(PyBList *self, PyObject *state) +{ + Py_ssize_t i; + + invariants(self, VALID_PARENT); + + if (!PyList_CheckExact(state) || PyList_GET_SIZE(state) > LIMIT) { + PyErr_SetString(PyExc_TypeError, "invalid state"); + return _ob(NULL); + } + + for (self->n = i = 0; i < PyList_GET_SIZE(state); i++) { + PyObject *child = PyList_GET_ITEM(state, i); + if (Py_TYPE(child) == &PyBList_Type) { + self->leaf = 0; + self->n += ((PyBList*)child)->n; + } else + self->n++; + self->children[i] = child; + Py_INCREF(child); + } + + self->num_children = PyList_GET_SIZE(state); + + if (PyRootBList_CheckExact(self)) + ext_reindex_all((PyBListRoot*)self); + + Py_RETURN_NONE; +} + +BLIST_PYAPI(PyObject *) +py_blist_reduce(PyBList *self) +{ + PyObject *rv, *args, *type; + + invariants(self, VALID_PARENT); + + type = (PyObject *) Py_TYPE(self); + args = PyTuple_New(0); + rv = PyTuple_New(3); + PyTuple_SET_ITEM(rv, 0, type); + Py_INCREF(type); + PyTuple_SET_ITEM(rv, 1, args); + PyTuple_SET_ITEM(rv, 2, blist_getstate(self)); + + return _ob(rv); +} +#endif + +PyDoc_STRVAR(getitem_doc, + "x.__getitem__(y) <==> x[y]"); +PyDoc_STRVAR(reversed_doc, + "L.__reversed__() -- return a reverse iterator over the list"); +PyDoc_STRVAR(append_doc, +"L.append(object) -- append object to end"); +PyDoc_STRVAR(extend_doc, +"L.extend(iterable) -- extend list by appending elements from the iterable"); +PyDoc_STRVAR(insert_doc, +"L.insert(index, object) -- insert object before index"); +PyDoc_STRVAR(pop_doc, +"L.pop([index]) -> item -- remove and return item at index (default last)"); +PyDoc_STRVAR(remove_doc, +"L.remove(value) -- remove first occurrence of value"); +PyDoc_STRVAR(index_doc, +"L.index(value, [start, [stop]]) -> integer -- return first index of value"); +PyDoc_STRVAR(count_doc, +"L.count(value) -> integer -- return number of occurrences of value"); +PyDoc_STRVAR(reverse_doc, +"L.reverse() -- reverse *IN PLACE*"); +PyDoc_STRVAR(sort_doc, +"L.sort(cmp=None, key=None, reverse=False) -- stable sort *IN PLACE*;\n\ +cmp(x, y) -> -1, 0, 1"); +PyDoc_STRVAR(clear_doc, +"L.clear() -> None -- remove all items from L"); +PyDoc_STRVAR(copy_doc, +"L.copy() -> list -- a shallow copy of L"); + +static PyMethodDef blist_methods[] = { + {"__getitem__", (PyCFunction)py_blist_subscript, METH_O|METH_COEXIST, getitem_doc}, + {"__reversed__",(PyCFunction)py_blist_reversed, METH_NOARGS, reversed_doc}, +#ifndef BLIST_IN_PYTHON + {"__reduce__", (PyCFunction)py_blist_reduce, METH_NOARGS, NULL}, + {"__setstate__",(PyCFunction)py_blist_setstate, METH_O, NULL}, +#endif + {"append", (PyCFunction)py_blist_append, METH_O, append_doc}, + {"insert", (PyCFunction)py_blist_insert, METH_VARARGS, insert_doc}, + {"extend", (PyCFunction)py_blist_extend, METH_O, extend_doc}, + {"pop", (PyCFunction)py_blist_pop, METH_VARARGS, pop_doc}, + {"remove", (PyCFunction)py_blist_remove, METH_O, remove_doc}, + {"index", (PyCFunction)py_blist_index, METH_VARARGS, index_doc}, + {"clear", (PyCFunction)py_blist_clear, METH_NOARGS, clear_doc}, + {"copy", (PyCFunction)py_blist_copy, METH_NOARGS, copy_doc}, + + {"count", (PyCFunction)py_blist_count, METH_O, count_doc}, + {"reverse", (PyCFunction)py_blist_reverse, METH_NOARGS, reverse_doc}, + {"sort", (PyCFunction)py_blist_sort, METH_VARARGS | METH_KEYWORDS, sort_doc}, +#if defined(Py_DEBUG) && !defined(BLIST_IN_PYTHON) + {"debug", (PyCFunction)py_blist_debug, METH_NOARGS, NULL}, +#endif +#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6 || PY_MAJOR_VERSION >= 3 + {"__sizeof__", (PyCFunction)py_blist_root_sizeof, METH_NOARGS, sizeof_doc}, +#endif + {NULL, NULL} /* sentinel */ +}; + +static PyMethodDef blist_internal_methods[] = { +#ifndef BLIST_IN_PYTHON + {"__reduce__", (PyCFunction)py_blist_reduce, METH_NOARGS, NULL}, + {"__setstate__",(PyCFunction)py_blist_setstate, METH_O, NULL}, +#endif +#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6 || PY_MAJOR_VERSION >= 3 + {"__sizeof__", (PyCFunction)py_blist_sizeof, METH_NOARGS, sizeof_doc}, +#endif + {NULL, NULL} /* sentinel */ +}; + +static PySequenceMethods blist_as_sequence = { + py_blist_length, /* sq_length */ + 0, /* sq_concat */ + py_blist_repeat, /* sq_repeat */ + py_blist_get_item, /* sq_item */ + py_blist_get_slice, /* sq_slice */ + py_blist_ass_item, /* sq_ass_item */ + py_blist_ass_slice, /* sq_ass_slice */ + py_blist_contains, /* sq_contains */ + 0, /* sq_inplace_concat */ + py_blist_inplace_repeat, /* sq_inplace_repeat */ +}; + +PyDoc_STRVAR(blist_doc, +"blist() -> new list\n" +"blist(iterable) -> new list initialized from iterable's items"); + +static PyMappingMethods blist_as_mapping = { + py_blist_length, + py_blist_subscript, + py_blist_ass_subscript +}; + +/* All of this, just to get __radd__ to work */ +#if PY_MAJOR_VERSION < 3 +static PyNumberMethods blist_as_number = { + py_blist_concat, /* nb_add */ + 0, /* nb_subtract */ + 0, /* nb_multiply */ + 0, /* nb_divide */ + 0, /* nb_remainder */ + 0, /* nb_divmod */ + 0, /* nb_power */ + 0, /* nb_negative */ + 0, /* tp_positive */ + 0, /* tp_absolute */ + 0, /* tp_nonzero */ + 0, /* nb_invert */ + 0, /* nb_lshift */ + 0, /* nb_rshift */ + 0, /* nb_and */ + 0, /* nb_xor */ + 0, /* nb_or */ + 0, /* nb_coerce */ + 0, /* nb_int */ + 0, /* nb_long */ + 0, /* nb_float */ + 0, /* nb_oct */ + 0, /* nb_hex */ + py_blist_inplace_concat, /* nb_inplace_add */ + 0, /* nb_inplace_subtract */ + 0, /* nb_inplace_multiply */ + 0, /* nb_inplace_divide */ + 0, /* nb_inplace_remainder */ + 0, /* nb_inplace_power */ + 0, /* nb_inplace_lshift */ + 0, /* nb_inplace_rshift */ + 0, /* nb_inplace_and */ + 0, /* nb_inplace_xor */ + 0, /* nb_inplace_or */ + 0, /* nb_floor_divide */ + 0, /* nb_true_divide */ + 0, /* nb_inplace_floor_divide */ + 0, /* nb_inplace_true_divide */ + 0, /* nb_index */ +}; +#else +static PyNumberMethods blist_as_number = { + (binaryfunc) py_blist_concat, /* nb_add */ + 0, /* nb_subtract */ + 0, /* nb_multiply */ + 0, /* nb_remainder */ + 0, /* nb_divmod */ + 0, /* nb_power */ + 0, /* nb_negative */ + 0, /* tp_positive */ + 0, /* tp_absolute */ + 0, /* tp_bool */ + 0, /* nb_invert */ + 0, /* nb_lshift */ + 0, /* nb_rshift */ + 0, /* nb_and */ + 0, /* nb_xor */ + 0, /* nb_or */ + 0, /* nb_int */ + 0, /* nb_reserved */ + 0, /* nb_float */ + py_blist_inplace_concat, /* nb_inplace_add */ + 0, /* nb_inplace_subtract */ + 0, /* nb_inplace_multiply */ + 0, /* nb_inplace_remainder */ + 0, /* nb_inplace_power */ + 0, /* nb_inplace_lshift */ + 0, /* nb_inplace_rshift */ + 0, /* nb_inplace_and */ + 0, /* nb_inplace_xor */ + 0, /* nb_inplace_or */ + 0, /* nb_floor_divide */ + 0, /* nb_true_divide */ + 0, /* nb_inplace_floor_divide */ + 0, /* nb_inplace_true_divide */ + 0, /* nb_index */ +}; +#endif + +PyTypeObject PyBList_Type = { + PyVarObject_HEAD_INIT(NULL, 0) +#ifdef BLIST_IN_PYTHON + "__internal_blist", +#else + "blist._blist.__internal_blist", +#endif + sizeof(PyBList), + 0, + py_blist_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + py_blist_nohash, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_BASETYPE, /* tp_flags */ + blist_doc, /* tp_doc */ + py_blist_traverse, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + blist_internal_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + py_blist_internal_init, /* tp_init */ + 0, /* tp_alloc */ + py_blist_internal_tp_new, /* tp_new */ + PyObject_GC_Del, /* tp_free */ +}; + +PyTypeObject PyRootBList_Type = { + PyVarObject_HEAD_INIT(NULL, 0) +#ifdef BLIST_IN_PYTHON + "list", +#else + "blist.blist", +#endif + sizeof(PyBListRoot), + 0, + py_blist_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + py_blist_repr, /* tp_repr */ + &blist_as_number, /* tp_as_number */ + &blist_as_sequence, /* tp_as_sequence */ + &blist_as_mapping, /* tp_as_mapping */ + py_blist_nohash, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_BASETYPE /* tp_flags */ +#if (PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6 || PY_MAJOR_VERSION >= 3) && defined(BLIST_IN_PYTHON) + | Py_TPFLAGS_LIST_SUBCLASS +#endif + , + blist_doc, /* tp_doc */ + py_blist_traverse, /* tp_traverse */ + py_blist_tp_clear, /* tp_clear */ + py_blist_richcompare, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + py_blist_iter, /* tp_iter */ + 0, /* tp_iternext */ + blist_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + py_blist_init, /* tp_init */ + PyType_GenericAlloc, /* tp_alloc */ + py_blist_root_tp_new, /* tp_new */ + PyObject_GC_Del, /* tp_free */ +}; + +static PyMethodDef module_methods[] = { { NULL } }; + +BLIST_LOCAL(int) +init_blist_types1(void) +{ + decref_init(); + highest_set_bit_init(); + + Py_TYPE(&PyBList_Type) = &PyType_Type; + Py_TYPE(&PyRootBList_Type) = &PyType_Type; + Py_TYPE(&PyBListIter_Type) = &PyType_Type; + Py_TYPE(&PyBListReverseIter_Type) = &PyType_Type; + + Py_INCREF(&PyBList_Type); + Py_INCREF(&PyRootBList_Type); + Py_INCREF(&PyBListIter_Type); + Py_INCREF(&PyBListReverseIter_Type); + + return 0; +} + +BLIST_LOCAL(int) +init_blist_types2(void) +{ + if (PyType_Ready(&PyRootBList_Type) < 0) return -1; + if (PyType_Ready(&PyBList_Type) < 0) return -1; + if (PyType_Ready(&PyBListIter_Type) < 0) return -1; + if (PyType_Ready(&PyBListReverseIter_Type) < 0) return -1; + + return 0; +} + +#if PY_MAJOR_VERSION < 3 +PyMODINIT_FUNC +init_blist(void) +{ +#ifndef BLIST_IN_PYTHON + PyCFunctionObject *meth; + PyObject *gc_module; +#endif + + PyObject *m; + PyObject *limit = PyInt_FromLong(LIMIT); + + init_blist_types1(); + init_blist_types2(); + + m = Py_InitModule3("_blist", module_methods, "_blist"); + + PyModule_AddObject(m, "blist", (PyObject *) &PyRootBList_Type); + PyModule_AddObject(m, "_limit", limit); + PyModule_AddObject(m, "__internal_blist", (PyObject *) + &PyBList_Type); + +#ifndef BLIST_IN_PYTHON + gc_module = PyImport_ImportModule("gc"); + + meth = (PyCFunctionObject*)PyObject_GetAttrString(gc_module, "enable"); + pgc_enable = meth->m_ml->ml_meth; + + meth = (PyCFunctionObject*)PyObject_GetAttrString(gc_module,"disable"); + pgc_disable = meth->m_ml->ml_meth; + + meth = (PyCFunctionObject*)PyObject_GetAttrString(gc_module, + "isenabled"); + pgc_isenabled = meth->m_ml->ml_meth; +#endif +} +#else + +static struct PyModuleDef blist_module = { + PyModuleDef_HEAD_INIT, + "_blist", + NULL, + -1, + module_methods, + NULL, + NULL, + NULL, + NULL, +}; + +PyMODINIT_FUNC +PyInit__blist(void) +{ +#ifndef BLIST_IN_PYTHON + PyModuleDef *gc_module_def; + PyMethodDef *gc_methods; + PyObject *gc_module; +#endif + PyObject *m; + PyObject *limit = PyInt_FromLong(LIMIT); + + if (init_blist_types1() < 0) + return NULL; + if (init_blist_types2() < 0) + return NULL; + + m = PyModule_Create(&blist_module); + + PyModule_AddObject(m, "blist", (PyObject *) &PyRootBList_Type); + PyModule_AddObject(m, "_limit", limit); + PyModule_AddObject(m, "__internal_blist", (PyObject *) + &PyBList_Type); + +#ifndef BLIST_IN_PYTHON + gc_module = PyImport_ImportModule("gc"); + gc_module_def = PyModule_GetDef(gc_module); + gc_methods = gc_module_def->m_methods; + while (gc_methods->ml_name != NULL) { + if (0 == strcmp(gc_methods->ml_name, "enable")) + pgc_enable = gc_methods->ml_meth; + else if (0 == strcmp(gc_methods->ml_name, "disable")) + pgc_disable = gc_methods->ml_meth; + else if (0 == strcmp(gc_methods->ml_name, "isenabled")) + pgc_isenabled = gc_methods->ml_meth; + gc_methods++; + } +#endif + return m; +} +#endif + +/************************************************************************ + * The List C API, for building BList into the Python core + */ + +#ifdef Py_BUILD_CORE +int +PyList_Init1(void) +{ + return init_blist_types1(); +} + +int +PyList_Init2(void) +{ + return init_blist_types2(); +} + +PyObject *PyList_New(Py_ssize_t size) +{ + PyBList *self = blist_root_new(); + PyObject *tmp; + + if (self == NULL) + return NULL; + + if (size <= LIMIT) { + self->n = size; + self->num_children = size; + memset(self->children, 0, sizeof(PyObject *) * size); + check_invariants(self); + return (PyObject *) self; + } + + self->n = 1; + self->num_children = 1; + self->children[0] = NULL; + + tmp = blist_repeat(self, size); + check_invariants((PyBList *) tmp); + SAFE_DECREF(self); + _decref_flush(); + check_invariants((PyBList *) tmp); + assert(((PyBList *)tmp)->n == size); + ext_dealloc((PyBListRoot *) tmp); + return tmp; +} + +Py_ssize_t PyList_Size(PyObject *ob) +{ + if (ob == NULL || !PyList_Check(ob)) { + PyErr_BadInternalCall(); + return -1; + } + + return py_blist_length(ob); +} + +PyObject *PyList_GetItem(PyObject *ob, Py_ssize_t i) +{ + PyBList *self = (PyBList *) ob; + PyObject *ret; + + if (ob == NULL || !PyList_Check(ob)) { + PyErr_BadInternalCall(); + return NULL; + } + + assert(i >= 0 && i < self->n); /* XXX Remove */ + + if (i < 0 || i >= self->n) { + set_index_error(); + return NULL; + } + + ret = blist_get1((PyBList *) ob, i); + assert(ret != NULL); + return ret; +} + +int PyList_SetItem(PyObject *ob, Py_ssize_t i, PyObject *item) +{ + int ret; + + if (ob == NULL || !PyList_Check(ob)) { + PyErr_BadInternalCall(); + Py_XDECREF(item); + return -1; + } + + assert(i >= 0 && i < ((PyBList *)ob)->n); /* XXX Remove */ + ret = py_blist_ass_item(ob, i, item); + assert(Py_REFCNT(item) > 1); + Py_XDECREF(item); + return ret; +} + +int PyList_Insert(PyObject *ob, Py_ssize_t i, PyObject *v) +{ + PyBList *overflow; + PyBList *self = (PyBList *) ob; + + if (ob == NULL || !PyList_Check(ob)) { + PyErr_BadInternalCall(); + return -1; + } + + invariants(self, VALID_USER|VALID_RW); + + if (self->n == PY_SSIZE_T_MAX) { + PyErr_SetString(PyExc_OverflowError, + "cannot add more objects to list"); + return _int(0); + } + + if (i < 0) { + i += self->n; + if (i < 0) + i = 0; + } else if (i > self->n) + i = self->n; + + if (self->leaf && self->num_children < LIMIT) { + Py_INCREF(v); + + shift_right(self, i, 1); + self->num_children++; + self->n++; + self->children[i] = v; + return _int(0); + } + + overflow = ins1(self, i, v); + if (overflow) + blist_overflow_root(self, overflow); + ext_mark(self, 0, DIRTY); + + return _int(0); +} + +int PyList_Append(PyObject *ob, PyObject *item) +{ + if (ob == NULL || !PyList_Check(ob)) { + PyErr_BadInternalCall(); + return -1; + } + + return blist_append((PyBList *) ob, item); +} + +PyObject *PyList_GetSlice(PyObject *ob, Py_ssize_t i, Py_ssize_t j) +{ + if (ob == NULL || !PyList_Check(ob)) { + PyErr_BadInternalCall(); + return NULL; + } + + return py_blist_get_slice(ob, i, j); +} + +int PyList_SetSlice(PyObject *ob, Py_ssize_t i, Py_ssize_t j, PyObject *lst) +{ + if (ob == NULL || !PyList_Check(ob)) { + PyErr_BadInternalCall(); + return -1; + } + + return py_blist_ass_slice(ob, i, j, lst); +} + +int PyList_Sort(PyObject *ob) +{ + PyObject *ret; + + if (ob == NULL || !PyList_Check(ob)) { + PyErr_BadInternalCall(); + return -1; + } + + ret = py_blist_sort((PyBListRoot *) ob, NULL, NULL); + if (ret == NULL) + return -1; + + Py_DECREF(ret); + return 0; +} + +int PyList_Reverse(PyObject *ob) +{ + if (ob == NULL || !PyList_Check(ob)) { + PyErr_BadInternalCall(); + return -1; + } + + invariants((PyBList *) ob, VALID_USER|VALID_RW); + + blist_reverse((PyBListRoot *) ob); + + return _int(0); +} + +PyObject *PyList_AsTuple(PyObject *ob) +{ + PyBList *self = (PyBList *) ob; + PyObject *item; + PyTupleObject *tuple; + int i; + + if (ob == NULL || !PyList_Check(ob)) { + PyErr_BadInternalCall(); + return NULL; + } + + invariants(self, VALID_USER | VALID_DECREF); + + DANGER_BEGIN; + tuple = (PyTupleObject *) PyTuple_New(self->n); + DANGER_END; + if (tuple == NULL) + return _ob(NULL); + + i = 0; + ITER(self, item, { + tuple->ob_item[i++] = item; + Py_INCREF(item); + }) + + assert(i == self->n); + + decref_flush(); + + return _ob((PyObject *) tuple); + +} + +PyObject * +_PyList_Extend(PyBListRoot *ob, PyObject *b) +{ + return py_blist_extend((PyBList *) ob, b); +} + +#if PY_MAJOR_VERSION == 2 && PY_MINOR_VERSION >= 6 || PY_MAJOR_VERSION >= 3 +void +PyList_Fini(void) +{ + /* XXX free statically allocated memory here */ +} +#endif + +#endif diff --git a/blist/_btuple.py b/blist/_btuple.py new file mode 100644 index 0000000..1e409cd --- /dev/null +++ b/blist/_btuple.py @@ -0,0 +1,86 @@ +from blist._blist import blist +from ctypes import c_int +import collections +class btuple(collections.Sequence): + def __init__(self, seq=None): + if isinstance(seq, btuple): + self._blist = seq._blist + elif seq is not None: + self._blist = blist(seq) + else: + self._blist = blist() + self._hash = -1 + + def _btuple_or_tuple(self, other, f): + if isinstance(other, btuple): + rv = f(self._blist, other._blist) + elif isinstance(other, tuple): + rv = f(self._blist, blist(other)) + else: + return NotImplemented + if isinstance(rv, blist): + rv = btuple(rv) + return rv + + def __hash__(self): + # Based on tuplehash from tupleobject.c + if self._hash != -1: + return self._hash + + n = len(self) + mult = c_int(1000003) + x = c_int(0x345678) + for ob in self: + n -= 1 + y = c_int(hash(ob)) + x = (x ^ y) * mult + mult += c_int(82520) + n + n + x += c_int(97531) + if x == -1: + x = -2; + self._hash = x.value + return self._hash + + def __add__(self, other): + return self._btuple_or_tuple(other, blist.__add__) + def __radd__(self, other): + return self._btuple_or_tuple(other, blist.__radd__) + def __contains__(self, item): + return item in self._blist + def __eq__(self, other): + return self._btuple_or_tuple(other, blist.__eq__) + def __ge__(self, other): + return self._btuple_or_tuple(other, blist.__ge__) + def __gt__(self, other): + return self._btuple_or_tuple(other, blist.__gt__) + def __le__(self, other): + return self._btuple_or_tuple(other, blist.__le__) + def __lt__(self, other): + return self._btuple_or_tuple(other, blist.__lt__) + def __ne__(self, other): + return self._btuple_or_tuple(other, blist.__ne__) + def __iter__(self): + return iter(self._blist) + def __len__(self): + return len(self._blist) + def __getitem__(self, key): + if isinstance(key, slice): + return btuple(self._blist[key]) + return self._blist[key] + def __getslice__(self, i, j): + return btuple(self._blist[i:j]) + def __repr__(self): + return 'btuple((' + repr(self._blist)[7:-2] + '))' + def __str__(self): + return repr(self) + def __mul__(self, i): + return btuple(self._blist * i) + def __rmul__(self, i): + return btuple(i * self._blist) + def count(self, item): + return self._blist.count(item) + def index(self, item): + return self._blist.index(item) + +del c_int +del collections diff --git a/blist/_sorteddict.py b/blist/_sorteddict.py new file mode 100644 index 0000000..fcdf7e4 --- /dev/null +++ b/blist/_sorteddict.py @@ -0,0 +1,147 @@ +from blist._sortedlist import sortedset, ReprRecursion +import collections, sys +from blist._blist import blist + +class missingdict(dict): + def __missing__(self, key): + return self._missing(key) + +class KeysView(collections.KeysView, collections.Sequence): + def __getitem__(self, index): + return self._mapping._sortedkeys[index] + def __reversed__(self): + return reversed(self._mapping._sortedkeys) + def index(self, key): + return self._mapping._sortedkeys.index(key) + def count(self, key): + return 1 if key in self else 0 + def _from_iterable(self, it): + return sortedset(it, key=self._mapping._sortedkeys._key) + def bisect_left(self, key): + return self._mapping._sortedkeys.bisect_left(key) + def bisect_right(self, key): + return self._mapping._sortedkeys.bisect_right(key) + bisect = bisect_right + +class ItemsView(collections.ItemsView, collections.Sequence): + def __getitem__(self, index): + if isinstance(index, slice): + keys = self._mapping._sortedkeys[index] + return self._from_iterable((key, self._mapping[key]) + for key in keys) + key = self._mapping._sortedkeys[index] + return (key, self._mapping[key]) + def index(self, item): + key, value = item + i = self._mapping._sortedkeys.index(key) + if self._mapping[key] == value: + return i + raise ValueError + def count(self, item): + return 1 if item in self else 0 + def _from_iterable(self, it): + keyfunc = self._mapping._sortedkeys._key + if keyfunc is None: + return sortedset(it) + else: + return sortedset(it, key=lambda item: keyfunc(item[0])) + +class ValuesView(collections.ValuesView, collections.Sequence): + def __getitem__(self, index): + if isinstance(index, slice): + keys = self._mapping._sortedkeys[index] + return [self._mapping[key] for key in keys] + key = self._mapping._sortedkeys[index] + return self._mapping[key] + +class sorteddict(collections.MutableMapping): + def __init__(self, *args, **kw): + if hasattr(self, '__missing__'): + self._map = missingdict() + self._map._missing = self.__missing__ + else: + self._map = dict() + key = None + if len(args) > 0: + if hasattr(args[0], '__call__'): + key = args[0] + args = args[1:] + elif len(args) > 1: + raise TypeError("'%s' object is not callable" % + args[0].__class__.__name__) + if len(args) > 1: + raise TypeError('sorteddict expected at most 2 arguments, got %d' + % len(args)) + if len(args) == 1 and isinstance(args[0], sorteddict) and key is None: + key = args[0]._sortedkeys._key + self._sortedkeys = sortedset(key=key) + self.update(*args, **kw) + + if sys.version_info[0] < 3: + def keys(self): + return self._sortedkeys.copy() + def items(self): + return blist((key, self[key]) for key in self) + def values(self): + return blist(self[key] for key in self) + def viewkeys(self): + return KeysView(self) + def viewitems(self): + return ItemsView(self) + def viewvalues(self): + return ValuesView(self) + else: + def keys(self): + return KeysView(self) + def items(self): + return ItemsView(self) + def values(self): + return ValuesView(self) + + def __setitem__(self, key, value): + try: + if key not in self._map: + self._sortedkeys.add(key) + self._map[key] = value + except: + if key not in self._map: + self._sortedkeys.discard(key) + raise + + def __delitem__(self, key): + self._sortedkeys.discard(key) + del self._map[key] + + def __getitem__(self, key): + return self._map[key] + + def __iter__(self): + return iter(self._sortedkeys) + + def __len__(self): + return len(self._sortedkeys) + + def copy(self): + return sorteddict(self) + + @classmethod + def fromkeys(cls, keys, value=None, key=None): + if key is not None: + rv = cls(key) + else: + rv = cls() + for key in keys: + rv[key] = value + return rv + + def __repr__(self): + with ReprRecursion(self) as r: + if r: + return 'sorteddict({...})' + return ('sorteddict({%s})' % + ', '.join('%r: %r' % (k, self._map[k]) for k in self)) + + def __eq__(self, other): + if not isinstance(other, sorteddict): + return False + return self._map == other._map diff --git a/blist/_sortedlist.py b/blist/_sortedlist.py new file mode 100644 index 0000000..b34f69e --- /dev/null +++ b/blist/_sortedlist.py @@ -0,0 +1,647 @@ +from blist._blist import blist +import collections, bisect, weakref, operator, itertools, sys, threading +try: # pragma: no cover + izip = itertools.izip +except AttributeError: # pragma: no cover + izip = zip + +__all__ = ['sortedlist', 'weaksortedlist', 'sortedset', 'weaksortedset'] + +class ReprRecursion(object): + local = threading.local() + def __init__(self, ob): + if not hasattr(self.local, 'repr_count'): + self.local.repr_count = collections.defaultdict(int) + self.ob_id = id(ob) + self.value = self.ob_id in self.local.repr_count + + def __enter__(self): + self.local.repr_count[self.ob_id] += 1 + return self.value + + def __exit__(self, exc_type, exc_val, exc_tb): + self.local.repr_count[self.ob_id] -= 1 + if self.local.repr_count[self.ob_id] == 0: + del self.local.repr_count[self.ob_id] + return False + +class _sortedbase(collections.Sequence): + def __init__(self, iterable=(), key=None): + self._key = key + if key is not None and not hasattr(key, '__call__'): + raise TypeError("'%s' object is not callable" % str(type(key))) + if ((isinstance(iterable,type(self)) + or isinstance(self,type(iterable))) + and iterable._key is key): + self._blist = blist(iterable._blist) + else: + self._blist = blist() + for v in iterable: + self.add(v) + + def _from_iterable(self, iterable): + return self.__class__(iterable, self._key) + + def _u2key(self, value): + "Convert a user-object to the key" + if self._key is None: + return value + else: + return self._key(value) + + def _u2i(self, value): + "Convert a user-object to the internal representation" + if self._key is None: + return value + else: + return (self._key(value), value) + + def _i2u(self, value): + "Convert an internal object to a user-object" + if self._key is None: + return value + else: + return value[1] + + def _i2key(self, value): + "Convert an internal object to the key" + if self._key is None: + return value + else: + return value[0] + + def _bisect_left(self, v): + """Locate the point in the list where v would be inserted. + + Returns an (i, value) tuple: + - i is the position where v would be inserted + - value is the current user-object at position i + + This is the key function to override in subclasses. They must + accept a user-object v and return a user-object value. + """ + + key = self._u2key(v) + lo = 0 + hi = len(self._blist) + while lo < hi: + mid = (lo+hi)//2 + v = self._i2key(self._blist[mid]) + if v < key: lo = mid + 1 + else: hi = mid + if lo < len(self._blist): + return lo, self._i2u(self._blist[lo]) + return lo, None + + def _bisect_right(self, v): + """Same as _bisect_left, but go to the right of equal values""" + + key = self._u2key(v) + lo = 0 + hi = len(self._blist) + while lo < hi: + mid = (lo+hi)//2 + v = self._i2key(self._blist[mid]) + if key < v: hi = mid + else: lo = mid + 1 + if lo < len(self._blist): + return lo, self._i2u(self._blist[lo]) + return lo, None + + def bisect_left(self, v): + """L.bisect_left(v) -> index + + The return value i is such that all e in L[:i] have e < v, and + all e in a[i:] have e >= v. So if v already appears in the + list, i points just before the leftmost v already there. + """ + return self._bisect_left(v)[0] + + def bisect_right(self, v): + """L.bisect_right(v) -> index + + Return the index where to insert item v in the list. + + The return value i is such that all e in a[:i] have e <= v, + and all e in a[i:] have e > v. So if v already appears in the + list, i points just beyond the rightmost v already there. + """ + return self._bisect_right(v)[0] + + bisect = bisect_right + + def add(self, value): + """Add an element.""" + # Will throw a TypeError when trying to add an object that + # cannot be compared to objects already in the list. + i, _ = self._bisect_right(value) + self._blist.insert(i, self._u2i(value)) + + def discard(self, value): + """Remove an element if it is a member. + + If the element is not a member, do nothing. + + """ + + try: + i, v = self._bisect_left(value) + except TypeError: + # Value cannot be compared with values already in the list. + # Ergo, value isn't in the list. + return + i = self._advance(i, value) + if i >= 0: + del self._blist[i] + + def __contains__(self, value): + """x.__contains__(y) <==> y in x""" + try: + i, v = self._bisect_left(value) + except TypeError: + # Value cannot be compared with values already in the list. + # Ergo, value isn't in the list. + return False + i = self._advance(i, value) + return i >= 0 + + def __len__(self): + """x.__len__() <==> len(x)""" + return len(self._blist) + + def __iter__(self): + """ x.__iter__() <==> iter(x)""" + return (self._i2u(v) for v in self._blist) + + def __getitem__(self, index): + """x.__getitem__(y) <==> x[y]""" + if isinstance(index, slice): + rv = self.__class__() + rv._blist = self._blist[index] + rv._key = self._key + return rv + return self._i2u(self._blist[index]) + + def _advance(self, i, value): + "Do a linear search through all items with the same key" + key = self._u2key(value) + while i < len(self._blist): + if self._i2u(self._blist[i]) == value: + return i + elif key < self._i2key(self._blist[i]): + break + i += 1 + return -1 + + def __reversed__(self): + """L.__reversed__() -- return a reverse iterator over the list""" + return (self._i2u(v) for v in reversed(self._blist)) + + def index(self, value): + """L.index(value) -> integer -- return first index of value. + + Raises ValueError if the value is not present. + + """ + + try: + i, v = self._bisect_left(value) + except TypeError: + raise ValueError + i = self._advance(i, value) + if i >= 0: + return i + raise ValueError + + def count(self, value): + """L.count(value) -> integer -- return number of occurrences of value""" + try: + i, _ = self._bisect_left(value) + except TypeError: + return 0 + key = self._u2key(value) + count = 0 + while True: + i = self._advance(i, value) + if i == -1: + return count + count += 1 + i += 1 + + def pop(self, index=-1): + """L.pop([index]) -> item -- remove and return item at index (default last). + + Raises IndexError if list is empty or index is out of range. + + """ + + rv = self[index] + del self[index] + return rv + + def __delslice__(self, i, j): + """x.__delslice__(i, j) <==> del x[i:j] + + Use of negative indices is not supported. + + """ + del self._blist[i:j] + + def __delitem__(self, i): + """x.__delitem__(y) <==> del x[y]""" + del self._blist[i] + +class _weaksortedbase(_sortedbase): + def _bisect_left(self, value): + key = self._u2key(value) + lo = 0 + hi = len(self._blist) + while lo < hi: + mid = (lo+hi)//2 + n, v = self._squeeze(mid) + hi -= n + if n and hi == len(self._blist): + continue + if self._i2key(self._blist[mid]) < key: lo = mid+1 + else: hi = mid + n, v = self._squeeze(lo) + return lo, v + + def _bisect_right(self, value): + key = self._u2key(value) + lo = 0 + hi = len(self._blist) + while lo < hi: + mid = (lo+hi)//2 + n, v = self._squeeze(mid) + hi -= n + if n and hi == len(self._blist): + continue + if key < self._i2key(self._blist[mid]): hi = mid + else: lo = mid+1 + n, v = self._squeeze(lo) + return lo, v + + _bisect = _bisect_right + + def _u2i(self, value): + if self._key is None: + return weakref.ref(value) + else: + return (self._key(value), weakref.ref(value)) + + def _i2u(self, value): + if self._key is None: + return value() + else: + return value[1]() + + def _i2key(self, value): + if self._key is None: + return value() + else: + return value[0] + + def __iter__(self): + """ x.__iter__() <==> iter(x)""" + i = 0 + while i < len(self._blist): + n, v = self._squeeze(i) + if v is None: break + yield v + i += 1 + + def _squeeze(self, i): + n = 0 + while i < len(self._blist): + v = self._i2u(self._blist[i]) + if v is None: + del self._blist[i] + n += 1 + else: + return n, v + return n, None + + def __getitem__(self, index): + """x.__getitem__(y) <==> x[y]""" + if isinstance(index, slice): + return _sortedbase.__getitem__(self, index) + n, v = self._squeeze(index) + if v is None: + raise IndexError('list index out of range') + return v + + def __reversed__(self): + """L.__reversed__() -- return a reverse iterator over the list""" + i = len(self._blist)-1 + while i >= 0: + n, v = self._squeeze(i) + if not n: + yield v + i -= 1 + + def _advance(self, i, value): + "Do a linear search through all items with the same key" + key = self._u2key(value) + while i < len(self._blist): + n, v = self._squeeze(i) + if v is None: + break + if v == value: + return i + elif key < self._i2key(self._blist[i]): + break + i += 1 + return -1 + +class _listmixin(object): + def remove(self, value): + """L.remove(value) -- remove first occurrence of value. + + Raises ValueError if the value is not present. + """ + + del self[self.index(value)] + + def update(self, iterable): + """L.update(iterable) -- add all elements from iterable into the list""" + + for item in iterable: + self.add(item) + + def __mul__(self, k): + if not isinstance(k, int): + raise TypeError("can't multiply sequence by non-int of type '%s'" + % str(type(int))) + rv = self.__class__() + rv._key = self._key + rv._blist = sum((blist([x])*k for x in self._blist), blist()) + return rv + __rmul__ = __mul__ + + def __imul__(self, k): + if not isinstance(k, int): + raise TypeError("can't multiply sequence by non-int of type '%s'" + % str(type(int))) + self._blist = sum((blist([x])*k for x in self._blist), blist()) + return self + + def __eq__(self, other): + """x.__eq__(y) <==> x==y""" + return self._cmp_op(other, operator.eq) + def __ne__(self, other): + """x.__ne__(y) <==> x!=y""" + return self._cmp_op(other, operator.ne) + def __lt__(self, other): + """x.__lt__(y) <==> x x>y""" + return self._cmp_op(other, operator.gt) + def __le__(self, other): + """x.__le__(y) <==> x<=y""" + return self._cmp_op(other, operator.le) + def __ge__(self, other): + """x.__ge__(y) <==> x>=y""" + return self._cmp_op(other, operator.ge) + +class _setmixin(object): + "Methods that override our base class" + + def add(self, value): + """Add an element to the set. + + This has no effect if the element is already present. + + """ + if value in self: return + super(_setmixin, self).add(value) + + def __iter__(self): + it = super(_setmixin, self).__iter__() + while True: + item = next(it) + n = len(self) + yield item + if n != len(self): + raise RuntimeError('Set changed size during iteration') + +def safe_cmp(f): + def g(self, other): + if not isinstance(other, collections.Set): + raise TypeError("can only compare to a set") + return f(self, other) + return g + +class _setmixin2(collections.MutableSet): + "methods that override or supplement the collections.MutableSet methods" + + __ror__ = collections.MutableSet.__or__ + __rand__ = collections.MutableSet.__and__ + __rxor__ = collections.MutableSet.__xor__ + + if sys.version_info[0] < 3: # pragma: no cover + __lt__ = safe_cmp(collections.MutableSet.__lt__) + __gt__ = safe_cmp(collections.MutableSet.__gt__) + __le__ = safe_cmp(collections.MutableSet.__le__) + __ge__ = safe_cmp(collections.MutableSet.__ge__) + + def __ior__(self, it): + if self is it: + return self + for value in it: + self.add(value) + return self + + def __isub__(self, it): + if self is it: + self.clear() + return self + for value in it: + self.discard(value) + return self + + def __ixor__(self, it): + if self is it: + self.clear() + return self + for value in it: + if value in self: + self.discard(value) + else: + self.add(value) + return self + + def __rsub__(self, other): + return self._from_iterable(other) - self + + def _make_set(self, iterable): + if isinstance(iterable, collections.Set): + return iterable + return self._from_iterable(iterable) + + def difference(self, *args): + """Return a new set with elements in the set that are not in the others.""" + rv = self.copy() + rv.difference_update(*args) + return rv + + def intersection(self, *args): + """Return a new set with elements common to the set and all others.""" + rv = self.copy() + rv.intersection_update(*args) + return rv + + def issubset(self, other): + """Test whether every element in the set is in *other*.""" + return self <= self._make_set(other) + + def issuperset(self, other): + """Test whether every element in *other* is in the set.""" + return self >= self._make_set(other) + + def symmetric_difference(self, other): + """Return a new set with elements in either the set or *other* + but not both.""" + + return self ^ self._make_set(other) + + def union(self, *args): + """Return the union of sets as a new set. + + (i.e. all elements that are in either set.) + + """ + rv = self.copy() + for arg in args: + rv |= self._make_set(arg) + return rv + + def update(self, *args): + """Update the set, adding elements from all others.""" + for arg in args: + self |= self._make_set(arg) + + def difference_update(self, *args): + """Update the set, removing elements found in others.""" + for arg in args: + self -= self._make_set(arg) + + def intersection_update(self, *args): + """Update the set, keeping only elements found in it and all others.""" + for arg in args: + self &= self._make_set(arg) + + def symmetric_difference_update(self, other): + """Update the set, keeping only elements found in either set, + but not in both.""" + + self ^= self._make_set(other) + + def clear(self): + """Remove all elements""" + del self._blist[:] + + def copy(self): + return self[:] + +class sortedlist(_sortedbase, _listmixin): + """sortedlist(iterable=(), key=None) -> new sorted list + + Keyword arguments: + iterable -- items used to initially populate the sorted list + key -- a function to return the sort key of an item + + A sortedlist is indexable like a list, but always keeps its + members in sorted order. + + """ + + def __repr__(self): + """x.__repr__() <==> repr(x)""" + if not self: return 'sortedlist()' + with ReprRecursion(self) as r: + if r: return 'sortedlist(...)' + return ('sortedlist(%s)' % repr(list(self))) + + def _cmp_op(self, other, op): + if not (isinstance(other,type(self)) or isinstance(self,type(other))): + return NotImplemented + if len(self) != len(other): + if op is operator.eq: + return False + if op is operator.ne: + return True + for x, y in izip(self, other): + if x != y: + return op(x, y) + return op in (operator.eq, operator.le, operator.ge) + +class weaksortedlist(_listmixin, _weaksortedbase): + """weaksortedlist(iterable=(), key=None) -> new sorted weak list + + Keyword arguments: + iterable -- items used to initially populate the sorted list + key -- a function to return the sort key of an item + + A weaksortedlist is indexable like a list, but always keeps its + items in sorted order. The weaksortedlist weakly references its + members, so items will be discarded after there is no longer a + strong reference to the item. + + """ + + def __repr__(self): + """x.__repr__() <==> repr(x)""" + if not self: return 'weaksortedlist()' + with ReprRecursion(self) as r: + if r: return 'weaksortedlist(...)' + return 'weaksortedlist(%s)' % repr(list(self)) + + def _cmp_op(self, other, op): + if not (isinstance(other,type(self)) or isinstance(self,type(other))): + return NotImplemented + for x, y in izip(self, other): + if x != y: + return op(x, y) + return op in (operator.eq, operator.le, operator.ge) + +class sortedset(_setmixin, _sortedbase, _setmixin2): + """sortedset(iterable=(), key=None) -> new sorted set + + Keyword arguments: + iterable -- items used to initially populate the sorted set + key -- a function to return the sort key of an item + + A sortedset is similar to a set but is also indexable like a list. + Items are maintained in sorted order. + + """ + + def __repr__(self): + """x.__repr__() <==> repr(x)""" + if not self: return 'sortedset()' + with ReprRecursion(self) as r: + if r: return 'sortedset(...)' + return ('sortedset(%s)' % repr(list(self))) + +class weaksortedset(_setmixin, _weaksortedbase, _setmixin2): + """weaksortedset(iterable=(), key=None) -> new sorted weak set + + Keyword arguments: + iterable -- items used to initially populate the sorted set + key -- a function to return the sort key of an item + + A weaksortedset is similar to a set but is also indexable like a + list. Items are maintained in sorted order. The weaksortedset + weakly references its members, so items will be discarded after + there is no longer a strong reference to the item. + + """ + + def __repr__(self): + """x.__repr__() <==> repr(x)""" + if not self: return 'weaksortedset()' + with ReprRecursion(self) as r: + if r: return 'weaksortedset(...)' + return 'weaksortedset(%s)' % repr(list(self)) diff --git a/blist/blist.h b/blist/blist.h new file mode 100644 index 0000000..7476fe1 --- /dev/null +++ b/blist/blist.h @@ -0,0 +1,248 @@ + +/* List object interface */ + +/* +Another generally useful object type is an list of object pointers. +This is a mutable type: the list items can be changed, and items can be +added or removed. Out-of-range indices or non-list objects are ignored. + +*** WARNING *** PyList_SetItem does not increment the new item's reference +count, but does decrement the reference count of the item it replaces, +if not nil. It does *decrement* the reference count if it is *not* +inserted in the list. Similarly, PyList_GetItem does not increment the +returned item's reference count. +*/ + +/********************************************************************** + * * + * PLEASE READ blist.rst BEFORE MODIFYING THIS CODE * + * * + **********************************************************************/ + +#ifndef Py_BLISTOBJECT_H +#define Py_BLISTOBJECT_H +#ifdef __cplusplus +extern "C" { +#endif + +#if 0 +#define BLIST_IN_PYTHON /* Define if building BList into Python */ +#endif + +/* pyport.h includes similar defines, but they're broken and never use + * "inline" except on Windows :-( */ +#if defined(_MSC_VER) +/* ignore warnings if the compiler decides not to inline a function */ +#pragma warning(disable: 4710) +/* fastest possible local call under MSVC */ +#define BLIST_LOCAL(type) static type __fastcall +#define BLIST_LOCAL_INLINE(type) static __inline type __fastcall +#elif defined(__GNUC__) +#if defined(__i386__) +#define BLIST_LOCAL(type) static type __attribute__((fastcall)) +#define BLIST_LOCAL_INLINE(type) static inline __attribute__((fastcall)) type +#else +#define BLIST_LOCAL(type) static type +#define BLIST_LOCAL_INLINE(type) static inline type +#endif +#else +#define BLIST_LOCAL(type) static type +#define BLIST_LOCAL_INLINE(type) static type +#endif + +#ifndef LIMIT +#define LIMIT (128) /* Good performance value */ +#if 0 +#define LIMIT (8) /* Maximum size, currently low (for test purposes) */ +#endif +#endif +#define HALF (LIMIT/2) /* Minimum size */ +#define MAX_HEIGHT (16) /* ceil(log(PY_SSIZE_T_MAX)/log(HALF)); */ +#if LIMIT & 1 +#error LIMIT must be divisible by 2 +#endif +#if LIMIT < 8 +#error LIMIT must be at least 8 +#endif +#define INDEX_FACTOR (HALF) + +typedef struct PyBList { + PyObject_HEAD + Py_ssize_t n; /* Total # of user-object descendents */ + int num_children; /* Number of immediate children */ + int leaf; /* Boolean value */ + PyObject **children; /* Immediate children */ +} PyBList; + +typedef struct PyBListRoot { + PyObject_HEAD +#define BLIST_FIRST_FIELD n + Py_ssize_t n; /* Total # of user-object descendents */ + int num_children; /* Number of immediate children */ + int leaf; /* Boolean value */ + PyObject **children; /* Immediate children */ + + PyBList **index_list; + Py_ssize_t *offset_list; + unsigned *setclean_list; /* contains index_allocated _bits_ */ + Py_ssize_t index_allocated; + Py_ssize_t *dirty; + Py_ssize_t dirty_length; + Py_ssize_t dirty_root; + Py_ssize_t free_root; + +#ifdef Py_DEBUG + Py_ssize_t last_n; /* For debug */ +#endif +} PyBListRoot; + +#define PyBList_GET_ITEM(op, i) (((PyBList *)(op))->leaf ? (((PyBList *)(op))->children[(i)]) : _PyBList_GET_ITEM_FAST2((PyBListRoot*) (op), (i))) + +/************************************************************************ + * Code used when building BList into the interpreter + */ + +#ifdef BLIST_IN_PYTHON +int PyList_Init1(void); +int PyList_Init2(void); +typedef PyBListRoot PyListObject; + +//PyAPI_DATA(PyTypeObject) PyList_Type; + +PyAPI_DATA(PyTypeObject) PyBList_Type; +PyAPI_DATA(PyTypeObject) PyRootBList_Type; +#define PyList_Type PyRootBList_Type + +#define PyList_Check(op) \ + PyType_FastSubclass(Py_TYPE(op), Py_TPFLAGS_LIST_SUBCLASS) +#define PyList_CheckExact(op) ((op)->ob_type == &PyRootBList_Type) + +PyAPI_FUNC(PyObject *) PyList_New(Py_ssize_t size); +PyAPI_FUNC(Py_ssize_t) PyList_Size(PyObject *); +PyAPI_FUNC(PyObject *) PyList_GetItem(PyObject *, Py_ssize_t); +PyAPI_FUNC(int) PyList_SetItem(PyObject *, Py_ssize_t, PyObject *); +PyAPI_FUNC(int) PyList_Insert(PyObject *, Py_ssize_t, PyObject *); +PyAPI_FUNC(int) PyList_Append(PyObject *, PyObject *); +PyAPI_FUNC(PyObject *) PyList_GetSlice(PyObject *, Py_ssize_t, Py_ssize_t); +PyAPI_FUNC(int) PyList_SetSlice(PyObject *, Py_ssize_t, Py_ssize_t, PyObject *); +PyAPI_FUNC(int) PyList_Sort(PyObject *); +PyAPI_FUNC(int) PyList_Reverse(PyObject *); +PyAPI_FUNC(PyObject *) PyList_AsTuple(PyObject *); +PyAPI_FUNC(PyObject *) _PyList_Extend(PyBListRoot *, PyObject *); + +PyAPI_FUNC(void) _PyList_SetItemFast(PyObject *, Py_ssize_t, PyObject *); + +/* Macro, trading safety for speed */ +#define PyList_GET_ITEM(op, i) (PyBList_GET_ITEM((op), (i))) +//#define PyList_SET_ITEM(op, i, v) (((PyBList *)(op))->leaf ? (void) (((PyBList *)(op))->children[(i)] = (v)) : (void) _PyList_SetItemFast((PyObject *) (op), (i), (v))) +#define PyList_SET_ITEM(self, i, v) (((PyBList *)self)->leaf ? (void) (((PyBList*)self)->children[(i)] = (v)) : (void) blist_ass_item_return2((PyBListRoot*)(self), (i), (v))) + +//#define PyList_GET_ITEM(op, i) PyList_GetItem((PyObject*) (op), (i)) +//#define PyList_SET_ITEM(op, i, v) _PyList_SetItemFast((PyObject *) (op), (i), (v)) +#define PyList_GET_SIZE(op) ({ assert(PyList_Check(op)); (((PyBList *)(op))->n); }) + +#define PyList_IS_LEAF(op) ({ assert(PyList_Check(op)); (((PyBList *) (op))->leaf); }) + +PyAPI_FUNC(PyObject *) _PyBList_GetItemFast3(PyBListRoot *, Py_ssize_t); + +PyAPI_FUNC(PyObject *) blist_ass_item_return_slow(PyBListRoot *root, Py_ssize_t i, PyObject *v); +PyAPI_FUNC(PyObject *) ext_make_clean_set(PyBListRoot *root, Py_ssize_t i, PyObject *v); +#else +PyObject *_PyBList_GetItemFast3(PyBListRoot *, Py_ssize_t); +PyObject *blist_ass_item_return_slow(PyBListRoot *root, Py_ssize_t i, PyObject *v); +PyObject *ext_make_clean_set(PyBListRoot *root, Py_ssize_t i, PyObject *v); +#endif + +#define INDEX_FACTOR (HALF) + +/* This should only be called if we know the root is not a leaf */ +/* inlining a common case for speed */ +BLIST_LOCAL_INLINE(PyObject *) +_PyBList_GET_ITEM_FAST2(PyBListRoot *root, Py_ssize_t i) +{ + Py_ssize_t ioffset; + Py_ssize_t offset; + PyBList *p; + + assert(!root->leaf); + assert(i >= 0); + assert(i < root->n); + + if (root->dirty_root >= -1 /* DIRTY */) + return _PyBList_GetItemFast3(root, i); + + ioffset = i / INDEX_FACTOR; + offset = root->offset_list[ioffset]; + p = root->index_list[ioffset]; + + if (i < offset + p->n) + return p->children[i - offset]; + ioffset++; + offset = root->offset_list[ioffset]; + p = root->index_list[ioffset]; + return p->children[i - offset]; +} + +#define SETCLEAN_LEN(index_allocated) ((((index_allocated)-1) >> SETCLEAN_SHIFT)+1) +#if SIZEOF_INT == 4 +#define SETCLEAN_SHIFT (5u) +#define SETCLEAN_MASK (0x1fu) +#elif SIZEOF_INT == 8 +#define SETCLEAN_SHIFT (6u) +#define SETCLEAN_MASK (0x3fu) +#else +#error Unknown sizeof(unsigned) +#endif + +#define SET_BIT(setclean_list, i) (setclean_list[(i) >> SETCLEAN_SHIFT] |= (1u << ((i) & SETCLEAN_MASK))) +#define CLEAR_BIT(setclean_list, i) (setclean_list[(i) >> SETCLEAN_SHIFT] &= ~(1u << ((i) & SETCLEAN_MASK))) +#define GET_BIT(setclean_list, i) (setclean_list[(i) >> SETCLEAN_SHIFT] & (1u << ((i) & SETCLEAN_MASK))) + +BLIST_LOCAL_INLINE(PyObject *) +blist_ass_item_return2(PyBListRoot *root, Py_ssize_t i, PyObject *v) +{ + PyObject *rv; + Py_ssize_t offset; + PyBList *p; + Py_ssize_t ioffset = i / INDEX_FACTOR; + + assert(i >= 0); + assert(i < root->n); + assert(!root->leaf); + + if (root->dirty_root >= -1 /* DIRTY */ + || !GET_BIT(root->setclean_list, ioffset)) + return blist_ass_item_return_slow(root, i, v); + + offset = root->offset_list[ioffset]; + p = root->index_list[ioffset]; + assert(i >= offset); + assert(p); + assert(p->leaf); + if (i < offset + p->n) { + good: + /* Py_REFCNT(p) == 1, generally, but see comment in + * blist_ass_item_return_slow for caveats */ + rv = p->children[i - offset]; + p->children[i - offset] = v; + } else if (!GET_BIT(root->setclean_list, ioffset+1)) { + return ext_make_clean_set(root, i, v); + } else { + ioffset++; + assert(ioffset < root->index_allocated); + offset = root->offset_list[ioffset]; + p = root->index_list[ioffset]; + assert(p); + assert(p->leaf); + assert(i < offset + p->n); + + goto good; + } + + return rv; +} + +#ifdef __cplusplus +} +#endif +#endif /* !Py_BLISTOBJECT_H */ diff --git a/blist/test/__init__.py b/blist/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/blist/test/btuple_tests.py b/blist/test/btuple_tests.py new file mode 100644 index 0000000..a6ccbb6 --- /dev/null +++ b/blist/test/btuple_tests.py @@ -0,0 +1,159 @@ +# Based on Python's tuple_tests.py, licensed under the Python License +# Agreement + +import sys +import random +import gc +from blist import btuple +from blist.test import unittest +from blist.test import seq_tests + +class bTupleTest(seq_tests.CommonTest): + type2test = btuple + + def test_constructors(self): + super(bTupleTest, self).test_len() + # calling built-in types without argument must return empty + self.assertEqual(tuple(), ()) + t0_3 = (0, 1, 2, 3) + t0_3_bis = tuple(t0_3) + self.assert_(t0_3 is t0_3_bis) + self.assertEqual(tuple([]), ()) + self.assertEqual(tuple([0, 1, 2, 3]), (0, 1, 2, 3)) + self.assertEqual(tuple(''), ()) + self.assertEqual(tuple('spam'), ('s', 'p', 'a', 'm')) + + def test_truth(self): + super(bTupleTest, self).test_truth() + self.assert_(not ()) + self.assert_((42, )) + + def test_len(self): + super(bTupleTest, self).test_len() + self.assertEqual(len(()), 0) + self.assertEqual(len((0,)), 1) + self.assertEqual(len((0, 1, 2)), 3) + + def test_iadd(self): + super(bTupleTest, self).test_iadd() + u = (0, 1) + u2 = u + u += (2, 3) + self.assert_(u is not u2) + + def test_imul(self): + super(bTupleTest, self).test_imul() + u = (0, 1) + u2 = u + u *= 3 + self.assert_(u is not u2) + + def test_tupleresizebug(self): + # Check that a specific bug in _PyTuple_Resize() is squashed. + def f(): + for i in range(1000): + yield i + self.assertEqual(list(tuple(f())), list(range(1000))) + + def test_hash(self): + # See SF bug 942952: Weakness in tuple hash + # The hash should: + # be non-commutative + # should spread-out closely spaced values + # should not exhibit cancellation in tuples like (x,(x,y)) + # should be distinct from element hashes: hash(x)!=hash((x,)) + # This test exercises those cases. + # For a pure random hash and N=50, the expected number of occupied + # buckets when tossing 252,600 balls into 2**32 buckets + # is 252,592.6, or about 7.4 expected collisions. The + # standard deviation is 2.73. On a box with 64-bit hash + # codes, no collisions are expected. Here we accept no + # more than 15 collisions. Any worse and the hash function + # is sorely suspect. + + N=50 + base = list(range(N)) + xp = [(i, j) for i in base for j in base] + inps = base + [(i, j) for i in base for j in xp] + \ + [(i, j) for i in xp for j in base] + xp + list(zip(base)) + collisions = len(inps) - len(set(map(hash, inps))) + self.assert_(collisions <= 15) + + def test_repr(self): + l0 = btuple() + l2 = btuple((0, 1, 2)) + a0 = self.type2test(l0) + a2 = self.type2test(l2) + + self.assertEqual(str(a0), repr(l0)) + self.assertEqual(str(a2), repr(l2)) + self.assertEqual(repr(a0), "btuple(())") + self.assertEqual(repr(a2), "btuple((0, 1, 2))") + + def _not_tracked(self, t): + if sys.version_info[0] < 3: + return + else: # pragma: no cover + # Nested tuples can take several collections to untrack + gc.collect() + gc.collect() + self.assertFalse(gc.is_tracked(t), t) + + def _tracked(self, t): + if sys.version_info[0] < 3: + return + else: # pragma: no cover + self.assertTrue(gc.is_tracked(t), t) + gc.collect() + gc.collect() + self.assertTrue(gc.is_tracked(t), t) + + def test_track_literals(self): + # Test GC-optimization of tuple literals + x, y, z = 1.5, "a", [] + + self._not_tracked(()) + self._not_tracked((1,)) + self._not_tracked((1, 2)) + self._not_tracked((1, 2, "a")) + self._not_tracked((1, 2, (None, True, False, ()), int)) + self._not_tracked((object(),)) + self._not_tracked(((1, x), y, (2, 3))) + + # Tuples with mutable elements are always tracked, even if those + # elements are not tracked right now. + self._tracked(([],)) + self._tracked(([1],)) + self._tracked(({},)) + self._tracked((set(),)) + self._tracked((x, y, z)) + + def check_track_dynamic(self, tp, always_track): + x, y, z = 1.5, "a", [] + + check = self._tracked if always_track else self._not_tracked + check(tp()) + check(tp([])) + check(tp(set())) + check(tp([1, x, y])) + check(tp(obj for obj in [1, x, y])) + check(tp(set([1, x, y]))) + check(tp(tuple([obj]) for obj in [1, x, y])) + check(tuple(tp([obj]) for obj in [1, x, y])) + + self._tracked(tp([z])) + self._tracked(tp([[x, y]])) + self._tracked(tp([{x: y}])) + self._tracked(tp(obj for obj in [x, y, z])) + self._tracked(tp(tuple([obj]) for obj in [x, y, z])) + self._tracked(tuple(tp([obj]) for obj in [x, y, z])) + + def test_track_dynamic(self): + # Test GC-optimization of dynamically constructed tuples. + self.check_track_dynamic(tuple, False) + + def test_track_subtypes(self): + # Tuple subtypes must always be tracked + class MyTuple(tuple): + pass + self.check_track_dynamic(MyTuple, True) diff --git a/blist/test/list_tests.py b/blist/test/list_tests.py new file mode 100644 index 0000000..70dbe66 --- /dev/null +++ b/blist/test/list_tests.py @@ -0,0 +1,636 @@ +# This file taken from Python, licensed under the Python License Agreement + +from __future__ import print_function +""" +Tests common to list and UserList.UserList +""" + +import sys +import os + +from blist.test import unittest +from blist.test import test_support +from blist.test import seq_tests + +from decimal import Decimal + +def CmpToKey(mycmp): + 'Convert a cmp= function into a key= function' + class K(object): + def __init__(self, obj): + self.obj = obj + def __lt__(self, other): + return mycmp(self.obj, other.obj) == -1 + return K + +class CommonTest(seq_tests.CommonTest): + + def test_init(self): + # Iterable arg is optional + self.assertEqual(self.type2test([]), self.type2test()) + + # Init clears previous values + a = self.type2test([1, 2, 3]) + a.__init__() + self.assertEqual(a, self.type2test([])) + + # Init overwrites previous values + a = self.type2test([1, 2, 3]) + a.__init__([4, 5, 6]) + self.assertEqual(a, self.type2test([4, 5, 6])) + + # Mutables always return a new object + b = self.type2test(a) + self.assertNotEqual(id(a), id(b)) + self.assertEqual(a, b) + + def test_repr(self): + l0 = [] + l2 = [0, 1, 2] + a0 = self.type2test(l0) + a2 = self.type2test(l2) + + self.assertEqual(str(a0), 'blist(%s)' % str(l0)) + self.assertEqual(repr(a0), 'blist(%s)' % repr(l0)) + self.assertEqual(repr(a2), 'blist(%s)' % repr(l2)) + self.assertEqual(str(a2), "blist([0, 1, 2])") + self.assertEqual(repr(a2), "blist([0, 1, 2])") + + a2.append(a2) + a2.append(3) + self.assertEqual(str(a2), "blist([0, 1, 2, [...], 3])") + self.assertEqual(repr(a2), "blist([0, 1, 2, [...], 3])") + + def test_print(self): + d = self.type2test(range(200)) + d.append(d) + d.extend(range(200,400)) + d.append(d) + d.append(400) + try: + fo = open(test_support.TESTFN, "w") + fo.write(str(d)) + fo.close() + fo = open(test_support.TESTFN, "r") + self.assertEqual(fo.read(), repr(d)) + finally: + fo.close() + os.remove(test_support.TESTFN) + + def test_set_subscript(self): + a = self.type2test(list(range(20))) + self.assertRaises(ValueError, a.__setitem__, slice(0, 10, 0), [1,2,3]) + self.assertRaises(TypeError, a.__setitem__, slice(0, 10), 1) + self.assertRaises(ValueError, a.__setitem__, slice(0, 10, 2), [1,2]) + self.assertRaises(TypeError, a.__getitem__, 'x', 1) + a[slice(2,10,3)] = [1,2,3] + self.assertEqual(a, self.type2test([0, 1, 1, 3, 4, 2, 6, 7, 3, + 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19])) + + def test_reversed(self): + a = self.type2test(list(range(20))) + r = reversed(a) + self.assertEqual(list(r), self.type2test(list(range(19, -1, -1)))) + if hasattr(r, '__next__'): # pragma: no cover + self.assertRaises(StopIteration, r.__next__) + else: # pragma: no cover + self.assertRaises(StopIteration, r.next) + self.assertEqual(list(reversed(self.type2test())), + self.type2test()) + + def test_setitem(self): + a = self.type2test([0, 1]) + a[0] = 0 + a[1] = 100 + self.assertEqual(a, self.type2test([0, 100])) + a[-1] = 200 + self.assertEqual(a, self.type2test([0, 200])) + a[-2] = 100 + self.assertEqual(a, self.type2test([100, 200])) + self.assertRaises(IndexError, a.__setitem__, -3, 200) + self.assertRaises(IndexError, a.__setitem__, 2, 200) + + a = self.type2test([]) + self.assertRaises(IndexError, a.__setitem__, 0, 200) + self.assertRaises(IndexError, a.__setitem__, -1, 200) + self.assertRaises(TypeError, a.__setitem__) + + a = self.type2test([0,1,2,3,4]) + a[0] = 1 + a[1] = 2 + a[2] = 3 + self.assertEqual(a, self.type2test([1,2,3,3,4])) + a[0] = 5 + a[1] = 6 + a[2] = 7 + self.assertEqual(a, self.type2test([5,6,7,3,4])) + a[-2] = 88 + a[-1] = 99 + self.assertEqual(a, self.type2test([5,6,7,88,99])) + a[-2] = 8 + a[-1] = 9 + self.assertEqual(a, self.type2test([5,6,7,8,9])) + + def test_delitem(self): + a = self.type2test([0, 1]) + del a[1] + self.assertEqual(a, [0]) + del a[0] + self.assertEqual(a, []) + + a = self.type2test([0, 1]) + del a[-2] + self.assertEqual(a, [1]) + del a[-1] + self.assertEqual(a, []) + + a = self.type2test([0, 1]) + self.assertRaises(IndexError, a.__delitem__, -3) + self.assertRaises(IndexError, a.__delitem__, 2) + + a = self.type2test([]) + self.assertRaises(IndexError, a.__delitem__, 0) + + self.assertRaises(TypeError, a.__delitem__) + + def test_setslice(self): + l = [0, 1] + a = self.type2test(l) + + for i in range(-3, 4): + a[:i] = l[:i] + self.assertEqual(a, l) + a2 = a[:] + a2[:i] = a[:i] + self.assertEqual(a2, a) + a[i:] = l[i:] + self.assertEqual(a, l) + a2 = a[:] + a2[i:] = a[i:] + self.assertEqual(a2, a) + for j in range(-3, 4): + a[i:j] = l[i:j] + self.assertEqual(a, l) + a2 = a[:] + a2[i:j] = a[i:j] + self.assertEqual(a2, a) + + aa2 = a2[:] + aa2[:0] = [-2, -1] + self.assertEqual(aa2, [-2, -1, 0, 1]) + aa2[0:] = [] + self.assertEqual(aa2, []) + + a = self.type2test([1, 2, 3, 4, 5]) + a[:-1] = a + self.assertEqual(a, self.type2test([1, 2, 3, 4, 5, 5])) + a = self.type2test([1, 2, 3, 4, 5]) + a[1:] = a + self.assertEqual(a, self.type2test([1, 1, 2, 3, 4, 5])) + a = self.type2test([1, 2, 3, 4, 5]) + a[1:-1] = a + self.assertEqual(a, self.type2test([1, 1, 2, 3, 4, 5, 5])) + + a = self.type2test([]) + a[:] = tuple(range(10)) + self.assertEqual(a, self.type2test(list(range(10)))) + + if sys.version_info[0] < 3: + self.assertRaises(TypeError, a.__setslice__, 0, 1, 5) + self.assertRaises(TypeError, a.__setslice__) + + def test_delslice(self): + a = self.type2test([0, 1]) + del a[1:2] + del a[0:1] + self.assertEqual(a, self.type2test([])) + + a = self.type2test([0, 1]) + del a[1:2] + del a[0:1] + self.assertEqual(a, self.type2test([])) + + a = self.type2test([0, 1]) + del a[-2:-1] + self.assertEqual(a, self.type2test([1])) + + a = self.type2test([0, 1]) + del a[-2:-1] + self.assertEqual(a, self.type2test([1])) + + a = self.type2test([0, 1]) + del a[1:] + del a[:1] + self.assertEqual(a, self.type2test([])) + + a = self.type2test([0, 1]) + del a[1:] + del a[:1] + self.assertEqual(a, self.type2test([])) + + a = self.type2test([0, 1]) + del a[-1:] + self.assertEqual(a, self.type2test([0])) + + a = self.type2test([0, 1]) + del a[-1:] + self.assertEqual(a, self.type2test([0])) + + a = self.type2test([0, 1]) + del a[:] + self.assertEqual(a, self.type2test([])) + + def test_append(self): + a = self.type2test([]) + a.append(0) + a.append(1) + a.append(2) + self.assertEqual(a, self.type2test([0, 1, 2])) + + self.assertRaises(TypeError, a.append) + + def test_extend(self): + a1 = self.type2test([0]) + a2 = self.type2test((0, 1)) + a = a1[:] + a.extend(a2) + self.assertEqual(a, a1 + a2) + + a.extend(self.type2test([])) + self.assertEqual(a, a1 + a2) + + a.extend(a) + self.assertEqual(a, self.type2test([0, 0, 1, 0, 0, 1])) + + a = self.type2test("spam") + a.extend("eggs") + self.assertEqual(a, list("spameggs")) + + self.assertRaises(TypeError, a.extend, None) + + self.assertRaises(TypeError, a.extend) + + def test_insert(self): + a = self.type2test([0, 1, 2]) + a.insert(0, -2) + a.insert(1, -1) + a.insert(2, 0) + self.assertEqual(a, [-2, -1, 0, 0, 1, 2]) + + b = a[:] + b.insert(-2, "foo") + b.insert(-200, "left") + b.insert(200, "right") + self.assertEqual(b, self.type2test(["left",-2,-1,0,0,"foo",1,2,"right"])) + + self.assertRaises(TypeError, a.insert) + + def test_pop(self): + a = self.type2test([-1, 0, 1]) + a.pop() + self.assertEqual(a, [-1, 0]) + a.pop(0) + self.assertEqual(a, [0]) + self.assertRaises(IndexError, a.pop, 5) + a.pop(0) + self.assertEqual(a, []) + self.assertRaises(IndexError, a.pop) + self.assertRaises(TypeError, a.pop, 42, 42) + a = self.type2test([0, 10, 20, 30, 40]) + + def test_remove(self): + a = self.type2test([0, 0, 1]) + a.remove(1) + self.assertEqual(a, [0, 0]) + a.remove(0) + self.assertEqual(a, [0]) + a.remove(0) + self.assertEqual(a, []) + + self.assertRaises(ValueError, a.remove, 0) + + self.assertRaises(TypeError, a.remove) + + class BadExc(Exception): + pass + + class BadCmp: + def __eq__(self, other): + if other == 2: + raise BadExc() + return False + + a = self.type2test([0, 1, 2, 3]) + self.assertRaises(BadExc, a.remove, BadCmp()) + + class BadCmp2: + def __eq__(self, other): + raise BadExc() + + d = self.type2test('abcdefghcij') + d.remove('c') + self.assertEqual(d, self.type2test('abdefghcij')) + d.remove('c') + self.assertEqual(d, self.type2test('abdefghij')) + self.assertRaises(ValueError, d.remove, 'c') + self.assertEqual(d, self.type2test('abdefghij')) + + # Handle comparison errors + d = self.type2test(['a', 'b', BadCmp2(), 'c']) + e = self.type2test(d) + self.assertRaises(BadExc, d.remove, 'c') + for x, y in zip(d, e): + # verify that original order and values are retained. + self.assert_(x is y) + + def test_count(self): + a = self.type2test([0, 1, 2])*3 + self.assertEqual(a.count(0), 3) + self.assertEqual(a.count(1), 3) + self.assertEqual(a.count(3), 0) + + self.assertRaises(TypeError, a.count) + + class BadExc(Exception): + pass + + class BadCmp: + def __eq__(self, other): + if other == 2: + raise BadExc() + return False + + self.assertRaises(BadExc, a.count, BadCmp()) + + def test_index(self): + u = self.type2test([0, 1]) + self.assertEqual(u.index(0), 0) + self.assertEqual(u.index(1), 1) + self.assertRaises(ValueError, u.index, 2) + + u = self.type2test([-2, -1, 0, 0, 1, 2]) + self.assertEqual(u.count(0), 2) + self.assertEqual(u.index(0), 2) + self.assertEqual(u.index(0, 2), 2) + self.assertEqual(u.index(-2, -10), 0) + self.assertEqual(u.index(0, 3), 3) + self.assertEqual(u.index(0, 3, 4), 3) + self.assertRaises(ValueError, u.index, 2, 0, -10) + + self.assertRaises(TypeError, u.index) + + class BadExc(Exception): + pass + + class BadCmp: + def __eq__(self, other): + if other == 2: + raise BadExc() + return False + + a = self.type2test([0, 1, 2, 3]) + self.assertRaises(BadExc, a.index, BadCmp()) + + a = self.type2test([-2, -1, 0, 0, 1, 2]) + self.assertEqual(a.index(0), 2) + self.assertEqual(a.index(0, 2), 2) + self.assertEqual(a.index(0, -4), 2) + self.assertEqual(a.index(-2, -10), 0) + self.assertEqual(a.index(0, 3), 3) + self.assertEqual(a.index(0, -3), 3) + self.assertEqual(a.index(0, 3, 4), 3) + self.assertEqual(a.index(0, -3, -2), 3) + self.assertEqual(a.index(0, -4*sys.maxsize, 4*sys.maxsize), 2) + self.assertRaises(ValueError, a.index, 0, 4*sys.maxsize,-4*sys.maxsize) + self.assertRaises(ValueError, a.index, 2, 0, -10) + a.remove(0) + self.assertRaises(ValueError, a.index, 2, 0, 4) + self.assertEqual(a, self.type2test([-2, -1, 0, 1, 2])) + + # Test modifying the list during index's iteration + class EvilCmp: + def __init__(self, victim): + self.victim = victim + def __eq__(self, other): + del self.victim[:] + return False + a = self.type2test() + a[:] = [EvilCmp(a) for _ in range(100)] + # This used to seg fault before patch #1005778 + self.assertRaises(ValueError, a.index, None) + + def test_reverse(self): + u = self.type2test([-2, -1, 0, 1, 2]) + u2 = u[:] + u.reverse() + self.assertEqual(u, [2, 1, 0, -1, -2]) + u.reverse() + self.assertEqual(u, u2) + + self.assertRaises(TypeError, u.reverse, 42) + + def test_clear(self): + u = self.type2test([2, 3, 4]) + u.clear() + self.assertEqual(u, []) + + u = self.type2test([]) + u.clear() + self.assertEqual(u, []) + + u = self.type2test([]) + u.append(1) + u.clear() + u.append(2) + self.assertEqual(u, [2]) + + self.assertRaises(TypeError, u.clear, None) + + def test_copy(self): + u = self.type2test([1, 2, 3]) + v = u.copy() + self.assertEqual(v, [1, 2, 3]) + + u = self.type2test([]) + v = u.copy() + self.assertEqual(v, []) + + # test that it's indeed a copy and not a reference + u = self.type2test(['a', 'b']) + v = u.copy() + v.append('i') + self.assertEqual(u, ['a', 'b']) + self.assertEqual(v, u + self.type2test(['i'])) + + # test that it's a shallow, not a deep copy + u = self.type2test([1, 2, [3, 4], 5]) + v = u.copy() + self.assertEqual(u, v) + self.assertIs(v[3], u[3]) + + self.assertRaises(TypeError, u.copy, None) + + def test_sort(self): + u = self.type2test([1, 0]) + u.sort() + self.assertEqual(u, [0, 1]) + + u = self.type2test([2,1,0,-1,-2]) + u.sort() + self.assertEqual(u, self.type2test([-2,-1,0,1,2])) + + self.assertRaises(TypeError, u.sort, 42, 42) + + a = self.type2test(reversed(list(range(512)))) + a.sort() + self.assertEqual(a, self.type2test(list(range(512)))) + + def revcmp(a, b): # pragma: no cover + if a == b: + return 0 + elif a < b: + return 1 + else: # a > b + return -1 + u.sort(key=CmpToKey(revcmp)) + self.assertEqual(u, self.type2test([2,1,0,-1,-2])) + + # The following dumps core in unpatched Python 1.5: + def myComparison(x,y): + xmod, ymod = x%3, y%7 + if xmod == ymod: + return 0 + elif xmod < ymod: + return -1 + else: # xmod > ymod + return 1 + z = self.type2test(list(range(12))) + z.sort(key=CmpToKey(myComparison)) + + self.assertRaises(TypeError, z.sort, 2) + + def selfmodifyingComparison(x,y): + z.append(1) + return cmp(x, y) + self.assertRaises(ValueError, z.sort, key=CmpToKey(selfmodifyingComparison)) + + if sys.version_info[0] < 3: + self.assertRaises(TypeError, z.sort, lambda x, y: 's') + + self.assertRaises(TypeError, z.sort, 42, 42, 42, 42) + + def test_slice(self): + u = self.type2test("spam") + u[:2] = "h" + self.assertEqual(u, list("ham")) + + def test_iadd(self): + super(CommonTest, self).test_iadd() + u = self.type2test([0, 1]) + u2 = u + u += [2, 3] + self.assert_(u is u2) + + u = self.type2test("spam") + u += "eggs" + self.assertEqual(u, self.type2test("spameggs")) + + self.assertRaises(TypeError, u.__iadd__, None) + + def test_imul(self): + u = self.type2test([0, 1]) + u *= 3 + self.assertEqual(u, self.type2test([0, 1, 0, 1, 0, 1])) + u *= 0 + self.assertEqual(u, self.type2test([])) + s = self.type2test([]) + oldid = id(s) + s *= 10 + self.assertEqual(id(s), oldid) + + def test_extendedslicing(self): + # subscript + a = self.type2test([0,1,2,3,4]) + + # deletion + del a[::2] + self.assertEqual(a, self.type2test([1,3])) + a = self.type2test(list(range(5))) + del a[1::2] + self.assertEqual(a, self.type2test([0,2,4])) + a = self.type2test(list(range(5))) + del a[1::-2] + self.assertEqual(a, self.type2test([0,2,3,4])) + a = self.type2test(list(range(10))) + del a[::1000] + self.assertEqual(a, self.type2test([1, 2, 3, 4, 5, 6, 7, 8, 9])) + # assignment + a = self.type2test(list(range(10))) + a[::2] = [-1]*5 + self.assertEqual(a, self.type2test([-1, 1, -1, 3, -1, 5, -1, 7, -1, 9])) + a = self.type2test(list(range(10))) + a[::-4] = [10]*3 + self.assertEqual(a, self.type2test([0, 10, 2, 3, 4, 10, 6, 7, 8 ,10])) + a = self.type2test(list(range(4))) + a[::-1] = a + self.assertEqual(a, self.type2test([3, 2, 1, 0])) + a = self.type2test(list(range(10))) + b = a[:] + c = a[:] + a[2:3] = self.type2test(["two", "elements"]) + b[slice(2,3)] = self.type2test(["two", "elements"]) + c[2:3:] = self.type2test(["two", "elements"]) + self.assertEqual(a, b) + self.assertEqual(a, c) + a = self.type2test(list(range(10))) + a[::2] = tuple(range(5)) + self.assertEqual(a, self.type2test([0, 1, 1, 3, 2, 5, 3, 7, 4, 9])) + + def test_constructor_exception_handling(self): + # Bug #1242657 + class Iter(object): + def next(self): + raise KeyboardInterrupt + __next__ = next + + class F(object): + def __iter__(self): + return Iter() + self.assertRaises(KeyboardInterrupt, self.type2test, F()) + + def test_sort_cmp(self): + u = self.type2test([1, 0]) + u.sort() + self.assertEqual(u, [0, 1]) + + u = self.type2test([2,1,0,-1,-2]) + u.sort() + self.assertEqual(u, self.type2test([-2,-1,0,1,2])) + + self.assertRaises(TypeError, u.sort, 42, 42) + + if sys.version_info[0] >= 3: + return # Python 3 removed the cmp option for sort. + + def revcmp(a, b): + return cmp(b, a) + u.sort(revcmp) + self.assertEqual(u, self.type2test([2,1,0,-1,-2])) + + # The following dumps core in unpatched Python 1.5: + def myComparison(x,y): + return cmp(x%3, y%7) + z = self.type2test(range(12)) + z.sort(myComparison) + + self.assertRaises(TypeError, z.sort, 2) + + def selfmodifyingComparison(x,y): + z.append(1) + return cmp(x, y) + self.assertRaises(ValueError, z.sort, selfmodifyingComparison) + + self.assertRaises(TypeError, z.sort, lambda x, y: 's') + + self.assertRaises(TypeError, z.sort, 42, 42, 42, 42) diff --git a/blist/test/mapping_tests.py b/blist/test/mapping_tests.py new file mode 100644 index 0000000..d0a8da9 --- /dev/null +++ b/blist/test/mapping_tests.py @@ -0,0 +1,679 @@ +# This file taken from Python, licensed under the Python License Agreement + +# tests common to dict and UserDict +import sys +import collections +from blist.test import unittest +try: + from collections import UserDict # Python 3 +except ImportError: + from UserDict import UserDict # Python 2 + +class BasicTestMappingProtocol(unittest.TestCase): + # This base class can be used to check that an object conforms to the + # mapping protocol + + # Functions that can be useful to override to adapt to dictionary + # semantics + type2test = None # which class is being tested (overwrite in subclasses) + + def _reference(self): # pragma: no cover + """Return a dictionary of values which are invariant by storage + in the object under test.""" + return {1:2, "key1":"value1", "key2":(1,2,3)} + def _empty_mapping(self): + """Return an empty mapping object""" + return self.type2test() + def _full_mapping(self, data): + """Return a mapping object with the value contained in data + dictionary""" + x = self._empty_mapping() + for key, value in data.items(): + x[key] = value + return x + + def __init__(self, *args, **kw): + unittest.TestCase.__init__(self, *args, **kw) + self.reference = self._reference().copy() + + # A (key, value) pair not in the mapping + key, value = self.reference.popitem() + self.other = {key:value} + + # A (key, value) pair in the mapping + key, value = self.reference.popitem() + self.inmapping = {key:value} + self.reference[key] = value + + def test_read(self): + # Test for read only operations on mapping + p = self._empty_mapping() + p1 = dict(p) #workaround for singleton objects + d = self._full_mapping(self.reference) + if d is p: # pragma: no cover + p = p1 + #Indexing + for key, value in self.reference.items(): + self.assertEqual(d[key], value) + knownkey = list(self.other.keys())[0] + self.failUnlessRaises(KeyError, lambda:d[knownkey]) + #len + self.assertEqual(len(p), 0) + self.assertEqual(len(d), len(self.reference)) + #__contains__ + for k in self.reference: + self.assert_(k in d) + for k in self.other: + self.failIf(k in d) + #cmp + self.assertEqual(p, p) + self.assertEqual(d, d) + self.assertNotEqual(p, d) + self.assertNotEqual(d, p) + #__non__zero__ + if p: self.fail("Empty mapping must compare to False") + if not d: self.fail("Full mapping must compare to True") + # keys(), items(), iterkeys() ... + def check_iterandlist(iter, lst, ref): + if sys.version_info[0] < 3: # pragma: no cover + self.assert_(hasattr(iter, 'next')) + else: # pragma: no cover + self.assert_(hasattr(iter, '__next__')) + self.assert_(hasattr(iter, '__iter__')) + x = list(iter) + self.assert_(set(x)==set(lst)==set(ref)) + check_iterandlist(iter(d.keys()), list(d.keys()), + self.reference.keys()) + check_iterandlist(iter(d), list(d.keys()), self.reference.keys()) + check_iterandlist(iter(d.values()), list(d.values()), + self.reference.values()) + check_iterandlist(iter(d.items()), list(d.items()), + self.reference.items()) + #get + key, value = next(iter(d.items())) + knownkey, knownvalue = next(iter(self.other.items())) + self.assertEqual(d.get(key, knownvalue), value) + self.assertEqual(d.get(knownkey, knownvalue), knownvalue) + self.failIf(knownkey in d) + + def test_write(self): + # Test for write operations on mapping + p = self._empty_mapping() + #Indexing + for key, value in self.reference.items(): + p[key] = value + self.assertEqual(p[key], value) + for key in self.reference.keys(): + del p[key] + self.failUnlessRaises(KeyError, lambda:p[key]) + p = self._empty_mapping() + #update + p.update(self.reference) + self.assertEqual(dict(p), self.reference) + items = list(p.items()) + p = self._empty_mapping() + p.update(items) + self.assertEqual(dict(p), self.reference) + d = self._full_mapping(self.reference) + #setdefault + key, value = next(iter(d.items())) + knownkey, knownvalue = next(iter(self.other.items())) + self.assertEqual(d.setdefault(key, knownvalue), value) + self.assertEqual(d[key], value) + self.assertEqual(d.setdefault(knownkey, knownvalue), knownvalue) + self.assertEqual(d[knownkey], knownvalue) + #pop + self.assertEqual(d.pop(knownkey), knownvalue) + self.failIf(knownkey in d) + self.assertRaises(KeyError, d.pop, knownkey) + default = 909 + d[knownkey] = knownvalue + self.assertEqual(d.pop(knownkey, default), knownvalue) + self.failIf(knownkey in d) + self.assertEqual(d.pop(knownkey, default), default) + #popitem + key, value = d.popitem() + self.failIf(key in d) + self.assertEqual(value, self.reference[key]) + p=self._empty_mapping() + self.assertRaises(KeyError, p.popitem) + + def test_constructor(self): + self.assertEqual(self._empty_mapping(), self._empty_mapping()) + + def test_bool(self): + self.assert_(not self._empty_mapping()) + self.assert_(self.reference) + self.assert_(bool(self._empty_mapping()) is False) + self.assert_(bool(self.reference) is True) + + def test_keys(self): + d = self._empty_mapping() + self.assertEqual(list(d.keys()), []) + d = self.reference + self.assert_(list(self.inmapping.keys())[0] in d.keys()) + self.assert_(list(self.other.keys())[0] not in d.keys()) + self.assertRaises(TypeError, d.keys, None) + + def test_values(self): + d = self._empty_mapping() + self.assertEqual(list(d.values()), []) + + self.assertRaises(TypeError, d.values, None) + + def test_items(self): + d = self._empty_mapping() + self.assertEqual(list(d.items()), []) + + self.assertRaises(TypeError, d.items, None) + + def test_len(self): + d = self._empty_mapping() + self.assertEqual(len(d), 0) + + def test_getitem(self): + d = self.reference + self.assertEqual(d[list(self.inmapping.keys())[0]], + list(self.inmapping.values())[0]) + + self.assertRaises(TypeError, d.__getitem__) + + def test_update(self): + # mapping argument + d = self._empty_mapping() + d.update(self.other) + self.assertEqual(list(d.items()), list(self.other.items())) + + # No argument + d = self._empty_mapping() + d.update() + self.assertEqual(d, self._empty_mapping()) + + # item sequence + d = self._empty_mapping() + d.update(self.other.items()) + self.assertEqual(list(d.items()), list(self.other.items())) + + # Iterator + d = self._empty_mapping() + d.update(self.other.items()) + self.assertEqual(list(d.items()), list(self.other.items())) + + # FIXME: Doesn't work with UserDict + # self.assertRaises((TypeError, AttributeError), d.update, None) + self.assertRaises((TypeError, AttributeError), d.update, 42) + + outerself = self + class SimpleUserDict: + def __init__(self): + self.d = outerself.reference + def keys(self): + return self.d.keys() + def __getitem__(self, i): + return self.d[i] + d.clear() + d.update(SimpleUserDict()) + i1 = sorted(d.items()) + i2 = sorted(self.reference.items()) + self.assertEqual(i1, i2) + + class Exc(Exception): pass + + d = self._empty_mapping() + class FailingUserDict: + def keys(self): + raise Exc + self.assertRaises(Exc, d.update, FailingUserDict()) + + d.clear() + + class FailingUserDict: + def keys(self): + class BogonIter: + def __init__(self): + self.i = 1 + def __iter__(self): + return self + def __next__(self): + if self.i: + self.i = 0 + return 'a' + raise Exc + next = __next__ + return BogonIter() + def __getitem__(self, key): + return key + self.assertRaises(Exc, d.update, FailingUserDict()) + + class FailingUserDict: + def keys(self): + class BogonIter: + def __init__(self): + self.i = ord('a') + def __iter__(self): + return self + def __next__(self): + if self.i <= ord('z'): + rtn = chr(self.i) + self.i += 1 + return rtn + else: # pragma: no cover + raise StopIteration + next = __next__ + return BogonIter() + def __getitem__(self, key): + raise Exc + self.assertRaises(Exc, d.update, FailingUserDict()) + + d = self._empty_mapping() + class badseq(object): + def __iter__(self): + return self + def __next__(self): + raise Exc() + next = __next__ + + self.assertRaises(Exc, d.update, badseq()) + + self.assertRaises(ValueError, d.update, [(1, 2, 3)]) + + # no test_fromkeys or test_copy as both os.environ and selves don't support it + + def test_get(self): + d = self._empty_mapping() + self.assert_(d.get(list(self.other.keys())[0]) is None) + self.assertEqual(d.get(list(self.other.keys())[0], 3), 3) + d = self.reference + self.assert_(d.get(list(self.other.keys())[0]) is None) + self.assertEqual(d.get(list(self.other.keys())[0], 3), 3) + self.assertEqual(d.get(list(self.inmapping.keys())[0]), + list(self.inmapping.values())[0]) + self.assertEqual(d.get(list(self.inmapping.keys())[0], 3), + list(self.inmapping.values())[0]) + self.assertRaises(TypeError, d.get) + self.assertRaises(TypeError, d.get, None, None, None) + + def test_setdefault(self): + d = self._empty_mapping() + self.assertRaises(TypeError, d.setdefault) + + def test_popitem(self): + d = self._empty_mapping() + self.assertRaises(KeyError, d.popitem) + self.assertRaises(TypeError, d.popitem, 42) + + def test_pop(self): + d = self._empty_mapping() + k, v = list(self.inmapping.items())[0] + d[k] = v + self.assertRaises(KeyError, d.pop, list(self.other.keys())[0]) + + self.assertEqual(d.pop(k), v) + self.assertEqual(len(d), 0) + + self.assertRaises(KeyError, d.pop, k) + + +class TestMappingProtocol(BasicTestMappingProtocol): + def test_constructor(self): + BasicTestMappingProtocol.test_constructor(self) + self.assert_(self._empty_mapping() is not self._empty_mapping()) + self.assertEqual(self.type2test(x=1, y=2), self._full_mapping({"x": 1, "y": 2})) + + def test_bool(self): + BasicTestMappingProtocol.test_bool(self) + self.assert_(not self._empty_mapping()) + self.assert_(self._full_mapping({"x": "y"})) + self.assert_(bool(self._empty_mapping()) is False) + self.assert_(bool(self._full_mapping({"x": "y"})) is True) + + def test_keys(self): + BasicTestMappingProtocol.test_keys(self) + d = self._empty_mapping() + self.assertEqual(list(d.keys()), []) + d = self._full_mapping({'a': 1, 'b': 2}) + k = d.keys() + self.assert_('a' in k) + self.assert_('b' in k) + self.assert_('c' not in k) + + def test_values(self): + BasicTestMappingProtocol.test_values(self) + d = self._full_mapping({1:2}) + self.assertEqual(list(d.values()), [2]) + + def test_items(self): + BasicTestMappingProtocol.test_items(self) + + d = self._full_mapping({1:2}) + self.assertEqual(list(d.items()), [(1, 2)]) + + def test_contains(self): + d = self._empty_mapping() + self.assert_(not ('a' in d)) + self.assert_('a' not in d) + d = self._full_mapping({'a': 1, 'b': 2}) + self.assert_('a' in d) + self.assert_('b' in d) + self.assert_('c' not in d) + + self.assertRaises(TypeError, d.__contains__) + + def test_len(self): + BasicTestMappingProtocol.test_len(self) + d = self._full_mapping({'a': 1, 'b': 2}) + self.assertEqual(len(d), 2) + + def test_getitem(self): + BasicTestMappingProtocol.test_getitem(self) + d = self._full_mapping({'a': 1, 'b': 2}) + self.assertEqual(d['a'], 1) + self.assertEqual(d['b'], 2) + d['c'] = 3 + d['a'] = 4 + self.assertEqual(d['c'], 3) + self.assertEqual(d['a'], 4) + del d['b'] + self.assertEqual(d, self._full_mapping({'a': 4, 'c': 3})) + + self.assertRaises(TypeError, d.__getitem__) + + def test_clear(self): + d = self._full_mapping({1:1, 2:2, 3:3}) + d.clear() + self.assertEqual(d, self._full_mapping({})) + + self.assertRaises(TypeError, d.clear, None) + + def test_update(self): + BasicTestMappingProtocol.test_update(self) + # mapping argument + d = self._empty_mapping() + d.update({1:100}) + d.update({2:20}) + d.update({1:1, 2:2, 3:3}) + self.assertEqual(d, self._full_mapping({1:1, 2:2, 3:3})) + + # no argument + d.update() + self.assertEqual(d, self._full_mapping({1:1, 2:2, 3:3})) + + # keyword arguments + d = self._empty_mapping() + d.update(x=100) + d.update(y=20) + d.update(x=1, y=2, z=3) + self.assertEqual(d, self._full_mapping({"x":1, "y":2, "z":3})) + + # item sequence + d = self._empty_mapping() + d.update([("x", 100), ("y", 20)]) + self.assertEqual(d, self._full_mapping({"x":100, "y":20})) + + # Both item sequence and keyword arguments + d = self._empty_mapping() + d.update([("x", 100), ("y", 20)], x=1, y=2) + self.assertEqual(d, self._full_mapping({"x":1, "y":2})) + + # iterator + d = self._full_mapping({1:3, 2:4}) + d.update(self._full_mapping({1:2, 3:4, 5:6}).items()) + self.assertEqual(d, self._full_mapping({1:2, 2:4, 3:4, 5:6})) + + class SimpleUserDict: + def __init__(self): + self.d = {1:1, 2:2, 3:3} + def keys(self): + return self.d.keys() + def __getitem__(self, i): + return self.d[i] + d.clear() + d.update(SimpleUserDict()) + self.assertEqual(d, self._full_mapping({1:1, 2:2, 3:3})) + + def test_fromkeys(self): + self.assertEqual(self.type2test.fromkeys('abc'), self._full_mapping({'a':None, 'b':None, 'c':None})) + d = self._empty_mapping() + self.assert_(not(d.fromkeys('abc') is d)) + self.assertEqual(d.fromkeys('abc'), self._full_mapping({'a':None, 'b':None, 'c':None})) + self.assertEqual(d.fromkeys((4,5),0), self._full_mapping({4:0, 5:0})) + self.assertEqual(d.fromkeys([]), self._full_mapping({})) + def g(): + yield 1 + self.assertEqual(d.fromkeys(g()), self._full_mapping({1:None})) + self.assertRaises(TypeError, {}.fromkeys, 3) + class dictlike(self.type2test): pass + self.assertEqual(dictlike.fromkeys('a'), self._full_mapping({'a':None})) + self.assertEqual(dictlike().fromkeys('a'), self._full_mapping({'a':None})) + self.assert_(dictlike.fromkeys('a').__class__ is dictlike) + self.assert_(dictlike().fromkeys('a').__class__ is dictlike) + # FIXME: the following won't work with UserDict, because it's an old style class + # self.assert_(type(dictlike.fromkeys('a')) is dictlike) + class mydict(self.type2test): + def __new__(cls): + return UserDict() + ud = mydict.fromkeys('ab') + self.assertEqual(ud, {'a':None, 'b':None}) + # FIXME: the following won't work with UserDict, because it's an old style class + # self.assert_(isinstance(ud, collections.UserDict)) + self.assertRaises(TypeError, dict.fromkeys) + + class Exc(Exception): pass + + class baddict1(self.type2test): + def __init__(self): + raise Exc() + + self.assertRaises(Exc, baddict1.fromkeys, [1]) + + class BadSeq(object): + def __iter__(self): + return self + def __next__(self): + raise Exc() + next = __next__ + + self.assertRaises(Exc, self.type2test.fromkeys, BadSeq()) + + class baddict2(self.type2test): + def __setitem__(self, key, value): + raise Exc() + + self.assertRaises(Exc, baddict2.fromkeys, [1]) + + def test_copy(self): + d = self._full_mapping({1:1, 2:2, 3:3}) + self.assertEqual(d.copy(), self._full_mapping({1:1, 2:2, 3:3})) + d = self._empty_mapping() + self.assertEqual(d.copy(), d) + self.assert_(isinstance(d.copy(), d.__class__)) + self.assertRaises(TypeError, d.copy, None) + + def test_get(self): + BasicTestMappingProtocol.test_get(self) + d = self._empty_mapping() + self.assert_(d.get('c') is None) + self.assertEqual(d.get('c', 3), 3) + d = self._full_mapping({'a' : 1, 'b' : 2}) + self.assert_(d.get('c') is None) + self.assertEqual(d.get('c', 3), 3) + self.assertEqual(d.get('a'), 1) + self.assertEqual(d.get('a', 3), 1) + + def test_setdefault(self): + BasicTestMappingProtocol.test_setdefault(self) + d = self._empty_mapping() + self.assert_(d.setdefault('key0') is None) + d.setdefault('key0', []) + self.assert_(d.setdefault('key0') is None) + d.setdefault('key', []).append(3) + self.assertEqual(d['key'][0], 3) + d.setdefault('key', []).append(4) + self.assertEqual(len(d['key']), 2) + + def test_popitem(self): + BasicTestMappingProtocol.test_popitem(self) + for copymode in -1, +1: + # -1: b has same structure as a + # +1: b is a.copy() + for log2size in range(12): + size = 2**log2size + a = self._empty_mapping() + b = self._empty_mapping() + for i in range(size): + a[repr(i)] = i + if copymode < 0: + b[repr(i)] = i + if copymode > 0: + b = a.copy() + for i in range(size): + ka, va = ta = a.popitem() + self.assertEqual(va, int(ka)) + kb, vb = tb = b.popitem() + self.assertEqual(vb, int(kb)) + self.assert_(not(copymode < 0 and ta != tb)) + self.assert_(not a) + self.assert_(not b) + + def test_pop(self): + BasicTestMappingProtocol.test_pop(self) + + # Tests for pop with specified key + d = self._empty_mapping() + k, v = 'abc', 'def' + + # verify longs/ints get same value when key > 32 bits (for 64-bit archs) + # see SF bug #689659 + x = 4503599627370496 + y = 4503599627370496 + h = self._full_mapping({x: 'anything', y: 'something else'}) + self.assertEqual(h[x], h[y]) + + self.assertEqual(d.pop(k, v), v) + d[k] = v + self.assertEqual(d.pop(k, 1), v) + + +class TestHashMappingProtocol(TestMappingProtocol): + + def test_getitem(self): + TestMappingProtocol.test_getitem(self) + class Exc(Exception): pass + + class BadEq(object): + def __eq__(self, other): # pragma: no cover + raise Exc() + def __hash__(self): + return 24 + + d = self._empty_mapping() + d[BadEq()] = 42 + self.assertRaises(KeyError, d.__getitem__, 23) + + class BadHash(object): + fail = False + def __hash__(self): + if self.fail: + raise Exc() + else: + return 42 + + d = self._empty_mapping() + x = BadHash() + d[x] = 42 + x.fail = True + self.assertRaises(Exc, d.__getitem__, x) + + def test_fromkeys(self): + TestMappingProtocol.test_fromkeys(self) + class mydict(self.type2test): + def __new__(cls): + return UserDict() + ud = mydict.fromkeys('ab') + self.assertEqual(ud, {'a':None, 'b':None}) + self.assert_(isinstance(ud, UserDict)) + + def test_pop(self): + TestMappingProtocol.test_pop(self) + + class Exc(Exception): pass + + class BadHash(object): + fail = False + def __hash__(self): + if self.fail: + raise Exc() + else: + return 42 + + d = self._empty_mapping() + x = BadHash() + d[x] = 42 + x.fail = True + self.assertRaises(Exc, d.pop, x) + + def test_mutatingiteration(self): # pragma: no cover + d = self._empty_mapping() + d[1] = 1 + try: + for i in d: + d[i+1] = 1 + except RuntimeError: + pass + else: + self.fail("changing dict size during iteration doesn't raise Error") + + def test_repr(self): # pragma: no cover + d = self._empty_mapping() + self.assertEqual(repr(d), '{}') + d[1] = 2 + self.assertEqual(repr(d), '{1: 2}') + d = self._empty_mapping() + d[1] = d + self.assertEqual(repr(d), '{1: {...}}') + + class Exc(Exception): pass + + class BadRepr(object): + def __repr__(self): + raise Exc() + + d = self._full_mapping({1: BadRepr()}) + self.assertRaises(Exc, repr, d) + + def test_eq(self): + self.assertEqual(self._empty_mapping(), self._empty_mapping()) + self.assertEqual(self._full_mapping({1: 2}), + self._full_mapping({1: 2})) + + class Exc(Exception): pass + + class BadCmp(object): + def __eq__(self, other): + raise Exc() + def __hash__(self): + return 1 + + d1 = self._full_mapping({BadCmp(): 1}) + d2 = self._full_mapping({1: 1}) + self.assertRaises(Exc, lambda: BadCmp()==1) + #self.assertRaises(Exc, lambda: d1==d2) + + def test_setdefault(self): + TestMappingProtocol.test_setdefault(self) + + class Exc(Exception): pass + + class BadHash(object): + fail = False + def __hash__(self): + if self.fail: + raise Exc() + else: + return 42 + + d = self._empty_mapping() + x = BadHash() + d[x] = 42 + x.fail = True + self.assertRaises(Exc, d.setdefault, x, []) diff --git a/blist/test/seq_tests.py b/blist/test/seq_tests.py new file mode 100644 index 0000000..ba2b619 --- /dev/null +++ b/blist/test/seq_tests.py @@ -0,0 +1,332 @@ +# This file taken from Python, licensed under the Python License Agreement + +from __future__ import print_function +""" +Tests common to tuple, list and UserList.UserList +""" + +from blist.test import test_support +from blist.test import unittest +import sys + +# Various iterables +# This is used for checking the constructor (here and in test_deque.py) +def iterfunc(seqn): + 'Regular generator' + for i in seqn: + yield i + +class Sequence: + 'Sequence using __getitem__' + def __init__(self, seqn): + self.seqn = seqn + def __getitem__(self, i): + return self.seqn[i] + +class IterFunc: + 'Sequence using iterator protocol' + def __init__(self, seqn): + self.seqn = seqn + self.i = 0 + def __iter__(self): + return self + def __next__(self): + if self.i >= len(self.seqn): raise StopIteration + v = self.seqn[self.i] + self.i += 1 + return v + next = __next__ + +class IterGen: + 'Sequence using iterator protocol defined with a generator' + def __init__(self, seqn): + self.seqn = seqn + self.i = 0 + def __iter__(self): + for val in self.seqn: + yield val + +class IterNextOnly: + 'Missing __getitem__ and __iter__' + def __init__(self, seqn): + self.seqn = seqn + self.i = 0 + def __next__(self): # pragma: no cover + if self.i >= len(self.seqn): raise StopIteration + v = self.seqn[self.i] + self.i += 1 + return v + next = __next__ + +class IterNoNext: + 'Iterator missing next()' + def __init__(self, seqn): + self.seqn = seqn + self.i = 0 + def __iter__(self): + return self + +class IterGenExc: + 'Test propagation of exceptions' + def __init__(self, seqn): + self.seqn = seqn + self.i = 0 + def __iter__(self): + return self + def __next__(self): + 3 // 0 + next = __next__ + +class IterFuncStop: + 'Test immediate stop' + def __init__(self, seqn): + pass + def __iter__(self): + return self + def __next__(self): + raise StopIteration + next = __next__ + +from itertools import chain +def itermulti(seqn): + 'Test multiple tiers of iterators' + return chain(map(lambda x:x, iterfunc(IterGen(Sequence(seqn))))) + +class CommonTest(unittest.TestCase): + # The type to be tested + type2test = None + + def test_constructors(self): + l0 = [] + l1 = [0] + l2 = [0, 1] + + u = self.type2test() + u0 = self.type2test(l0) + u1 = self.type2test(l1) + u2 = self.type2test(l2) + + uu = self.type2test(u) + uu0 = self.type2test(u0) + uu1 = self.type2test(u1) + uu2 = self.type2test(u2) + + v = self.type2test(tuple(u)) + class OtherSeq: + def __init__(self, initseq): + self.__data = initseq + def __len__(self): + return len(self.__data) + def __getitem__(self, i): + return self.__data[i] + s = OtherSeq(u0) + v0 = self.type2test(s) + self.assertEqual(len(v0), len(s)) + + s = "this is also a sequence" + vv = self.type2test(s) + self.assertEqual(len(vv), len(s)) + + # Create from various iteratables + for s in ("123", "", list(range(1000)), ('do', 1.2), range(2000,2200,5)): + for g in (Sequence, IterFunc, IterGen, + itermulti, iterfunc): + self.assertEqual(self.type2test(g(s)), self.type2test(s)) + self.assertEqual(self.type2test(IterFuncStop(s)), self.type2test()) + self.assertEqual(self.type2test(c for c in "123"), self.type2test("123")) + self.assertRaises(TypeError, self.type2test, IterNextOnly(s)) + self.assertRaises(TypeError, self.type2test, IterNoNext(s)) + self.assertRaises(ZeroDivisionError, self.type2test, IterGenExc(s)) + + def test_truth(self): + self.assert_(not self.type2test()) + self.assert_(self.type2test([42])) + + def test_getitem(self): + u = self.type2test([0, 1, 2, 3, 4]) + for i in range(len(u)): + self.assertEqual(u[i], i) + self.assertEqual(u[int(i)], i) + for i in range(-len(u), -1): + self.assertEqual(u[i], len(u)+i) + self.assertEqual(u[int(i)], len(u)+i) + self.assertRaises(IndexError, u.__getitem__, -len(u)-1) + self.assertRaises(IndexError, u.__getitem__, len(u)) + self.assertRaises(ValueError, u.__getitem__, slice(0,10,0)) + + u = self.type2test() + self.assertRaises(IndexError, u.__getitem__, 0) + self.assertRaises(IndexError, u.__getitem__, -1) + + self.assertRaises(TypeError, u.__getitem__) + + a = self.type2test([10, 11]) + self.assertEqual(a[0], 10) + self.assertEqual(a[1], 11) + self.assertEqual(a[-2], 10) + self.assertEqual(a[-1], 11) + self.assertRaises(IndexError, a.__getitem__, -3) + self.assertRaises(IndexError, a.__getitem__, 3) + + def test_getslice(self): + l = [0, 1, 2, 3, 4] + u = self.type2test(l) + + self.assertEqual(u[0:0], self.type2test()) + self.assertEqual(u[1:2], self.type2test([1])) + self.assertEqual(u[-2:-1], self.type2test([3])) + self.assertEqual(u[-1000:1000], u) + self.assertEqual(u[1000:-1000], self.type2test([])) + self.assertEqual(u[:], u) + self.assertEqual(u[1:None], self.type2test([1, 2, 3, 4])) + self.assertEqual(u[None:3], self.type2test([0, 1, 2])) + + # Extended slices + self.assertEqual(u[::], u) + self.assertEqual(u[::2], self.type2test([0, 2, 4])) + self.assertEqual(u[1::2], self.type2test([1, 3])) + self.assertEqual(u[::-1], self.type2test([4, 3, 2, 1, 0])) + self.assertEqual(u[::-2], self.type2test([4, 2, 0])) + self.assertEqual(u[3::-2], self.type2test([3, 1])) + self.assertEqual(u[3:3:-2], self.type2test([])) + self.assertEqual(u[3:2:-2], self.type2test([3])) + self.assertEqual(u[3:1:-2], self.type2test([3])) + self.assertEqual(u[3:0:-2], self.type2test([3, 1])) + self.assertEqual(u[::-100], self.type2test([4])) + self.assertEqual(u[100:-100:], self.type2test([])) + self.assertEqual(u[-100:100:], u) + self.assertEqual(u[100:-100:-1], u[::-1]) + self.assertEqual(u[-100:100:-1], self.type2test([])) + self.assertEqual(u[-100:100:2], self.type2test([0, 2, 4])) + + # Test extreme cases with long ints + a = self.type2test([0,1,2,3,4]) + self.assertEqual(a[ -pow(2,128): 3 ], self.type2test([0,1,2])) + self.assertEqual(a[ 3: pow(2,145) ], self.type2test([3,4])) + + if sys.version_info[0] < 3: + self.assertRaises(TypeError, u.__getslice__) + + def test_contains(self): + u = self.type2test([0, 1, 2]) + for i in u: + self.assert_(i in u) + for i in min(u)-1, max(u)+1: + self.assert_(i not in u) + + self.assertRaises(TypeError, u.__contains__) + + def test_contains_fake(self): + class AllEq: + # Sequences must use rich comparison against each item + # (unless "is" is true, or an earlier item answered) + # So instances of AllEq must be found in all non-empty sequences. + def __eq__(self, other): + return True + def __hash__(self): # pragma: no cover + raise NotImplemented + self.assert_(AllEq() not in self.type2test([])) + self.assert_(AllEq() in self.type2test([1])) + + def test_contains_order(self): + # Sequences must test in-order. If a rich comparison has side + # effects, these will be visible to tests against later members. + # In this test, the "side effect" is a short-circuiting raise. + class DoNotTestEq(Exception): + pass + class StopCompares: + def __eq__(self, other): + raise DoNotTestEq + + checkfirst = self.type2test([1, StopCompares()]) + self.assert_(1 in checkfirst) + checklast = self.type2test([StopCompares(), 1]) + self.assertRaises(DoNotTestEq, checklast.__contains__, 1) + + def test_len(self): + self.assertEqual(len(self.type2test()), 0) + self.assertEqual(len(self.type2test([])), 0) + self.assertEqual(len(self.type2test([0])), 1) + self.assertEqual(len(self.type2test([0, 1, 2])), 3) + + def test_minmax(self): + u = self.type2test([0, 1, 2]) + self.assertEqual(min(u), 0) + self.assertEqual(max(u), 2) + + def test_addmul(self): + u1 = self.type2test([0]) + u2 = self.type2test([0, 1]) + self.assertEqual(u1, u1 + self.type2test()) + self.assertEqual(u1, self.type2test() + u1) + self.assertEqual(u1 + self.type2test([1]), u2) + self.assertEqual(self.type2test([-1]) + u1, self.type2test([-1, 0])) + self.assertEqual(self.type2test(), u2*0) + self.assertEqual(self.type2test(), 0*u2) + self.assertEqual(self.type2test(), u2*0) + self.assertEqual(self.type2test(), 0*u2) + self.assertEqual(u2, u2*1) + self.assertEqual(u2, 1*u2) + self.assertEqual(u2, u2*1) + self.assertEqual(u2, 1*u2) + self.assertEqual(u2+u2, u2*2) + self.assertEqual(u2+u2, 2*u2) + self.assertEqual(u2+u2, u2*2) + self.assertEqual(u2+u2, 2*u2) + self.assertEqual(u2+u2+u2, u2*3) + self.assertEqual(u2+u2+u2, 3*u2) + + class subclass(self.type2test): + pass + u3 = subclass([0, 1]) + self.assertEqual(u3, u3*1) + self.assert_(u3 is not u3*1) + + def test_iadd(self): + u = self.type2test([0, 1]) + u += self.type2test() + self.assertEqual(u, self.type2test([0, 1])) + u += self.type2test([2, 3]) + self.assertEqual(u, self.type2test([0, 1, 2, 3])) + u += self.type2test([4, 5]) + self.assertEqual(u, self.type2test([0, 1, 2, 3, 4, 5])) + + u = self.type2test("spam") + u += self.type2test("eggs") + self.assertEqual(u, self.type2test("spameggs")) + + def test_imul(self): + u = self.type2test([0, 1]) + u *= 3 + self.assertEqual(u, self.type2test([0, 1, 0, 1, 0, 1])) + + def test_getitemoverwriteiter(self): + # Verify that __getitem__ overrides are not recognized by __iter__ + class T(self.type2test): + def __getitem__(self, key): # pragma: no cover + return str(key) + '!!!' + self.assertEqual(next(iter(T((1,2)))), 1) + + def test_repeat(self): + for m in range(4): + s = tuple(range(m)) + for n in range(-3, 5): + self.assertEqual(self.type2test(s*n), self.type2test(s)*n) + self.assertEqual(self.type2test(s)*(-4), self.type2test([])) + self.assertEqual(id(s), id(s*1)) + + def test_subscript(self): + a = self.type2test([10, 11]) + self.assertEqual(a.__getitem__(0), 10) + self.assertEqual(a.__getitem__(1), 11) + self.assertEqual(a.__getitem__(-2), 10) + self.assertEqual(a.__getitem__(-1), 11) + self.assertRaises(IndexError, a.__getitem__, -3) + self.assertRaises(IndexError, a.__getitem__, 3) + self.assertEqual(a.__getitem__(slice(0,1)), self.type2test([10])) + self.assertEqual(a.__getitem__(slice(1,2)), self.type2test([11])) + self.assertEqual(a.__getitem__(slice(0,2)), self.type2test([10, 11])) + self.assertEqual(a.__getitem__(slice(0,3)), self.type2test([10, 11])) + self.assertEqual(a.__getitem__(slice(3,5)), self.type2test([])) + self.assertRaises(ValueError, a.__getitem__, slice(0, 10, 0)) + self.assertRaises(TypeError, a.__getitem__, 'x') diff --git a/blist/test/sorteddict_tests.py b/blist/test/sorteddict_tests.py new file mode 100644 index 0000000..b92033f --- /dev/null +++ b/blist/test/sorteddict_tests.py @@ -0,0 +1,160 @@ +import bisect +import sys +import blist +from blist.test import mapping_tests + +def CmpToKey(mycmp): + 'Convert a cmp= function into a key= function' + class K(object): + def __init__(self, obj): + self.obj = obj + def __lt__(self, other): + return mycmp(self.obj, other.obj) == -1 + return K + +class sorteddict_test(mapping_tests.TestHashMappingProtocol): + type2test = blist.sorteddict + + def _reference(self): + """Return a dictionary of values which are invariant by storage + in the object under test.""" + return {1:2, 3:4, 5:6} + + class Collider(object): + def __init__(self, x): + self.x = x + def __repr__(self): + return 'Collider(%r)' % self.x + def __eq__(self, other): + return self.__class__ == other.__class__ and self.x == other.x + def __lt__(self, other): + if type(self) != type(other): + return NotImplemented + return self.x < other.x + def __hash__(self): + return 42 + + def test_repr(self): + d = self._empty_mapping() + self.assertEqual(repr(d), 'sorteddict({})') + d[1] = 2 + self.assertEqual(repr(d), 'sorteddict({1: 2})') + d = self._empty_mapping() + d[1] = d + self.assertEqual(repr(d), 'sorteddict({1: sorteddict({...})})') + d = self._empty_mapping() + d[self.Collider(1)] = 1 + d[self.Collider(2)] = 2 + self.assertEqual(repr(d), + 'sorteddict({Collider(1): 1, Collider(2): 2})') + d = self._empty_mapping() + d[self.Collider(2)] = 2 + d[self.Collider(1)] = 1 + self.assertEqual(repr(d), + 'sorteddict({Collider(1): 1, Collider(2): 2})') + + class Exc(Exception): pass + + class BadRepr(object): + def __repr__(self): + raise Exc() + + d = self._full_mapping({1: BadRepr()}) + self.assertRaises(Exc, repr, d) + + def test_mutatingiteration(self): + pass + + def test_sort(self): + u = self.type2test.fromkeys([1, 0]) + self.assertEqual(list(u.keys()), [0, 1]) + + u = self.type2test.fromkeys([2,1,0,-1,-2]) + self.assertEqual(u, self.type2test.fromkeys([-2,-1,0,1,2])) + self.assertEqual(list(u.keys()), [-2,-1,0,1,2]) + + a = self.type2test.fromkeys(reversed(list(range(512)))) + self.assertEqual(list(a.keys()), list(range(512))) + + def revcmp(a, b): # pragma: no cover + if a == b: + return 0 + elif a < b: + return 1 + else: # a > b + return -1 + u = self.type2test.fromkeys([2,1,0,-1,-2], key=CmpToKey(revcmp)) + self.assertEqual(list(u.keys()), [2,1,0,-1,-2]) + + # The following dumps core in unpatched Python 1.5: + def myComparison(x,y): + xmod, ymod = x%3, y%7 + if xmod == ymod: + return 0 + elif xmod < ymod: + return -1 + else: # xmod > ymod + return 1 + + self.type2test.fromkeys(list(range(12)), key=CmpToKey(myComparison)) + + #def selfmodifyingComparison(x,y): + # z[x+y] = None + # return cmp(x, y) + #z = self.type2test(CmpToKey(selfmodifyingComparison)) + #self.assertRaises(ValueError, z.update, [(i,i) for i in range(12)]) + + self.assertRaises(TypeError, self.type2test.fromkeys, 42, 42, 42, 42) + + def test_view_indexing_without_key(self): + self._test_view_indexing(key=None) + + def test_view_indexing_with_key(self): + self._test_view_indexing(key=lambda x: -x) + + def _test_view_indexing(self, key): + expected_items = [(3, "first"), (7, "second")] + if key is not None: + u = self.type2test(key, expected_items) + expected_items.sort(key=lambda item: key(item[0])) + else: + u = self.type2test(expected_items) + expected_items.sort() + expected_keys, expected_values = list(zip(*expected_items)) + + if sys.version_info[0] < 3: + keys = u.viewkeys() + values = u.viewvalues() + items = u.viewitems() + else: + keys = u.keys() + values = u.values() + items = u.items() + + for i in range(len(expected_items)): + self.assertEqual(keys[i], expected_keys[i]) + self.assertEqual(values[i], expected_values[i]) + self.assertEqual(items[i], expected_items[i]) + + for i in range(-1, len(expected_items)+1): + for j in range(-1, len(expected_items)+1): + self.assertEqual(keys[i:j], blist.sortedset(expected_keys[i:j])) + self.assertEqual(values[i:j], list(expected_values[i:j])) + self.assertEqual(items[i:j], blist.sortedset(expected_items[i:j])) + + self.assertEqual(list(reversed(keys)), list(reversed(expected_keys))) + for i, key in enumerate(expected_keys): + self.assertEqual(keys.index(key), expected_keys.index(key)) + self.assertEqual(keys.count(key), 1) + self.assertEqual(keys.bisect_left(key), i) + self.assertEqual(keys.bisect_right(key), i+1) + self.assertEqual(keys.count(object()), 0) + self.assertRaises(ValueError, keys.index, object()) + + for item in expected_items: + self.assertEqual(items.index(item), expected_items.index(item)) + self.assertEqual(items.count(item), 1) + self.assertEqual(items.count((7, "foo")), 0) + self.assertEqual(items.count((object(), object())), 0) + self.assertRaises(ValueError, items.index, (7, "foo")) + self.assertRaises(ValueError, items.index, (object(), object())) diff --git a/blist/test/sortedlist_tests.py b/blist/test/sortedlist_tests.py new file mode 100644 index 0000000..1cff8b9 --- /dev/null +++ b/blist/test/sortedlist_tests.py @@ -0,0 +1,615 @@ +# This file based loosely on Python's list_tests.py. + +import sys +import collections, operator +import gc +import random +import blist +from blist.test import unittest +from blist.test import list_tests, seq_tests + +def CmpToKey(mycmp): + 'Convert a cmp= function into a key= function' + class K(object): + def __init__(self, obj): + self.obj = obj + def __lt__(self, other): + return mycmp(self.obj, other.obj) == -1 + return K + +class SortedBase(object): + def build_items(self, n): + return list(range(n)) + + def build_item(self, x): + return x + + def test_empty_repr(self): + self.assertEqual('%s()' % self.type2test.__name__, + repr(self.type2test())) + + def validate_comparison(self, instance): + if sys.version_info[0] < 3 and isinstance(instance, collections.Set): + ops = ['ne', 'or', 'and', 'xor', 'sub'] + else: + ops = ['lt', 'gt', 'le', 'ge', 'ne', 'or', 'and', 'xor', 'sub'] + operators = {} + for op in ops: + name = '__'+op+'__' + operators['__'+op+'__'] = getattr(operator, name) + + class Other(object): + def __init__(self): + self.right_side = False + def __eq__(self, other): + self.right_side = True + return True + __lt__ = __eq__ + __gt__ = __eq__ + __le__ = __eq__ + __ge__ = __eq__ + __ne__ = __eq__ + __ror__ = __eq__ + __rand__ = __eq__ + __rxor__ = __eq__ + __rsub__ = __eq__ + + for name, op in operators.items(): + if not hasattr(instance, name): continue + other = Other() + op(instance, other) + self.assertTrue(other.right_side,'Right side not called for %s.%s' + % (type(instance), name)) + + def test_right_side(self): + self.validate_comparison(self.type2test()) + + def test_delitem(self): + items = self.build_items(2) + a = self.type2test(items) + del a[1] + self.assertEqual(a, self.type2test(items[:1])) + del a[0] + self.assertEqual(a, self.type2test([])) + + a = self.type2test(items) + del a[-2] + self.assertEqual(a, self.type2test(items[1:])) + del a[-1] + self.assertEqual(a, self.type2test([])) + + a = self.type2test(items) + self.assertRaises(IndexError, a.__delitem__, -3) + self.assertRaises(IndexError, a.__delitem__, 2) + + a = self.type2test([]) + self.assertRaises(IndexError, a.__delitem__, 0) + + self.assertRaises(TypeError, a.__delitem__) + + def test_delslice(self): + items = self.build_items(2) + a = self.type2test(items) + del a[1:2] + del a[0:1] + self.assertEqual(a, self.type2test([])) + + a = self.type2test(items) + del a[1:2] + del a[0:1] + self.assertEqual(a, self.type2test([])) + + a = self.type2test(items) + del a[-2:-1] + self.assertEqual(a, self.type2test(items[1:])) + + a = self.type2test(items) + del a[-2:-1] + self.assertEqual(a, self.type2test(items[1:])) + + a = self.type2test(items) + del a[1:] + del a[:1] + self.assertEqual(a, self.type2test([])) + + a = self.type2test(items) + del a[1:] + del a[:1] + self.assertEqual(a, self.type2test([])) + + a = self.type2test(items) + del a[-1:] + self.assertEqual(a, self.type2test(items[:1])) + + a = self.type2test(items) + del a[-1:] + self.assertEqual(a, self.type2test(items[:1])) + + a = self.type2test(items) + del a[:] + self.assertEqual(a, self.type2test([])) + + def test_out_of_range(self): + u = self.type2test() + def del_test(): + del u[0] + self.assertRaises(IndexError, lambda: u[0]) + self.assertRaises(IndexError, del_test) + + def test_bad_mul(self): + u = self.type2test() + self.assertRaises(TypeError, lambda: u * 'q') + def imul_test(): + u = self.type2test() + u *= 'q' + self.assertRaises(TypeError, imul_test) + + def test_pop(self): + lst = self.build_items(20) + random.shuffle(lst) + u = self.type2test(lst) + for i in range(20-1,-1,-1): + x = u.pop(i) + self.assertEqual(x, i) + self.assertEqual(0, len(u)) + + def test_reversed(self): + lst = list(range(20)) + a = self.type2test(lst) + r = reversed(a) + self.assertEqual(list(r), list(range(19, -1, -1))) + if hasattr(r, '__next__'): # pragma: no cover + self.assertRaises(StopIteration, r.__next__) + else: # pragma: no cover + self.assertRaises(StopIteration, r.next) + self.assertEqual(self.type2test(reversed(self.type2test())), + self.type2test()) + + def test_mismatched_types(self): + class NotComparable: + def __lt__(self, other): # pragma: no cover + raise TypeError + def __cmp__(self, other): # pragma: no cover + raise TypeError + NotComparable = NotComparable() + + item = self.build_item(5) + sl = self.type2test() + sl.add(item) + self.assertRaises(TypeError, sl.add, NotComparable) + self.assertFalse(NotComparable in sl) + self.assertEqual(sl.count(NotComparable), 0) + sl.discard(NotComparable) + self.assertRaises(ValueError, sl.index, NotComparable) + + def test_order(self): + stuff = [self.build_item(random.randrange(1000000)) + for i in range(1000)] + if issubclass(self.type2test, collections.Set): + stuff = set(stuff) + sorted_stuff = list(sorted(stuff)) + u = self.type2test + + self.assertEqual(sorted_stuff, list(u(stuff))) + sl = u() + for x in stuff: + sl.add(x) + self.assertEqual(sorted_stuff, list(sl)) + x = sorted_stuff.pop(len(stuff)//2) + sl.discard(x) + self.assertEqual(sorted_stuff, list(sl)) + + def test_constructors(self): + # Based on the seq_test, but without adding incomparable types + # to the list. + + l0 = self.build_items(0) + l1 = self.build_items(1) + l2 = self.build_items(2) + + u = self.type2test() + u0 = self.type2test(l0) + u1 = self.type2test(l1) + u2 = self.type2test(l2) + + uu = self.type2test(u) + uu0 = self.type2test(u0) + uu1 = self.type2test(u1) + uu2 = self.type2test(u2) + + v = self.type2test(tuple(u)) + class OtherSeq: + def __init__(self, initseq): + self.__data = initseq + def __len__(self): + return len(self.__data) + def __getitem__(self, i): + return self.__data[i] + s = OtherSeq(u0) + v0 = self.type2test(s) + self.assertEqual(len(v0), len(s)) + + def test_sort(self): + # based on list_tests.py + lst = [1, 0] + lst = [self.build_item(x) for x in lst] + u = self.type2test(lst) + self.assertEqual(list(u), [0, 1]) + + lst = [2,1,0,-1,-2] + lst = [self.build_item(x) for x in lst] + u = self.type2test(lst) + self.assertEqual(list(u), [-2,-1,0,1,2]) + + lst = list(range(512)) + lst = [self.build_item(x) for x in lst] + a = self.type2test(reversed(lst)) + self.assertEqual(list(a), lst) + + def revcmp(a, b): # pragma: no cover + if a == b: + return 0 + elif a < b: + return 1 + else: # a > b + return -1 + u = self.type2test(u, key=CmpToKey(revcmp)) + self.assertEqual(list(u), [2,1,0,-1,-2]) + + # The following dumps core in unpatched Python 1.5: + def myComparison(x,y): + xmod, ymod = x%3, y%7 + if xmod == ymod: + return 0 + elif xmod < ymod: + return -1 + else: # xmod > ymod + return 1 + z = self.type2test(list(range(12)), key=CmpToKey(myComparison)) + + self.assertRaises(TypeError, self.type2test, 42, 42, 42, 42) + +class StrongSortedBase(SortedBase, seq_tests.CommonTest): + def not_applicable(self): + pass + test_repeat = not_applicable + test_imul = not_applicable + test_addmul = not_applicable + test_iadd = not_applicable + test_getslice = not_applicable + test_contains_order = not_applicable + test_contains_fake = not_applicable + + def test_constructors2(self): + s = "a seq" + vv = self.type2test(s) + self.assertEqual(len(vv), len(s)) + + # Create from various iteratables + for s in ("123", "", list(range(1000)), (1.5, 1.2), range(2000,2200,5)): + for g in (seq_tests.Sequence, seq_tests.IterFunc, + seq_tests.IterGen, seq_tests.itermulti, + seq_tests.iterfunc): + self.assertEqual(self.type2test(g(s)), self.type2test(s)) + self.assertEqual(self.type2test(seq_tests.IterFuncStop(s)), + self.type2test()) + self.assertEqual(self.type2test(c for c in "123"), + self.type2test("123")) + self.assertRaises(TypeError, self.type2test, + seq_tests.IterNextOnly(s)) + self.assertRaises(TypeError, self.type2test, + seq_tests.IterNoNext(s)) + self.assertRaises(ZeroDivisionError, self.type2test, + seq_tests.IterGenExc(s)) + +class weak_int: + def __init__(self, v): + self.value = v + def unwrap(self, other): + if isinstance(other, weak_int): + return other.value + return other + def __hash__(self): + return hash(self.value) + def __repr__(self): # pragma: no cover + return repr(self.value) + def __lt__(self, other): + return self.value < self.unwrap(other) + def __le__(self, other): + return self.value <= self.unwrap(other) + def __gt__(self, other): + return self.value > self.unwrap(other) + def __ge__(self, other): + return self.value >= self.unwrap(other) + def __eq__(self, other): + return self.value == self.unwrap(other) + def __ne__(self, other): + return self.value != self.unwrap(other) + def __mod__(self, other): + return self.value % self.unwrap(other) + def __neg__(self): + return weak_int(-self.value) + +class weak_manager(): + def __init__(self): + self.all = [weak_int(i) for i in range(10)] + self.live = [v for v in self.all if random.randrange(2)] + random.shuffle(self.all) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + del self.all + gc.collect() + +class WeakSortedBase(SortedBase, unittest.TestCase): + def build_items(self, n): + return [weak_int(i) for i in range(n)] + + def build_item(self, x): + return weak_int(x) + + def test_collapse(self): + items = self.build_items(10) + u = self.type2test(items) + del items + gc.collect() + self.assertEqual(list(u), []) + + def test_sort(self): + # based on list_tests.py + x = [weak_int(i) for i in [1, 0]] + u = self.type2test(x) + self.assertEqual(list(u), list(reversed(x))) + + x = [weak_int(i) for i in [2,1,0,-1,-2]] + u = self.type2test(x) + self.assertEqual(list(u), list(reversed(x))) + + #y = [weak_int(i) for i in reversed(list(range(512)))] + #a = self.type2test(y) + #self.assertEqual(list(a), list(reversed(y))) + + def revcmp(a, b): # pragma: no cover + if a == b: + return 0 + elif a < b: + return 1 + else: # a > b + return -1 + u = self.type2test(u, key=CmpToKey(revcmp)) + self.assertEqual(list(u), x) + + # The following dumps core in unpatched Python 1.5: + def myComparison(x,y): + xmod, ymod = x%3, y%7 + if xmod == ymod: + return 0 + elif xmod < ymod: + return -1 + else: # xmod > ymod + return 1 + x = [weak_int(i) for i in range(12)] + z = self.type2test(x, key=CmpToKey(myComparison)) + + self.assertRaises(TypeError, self.type2test, 42, 42, 42, 42) + + def test_constructor(self): + with weak_manager() as m: + wsl = self.type2test(m.all) + self.assertEqual(list(wsl), m.live) + + def test_add(self): + with weak_manager() as m: + wsl = self.type2test() + for x in m.all: + wsl.add(x) + del x + self.assertEqual(list(wsl), m.live) + + def test_discard(self): + with weak_manager() as m: + wsl = self.type2test(m.all) + x = m.live.pop(len(m.live)//2) + wsl.discard(x) + self.assertEqual(list(wsl), m.live) + + def test_contains(self): + with weak_manager() as m: + wsl = self.type2test(m.all) + for x in m.live: + self.assertTrue(x in wsl) + self.assertFalse(weak_int(-1) in wsl) + + def test_iter(self): + with weak_manager() as m: + wsl = self.type2test(m.all) + for i, x in enumerate(wsl): + self.assertEqual(x, m.live[i]) + + def test_getitem(self): + with weak_manager() as m: + wsl = self.type2test(m.all) + for i in range(len(m.live)): + self.assertEqual(wsl[i], m.live[i]) + + def test_reversed(self): + with weak_manager() as m: + wsl = self.type2test(m.all) + r1 = list(reversed(wsl)) + r2 = list(reversed(m.live)) + self.assertEqual(r1, r2) + + all = [weak_int(i) for i in range(6)] + wsl = self.type2test(all) + del all[-1] + self.assertEqual(list(reversed(wsl)), list(reversed(all))) + + def test_index(self): + with weak_manager() as m: + wsl = self.type2test(m.all) + for x in m.live: + self.assertEqual(wsl[wsl.index(x)], x) + self.assertRaises(ValueError, wsl.index, weak_int(-1)) + + def test_count(self): + with weak_manager() as m: + wsl = self.type2test(m.all) + for x in m.live: + self.assertEqual(wsl.count(x), 1) + self.assertEqual(wsl.count(weak_int(-1)), 0) + + def test_getslice(self): + with weak_manager() as m: + wsl = self.type2test(m.all) + self.assertEqual(m.live, list(wsl[:])) + +class SortedListMixin: + def test_eq(self): + items = self.build_items(20) + u = self.type2test(items) + v = self.type2test(items, key=lambda x: -x) + self.assertNotEqual(u, v) + + def test_cmp(self): + items = self.build_items(20) + u = self.type2test(items) + low = u[:10] + high = u[10:] + self.assert_(low != high) + self.assert_(low == u[:10]) + self.assert_(low < high) + self.assert_(low <= high, str((low, high))) + self.assert_(high > low) + self.assert_(high >= low) + self.assertFalse(low == high) + self.assertFalse(high < low) + self.assertFalse(high <= low) + self.assertFalse(low > high) + self.assertFalse(low >= high) + + low = u[:5] + self.assert_(low != high) + self.assertFalse(low == high) + + def test_update(self): + items = self.build_items(20) + u = self.type2test() + u.update(items) + self.assertEqual(u, self.type2test(items)) + + def test_remove(self): + items = self.build_items(20) + u = self.type2test(items) + u.remove(items[-1]) + self.assertEqual(u, self.type2test(items[:19])) + self.assertRaises(ValueError, u.remove, items[-1]) + + def test_mul(self): + items = self.build_items(2) + u1 = self.type2test(items[:1]) + u2 = self.type2test(items) + self.assertEqual(self.type2test(), u2*0) + self.assertEqual(self.type2test(), 0*u2) + self.assertEqual(self.type2test(), u2*0) + self.assertEqual(self.type2test(), 0*u2) + self.assertEqual(u2, u2*1) + self.assertEqual(u2, 1*u2) + self.assertEqual(u2, u2*1) + self.assertEqual(u2, 1*u2) + self.assertEqual(self.type2test(items + items), u2*2) + self.assertEqual(self.type2test(items + items), 2*u2) + self.assertEqual(self.type2test(items + items + items), 3*u2) + self.assertEqual(self.type2test(items + items + items), u2*3) + + class subclass(self.type2test): + pass + u3 = subclass(items) + self.assertEqual(u3, u3*1) + self.assert_(u3 is not u3*1) + + def test_imul(self): + items = self.build_items(2) + items6 = items[:1]*3 + items[1:]*3 + u = self.type2test(items) + u *= 3 + self.assertEqual(u, self.type2test(items6)) + u *= 0 + self.assertEqual(u, self.type2test([])) + s = self.type2test([]) + oldid = id(s) + s *= 10 + self.assertEqual(id(s), oldid) + + def test_repr(self): + name = self.type2test.__name__ + u = self.type2test() + self.assertEqual(repr(u), '%s()' % name) + items = self.build_items(3) + u.update(items) + self.assertEqual(repr(u), '%s([0, 1, 2])' % name) + u = self.type2test() + u.update([u]) + self.assertEqual(repr(u), '%s([%s(...)])' % (name, name)) + + def test_bisect(self): + items = self.build_items(5) + del items[0] + del items[2] # We end up with [1, 2, 4] + u = self.type2test(items, key=lambda x: -x) # We end up with [4, 2, 1] + self.assertEqual(u.bisect_left(3), 1) + self.assertEqual(u.bisect(2), 2) # bisect == bisect_right + self.assertEqual(u.bisect_right(2), 2) + +class SortedSetMixin: + def test_duplicates(self): + u = self.type2test + ss = u() + stuff = [weak_int(random.randrange(100000)) for i in range(10)] + sorted_stuff = list(sorted(stuff)) + for x in stuff: + ss.add(x) + for x in stuff: + ss.add(x) + self.assertEqual(sorted_stuff, list(ss)) + x = sorted_stuff.pop(len(stuff)//2) + ss.discard(x) + self.assertEqual(sorted_stuff, list(ss)) + + def test_eq(self): + items = self.build_items(20) + u = self.type2test(items) + v = self.type2test(items, key=lambda x: -x) + self.assertEqual(u, v) + + def test_remove(self): + items = self.build_items(20) + u = self.type2test(items) + u.remove(items[-1]) + self.assertEqual(u, self.type2test(items[:19])) + self.assertRaises(KeyError, u.remove, items[-1]) + +class SortedListTest(StrongSortedBase, SortedListMixin): + type2test = blist.sortedlist + +class WeakSortedListTest(WeakSortedBase, SortedListMixin): + type2test = blist.weaksortedlist + + def test_advance(self): + items = [weak_int(0), weak_int(0)] + u = self.type2test(items) + del items[0] + gc.collect() + self.assertEqual(u.count(items[0]), 1) + +class SortedSetTest(StrongSortedBase, SortedSetMixin): + type2test = blist.sortedset + +class WeakSortedSetTest(WeakSortedBase, SortedSetMixin): + type2test = blist.weaksortedset + + def test_repr(self): + items = self.build_items(20) + u = self.type2test(items) + self.assertEqual(repr(u), 'weaksortedset(%s)' % repr(items)) diff --git a/blist/test/test_list.py b/blist/test/test_list.py new file mode 100644 index 0000000..060bf79 --- /dev/null +++ b/blist/test/test_list.py @@ -0,0 +1,39 @@ +from __future__ import print_function +from blist.test import unittest +from blist.test import test_support, list_tests + +class ListTest(list_tests.CommonTest): + type2test = list + + def test_truth(self): + super(ListTest, self).test_truth() + self.assert_(not []) + self.assert_([42]) + + def test_identity(self): + self.assert_([] is not []) + + def test_len(self): + super(ListTest, self).test_len() + self.assertEqual(len([]), 0) + self.assertEqual(len([0]), 1) + self.assertEqual(len([0, 1, 2]), 3) + +def test_main(verbose=None): + test_support.run_unittest(ListTest) + + # verify reference counting + import sys + if verbose: + import gc + counts = [None] * 5 + for i in range(len(counts)): + test_support.run_unittest(ListTest) + gc.set_debug(gc.DEBUG_STATS) + gc.collect() + #counts[i] = sys.gettotalrefcount() + print(counts) + + +if __name__ == "__main__": + test_main(verbose=False) diff --git a/blist/test/test_set.py b/blist/test/test_set.py new file mode 100644 index 0000000..da78daf --- /dev/null +++ b/blist/test/test_set.py @@ -0,0 +1,1537 @@ +# This file taken from Python, licensed under the Python License Agreement + +from __future__ import print_function + +import gc +import weakref +import operator +import copy +import pickle +from random import randrange, shuffle +import sys +import warnings +import collections + +from blist.test import unittest +from blist.test import test_support as support + +from blist import sortedset as set + +class PassThru(Exception): + pass + +def check_pass_thru(): + raise PassThru + yield 1 # pragma: no cover + +class BadCmp: # pragma: no cover + def __hash__(self): + return 1 + def __lt__(self, other): + raise RuntimeError + def __eq__(self, other): + raise RuntimeError + +class ReprWrapper: + 'Used to test self-referential repr() calls' + def __repr__(self): + return repr(self.value) + +class TestJointOps(unittest.TestCase): + # Tests common to both set and frozenset + + def setUp(self): + self.word = word = 'simsalabim' + self.otherword = 'madagascar' + self.letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + self.s = self.thetype(word) + self.d = dict.fromkeys(word) + + def test_new_or_init(self): + self.assertRaises(TypeError, self.thetype, [], 2) + self.assertRaises(TypeError, set().__init__, a=1) + + def test_uniquification(self): + actual = sorted(self.s) + expected = sorted(self.d) + self.assertEqual(actual, expected) + self.assertRaises(PassThru, self.thetype, check_pass_thru()) + + def test_len(self): + self.assertEqual(len(self.s), len(self.d)) + + def test_contains(self): + for c in self.letters: + self.assertEqual(c in self.s, c in self.d) + s = self.thetype([frozenset(self.letters)]) + # Issue 8752 + #self.assertIn(self.thetype(self.letters), s) + + def test_union(self): + u = self.s.union(self.otherword) + for c in self.letters: + self.assertEqual(c in u, c in self.d or c in self.otherword) + self.assertEqual(self.s, self.thetype(self.word)) + self.assertRaises(PassThru, self.s.union, check_pass_thru()) + for C in set, frozenset, dict.fromkeys, str, list, tuple: + self.assertEqual(self.thetype('abcba').union(C('cdc')), set('abcd')) + self.assertEqual(self.thetype('abcba').union(C('efgfe')), set('abcefg')) + self.assertEqual(self.thetype('abcba').union(C('ccb')), set('abc')) + self.assertEqual(self.thetype('abcba').union(C('ef')), set('abcef')) + self.assertEqual(self.thetype('abcba').union(C('ef'), C('fg')), set('abcefg')) + + # Issue #6573 + x = self.thetype() + self.assertEqual(x.union(set([1]), x, set([2])), self.thetype([1, 2])) + + def test_or(self): + i = self.s.union(self.otherword) + self.assertEqual(self.s | set(self.otherword), i) + self.assertEqual(self.s | frozenset(self.otherword), i) + self.assertEqual(self.s | self.otherword, i) + + def test_intersection(self): + i = self.s.intersection(self.otherword) + for c in self.letters: + self.assertEqual(c in i, c in self.d and c in self.otherword) + self.assertEqual(self.s, self.thetype(self.word)) + self.assertRaises(PassThru, self.s.intersection, check_pass_thru()) + for C in set, frozenset, dict.fromkeys, str, list, tuple: + self.assertEqual(self.thetype('abcba').intersection(C('cdc')), set('cc')) + self.assertEqual(self.thetype('abcba').intersection(C('efgfe')), set('')) + self.assertEqual(self.thetype('abcba').intersection(C('ccb')), set('bc')) + self.assertEqual(self.thetype('abcba').intersection(C('ef')), set('')) + self.assertEqual(self.thetype('abcba').intersection(C('cbcf'), C('bag')), set('b')) + s = self.thetype('abcba') + z = s.intersection() + if self.thetype == frozenset(): # pragma: no cover + self.assertEqual(id(s), id(z)) + else: + self.assertNotEqual(id(s), id(z)) + + def test_isdisjoint(self): + def f(s1, s2): + 'Pure python equivalent of isdisjoint()' + return not set(s1).intersection(s2) + for larg in '', 'a', 'ab', 'abc', 'ababac', 'cdc', 'cc', 'efgfe', 'ccb', 'ef': + s1 = self.thetype(larg) + for rarg in '', 'a', 'ab', 'abc', 'ababac', 'cdc', 'cc', 'efgfe', 'ccb', 'ef': + for C in set, frozenset, dict.fromkeys, str, list, tuple: + s2 = C(rarg) + actual = s1.isdisjoint(s2) + expected = f(s1, s2) + self.assertEqual(actual, expected) + self.assertTrue(actual is True or actual is False) + + def test_and(self): + i = self.s.intersection(self.otherword) + self.assertEqual(self.s & set(self.otherword), i) + self.assertEqual(self.s & frozenset(self.otherword), i) + self.assertEqual(self.s & self.otherword, i) + + def test_difference(self): + i = self.s.difference(self.otherword) + for c in self.letters: + self.assertEqual(c in i, c in self.d and c not in self.otherword) + self.assertEqual(self.s, self.thetype(self.word)) + self.assertRaises(PassThru, self.s.difference, check_pass_thru()) + for C in set, frozenset, dict.fromkeys, str, list, tuple: + self.assertEqual(self.thetype('abcba').difference(C('cdc')), set('ab')) + self.assertEqual(self.thetype('abcba').difference(C('efgfe')), set('abc')) + self.assertEqual(self.thetype('abcba').difference(C('ccb')), set('a')) + self.assertEqual(self.thetype('abcba').difference(C('ef')), set('abc')) + self.assertEqual(self.thetype('abcba').difference(), set('abc')) + self.assertEqual(self.thetype('abcba').difference(C('a'), C('b')), set('c')) + + def test_sub(self): + i = self.s.difference(self.otherword) + self.assertEqual(self.s - set(self.otherword), i) + self.assertEqual(self.s - frozenset(self.otherword), i) + self.assertEqual(self.s - self.otherword, i) + + def test_symmetric_difference(self): + i = self.s.symmetric_difference(self.otherword) + for c in self.letters: + self.assertEqual(c in i, (c in self.d) ^ (c in self.otherword)) + self.assertEqual(self.s, self.thetype(self.word)) + self.assertRaises(PassThru, self.s.symmetric_difference, check_pass_thru()) + for C in set, frozenset, dict.fromkeys, str, list, tuple: + self.assertEqual(self.thetype('abcba').symmetric_difference(C('cdc')), set('abd')) + self.assertEqual(self.thetype('abcba').symmetric_difference(C('efgfe')), set('abcefg')) + self.assertEqual(self.thetype('abcba').symmetric_difference(C('ccb')), set('a')) + self.assertEqual(self.thetype('abcba').symmetric_difference(C('ef')), set('abcef')) + + def test_xor(self): + i = self.s.symmetric_difference(self.otherword) + self.assertEqual(self.s ^ set(self.otherword), i) + self.assertEqual(self.s ^ frozenset(self.otherword), i) + self.assertEqual(self.s ^ self.otherword, i) + + def test_equality(self): + self.assertEqual(self.s, set(self.word)) + self.assertEqual(self.s, frozenset(self.word)) + self.assertEqual(self.s == self.word, False) + self.assertNotEqual(self.s, set(self.otherword)) + self.assertNotEqual(self.s, frozenset(self.otherword)) + self.assertEqual(self.s != self.word, True) + + def test_setOfFrozensets(self): + t = map(frozenset, ['abcdef', 'bcd', 'bdcb', 'fed', 'fedccba']) + s = self.thetype(t) + self.assertEqual(len(s), 3) + + def test_sub_and_super(self): + p, q, r = map(self.thetype, ['ab', 'abcde', 'def']) + self.assertTrue(p < q) + self.assertTrue(p <= q) + self.assertTrue(q <= q) + self.assertTrue(q > p) + self.assertTrue(q >= p) + self.assertFalse(q < r) + self.assertFalse(q <= r) + self.assertFalse(q > r) + self.assertFalse(q >= r) + self.assertTrue(set('a').issubset('abc')) + self.assertTrue(set('abc').issuperset('a')) + self.assertFalse(set('a').issubset('cbs')) + self.assertFalse(set('cbs').issuperset('a')) + + def test_pickling(self): + for i in range(pickle.HIGHEST_PROTOCOL + 1): + p = pickle.dumps(self.s, i) + dup = pickle.loads(p) + self.assertEqual(self.s, dup, "%s != %s" % (self.s, dup)) + if type(self.s) not in (set, frozenset): + self.s.x = 10 + p = pickle.dumps(self.s) + dup = pickle.loads(p) + self.assertEqual(self.s.x, dup.x) + + def test_deepcopy(self): + class Tracer: + def __init__(self, value): + self.value = value + def __hash__(self): # pragma: no cover + return self.value + def __deepcopy__(self, memo=None): + return Tracer(self.value + 1) + t = Tracer(10) + s = self.thetype([t]) + dup = copy.deepcopy(s) + self.assertNotEqual(id(s), id(dup)) + for elem in dup: + newt = elem + self.assertNotEqual(id(t), id(newt)) + self.assertEqual(t.value + 1, newt.value) + + def test_gc(self): + # Create a nest of cycles to exercise overall ref count check + class A: + def __lt__(self, other): + return id(self) < id(other) + def __gt__(self, other): + return id(self) > id(other) + s = set(A() for i in range(1000)) + for elem in s: + elem.cycle = s + elem.sub = elem + elem.set = set([elem]) + + def test_badcmp(self): + s = self.thetype([BadCmp()]) + # Detect comparison errors during insertion and lookup + self.assertRaises(RuntimeError, self.thetype, [BadCmp(), BadCmp()]) + self.assertRaises(RuntimeError, s.__contains__, BadCmp()) + # Detect errors during mutating operations + if hasattr(s, 'add'): + self.assertRaises(RuntimeError, s.add, BadCmp()) + self.assertRaises(RuntimeError, s.discard, BadCmp()) + self.assertRaises(RuntimeError, s.remove, BadCmp()) + + def test_cyclical_repr(self): + w = ReprWrapper() + s = self.thetype([w]) + w.value = s + if self.thetype == set: + self.assertEqual(repr(s), 'sortedset([sortedset(...)])') + else: + name = repr(s).partition('(')[0] # strip class name + self.assertEqual(repr(s), '%s([%s(...)])' % (name, name)) + + def test_cyclical_print(self): + w = ReprWrapper() + s = self.thetype([w]) + w.value = s + fo = open(support.TESTFN, "w") + try: + fo.write(str(s)) + fo.close() + fo = open(support.TESTFN, "r") + self.assertEqual(fo.read(), repr(s)) + finally: + fo.close() + support.unlink(support.TESTFN) + + def test_container_iterator(self): + # Bug #3680: tp_traverse was not implemented for set iterator object + class C(object): + def __lt__(self, other): + return id(self) < id(other) + def __gt__(self, other): + return id(self) > id(other) + obj = C() + ref = weakref.ref(obj) + container = set([obj, 1]) + obj.x = iter(container) + del obj, container + gc.collect() + self.assertTrue(ref() is None, "Cycle was not collected") + +class TestSet(TestJointOps): + thetype = set + basetype = set + + def test_init(self): + s = self.thetype() + s.__init__(self.word) + self.assertEqual(s, set(self.word)) + s.__init__(self.otherword) + self.assertEqual(s, set(self.otherword)) + self.assertRaises(TypeError, s.__init__, s, 2); + self.assertRaises(TypeError, s.__init__, 1); + + def test_constructor_identity(self): + s = self.thetype(range(3)) + t = self.thetype(s) + self.assertNotEqual(id(s), id(t)) + + def test_hash(self): + self.assertRaises(TypeError, hash, self.s) + + def test_clear(self): + self.s.clear() + self.assertEqual(self.s, set()) + self.assertEqual(len(self.s), 0) + + def test_copy(self): + dup = self.s.copy() + self.assertEqual(self.s, dup) + self.assertNotEqual(id(self.s), id(dup)) + #self.assertEqual(type(dup), self.basetype) + + def test_add(self): + self.s.add('Q') + self.assertIn('Q', self.s) + dup = self.s.copy() + self.s.add('Q') + self.assertEqual(self.s, dup) + #self.assertRaises(TypeError, self.s.add, []) + + def test_remove(self): + self.s.remove('a') + self.assertNotIn('a', self.s) + self.assertRaises(KeyError, self.s.remove, 'Q') + s = self.thetype([frozenset(self.word)]) + #self.assertIn(self.thetype(self.word), s) + #s.remove(self.thetype(self.word)) + #self.assertNotIn(self.thetype(self.word), s) + #self.assertRaises(KeyError, self.s.remove, self.thetype(self.word)) + + def test_remove_keyerror_unpacking(self): + # bug: www.python.org/sf/1576657 + for v1 in ['Q', (1,)]: + try: + self.s.remove(v1) + except KeyError as e: + v2 = e.args[0] + self.assertEqual(v1, v2) + else: # pragma: no cover + self.fail() + + def test_remove_keyerror_set(self): + key = self.thetype([3, 4]) + try: + self.s.remove(key) + except KeyError as e: + self.assertTrue(e.args[0] is key, + "KeyError should be {0}, not {1}".format(key, + e.args[0])) + else: # pragma: no cover + self.fail() + + def test_discard(self): + self.s.discard('a') + self.assertNotIn('a', self.s) + self.s.discard('Q') + #self.assertRaises(TypeError, self.s.discard, []) + s = self.thetype([frozenset(self.word)]) + #self.assertIn(self.thetype(self.word), s) + #s.discard(self.thetype(self.word)) + #self.assertNotIn(self.thetype(self.word), s) + #s.discard(self.thetype(self.word)) + + def test_pop(self): + for i in range(len(self.s)): + elem = self.s.pop() + self.assertNotIn(elem, self.s) + self.assertRaises(IndexError, self.s.pop) + + def test_update(self): + retval = self.s.update(self.otherword) + self.assertEqual(retval, None) + for c in (self.word + self.otherword): + self.assertIn(c, self.s) + self.assertRaises(PassThru, self.s.update, check_pass_thru()) + self.assertRaises(TypeError, self.s.update, 6) + for p, q in (('cdc', 'abcd'), ('efgfe', 'abcefg'), ('ccb', 'abc'), ('ef', 'abcef')): + for C in set, frozenset, dict.fromkeys, str, list, tuple: + s = self.thetype('abcba') + self.assertEqual(s.update(C(p)), None) + self.assertEqual(s, set(q)) + for p in ('cdc', 'efgfe', 'ccb', 'ef', 'abcda'): + q = 'ahi' + for C in set, frozenset, dict.fromkeys, str, list, tuple: + s = self.thetype('abcba') + self.assertEqual(s.update(C(p), C(q)), None) + self.assertEqual(s, set(s) | set(p) | set(q)) + + def test_ior(self): + self.s |= set(self.otherword) + for c in (self.word + self.otherword): + self.assertIn(c, self.s) + + def test_intersection_update(self): + retval = self.s.intersection_update(self.otherword) + self.assertEqual(retval, None) + for c in (self.word + self.otherword): + if c in self.otherword and c in self.word: + self.assertIn(c, self.s) + else: + self.assertNotIn(c, self.s) + self.assertRaises(PassThru, self.s.intersection_update, check_pass_thru()) + self.assertRaises(TypeError, self.s.intersection_update, 6) + for p, q in (('cdc', 'c'), ('efgfe', ''), ('ccb', 'bc'), ('ef', '')): + for C in set, frozenset, dict.fromkeys, str, list, tuple: + s = self.thetype('abcba') + self.assertEqual(s.intersection_update(C(p)), None) + self.assertEqual(s, set(q)) + ss = 'abcba' + s = self.thetype(ss) + t = 'cbc' + self.assertEqual(s.intersection_update(C(p), C(t)), None) + self.assertEqual(s, set('abcba')&set(p)&set(t)) + + def test_iand(self): + self.s &= set(self.otherword) + for c in (self.word + self.otherword): + if c in self.otherword and c in self.word: + self.assertIn(c, self.s) + else: + self.assertNotIn(c, self.s) + + def test_difference_update(self): + retval = self.s.difference_update(self.otherword) + self.assertEqual(retval, None) + for c in (self.word + self.otherword): + if c in self.word and c not in self.otherword: + self.assertIn(c, self.s) + else: + self.assertNotIn(c, self.s) + self.assertRaises(PassThru, self.s.difference_update, check_pass_thru()) + self.assertRaises(TypeError, self.s.difference_update, 6) + self.assertRaises(TypeError, self.s.symmetric_difference_update, 6) + for p, q in (('cdc', 'ab'), ('efgfe', 'abc'), ('ccb', 'a'), ('ef', 'abc')): + for C in set, frozenset, dict.fromkeys, str, list, tuple: + s = self.thetype('abcba') + self.assertEqual(s.difference_update(C(p)), None) + self.assertEqual(s, set(q)) + + s = self.thetype('abcdefghih') + s.difference_update() + self.assertEqual(s, self.thetype('abcdefghih')) + + s = self.thetype('abcdefghih') + s.difference_update(C('aba')) + self.assertEqual(s, self.thetype('cdefghih')) + + s = self.thetype('abcdefghih') + s.difference_update(C('cdc'), C('aba')) + self.assertEqual(s, self.thetype('efghih')) + + def test_isub(self): + self.s -= set(self.otherword) + for c in (self.word + self.otherword): + if c in self.word and c not in self.otherword: + self.assertIn(c, self.s) + else: + self.assertNotIn(c, self.s) + + def test_symmetric_difference_update(self): + retval = self.s.symmetric_difference_update(self.otherword) + self.assertEqual(retval, None) + for c in (self.word + self.otherword): + if (c in self.word) ^ (c in self.otherword): + self.assertIn(c, self.s) + else: + self.assertNotIn(c, self.s) + self.assertRaises(PassThru, self.s.symmetric_difference_update, check_pass_thru()) + self.assertRaises(TypeError, self.s.symmetric_difference_update, 6) + for p, q in (('cdc', 'abd'), ('efgfe', 'abcefg'), ('ccb', 'a'), ('ef', 'abcef')): + for C in set, frozenset, dict.fromkeys, str, list, tuple: + s = self.thetype('abcba') + self.assertEqual(s.symmetric_difference_update(C(p)), None) + self.assertEqual(s, set(q)) + + def test_ixor(self): + self.s ^= set(self.otherword) + for c in (self.word + self.otherword): + if (c in self.word) ^ (c in self.otherword): + self.assertIn(c, self.s) + else: + self.assertNotIn(c, self.s) + + def test_inplace_on_self(self): + t = self.s.copy() + t |= t + self.assertEqual(t, self.s) + t &= t + self.assertEqual(t, self.s) + t -= t + self.assertEqual(t, self.thetype()) + t = self.s.copy() + t ^= t + self.assertEqual(t, self.thetype()) + + def test_weakref(self): + s = self.thetype('gallahad') + p = weakref.proxy(s) + self.assertEqual(str(p), str(s)) + s = None + self.assertRaises(ReferenceError, str, p) + + def test_rich_compare(self): # pragma: no cover + if sys.version_info[0] < 3: + return + + class TestRichSetCompare: + def __gt__(self, some_set): + self.gt_called = True + return False + def __lt__(self, some_set): + self.lt_called = True + return False + def __ge__(self, some_set): + self.ge_called = True + return False + def __le__(self, some_set): + self.le_called = True + return False + + # This first tries the bulitin rich set comparison, which doesn't know + # how to handle the custom object. Upon returning NotImplemented, the + # corresponding comparison on the right object is invoked. + myset = set((1, 2, 3)) + + myobj = TestRichSetCompare() + myset < myobj + self.assertTrue(myobj.gt_called) + + myobj = TestRichSetCompare() + myset > myobj + self.assertTrue(myobj.lt_called) + + myobj = TestRichSetCompare() + myset <= myobj + self.assertTrue(myobj.ge_called) + + myobj = TestRichSetCompare() + myset >= myobj + self.assertTrue(myobj.le_called) + +class SetSubclass(set): + pass + +class TestSetSubclass(TestSet): + thetype = SetSubclass + basetype = set + +class SetSubclassWithKeywordArgs(set): + def __init__(self, iterable=[], newarg=None): + set.__init__(self, iterable) + +class TestSetSubclassWithKeywordArgs(TestSet): + + def test_keywords_in_subclass(self): + 'SF bug #1486663 -- this used to erroneously raise a TypeError' + SetSubclassWithKeywordArgs(newarg=1) + +# Tests taken from test_sets.py ============================================= + +empty_set = set() + +#============================================================================== + +class TestBasicOps(unittest.TestCase): + + def test_repr(self): + if self.repr is not None: + self.assertEqual(repr(self.set), self.repr) + + def test_print(self): + try: + fo = open(support.TESTFN, "w") + fo.write(str(self.set)) + fo.close() + fo = open(support.TESTFN, "r") + self.assertEqual(fo.read(), repr(self.set)) + finally: + fo.close() + support.unlink(support.TESTFN) + + def test_length(self): + self.assertEqual(len(self.set), self.length) + + def test_self_equality(self): + self.assertEqual(self.set, self.set) + + def test_equivalent_equality(self): + self.assertEqual(self.set, self.dup) + + def test_copy(self): + self.assertEqual(self.set.copy(), self.dup) + + def test_self_union(self): + result = self.set | self.set + self.assertEqual(result, self.dup) + + def test_empty_union(self): + result = self.set | empty_set + self.assertEqual(result, self.dup) + + def test_union_empty(self): + result = empty_set | self.set + self.assertEqual(result, self.dup) + + def test_self_intersection(self): + result = self.set & self.set + self.assertEqual(result, self.dup) + + def test_empty_intersection(self): + result = self.set & empty_set + self.assertEqual(result, empty_set) + + def test_intersection_empty(self): + result = empty_set & self.set + self.assertEqual(result, empty_set) + + def test_self_isdisjoint(self): + result = self.set.isdisjoint(self.set) + self.assertEqual(result, not self.set) + + def test_empty_isdisjoint(self): + result = self.set.isdisjoint(empty_set) + self.assertEqual(result, True) + + def test_isdisjoint_empty(self): + result = empty_set.isdisjoint(self.set) + self.assertEqual(result, True) + + def test_self_symmetric_difference(self): + result = self.set ^ self.set + self.assertEqual(result, empty_set) + + def test_checkempty_symmetric_difference(self): + result = self.set ^ empty_set + self.assertEqual(result, self.set) + + def test_self_difference(self): + result = self.set - self.set + self.assertEqual(result, empty_set) + + def test_empty_difference(self): + result = self.set - empty_set + self.assertEqual(result, self.dup) + + def test_empty_difference_rev(self): + result = empty_set - self.set + self.assertEqual(result, empty_set) + + def test_iteration(self): + for v in self.set: + self.assertIn(v, self.values) + setiter = iter(self.set) + # note: __length_hint__ is an internal undocumented API, + # don't rely on it in your own programs + #self.assertEqual(setiter.__length_hint__(), len(self.set)) + + def test_pickling(self): + p = pickle.dumps(self.set) + copy = pickle.loads(p) + self.assertEqual(self.set, copy, + "%s != %s" % (self.set, copy)) + +#------------------------------------------------------------------------------ + +class TestBasicOpsEmpty(TestBasicOps): + def setUp(self): + self.case = "empty set" + self.values = [] + self.set = set(self.values) + self.dup = set(self.values) + self.length = 0 + self.repr = "sortedset()" + +#------------------------------------------------------------------------------ + +class TestBasicOpsSingleton(TestBasicOps): + def setUp(self): + self.case = "unit set (number)" + self.values = [3] + self.set = set(self.values) + self.dup = set(self.values) + self.length = 1 + self.repr = "sortedset([3])" + + def test_in(self): + self.assertIn(3, self.set) + + def test_not_in(self): + self.assertNotIn(2, self.set) + +#------------------------------------------------------------------------------ + +class TestBasicOpsTuple(TestBasicOps): + def setUp(self): + self.case = "unit set (tuple)" + self.values = [(0, 1)] + self.set = set(self.values) + self.dup = set(self.values) + self.length = 1 + self.repr = "sortedset([(0, 1)])" + + def test_in(self): + self.assertIn((0, 1), self.set) + + def test_not_in(self): + self.assertNotIn(9, self.set) + +#------------------------------------------------------------------------------ + +class TestBasicOpsTriple(TestBasicOps): + def setUp(self): + self.case = "triple set" + self.values = [0, 1, 2] + self.set = set(self.values) + self.dup = set(self.values) + self.length = 3 + self.repr = None + +#------------------------------------------------------------------------------ + +class TestBasicOpsString(TestBasicOps): + def setUp(self): + self.case = "string set" + self.values = ["a", "b", "c"] + self.set = set(self.values) + self.dup = set(self.values) + self.length = 3 + self.repr = "sortedset(['a', 'b', 'c'])" + +#------------------------------------------------------------------------------ + +def baditer(): + raise TypeError + yield True # pragma: no cover + +def gooditer(): + yield True + +class TestExceptionPropagation(unittest.TestCase): + """SF 628246: Set constructor should not trap iterator TypeErrors""" + + def test_instanceWithException(self): + self.assertRaises(TypeError, set, baditer()) + + def test_instancesWithoutException(self): + # All of these iterables should load without exception. + set([1,2,3]) + set((1,2,3)) + set({'one':1, 'two':2, 'three':3}) + set(range(3)) + set('abc') + set(gooditer()) + + def test_changingSizeWhileIterating(self): + s = set([1,2,3]) + try: + for i in s: + s.update([4]) + except RuntimeError: + pass + else: # pragma: no cover + self.fail("no exception when changing size during iteration") + +#============================================================================== + +class TestSetOfSets(unittest.TestCase): + def test_constructor(self): + inner = frozenset([1]) + outer = set([inner]) + element = outer.pop() + self.assertEqual(type(element), frozenset) + outer.add(inner) # Rebuild set of sets with .add method + outer.remove(inner) + self.assertEqual(outer, set()) # Verify that remove worked + outer.discard(inner) # Absence of KeyError indicates working fine + +#============================================================================== + +class TestBinaryOps(unittest.TestCase): + def setUp(self): + self.set = set((2, 4, 6)) + + def test_eq(self): # SF bug 643115 + self.assertEqual(self.set, set({2:1,4:3,6:5})) + + def test_union_subset(self): + result = self.set | set([2]) + self.assertEqual(result, set((2, 4, 6))) + + def test_union_superset(self): + result = self.set | set([2, 4, 6, 8]) + self.assertEqual(result, set([2, 4, 6, 8])) + + def test_union_overlap(self): + result = self.set | set([3, 4, 5]) + self.assertEqual(result, set([2, 3, 4, 5, 6])) + + def test_union_non_overlap(self): + result = self.set | set([8]) + self.assertEqual(result, set([2, 4, 6, 8])) + + def test_intersection_subset(self): + result = self.set & set((2, 4)) + self.assertEqual(result, set((2, 4))) + + def test_intersection_superset(self): + result = self.set & set([2, 4, 6, 8]) + self.assertEqual(result, set([2, 4, 6])) + + def test_intersection_overlap(self): + result = self.set & set([3, 4, 5]) + self.assertEqual(result, set([4])) + + def test_intersection_non_overlap(self): + result = self.set & set([8]) + self.assertEqual(result, empty_set) + + def test_isdisjoint_subset(self): + result = self.set.isdisjoint(set((2, 4))) + self.assertEqual(result, False) + + def test_isdisjoint_superset(self): + result = self.set.isdisjoint(set([2, 4, 6, 8])) + self.assertEqual(result, False) + + def test_isdisjoint_overlap(self): + result = self.set.isdisjoint(set([3, 4, 5])) + self.assertEqual(result, False) + + def test_isdisjoint_non_overlap(self): + result = self.set.isdisjoint(set([8])) + self.assertEqual(result, True) + + def test_sym_difference_subset(self): + result = self.set ^ set((2, 4)) + self.assertEqual(result, set([6])) + + def test_sym_difference_superset(self): + result = self.set ^ set((2, 4, 6, 8)) + self.assertEqual(result, set([8])) + + def test_sym_difference_overlap(self): + result = self.set ^ set((3, 4, 5)) + self.assertEqual(result, set([2, 3, 5, 6])) + + def test_sym_difference_non_overlap(self): + result = self.set ^ set([8]) + self.assertEqual(result, set([2, 4, 6, 8])) + +#============================================================================== + +class TestUpdateOps(unittest.TestCase): + def setUp(self): + self.set = set((2, 4, 6)) + + def test_union_subset(self): + self.set |= set([2]) + self.assertEqual(self.set, set((2, 4, 6))) + + def test_union_superset(self): + self.set |= set([2, 4, 6, 8]) + self.assertEqual(self.set, set([2, 4, 6, 8])) + + def test_union_overlap(self): + self.set |= set([3, 4, 5]) + self.assertEqual(self.set, set([2, 3, 4, 5, 6])) + + def test_union_non_overlap(self): + self.set |= set([8]) + self.assertEqual(self.set, set([2, 4, 6, 8])) + + def test_union_method_call(self): + self.set.update(set([3, 4, 5])) + self.assertEqual(self.set, set([2, 3, 4, 5, 6])) + + def test_intersection_subset(self): + self.set &= set((2, 4)) + self.assertEqual(self.set, set((2, 4))) + + def test_intersection_superset(self): + self.set &= set([2, 4, 6, 8]) + self.assertEqual(self.set, set([2, 4, 6])) + + def test_intersection_overlap(self): + self.set &= set([3, 4, 5]) + self.assertEqual(self.set, set([4])) + + def test_intersection_non_overlap(self): + self.set &= set([8]) + self.assertEqual(self.set, empty_set) + + def test_intersection_method_call(self): + self.set.intersection_update(set([3, 4, 5])) + self.assertEqual(self.set, set([4])) + + def test_sym_difference_subset(self): + self.set ^= set((2, 4)) + self.assertEqual(self.set, set([6])) + + def test_sym_difference_superset(self): + self.set ^= set((2, 4, 6, 8)) + self.assertEqual(self.set, set([8])) + + def test_sym_difference_overlap(self): + self.set ^= set((3, 4, 5)) + self.assertEqual(self.set, set([2, 3, 5, 6])) + + def test_sym_difference_non_overlap(self): + self.set ^= set([8]) + self.assertEqual(self.set, set([2, 4, 6, 8])) + + def test_sym_difference_method_call(self): + self.set.symmetric_difference_update(set([3, 4, 5])) + self.assertEqual(self.set, set([2, 3, 5, 6])) + + def test_difference_subset(self): + self.set -= set((2, 4)) + self.assertEqual(self.set, set([6])) + + def test_difference_superset(self): + self.set -= set((2, 4, 6, 8)) + self.assertEqual(self.set, set([])) + + def test_difference_overlap(self): + self.set -= set((3, 4, 5)) + self.assertEqual(self.set, set([2, 6])) + + def test_difference_non_overlap(self): + self.set -= set([8]) + self.assertEqual(self.set, set([2, 4, 6])) + + def test_difference_method_call(self): + self.set.difference_update(set([3, 4, 5])) + self.assertEqual(self.set, set([2, 6])) + +#============================================================================== + +class TestMutate(unittest.TestCase): + def setUp(self): + self.values = ["a", "b", "c"] + self.set = set(self.values) + + def test_add_present(self): + self.set.add("c") + self.assertEqual(self.set, set("abc")) + + def test_add_absent(self): + self.set.add("d") + self.assertEqual(self.set, set("abcd")) + + def test_add_until_full(self): + tmp = set() + expected_len = 0 + for v in self.values: + tmp.add(v) + expected_len += 1 + self.assertEqual(len(tmp), expected_len) + self.assertEqual(tmp, self.set) + + def test_remove_present(self): + self.set.remove("b") + self.assertEqual(self.set, set("ac")) + + def test_remove_absent(self): + try: + self.set.remove("d") + self.fail("Removing missing element should have raised LookupError") # pragma: no cover + except LookupError: + pass + + def test_remove_until_empty(self): + expected_len = len(self.set) + for v in self.values: + self.set.remove(v) + expected_len -= 1 + self.assertEqual(len(self.set), expected_len) + + def test_discard_present(self): + self.set.discard("c") + self.assertEqual(self.set, set("ab")) + + def test_discard_absent(self): + self.set.discard("d") + self.assertEqual(self.set, set("abc")) + + def test_clear(self): + self.set.clear() + self.assertEqual(len(self.set), 0) + + def test_pop(self): + popped = {} + while self.set: + popped[self.set.pop()] = None + self.assertEqual(len(popped), len(self.values)) + for v in self.values: + self.assertIn(v, popped) + + def test_update_empty_tuple(self): + self.set.update(()) + self.assertEqual(self.set, set(self.values)) + + def test_update_unit_tuple_overlap(self): + self.set.update(("a",)) + self.assertEqual(self.set, set(self.values)) + + def test_update_unit_tuple_non_overlap(self): + self.set.update(("a", "z")) + self.assertEqual(self.set, set(self.values + ["z"])) + +#============================================================================== + +class TestSubsets(unittest.TestCase): + + case2method = {"<=": "issubset", + ">=": "issuperset", + } + + reverse = {"==": "==", + "!=": "!=", + "<": ">", + ">": "<", + "<=": ">=", + ">=": "<=", + } + + def test_issubset(self): + x = self.left + y = self.right + for case in "!=", "==", "<", "<=", ">", ">=": + expected = case in self.cases + # Test the binary infix spelling. + result = eval("x" + case + "y", locals()) + self.assertEqual(result, expected) + # Test the "friendly" method-name spelling, if one exists. + if case in TestSubsets.case2method: + method = getattr(x, TestSubsets.case2method[case]) + result = method(y) + self.assertEqual(result, expected) + + # Now do the same for the operands reversed. + rcase = TestSubsets.reverse[case] + result = eval("y" + rcase + "x", locals()) + self.assertEqual(result, expected) + if rcase in TestSubsets.case2method: + method = getattr(y, TestSubsets.case2method[rcase]) + result = method(x) + self.assertEqual(result, expected) +#------------------------------------------------------------------------------ + +class TestSubsetEqualEmpty(TestSubsets): + left = set() + right = set() + name = "both empty" + cases = "==", "<=", ">=" + +#------------------------------------------------------------------------------ + +class TestSubsetEqualNonEmpty(TestSubsets): + left = set([1, 2]) + right = set([1, 2]) + name = "equal pair" + cases = "==", "<=", ">=" + +#------------------------------------------------------------------------------ + +class TestSubsetEmptyNonEmpty(TestSubsets): + left = set() + right = set([1, 2]) + name = "one empty, one non-empty" + cases = "!=", "<", "<=" + +#------------------------------------------------------------------------------ + +class TestSubsetPartial(TestSubsets): + left = set([1]) + right = set([1, 2]) + name = "one a non-empty proper subset of other" + cases = "!=", "<", "<=" + +#------------------------------------------------------------------------------ + +class TestSubsetNonOverlap(TestSubsets): + left = set([1]) + right = set([2]) + name = "neither empty, neither contains" + cases = "!=" + +#============================================================================== + +class TestOnlySetsInBinaryOps(unittest.TestCase): + + def test_eq_ne(self): + # Unlike the others, this is testing that == and != *are* allowed. + self.assertEqual(self.other == self.set, False) + self.assertEqual(self.set == self.other, False) + self.assertEqual(self.other != self.set, True) + self.assertEqual(self.set != self.other, True) + + def test_ge_gt_le_lt(self): + self.assertRaises(TypeError, lambda: self.set < self.other) + self.assertRaises(TypeError, lambda: self.set <= self.other) + self.assertRaises(TypeError, lambda: self.set > self.other) + self.assertRaises(TypeError, lambda: self.set >= self.other) + + self.assertRaises(TypeError, lambda: self.other < self.set) + self.assertRaises(TypeError, lambda: self.other <= self.set) + self.assertRaises(TypeError, lambda: self.other > self.set) + self.assertRaises(TypeError, lambda: self.other >= self.set) + + def test_update_operator(self): + if self.otherIsIterable: + self.set |= self.other + else: + try: + self.set |= self.other + except TypeError: + pass + else: # pragma: no cover + self.fail("expected TypeError") + + def test_update(self): + if self.otherIsIterable: + self.set.update(self.other) + else: + self.assertRaises(TypeError, self.set.update, self.other) + + def test_union(self): + if self.otherIsIterable: + self.set | self.other + self.other | self.set + self.set.union(self.other) + else: + self.assertRaises(TypeError, lambda: self.set | self.other) + self.assertRaises(TypeError, lambda: self.other | self.set) + self.assertRaises(TypeError, self.set.union, self.other) + + def test_intersection_update_operator(self): + if self.otherIsIterable: + self.set &= self.other + else: + try: + self.set &= self.other + except TypeError: + pass + else: # pragma: no cover + self.fail("expected TypeError") + + def test_intersection_update(self): + if self.otherIsIterable: + self.set.intersection_update(self.other) + else: + self.assertRaises(TypeError, + self.set.intersection_update, + self.other) + + def test_intersection(self): + if self.otherIsIterable: + self.set & self.other + self.other & self.set + self.set.intersection(self.other) + else: + self.assertRaises(TypeError, lambda: self.set & self.other) + self.assertRaises(TypeError, lambda: self.other & self.set) + self.assertRaises(TypeError, self.set.intersection, self.other) + + def test_sym_difference_update_operator(self): + if self.otherIsIterable: + self.set ^= self.other + else: + try: + self.set ^= self.other + except TypeError: + pass + else: # pragma: no cover + self.fail("expected TypeError") + + def test_sym_difference_update(self): + if self.otherIsIterable: + self.set.symmetric_difference_update(self.other) + else: + self.assertRaises(TypeError, + self.set.symmetric_difference_update, + self.other) + + def test_sym_difference(self): + if self.otherIsIterable: + self.set ^ self.other + self.other ^ self.set + self.set.symmetric_difference(self.other) + else: + self.assertRaises(TypeError, lambda: self.set ^ self.other) + self.assertRaises(TypeError, lambda: self.other ^ self.set) + self.assertRaises(TypeError, self.set.symmetric_difference, self.other) + + def test_difference_update_operator(self): + if self.otherIsIterable: + self.set -= self.other + else: + try: + self.set -= self.other + except TypeError: + pass + else: # pragma: no cover + self.fail("expected TypeError") + + def test_difference_update(self): + if self.otherIsIterable: + self.set.difference_update(self.other) + else: + self.assertRaises(TypeError, + self.set.difference_update, + self.other) + + def test_difference(self): + if self.otherIsIterable: + self.set - self.other + self.other - self.set + self.set.difference(self.other) + else: + self.assertRaises(TypeError, lambda: self.set - self.other) + self.assertRaises(TypeError, lambda: self.other - self.set) + self.assertRaises(TypeError, self.set.difference, self.other) + +#------------------------------------------------------------------------------ + +class TestOnlySetsNumeric(TestOnlySetsInBinaryOps): + def setUp(self): + self.set = set((1, 2, 3)) + self.other = 19 + self.otherIsIterable = False + +#------------------------------------------------------------------------------ + +class TestOnlySetsDict(TestOnlySetsInBinaryOps): + def setUp(self): + self.set = set((1, 2, 3)) + self.other = {1:2, 3:4} + self.otherIsIterable = True + +#------------------------------------------------------------------------------ + +class TestOnlySetsOperator(TestOnlySetsInBinaryOps): + def setUp(self): + self.set = set((1, 2, 3)) + self.other = operator.add + self.otherIsIterable = False + +#------------------------------------------------------------------------------ + +class TestOnlySetsTuple(TestOnlySetsInBinaryOps): + def setUp(self): + self.set = set((1, 2, 3)) + self.other = (2, 4, 6) + self.otherIsIterable = True + +#------------------------------------------------------------------------------ + +class TestOnlySetsString(TestOnlySetsInBinaryOps): + def setUp(self): + self.set = set('xyz') + self.other = 'abc' + self.otherIsIterable = True + +#------------------------------------------------------------------------------ + +class TestOnlySetsGenerator(TestOnlySetsInBinaryOps): + def setUp(self): + def gen(): + for i in range(0, 10, 2): + yield i + self.set = set((1, 2, 3)) + self.other = gen() + self.otherIsIterable = True + +#============================================================================== + +class TestCopying(unittest.TestCase): + + def test_copy(self): + dup = self.set.copy() + dup_list = sorted(dup, key=repr) + set_list = sorted(self.set, key=repr) + self.assertEqual(len(dup_list), len(set_list)) + for i in range(len(dup_list)): + self.assertTrue(dup_list[i] is set_list[i]) + + def test_deep_copy(self): + dup = copy.deepcopy(self.set) + ##print type(dup), repr(dup) + dup_list = sorted(dup, key=repr) + set_list = sorted(self.set, key=repr) + self.assertEqual(len(dup_list), len(set_list)) + for i in range(len(dup_list)): + self.assertEqual(dup_list[i], set_list[i]) + +#------------------------------------------------------------------------------ + +class TestCopyingEmpty(TestCopying): + def setUp(self): + self.set = set() + +#------------------------------------------------------------------------------ + +class TestCopyingSingleton(TestCopying): + def setUp(self): + self.set = set(["hello"]) + +#------------------------------------------------------------------------------ + +class TestCopyingTriple(TestCopying): + def setUp(self): + self.set = set([-1, 0, 1]) + +#------------------------------------------------------------------------------ + +class TestCopyingTuple(TestCopying): + def setUp(self): + self.set = set([(1, 2)]) + +#------------------------------------------------------------------------------ + +class TestCopyingNested(TestCopying): + def setUp(self): + self.set = set([((1, 2), (3, 4))]) + +#============================================================================== + +class TestIdentities(unittest.TestCase): + def setUp(self): + self.a = set('abracadabra') + self.b = set('alacazam') + + def test_binopsVsSubsets(self): + a, b = self.a, self.b + self.assertTrue(a - b < a) + self.assertTrue(b - a < b) + self.assertTrue(a & b < a) + self.assertTrue(a & b < b) + self.assertTrue(a | b > a) + self.assertTrue(a | b > b) + self.assertTrue(a ^ b < a | b) + + def test_commutativity(self): + a, b = self.a, self.b + self.assertEqual(a&b, b&a) + self.assertEqual(a|b, b|a) + self.assertEqual(a^b, b^a) + if a != b: + self.assertNotEqual(a-b, b-a) + + def test_summations(self): + # check that sums of parts equal the whole + a, b = self.a, self.b + self.assertEqual((a-b)|(a&b)|(b-a), a|b) + self.assertEqual((a&b)|(a^b), a|b) + self.assertEqual(a|(b-a), a|b) + self.assertEqual((a-b)|b, a|b) + self.assertEqual((a-b)|(a&b), a) + self.assertEqual((b-a)|(a&b), b) + self.assertEqual((a-b)|(b-a), a^b) + + def test_exclusion(self): + # check that inverse operations show non-overlap + a, b, zero = self.a, self.b, set() + self.assertEqual((a-b)&b, zero) + self.assertEqual((b-a)&a, zero) + self.assertEqual((a&b)&(a^b), zero) + +# Tests derived from test_itertools.py ======================================= + +def R(seqn): + 'Regular generator' + for i in seqn: + yield i + +class G: + 'Sequence using __getitem__' + def __init__(self, seqn): + self.seqn = seqn + def __getitem__(self, i): + return self.seqn[i] + +class I: + 'Sequence using iterator protocol' + def __init__(self, seqn): + self.seqn = seqn + self.i = 0 + def __iter__(self): + return self + def __next__(self): + if self.i >= len(self.seqn): raise StopIteration + v = self.seqn[self.i] + self.i += 1 + return v + next = __next__ + +class Ig: + 'Sequence using iterator protocol defined with a generator' + def __init__(self, seqn): + self.seqn = seqn + self.i = 0 + def __iter__(self): + for val in self.seqn: + yield val + +class X: + 'Missing __getitem__ and __iter__' + def __init__(self, seqn): + self.seqn = seqn + self.i = 0 + def __next__(self): # pragma: no cover + if self.i >= len(self.seqn): raise StopIteration + v = self.seqn[self.i] + self.i += 1 + return v + next = __next__ + +class N: + 'Iterator missing __next__()' + def __init__(self, seqn): + self.seqn = seqn + self.i = 0 + def __iter__(self): + return self + +class E: + 'Test propagation of exceptions' + def __init__(self, seqn): + self.seqn = seqn + self.i = 0 + def __iter__(self): + return self + def __next__(self): + 3 // 0 + next = __next__ + +class S: + 'Test immediate stop' + def __init__(self, seqn): + pass + def __iter__(self): + return self + def __next__(self): + raise StopIteration + next = __next__ + +from itertools import chain +def L(seqn): + 'Test multiple tiers of iterators' + return chain(map(lambda x:x, R(Ig(G(seqn))))) + +class TestVariousIteratorArgs(unittest.TestCase): + + def test_constructor(self): + for cons in (set,): + for s in (range(3), (), range(1000), (1.1, 1.2), range(2000,2200,5), range(10)): + for g in (G, I, Ig, S, L, R): + self.assertEqual(sorted(cons(g(s)), key=repr), sorted(g(s), key=repr)) + self.assertRaises(TypeError, cons , X(s)) + self.assertRaises(TypeError, cons , N(s)) + self.assertRaises(ZeroDivisionError, cons , E(s)) + + def test_inline_methods(self): + s = set([-1,-2,-3]) + for data in (range(3), (), range(1000), (1.1, 1.2), range(2000,2200,5), range(10)): + for meth in (s.union, s.intersection, s.difference, s.symmetric_difference, s.isdisjoint): + for g in (G, I, Ig, L, R): + expected = meth(data) + actual = meth(G(data)) + if isinstance(expected, bool): + self.assertEqual(actual, expected) + else: + self.assertEqual(sorted(actual, key=repr), sorted(expected, key=repr)) + self.assertRaises(TypeError, meth, X(s)) + self.assertRaises(TypeError, meth, N(s)) + self.assertRaises(ZeroDivisionError, meth, E(s)) + + def test_inplace_methods(self): + for data in (range(3), (), range(1000), (1.1, 1.2), range(2000,2200,5), range(10)): + for methname in ('update', 'intersection_update', + 'difference_update', 'symmetric_difference_update'): + for g in (G, I, Ig, S, L, R): + s = set([1,2,3]) + t = s.copy() + getattr(s, methname)(list(g(data))) + getattr(t, methname)(g(data)) + self.assertEqual(sorted(s, key=repr), sorted(t, key=repr)) + + self.assertRaises(TypeError, getattr(set([1,2,3]), methname), X(data)) + self.assertRaises(TypeError, getattr(set([1,2,3]), methname), N(data)) + self.assertRaises(ZeroDivisionError, getattr(set([1,2,3]), methname), E(data)) + +#============================================================================== + +test_classes = ( + TestSet, + TestSetSubclass, + TestSetSubclassWithKeywordArgs, + TestSetOfSets, + TestExceptionPropagation, + TestBasicOpsEmpty, + TestBasicOpsSingleton, + TestBasicOpsTuple, + TestBasicOpsTriple, + TestBasicOpsString, + TestBinaryOps, + TestUpdateOps, + TestMutate, + TestSubsetEqualEmpty, + TestSubsetEqualNonEmpty, + TestSubsetEmptyNonEmpty, + TestSubsetPartial, + TestSubsetNonOverlap, + TestOnlySetsNumeric, + TestOnlySetsDict, + TestOnlySetsOperator, + TestOnlySetsTuple, + TestOnlySetsString, + TestOnlySetsGenerator, + TestCopyingEmpty, + TestCopyingSingleton, + TestCopyingTriple, + TestCopyingTuple, + TestCopyingNested, + TestIdentities, + TestVariousIteratorArgs, + ) diff --git a/blist/test/test_support.py b/blist/test/test_support.py new file mode 100644 index 0000000..5d7550d --- /dev/null +++ b/blist/test/test_support.py @@ -0,0 +1,415 @@ +# This file taken from Python, licensed under the Python License Agreement + +from __future__ import print_function +"""Supporting definitions for the Python regression tests.""" + +if __name__ != 'blist.test.test_support': + raise ImportError('test_support must be imported from the test package') + +import sys + +class Error(Exception): + """Base class for regression test exceptions.""" + +class TestFailed(Error): + """Test failed.""" + +class TestSkipped(Error): + """Test skipped. + + This can be raised to indicate that a test was deliberatly + skipped, but not because a feature wasn't available. For + example, if some resource can't be used, such as the network + appears to be unavailable, this should be raised instead of + TestFailed. + """ + +class ResourceDenied(TestSkipped): + """Test skipped because it requested a disallowed resource. + + This is raised when a test calls requires() for a resource that + has not be enabled. It is used to distinguish between expected + and unexpected skips. + """ + +verbose = 1 # Flag set to 0 by regrtest.py +use_resources = None # Flag set to [] by regrtest.py +max_memuse = 0 # Disable bigmem tests (they will still be run with + # small sizes, to make sure they work.) + +# _original_stdout is meant to hold stdout at the time regrtest began. +# This may be "the real" stdout, or IDLE's emulation of stdout, or whatever. +# The point is to have some flavor of stdout the user can actually see. +_original_stdout = None +def record_original_stdout(stdout): + global _original_stdout + _original_stdout = stdout + +def get_original_stdout(): + return _original_stdout or sys.stdout + +def unload(name): + try: + del sys.modules[name] + except KeyError: + pass + +def unlink(filename): + import os + try: + os.unlink(filename) + except OSError: + pass + +def forget(modname): + '''"Forget" a module was ever imported by removing it from sys.modules and + deleting any .pyc and .pyo files.''' + unload(modname) + import os + for dirname in sys.path: + unlink(os.path.join(dirname, modname + os.extsep + 'pyc')) + # Deleting the .pyo file cannot be within the 'try' for the .pyc since + # the chance exists that there is no .pyc (and thus the 'try' statement + # is exited) but there is a .pyo file. + unlink(os.path.join(dirname, modname + os.extsep + 'pyo')) + +def is_resource_enabled(resource): + """Test whether a resource is enabled. Known resources are set by + regrtest.py.""" + return use_resources is not None and resource in use_resources + +def requires(resource, msg=None): + """Raise ResourceDenied if the specified resource is not available. + + If the caller's module is __main__ then automatically return True. The + possibility of False being returned occurs when regrtest.py is executing.""" + # see if the caller's module is __main__ - if so, treat as if + # the resource was set + if sys._getframe().f_back.f_globals.get("__name__") == "__main__": + return + if not is_resource_enabled(resource): + if msg is None: + msg = "Use of the `%s' resource not enabled" % resource + raise ResourceDenied(msg) + +FUZZ = 1e-6 + +def fcmp(x, y): # fuzzy comparison function + if type(x) == type(0.0) or type(y) == type(0.0): + try: + x, y = coerce(x, y) + fuzz = (abs(x) + abs(y)) * FUZZ + if abs(x-y) <= fuzz: + return 0 + except: + pass + elif type(x) == type(y) and type(x) in (type(()), type([])): + for i in range(min(len(x), len(y))): + outcome = fcmp(x[i], y[i]) + if outcome != 0: + return outcome + return cmp(len(x), len(y)) + return cmp(x, y) + +try: + str + have_unicode = 1 +except NameError: + have_unicode = 0 + +is_jython = sys.platform.startswith('java') + +import os +# Filename used for testing +if os.name == 'java': + # Jython disallows @ in module names + TESTFN = '$test' +elif os.name == 'riscos': + TESTFN = 'testfile' +else: + TESTFN = '@test' + # Unicode name only used if TEST_FN_ENCODING exists for the platform. + if have_unicode: + # Assuming sys.getfilesystemencoding()!=sys.getdefaultencoding() + # TESTFN_UNICODE is a filename that can be encoded using the + # file system encoding, but *not* with the default (ascii) encoding + if isinstance('', str): + # python -U + # XXX perhaps unicode() should accept Unicode strings? + TESTFN_UNICODE = "@test-\xe0\xf2" + else: + # 2 latin characters. + TESTFN_UNICODE = str("@test-\xe0\xf2", "latin-1") + TESTFN_ENCODING = sys.getfilesystemencoding() + +# Make sure we can write to TESTFN, try in /tmp if we can't +fp = None +try: + fp = open(TESTFN, 'w+') +except IOError: + TMP_TESTFN = os.path.join('/tmp', TESTFN) + try: + fp = open(TMP_TESTFN, 'w+') + TESTFN = TMP_TESTFN + del TMP_TESTFN + except IOError: + print(('WARNING: tests will fail, unable to write to: %s or %s' % + (TESTFN, TMP_TESTFN))) +if fp is not None: + fp.close() + unlink(TESTFN) +del os, fp + +def findfile(file, here=__file__): + """Try to find a file on sys.path and the working directory. If it is not + found the argument passed to the function is returned (this does not + necessarily signal failure; could still be the legitimate path).""" + import os + if os.path.isabs(file): + return file + path = sys.path + path = [os.path.dirname(here)] + path + for dn in path: + fn = os.path.join(dn, file) + if os.path.exists(fn): return fn + return file + +def verify(condition, reason='test failed'): + """Verify that condition is true. If not, raise TestFailed. + + The optional argument reason can be given to provide + a better error text. + """ + + if not condition: + raise TestFailed(reason) + +def vereq(a, b): + """Raise TestFailed if a == b is false. + + This is better than verify(a == b) because, in case of failure, the + error message incorporates repr(a) and repr(b) so you can see the + inputs. + + Note that "not (a == b)" isn't necessarily the same as "a != b"; the + former is tested. + """ + + if not (a == b): + raise TestFailed("%r == %r" % (a, b)) + +def sortdict(dict): + "Like repr(dict), but in sorted order." + items = list(dict.items()) + items.sort() + reprpairs = ["%r: %r" % pair for pair in items] + withcommas = ", ".join(reprpairs) + return "{%s}" % withcommas + +def check_syntax(statement): + try: + compile(statement, '', 'exec') + except SyntaxError: + pass + else: + print('Missing SyntaxError: "%s"' % statement) + +def open_urlresource(url): + import urllib.request, urllib.parse, urllib.error, urllib.parse + import os.path + + filename = urllib.parse.urlparse(url)[2].split('/')[-1] # '/': it's URL! + + for path in [os.path.curdir, os.path.pardir]: + fn = os.path.join(path, filename) + if os.path.exists(fn): + return open(fn) + + requires('urlfetch') + print('\tfetching %s ...' % url, file=get_original_stdout()) + fn, _ = urllib.request.urlretrieve(url, filename) + return open(fn) + +#======================================================================= +# Decorator for running a function in a different locale, correctly resetting +# it afterwards. + +def run_with_locale(catstr, *locales): + def decorator(func): + def inner(*args, **kwds): + try: + import locale + category = getattr(locale, catstr) + orig_locale = locale.setlocale(category) + except AttributeError: + # if the test author gives us an invalid category string + raise + except: + # cannot retrieve original locale, so do nothing + locale = orig_locale = None + else: + for loc in locales: + try: + locale.setlocale(category, loc) + break + except: + pass + + # now run the function, resetting the locale on exceptions + try: + return func(*args, **kwds) + finally: + if locale and orig_locale: + locale.setlocale(category, orig_locale) + inner.__name__ = func.__name__ + inner.__doc__ = func.__doc__ + return inner + return decorator + +#======================================================================= +# Big-memory-test support. Separate from 'resources' because memory use should be configurable. + +# Some handy shorthands. Note that these are used for byte-limits as well +# as size-limits, in the various bigmem tests +_1M = 1024*1024 +_1G = 1024 * _1M +_2G = 2 * _1G + +def set_memlimit(limit): + import re + global max_memuse + sizes = { + 'k': 1024, + 'm': _1M, + 'g': _1G, + 't': 1024*_1G, + } + m = re.match(r'(\d+(\.\d+)?) (K|M|G|T)b?$', limit, + re.IGNORECASE | re.VERBOSE) + if m is None: + raise ValueError('Invalid memory limit %r' % (limit,)) + memlimit = int(float(m.group(1)) * sizes[m.group(3).lower()]) + if memlimit < 2.5*_1G: + raise ValueError('Memory limit %r too low to be useful' % (limit,)) + max_memuse = memlimit + +def bigmemtest(minsize, memuse, overhead=5*_1M): + """Decorator for bigmem tests. + + 'minsize' is the minimum useful size for the test (in arbitrary, + test-interpreted units.) 'memuse' is the number of 'bytes per size' for + the test, or a good estimate of it. 'overhead' specifies fixed overhead, + independant of the testsize, and defaults to 5Mb. + + The decorator tries to guess a good value for 'size' and passes it to + the decorated test function. If minsize * memuse is more than the + allowed memory use (as defined by max_memuse), the test is skipped. + Otherwise, minsize is adjusted upward to use up to max_memuse. + """ + def decorator(f): + def wrapper(self): + if not max_memuse: + # If max_memuse is 0 (the default), + # we still want to run the tests with size set to a few kb, + # to make sure they work. We still want to avoid using + # too much memory, though, but we do that noisily. + maxsize = 5147 + self.failIf(maxsize * memuse + overhead > 20 * _1M) + else: + maxsize = int((max_memuse - overhead) / memuse) + if maxsize < minsize: + # Really ought to print 'test skipped' or something + if verbose: + sys.stderr.write("Skipping %s because of memory " + "constraint\n" % (f.__name__,)) + return + # Try to keep some breathing room in memory use + maxsize = max(maxsize - 50 * _1M, minsize) + return f(self, maxsize) + wrapper.minsize = minsize + wrapper.memuse = memuse + wrapper.overhead = overhead + return wrapper + return decorator + +#======================================================================= +# Preliminary PyUNIT integration. + +from . import unittest + + +class BasicTestRunner: + def run(self, test): + result = unittest.TestResult() + test(result) + return result + +def run_suite(suite, testclass=None): + """Run tests from a unittest.TestSuite-derived class.""" + if verbose: + runner = unittest.TextTestRunner(sys.stdout, verbosity=2) + else: + runner = BasicTestRunner() + + result = runner.run(suite) + if not result.wasSuccessful(): + if len(result.errors) == 1 and not result.failures: + err = result.errors[0][1] + elif len(result.failures) == 1 and not result.errors: + err = result.failures[0][1] + else: + if testclass is None: + msg = "errors occurred; run in verbose mode for details" + else: + msg = "errors occurred in %s.%s" \ + % (testclass.__module__, testclass.__name__) + raise TestFailed(msg) + raise TestFailed(err) + + +def run_unittest(*classes): + """Run tests from unittest.TestCase-derived classes.""" + suite = unittest.TestSuite() + for cls in classes: + if isinstance(cls, (unittest.TestSuite, unittest.TestCase)): + suite.addTest(cls) + else: + suite.addTest(unittest.makeSuite(cls)) + if len(classes)==1: + testclass = classes[0] + else: + testclass = None + run_suite(suite, testclass) + + +#======================================================================= +# doctest driver. + +def run_doctest(module, verbosity=None): + """Run doctest on the given module. Return (#failures, #tests). + + If optional argument verbosity is not specified (or is None), pass + test_support's belief about verbosity on to doctest. Else doctest's + usual behavior is used (it searches sys.argv for -v). + """ + + import doctest + + if verbosity is None: + verbosity = verbose + else: + verbosity = None + + # Direct doctest output (normally just errors) to real stdout; doctest + # output shouldn't be compared by regrtest. + save_stdout = sys.stdout + sys.stdout = get_original_stdout() + try: + f, t = doctest.testmod(module, verbose=verbosity) + if f: + raise TestFailed("%d of %d doctests failed" % (f, t)) + finally: + sys.stdout = save_stdout + if verbose: + print('doctest (%s) ... %d tests with zero failures' % (module.__name__, t)) + return f, t diff --git a/blist/test/unittest.py b/blist/test/unittest.py new file mode 100644 index 0000000..46398c1 --- /dev/null +++ b/blist/test/unittest.py @@ -0,0 +1,848 @@ +#! /usr/bin/python2.5 +from __future__ import print_function +''' +Python unit testing framework, based on Erich Gamma's JUnit and Kent Beck's +Smalltalk testing framework. + +This module contains the core framework classes that form the basis of +specific test cases and suites (TestCase, TestSuite etc.), and also a +text-based utility class for running the tests and reporting the results + (TextTestRunner). + +Simple usage: + + import unittest + + class IntegerArithmenticTestCase(unittest.TestCase): + def testAdd(self): ## test method names begin 'test*' + self.assertEquals((1 + 2), 3) + self.assertEquals(0 + 1, 1) + def testMultiply(self): + self.assertEquals((0 * 10), 0) + self.assertEquals((5 * 8), 40) + + if __name__ == '__main__': + unittest.main() + +Further information is available in the bundled documentation, and from + + http://pyunit.sourceforge.net/ + +Copyright (c) 1999-2003 Steve Purcell +This module is free software, and you may redistribute it and/or modify +it under the same terms as Python itself, so long as this copyright message +and disclaimer are retained in their original form. + +IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF +THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, +AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, +SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +''' + +__author__ = "Steve Purcell" +__email__ = "stephen_purcell at yahoo dot com" +__version__ = "#Revision: 1.63 $"[11:-2] + +import time +import sys +import traceback +import os +import types + +class ReferenceLeak(Exception): + def __init__(self, s, n): + self.n = n + self.s = s + + def __str__(self): + return '%s: %d' % (self.s, self.n) + +############################################################################## +# Exported classes and functions +############################################################################## +__all__ = ['TestResult', 'TestCase', 'TestSuite', 'TextTestRunner', + 'TestLoader', 'FunctionTestCase', 'main', 'defaultTestLoader'] + +# Expose obsolete functions for backwards compatibility +__all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases']) + + +############################################################################## +# Test framework core +############################################################################## + +# All classes defined herein are 'new-style' classes, allowing use of 'super()' +__metaclass__ = type + +def _strclass(cls): + return "%s.%s" % (cls.__module__, cls.__name__) + +__unittest = 1 + +class TestResult: + """Holder for test result information. + + Test results are automatically managed by the TestCase and TestSuite + classes, and do not need to be explicitly manipulated by writers of tests. + + Each instance holds the total number of tests run, and collections of + failures and errors that occurred among those test runs. The collections + contain tuples of (testcase, exceptioninfo), where exceptioninfo is the + formatted traceback of the error that occurred. + """ + def __init__(self): + self.failures = [] + self.errors = [] + self.testsRun = 0 + self.shouldStop = 0 + + def startTest(self, test): + "Called when the given test is about to be run" + self.testsRun = self.testsRun + 1 + + def stopTest(self, test): + "Called when the given test has been run" + pass + + def addError(self, test, err): + """Called when an error has occurred. 'err' is a tuple of values as + returned by sys.exc_info(). + """ + self.errors.append((test, self._exc_info_to_string(err, test))) + + def addFailure(self, test, err): + """Called when an error has occurred. 'err' is a tuple of values as + returned by sys.exc_info().""" + self.failures.append((test, self._exc_info_to_string(err, test))) + + def addSuccess(self, test): + "Called when a test has completed successfully" + pass + + def wasSuccessful(self): + "Tells whether or not this result was a success" + return len(self.failures) == len(self.errors) == 0 + + def stop(self): + "Indicates that the tests should be aborted" + self.shouldStop = True + + def _exc_info_to_string(self, err, test): + """Converts a sys.exc_info()-style tuple of values into a string.""" + exctype, value, tb = err + # Skip test runner traceback levels + while tb and self._is_relevant_tb_level(tb): + tb = tb.tb_next + if exctype is test.failureException: + # Skip assert*() traceback levels + length = self._count_relevant_tb_levels(tb) + return ''.join(traceback.format_exception(exctype, value, tb, length)) + return ''.join(traceback.format_exception(exctype, value, tb)) + + def _is_relevant_tb_level(self, tb): + return '__unittest' in tb.tb_frame.f_globals + + def _count_relevant_tb_levels(self, tb): + length = 0 + while tb and not self._is_relevant_tb_level(tb): + length += 1 + tb = tb.tb_next + return length + + def __repr__(self): + return "<%s run=%i errors=%i failures=%i>" % \ + (_strclass(self.__class__), self.testsRun, len(self.errors), + len(self.failures)) + +class TestCase: + """A class whose instances are single test cases. + + By default, the test code itself should be placed in a method named + 'runTest'. + + If the fixture may be used for many test cases, create as + many test methods as are needed. When instantiating such a TestCase + subclass, specify in the constructor arguments the name of the test method + that the instance is to execute. + + Test authors should subclass TestCase for their own tests. Construction + and deconstruction of the test's environment ('fixture') can be + implemented by overriding the 'setUp' and 'tearDown' methods respectively. + + If it is necessary to override the __init__ method, the base class + __init__ method must always be called. It is important that subclasses + should not change the signature of their __init__ method, since instances + of the classes are instantiated automatically by parts of the framework + in order to be run. + """ + + # This attribute determines which exception will be raised when + # the instance's assertion methods fail; test methods raising this + # exception will be deemed to have 'failed' rather than 'errored' + + failureException = AssertionError + + def __init__(self, methodName='runTest'): + """Create an instance of the class that will use the named test + method when executed. Raises a ValueError if the instance does + not have a method with the specified name. + """ + try: + self._testMethodName = methodName + testMethod = getattr(self, methodName) + self._testMethodDoc = testMethod.__doc__ + except AttributeError: + raise ValueError("no such test method in %s: %s" % \ + (self.__class__, methodName)) + + def assertIs(self, expr1, expr2, msg=None): + """Just like self.assertTrue(a is b), but with a nicer default message.""" + if expr1 is not expr2: + standardMsg = '%s is not %s' % (safe_repr(expr1), + safe_repr(expr2)) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIn(self, member, container, msg=None): + """Just like self.assertTrue(a in b), but with a nicer default message.""" + if member not in container: + standardMsg = '%s not found in %s' % (repr(member), + repr(container)) + self.fail(str((msg, standardMsg))) + + def assertNotIn(self, member, container, msg=None): + """Just like self.assertTrue(a not in b), but with a nicer default message.""" + if member in container: + standardMsg = '%s unexpectedly found in %s' % (repr(member), + repr(container)) + self.fail(str((msg, standardMsg))) + + + def setUp(self): + "Hook method for setting up the test fixture before exercising it." + pass + + def tearDown(self): + "Hook method for deconstructing the test fixture after testing it." + pass + + def countTestCases(self): + return 1 + + def defaultTestResult(self): + return TestResult() + + def shortDescription(self): + """Returns a one-line description of the test, or None if no + description has been provided. + + The default implementation of this method returns the first line of + the specified test method's docstring. + """ + doc = self._testMethodDoc + return doc and doc.split("\n")[0].strip() or None + + def id(self): + return "%s.%s" % (_strclass(self.__class__), self._testMethodName) + + def __str__(self): + return "%s (%s)" % (self._testMethodName, _strclass(self.__class__)) + + def __repr__(self): + return "<%s testMethod=%s>" % \ + (_strclass(self.__class__), self._testMethodName) + + def run(self, result=None): + self.run_(result, False) + self.run_(result, False) + try: + self.run_(result, True) + except AttributeError: + pass + + def run_(self, result, memcheck): + if result is None: result = self.defaultTestResult() + if memcheck and not hasattr(sys, 'gettotalrefcount'): + return + result.startTest(self) + testMethod = getattr(self, self._testMethodName) + try: + import gc + + total_start = 0 + total_finish = 0 + ok = True + gc.collect() + ob_start = set(id(x) for x in gc.get_objects()) + try: + total_start = sys.gettotalrefcount() + except AttributeError: + pass + + try: + self.setUp() + except KeyboardInterrupt: + raise + except: + result.addError(self, self._exc_info()) + return + + ok = False + try: + testMethod() + ok = True + except self.failureException: + result.addFailure(self, self._exc_info()) + except KeyboardInterrupt: + raise + except: + result.addError(self, self._exc_info()) + + try: + self.tearDown() + if ok and memcheck: + gc.collect() + gc.collect() + total_finish = sys.gettotalrefcount() + ob_finish = gc.get_objects() + if len(ob_start) != len(ob_finish): + #for ob in ob_finish: + # if id(ob) not in ob_start and ob is not ob_start: + # print ob + raise ReferenceLeak('more objects', len(ob_finish)-len(ob_start)) + if total_finish != total_start: + print(total_start, total_finish) + raise ReferenceLeak('more references', total_finish - total_start) + except KeyboardInterrupt: + raise + except: + result.addError(self, self._exc_info()) + ok = False + if ok: result.addSuccess(self) + finally: + result.stopTest(self) + + def __call__(self, *args, **kwds): + return self.run(*args, **kwds) + + def debug(self): + """Run the test without collecting errors in a TestResult""" + self.setUp() + getattr(self, self._testMethodName)() + self.tearDown() + + def _exc_info(self): + """Return a version of sys.exc_info() with the traceback frame + minimised; usually the top level of the traceback frame is not + needed. + """ + exctype, excvalue, tb = sys.exc_info() + if sys.platform[:4] == 'java': ## tracebacks look different in Jython + return (exctype, excvalue, tb) + return (exctype, excvalue, tb) + + def fail(self, msg=None): + """Fail immediately, with the given message.""" + raise self.failureException(msg) + + def failIf(self, expr, msg=None): + "Fail the test if the expression is true." + if expr: raise self.failureException(msg) + + def failUnless(self, expr, msg=None): + """Fail the test unless the expression is true.""" + if not expr: raise self.failureException(msg) + + def failUnlessRaises(self, excClass, callableObj, *args, **kwargs): + """Fail unless an exception of class excClass is thrown + by callableObj when invoked with arguments args and keyword + arguments kwargs. If a different type of exception is + thrown, it will not be caught, and the test case will be + deemed to have suffered an error, exactly as for an + unexpected exception. + """ + try: + callableObj(*args, **kwargs) + except excClass: + return + else: + if hasattr(excClass,'__name__'): excName = excClass.__name__ + else: excName = str(excClass) + raise self.failureException("%s not raised" % excName) + + def failUnlessEqual(self, first, second, msg=None): + """Fail if the two objects are unequal as determined by the '==' + operator. + """ + if not first == second: + raise self.failureException(msg or '%r != %r' % (first, second)) + + def failIfEqual(self, first, second, msg=None): + """Fail if the two objects are equal as determined by the '==' + operator. + """ + if first == second: + raise self.failureException(msg or '%r == %r' % (first, second)) + + def failUnlessAlmostEqual(self, first, second, places=7, msg=None): + """Fail if the two objects are unequal as determined by their + difference rounded to the given number of decimal places + (default 7) and comparing to zero. + + Note that decimal places (from zero) are usually not the same + as significant digits (measured from the most signficant digit). + """ + if round(second-first, places) != 0: + raise self.failureException(msg or '%r != %r within %r places' % (first, second, places)) + + def failIfAlmostEqual(self, first, second, places=7, msg=None): + """Fail if the two objects are equal as determined by their + difference rounded to the given number of decimal places + (default 7) and comparing to zero. + + Note that decimal places (from zero) are usually not the same + as significant digits (measured from the most signficant digit). + """ + if round(second-first, places) == 0: + raise self.failureException(msg or '%r == %r within %r places' % (first, second, places)) + + # Synonyms for assertion methods + + assertEqual = assertEquals = failUnlessEqual + + assertNotEqual = assertNotEquals = failIfEqual + + assertAlmostEqual = assertAlmostEquals = failUnlessAlmostEqual + + assertNotAlmostEqual = assertNotAlmostEquals = failIfAlmostEqual + + assertRaises = failUnlessRaises + + assert_ = assertTrue = failUnless + + assertFalse = failIf + + + +class TestSuite: + """A test suite is a composite test consisting of a number of TestCases. + + For use, create an instance of TestSuite, then add test case instances. + When all tests have been added, the suite can be passed to a test + runner, such as TextTestRunner. It will run the individual test cases + in the order in which they were added, aggregating the results. When + subclassing, do not forget to call the base class constructor. + """ + def __init__(self, tests=()): + self._tests = [] + self.addTests(tests) + + def __repr__(self): + return "<%s tests=%s>" % (_strclass(self.__class__), self._tests) + + __str__ = __repr__ + + def __iter__(self): + return iter(self._tests) + + def countTestCases(self): + cases = 0 + for test in self._tests: + cases += test.countTestCases() + return cases + + def addTest(self, test): + self._tests.append(test) + + def addTests(self, tests): + for test in tests: + self.addTest(test) + + def run(self, result): + for test in self._tests: + if result.shouldStop: + break + test(result) + return result + + def __call__(self, *args, **kwds): + return self.run(*args, **kwds) + + def debug(self): + """Run the tests without collecting errors in a TestResult""" + for test in self._tests: test.debug() + + +class FunctionTestCase(TestCase): + """A test case that wraps a test function. + + This is useful for slipping pre-existing test functions into the + PyUnit framework. Optionally, set-up and tidy-up functions can be + supplied. As with TestCase, the tidy-up ('tearDown') function will + always be called if the set-up ('setUp') function ran successfully. + """ + + def __init__(self, testFunc, setUp=None, tearDown=None, + description=None): + TestCase.__init__(self) + self.__setUpFunc = setUp + self.__tearDownFunc = tearDown + self.__testFunc = testFunc + self.__description = description + + def setUp(self): + if self.__setUpFunc is not None: + self.__setUpFunc() + + def tearDown(self): + if self.__tearDownFunc is not None: + self.__tearDownFunc() + + def runTest(self): + self.__testFunc() + + def id(self): + return self.__testFunc.__name__ + + def __str__(self): + return "%s (%s)" % (_strclass(self.__class__), self.__testFunc.__name__) + + def __repr__(self): + return "<%s testFunc=%s>" % (_strclass(self.__class__), self.__testFunc) + + def shortDescription(self): + if self.__description is not None: return self.__description + doc = self.__testFunc.__doc__ + return doc and doc.split("\n")[0].strip() or None + + + +############################################################################## +# Locating and loading tests +############################################################################## + +class TestLoader: + """This class is responsible for loading tests according to various + criteria and returning them wrapped in a Test + """ + testMethodPrefix = 'test' + suiteClass = TestSuite + + def loadTestsFromTestCase(self, testCaseClass): + """Return a suite of all tests cases contained in testCaseClass""" + if issubclass(testCaseClass, TestSuite): + raise TypeError("Test cases should not be derived from TestSuite. Maybe you meant to derive from TestCase?") + testCaseNames = self.getTestCaseNames(testCaseClass) + if not testCaseNames and hasattr(testCaseClass, 'runTest'): + testCaseNames = ['runTest'] + return self.suiteClass(list(map(testCaseClass, testCaseNames))) + + def loadTestsFromModule(self, module): + """Return a suite of all tests cases contained in the given module""" + tests = [] + for name in dir(module): + obj = getattr(module, name) + if (isinstance(obj, type) and + issubclass(obj, TestCase)): + tests.append(self.loadTestsFromTestCase(obj)) + return self.suiteClass(tests) + + def loadTestsFromName(self, name, module=None): + """Return a suite of all tests cases given a string specifier. + + The name may resolve either to a module, a test case class, a + test method within a test case class, or a callable object which + returns a TestCase or TestSuite instance. + + The method optionally resolves the names relative to a given module. + """ + parts = name.split('.') + if module is None: + parts_copy = parts[:] + while parts_copy: + try: + module = __import__('.'.join(parts_copy)) + break + except ImportError: + del parts_copy[-1] + if not parts_copy: raise + parts = parts[1:] + obj = module + for part in parts: + parent, obj = obj, getattr(obj, part) + + if type(obj) == types.ModuleType: + return self.loadTestsFromModule(obj) + elif (isinstance(obj, type) and + issubclass(obj, TestCase)): + return self.loadTestsFromTestCase(obj) + elif type(obj) == types.UnboundMethodType: + return parent(obj.__name__) + elif isinstance(obj, TestSuite): + return obj + elif hasattr(obj, '__call__'): + test = obj() + if not isinstance(test, (TestCase, TestSuite)): + raise ValueError("calling %s returned %s, not a test" % (obj,test)) + return test + else: + raise ValueError("don't know how to make test from: %s" % obj) + + def loadTestsFromNames(self, names, module=None): + """Return a suite of all tests cases found using the given sequence + of string specifiers. See 'loadTestsFromName()'. + """ + suites = [self.loadTestsFromName(name, module) for name in names] + return self.suiteClass(suites) + + def getTestCaseNames(self, testCaseClass): + """Return a sorted sequence of method names found within testCaseClass + """ + def isTestMethod(attrname, testCaseClass=testCaseClass, prefix=self.testMethodPrefix): + return attrname.startswith(prefix) and hasattr(getattr(testCaseClass, attrname), '__call__') + testFnNames = list(filter(isTestMethod, dir(testCaseClass))) + for baseclass in testCaseClass.__bases__: + for testFnName in self.getTestCaseNames(baseclass): + if testFnName not in testFnNames: # handle overridden methods + testFnNames.append(testFnName) + return testFnNames + + + +defaultTestLoader = TestLoader() + + +############################################################################## +# Patches for old functions: these functions should be considered obsolete +############################################################################## + +def _makeLoader(prefix, suiteClass=None): + loader = TestLoader() + loader.testMethodPrefix = prefix + if suiteClass: loader.suiteClass = suiteClass + return loader + +def getTestCaseNames(testCaseClass, prefix): + return _makeLoader(prefix).getTestCaseNames(testCaseClass) + +def makeSuite(testCaseClass, prefix='test', suiteClass=TestSuite): + return _makeLoader(prefix, suiteClass).loadTestsFromTestCase(testCaseClass) + +def findTestCases(module, prefix='test', suiteClass=TestSuite): + return _makeLoader(prefix, suiteClass).loadTestsFromModule(module) + + +############################################################################## +# Text UI +############################################################################## + +class _WritelnDecorator: + """Used to decorate file-like objects with a handy 'writeln' method""" + def __init__(self,stream): + self.stream = stream + + def __getattr__(self, attr): + return getattr(self.stream,attr) + + def writeln(self, arg=None): + if arg: self.write(arg) + self.write('\n') # text-mode streams translate to \r\n if needed + + +class _TextTestResult(TestResult): + """A test result class that can print formatted text results to a stream. + + Used by TextTestRunner. + """ + separator1 = '=' * 70 + separator2 = '-' * 70 + + def __init__(self, stream, descriptions, verbosity): + TestResult.__init__(self) + self.stream = stream + self.showAll = verbosity > 1 + self.dots = verbosity == 1 + self.descriptions = descriptions + + def getDescription(self, test): + if self.descriptions: + return test.shortDescription() or str(test) + else: + return str(test) + + def startTest(self, test): + TestResult.startTest(self, test) + if self.showAll: + self.stream.write(self.getDescription(test)) + self.stream.write(" ... ") + + def addSuccess(self, test): + TestResult.addSuccess(self, test) + if self.showAll: + self.stream.writeln("ok") + elif self.dots: + self.stream.write('.') + + def addError(self, test, err): + TestResult.addError(self, test, err) + if self.showAll: + self.stream.writeln("ERROR") + elif self.dots: + self.stream.write('E') + + def addFailure(self, test, err): + TestResult.addFailure(self, test, err) + if self.showAll: + self.stream.writeln("FAIL") + elif self.dots: + self.stream.write('F') + + def printErrors(self): + if self.dots or self.showAll: + self.stream.writeln() + self.printErrorList('ERROR', self.errors) + self.printErrorList('FAIL', self.failures) + + def printErrorList(self, flavour, errors): + for test, err in errors: + self.stream.writeln(self.separator1) + self.stream.writeln("%s: %s" % (flavour,self.getDescription(test))) + self.stream.writeln(self.separator2) + self.stream.writeln("%s" % err) + + +class TextTestRunner: + """A test runner class that displays results in textual form. + + It prints out the names of tests as they are run, errors as they + occur, and a summary of the results at the end of the test run. + """ + def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1): + self.stream = _WritelnDecorator(stream) + self.descriptions = descriptions + self.verbosity = verbosity + + def _makeResult(self): + return _TextTestResult(self.stream, self.descriptions, self.verbosity) + + def run(self, test): + "Run the given test case or test suite." + result = self._makeResult() + startTime = time.time() + test(result) + stopTime = time.time() + timeTaken = stopTime - startTime + result.printErrors() + self.stream.writeln(result.separator2) + run = result.testsRun + self.stream.writeln("Ran %d test%s in %.3fs" % + (run, run != 1 and "s" or "", timeTaken)) + self.stream.writeln() + if not result.wasSuccessful(): + self.stream.write("FAILED (") + failed, errored = list(map(len, (result.failures, result.errors))) + if failed: + self.stream.write("failures=%d" % failed) + if errored: + if failed: self.stream.write(", ") + self.stream.write("errors=%d" % errored) + self.stream.writeln(")") + else: + self.stream.writeln("OK") + return result + + + +############################################################################## +# Facilities for running tests from the command line +############################################################################## + +class TestProgram: + """A command-line program that runs a set of tests; this is primarily + for making test modules conveniently executable. + """ + USAGE = """\ +Usage: %(progName)s [options] [test] [...] + +Options: + -h, --help Show this message + -v, --verbose Verbose output + -q, --quiet Minimal output + +Examples: + %(progName)s - run default set of tests + %(progName)s MyTestSuite - run suite 'MyTestSuite' + %(progName)s MyTestCase.testSomething - run MyTestCase.testSomething + %(progName)s MyTestCase - run all 'test*' test methods + in MyTestCase +""" + def __init__(self, module='__main__', defaultTest=None, + argv=None, testRunner=None, testLoader=defaultTestLoader): + if type(module) == type(''): + self.module = __import__(module) + for part in module.split('.')[1:]: + self.module = getattr(self.module, part) + else: + self.module = module + if argv is None: + argv = sys.argv + self.verbosity = 1 + self.defaultTest = defaultTest + self.testRunner = testRunner + self.testLoader = testLoader + self.progName = os.path.basename(argv[0]) + self.parseArgs(argv) + self.runTests() + + def usageExit(self, msg=None): + if msg: print(msg) + print(self.USAGE % self.__dict__) + sys.exit(2) + + def parseArgs(self, argv): + import getopt + try: + options, args = getopt.getopt(argv[1:], 'hHvq', + ['help','verbose','quiet']) + for opt, value in options: + if opt in ('-h','-H','--help'): + self.usageExit() + if opt in ('-q','--quiet'): + self.verbosity = 0 + if opt in ('-v','--verbose'): + self.verbosity = 2 + if len(args) == 0 and self.defaultTest is None: + self.test = self.testLoader.loadTestsFromModule(self.module) + return + if len(args) > 0: + self.testNames = args + else: + self.testNames = (self.defaultTest,) + self.createTests() + except getopt.error as msg: + self.usageExit(msg) + + def createTests(self): + self.test = self.testLoader.loadTestsFromNames(self.testNames, + self.module) + + def runTests(self): + if self.testRunner is None: + self.testRunner = TextTestRunner(verbosity=self.verbosity) + result = self.testRunner.run(self.test) + sys.exit(not result.wasSuccessful()) + +main = TestProgram + + +############################################################################## +# Executing this module from the command line +############################################################################## + +if __name__ == "__main__": + main(module=None) diff --git a/distribute_setup.py b/distribute_setup.py deleted file mode 100644 index 4f7bd08..0000000 --- a/distribute_setup.py +++ /dev/null @@ -1,481 +0,0 @@ -#!python -"""Bootstrap distribute installation - -If you want to use setuptools in your package's setup.py, just include this -file in the same directory with it, and add this to the top of your setup.py:: - - from distribute_setup import use_setuptools - use_setuptools() - -If you want to require a specific version of setuptools, set a download -mirror, or use an alternate download directory, you can do so by supplying -the appropriate options to ``use_setuptools()``. - -This file can also be run as a script to install or upgrade setuptools. -""" -import os -import sys -import time -import fnmatch -import tempfile -import tarfile -from distutils import log - -try: - from site import USER_SITE -except ImportError: - USER_SITE = None - -try: - import subprocess - - def _python_cmd(*args): - args = (sys.executable,) + args - return subprocess.call(args) == 0 - -except ImportError: - # will be used for python 2.3 - def _python_cmd(*args): - args = (sys.executable,) + args - # quoting arguments if windows - if sys.platform == 'win32': - def quote(arg): - if ' ' in arg: - return '"%s"' % arg - return arg - args = [quote(arg) for arg in args] - return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 - -DEFAULT_VERSION = "0.6.12" -DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" -SETUPTOOLS_FAKED_VERSION = "0.6c11" - -SETUPTOOLS_PKG_INFO = """\ -Metadata-Version: 1.0 -Name: setuptools -Version: %s -Summary: xxxx -Home-page: xxx -Author: xxx -Author-email: xxx -License: xxx -Description: xxx -""" % SETUPTOOLS_FAKED_VERSION - - -def _install(tarball): - # extracting the tarball - tmpdir = tempfile.mkdtemp() - log.warn('Extracting in %s', tmpdir) - old_wd = os.getcwd() - try: - os.chdir(tmpdir) - tar = tarfile.open(tarball) - _extractall(tar) - tar.close() - - # going in the directory - subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) - os.chdir(subdir) - log.warn('Now working in %s', subdir) - - # installing - log.warn('Installing Distribute') - if not _python_cmd('setup.py', 'install'): - log.warn('Something went wrong during the installation.') - log.warn('See the error message above.') - finally: - os.chdir(old_wd) - - -def _build_egg(egg, tarball, to_dir): - # extracting the tarball - tmpdir = tempfile.mkdtemp() - log.warn('Extracting in %s', tmpdir) - old_wd = os.getcwd() - try: - os.chdir(tmpdir) - tar = tarfile.open(tarball) - _extractall(tar) - tar.close() - - # going in the directory - subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) - os.chdir(subdir) - log.warn('Now working in %s', subdir) - - # building an egg - log.warn('Building a Distribute egg in %s', to_dir) - _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) - - finally: - os.chdir(old_wd) - # returning the result - log.warn(egg) - if not os.path.exists(egg): - raise IOError('Could not build the egg.') - - -def _do_download(version, download_base, to_dir, download_delay): - egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' - % (version, sys.version_info[0], sys.version_info[1])) - if not os.path.exists(egg): - tarball = download_setuptools(version, download_base, - to_dir, download_delay) - _build_egg(egg, tarball, to_dir) - sys.path.insert(0, egg) - import setuptools - setuptools.bootstrap_install_from = egg - - -def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, - to_dir=os.curdir, download_delay=15, no_fake=True): - # making sure we use the absolute path - to_dir = os.path.abspath(to_dir) - was_imported = 'pkg_resources' in sys.modules or \ - 'setuptools' in sys.modules - try: - try: - import pkg_resources - if not hasattr(pkg_resources, '_distribute'): - if not no_fake: - _fake_setuptools() - raise ImportError - except ImportError: - return _do_download(version, download_base, to_dir, download_delay) - try: - pkg_resources.require("distribute>="+version) - return - except pkg_resources.VersionConflict: - e = sys.exc_info()[1] - if was_imported: - sys.stderr.write( - "The required version of distribute (>=%s) is not available,\n" - "and can't be installed while this script is running. Please\n" - "install a more recent version first, using\n" - "'easy_install -U distribute'." - "\n\n(Currently using %r)\n" % (version, e.args[0])) - sys.exit(2) - else: - del pkg_resources, sys.modules['pkg_resources'] # reload ok - return _do_download(version, download_base, to_dir, - download_delay) - except pkg_resources.DistributionNotFound: - return _do_download(version, download_base, to_dir, - download_delay) - finally: - if not no_fake: - _create_fake_setuptools_pkg_info(to_dir) - -def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, - to_dir=os.curdir, delay=15): - """Download distribute from a specified location and return its filename - - `version` should be a valid distribute version number that is available - as an egg for download under the `download_base` URL (which should end - with a '/'). `to_dir` is the directory where the egg will be downloaded. - `delay` is the number of seconds to pause before an actual download - attempt. - """ - # making sure we use the absolute path - to_dir = os.path.abspath(to_dir) - try: - from urllib.request import urlopen - except ImportError: - from urllib2 import urlopen - tgz_name = "distribute-%s.tar.gz" % version - url = download_base + tgz_name - saveto = os.path.join(to_dir, tgz_name) - src = dst = None - if not os.path.exists(saveto): # Avoid repeated downloads - try: - log.warn("Downloading %s", url) - src = urlopen(url) - # Read/write all in one block, so we don't create a corrupt file - # if the download is interrupted. - data = src.read() - dst = open(saveto, "wb") - dst.write(data) - finally: - if src: - src.close() - if dst: - dst.close() - return os.path.realpath(saveto) - -def _no_sandbox(function): - def __no_sandbox(*args, **kw): - try: - from setuptools.sandbox import DirectorySandbox - if not hasattr(DirectorySandbox, '_old'): - def violation(*args): - pass - DirectorySandbox._old = DirectorySandbox._violation - DirectorySandbox._violation = violation - patched = True - else: - patched = False - except ImportError: - patched = False - - try: - return function(*args, **kw) - finally: - if patched: - DirectorySandbox._violation = DirectorySandbox._old - del DirectorySandbox._old - - return __no_sandbox - -@_no_sandbox -def _patch_file(path, content): - """Will backup the file then patch it""" - existing_content = open(path).read() - if existing_content == content: - # already patched - log.warn('Already patched.') - return False - log.warn('Patching...') - _rename_path(path) - f = open(path, 'w') - try: - f.write(content) - finally: - f.close() - return True - - -def _same_content(path, content): - return open(path).read() == content - -def _rename_path(path): - new_name = path + '.OLD.%s' % time.time() - log.warn('Renaming %s into %s', path, new_name) - os.rename(path, new_name) - return new_name - -@_no_sandbox -def _remove_flat_installation(placeholder): - if not os.path.isdir(placeholder): - log.warn('Unkown installation at %s', placeholder) - return False - found = False - for file in os.listdir(placeholder): - if fnmatch.fnmatch(file, 'setuptools*.egg-info'): - found = True - break - if not found: - log.warn('Could not locate setuptools*.egg-info') - return - - log.warn('Removing elements out of the way...') - pkg_info = os.path.join(placeholder, file) - if os.path.isdir(pkg_info): - patched = _patch_egg_dir(pkg_info) - else: - patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO) - - if not patched: - log.warn('%s already patched.', pkg_info) - return False - # now let's move the files out of the way - for element in ('setuptools', 'pkg_resources.py', 'site.py'): - element = os.path.join(placeholder, element) - if os.path.exists(element): - _rename_path(element) - else: - log.warn('Could not find the %s element of the ' - 'Setuptools distribution', element) - return True - - -def _after_install(dist): - log.warn('After install bootstrap.') - placeholder = dist.get_command_obj('install').install_purelib - _create_fake_setuptools_pkg_info(placeholder) - -@_no_sandbox -def _create_fake_setuptools_pkg_info(placeholder): - if not placeholder or not os.path.exists(placeholder): - log.warn('Could not find the install location') - return - pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) - setuptools_file = 'setuptools-%s-py%s.egg-info' % \ - (SETUPTOOLS_FAKED_VERSION, pyver) - pkg_info = os.path.join(placeholder, setuptools_file) - if os.path.exists(pkg_info): - log.warn('%s already exists', pkg_info) - return - - log.warn('Creating %s', pkg_info) - f = open(pkg_info, 'w') - try: - f.write(SETUPTOOLS_PKG_INFO) - finally: - f.close() - - pth_file = os.path.join(placeholder, 'setuptools.pth') - log.warn('Creating %s', pth_file) - f = open(pth_file, 'w') - try: - f.write(os.path.join(os.curdir, setuptools_file)) - finally: - f.close() - -@_no_sandbox -def _patch_egg_dir(path): - # let's check if it's already patched - pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') - if os.path.exists(pkg_info): - if _same_content(pkg_info, SETUPTOOLS_PKG_INFO): - log.warn('%s already patched.', pkg_info) - return False - _rename_path(path) - os.mkdir(path) - os.mkdir(os.path.join(path, 'EGG-INFO')) - pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') - f = open(pkg_info, 'w') - try: - f.write(SETUPTOOLS_PKG_INFO) - finally: - f.close() - return True - - -def _before_install(): - log.warn('Before install bootstrap.') - _fake_setuptools() - - -def _under_prefix(location): - if 'install' not in sys.argv: - return True - args = sys.argv[sys.argv.index('install')+1:] - for index, arg in enumerate(args): - for option in ('--root', '--prefix'): - if arg.startswith('%s=' % option): - top_dir = arg.split('root=')[-1] - return location.startswith(top_dir) - elif arg == option: - if len(args) > index: - top_dir = args[index+1] - return location.startswith(top_dir) - elif option == '--user' and USER_SITE is not None: - return location.startswith(USER_SITE) - return True - - -def _fake_setuptools(): - log.warn('Scanning installed packages') - try: - import pkg_resources - except ImportError: - # we're cool - log.warn('Setuptools or Distribute does not seem to be installed.') - return - ws = pkg_resources.working_set - try: - setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools', - replacement=False)) - except TypeError: - # old distribute API - setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools')) - - if setuptools_dist is None: - log.warn('No setuptools distribution found') - return - # detecting if it was already faked - setuptools_location = setuptools_dist.location - log.warn('Setuptools installation detected at %s', setuptools_location) - - # if --root or --preix was provided, and if - # setuptools is not located in them, we don't patch it - if not _under_prefix(setuptools_location): - log.warn('Not patching, --root or --prefix is installing Distribute' - ' in another location') - return - - # let's see if its an egg - if not setuptools_location.endswith('.egg'): - log.warn('Non-egg installation') - res = _remove_flat_installation(setuptools_location) - if not res: - return - else: - log.warn('Egg installation') - pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') - if (os.path.exists(pkg_info) and - _same_content(pkg_info, SETUPTOOLS_PKG_INFO)): - log.warn('Already patched.') - return - log.warn('Patching...') - # let's create a fake egg replacing setuptools one - res = _patch_egg_dir(setuptools_location) - if not res: - return - log.warn('Patched done.') - _relaunch() - - -def _relaunch(): - log.warn('Relaunching...') - # we have to relaunch the process - args = [sys.executable] + sys.argv - sys.exit(subprocess.call(args)) - - -def _extractall(self, path=".", members=None): - """Extract all members from the archive to the current working - directory and set owner, modification time and permissions on - directories afterwards. `path' specifies a different directory - to extract to. `members' is optional and must be a subset of the - list returned by getmembers(). - """ - import copy - import operator - from tarfile import ExtractError - directories = [] - - if members is None: - members = self - - for tarinfo in members: - if tarinfo.isdir(): - # Extract directories with a safe mode. - directories.append(tarinfo) - tarinfo = copy.copy(tarinfo) - tarinfo.mode = 448 # decimal for oct 0700 - self.extract(tarinfo, path) - - # Reverse sort directories. - if sys.version_info < (2, 4): - def sorter(dir1, dir2): - return cmp(dir1.name, dir2.name) - directories.sort(sorter) - directories.reverse() - else: - directories.sort(key=operator.attrgetter('name'), reverse=True) - - # Set correct owner, mtime and filemode on directories. - for tarinfo in directories: - dirpath = os.path.join(path, tarinfo.name) - try: - self.chown(tarinfo, dirpath) - self.utime(tarinfo, dirpath) - self.chmod(tarinfo, dirpath) - except ExtractError: - e = sys.exc_info()[1] - if self.errorlevel > 1: - raise - else: - self._dbg(1, "tarfile: %s" % e) - - -def main(argv, version=DEFAULT_VERSION): - """Install or upgrade setuptools and EasyInstall""" - tarball = download_setuptools() - _install(tarball) - - -if __name__ == '__main__': - main(sys.argv[1:]) diff --git a/ez_setup.py b/ez_setup.py new file mode 100644 index 0000000..b02f3f1 --- /dev/null +++ b/ez_setup.py @@ -0,0 +1,370 @@ +#!python +"""Bootstrap setuptools installation + +If you want to use setuptools in your package's setup.py, just include this +file in the same directory with it, and add this to the top of your setup.py:: + + from ez_setup import use_setuptools + use_setuptools() + +If you want to require a specific version of setuptools, set a download +mirror, or use an alternate download directory, you can do so by supplying +the appropriate options to ``use_setuptools()``. + +This file can also be run as a script to install or upgrade setuptools. +""" +import os +import shutil +import sys +import tempfile +import tarfile +import optparse +import subprocess +import platform + +from distutils import log + +try: + from site import USER_SITE +except ImportError: + USER_SITE = None + +DEFAULT_VERSION = "1.1.6" +DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/" + +def _python_cmd(*args): + args = (sys.executable,) + args + return subprocess.call(args) == 0 + +def _check_call_py24(cmd, *args, **kwargs): + res = subprocess.call(cmd, *args, **kwargs) + class CalledProcessError(Exception): + pass + if not res == 0: + msg = "Command '%s' return non-zero exit status %d" % (cmd, res) + raise CalledProcessError(msg) +vars(subprocess).setdefault('check_call', _check_call_py24) + +def _install(tarball, install_args=()): + # extracting the tarball + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + tar = tarfile.open(tarball) + _extractall(tar) + tar.close() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) + + # installing + log.warn('Installing Setuptools') + if not _python_cmd('setup.py', 'install', *install_args): + log.warn('Something went wrong during the installation.') + log.warn('See the error message above.') + # exitcode will be 2 + return 2 + finally: + os.chdir(old_wd) + shutil.rmtree(tmpdir) + + +def _build_egg(egg, tarball, to_dir): + # extracting the tarball + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + tar = tarfile.open(tarball) + _extractall(tar) + tar.close() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) + + # building an egg + log.warn('Building a Setuptools egg in %s', to_dir) + _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) + + finally: + os.chdir(old_wd) + shutil.rmtree(tmpdir) + # returning the result + log.warn(egg) + if not os.path.exists(egg): + raise IOError('Could not build the egg.') + + +def _do_download(version, download_base, to_dir, download_delay): + egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg' + % (version, sys.version_info[0], sys.version_info[1])) + if not os.path.exists(egg): + tarball = download_setuptools(version, download_base, + to_dir, download_delay) + _build_egg(egg, tarball, to_dir) + sys.path.insert(0, egg) + + # Remove previously-imported pkg_resources if present (see + # https://bitbucket.org/pypa/setuptools/pull-request/7/ for details). + if 'pkg_resources' in sys.modules: + del sys.modules['pkg_resources'] + + import setuptools + setuptools.bootstrap_install_from = egg + + +def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, download_delay=15): + # making sure we use the absolute path + to_dir = os.path.abspath(to_dir) + was_imported = 'pkg_resources' in sys.modules or \ + 'setuptools' in sys.modules + try: + import pkg_resources + except ImportError: + return _do_download(version, download_base, to_dir, download_delay) + try: + pkg_resources.require("setuptools>=" + version) + return + except pkg_resources.VersionConflict: + e = sys.exc_info()[1] + if was_imported: + sys.stderr.write( + "The required version of setuptools (>=%s) is not available,\n" + "and can't be installed while this script is running. Please\n" + "install a more recent version first, using\n" + "'easy_install -U setuptools'." + "\n\n(Currently using %r)\n" % (version, e.args[0])) + sys.exit(2) + else: + del pkg_resources, sys.modules['pkg_resources'] # reload ok + return _do_download(version, download_base, to_dir, + download_delay) + except pkg_resources.DistributionNotFound: + return _do_download(version, download_base, to_dir, + download_delay) + +def download_file_powershell(url, target): + """ + Download the file at url to target using Powershell (which will validate + trust). Raise an exception if the command cannot complete. + """ + target = os.path.abspath(target) + cmd = [ + 'powershell', + '-Command', + "(new-object System.Net.WebClient).DownloadFile(%(url)r, %(target)r)" % vars(), + ] + subprocess.check_call(cmd) + +def has_powershell(): + if platform.system() != 'Windows': + return False + cmd = ['powershell', '-Command', 'echo test'] + devnull = open(os.path.devnull, 'wb') + try: + try: + subprocess.check_call(cmd, stdout=devnull, stderr=devnull) + except: + return False + finally: + devnull.close() + return True + +download_file_powershell.viable = has_powershell + +def download_file_curl(url, target): + cmd = ['curl', url, '--silent', '--output', target] + subprocess.check_call(cmd) + +def has_curl(): + cmd = ['curl', '--version'] + devnull = open(os.path.devnull, 'wb') + try: + try: + subprocess.check_call(cmd, stdout=devnull, stderr=devnull) + except: + return False + finally: + devnull.close() + return True + +download_file_curl.viable = has_curl + +def download_file_wget(url, target): + cmd = ['wget', url, '--quiet', '--output-document', target] + subprocess.check_call(cmd) + +def has_wget(): + cmd = ['wget', '--version'] + devnull = open(os.path.devnull, 'wb') + try: + try: + subprocess.check_call(cmd, stdout=devnull, stderr=devnull) + except: + return False + finally: + devnull.close() + return True + +download_file_wget.viable = has_wget + +def download_file_insecure(url, target): + """ + Use Python to download the file, even though it cannot authenticate the + connection. + """ + try: + from urllib.request import urlopen + except ImportError: + from urllib2 import urlopen + src = dst = None + try: + src = urlopen(url) + # Read/write all in one block, so we don't create a corrupt file + # if the download is interrupted. + data = src.read() + dst = open(target, "wb") + dst.write(data) + finally: + if src: + src.close() + if dst: + dst.close() + +download_file_insecure.viable = lambda: True + +def get_best_downloader(): + downloaders = [ + download_file_powershell, + download_file_curl, + download_file_wget, + download_file_insecure, + ] + + for dl in downloaders: + if dl.viable(): + return dl + +def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, delay=15, + downloader_factory=get_best_downloader): + """Download setuptools from a specified location and return its filename + + `version` should be a valid setuptools version number that is available + as an egg for download under the `download_base` URL (which should end + with a '/'). `to_dir` is the directory where the egg will be downloaded. + `delay` is the number of seconds to pause before an actual download + attempt. + + ``downloader_factory`` should be a function taking no arguments and + returning a function for downloading a URL to a target. + """ + # making sure we use the absolute path + to_dir = os.path.abspath(to_dir) + tgz_name = "setuptools-%s.tar.gz" % version + url = download_base + tgz_name + saveto = os.path.join(to_dir, tgz_name) + if not os.path.exists(saveto): # Avoid repeated downloads + log.warn("Downloading %s", url) + downloader = downloader_factory() + downloader(url, saveto) + return os.path.realpath(saveto) + + +def _extractall(self, path=".", members=None): + """Extract all members from the archive to the current working + directory and set owner, modification time and permissions on + directories afterwards. `path' specifies a different directory + to extract to. `members' is optional and must be a subset of the + list returned by getmembers(). + """ + import copy + import operator + from tarfile import ExtractError + directories = [] + + if members is None: + members = self + + for tarinfo in members: + if tarinfo.isdir(): + # Extract directories with a safe mode. + directories.append(tarinfo) + tarinfo = copy.copy(tarinfo) + tarinfo.mode = 448 # decimal for oct 0700 + self.extract(tarinfo, path) + + # Reverse sort directories. + if sys.version_info < (2, 4): + def sorter(dir1, dir2): + return cmp(dir1.name, dir2.name) + directories.sort(sorter) + directories.reverse() + else: + directories.sort(key=operator.attrgetter('name'), reverse=True) + + # Set correct owner, mtime and filemode on directories. + for tarinfo in directories: + dirpath = os.path.join(path, tarinfo.name) + try: + self.chown(tarinfo, dirpath) + self.utime(tarinfo, dirpath) + self.chmod(tarinfo, dirpath) + except ExtractError: + e = sys.exc_info()[1] + if self.errorlevel > 1: + raise + else: + self._dbg(1, "tarfile: %s" % e) + + +def _build_install_args(options): + """ + Build the arguments to 'python setup.py install' on the setuptools package + """ + install_args = [] + if options.user_install: + if sys.version_info < (2, 6): + log.warn("--user requires Python 2.6 or later") + raise SystemExit(1) + install_args.append('--user') + return install_args + +def _parse_args(): + """ + Parse the command line for options + """ + parser = optparse.OptionParser() + parser.add_option( + '--user', dest='user_install', action='store_true', default=False, + help='install in user site package (requires Python 2.6 or later)') + parser.add_option( + '--download-base', dest='download_base', metavar="URL", + default=DEFAULT_URL, + help='alternative URL from where to download the setuptools package') + parser.add_option( + '--insecure', dest='downloader_factory', action='store_const', + const=lambda: download_file_insecure, default=get_best_downloader, + help='Use internal, non-validating downloader' + ) + options, args = parser.parse_args() + # positional arguments are ignored + return options + +def main(version=DEFAULT_VERSION): + """Install or upgrade setuptools and EasyInstall""" + options = _parse_args() + tarball = download_setuptools(download_base=options.download_base, + downloader_factory=options.downloader_factory) + return _install(tarball, _build_install_args(options)) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/setup.py b/setup.py index 22751ff..5f1ff5d 100755 --- a/setup.py +++ b/setup.py @@ -1,10 +1,12 @@ #!/usr/bin/env python +import re import sys -import distribute_setup -distribute_setup.use_setuptools() +import ez_setup +ez_setup.use_setuptools() from setuptools import setup, Extension + define_macros = [] import ctypes @@ -14,19 +16,24 @@ if ctypes.sizeof(ctypes.c_double) == 8: if iv.contents.value == 0x433fff0102030405: define_macros.append(('BLIST_FLOAT_RADIX_SORT', 1)) +with open('blist/__init__.py') as f: + line = f.readline() + match = re.search(r'= *[\'"](.*)[\'"]', line) + version = match.group(1) + setup(name='blist', - version='1.3.4', + version=version, description='a list-like type with better asymptotic performance and similar performance on small lists', author='Stutzbach Enterprises, LLC', author_email='daniel@stutzbachenterprises.com', url='http://stutzbachenterprises.com/blist/', license = "BSD", keywords = "blist list b+tree btree fast copy-on-write sparse array sortedlist sorted sortedset weak weaksortedlist weaksortedset sorteddict btuple", - ext_modules=[Extension('_blist', ['_blist.c'], + ext_modules=[Extension('blist._blist', ['blist/_blist.c'], define_macros=define_macros, )], + packages=['blist'], provides = ['blist'], - py_modules=['blist', '_sortedlist', '_sorteddict', '_btuple'], test_suite = "test_blist.test_suite", zip_safe = False, # zips are broken on cygwin for C extension modules classifiers = [ diff --git a/test/__init__.py b/test/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/test/btuple_tests.py b/test/btuple_tests.py deleted file mode 100644 index d027ea6..0000000 --- a/test/btuple_tests.py +++ /dev/null @@ -1,161 +0,0 @@ -# Based on Python's tuple_tests.py, licensed under the Python License -# Agreement - -import unittest -import sys -from . import seq_tests -import blist -import random -import gc -from _btuple import btuple -import gc - -class bTupleTest(seq_tests.CommonTest): - type2test = btuple - - def test_constructors(self): - super(bTupleTest, self).test_len() - # calling built-in types without argument must return empty - self.assertEqual(tuple(), ()) - t0_3 = (0, 1, 2, 3) - t0_3_bis = tuple(t0_3) - self.assert_(t0_3 is t0_3_bis) - self.assertEqual(tuple([]), ()) - self.assertEqual(tuple([0, 1, 2, 3]), (0, 1, 2, 3)) - self.assertEqual(tuple(''), ()) - self.assertEqual(tuple('spam'), ('s', 'p', 'a', 'm')) - - def test_truth(self): - super(bTupleTest, self).test_truth() - self.assert_(not ()) - self.assert_((42, )) - - def test_len(self): - super(bTupleTest, self).test_len() - self.assertEqual(len(()), 0) - self.assertEqual(len((0,)), 1) - self.assertEqual(len((0, 1, 2)), 3) - - def test_iadd(self): - super(bTupleTest, self).test_iadd() - u = (0, 1) - u2 = u - u += (2, 3) - self.assert_(u is not u2) - - def test_imul(self): - super(bTupleTest, self).test_imul() - u = (0, 1) - u2 = u - u *= 3 - self.assert_(u is not u2) - - def test_tupleresizebug(self): - # Check that a specific bug in _PyTuple_Resize() is squashed. - def f(): - for i in range(1000): - yield i - self.assertEqual(list(tuple(f())), list(range(1000))) - - def test_hash(self): - # See SF bug 942952: Weakness in tuple hash - # The hash should: - # be non-commutative - # should spread-out closely spaced values - # should not exhibit cancellation in tuples like (x,(x,y)) - # should be distinct from element hashes: hash(x)!=hash((x,)) - # This test exercises those cases. - # For a pure random hash and N=50, the expected number of occupied - # buckets when tossing 252,600 balls into 2**32 buckets - # is 252,592.6, or about 7.4 expected collisions. The - # standard deviation is 2.73. On a box with 64-bit hash - # codes, no collisions are expected. Here we accept no - # more than 15 collisions. Any worse and the hash function - # is sorely suspect. - - N=50 - base = list(range(N)) - xp = [(i, j) for i in base for j in base] - inps = base + [(i, j) for i in base for j in xp] + \ - [(i, j) for i in xp for j in base] + xp + list(zip(base)) - collisions = len(inps) - len(set(map(hash, inps))) - self.assert_(collisions <= 15) - - def test_repr(self): - l0 = btuple() - l2 = btuple((0, 1, 2)) - a0 = self.type2test(l0) - a2 = self.type2test(l2) - - self.assertEqual(str(a0), repr(l0)) - self.assertEqual(str(a2), repr(l2)) - self.assertEqual(repr(a0), "btuple(())") - self.assertEqual(repr(a2), "btuple((0, 1, 2))") - - def _not_tracked(self, t): - if sys.version_info[0] < 3: - return - else: # pragma: no cover - # Nested tuples can take several collections to untrack - gc.collect() - gc.collect() - self.assertFalse(gc.is_tracked(t), t) - - def _tracked(self, t): - if sys.version_info[0] < 3: - return - else: # pragma: no cover - self.assertTrue(gc.is_tracked(t), t) - gc.collect() - gc.collect() - self.assertTrue(gc.is_tracked(t), t) - - def test_track_literals(self): - # Test GC-optimization of tuple literals - x, y, z = 1.5, "a", [] - - self._not_tracked(()) - self._not_tracked((1,)) - self._not_tracked((1, 2)) - self._not_tracked((1, 2, "a")) - self._not_tracked((1, 2, (None, True, False, ()), int)) - self._not_tracked((object(),)) - self._not_tracked(((1, x), y, (2, 3))) - - # Tuples with mutable elements are always tracked, even if those - # elements are not tracked right now. - self._tracked(([],)) - self._tracked(([1],)) - self._tracked(({},)) - self._tracked((set(),)) - self._tracked((x, y, z)) - - def check_track_dynamic(self, tp, always_track): - x, y, z = 1.5, "a", [] - - check = self._tracked if always_track else self._not_tracked - check(tp()) - check(tp([])) - check(tp(set())) - check(tp([1, x, y])) - check(tp(obj for obj in [1, x, y])) - check(tp(set([1, x, y]))) - check(tp(tuple([obj]) for obj in [1, x, y])) - check(tuple(tp([obj]) for obj in [1, x, y])) - - self._tracked(tp([z])) - self._tracked(tp([[x, y]])) - self._tracked(tp([{x: y}])) - self._tracked(tp(obj for obj in [x, y, z])) - self._tracked(tp(tuple([obj]) for obj in [x, y, z])) - self._tracked(tuple(tp([obj]) for obj in [x, y, z])) - - def test_track_dynamic(self): - # Test GC-optimization of dynamically constructed tuples. - self.check_track_dynamic(tuple, False) - - def test_track_subtypes(self): - # Tuple subtypes must always be tracked - class MyTuple(tuple): - pass - self.check_track_dynamic(MyTuple, True) diff --git a/test/list_tests.py b/test/list_tests.py deleted file mode 100644 index 53976a9..0000000 --- a/test/list_tests.py +++ /dev/null @@ -1,558 +0,0 @@ -# This file taken from Python, licensed under the Python License Agreement - -from __future__ import print_function -""" -Tests common to list and UserList.UserList -""" - -import sys -import os - -from . import unittest -from test import test_support, seq_tests - -from decimal import Decimal - -def CmpToKey(mycmp): - 'Convert a cmp= function into a key= function' - class K(object): - def __init__(self, obj): - self.obj = obj - def __lt__(self, other): - return mycmp(self.obj, other.obj) == -1 - return K - -class CommonTest(seq_tests.CommonTest): - - def test_init(self): - # Iterable arg is optional - self.assertEqual(self.type2test([]), self.type2test()) - - # Init clears previous values - a = self.type2test([1, 2, 3]) - a.__init__() - self.assertEqual(a, self.type2test([])) - - # Init overwrites previous values - a = self.type2test([1, 2, 3]) - a.__init__([4, 5, 6]) - self.assertEqual(a, self.type2test([4, 5, 6])) - - # Mutables always return a new object - b = self.type2test(a) - self.assertNotEqual(id(a), id(b)) - self.assertEqual(a, b) - - def test_repr(self): - l0 = [] - l2 = [0, 1, 2] - a0 = self.type2test(l0) - a2 = self.type2test(l2) - - self.assertEqual(str(a0), 'blist(%s)' % str(l0)) - self.assertEqual(repr(a0), 'blist(%s)' % repr(l0)) - self.assertEqual(repr(a2), 'blist(%s)' % repr(l2)) - self.assertEqual(str(a2), "blist([0, 1, 2])") - self.assertEqual(repr(a2), "blist([0, 1, 2])") - - a2.append(a2) - a2.append(3) - self.assertEqual(str(a2), "blist([0, 1, 2, [...], 3])") - self.assertEqual(repr(a2), "blist([0, 1, 2, [...], 3])") - - def test_print(self): - d = self.type2test(range(200)) - d.append(d) - d.extend(range(200,400)) - d.append(d) - d.append(400) - try: - fo = open(test_support.TESTFN, "w") - fo.write(str(d)) - fo.close() - fo = open(test_support.TESTFN, "r") - self.assertEqual(fo.read(), repr(d)) - finally: - fo.close() - os.remove(test_support.TESTFN) - - def test_set_subscript(self): - a = self.type2test(list(range(20))) - self.assertRaises(ValueError, a.__setitem__, slice(0, 10, 0), [1,2,3]) - self.assertRaises(TypeError, a.__setitem__, slice(0, 10), 1) - self.assertRaises(ValueError, a.__setitem__, slice(0, 10, 2), [1,2]) - self.assertRaises(TypeError, a.__getitem__, 'x', 1) - a[slice(2,10,3)] = [1,2,3] - self.assertEqual(a, self.type2test([0, 1, 1, 3, 4, 2, 6, 7, 3, - 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19])) - - def test_reversed(self): - a = self.type2test(list(range(20))) - r = reversed(a) - self.assertEqual(list(r), self.type2test(list(range(19, -1, -1)))) - if hasattr(r, '__next__'): # pragma: no cover - self.assertRaises(StopIteration, r.__next__) - else: # pragma: no cover - self.assertRaises(StopIteration, r.next) - self.assertEqual(list(reversed(self.type2test())), - self.type2test()) - - def test_setitem(self): - a = self.type2test([0, 1]) - a[0] = 0 - a[1] = 100 - self.assertEqual(a, self.type2test([0, 100])) - a[-1] = 200 - self.assertEqual(a, self.type2test([0, 200])) - a[-2] = 100 - self.assertEqual(a, self.type2test([100, 200])) - self.assertRaises(IndexError, a.__setitem__, -3, 200) - self.assertRaises(IndexError, a.__setitem__, 2, 200) - - a = self.type2test([]) - self.assertRaises(IndexError, a.__setitem__, 0, 200) - self.assertRaises(IndexError, a.__setitem__, -1, 200) - self.assertRaises(TypeError, a.__setitem__) - - a = self.type2test([0,1,2,3,4]) - a[0] = 1 - a[1] = 2 - a[2] = 3 - self.assertEqual(a, self.type2test([1,2,3,3,4])) - a[0] = 5 - a[1] = 6 - a[2] = 7 - self.assertEqual(a, self.type2test([5,6,7,3,4])) - a[-2] = 88 - a[-1] = 99 - self.assertEqual(a, self.type2test([5,6,7,88,99])) - a[-2] = 8 - a[-1] = 9 - self.assertEqual(a, self.type2test([5,6,7,8,9])) - - def test_delitem(self): - a = self.type2test([0, 1]) - del a[1] - self.assertEqual(a, [0]) - del a[0] - self.assertEqual(a, []) - - a = self.type2test([0, 1]) - del a[-2] - self.assertEqual(a, [1]) - del a[-1] - self.assertEqual(a, []) - - a = self.type2test([0, 1]) - self.assertRaises(IndexError, a.__delitem__, -3) - self.assertRaises(IndexError, a.__delitem__, 2) - - a = self.type2test([]) - self.assertRaises(IndexError, a.__delitem__, 0) - - self.assertRaises(TypeError, a.__delitem__) - - def test_setslice(self): - l = [0, 1] - a = self.type2test(l) - - for i in range(-3, 4): - a[:i] = l[:i] - self.assertEqual(a, l) - a2 = a[:] - a2[:i] = a[:i] - self.assertEqual(a2, a) - a[i:] = l[i:] - self.assertEqual(a, l) - a2 = a[:] - a2[i:] = a[i:] - self.assertEqual(a2, a) - for j in range(-3, 4): - a[i:j] = l[i:j] - self.assertEqual(a, l) - a2 = a[:] - a2[i:j] = a[i:j] - self.assertEqual(a2, a) - - aa2 = a2[:] - aa2[:0] = [-2, -1] - self.assertEqual(aa2, [-2, -1, 0, 1]) - aa2[0:] = [] - self.assertEqual(aa2, []) - - a = self.type2test([1, 2, 3, 4, 5]) - a[:-1] = a - self.assertEqual(a, self.type2test([1, 2, 3, 4, 5, 5])) - a = self.type2test([1, 2, 3, 4, 5]) - a[1:] = a - self.assertEqual(a, self.type2test([1, 1, 2, 3, 4, 5])) - a = self.type2test([1, 2, 3, 4, 5]) - a[1:-1] = a - self.assertEqual(a, self.type2test([1, 1, 2, 3, 4, 5, 5])) - - a = self.type2test([]) - a[:] = tuple(range(10)) - self.assertEqual(a, self.type2test(list(range(10)))) - - if sys.version_info[0] < 3: - self.assertRaises(TypeError, a.__setslice__, 0, 1, 5) - self.assertRaises(TypeError, a.__setslice__) - - def test_delslice(self): - a = self.type2test([0, 1]) - del a[1:2] - del a[0:1] - self.assertEqual(a, self.type2test([])) - - a = self.type2test([0, 1]) - del a[1:2] - del a[0:1] - self.assertEqual(a, self.type2test([])) - - a = self.type2test([0, 1]) - del a[-2:-1] - self.assertEqual(a, self.type2test([1])) - - a = self.type2test([0, 1]) - del a[-2:-1] - self.assertEqual(a, self.type2test([1])) - - a = self.type2test([0, 1]) - del a[1:] - del a[:1] - self.assertEqual(a, self.type2test([])) - - a = self.type2test([0, 1]) - del a[1:] - del a[:1] - self.assertEqual(a, self.type2test([])) - - a = self.type2test([0, 1]) - del a[-1:] - self.assertEqual(a, self.type2test([0])) - - a = self.type2test([0, 1]) - del a[-1:] - self.assertEqual(a, self.type2test([0])) - - a = self.type2test([0, 1]) - del a[:] - self.assertEqual(a, self.type2test([])) - - def test_append(self): - a = self.type2test([]) - a.append(0) - a.append(1) - a.append(2) - self.assertEqual(a, self.type2test([0, 1, 2])) - - self.assertRaises(TypeError, a.append) - - def test_extend(self): - a1 = self.type2test([0]) - a2 = self.type2test((0, 1)) - a = a1[:] - a.extend(a2) - self.assertEqual(a, a1 + a2) - - a.extend(self.type2test([])) - self.assertEqual(a, a1 + a2) - - a.extend(a) - self.assertEqual(a, self.type2test([0, 0, 1, 0, 0, 1])) - - a = self.type2test("spam") - a.extend("eggs") - self.assertEqual(a, list("spameggs")) - - self.assertRaises(TypeError, a.extend, None) - - self.assertRaises(TypeError, a.extend) - - def test_insert(self): - a = self.type2test([0, 1, 2]) - a.insert(0, -2) - a.insert(1, -1) - a.insert(2, 0) - self.assertEqual(a, [-2, -1, 0, 0, 1, 2]) - - b = a[:] - b.insert(-2, "foo") - b.insert(-200, "left") - b.insert(200, "right") - self.assertEqual(b, self.type2test(["left",-2,-1,0,0,"foo",1,2,"right"])) - - self.assertRaises(TypeError, a.insert) - - def test_pop(self): - a = self.type2test([-1, 0, 1]) - a.pop() - self.assertEqual(a, [-1, 0]) - a.pop(0) - self.assertEqual(a, [0]) - self.assertRaises(IndexError, a.pop, 5) - a.pop(0) - self.assertEqual(a, []) - self.assertRaises(IndexError, a.pop) - self.assertRaises(TypeError, a.pop, 42, 42) - a = self.type2test([0, 10, 20, 30, 40]) - - def test_remove(self): - a = self.type2test([0, 0, 1]) - a.remove(1) - self.assertEqual(a, [0, 0]) - a.remove(0) - self.assertEqual(a, [0]) - a.remove(0) - self.assertEqual(a, []) - - self.assertRaises(ValueError, a.remove, 0) - - self.assertRaises(TypeError, a.remove) - - class BadExc(Exception): - pass - - class BadCmp: - def __eq__(self, other): - if other == 2: - raise BadExc() - return False - - a = self.type2test([0, 1, 2, 3]) - self.assertRaises(BadExc, a.remove, BadCmp()) - - class BadCmp2: - def __eq__(self, other): - raise BadExc() - - d = self.type2test('abcdefghcij') - d.remove('c') - self.assertEqual(d, self.type2test('abdefghcij')) - d.remove('c') - self.assertEqual(d, self.type2test('abdefghij')) - self.assertRaises(ValueError, d.remove, 'c') - self.assertEqual(d, self.type2test('abdefghij')) - - # Handle comparison errors - d = self.type2test(['a', 'b', BadCmp2(), 'c']) - e = self.type2test(d) - self.assertRaises(BadExc, d.remove, 'c') - for x, y in zip(d, e): - # verify that original order and values are retained. - self.assert_(x is y) - - def test_count(self): - a = self.type2test([0, 1, 2])*3 - self.assertEqual(a.count(0), 3) - self.assertEqual(a.count(1), 3) - self.assertEqual(a.count(3), 0) - - self.assertRaises(TypeError, a.count) - - class BadExc(Exception): - pass - - class BadCmp: - def __eq__(self, other): - if other == 2: - raise BadExc() - return False - - self.assertRaises(BadExc, a.count, BadCmp()) - - def test_index(self): - u = self.type2test([0, 1]) - self.assertEqual(u.index(0), 0) - self.assertEqual(u.index(1), 1) - self.assertRaises(ValueError, u.index, 2) - - u = self.type2test([-2, -1, 0, 0, 1, 2]) - self.assertEqual(u.count(0), 2) - self.assertEqual(u.index(0), 2) - self.assertEqual(u.index(0, 2), 2) - self.assertEqual(u.index(-2, -10), 0) - self.assertEqual(u.index(0, 3), 3) - self.assertEqual(u.index(0, 3, 4), 3) - self.assertRaises(ValueError, u.index, 2, 0, -10) - - self.assertRaises(TypeError, u.index) - - class BadExc(Exception): - pass - - class BadCmp: - def __eq__(self, other): - if other == 2: - raise BadExc() - return False - - a = self.type2test([0, 1, 2, 3]) - self.assertRaises(BadExc, a.index, BadCmp()) - - a = self.type2test([-2, -1, 0, 0, 1, 2]) - self.assertEqual(a.index(0), 2) - self.assertEqual(a.index(0, 2), 2) - self.assertEqual(a.index(0, -4), 2) - self.assertEqual(a.index(-2, -10), 0) - self.assertEqual(a.index(0, 3), 3) - self.assertEqual(a.index(0, -3), 3) - self.assertEqual(a.index(0, 3, 4), 3) - self.assertEqual(a.index(0, -3, -2), 3) - self.assertEqual(a.index(0, -4*sys.maxsize, 4*sys.maxsize), 2) - self.assertRaises(ValueError, a.index, 0, 4*sys.maxsize,-4*sys.maxsize) - self.assertRaises(ValueError, a.index, 2, 0, -10) - a.remove(0) - self.assertRaises(ValueError, a.index, 2, 0, 4) - self.assertEqual(a, self.type2test([-2, -1, 0, 1, 2])) - - # Test modifying the list during index's iteration - class EvilCmp: - def __init__(self, victim): - self.victim = victim - def __eq__(self, other): - del self.victim[:] - return False - a = self.type2test() - a[:] = [EvilCmp(a) for _ in range(100)] - # This used to seg fault before patch #1005778 - self.assertRaises(ValueError, a.index, None) - - def test_reverse(self): - u = self.type2test([-2, -1, 0, 1, 2]) - u2 = u[:] - u.reverse() - self.assertEqual(u, [2, 1, 0, -1, -2]) - u.reverse() - self.assertEqual(u, u2) - - self.assertRaises(TypeError, u.reverse, 42) - - def test_sort(self): - u = self.type2test([1, 0]) - u.sort() - self.assertEqual(u, [0, 1]) - - u = self.type2test([2,1,0,-1,-2]) - u.sort() - self.assertEqual(u, self.type2test([-2,-1,0,1,2])) - - self.assertRaises(TypeError, u.sort, 42, 42) - - a = self.type2test(reversed(list(range(512)))) - a.sort() - self.assertEqual(a, self.type2test(list(range(512)))) - - def revcmp(a, b): # pragma: no cover - if a == b: - return 0 - elif a < b: - return 1 - else: # a > b - return -1 - u.sort(key=CmpToKey(revcmp)) - self.assertEqual(u, self.type2test([2,1,0,-1,-2])) - - # The following dumps core in unpatched Python 1.5: - def myComparison(x,y): - xmod, ymod = x%3, y%7 - if xmod == ymod: - return 0 - elif xmod < ymod: - return -1 - else: # xmod > ymod - return 1 - z = self.type2test(list(range(12))) - z.sort(key=CmpToKey(myComparison)) - - self.assertRaises(TypeError, z.sort, 2) - - def selfmodifyingComparison(x,y): - z.append(1) - return cmp(x, y) - self.assertRaises(ValueError, z.sort, key=CmpToKey(selfmodifyingComparison)) - - if sys.version_info[0] < 3: - self.assertRaises(TypeError, z.sort, lambda x, y: 's') - - self.assertRaises(TypeError, z.sort, 42, 42, 42, 42) - - def test_slice(self): - u = self.type2test("spam") - u[:2] = "h" - self.assertEqual(u, list("ham")) - - def test_iadd(self): - super(CommonTest, self).test_iadd() - u = self.type2test([0, 1]) - u2 = u - u += [2, 3] - self.assert_(u is u2) - - u = self.type2test("spam") - u += "eggs" - self.assertEqual(u, self.type2test("spameggs")) - - self.assertRaises(TypeError, u.__iadd__, None) - - def test_imul(self): - u = self.type2test([0, 1]) - u *= 3 - self.assertEqual(u, self.type2test([0, 1, 0, 1, 0, 1])) - u *= 0 - self.assertEqual(u, self.type2test([])) - s = self.type2test([]) - oldid = id(s) - s *= 10 - self.assertEqual(id(s), oldid) - - def test_extendedslicing(self): - # subscript - a = self.type2test([0,1,2,3,4]) - - # deletion - del a[::2] - self.assertEqual(a, self.type2test([1,3])) - a = self.type2test(list(range(5))) - del a[1::2] - self.assertEqual(a, self.type2test([0,2,4])) - a = self.type2test(list(range(5))) - del a[1::-2] - self.assertEqual(a, self.type2test([0,2,3,4])) - a = self.type2test(list(range(10))) - del a[::1000] - self.assertEqual(a, self.type2test([1, 2, 3, 4, 5, 6, 7, 8, 9])) - # assignment - a = self.type2test(list(range(10))) - a[::2] = [-1]*5 - self.assertEqual(a, self.type2test([-1, 1, -1, 3, -1, 5, -1, 7, -1, 9])) - a = self.type2test(list(range(10))) - a[::-4] = [10]*3 - self.assertEqual(a, self.type2test([0, 10, 2, 3, 4, 10, 6, 7, 8 ,10])) - a = self.type2test(list(range(4))) - a[::-1] = a - self.assertEqual(a, self.type2test([3, 2, 1, 0])) - a = self.type2test(list(range(10))) - b = a[:] - c = a[:] - a[2:3] = self.type2test(["two", "elements"]) - b[slice(2,3)] = self.type2test(["two", "elements"]) - c[2:3:] = self.type2test(["two", "elements"]) - self.assertEqual(a, b) - self.assertEqual(a, c) - a = self.type2test(list(range(10))) - a[::2] = tuple(range(5)) - self.assertEqual(a, self.type2test([0, 1, 1, 3, 2, 5, 3, 7, 4, 9])) - - def test_constructor_exception_handling(self): - # Bug #1242657 - class Iter(object): - def next(self): - raise KeyboardInterrupt - __next__ = next - - class F(object): - def __iter__(self): - return Iter() - self.assertRaises(KeyboardInterrupt, self.type2test, F()) diff --git a/test/mapping_tests.py b/test/mapping_tests.py deleted file mode 100644 index e51756f..0000000 --- a/test/mapping_tests.py +++ /dev/null @@ -1,678 +0,0 @@ -# This file taken from Python, licensed under the Python License Agreement - -# tests common to dict and UserDict -import unittest, sys -import collections -try: - from collections import UserDict # Python 3 -except ImportError: - from UserDict import UserDict # Python 2 - -class BasicTestMappingProtocol(unittest.TestCase): - # This base class can be used to check that an object conforms to the - # mapping protocol - - # Functions that can be useful to override to adapt to dictionary - # semantics - type2test = None # which class is being tested (overwrite in subclasses) - - def _reference(self): # pragma: no cover - """Return a dictionary of values which are invariant by storage - in the object under test.""" - return {1:2, "key1":"value1", "key2":(1,2,3)} - def _empty_mapping(self): - """Return an empty mapping object""" - return self.type2test() - def _full_mapping(self, data): - """Return a mapping object with the value contained in data - dictionary""" - x = self._empty_mapping() - for key, value in data.items(): - x[key] = value - return x - - def __init__(self, *args, **kw): - unittest.TestCase.__init__(self, *args, **kw) - self.reference = self._reference().copy() - - # A (key, value) pair not in the mapping - key, value = self.reference.popitem() - self.other = {key:value} - - # A (key, value) pair in the mapping - key, value = self.reference.popitem() - self.inmapping = {key:value} - self.reference[key] = value - - def test_read(self): - # Test for read only operations on mapping - p = self._empty_mapping() - p1 = dict(p) #workaround for singleton objects - d = self._full_mapping(self.reference) - if d is p: # pragma: no cover - p = p1 - #Indexing - for key, value in self.reference.items(): - self.assertEqual(d[key], value) - knownkey = list(self.other.keys())[0] - self.failUnlessRaises(KeyError, lambda:d[knownkey]) - #len - self.assertEqual(len(p), 0) - self.assertEqual(len(d), len(self.reference)) - #__contains__ - for k in self.reference: - self.assert_(k in d) - for k in self.other: - self.failIf(k in d) - #cmp - self.assertEqual(p, p) - self.assertEqual(d, d) - self.assertNotEqual(p, d) - self.assertNotEqual(d, p) - #__non__zero__ - if p: self.fail("Empty mapping must compare to False") - if not d: self.fail("Full mapping must compare to True") - # keys(), items(), iterkeys() ... - def check_iterandlist(iter, lst, ref): - if sys.version_info[0] < 3: # pragma: no cover - self.assert_(hasattr(iter, 'next')) - else: # pragma: no cover - self.assert_(hasattr(iter, '__next__')) - self.assert_(hasattr(iter, '__iter__')) - x = list(iter) - self.assert_(set(x)==set(lst)==set(ref)) - check_iterandlist(iter(d.keys()), list(d.keys()), - self.reference.keys()) - check_iterandlist(iter(d), list(d.keys()), self.reference.keys()) - check_iterandlist(iter(d.values()), list(d.values()), - self.reference.values()) - check_iterandlist(iter(d.items()), list(d.items()), - self.reference.items()) - #get - key, value = next(iter(d.items())) - knownkey, knownvalue = next(iter(self.other.items())) - self.assertEqual(d.get(key, knownvalue), value) - self.assertEqual(d.get(knownkey, knownvalue), knownvalue) - self.failIf(knownkey in d) - - def test_write(self): - # Test for write operations on mapping - p = self._empty_mapping() - #Indexing - for key, value in self.reference.items(): - p[key] = value - self.assertEqual(p[key], value) - for key in self.reference.keys(): - del p[key] - self.failUnlessRaises(KeyError, lambda:p[key]) - p = self._empty_mapping() - #update - p.update(self.reference) - self.assertEqual(dict(p), self.reference) - items = list(p.items()) - p = self._empty_mapping() - p.update(items) - self.assertEqual(dict(p), self.reference) - d = self._full_mapping(self.reference) - #setdefault - key, value = next(iter(d.items())) - knownkey, knownvalue = next(iter(self.other.items())) - self.assertEqual(d.setdefault(key, knownvalue), value) - self.assertEqual(d[key], value) - self.assertEqual(d.setdefault(knownkey, knownvalue), knownvalue) - self.assertEqual(d[knownkey], knownvalue) - #pop - self.assertEqual(d.pop(knownkey), knownvalue) - self.failIf(knownkey in d) - self.assertRaises(KeyError, d.pop, knownkey) - default = 909 - d[knownkey] = knownvalue - self.assertEqual(d.pop(knownkey, default), knownvalue) - self.failIf(knownkey in d) - self.assertEqual(d.pop(knownkey, default), default) - #popitem - key, value = d.popitem() - self.failIf(key in d) - self.assertEqual(value, self.reference[key]) - p=self._empty_mapping() - self.assertRaises(KeyError, p.popitem) - - def test_constructor(self): - self.assertEqual(self._empty_mapping(), self._empty_mapping()) - - def test_bool(self): - self.assert_(not self._empty_mapping()) - self.assert_(self.reference) - self.assert_(bool(self._empty_mapping()) is False) - self.assert_(bool(self.reference) is True) - - def test_keys(self): - d = self._empty_mapping() - self.assertEqual(list(d.keys()), []) - d = self.reference - self.assert_(list(self.inmapping.keys())[0] in d.keys()) - self.assert_(list(self.other.keys())[0] not in d.keys()) - self.assertRaises(TypeError, d.keys, None) - - def test_values(self): - d = self._empty_mapping() - self.assertEqual(list(d.values()), []) - - self.assertRaises(TypeError, d.values, None) - - def test_items(self): - d = self._empty_mapping() - self.assertEqual(list(d.items()), []) - - self.assertRaises(TypeError, d.items, None) - - def test_len(self): - d = self._empty_mapping() - self.assertEqual(len(d), 0) - - def test_getitem(self): - d = self.reference - self.assertEqual(d[list(self.inmapping.keys())[0]], - list(self.inmapping.values())[0]) - - self.assertRaises(TypeError, d.__getitem__) - - def test_update(self): - # mapping argument - d = self._empty_mapping() - d.update(self.other) - self.assertEqual(list(d.items()), list(self.other.items())) - - # No argument - d = self._empty_mapping() - d.update() - self.assertEqual(d, self._empty_mapping()) - - # item sequence - d = self._empty_mapping() - d.update(self.other.items()) - self.assertEqual(list(d.items()), list(self.other.items())) - - # Iterator - d = self._empty_mapping() - d.update(self.other.items()) - self.assertEqual(list(d.items()), list(self.other.items())) - - # FIXME: Doesn't work with UserDict - # self.assertRaises((TypeError, AttributeError), d.update, None) - self.assertRaises((TypeError, AttributeError), d.update, 42) - - outerself = self - class SimpleUserDict: - def __init__(self): - self.d = outerself.reference - def keys(self): - return self.d.keys() - def __getitem__(self, i): - return self.d[i] - d.clear() - d.update(SimpleUserDict()) - i1 = sorted(d.items()) - i2 = sorted(self.reference.items()) - self.assertEqual(i1, i2) - - class Exc(Exception): pass - - d = self._empty_mapping() - class FailingUserDict: - def keys(self): - raise Exc - self.assertRaises(Exc, d.update, FailingUserDict()) - - d.clear() - - class FailingUserDict: - def keys(self): - class BogonIter: - def __init__(self): - self.i = 1 - def __iter__(self): - return self - def __next__(self): - if self.i: - self.i = 0 - return 'a' - raise Exc - next = __next__ - return BogonIter() - def __getitem__(self, key): - return key - self.assertRaises(Exc, d.update, FailingUserDict()) - - class FailingUserDict: - def keys(self): - class BogonIter: - def __init__(self): - self.i = ord('a') - def __iter__(self): - return self - def __next__(self): - if self.i <= ord('z'): - rtn = chr(self.i) - self.i += 1 - return rtn - else: # pragma: no cover - raise StopIteration - next = __next__ - return BogonIter() - def __getitem__(self, key): - raise Exc - self.assertRaises(Exc, d.update, FailingUserDict()) - - d = self._empty_mapping() - class badseq(object): - def __iter__(self): - return self - def __next__(self): - raise Exc() - next = __next__ - - self.assertRaises(Exc, d.update, badseq()) - - self.assertRaises(ValueError, d.update, [(1, 2, 3)]) - - # no test_fromkeys or test_copy as both os.environ and selves don't support it - - def test_get(self): - d = self._empty_mapping() - self.assert_(d.get(list(self.other.keys())[0]) is None) - self.assertEqual(d.get(list(self.other.keys())[0], 3), 3) - d = self.reference - self.assert_(d.get(list(self.other.keys())[0]) is None) - self.assertEqual(d.get(list(self.other.keys())[0], 3), 3) - self.assertEqual(d.get(list(self.inmapping.keys())[0]), - list(self.inmapping.values())[0]) - self.assertEqual(d.get(list(self.inmapping.keys())[0], 3), - list(self.inmapping.values())[0]) - self.assertRaises(TypeError, d.get) - self.assertRaises(TypeError, d.get, None, None, None) - - def test_setdefault(self): - d = self._empty_mapping() - self.assertRaises(TypeError, d.setdefault) - - def test_popitem(self): - d = self._empty_mapping() - self.assertRaises(KeyError, d.popitem) - self.assertRaises(TypeError, d.popitem, 42) - - def test_pop(self): - d = self._empty_mapping() - k, v = list(self.inmapping.items())[0] - d[k] = v - self.assertRaises(KeyError, d.pop, list(self.other.keys())[0]) - - self.assertEqual(d.pop(k), v) - self.assertEqual(len(d), 0) - - self.assertRaises(KeyError, d.pop, k) - - -class TestMappingProtocol(BasicTestMappingProtocol): - def test_constructor(self): - BasicTestMappingProtocol.test_constructor(self) - self.assert_(self._empty_mapping() is not self._empty_mapping()) - self.assertEqual(self.type2test(x=1, y=2), self._full_mapping({"x": 1, "y": 2})) - - def test_bool(self): - BasicTestMappingProtocol.test_bool(self) - self.assert_(not self._empty_mapping()) - self.assert_(self._full_mapping({"x": "y"})) - self.assert_(bool(self._empty_mapping()) is False) - self.assert_(bool(self._full_mapping({"x": "y"})) is True) - - def test_keys(self): - BasicTestMappingProtocol.test_keys(self) - d = self._empty_mapping() - self.assertEqual(list(d.keys()), []) - d = self._full_mapping({'a': 1, 'b': 2}) - k = d.keys() - self.assert_('a' in k) - self.assert_('b' in k) - self.assert_('c' not in k) - - def test_values(self): - BasicTestMappingProtocol.test_values(self) - d = self._full_mapping({1:2}) - self.assertEqual(list(d.values()), [2]) - - def test_items(self): - BasicTestMappingProtocol.test_items(self) - - d = self._full_mapping({1:2}) - self.assertEqual(list(d.items()), [(1, 2)]) - - def test_contains(self): - d = self._empty_mapping() - self.assert_(not ('a' in d)) - self.assert_('a' not in d) - d = self._full_mapping({'a': 1, 'b': 2}) - self.assert_('a' in d) - self.assert_('b' in d) - self.assert_('c' not in d) - - self.assertRaises(TypeError, d.__contains__) - - def test_len(self): - BasicTestMappingProtocol.test_len(self) - d = self._full_mapping({'a': 1, 'b': 2}) - self.assertEqual(len(d), 2) - - def test_getitem(self): - BasicTestMappingProtocol.test_getitem(self) - d = self._full_mapping({'a': 1, 'b': 2}) - self.assertEqual(d['a'], 1) - self.assertEqual(d['b'], 2) - d['c'] = 3 - d['a'] = 4 - self.assertEqual(d['c'], 3) - self.assertEqual(d['a'], 4) - del d['b'] - self.assertEqual(d, self._full_mapping({'a': 4, 'c': 3})) - - self.assertRaises(TypeError, d.__getitem__) - - def test_clear(self): - d = self._full_mapping({1:1, 2:2, 3:3}) - d.clear() - self.assertEqual(d, self._full_mapping({})) - - self.assertRaises(TypeError, d.clear, None) - - def test_update(self): - BasicTestMappingProtocol.test_update(self) - # mapping argument - d = self._empty_mapping() - d.update({1:100}) - d.update({2:20}) - d.update({1:1, 2:2, 3:3}) - self.assertEqual(d, self._full_mapping({1:1, 2:2, 3:3})) - - # no argument - d.update() - self.assertEqual(d, self._full_mapping({1:1, 2:2, 3:3})) - - # keyword arguments - d = self._empty_mapping() - d.update(x=100) - d.update(y=20) - d.update(x=1, y=2, z=3) - self.assertEqual(d, self._full_mapping({"x":1, "y":2, "z":3})) - - # item sequence - d = self._empty_mapping() - d.update([("x", 100), ("y", 20)]) - self.assertEqual(d, self._full_mapping({"x":100, "y":20})) - - # Both item sequence and keyword arguments - d = self._empty_mapping() - d.update([("x", 100), ("y", 20)], x=1, y=2) - self.assertEqual(d, self._full_mapping({"x":1, "y":2})) - - # iterator - d = self._full_mapping({1:3, 2:4}) - d.update(self._full_mapping({1:2, 3:4, 5:6}).items()) - self.assertEqual(d, self._full_mapping({1:2, 2:4, 3:4, 5:6})) - - class SimpleUserDict: - def __init__(self): - self.d = {1:1, 2:2, 3:3} - def keys(self): - return self.d.keys() - def __getitem__(self, i): - return self.d[i] - d.clear() - d.update(SimpleUserDict()) - self.assertEqual(d, self._full_mapping({1:1, 2:2, 3:3})) - - def test_fromkeys(self): - self.assertEqual(self.type2test.fromkeys('abc'), self._full_mapping({'a':None, 'b':None, 'c':None})) - d = self._empty_mapping() - self.assert_(not(d.fromkeys('abc') is d)) - self.assertEqual(d.fromkeys('abc'), self._full_mapping({'a':None, 'b':None, 'c':None})) - self.assertEqual(d.fromkeys((4,5),0), self._full_mapping({4:0, 5:0})) - self.assertEqual(d.fromkeys([]), self._full_mapping({})) - def g(): - yield 1 - self.assertEqual(d.fromkeys(g()), self._full_mapping({1:None})) - self.assertRaises(TypeError, {}.fromkeys, 3) - class dictlike(self.type2test): pass - self.assertEqual(dictlike.fromkeys('a'), self._full_mapping({'a':None})) - self.assertEqual(dictlike().fromkeys('a'), self._full_mapping({'a':None})) - self.assert_(dictlike.fromkeys('a').__class__ is dictlike) - self.assert_(dictlike().fromkeys('a').__class__ is dictlike) - # FIXME: the following won't work with UserDict, because it's an old style class - # self.assert_(type(dictlike.fromkeys('a')) is dictlike) - class mydict(self.type2test): - def __new__(cls): - return UserDict() - ud = mydict.fromkeys('ab') - self.assertEqual(ud, {'a':None, 'b':None}) - # FIXME: the following won't work with UserDict, because it's an old style class - # self.assert_(isinstance(ud, collections.UserDict)) - self.assertRaises(TypeError, dict.fromkeys) - - class Exc(Exception): pass - - class baddict1(self.type2test): - def __init__(self): - raise Exc() - - self.assertRaises(Exc, baddict1.fromkeys, [1]) - - class BadSeq(object): - def __iter__(self): - return self - def __next__(self): - raise Exc() - next = __next__ - - self.assertRaises(Exc, self.type2test.fromkeys, BadSeq()) - - class baddict2(self.type2test): - def __setitem__(self, key, value): - raise Exc() - - self.assertRaises(Exc, baddict2.fromkeys, [1]) - - def test_copy(self): - d = self._full_mapping({1:1, 2:2, 3:3}) - self.assertEqual(d.copy(), self._full_mapping({1:1, 2:2, 3:3})) - d = self._empty_mapping() - self.assertEqual(d.copy(), d) - self.assert_(isinstance(d.copy(), d.__class__)) - self.assertRaises(TypeError, d.copy, None) - - def test_get(self): - BasicTestMappingProtocol.test_get(self) - d = self._empty_mapping() - self.assert_(d.get('c') is None) - self.assertEqual(d.get('c', 3), 3) - d = self._full_mapping({'a' : 1, 'b' : 2}) - self.assert_(d.get('c') is None) - self.assertEqual(d.get('c', 3), 3) - self.assertEqual(d.get('a'), 1) - self.assertEqual(d.get('a', 3), 1) - - def test_setdefault(self): - BasicTestMappingProtocol.test_setdefault(self) - d = self._empty_mapping() - self.assert_(d.setdefault('key0') is None) - d.setdefault('key0', []) - self.assert_(d.setdefault('key0') is None) - d.setdefault('key', []).append(3) - self.assertEqual(d['key'][0], 3) - d.setdefault('key', []).append(4) - self.assertEqual(len(d['key']), 2) - - def test_popitem(self): - BasicTestMappingProtocol.test_popitem(self) - for copymode in -1, +1: - # -1: b has same structure as a - # +1: b is a.copy() - for log2size in range(12): - size = 2**log2size - a = self._empty_mapping() - b = self._empty_mapping() - for i in range(size): - a[repr(i)] = i - if copymode < 0: - b[repr(i)] = i - if copymode > 0: - b = a.copy() - for i in range(size): - ka, va = ta = a.popitem() - self.assertEqual(va, int(ka)) - kb, vb = tb = b.popitem() - self.assertEqual(vb, int(kb)) - self.assert_(not(copymode < 0 and ta != tb)) - self.assert_(not a) - self.assert_(not b) - - def test_pop(self): - BasicTestMappingProtocol.test_pop(self) - - # Tests for pop with specified key - d = self._empty_mapping() - k, v = 'abc', 'def' - - # verify longs/ints get same value when key > 32 bits (for 64-bit archs) - # see SF bug #689659 - x = 4503599627370496 - y = 4503599627370496 - h = self._full_mapping({x: 'anything', y: 'something else'}) - self.assertEqual(h[x], h[y]) - - self.assertEqual(d.pop(k, v), v) - d[k] = v - self.assertEqual(d.pop(k, 1), v) - - -class TestHashMappingProtocol(TestMappingProtocol): - - def test_getitem(self): - TestMappingProtocol.test_getitem(self) - class Exc(Exception): pass - - class BadEq(object): - def __eq__(self, other): # pragma: no cover - raise Exc() - def __hash__(self): - return 24 - - d = self._empty_mapping() - d[BadEq()] = 42 - self.assertRaises(KeyError, d.__getitem__, 23) - - class BadHash(object): - fail = False - def __hash__(self): - if self.fail: - raise Exc() - else: - return 42 - - d = self._empty_mapping() - x = BadHash() - d[x] = 42 - x.fail = True - self.assertRaises(Exc, d.__getitem__, x) - - def test_fromkeys(self): - TestMappingProtocol.test_fromkeys(self) - class mydict(self.type2test): - def __new__(cls): - return UserDict() - ud = mydict.fromkeys('ab') - self.assertEqual(ud, {'a':None, 'b':None}) - self.assert_(isinstance(ud, UserDict)) - - def test_pop(self): - TestMappingProtocol.test_pop(self) - - class Exc(Exception): pass - - class BadHash(object): - fail = False - def __hash__(self): - if self.fail: - raise Exc() - else: - return 42 - - d = self._empty_mapping() - x = BadHash() - d[x] = 42 - x.fail = True - self.assertRaises(Exc, d.pop, x) - - def test_mutatingiteration(self): # pragma: no cover - d = self._empty_mapping() - d[1] = 1 - try: - for i in d: - d[i+1] = 1 - except RuntimeError: - pass - else: - self.fail("changing dict size during iteration doesn't raise Error") - - def test_repr(self): # pragma: no cover - d = self._empty_mapping() - self.assertEqual(repr(d), '{}') - d[1] = 2 - self.assertEqual(repr(d), '{1: 2}') - d = self._empty_mapping() - d[1] = d - self.assertEqual(repr(d), '{1: {...}}') - - class Exc(Exception): pass - - class BadRepr(object): - def __repr__(self): - raise Exc() - - d = self._full_mapping({1: BadRepr()}) - self.assertRaises(Exc, repr, d) - - def test_eq(self): - self.assertEqual(self._empty_mapping(), self._empty_mapping()) - self.assertEqual(self._full_mapping({1: 2}), - self._full_mapping({1: 2})) - - class Exc(Exception): pass - - class BadCmp(object): - def __eq__(self, other): - raise Exc() - def __hash__(self): - return 1 - - d1 = self._full_mapping({BadCmp(): 1}) - d2 = self._full_mapping({1: 1}) - self.assertRaises(Exc, lambda: BadCmp()==1) - #self.assertRaises(Exc, lambda: d1==d2) - - def test_setdefault(self): - TestMappingProtocol.test_setdefault(self) - - class Exc(Exception): pass - - class BadHash(object): - fail = False - def __hash__(self): - if self.fail: - raise Exc() - else: - return 42 - - d = self._empty_mapping() - x = BadHash() - d[x] = 42 - x.fail = True - self.assertRaises(Exc, d.setdefault, x, []) diff --git a/test/seq_tests.py b/test/seq_tests.py deleted file mode 100644 index 3d87126..0000000 --- a/test/seq_tests.py +++ /dev/null @@ -1,332 +0,0 @@ -# This file taken from Python, licensed under the Python License Agreement - -from __future__ import print_function -""" -Tests common to tuple, list and UserList.UserList -""" - -from . import unittest -from test import test_support -import sys - -# Various iterables -# This is used for checking the constructor (here and in test_deque.py) -def iterfunc(seqn): - 'Regular generator' - for i in seqn: - yield i - -class Sequence: - 'Sequence using __getitem__' - def __init__(self, seqn): - self.seqn = seqn - def __getitem__(self, i): - return self.seqn[i] - -class IterFunc: - 'Sequence using iterator protocol' - def __init__(self, seqn): - self.seqn = seqn - self.i = 0 - def __iter__(self): - return self - def __next__(self): - if self.i >= len(self.seqn): raise StopIteration - v = self.seqn[self.i] - self.i += 1 - return v - next = __next__ - -class IterGen: - 'Sequence using iterator protocol defined with a generator' - def __init__(self, seqn): - self.seqn = seqn - self.i = 0 - def __iter__(self): - for val in self.seqn: - yield val - -class IterNextOnly: - 'Missing __getitem__ and __iter__' - def __init__(self, seqn): - self.seqn = seqn - self.i = 0 - def __next__(self): # pragma: no cover - if self.i >= len(self.seqn): raise StopIteration - v = self.seqn[self.i] - self.i += 1 - return v - next = __next__ - -class IterNoNext: - 'Iterator missing next()' - def __init__(self, seqn): - self.seqn = seqn - self.i = 0 - def __iter__(self): - return self - -class IterGenExc: - 'Test propagation of exceptions' - def __init__(self, seqn): - self.seqn = seqn - self.i = 0 - def __iter__(self): - return self - def __next__(self): - 3 // 0 - next = __next__ - -class IterFuncStop: - 'Test immediate stop' - def __init__(self, seqn): - pass - def __iter__(self): - return self - def __next__(self): - raise StopIteration - next = __next__ - -from itertools import chain -def itermulti(seqn): - 'Test multiple tiers of iterators' - return chain(map(lambda x:x, iterfunc(IterGen(Sequence(seqn))))) - -class CommonTest(unittest.TestCase): - # The type to be tested - type2test = None - - def test_constructors(self): - l0 = [] - l1 = [0] - l2 = [0, 1] - - u = self.type2test() - u0 = self.type2test(l0) - u1 = self.type2test(l1) - u2 = self.type2test(l2) - - uu = self.type2test(u) - uu0 = self.type2test(u0) - uu1 = self.type2test(u1) - uu2 = self.type2test(u2) - - v = self.type2test(tuple(u)) - class OtherSeq: - def __init__(self, initseq): - self.__data = initseq - def __len__(self): - return len(self.__data) - def __getitem__(self, i): - return self.__data[i] - s = OtherSeq(u0) - v0 = self.type2test(s) - self.assertEqual(len(v0), len(s)) - - s = "this is also a sequence" - vv = self.type2test(s) - self.assertEqual(len(vv), len(s)) - - # Create from various iteratables - for s in ("123", "", list(range(1000)), ('do', 1.2), range(2000,2200,5)): - for g in (Sequence, IterFunc, IterGen, - itermulti, iterfunc): - self.assertEqual(self.type2test(g(s)), self.type2test(s)) - self.assertEqual(self.type2test(IterFuncStop(s)), self.type2test()) - self.assertEqual(self.type2test(c for c in "123"), self.type2test("123")) - self.assertRaises(TypeError, self.type2test, IterNextOnly(s)) - self.assertRaises(TypeError, self.type2test, IterNoNext(s)) - self.assertRaises(ZeroDivisionError, self.type2test, IterGenExc(s)) - - def test_truth(self): - self.assert_(not self.type2test()) - self.assert_(self.type2test([42])) - - def test_getitem(self): - u = self.type2test([0, 1, 2, 3, 4]) - for i in range(len(u)): - self.assertEqual(u[i], i) - self.assertEqual(u[int(i)], i) - for i in range(-len(u), -1): - self.assertEqual(u[i], len(u)+i) - self.assertEqual(u[int(i)], len(u)+i) - self.assertRaises(IndexError, u.__getitem__, -len(u)-1) - self.assertRaises(IndexError, u.__getitem__, len(u)) - self.assertRaises(ValueError, u.__getitem__, slice(0,10,0)) - - u = self.type2test() - self.assertRaises(IndexError, u.__getitem__, 0) - self.assertRaises(IndexError, u.__getitem__, -1) - - self.assertRaises(TypeError, u.__getitem__) - - a = self.type2test([10, 11]) - self.assertEqual(a[0], 10) - self.assertEqual(a[1], 11) - self.assertEqual(a[-2], 10) - self.assertEqual(a[-1], 11) - self.assertRaises(IndexError, a.__getitem__, -3) - self.assertRaises(IndexError, a.__getitem__, 3) - - def test_getslice(self): - l = [0, 1, 2, 3, 4] - u = self.type2test(l) - - self.assertEqual(u[0:0], self.type2test()) - self.assertEqual(u[1:2], self.type2test([1])) - self.assertEqual(u[-2:-1], self.type2test([3])) - self.assertEqual(u[-1000:1000], u) - self.assertEqual(u[1000:-1000], self.type2test([])) - self.assertEqual(u[:], u) - self.assertEqual(u[1:None], self.type2test([1, 2, 3, 4])) - self.assertEqual(u[None:3], self.type2test([0, 1, 2])) - - # Extended slices - self.assertEqual(u[::], u) - self.assertEqual(u[::2], self.type2test([0, 2, 4])) - self.assertEqual(u[1::2], self.type2test([1, 3])) - self.assertEqual(u[::-1], self.type2test([4, 3, 2, 1, 0])) - self.assertEqual(u[::-2], self.type2test([4, 2, 0])) - self.assertEqual(u[3::-2], self.type2test([3, 1])) - self.assertEqual(u[3:3:-2], self.type2test([])) - self.assertEqual(u[3:2:-2], self.type2test([3])) - self.assertEqual(u[3:1:-2], self.type2test([3])) - self.assertEqual(u[3:0:-2], self.type2test([3, 1])) - self.assertEqual(u[::-100], self.type2test([4])) - self.assertEqual(u[100:-100:], self.type2test([])) - self.assertEqual(u[-100:100:], u) - self.assertEqual(u[100:-100:-1], u[::-1]) - self.assertEqual(u[-100:100:-1], self.type2test([])) - self.assertEqual(u[-100:100:2], self.type2test([0, 2, 4])) - - # Test extreme cases with long ints - a = self.type2test([0,1,2,3,4]) - self.assertEqual(a[ -pow(2,128): 3 ], self.type2test([0,1,2])) - self.assertEqual(a[ 3: pow(2,145) ], self.type2test([3,4])) - - if sys.version_info[0] < 3: - self.assertRaises(TypeError, u.__getslice__) - - def test_contains(self): - u = self.type2test([0, 1, 2]) - for i in u: - self.assert_(i in u) - for i in min(u)-1, max(u)+1: - self.assert_(i not in u) - - self.assertRaises(TypeError, u.__contains__) - - def test_contains_fake(self): - class AllEq: - # Sequences must use rich comparison against each item - # (unless "is" is true, or an earlier item answered) - # So instances of AllEq must be found in all non-empty sequences. - def __eq__(self, other): - return True - def __hash__(self): # pragma: no cover - raise NotImplemented - self.assert_(AllEq() not in self.type2test([])) - self.assert_(AllEq() in self.type2test([1])) - - def test_contains_order(self): - # Sequences must test in-order. If a rich comparison has side - # effects, these will be visible to tests against later members. - # In this test, the "side effect" is a short-circuiting raise. - class DoNotTestEq(Exception): - pass - class StopCompares: - def __eq__(self, other): - raise DoNotTestEq - - checkfirst = self.type2test([1, StopCompares()]) - self.assert_(1 in checkfirst) - checklast = self.type2test([StopCompares(), 1]) - self.assertRaises(DoNotTestEq, checklast.__contains__, 1) - - def test_len(self): - self.assertEqual(len(self.type2test()), 0) - self.assertEqual(len(self.type2test([])), 0) - self.assertEqual(len(self.type2test([0])), 1) - self.assertEqual(len(self.type2test([0, 1, 2])), 3) - - def test_minmax(self): - u = self.type2test([0, 1, 2]) - self.assertEqual(min(u), 0) - self.assertEqual(max(u), 2) - - def test_addmul(self): - u1 = self.type2test([0]) - u2 = self.type2test([0, 1]) - self.assertEqual(u1, u1 + self.type2test()) - self.assertEqual(u1, self.type2test() + u1) - self.assertEqual(u1 + self.type2test([1]), u2) - self.assertEqual(self.type2test([-1]) + u1, self.type2test([-1, 0])) - self.assertEqual(self.type2test(), u2*0) - self.assertEqual(self.type2test(), 0*u2) - self.assertEqual(self.type2test(), u2*0) - self.assertEqual(self.type2test(), 0*u2) - self.assertEqual(u2, u2*1) - self.assertEqual(u2, 1*u2) - self.assertEqual(u2, u2*1) - self.assertEqual(u2, 1*u2) - self.assertEqual(u2+u2, u2*2) - self.assertEqual(u2+u2, 2*u2) - self.assertEqual(u2+u2, u2*2) - self.assertEqual(u2+u2, 2*u2) - self.assertEqual(u2+u2+u2, u2*3) - self.assertEqual(u2+u2+u2, 3*u2) - - class subclass(self.type2test): - pass - u3 = subclass([0, 1]) - self.assertEqual(u3, u3*1) - self.assert_(u3 is not u3*1) - - def test_iadd(self): - u = self.type2test([0, 1]) - u += self.type2test() - self.assertEqual(u, self.type2test([0, 1])) - u += self.type2test([2, 3]) - self.assertEqual(u, self.type2test([0, 1, 2, 3])) - u += self.type2test([4, 5]) - self.assertEqual(u, self.type2test([0, 1, 2, 3, 4, 5])) - - u = self.type2test("spam") - u += self.type2test("eggs") - self.assertEqual(u, self.type2test("spameggs")) - - def test_imul(self): - u = self.type2test([0, 1]) - u *= 3 - self.assertEqual(u, self.type2test([0, 1, 0, 1, 0, 1])) - - def test_getitemoverwriteiter(self): - # Verify that __getitem__ overrides are not recognized by __iter__ - class T(self.type2test): - def __getitem__(self, key): # pragma: no cover - return str(key) + '!!!' - self.assertEqual(next(iter(T((1,2)))), 1) - - def test_repeat(self): - for m in range(4): - s = tuple(range(m)) - for n in range(-3, 5): - self.assertEqual(self.type2test(s*n), self.type2test(s)*n) - self.assertEqual(self.type2test(s)*(-4), self.type2test([])) - self.assertEqual(id(s), id(s*1)) - - def test_subscript(self): - a = self.type2test([10, 11]) - self.assertEqual(a.__getitem__(0), 10) - self.assertEqual(a.__getitem__(1), 11) - self.assertEqual(a.__getitem__(-2), 10) - self.assertEqual(a.__getitem__(-1), 11) - self.assertRaises(IndexError, a.__getitem__, -3) - self.assertRaises(IndexError, a.__getitem__, 3) - self.assertEqual(a.__getitem__(slice(0,1)), self.type2test([10])) - self.assertEqual(a.__getitem__(slice(1,2)), self.type2test([11])) - self.assertEqual(a.__getitem__(slice(0,2)), self.type2test([10, 11])) - self.assertEqual(a.__getitem__(slice(0,3)), self.type2test([10, 11])) - self.assertEqual(a.__getitem__(slice(3,5)), self.type2test([])) - self.assertRaises(ValueError, a.__getitem__, slice(0, 10, 0)) - self.assertRaises(TypeError, a.__getitem__, 'x') diff --git a/test/sorteddict_tests.py b/test/sorteddict_tests.py deleted file mode 100644 index c81436b..0000000 --- a/test/sorteddict_tests.py +++ /dev/null @@ -1,81 +0,0 @@ -from . import mapping_tests -import blist - -def CmpToKey(mycmp): - 'Convert a cmp= function into a key= function' - class K(object): - def __init__(self, obj): - self.obj = obj - def __lt__(self, other): - return mycmp(self.obj, other.obj) == -1 - return K - -class sorteddict_test(mapping_tests.TestHashMappingProtocol): - type2test = blist.sorteddict - - def _reference(self): - """Return a dictionary of values which are invariant by storage - in the object under test.""" - return {1:2, 3:4, 5:6} - - def test_repr(self): - d = self._empty_mapping() - self.assertEqual(repr(d), 'sorteddict({})') - d[1] = 2 - self.assertEqual(repr(d), 'sorteddict({1: 2})') - d = self._empty_mapping() - d[1] = d - self.assertEqual(repr(d), 'sorteddict({1: sorteddict({...})})') - - class Exc(Exception): pass - - class BadRepr(object): - def __repr__(self): - raise Exc() - - d = self._full_mapping({1: BadRepr()}) - self.assertRaises(Exc, repr, d) - - def test_mutatingiteration(self): - pass - - def test_sort(self): - u = self.type2test.fromkeys([1, 0]) - self.assertEqual(list(u.keys()), [0, 1]) - - u = self.type2test.fromkeys([2,1,0,-1,-2]) - self.assertEqual(u, self.type2test.fromkeys([-2,-1,0,1,2])) - self.assertEqual(list(u.keys()), [-2,-1,0,1,2]) - - a = self.type2test.fromkeys(reversed(list(range(512)))) - self.assertEqual(list(a.keys()), list(range(512))) - - def revcmp(a, b): # pragma: no cover - if a == b: - return 0 - elif a < b: - return 1 - else: # a > b - return -1 - u = self.type2test.fromkeys([2,1,0,-1,-2], key=CmpToKey(revcmp)) - self.assertEqual(list(u.keys()), [2,1,0,-1,-2]) - - # The following dumps core in unpatched Python 1.5: - def myComparison(x,y): - xmod, ymod = x%3, y%7 - if xmod == ymod: - return 0 - elif xmod < ymod: - return -1 - else: # xmod > ymod - return 1 - - self.type2test.fromkeys(list(range(12)), key=CmpToKey(myComparison)) - - #def selfmodifyingComparison(x,y): - # z[x+y] = None - # return cmp(x, y) - #z = self.type2test(CmpToKey(selfmodifyingComparison)) - #self.assertRaises(ValueError, z.update, [(i,i) for i in range(12)]) - - self.assertRaises(TypeError, self.type2test.fromkeys, 42, 42, 42, 42) diff --git a/test/sortedlist_tests.py b/test/sortedlist_tests.py deleted file mode 100644 index 7618b4d..0000000 --- a/test/sortedlist_tests.py +++ /dev/null @@ -1,614 +0,0 @@ -# This file based loosely on Python's list_tests.py. - -import unittest, collections, operator -import sys -from . import list_tests, seq_tests -import blist -import random -import gc - -def CmpToKey(mycmp): - 'Convert a cmp= function into a key= function' - class K(object): - def __init__(self, obj): - self.obj = obj - def __lt__(self, other): - return mycmp(self.obj, other.obj) == -1 - return K - -class SortedBase(object): - def build_items(self, n): - return list(range(n)) - - def build_item(self, x): - return x - - def test_empty_repr(self): - self.assertEqual('%s()' % self.type2test.__name__, - repr(self.type2test())) - - def validate_comparison(self, instance): - if sys.version_info[0] < 3 and isinstance(instance, collections.Set): - ops = ['ne', 'or', 'and', 'xor', 'sub'] - else: - ops = ['lt', 'gt', 'le', 'ge', 'ne', 'or', 'and', 'xor', 'sub'] - operators = {} - for op in ops: - name = '__'+op+'__' - operators['__'+op+'__'] = getattr(operator, name) - - class Other(object): - def __init__(self): - self.right_side = False - def __eq__(self, other): - self.right_side = True - return True - __lt__ = __eq__ - __gt__ = __eq__ - __le__ = __eq__ - __ge__ = __eq__ - __ne__ = __eq__ - __ror__ = __eq__ - __rand__ = __eq__ - __rxor__ = __eq__ - __rsub__ = __eq__ - - for name, op in operators.items(): - if not hasattr(instance, name): continue - other = Other() - op(instance, other) - self.assertTrue(other.right_side,'Right side not called for %s.%s' - % (type(instance), name)) - - def test_right_side(self): - self.validate_comparison(self.type2test()) - - def test_delitem(self): - items = self.build_items(2) - a = self.type2test(items) - del a[1] - self.assertEqual(a, self.type2test(items[:1])) - del a[0] - self.assertEqual(a, self.type2test([])) - - a = self.type2test(items) - del a[-2] - self.assertEqual(a, self.type2test(items[1:])) - del a[-1] - self.assertEqual(a, self.type2test([])) - - a = self.type2test(items) - self.assertRaises(IndexError, a.__delitem__, -3) - self.assertRaises(IndexError, a.__delitem__, 2) - - a = self.type2test([]) - self.assertRaises(IndexError, a.__delitem__, 0) - - self.assertRaises(TypeError, a.__delitem__) - - def test_delslice(self): - items = self.build_items(2) - a = self.type2test(items) - del a[1:2] - del a[0:1] - self.assertEqual(a, self.type2test([])) - - a = self.type2test(items) - del a[1:2] - del a[0:1] - self.assertEqual(a, self.type2test([])) - - a = self.type2test(items) - del a[-2:-1] - self.assertEqual(a, self.type2test(items[1:])) - - a = self.type2test(items) - del a[-2:-1] - self.assertEqual(a, self.type2test(items[1:])) - - a = self.type2test(items) - del a[1:] - del a[:1] - self.assertEqual(a, self.type2test([])) - - a = self.type2test(items) - del a[1:] - del a[:1] - self.assertEqual(a, self.type2test([])) - - a = self.type2test(items) - del a[-1:] - self.assertEqual(a, self.type2test(items[:1])) - - a = self.type2test(items) - del a[-1:] - self.assertEqual(a, self.type2test(items[:1])) - - a = self.type2test(items) - del a[:] - self.assertEqual(a, self.type2test([])) - - def test_out_of_range(self): - u = self.type2test() - def del_test(): - del u[0] - self.assertRaises(IndexError, lambda: u[0]) - self.assertRaises(IndexError, del_test) - - def test_bad_mul(self): - u = self.type2test() - self.assertRaises(TypeError, lambda: u * 'q') - def imul_test(): - u = self.type2test() - u *= 'q' - self.assertRaises(TypeError, imul_test) - - def test_pop(self): - lst = self.build_items(20) - random.shuffle(lst) - u = self.type2test(lst) - for i in range(20-1,-1,-1): - x = u.pop(i) - self.assertEqual(x, i) - self.assertEqual(0, len(u)) - - def test_reversed(self): - lst = list(range(20)) - a = self.type2test(lst) - r = reversed(a) - self.assertEqual(list(r), list(range(19, -1, -1))) - if hasattr(r, '__next__'): # pragma: no cover - self.assertRaises(StopIteration, r.__next__) - else: # pragma: no cover - self.assertRaises(StopIteration, r.next) - self.assertEqual(self.type2test(reversed(self.type2test())), - self.type2test()) - - def test_mismatched_types(self): - class NotComparable: - def __lt__(self, other): # pragma: no cover - raise TypeError - def __cmp__(self, other): # pragma: no cover - raise TypeError - NotComparable = NotComparable() - - item = self.build_item(5) - sl = self.type2test() - sl.add(item) - self.assertRaises(TypeError, sl.add, NotComparable) - self.assertFalse(NotComparable in sl) - self.assertEqual(sl.count(NotComparable), 0) - sl.discard(NotComparable) - self.assertRaises(ValueError, sl.index, NotComparable) - - def test_order(self): - stuff = [self.build_item(random.randrange(1000000)) - for i in range(1000)] - if issubclass(self.type2test, collections.Set): - stuff = set(stuff) - sorted_stuff = list(sorted(stuff)) - u = self.type2test - - self.assertEqual(sorted_stuff, list(u(stuff))) - sl = u() - for x in stuff: - sl.add(x) - self.assertEqual(sorted_stuff, list(sl)) - x = sorted_stuff.pop(len(stuff)//2) - sl.discard(x) - self.assertEqual(sorted_stuff, list(sl)) - - def test_constructors(self): - # Based on the seq_test, but without adding incomparable types - # to the list. - - l0 = self.build_items(0) - l1 = self.build_items(1) - l2 = self.build_items(2) - - u = self.type2test() - u0 = self.type2test(l0) - u1 = self.type2test(l1) - u2 = self.type2test(l2) - - uu = self.type2test(u) - uu0 = self.type2test(u0) - uu1 = self.type2test(u1) - uu2 = self.type2test(u2) - - v = self.type2test(tuple(u)) - class OtherSeq: - def __init__(self, initseq): - self.__data = initseq - def __len__(self): - return len(self.__data) - def __getitem__(self, i): - return self.__data[i] - s = OtherSeq(u0) - v0 = self.type2test(s) - self.assertEqual(len(v0), len(s)) - - def test_sort(self): - # based on list_tests.py - lst = [1, 0] - lst = [self.build_item(x) for x in lst] - u = self.type2test(lst) - self.assertEqual(list(u), [0, 1]) - - lst = [2,1,0,-1,-2] - lst = [self.build_item(x) for x in lst] - u = self.type2test(lst) - self.assertEqual(list(u), [-2,-1,0,1,2]) - - lst = list(range(512)) - lst = [self.build_item(x) for x in lst] - a = self.type2test(reversed(lst)) - self.assertEqual(list(a), lst) - - def revcmp(a, b): # pragma: no cover - if a == b: - return 0 - elif a < b: - return 1 - else: # a > b - return -1 - u = self.type2test(u, key=CmpToKey(revcmp)) - self.assertEqual(list(u), [2,1,0,-1,-2]) - - # The following dumps core in unpatched Python 1.5: - def myComparison(x,y): - xmod, ymod = x%3, y%7 - if xmod == ymod: - return 0 - elif xmod < ymod: - return -1 - else: # xmod > ymod - return 1 - z = self.type2test(list(range(12)), key=CmpToKey(myComparison)) - - self.assertRaises(TypeError, self.type2test, 42, 42, 42, 42) - -class StrongSortedBase(SortedBase, seq_tests.CommonTest): - def not_applicable(self): - pass - test_repeat = not_applicable - test_imul = not_applicable - test_addmul = not_applicable - test_iadd = not_applicable - test_getslice = not_applicable - test_contains_order = not_applicable - test_contains_fake = not_applicable - - def test_constructors2(self): - s = "a seq" - vv = self.type2test(s) - self.assertEqual(len(vv), len(s)) - - # Create from various iteratables - for s in ("123", "", list(range(1000)), (1.5, 1.2), range(2000,2200,5)): - for g in (seq_tests.Sequence, seq_tests.IterFunc, - seq_tests.IterGen, seq_tests.itermulti, - seq_tests.iterfunc): - self.assertEqual(self.type2test(g(s)), self.type2test(s)) - self.assertEqual(self.type2test(seq_tests.IterFuncStop(s)), - self.type2test()) - self.assertEqual(self.type2test(c for c in "123"), - self.type2test("123")) - self.assertRaises(TypeError, self.type2test, - seq_tests.IterNextOnly(s)) - self.assertRaises(TypeError, self.type2test, - seq_tests.IterNoNext(s)) - self.assertRaises(ZeroDivisionError, self.type2test, - seq_tests.IterGenExc(s)) - -class weak_int: - def __init__(self, v): - self.value = v - def unwrap(self, other): - if isinstance(other, weak_int): - return other.value - return other - def __hash__(self): - return hash(self.value) - def __repr__(self): # pragma: no cover - return repr(self.value) - def __lt__(self, other): - return self.value < self.unwrap(other) - def __le__(self, other): - return self.value <= self.unwrap(other) - def __gt__(self, other): - return self.value > self.unwrap(other) - def __ge__(self, other): - return self.value >= self.unwrap(other) - def __eq__(self, other): - return self.value == self.unwrap(other) - def __ne__(self, other): - return self.value != self.unwrap(other) - def __mod__(self, other): - return self.value % self.unwrap(other) - def __neg__(self): - return weak_int(-self.value) - -class weak_manager(): - def __init__(self): - self.all = [weak_int(i) for i in range(10)] - self.live = [v for v in self.all if random.randrange(2)] - random.shuffle(self.all) - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - del self.all - gc.collect() - -class WeakSortedBase(SortedBase, unittest.TestCase): - def build_items(self, n): - return [weak_int(i) for i in range(n)] - - def build_item(self, x): - return weak_int(x) - - def test_collapse(self): - items = self.build_items(10) - u = self.type2test(items) - del items - gc.collect() - self.assertEqual(list(u), []) - - def test_sort(self): - # based on list_tests.py - x = [weak_int(i) for i in [1, 0]] - u = self.type2test(x) - self.assertEqual(list(u), list(reversed(x))) - - x = [weak_int(i) for i in [2,1,0,-1,-2]] - u = self.type2test(x) - self.assertEqual(list(u), list(reversed(x))) - - #y = [weak_int(i) for i in reversed(list(range(512)))] - #a = self.type2test(y) - #self.assertEqual(list(a), list(reversed(y))) - - def revcmp(a, b): # pragma: no cover - if a == b: - return 0 - elif a < b: - return 1 - else: # a > b - return -1 - u = self.type2test(u, key=CmpToKey(revcmp)) - self.assertEqual(list(u), x) - - # The following dumps core in unpatched Python 1.5: - def myComparison(x,y): - xmod, ymod = x%3, y%7 - if xmod == ymod: - return 0 - elif xmod < ymod: - return -1 - else: # xmod > ymod - return 1 - x = [weak_int(i) for i in range(12)] - z = self.type2test(x, key=CmpToKey(myComparison)) - - self.assertRaises(TypeError, self.type2test, 42, 42, 42, 42) - - def test_constructor(self): - with weak_manager() as m: - wsl = self.type2test(m.all) - self.assertEqual(list(wsl), m.live) - - def test_add(self): - with weak_manager() as m: - wsl = self.type2test() - for x in m.all: - wsl.add(x) - del x - self.assertEqual(list(wsl), m.live) - - def test_discard(self): - with weak_manager() as m: - wsl = self.type2test(m.all) - x = m.live.pop(len(m.live)//2) - wsl.discard(x) - self.assertEqual(list(wsl), m.live) - - def test_contains(self): - with weak_manager() as m: - wsl = self.type2test(m.all) - for x in m.live: - self.assertTrue(x in wsl) - self.assertFalse(weak_int(-1) in wsl) - - def test_iter(self): - with weak_manager() as m: - wsl = self.type2test(m.all) - for i, x in enumerate(wsl): - self.assertEqual(x, m.live[i]) - - def test_getitem(self): - with weak_manager() as m: - wsl = self.type2test(m.all) - for i in range(len(m.live)): - self.assertEqual(wsl[i], m.live[i]) - - def test_reversed(self): - with weak_manager() as m: - wsl = self.type2test(m.all) - r1 = list(reversed(wsl)) - r2 = list(reversed(m.live)) - self.assertEqual(r1, r2) - - all = [weak_int(i) for i in range(6)] - wsl = self.type2test(all) - del all[-1] - self.assertEqual(list(reversed(wsl)), list(reversed(all))) - - def test_index(self): - with weak_manager() as m: - wsl = self.type2test(m.all) - for x in m.live: - self.assertEqual(wsl[wsl.index(x)], x) - self.assertRaises(ValueError, wsl.index, weak_int(-1)) - - def test_count(self): - with weak_manager() as m: - wsl = self.type2test(m.all) - for x in m.live: - self.assertEqual(wsl.count(x), 1) - self.assertEqual(wsl.count(weak_int(-1)), 0) - - def test_getslice(self): - with weak_manager() as m: - wsl = self.type2test(m.all) - self.assertEqual(m.live, list(wsl[:])) - -class SortedListMixin: - def test_eq(self): - items = self.build_items(20) - u = self.type2test(items) - v = self.type2test(items, key=lambda x: -x) - self.assertNotEqual(u, v) - - def test_cmp(self): - items = self.build_items(20) - u = self.type2test(items) - low = u[:10] - high = u[10:] - self.assert_(low != high) - self.assert_(low == u[:10]) - self.assert_(low < high) - self.assert_(low <= high, str((low, high))) - self.assert_(high > low) - self.assert_(high >= low) - self.assertFalse(low == high) - self.assertFalse(high < low) - self.assertFalse(high <= low) - self.assertFalse(low > high) - self.assertFalse(low >= high) - - low = u[:5] - self.assert_(low != high) - self.assertFalse(low == high) - - def test_update(self): - items = self.build_items(20) - u = self.type2test() - u.update(items) - self.assertEqual(u, self.type2test(items)) - - def test_remove(self): - items = self.build_items(20) - u = self.type2test(items) - u.remove(items[-1]) - self.assertEqual(u, self.type2test(items[:19])) - self.assertRaises(ValueError, u.remove, items[-1]) - - def test_mul(self): - items = self.build_items(2) - u1 = self.type2test(items[:1]) - u2 = self.type2test(items) - self.assertEqual(self.type2test(), u2*0) - self.assertEqual(self.type2test(), 0*u2) - self.assertEqual(self.type2test(), u2*0) - self.assertEqual(self.type2test(), 0*u2) - self.assertEqual(u2, u2*1) - self.assertEqual(u2, 1*u2) - self.assertEqual(u2, u2*1) - self.assertEqual(u2, 1*u2) - self.assertEqual(self.type2test(items + items), u2*2) - self.assertEqual(self.type2test(items + items), 2*u2) - self.assertEqual(self.type2test(items + items + items), 3*u2) - self.assertEqual(self.type2test(items + items + items), u2*3) - - class subclass(self.type2test): - pass - u3 = subclass(items) - self.assertEqual(u3, u3*1) - self.assert_(u3 is not u3*1) - - def test_imul(self): - items = self.build_items(2) - items6 = items[:1]*3 + items[1:]*3 - u = self.type2test(items) - u *= 3 - self.assertEqual(u, self.type2test(items6)) - u *= 0 - self.assertEqual(u, self.type2test([])) - s = self.type2test([]) - oldid = id(s) - s *= 10 - self.assertEqual(id(s), oldid) - - def test_repr(self): - name = self.type2test.__name__ - u = self.type2test() - self.assertEqual(repr(u), '%s()' % name) - items = self.build_items(3) - u.update(items) - self.assertEqual(repr(u), '%s([0, 1, 2])' % name) - u = self.type2test() - u.update([u]) - self.assertEqual(repr(u), '%s([%s(...)])' % (name, name)) - - def test_bisect(self): - items = self.build_items(5) - del items[0] - del items[2] # We end up with [1, 2, 4] - u = self.type2test(items, key=lambda x: -x) # We end up with [4, 2, 1] - self.assertEqual(u.bisect_left(3), 1) - self.assertEqual(u.bisect(2), 2) # bisect == bisect_right - self.assertEqual(u.bisect_right(2), 2) - -class SortedSetMixin: - def test_duplicates(self): - u = self.type2test - ss = u() - stuff = [weak_int(random.randrange(100000)) for i in range(10)] - sorted_stuff = list(sorted(stuff)) - for x in stuff: - ss.add(x) - for x in stuff: - ss.add(x) - self.assertEqual(sorted_stuff, list(ss)) - x = sorted_stuff.pop(len(stuff)//2) - ss.discard(x) - self.assertEqual(sorted_stuff, list(ss)) - - def test_eq(self): - items = self.build_items(20) - u = self.type2test(items) - v = self.type2test(items, key=lambda x: -x) - self.assertEqual(u, v) - - def test_remove(self): - items = self.build_items(20) - u = self.type2test(items) - u.remove(items[-1]) - self.assertEqual(u, self.type2test(items[:19])) - self.assertRaises(KeyError, u.remove, items[-1]) - -class SortedListTest(StrongSortedBase, SortedListMixin): - type2test = blist.sortedlist - -class WeakSortedListTest(WeakSortedBase, SortedListMixin): - type2test = blist.weaksortedlist - - def test_advance(self): - items = [weak_int(0), weak_int(0)] - u = self.type2test(items) - del items[0] - gc.collect() - self.assertEqual(u.count(items[0]), 1) - -class SortedSetTest(StrongSortedBase, SortedSetMixin): - type2test = blist.sortedset - -class WeakSortedSetTest(WeakSortedBase, SortedSetMixin): - type2test = blist.weaksortedset - - def test_repr(self): - items = self.build_items(20) - u = self.type2test(items) - self.assertEqual(repr(u), 'weaksortedset(%s)' % repr(items)) diff --git a/test/test_list.py b/test/test_list.py deleted file mode 100644 index 51c22e4..0000000 --- a/test/test_list.py +++ /dev/null @@ -1,39 +0,0 @@ -from __future__ import print_function -from . import unittest -from test import test_support, list_tests - -class ListTest(list_tests.CommonTest): - type2test = list - - def test_truth(self): - super(ListTest, self).test_truth() - self.assert_(not []) - self.assert_([42]) - - def test_identity(self): - self.assert_([] is not []) - - def test_len(self): - super(ListTest, self).test_len() - self.assertEqual(len([]), 0) - self.assertEqual(len([0]), 1) - self.assertEqual(len([0, 1, 2]), 3) - -def test_main(verbose=None): - test_support.run_unittest(ListTest) - - # verify reference counting - import sys - if verbose: - import gc - counts = [None] * 5 - for i in range(len(counts)): - test_support.run_unittest(ListTest) - gc.set_debug(gc.DEBUG_STATS) - gc.collect() - #counts[i] = sys.gettotalrefcount() - print(counts) - - -if __name__ == "__main__": - test_main(verbose=False) diff --git a/test/test_set.py b/test/test_set.py deleted file mode 100644 index a27d01e..0000000 --- a/test/test_set.py +++ /dev/null @@ -1,1536 +0,0 @@ -# This file taken from Python, licensed under the Python License Agreement - -from __future__ import print_function - -import unittest -from . import test_support as support -import gc -import weakref -import operator -import copy -import pickle -from random import randrange, shuffle -import sys -import warnings -import collections - -from blist import sortedset as set - -class PassThru(Exception): - pass - -def check_pass_thru(): - raise PassThru - yield 1 # pragma: no cover - -class BadCmp: # pragma: no cover - def __hash__(self): - return 1 - def __lt__(self, other): - raise RuntimeError - def __eq__(self, other): - raise RuntimeError - -class ReprWrapper: - 'Used to test self-referential repr() calls' - def __repr__(self): - return repr(self.value) - -class TestJointOps(unittest.TestCase): - # Tests common to both set and frozenset - - def setUp(self): - self.word = word = 'simsalabim' - self.otherword = 'madagascar' - self.letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' - self.s = self.thetype(word) - self.d = dict.fromkeys(word) - - def test_new_or_init(self): - self.assertRaises(TypeError, self.thetype, [], 2) - self.assertRaises(TypeError, set().__init__, a=1) - - def test_uniquification(self): - actual = sorted(self.s) - expected = sorted(self.d) - self.assertEqual(actual, expected) - self.assertRaises(PassThru, self.thetype, check_pass_thru()) - - def test_len(self): - self.assertEqual(len(self.s), len(self.d)) - - def test_contains(self): - for c in self.letters: - self.assertEqual(c in self.s, c in self.d) - s = self.thetype([frozenset(self.letters)]) - # Issue 8752 - #self.assertIn(self.thetype(self.letters), s) - - def test_union(self): - u = self.s.union(self.otherword) - for c in self.letters: - self.assertEqual(c in u, c in self.d or c in self.otherword) - self.assertEqual(self.s, self.thetype(self.word)) - self.assertRaises(PassThru, self.s.union, check_pass_thru()) - for C in set, frozenset, dict.fromkeys, str, list, tuple: - self.assertEqual(self.thetype('abcba').union(C('cdc')), set('abcd')) - self.assertEqual(self.thetype('abcba').union(C('efgfe')), set('abcefg')) - self.assertEqual(self.thetype('abcba').union(C('ccb')), set('abc')) - self.assertEqual(self.thetype('abcba').union(C('ef')), set('abcef')) - self.assertEqual(self.thetype('abcba').union(C('ef'), C('fg')), set('abcefg')) - - # Issue #6573 - x = self.thetype() - self.assertEqual(x.union(set([1]), x, set([2])), self.thetype([1, 2])) - - def test_or(self): - i = self.s.union(self.otherword) - self.assertEqual(self.s | set(self.otherword), i) - self.assertEqual(self.s | frozenset(self.otherword), i) - self.assertEqual(self.s | self.otherword, i) - - def test_intersection(self): - i = self.s.intersection(self.otherword) - for c in self.letters: - self.assertEqual(c in i, c in self.d and c in self.otherword) - self.assertEqual(self.s, self.thetype(self.word)) - self.assertRaises(PassThru, self.s.intersection, check_pass_thru()) - for C in set, frozenset, dict.fromkeys, str, list, tuple: - self.assertEqual(self.thetype('abcba').intersection(C('cdc')), set('cc')) - self.assertEqual(self.thetype('abcba').intersection(C('efgfe')), set('')) - self.assertEqual(self.thetype('abcba').intersection(C('ccb')), set('bc')) - self.assertEqual(self.thetype('abcba').intersection(C('ef')), set('')) - self.assertEqual(self.thetype('abcba').intersection(C('cbcf'), C('bag')), set('b')) - s = self.thetype('abcba') - z = s.intersection() - if self.thetype == frozenset(): # pragma: no cover - self.assertEqual(id(s), id(z)) - else: - self.assertNotEqual(id(s), id(z)) - - def test_isdisjoint(self): - def f(s1, s2): - 'Pure python equivalent of isdisjoint()' - return not set(s1).intersection(s2) - for larg in '', 'a', 'ab', 'abc', 'ababac', 'cdc', 'cc', 'efgfe', 'ccb', 'ef': - s1 = self.thetype(larg) - for rarg in '', 'a', 'ab', 'abc', 'ababac', 'cdc', 'cc', 'efgfe', 'ccb', 'ef': - for C in set, frozenset, dict.fromkeys, str, list, tuple: - s2 = C(rarg) - actual = s1.isdisjoint(s2) - expected = f(s1, s2) - self.assertEqual(actual, expected) - self.assertTrue(actual is True or actual is False) - - def test_and(self): - i = self.s.intersection(self.otherword) - self.assertEqual(self.s & set(self.otherword), i) - self.assertEqual(self.s & frozenset(self.otherword), i) - self.assertEqual(self.s & self.otherword, i) - - def test_difference(self): - i = self.s.difference(self.otherword) - for c in self.letters: - self.assertEqual(c in i, c in self.d and c not in self.otherword) - self.assertEqual(self.s, self.thetype(self.word)) - self.assertRaises(PassThru, self.s.difference, check_pass_thru()) - for C in set, frozenset, dict.fromkeys, str, list, tuple: - self.assertEqual(self.thetype('abcba').difference(C('cdc')), set('ab')) - self.assertEqual(self.thetype('abcba').difference(C('efgfe')), set('abc')) - self.assertEqual(self.thetype('abcba').difference(C('ccb')), set('a')) - self.assertEqual(self.thetype('abcba').difference(C('ef')), set('abc')) - self.assertEqual(self.thetype('abcba').difference(), set('abc')) - self.assertEqual(self.thetype('abcba').difference(C('a'), C('b')), set('c')) - - def test_sub(self): - i = self.s.difference(self.otherword) - self.assertEqual(self.s - set(self.otherword), i) - self.assertEqual(self.s - frozenset(self.otherword), i) - self.assertEqual(self.s - self.otherword, i) - - def test_symmetric_difference(self): - i = self.s.symmetric_difference(self.otherword) - for c in self.letters: - self.assertEqual(c in i, (c in self.d) ^ (c in self.otherword)) - self.assertEqual(self.s, self.thetype(self.word)) - self.assertRaises(PassThru, self.s.symmetric_difference, check_pass_thru()) - for C in set, frozenset, dict.fromkeys, str, list, tuple: - self.assertEqual(self.thetype('abcba').symmetric_difference(C('cdc')), set('abd')) - self.assertEqual(self.thetype('abcba').symmetric_difference(C('efgfe')), set('abcefg')) - self.assertEqual(self.thetype('abcba').symmetric_difference(C('ccb')), set('a')) - self.assertEqual(self.thetype('abcba').symmetric_difference(C('ef')), set('abcef')) - - def test_xor(self): - i = self.s.symmetric_difference(self.otherword) - self.assertEqual(self.s ^ set(self.otherword), i) - self.assertEqual(self.s ^ frozenset(self.otherword), i) - self.assertEqual(self.s ^ self.otherword, i) - - def test_equality(self): - self.assertEqual(self.s, set(self.word)) - self.assertEqual(self.s, frozenset(self.word)) - self.assertEqual(self.s == self.word, False) - self.assertNotEqual(self.s, set(self.otherword)) - self.assertNotEqual(self.s, frozenset(self.otherword)) - self.assertEqual(self.s != self.word, True) - - def test_setOfFrozensets(self): - t = map(frozenset, ['abcdef', 'bcd', 'bdcb', 'fed', 'fedccba']) - s = self.thetype(t) - self.assertEqual(len(s), 3) - - def test_sub_and_super(self): - p, q, r = map(self.thetype, ['ab', 'abcde', 'def']) - self.assertTrue(p < q) - self.assertTrue(p <= q) - self.assertTrue(q <= q) - self.assertTrue(q > p) - self.assertTrue(q >= p) - self.assertFalse(q < r) - self.assertFalse(q <= r) - self.assertFalse(q > r) - self.assertFalse(q >= r) - self.assertTrue(set('a').issubset('abc')) - self.assertTrue(set('abc').issuperset('a')) - self.assertFalse(set('a').issubset('cbs')) - self.assertFalse(set('cbs').issuperset('a')) - - def test_pickling(self): - for i in range(pickle.HIGHEST_PROTOCOL + 1): - p = pickle.dumps(self.s, i) - dup = pickle.loads(p) - self.assertEqual(self.s, dup, "%s != %s" % (self.s, dup)) - if type(self.s) not in (set, frozenset): - self.s.x = 10 - p = pickle.dumps(self.s) - dup = pickle.loads(p) - self.assertEqual(self.s.x, dup.x) - - def test_deepcopy(self): - class Tracer: - def __init__(self, value): - self.value = value - def __hash__(self): # pragma: no cover - return self.value - def __deepcopy__(self, memo=None): - return Tracer(self.value + 1) - t = Tracer(10) - s = self.thetype([t]) - dup = copy.deepcopy(s) - self.assertNotEqual(id(s), id(dup)) - for elem in dup: - newt = elem - self.assertNotEqual(id(t), id(newt)) - self.assertEqual(t.value + 1, newt.value) - - def test_gc(self): - # Create a nest of cycles to exercise overall ref count check - class A: - def __lt__(self, other): - return id(self) < id(other) - def __gt__(self, other): - return id(self) > id(other) - s = set(A() for i in range(1000)) - for elem in s: - elem.cycle = s - elem.sub = elem - elem.set = set([elem]) - - def test_badcmp(self): - s = self.thetype([BadCmp()]) - # Detect comparison errors during insertion and lookup - self.assertRaises(RuntimeError, self.thetype, [BadCmp(), BadCmp()]) - self.assertRaises(RuntimeError, s.__contains__, BadCmp()) - # Detect errors during mutating operations - if hasattr(s, 'add'): - self.assertRaises(RuntimeError, s.add, BadCmp()) - self.assertRaises(RuntimeError, s.discard, BadCmp()) - self.assertRaises(RuntimeError, s.remove, BadCmp()) - - def test_cyclical_repr(self): - w = ReprWrapper() - s = self.thetype([w]) - w.value = s - if self.thetype == set: - self.assertEqual(repr(s), 'sortedset([sortedset(...)])') - else: - name = repr(s).partition('(')[0] # strip class name - self.assertEqual(repr(s), '%s([%s(...)])' % (name, name)) - - def test_cyclical_print(self): - w = ReprWrapper() - s = self.thetype([w]) - w.value = s - fo = open(support.TESTFN, "w") - try: - fo.write(str(s)) - fo.close() - fo = open(support.TESTFN, "r") - self.assertEqual(fo.read(), repr(s)) - finally: - fo.close() - support.unlink(support.TESTFN) - - def test_container_iterator(self): - # Bug #3680: tp_traverse was not implemented for set iterator object - class C(object): - def __lt__(self, other): - return id(self) < id(other) - def __gt__(self, other): - return id(self) > id(other) - obj = C() - ref = weakref.ref(obj) - container = set([obj, 1]) - obj.x = iter(container) - del obj, container - gc.collect() - self.assertTrue(ref() is None, "Cycle was not collected") - -class TestSet(TestJointOps): - thetype = set - basetype = set - - def test_init(self): - s = self.thetype() - s.__init__(self.word) - self.assertEqual(s, set(self.word)) - s.__init__(self.otherword) - self.assertEqual(s, set(self.otherword)) - self.assertRaises(TypeError, s.__init__, s, 2); - self.assertRaises(TypeError, s.__init__, 1); - - def test_constructor_identity(self): - s = self.thetype(range(3)) - t = self.thetype(s) - self.assertNotEqual(id(s), id(t)) - - def test_hash(self): - self.assertRaises(TypeError, hash, self.s) - - def test_clear(self): - self.s.clear() - self.assertEqual(self.s, set()) - self.assertEqual(len(self.s), 0) - - def test_copy(self): - dup = self.s.copy() - self.assertEqual(self.s, dup) - self.assertNotEqual(id(self.s), id(dup)) - #self.assertEqual(type(dup), self.basetype) - - def test_add(self): - self.s.add('Q') - self.assertIn('Q', self.s) - dup = self.s.copy() - self.s.add('Q') - self.assertEqual(self.s, dup) - #self.assertRaises(TypeError, self.s.add, []) - - def test_remove(self): - self.s.remove('a') - self.assertNotIn('a', self.s) - self.assertRaises(KeyError, self.s.remove, 'Q') - s = self.thetype([frozenset(self.word)]) - #self.assertIn(self.thetype(self.word), s) - #s.remove(self.thetype(self.word)) - #self.assertNotIn(self.thetype(self.word), s) - #self.assertRaises(KeyError, self.s.remove, self.thetype(self.word)) - - def test_remove_keyerror_unpacking(self): - # bug: www.python.org/sf/1576657 - for v1 in ['Q', (1,)]: - try: - self.s.remove(v1) - except KeyError as e: - v2 = e.args[0] - self.assertEqual(v1, v2) - else: # pragma: no cover - self.fail() - - def test_remove_keyerror_set(self): - key = self.thetype([3, 4]) - try: - self.s.remove(key) - except KeyError as e: - self.assertTrue(e.args[0] is key, - "KeyError should be {0}, not {1}".format(key, - e.args[0])) - else: # pragma: no cover - self.fail() - - def test_discard(self): - self.s.discard('a') - self.assertNotIn('a', self.s) - self.s.discard('Q') - #self.assertRaises(TypeError, self.s.discard, []) - s = self.thetype([frozenset(self.word)]) - #self.assertIn(self.thetype(self.word), s) - #s.discard(self.thetype(self.word)) - #self.assertNotIn(self.thetype(self.word), s) - #s.discard(self.thetype(self.word)) - - def test_pop(self): - for i in range(len(self.s)): - elem = self.s.pop() - self.assertNotIn(elem, self.s) - self.assertRaises(IndexError, self.s.pop) - - def test_update(self): - retval = self.s.update(self.otherword) - self.assertEqual(retval, None) - for c in (self.word + self.otherword): - self.assertIn(c, self.s) - self.assertRaises(PassThru, self.s.update, check_pass_thru()) - self.assertRaises(TypeError, self.s.update, 6) - for p, q in (('cdc', 'abcd'), ('efgfe', 'abcefg'), ('ccb', 'abc'), ('ef', 'abcef')): - for C in set, frozenset, dict.fromkeys, str, list, tuple: - s = self.thetype('abcba') - self.assertEqual(s.update(C(p)), None) - self.assertEqual(s, set(q)) - for p in ('cdc', 'efgfe', 'ccb', 'ef', 'abcda'): - q = 'ahi' - for C in set, frozenset, dict.fromkeys, str, list, tuple: - s = self.thetype('abcba') - self.assertEqual(s.update(C(p), C(q)), None) - self.assertEqual(s, set(s) | set(p) | set(q)) - - def test_ior(self): - self.s |= set(self.otherword) - for c in (self.word + self.otherword): - self.assertIn(c, self.s) - - def test_intersection_update(self): - retval = self.s.intersection_update(self.otherword) - self.assertEqual(retval, None) - for c in (self.word + self.otherword): - if c in self.otherword and c in self.word: - self.assertIn(c, self.s) - else: - self.assertNotIn(c, self.s) - self.assertRaises(PassThru, self.s.intersection_update, check_pass_thru()) - self.assertRaises(TypeError, self.s.intersection_update, 6) - for p, q in (('cdc', 'c'), ('efgfe', ''), ('ccb', 'bc'), ('ef', '')): - for C in set, frozenset, dict.fromkeys, str, list, tuple: - s = self.thetype('abcba') - self.assertEqual(s.intersection_update(C(p)), None) - self.assertEqual(s, set(q)) - ss = 'abcba' - s = self.thetype(ss) - t = 'cbc' - self.assertEqual(s.intersection_update(C(p), C(t)), None) - self.assertEqual(s, set('abcba')&set(p)&set(t)) - - def test_iand(self): - self.s &= set(self.otherword) - for c in (self.word + self.otherword): - if c in self.otherword and c in self.word: - self.assertIn(c, self.s) - else: - self.assertNotIn(c, self.s) - - def test_difference_update(self): - retval = self.s.difference_update(self.otherword) - self.assertEqual(retval, None) - for c in (self.word + self.otherword): - if c in self.word and c not in self.otherword: - self.assertIn(c, self.s) - else: - self.assertNotIn(c, self.s) - self.assertRaises(PassThru, self.s.difference_update, check_pass_thru()) - self.assertRaises(TypeError, self.s.difference_update, 6) - self.assertRaises(TypeError, self.s.symmetric_difference_update, 6) - for p, q in (('cdc', 'ab'), ('efgfe', 'abc'), ('ccb', 'a'), ('ef', 'abc')): - for C in set, frozenset, dict.fromkeys, str, list, tuple: - s = self.thetype('abcba') - self.assertEqual(s.difference_update(C(p)), None) - self.assertEqual(s, set(q)) - - s = self.thetype('abcdefghih') - s.difference_update() - self.assertEqual(s, self.thetype('abcdefghih')) - - s = self.thetype('abcdefghih') - s.difference_update(C('aba')) - self.assertEqual(s, self.thetype('cdefghih')) - - s = self.thetype('abcdefghih') - s.difference_update(C('cdc'), C('aba')) - self.assertEqual(s, self.thetype('efghih')) - - def test_isub(self): - self.s -= set(self.otherword) - for c in (self.word + self.otherword): - if c in self.word and c not in self.otherword: - self.assertIn(c, self.s) - else: - self.assertNotIn(c, self.s) - - def test_symmetric_difference_update(self): - retval = self.s.symmetric_difference_update(self.otherword) - self.assertEqual(retval, None) - for c in (self.word + self.otherword): - if (c in self.word) ^ (c in self.otherword): - self.assertIn(c, self.s) - else: - self.assertNotIn(c, self.s) - self.assertRaises(PassThru, self.s.symmetric_difference_update, check_pass_thru()) - self.assertRaises(TypeError, self.s.symmetric_difference_update, 6) - for p, q in (('cdc', 'abd'), ('efgfe', 'abcefg'), ('ccb', 'a'), ('ef', 'abcef')): - for C in set, frozenset, dict.fromkeys, str, list, tuple: - s = self.thetype('abcba') - self.assertEqual(s.symmetric_difference_update(C(p)), None) - self.assertEqual(s, set(q)) - - def test_ixor(self): - self.s ^= set(self.otherword) - for c in (self.word + self.otherword): - if (c in self.word) ^ (c in self.otherword): - self.assertIn(c, self.s) - else: - self.assertNotIn(c, self.s) - - def test_inplace_on_self(self): - t = self.s.copy() - t |= t - self.assertEqual(t, self.s) - t &= t - self.assertEqual(t, self.s) - t -= t - self.assertEqual(t, self.thetype()) - t = self.s.copy() - t ^= t - self.assertEqual(t, self.thetype()) - - def test_weakref(self): - s = self.thetype('gallahad') - p = weakref.proxy(s) - self.assertEqual(str(p), str(s)) - s = None - self.assertRaises(ReferenceError, str, p) - - def test_rich_compare(self): # pragma: no cover - if sys.version_info[0] < 3: - return - - class TestRichSetCompare: - def __gt__(self, some_set): - self.gt_called = True - return False - def __lt__(self, some_set): - self.lt_called = True - return False - def __ge__(self, some_set): - self.ge_called = True - return False - def __le__(self, some_set): - self.le_called = True - return False - - # This first tries the bulitin rich set comparison, which doesn't know - # how to handle the custom object. Upon returning NotImplemented, the - # corresponding comparison on the right object is invoked. - myset = set((1, 2, 3)) - - myobj = TestRichSetCompare() - myset < myobj - self.assertTrue(myobj.gt_called) - - myobj = TestRichSetCompare() - myset > myobj - self.assertTrue(myobj.lt_called) - - myobj = TestRichSetCompare() - myset <= myobj - self.assertTrue(myobj.ge_called) - - myobj = TestRichSetCompare() - myset >= myobj - self.assertTrue(myobj.le_called) - -class SetSubclass(set): - pass - -class TestSetSubclass(TestSet): - thetype = SetSubclass - basetype = set - -class SetSubclassWithKeywordArgs(set): - def __init__(self, iterable=[], newarg=None): - set.__init__(self, iterable) - -class TestSetSubclassWithKeywordArgs(TestSet): - - def test_keywords_in_subclass(self): - 'SF bug #1486663 -- this used to erroneously raise a TypeError' - SetSubclassWithKeywordArgs(newarg=1) - -# Tests taken from test_sets.py ============================================= - -empty_set = set() - -#============================================================================== - -class TestBasicOps(unittest.TestCase): - - def test_repr(self): - if self.repr is not None: - self.assertEqual(repr(self.set), self.repr) - - def test_print(self): - try: - fo = open(support.TESTFN, "w") - fo.write(str(self.set)) - fo.close() - fo = open(support.TESTFN, "r") - self.assertEqual(fo.read(), repr(self.set)) - finally: - fo.close() - support.unlink(support.TESTFN) - - def test_length(self): - self.assertEqual(len(self.set), self.length) - - def test_self_equality(self): - self.assertEqual(self.set, self.set) - - def test_equivalent_equality(self): - self.assertEqual(self.set, self.dup) - - def test_copy(self): - self.assertEqual(self.set.copy(), self.dup) - - def test_self_union(self): - result = self.set | self.set - self.assertEqual(result, self.dup) - - def test_empty_union(self): - result = self.set | empty_set - self.assertEqual(result, self.dup) - - def test_union_empty(self): - result = empty_set | self.set - self.assertEqual(result, self.dup) - - def test_self_intersection(self): - result = self.set & self.set - self.assertEqual(result, self.dup) - - def test_empty_intersection(self): - result = self.set & empty_set - self.assertEqual(result, empty_set) - - def test_intersection_empty(self): - result = empty_set & self.set - self.assertEqual(result, empty_set) - - def test_self_isdisjoint(self): - result = self.set.isdisjoint(self.set) - self.assertEqual(result, not self.set) - - def test_empty_isdisjoint(self): - result = self.set.isdisjoint(empty_set) - self.assertEqual(result, True) - - def test_isdisjoint_empty(self): - result = empty_set.isdisjoint(self.set) - self.assertEqual(result, True) - - def test_self_symmetric_difference(self): - result = self.set ^ self.set - self.assertEqual(result, empty_set) - - def test_checkempty_symmetric_difference(self): - result = self.set ^ empty_set - self.assertEqual(result, self.set) - - def test_self_difference(self): - result = self.set - self.set - self.assertEqual(result, empty_set) - - def test_empty_difference(self): - result = self.set - empty_set - self.assertEqual(result, self.dup) - - def test_empty_difference_rev(self): - result = empty_set - self.set - self.assertEqual(result, empty_set) - - def test_iteration(self): - for v in self.set: - self.assertIn(v, self.values) - setiter = iter(self.set) - # note: __length_hint__ is an internal undocumented API, - # don't rely on it in your own programs - #self.assertEqual(setiter.__length_hint__(), len(self.set)) - - def test_pickling(self): - p = pickle.dumps(self.set) - copy = pickle.loads(p) - self.assertEqual(self.set, copy, - "%s != %s" % (self.set, copy)) - -#------------------------------------------------------------------------------ - -class TestBasicOpsEmpty(TestBasicOps): - def setUp(self): - self.case = "empty set" - self.values = [] - self.set = set(self.values) - self.dup = set(self.values) - self.length = 0 - self.repr = "sortedset()" - -#------------------------------------------------------------------------------ - -class TestBasicOpsSingleton(TestBasicOps): - def setUp(self): - self.case = "unit set (number)" - self.values = [3] - self.set = set(self.values) - self.dup = set(self.values) - self.length = 1 - self.repr = "sortedset([3])" - - def test_in(self): - self.assertIn(3, self.set) - - def test_not_in(self): - self.assertNotIn(2, self.set) - -#------------------------------------------------------------------------------ - -class TestBasicOpsTuple(TestBasicOps): - def setUp(self): - self.case = "unit set (tuple)" - self.values = [(0, 1)] - self.set = set(self.values) - self.dup = set(self.values) - self.length = 1 - self.repr = "sortedset([(0, 1)])" - - def test_in(self): - self.assertIn((0, 1), self.set) - - def test_not_in(self): - self.assertNotIn(9, self.set) - -#------------------------------------------------------------------------------ - -class TestBasicOpsTriple(TestBasicOps): - def setUp(self): - self.case = "triple set" - self.values = [0, 1, 2] - self.set = set(self.values) - self.dup = set(self.values) - self.length = 3 - self.repr = None - -#------------------------------------------------------------------------------ - -class TestBasicOpsString(TestBasicOps): - def setUp(self): - self.case = "string set" - self.values = ["a", "b", "c"] - self.set = set(self.values) - self.dup = set(self.values) - self.length = 3 - self.repr = "sortedset(['a', 'b', 'c'])" - -#------------------------------------------------------------------------------ - -def baditer(): - raise TypeError - yield True # pragma: no cover - -def gooditer(): - yield True - -class TestExceptionPropagation(unittest.TestCase): - """SF 628246: Set constructor should not trap iterator TypeErrors""" - - def test_instanceWithException(self): - self.assertRaises(TypeError, set, baditer()) - - def test_instancesWithoutException(self): - # All of these iterables should load without exception. - set([1,2,3]) - set((1,2,3)) - set({'one':1, 'two':2, 'three':3}) - set(range(3)) - set('abc') - set(gooditer()) - - def test_changingSizeWhileIterating(self): - s = set([1,2,3]) - try: - for i in s: - s.update([4]) - except RuntimeError: - pass - else: # pragma: no cover - self.fail("no exception when changing size during iteration") - -#============================================================================== - -class TestSetOfSets(unittest.TestCase): - def test_constructor(self): - inner = frozenset([1]) - outer = set([inner]) - element = outer.pop() - self.assertEqual(type(element), frozenset) - outer.add(inner) # Rebuild set of sets with .add method - outer.remove(inner) - self.assertEqual(outer, set()) # Verify that remove worked - outer.discard(inner) # Absence of KeyError indicates working fine - -#============================================================================== - -class TestBinaryOps(unittest.TestCase): - def setUp(self): - self.set = set((2, 4, 6)) - - def test_eq(self): # SF bug 643115 - self.assertEqual(self.set, set({2:1,4:3,6:5})) - - def test_union_subset(self): - result = self.set | set([2]) - self.assertEqual(result, set((2, 4, 6))) - - def test_union_superset(self): - result = self.set | set([2, 4, 6, 8]) - self.assertEqual(result, set([2, 4, 6, 8])) - - def test_union_overlap(self): - result = self.set | set([3, 4, 5]) - self.assertEqual(result, set([2, 3, 4, 5, 6])) - - def test_union_non_overlap(self): - result = self.set | set([8]) - self.assertEqual(result, set([2, 4, 6, 8])) - - def test_intersection_subset(self): - result = self.set & set((2, 4)) - self.assertEqual(result, set((2, 4))) - - def test_intersection_superset(self): - result = self.set & set([2, 4, 6, 8]) - self.assertEqual(result, set([2, 4, 6])) - - def test_intersection_overlap(self): - result = self.set & set([3, 4, 5]) - self.assertEqual(result, set([4])) - - def test_intersection_non_overlap(self): - result = self.set & set([8]) - self.assertEqual(result, empty_set) - - def test_isdisjoint_subset(self): - result = self.set.isdisjoint(set((2, 4))) - self.assertEqual(result, False) - - def test_isdisjoint_superset(self): - result = self.set.isdisjoint(set([2, 4, 6, 8])) - self.assertEqual(result, False) - - def test_isdisjoint_overlap(self): - result = self.set.isdisjoint(set([3, 4, 5])) - self.assertEqual(result, False) - - def test_isdisjoint_non_overlap(self): - result = self.set.isdisjoint(set([8])) - self.assertEqual(result, True) - - def test_sym_difference_subset(self): - result = self.set ^ set((2, 4)) - self.assertEqual(result, set([6])) - - def test_sym_difference_superset(self): - result = self.set ^ set((2, 4, 6, 8)) - self.assertEqual(result, set([8])) - - def test_sym_difference_overlap(self): - result = self.set ^ set((3, 4, 5)) - self.assertEqual(result, set([2, 3, 5, 6])) - - def test_sym_difference_non_overlap(self): - result = self.set ^ set([8]) - self.assertEqual(result, set([2, 4, 6, 8])) - -#============================================================================== - -class TestUpdateOps(unittest.TestCase): - def setUp(self): - self.set = set((2, 4, 6)) - - def test_union_subset(self): - self.set |= set([2]) - self.assertEqual(self.set, set((2, 4, 6))) - - def test_union_superset(self): - self.set |= set([2, 4, 6, 8]) - self.assertEqual(self.set, set([2, 4, 6, 8])) - - def test_union_overlap(self): - self.set |= set([3, 4, 5]) - self.assertEqual(self.set, set([2, 3, 4, 5, 6])) - - def test_union_non_overlap(self): - self.set |= set([8]) - self.assertEqual(self.set, set([2, 4, 6, 8])) - - def test_union_method_call(self): - self.set.update(set([3, 4, 5])) - self.assertEqual(self.set, set([2, 3, 4, 5, 6])) - - def test_intersection_subset(self): - self.set &= set((2, 4)) - self.assertEqual(self.set, set((2, 4))) - - def test_intersection_superset(self): - self.set &= set([2, 4, 6, 8]) - self.assertEqual(self.set, set([2, 4, 6])) - - def test_intersection_overlap(self): - self.set &= set([3, 4, 5]) - self.assertEqual(self.set, set([4])) - - def test_intersection_non_overlap(self): - self.set &= set([8]) - self.assertEqual(self.set, empty_set) - - def test_intersection_method_call(self): - self.set.intersection_update(set([3, 4, 5])) - self.assertEqual(self.set, set([4])) - - def test_sym_difference_subset(self): - self.set ^= set((2, 4)) - self.assertEqual(self.set, set([6])) - - def test_sym_difference_superset(self): - self.set ^= set((2, 4, 6, 8)) - self.assertEqual(self.set, set([8])) - - def test_sym_difference_overlap(self): - self.set ^= set((3, 4, 5)) - self.assertEqual(self.set, set([2, 3, 5, 6])) - - def test_sym_difference_non_overlap(self): - self.set ^= set([8]) - self.assertEqual(self.set, set([2, 4, 6, 8])) - - def test_sym_difference_method_call(self): - self.set.symmetric_difference_update(set([3, 4, 5])) - self.assertEqual(self.set, set([2, 3, 5, 6])) - - def test_difference_subset(self): - self.set -= set((2, 4)) - self.assertEqual(self.set, set([6])) - - def test_difference_superset(self): - self.set -= set((2, 4, 6, 8)) - self.assertEqual(self.set, set([])) - - def test_difference_overlap(self): - self.set -= set((3, 4, 5)) - self.assertEqual(self.set, set([2, 6])) - - def test_difference_non_overlap(self): - self.set -= set([8]) - self.assertEqual(self.set, set([2, 4, 6])) - - def test_difference_method_call(self): - self.set.difference_update(set([3, 4, 5])) - self.assertEqual(self.set, set([2, 6])) - -#============================================================================== - -class TestMutate(unittest.TestCase): - def setUp(self): - self.values = ["a", "b", "c"] - self.set = set(self.values) - - def test_add_present(self): - self.set.add("c") - self.assertEqual(self.set, set("abc")) - - def test_add_absent(self): - self.set.add("d") - self.assertEqual(self.set, set("abcd")) - - def test_add_until_full(self): - tmp = set() - expected_len = 0 - for v in self.values: - tmp.add(v) - expected_len += 1 - self.assertEqual(len(tmp), expected_len) - self.assertEqual(tmp, self.set) - - def test_remove_present(self): - self.set.remove("b") - self.assertEqual(self.set, set("ac")) - - def test_remove_absent(self): - try: - self.set.remove("d") - self.fail("Removing missing element should have raised LookupError") # pragma: no cover - except LookupError: - pass - - def test_remove_until_empty(self): - expected_len = len(self.set) - for v in self.values: - self.set.remove(v) - expected_len -= 1 - self.assertEqual(len(self.set), expected_len) - - def test_discard_present(self): - self.set.discard("c") - self.assertEqual(self.set, set("ab")) - - def test_discard_absent(self): - self.set.discard("d") - self.assertEqual(self.set, set("abc")) - - def test_clear(self): - self.set.clear() - self.assertEqual(len(self.set), 0) - - def test_pop(self): - popped = {} - while self.set: - popped[self.set.pop()] = None - self.assertEqual(len(popped), len(self.values)) - for v in self.values: - self.assertIn(v, popped) - - def test_update_empty_tuple(self): - self.set.update(()) - self.assertEqual(self.set, set(self.values)) - - def test_update_unit_tuple_overlap(self): - self.set.update(("a",)) - self.assertEqual(self.set, set(self.values)) - - def test_update_unit_tuple_non_overlap(self): - self.set.update(("a", "z")) - self.assertEqual(self.set, set(self.values + ["z"])) - -#============================================================================== - -class TestSubsets(unittest.TestCase): - - case2method = {"<=": "issubset", - ">=": "issuperset", - } - - reverse = {"==": "==", - "!=": "!=", - "<": ">", - ">": "<", - "<=": ">=", - ">=": "<=", - } - - def test_issubset(self): - x = self.left - y = self.right - for case in "!=", "==", "<", "<=", ">", ">=": - expected = case in self.cases - # Test the binary infix spelling. - result = eval("x" + case + "y", locals()) - self.assertEqual(result, expected) - # Test the "friendly" method-name spelling, if one exists. - if case in TestSubsets.case2method: - method = getattr(x, TestSubsets.case2method[case]) - result = method(y) - self.assertEqual(result, expected) - - # Now do the same for the operands reversed. - rcase = TestSubsets.reverse[case] - result = eval("y" + rcase + "x", locals()) - self.assertEqual(result, expected) - if rcase in TestSubsets.case2method: - method = getattr(y, TestSubsets.case2method[rcase]) - result = method(x) - self.assertEqual(result, expected) -#------------------------------------------------------------------------------ - -class TestSubsetEqualEmpty(TestSubsets): - left = set() - right = set() - name = "both empty" - cases = "==", "<=", ">=" - -#------------------------------------------------------------------------------ - -class TestSubsetEqualNonEmpty(TestSubsets): - left = set([1, 2]) - right = set([1, 2]) - name = "equal pair" - cases = "==", "<=", ">=" - -#------------------------------------------------------------------------------ - -class TestSubsetEmptyNonEmpty(TestSubsets): - left = set() - right = set([1, 2]) - name = "one empty, one non-empty" - cases = "!=", "<", "<=" - -#------------------------------------------------------------------------------ - -class TestSubsetPartial(TestSubsets): - left = set([1]) - right = set([1, 2]) - name = "one a non-empty proper subset of other" - cases = "!=", "<", "<=" - -#------------------------------------------------------------------------------ - -class TestSubsetNonOverlap(TestSubsets): - left = set([1]) - right = set([2]) - name = "neither empty, neither contains" - cases = "!=" - -#============================================================================== - -class TestOnlySetsInBinaryOps(unittest.TestCase): - - def test_eq_ne(self): - # Unlike the others, this is testing that == and != *are* allowed. - self.assertEqual(self.other == self.set, False) - self.assertEqual(self.set == self.other, False) - self.assertEqual(self.other != self.set, True) - self.assertEqual(self.set != self.other, True) - - def test_ge_gt_le_lt(self): - self.assertRaises(TypeError, lambda: self.set < self.other) - self.assertRaises(TypeError, lambda: self.set <= self.other) - self.assertRaises(TypeError, lambda: self.set > self.other) - self.assertRaises(TypeError, lambda: self.set >= self.other) - - self.assertRaises(TypeError, lambda: self.other < self.set) - self.assertRaises(TypeError, lambda: self.other <= self.set) - self.assertRaises(TypeError, lambda: self.other > self.set) - self.assertRaises(TypeError, lambda: self.other >= self.set) - - def test_update_operator(self): - if self.otherIsIterable: - self.set |= self.other - else: - try: - self.set |= self.other - except TypeError: - pass - else: # pragma: no cover - self.fail("expected TypeError") - - def test_update(self): - if self.otherIsIterable: - self.set.update(self.other) - else: - self.assertRaises(TypeError, self.set.update, self.other) - - def test_union(self): - if self.otherIsIterable: - self.set | self.other - self.other | self.set - self.set.union(self.other) - else: - self.assertRaises(TypeError, lambda: self.set | self.other) - self.assertRaises(TypeError, lambda: self.other | self.set) - self.assertRaises(TypeError, self.set.union, self.other) - - def test_intersection_update_operator(self): - if self.otherIsIterable: - self.set &= self.other - else: - try: - self.set &= self.other - except TypeError: - pass - else: # pragma: no cover - self.fail("expected TypeError") - - def test_intersection_update(self): - if self.otherIsIterable: - self.set.intersection_update(self.other) - else: - self.assertRaises(TypeError, - self.set.intersection_update, - self.other) - - def test_intersection(self): - if self.otherIsIterable: - self.set & self.other - self.other & self.set - self.set.intersection(self.other) - else: - self.assertRaises(TypeError, lambda: self.set & self.other) - self.assertRaises(TypeError, lambda: self.other & self.set) - self.assertRaises(TypeError, self.set.intersection, self.other) - - def test_sym_difference_update_operator(self): - if self.otherIsIterable: - self.set ^= self.other - else: - try: - self.set ^= self.other - except TypeError: - pass - else: # pragma: no cover - self.fail("expected TypeError") - - def test_sym_difference_update(self): - if self.otherIsIterable: - self.set.symmetric_difference_update(self.other) - else: - self.assertRaises(TypeError, - self.set.symmetric_difference_update, - self.other) - - def test_sym_difference(self): - if self.otherIsIterable: - self.set ^ self.other - self.other ^ self.set - self.set.symmetric_difference(self.other) - else: - self.assertRaises(TypeError, lambda: self.set ^ self.other) - self.assertRaises(TypeError, lambda: self.other ^ self.set) - self.assertRaises(TypeError, self.set.symmetric_difference, self.other) - - def test_difference_update_operator(self): - if self.otherIsIterable: - self.set -= self.other - else: - try: - self.set -= self.other - except TypeError: - pass - else: # pragma: no cover - self.fail("expected TypeError") - - def test_difference_update(self): - if self.otherIsIterable: - self.set.difference_update(self.other) - else: - self.assertRaises(TypeError, - self.set.difference_update, - self.other) - - def test_difference(self): - if self.otherIsIterable: - self.set - self.other - self.other - self.set - self.set.difference(self.other) - else: - self.assertRaises(TypeError, lambda: self.set - self.other) - self.assertRaises(TypeError, lambda: self.other - self.set) - self.assertRaises(TypeError, self.set.difference, self.other) - -#------------------------------------------------------------------------------ - -class TestOnlySetsNumeric(TestOnlySetsInBinaryOps): - def setUp(self): - self.set = set((1, 2, 3)) - self.other = 19 - self.otherIsIterable = False - -#------------------------------------------------------------------------------ - -class TestOnlySetsDict(TestOnlySetsInBinaryOps): - def setUp(self): - self.set = set((1, 2, 3)) - self.other = {1:2, 3:4} - self.otherIsIterable = True - -#------------------------------------------------------------------------------ - -class TestOnlySetsOperator(TestOnlySetsInBinaryOps): - def setUp(self): - self.set = set((1, 2, 3)) - self.other = operator.add - self.otherIsIterable = False - -#------------------------------------------------------------------------------ - -class TestOnlySetsTuple(TestOnlySetsInBinaryOps): - def setUp(self): - self.set = set((1, 2, 3)) - self.other = (2, 4, 6) - self.otherIsIterable = True - -#------------------------------------------------------------------------------ - -class TestOnlySetsString(TestOnlySetsInBinaryOps): - def setUp(self): - self.set = set('xyz') - self.other = 'abc' - self.otherIsIterable = True - -#------------------------------------------------------------------------------ - -class TestOnlySetsGenerator(TestOnlySetsInBinaryOps): - def setUp(self): - def gen(): - for i in range(0, 10, 2): - yield i - self.set = set((1, 2, 3)) - self.other = gen() - self.otherIsIterable = True - -#============================================================================== - -class TestCopying(unittest.TestCase): - - def test_copy(self): - dup = self.set.copy() - dup_list = sorted(dup, key=repr) - set_list = sorted(self.set, key=repr) - self.assertEqual(len(dup_list), len(set_list)) - for i in range(len(dup_list)): - self.assertTrue(dup_list[i] is set_list[i]) - - def test_deep_copy(self): - dup = copy.deepcopy(self.set) - ##print type(dup), repr(dup) - dup_list = sorted(dup, key=repr) - set_list = sorted(self.set, key=repr) - self.assertEqual(len(dup_list), len(set_list)) - for i in range(len(dup_list)): - self.assertEqual(dup_list[i], set_list[i]) - -#------------------------------------------------------------------------------ - -class TestCopyingEmpty(TestCopying): - def setUp(self): - self.set = set() - -#------------------------------------------------------------------------------ - -class TestCopyingSingleton(TestCopying): - def setUp(self): - self.set = set(["hello"]) - -#------------------------------------------------------------------------------ - -class TestCopyingTriple(TestCopying): - def setUp(self): - self.set = set([-1, 0, 1]) - -#------------------------------------------------------------------------------ - -class TestCopyingTuple(TestCopying): - def setUp(self): - self.set = set([(1, 2)]) - -#------------------------------------------------------------------------------ - -class TestCopyingNested(TestCopying): - def setUp(self): - self.set = set([((1, 2), (3, 4))]) - -#============================================================================== - -class TestIdentities(unittest.TestCase): - def setUp(self): - self.a = set('abracadabra') - self.b = set('alacazam') - - def test_binopsVsSubsets(self): - a, b = self.a, self.b - self.assertTrue(a - b < a) - self.assertTrue(b - a < b) - self.assertTrue(a & b < a) - self.assertTrue(a & b < b) - self.assertTrue(a | b > a) - self.assertTrue(a | b > b) - self.assertTrue(a ^ b < a | b) - - def test_commutativity(self): - a, b = self.a, self.b - self.assertEqual(a&b, b&a) - self.assertEqual(a|b, b|a) - self.assertEqual(a^b, b^a) - if a != b: - self.assertNotEqual(a-b, b-a) - - def test_summations(self): - # check that sums of parts equal the whole - a, b = self.a, self.b - self.assertEqual((a-b)|(a&b)|(b-a), a|b) - self.assertEqual((a&b)|(a^b), a|b) - self.assertEqual(a|(b-a), a|b) - self.assertEqual((a-b)|b, a|b) - self.assertEqual((a-b)|(a&b), a) - self.assertEqual((b-a)|(a&b), b) - self.assertEqual((a-b)|(b-a), a^b) - - def test_exclusion(self): - # check that inverse operations show non-overlap - a, b, zero = self.a, self.b, set() - self.assertEqual((a-b)&b, zero) - self.assertEqual((b-a)&a, zero) - self.assertEqual((a&b)&(a^b), zero) - -# Tests derived from test_itertools.py ======================================= - -def R(seqn): - 'Regular generator' - for i in seqn: - yield i - -class G: - 'Sequence using __getitem__' - def __init__(self, seqn): - self.seqn = seqn - def __getitem__(self, i): - return self.seqn[i] - -class I: - 'Sequence using iterator protocol' - def __init__(self, seqn): - self.seqn = seqn - self.i = 0 - def __iter__(self): - return self - def __next__(self): - if self.i >= len(self.seqn): raise StopIteration - v = self.seqn[self.i] - self.i += 1 - return v - next = __next__ - -class Ig: - 'Sequence using iterator protocol defined with a generator' - def __init__(self, seqn): - self.seqn = seqn - self.i = 0 - def __iter__(self): - for val in self.seqn: - yield val - -class X: - 'Missing __getitem__ and __iter__' - def __init__(self, seqn): - self.seqn = seqn - self.i = 0 - def __next__(self): # pragma: no cover - if self.i >= len(self.seqn): raise StopIteration - v = self.seqn[self.i] - self.i += 1 - return v - next = __next__ - -class N: - 'Iterator missing __next__()' - def __init__(self, seqn): - self.seqn = seqn - self.i = 0 - def __iter__(self): - return self - -class E: - 'Test propagation of exceptions' - def __init__(self, seqn): - self.seqn = seqn - self.i = 0 - def __iter__(self): - return self - def __next__(self): - 3 // 0 - next = __next__ - -class S: - 'Test immediate stop' - def __init__(self, seqn): - pass - def __iter__(self): - return self - def __next__(self): - raise StopIteration - next = __next__ - -from itertools import chain -def L(seqn): - 'Test multiple tiers of iterators' - return chain(map(lambda x:x, R(Ig(G(seqn))))) - -class TestVariousIteratorArgs(unittest.TestCase): - - def test_constructor(self): - for cons in (set,): - for s in (range(3), (), range(1000), (1.1, 1.2), range(2000,2200,5), range(10)): - for g in (G, I, Ig, S, L, R): - self.assertEqual(sorted(cons(g(s)), key=repr), sorted(g(s), key=repr)) - self.assertRaises(TypeError, cons , X(s)) - self.assertRaises(TypeError, cons , N(s)) - self.assertRaises(ZeroDivisionError, cons , E(s)) - - def test_inline_methods(self): - s = set([-1,-2,-3]) - for data in (range(3), (), range(1000), (1.1, 1.2), range(2000,2200,5), range(10)): - for meth in (s.union, s.intersection, s.difference, s.symmetric_difference, s.isdisjoint): - for g in (G, I, Ig, L, R): - expected = meth(data) - actual = meth(G(data)) - if isinstance(expected, bool): - self.assertEqual(actual, expected) - else: - self.assertEqual(sorted(actual, key=repr), sorted(expected, key=repr)) - self.assertRaises(TypeError, meth, X(s)) - self.assertRaises(TypeError, meth, N(s)) - self.assertRaises(ZeroDivisionError, meth, E(s)) - - def test_inplace_methods(self): - for data in (range(3), (), range(1000), (1.1, 1.2), range(2000,2200,5), range(10)): - for methname in ('update', 'intersection_update', - 'difference_update', 'symmetric_difference_update'): - for g in (G, I, Ig, S, L, R): - s = set([1,2,3]) - t = s.copy() - getattr(s, methname)(list(g(data))) - getattr(t, methname)(g(data)) - self.assertEqual(sorted(s, key=repr), sorted(t, key=repr)) - - self.assertRaises(TypeError, getattr(set([1,2,3]), methname), X(data)) - self.assertRaises(TypeError, getattr(set([1,2,3]), methname), N(data)) - self.assertRaises(ZeroDivisionError, getattr(set([1,2,3]), methname), E(data)) - -#============================================================================== - -test_classes = ( - TestSet, - TestSetSubclass, - TestSetSubclassWithKeywordArgs, - TestSetOfSets, - TestExceptionPropagation, - TestBasicOpsEmpty, - TestBasicOpsSingleton, - TestBasicOpsTuple, - TestBasicOpsTriple, - TestBasicOpsString, - TestBinaryOps, - TestUpdateOps, - TestMutate, - TestSubsetEqualEmpty, - TestSubsetEqualNonEmpty, - TestSubsetEmptyNonEmpty, - TestSubsetPartial, - TestSubsetNonOverlap, - TestOnlySetsNumeric, - TestOnlySetsDict, - TestOnlySetsOperator, - TestOnlySetsTuple, - TestOnlySetsString, - TestOnlySetsGenerator, - TestCopyingEmpty, - TestCopyingSingleton, - TestCopyingTriple, - TestCopyingTuple, - TestCopyingNested, - TestIdentities, - TestVariousIteratorArgs, - ) diff --git a/test/test_support.py b/test/test_support.py deleted file mode 100644 index d632ba4..0000000 --- a/test/test_support.py +++ /dev/null @@ -1,415 +0,0 @@ -# This file taken from Python, licensed under the Python License Agreement - -from __future__ import print_function -"""Supporting definitions for the Python regression tests.""" - -if __name__ != 'test.test_support': - raise ImportError('test_support must be imported from the test package') - -import sys - -class Error(Exception): - """Base class for regression test exceptions.""" - -class TestFailed(Error): - """Test failed.""" - -class TestSkipped(Error): - """Test skipped. - - This can be raised to indicate that a test was deliberatly - skipped, but not because a feature wasn't available. For - example, if some resource can't be used, such as the network - appears to be unavailable, this should be raised instead of - TestFailed. - """ - -class ResourceDenied(TestSkipped): - """Test skipped because it requested a disallowed resource. - - This is raised when a test calls requires() for a resource that - has not be enabled. It is used to distinguish between expected - and unexpected skips. - """ - -verbose = 1 # Flag set to 0 by regrtest.py -use_resources = None # Flag set to [] by regrtest.py -max_memuse = 0 # Disable bigmem tests (they will still be run with - # small sizes, to make sure they work.) - -# _original_stdout is meant to hold stdout at the time regrtest began. -# This may be "the real" stdout, or IDLE's emulation of stdout, or whatever. -# The point is to have some flavor of stdout the user can actually see. -_original_stdout = None -def record_original_stdout(stdout): - global _original_stdout - _original_stdout = stdout - -def get_original_stdout(): - return _original_stdout or sys.stdout - -def unload(name): - try: - del sys.modules[name] - except KeyError: - pass - -def unlink(filename): - import os - try: - os.unlink(filename) - except OSError: - pass - -def forget(modname): - '''"Forget" a module was ever imported by removing it from sys.modules and - deleting any .pyc and .pyo files.''' - unload(modname) - import os - for dirname in sys.path: - unlink(os.path.join(dirname, modname + os.extsep + 'pyc')) - # Deleting the .pyo file cannot be within the 'try' for the .pyc since - # the chance exists that there is no .pyc (and thus the 'try' statement - # is exited) but there is a .pyo file. - unlink(os.path.join(dirname, modname + os.extsep + 'pyo')) - -def is_resource_enabled(resource): - """Test whether a resource is enabled. Known resources are set by - regrtest.py.""" - return use_resources is not None and resource in use_resources - -def requires(resource, msg=None): - """Raise ResourceDenied if the specified resource is not available. - - If the caller's module is __main__ then automatically return True. The - possibility of False being returned occurs when regrtest.py is executing.""" - # see if the caller's module is __main__ - if so, treat as if - # the resource was set - if sys._getframe().f_back.f_globals.get("__name__") == "__main__": - return - if not is_resource_enabled(resource): - if msg is None: - msg = "Use of the `%s' resource not enabled" % resource - raise ResourceDenied(msg) - -FUZZ = 1e-6 - -def fcmp(x, y): # fuzzy comparison function - if type(x) == type(0.0) or type(y) == type(0.0): - try: - x, y = coerce(x, y) - fuzz = (abs(x) + abs(y)) * FUZZ - if abs(x-y) <= fuzz: - return 0 - except: - pass - elif type(x) == type(y) and type(x) in (type(()), type([])): - for i in range(min(len(x), len(y))): - outcome = fcmp(x[i], y[i]) - if outcome != 0: - return outcome - return cmp(len(x), len(y)) - return cmp(x, y) - -try: - str - have_unicode = 1 -except NameError: - have_unicode = 0 - -is_jython = sys.platform.startswith('java') - -import os -# Filename used for testing -if os.name == 'java': - # Jython disallows @ in module names - TESTFN = '$test' -elif os.name == 'riscos': - TESTFN = 'testfile' -else: - TESTFN = '@test' - # Unicode name only used if TEST_FN_ENCODING exists for the platform. - if have_unicode: - # Assuming sys.getfilesystemencoding()!=sys.getdefaultencoding() - # TESTFN_UNICODE is a filename that can be encoded using the - # file system encoding, but *not* with the default (ascii) encoding - if isinstance('', str): - # python -U - # XXX perhaps unicode() should accept Unicode strings? - TESTFN_UNICODE = "@test-\xe0\xf2" - else: - # 2 latin characters. - TESTFN_UNICODE = str("@test-\xe0\xf2", "latin-1") - TESTFN_ENCODING = sys.getfilesystemencoding() - -# Make sure we can write to TESTFN, try in /tmp if we can't -fp = None -try: - fp = open(TESTFN, 'w+') -except IOError: - TMP_TESTFN = os.path.join('/tmp', TESTFN) - try: - fp = open(TMP_TESTFN, 'w+') - TESTFN = TMP_TESTFN - del TMP_TESTFN - except IOError: - print(('WARNING: tests will fail, unable to write to: %s or %s' % - (TESTFN, TMP_TESTFN))) -if fp is not None: - fp.close() - unlink(TESTFN) -del os, fp - -def findfile(file, here=__file__): - """Try to find a file on sys.path and the working directory. If it is not - found the argument passed to the function is returned (this does not - necessarily signal failure; could still be the legitimate path).""" - import os - if os.path.isabs(file): - return file - path = sys.path - path = [os.path.dirname(here)] + path - for dn in path: - fn = os.path.join(dn, file) - if os.path.exists(fn): return fn - return file - -def verify(condition, reason='test failed'): - """Verify that condition is true. If not, raise TestFailed. - - The optional argument reason can be given to provide - a better error text. - """ - - if not condition: - raise TestFailed(reason) - -def vereq(a, b): - """Raise TestFailed if a == b is false. - - This is better than verify(a == b) because, in case of failure, the - error message incorporates repr(a) and repr(b) so you can see the - inputs. - - Note that "not (a == b)" isn't necessarily the same as "a != b"; the - former is tested. - """ - - if not (a == b): - raise TestFailed("%r == %r" % (a, b)) - -def sortdict(dict): - "Like repr(dict), but in sorted order." - items = list(dict.items()) - items.sort() - reprpairs = ["%r: %r" % pair for pair in items] - withcommas = ", ".join(reprpairs) - return "{%s}" % withcommas - -def check_syntax(statement): - try: - compile(statement, '', 'exec') - except SyntaxError: - pass - else: - print('Missing SyntaxError: "%s"' % statement) - -def open_urlresource(url): - import urllib.request, urllib.parse, urllib.error, urllib.parse - import os.path - - filename = urllib.parse.urlparse(url)[2].split('/')[-1] # '/': it's URL! - - for path in [os.path.curdir, os.path.pardir]: - fn = os.path.join(path, filename) - if os.path.exists(fn): - return open(fn) - - requires('urlfetch') - print('\tfetching %s ...' % url, file=get_original_stdout()) - fn, _ = urllib.request.urlretrieve(url, filename) - return open(fn) - -#======================================================================= -# Decorator for running a function in a different locale, correctly resetting -# it afterwards. - -def run_with_locale(catstr, *locales): - def decorator(func): - def inner(*args, **kwds): - try: - import locale - category = getattr(locale, catstr) - orig_locale = locale.setlocale(category) - except AttributeError: - # if the test author gives us an invalid category string - raise - except: - # cannot retrieve original locale, so do nothing - locale = orig_locale = None - else: - for loc in locales: - try: - locale.setlocale(category, loc) - break - except: - pass - - # now run the function, resetting the locale on exceptions - try: - return func(*args, **kwds) - finally: - if locale and orig_locale: - locale.setlocale(category, orig_locale) - inner.__name__ = func.__name__ - inner.__doc__ = func.__doc__ - return inner - return decorator - -#======================================================================= -# Big-memory-test support. Separate from 'resources' because memory use should be configurable. - -# Some handy shorthands. Note that these are used for byte-limits as well -# as size-limits, in the various bigmem tests -_1M = 1024*1024 -_1G = 1024 * _1M -_2G = 2 * _1G - -def set_memlimit(limit): - import re - global max_memuse - sizes = { - 'k': 1024, - 'm': _1M, - 'g': _1G, - 't': 1024*_1G, - } - m = re.match(r'(\d+(\.\d+)?) (K|M|G|T)b?$', limit, - re.IGNORECASE | re.VERBOSE) - if m is None: - raise ValueError('Invalid memory limit %r' % (limit,)) - memlimit = int(float(m.group(1)) * sizes[m.group(3).lower()]) - if memlimit < 2.5*_1G: - raise ValueError('Memory limit %r too low to be useful' % (limit,)) - max_memuse = memlimit - -def bigmemtest(minsize, memuse, overhead=5*_1M): - """Decorator for bigmem tests. - - 'minsize' is the minimum useful size for the test (in arbitrary, - test-interpreted units.) 'memuse' is the number of 'bytes per size' for - the test, or a good estimate of it. 'overhead' specifies fixed overhead, - independant of the testsize, and defaults to 5Mb. - - The decorator tries to guess a good value for 'size' and passes it to - the decorated test function. If minsize * memuse is more than the - allowed memory use (as defined by max_memuse), the test is skipped. - Otherwise, minsize is adjusted upward to use up to max_memuse. - """ - def decorator(f): - def wrapper(self): - if not max_memuse: - # If max_memuse is 0 (the default), - # we still want to run the tests with size set to a few kb, - # to make sure they work. We still want to avoid using - # too much memory, though, but we do that noisily. - maxsize = 5147 - self.failIf(maxsize * memuse + overhead > 20 * _1M) - else: - maxsize = int((max_memuse - overhead) / memuse) - if maxsize < minsize: - # Really ought to print 'test skipped' or something - if verbose: - sys.stderr.write("Skipping %s because of memory " - "constraint\n" % (f.__name__,)) - return - # Try to keep some breathing room in memory use - maxsize = max(maxsize - 50 * _1M, minsize) - return f(self, maxsize) - wrapper.minsize = minsize - wrapper.memuse = memuse - wrapper.overhead = overhead - return wrapper - return decorator - -#======================================================================= -# Preliminary PyUNIT integration. - -from . import unittest - - -class BasicTestRunner: - def run(self, test): - result = unittest.TestResult() - test(result) - return result - -def run_suite(suite, testclass=None): - """Run tests from a unittest.TestSuite-derived class.""" - if verbose: - runner = unittest.TextTestRunner(sys.stdout, verbosity=2) - else: - runner = BasicTestRunner() - - result = runner.run(suite) - if not result.wasSuccessful(): - if len(result.errors) == 1 and not result.failures: - err = result.errors[0][1] - elif len(result.failures) == 1 and not result.errors: - err = result.failures[0][1] - else: - if testclass is None: - msg = "errors occurred; run in verbose mode for details" - else: - msg = "errors occurred in %s.%s" \ - % (testclass.__module__, testclass.__name__) - raise TestFailed(msg) - raise TestFailed(err) - - -def run_unittest(*classes): - """Run tests from unittest.TestCase-derived classes.""" - suite = unittest.TestSuite() - for cls in classes: - if isinstance(cls, (unittest.TestSuite, unittest.TestCase)): - suite.addTest(cls) - else: - suite.addTest(unittest.makeSuite(cls)) - if len(classes)==1: - testclass = classes[0] - else: - testclass = None - run_suite(suite, testclass) - - -#======================================================================= -# doctest driver. - -def run_doctest(module, verbosity=None): - """Run doctest on the given module. Return (#failures, #tests). - - If optional argument verbosity is not specified (or is None), pass - test_support's belief about verbosity on to doctest. Else doctest's - usual behavior is used (it searches sys.argv for -v). - """ - - import doctest - - if verbosity is None: - verbosity = verbose - else: - verbosity = None - - # Direct doctest output (normally just errors) to real stdout; doctest - # output shouldn't be compared by regrtest. - save_stdout = sys.stdout - sys.stdout = get_original_stdout() - try: - f, t = doctest.testmod(module, verbose=verbosity) - if f: - raise TestFailed("%d of %d doctests failed" % (f, t)) - finally: - sys.stdout = save_stdout - if verbose: - print('doctest (%s) ... %d tests with zero failures' % (module.__name__, t)) - return f, t diff --git a/test/unittest.py b/test/unittest.py deleted file mode 100644 index 2ff3e2d..0000000 --- a/test/unittest.py +++ /dev/null @@ -1,840 +0,0 @@ -#! /usr/bin/python2.5 -from __future__ import print_function -''' -Python unit testing framework, based on Erich Gamma's JUnit and Kent Beck's -Smalltalk testing framework. - -This module contains the core framework classes that form the basis of -specific test cases and suites (TestCase, TestSuite etc.), and also a -text-based utility class for running the tests and reporting the results - (TextTestRunner). - -Simple usage: - - import unittest - - class IntegerArithmenticTestCase(unittest.TestCase): - def testAdd(self): ## test method names begin 'test*' - self.assertEquals((1 + 2), 3) - self.assertEquals(0 + 1, 1) - def testMultiply(self): - self.assertEquals((0 * 10), 0) - self.assertEquals((5 * 8), 40) - - if __name__ == '__main__': - unittest.main() - -Further information is available in the bundled documentation, and from - - http://pyunit.sourceforge.net/ - -Copyright (c) 1999-2003 Steve Purcell -This module is free software, and you may redistribute it and/or modify -it under the same terms as Python itself, so long as this copyright message -and disclaimer are retained in their original form. - -IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, -SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF -THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGE. - -THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, -AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, -SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. -''' - -__author__ = "Steve Purcell" -__email__ = "stephen_purcell at yahoo dot com" -__version__ = "#Revision: 1.63 $"[11:-2] - -import time -import sys -import traceback -import os -import types - -class ReferenceLeak(Exception): - def __init__(self, s, n): - self.n = n - self.s = s - - def __str__(self): - return '%s: %d' % (self.s, self.n) - -############################################################################## -# Exported classes and functions -############################################################################## -__all__ = ['TestResult', 'TestCase', 'TestSuite', 'TextTestRunner', - 'TestLoader', 'FunctionTestCase', 'main', 'defaultTestLoader'] - -# Expose obsolete functions for backwards compatibility -__all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases']) - - -############################################################################## -# Test framework core -############################################################################## - -# All classes defined herein are 'new-style' classes, allowing use of 'super()' -__metaclass__ = type - -def _strclass(cls): - return "%s.%s" % (cls.__module__, cls.__name__) - -__unittest = 1 - -class TestResult: - """Holder for test result information. - - Test results are automatically managed by the TestCase and TestSuite - classes, and do not need to be explicitly manipulated by writers of tests. - - Each instance holds the total number of tests run, and collections of - failures and errors that occurred among those test runs. The collections - contain tuples of (testcase, exceptioninfo), where exceptioninfo is the - formatted traceback of the error that occurred. - """ - def __init__(self): - self.failures = [] - self.errors = [] - self.testsRun = 0 - self.shouldStop = 0 - - def startTest(self, test): - "Called when the given test is about to be run" - self.testsRun = self.testsRun + 1 - - def stopTest(self, test): - "Called when the given test has been run" - pass - - def addError(self, test, err): - """Called when an error has occurred. 'err' is a tuple of values as - returned by sys.exc_info(). - """ - self.errors.append((test, self._exc_info_to_string(err, test))) - - def addFailure(self, test, err): - """Called when an error has occurred. 'err' is a tuple of values as - returned by sys.exc_info().""" - self.failures.append((test, self._exc_info_to_string(err, test))) - - def addSuccess(self, test): - "Called when a test has completed successfully" - pass - - def wasSuccessful(self): - "Tells whether or not this result was a success" - return len(self.failures) == len(self.errors) == 0 - - def stop(self): - "Indicates that the tests should be aborted" - self.shouldStop = True - - def _exc_info_to_string(self, err, test): - """Converts a sys.exc_info()-style tuple of values into a string.""" - exctype, value, tb = err - # Skip test runner traceback levels - while tb and self._is_relevant_tb_level(tb): - tb = tb.tb_next - if exctype is test.failureException: - # Skip assert*() traceback levels - length = self._count_relevant_tb_levels(tb) - return ''.join(traceback.format_exception(exctype, value, tb, length)) - return ''.join(traceback.format_exception(exctype, value, tb)) - - def _is_relevant_tb_level(self, tb): - return '__unittest' in tb.tb_frame.f_globals - - def _count_relevant_tb_levels(self, tb): - length = 0 - while tb and not self._is_relevant_tb_level(tb): - length += 1 - tb = tb.tb_next - return length - - def __repr__(self): - return "<%s run=%i errors=%i failures=%i>" % \ - (_strclass(self.__class__), self.testsRun, len(self.errors), - len(self.failures)) - -class TestCase: - """A class whose instances are single test cases. - - By default, the test code itself should be placed in a method named - 'runTest'. - - If the fixture may be used for many test cases, create as - many test methods as are needed. When instantiating such a TestCase - subclass, specify in the constructor arguments the name of the test method - that the instance is to execute. - - Test authors should subclass TestCase for their own tests. Construction - and deconstruction of the test's environment ('fixture') can be - implemented by overriding the 'setUp' and 'tearDown' methods respectively. - - If it is necessary to override the __init__ method, the base class - __init__ method must always be called. It is important that subclasses - should not change the signature of their __init__ method, since instances - of the classes are instantiated automatically by parts of the framework - in order to be run. - """ - - # This attribute determines which exception will be raised when - # the instance's assertion methods fail; test methods raising this - # exception will be deemed to have 'failed' rather than 'errored' - - failureException = AssertionError - - def __init__(self, methodName='runTest'): - """Create an instance of the class that will use the named test - method when executed. Raises a ValueError if the instance does - not have a method with the specified name. - """ - try: - self._testMethodName = methodName - testMethod = getattr(self, methodName) - self._testMethodDoc = testMethod.__doc__ - except AttributeError: - raise ValueError("no such test method in %s: %s" % \ - (self.__class__, methodName)) - - def assertIn(self, member, container, msg=None): - """Just like self.assertTrue(a in b), but with a nicer default message.""" - if member not in container: - standardMsg = '%s not found in %s' % (repr(member), - repr(container)) - self.fail(str((msg, standardMsg))) - - def assertNotIn(self, member, container, msg=None): - """Just like self.assertTrue(a not in b), but with a nicer default message.""" - if member in container: - standardMsg = '%s unexpectedly found in %s' % (repr(member), - repr(container)) - self.fail(str((msg, standardMsg))) - - - def setUp(self): - "Hook method for setting up the test fixture before exercising it." - pass - - def tearDown(self): - "Hook method for deconstructing the test fixture after testing it." - pass - - def countTestCases(self): - return 1 - - def defaultTestResult(self): - return TestResult() - - def shortDescription(self): - """Returns a one-line description of the test, or None if no - description has been provided. - - The default implementation of this method returns the first line of - the specified test method's docstring. - """ - doc = self._testMethodDoc - return doc and doc.split("\n")[0].strip() or None - - def id(self): - return "%s.%s" % (_strclass(self.__class__), self._testMethodName) - - def __str__(self): - return "%s (%s)" % (self._testMethodName, _strclass(self.__class__)) - - def __repr__(self): - return "<%s testMethod=%s>" % \ - (_strclass(self.__class__), self._testMethodName) - - def run(self, result=None): - self.run_(result, False) - try: - self.run_(result, True) - except AttributeError: - pass - - def run_(self, result, memcheck): - if result is None: result = self.defaultTestResult() - if memcheck and not hasattr(sys, 'gettotalrefcount'): - return - result.startTest(self) - testMethod = getattr(self, self._testMethodName) - try: - import gc - - total_start = 0 - total_finish = 0 - ok = True - gc.collect() - ob_start = set(id(x) for x in gc.get_objects()) - try: - total_start = sys.gettotalrefcount() - except AttributeError: - pass - - try: - self.setUp() - except KeyboardInterrupt: - raise - except: - result.addError(self, self._exc_info()) - return - - ok = False - try: - testMethod() - ok = True - except self.failureException: - result.addFailure(self, self._exc_info()) - except KeyboardInterrupt: - raise - except: - result.addError(self, self._exc_info()) - - try: - self.tearDown() - if ok and memcheck: - gc.collect() - gc.collect() - total_finish = sys.gettotalrefcount() - ob_finish = gc.get_objects() - if len(ob_start) != len(ob_finish): - #for ob in ob_finish: - # if id(ob) not in ob_start and ob is not ob_start: - # print ob - raise ReferenceLeak('more objects', len(ob_finish)-len(ob_start)) - if total_finish != total_start: - print(total_start, total_finish) - raise ReferenceLeak('more references', total_finish - total_start) - except KeyboardInterrupt: - raise - except: - result.addError(self, self._exc_info()) - ok = False - if ok: result.addSuccess(self) - finally: - result.stopTest(self) - - def __call__(self, *args, **kwds): - return self.run(*args, **kwds) - - def debug(self): - """Run the test without collecting errors in a TestResult""" - self.setUp() - getattr(self, self._testMethodName)() - self.tearDown() - - def _exc_info(self): - """Return a version of sys.exc_info() with the traceback frame - minimised; usually the top level of the traceback frame is not - needed. - """ - exctype, excvalue, tb = sys.exc_info() - if sys.platform[:4] == 'java': ## tracebacks look different in Jython - return (exctype, excvalue, tb) - return (exctype, excvalue, tb) - - def fail(self, msg=None): - """Fail immediately, with the given message.""" - raise self.failureException(msg) - - def failIf(self, expr, msg=None): - "Fail the test if the expression is true." - if expr: raise self.failureException(msg) - - def failUnless(self, expr, msg=None): - """Fail the test unless the expression is true.""" - if not expr: raise self.failureException(msg) - - def failUnlessRaises(self, excClass, callableObj, *args, **kwargs): - """Fail unless an exception of class excClass is thrown - by callableObj when invoked with arguments args and keyword - arguments kwargs. If a different type of exception is - thrown, it will not be caught, and the test case will be - deemed to have suffered an error, exactly as for an - unexpected exception. - """ - try: - callableObj(*args, **kwargs) - except excClass: - return - else: - if hasattr(excClass,'__name__'): excName = excClass.__name__ - else: excName = str(excClass) - raise self.failureException("%s not raised" % excName) - - def failUnlessEqual(self, first, second, msg=None): - """Fail if the two objects are unequal as determined by the '==' - operator. - """ - if not first == second: - raise self.failureException(msg or '%r != %r' % (first, second)) - - def failIfEqual(self, first, second, msg=None): - """Fail if the two objects are equal as determined by the '==' - operator. - """ - if first == second: - raise self.failureException(msg or '%r == %r' % (first, second)) - - def failUnlessAlmostEqual(self, first, second, places=7, msg=None): - """Fail if the two objects are unequal as determined by their - difference rounded to the given number of decimal places - (default 7) and comparing to zero. - - Note that decimal places (from zero) are usually not the same - as significant digits (measured from the most signficant digit). - """ - if round(second-first, places) != 0: - raise self.failureException(msg or '%r != %r within %r places' % (first, second, places)) - - def failIfAlmostEqual(self, first, second, places=7, msg=None): - """Fail if the two objects are equal as determined by their - difference rounded to the given number of decimal places - (default 7) and comparing to zero. - - Note that decimal places (from zero) are usually not the same - as significant digits (measured from the most signficant digit). - """ - if round(second-first, places) == 0: - raise self.failureException(msg or '%r == %r within %r places' % (first, second, places)) - - # Synonyms for assertion methods - - assertEqual = assertEquals = failUnlessEqual - - assertNotEqual = assertNotEquals = failIfEqual - - assertAlmostEqual = assertAlmostEquals = failUnlessAlmostEqual - - assertNotAlmostEqual = assertNotAlmostEquals = failIfAlmostEqual - - assertRaises = failUnlessRaises - - assert_ = assertTrue = failUnless - - assertFalse = failIf - - - -class TestSuite: - """A test suite is a composite test consisting of a number of TestCases. - - For use, create an instance of TestSuite, then add test case instances. - When all tests have been added, the suite can be passed to a test - runner, such as TextTestRunner. It will run the individual test cases - in the order in which they were added, aggregating the results. When - subclassing, do not forget to call the base class constructor. - """ - def __init__(self, tests=()): - self._tests = [] - self.addTests(tests) - - def __repr__(self): - return "<%s tests=%s>" % (_strclass(self.__class__), self._tests) - - __str__ = __repr__ - - def __iter__(self): - return iter(self._tests) - - def countTestCases(self): - cases = 0 - for test in self._tests: - cases += test.countTestCases() - return cases - - def addTest(self, test): - self._tests.append(test) - - def addTests(self, tests): - for test in tests: - self.addTest(test) - - def run(self, result): - for test in self._tests: - if result.shouldStop: - break - test(result) - return result - - def __call__(self, *args, **kwds): - return self.run(*args, **kwds) - - def debug(self): - """Run the tests without collecting errors in a TestResult""" - for test in self._tests: test.debug() - - -class FunctionTestCase(TestCase): - """A test case that wraps a test function. - - This is useful for slipping pre-existing test functions into the - PyUnit framework. Optionally, set-up and tidy-up functions can be - supplied. As with TestCase, the tidy-up ('tearDown') function will - always be called if the set-up ('setUp') function ran successfully. - """ - - def __init__(self, testFunc, setUp=None, tearDown=None, - description=None): - TestCase.__init__(self) - self.__setUpFunc = setUp - self.__tearDownFunc = tearDown - self.__testFunc = testFunc - self.__description = description - - def setUp(self): - if self.__setUpFunc is not None: - self.__setUpFunc() - - def tearDown(self): - if self.__tearDownFunc is not None: - self.__tearDownFunc() - - def runTest(self): - self.__testFunc() - - def id(self): - return self.__testFunc.__name__ - - def __str__(self): - return "%s (%s)" % (_strclass(self.__class__), self.__testFunc.__name__) - - def __repr__(self): - return "<%s testFunc=%s>" % (_strclass(self.__class__), self.__testFunc) - - def shortDescription(self): - if self.__description is not None: return self.__description - doc = self.__testFunc.__doc__ - return doc and doc.split("\n")[0].strip() or None - - - -############################################################################## -# Locating and loading tests -############################################################################## - -class TestLoader: - """This class is responsible for loading tests according to various - criteria and returning them wrapped in a Test - """ - testMethodPrefix = 'test' - suiteClass = TestSuite - - def loadTestsFromTestCase(self, testCaseClass): - """Return a suite of all tests cases contained in testCaseClass""" - if issubclass(testCaseClass, TestSuite): - raise TypeError("Test cases should not be derived from TestSuite. Maybe you meant to derive from TestCase?") - testCaseNames = self.getTestCaseNames(testCaseClass) - if not testCaseNames and hasattr(testCaseClass, 'runTest'): - testCaseNames = ['runTest'] - return self.suiteClass(list(map(testCaseClass, testCaseNames))) - - def loadTestsFromModule(self, module): - """Return a suite of all tests cases contained in the given module""" - tests = [] - for name in dir(module): - obj = getattr(module, name) - if (isinstance(obj, type) and - issubclass(obj, TestCase)): - tests.append(self.loadTestsFromTestCase(obj)) - return self.suiteClass(tests) - - def loadTestsFromName(self, name, module=None): - """Return a suite of all tests cases given a string specifier. - - The name may resolve either to a module, a test case class, a - test method within a test case class, or a callable object which - returns a TestCase or TestSuite instance. - - The method optionally resolves the names relative to a given module. - """ - parts = name.split('.') - if module is None: - parts_copy = parts[:] - while parts_copy: - try: - module = __import__('.'.join(parts_copy)) - break - except ImportError: - del parts_copy[-1] - if not parts_copy: raise - parts = parts[1:] - obj = module - for part in parts: - parent, obj = obj, getattr(obj, part) - - if type(obj) == types.ModuleType: - return self.loadTestsFromModule(obj) - elif (isinstance(obj, type) and - issubclass(obj, TestCase)): - return self.loadTestsFromTestCase(obj) - elif type(obj) == types.UnboundMethodType: - return parent(obj.__name__) - elif isinstance(obj, TestSuite): - return obj - elif hasattr(obj, '__call__'): - test = obj() - if not isinstance(test, (TestCase, TestSuite)): - raise ValueError("calling %s returned %s, not a test" % (obj,test)) - return test - else: - raise ValueError("don't know how to make test from: %s" % obj) - - def loadTestsFromNames(self, names, module=None): - """Return a suite of all tests cases found using the given sequence - of string specifiers. See 'loadTestsFromName()'. - """ - suites = [self.loadTestsFromName(name, module) for name in names] - return self.suiteClass(suites) - - def getTestCaseNames(self, testCaseClass): - """Return a sorted sequence of method names found within testCaseClass - """ - def isTestMethod(attrname, testCaseClass=testCaseClass, prefix=self.testMethodPrefix): - return attrname.startswith(prefix) and hasattr(getattr(testCaseClass, attrname), '__call__') - testFnNames = list(filter(isTestMethod, dir(testCaseClass))) - for baseclass in testCaseClass.__bases__: - for testFnName in self.getTestCaseNames(baseclass): - if testFnName not in testFnNames: # handle overridden methods - testFnNames.append(testFnName) - return testFnNames - - - -defaultTestLoader = TestLoader() - - -############################################################################## -# Patches for old functions: these functions should be considered obsolete -############################################################################## - -def _makeLoader(prefix, suiteClass=None): - loader = TestLoader() - loader.testMethodPrefix = prefix - if suiteClass: loader.suiteClass = suiteClass - return loader - -def getTestCaseNames(testCaseClass, prefix): - return _makeLoader(prefix).getTestCaseNames(testCaseClass) - -def makeSuite(testCaseClass, prefix='test', suiteClass=TestSuite): - return _makeLoader(prefix, suiteClass).loadTestsFromTestCase(testCaseClass) - -def findTestCases(module, prefix='test', suiteClass=TestSuite): - return _makeLoader(prefix, suiteClass).loadTestsFromModule(module) - - -############################################################################## -# Text UI -############################################################################## - -class _WritelnDecorator: - """Used to decorate file-like objects with a handy 'writeln' method""" - def __init__(self,stream): - self.stream = stream - - def __getattr__(self, attr): - return getattr(self.stream,attr) - - def writeln(self, arg=None): - if arg: self.write(arg) - self.write('\n') # text-mode streams translate to \r\n if needed - - -class _TextTestResult(TestResult): - """A test result class that can print formatted text results to a stream. - - Used by TextTestRunner. - """ - separator1 = '=' * 70 - separator2 = '-' * 70 - - def __init__(self, stream, descriptions, verbosity): - TestResult.__init__(self) - self.stream = stream - self.showAll = verbosity > 1 - self.dots = verbosity == 1 - self.descriptions = descriptions - - def getDescription(self, test): - if self.descriptions: - return test.shortDescription() or str(test) - else: - return str(test) - - def startTest(self, test): - TestResult.startTest(self, test) - if self.showAll: - self.stream.write(self.getDescription(test)) - self.stream.write(" ... ") - - def addSuccess(self, test): - TestResult.addSuccess(self, test) - if self.showAll: - self.stream.writeln("ok") - elif self.dots: - self.stream.write('.') - - def addError(self, test, err): - TestResult.addError(self, test, err) - if self.showAll: - self.stream.writeln("ERROR") - elif self.dots: - self.stream.write('E') - - def addFailure(self, test, err): - TestResult.addFailure(self, test, err) - if self.showAll: - self.stream.writeln("FAIL") - elif self.dots: - self.stream.write('F') - - def printErrors(self): - if self.dots or self.showAll: - self.stream.writeln() - self.printErrorList('ERROR', self.errors) - self.printErrorList('FAIL', self.failures) - - def printErrorList(self, flavour, errors): - for test, err in errors: - self.stream.writeln(self.separator1) - self.stream.writeln("%s: %s" % (flavour,self.getDescription(test))) - self.stream.writeln(self.separator2) - self.stream.writeln("%s" % err) - - -class TextTestRunner: - """A test runner class that displays results in textual form. - - It prints out the names of tests as they are run, errors as they - occur, and a summary of the results at the end of the test run. - """ - def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1): - self.stream = _WritelnDecorator(stream) - self.descriptions = descriptions - self.verbosity = verbosity - - def _makeResult(self): - return _TextTestResult(self.stream, self.descriptions, self.verbosity) - - def run(self, test): - "Run the given test case or test suite." - result = self._makeResult() - startTime = time.time() - test(result) - stopTime = time.time() - timeTaken = stopTime - startTime - result.printErrors() - self.stream.writeln(result.separator2) - run = result.testsRun - self.stream.writeln("Ran %d test%s in %.3fs" % - (run, run != 1 and "s" or "", timeTaken)) - self.stream.writeln() - if not result.wasSuccessful(): - self.stream.write("FAILED (") - failed, errored = list(map(len, (result.failures, result.errors))) - if failed: - self.stream.write("failures=%d" % failed) - if errored: - if failed: self.stream.write(", ") - self.stream.write("errors=%d" % errored) - self.stream.writeln(")") - else: - self.stream.writeln("OK") - return result - - - -############################################################################## -# Facilities for running tests from the command line -############################################################################## - -class TestProgram: - """A command-line program that runs a set of tests; this is primarily - for making test modules conveniently executable. - """ - USAGE = """\ -Usage: %(progName)s [options] [test] [...] - -Options: - -h, --help Show this message - -v, --verbose Verbose output - -q, --quiet Minimal output - -Examples: - %(progName)s - run default set of tests - %(progName)s MyTestSuite - run suite 'MyTestSuite' - %(progName)s MyTestCase.testSomething - run MyTestCase.testSomething - %(progName)s MyTestCase - run all 'test*' test methods - in MyTestCase -""" - def __init__(self, module='__main__', defaultTest=None, - argv=None, testRunner=None, testLoader=defaultTestLoader): - if type(module) == type(''): - self.module = __import__(module) - for part in module.split('.')[1:]: - self.module = getattr(self.module, part) - else: - self.module = module - if argv is None: - argv = sys.argv - self.verbosity = 1 - self.defaultTest = defaultTest - self.testRunner = testRunner - self.testLoader = testLoader - self.progName = os.path.basename(argv[0]) - self.parseArgs(argv) - self.runTests() - - def usageExit(self, msg=None): - if msg: print(msg) - print(self.USAGE % self.__dict__) - sys.exit(2) - - def parseArgs(self, argv): - import getopt - try: - options, args = getopt.getopt(argv[1:], 'hHvq', - ['help','verbose','quiet']) - for opt, value in options: - if opt in ('-h','-H','--help'): - self.usageExit() - if opt in ('-q','--quiet'): - self.verbosity = 0 - if opt in ('-v','--verbose'): - self.verbosity = 2 - if len(args) == 0 and self.defaultTest is None: - self.test = self.testLoader.loadTestsFromModule(self.module) - return - if len(args) > 0: - self.testNames = args - else: - self.testNames = (self.defaultTest,) - self.createTests() - except getopt.error as msg: - self.usageExit(msg) - - def createTests(self): - self.test = self.testLoader.loadTestsFromNames(self.testNames, - self.module) - - def runTests(self): - if self.testRunner is None: - self.testRunner = TextTestRunner(verbosity=self.verbosity) - result = self.testRunner.run(self.test) - sys.exit(not result.wasSuccessful()) - -main = TestProgram - - -############################################################################## -# Executing this module from the command line -############################################################################## - -if __name__ == "__main__": - main(module=None) diff --git a/test_blist.py b/test_blist.py index 9a32a42..2028913 100755 --- a/test_blist.py +++ b/test_blist.py @@ -37,10 +37,11 @@ import sys import os import unittest, operator -import blist, pickle, _blist +import blist, pickle +from blist import _blist #BList = list -from test import test_support, list_tests, sortedlist_tests, btuple_tests -from test import sorteddict_tests, test_set +from blist.test import test_support, list_tests, sortedlist_tests, btuple_tests +from blist.test import sorteddict_tests, test_set limit = _blist._limit n = 512//8 * limit -- cgit v1.2.3