Index: src/org/python/core/PySequence.java =================================================================== --- src/org/python/core/PySequence.java (revision 3589) +++ src/org/python/core/PySequence.java (working copy) @@ -23,7 +23,8 @@ * This constructor is used by PyJavaClass.init() */ public PySequence() {} - + public int gListAllocatedStatus = -1; + protected PySequence(PyType type) { super(type); } @@ -384,6 +385,7 @@ int start = getStart(s_start, step, length); int stop = getStop(s_stop, start, step, length); setslice(start, stop, step, value); + gListAllocatedStatus = __len__(); } public synchronized void __delslice__(PyObject s_start, PyObject s_stop, @@ -399,6 +401,7 @@ int start = getStart(s_start, step, length); int stop = getStop(s_stop, start, step, length); delRange(start, stop, step); + gListAllocatedStatus = __len__(); } public synchronized void __setitem__(int index, PyObject value) { @@ -426,6 +429,7 @@ "sequence subscript must be integer or slice"); } } + gListAllocatedStatus = __len__(); } public synchronized void __delitem__(PyObject index) { @@ -453,6 +457,7 @@ "sequence subscript must be integer or slice"); } } + gListAllocatedStatus = __len__(); } public synchronized Object __tojava__(Class c) throws PyIgnoreMethodTag { Index: src/org/python/core/MergeState.java =================================================================== --- src/org/python/core/MergeState.java (revision 3589) +++ src/org/python/core/MergeState.java (working copy) @@ -31,26 +31,79 @@ */ static final int MERGESTATE_TEMP_SIZE = 256; - private PyObject[] a = new PyObject[MERGESTATE_TEMP_SIZE]; + private KVPair[] a = new KVPair[MERGESTATE_TEMP_SIZE]; private int[] base = new int[MAX_MERGE_PENDING]; private int[] len = new int[MAX_MERGE_PENDING]; private PyObject compare; - private PyObject[] data; + private PyObject key; + private boolean reverse; private int size; private int n; - - MergeState(PyObject[] data, int size, PyObject compare) { - this.data = data; + private PyList gOriginalList; + private KVPair[] kvdata; + + MergeState(PyList list, PyObject compare, PyObject key, boolean reverse) { if(compare != Py.None) { this.compare = compare; - } - this.size = size; + } + if(key != Py.None) { + this.key = key; + } + this.reverse = reverse; this.n = 0; + + this.size = list.size(); + this.kvdata = new KVPair[size]; + + //resetting the list to find if any update is done after sorting + this.gOriginalList = list; + this.gOriginalList.gListAllocatedStatus = -1; } + + private class KVPair { + public PyObject key; + public PyObject value; - public void sort() { + public KVPair(PyObject key, PyObject value) { + this.key = key; + this.value = value; + } + } + + public void sort() { + PyObject origData[] = gOriginalList.getArray(); + PyObject[] data = new PyObject[size]; + //list data is copied to new array and is temporarily made empty, so that + //mutations performed by comparison or key functions can't affect + //the slice of memory we're sorting. + System.arraycopy(origData, 0, data, 0, size); + //list.clear(); + + //If keyfunction is given, object of type KVPair with key resulting from + //key(data[pos]) and value from data[pos] is created. Otherwise the key + //of KVPair object will take the value of data[pos] and the corresponding + //value will be null. Thus, we will do sorting on the keys of KVPair object + //array effectively without disturbing the incoming list object. + if (this.key != null) { + for (int i = 0; i < size; i++) { + this.kvdata[i] = new KVPair(key.__call__(data[i]), data[i]); + } + } else { + for (int i = 0; i < size; i++) { + this.kvdata[i] = new KVPair(data[i], null); + } + } + //make data null, we dont need this reference afterwards + data = null; + + //Reverse sort stability achieved by initially reversing the list, + //applying a stable forward sort, then reversing the final result. + if (reverse && size > 1) { + reverse_slice(0, size); + } + int nremaining = this.size; if (nremaining < 2) { return; @@ -88,12 +141,34 @@ //assert_(ms.n == 1); //assert_(ms.base[0] == 0); //assert_(ms.len[0] == size); + + //The user mucked up with the list during the sort, + //and so, the value error is thrown + if (gOriginalList.gListAllocatedStatus >= 0) { + throw Py.ValueError("list modified during sort"); + } + + if (reverse && size > 1) { + reverse_slice(0, size); + } + + //Now copy the sorted values from KVPairs if key function is given, + //otherwise the keys from KVPairs. + if (this.key != null) { + for (int i = 0; i < size; i++) { + origData[i] = this.kvdata[i].value; + } + } else { + for (int i = 0; i < size; i++) { + origData[i] = this.kvdata[i].key; + } + } } public void getmem(int need) { if (need <= this.a.length) return; - this.a = new PyObject[need]; + this.a = new KVPair[need]; } int count_run(int lo, int hi, boolean[] descending) { @@ -103,15 +178,15 @@ if (lo == hi) return 1; int localN = 2; - if (iflt(this.data[lo], this.data[lo-1])) { + if (iflt(this.kvdata[lo].key, this.kvdata[lo-1].key)) { descending[0] = true; for (lo = lo + 1; lo < hi; ++lo, ++localN) { - if (! iflt(this.data[lo], this.data[lo-1])) + if (! iflt(this.kvdata[lo].key, this.kvdata[lo-1].key)) break; } } else { for (lo = lo + 1; lo < hi; ++lo, ++localN) { - if (iflt(this.data[lo], this.data[lo-1])) + if (iflt(this.kvdata[lo].key, this.kvdata[lo-1].key)) break; } } @@ -126,18 +201,18 @@ //assert_(na > 0 && nb > 0 && pa + na == pb); getmem(na); - System.arraycopy(this.data, pa, this.a, 0, na); + System.arraycopy(this.kvdata, pa, this.a, 0, na); int dest = pa; pa = 0; - this.data[dest++] = this.data[pb++]; + this.kvdata[dest++] = this.kvdata[pb++]; --nb; if (nb == 0) return; if (na == 1) { // CopyB; - System.arraycopy(this.data, pb, this.data, dest, nb); - this.data[dest + nb] = this.a[pa]; + System.arraycopy(this.kvdata, pb, this.kvdata, dest, nb); + this.kvdata[dest + nb] = this.a[pa]; return; } @@ -150,9 +225,9 @@ * appears to win consistently. */ for (;;) { - boolean k = iflt(this.data[pb], this.a[pa]); + boolean k = iflt(this.kvdata[pb].key, this.a[pa].key); if (k) { - this.data[dest++] = this.data[pb++]; + this.kvdata[dest++] = this.kvdata[pb++]; ++bcount; acount = 0; --nb; @@ -161,14 +236,14 @@ if (bcount >= MIN_GALLOP) break; } else { - this.data[dest++] = this.a[pa++]; + this.kvdata[dest++] = this.a[pa++]; ++acount; bcount = 0; --na; if (na == 1) { // CopyB; - System.arraycopy(this.data, pb, this.data, dest, nb); - this.data[dest + nb] = this.a[pa]; + System.arraycopy(this.kvdata, pb, this.kvdata, dest, nb); + this.kvdata[dest + nb] = this.a[pa]; na = 0; return; } @@ -183,17 +258,17 @@ * anymore. */ do { - int k = gallop_right(this.data[pb], this.a, pa, na, 0); + int k = gallop_right(this.kvdata[pb].key, this.a, pa, na, 0); acount = k; if (k != 0) { - System.arraycopy(this.a, pa, this.data, dest, k); + System.arraycopy(this.a, pa, this.kvdata, dest, k); dest += k; pa += k; na -= k; if (na == 1) { // CopyB - System.arraycopy(this.data, pb, this.data, dest, nb); - this.data[dest + nb] = this.a[pa]; + System.arraycopy(this.kvdata, pb, this.kvdata, dest, nb); + this.kvdata[dest + nb] = this.a[pa]; na = 0; return; } @@ -205,27 +280,27 @@ return; } - this.data[dest++] = this.data[pb++]; + this.kvdata[dest++] = this.kvdata[pb++]; --nb; if (nb == 0) return; - k = gallop_left(this.a[pa], this.data, pb, nb, 0); + k = gallop_left(this.a[pa].key, this.kvdata, pb, nb, 0); bcount = k; if (k != 0) { - System.arraycopy(this.data, pb, this.data, dest, k); + System.arraycopy(this.kvdata, pb, this.kvdata, dest, k); dest += k; pb += k; nb -= k; if (nb == 0) return; } - this.data[dest++] = this.a[pa++]; + this.kvdata[dest++] = this.a[pa++]; --na; if (na == 1) { // CopyB; - System.arraycopy(this.data, pb, this.data, dest, nb); - this.data[dest + nb] = this.a[pa]; + System.arraycopy(this.kvdata, pb, this.kvdata, dest, nb); + this.kvdata[dest + nb] = this.a[pa]; na = 0; return; } @@ -233,7 +308,7 @@ } } finally { if (na != 0) - System.arraycopy(this.a, pa, this.data, dest, na); + System.arraycopy(this.a, pa, this.kvdata, dest, na); //dump_data("result", origpa, cnt); } @@ -250,12 +325,12 @@ getmem(nb); int dest = pb + nb - 1; int basea = pa; - System.arraycopy(this.data, pb, this.a, 0, nb); + System.arraycopy(this.kvdata, pb, this.a, 0, nb); pb = nb - 1; pa += na - 1; - this.data[dest--] = this.data[pa--]; + this.kvdata[dest--] = this.kvdata[pa--]; --na; if (na == 0) return; @@ -263,8 +338,8 @@ // CopyA; dest -= na; pa -= na; - System.arraycopy(this.data, pa+1, this.data, dest+1, na); - this.data[dest] = this.a[pb]; + System.arraycopy(this.kvdata, pa+1, this.kvdata, dest+1, na); + this.kvdata[dest] = this.a[pb]; nb = 0; return; } @@ -278,9 +353,9 @@ * appears to win consistently. */ for (;;) { - boolean k = iflt(this.a[pb], this.data[pa]); + boolean k = iflt(this.a[pb].key, this.kvdata[pa].key); if (k) { - this.data[dest--] = this.data[pa--]; + this.kvdata[dest--] = this.kvdata[pa--]; ++acount; bcount = 0; --na; @@ -289,7 +364,7 @@ if (acount >= MIN_GALLOP) break; } else { - this.data[dest--] = this.a[pb--]; + this.kvdata[dest--] = this.a[pb--]; ++bcount; acount = 0; --nb; @@ -297,8 +372,8 @@ // CopyA dest -= na; pa -= na; - System.arraycopy(this.data, pa+1, this.data, dest+1, na); - this.data[dest] = this.a[pb]; + System.arraycopy(this.kvdata, pa+1, this.kvdata, dest+1, na); + this.kvdata[dest] = this.a[pb]; nb = 0; return; } @@ -313,42 +388,42 @@ * anymore. */ do { - int k = gallop_right(this.a[pb], this.data, basea, na, na-1); + int k = gallop_right(this.a[pb].key, this.kvdata, basea, na, na-1); acount = k = na - k; if (k != 0) { dest -= k; pa -= k; - System.arraycopy(this.data, pa+1, this.data, dest+1, k); + System.arraycopy(this.kvdata, pa+1, this.kvdata, dest+1, k); na -= k; if (na == 0) return; } - this.data[dest--] = this.a[pb--]; + this.kvdata[dest--] = this.a[pb--]; --nb; if (nb == 1) { // CopyA dest -= na; pa -= na; - System.arraycopy(this.data, pa+1, this.data, dest+1, na); - this.data[dest] = this.a[pb]; + System.arraycopy(this.kvdata, pa+1, this.kvdata, dest+1, na); + this.kvdata[dest] = this.a[pb]; nb = 0; return; } - k = gallop_left(this.data[pa], this.a, 0, nb, nb-1); + k = gallop_left(this.kvdata[pa].key, this.a, 0, nb, nb-1); bcount = k = nb - k; if (k != 0) { dest -= k; pb -= k; - System.arraycopy(this.a, pb+1, this.data, dest+1, k); + System.arraycopy(this.a, pb+1, this.kvdata, dest+1, k); nb -= k; if (nb == 1) { // CopyA dest -= na; pa -= na; - System.arraycopy(this.data, pa+1, this.data, dest+1, na); - this.data[dest] = this.a[pb]; + System.arraycopy(this.kvdata, pa+1, this.kvdata, dest+1, na); + this.kvdata[dest] = this.a[pb]; nb = 0; return; } @@ -359,7 +434,7 @@ if (nb == 0) return; } - this.data[dest--] = this.data[pa--]; + this.kvdata[dest--] = this.kvdata[pa--]; --na; if (na == 0) return; @@ -367,7 +442,7 @@ } } finally { if (nb != 0) - System.arraycopy(this.a, 0, this.data, dest-(nb-1), nb); + System.arraycopy(this.a, 0, this.kvdata, dest-(nb-1), nb); //dump_data("result", origpa, cnt); } @@ -397,7 +472,7 @@ Returns -1 on error. See listsort.txt for info on the method. */ - private int gallop_left(PyObject key, PyObject[] localData, int localA, int localN, + private int gallop_left(PyObject key, KVPair[] localData, int localA, int localN, int hint) { //assert_(n > 0 && hint >= 0 && hint < n); @@ -405,13 +480,13 @@ int ofs = 1; int lastofs = 0; - if (iflt(localData[localA], key)) { + if (iflt(localData[localA].key, key)) { /* a[hint] < key -- gallop right, until * a[hint + lastofs] < key <= a[hint + ofs] */ int maxofs = localN - hint; // data[a + n - 1] is highest while (ofs < maxofs) { - if (iflt(localData[localA + ofs], key)) { + if (iflt(localData[localA + ofs].key, key)) { lastofs = ofs; ofs = (ofs << 1) + 1; if (ofs <= 0) // int overflow @@ -432,7 +507,7 @@ */ int maxofs = hint + 1; // data[a] is lowest while (ofs < maxofs) { - if (iflt(localData[localA - ofs], key)) + if (iflt(localData[localA - ofs].key, key)) break; // key <= data[a + hint - ofs] lastofs = ofs; @@ -456,7 +531,7 @@ ++lastofs; while (lastofs < ofs) { int m = lastofs + ((ofs - lastofs) >> 1); - if (iflt(localData[localA + m], key)) + if (iflt(localData[localA + m].key, key)) lastofs = m+1; // data[a + m] < key else ofs = m; // key <= data[a + m] @@ -479,7 +554,7 @@ * written as one routine with yet another "left or right?" flag. */ - private int gallop_right(PyObject key, PyObject[] aData, int localA, int localN, + private int gallop_right(PyObject key, KVPair[] aData, int localA, int localN, int hint) { //assert_(n > 0 && hint >= 0 && hint < n); @@ -487,13 +562,13 @@ int lastofs = 0; int ofs = 1; - if (iflt(key, aData[localA])) { + if (iflt(key, aData[localA].key)) { /* key < a[hint] -- gallop left, until * a[hint - ofs] <= key < a[hint - lastofs] */ int maxofs = hint + 1; /* data[a] is lowest */ while (ofs < maxofs) { - if (iflt(key, aData[localA - ofs])) { + if (iflt(key, aData[localA - ofs].key)) { lastofs = ofs; ofs = (ofs << 1) + 1; if (ofs <= 0) // int overflow @@ -515,7 +590,7 @@ */ int maxofs = localN - hint; /* data[a + n - 1] is highest */ while (ofs < maxofs) { - if (iflt(key, aData[localA + ofs])) + if (iflt(key, aData[localA + ofs].key)) break; /* a[hint + ofs] <= key */ lastofs = ofs; @@ -540,7 +615,7 @@ ++lastofs; while (lastofs < ofs) { int m = lastofs + ((ofs - lastofs) >> 1); - if (iflt(key, aData[localA + m])) + if (iflt(key, aData[localA + m].key)) ofs = m; // key < data[a + m] else lastofs = m+1; // data[a + m] <= key @@ -575,7 +650,7 @@ // Where does b start in a? Elements in a before that can be // ignored (already in place). - int k = gallop_right(this.data[pb], this.data, pa, na, 0); + int k = gallop_right(this.kvdata[pb].key, this.kvdata, pa, na, 0); pa += k; na -= k; if (na == 0) @@ -583,7 +658,7 @@ // Where does a end in b? Elements in b after that can be // ignored (already in place). - nb = gallop_left(this.data[pa + na - 1], this.data, pb, nb, nb-1); + nb = gallop_left(this.kvdata[pa + na - 1].key, this.kvdata, pb, nb, nb-1); if (nb == 0) return; @@ -689,9 +764,9 @@ void reverse_slice(int lo, int hi) { --hi; while (lo < hi) { - PyObject t = this.data[lo]; - this.data[lo] = this.data[hi]; - this.data[hi] = t; + KVPair t = this.kvdata[lo]; + this.kvdata[lo] = this.kvdata[hi]; + this.kvdata[hi] = t; ++lo; --hi; } @@ -723,7 +798,7 @@ /* set l to where *start belongs */ int l = lo; int r = start; - PyObject pivot = this.data[r]; + KVPair pivot = this.kvdata[r]; // Invariants: // pivot >= all in [lo, l). // pivot < all in [r, start). @@ -731,7 +806,7 @@ //assert_(l < r); do { p = l + ((r - l) >> 1); - if (iflt(pivot, this.data[p])) + if (iflt(pivot.key, this.kvdata[p].key)) r = p; else l = p+1; @@ -743,8 +818,8 @@ // first slot after them -- that's why this sort is stable. // Slide over to make room. for (p = start; p > l; --p) - this.data[p] = this.data[p - 1]; - this.data[l] = pivot; + this.kvdata[p] = this.kvdata[p - 1]; + this.kvdata[l] = pivot; } //dump_data("binsort", lo, hi - lo); } Index: src/org/python/core/PyList.java =================================================================== --- src/org/python/core/PyList.java (revision 3589) +++ src/org/python/core/PyList.java (working copy) @@ -552,28 +552,6 @@ } dict.__setitem__("reverse",new PyMethodDescr("reverse",PyList.class,0,0,new exposed_reverse(null,null))); - class exposed_sort extends PyBuiltinMethodNarrow { - - exposed_sort(PyObject self,PyBuiltinFunction.Info info) { - super(self,info); - } - - public PyBuiltinFunction bind(PyObject self) { - return new exposed_sort(self,info); - } - - public PyObject __call__(PyObject arg0) { - ((PyList)self).list_sort(arg0); - return Py.None; - } - - public PyObject __call__() { - ((PyList)self).list_sort(); - return Py.None; - } - - } - dict.__setitem__("sort",new PyMethodDescr("sort",PyList.class,0,1,new exposed_sort(null,null))); class exposed___len__ extends PyBuiltinMethodNarrow { exposed___len__(PyObject self,PyBuiltinFunction.Info info) { @@ -654,6 +632,27 @@ } dict.__setitem__("__repr__",new PyMethodDescr("__repr__",PyList.class,0,0,new exposed___repr__(null,null))); + class exposed_sort extends PyBuiltinMethod { + + exposed_sort(PyObject self,PyBuiltinFunction.Info info) { + super(self,info); + } + + public PyBuiltinFunction bind(PyObject self) { + return new exposed_sort(self,info); + } + + public PyObject __call__(PyObject[]args) { + return __call__(args,Py.NoKeywords); + } + + public PyObject __call__(PyObject[]args,String[]keywords) { + ((PyList)self).list_sort(args,keywords); + return Py.None; + } + + } + dict.__setitem__("sort",new PyMethodDescr("sort",PyList.class,0,3,new exposed_sort(null,null))); class exposed___init__ extends PyBuiltinMethod { exposed___init__(PyObject self,PyBuiltinFunction.Info info) { @@ -941,6 +940,7 @@ for(int i = 1; i < count; i++) { System.arraycopy(array, 0, array, i * l, l); } + gListAllocatedStatus = __len__(); return this; } @@ -1051,6 +1051,7 @@ final void list_append(PyObject o) { pyadd(o); + gListAllocatedStatus = __len__(); } /** @@ -1157,6 +1158,7 @@ if (index < 0) index = Math.max(0, size() + index); if (index > size()) index = size(); list.pyadd(index, o); + gListAllocatedStatus = __len__(); } /** @@ -1173,6 +1175,7 @@ final void list_remove(PyObject o) { del(_index(o, "list.remove(x): x not in list", 0, size())); + gListAllocatedStatus = __len__(); } /** @@ -1195,6 +1198,7 @@ array[i] = array[j]; array[j] = tmp; } + gListAllocatedStatus = __len__(); } /** @@ -1230,6 +1234,7 @@ PyObject v = pyget(n); setslice(n, n+1, 1, Py.EmptyTuple); + gListAllocatedStatus = __len__(); return v; } @@ -1247,6 +1252,7 @@ final void list_extend(PyObject o) { int length = size(); setslice(length, length, 1, o); + gListAllocatedStatus = __len__(); } public PyObject __iadd__(PyObject o) { @@ -1255,6 +1261,7 @@ final PyObject list___iadd__(PyObject o) { extend(fastSequence(o, "argument to += must be a sequence")); + return this; } @@ -1270,13 +1277,17 @@ * that reverses the ordering of the elements. * * @param compare the comparison function. + * @param key a single-parameter function that takes a list element + * and returns a comparison key for the element + * @param reverse this parameter takes a Boolean value. If it is true, + * the list will be sorted into reverse order. */ - public synchronized void sort(PyObject compare) { - list_sort(compare); + public synchronized void sort(PyObject compare, PyObject key, boolean reverse) { + list_sort(compare, key, reverse); } - final synchronized void list_sort(PyObject compare) { - MergeState ms = new MergeState(getArray(), size(), compare); + final synchronized void list_sort(PyObject compare, PyObject key, boolean reverse) { + MergeState ms = new MergeState(this, compare, key, reverse); ms.sort(); } @@ -1289,9 +1300,23 @@ } final void list_sort() { - list_sort(null); + list_sort(null, null, false); } + final void list_sort(PyObject[] args,String[] kwds) { + ArgParser ap = new ArgParser("sort", args, kwds, new String[] {"cmp", "key", "reverse"}, 0); + + PyObject cmp = ap.getPyObject(0, Py.None); + PyObject key = ap.getPyObject(1, Py.None); + PyObject reverse = ap.getPyObject(2, Py.None); + + if (reverse != Py.None) + list_sort(cmp, key, ((PyInteger)reverse).getValue() != 0); + else + list_sort(cmp, key, false); + } + + public int hashCode() { return list_hashCode(); } Index: src/org/python/core/__builtin__.java =================================================================== --- src/org/python/core/__builtin__.java (revision 3607) +++ src/org/python/core/__builtin__.java (working copy) @@ -390,6 +390,7 @@ dict.__setitem__("zip", new BuiltinFunctions("zip", 43, 1, -1)); dict.__setitem__("reversed", new BuiltinFunctions("reversed", 45, 1)); dict.__setitem__("__import__", new ImportFunction()); + dict.__setitem__("sorted", new SortedFunction()); } public static PyObject abs(PyObject o) { @@ -1216,5 +1217,40 @@ public String toString() { return ""; - } + } } + +class SortedFunction extends PyObject { + public PyObject __call__(PyObject args[], String kwds[]) { + if (args.length == 0) { + throw Py.TypeError(" sorted() takes at least 1 argument (0 given)"); + } else if (args.length > 4) { + throw Py.TypeError(" sorted() takes at most 4 arguments (" + args.length + " given)"); + } else { + PyObject iter = args[0].__iter__(); + if (iter == null) + throw Py.TypeError("'" + args[0].getType().fastGetName() + "' object is not iterable"); + } + + PyList seq = new PyList(args[0]); + + PyObject newargs[] = new PyObject[args.length - 1]; + System.arraycopy(args, 1, newargs, 0, args.length - 1); + ArgParser ap = new ArgParser("sorted", newargs, kwds, new String[] {"cmp", "key", "reverse"}, 0); + + PyObject cmp = ap.getPyObject(0, Py.None); + PyObject key = ap.getPyObject(1, Py.None); + PyObject reverse = ap.getPyObject(2, Py.None); + + if (reverse != Py.None) { + seq.sort(cmp, key, ((PyInteger)reverse).getValue() != 0); + } else { + seq.sort(cmp, key, false); + } + return seq; + } + + public String toString() { + return ""; + } +} Index: src/templates/list.expose =================================================================== --- src/templates/list.expose (revision 3589) +++ src/templates/list.expose (working copy) @@ -14,7 +14,7 @@ expose_meth: pop i? expose_meth: :- remove o expose_meth: :- reverse -expose_meth: :- sort o? +expose_wide_meth: :- sort 0 3 expose_meth: :i __len__ expose_meth: __iadd__ o expose_meth: __imul__ o Index: Lib/test/test_sort.py =================================================================== --- Lib/test/test_sort.py (revision 3589) +++ Lib/test/test_sort.py (working copy) @@ -1,5 +1,6 @@ from test.test_support import verbose import random +from UserList import UserList nerrors = 0 @@ -116,57 +117,181 @@ x = [e for e, i in augmented] # a stable sort of s check("stability", x, s) -def bug453523(): - global nerrors - from random import random - # If this fails, the most likely outcome is a core dump. - if verbose: - print "Testing bug 453523 -- list.sort() crasher." +import unittest +from test import test_support +import sys - class C: - def __lt__(self, other): - if L and random() < 0.75: - pop() - else: - push(3) - return random() < 0.5 +#============================================================================== - L = [C() for i in range(50)] - pop = L.pop - push = L.append - try: - L.sort() - except ValueError: - pass - else: - print " Mutation during list.sort() wasn't caught." - nerrors += 1 -# Jython transition 2.3 -# Mutation during list.sort doesn't throw a ValueError -# http://jython.org/bugs/1758322 -#bug453523() +class TestBugs(unittest.TestCase): -def cmpNone(): - global nerrors + def test_bug453523(self): + # bug 453523 -- list.sort() crasher. + # If this fails, the most likely outcome is a core dump. + # Mutations during a list sort should raise a ValueError. - if verbose: - print "Testing None as a comparison function." + class C: + def __lt__(self, other): + if L and random.random() < 0.75: + L.pop() + else: + L.append(3) + return random.random() < 0.5 - L = range(50) - random.shuffle(L) - try: + L = [C() for i in range(50)] + self.assertRaises(ValueError, L.sort) + + def test_cmpNone(self): + # Testing None as a comparison function. + + L = range(50) + random.shuffle(L) L.sort(None) - except TypeError: - print " Passing None as cmpfunc failed." - nerrors += 1 - else: - if L != range(50): - print " Passing None as cmpfunc failed." - nerrors += 1 -cmpNone() + self.assertEqual(L, range(50)) -if nerrors: - print "Test failed", nerrors -elif verbose: - print "Test passed -- no errors." + def test_undetected_mutation(self): + # Python 2.4a1 did not always detect mutation + memorywaster = [] + for i in range(20): + def mutating_cmp(x, y): + L.append(3) + L.pop() + return cmp(x, y) + L = [1,2] + self.assertRaises(ValueError, L.sort, mutating_cmp) + def mutating_cmp(x, y): + L.append(3) + del L[:] + return cmp(x, y) + self.assertRaises(ValueError, L.sort, mutating_cmp) + memorywaster = [memorywaster] + +#============================================================================== + +class TestDecorateSortUndecorate(unittest.TestCase): + + def test_decorated(self): + data = 'The quick Brown fox Jumped over The lazy Dog'.split() + copy = data[:] + random.shuffle(data) + data.sort(key=str.lower) + copy.sort(cmp=lambda x,y: cmp(x.lower(), y.lower())) + + def test_baddecorator(self): + data = 'The quick Brown fox Jumped over The lazy Dog'.split() + self.assertRaises(TypeError, data.sort, None, lambda x,y: 0) + + def test_stability(self): + data = [(random.randrange(100), i) for i in xrange(200)] + copy = data[:] + data.sort(key=lambda (x,y): x) # sort on the random first field + copy.sort() # sort using both fields + self.assertEqual(data, copy) # should get the same result + + def test_cmp_and_key_combination(self): + # Verify that the wrapper has been removed + def compare(x, y): + self.assertEqual(type(x), str) + self.assertEqual(type(x), str) + return cmp(x, y) + data = 'The quick Brown fox Jumped over The lazy Dog'.split() + data.sort(cmp=compare, key=str.lower) + + def test_badcmp_with_key(self): + # Verify that the wrapper has been removed + data = 'The quick Brown fox Jumped over The lazy Dog'.split() + self.assertRaises(TypeError, data.sort, "bad", str.lower) + + def test_key_with_exception(self): + # Verify that the wrapper has been removed + data = range(-2,2) + dup = data[:] + self.assertRaises(ZeroDivisionError, data.sort, None, lambda x: 1/x) + self.assertEqual(data, dup) + + def test_key_with_mutation(self): + data = range(10) + def k(x): + del data[:] + data[:] = range(20) + return x + self.assertRaises(ValueError, data.sort, key=k) + + def test_key_with_mutating_del(self): + data = range(10) + class SortKiller(object): + def __init__(self, x): + pass + def __del__(self): + del data[:] + data[:] = range(20) + self.assertRaises(ValueError, data.sort, key=SortKiller) + + def test_key_with_mutating_del_and_exception(self): + data = range(10) + ## dup = data[:] + class SortKiller(object): + def __init__(self, x): + if x > 2: + raise RuntimeError + def __del__(self): + del data[:] + data[:] = range(20) + self.assertRaises(RuntimeError, data.sort, key=SortKiller) + ## major honking subtlety: we *can't* do: + ## + ## self.assertEqual(data, dup) + ## + ## because there is a reference to a SortKiller in the + ## traceback and by the time it dies we're outside the call to + ## .sort() and so the list protection gimmicks are out of + ## date (this cost some brain cells to figure out...). + + def test_reverse(self): + data = range(100) + random.shuffle(data) + data.sort(reverse=True) + self.assertEqual(data, range(99,-1,-1)) + self.assertRaises(TypeError, data.sort, "wrong type") + + def test_reverse_stability(self): + data = [(random.randrange(100), i) for i in xrange(200)] + copy1 = data[:] + copy2 = data[:] + data.sort(cmp=lambda x,y: cmp(x[0],y[0]), reverse=True) + copy1.sort(cmp=lambda x,y: cmp(y[0],x[0])) + self.assertEqual(data, copy1) + copy2.sort(key=lambda x: x[0], reverse=True) + self.assertEqual(data, copy2) + +#============================================================================== + +def test_main(verbose=None): + test_classes = ( + TestDecorateSortUndecorate, + TestBugs, + ) + + # In the following test cases, class obj, which has function that changes + # the data upon which sort is invoked, is passed for "key" argument. + # It can not be checked if that function changes data as long as it is + # invoked(e.g. __del__ in SortKiller). so these are currently commented out. + del TestDecorateSortUndecorate.test_key_with_mutating_del + del TestDecorateSortUndecorate.test_key_with_mutating_del_and_exception + # + + test_support.run_unittest(*test_classes) + + # verify reference counting + if verbose and hasattr(sys, "gettotalrefcount"): + import gc + counts = [None] * 5 + for i in xrange(len(counts)): + test_support.run_unittest(*test_classes) + gc.collect() + counts[i] = sys.gettotalrefcount() + print counts + +if __name__ == "__main__": + test_main(verbose=True) Index: Lib/test/test_builtin.py =================================================================== --- Lib/test/test_builtin.py (revision 3589) +++ Lib/test/test_builtin.py (working copy) @@ -4,7 +4,7 @@ from test.test_support import fcmp, have_unicode, TESTFN, unlink from sets import Set -import sys, warnings, cStringIO +import sys, warnings, cStringIO, random warnings.filterwarnings("ignore", "hex../oct.. of negative int", FutureWarning, __name__) warnings.filterwarnings("ignore", "integer argument expected", @@ -1174,6 +1174,45 @@ return i self.assertRaises(ValueError, zip, BadSeq(), BadSeq()) +class TestSorted(unittest.TestCase): + + def test_basic(self): + data = range(100) + copy = data[:] + random.shuffle(copy) + self.assertEqual(data, sorted(copy)) + self.assertNotEqual(data, copy) + + data.reverse() + random.shuffle(copy) + self.assertEqual(data, sorted(copy, cmp=lambda x, y: cmp(y,x))) + self.assertNotEqual(data, copy) + random.shuffle(copy) + self.assertEqual(data, sorted(copy, key=lambda x: -x)) + self.assertNotEqual(data, copy) + random.shuffle(copy) + self.assertEqual(data, sorted(copy, reverse=1)) + self.assertNotEqual(data, copy) + + def test_inputtypes(self): + s = 'abracadabra' + types = [list, tuple] + if have_unicode: + types.insert(0, unicode) + for T in types: + self.assertEqual(sorted(s), sorted(T(s))) + + s = ''.join(dict.fromkeys(s).keys()) # unique letters only + types = [set, frozenset, list, tuple, dict.fromkeys] + if have_unicode: + types.insert(0, unicode) + for T in types: + self.assertEqual(sorted(s), sorted(T(s))) + + def test_baddecorator(self): + data = 'The quick Brown fox Jumped over The lazy Dog'.split() + self.assertRaises(TypeError, sorted, data, None, lambda x,y: 0) + def test_main(): if test.test_support.is_jython: # Jython transition 2.3 @@ -1184,7 +1223,7 @@ # filter doesn't respect subclass type # http://jython.org/1768969 del BuiltinTest.test_filter_subclasses - test.test_support.run_unittest(BuiltinTest) + test.test_support.run_unittest(BuiltinTest, TestSorted) if __name__ == "__main__": test_main()