From noreply at buildbot.pypy.org Sun Apr 1 08:32:59 2012 From: noreply at buildbot.pypy.org (taavi_burns) Date: Sun, 1 Apr 2012 08:32:59 +0200 (CEST) Subject: [pypy-commit] pypy numpy-ufuncs3: Expose and test left_shift and right_shift Message-ID: <20120401063259.5699A820C7@wyvern.cs.uni-duesseldorf.de> Author: Taavi Burns Branch: numpy-ufuncs3 Changeset: r54114:24b6c8a2f4ce Date: 2012-04-01 02:32 -0400 http://bitbucket.org/pypy/pypy/changeset/24b6c8a2f4ce/ Log: Expose and test left_shift and right_shift diff --git a/pypy/module/micronumpy/__init__.py b/pypy/module/micronumpy/__init__.py --- a/pypy/module/micronumpy/__init__.py +++ b/pypy/module/micronumpy/__init__.py @@ -131,6 +131,8 @@ ('bitwise_or', 'bitwise_or'), ('bitwise_xor', 'bitwise_xor'), ('bitwise_not', 'invert'), + ('left_shift', 'left_shift'), + ('right_shift', 'right_shift'), ('invert', 'invert'), ('isnan', 'isnan'), ('isinf', 'isinf'), diff --git a/pypy/module/micronumpy/test/test_ufuncs.py b/pypy/module/micronumpy/test/test_ufuncs.py --- a/pypy/module/micronumpy/test/test_ufuncs.py +++ b/pypy/module/micronumpy/test/test_ufuncs.py @@ -597,6 +597,13 @@ assert (bitwise_not(a) == ~a).all() assert (invert(a) == ~a).all() + def test_shift(self): + from _numpypy import left_shift, right_shift + import sys + + assert (left_shift([5, 1], [2, 31]) == [20, 2**31]).all() + assert (right_shift(10, range(5)) == [10, 5, 2, 1, 0]).all() + def test_comparisons(self): import operator from _numpypy import equal, not_equal, less, less_equal, greater, greater_equal From noreply at buildbot.pypy.org Sun Apr 1 12:34:34 2012 From: noreply at buildbot.pypy.org (arigo) Date: Sun, 1 Apr 2012 12:34:34 +0200 (CEST) Subject: [pypy-commit] pypy default: Extension to cpyext proposed by Stefan Behnel: PyErr_{Get, Set}ExcInfo(). Message-ID: <20120401103434.C1140820C7@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: Changeset: r54115:623bcea85df3 Date: 2012-04-01 12:30 +0200 http://bitbucket.org/pypy/pypy/changeset/623bcea85df3/ Log: Extension to cpyext proposed by Stefan Behnel: PyErr_{Get,Set}ExcInfo(). diff --git a/pypy/interpreter/executioncontext.py b/pypy/interpreter/executioncontext.py --- a/pypy/interpreter/executioncontext.py +++ b/pypy/interpreter/executioncontext.py @@ -167,6 +167,11 @@ frame = self.getnextframe_nohidden(frame) return None + def set_sys_exc_info(self, operror): + frame = self.gettopframe_nohidden() + if frame: # else, the exception goes nowhere and is lost + frame.last_exception = operror + def settrace(self, w_func): """Set the global trace function.""" if self.space.is_w(w_func, self.space.w_None): diff --git a/pypy/module/cpyext/pyerrors.py b/pypy/module/cpyext/pyerrors.py --- a/pypy/module/cpyext/pyerrors.py +++ b/pypy/module/cpyext/pyerrors.py @@ -315,3 +315,63 @@ It may be called without holding the interpreter lock.""" space.check_signal_action.set_interrupt() + at cpython_api([PyObjectP, PyObjectP, PyObjectP], lltype.Void) +def PyErr_GetExcInfo(space, ptype, pvalue, ptraceback): + """---Cython extension--- + + Retrieve the exception info, as known from ``sys.exc_info()``. This + refers to an exception that was already caught, not to an exception + that was freshly raised. Returns new references for the three + objects, any of which may be *NULL*. Does not modify the exception + info state. + + .. note:: + + This function is not normally used by code that wants to handle + exceptions. Rather, it can be used when code needs to save and + restore the exception state temporarily. Use + :c:func:`PyErr_SetExcInfo` to restore or clear the exception + state. + """ + ec = space.getexecutioncontext() + operror = ec.sys_exc_info() + if operror: + ptype[0] = make_ref(space, operror.w_type) + pvalue[0] = make_ref(space, operror.get_w_value(space)) + ptraceback[0] = make_ref(space, space.wrap(operror.get_traceback())) + else: + ptype[0] = lltype.nullptr(PyObject.TO) + pvalue[0] = lltype.nullptr(PyObject.TO) + ptraceback[0] = lltype.nullptr(PyObject.TO) + + at cpython_api([PyObject, PyObject, PyObject], lltype.Void) +def PyErr_SetExcInfo(space, w_type, w_value, w_traceback): + """---Cython extension--- + + Set the exception info, as known from ``sys.exc_info()``. This refers + to an exception that was already caught, not to an exception that was + freshly raised. This function steals the references of the arguments. + To clear the exception state, pass *NULL* for all three arguments. + For general rules about the three arguments, see :c:func:`PyErr_Restore`. + + .. note:: + + This function is not normally used by code that wants to handle + exceptions. Rather, it can be used when code needs to save and + restore the exception state temporarily. Use + :c:func:`PyErr_GetExcInfo` to read the exception state. + """ + if w_value is None or space.is_w(w_value, space.w_None): + operror = None + else: + if w_traceback is None or space.is_w(w_traceback, space.w_None): + tb = None + else: + tb = w_traceback + operror = OperationError(w_type, w_value, tb) + # + ec = space.getexecutioncontext() + ec.set_sys_exc_info(operror) + Py_DecRef(space, w_type) + Py_DecRef(space, w_value) + Py_DecRef(space, w_traceback) diff --git a/pypy/module/cpyext/test/test_pyerrors.py b/pypy/module/cpyext/test/test_pyerrors.py --- a/pypy/module/cpyext/test/test_pyerrors.py +++ b/pypy/module/cpyext/test/test_pyerrors.py @@ -218,3 +218,51 @@ assert e.filename == "blyf" assert e.errno == errno.EBADF assert e.strerror == os.strerror(errno.EBADF) + + def test_GetSetExcInfo(self): + import sys + module = self.import_extension('foo', [ + ("getset_exc_info", "METH_VARARGS", + r''' + PyObject *type, *val, *tb; + PyObject *new_type, *new_val, *new_tb; + PyObject *result; + + if (!PyArg_ParseTuple(args, "OOO", &new_type, &new_val, &new_tb)) + return NULL; + + PyErr_GetExcInfo(&type, &val, &tb); + + Py_INCREF(new_type); + Py_INCREF(new_val); + Py_INCREF(new_tb); + PyErr_SetExcInfo(new_type, new_val, new_tb); + + result = Py_BuildValue("OOO", + type ? type : Py_None, + val ? val : Py_None, + tb ? tb : Py_None); + Py_XDECREF(type); + Py_XDECREF(val); + Py_XDECREF(tb); + return result; + ''' + ), + ]) + try: + raise ValueError(5) + except ValueError, old_exc: + new_exc = TypeError("TEST") + orig_sys_exc_info = sys.exc_info() + orig_exc_info = module.getset_exc_info(new_exc.__class__, + new_exc, None) + new_sys_exc_info = sys.exc_info() + new_exc_info = module.getset_exc_info(*orig_exc_info) + reset_sys_exc_info = sys.exc_info() + + assert orig_exc_info[0] is old_exc.__class__ + assert orig_exc_info[1] is old_exc + assert orig_exc_info == orig_sys_exc_info + assert orig_exc_info == reset_sys_exc_info + assert new_exc_info == (new_exc.__class__, new_exc, None) + assert new_exc_info == new_sys_exc_info From notifications-noreply at bitbucket.org Sun Apr 1 14:08:13 2012 From: notifications-noreply at bitbucket.org (Bitbucket) Date: Sun, 01 Apr 2012 12:08:13 -0000 Subject: [pypy-commit] Notification: pypy Message-ID: <20120401120813.11994.41784@bitbucket12.managed.contegix.com> You have received a notification from unbit. Hi, I forked pypy. My fork is at https://bitbucket.org/unbit/pypy. -- Disable notifications at https://bitbucket.org/account/notifications/ From noreply at buildbot.pypy.org Sun Apr 1 15:53:02 2012 From: noreply at buildbot.pypy.org (fijal) Date: Sun, 1 Apr 2012 15:53:02 +0200 (CEST) Subject: [pypy-commit] pypy default: fix translation (?) Message-ID: <20120401135302.1BD08820C7@wyvern.cs.uni-duesseldorf.de> Author: Maciej Fijalkowski Branch: Changeset: r54116:ba8760573e6d Date: 2012-04-01 15:52 +0200 http://bitbucket.org/pypy/pypy/changeset/ba8760573e6d/ Log: fix translation (?) diff --git a/pypy/module/cpyext/pyerrors.py b/pypy/module/cpyext/pyerrors.py --- a/pypy/module/cpyext/pyerrors.py +++ b/pypy/module/cpyext/pyerrors.py @@ -57,7 +57,7 @@ if operror: ptype[0] = make_ref(space, operror.w_type) pvalue[0] = make_ref(space, operror.get_w_value(space)) - ptraceback[0] = make_ref(space, space.wrap(operror.get_traceback())) + ptraceback[0] = make_ref(space, operror.get_traceback()) else: ptype[0] = lltype.nullptr(PyObject.TO) pvalue[0] = lltype.nullptr(PyObject.TO) From noreply at buildbot.pypy.org Sun Apr 1 16:25:47 2012 From: noreply at buildbot.pypy.org (arigo) Date: Sun, 1 Apr 2012 16:25:47 +0200 (CEST) Subject: [pypy-commit] pypy default: Backed out changeset ba8760573e6d Message-ID: <20120401142547.47613820C7@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: Changeset: r54117:24d65f2b0735 Date: 2012-04-01 16:20 +0200 http://bitbucket.org/pypy/pypy/changeset/24d65f2b0735/ Log: Backed out changeset ba8760573e6d diff --git a/pypy/module/cpyext/pyerrors.py b/pypy/module/cpyext/pyerrors.py --- a/pypy/module/cpyext/pyerrors.py +++ b/pypy/module/cpyext/pyerrors.py @@ -57,7 +57,7 @@ if operror: ptype[0] = make_ref(space, operror.w_type) pvalue[0] = make_ref(space, operror.get_w_value(space)) - ptraceback[0] = make_ref(space, operror.get_traceback()) + ptraceback[0] = make_ref(space, space.wrap(operror.get_traceback())) else: ptype[0] = lltype.nullptr(PyObject.TO) pvalue[0] = lltype.nullptr(PyObject.TO) From noreply at buildbot.pypy.org Sun Apr 1 16:25:48 2012 From: noreply at buildbot.pypy.org (arigo) Date: Sun, 1 Apr 2012 16:25:48 +0200 (CEST) Subject: [pypy-commit] pypy default: Fix. Message-ID: <20120401142548.88570820C7@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: Changeset: r54118:ee1a3b5094b8 Date: 2012-04-01 16:25 +0200 http://bitbucket.org/pypy/pypy/changeset/ee1a3b5094b8/ Log: Fix. diff --git a/pypy/module/cpyext/pyerrors.py b/pypy/module/cpyext/pyerrors.py --- a/pypy/module/cpyext/pyerrors.py +++ b/pypy/module/cpyext/pyerrors.py @@ -2,6 +2,7 @@ from pypy.rpython.lltypesystem import rffi, lltype from pypy.interpreter.error import OperationError +from pypy.interpreter import pytraceback from pypy.module.cpyext.api import cpython_api, CANNOT_FAIL, CONST_STRING from pypy.module.exceptions.interp_exceptions import W_RuntimeWarning from pypy.module.cpyext.pyobject import ( @@ -364,10 +365,12 @@ if w_value is None or space.is_w(w_value, space.w_None): operror = None else: - if w_traceback is None or space.is_w(w_traceback, space.w_None): - tb = None - else: - tb = w_traceback + tb = None + if w_traceback is not None: + try: + tb = pytraceback.check_traceback(space, w_traceback, '?') + except OperationError: # catch and ignore bogus objects + pass operror = OperationError(w_type, w_value, tb) # ec = space.getexecutioncontext() From noreply at buildbot.pypy.org Sun Apr 1 16:34:01 2012 From: noreply at buildbot.pypy.org (arigo) Date: Sun, 1 Apr 2012 16:34:01 +0200 (CEST) Subject: [pypy-commit] pypy default: Cannot do this testing when running with "py.test -A". Message-ID: <20120401143401.A2E22820C7@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: Changeset: r54119:7acf5b725d8f Date: 2012-04-01 16:33 +0200 http://bitbucket.org/pypy/pypy/changeset/7acf5b725d8f/ Log: Cannot do this testing when running with "py.test -A". diff --git a/pypy/module/sys/test/test_sysmodule.py b/pypy/module/sys/test/test_sysmodule.py --- a/pypy/module/sys/test/test_sysmodule.py +++ b/pypy/module/sys/test/test_sysmodule.py @@ -600,30 +600,33 @@ class AppTestSysExcInfoDirect: def setup_method(self, meth): - self.seen = [] - from pypy.module.sys import vm - def exc_info_with_tb(*args): - self.seen.append("n") # not optimized - return self.old[0](*args) - def exc_info_without_tb(*args): - self.seen.append("y") # optimized - return self.old[1](*args) - self.old = [vm.exc_info_with_tb, vm.exc_info_without_tb] - vm.exc_info_with_tb = exc_info_with_tb - vm.exc_info_without_tb = exc_info_without_tb - # - from pypy.rlib import jit - self.old2 = [jit.we_are_jitted] - jit.we_are_jitted = lambda: True + self.checking = not option.runappdirect + if self.checking: + self.seen = [] + from pypy.module.sys import vm + def exc_info_with_tb(*args): + self.seen.append("n") # not optimized + return self.old[0](*args) + def exc_info_without_tb(*args): + self.seen.append("y") # optimized + return self.old[1](*args) + self.old = [vm.exc_info_with_tb, vm.exc_info_without_tb] + vm.exc_info_with_tb = exc_info_with_tb + vm.exc_info_without_tb = exc_info_without_tb + # + from pypy.rlib import jit + self.old2 = [jit.we_are_jitted] + jit.we_are_jitted = lambda: True def teardown_method(self, meth): - from pypy.module.sys import vm - from pypy.rlib import jit - vm.exc_info_with_tb = self.old[0] - vm.exc_info_without_tb = self.old[1] - jit.we_are_jitted = self.old2[0] - # - assert ''.join(self.seen) == meth.expected + if self.checking: + from pypy.module.sys import vm + from pypy.rlib import jit + vm.exc_info_with_tb = self.old[0] + vm.exc_info_without_tb = self.old[1] + jit.we_are_jitted = self.old2[0] + # + assert ''.join(self.seen) == meth.expected def test_returns_none(self): import sys From noreply at buildbot.pypy.org Sun Apr 1 16:48:33 2012 From: noreply at buildbot.pypy.org (arigo) Date: Sun, 1 Apr 2012 16:48:33 +0200 (CEST) Subject: [pypy-commit] pypy default: Fix the test: the 'len' attribute is now promoted to the common base Message-ID: <20120401144833.4EF74820C7@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: Changeset: r54120:27de13d1a9e7 Date: 2012-04-01 16:48 +0200 http://bitbucket.org/pypy/pypy/changeset/27de13d1a9e7/ Log: Fix the test: the 'len' attribute is now promoted to the common base class W_ArrayBase, which is ok. diff --git a/pypy/module/pypyjit/test_pypy_c/test_misc.py b/pypy/module/pypyjit/test_pypy_c/test_misc.py --- a/pypy/module/pypyjit/test_pypy_c/test_misc.py +++ b/pypy/module/pypyjit/test_pypy_c/test_misc.py @@ -212,7 +212,7 @@ i19 = int_add(i12, 1) setfield_gc(p9, i19, descr=) guard_nonnull_class(p17, 146982464, descr=...) - i21 = getfield_gc(p17, descr=) + i21 = getfield_gc(p17, descr=) i23 = int_lt(0, i21) guard_true(i23, descr=...) i24 = getfield_gc(p17, descr=) From noreply at buildbot.pypy.org Sun Apr 1 17:57:48 2012 From: noreply at buildbot.pypy.org (fijal) Date: Sun, 1 Apr 2012 17:57:48 +0200 (CEST) Subject: [pypy-commit] pypy default: two missing hop.exception_cannot_occur() Message-ID: <20120401155748.D8534820C7@wyvern.cs.uni-duesseldorf.de> Author: Maciej Fijalkowski Branch: Changeset: r54121:db8d9bfdd9aa Date: 2012-04-01 17:57 +0200 http://bitbucket.org/pypy/pypy/changeset/db8d9bfdd9aa/ Log: two missing hop.exception_cannot_occur() diff --git a/pypy/rlib/rerased.py b/pypy/rlib/rerased.py --- a/pypy/rlib/rerased.py +++ b/pypy/rlib/rerased.py @@ -100,6 +100,7 @@ def specialize_call(self, hop): bk = hop.rtyper.annotator.bookkeeper s_obj = identity.get_input_annotation(bk) + hop.exception_cannot_occur() return hop.r_result.rtype_erase(hop, s_obj) class Entry(ExtRegistryEntry): @@ -113,6 +114,7 @@ if hop.r_result.lowleveltype is lltype.Void: return hop.inputconst(lltype.Void, None) [v] = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.args_r[0].rtype_unerase(hop, v) return erase, unerase From noreply at buildbot.pypy.org Sun Apr 1 18:15:45 2012 From: noreply at buildbot.pypy.org (arigo) Date: Sun, 1 Apr 2012 18:15:45 +0200 (CEST) Subject: [pypy-commit] pypy default: Add two new passing tests about calls to string methods. Message-ID: <20120401161545.39209820C7@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: Changeset: r54122:0e905871fc78 Date: 2012-04-01 16:58 +0200 http://bitbucket.org/pypy/pypy/changeset/0e905871fc78/ Log: Add two new passing tests about calls to string methods. diff --git a/pypy/module/pypyjit/test_pypy_c/test_string.py b/pypy/module/pypyjit/test_pypy_c/test_string.py --- a/pypy/module/pypyjit/test_pypy_c/test_string.py +++ b/pypy/module/pypyjit/test_pypy_c/test_string.py @@ -198,3 +198,37 @@ i49 = call(ConstClass(_ll_2_str_eq_nonnull__rpy_stringPtr_rpy_stringPtr), p35, ConstPtr(ptr48), descr=) guard_value(i49, 1, descr=...) ''') + + def test_remove_duplicate_method_calls(self): + def main(n): + lst = [] + for i in range(n): + s = 'Hello %d' % i + t = s.lower() # ID: callone + u = s.lower() # ID: calltwo + lst.append(t) + lst.append(u) + return len(','.join(lst)) + log = self.run(main, [1000]) + assert log.result == main(1000) + loops = log.loops_by_filename(self.filepath) + loop, = loops + loop.match_by_id('callone', ''' + p114 = call(ConstClass(ll_lower__rpy_stringPtr), p113, descr=) + guard_no_exception(descr=...) + ''') + loop.match_by_id('calltwo', '') # nothing + + def test_move_method_call_out_of_loop(self): + def main(n): + lst = [] + s = 'Hello %d' % n + for i in range(n): + t = s.lower() # ID: callone + lst.append(t) + return len(','.join(lst)) + log = self.run(main, [1000]) + assert log.result == main(1000) + loops = log.loops_by_filename(self.filepath) + loop, = loops + loop.match_by_id('callone', '') # nothing From noreply at buildbot.pypy.org Sun Apr 1 18:15:46 2012 From: noreply at buildbot.pypy.org (arigo) Date: Sun, 1 Apr 2012 18:15:46 +0200 (CEST) Subject: [pypy-commit] pypy default: merge heads Message-ID: <20120401161546.C5B34820C7@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: Changeset: r54123:3baa10110134 Date: 2012-04-01 18:12 +0200 http://bitbucket.org/pypy/pypy/changeset/3baa10110134/ Log: merge heads diff --git a/pypy/rlib/rerased.py b/pypy/rlib/rerased.py --- a/pypy/rlib/rerased.py +++ b/pypy/rlib/rerased.py @@ -100,6 +100,7 @@ def specialize_call(self, hop): bk = hop.rtyper.annotator.bookkeeper s_obj = identity.get_input_annotation(bk) + hop.exception_cannot_occur() return hop.r_result.rtype_erase(hop, s_obj) class Entry(ExtRegistryEntry): @@ -113,6 +114,7 @@ if hop.r_result.lowleveltype is lltype.Void: return hop.inputconst(lltype.Void, None) [v] = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.args_r[0].rtype_unerase(hop, v) return erase, unerase From noreply at buildbot.pypy.org Sun Apr 1 20:08:49 2012 From: noreply at buildbot.pypy.org (arigo) Date: Sun, 1 Apr 2012 20:08:49 +0200 (CEST) Subject: [pypy-commit] pypy exception-cannot-occur: A branch in which to make exception_cannot_occur() or Message-ID: <20120401180849.D8226820C7@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: exception-cannot-occur Changeset: r54124:0d14d5e18743 Date: 2012-04-01 19:02 +0200 http://bitbucket.org/pypy/pypy/changeset/0d14d5e18743/ Log: A branch in which to make exception_cannot_occur() or exception_is_here() mandatory in specialize_call()s. From noreply at buildbot.pypy.org Sun Apr 1 20:08:51 2012 From: noreply at buildbot.pypy.org (arigo) Date: Sun, 1 Apr 2012 20:08:51 +0200 (CEST) Subject: [pypy-commit] pypy exception-cannot-occur: Add the logic to detect missing cases. Message-ID: <20120401180851.2BBC6820C7@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: exception-cannot-occur Changeset: r54125:6ec59dc45235 Date: 2012-04-01 19:03 +0200 http://bitbucket.org/pypy/pypy/changeset/6ec59dc45235/ Log: Add the logic to detect missing cases. diff --git a/pypy/rpython/rbuiltin.py b/pypy/rpython/rbuiltin.py --- a/pypy/rpython/rbuiltin.py +++ b/pypy/rpython/rbuiltin.py @@ -111,25 +111,32 @@ raise TyperError("don't know about built-in function %r" % ( self.builtinfunc,)) + def _call(self, hop2, **kwds_i): + bltintyper = self.findbltintyper(hop2.rtyper) + hop2.llops._called_exception_is_here_or_cannot_occur = False + v_result = bltintyper(hop2, **kwds_i) + if not hop2.llops._called_exception_is_here_or_cannot_occur: + raise TyperError("missing hop.exception_cannot_occur() or " + "hop.exception_is_here() in %s" % bltintyper) + return v_result + def rtype_simple_call(self, hop): - bltintyper = self.findbltintyper(hop.rtyper) hop2 = hop.copy() hop2.r_s_popfirstarg() - return bltintyper(hop2) + return self._call(hop2) def rtype_call_args(self, hop): # calling a built-in function with keyword arguments: # mostly for rpython.objectmodel.hint() hop, kwds_i = call_args_expand(hop) - bltintyper = self.findbltintyper(hop.rtyper) hop2 = hop.copy() hop2.r_s_popfirstarg() hop2.r_s_popfirstarg() # the RPython-level keyword args are passed with an 'i_' prefix and # the corresponding value is an *index* in the hop2 arguments, # to be used with hop.inputarg(arg=..) - return bltintyper(hop2, **kwds_i) + return self._call(hop2, **kwds_i) class BuiltinMethodRepr(Repr): @@ -264,6 +271,7 @@ pass def rtype_OSError__init__(hop): + hop.exception_cannot_occur() if hop.nb_args == 2: raise TyperError("OSError() should not be called with " "a single argument") @@ -274,6 +282,7 @@ r_self.setfield(v_self, 'errno', v_errno, hop.llops) def rtype_WindowsError__init__(hop): + hop.exception_cannot_occur() if hop.nb_args == 2: raise TyperError("WindowsError() should not be called with " "a single argument") diff --git a/pypy/rpython/rtyper.py b/pypy/rpython/rtyper.py --- a/pypy/rpython/rtyper.py +++ b/pypy/rpython/rtyper.py @@ -846,6 +846,7 @@ return result def exception_is_here(self): + self.llops._called_exception_is_here_or_cannot_occur = True if self.llops.llop_raising_exceptions is not None: raise TyperError("cannot catch an exception at more than one llop") if not self.exceptionlinks: @@ -861,6 +862,7 @@ self.llops.llop_raising_exceptions = len(self.llops) def exception_cannot_occur(self): + self.llops._called_exception_is_here_or_cannot_occur = True if self.llops.llop_raising_exceptions is not None: raise TyperError("cannot catch an exception at more than one llop") if not self.exceptionlinks: From noreply at buildbot.pypy.org Sun Apr 1 20:08:52 2012 From: noreply at buildbot.pypy.org (arigo) Date: Sun, 1 Apr 2012 20:08:52 +0200 (CEST) Subject: [pypy-commit] pypy exception-cannot-occur: Add some exception_cannot_occur/exception_is_here. Message-ID: <20120401180852.6A55E820C7@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: exception-cannot-occur Changeset: r54126:7adda6345851 Date: 2012-04-01 19:17 +0200 http://bitbucket.org/pypy/pypy/changeset/7adda6345851/ Log: Add some exception_cannot_occur/exception_is_here. diff --git a/pypy/rlib/rerased.py b/pypy/rlib/rerased.py --- a/pypy/rlib/rerased.py +++ b/pypy/rlib/rerased.py @@ -111,10 +111,10 @@ return identity.leave_tunnel(self.bookkeeper) def specialize_call(self, hop): + hop.exception_cannot_occur() if hop.r_result.lowleveltype is lltype.Void: return hop.inputconst(lltype.Void, None) [v] = hop.inputargs(hop.args_r[0]) - hop.exception_cannot_occur() return hop.args_r[0].rtype_unerase(hop, v) return erase, unerase @@ -155,6 +155,7 @@ def specialize_call(self, hop): [v] = hop.inputargs(hop.args_r[0]) assert isinstance(hop.s_result, annmodel.SomeInteger) + hop.exception_cannot_occur() return hop.args_r[0].rtype_unerase_int(hop, v) def ll_unerase_int(gcref): diff --git a/pypy/rpython/lltypesystem/rbuiltin.py b/pypy/rpython/lltypesystem/rbuiltin.py --- a/pypy/rpython/lltypesystem/rbuiltin.py +++ b/pypy/rpython/lltypesystem/rbuiltin.py @@ -9,6 +9,7 @@ from pypy.rpython.rbool import bool_repr def rtype_builtin_isinstance(hop): + hop.exception_cannot_occur() if hop.s_result.is_constant(): return hop.inputconst(lltype.Bool, hop.s_result.const) if hop.args_r[0] == pyobj_repr or hop.args_r[1] == pyobj_repr: diff --git a/pypy/rpython/ootypesystem/rbuiltin.py b/pypy/rpython/ootypesystem/rbuiltin.py --- a/pypy/rpython/ootypesystem/rbuiltin.py +++ b/pypy/rpython/ootypesystem/rbuiltin.py @@ -7,12 +7,14 @@ from pypy.rpython.error import TyperError def rtype_new(hop): + hop.exception_cannot_occur() assert hop.args_s[0].is_constant() vlist = hop.inputargs(ootype.Void) return hop.genop('new', vlist, resulttype = hop.r_result.lowleveltype) def rtype_oonewarray(hop): + hop.exception_cannot_occur() assert hop.args_s[0].is_constant() vlist = hop.inputarg(ootype.Void, arg=0) vlength = hop.inputarg(ootype.Signed, arg=1) @@ -20,23 +22,27 @@ resulttype = hop.r_result.lowleveltype) def rtype_null(hop): + hop.exception_cannot_occur() assert hop.args_s[0].is_constant() TYPE = hop.args_s[0].const nullvalue = ootype.null(TYPE) return hop.inputconst(TYPE, nullvalue) def rtype_classof(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0], annmodel.SomeOOInstance) vlist = hop.inputargs(hop.args_r[0]) return hop.genop('classof', vlist, resulttype = ootype.Class) def rtype_subclassof(hop): + hop.exception_cannot_occur() vlist = hop.inputargs(rootype.ooclass_repr, rootype.ooclass_repr) return hop.genop('subclassof', vlist, resulttype = ootype.Bool) def rtype_instanceof(hop): + hop.exception_cannot_occur() INSTANCE = hop.args_v[1].value v_inst = hop.inputarg(hop.args_r[0], arg=0) c_cls = hop.inputconst(ootype.Void, INSTANCE) @@ -44,23 +50,27 @@ resulttype=ootype.Bool) def rtype_runtimenew(hop): + hop.exception_cannot_occur() vlist = hop.inputargs(rootype.ooclass_repr) return hop.genop('runtimenew', vlist, resulttype = hop.r_result.lowleveltype) def rtype_ooupcast(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0].const, ootype.Instance) assert isinstance(hop.args_s[1], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[1], arg=1) return hop.genop('ooupcast', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_oodowncast(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0].const, ootype.Instance) assert isinstance(hop.args_s[1], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[1], arg=1) return hop.genop('oodowncast', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_cast_to_object(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0], annmodel.SomeOOStaticMeth) or \ isinstance(hop.args_s[0], annmodel.SomeOOClass) or \ isinstance(hop.args_s[0].ootype, ootype.OOType) @@ -68,12 +78,14 @@ return hop.genop('cast_to_object', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_cast_from_object(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0].const, ootype.OOType) assert isinstance(hop.args_s[1], annmodel.SomeOOObject) v_inst = hop.inputarg(hop.args_r[1], arg=1) return hop.genop('cast_from_object', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_builtin_isinstance(hop): + hop.exception_cannot_occur() if hop.s_result.is_constant(): return hop.inputconst(ootype.Bool, hop.s_result.const) @@ -99,6 +111,7 @@ return ootype.subclassof(c1, class_) def rtype_instantiate(hop): + hop.exception_cannot_occur() if hop.args_s[0].is_constant(): ## INSTANCE = hop.s_result.rtyper_makerepr(hop.rtyper).lowleveltype ## v_instance = hop.inputconst(ootype.Void, INSTANCE) diff --git a/pypy/rpython/rbuiltin.py b/pypy/rpython/rbuiltin.py --- a/pypy/rpython/rbuiltin.py +++ b/pypy/rpython/rbuiltin.py @@ -605,6 +605,7 @@ def rtype_offsetof(hop): TYPE, field = hop.inputargs(lltype.Void, lltype.Void) + hop.exception_cannot_occur() return hop.inputconst(lltype.Signed, llmemory.offsetof(TYPE.value, field.value)) @@ -626,6 +627,7 @@ # keepalive_until_here def rtype_keepalive_until_here(hop): + hop.exception_cannot_occur() for v in hop.args_v: hop.genop('keepalive', [v], resulttype=lltype.Void) return hop.inputconst(lltype.Void, None) diff --git a/pypy/rpython/rrange.py b/pypy/rpython/rrange.py --- a/pypy/rpython/rrange.py +++ b/pypy/rpython/rrange.py @@ -107,8 +107,10 @@ if isinstance(hop.r_result, AbstractRangeRepr): if hop.r_result.step != 0: c_rng = hop.inputconst(Void, hop.r_result.RANGE) + hop.exception_is_here() return hop.gendirectcall(hop.r_result.ll_newrange, c_rng, vstart, vstop) else: + hop.exception_is_here() return hop.gendirectcall(hop.r_result.ll_newrangest, vstart, vstop, vstep) else: # cannot build a RANGE object, needs a real list @@ -117,6 +119,7 @@ if isinstance(ITEMTYPE, Ptr): ITEMTYPE = ITEMTYPE.TO cLIST = hop.inputconst(Void, ITEMTYPE) + hop.exception_is_here() return hop.gendirectcall(ll_range2list, cLIST, vstart, vstop, vstep) rtype_builtin_xrange = rtype_builtin_range diff --git a/pypy/rpython/test/test_extregistry.py b/pypy/rpython/test/test_extregistry.py --- a/pypy/rpython/test/test_extregistry.py +++ b/pypy/rpython/test/test_extregistry.py @@ -114,6 +114,7 @@ _about_ = dummy_func s_result_annotation = annmodel.SomeInteger() def specialize_call(self, hop): + hop.exception_cannot_occur() return hop.inputconst(lltype.Signed, 42) def func(): From noreply at buildbot.pypy.org Sun Apr 1 20:08:53 2012 From: noreply at buildbot.pypy.org (arigo) Date: Sun, 1 Apr 2012 20:08:53 +0200 (CEST) Subject: [pypy-commit] pypy exception-cannot-occur: A bunch more. Message-ID: <20120401180853.A3614820C7@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: exception-cannot-occur Changeset: r54127:83e14fbf623c Date: 2012-04-01 19:31 +0200 http://bitbucket.org/pypy/pypy/changeset/83e14fbf623c/ Log: A bunch more. diff --git a/pypy/rlib/objectmodel.py b/pypy/rlib/objectmodel.py --- a/pypy/rlib/objectmodel.py +++ b/pypy/rlib/objectmodel.py @@ -419,6 +419,7 @@ from pypy.rpython.error import TyperError raise TyperError("compute_identity_hash() cannot be applied to" " %r" % (vobj.concretetype,)) + hop.exception_cannot_occur() return hop.genop('gc_identityhash', [vobj], resulttype=lltype.Signed) class Entry(ExtRegistryEntry): @@ -441,6 +442,7 @@ from pypy.rpython.error import TyperError raise TyperError("compute_unique_id() cannot be applied to" " %r" % (vobj.concretetype,)) + hop.exception_cannot_occur() return hop.genop('gc_id', [vobj], resulttype=lltype.Signed) class Entry(ExtRegistryEntry): @@ -452,6 +454,7 @@ def specialize_call(self, hop): vobj, = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() if hop.rtyper.type_system.name == 'lltypesystem': from pypy.rpython.lltypesystem import lltype if isinstance(vobj.concretetype, lltype.Ptr): diff --git a/pypy/rpython/lltypesystem/rbuiltin.py b/pypy/rpython/lltypesystem/rbuiltin.py --- a/pypy/rpython/lltypesystem/rbuiltin.py +++ b/pypy/rpython/lltypesystem/rbuiltin.py @@ -34,6 +34,7 @@ return my_instantiate() def rtype_instantiate(hop): + hop.exception_cannot_occur() s_class = hop.args_s[0] assert isinstance(s_class, annmodel.SomePBC) if len(s_class.descriptions) != 1: @@ -47,6 +48,7 @@ return rclass.rtype_new_instance(hop.rtyper, classdef, hop.llops) def rtype_builtin_hasattr(hop): + hop.exception_cannot_occur() if hop.s_result.is_constant(): return hop.inputconst(lltype.Bool, hop.s_result.const) if hop.args_r[0] == pyobj_repr: @@ -57,6 +59,7 @@ raise TyperError("hasattr is only suported on a constant or on PyObject") def rtype_builtin___import__(hop): + xxx # should not be used any more args_v = hop.inputargs(*[pyobj_repr for ign in hop.args_r]) c = hop.inputconst(pyobj_repr, __import__) return hop.genop('simple_call', [c] + args_v, resulttype = pyobj_repr) diff --git a/pypy/rpython/ootypesystem/ooregistry.py b/pypy/rpython/ootypesystem/ooregistry.py --- a/pypy/rpython/ootypesystem/ooregistry.py +++ b/pypy/rpython/ootypesystem/ooregistry.py @@ -22,6 +22,7 @@ annmodel.SomeOOInstance, annmodel.SomeString)) vlist = hop.inputargs(hop.args_r[0], ootype.Signed) + hop.exception_cannot_occur() return hop.genop('oostring', vlist, resulttype = ootype.String) class Entry_oounicode(ExtRegistryEntry): @@ -38,6 +39,7 @@ assert isinstance(hop.args_s[0], (annmodel.SomeUnicodeCodePoint, annmodel.SomeOOInstance)) vlist = hop.inputargs(hop.args_r[0], ootype.Signed) + hop.exception_cannot_occur() return hop.genop('oounicode', vlist, resulttype = ootype.Unicode) diff --git a/pypy/rpython/rbuiltin.py b/pypy/rpython/rbuiltin.py --- a/pypy/rpython/rbuiltin.py +++ b/pypy/rpython/rbuiltin.py @@ -205,6 +205,7 @@ # ____________________________________________________________ def rtype_builtin_bool(hop): + xxx # not called any more? assert hop.nb_args == 1 return hop.args_r[0].rtype_is_true(hop) @@ -451,6 +452,7 @@ assert hop.args_s[0].is_constant() TGT = hop.args_s[0].const v_type, v_value = hop.inputargs(lltype.Void, hop.args_r[1]) + hop.exception_cannot_occur() return gen_cast(hop.llops, TGT, v_value) _cast_to_Signed = { @@ -615,6 +617,7 @@ # non-gc objects def rtype_free_non_gc_object(hop): + hop.exception_cannot_occur() vinst, = hop.inputargs(hop.args_r[0]) flavor = hop.args_r[0].gcflavor assert flavor != 'gc' diff --git a/pypy/rpython/rint.py b/pypy/rpython/rint.py --- a/pypy/rpython/rint.py +++ b/pypy/rpython/rint.py @@ -310,6 +310,8 @@ if hop.has_implicit_exception(ValueError): hop.exception_is_here() hop.gendirectcall(ll_check_chr, vlist[0]) + else: + hop.exception_cannot_occur() return hop.genop('cast_int_to_char', vlist, resulttype=Char) def rtype_unichr(_, hop): @@ -317,6 +319,8 @@ if hop.has_implicit_exception(ValueError): hop.exception_is_here() hop.gendirectcall(ll_check_unichr, vlist[0]) + else: + hop.exception_cannot_occur() return hop.genop('cast_int_to_unichar', vlist, resulttype=UniChar) def rtype_is_true(self, hop): From noreply at buildbot.pypy.org Sun Apr 1 20:08:54 2012 From: noreply at buildbot.pypy.org (arigo) Date: Sun, 1 Apr 2012 20:08:54 +0200 (CEST) Subject: [pypy-commit] pypy exception-cannot-occur: More. Message-ID: <20120401180854.E117A820C7@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: exception-cannot-occur Changeset: r54128:2335956a2d4d Date: 2012-04-01 19:37 +0200 http://bitbucket.org/pypy/pypy/changeset/2335956a2d4d/ Log: More. diff --git a/pypy/rpython/lltypesystem/rtuple.py b/pypy/rpython/lltypesystem/rtuple.py --- a/pypy/rpython/lltypesystem/rtuple.py +++ b/pypy/rpython/lltypesystem/rtuple.py @@ -55,6 +55,7 @@ vtup = hop.inputarg(self, 0) LIST = hop.r_result.lowleveltype.TO cno = inputconst(Signed, nitems) + hop.exception_is_here() vlist = hop.gendirectcall(LIST.ll_newlist, cno) v_func = hop.inputconst(Void, rlist.dum_nocheck) for index in range(nitems): diff --git a/pypy/rpython/ootypesystem/rtuple.py b/pypy/rpython/ootypesystem/rtuple.py --- a/pypy/rpython/ootypesystem/rtuple.py +++ b/pypy/rpython/ootypesystem/rtuple.py @@ -39,6 +39,7 @@ RESULT = hop.r_result.lowleveltype c_resulttype = inputconst(ootype.Void, RESULT) c_length = inputconst(ootype.Signed, len(self.items_r)) + hop.exception_is_here() if isinstance(RESULT, ootype.Array): v_list = hop.genop('oonewarray', [c_resulttype, c_length], resulttype=RESULT) else: diff --git a/pypy/rpython/rbuiltin.py b/pypy/rpython/rbuiltin.py --- a/pypy/rpython/rbuiltin.py +++ b/pypy/rpython/rbuiltin.py @@ -249,6 +249,7 @@ def rtype_builtin_min(hop): v1, v2 = hop.inputargs(hop.r_result, hop.r_result) + hop.exception_cannot_occur() return hop.gendirectcall(ll_min, v1, v2) def ll_min(i1, i2): @@ -258,6 +259,7 @@ def rtype_builtin_max(hop): v1, v2 = hop.inputargs(hop.r_result, hop.r_result) + hop.exception_cannot_occur() return hop.gendirectcall(ll_max, v1, v2) def ll_max(i1, i2): @@ -569,6 +571,7 @@ def rtype_raw_malloc(hop): v_size, = hop.inputargs(lltype.Signed) + hop.exception_cannot_occur() return hop.genop('raw_malloc', [v_size], resulttype=llmemory.Address) def rtype_raw_malloc_usage(hop): @@ -597,6 +600,7 @@ if s_addr.is_null_address(): raise TyperError("raw_memclear(x, n) where x is the constant NULL") v_list = hop.inputargs(llmemory.Address, lltype.Signed) + hop.exception_cannot_occur() return hop.genop('raw_memclear', v_list) BUILTIN_TYPER[llmemory.raw_malloc] = rtype_raw_malloc diff --git a/pypy/rpython/rlist.py b/pypy/rpython/rlist.py --- a/pypy/rpython/rlist.py +++ b/pypy/rpython/rlist.py @@ -115,6 +115,7 @@ def rtype_bltn_list(self, hop): v_lst = hop.inputarg(self, 0) cRESLIST = hop.inputconst(Void, hop.r_result.LIST) + hop.exception_is_here() return hop.gendirectcall(ll_copy, cRESLIST, v_lst) def rtype_len(self, hop): From noreply at buildbot.pypy.org Sun Apr 1 20:08:56 2012 From: noreply at buildbot.pypy.org (arigo) Date: Sun, 1 Apr 2012 20:08:56 +0200 (CEST) Subject: [pypy-commit] pypy exception-cannot-occur: More. Message-ID: <20120401180856.3D18B820C7@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: exception-cannot-occur Changeset: r54129:9a1134601e5b Date: 2012-04-01 19:46 +0200 http://bitbucket.org/pypy/pypy/changeset/9a1134601e5b/ Log: More. diff --git a/pypy/rlib/objectmodel.py b/pypy/rlib/objectmodel.py --- a/pypy/rlib/objectmodel.py +++ b/pypy/rlib/objectmodel.py @@ -215,6 +215,7 @@ def specialize_call(self, hop): from pypy.rpython.lltypesystem import lltype + hop.exception_cannot_occur() return hop.inputconst(lltype.Bool, hop.s_result.const) # ____________________________________________________________ @@ -397,6 +398,7 @@ r_obj, = hop.args_r v_obj, = hop.inputargs(r_obj) ll_fn = r_obj.get_ll_hash_function() + hop.exception_is_here() return hop.gendirectcall(ll_fn, v_obj) class Entry(ExtRegistryEntry): diff --git a/pypy/rpython/rbool.py b/pypy/rpython/rbool.py --- a/pypy/rpython/rbool.py +++ b/pypy/rpython/rbool.py @@ -34,6 +34,7 @@ def rtype_float(_, hop): vlist = hop.inputargs(Float) + hop.exception_cannot_occur() return vlist[0] # diff --git a/pypy/rpython/rbuiltin.py b/pypy/rpython/rbuiltin.py --- a/pypy/rpython/rbuiltin.py +++ b/pypy/rpython/rbuiltin.py @@ -205,7 +205,7 @@ # ____________________________________________________________ def rtype_builtin_bool(hop): - xxx # not called any more? + # not called any more? assert hop.nb_args == 1 return hop.args_r[0].rtype_is_true(hop) @@ -536,11 +536,13 @@ def rtype_identity_hash(hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_identityhash', vlist, resulttype=lltype.Signed) def rtype_runtime_type_info(hop): assert isinstance(hop.args_r[0], rptr.PtrRepr) vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('runtime_type_info', vlist, resulttype = hop.r_result.lowleveltype) diff --git a/pypy/rpython/rfloat.py b/pypy/rpython/rfloat.py --- a/pypy/rpython/rfloat.py +++ b/pypy/rpython/rfloat.py @@ -136,7 +136,10 @@ hop.exception_cannot_occur() return hop.genop('cast_float_to_int', vlist, resulttype=Signed) - rtype_float = rtype_pos + def rtype_float(_, hop): + vlist = hop.inputargs(Float) + hop.exception_cannot_occur() + return vlist[0] # version picked by specialisation based on which # type system rtyping is using, from .ll_str module diff --git a/pypy/rpython/rrange.py b/pypy/rpython/rrange.py --- a/pypy/rpython/rrange.py +++ b/pypy/rpython/rrange.py @@ -215,4 +215,5 @@ [v_index, v_item]) def rtype_builtin_enumerate(hop): + hop.exception_cannot_occur() return hop.r_result.r_baseiter.newiter(hop) diff --git a/pypy/rpython/test/test_rclass.py b/pypy/rpython/test/test_rclass.py --- a/pypy/rpython/test/test_rclass.py +++ b/pypy/rpython/test/test_rclass.py @@ -1085,6 +1085,7 @@ return annmodel.SomeInteger() def specialize_call(self, hop): [v_instance] = hop.inputargs(*hop.args_r) + hop.exception_is_here() return hop.gendirectcall(ll_my_gethash, v_instance) def f(n): From noreply at buildbot.pypy.org Sun Apr 1 20:08:57 2012 From: noreply at buildbot.pypy.org (arigo) Date: Sun, 1 Apr 2012 20:08:57 +0200 (CEST) Subject: [pypy-commit] pypy exception-cannot-occur: More. Message-ID: <20120401180857.9300E820C7@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: exception-cannot-occur Changeset: r54130:b39ccd2db7fa Date: 2012-04-01 19:59 +0200 http://bitbucket.org/pypy/pypy/changeset/b39ccd2db7fa/ Log: More. diff --git a/pypy/rlib/jit.py b/pypy/rlib/jit.py --- a/pypy/rlib/jit.py +++ b/pypy/rlib/jit.py @@ -382,7 +382,7 @@ pass def specialize_call(self, hop): - pass + hop.exception_cannot_occur() vref_None = non_virtual_ref(None) diff --git a/pypy/rlib/rbigint.py b/pypy/rlib/rbigint.py --- a/pypy/rlib/rbigint.py +++ b/pypy/rlib/rbigint.py @@ -85,7 +85,7 @@ s_DIGIT = self.bookkeeper.valueoftype(type(NULLDIGIT)) assert s_DIGIT.contains(s_list.listdef.listitem.s_value) def specialize_call(self, hop): - pass + hop.exception_cannot_occur() class rbigint(object): diff --git a/pypy/rpython/lltypesystem/lloperation.py b/pypy/rpython/lltypesystem/lloperation.py --- a/pypy/rpython/lltypesystem/lloperation.py +++ b/pypy/rpython/lltypesystem/lloperation.py @@ -130,6 +130,7 @@ def specialize_call(self, hop): from pypy.rpython.lltypesystem import lltype + hop.exception_cannot_occur() return hop.inputconst(lltype.Void, None) def enum_ops_without_sideeffects(raising_is_ok=False): diff --git a/pypy/rpython/lltypesystem/rclass.py b/pypy/rpython/lltypesystem/rclass.py --- a/pypy/rpython/lltypesystem/rclass.py +++ b/pypy/rpython/lltypesystem/rclass.py @@ -746,4 +746,5 @@ assert isinstance(TYPE, GcStruct) assert lltype._castdepth(TYPE, OBJECT) > 0 hop.rtyper.set_type_for_typeptr(vtable, TYPE) + hop.exception_cannot_occur() return hop.inputconst(lltype.Void, None) diff --git a/pypy/rpython/module/r_os_stat.py b/pypy/rpython/module/r_os_stat.py --- a/pypy/rpython/module/r_os_stat.py +++ b/pypy/rpython/module/r_os_stat.py @@ -65,4 +65,5 @@ r_StatResult = hop.rtyper.getrepr(ll_os_stat.s_StatResult) [v_result] = hop.inputargs(r_StatResult.r_tuple) # no-op conversion from r_StatResult.r_tuple to r_StatResult + hop.exception_cannot_occur() return v_result diff --git a/pypy/rpython/rstr.py b/pypy/rpython/rstr.py --- a/pypy/rpython/rstr.py +++ b/pypy/rpython/rstr.py @@ -288,6 +288,8 @@ def rtype_unicode(self, hop): if hop.args_s[0].is_constant(): + # convertion errors occur during annotation, so cannot any more: + hop.exception_cannot_occur() return hop.inputconst(hop.r_result, hop.s_result.const) repr = hop.args_r[0].repr v_str = hop.inputarg(repr, 0) From noreply at buildbot.pypy.org Sun Apr 1 20:27:55 2012 From: noreply at buildbot.pypy.org (arigo) Date: Sun, 1 Apr 2012 20:27:55 +0200 (CEST) Subject: [pypy-commit] pypy default: Bah. This test used to fail if the current "nice" level is 17, 18 or Message-ID: <20120401182755.86263820C7@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: Changeset: r54131:76c5931f64cc Date: 2012-04-01 20:27 +0200 http://bitbucket.org/pypy/pypy/changeset/76c5931f64cc/ Log: Bah. This test used to fail if the current "nice" level is 17, 18 or 19. diff --git a/pypy/translator/c/test/test_extfunc.py b/pypy/translator/c/test/test_extfunc.py --- a/pypy/translator/c/test/test_extfunc.py +++ b/pypy/translator/c/test/test_extfunc.py @@ -919,4 +919,5 @@ t, cbuilder = self.compile(does_stuff) data = cbuilder.cmdexec('') res = os.nice(0) + 3 + if res > 19: res = 19 # xxx Linux specific, probably assert data.startswith('os.nice returned %d\n' % res) From noreply at buildbot.pypy.org Sun Apr 1 20:34:38 2012 From: noreply at buildbot.pypy.org (arigo) Date: Sun, 1 Apr 2012 20:34:38 +0200 (CEST) Subject: [pypy-commit] pypy exception-cannot-occur: More. Message-ID: <20120401183438.6F503820C7@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: exception-cannot-occur Changeset: r54132:7d6635820406 Date: 2012-04-01 20:32 +0200 http://bitbucket.org/pypy/pypy/changeset/7d6635820406/ Log: More. diff --git a/pypy/jit/backend/llgraph/llimpl.py b/pypy/jit/backend/llgraph/llimpl.py --- a/pypy/jit/backend/llgraph/llimpl.py +++ b/pypy/jit/backend/llgraph/llimpl.py @@ -1797,6 +1797,7 @@ if specialize_as_constant: def specialize_call(self, hop): llvalue = func(hop.args_s[0].const) + hop.exception_cannot_occur() return hop.inputconst(lltype.typeOf(llvalue), llvalue) else: # specialize as direct_call @@ -1813,6 +1814,7 @@ sm = ootype._static_meth(FUNCTYPE, _name=func.__name__, _callable=func) cfunc = hop.inputconst(FUNCTYPE, sm) args_v = hop.inputargs(*hop.args_r) + hop.exception_is_here() return hop.genop('direct_call', [cfunc] + args_v, hop.r_result) diff --git a/pypy/objspace/fake/objspace.py b/pypy/objspace/fake/objspace.py --- a/pypy/objspace/fake/objspace.py +++ b/pypy/objspace/fake/objspace.py @@ -86,6 +86,7 @@ return s_None def specialize_call(self, hop): + hop.exception_cannot_occur() return hop.inputconst(lltype.Void, None) # ____________________________________________________________ diff --git a/pypy/rlib/jit_hooks.py b/pypy/rlib/jit_hooks.py --- a/pypy/rlib/jit_hooks.py +++ b/pypy/rlib/jit_hooks.py @@ -22,6 +22,7 @@ c_name = hop.inputconst(lltype.Void, 'access_helper') args_v = [hop.inputarg(arg, arg=i) for i, arg in enumerate(hop.args_r)] + hop.exception_cannot_occur() return hop.genop('jit_marker', [c_name, c_func] + args_v, resulttype=hop.r_result) return helper diff --git a/pypy/rlib/rerased.py b/pypy/rlib/rerased.py --- a/pypy/rlib/rerased.py +++ b/pypy/rlib/rerased.py @@ -155,7 +155,6 @@ def specialize_call(self, hop): [v] = hop.inputargs(hop.args_r[0]) assert isinstance(hop.s_result, annmodel.SomeInteger) - hop.exception_cannot_occur() return hop.args_r[0].rtype_unerase_int(hop, v) def ll_unerase_int(gcref): @@ -217,6 +216,7 @@ return hop.genop('cast_opaque_ptr', [v], resulttype=hop.r_result) def rtype_unerase_int(self, hop, v): + hop.exception_cannot_occur() return hop.gendirectcall(ll_unerase_int, v) def rtype_erase_int(self, hop): @@ -267,6 +267,7 @@ def rtype_unerase_int(self, hop, v): c_one = hop.inputconst(lltype.Signed, 1) + hop.exception_cannot_occur() v2 = hop.genop('oounbox_int', [v], resulttype=hop.r_result) return hop.genop('int_rshift', [v2, c_one], resulttype=lltype.Signed) diff --git a/pypy/rlib/rgc.py b/pypy/rlib/rgc.py --- a/pypy/rlib/rgc.py +++ b/pypy/rlib/rgc.py @@ -382,6 +382,7 @@ def compute_result_annotation(self): return s_list_of_gcrefs() def specialize_call(self, hop): + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_roots', [], resulttype = hop.r_result) class Entry(ExtRegistryEntry): @@ -392,6 +393,7 @@ return s_list_of_gcrefs() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_referents', vlist, resulttype = hop.r_result) @@ -402,6 +404,7 @@ return annmodel.SomeInteger() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_memory_usage', vlist, resulttype = hop.r_result) @@ -412,6 +415,7 @@ return annmodel.SomeInteger() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_type_index', vlist, resulttype = hop.r_result) @@ -430,6 +434,7 @@ return annmodel.SomeBool() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_is_rpy_instance', vlist, resulttype = hop.r_result) @@ -449,6 +454,7 @@ classrepr = getclassrepr(hop.rtyper, classdef) vtable = classrepr.getvtable() assert lltype.typeOf(vtable) == rclass.CLASSTYPE + hop.exception_cannot_occur() return Constant(vtable, concretetype=rclass.CLASSTYPE) class Entry(ExtRegistryEntry): diff --git a/pypy/rlib/rstring.py b/pypy/rlib/rstring.py --- a/pypy/rlib/rstring.py +++ b/pypy/rlib/rstring.py @@ -245,5 +245,5 @@ raise ValueError("Value is not no_nul") def specialize_call(self, hop): - pass + hop.exception_cannot_occur() diff --git a/pypy/rpython/controllerentry.py b/pypy/rpython/controllerentry.py --- a/pypy/rpython/controllerentry.py +++ b/pypy/rpython/controllerentry.py @@ -201,6 +201,7 @@ def specialize_call(self, hop): from pypy.rpython.lltypesystem import lltype assert hop.s_result.is_constant() + hop.exception_cannot_occur() return hop.inputconst(lltype.Bool, hop.s_result.const) # ____________________________________________________________ diff --git a/pypy/translator/cli/dotnet.py b/pypy/translator/cli/dotnet.py --- a/pypy/translator/cli/dotnet.py +++ b/pypy/translator/cli/dotnet.py @@ -459,6 +459,7 @@ def specialize_call(self, hop): + hop.exception_cannot_occur() assert hop.args_s[1].is_constant() TYPE = hop.args_s[1].const v_obj = hop.inputarg(hop.args_r[0], arg=0) @@ -507,6 +508,7 @@ def specialize_call(self, hop): v_obj, = hop.inputargs(*hop.args_r) + hop.exception_cannot_occur() return hop.genop('same_as', [v_obj], hop.r_result.lowleveltype) def new_array(type, length): @@ -608,6 +610,7 @@ def specialize_call(self, hop): v_type, = hop.inputargs(*hop.args_r) + hop.exception_cannot_occur() return hop.genop('cli_typeof', [v_type], hop.r_result.lowleveltype) @@ -626,6 +629,7 @@ v_obj, = hop.inputargs(*hop.args_r) methodname = hop.args_r[0].methodname c_methodname = hop.inputconst(ootype.Void, methodname) + hop.exception_cannot_occur() return hop.genop('cli_eventhandler', [v_obj, c_methodname], hop.r_result.lowleveltype) @@ -647,6 +651,7 @@ def specialize_call(self, hop): assert isinstance(hop.args_s[0], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[0], arg=0) + hop.exception_cannot_occur() return hop.genop('oodowncast', [v_inst], resulttype = hop.r_result.lowleveltype) @@ -668,6 +673,7 @@ def specialize_call(self, hop): assert isinstance(hop.args_s[0], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[0], arg=0) + hop.exception_cannot_occur() return hop.genop('ooupcast', [v_inst], resulttype = hop.r_result.lowleveltype) @@ -701,6 +707,7 @@ def specialize_call(self, hop): v_obj = hop.inputarg(hop.args_r[0], arg=0) + hop.exception_cannot_occur() return hop.genop('oodowncast', [v_obj], hop.r_result.lowleveltype) From noreply at buildbot.pypy.org Mon Apr 2 07:51:20 2012 From: noreply at buildbot.pypy.org (fijal) Date: Mon, 2 Apr 2012 07:51:20 +0200 (CEST) Subject: [pypy-commit] pypy default: fix for issue #1104, allow null bytes in certs Message-ID: <20120402055120.7EDF0820C7@wyvern.cs.uni-duesseldorf.de> Author: Maciej Fijalkowski Branch: Changeset: r54133:5bfd8e84f0f2 Date: 2012-04-02 07:50 +0200 http://bitbucket.org/pypy/pypy/changeset/5bfd8e84f0f2/ Log: fix for issue #1104, allow null bytes in certs diff --git a/pypy/module/_ssl/interp_ssl.py b/pypy/module/_ssl/interp_ssl.py --- a/pypy/module/_ssl/interp_ssl.py +++ b/pypy/module/_ssl/interp_ssl.py @@ -432,7 +432,8 @@ raise _ssl_seterror(self.space, self, length) try: # this is actually an immutable bytes sequence - return self.space.wrap(rffi.charp2str(buf_ptr[0])) + return self.space.wrap(rffi.charpsize2str(buf_ptr[0], + length)) finally: libssl_OPENSSL_free(buf_ptr[0]) else: From noreply at buildbot.pypy.org Mon Apr 2 08:45:22 2012 From: noreply at buildbot.pypy.org (fijal) Date: Mon, 2 Apr 2012 08:45:22 +0200 (CEST) Subject: [pypy-commit] pypy default: add a debug repr and a passing test Message-ID: <20120402064522.BADA0820C7@wyvern.cs.uni-duesseldorf.de> Author: Maciej Fijalkowski Branch: Changeset: r54134:80e8c18538f3 Date: 2012-04-02 08:44 +0200 http://bitbucket.org/pypy/pypy/changeset/80e8c18538f3/ Log: add a debug repr and a passing test diff --git a/pypy/module/micronumpy/signature.py b/pypy/module/micronumpy/signature.py --- a/pypy/module/micronumpy/signature.py +++ b/pypy/module/micronumpy/signature.py @@ -219,6 +219,9 @@ assert isinstance(arr, VirtualSlice) return self.child.eval(frame, arr.child) + def debug_repr(self): + return 'VirtualSlice(%s)' % self.child.debug_repr() + class Call1(Signature): _immutable_fields_ = ['unfunc', 'name', 'child', 'res', 'dtype'] diff --git a/pypy/module/micronumpy/test/test_numarray.py b/pypy/module/micronumpy/test/test_numarray.py --- a/pypy/module/micronumpy/test/test_numarray.py +++ b/pypy/module/micronumpy/test/test_numarray.py @@ -1338,6 +1338,10 @@ dims_disagree = raises(ValueError, concatenate, (a1, b1), axis=0) assert str(dims_disagree.value) == \ "array dimensions must agree except for axis being concatenated" + a = array([1, 2, 3, 4, 5, 6]) + a = (a + a)[::2] + b = concatenate((a[:3], a[-3:])) + assert (b == [2, 6, 10, 2, 6, 10]).all() def test_std(self): from _numpypy import array From noreply at buildbot.pypy.org Mon Apr 2 09:19:32 2012 From: noreply at buildbot.pypy.org (fijal) Date: Mon, 2 Apr 2012 09:19:32 +0200 (CEST) Subject: [pypy-commit] pypy default: a failing test Message-ID: <20120402071932.AFED346E0EF@wyvern.cs.uni-duesseldorf.de> Author: Maciej Fijalkowski Branch: Changeset: r54135:e69f9eaa2352 Date: 2012-04-02 09:15 +0200 http://bitbucket.org/pypy/pypy/changeset/e69f9eaa2352/ Log: a failing test diff --git a/pypy/module/micronumpy/test/test_numarray.py b/pypy/module/micronumpy/test/test_numarray.py --- a/pypy/module/micronumpy/test/test_numarray.py +++ b/pypy/module/micronumpy/test/test_numarray.py @@ -395,11 +395,21 @@ assert a[3] == 0. def test_newaxis(self): - from _numpypy import array + import math + from _numpypy import array, cos, zeros from numpypy.core.numeric import newaxis a = array(range(5)) b = array([range(5)]) assert (a[newaxis] == b).all() + a = array(range(3)) + b = array([1, 3]) + expected = zeros((3, 2)) + for x in range(3): + for y in range(2): + expected[x, y] = math.cos(a[x]) * math.cos(b[y]) + print (cos(a)[:,newaxis] * cos(b).T) + print expected + assert ((cos(a)[:,newaxis] * cos(b).T) == expected).all() def test_newaxis_slice(self): from _numpypy import array From noreply at buildbot.pypy.org Mon Apr 2 09:37:21 2012 From: noreply at buildbot.pypy.org (fijal) Date: Mon, 2 Apr 2012 09:37:21 +0200 (CEST) Subject: [pypy-commit] pypy default: oops Message-ID: <20120402073721.B535746E0EF@wyvern.cs.uni-duesseldorf.de> Author: Maciej Fijalkowski Branch: Changeset: r54136:78c41b32d5f1 Date: 2012-04-02 09:22 +0200 http://bitbucket.org/pypy/pypy/changeset/78c41b32d5f1/ Log: oops diff --git a/pypy/module/micronumpy/signature.py b/pypy/module/micronumpy/signature.py --- a/pypy/module/micronumpy/signature.py +++ b/pypy/module/micronumpy/signature.py @@ -431,7 +431,7 @@ frame.cur_value = self.binfunc(self.calc_dtype, frame.cur_value, rval) def debug_repr(self): - return 'ReduceSig(%s)' % (self.name, self.right.debug_repr()) + return 'ReduceSig(%s, %s)' % (self.name, self.right.debug_repr()) class SliceloopSignature(Call2): def eval(self, frame, arr): From noreply at buildbot.pypy.org Mon Apr 2 09:37:22 2012 From: noreply at buildbot.pypy.org (fijal) Date: Mon, 2 Apr 2012 09:37:22 +0200 (CEST) Subject: [pypy-commit] pypy default: grumble, of course you apply stuff in this order Message-ID: <20120402073722.EDADA46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Maciej Fijalkowski Branch: Changeset: r54137:73cc3cc4b016 Date: 2012-04-02 09:36 +0200 http://bitbucket.org/pypy/pypy/changeset/73cc3cc4b016/ Log: grumble, of course you apply stuff in this order diff --git a/pypy/module/micronumpy/signature.py b/pypy/module/micronumpy/signature.py --- a/pypy/module/micronumpy/signature.py +++ b/pypy/module/micronumpy/signature.py @@ -211,7 +211,7 @@ def _create_iter(self, iterlist, arraylist, arr, transforms): from pypy.module.micronumpy.interp_numarray import VirtualSlice assert isinstance(arr, VirtualSlice) - transforms = transforms + [ViewTransform(arr.chunks)] + transforms = [ViewTransform(arr.chunks)] + transforms self.child._create_iter(iterlist, arraylist, arr.child, transforms) def eval(self, frame, arr): @@ -277,7 +277,7 @@ from pypy.module.micronumpy.interp_numarray import Call1 assert isinstance(arr, Call1) - vtransforms = transforms + [BroadcastTransform(arr.values.shape)] + vtransforms = [BroadcastTransform(arr.values.shape)] + transforms self.child._create_iter(iterlist, arraylist, arr.values, vtransforms) self.res._create_iter(iterlist, arraylist, arr.res, transforms) @@ -355,7 +355,7 @@ from pypy.module.micronumpy.interp_numarray import ResultArray assert isinstance(arr, ResultArray) - rtransforms = transforms + [BroadcastTransform(arr.left.shape)] + rtransforms = [BroadcastTransform(arr.left.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, transforms) self.right._create_iter(iterlist, arraylist, arr.right, rtransforms) @@ -382,7 +382,7 @@ from pypy.module.micronumpy.interp_numarray import Call2 assert isinstance(arr, Call2) - ltransforms = transforms + [BroadcastTransform(arr.shape)] + ltransforms = [BroadcastTransform(arr.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, ltransforms) self.right._create_iter(iterlist, arraylist, arr.right, transforms) @@ -395,7 +395,7 @@ from pypy.module.micronumpy.interp_numarray import Call2 assert isinstance(arr, Call2) - rtransforms = transforms + [BroadcastTransform(arr.shape)] + rtransforms = [BroadcastTransform(arr.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, transforms) self.right._create_iter(iterlist, arraylist, arr.right, rtransforms) @@ -408,8 +408,8 @@ from pypy.module.micronumpy.interp_numarray import Call2 assert isinstance(arr, Call2) - rtransforms = transforms + [BroadcastTransform(arr.shape)] - ltransforms = transforms + [BroadcastTransform(arr.shape)] + rtransforms = [BroadcastTransform(arr.shape)] + transforms + ltransforms = [BroadcastTransform(arr.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, ltransforms) self.right._create_iter(iterlist, arraylist, arr.right, rtransforms) @@ -455,7 +455,7 @@ from pypy.module.micronumpy.interp_numarray import SliceArray assert isinstance(arr, SliceArray) - rtransforms = transforms + [BroadcastTransform(arr.shape)] + rtransforms = [BroadcastTransform(arr.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, transforms) self.right._create_iter(iterlist, arraylist, arr.right, rtransforms) diff --git a/pypy/module/micronumpy/test/test_numarray.py b/pypy/module/micronumpy/test/test_numarray.py --- a/pypy/module/micronumpy/test/test_numarray.py +++ b/pypy/module/micronumpy/test/test_numarray.py @@ -407,8 +407,6 @@ for x in range(3): for y in range(2): expected[x, y] = math.cos(a[x]) * math.cos(b[y]) - print (cos(a)[:,newaxis] * cos(b).T) - print expected assert ((cos(a)[:,newaxis] * cos(b).T) == expected).all() def test_newaxis_slice(self): From noreply at buildbot.pypy.org Mon Apr 2 09:45:00 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 09:45:00 +0200 (CEST) Subject: [pypy-commit] pypy default: 64-bit compat. Message-ID: <20120402074500.21BF446E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: Changeset: r54138:4b68bb8c88de Date: 2012-04-02 09:44 +0200 http://bitbucket.org/pypy/pypy/changeset/4b68bb8c88de/ Log: 64-bit compat. diff --git a/pypy/module/pypyjit/test_pypy_c/test_string.py b/pypy/module/pypyjit/test_pypy_c/test_string.py --- a/pypy/module/pypyjit/test_pypy_c/test_string.py +++ b/pypy/module/pypyjit/test_pypy_c/test_string.py @@ -214,7 +214,7 @@ loops = log.loops_by_filename(self.filepath) loop, = loops loop.match_by_id('callone', ''' - p114 = call(ConstClass(ll_lower__rpy_stringPtr), p113, descr=) + p114 = call(ConstClass(ll_lower__rpy_stringPtr), p113, descr=) guard_no_exception(descr=...) ''') loop.match_by_id('calltwo', '') # nothing From noreply at buildbot.pypy.org Mon Apr 2 10:19:09 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 10:19:09 +0200 (CEST) Subject: [pypy-commit] pypy exception-cannot-occur: Disable a test that occasionally crashes because of ll2ctypes not being Message-ID: <20120402081909.226E246E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: exception-cannot-occur Changeset: r54139:2b2762fea668 Date: 2012-04-02 10:13 +0200 http://bitbucket.org/pypy/pypy/changeset/2b2762fea668/ Log: Disable a test that occasionally crashes because of ll2ctypes not being thread-safe at all. diff --git a/pypy/module/thread/test/test_ll_thread.py b/pypy/module/thread/test/test_ll_thread.py --- a/pypy/module/thread/test/test_ll_thread.py +++ b/pypy/module/thread/test/test_ll_thread.py @@ -66,7 +66,6 @@ def test_gc_locking(self): import time from pypy.rlib.objectmodel import invoke_around_extcall - from pypy.rlib.objectmodel import we_are_translated from pypy.rlib.debug import ll_assert class State: @@ -129,8 +128,6 @@ state.finished = 0 # the next line installs before_extcall() and after_extcall() # to be called automatically around external function calls. - # When not translated it does not work around time.sleep(), - # so we have to call them manually for this test. invoke_around_extcall(before_extcall, after_extcall) g(10, 1) @@ -142,13 +139,9 @@ willing_to_wait_more -= 1 done = len(state.answers) == expected - if not we_are_translated(): before_extcall() time.sleep(0.01) - if not we_are_translated(): after_extcall() - if not we_are_translated(): before_extcall() time.sleep(0.1) - if not we_are_translated(): after_extcall() return len(state.answers) @@ -160,12 +153,11 @@ answers = fn() assert answers == expected -class TestRunDirectly(AbstractThreadTests): - def getcompiled(self, f, argtypes): - return f - - def test_start_new_thread(self): - py.test.skip("deadlocks occasionally -- why???") +#class TestRunDirectly(AbstractThreadTests): +# def getcompiled(self, f, argtypes): +# return f +# These are disabled because they crash occasionally for bad reasons +# related to the fact that ll2ctypes is not at all thread-safe class TestUsingBoehm(AbstractThreadTests): gcpolicy = 'boehm' From noreply at buildbot.pypy.org Mon Apr 2 10:19:10 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 10:19:10 +0200 (CEST) Subject: [pypy-commit] pypy exception-cannot-occur: Close branch ready for merge. Message-ID: <20120402081910.525BD46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: exception-cannot-occur Changeset: r54140:cbe26c9ba624 Date: 2012-04-02 10:14 +0200 http://bitbucket.org/pypy/pypy/changeset/cbe26c9ba624/ Log: Close branch ready for merge. From noreply at buildbot.pypy.org Mon Apr 2 10:19:12 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 10:19:12 +0200 (CEST) Subject: [pypy-commit] pypy default: Merge exception-cannot-occur: now we have to say either Message-ID: <20120402081912.8D06F46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: Changeset: r54141:5820e4a53018 Date: 2012-04-02 10:17 +0200 http://bitbucket.org/pypy/pypy/changeset/5820e4a53018/ Log: Merge exception-cannot-occur: now we have to say either hop.exception_cannot_occur() or hop.exception_is_here() in all specialize_call() methods we write. Previously, it "kind of worked" for a while until someone tried to call the special function within a try: block. diff --git a/pypy/jit/backend/llgraph/llimpl.py b/pypy/jit/backend/llgraph/llimpl.py --- a/pypy/jit/backend/llgraph/llimpl.py +++ b/pypy/jit/backend/llgraph/llimpl.py @@ -1797,6 +1797,7 @@ if specialize_as_constant: def specialize_call(self, hop): llvalue = func(hop.args_s[0].const) + hop.exception_cannot_occur() return hop.inputconst(lltype.typeOf(llvalue), llvalue) else: # specialize as direct_call @@ -1813,6 +1814,7 @@ sm = ootype._static_meth(FUNCTYPE, _name=func.__name__, _callable=func) cfunc = hop.inputconst(FUNCTYPE, sm) args_v = hop.inputargs(*hop.args_r) + hop.exception_is_here() return hop.genop('direct_call', [cfunc] + args_v, hop.r_result) diff --git a/pypy/module/thread/test/test_ll_thread.py b/pypy/module/thread/test/test_ll_thread.py --- a/pypy/module/thread/test/test_ll_thread.py +++ b/pypy/module/thread/test/test_ll_thread.py @@ -66,7 +66,6 @@ def test_gc_locking(self): import time from pypy.rlib.objectmodel import invoke_around_extcall - from pypy.rlib.objectmodel import we_are_translated from pypy.rlib.debug import ll_assert class State: @@ -129,8 +128,6 @@ state.finished = 0 # the next line installs before_extcall() and after_extcall() # to be called automatically around external function calls. - # When not translated it does not work around time.sleep(), - # so we have to call them manually for this test. invoke_around_extcall(before_extcall, after_extcall) g(10, 1) @@ -142,13 +139,9 @@ willing_to_wait_more -= 1 done = len(state.answers) == expected - if not we_are_translated(): before_extcall() time.sleep(0.01) - if not we_are_translated(): after_extcall() - if not we_are_translated(): before_extcall() time.sleep(0.1) - if not we_are_translated(): after_extcall() return len(state.answers) @@ -160,12 +153,11 @@ answers = fn() assert answers == expected -class TestRunDirectly(AbstractThreadTests): - def getcompiled(self, f, argtypes): - return f - - def test_start_new_thread(self): - py.test.skip("deadlocks occasionally -- why???") +#class TestRunDirectly(AbstractThreadTests): +# def getcompiled(self, f, argtypes): +# return f +# These are disabled because they crash occasionally for bad reasons +# related to the fact that ll2ctypes is not at all thread-safe class TestUsingBoehm(AbstractThreadTests): gcpolicy = 'boehm' diff --git a/pypy/objspace/fake/objspace.py b/pypy/objspace/fake/objspace.py --- a/pypy/objspace/fake/objspace.py +++ b/pypy/objspace/fake/objspace.py @@ -86,6 +86,7 @@ return s_None def specialize_call(self, hop): + hop.exception_cannot_occur() return hop.inputconst(lltype.Void, None) # ____________________________________________________________ diff --git a/pypy/rlib/jit.py b/pypy/rlib/jit.py --- a/pypy/rlib/jit.py +++ b/pypy/rlib/jit.py @@ -382,7 +382,7 @@ pass def specialize_call(self, hop): - pass + hop.exception_cannot_occur() vref_None = non_virtual_ref(None) diff --git a/pypy/rlib/jit_hooks.py b/pypy/rlib/jit_hooks.py --- a/pypy/rlib/jit_hooks.py +++ b/pypy/rlib/jit_hooks.py @@ -22,6 +22,7 @@ c_name = hop.inputconst(lltype.Void, 'access_helper') args_v = [hop.inputarg(arg, arg=i) for i, arg in enumerate(hop.args_r)] + hop.exception_cannot_occur() return hop.genop('jit_marker', [c_name, c_func] + args_v, resulttype=hop.r_result) return helper diff --git a/pypy/rlib/objectmodel.py b/pypy/rlib/objectmodel.py --- a/pypy/rlib/objectmodel.py +++ b/pypy/rlib/objectmodel.py @@ -215,6 +215,7 @@ def specialize_call(self, hop): from pypy.rpython.lltypesystem import lltype + hop.exception_cannot_occur() return hop.inputconst(lltype.Bool, hop.s_result.const) # ____________________________________________________________ @@ -397,6 +398,7 @@ r_obj, = hop.args_r v_obj, = hop.inputargs(r_obj) ll_fn = r_obj.get_ll_hash_function() + hop.exception_is_here() return hop.gendirectcall(ll_fn, v_obj) class Entry(ExtRegistryEntry): @@ -419,6 +421,7 @@ from pypy.rpython.error import TyperError raise TyperError("compute_identity_hash() cannot be applied to" " %r" % (vobj.concretetype,)) + hop.exception_cannot_occur() return hop.genop('gc_identityhash', [vobj], resulttype=lltype.Signed) class Entry(ExtRegistryEntry): @@ -441,6 +444,7 @@ from pypy.rpython.error import TyperError raise TyperError("compute_unique_id() cannot be applied to" " %r" % (vobj.concretetype,)) + hop.exception_cannot_occur() return hop.genop('gc_id', [vobj], resulttype=lltype.Signed) class Entry(ExtRegistryEntry): @@ -452,6 +456,7 @@ def specialize_call(self, hop): vobj, = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() if hop.rtyper.type_system.name == 'lltypesystem': from pypy.rpython.lltypesystem import lltype if isinstance(vobj.concretetype, lltype.Ptr): diff --git a/pypy/rlib/rbigint.py b/pypy/rlib/rbigint.py --- a/pypy/rlib/rbigint.py +++ b/pypy/rlib/rbigint.py @@ -85,7 +85,7 @@ s_DIGIT = self.bookkeeper.valueoftype(type(NULLDIGIT)) assert s_DIGIT.contains(s_list.listdef.listitem.s_value) def specialize_call(self, hop): - pass + hop.exception_cannot_occur() class rbigint(object): diff --git a/pypy/rlib/rerased.py b/pypy/rlib/rerased.py --- a/pypy/rlib/rerased.py +++ b/pypy/rlib/rerased.py @@ -111,10 +111,10 @@ return identity.leave_tunnel(self.bookkeeper) def specialize_call(self, hop): + hop.exception_cannot_occur() if hop.r_result.lowleveltype is lltype.Void: return hop.inputconst(lltype.Void, None) [v] = hop.inputargs(hop.args_r[0]) - hop.exception_cannot_occur() return hop.args_r[0].rtype_unerase(hop, v) return erase, unerase @@ -216,6 +216,7 @@ return hop.genop('cast_opaque_ptr', [v], resulttype=hop.r_result) def rtype_unerase_int(self, hop, v): + hop.exception_cannot_occur() return hop.gendirectcall(ll_unerase_int, v) def rtype_erase_int(self, hop): @@ -266,6 +267,7 @@ def rtype_unerase_int(self, hop, v): c_one = hop.inputconst(lltype.Signed, 1) + hop.exception_cannot_occur() v2 = hop.genop('oounbox_int', [v], resulttype=hop.r_result) return hop.genop('int_rshift', [v2, c_one], resulttype=lltype.Signed) diff --git a/pypy/rlib/rgc.py b/pypy/rlib/rgc.py --- a/pypy/rlib/rgc.py +++ b/pypy/rlib/rgc.py @@ -382,6 +382,7 @@ def compute_result_annotation(self): return s_list_of_gcrefs() def specialize_call(self, hop): + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_roots', [], resulttype = hop.r_result) class Entry(ExtRegistryEntry): @@ -392,6 +393,7 @@ return s_list_of_gcrefs() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_referents', vlist, resulttype = hop.r_result) @@ -402,6 +404,7 @@ return annmodel.SomeInteger() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_memory_usage', vlist, resulttype = hop.r_result) @@ -412,6 +415,7 @@ return annmodel.SomeInteger() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_type_index', vlist, resulttype = hop.r_result) @@ -430,6 +434,7 @@ return annmodel.SomeBool() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_is_rpy_instance', vlist, resulttype = hop.r_result) @@ -449,6 +454,7 @@ classrepr = getclassrepr(hop.rtyper, classdef) vtable = classrepr.getvtable() assert lltype.typeOf(vtable) == rclass.CLASSTYPE + hop.exception_cannot_occur() return Constant(vtable, concretetype=rclass.CLASSTYPE) class Entry(ExtRegistryEntry): diff --git a/pypy/rlib/rstring.py b/pypy/rlib/rstring.py --- a/pypy/rlib/rstring.py +++ b/pypy/rlib/rstring.py @@ -245,5 +245,5 @@ raise ValueError("Value is not no_nul") def specialize_call(self, hop): - pass + hop.exception_cannot_occur() diff --git a/pypy/rpython/controllerentry.py b/pypy/rpython/controllerentry.py --- a/pypy/rpython/controllerentry.py +++ b/pypy/rpython/controllerentry.py @@ -201,6 +201,7 @@ def specialize_call(self, hop): from pypy.rpython.lltypesystem import lltype assert hop.s_result.is_constant() + hop.exception_cannot_occur() return hop.inputconst(lltype.Bool, hop.s_result.const) # ____________________________________________________________ diff --git a/pypy/rpython/lltypesystem/lloperation.py b/pypy/rpython/lltypesystem/lloperation.py --- a/pypy/rpython/lltypesystem/lloperation.py +++ b/pypy/rpython/lltypesystem/lloperation.py @@ -130,6 +130,7 @@ def specialize_call(self, hop): from pypy.rpython.lltypesystem import lltype + hop.exception_cannot_occur() return hop.inputconst(lltype.Void, None) def enum_ops_without_sideeffects(raising_is_ok=False): diff --git a/pypy/rpython/lltypesystem/rbuiltin.py b/pypy/rpython/lltypesystem/rbuiltin.py --- a/pypy/rpython/lltypesystem/rbuiltin.py +++ b/pypy/rpython/lltypesystem/rbuiltin.py @@ -9,6 +9,7 @@ from pypy.rpython.rbool import bool_repr def rtype_builtin_isinstance(hop): + hop.exception_cannot_occur() if hop.s_result.is_constant(): return hop.inputconst(lltype.Bool, hop.s_result.const) if hop.args_r[0] == pyobj_repr or hop.args_r[1] == pyobj_repr: @@ -33,6 +34,7 @@ return my_instantiate() def rtype_instantiate(hop): + hop.exception_cannot_occur() s_class = hop.args_s[0] assert isinstance(s_class, annmodel.SomePBC) if len(s_class.descriptions) != 1: @@ -46,6 +48,7 @@ return rclass.rtype_new_instance(hop.rtyper, classdef, hop.llops) def rtype_builtin_hasattr(hop): + hop.exception_cannot_occur() if hop.s_result.is_constant(): return hop.inputconst(lltype.Bool, hop.s_result.const) if hop.args_r[0] == pyobj_repr: @@ -56,6 +59,7 @@ raise TyperError("hasattr is only suported on a constant or on PyObject") def rtype_builtin___import__(hop): + xxx # should not be used any more args_v = hop.inputargs(*[pyobj_repr for ign in hop.args_r]) c = hop.inputconst(pyobj_repr, __import__) return hop.genop('simple_call', [c] + args_v, resulttype = pyobj_repr) diff --git a/pypy/rpython/lltypesystem/rclass.py b/pypy/rpython/lltypesystem/rclass.py --- a/pypy/rpython/lltypesystem/rclass.py +++ b/pypy/rpython/lltypesystem/rclass.py @@ -746,4 +746,5 @@ assert isinstance(TYPE, GcStruct) assert lltype._castdepth(TYPE, OBJECT) > 0 hop.rtyper.set_type_for_typeptr(vtable, TYPE) + hop.exception_cannot_occur() return hop.inputconst(lltype.Void, None) diff --git a/pypy/rpython/lltypesystem/rtuple.py b/pypy/rpython/lltypesystem/rtuple.py --- a/pypy/rpython/lltypesystem/rtuple.py +++ b/pypy/rpython/lltypesystem/rtuple.py @@ -55,6 +55,7 @@ vtup = hop.inputarg(self, 0) LIST = hop.r_result.lowleveltype.TO cno = inputconst(Signed, nitems) + hop.exception_is_here() vlist = hop.gendirectcall(LIST.ll_newlist, cno) v_func = hop.inputconst(Void, rlist.dum_nocheck) for index in range(nitems): diff --git a/pypy/rpython/module/r_os_stat.py b/pypy/rpython/module/r_os_stat.py --- a/pypy/rpython/module/r_os_stat.py +++ b/pypy/rpython/module/r_os_stat.py @@ -65,4 +65,5 @@ r_StatResult = hop.rtyper.getrepr(ll_os_stat.s_StatResult) [v_result] = hop.inputargs(r_StatResult.r_tuple) # no-op conversion from r_StatResult.r_tuple to r_StatResult + hop.exception_cannot_occur() return v_result diff --git a/pypy/rpython/ootypesystem/ooregistry.py b/pypy/rpython/ootypesystem/ooregistry.py --- a/pypy/rpython/ootypesystem/ooregistry.py +++ b/pypy/rpython/ootypesystem/ooregistry.py @@ -22,6 +22,7 @@ annmodel.SomeOOInstance, annmodel.SomeString)) vlist = hop.inputargs(hop.args_r[0], ootype.Signed) + hop.exception_cannot_occur() return hop.genop('oostring', vlist, resulttype = ootype.String) class Entry_oounicode(ExtRegistryEntry): @@ -38,6 +39,7 @@ assert isinstance(hop.args_s[0], (annmodel.SomeUnicodeCodePoint, annmodel.SomeOOInstance)) vlist = hop.inputargs(hop.args_r[0], ootype.Signed) + hop.exception_cannot_occur() return hop.genop('oounicode', vlist, resulttype = ootype.Unicode) diff --git a/pypy/rpython/ootypesystem/rbuiltin.py b/pypy/rpython/ootypesystem/rbuiltin.py --- a/pypy/rpython/ootypesystem/rbuiltin.py +++ b/pypy/rpython/ootypesystem/rbuiltin.py @@ -7,12 +7,14 @@ from pypy.rpython.error import TyperError def rtype_new(hop): + hop.exception_cannot_occur() assert hop.args_s[0].is_constant() vlist = hop.inputargs(ootype.Void) return hop.genop('new', vlist, resulttype = hop.r_result.lowleveltype) def rtype_oonewarray(hop): + hop.exception_cannot_occur() assert hop.args_s[0].is_constant() vlist = hop.inputarg(ootype.Void, arg=0) vlength = hop.inputarg(ootype.Signed, arg=1) @@ -20,23 +22,27 @@ resulttype = hop.r_result.lowleveltype) def rtype_null(hop): + hop.exception_cannot_occur() assert hop.args_s[0].is_constant() TYPE = hop.args_s[0].const nullvalue = ootype.null(TYPE) return hop.inputconst(TYPE, nullvalue) def rtype_classof(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0], annmodel.SomeOOInstance) vlist = hop.inputargs(hop.args_r[0]) return hop.genop('classof', vlist, resulttype = ootype.Class) def rtype_subclassof(hop): + hop.exception_cannot_occur() vlist = hop.inputargs(rootype.ooclass_repr, rootype.ooclass_repr) return hop.genop('subclassof', vlist, resulttype = ootype.Bool) def rtype_instanceof(hop): + hop.exception_cannot_occur() INSTANCE = hop.args_v[1].value v_inst = hop.inputarg(hop.args_r[0], arg=0) c_cls = hop.inputconst(ootype.Void, INSTANCE) @@ -44,23 +50,27 @@ resulttype=ootype.Bool) def rtype_runtimenew(hop): + hop.exception_cannot_occur() vlist = hop.inputargs(rootype.ooclass_repr) return hop.genop('runtimenew', vlist, resulttype = hop.r_result.lowleveltype) def rtype_ooupcast(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0].const, ootype.Instance) assert isinstance(hop.args_s[1], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[1], arg=1) return hop.genop('ooupcast', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_oodowncast(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0].const, ootype.Instance) assert isinstance(hop.args_s[1], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[1], arg=1) return hop.genop('oodowncast', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_cast_to_object(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0], annmodel.SomeOOStaticMeth) or \ isinstance(hop.args_s[0], annmodel.SomeOOClass) or \ isinstance(hop.args_s[0].ootype, ootype.OOType) @@ -68,12 +78,14 @@ return hop.genop('cast_to_object', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_cast_from_object(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0].const, ootype.OOType) assert isinstance(hop.args_s[1], annmodel.SomeOOObject) v_inst = hop.inputarg(hop.args_r[1], arg=1) return hop.genop('cast_from_object', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_builtin_isinstance(hop): + hop.exception_cannot_occur() if hop.s_result.is_constant(): return hop.inputconst(ootype.Bool, hop.s_result.const) @@ -99,6 +111,7 @@ return ootype.subclassof(c1, class_) def rtype_instantiate(hop): + hop.exception_cannot_occur() if hop.args_s[0].is_constant(): ## INSTANCE = hop.s_result.rtyper_makerepr(hop.rtyper).lowleveltype ## v_instance = hop.inputconst(ootype.Void, INSTANCE) diff --git a/pypy/rpython/ootypesystem/rtuple.py b/pypy/rpython/ootypesystem/rtuple.py --- a/pypy/rpython/ootypesystem/rtuple.py +++ b/pypy/rpython/ootypesystem/rtuple.py @@ -39,6 +39,7 @@ RESULT = hop.r_result.lowleveltype c_resulttype = inputconst(ootype.Void, RESULT) c_length = inputconst(ootype.Signed, len(self.items_r)) + hop.exception_is_here() if isinstance(RESULT, ootype.Array): v_list = hop.genop('oonewarray', [c_resulttype, c_length], resulttype=RESULT) else: diff --git a/pypy/rpython/rbool.py b/pypy/rpython/rbool.py --- a/pypy/rpython/rbool.py +++ b/pypy/rpython/rbool.py @@ -34,6 +34,7 @@ def rtype_float(_, hop): vlist = hop.inputargs(Float) + hop.exception_cannot_occur() return vlist[0] # diff --git a/pypy/rpython/rbuiltin.py b/pypy/rpython/rbuiltin.py --- a/pypy/rpython/rbuiltin.py +++ b/pypy/rpython/rbuiltin.py @@ -111,25 +111,32 @@ raise TyperError("don't know about built-in function %r" % ( self.builtinfunc,)) + def _call(self, hop2, **kwds_i): + bltintyper = self.findbltintyper(hop2.rtyper) + hop2.llops._called_exception_is_here_or_cannot_occur = False + v_result = bltintyper(hop2, **kwds_i) + if not hop2.llops._called_exception_is_here_or_cannot_occur: + raise TyperError("missing hop.exception_cannot_occur() or " + "hop.exception_is_here() in %s" % bltintyper) + return v_result + def rtype_simple_call(self, hop): - bltintyper = self.findbltintyper(hop.rtyper) hop2 = hop.copy() hop2.r_s_popfirstarg() - return bltintyper(hop2) + return self._call(hop2) def rtype_call_args(self, hop): # calling a built-in function with keyword arguments: # mostly for rpython.objectmodel.hint() hop, kwds_i = call_args_expand(hop) - bltintyper = self.findbltintyper(hop.rtyper) hop2 = hop.copy() hop2.r_s_popfirstarg() hop2.r_s_popfirstarg() # the RPython-level keyword args are passed with an 'i_' prefix and # the corresponding value is an *index* in the hop2 arguments, # to be used with hop.inputarg(arg=..) - return bltintyper(hop2, **kwds_i) + return self._call(hop2, **kwds_i) class BuiltinMethodRepr(Repr): @@ -198,6 +205,7 @@ # ____________________________________________________________ def rtype_builtin_bool(hop): + # not called any more? assert hop.nb_args == 1 return hop.args_r[0].rtype_is_true(hop) @@ -241,6 +249,7 @@ def rtype_builtin_min(hop): v1, v2 = hop.inputargs(hop.r_result, hop.r_result) + hop.exception_cannot_occur() return hop.gendirectcall(ll_min, v1, v2) def ll_min(i1, i2): @@ -250,6 +259,7 @@ def rtype_builtin_max(hop): v1, v2 = hop.inputargs(hop.r_result, hop.r_result) + hop.exception_cannot_occur() return hop.gendirectcall(ll_max, v1, v2) def ll_max(i1, i2): @@ -264,6 +274,7 @@ pass def rtype_OSError__init__(hop): + hop.exception_cannot_occur() if hop.nb_args == 2: raise TyperError("OSError() should not be called with " "a single argument") @@ -274,6 +285,7 @@ r_self.setfield(v_self, 'errno', v_errno, hop.llops) def rtype_WindowsError__init__(hop): + hop.exception_cannot_occur() if hop.nb_args == 2: raise TyperError("WindowsError() should not be called with " "a single argument") @@ -442,6 +454,7 @@ assert hop.args_s[0].is_constant() TGT = hop.args_s[0].const v_type, v_value = hop.inputargs(lltype.Void, hop.args_r[1]) + hop.exception_cannot_occur() return gen_cast(hop.llops, TGT, v_value) _cast_to_Signed = { @@ -523,11 +536,13 @@ def rtype_identity_hash(hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_identityhash', vlist, resulttype=lltype.Signed) def rtype_runtime_type_info(hop): assert isinstance(hop.args_r[0], rptr.PtrRepr) vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('runtime_type_info', vlist, resulttype = hop.r_result.lowleveltype) @@ -558,6 +573,7 @@ def rtype_raw_malloc(hop): v_size, = hop.inputargs(lltype.Signed) + hop.exception_cannot_occur() return hop.genop('raw_malloc', [v_size], resulttype=llmemory.Address) def rtype_raw_malloc_usage(hop): @@ -586,6 +602,7 @@ if s_addr.is_null_address(): raise TyperError("raw_memclear(x, n) where x is the constant NULL") v_list = hop.inputargs(llmemory.Address, lltype.Signed) + hop.exception_cannot_occur() return hop.genop('raw_memclear', v_list) BUILTIN_TYPER[llmemory.raw_malloc] = rtype_raw_malloc @@ -596,6 +613,7 @@ def rtype_offsetof(hop): TYPE, field = hop.inputargs(lltype.Void, lltype.Void) + hop.exception_cannot_occur() return hop.inputconst(lltype.Signed, llmemory.offsetof(TYPE.value, field.value)) @@ -605,6 +623,7 @@ # non-gc objects def rtype_free_non_gc_object(hop): + hop.exception_cannot_occur() vinst, = hop.inputargs(hop.args_r[0]) flavor = hop.args_r[0].gcflavor assert flavor != 'gc' @@ -617,6 +636,7 @@ # keepalive_until_here def rtype_keepalive_until_here(hop): + hop.exception_cannot_occur() for v in hop.args_v: hop.genop('keepalive', [v], resulttype=lltype.Void) return hop.inputconst(lltype.Void, None) diff --git a/pypy/rpython/rfloat.py b/pypy/rpython/rfloat.py --- a/pypy/rpython/rfloat.py +++ b/pypy/rpython/rfloat.py @@ -136,7 +136,10 @@ hop.exception_cannot_occur() return hop.genop('cast_float_to_int', vlist, resulttype=Signed) - rtype_float = rtype_pos + def rtype_float(_, hop): + vlist = hop.inputargs(Float) + hop.exception_cannot_occur() + return vlist[0] # version picked by specialisation based on which # type system rtyping is using, from .ll_str module diff --git a/pypy/rpython/rint.py b/pypy/rpython/rint.py --- a/pypy/rpython/rint.py +++ b/pypy/rpython/rint.py @@ -310,6 +310,8 @@ if hop.has_implicit_exception(ValueError): hop.exception_is_here() hop.gendirectcall(ll_check_chr, vlist[0]) + else: + hop.exception_cannot_occur() return hop.genop('cast_int_to_char', vlist, resulttype=Char) def rtype_unichr(_, hop): @@ -317,6 +319,8 @@ if hop.has_implicit_exception(ValueError): hop.exception_is_here() hop.gendirectcall(ll_check_unichr, vlist[0]) + else: + hop.exception_cannot_occur() return hop.genop('cast_int_to_unichar', vlist, resulttype=UniChar) def rtype_is_true(self, hop): diff --git a/pypy/rpython/rlist.py b/pypy/rpython/rlist.py --- a/pypy/rpython/rlist.py +++ b/pypy/rpython/rlist.py @@ -115,6 +115,7 @@ def rtype_bltn_list(self, hop): v_lst = hop.inputarg(self, 0) cRESLIST = hop.inputconst(Void, hop.r_result.LIST) + hop.exception_is_here() return hop.gendirectcall(ll_copy, cRESLIST, v_lst) def rtype_len(self, hop): diff --git a/pypy/rpython/rrange.py b/pypy/rpython/rrange.py --- a/pypy/rpython/rrange.py +++ b/pypy/rpython/rrange.py @@ -107,8 +107,10 @@ if isinstance(hop.r_result, AbstractRangeRepr): if hop.r_result.step != 0: c_rng = hop.inputconst(Void, hop.r_result.RANGE) + hop.exception_is_here() return hop.gendirectcall(hop.r_result.ll_newrange, c_rng, vstart, vstop) else: + hop.exception_is_here() return hop.gendirectcall(hop.r_result.ll_newrangest, vstart, vstop, vstep) else: # cannot build a RANGE object, needs a real list @@ -117,6 +119,7 @@ if isinstance(ITEMTYPE, Ptr): ITEMTYPE = ITEMTYPE.TO cLIST = hop.inputconst(Void, ITEMTYPE) + hop.exception_is_here() return hop.gendirectcall(ll_range2list, cLIST, vstart, vstop, vstep) rtype_builtin_xrange = rtype_builtin_range @@ -212,4 +215,5 @@ [v_index, v_item]) def rtype_builtin_enumerate(hop): + hop.exception_cannot_occur() return hop.r_result.r_baseiter.newiter(hop) diff --git a/pypy/rpython/rstr.py b/pypy/rpython/rstr.py --- a/pypy/rpython/rstr.py +++ b/pypy/rpython/rstr.py @@ -288,6 +288,8 @@ def rtype_unicode(self, hop): if hop.args_s[0].is_constant(): + # convertion errors occur during annotation, so cannot any more: + hop.exception_cannot_occur() return hop.inputconst(hop.r_result, hop.s_result.const) repr = hop.args_r[0].repr v_str = hop.inputarg(repr, 0) diff --git a/pypy/rpython/rtyper.py b/pypy/rpython/rtyper.py --- a/pypy/rpython/rtyper.py +++ b/pypy/rpython/rtyper.py @@ -846,6 +846,7 @@ return result def exception_is_here(self): + self.llops._called_exception_is_here_or_cannot_occur = True if self.llops.llop_raising_exceptions is not None: raise TyperError("cannot catch an exception at more than one llop") if not self.exceptionlinks: @@ -861,6 +862,7 @@ self.llops.llop_raising_exceptions = len(self.llops) def exception_cannot_occur(self): + self.llops._called_exception_is_here_or_cannot_occur = True if self.llops.llop_raising_exceptions is not None: raise TyperError("cannot catch an exception at more than one llop") if not self.exceptionlinks: diff --git a/pypy/rpython/test/test_extregistry.py b/pypy/rpython/test/test_extregistry.py --- a/pypy/rpython/test/test_extregistry.py +++ b/pypy/rpython/test/test_extregistry.py @@ -114,6 +114,7 @@ _about_ = dummy_func s_result_annotation = annmodel.SomeInteger() def specialize_call(self, hop): + hop.exception_cannot_occur() return hop.inputconst(lltype.Signed, 42) def func(): diff --git a/pypy/rpython/test/test_rclass.py b/pypy/rpython/test/test_rclass.py --- a/pypy/rpython/test/test_rclass.py +++ b/pypy/rpython/test/test_rclass.py @@ -1085,6 +1085,7 @@ return annmodel.SomeInteger() def specialize_call(self, hop): [v_instance] = hop.inputargs(*hop.args_r) + hop.exception_is_here() return hop.gendirectcall(ll_my_gethash, v_instance) def f(n): diff --git a/pypy/translator/cli/dotnet.py b/pypy/translator/cli/dotnet.py --- a/pypy/translator/cli/dotnet.py +++ b/pypy/translator/cli/dotnet.py @@ -459,6 +459,7 @@ def specialize_call(self, hop): + hop.exception_cannot_occur() assert hop.args_s[1].is_constant() TYPE = hop.args_s[1].const v_obj = hop.inputarg(hop.args_r[0], arg=0) @@ -507,6 +508,7 @@ def specialize_call(self, hop): v_obj, = hop.inputargs(*hop.args_r) + hop.exception_cannot_occur() return hop.genop('same_as', [v_obj], hop.r_result.lowleveltype) def new_array(type, length): @@ -608,6 +610,7 @@ def specialize_call(self, hop): v_type, = hop.inputargs(*hop.args_r) + hop.exception_cannot_occur() return hop.genop('cli_typeof', [v_type], hop.r_result.lowleveltype) @@ -626,6 +629,7 @@ v_obj, = hop.inputargs(*hop.args_r) methodname = hop.args_r[0].methodname c_methodname = hop.inputconst(ootype.Void, methodname) + hop.exception_cannot_occur() return hop.genop('cli_eventhandler', [v_obj, c_methodname], hop.r_result.lowleveltype) @@ -647,6 +651,7 @@ def specialize_call(self, hop): assert isinstance(hop.args_s[0], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[0], arg=0) + hop.exception_cannot_occur() return hop.genop('oodowncast', [v_inst], resulttype = hop.r_result.lowleveltype) @@ -668,6 +673,7 @@ def specialize_call(self, hop): assert isinstance(hop.args_s[0], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[0], arg=0) + hop.exception_cannot_occur() return hop.genop('ooupcast', [v_inst], resulttype = hop.r_result.lowleveltype) @@ -701,6 +707,7 @@ def specialize_call(self, hop): v_obj = hop.inputarg(hop.args_r[0], arg=0) + hop.exception_cannot_occur() return hop.genop('oodowncast', [v_obj], hop.r_result.lowleveltype) From noreply at buildbot.pypy.org Mon Apr 2 10:27:24 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 10:27:24 +0200 (CEST) Subject: [pypy-commit] pypy default: Export tp_doc. Message-ID: <20120402082724.4DAC846E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: Changeset: r54142:171b149844d2 Date: 2012-04-02 10:26 +0200 http://bitbucket.org/pypy/pypy/changeset/171b149844d2/ Log: Export tp_doc. diff --git a/pypy/module/cpyext/test/foo.c b/pypy/module/cpyext/test/foo.c --- a/pypy/module/cpyext/test/foo.c +++ b/pypy/module/cpyext/test/foo.c @@ -176,6 +176,8 @@ {NULL} /* Sentinel */ }; +PyDoc_STRVAR(foo_doc, "foo is for testing."); + static PyTypeObject footype = { PyVarObject_HEAD_INIT(NULL, 0) "foo.foo", /*tp_name*/ @@ -198,7 +200,7 @@ (setattrofunc)foo_setattro, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ + foo_doc, /*tp_doc*/ 0, /*tp_traverse*/ 0, /*tp_clear*/ 0, /*tp_richcompare*/ diff --git a/pypy/module/cpyext/test/test_typeobject.py b/pypy/module/cpyext/test/test_typeobject.py --- a/pypy/module/cpyext/test/test_typeobject.py +++ b/pypy/module/cpyext/test/test_typeobject.py @@ -20,6 +20,7 @@ assert type(obj) is module.fooType print "type of obj has type", type(type(obj)) print "type of type of obj has type", type(type(type(obj))) + assert module.fooType.__doc__ == "foo is for testing." def test_typeobject_method_descriptor(self): module = self.import_module(name='foo') diff --git a/pypy/module/cpyext/typeobject.py b/pypy/module/cpyext/typeobject.py --- a/pypy/module/cpyext/typeobject.py +++ b/pypy/module/cpyext/typeobject.py @@ -307,6 +307,8 @@ if not space.is_true(space.issubtype(self, space.w_type)): self.flag_cpytype = True self.flag_heaptype = False + if pto.c_tp_doc: + self.w_doc = space.wrap(rffi.charp2str(pto.c_tp_doc)) @bootstrap_function def init_typeobject(space): @@ -624,7 +626,6 @@ Creates an interpreter type from a PyTypeObject structure. """ # missing: - # setting __doc__ if not defined and tp_doc defined # inheriting tp_as_* slots # unsupported: # tp_mro, tp_subclasses From noreply at buildbot.pypy.org Mon Apr 2 10:34:10 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 10:34:10 +0200 (CEST) Subject: [pypy-commit] pypy default: Fix for issue1111: wrap_objobjargproc() returns None in CPython. Message-ID: <20120402083410.C650946E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: Changeset: r54143:7da597d3c86d Date: 2012-04-02 10:33 +0200 http://bitbucket.org/pypy/pypy/changeset/7da597d3c86d/ Log: Fix for issue1111: wrap_objobjargproc() returns None in CPython. diff --git a/pypy/module/cpyext/slotdefs.py b/pypy/module/cpyext/slotdefs.py --- a/pypy/module/cpyext/slotdefs.py +++ b/pypy/module/cpyext/slotdefs.py @@ -183,7 +183,7 @@ res = generic_cpy_call(space, func_target, w_self, w_key, w_value) if rffi.cast(lltype.Signed, res) == -1: space.fromcache(State).check_and_raise_exception(always=True) - return space.wrap(res) + return space.w_None def wrap_delitem(space, w_self, w_args, func): func_target = rffi.cast(objobjargproc, func) diff --git a/pypy/module/cpyext/test/test_typeobject.py b/pypy/module/cpyext/test/test_typeobject.py --- a/pypy/module/cpyext/test/test_typeobject.py +++ b/pypy/module/cpyext/test/test_typeobject.py @@ -415,8 +415,11 @@ static int mp_ass_subscript(PyObject *self, PyObject *key, PyObject *value) { - PyErr_SetNone(PyExc_ZeroDivisionError); - return -1; + if (PyInt_Check(key)) { + PyErr_SetNone(PyExc_ZeroDivisionError); + return -1; + } + return 0; } PyMappingMethods tp_as_mapping; static PyTypeObject Foo_Type = { @@ -426,6 +429,8 @@ ''') obj = module.new_obj() raises(ZeroDivisionError, obj.__setitem__, 5, None) + res = obj.__setitem__('foo', None) + assert res is None def test_tp_iter(self): module = self.import_extension('foo', [ From noreply at buildbot.pypy.org Mon Apr 2 10:51:38 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 10:51:38 +0200 (CEST) Subject: [pypy-commit] pypy default: Test and fix for issue1107: "x in y" must return a bool. Message-ID: <20120402085138.CDB0A46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: Changeset: r54144:849cc68d148d Date: 2012-04-02 10:47 +0200 http://bitbucket.org/pypy/pypy/changeset/849cc68d148d/ Log: Test and fix for issue1107: "x in y" must return a bool. diff --git a/pypy/module/cpyext/slotdefs.py b/pypy/module/cpyext/slotdefs.py --- a/pypy/module/cpyext/slotdefs.py +++ b/pypy/module/cpyext/slotdefs.py @@ -167,6 +167,7 @@ if rffi.cast(lltype.Signed, res) == -1: space.fromcache(State).check_and_raise_exception(always=True) +# Warning, confusing function name (like CPython). Used only for sq_contains. def wrap_objobjproc(space, w_self, w_args, func): func_target = rffi.cast(objobjproc, func) check_num_args(space, w_args, 1) @@ -174,7 +175,7 @@ res = generic_cpy_call(space, func_target, w_self, w_value) if rffi.cast(lltype.Signed, res) == -1: space.fromcache(State).check_and_raise_exception(always=True) - return space.wrap(res) + return space.wrap(bool(res)) def wrap_objobjargproc(space, w_self, w_args, func): func_target = rffi.cast(objobjargproc, func) diff --git a/pypy/module/cpyext/test/test_typeobject.py b/pypy/module/cpyext/test/test_typeobject.py --- a/pypy/module/cpyext/test/test_typeobject.py +++ b/pypy/module/cpyext/test/test_typeobject.py @@ -432,6 +432,34 @@ res = obj.__setitem__('foo', None) assert res is None + def test_sq_contains(self): + module = self.import_extension('foo', [ + ("new_obj", "METH_NOARGS", + ''' + PyObject *obj; + Foo_Type.tp_as_sequence = &tp_as_sequence; + tp_as_sequence.sq_contains = sq_contains; + if (PyType_Ready(&Foo_Type) < 0) return NULL; + obj = PyObject_New(PyObject, &Foo_Type); + return obj; + ''' + )], + ''' + static int + sq_contains(PyObject *self, PyObject *value) + { + return 42; + } + PySequenceMethods tp_as_sequence; + static PyTypeObject Foo_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "foo.foo", + }; + ''') + obj = module.new_obj() + res = "foo" in obj + assert res is True + def test_tp_iter(self): module = self.import_extension('foo', [ ("tp_iter", "METH_O", From noreply at buildbot.pypy.org Mon Apr 2 11:21:38 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 11:21:38 +0200 (CEST) Subject: [pypy-commit] pypy default: Re-add an optimization that was incorrectly implemented and Message-ID: <20120402092138.8D12646E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: Changeset: r54145:4767e4614bfa Date: 2012-04-02 11:20 +0200 http://bitbucket.org/pypy/pypy/changeset/4767e4614bfa/ Log: Re-add an optimization that was incorrectly implemented and so killed by the merge of list strategies. diff --git a/pypy/objspace/descroperation.py b/pypy/objspace/descroperation.py --- a/pypy/objspace/descroperation.py +++ b/pypy/objspace/descroperation.py @@ -43,6 +43,13 @@ return w_eq type_eq._annspecialcase_ = 'specialize:memo' +def list_iter(space): + "Utility that returns the app-level descriptor list.__iter__." + w_src, w_iter = space.lookup_in_type_where(space.w_list, + '__iter__') + return w_iter +list_iter._annspecialcase_ = 'specialize:memo' + def raiseattrerror(space, w_obj, name, w_descr=None): w_type = space.type(w_obj) typename = w_type.getname(space) diff --git a/pypy/objspace/std/objspace.py b/pypy/objspace/std/objspace.py --- a/pypy/objspace/std/objspace.py +++ b/pypy/objspace/std/objspace.py @@ -439,6 +439,8 @@ t = w_obj.getitems() elif isinstance(w_obj, W_AbstractTupleObject): t = w_obj.getitems_copy() + elif isinstance(w_obj, W_ListObject) and self._uses_list_iter(w_obj): + t = w_obj.getitems() else: return ObjSpace.unpackiterable(self, w_obj, expected_length) if expected_length != -1 and len(t) != expected_length: @@ -456,6 +458,8 @@ return w_obj.listview_str() if isinstance(w_obj, W_StringObject): return w_obj.listview_str() + if isinstance(w_obj, W_ListObject) and self._uses_list_iter(w_obj): + return w_obj.getitems_str() return None def listview_int(self, w_obj): @@ -465,8 +469,14 @@ return w_obj.listview_int() if type(w_obj) is W_SetObject or type(w_obj) is W_FrozensetObject: return w_obj.listview_int() + if isinstance(w_obj, W_ListObject) and self._uses_list_iter(w_obj): + return w_obj.getitems_int() return None + def _uses_list_iter(self, w_obj): + from pypy.objspace.descroperation import list_iter + return self.lookup(w_obj, '__iter__') is list_iter(self) + def sliceindices(self, w_slice, w_length): if isinstance(w_slice, W_SliceObject): a, b, c = w_slice.indices3(self, self.int_w(w_length)) diff --git a/pypy/objspace/std/test/test_listobject.py b/pypy/objspace/std/test/test_listobject.py --- a/pypy/objspace/std/test/test_listobject.py +++ b/pypy/objspace/std/test/test_listobject.py @@ -1186,14 +1186,23 @@ # of dicts, because the OrderedDict in the stdlib relies on this. # we extend the use case to lists and sets, i.e. all types that have # strategies, to avoid surprizes depending on the strategy. - for base, arg in [(list, []), (list, [5]), (list, ['x']), - (set, []), (set, [5]), (set, ['x']), - (dict, []), (dict, [(5,6)]), (dict, [('x',7)])]: + class X: pass + for base, arg in [ + (list, []), (list, [5]), (list, ['x']), (list, [X]), + (set, []), (set, [5]), (set, ['x']), (set, [X]), + (dict, []), (dict, [(5,6)]), (dict, [('x',7)]), (dict, [(X,8)]), + ]: print base, arg class SubClass(base): def __iter__(self): return iter("foobar") assert list(SubClass(arg)) == ['f', 'o', 'o', 'b', 'a', 'r'] + class Sub2(base): + pass + assert list(Sub2(arg)) == list(base(arg)) + s = set() + s.update(Sub2(arg)) + assert s == set(base(arg)) class AppTestForRangeLists(AppTestW_ListObject): From noreply at buildbot.pypy.org Mon Apr 2 12:11:13 2012 From: noreply at buildbot.pypy.org (fijal) Date: Mon, 2 Apr 2012 12:11:13 +0200 (CEST) Subject: [pypy-commit] pypy default: implement repeat. a tad inefficient, but I could not care less Message-ID: <20120402101113.E9E1C46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Maciej Fijalkowski Branch: Changeset: r54146:5da12de5d696 Date: 2012-04-02 10:41 +0200 http://bitbucket.org/pypy/pypy/changeset/5da12de5d696/ Log: implement repeat. a tad inefficient, but I could not care less diff --git a/pypy/module/micronumpy/__init__.py b/pypy/module/micronumpy/__init__.py --- a/pypy/module/micronumpy/__init__.py +++ b/pypy/module/micronumpy/__init__.py @@ -29,6 +29,7 @@ 'flatiter': 'interp_numarray.W_FlatIterator', 'isna': 'interp_numarray.isna', 'concatenate': 'interp_numarray.concatenate', + 'repeat': 'interp_numarray.repeat', 'set_string_function': 'appbridge.set_string_function', diff --git a/pypy/module/micronumpy/interp_numarray.py b/pypy/module/micronumpy/interp_numarray.py --- a/pypy/module/micronumpy/interp_numarray.py +++ b/pypy/module/micronumpy/interp_numarray.py @@ -1261,6 +1261,31 @@ return convert_to_array(space, w_obj2).descr_dot(space, w_arr) return w_arr.descr_dot(space, w_obj2) + at unwrap_spec(repeats=int) +def repeat(space, w_arr, repeats, w_axis=None): + arr = convert_to_array(space, w_arr) + if space.is_w(w_axis, space.w_None): + arr = arr.descr_flatten(space).get_concrete() + orig_size = arr.shape[0] + shape = [arr.shape[0] * repeats] + res = W_NDimArray(shape, arr.find_dtype()) + for i in range(repeats): + Chunks([Chunk(i, shape[0] - repeats + i, repeats, + orig_size)]).apply(res).setslice(space, arr) + else: + arr = arr.get_concrete() + axis = space.int_w(w_axis) + shape = arr.shape[:] + chunks = [Chunk(0, i, 1, i) for i in shape] + orig_size = shape[axis] + shape[axis] *= repeats + res = W_NDimArray(shape, arr.find_dtype()) + for i in range(repeats): + chunks[axis] = Chunk(i, shape[axis] - repeats + i, repeats, + orig_size) + Chunks(chunks).apply(res).setslice(space, arr) + return res + @unwrap_spec(axis=int) def concatenate(space, w_args, axis=0): args_w = space.listview(w_args) diff --git a/pypy/module/micronumpy/test/test_numarray.py b/pypy/module/micronumpy/test/test_numarray.py --- a/pypy/module/micronumpy/test/test_numarray.py +++ b/pypy/module/micronumpy/test/test_numarray.py @@ -1399,6 +1399,15 @@ assert (ones(1) + ones(1)).nbytes == 8 assert array(3.0).nbytes == 8 + def test_repeat(self): + from _numpypy import repeat + assert (repeat([[1, 2], [3, 4]], 3) == [1, 1, 1, 2, 2, 2, + 3, 3, 3, 4, 4, 4]).all() + assert (repeat([[1, 2], [3, 4]], 2, axis=0) == [[1, 2], [1, 2], [3, 4], + [3, 4]]).all() + assert (repeat([[1, 2], [3, 4]], 2, axis=1) == [[1, 1, 2, 2], [3, 3, + 4, 4]]).all() + class AppTestMultiDim(BaseNumpyAppTest): def test_init(self): From noreply at buildbot.pypy.org Mon Apr 2 12:11:15 2012 From: noreply at buildbot.pypy.org (fijal) Date: Mon, 2 Apr 2012 12:11:15 +0200 (CEST) Subject: [pypy-commit] pypy default: repeat as a method Message-ID: <20120402101115.8374846E0EF@wyvern.cs.uni-duesseldorf.de> Author: Maciej Fijalkowski Branch: Changeset: r54147:6e49d0a11647 Date: 2012-04-02 11:52 +0200 http://bitbucket.org/pypy/pypy/changeset/6e49d0a11647/ Log: repeat as a method diff --git a/pypy/module/micronumpy/interp_numarray.py b/pypy/module/micronumpy/interp_numarray.py --- a/pypy/module/micronumpy/interp_numarray.py +++ b/pypy/module/micronumpy/interp_numarray.py @@ -673,6 +673,10 @@ def compute_first_step(self, sig, frame): pass + @unwrap_spec(repeats=int) + def descr_repeat(self, space, repeats, w_axis=None): + return repeat(space, self, repeats, w_axis) + def convert_to_array(space, w_obj): if isinstance(w_obj, BaseArray): return w_obj @@ -1411,6 +1415,7 @@ tolist = interp2app(BaseArray.descr_tolist), take = interp2app(BaseArray.descr_take), compress = interp2app(BaseArray.descr_compress), + repeat = interp2app(BaseArray.descr_repeat), ) diff --git a/pypy/module/micronumpy/test/test_numarray.py b/pypy/module/micronumpy/test/test_numarray.py --- a/pypy/module/micronumpy/test/test_numarray.py +++ b/pypy/module/micronumpy/test/test_numarray.py @@ -1400,13 +1400,14 @@ assert array(3.0).nbytes == 8 def test_repeat(self): - from _numpypy import repeat + from _numpypy import repeat, array assert (repeat([[1, 2], [3, 4]], 3) == [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]).all() assert (repeat([[1, 2], [3, 4]], 2, axis=0) == [[1, 2], [1, 2], [3, 4], [3, 4]]).all() assert (repeat([[1, 2], [3, 4]], 2, axis=1) == [[1, 1, 2, 2], [3, 3, 4, 4]]).all() + assert (array([1, 2]).repeat(2) == array([1, 1, 2, 2])).all() class AppTestMultiDim(BaseNumpyAppTest): From noreply at buildbot.pypy.org Mon Apr 2 12:11:16 2012 From: noreply at buildbot.pypy.org (fijal) Date: Mon, 2 Apr 2012 12:11:16 +0200 (CEST) Subject: [pypy-commit] pypy default: merge Message-ID: <20120402101116.BE47546E0EF@wyvern.cs.uni-duesseldorf.de> Author: Maciej Fijalkowski Branch: Changeset: r54148:5708f4c44e53 Date: 2012-04-02 12:10 +0200 http://bitbucket.org/pypy/pypy/changeset/5708f4c44e53/ Log: merge diff --git a/pypy/module/micronumpy/__init__.py b/pypy/module/micronumpy/__init__.py --- a/pypy/module/micronumpy/__init__.py +++ b/pypy/module/micronumpy/__init__.py @@ -29,6 +29,7 @@ 'flatiter': 'interp_numarray.W_FlatIterator', 'isna': 'interp_numarray.isna', 'concatenate': 'interp_numarray.concatenate', + 'repeat': 'interp_numarray.repeat', 'set_string_function': 'appbridge.set_string_function', diff --git a/pypy/module/micronumpy/interp_numarray.py b/pypy/module/micronumpy/interp_numarray.py --- a/pypy/module/micronumpy/interp_numarray.py +++ b/pypy/module/micronumpy/interp_numarray.py @@ -673,6 +673,10 @@ def compute_first_step(self, sig, frame): pass + @unwrap_spec(repeats=int) + def descr_repeat(self, space, repeats, w_axis=None): + return repeat(space, self, repeats, w_axis) + def convert_to_array(space, w_obj): if isinstance(w_obj, BaseArray): return w_obj @@ -1261,6 +1265,31 @@ return convert_to_array(space, w_obj2).descr_dot(space, w_arr) return w_arr.descr_dot(space, w_obj2) + at unwrap_spec(repeats=int) +def repeat(space, w_arr, repeats, w_axis=None): + arr = convert_to_array(space, w_arr) + if space.is_w(w_axis, space.w_None): + arr = arr.descr_flatten(space).get_concrete() + orig_size = arr.shape[0] + shape = [arr.shape[0] * repeats] + res = W_NDimArray(shape, arr.find_dtype()) + for i in range(repeats): + Chunks([Chunk(i, shape[0] - repeats + i, repeats, + orig_size)]).apply(res).setslice(space, arr) + else: + arr = arr.get_concrete() + axis = space.int_w(w_axis) + shape = arr.shape[:] + chunks = [Chunk(0, i, 1, i) for i in shape] + orig_size = shape[axis] + shape[axis] *= repeats + res = W_NDimArray(shape, arr.find_dtype()) + for i in range(repeats): + chunks[axis] = Chunk(i, shape[axis] - repeats + i, repeats, + orig_size) + Chunks(chunks).apply(res).setslice(space, arr) + return res + @unwrap_spec(axis=int) def concatenate(space, w_args, axis=0): args_w = space.listview(w_args) @@ -1386,6 +1415,7 @@ tolist = interp2app(BaseArray.descr_tolist), take = interp2app(BaseArray.descr_take), compress = interp2app(BaseArray.descr_compress), + repeat = interp2app(BaseArray.descr_repeat), ) diff --git a/pypy/module/micronumpy/test/test_numarray.py b/pypy/module/micronumpy/test/test_numarray.py --- a/pypy/module/micronumpy/test/test_numarray.py +++ b/pypy/module/micronumpy/test/test_numarray.py @@ -1399,6 +1399,16 @@ assert (ones(1) + ones(1)).nbytes == 8 assert array(3.0).nbytes == 8 + def test_repeat(self): + from _numpypy import repeat, array + assert (repeat([[1, 2], [3, 4]], 3) == [1, 1, 1, 2, 2, 2, + 3, 3, 3, 4, 4, 4]).all() + assert (repeat([[1, 2], [3, 4]], 2, axis=0) == [[1, 2], [1, 2], [3, 4], + [3, 4]]).all() + assert (repeat([[1, 2], [3, 4]], 2, axis=1) == [[1, 1, 2, 2], [3, 3, + 4, 4]]).all() + assert (array([1, 2]).repeat(2) == array([1, 1, 2, 2])).all() + class AppTestMultiDim(BaseNumpyAppTest): def test_init(self): From noreply at buildbot.pypy.org Mon Apr 2 12:20:15 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 12:20:15 +0200 (CEST) Subject: [pypy-commit] pypy default: Translation fix. Message-ID: <20120402102015.BCFC646E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: Changeset: r54149:53c5958727e6 Date: 2012-04-02 12:18 +0200 http://bitbucket.org/pypy/pypy/changeset/53c5958727e6/ Log: Translation fix. diff --git a/pypy/module/cpyext/slotdefs.py b/pypy/module/cpyext/slotdefs.py --- a/pypy/module/cpyext/slotdefs.py +++ b/pypy/module/cpyext/slotdefs.py @@ -173,7 +173,8 @@ check_num_args(space, w_args, 1) w_value, = space.fixedview(w_args) res = generic_cpy_call(space, func_target, w_self, w_value) - if rffi.cast(lltype.Signed, res) == -1: + res = rffi.cast(lltype.Signed, res) + if res == -1: space.fromcache(State).check_and_raise_exception(always=True) return space.wrap(bool(res)) From noreply at buildbot.pypy.org Mon Apr 2 12:20:17 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 12:20:17 +0200 (CEST) Subject: [pypy-commit] pypy default: merge heads Message-ID: <20120402102017.0387A46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: Changeset: r54150:c519e300f325 Date: 2012-04-02 12:19 +0200 http://bitbucket.org/pypy/pypy/changeset/c519e300f325/ Log: merge heads diff --git a/pypy/module/micronumpy/__init__.py b/pypy/module/micronumpy/__init__.py --- a/pypy/module/micronumpy/__init__.py +++ b/pypy/module/micronumpy/__init__.py @@ -29,6 +29,7 @@ 'flatiter': 'interp_numarray.W_FlatIterator', 'isna': 'interp_numarray.isna', 'concatenate': 'interp_numarray.concatenate', + 'repeat': 'interp_numarray.repeat', 'set_string_function': 'appbridge.set_string_function', diff --git a/pypy/module/micronumpy/interp_numarray.py b/pypy/module/micronumpy/interp_numarray.py --- a/pypy/module/micronumpy/interp_numarray.py +++ b/pypy/module/micronumpy/interp_numarray.py @@ -673,6 +673,10 @@ def compute_first_step(self, sig, frame): pass + @unwrap_spec(repeats=int) + def descr_repeat(self, space, repeats, w_axis=None): + return repeat(space, self, repeats, w_axis) + def convert_to_array(space, w_obj): if isinstance(w_obj, BaseArray): return w_obj @@ -1261,6 +1265,31 @@ return convert_to_array(space, w_obj2).descr_dot(space, w_arr) return w_arr.descr_dot(space, w_obj2) + at unwrap_spec(repeats=int) +def repeat(space, w_arr, repeats, w_axis=None): + arr = convert_to_array(space, w_arr) + if space.is_w(w_axis, space.w_None): + arr = arr.descr_flatten(space).get_concrete() + orig_size = arr.shape[0] + shape = [arr.shape[0] * repeats] + res = W_NDimArray(shape, arr.find_dtype()) + for i in range(repeats): + Chunks([Chunk(i, shape[0] - repeats + i, repeats, + orig_size)]).apply(res).setslice(space, arr) + else: + arr = arr.get_concrete() + axis = space.int_w(w_axis) + shape = arr.shape[:] + chunks = [Chunk(0, i, 1, i) for i in shape] + orig_size = shape[axis] + shape[axis] *= repeats + res = W_NDimArray(shape, arr.find_dtype()) + for i in range(repeats): + chunks[axis] = Chunk(i, shape[axis] - repeats + i, repeats, + orig_size) + Chunks(chunks).apply(res).setslice(space, arr) + return res + @unwrap_spec(axis=int) def concatenate(space, w_args, axis=0): args_w = space.listview(w_args) @@ -1386,6 +1415,7 @@ tolist = interp2app(BaseArray.descr_tolist), take = interp2app(BaseArray.descr_take), compress = interp2app(BaseArray.descr_compress), + repeat = interp2app(BaseArray.descr_repeat), ) diff --git a/pypy/module/micronumpy/test/test_numarray.py b/pypy/module/micronumpy/test/test_numarray.py --- a/pypy/module/micronumpy/test/test_numarray.py +++ b/pypy/module/micronumpy/test/test_numarray.py @@ -1399,6 +1399,16 @@ assert (ones(1) + ones(1)).nbytes == 8 assert array(3.0).nbytes == 8 + def test_repeat(self): + from _numpypy import repeat, array + assert (repeat([[1, 2], [3, 4]], 3) == [1, 1, 1, 2, 2, 2, + 3, 3, 3, 4, 4, 4]).all() + assert (repeat([[1, 2], [3, 4]], 2, axis=0) == [[1, 2], [1, 2], [3, 4], + [3, 4]]).all() + assert (repeat([[1, 2], [3, 4]], 2, axis=1) == [[1, 1, 2, 2], [3, 3, + 4, 4]]).all() + assert (array([1, 2]).repeat(2) == array([1, 1, 2, 2])).all() + class AppTestMultiDim(BaseNumpyAppTest): def test_init(self): From noreply at buildbot.pypy.org Mon Apr 2 14:04:21 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 14:04:21 +0200 (CEST) Subject: [pypy-commit] extradoc extradoc: Sprint announcement, first draft Message-ID: <20120402120421.B873D46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: extradoc Changeset: r4169:6da880f2846e Date: 2012-04-02 14:03 +0200 http://bitbucket.org/pypy/extradoc/changeset/6da880f2846e/ Log: Sprint announcement, first draft diff --git a/sprintinfo/leipzig2012/announce.txt b/sprintinfo/leipzig2012/announce.txt new file mode 100644 --- /dev/null +++ b/sprintinfo/leipzig2012/announce.txt @@ -0,0 +1,47 @@ +Leipzig PyPy sprint June 22 - 27, 2012 +====================================== + +The next PyPy sprint will be held in the Python Academy of Leipzig, +Germany, from the 22nd to the 27th of June 2012. This is a fully public +sprint, everyone is welcome to join us. + +Topics and goals +---------------- + +Open. (to be defined more precisely) + +Location +-------- + +Python Academy Leipzig, Germany. +http://www.python-academy.com/center/find.html +Thanks to Mike Müller for inviting us. + +The room holds 8 - 12 people depending how much space each individual +needs. Drinks, snacks and a simple lunch, e.g. pizza or other fast food +is provided. Dinner would be extra. + +Some accommodations: +http://www.python-academy.com/center/accommodation.html +Pretty much all hotels and hostels in the city center +are fine too. The tram needs only 16 minutes from the +central station to the Academy location. + +Grants +------ + +For students, we have the possibility to support some costs via PyPy +funds. Additionally, we can support you applying for grants from the +PSF and other sources. + +Registration +------------ + +If you'd like to come, please *sign up* either by announcing yourself on +`pypy-dev`_, or by directly adding yourself to the `list of people`_. +(We need to have a head count for the organization.) If you are new to +the project please drop a note about your interests and post any +questions. + +.. _`pypy-dev`: http://mail.python.org/mailman/listinfo/pypy-dev +.. _`list of poeple`: https://bitbucket.org/pypy/extradoc/raw/extradoc/sprintinfo/leipzig2012/people.txt diff --git a/sprintinfo/leipzig2012/people.txt b/sprintinfo/leipzig2012/people.txt new file mode 100644 --- /dev/null +++ b/sprintinfo/leipzig2012/people.txt @@ -0,0 +1,13 @@ +People coming to the Leipzig sprint June 2012 +============================================= + +People who have a ``?`` in their arrive/depart or accomodation +column are known to be coming but there are no details +available yet from them. + +==================== ============== ===================== + Name Arrive/Depart Accomodation +==================== ============== ===================== +Armin Rigo ? ? +==================== ============== ===================== + From noreply at buildbot.pypy.org Mon Apr 2 14:10:28 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 14:10:28 +0200 (CEST) Subject: [pypy-commit] extradoc extradoc: Initial goals Message-ID: <20120402121028.3562946E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: extradoc Changeset: r4170:4ae5466dbaf3 Date: 2012-04-02 14:10 +0200 http://bitbucket.org/pypy/extradoc/changeset/4ae5466dbaf3/ Log: Initial goals diff --git a/sprintinfo/leipzig2012/announce.txt b/sprintinfo/leipzig2012/announce.txt --- a/sprintinfo/leipzig2012/announce.txt +++ b/sprintinfo/leipzig2012/announce.txt @@ -8,7 +8,10 @@ Topics and goals ---------------- -Open. (to be defined more precisely) +Open. Here are some goals: + +- numpy: progress towards completing the ``numpypy`` module +- stm: try out the ``transaction`` module on real code Location -------- From noreply at buildbot.pypy.org Mon Apr 2 14:15:00 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 14:15:00 +0200 (CEST) Subject: [pypy-commit] extradoc extradoc: Make the dates more precise. Message-ID: <20120402121500.1462146E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: extradoc Changeset: r4171:e2291545b923 Date: 2012-04-02 14:14 +0200 http://bitbucket.org/pypy/extradoc/changeset/e2291545b923/ Log: Make the dates more precise. diff --git a/sprintinfo/leipzig2012/announce.txt b/sprintinfo/leipzig2012/announce.txt --- a/sprintinfo/leipzig2012/announce.txt +++ b/sprintinfo/leipzig2012/announce.txt @@ -3,7 +3,8 @@ The next PyPy sprint will be held in the Python Academy of Leipzig, Germany, from the 22nd to the 27th of June 2012. This is a fully public -sprint, everyone is welcome to join us. +sprint, everyone is welcome to join us. All days are full sprint days, +so it is recommended to arrive the 21st and leave the 28th. Topics and goals ---------------- From noreply at buildbot.pypy.org Mon Apr 2 15:40:56 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 15:40:56 +0200 (CEST) Subject: [pypy-commit] extradoc extradoc: Update. Message-ID: <20120402134056.62DB446E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: extradoc Changeset: r4172:71246f66f47a Date: 2012-04-02 15:40 +0200 http://bitbucket.org/pypy/extradoc/changeset/71246f66f47a/ Log: Update. diff --git a/sprintinfo/leipzig2012/announce.txt b/sprintinfo/leipzig2012/announce.txt --- a/sprintinfo/leipzig2012/announce.txt +++ b/sprintinfo/leipzig2012/announce.txt @@ -1,18 +1,31 @@ Leipzig PyPy sprint June 22 - 27, 2012 ====================================== -The next PyPy sprint will be held in the Python Academy of Leipzig, -Germany, from the 22nd to the 27th of June 2012. This is a fully public -sprint, everyone is welcome to join us. All days are full sprint days, -so it is recommended to arrive the 21st and leave the 28th. +The next PyPy sprint will be held --- for the first time in a while --- +in a place where we haven't been so far: Leipzig, Germany, at the +`Python Academy`_'s Teaching Center. It will take place from the 22nd +to the 27th of June 2012, before EuroPython. Thanks to Mike Müller for +organizing it! + +.. _`Python Academy`: http://www.python-academy.com/ + +This is a fully public sprint, everyone is welcome to join us. All days are +full sprint days, so it is recommended to arrive the 21st and leave the 28th. + Topics and goals ---------------- Open. Here are some goals: -- numpy: progress towards completing the ``numpypy`` module -- stm: try out the ``transaction`` module on real code +- numpy: progress towards completing the ``numpypy`` module; try to + use it in real code + +- stm: progress on Transactional Memory; try out the ``transaction`` + module on real code. + +- any other PyPy-related topic is fine too. + Location -------- @@ -31,6 +44,7 @@ are fine too. The tram needs only 16 minutes from the central station to the Academy location. + Grants ------ @@ -38,6 +52,7 @@ funds. Additionally, we can support you applying for grants from the PSF and other sources. + Registration ------------ @@ -48,4 +63,4 @@ questions. .. _`pypy-dev`: http://mail.python.org/mailman/listinfo/pypy-dev -.. _`list of poeple`: https://bitbucket.org/pypy/extradoc/raw/extradoc/sprintinfo/leipzig2012/people.txt +.. _`list of people`: https://bitbucket.org/pypy/extradoc/raw/extradoc/sprintinfo/leipzig2012/people.txt From noreply at buildbot.pypy.org Mon Apr 2 15:40:57 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 15:40:57 +0200 (CEST) Subject: [pypy-commit] extradoc extradoc: Also add the html version, to be linked from the blog post. Message-ID: <20120402134057.94DBC46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: extradoc Changeset: r4173:477481830a61 Date: 2012-04-02 15:40 +0200 http://bitbucket.org/pypy/extradoc/changeset/477481830a61/ Log: Also add the html version, to be linked from the blog post. diff --git a/sprintinfo/leipzig2012/announce.html b/sprintinfo/leipzig2012/announce.html new file mode 100644 --- /dev/null +++ b/sprintinfo/leipzig2012/announce.html @@ -0,0 +1,342 @@ + + + + + + +Leipzig PyPy sprint June 22 - 27, 2012 + + + +
+

Leipzig PyPy sprint June 22 - 27, 2012

+

The next PyPy sprint will be held --- for the first time in a while --- +in a place where we haven't been so far: Leipzig, Germany, at the +Python Academy's Teaching Center, from the 22nd to the 27th of June +2012, before EuroPython. Thanks to Mike Müller for organizing it!

+

This is a fully public sprint, everyone is welcome to join us. All days are +full sprint days, so it is recommended to arrive the 21st and leave the 28th.

+
+

Topics and goals

+

Open. Here are some goals:

+
    +
  • numpy: progress towards completing the numpypy module; try to +use it in real code
  • +
  • stm: progress on Transactional Memory; try out the transaction +module on real code.
  • +
  • any other PyPy-related topic is fine too.
  • +
+
+
+

Location

+

Python Academy Leipzig, Germany. +http://www.python-academy.com/center/find.html +Thanks to Mike Müller for inviting us.

+

The room holds 8 - 12 people depending how much space each individual +needs. Drinks, snacks and a simple lunch, e.g. pizza or other fast food +is provided. Dinner would be extra.

+

Some accommodations: +http://www.python-academy.com/center/accommodation.html +Pretty much all hotels and hostels in the city center +are fine too. The tram needs only 16 minutes from the +central station to the Academy location.

+
+
+

Grants

+

For students, we have the possibility to support some costs via PyPy +funds. Additionally, we can support you applying for grants from the +PSF and other sources.

+
+
+

Registration

+

If you'd like to come, please sign up either by announcing yourself on +pypy-dev, or by directly adding yourself to the list of people. +(We need to have a head count for the organization.) If you are new to +the project please drop a note about your interests and post any +questions.

+
+
+ + From noreply at buildbot.pypy.org Mon Apr 2 15:44:38 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 15:44:38 +0200 (CEST) Subject: [pypy-commit] extradoc extradoc: Remove the html version; we can't seem to link to it anyway. Message-ID: <20120402134438.3027346E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: extradoc Changeset: r4174:13d4f71f9018 Date: 2012-04-02 15:43 +0200 http://bitbucket.org/pypy/extradoc/changeset/13d4f71f9018/ Log: Remove the html version; we can't seem to link to it anyway. diff --git a/sprintinfo/leipzig2012/announce.html b/sprintinfo/leipzig2012/announce.html deleted file mode 100644 --- a/sprintinfo/leipzig2012/announce.html +++ /dev/null @@ -1,342 +0,0 @@ - - - - - - -Leipzig PyPy sprint June 22 - 27, 2012 - - - -
-

Leipzig PyPy sprint June 22 - 27, 2012

-

The next PyPy sprint will be held --- for the first time in a while --- -in a place where we haven't been so far: Leipzig, Germany, at the -Python Academy's Teaching Center, from the 22nd to the 27th of June -2012, before EuroPython. Thanks to Mike Müller for organizing it!

-

This is a fully public sprint, everyone is welcome to join us. All days are -full sprint days, so it is recommended to arrive the 21st and leave the 28th.

-
-

Topics and goals

-

Open. Here are some goals:

-
    -
  • numpy: progress towards completing the numpypy module; try to -use it in real code
  • -
  • stm: progress on Transactional Memory; try out the transaction -module on real code.
  • -
  • any other PyPy-related topic is fine too.
  • -
-
-
-

Location

-

Python Academy Leipzig, Germany. -http://www.python-academy.com/center/find.html -Thanks to Mike Müller for inviting us.

-

The room holds 8 - 12 people depending how much space each individual -needs. Drinks, snacks and a simple lunch, e.g. pizza or other fast food -is provided. Dinner would be extra.

-

Some accommodations: -http://www.python-academy.com/center/accommodation.html -Pretty much all hotels and hostels in the city center -are fine too. The tram needs only 16 minutes from the -central station to the Academy location.

-
-
-

Grants

-

For students, we have the possibility to support some costs via PyPy -funds. Additionally, we can support you applying for grants from the -PSF and other sources.

-
-
-

Registration

-

If you'd like to come, please sign up either by announcing yourself on -pypy-dev, or by directly adding yourself to the list of people. -(We need to have a head count for the organization.) If you are new to -the project please drop a note about your interests and post any -questions.

-
-
- - From noreply at buildbot.pypy.org Mon Apr 2 16:10:00 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 16:10:00 +0200 (CEST) Subject: [pypy-commit] pypy stm-gc: Add a minimal usage example. Message-ID: <20120402141000.31DD846E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: stm-gc Changeset: r54151:ae91dbd26901 Date: 2012-04-02 16:09 +0200 http://bitbucket.org/pypy/pypy/changeset/ae91dbd26901/ Log: Add a minimal usage example. diff --git a/lib_pypy/transaction.py b/lib_pypy/transaction.py --- a/lib_pypy/transaction.py +++ b/lib_pypy/transaction.py @@ -1,3 +1,20 @@ +""" +Minimal example of usage: + + for i in range(10): + transaction.add(do_stuff, i) + transaction.run() + +This schedules and runs all ten do_stuff(i), each in its own transaction. +Each one can also add more transactions to run afterwards, and so on. +The call to run() returns when all transactions have completed. + +From the API point of view it is as if the do_stuff(i) were run serially +in some random order. If you use a real implementation instead of this +one (which is here for trying things out), then the transactions can +actually run in parallel on multiple cores. +""" + import sys import random From noreply at buildbot.pypy.org Mon Apr 2 16:11:20 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 16:11:20 +0200 (CEST) Subject: [pypy-commit] pypy.org extradoc: Update with a link to the documentation -- so far only in transaction.py. Message-ID: <20120402141120.0BFFE46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: extradoc Changeset: r351:0bffaf37ab63 Date: 2012-04-02 16:11 +0200 http://bitbucket.org/pypy/pypy.org/changeset/0bffaf37ab63/ Log: Update with a link to the documentation -- so far only in transaction.py. diff --git a/source/tmdonate.txt b/source/tmdonate.txt --- a/source/tmdonate.txt +++ b/source/tmdonate.txt @@ -189,6 +189,10 @@ detail once they are known). But the point is that your program is always correct. +See some `concrete example`__ of the API. + +.. __: https://bitbucket.org/pypy/pypy/raw/stm-gc/lib_pypy/transaction.py + Speed ----- diff --git a/tmdonate.html b/tmdonate.html --- a/tmdonate.html +++ b/tmdonate.html @@ -183,6 +183,7 @@ transactions into several parts; the exact rules will be published in detail once they are known). But the point is that your program is always correct.

+

See some concrete example of the API.

Speed

From noreply at buildbot.pypy.org Mon Apr 2 17:21:51 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 17:21:51 +0200 (CEST) Subject: [pypy-commit] pypy default: Fix: apply this change, even if it's incompatible with CPython 2.2(!). Message-ID: <20120402152151.F26C246E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: Changeset: r54152:49d683bbd92b Date: 2012-04-02 17:17 +0200 http://bitbucket.org/pypy/pypy/changeset/49d683bbd92b/ Log: Fix: apply this change, even if it's incompatible with CPython 2.2(!). It fixes an issue reported by adoven on irc. Add a test. diff --git a/lib_pypy/datetime.py b/lib_pypy/datetime.py --- a/lib_pypy/datetime.py +++ b/lib_pypy/datetime.py @@ -968,8 +968,7 @@ self._checkOverflow(t.year) result = date(t.year, t.month, t.day) return result - raise TypeError - # XXX Should be 'return NotImplemented', but there's a bug in 2.2... + return NotImplemented # note that this doesn't work on CPython 2.2 __radd__ = __add__ diff --git a/pypy/module/test_lib_pypy/test_datetime.py b/pypy/module/test_lib_pypy/test_datetime.py --- a/pypy/module/test_lib_pypy/test_datetime.py +++ b/pypy/module/test_lib_pypy/test_datetime.py @@ -44,3 +44,9 @@ assert type(dt.microsecond) is int copy.copy(dt) + +def test_radd(): + class X(object): + def __radd__(self, other): + return "radd" + assert datetime.date(10, 10, 10) + X() == "radd" From noreply at buildbot.pypy.org Mon Apr 2 17:32:59 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 17:32:59 +0200 (CEST) Subject: [pypy-commit] pypy default: Fix for issue1100. Message-ID: <20120402153259.F0D0846E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: Changeset: r54153:ae4a7ab99a76 Date: 2012-04-02 17:32 +0200 http://bitbucket.org/pypy/pypy/changeset/ae4a7ab99a76/ Log: Fix for issue1100. diff --git a/pypy/objspace/std/dictmultiobject.py b/pypy/objspace/std/dictmultiobject.py --- a/pypy/objspace/std/dictmultiobject.py +++ b/pypy/objspace/std/dictmultiobject.py @@ -247,7 +247,7 @@ return 0 def iter(self, w_dict): - return EmptyIteratorImplementation(self.space, w_dict) + return EmptyIteratorImplementation(self.space, self, w_dict) def clear(self, w_dict): return @@ -263,8 +263,9 @@ # Iterator Implementation base classes class IteratorImplementation(object): - def __init__(self, space, implementation): + def __init__(self, space, strategy, implementation): self.space = space + self.strategy = strategy self.dictimplementation = implementation self.len = implementation.length() self.pos = 0 @@ -272,7 +273,8 @@ def next(self): if self.dictimplementation is None: return None, None - if self.len != self.dictimplementation.length(): + if (self.len != self.dictimplementation.length() + or self.strategy is not self.dictimplementation.strategy): self.len = -1 # Make this error state sticky raise OperationError(self.space.w_RuntimeError, self.space.wrap("dictionary changed size during iteration")) @@ -489,7 +491,7 @@ _mixin_ = True def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) + IteratorImplementation.__init__(self, space, strategy, dictimplementation) self.iterator = strategy.unerase(dictimplementation.dstorage).iteritems() def next_entry(self): @@ -503,7 +505,7 @@ _mixin_ = True def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) + IteratorImplementation.__init__(self, space, strategy, dictimplementation) self.iterator = strategy.unerase(dictimplementation.dstorage).iteritems() def next_entry(self): diff --git a/pypy/objspace/std/test/test_dictmultiobject.py b/pypy/objspace/std/test/test_dictmultiobject.py --- a/pypy/objspace/std/test/test_dictmultiobject.py +++ b/pypy/objspace/std/test/test_dictmultiobject.py @@ -804,6 +804,21 @@ assert "IntDictStrategy" in self.get_strategy(d) assert d[1L] == "hi" + def test_iter_dict_length_change(self): + d = {1: 2, 3: 4, 5: 6} + it = d.iteritems() + d[7] = 8 + # 'd' is now length 4 + raises(RuntimeError, it.next) + + def test_iter_dict_strategy_only_change(self): + d = {1: 2, 3: 4, 5: 6} + it = d.iteritems() + d['foo'] = 'bar' + del d[1] + # 'd' is still length 3, but its strategy changed + raises(RuntimeError, it.next) + class FakeString(str): hash_count = 0 From noreply at buildbot.pypy.org Mon Apr 2 18:15:13 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 18:15:13 +0200 (CEST) Subject: [pypy-commit] pypy default: Another case: iterating should work if the dict strategy changed "just" Message-ID: <20120402161513.5403146E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: Changeset: r54154:7e37a82840df Date: 2012-04-02 18:14 +0200 http://bitbucket.org/pypy/pypy/changeset/7e37a82840df/ Log: Another case: iterating should work if the dict strategy changed "just" because of a __getitem__. It's workaroundesque but enough, I hope. diff --git a/pypy/objspace/std/dictmultiobject.py b/pypy/objspace/std/dictmultiobject.py --- a/pypy/objspace/std/dictmultiobject.py +++ b/pypy/objspace/std/dictmultiobject.py @@ -273,8 +273,7 @@ def next(self): if self.dictimplementation is None: return None, None - if (self.len != self.dictimplementation.length() - or self.strategy is not self.dictimplementation.strategy): + if self.len != self.dictimplementation.length(): self.len = -1 # Make this error state sticky raise OperationError(self.space.w_RuntimeError, self.space.wrap("dictionary changed size during iteration")) @@ -282,7 +281,20 @@ if self.pos < self.len: result = self.next_entry() self.pos += 1 - return result + if self.strategy is self.dictimplementation.strategy: + return result # common case + else: + # waaa, obscure case: the strategy changed, but not the + # length of the dict. The (key, value) pair in 'result' + # might be out-of-date. We try to explicitly look up + # the key in the dict. + w_key = result[0] + w_value = self.dictimplementation.getitem(w_key) + if w_value is None: + self.len = -1 # Make this error state sticky + raise OperationError(self.space.w_RuntimeError, + self.space.wrap("dictionary changed during iteration")) + return (w_key, w_value) # no more entries self.dictimplementation = None return None, None diff --git a/pypy/objspace/std/test/test_dictmultiobject.py b/pypy/objspace/std/test/test_dictmultiobject.py --- a/pypy/objspace/std/test/test_dictmultiobject.py +++ b/pypy/objspace/std/test/test_dictmultiobject.py @@ -811,7 +811,17 @@ # 'd' is now length 4 raises(RuntimeError, it.next) - def test_iter_dict_strategy_only_change(self): + def test_iter_dict_strategy_only_change_1(self): + d = {1: 2, 3: 4, 5: 6} + it = d.iteritems() + class Foo(object): + def __eq__(self, other): + return False + d.get(Foo()) # this changes the strategy of 'd' + lst = list(it) # but iterating still works + assert sorted(lst) == [(1, 2), (3, 4), (5, 6)] + + def test_iter_dict_strategy_only_change_2(self): d = {1: 2, 3: 4, 5: 6} it = d.iteritems() d['foo'] = 'bar' From pullrequests-noreply at bitbucket.org Mon Apr 2 18:22:13 2012 From: pullrequests-noreply at bitbucket.org (unbit) Date: Mon, 02 Apr 2012 16:22:13 -0000 Subject: [pypy-commit] [pypy/pypy] added implementation of Py_Initialize (pull request #67) Message-ID: A new pull request has been opened by unbit. unbit/pypy has changes to be pulled into pypy/pypy. https://bitbucket.org/pypy/pypy/pull-request/67/added-implementation-of-py_initialize Title: added implementation of Py_Initialize Py_Initialize() is the function called by apps wanting to embed a python intepreter. The commit adds Py_Initialize implementation, its definition in pythonrun header file and exports the symbol in the translator scripts. Changes to be pulled: 4c1e64afbe9a by rob... at goyle: "added implementation of Py_Initialize" -- This is an issue notification from bitbucket.org. You are receiving this either because you are the participating in a pull request, or you are following it. From noreply at buildbot.pypy.org Mon Apr 2 18:22:25 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 18:22:25 +0200 (CEST) Subject: [pypy-commit] pypy default: Same issue and same fix for sets. Message-ID: <20120402162225.6152046E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: Changeset: r54155:f16be5ae31e6 Date: 2012-04-02 18:22 +0200 http://bitbucket.org/pypy/pypy/changeset/f16be5ae31e6/ Log: Same issue and same fix for sets. diff --git a/pypy/objspace/std/setobject.py b/pypy/objspace/std/setobject.py --- a/pypy/objspace/std/setobject.py +++ b/pypy/objspace/std/setobject.py @@ -359,7 +359,7 @@ w_set.sstorage = w_other.get_storage_copy() def iter(self, w_set): - return EmptyIteratorImplementation(self.space, w_set) + return EmptyIteratorImplementation(self.space, self, w_set) def popitem(self, w_set): raise OperationError(self.space.w_KeyError, @@ -784,8 +784,9 @@ d_obj[w_item] = None class IteratorImplementation(object): - def __init__(self, space, implementation): + def __init__(self, space, strategy, implementation): self.space = space + self.strategy = strategy self.setimplementation = implementation self.len = implementation.length() self.pos = 0 @@ -801,7 +802,17 @@ if self.pos < self.len: result = self.next_entry() self.pos += 1 - return result + if self.strategy is self.setimplementation.strategy: + return result # common case + else: + # waaa, obscure case: the strategy changed, but not the + # length of the set. The 'result' might be out-of-date. + # We try to explicitly look it up in the set. + if not self.setimplementation.has_key(result): + self.len = -1 # Make this error state sticky + raise OperationError(self.space.w_RuntimeError, + self.space.wrap("dictionary changed during iteration")) + return result # no more entries self.setimplementation = None return None @@ -823,7 +834,7 @@ class StringIteratorImplementation(IteratorImplementation): def __init__(self, space, strategy, w_set): - IteratorImplementation.__init__(self, space, w_set) + IteratorImplementation.__init__(self, space, strategy, w_set) d = strategy.unerase(w_set.sstorage) self.iterator = d.iterkeys() @@ -835,9 +846,9 @@ class IntegerIteratorImplementation(IteratorImplementation): #XXX same implementation in dictmultiobject on dictstrategy-branch - def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) - d = strategy.unerase(dictimplementation.sstorage) + def __init__(self, space, strategy, w_set): + IteratorImplementation.__init__(self, space, strategy, w_set) + d = strategy.unerase(w_set.sstorage) self.iterator = d.iterkeys() def next_entry(self): @@ -848,9 +859,9 @@ return None class RDictIteratorImplementation(IteratorImplementation): - def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) - d = strategy.unerase(dictimplementation.sstorage) + def __init__(self, space, strategy, w_set): + IteratorImplementation.__init__(self, space, strategy, w_set) + d = strategy.unerase(w_set.sstorage) self.iterator = d.iterkeys() def next_entry(self): diff --git a/pypy/objspace/std/test/test_dictmultiobject.py b/pypy/objspace/std/test/test_dictmultiobject.py --- a/pypy/objspace/std/test/test_dictmultiobject.py +++ b/pypy/objspace/std/test/test_dictmultiobject.py @@ -817,7 +817,7 @@ class Foo(object): def __eq__(self, other): return False - d.get(Foo()) # this changes the strategy of 'd' + assert d.get(Foo()) is None # this changes the strategy of 'd' lst = list(it) # but iterating still works assert sorted(lst) == [(1, 2), (3, 4), (5, 6)] @@ -826,8 +826,10 @@ it = d.iteritems() d['foo'] = 'bar' del d[1] - # 'd' is still length 3, but its strategy changed - raises(RuntimeError, it.next) + # 'd' is still length 3, but its strategy changed. we are + # getting a RuntimeError because iterating over the old storage + # gives us (1, 2), but 1 is not in the dict any longer. + raises(RuntimeError, list, it) class FakeString(str): diff --git a/pypy/objspace/std/test/test_setobject.py b/pypy/objspace/std/test/test_setobject.py --- a/pypy/objspace/std/test/test_setobject.py +++ b/pypy/objspace/std/test/test_setobject.py @@ -907,3 +907,30 @@ return [5, 3, 4][i] s = set([10,3,2]).intersection(Obj()) assert list(s) == [3] + + def test_iter_set_length_change(self): + s = set([1, 3, 5]) + it = iter(s) + s.add(7) + # 's' is now length 4 + raises(RuntimeError, it.next) + + def test_iter_set_strategy_only_change_1(self): + s = set([1, 3, 5]) + it = iter(s) + class Foo(object): + def __eq__(self, other): + return False + assert Foo() not in s # this changes the strategy of 'd' + lst = list(s) # but iterating still works + assert sorted(lst) == [1, 3, 5] + + def test_iter_set_strategy_only_change_2(self): + s = set([1, 3, 5]) + it = iter(s) + s.add('foo') + s.remove(1) + # 's' is still length 3, but its strategy changed. we are + # getting a RuntimeError because iterating over the old storage + # gives us 1, but 1 is not in the set any longer. + raises(RuntimeError, list, it) From noreply at buildbot.pypy.org Mon Apr 2 19:59:15 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 2 Apr 2012 19:59:15 +0200 (CEST) Subject: [pypy-commit] extradoc extradoc: Add a point Message-ID: <20120402175915.322BA46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: extradoc Changeset: r4175:1c44b080a0e5 Date: 2012-04-02 19:58 +0200 http://bitbucket.org/pypy/extradoc/changeset/1c44b080a0e5/ Log: Add a point diff --git a/sprintinfo/leipzig2012/announce.txt b/sprintinfo/leipzig2012/announce.txt --- a/sprintinfo/leipzig2012/announce.txt +++ b/sprintinfo/leipzig2012/announce.txt @@ -24,6 +24,9 @@ - stm: progress on Transactional Memory; try out the ``transaction`` module on real code. +- jit optimizations: there are a number of optimizations we can still + try out or refactor. + - any other PyPy-related topic is fine too. From noreply at buildbot.pypy.org Mon Apr 2 20:04:08 2012 From: noreply at buildbot.pypy.org (fijal) Date: Mon, 2 Apr 2012 20:04:08 +0200 (CEST) Subject: [pypy-commit] extradoc extradoc: add a point Message-ID: <20120402180408.831A446E0EF@wyvern.cs.uni-duesseldorf.de> Author: Maciej Fijalkowski Branch: extradoc Changeset: r4176:1bdfa84745f2 Date: 2012-04-02 20:03 +0200 http://bitbucket.org/pypy/extradoc/changeset/1bdfa84745f2/ Log: add a point diff --git a/sprintinfo/leipzig2012/announce.txt b/sprintinfo/leipzig2012/announce.txt --- a/sprintinfo/leipzig2012/announce.txt +++ b/sprintinfo/leipzig2012/announce.txt @@ -27,6 +27,10 @@ - jit optimizations: there are a number of optimizations we can still try out or refactor. +- work on various, more efficient data structures for Python language. + A good example would be lazy string slicing/concatenation or more efficient + objects. + - any other PyPy-related topic is fine too. diff --git a/talk/uct2012/talk.rst b/talk/uct2012/talk.rst --- a/talk/uct2012/talk.rst +++ b/talk/uct2012/talk.rst @@ -6,7 +6,7 @@ * PyPy developer since 2006 -XXX +* Worked on many areas, including the GC, the JIT, etc. What is PyPy? ------------- From noreply at buildbot.pypy.org Mon Apr 2 21:08:53 2012 From: noreply at buildbot.pypy.org (ctismer) Date: Mon, 2 Apr 2012 21:08:53 +0200 (CEST) Subject: [pypy-commit] pypy win64-stage1: Merge from default Message-ID: <20120402190853.3009346E0EF@wyvern.cs.uni-duesseldorf.de> Author: Christian Tismer Branch: win64-stage1 Changeset: r54156:c624ccedd559 Date: 2012-04-02 21:08 +0200 http://bitbucket.org/pypy/pypy/changeset/c624ccedd559/ Log: Merge from default diff --git a/dotviewer/graphparse.py b/dotviewer/graphparse.py --- a/dotviewer/graphparse.py +++ b/dotviewer/graphparse.py @@ -93,6 +93,7 @@ return result def parse_plain(graph_id, plaincontent, links={}, fixedfont=False): + plaincontent = plaincontent.replace('\r\n', '\n') # fix Windows EOL lines = plaincontent.splitlines(True) for i in range(len(lines)-2, -1, -1): if lines[i].endswith('\\\n'): # line ending in '\' diff --git a/lib_pypy/datetime.py b/lib_pypy/datetime.py --- a/lib_pypy/datetime.py +++ b/lib_pypy/datetime.py @@ -968,8 +968,7 @@ self._checkOverflow(t.year) result = date(t.year, t.month, t.day) return result - raise TypeError - # XXX Should be 'return NotImplemented', but there's a bug in 2.2... + return NotImplemented # note that this doesn't work on CPython 2.2 __radd__ = __add__ diff --git a/pypy/interpreter/executioncontext.py b/pypy/interpreter/executioncontext.py --- a/pypy/interpreter/executioncontext.py +++ b/pypy/interpreter/executioncontext.py @@ -167,6 +167,11 @@ frame = self.getnextframe_nohidden(frame) return None + def set_sys_exc_info(self, operror): + frame = self.gettopframe_nohidden() + if frame: # else, the exception goes nowhere and is lost + frame.last_exception = operror + def settrace(self, w_func): """Set the global trace function.""" if self.space.is_w(w_func, self.space.w_None): diff --git a/pypy/jit/backend/llgraph/llimpl.py b/pypy/jit/backend/llgraph/llimpl.py --- a/pypy/jit/backend/llgraph/llimpl.py +++ b/pypy/jit/backend/llgraph/llimpl.py @@ -1797,6 +1797,7 @@ if specialize_as_constant: def specialize_call(self, hop): llvalue = func(hop.args_s[0].const) + hop.exception_cannot_occur() return hop.inputconst(lltype.typeOf(llvalue), llvalue) else: # specialize as direct_call @@ -1813,6 +1814,7 @@ sm = ootype._static_meth(FUNCTYPE, _name=func.__name__, _callable=func) cfunc = hop.inputconst(FUNCTYPE, sm) args_v = hop.inputargs(*hop.args_r) + hop.exception_is_here() return hop.genop('direct_call', [cfunc] + args_v, hop.r_result) diff --git a/pypy/module/_ssl/interp_ssl.py b/pypy/module/_ssl/interp_ssl.py --- a/pypy/module/_ssl/interp_ssl.py +++ b/pypy/module/_ssl/interp_ssl.py @@ -432,7 +432,8 @@ raise _ssl_seterror(self.space, self, length) try: # this is actually an immutable bytes sequence - return self.space.wrap(rffi.charp2str(buf_ptr[0])) + return self.space.wrap(rffi.charpsize2str(buf_ptr[0], + length)) finally: libssl_OPENSSL_free(buf_ptr[0]) else: diff --git a/pypy/module/array/interp_array.py b/pypy/module/array/interp_array.py --- a/pypy/module/array/interp_array.py +++ b/pypy/module/array/interp_array.py @@ -11,7 +11,7 @@ from pypy.objspace.std.register_all import register_all from pypy.rlib.rarithmetic import ovfcheck from pypy.rlib.unroll import unrolling_iterable -from pypy.rlib.objectmodel import specialize +from pypy.rlib.objectmodel import specialize, keepalive_until_here from pypy.rpython.lltypesystem import lltype, rffi @@ -145,18 +145,24 @@ unroll_typecodes = unrolling_iterable(types.keys()) class ArrayBuffer(RWBuffer): - def __init__(self, data, bytes): - self.data = data - self.len = bytes + def __init__(self, array): + self.array = array def getlength(self): - return self.len + return self.array.len * self.array.itemsize def getitem(self, index): - return self.data[index] + array = self.array + data = array._charbuf_start() + char = data[index] + array._charbuf_stop() + return char def setitem(self, index, char): - self.data[index] = char + array = self.array + data = array._charbuf_start() + data[index] = char + array._charbuf_stop() def make_array(mytype): @@ -278,9 +284,10 @@ oldlen = self.len new = len(s) / mytype.bytes self.setlen(oldlen + new) - cbuf = self.charbuf() + cbuf = self._charbuf_start() for i in range(len(s)): cbuf[oldlen * mytype.bytes + i] = s[i] + self._charbuf_stop() def fromlist(self, w_lst): s = self.len @@ -310,8 +317,11 @@ else: self.fromsequence(w_iterable) - def charbuf(self): - return rffi.cast(rffi.CCHARP, self.buffer) + def _charbuf_start(self): + return rffi.cast(rffi.CCHARP, self.buffer) + + def _charbuf_stop(self): + keepalive_until_here(self) def w_getitem(self, space, idx): item = self.buffer[idx] @@ -530,8 +540,10 @@ self.fromstring(space.str_w(w_s)) def array_tostring__Array(space, self): - cbuf = self.charbuf() - return self.space.wrap(rffi.charpsize2str(cbuf, self.len * mytype.bytes)) + cbuf = self._charbuf_start() + s = rffi.charpsize2str(cbuf, self.len * mytype.bytes) + self._charbuf_stop() + return self.space.wrap(s) def array_fromfile__Array_ANY_ANY(space, self, w_f, w_n): if not isinstance(w_f, W_File): @@ -613,8 +625,7 @@ # Misc methods def buffer__Array(space, self): - b = ArrayBuffer(self.charbuf(), self.len * mytype.bytes) - return space.wrap(b) + return space.wrap(ArrayBuffer(self)) def array_buffer_info__Array(space, self): w_ptr = space.wrap(rffi.cast(lltype.Unsigned, self.buffer)) @@ -649,7 +660,7 @@ raise OperationError(space.w_RuntimeError, space.wrap(msg)) if self.len == 0: return - bytes = self.charbuf() + bytes = self._charbuf_start() tmp = [bytes[0]] * mytype.bytes for start in range(0, self.len * mytype.bytes, mytype.bytes): stop = start + mytype.bytes - 1 @@ -657,6 +668,7 @@ tmp[i] = bytes[start + i] for i in range(mytype.bytes): bytes[stop - i] = tmp[i] + self._charbuf_stop() def repr__Array(space, self): if self.len == 0: diff --git a/pypy/module/array/test/test_array.py b/pypy/module/array/test/test_array.py --- a/pypy/module/array/test/test_array.py +++ b/pypy/module/array/test/test_array.py @@ -433,7 +433,25 @@ a = self.array('h', 'Hi') buf = buffer(a) assert buf[1] == 'i' - #raises(TypeError, buf.__setitem__, 1, 'o') + + def test_buffer_write(self): + a = self.array('c', 'hello') + buf = buffer(a) + print repr(buf) + try: + buf[3] = 'L' + except TypeError: + skip("buffer(array) returns a read-only buffer on CPython") + assert a.tostring() == 'helLo' + + def test_buffer_keepalive(self): + buf = buffer(self.array('c', 'text')) + assert buf[2] == 'x' + # + a = self.array('c', 'foobarbaz') + buf = buffer(a) + a.fromstring('some extra text') + assert buf[:] == 'foobarbazsome extra text' def test_list_methods(self): assert repr(self.array('i')) == "array('i')" diff --git a/pypy/module/cpyext/bufferobject.py b/pypy/module/cpyext/bufferobject.py --- a/pypy/module/cpyext/bufferobject.py +++ b/pypy/module/cpyext/bufferobject.py @@ -2,7 +2,7 @@ from pypy.module.cpyext.api import ( cpython_api, Py_ssize_t, cpython_struct, bootstrap_function, PyObjectFields, PyObject) -from pypy.module.cpyext.pyobject import make_typedescr, Py_DecRef +from pypy.module.cpyext.pyobject import make_typedescr, Py_DecRef, make_ref from pypy.interpreter.buffer import Buffer, StringBuffer, SubBuffer from pypy.interpreter.error import OperationError from pypy.module.array.interp_array import ArrayBuffer @@ -43,13 +43,18 @@ py_buf.c_b_offset = w_obj.offset w_obj = w_obj.buffer + # If w_obj already allocated a fixed buffer, use it, and keep a + # reference to w_obj. + # Otherwise, b_base stays NULL, and we own the b_ptr. + if isinstance(w_obj, StringBuffer): - py_buf.c_b_base = rffi.cast(PyObject, 0) # space.wrap(w_obj.value) + py_buf.c_b_base = lltype.nullptr(PyObject.TO) py_buf.c_b_ptr = rffi.cast(rffi.VOIDP, rffi.str2charp(w_obj.value)) py_buf.c_b_size = w_obj.getlength() elif isinstance(w_obj, ArrayBuffer): - py_buf.c_b_base = rffi.cast(PyObject, 0) # space.wrap(w_obj.value) - py_buf.c_b_ptr = rffi.cast(rffi.VOIDP, w_obj.data) + w_base = w_obj.array + py_buf.c_b_base = make_ref(space, w_base) + py_buf.c_b_ptr = rffi.cast(rffi.VOIDP, w_obj.array._charbuf_start()) py_buf.c_b_size = w_obj.getlength() else: raise OperationError(space.w_NotImplementedError, space.wrap( @@ -60,14 +65,16 @@ """ Creates the buffer in the PyPy interpreter from a cpyext representation. """ - raise Exception("realize fail fail fail") - + raise OperationError(space.w_NotImplementedError, space.wrap( + "Don't know how to realize a buffer")) @cpython_api([PyObject], lltype.Void, external=False) def buffer_dealloc(space, py_obj): py_buf = rffi.cast(PyBufferObject, py_obj) - Py_DecRef(space, py_buf.c_b_base) - rffi.free_charp(rffi.cast(rffi.CCHARP, py_buf.c_b_ptr)) + if py_buf.c_b_base: + Py_DecRef(space, py_buf.c_b_base) + else: + rffi.free_charp(rffi.cast(rffi.CCHARP, py_buf.c_b_ptr)) from pypy.module.cpyext.object import PyObject_dealloc PyObject_dealloc(space, py_obj) diff --git a/pypy/module/cpyext/pyerrors.py b/pypy/module/cpyext/pyerrors.py --- a/pypy/module/cpyext/pyerrors.py +++ b/pypy/module/cpyext/pyerrors.py @@ -2,6 +2,7 @@ from pypy.rpython.lltypesystem import rffi, lltype from pypy.interpreter.error import OperationError +from pypy.interpreter import pytraceback from pypy.module.cpyext.api import cpython_api, CANNOT_FAIL, CONST_STRING from pypy.module.exceptions.interp_exceptions import W_RuntimeWarning from pypy.module.cpyext.pyobject import ( @@ -315,3 +316,65 @@ It may be called without holding the interpreter lock.""" space.check_signal_action.set_interrupt() + at cpython_api([PyObjectP, PyObjectP, PyObjectP], lltype.Void) +def PyErr_GetExcInfo(space, ptype, pvalue, ptraceback): + """---Cython extension--- + + Retrieve the exception info, as known from ``sys.exc_info()``. This + refers to an exception that was already caught, not to an exception + that was freshly raised. Returns new references for the three + objects, any of which may be *NULL*. Does not modify the exception + info state. + + .. note:: + + This function is not normally used by code that wants to handle + exceptions. Rather, it can be used when code needs to save and + restore the exception state temporarily. Use + :c:func:`PyErr_SetExcInfo` to restore or clear the exception + state. + """ + ec = space.getexecutioncontext() + operror = ec.sys_exc_info() + if operror: + ptype[0] = make_ref(space, operror.w_type) + pvalue[0] = make_ref(space, operror.get_w_value(space)) + ptraceback[0] = make_ref(space, space.wrap(operror.get_traceback())) + else: + ptype[0] = lltype.nullptr(PyObject.TO) + pvalue[0] = lltype.nullptr(PyObject.TO) + ptraceback[0] = lltype.nullptr(PyObject.TO) + + at cpython_api([PyObject, PyObject, PyObject], lltype.Void) +def PyErr_SetExcInfo(space, w_type, w_value, w_traceback): + """---Cython extension--- + + Set the exception info, as known from ``sys.exc_info()``. This refers + to an exception that was already caught, not to an exception that was + freshly raised. This function steals the references of the arguments. + To clear the exception state, pass *NULL* for all three arguments. + For general rules about the three arguments, see :c:func:`PyErr_Restore`. + + .. note:: + + This function is not normally used by code that wants to handle + exceptions. Rather, it can be used when code needs to save and + restore the exception state temporarily. Use + :c:func:`PyErr_GetExcInfo` to read the exception state. + """ + if w_value is None or space.is_w(w_value, space.w_None): + operror = None + else: + tb = None + if w_traceback is not None: + try: + tb = pytraceback.check_traceback(space, w_traceback, '?') + except OperationError: # catch and ignore bogus objects + pass + operror = OperationError(w_type, w_value, tb) + # + ec = space.getexecutioncontext() + ec.set_sys_exc_info(operror) + Py_DecRef(space, w_type) + Py_DecRef(space, w_value) + Py_DecRef(space, w_traceback) diff --git a/pypy/module/cpyext/slotdefs.py b/pypy/module/cpyext/slotdefs.py --- a/pypy/module/cpyext/slotdefs.py +++ b/pypy/module/cpyext/slotdefs.py @@ -167,14 +167,16 @@ if rffi.cast(lltype.Signed, res) == -1: space.fromcache(State).check_and_raise_exception(always=True) +# Warning, confusing function name (like CPython). Used only for sq_contains. def wrap_objobjproc(space, w_self, w_args, func): func_target = rffi.cast(objobjproc, func) check_num_args(space, w_args, 1) w_value, = space.fixedview(w_args) res = generic_cpy_call(space, func_target, w_self, w_value) - if rffi.cast(lltype.Signed, res) == -1: + res = rffi.cast(lltype.Signed, res) + if res == -1: space.fromcache(State).check_and_raise_exception(always=True) - return space.wrap(res) + return space.wrap(bool(res)) def wrap_objobjargproc(space, w_self, w_args, func): func_target = rffi.cast(objobjargproc, func) @@ -183,7 +185,7 @@ res = generic_cpy_call(space, func_target, w_self, w_key, w_value) if rffi.cast(lltype.Signed, res) == -1: space.fromcache(State).check_and_raise_exception(always=True) - return space.wrap(res) + return space.w_None def wrap_delitem(space, w_self, w_args, func): func_target = rffi.cast(objobjargproc, func) diff --git a/pypy/module/cpyext/test/foo.c b/pypy/module/cpyext/test/foo.c --- a/pypy/module/cpyext/test/foo.c +++ b/pypy/module/cpyext/test/foo.c @@ -176,6 +176,8 @@ {NULL} /* Sentinel */ }; +PyDoc_STRVAR(foo_doc, "foo is for testing."); + static PyTypeObject footype = { PyVarObject_HEAD_INIT(NULL, 0) "foo.foo", /*tp_name*/ @@ -198,7 +200,7 @@ (setattrofunc)foo_setattro, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ + foo_doc, /*tp_doc*/ 0, /*tp_traverse*/ 0, /*tp_clear*/ 0, /*tp_richcompare*/ diff --git a/pypy/module/cpyext/test/test_pyerrors.py b/pypy/module/cpyext/test/test_pyerrors.py --- a/pypy/module/cpyext/test/test_pyerrors.py +++ b/pypy/module/cpyext/test/test_pyerrors.py @@ -218,3 +218,51 @@ assert e.filename == "blyf" assert e.errno == errno.EBADF assert e.strerror == os.strerror(errno.EBADF) + + def test_GetSetExcInfo(self): + import sys + module = self.import_extension('foo', [ + ("getset_exc_info", "METH_VARARGS", + r''' + PyObject *type, *val, *tb; + PyObject *new_type, *new_val, *new_tb; + PyObject *result; + + if (!PyArg_ParseTuple(args, "OOO", &new_type, &new_val, &new_tb)) + return NULL; + + PyErr_GetExcInfo(&type, &val, &tb); + + Py_INCREF(new_type); + Py_INCREF(new_val); + Py_INCREF(new_tb); + PyErr_SetExcInfo(new_type, new_val, new_tb); + + result = Py_BuildValue("OOO", + type ? type : Py_None, + val ? val : Py_None, + tb ? tb : Py_None); + Py_XDECREF(type); + Py_XDECREF(val); + Py_XDECREF(tb); + return result; + ''' + ), + ]) + try: + raise ValueError(5) + except ValueError, old_exc: + new_exc = TypeError("TEST") + orig_sys_exc_info = sys.exc_info() + orig_exc_info = module.getset_exc_info(new_exc.__class__, + new_exc, None) + new_sys_exc_info = sys.exc_info() + new_exc_info = module.getset_exc_info(*orig_exc_info) + reset_sys_exc_info = sys.exc_info() + + assert orig_exc_info[0] is old_exc.__class__ + assert orig_exc_info[1] is old_exc + assert orig_exc_info == orig_sys_exc_info + assert orig_exc_info == reset_sys_exc_info + assert new_exc_info == (new_exc.__class__, new_exc, None) + assert new_exc_info == new_sys_exc_info diff --git a/pypy/module/cpyext/test/test_typeobject.py b/pypy/module/cpyext/test/test_typeobject.py --- a/pypy/module/cpyext/test/test_typeobject.py +++ b/pypy/module/cpyext/test/test_typeobject.py @@ -20,6 +20,7 @@ assert type(obj) is module.fooType print "type of obj has type", type(type(obj)) print "type of type of obj has type", type(type(type(obj))) + assert module.fooType.__doc__ == "foo is for testing." def test_typeobject_method_descriptor(self): module = self.import_module(name='foo') @@ -414,8 +415,11 @@ static int mp_ass_subscript(PyObject *self, PyObject *key, PyObject *value) { - PyErr_SetNone(PyExc_ZeroDivisionError); - return -1; + if (PyInt_Check(key)) { + PyErr_SetNone(PyExc_ZeroDivisionError); + return -1; + } + return 0; } PyMappingMethods tp_as_mapping; static PyTypeObject Foo_Type = { @@ -425,6 +429,36 @@ ''') obj = module.new_obj() raises(ZeroDivisionError, obj.__setitem__, 5, None) + res = obj.__setitem__('foo', None) + assert res is None + + def test_sq_contains(self): + module = self.import_extension('foo', [ + ("new_obj", "METH_NOARGS", + ''' + PyObject *obj; + Foo_Type.tp_as_sequence = &tp_as_sequence; + tp_as_sequence.sq_contains = sq_contains; + if (PyType_Ready(&Foo_Type) < 0) return NULL; + obj = PyObject_New(PyObject, &Foo_Type); + return obj; + ''' + )], + ''' + static int + sq_contains(PyObject *self, PyObject *value) + { + return 42; + } + PySequenceMethods tp_as_sequence; + static PyTypeObject Foo_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "foo.foo", + }; + ''') + obj = module.new_obj() + res = "foo" in obj + assert res is True def test_tp_iter(self): module = self.import_extension('foo', [ diff --git a/pypy/module/cpyext/typeobject.py b/pypy/module/cpyext/typeobject.py --- a/pypy/module/cpyext/typeobject.py +++ b/pypy/module/cpyext/typeobject.py @@ -307,6 +307,8 @@ if not space.is_true(space.issubtype(self, space.w_type)): self.flag_cpytype = True self.flag_heaptype = False + if pto.c_tp_doc: + self.w_doc = space.wrap(rffi.charp2str(pto.c_tp_doc)) @bootstrap_function def init_typeobject(space): @@ -624,7 +626,6 @@ Creates an interpreter type from a PyTypeObject structure. """ # missing: - # setting __doc__ if not defined and tp_doc defined # inheriting tp_as_* slots # unsupported: # tp_mro, tp_subclasses diff --git a/pypy/module/micronumpy/__init__.py b/pypy/module/micronumpy/__init__.py --- a/pypy/module/micronumpy/__init__.py +++ b/pypy/module/micronumpy/__init__.py @@ -29,6 +29,7 @@ 'flatiter': 'interp_numarray.W_FlatIterator', 'isna': 'interp_numarray.isna', 'concatenate': 'interp_numarray.concatenate', + 'repeat': 'interp_numarray.repeat', 'set_string_function': 'appbridge.set_string_function', diff --git a/pypy/module/micronumpy/interp_numarray.py b/pypy/module/micronumpy/interp_numarray.py --- a/pypy/module/micronumpy/interp_numarray.py +++ b/pypy/module/micronumpy/interp_numarray.py @@ -673,6 +673,10 @@ def compute_first_step(self, sig, frame): pass + @unwrap_spec(repeats=int) + def descr_repeat(self, space, repeats, w_axis=None): + return repeat(space, self, repeats, w_axis) + def convert_to_array(space, w_obj): if isinstance(w_obj, BaseArray): return w_obj @@ -1261,6 +1265,31 @@ return convert_to_array(space, w_obj2).descr_dot(space, w_arr) return w_arr.descr_dot(space, w_obj2) + at unwrap_spec(repeats=int) +def repeat(space, w_arr, repeats, w_axis=None): + arr = convert_to_array(space, w_arr) + if space.is_w(w_axis, space.w_None): + arr = arr.descr_flatten(space).get_concrete() + orig_size = arr.shape[0] + shape = [arr.shape[0] * repeats] + res = W_NDimArray(shape, arr.find_dtype()) + for i in range(repeats): + Chunks([Chunk(i, shape[0] - repeats + i, repeats, + orig_size)]).apply(res).setslice(space, arr) + else: + arr = arr.get_concrete() + axis = space.int_w(w_axis) + shape = arr.shape[:] + chunks = [Chunk(0, i, 1, i) for i in shape] + orig_size = shape[axis] + shape[axis] *= repeats + res = W_NDimArray(shape, arr.find_dtype()) + for i in range(repeats): + chunks[axis] = Chunk(i, shape[axis] - repeats + i, repeats, + orig_size) + Chunks(chunks).apply(res).setslice(space, arr) + return res + @unwrap_spec(axis=int) def concatenate(space, w_args, axis=0): args_w = space.listview(w_args) @@ -1386,6 +1415,7 @@ tolist = interp2app(BaseArray.descr_tolist), take = interp2app(BaseArray.descr_take), compress = interp2app(BaseArray.descr_compress), + repeat = interp2app(BaseArray.descr_repeat), ) diff --git a/pypy/module/micronumpy/signature.py b/pypy/module/micronumpy/signature.py --- a/pypy/module/micronumpy/signature.py +++ b/pypy/module/micronumpy/signature.py @@ -107,6 +107,10 @@ arr.compute_first_step(self, f) return f + def debug_repr(self): + # should be overridden, but in case it isn't, provide a default + return str(self) + class ConcreteSignature(Signature): _immutable_fields_ = ['dtype'] @@ -207,7 +211,7 @@ def _create_iter(self, iterlist, arraylist, arr, transforms): from pypy.module.micronumpy.interp_numarray import VirtualSlice assert isinstance(arr, VirtualSlice) - transforms = transforms + [ViewTransform(arr.chunks)] + transforms = [ViewTransform(arr.chunks)] + transforms self.child._create_iter(iterlist, arraylist, arr.child, transforms) def eval(self, frame, arr): @@ -215,6 +219,9 @@ assert isinstance(arr, VirtualSlice) return self.child.eval(frame, arr.child) + def debug_repr(self): + return 'VirtualSlice(%s)' % self.child.debug_repr() + class Call1(Signature): _immutable_fields_ = ['unfunc', 'name', 'child', 'res', 'dtype'] @@ -270,7 +277,7 @@ from pypy.module.micronumpy.interp_numarray import Call1 assert isinstance(arr, Call1) - vtransforms = transforms + [BroadcastTransform(arr.values.shape)] + vtransforms = [BroadcastTransform(arr.values.shape)] + transforms self.child._create_iter(iterlist, arraylist, arr.values, vtransforms) self.res._create_iter(iterlist, arraylist, arr.res, transforms) @@ -348,7 +355,7 @@ from pypy.module.micronumpy.interp_numarray import ResultArray assert isinstance(arr, ResultArray) - rtransforms = transforms + [BroadcastTransform(arr.left.shape)] + rtransforms = [BroadcastTransform(arr.left.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, transforms) self.right._create_iter(iterlist, arraylist, arr.right, rtransforms) @@ -375,7 +382,7 @@ from pypy.module.micronumpy.interp_numarray import Call2 assert isinstance(arr, Call2) - ltransforms = transforms + [BroadcastTransform(arr.shape)] + ltransforms = [BroadcastTransform(arr.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, ltransforms) self.right._create_iter(iterlist, arraylist, arr.right, transforms) @@ -388,7 +395,7 @@ from pypy.module.micronumpy.interp_numarray import Call2 assert isinstance(arr, Call2) - rtransforms = transforms + [BroadcastTransform(arr.shape)] + rtransforms = [BroadcastTransform(arr.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, transforms) self.right._create_iter(iterlist, arraylist, arr.right, rtransforms) @@ -401,8 +408,8 @@ from pypy.module.micronumpy.interp_numarray import Call2 assert isinstance(arr, Call2) - rtransforms = transforms + [BroadcastTransform(arr.shape)] - ltransforms = transforms + [BroadcastTransform(arr.shape)] + rtransforms = [BroadcastTransform(arr.shape)] + transforms + ltransforms = [BroadcastTransform(arr.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, ltransforms) self.right._create_iter(iterlist, arraylist, arr.right, rtransforms) @@ -424,7 +431,7 @@ frame.cur_value = self.binfunc(self.calc_dtype, frame.cur_value, rval) def debug_repr(self): - return 'ReduceSig(%s)' % (self.name, self.right.debug_repr()) + return 'ReduceSig(%s, %s)' % (self.name, self.right.debug_repr()) class SliceloopSignature(Call2): def eval(self, frame, arr): @@ -448,7 +455,7 @@ from pypy.module.micronumpy.interp_numarray import SliceArray assert isinstance(arr, SliceArray) - rtransforms = transforms + [BroadcastTransform(arr.shape)] + rtransforms = [BroadcastTransform(arr.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, transforms) self.right._create_iter(iterlist, arraylist, arr.right, rtransforms) diff --git a/pypy/module/micronumpy/support.py b/pypy/module/micronumpy/support.py --- a/pypy/module/micronumpy/support.py +++ b/pypy/module/micronumpy/support.py @@ -1,9 +1,9 @@ from pypy.rlib import jit - at jit.look_inside_iff(lambda s: jit.isconstant(len(s))) + at jit.unroll_safe def product(s): i = 1 for x in s: i *= x - return i \ No newline at end of file + return i diff --git a/pypy/module/micronumpy/test/test_numarray.py b/pypy/module/micronumpy/test/test_numarray.py --- a/pypy/module/micronumpy/test/test_numarray.py +++ b/pypy/module/micronumpy/test/test_numarray.py @@ -395,11 +395,19 @@ assert a[3] == 0. def test_newaxis(self): - from _numpypy import array + import math + from _numpypy import array, cos, zeros from numpypy.core.numeric import newaxis a = array(range(5)) b = array([range(5)]) assert (a[newaxis] == b).all() + a = array(range(3)) + b = array([1, 3]) + expected = zeros((3, 2)) + for x in range(3): + for y in range(2): + expected[x, y] = math.cos(a[x]) * math.cos(b[y]) + assert ((cos(a)[:,newaxis] * cos(b).T) == expected).all() def test_newaxis_slice(self): from _numpypy import array @@ -1338,6 +1346,10 @@ dims_disagree = raises(ValueError, concatenate, (a1, b1), axis=0) assert str(dims_disagree.value) == \ "array dimensions must agree except for axis being concatenated" + a = array([1, 2, 3, 4, 5, 6]) + a = (a + a)[::2] + b = concatenate((a[:3], a[-3:])) + assert (b == [2, 6, 10, 2, 6, 10]).all() def test_std(self): from _numpypy import array @@ -1387,6 +1399,16 @@ assert (ones(1) + ones(1)).nbytes == 8 assert array(3.0).nbytes == 8 + def test_repeat(self): + from _numpypy import repeat, array + assert (repeat([[1, 2], [3, 4]], 3) == [1, 1, 1, 2, 2, 2, + 3, 3, 3, 4, 4, 4]).all() + assert (repeat([[1, 2], [3, 4]], 2, axis=0) == [[1, 2], [1, 2], [3, 4], + [3, 4]]).all() + assert (repeat([[1, 2], [3, 4]], 2, axis=1) == [[1, 1, 2, 2], [3, 3, + 4, 4]]).all() + assert (array([1, 2]).repeat(2) == array([1, 1, 2, 2])).all() + class AppTestMultiDim(BaseNumpyAppTest): def test_init(self): diff --git a/pypy/module/pypyjit/test_pypy_c/test_misc.py b/pypy/module/pypyjit/test_pypy_c/test_misc.py --- a/pypy/module/pypyjit/test_pypy_c/test_misc.py +++ b/pypy/module/pypyjit/test_pypy_c/test_misc.py @@ -212,7 +212,7 @@ i19 = int_add(i12, 1) setfield_gc(p9, i19, descr=) guard_nonnull_class(p17, 146982464, descr=...) - i21 = getfield_gc(p17, descr=) + i21 = getfield_gc(p17, descr=) i23 = int_lt(0, i21) guard_true(i23, descr=...) i24 = getfield_gc(p17, descr=) diff --git a/pypy/module/pypyjit/test_pypy_c/test_string.py b/pypy/module/pypyjit/test_pypy_c/test_string.py --- a/pypy/module/pypyjit/test_pypy_c/test_string.py +++ b/pypy/module/pypyjit/test_pypy_c/test_string.py @@ -198,3 +198,37 @@ i49 = call(ConstClass(_ll_2_str_eq_nonnull__rpy_stringPtr_rpy_stringPtr), p35, ConstPtr(ptr48), descr=) guard_value(i49, 1, descr=...) ''') + + def test_remove_duplicate_method_calls(self): + def main(n): + lst = [] + for i in range(n): + s = 'Hello %d' % i + t = s.lower() # ID: callone + u = s.lower() # ID: calltwo + lst.append(t) + lst.append(u) + return len(','.join(lst)) + log = self.run(main, [1000]) + assert log.result == main(1000) + loops = log.loops_by_filename(self.filepath) + loop, = loops + loop.match_by_id('callone', ''' + p114 = call(ConstClass(ll_lower__rpy_stringPtr), p113, descr=) + guard_no_exception(descr=...) + ''') + loop.match_by_id('calltwo', '') # nothing + + def test_move_method_call_out_of_loop(self): + def main(n): + lst = [] + s = 'Hello %d' % n + for i in range(n): + t = s.lower() # ID: callone + lst.append(t) + return len(','.join(lst)) + log = self.run(main, [1000]) + assert log.result == main(1000) + loops = log.loops_by_filename(self.filepath) + loop, = loops + loop.match_by_id('callone', '') # nothing diff --git a/pypy/module/sys/test/test_sysmodule.py b/pypy/module/sys/test/test_sysmodule.py --- a/pypy/module/sys/test/test_sysmodule.py +++ b/pypy/module/sys/test/test_sysmodule.py @@ -600,30 +600,33 @@ class AppTestSysExcInfoDirect: def setup_method(self, meth): - self.seen = [] - from pypy.module.sys import vm - def exc_info_with_tb(*args): - self.seen.append("n") # not optimized - return self.old[0](*args) - def exc_info_without_tb(*args): - self.seen.append("y") # optimized - return self.old[1](*args) - self.old = [vm.exc_info_with_tb, vm.exc_info_without_tb] - vm.exc_info_with_tb = exc_info_with_tb - vm.exc_info_without_tb = exc_info_without_tb - # - from pypy.rlib import jit - self.old2 = [jit.we_are_jitted] - jit.we_are_jitted = lambda: True + self.checking = not option.runappdirect + if self.checking: + self.seen = [] + from pypy.module.sys import vm + def exc_info_with_tb(*args): + self.seen.append("n") # not optimized + return self.old[0](*args) + def exc_info_without_tb(*args): + self.seen.append("y") # optimized + return self.old[1](*args) + self.old = [vm.exc_info_with_tb, vm.exc_info_without_tb] + vm.exc_info_with_tb = exc_info_with_tb + vm.exc_info_without_tb = exc_info_without_tb + # + from pypy.rlib import jit + self.old2 = [jit.we_are_jitted] + jit.we_are_jitted = lambda: True def teardown_method(self, meth): - from pypy.module.sys import vm - from pypy.rlib import jit - vm.exc_info_with_tb = self.old[0] - vm.exc_info_without_tb = self.old[1] - jit.we_are_jitted = self.old2[0] - # - assert ''.join(self.seen) == meth.expected + if self.checking: + from pypy.module.sys import vm + from pypy.rlib import jit + vm.exc_info_with_tb = self.old[0] + vm.exc_info_without_tb = self.old[1] + jit.we_are_jitted = self.old2[0] + # + assert ''.join(self.seen) == meth.expected def test_returns_none(self): import sys diff --git a/pypy/module/test_lib_pypy/test_datetime.py b/pypy/module/test_lib_pypy/test_datetime.py --- a/pypy/module/test_lib_pypy/test_datetime.py +++ b/pypy/module/test_lib_pypy/test_datetime.py @@ -44,3 +44,9 @@ assert type(dt.microsecond) is int copy.copy(dt) + +def test_radd(): + class X(object): + def __radd__(self, other): + return "radd" + assert datetime.date(10, 10, 10) + X() == "radd" diff --git a/pypy/module/thread/test/test_ll_thread.py b/pypy/module/thread/test/test_ll_thread.py --- a/pypy/module/thread/test/test_ll_thread.py +++ b/pypy/module/thread/test/test_ll_thread.py @@ -66,7 +66,6 @@ def test_gc_locking(self): import time from pypy.rlib.objectmodel import invoke_around_extcall - from pypy.rlib.objectmodel import we_are_translated from pypy.rlib.debug import ll_assert class State: @@ -129,8 +128,6 @@ state.finished = 0 # the next line installs before_extcall() and after_extcall() # to be called automatically around external function calls. - # When not translated it does not work around time.sleep(), - # so we have to call them manually for this test. invoke_around_extcall(before_extcall, after_extcall) g(10, 1) @@ -142,13 +139,9 @@ willing_to_wait_more -= 1 done = len(state.answers) == expected - if not we_are_translated(): before_extcall() time.sleep(0.01) - if not we_are_translated(): after_extcall() - if not we_are_translated(): before_extcall() time.sleep(0.1) - if not we_are_translated(): after_extcall() return len(state.answers) @@ -160,12 +153,11 @@ answers = fn() assert answers == expected -class TestRunDirectly(AbstractThreadTests): - def getcompiled(self, f, argtypes): - return f - - def test_start_new_thread(self): - py.test.skip("deadlocks occasionally -- why???") +#class TestRunDirectly(AbstractThreadTests): +# def getcompiled(self, f, argtypes): +# return f +# These are disabled because they crash occasionally for bad reasons +# related to the fact that ll2ctypes is not at all thread-safe class TestUsingBoehm(AbstractThreadTests): gcpolicy = 'boehm' diff --git a/pypy/objspace/descroperation.py b/pypy/objspace/descroperation.py --- a/pypy/objspace/descroperation.py +++ b/pypy/objspace/descroperation.py @@ -43,6 +43,13 @@ return w_eq type_eq._annspecialcase_ = 'specialize:memo' +def list_iter(space): + "Utility that returns the app-level descriptor list.__iter__." + w_src, w_iter = space.lookup_in_type_where(space.w_list, + '__iter__') + return w_iter +list_iter._annspecialcase_ = 'specialize:memo' + def raiseattrerror(space, w_obj, name, w_descr=None): w_type = space.type(w_obj) typename = w_type.getname(space) diff --git a/pypy/objspace/fake/objspace.py b/pypy/objspace/fake/objspace.py --- a/pypy/objspace/fake/objspace.py +++ b/pypy/objspace/fake/objspace.py @@ -86,6 +86,7 @@ return s_None def specialize_call(self, hop): + hop.exception_cannot_occur() return hop.inputconst(lltype.Void, None) # ____________________________________________________________ diff --git a/pypy/objspace/std/dictmultiobject.py b/pypy/objspace/std/dictmultiobject.py --- a/pypy/objspace/std/dictmultiobject.py +++ b/pypy/objspace/std/dictmultiobject.py @@ -247,7 +247,7 @@ return 0 def iter(self, w_dict): - return EmptyIteratorImplementation(self.space, w_dict) + return EmptyIteratorImplementation(self.space, self, w_dict) def clear(self, w_dict): return @@ -263,8 +263,9 @@ # Iterator Implementation base classes class IteratorImplementation(object): - def __init__(self, space, implementation): + def __init__(self, space, strategy, implementation): self.space = space + self.strategy = strategy self.dictimplementation = implementation self.len = implementation.length() self.pos = 0 @@ -280,7 +281,20 @@ if self.pos < self.len: result = self.next_entry() self.pos += 1 - return result + if self.strategy is self.dictimplementation.strategy: + return result # common case + else: + # waaa, obscure case: the strategy changed, but not the + # length of the dict. The (key, value) pair in 'result' + # might be out-of-date. We try to explicitly look up + # the key in the dict. + w_key = result[0] + w_value = self.dictimplementation.getitem(w_key) + if w_value is None: + self.len = -1 # Make this error state sticky + raise OperationError(self.space.w_RuntimeError, + self.space.wrap("dictionary changed during iteration")) + return (w_key, w_value) # no more entries self.dictimplementation = None return None, None @@ -489,7 +503,7 @@ _mixin_ = True def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) + IteratorImplementation.__init__(self, space, strategy, dictimplementation) self.iterator = strategy.unerase(dictimplementation.dstorage).iteritems() def next_entry(self): @@ -503,7 +517,7 @@ _mixin_ = True def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) + IteratorImplementation.__init__(self, space, strategy, dictimplementation) self.iterator = strategy.unerase(dictimplementation.dstorage).iteritems() def next_entry(self): diff --git a/pypy/objspace/std/dictproxyobject.py b/pypy/objspace/std/dictproxyobject.py --- a/pypy/objspace/std/dictproxyobject.py +++ b/pypy/objspace/std/dictproxyobject.py @@ -20,7 +20,17 @@ def getitem(self, w_dict, w_key): space = self.space w_lookup_type = space.type(w_key) - if space.is_w(w_lookup_type, space.w_str): + if (space.is_w(w_lookup_type, space.w_str) or # Most common path first + space.abstract_issubclass_w(w_lookup_type, space.w_str)): + return self.getitem_str(w_dict, space.str_w(w_key)) + elif space.abstract_issubclass_w(w_lookup_type, space.w_unicode): + try: + w_key = space.str(w_key) + except OperationError, e: + if not e.match(space, space.w_UnicodeEncodeError): + raise + # non-ascii unicode is never equal to a byte string + return None return self.getitem_str(w_dict, space.str_w(w_key)) else: return None diff --git a/pypy/objspace/std/objspace.py b/pypy/objspace/std/objspace.py --- a/pypy/objspace/std/objspace.py +++ b/pypy/objspace/std/objspace.py @@ -439,6 +439,8 @@ t = w_obj.getitems() elif isinstance(w_obj, W_AbstractTupleObject): t = w_obj.getitems_copy() + elif isinstance(w_obj, W_ListObject) and self._uses_list_iter(w_obj): + t = w_obj.getitems() else: return ObjSpace.unpackiterable(self, w_obj, expected_length) if expected_length != -1 and len(t) != expected_length: @@ -456,6 +458,8 @@ return w_obj.listview_str() if isinstance(w_obj, W_StringObject): return w_obj.listview_str() + if isinstance(w_obj, W_ListObject) and self._uses_list_iter(w_obj): + return w_obj.getitems_str() return None def listview_int(self, w_obj): @@ -465,8 +469,14 @@ return w_obj.listview_int() if type(w_obj) is W_SetObject or type(w_obj) is W_FrozensetObject: return w_obj.listview_int() + if isinstance(w_obj, W_ListObject) and self._uses_list_iter(w_obj): + return w_obj.getitems_int() return None + def _uses_list_iter(self, w_obj): + from pypy.objspace.descroperation import list_iter + return self.lookup(w_obj, '__iter__') is list_iter(self) + def sliceindices(self, w_slice, w_length): if isinstance(w_slice, W_SliceObject): a, b, c = w_slice.indices3(self, self.int_w(w_length)) diff --git a/pypy/objspace/std/setobject.py b/pypy/objspace/std/setobject.py --- a/pypy/objspace/std/setobject.py +++ b/pypy/objspace/std/setobject.py @@ -359,7 +359,7 @@ w_set.sstorage = w_other.get_storage_copy() def iter(self, w_set): - return EmptyIteratorImplementation(self.space, w_set) + return EmptyIteratorImplementation(self.space, self, w_set) def popitem(self, w_set): raise OperationError(self.space.w_KeyError, @@ -784,8 +784,9 @@ d_obj[w_item] = None class IteratorImplementation(object): - def __init__(self, space, implementation): + def __init__(self, space, strategy, implementation): self.space = space + self.strategy = strategy self.setimplementation = implementation self.len = implementation.length() self.pos = 0 @@ -801,7 +802,17 @@ if self.pos < self.len: result = self.next_entry() self.pos += 1 - return result + if self.strategy is self.setimplementation.strategy: + return result # common case + else: + # waaa, obscure case: the strategy changed, but not the + # length of the set. The 'result' might be out-of-date. + # We try to explicitly look it up in the set. + if not self.setimplementation.has_key(result): + self.len = -1 # Make this error state sticky + raise OperationError(self.space.w_RuntimeError, + self.space.wrap("dictionary changed during iteration")) + return result # no more entries self.setimplementation = None return None @@ -823,7 +834,7 @@ class StringIteratorImplementation(IteratorImplementation): def __init__(self, space, strategy, w_set): - IteratorImplementation.__init__(self, space, w_set) + IteratorImplementation.__init__(self, space, strategy, w_set) d = strategy.unerase(w_set.sstorage) self.iterator = d.iterkeys() @@ -835,9 +846,9 @@ class IntegerIteratorImplementation(IteratorImplementation): #XXX same implementation in dictmultiobject on dictstrategy-branch - def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) - d = strategy.unerase(dictimplementation.sstorage) + def __init__(self, space, strategy, w_set): + IteratorImplementation.__init__(self, space, strategy, w_set) + d = strategy.unerase(w_set.sstorage) self.iterator = d.iterkeys() def next_entry(self): @@ -848,9 +859,9 @@ return None class RDictIteratorImplementation(IteratorImplementation): - def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) - d = strategy.unerase(dictimplementation.sstorage) + def __init__(self, space, strategy, w_set): + IteratorImplementation.__init__(self, space, strategy, w_set) + d = strategy.unerase(w_set.sstorage) self.iterator = d.iterkeys() def next_entry(self): diff --git a/pypy/objspace/std/test/test_dictmultiobject.py b/pypy/objspace/std/test/test_dictmultiobject.py --- a/pypy/objspace/std/test/test_dictmultiobject.py +++ b/pypy/objspace/std/test/test_dictmultiobject.py @@ -804,6 +804,33 @@ assert "IntDictStrategy" in self.get_strategy(d) assert d[1L] == "hi" + def test_iter_dict_length_change(self): + d = {1: 2, 3: 4, 5: 6} + it = d.iteritems() + d[7] = 8 + # 'd' is now length 4 + raises(RuntimeError, it.next) + + def test_iter_dict_strategy_only_change_1(self): + d = {1: 2, 3: 4, 5: 6} + it = d.iteritems() + class Foo(object): + def __eq__(self, other): + return False + assert d.get(Foo()) is None # this changes the strategy of 'd' + lst = list(it) # but iterating still works + assert sorted(lst) == [(1, 2), (3, 4), (5, 6)] + + def test_iter_dict_strategy_only_change_2(self): + d = {1: 2, 3: 4, 5: 6} + it = d.iteritems() + d['foo'] = 'bar' + del d[1] + # 'd' is still length 3, but its strategy changed. we are + # getting a RuntimeError because iterating over the old storage + # gives us (1, 2), but 1 is not in the dict any longer. + raises(RuntimeError, list, it) + class FakeString(str): hash_count = 0 diff --git a/pypy/objspace/std/test/test_dictproxy.py b/pypy/objspace/std/test/test_dictproxy.py --- a/pypy/objspace/std/test/test_dictproxy.py +++ b/pypy/objspace/std/test/test_dictproxy.py @@ -25,6 +25,16 @@ key, value = NotEmpty.__dict__.popitem() assert (key == 'a' and value == 1) or (key == 'b' and value == 4) + def test_dictproxy_getitem(self): + class NotEmpty(object): + a = 1 + assert 'a' in NotEmpty.__dict__ + class substr(str): pass + assert substr('a') in NotEmpty.__dict__ + assert u'a' in NotEmpty.__dict__ + assert NotEmpty.__dict__[u'a'] == 1 + assert u'\xe9' not in NotEmpty.__dict__ + def test_dictproxyeq(self): class a(object): pass diff --git a/pypy/objspace/std/test/test_listobject.py b/pypy/objspace/std/test/test_listobject.py --- a/pypy/objspace/std/test/test_listobject.py +++ b/pypy/objspace/std/test/test_listobject.py @@ -1186,14 +1186,23 @@ # of dicts, because the OrderedDict in the stdlib relies on this. # we extend the use case to lists and sets, i.e. all types that have # strategies, to avoid surprizes depending on the strategy. - for base, arg in [(list, []), (list, [5]), (list, ['x']), - (set, []), (set, [5]), (set, ['x']), - (dict, []), (dict, [(5,6)]), (dict, [('x',7)])]: + class X: pass + for base, arg in [ + (list, []), (list, [5]), (list, ['x']), (list, [X]), + (set, []), (set, [5]), (set, ['x']), (set, [X]), + (dict, []), (dict, [(5,6)]), (dict, [('x',7)]), (dict, [(X,8)]), + ]: print base, arg class SubClass(base): def __iter__(self): return iter("foobar") assert list(SubClass(arg)) == ['f', 'o', 'o', 'b', 'a', 'r'] + class Sub2(base): + pass + assert list(Sub2(arg)) == list(base(arg)) + s = set() + s.update(Sub2(arg)) + assert s == set(base(arg)) class AppTestForRangeLists(AppTestW_ListObject): diff --git a/pypy/objspace/std/test/test_setobject.py b/pypy/objspace/std/test/test_setobject.py --- a/pypy/objspace/std/test/test_setobject.py +++ b/pypy/objspace/std/test/test_setobject.py @@ -907,3 +907,30 @@ return [5, 3, 4][i] s = set([10,3,2]).intersection(Obj()) assert list(s) == [3] + + def test_iter_set_length_change(self): + s = set([1, 3, 5]) + it = iter(s) + s.add(7) + # 's' is now length 4 + raises(RuntimeError, it.next) + + def test_iter_set_strategy_only_change_1(self): + s = set([1, 3, 5]) + it = iter(s) + class Foo(object): + def __eq__(self, other): + return False + assert Foo() not in s # this changes the strategy of 'd' + lst = list(s) # but iterating still works + assert sorted(lst) == [1, 3, 5] + + def test_iter_set_strategy_only_change_2(self): + s = set([1, 3, 5]) + it = iter(s) + s.add('foo') + s.remove(1) + # 's' is still length 3, but its strategy changed. we are + # getting a RuntimeError because iterating over the old storage + # gives us 1, but 1 is not in the set any longer. + raises(RuntimeError, list, it) diff --git a/pypy/rlib/jit.py b/pypy/rlib/jit.py --- a/pypy/rlib/jit.py +++ b/pypy/rlib/jit.py @@ -382,7 +382,7 @@ pass def specialize_call(self, hop): - pass + hop.exception_cannot_occur() vref_None = non_virtual_ref(None) diff --git a/pypy/rlib/jit_hooks.py b/pypy/rlib/jit_hooks.py --- a/pypy/rlib/jit_hooks.py +++ b/pypy/rlib/jit_hooks.py @@ -22,6 +22,7 @@ c_name = hop.inputconst(lltype.Void, 'access_helper') args_v = [hop.inputarg(arg, arg=i) for i, arg in enumerate(hop.args_r)] + hop.exception_cannot_occur() return hop.genop('jit_marker', [c_name, c_func] + args_v, resulttype=hop.r_result) return helper diff --git a/pypy/rlib/objectmodel.py b/pypy/rlib/objectmodel.py --- a/pypy/rlib/objectmodel.py +++ b/pypy/rlib/objectmodel.py @@ -215,6 +215,7 @@ def specialize_call(self, hop): from pypy.rpython.lltypesystem import lltype + hop.exception_cannot_occur() return hop.inputconst(lltype.Bool, hop.s_result.const) # ____________________________________________________________ @@ -397,6 +398,7 @@ r_obj, = hop.args_r v_obj, = hop.inputargs(r_obj) ll_fn = r_obj.get_ll_hash_function() + hop.exception_is_here() return hop.gendirectcall(ll_fn, v_obj) class Entry(ExtRegistryEntry): @@ -419,6 +421,7 @@ from pypy.rpython.error import TyperError raise TyperError("compute_identity_hash() cannot be applied to" " %r" % (vobj.concretetype,)) + hop.exception_cannot_occur() return hop.genop('gc_identityhash', [vobj], resulttype=lltype.Signed) class Entry(ExtRegistryEntry): @@ -441,6 +444,7 @@ from pypy.rpython.error import TyperError raise TyperError("compute_unique_id() cannot be applied to" " %r" % (vobj.concretetype,)) + hop.exception_cannot_occur() return hop.genop('gc_id', [vobj], resulttype=lltype.Signed) class Entry(ExtRegistryEntry): @@ -452,6 +456,7 @@ def specialize_call(self, hop): vobj, = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() if hop.rtyper.type_system.name == 'lltypesystem': from pypy.rpython.lltypesystem import lltype if isinstance(vobj.concretetype, lltype.Ptr): diff --git a/pypy/rlib/rbigint.py b/pypy/rlib/rbigint.py --- a/pypy/rlib/rbigint.py +++ b/pypy/rlib/rbigint.py @@ -85,7 +85,7 @@ s_DIGIT = self.bookkeeper.valueoftype(type(NULLDIGIT)) assert s_DIGIT.contains(s_list.listdef.listitem.s_value) def specialize_call(self, hop): - pass + hop.exception_cannot_occur() class rbigint(object): diff --git a/pypy/rlib/rerased.py b/pypy/rlib/rerased.py --- a/pypy/rlib/rerased.py +++ b/pypy/rlib/rerased.py @@ -100,6 +100,7 @@ def specialize_call(self, hop): bk = hop.rtyper.annotator.bookkeeper s_obj = identity.get_input_annotation(bk) + hop.exception_cannot_occur() return hop.r_result.rtype_erase(hop, s_obj) class Entry(ExtRegistryEntry): @@ -110,6 +111,7 @@ return identity.leave_tunnel(self.bookkeeper) def specialize_call(self, hop): + hop.exception_cannot_occur() if hop.r_result.lowleveltype is lltype.Void: return hop.inputconst(lltype.Void, None) [v] = hop.inputargs(hop.args_r[0]) @@ -214,6 +216,7 @@ return hop.genop('cast_opaque_ptr', [v], resulttype=hop.r_result) def rtype_unerase_int(self, hop, v): + hop.exception_cannot_occur() return hop.gendirectcall(ll_unerase_int, v) def rtype_erase_int(self, hop): @@ -264,6 +267,7 @@ def rtype_unerase_int(self, hop, v): c_one = hop.inputconst(lltype.Signed, 1) + hop.exception_cannot_occur() v2 = hop.genop('oounbox_int', [v], resulttype=hop.r_result) return hop.genop('int_rshift', [v2, c_one], resulttype=lltype.Signed) diff --git a/pypy/rlib/rgc.py b/pypy/rlib/rgc.py --- a/pypy/rlib/rgc.py +++ b/pypy/rlib/rgc.py @@ -382,6 +382,7 @@ def compute_result_annotation(self): return s_list_of_gcrefs() def specialize_call(self, hop): + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_roots', [], resulttype = hop.r_result) class Entry(ExtRegistryEntry): @@ -392,6 +393,7 @@ return s_list_of_gcrefs() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_referents', vlist, resulttype = hop.r_result) @@ -402,6 +404,7 @@ return annmodel.SomeInteger() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_memory_usage', vlist, resulttype = hop.r_result) @@ -412,6 +415,7 @@ return annmodel.SomeInteger() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_type_index', vlist, resulttype = hop.r_result) @@ -430,6 +434,7 @@ return annmodel.SomeBool() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_is_rpy_instance', vlist, resulttype = hop.r_result) @@ -449,6 +454,7 @@ classrepr = getclassrepr(hop.rtyper, classdef) vtable = classrepr.getvtable() assert lltype.typeOf(vtable) == rclass.CLASSTYPE + hop.exception_cannot_occur() return Constant(vtable, concretetype=rclass.CLASSTYPE) class Entry(ExtRegistryEntry): diff --git a/pypy/rlib/rstring.py b/pypy/rlib/rstring.py --- a/pypy/rlib/rstring.py +++ b/pypy/rlib/rstring.py @@ -245,5 +245,5 @@ raise ValueError("Value is not no_nul") def specialize_call(self, hop): - pass + hop.exception_cannot_occur() diff --git a/pypy/rpython/controllerentry.py b/pypy/rpython/controllerentry.py --- a/pypy/rpython/controllerentry.py +++ b/pypy/rpython/controllerentry.py @@ -201,6 +201,7 @@ def specialize_call(self, hop): from pypy.rpython.lltypesystem import lltype assert hop.s_result.is_constant() + hop.exception_cannot_occur() return hop.inputconst(lltype.Bool, hop.s_result.const) # ____________________________________________________________ diff --git a/pypy/rpython/lltypesystem/lloperation.py b/pypy/rpython/lltypesystem/lloperation.py --- a/pypy/rpython/lltypesystem/lloperation.py +++ b/pypy/rpython/lltypesystem/lloperation.py @@ -130,6 +130,7 @@ def specialize_call(self, hop): from pypy.rpython.lltypesystem import lltype + hop.exception_cannot_occur() return hop.inputconst(lltype.Void, None) def enum_ops_without_sideeffects(raising_is_ok=False): diff --git a/pypy/rpython/lltypesystem/rbuiltin.py b/pypy/rpython/lltypesystem/rbuiltin.py --- a/pypy/rpython/lltypesystem/rbuiltin.py +++ b/pypy/rpython/lltypesystem/rbuiltin.py @@ -9,6 +9,7 @@ from pypy.rpython.rbool import bool_repr def rtype_builtin_isinstance(hop): + hop.exception_cannot_occur() if hop.s_result.is_constant(): return hop.inputconst(lltype.Bool, hop.s_result.const) if hop.args_r[0] == pyobj_repr or hop.args_r[1] == pyobj_repr: @@ -33,6 +34,7 @@ return my_instantiate() def rtype_instantiate(hop): + hop.exception_cannot_occur() s_class = hop.args_s[0] assert isinstance(s_class, annmodel.SomePBC) if len(s_class.descriptions) != 1: @@ -46,6 +48,7 @@ return rclass.rtype_new_instance(hop.rtyper, classdef, hop.llops) def rtype_builtin_hasattr(hop): + hop.exception_cannot_occur() if hop.s_result.is_constant(): return hop.inputconst(lltype.Bool, hop.s_result.const) if hop.args_r[0] == pyobj_repr: @@ -56,6 +59,7 @@ raise TyperError("hasattr is only suported on a constant or on PyObject") def rtype_builtin___import__(hop): + xxx # should not be used any more args_v = hop.inputargs(*[pyobj_repr for ign in hop.args_r]) c = hop.inputconst(pyobj_repr, __import__) return hop.genop('simple_call', [c] + args_v, resulttype = pyobj_repr) diff --git a/pypy/rpython/lltypesystem/rclass.py b/pypy/rpython/lltypesystem/rclass.py --- a/pypy/rpython/lltypesystem/rclass.py +++ b/pypy/rpython/lltypesystem/rclass.py @@ -746,4 +746,5 @@ assert isinstance(TYPE, GcStruct) assert lltype._castdepth(TYPE, OBJECT) > 0 hop.rtyper.set_type_for_typeptr(vtable, TYPE) + hop.exception_cannot_occur() return hop.inputconst(lltype.Void, None) diff --git a/pypy/rpython/lltypesystem/rtuple.py b/pypy/rpython/lltypesystem/rtuple.py --- a/pypy/rpython/lltypesystem/rtuple.py +++ b/pypy/rpython/lltypesystem/rtuple.py @@ -55,6 +55,7 @@ vtup = hop.inputarg(self, 0) LIST = hop.r_result.lowleveltype.TO cno = inputconst(Signed, nitems) + hop.exception_is_here() vlist = hop.gendirectcall(LIST.ll_newlist, cno) v_func = hop.inputconst(Void, rlist.dum_nocheck) for index in range(nitems): diff --git a/pypy/rpython/module/r_os_stat.py b/pypy/rpython/module/r_os_stat.py --- a/pypy/rpython/module/r_os_stat.py +++ b/pypy/rpython/module/r_os_stat.py @@ -65,4 +65,5 @@ r_StatResult = hop.rtyper.getrepr(ll_os_stat.s_StatResult) [v_result] = hop.inputargs(r_StatResult.r_tuple) # no-op conversion from r_StatResult.r_tuple to r_StatResult + hop.exception_cannot_occur() return v_result diff --git a/pypy/rpython/ootypesystem/ooregistry.py b/pypy/rpython/ootypesystem/ooregistry.py --- a/pypy/rpython/ootypesystem/ooregistry.py +++ b/pypy/rpython/ootypesystem/ooregistry.py @@ -22,6 +22,7 @@ annmodel.SomeOOInstance, annmodel.SomeString)) vlist = hop.inputargs(hop.args_r[0], ootype.Signed) + hop.exception_cannot_occur() return hop.genop('oostring', vlist, resulttype = ootype.String) class Entry_oounicode(ExtRegistryEntry): @@ -38,6 +39,7 @@ assert isinstance(hop.args_s[0], (annmodel.SomeUnicodeCodePoint, annmodel.SomeOOInstance)) vlist = hop.inputargs(hop.args_r[0], ootype.Signed) + hop.exception_cannot_occur() return hop.genop('oounicode', vlist, resulttype = ootype.Unicode) diff --git a/pypy/rpython/ootypesystem/rbuiltin.py b/pypy/rpython/ootypesystem/rbuiltin.py --- a/pypy/rpython/ootypesystem/rbuiltin.py +++ b/pypy/rpython/ootypesystem/rbuiltin.py @@ -7,12 +7,14 @@ from pypy.rpython.error import TyperError def rtype_new(hop): + hop.exception_cannot_occur() assert hop.args_s[0].is_constant() vlist = hop.inputargs(ootype.Void) return hop.genop('new', vlist, resulttype = hop.r_result.lowleveltype) def rtype_oonewarray(hop): + hop.exception_cannot_occur() assert hop.args_s[0].is_constant() vlist = hop.inputarg(ootype.Void, arg=0) vlength = hop.inputarg(ootype.Signed, arg=1) @@ -20,23 +22,27 @@ resulttype = hop.r_result.lowleveltype) def rtype_null(hop): + hop.exception_cannot_occur() assert hop.args_s[0].is_constant() TYPE = hop.args_s[0].const nullvalue = ootype.null(TYPE) return hop.inputconst(TYPE, nullvalue) def rtype_classof(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0], annmodel.SomeOOInstance) vlist = hop.inputargs(hop.args_r[0]) return hop.genop('classof', vlist, resulttype = ootype.Class) def rtype_subclassof(hop): + hop.exception_cannot_occur() vlist = hop.inputargs(rootype.ooclass_repr, rootype.ooclass_repr) return hop.genop('subclassof', vlist, resulttype = ootype.Bool) def rtype_instanceof(hop): + hop.exception_cannot_occur() INSTANCE = hop.args_v[1].value v_inst = hop.inputarg(hop.args_r[0], arg=0) c_cls = hop.inputconst(ootype.Void, INSTANCE) @@ -44,23 +50,27 @@ resulttype=ootype.Bool) def rtype_runtimenew(hop): + hop.exception_cannot_occur() vlist = hop.inputargs(rootype.ooclass_repr) return hop.genop('runtimenew', vlist, resulttype = hop.r_result.lowleveltype) def rtype_ooupcast(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0].const, ootype.Instance) assert isinstance(hop.args_s[1], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[1], arg=1) return hop.genop('ooupcast', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_oodowncast(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0].const, ootype.Instance) assert isinstance(hop.args_s[1], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[1], arg=1) return hop.genop('oodowncast', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_cast_to_object(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0], annmodel.SomeOOStaticMeth) or \ isinstance(hop.args_s[0], annmodel.SomeOOClass) or \ isinstance(hop.args_s[0].ootype, ootype.OOType) @@ -68,12 +78,14 @@ return hop.genop('cast_to_object', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_cast_from_object(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0].const, ootype.OOType) assert isinstance(hop.args_s[1], annmodel.SomeOOObject) v_inst = hop.inputarg(hop.args_r[1], arg=1) return hop.genop('cast_from_object', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_builtin_isinstance(hop): + hop.exception_cannot_occur() if hop.s_result.is_constant(): return hop.inputconst(ootype.Bool, hop.s_result.const) @@ -99,6 +111,7 @@ return ootype.subclassof(c1, class_) def rtype_instantiate(hop): + hop.exception_cannot_occur() if hop.args_s[0].is_constant(): ## INSTANCE = hop.s_result.rtyper_makerepr(hop.rtyper).lowleveltype ## v_instance = hop.inputconst(ootype.Void, INSTANCE) diff --git a/pypy/rpython/ootypesystem/rtuple.py b/pypy/rpython/ootypesystem/rtuple.py --- a/pypy/rpython/ootypesystem/rtuple.py +++ b/pypy/rpython/ootypesystem/rtuple.py @@ -39,6 +39,7 @@ RESULT = hop.r_result.lowleveltype c_resulttype = inputconst(ootype.Void, RESULT) c_length = inputconst(ootype.Signed, len(self.items_r)) + hop.exception_is_here() if isinstance(RESULT, ootype.Array): v_list = hop.genop('oonewarray', [c_resulttype, c_length], resulttype=RESULT) else: diff --git a/pypy/rpython/rbool.py b/pypy/rpython/rbool.py --- a/pypy/rpython/rbool.py +++ b/pypy/rpython/rbool.py @@ -34,6 +34,7 @@ def rtype_float(_, hop): vlist = hop.inputargs(Float) + hop.exception_cannot_occur() return vlist[0] # diff --git a/pypy/rpython/rbuiltin.py b/pypy/rpython/rbuiltin.py --- a/pypy/rpython/rbuiltin.py +++ b/pypy/rpython/rbuiltin.py @@ -111,25 +111,32 @@ raise TyperError("don't know about built-in function %r" % ( self.builtinfunc,)) + def _call(self, hop2, **kwds_i): + bltintyper = self.findbltintyper(hop2.rtyper) + hop2.llops._called_exception_is_here_or_cannot_occur = False + v_result = bltintyper(hop2, **kwds_i) + if not hop2.llops._called_exception_is_here_or_cannot_occur: + raise TyperError("missing hop.exception_cannot_occur() or " + "hop.exception_is_here() in %s" % bltintyper) + return v_result + def rtype_simple_call(self, hop): - bltintyper = self.findbltintyper(hop.rtyper) hop2 = hop.copy() hop2.r_s_popfirstarg() - return bltintyper(hop2) + return self._call(hop2) def rtype_call_args(self, hop): # calling a built-in function with keyword arguments: # mostly for rpython.objectmodel.hint() hop, kwds_i = call_args_expand(hop) - bltintyper = self.findbltintyper(hop.rtyper) hop2 = hop.copy() hop2.r_s_popfirstarg() hop2.r_s_popfirstarg() # the RPython-level keyword args are passed with an 'i_' prefix and # the corresponding value is an *index* in the hop2 arguments, # to be used with hop.inputarg(arg=..) - return bltintyper(hop2, **kwds_i) + return self._call(hop2, **kwds_i) class BuiltinMethodRepr(Repr): @@ -198,6 +205,7 @@ # ____________________________________________________________ def rtype_builtin_bool(hop): + # not called any more? assert hop.nb_args == 1 return hop.args_r[0].rtype_is_true(hop) @@ -241,6 +249,7 @@ def rtype_builtin_min(hop): v1, v2 = hop.inputargs(hop.r_result, hop.r_result) + hop.exception_cannot_occur() return hop.gendirectcall(ll_min, v1, v2) def ll_min(i1, i2): @@ -250,6 +259,7 @@ def rtype_builtin_max(hop): v1, v2 = hop.inputargs(hop.r_result, hop.r_result) + hop.exception_cannot_occur() return hop.gendirectcall(ll_max, v1, v2) def ll_max(i1, i2): @@ -264,6 +274,7 @@ pass def rtype_OSError__init__(hop): + hop.exception_cannot_occur() if hop.nb_args == 2: raise TyperError("OSError() should not be called with " "a single argument") @@ -274,6 +285,7 @@ r_self.setfield(v_self, 'errno', v_errno, hop.llops) def rtype_WindowsError__init__(hop): + hop.exception_cannot_occur() if hop.nb_args == 2: raise TyperError("WindowsError() should not be called with " "a single argument") @@ -442,6 +454,7 @@ assert hop.args_s[0].is_constant() TGT = hop.args_s[0].const v_type, v_value = hop.inputargs(lltype.Void, hop.args_r[1]) + hop.exception_cannot_occur() return gen_cast(hop.llops, TGT, v_value) _cast_to_Signed = { @@ -523,11 +536,13 @@ def rtype_identity_hash(hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_identityhash', vlist, resulttype=lltype.Signed) def rtype_runtime_type_info(hop): assert isinstance(hop.args_r[0], rptr.PtrRepr) vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('runtime_type_info', vlist, resulttype = hop.r_result.lowleveltype) @@ -558,6 +573,7 @@ def rtype_raw_malloc(hop): v_size, = hop.inputargs(lltype.Signed) + hop.exception_cannot_occur() return hop.genop('raw_malloc', [v_size], resulttype=llmemory.Address) def rtype_raw_malloc_usage(hop): @@ -586,6 +602,7 @@ if s_addr.is_null_address(): raise TyperError("raw_memclear(x, n) where x is the constant NULL") v_list = hop.inputargs(llmemory.Address, lltype.Signed) + hop.exception_cannot_occur() return hop.genop('raw_memclear', v_list) BUILTIN_TYPER[llmemory.raw_malloc] = rtype_raw_malloc @@ -596,6 +613,7 @@ def rtype_offsetof(hop): TYPE, field = hop.inputargs(lltype.Void, lltype.Void) + hop.exception_cannot_occur() return hop.inputconst(lltype.Signed, llmemory.offsetof(TYPE.value, field.value)) @@ -605,6 +623,7 @@ # non-gc objects def rtype_free_non_gc_object(hop): + hop.exception_cannot_occur() vinst, = hop.inputargs(hop.args_r[0]) flavor = hop.args_r[0].gcflavor assert flavor != 'gc' @@ -617,6 +636,7 @@ # keepalive_until_here def rtype_keepalive_until_here(hop): + hop.exception_cannot_occur() for v in hop.args_v: hop.genop('keepalive', [v], resulttype=lltype.Void) return hop.inputconst(lltype.Void, None) diff --git a/pypy/rpython/rfloat.py b/pypy/rpython/rfloat.py --- a/pypy/rpython/rfloat.py +++ b/pypy/rpython/rfloat.py @@ -136,7 +136,10 @@ hop.exception_cannot_occur() return hop.genop('cast_float_to_int', vlist, resulttype=Signed) - rtype_float = rtype_pos + def rtype_float(_, hop): + vlist = hop.inputargs(Float) + hop.exception_cannot_occur() + return vlist[0] # version picked by specialisation based on which # type system rtyping is using, from .ll_str module diff --git a/pypy/rpython/rint.py b/pypy/rpython/rint.py --- a/pypy/rpython/rint.py +++ b/pypy/rpython/rint.py @@ -310,6 +310,8 @@ if hop.has_implicit_exception(ValueError): hop.exception_is_here() hop.gendirectcall(ll_check_chr, vlist[0]) + else: + hop.exception_cannot_occur() return hop.genop('cast_int_to_char', vlist, resulttype=Char) def rtype_unichr(_, hop): @@ -317,6 +319,8 @@ if hop.has_implicit_exception(ValueError): hop.exception_is_here() hop.gendirectcall(ll_check_unichr, vlist[0]) + else: + hop.exception_cannot_occur() return hop.genop('cast_int_to_unichar', vlist, resulttype=UniChar) def rtype_is_true(self, hop): diff --git a/pypy/rpython/rlist.py b/pypy/rpython/rlist.py --- a/pypy/rpython/rlist.py +++ b/pypy/rpython/rlist.py @@ -115,6 +115,7 @@ def rtype_bltn_list(self, hop): v_lst = hop.inputarg(self, 0) cRESLIST = hop.inputconst(Void, hop.r_result.LIST) + hop.exception_is_here() return hop.gendirectcall(ll_copy, cRESLIST, v_lst) def rtype_len(self, hop): diff --git a/pypy/rpython/rrange.py b/pypy/rpython/rrange.py --- a/pypy/rpython/rrange.py +++ b/pypy/rpython/rrange.py @@ -107,8 +107,10 @@ if isinstance(hop.r_result, AbstractRangeRepr): if hop.r_result.step != 0: c_rng = hop.inputconst(Void, hop.r_result.RANGE) + hop.exception_is_here() return hop.gendirectcall(hop.r_result.ll_newrange, c_rng, vstart, vstop) else: + hop.exception_is_here() return hop.gendirectcall(hop.r_result.ll_newrangest, vstart, vstop, vstep) else: # cannot build a RANGE object, needs a real list @@ -117,6 +119,7 @@ if isinstance(ITEMTYPE, Ptr): ITEMTYPE = ITEMTYPE.TO cLIST = hop.inputconst(Void, ITEMTYPE) + hop.exception_is_here() return hop.gendirectcall(ll_range2list, cLIST, vstart, vstop, vstep) rtype_builtin_xrange = rtype_builtin_range @@ -212,4 +215,5 @@ [v_index, v_item]) def rtype_builtin_enumerate(hop): + hop.exception_cannot_occur() return hop.r_result.r_baseiter.newiter(hop) diff --git a/pypy/rpython/rstr.py b/pypy/rpython/rstr.py --- a/pypy/rpython/rstr.py +++ b/pypy/rpython/rstr.py @@ -288,6 +288,8 @@ def rtype_unicode(self, hop): if hop.args_s[0].is_constant(): + # convertion errors occur during annotation, so cannot any more: + hop.exception_cannot_occur() return hop.inputconst(hop.r_result, hop.s_result.const) repr = hop.args_r[0].repr v_str = hop.inputarg(repr, 0) diff --git a/pypy/rpython/rtyper.py b/pypy/rpython/rtyper.py --- a/pypy/rpython/rtyper.py +++ b/pypy/rpython/rtyper.py @@ -846,6 +846,7 @@ return result def exception_is_here(self): + self.llops._called_exception_is_here_or_cannot_occur = True if self.llops.llop_raising_exceptions is not None: raise TyperError("cannot catch an exception at more than one llop") if not self.exceptionlinks: @@ -861,6 +862,7 @@ self.llops.llop_raising_exceptions = len(self.llops) def exception_cannot_occur(self): + self.llops._called_exception_is_here_or_cannot_occur = True if self.llops.llop_raising_exceptions is not None: raise TyperError("cannot catch an exception at more than one llop") if not self.exceptionlinks: diff --git a/pypy/rpython/test/test_extregistry.py b/pypy/rpython/test/test_extregistry.py --- a/pypy/rpython/test/test_extregistry.py +++ b/pypy/rpython/test/test_extregistry.py @@ -114,6 +114,7 @@ _about_ = dummy_func s_result_annotation = annmodel.SomeInteger() def specialize_call(self, hop): + hop.exception_cannot_occur() return hop.inputconst(lltype.Signed, 42) def func(): diff --git a/pypy/rpython/test/test_rclass.py b/pypy/rpython/test/test_rclass.py --- a/pypy/rpython/test/test_rclass.py +++ b/pypy/rpython/test/test_rclass.py @@ -1085,6 +1085,7 @@ return annmodel.SomeInteger() def specialize_call(self, hop): [v_instance] = hop.inputargs(*hop.args_r) + hop.exception_is_here() return hop.gendirectcall(ll_my_gethash, v_instance) def f(n): diff --git a/pypy/translator/c/test/test_extfunc.py b/pypy/translator/c/test/test_extfunc.py --- a/pypy/translator/c/test/test_extfunc.py +++ b/pypy/translator/c/test/test_extfunc.py @@ -919,4 +919,5 @@ t, cbuilder = self.compile(does_stuff) data = cbuilder.cmdexec('') res = os.nice(0) + 3 + if res > 19: res = 19 # xxx Linux specific, probably assert data.startswith('os.nice returned %d\n' % res) diff --git a/pypy/translator/cli/dotnet.py b/pypy/translator/cli/dotnet.py --- a/pypy/translator/cli/dotnet.py +++ b/pypy/translator/cli/dotnet.py @@ -459,6 +459,7 @@ def specialize_call(self, hop): + hop.exception_cannot_occur() assert hop.args_s[1].is_constant() TYPE = hop.args_s[1].const v_obj = hop.inputarg(hop.args_r[0], arg=0) @@ -507,6 +508,7 @@ def specialize_call(self, hop): v_obj, = hop.inputargs(*hop.args_r) + hop.exception_cannot_occur() return hop.genop('same_as', [v_obj], hop.r_result.lowleveltype) def new_array(type, length): @@ -608,6 +610,7 @@ def specialize_call(self, hop): v_type, = hop.inputargs(*hop.args_r) + hop.exception_cannot_occur() return hop.genop('cli_typeof', [v_type], hop.r_result.lowleveltype) @@ -626,6 +629,7 @@ v_obj, = hop.inputargs(*hop.args_r) methodname = hop.args_r[0].methodname c_methodname = hop.inputconst(ootype.Void, methodname) + hop.exception_cannot_occur() return hop.genop('cli_eventhandler', [v_obj, c_methodname], hop.r_result.lowleveltype) @@ -647,6 +651,7 @@ def specialize_call(self, hop): assert isinstance(hop.args_s[0], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[0], arg=0) + hop.exception_cannot_occur() return hop.genop('oodowncast', [v_inst], resulttype = hop.r_result.lowleveltype) @@ -668,6 +673,7 @@ def specialize_call(self, hop): assert isinstance(hop.args_s[0], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[0], arg=0) + hop.exception_cannot_occur() return hop.genop('ooupcast', [v_inst], resulttype = hop.r_result.lowleveltype) @@ -701,6 +707,7 @@ def specialize_call(self, hop): v_obj = hop.inputarg(hop.args_r[0], arg=0) + hop.exception_cannot_occur() return hop.genop('oodowncast', [v_obj], hop.r_result.lowleveltype) From noreply at buildbot.pypy.org Mon Apr 2 21:18:30 2012 From: noreply at buildbot.pypy.org (ctismer) Date: Mon, 2 Apr 2012 21:18:30 +0200 (CEST) Subject: [pypy-commit] extradoc extradoc: Added myself, without exact dates yet Message-ID: <20120402191830.5DB4746E0EF@wyvern.cs.uni-duesseldorf.de> Author: Christian Tismer Branch: extradoc Changeset: r4177:5099f4763dbb Date: 2012-04-02 21:17 +0200 http://bitbucket.org/pypy/extradoc/changeset/5099f4763dbb/ Log: Added myself, without exact dates yet diff --git a/sprintinfo/leipzig2012/people.txt b/sprintinfo/leipzig2012/people.txt --- a/sprintinfo/leipzig2012/people.txt +++ b/sprintinfo/leipzig2012/people.txt @@ -9,5 +9,6 @@ Name Arrive/Depart Accomodation ==================== ============== ===================== Armin Rigo ? ? +Christian Tismer ? ? ==================== ============== ===================== From noreply at buildbot.pypy.org Tue Apr 3 00:32:37 2012 From: noreply at buildbot.pypy.org (amauryfa) Date: Tue, 3 Apr 2012 00:32:37 +0200 (CEST) Subject: [pypy-commit] pypy default: Fix test_celldict, and translation as well. Message-ID: <20120402223237.D091346E0EF@wyvern.cs.uni-duesseldorf.de> Author: Amaury Forgeot d'Arc Branch: Changeset: r54157:3f8bd87cfc04 Date: 2012-04-03 00:31 +0200 http://bitbucket.org/pypy/pypy/changeset/3f8bd87cfc04/ Log: Fix test_celldict, and translation as well. diff --git a/pypy/objspace/std/celldict.py b/pypy/objspace/std/celldict.py --- a/pypy/objspace/std/celldict.py +++ b/pypy/objspace/std/celldict.py @@ -163,7 +163,8 @@ class ModuleDictIteratorImplementation(IteratorImplementation): def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) + IteratorImplementation.__init__( + self, space, strategy, dictimplementation) dict_w = strategy.unerase(dictimplementation.dstorage) self.iterator = dict_w.iteritems() From noreply at buildbot.pypy.org Tue Apr 3 00:51:59 2012 From: noreply at buildbot.pypy.org (amauryfa) Date: Tue, 3 Apr 2012 00:51:59 +0200 (CEST) Subject: [pypy-commit] pypy default: Same fix on other dict implementations :( Message-ID: <20120402225159.2BFDF46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Amaury Forgeot d'Arc Branch: Changeset: r54158:cc56984630fc Date: 2012-04-03 00:51 +0200 http://bitbucket.org/pypy/pypy/changeset/cc56984630fc/ Log: Same fix on other dict implementations :( diff --git a/pypy/objspace/std/dictproxyobject.py b/pypy/objspace/std/dictproxyobject.py --- a/pypy/objspace/std/dictproxyobject.py +++ b/pypy/objspace/std/dictproxyobject.py @@ -108,7 +108,8 @@ class DictProxyIteratorImplementation(IteratorImplementation): def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) + IteratorImplementation.__init__( + self, space, strategy, dictimplementation) w_type = strategy.unerase(dictimplementation.dstorage) self.iterator = w_type.dict_w.iteritems() diff --git a/pypy/objspace/std/mapdict.py b/pypy/objspace/std/mapdict.py --- a/pypy/objspace/std/mapdict.py +++ b/pypy/objspace/std/mapdict.py @@ -703,7 +703,8 @@ class MapDictIteratorImplementation(IteratorImplementation): def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) + IteratorImplementation.__init__( + self, space, strategy, dictimplementation) w_obj = strategy.unerase(dictimplementation.dstorage) self.w_obj = w_obj self.orig_map = self.curr_map = w_obj._get_mapdict_map() From noreply at buildbot.pypy.org Tue Apr 3 01:43:36 2012 From: noreply at buildbot.pypy.org (wlav) Date: Tue, 3 Apr 2012 01:43:36 +0200 (CEST) Subject: [pypy-commit] pypy reflex-support: groundwork for lazy lookup on namespaces (for now, only for updates) Message-ID: <20120402234336.3761746E0EF@wyvern.cs.uni-duesseldorf.de> Author: Wim Lavrijsen Branch: reflex-support Changeset: r54159:228864642d3d Date: 2012-03-30 13:51 -0700 http://bitbucket.org/pypy/pypy/changeset/228864642d3d/ Log: groundwork for lazy lookup on namespaces (for now, only for updates) diff --git a/pypy/module/cppyy/capi/__init__.py b/pypy/module/cppyy/capi/__init__.py --- a/pypy/module/cppyy/capi/__init__.py +++ b/pypy/module/cppyy/capi/__init__.py @@ -293,6 +293,12 @@ def c_method_signature(cppscope, method_index): return charp2str_free(_c_method_signature(cppscope, method_index)) +c_method_index = rffi.llexternal( + "cppyy_method_index", + [C_SCOPE, rffi.CCHARP], rffi.INT, + threadsafe=threadsafe, + compilation_info=backend.eci) + c_get_method = rffi.llexternal( "cppyy_get_method", [C_SCOPE, rffi.INT], C_METHOD, @@ -337,6 +343,12 @@ threadsafe=threadsafe, compilation_info=backend.eci) +c_data_member_index = rffi.llexternal( + "cppyy_data_member_index", + [C_SCOPE, rffi.CCHARP], rffi.INT, + threadsafe=threadsafe, + compilation_info=backend.eci) + # data member properties ----------------------------------------------------- c_is_publicdata = rffi.llexternal( "cppyy_is_publicdata", diff --git a/pypy/module/cppyy/include/capi.h b/pypy/module/cppyy/include/capi.h --- a/pypy/module/cppyy/include/capi.h +++ b/pypy/module/cppyy/include/capi.h @@ -71,6 +71,8 @@ char* cppyy_method_arg_default(cppyy_scope_t scope, int method_index, int arg_index); char* cppyy_method_signature(cppyy_scope_t scope, int method_index); + int cppyy_method_index(cppyy_scope_t scope, const char* name); + cppyy_method_t cppyy_get_method(cppyy_scope_t scope, int method_index); /* method properties ----------------------------------------------------- */ @@ -83,6 +85,8 @@ char* cppyy_data_member_type(cppyy_scope_t scope, int data_member_index); size_t cppyy_data_member_offset(cppyy_scope_t scope, int data_member_index); + int cppyy_data_member_index(cppyy_scope_t scope, const char* name); + /* data member properties ------------------------------------------------ */ int cppyy_is_publicdata(cppyy_type_t type, int data_member_index); int cppyy_is_staticdata(cppyy_type_t type, int data_member_index); diff --git a/pypy/module/cppyy/interp_cppyy.py b/pypy/module/cppyy/interp_cppyy.py --- a/pypy/module/cppyy/interp_cppyy.py +++ b/pypy/module/cppyy/interp_cppyy.py @@ -413,7 +413,8 @@ try: return self.methods[name] except KeyError: - raise self.missing_attribute_error(name) + pass + return self.find_overload(name) def get_data_member_names(self): return self.space.newlist([self.space.wrap(name) for name in self.data_members]) @@ -423,7 +424,8 @@ try: return self.data_members[name] except KeyError: - raise self.missing_attribute_error(name) + pass + return self.find_data_member(name) def missing_attribute_error(self, name): return OperationError( @@ -451,6 +453,13 @@ arg_defs.append((arg_type, arg_dflt)) return CPPFunction(self, method_index, result_type, arg_defs, args_required) + def _make_data_member(self, dm_name, dm_idx): + type_name = capi.c_data_member_type(self.handle, dm_idx) + offset = capi.c_data_member_offset(self.handle, dm_idx) + data_member = W_CPPDataMember(self.space, self.handle, type_name, offset, True) + self.data_members[dm_name] = data_member + return data_member + def _find_data_members(self): num_data_members = capi.c_num_data_members(self.handle) for i in range(num_data_members): @@ -458,10 +467,24 @@ continue data_member_name = capi.c_data_member_name(self.handle, i) if not data_member_name in self.data_members: - type_name = capi.c_data_member_type(self.handle, i) - offset = capi.c_data_member_offset(self.handle, i) - data_member = W_CPPDataMember(self.space, self.handle, type_name, offset, True) - self.data_members[data_member_name] = data_member + self._make_data_member(data_member_name, i) + + def find_overload(self, meth_name): + # TODO: collect all overloads, not just the non-overloaded version + meth_idx = capi.c_method_index(self.handle, meth_name) + if meth_idx < 0: + raise self.missing_attribute_error(meth_name) + cppfunction = self._make_cppfunction(meth_idx) + overload = W_CPPOverload(self.space, self.handle, meth_name, [cppfunction]) + self.methods[meth_name] = overload + return overload + + def find_data_member(self, dm_name): + dm_idx = capi.c_data_member_index(self.handle, dm_name) + if dm_idx < 0: + raise self.missing_attribute_error(dm_name) + data_member = self._make_data_member(dm_name, dm_idx) + return data_member def update(self): self._find_methods() @@ -517,6 +540,12 @@ data_member = W_CPPDataMember(self.space, self.handle, type_name, offset, is_static) self.data_members[data_member_name] = data_member + def find_overload(self, name): + raise self.missing_attribute_error(name) + + def find_data_member(self, name): + raise self.missing_attribute_error(name) + def get_cppthis(self, cppinstance, scope_handle): assert self.handle == cppinstance.cppclass.handle return cppinstance.get_rawobject() diff --git a/pypy/module/cppyy/pythonify.py b/pypy/module/cppyy/pythonify.py --- a/pypy/module/cppyy/pythonify.py +++ b/pypy/module/cppyy/pythonify.py @@ -207,13 +207,6 @@ pycppitem = None - # namespaces are "open"; TODO: classes are too (template methods, inner classes ...) - if isinstance(scope, CppyyNamespaceMeta): - global _loaded_dictionaries_isdirty - if _loaded_dictionaries_isdirty: # TODO: this should be per namespace - scope._cpp_proxy.update() # TODO: this is currently quadratic - _loaded_dictionaries_isdirty = False - # classes cppitem = cppyy._type_byname(true_name) if cppitem: @@ -365,15 +358,12 @@ pyclass.__getitem__ = pyclass.__setitem__ _loaded_dictionaries = {} -_loaded_dictionaries_isdirty = True # should be per namespace def load_reflection_info(name): try: return _loaded_dictionaries[name] except KeyError: dct = cppyy._load_dictionary(name) _loaded_dictionaries[name] = dct - global _loaded_dictionaries_isdirty - _loaded_dictionaries_isdirty = True return dct diff --git a/pypy/module/cppyy/src/reflexcwrapper.cxx b/pypy/module/cppyy/src/reflexcwrapper.cxx --- a/pypy/module/cppyy/src/reflexcwrapper.cxx +++ b/pypy/module/cppyy/src/reflexcwrapper.cxx @@ -372,6 +372,23 @@ return cppstring_to_cstring(sig.str()); } +int cppyy_method_index(cppyy_scope_t handle, const char* name) { + Reflex::Scope s = scope_from_handle(handle); + // the following appears dumb, but the internal storage for Reflex is an + // unsorted std::vector anyway, so there's no gain to be had in using the + // Scope::FunctionMemberByName() function + int num_meth = s.FunctionMemberSize(); + for (int imeth = 0; imeth < num_meth; ++imeth) { + Reflex::Member m = s.FunctionMemberAt(imeth); + if (m.Name() == name) { + if (m.IsPublic()) + return imeth; + return -1; + } + } + return -1; +} + cppyy_method_t cppyy_get_method(cppyy_scope_t handle, int method_index) { Reflex::Scope s = scope_from_handle(handle); Reflex::Member m = s.FunctionMemberAt(method_index); @@ -436,6 +453,23 @@ return m.Offset(); } +int cppyy_data_member_index(cppyy_scope_t handle, const char* name) { + Reflex::Scope s = scope_from_handle(handle); + // the following appears dumb, but the internal storage for Reflex is an + // unsorted std::vector anyway, so there's no gain to be had in using the + // Scope::DataMemberByName() function (which returns Member, not an index) + int num_dm = cppyy_num_data_members(handle); + for (int idm = 0; idm < num_dm; ++idm) { + Reflex::Member m = s.DataMemberAt(idm); + if (m.Name() == name || m.Name(Reflex::FINAL) == name) { + if (m.IsPublic()) + return idm; + return -1; + } + } + return -1; +} + /* data member properties ------------------------------------------------ */ int cppyy_is_publicdata(cppyy_scope_t handle, int data_member_index) { diff --git a/pypy/module/cppyy/test/Makefile b/pypy/module/cppyy/test/Makefile --- a/pypy/module/cppyy/test/Makefile +++ b/pypy/module/cppyy/test/Makefile @@ -1,5 +1,6 @@ -dicts = example01Dict.so datatypesDict.so advancedcppDict.so overloadsDict.so \ -stltypesDict.so operatorsDict.so fragileDict.so std_streamsDict.so +dicts = example01Dict.so datatypesDict.so advancedcppDict.so advancedcpp2Dict.so \ +overloadsDict.so stltypesDict.so operatorsDict.so fragileDict.so \ +std_streamsDict.so all : $(dicts) ROOTSYS := ${ROOTSYS} diff --git a/pypy/module/cppyy/test/advancedcpp.cxx b/pypy/module/cppyy/test/advancedcpp.cxx --- a/pypy/module/cppyy/test/advancedcpp.cxx +++ b/pypy/module/cppyy/test/advancedcpp.cxx @@ -24,6 +24,9 @@ int a_ns::d_ns::e_class::s_e = 55; int a_ns::d_ns::e_class::f_class::s_f = 66; +int a_ns::get_g_a() { return g_a; } +int a_ns::d_ns::get_g_d() { return g_d; } + // for template testing template class T1; diff --git a/pypy/module/cppyy/test/advancedcpp.h b/pypy/module/cppyy/test/advancedcpp.h --- a/pypy/module/cppyy/test/advancedcpp.h +++ b/pypy/module/cppyy/test/advancedcpp.h @@ -100,6 +100,7 @@ //=========================================================================== namespace a_ns { // for namespace testing extern int g_a; + int get_g_a(); struct b_class { b_class() { m_b = -2; } @@ -115,6 +116,7 @@ namespace d_ns { extern int g_d; + int get_g_d(); struct e_class { e_class() { m_e = -5; } diff --git a/pypy/module/cppyy/test/advancedcpp.xml b/pypy/module/cppyy/test/advancedcpp.xml --- a/pypy/module/cppyy/test/advancedcpp.xml +++ b/pypy/module/cppyy/test/advancedcpp.xml @@ -22,7 +22,9 @@ + + diff --git a/pypy/module/cppyy/test/test_advancedcpp.py b/pypy/module/cppyy/test/test_advancedcpp.py --- a/pypy/module/cppyy/test/test_advancedcpp.py +++ b/pypy/module/cppyy/test/test_advancedcpp.py @@ -10,9 +10,10 @@ def setup_module(mod): if sys.platform == 'win32': py.test.skip("win32 not supported so far") - err = os.system("cd '%s' && make advancedcppDict.so" % currpath) - if err: - raise OSError("'make' failed (see stderr)") + for refl_dict in ["advancedcppDict.so", "advancedcpp2Dict.so"]: + err = os.system("cd '%s' && make %s" % (currpath, refl_dict)) + if err: + raise OSError("'make' failed (see stderr)") class AppTestADVANCEDCPP: def setup_class(cls): @@ -113,23 +114,55 @@ import cppyy gbl = cppyy.gbl + assert gbl.a_ns is gbl.a_ns + assert gbl.a_ns.d_ns is gbl.a_ns.d_ns + + assert gbl.a_ns.b_class is gbl.a_ns.b_class + assert gbl.a_ns.b_class.c_class is gbl.a_ns.b_class.c_class + assert gbl.a_ns.d_ns.e_class is gbl.a_ns.d_ns.e_class + assert gbl.a_ns.d_ns.e_class.f_class is gbl.a_ns.d_ns.e_class.f_class + assert gbl.a_ns.g_a == 11 + assert gbl.a_ns.get_g_a() == 11 assert gbl.a_ns.b_class.s_b == 22 assert gbl.a_ns.b_class().m_b == -2 assert gbl.a_ns.b_class.c_class.s_c == 33 assert gbl.a_ns.b_class.c_class().m_c == -3 assert gbl.a_ns.d_ns.g_d == 44 + assert gbl.a_ns.d_ns.get_g_d() == 44 assert gbl.a_ns.d_ns.e_class.s_e == 55 assert gbl.a_ns.d_ns.e_class().m_e == -5 assert gbl.a_ns.d_ns.e_class.f_class.s_f == 66 assert gbl.a_ns.d_ns.e_class.f_class().m_f == -6 + def test03a_namespace_lookup_on_update(self): + """Test whether namespaces can be shared across dictionaries.""" + + import cppyy + gbl = cppyy.gbl + + lib2 = cppyy.load_reflection_info("advancedcpp2Dict.so") + assert gbl.a_ns is gbl.a_ns assert gbl.a_ns.d_ns is gbl.a_ns.d_ns - assert gbl.a_ns.b_class is gbl.a_ns.b_class - assert gbl.a_ns.d_ns.e_class is gbl.a_ns.d_ns.e_class - assert gbl.a_ns.d_ns.e_class.f_class is gbl.a_ns.d_ns.e_class.f_class + assert gbl.a_ns.g_class is gbl.a_ns.g_class + assert gbl.a_ns.g_class.h_class is gbl.a_ns.g_class.h_class + assert gbl.a_ns.d_ns.i_class is gbl.a_ns.d_ns.i_class + assert gbl.a_ns.d_ns.i_class.j_class is gbl.a_ns.d_ns.i_class.j_class + + assert gbl.a_ns.g_g == 77 + assert gbl.a_ns.get_g_g() == 77 + assert gbl.a_ns.g_class.s_g == 88 + assert gbl.a_ns.g_class().m_g == -7 + assert gbl.a_ns.g_class.h_class.s_h == 99 + assert gbl.a_ns.g_class.h_class().m_h == -8 + assert gbl.a_ns.d_ns.g_i == 111 + assert gbl.a_ns.d_ns.get_g_i() == 111 + assert gbl.a_ns.d_ns.i_class.s_i == 222 + assert gbl.a_ns.d_ns.i_class().m_i == -9 + assert gbl.a_ns.d_ns.i_class.j_class.s_j == 333 + assert gbl.a_ns.d_ns.i_class.j_class().m_j == -10 def test04_template_types(self): """Test bindings of templated types""" diff --git a/pypy/module/cppyy/test/test_pythonify.py b/pypy/module/cppyy/test/test_pythonify.py --- a/pypy/module/cppyy/test/test_pythonify.py +++ b/pypy/module/cppyy/test/test_pythonify.py @@ -24,7 +24,7 @@ import cppyy return cppyy.load_reflection_info(%r)""" % (test_dct, )) - def test01_load_dicionary_cache(self): + def test01_load_dictionary_cache(self): """Test whether loading a dictionary twice results in the same object.""" import cppyy lib2 = cppyy.load_reflection_info(self.test_dct) From noreply at buildbot.pypy.org Tue Apr 3 01:43:37 2012 From: noreply at buildbot.pypy.org (wlav) Date: Tue, 3 Apr 2012 01:43:37 +0200 (CEST) Subject: [pypy-commit] pypy reflex-support: files needed for update-handling testing Message-ID: <20120402234337.7155946E0EF@wyvern.cs.uni-duesseldorf.de> Author: Wim Lavrijsen Branch: reflex-support Changeset: r54160:f2d76f0952a9 Date: 2012-03-30 14:03 -0700 http://bitbucket.org/pypy/pypy/changeset/f2d76f0952a9/ Log: files needed for update-handling testing diff --git a/pypy/module/cppyy/test/advancedcpp2.cxx b/pypy/module/cppyy/test/advancedcpp2.cxx new file mode 100644 --- /dev/null +++ b/pypy/module/cppyy/test/advancedcpp2.cxx @@ -0,0 +1,13 @@ +#include "advancedcpp2.h" + + +// for namespace testing +int a_ns::g_g = 77; +int a_ns::g_class::s_g = 88; +int a_ns::g_class::h_class::s_h = 99; +int a_ns::d_ns::g_i = 111; +int a_ns::d_ns::i_class::s_i = 222; +int a_ns::d_ns::i_class::j_class::s_j = 333; + +int a_ns::get_g_g() { return g_g; } +int a_ns::d_ns::get_g_i() { return g_i; } diff --git a/pypy/module/cppyy/test/advancedcpp2.h b/pypy/module/cppyy/test/advancedcpp2.h new file mode 100644 --- /dev/null +++ b/pypy/module/cppyy/test/advancedcpp2.h @@ -0,0 +1,36 @@ +//=========================================================================== +namespace a_ns { // for namespace testing + extern int g_g; + int get_g_g(); + + struct g_class { + g_class() { m_g = -7; } + int m_g; + static int s_g; + + struct h_class { + h_class() { m_h = -8; } + int m_h; + static int s_h; + }; + }; + + namespace d_ns { + extern int g_i; + int get_g_i(); + + struct i_class { + i_class() { m_i = -9; } + int m_i; + static int s_i; + + struct j_class { + j_class() { m_j = -10; } + int m_j; + static int s_j; + }; + }; + + } // namespace d_ns + +} // namespace a_ns diff --git a/pypy/module/cppyy/test/advancedcpp2.xml b/pypy/module/cppyy/test/advancedcpp2.xml new file mode 100644 --- /dev/null +++ b/pypy/module/cppyy/test/advancedcpp2.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + From noreply at buildbot.pypy.org Tue Apr 3 01:43:38 2012 From: noreply at buildbot.pypy.org (wlav) Date: Tue, 3 Apr 2012 01:43:38 +0200 (CEST) Subject: [pypy-commit] pypy reflex-support: likewise, lazy lookups for CINT back-end Message-ID: <20120402234338.BF81F46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Wim Lavrijsen Branch: reflex-support Changeset: r54161:8277dedafc32 Date: 2012-03-30 14:57 -0700 http://bitbucket.org/pypy/pypy/changeset/8277dedafc32/ Log: likewise, lazy lookups for CINT back-end diff --git a/pypy/module/cppyy/src/cintcwrapper.cxx b/pypy/module/cppyy/src/cintcwrapper.cxx --- a/pypy/module/cppyy/src/cintcwrapper.cxx +++ b/pypy/module/cppyy/src/cintcwrapper.cxx @@ -36,6 +36,23 @@ extern "C" void G__LockCriticalSection(); extern "C" void G__UnlockCriticalSection(); +/* ROOT meta internals ---------------------------------------------------- */ +namespace { + +class Cppyy_OpenedTClass : public TDictionary { +public: + mutable TObjArray *fStreamerInfo; //Array of TVirtualStreamerInfo + mutable std::map *fConversionStreamerInfo; //Array of the streamer infos derived from another class. + TList *fRealData; //linked list for persistent members including base classes + TList *fBase; //linked list for base classes + TList *fData; //linked list for data members + TList *fMethod; //linked list for methods + TList *fAllPubData; //all public data members (including from base classes) + TList *fAllPubMethod; //all public methods (including from base classes) +}; + +} // unnamed namespace + /* data for life time management ------------------------------------------ */ #define GLOBAL_HANDLE 1l @@ -540,6 +557,30 @@ return cppstring_to_cstring(sig.str()); } +int cppyy_method_index(cppyy_scope_t handle, const char* name) { + TClassRef cr = type_from_handle(handle); + if (cr.GetClass()) { + gInterpreter->UpdateListOfMethods(cr.GetClass()); + int imeth = 0; + TFunction* func; + TIter next(cr->GetListOfMethods()); + while ((func = (TFunction*)next())) { + if (strcmp(name, func->GetName()) == 0) { + if (func->Property() & G__BIT_ISPUBLIC) + return imeth; + return -1; + } + ++imeth; + } + } + TFunction* func = gROOT->GetGlobalFunction(name, NULL, kTRUE); + if (!func) + return -1; + int idx = g_globalfuncs.size(); + g_globalfuncs.push_back(*func); + return idx; +} + cppyy_method_t cppyy_get_method(cppyy_scope_t handle, int method_index) { TFunction* f = type_get_method(handle, method_index); return (cppyy_method_t)f->InterfaceMethod(); @@ -621,6 +662,39 @@ return (size_t)gbl.GetAddress(); } +int cppyy_data_member_index(cppyy_scope_t handle, const char* name) { + TClassRef cr = type_from_handle(handle); + if (cr.GetClass()) { + // called from updates; add a hard reset as the code itself caches in + // Class (TODO: by-pass ROOT/meta) + Cppyy_OpenedTClass* c = (Cppyy_OpenedTClass*)cr.GetClass(); + if (c->fData) { + c->fData->Delete(); + delete c->fData; c->fData = 0; + delete c->fAllPubData; c->fAllPubData = 0; + } + // the following appears dumb, but TClass::GetDataMember() does a linear + // search itself, so there is no gain + int idm = 0; + TDataMember* dm; + TIter next(cr->GetListOfDataMembers()); + while ((dm = (TDataMember*)next())) { + if (strcmp(name, dm->GetName()) == 0) { + if (dm->Property() & G__BIT_ISPUBLIC) + return idm; + return -1; + } + ++idm; + } + } + TGlobal* gbl = (TGlobal*)gROOT->GetListOfGlobals(kTRUE)->FindObject(name); + if (!gbl) + return -1; + int idx = g_globalvars.size(); + g_globalvars.push_back(*gbl); + return idx; +} + /* data member properties ------------------------------------------------ */ int cppyy_is_publicdata(cppyy_scope_t handle, int data_member_index) { diff --git a/pypy/module/cppyy/test/advancedcpp_LinkDef.h b/pypy/module/cppyy/test/advancedcpp_LinkDef.h --- a/pypy/module/cppyy/test/advancedcpp_LinkDef.h +++ b/pypy/module/cppyy/test/advancedcpp_LinkDef.h @@ -36,7 +36,9 @@ #pragma link C++ struct a_ns::d_ns::e_class; #pragma link C++ struct a_ns::d_ns::e_class::f_class; #pragma link C++ variable a_ns::g_a; +#pragma link C++ function a_ns::get_g_a; #pragma link C++ variable a_ns::d_ns::g_d; +#pragma link C++ function a_ns::d_ns::get_g_d; #pragma link C++ class some_abstract_class; #pragma link C++ class some_concrete_class; From noreply at buildbot.pypy.org Tue Apr 3 01:43:40 2012 From: noreply at buildbot.pypy.org (wlav) Date: Tue, 3 Apr 2012 01:43:40 +0200 (CEST) Subject: [pypy-commit] pypy reflex-support: rollback (translation fails in array) Message-ID: <20120402234340.940B446E0EF@wyvern.cs.uni-duesseldorf.de> Author: Wim Lavrijsen Branch: reflex-support Changeset: r54162:a869f2ef3eb2 Date: 2012-03-30 16:20 -0700 http://bitbucket.org/pypy/pypy/changeset/a869f2ef3eb2/ Log: rollback (translation fails in array) From noreply at buildbot.pypy.org Tue Apr 3 01:43:41 2012 From: noreply at buildbot.pypy.org (wlav) Date: Tue, 3 Apr 2012 01:43:41 +0200 (CEST) Subject: [pypy-commit] pypy reflex-support: needed for CINT update tests Message-ID: <20120402234341.CB58E46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Wim Lavrijsen Branch: reflex-support Changeset: r54163:4794b0f265d6 Date: 2012-03-30 16:21 -0700 http://bitbucket.org/pypy/pypy/changeset/4794b0f265d6/ Log: needed for CINT update tests diff --git a/pypy/module/cppyy/test/advancedcpp2_LinkDef.h b/pypy/module/cppyy/test/advancedcpp2_LinkDef.h new file mode 100644 --- /dev/null +++ b/pypy/module/cppyy/test/advancedcpp2_LinkDef.h @@ -0,0 +1,18 @@ +#ifdef __CINT__ + +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ namespace a_ns; +#pragma link C++ namespace a_ns::d_ns; +#pragma link C++ struct a_ns::g_class; +#pragma link C++ struct a_ns::g_class::h_class; +#pragma link C++ struct a_ns::d_ns::i_class; +#pragma link C++ struct a_ns::d_ns::i_class::j_class; +#pragma link C++ variable a_ns::g_g; +#pragma link C++ function a_ns::get_g_g; +#pragma link C++ variable a_ns::d_ns::g_i; +#pragma link C++ function a_ns::d_ns::get_g_i; + +#endif From noreply at buildbot.pypy.org Tue Apr 3 01:43:43 2012 From: noreply at buildbot.pypy.org (wlav) Date: Tue, 3 Apr 2012 01:43:43 +0200 (CEST) Subject: [pypy-commit] pypy reflex-support: easier use for testing the various executables Message-ID: <20120402234343.7740046E0EF@wyvern.cs.uni-duesseldorf.de> Author: Wim Lavrijsen Branch: reflex-support Changeset: r54164:ea7e2c6e0d5b Date: 2012-03-30 16:52 -0700 http://bitbucket.org/pypy/pypy/changeset/ea7e2c6e0d5b/ Log: easier use for testing the various executables diff --git a/pypy/module/cppyy/bench/bench02.h b/pypy/module/cppyy/bench/bench02.h --- a/pypy/module/cppyy/bench/bench02.h +++ b/pypy/module/cppyy/bench/bench02.h @@ -7,12 +7,15 @@ #include "TH1F.h" #include "TH2F.h" #include "TRandom.h" +#include "TRandom3.h" #include "TROOT.h" #include "TApplication.h" +#include "TSystem.h" #include "TArchiveFile.h" #include "TBasket.h" +#include "TBenchmark.h" #include "TBox.h" #include "TBranchRef.h" #include "TBrowser.h" @@ -67,10 +70,3 @@ void report(); void close_file(TFile* f); }; - -/* -gROOT = cppyy.gbl.gROOT -gBenchmark = cppyy.gbl.gBenchmark -gRandom = cppyy.gbl.gRandom -gSystem = cppyy.gbl.gSystem -*/ diff --git a/pypy/module/cppyy/bench/bench02.xml b/pypy/module/cppyy/bench/bench02.xml --- a/pypy/module/cppyy/bench/bench02.xml +++ b/pypy/module/cppyy/bench/bench02.xml @@ -7,6 +7,11 @@ + + + + + diff --git a/pypy/module/cppyy/bench/hsimple.py b/pypy/module/cppyy/bench/hsimple.py --- a/pypy/module/cppyy/bench/hsimple.py +++ b/pypy/module/cppyy/bench/hsimple.py @@ -10,9 +10,15 @@ #*-* #*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* +_reflex = True # to keep things equal, set to False for full macro + try: import cppyy, random + if not hasattr(cppyy.gbl, 'gROOT'): + cppyy.load_reflection_info('bench02Dict_reflex.so') + _reflex = True + TCanvas = cppyy.gbl.TCanvas TFile = cppyy.gbl.TFile TProfile = cppyy.gbl.TProfile @@ -30,22 +36,27 @@ from ROOT import gROOT, gBenchmark, gSystem import random +if _reflex: + gROOT.SetBatch(True) + # Create a new ROOT binary machine independent file. # Note that this file may contain any kind of ROOT objects, histograms, # pictures, graphics objects, detector geometries, tracks, events, etc.. # This file is now becoming the current directory. -hfile = gROOT.FindObject('hsimple.root') -if hfile: - hfile.Close() -hfile = TFile('hsimple.root', 'RECREATE', 'Demo ROOT file with histograms' ) +if not _reflex: + hfile = gROOT.FindObject('hsimple.root') + if hfile: + hfile.Close() + hfile = TFile('hsimple.root', 'RECREATE', 'Demo ROOT file with histograms' ) # Create some histograms, a profile histogram and an ntuple hpx = TH1F('hpx', 'This is the px distribution', 100, -4, 4) hpx.SetFillColor(48) hpxpy = TH2F('hpxpy', 'py vs px', 40, -4, 4, 40, -4, 4) hprof = TProfile('hprof', 'Profile of pz versus px', 100, -4, 4, 0, 20) -ntuple = TNtuple('ntuple', 'Demo ntuple', 'px:py:pz:random:i') +if not _reflex: + ntuple = TNtuple('ntuple', 'Demo ntuple', 'px:py:pz:random:i') gBenchmark.Start('hsimple') @@ -71,14 +82,15 @@ hpx.Fill(px) hpxpy.Fill(px, py) hprof.Fill(px, pz) - ntuple.Fill(px, py, pz, rnd, i) + if not _reflex: + ntuple.Fill(px, py, pz, rnd, i) # Update display every kUPDATE events if i and i%kUPDATE == 0: if i == kUPDATE: hpx.Draw() - c1.Modified() + c1.Modified(True) c1.Update() if gSystem.ProcessEvents(): # allow user interrupt @@ -88,9 +100,10 @@ # Save all objects in this file hpx.SetFillColor(0) -hfile.Write() +if not _reflex: + hfile.Write() hpx.SetFillColor(48) -c1.Modified() +c1.Modified(True) c1.Update() # Note that the file is automatically closed when application terminates From noreply at buildbot.pypy.org Tue Apr 3 01:43:44 2012 From: noreply at buildbot.pypy.org (wlav) Date: Tue, 3 Apr 2012 01:43:44 +0200 (CEST) Subject: [pypy-commit] pypy reflex-support: ignore reflection information by-products Message-ID: <20120402234344.B2B3E46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Wim Lavrijsen Branch: reflex-support Changeset: r54165:42cf027dd5ea Date: 2012-04-02 15:44 -0700 http://bitbucket.org/pypy/pypy/changeset/42cf027dd5ea/ Log: ignore reflection information by-products diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -19,10 +19,14 @@ ^pypy/module/cpyext/test/.+\.manifest$ ^pypy/module/test_lib_pypy/ctypes_tests/.+\.o$ ^pypy/module/cppyy/src/.+\.o$ +^pypy/module/cppyy/bench/.+\.so$ +^pypy/module/cppyy/bench/.+\.root$ +^pypy/module/cppyy/bench/.+\.d$ ^pypy/module/cppyy/src/.+\.errors$ ^pypy/module/cppyy/test/.+_rflx\.cpp$ ^pypy/module/cppyy/test/.+\.so$ ^pypy/module/cppyy/test/.+\.exe$ +^pypy/module/cppyy/test/.+_cint.h$ ^pypy/doc/.+\.html$ ^pypy/doc/config/.+\.rst$ ^pypy/doc/basicblock\.asc$ From noreply at buildbot.pypy.org Tue Apr 3 01:43:46 2012 From: noreply at buildbot.pypy.org (wlav) Date: Tue, 3 Apr 2012 01:43:46 +0200 (CEST) Subject: [pypy-commit] pypy reflex-support: o) merge default into branch Message-ID: <20120402234346.3A42646E0EF@wyvern.cs.uni-duesseldorf.de> Author: Wim Lavrijsen Branch: reflex-support Changeset: r54166:ec9d1aee9f1a Date: 2012-04-02 15:55 -0700 http://bitbucket.org/pypy/pypy/changeset/ec9d1aee9f1a/ Log: o) merge default into branch o) fix on my side the problems due to changes in array diff --git a/pypy/doc/project-ideas.rst b/pypy/doc/project-ideas.rst --- a/pypy/doc/project-ideas.rst +++ b/pypy/doc/project-ideas.rst @@ -149,6 +149,22 @@ exported. This would give us a one-size-fits-all generic .so file to be imported by any application that wants to load .so files :-) +Optimising cpyext (CPython C-API compatibility layer) +----------------------------------------------------- + +A lot of work has gone into PyPy's implementation of CPython's C-API over +the last years to let it reach a practical level of compatibility, so that +C extensions for CPython work on PyPy without major rewrites. However, +there are still many edges and corner cases where it misbehaves, and it has +not received any substantial optimisation so far. + +The objective of this project is to fix bugs in cpyext and to optimise +several performance critical parts of it, such as the reference counting +support and other heavily used C-API functions. The net result would be to +have CPython extensions run much faster on PyPy than they currently do, or +to make them work at all if they currently don't. A part of this work would +be to get cpyext into a shape where it supports running Cython generated +extensions. .. _`issue tracker`: http://bugs.pypy.org .. _`mailing list`: http://mail.python.org/mailman/listinfo/pypy-dev diff --git a/pypy/doc/stackless.rst b/pypy/doc/stackless.rst --- a/pypy/doc/stackless.rst +++ b/pypy/doc/stackless.rst @@ -199,17 +199,11 @@ The following features (present in some past Stackless version of PyPy) are for the time being not supported any more: -* Tasklets and channels (currently ``stackless.py`` seems to import, - but you have tasklets on top of coroutines on top of greenlets on - top of continulets on top of stacklets, and it's probably not too - hard to cut two of these levels by adapting ``stackless.py`` to - use directly continulets) - * Coroutines (could be rewritten at app-level) -* Pickling and unpickling continulets (*) - -* Continuing execution of a continulet in a different thread (*) +* Continuing execution of a continulet in a different thread + (but if it is "simple enough", you can pickle it and unpickle it + in the other thread). * Automatic unlimited stack (must be emulated__ so far) @@ -217,15 +211,6 @@ .. __: `recursion depth limit`_ -(*) Pickling, as well as changing threads, could be implemented by using -a "soft" stack switching mode again. We would get either "hard" or -"soft" switches, similarly to Stackless Python 3rd version: you get a -"hard" switch (like now) when the C stack contains non-trivial C frames -to save, and a "soft" switch (like previously) when it contains only -simple calls from Python to Python. Soft-switched continulets would -also consume a bit less RAM, and the switch might be a bit faster too -(unsure about that; what is the Stackless Python experience?). - Recursion depth limit +++++++++++++++++++++ diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py --- a/pypy/interpreter/baseobjspace.py +++ b/pypy/interpreter/baseobjspace.py @@ -296,6 +296,7 @@ self.check_signal_action = None # changed by the signal module self.user_del_action = UserDelAction(self) self.frame_trace_action = FrameTraceAction(self) + self._code_of_sys_exc_info = None from pypy.interpreter.pycode import cpython_magic, default_magic self.our_magic = default_magic @@ -467,9 +468,9 @@ if name not in modules: modules.append(name) - # a bit of custom logic: time2 or rctime take precedence over time + # a bit of custom logic: rctime take precedence over time # XXX this could probably be done as a "requires" in the config - if ('time2' in modules or 'rctime' in modules) and 'time' in modules: + if 'rctime' in modules and 'time' in modules: modules.remove('time') if not self.config.objspace.nofaking: diff --git a/pypy/interpreter/executioncontext.py b/pypy/interpreter/executioncontext.py --- a/pypy/interpreter/executioncontext.py +++ b/pypy/interpreter/executioncontext.py @@ -154,6 +154,7 @@ #operationerr.print_detailed_traceback(self.space) def _convert_exc(self, operr): + # Only for the flow object space return operr def sys_exc_info(self): # attn: the result is not the wrapped sys.exc_info() !!! diff --git a/pypy/interpreter/function.py b/pypy/interpreter/function.py --- a/pypy/interpreter/function.py +++ b/pypy/interpreter/function.py @@ -113,6 +113,12 @@ from pypy.interpreter.pycode import PyCode code = self.getcode() # hook for the jit + # + if (jit.we_are_jitted() and code is self.space._code_of_sys_exc_info + and nargs == 0): + from pypy.module.sys.vm import exc_info_direct + return exc_info_direct(self.space, frame) + # fast_natural_arity = code.fast_natural_arity if nargs == fast_natural_arity: if nargs == 0: diff --git a/pypy/interpreter/gateway.py b/pypy/interpreter/gateway.py --- a/pypy/interpreter/gateway.py +++ b/pypy/interpreter/gateway.py @@ -874,6 +874,12 @@ fn.add_to_table() if gateway.as_classmethod: fn = ClassMethod(space.wrap(fn)) + # + from pypy.module.sys.vm import exc_info + if code._bltin is exc_info: + assert space._code_of_sys_exc_info is None + space._code_of_sys_exc_info = code + # return fn diff --git a/pypy/interpreter/pyparser/parsestring.py b/pypy/interpreter/pyparser/parsestring.py --- a/pypy/interpreter/pyparser/parsestring.py +++ b/pypy/interpreter/pyparser/parsestring.py @@ -2,34 +2,39 @@ from pypy.interpreter import unicodehelper from pypy.rlib.rstring import StringBuilder -def parsestr(space, encoding, s, unicode_literals=False): - # compiler.transformer.Transformer.decode_literal depends on what - # might seem like minor details of this function -- changes here - # must be reflected there. +def parsestr(space, encoding, s, unicode_literal=False): + """Parses a string or unicode literal, and return a wrapped value. + + If encoding=iso8859-1, the source string is also in this encoding. + If encoding=None, the source string is ascii only. + In other cases, the source string is in utf-8 encoding. + + When a bytes string is returned, it will be encoded with the + original encoding. + + Yes, it's very inefficient. + Yes, CPython has very similar code. + """ # we use ps as "pointer to s" # q is the virtual last char index of the string ps = 0 quote = s[ps] rawmode = False - unicode = unicode_literals # string decoration handling - o = ord(quote) - isalpha = (o>=97 and o<=122) or (o>=65 and o<=90) - if isalpha or quote == '_': - if quote == 'b' or quote == 'B': - ps += 1 - quote = s[ps] - unicode = False - elif quote == 'u' or quote == 'U': - ps += 1 - quote = s[ps] - unicode = True - if quote == 'r' or quote == 'R': - ps += 1 - quote = s[ps] - rawmode = True + if quote == 'b' or quote == 'B': + ps += 1 + quote = s[ps] + unicode_literal = False + elif quote == 'u' or quote == 'U': + ps += 1 + quote = s[ps] + unicode_literal = True + if quote == 'r' or quote == 'R': + ps += 1 + quote = s[ps] + rawmode = True if quote != "'" and quote != '"': raise_app_valueerror(space, 'Internal error: parser passed unquoted literal') @@ -46,21 +51,28 @@ 'unmatched triple quotes in literal') q -= 2 - if unicode: # XXX Py_UnicodeFlag is ignored for now + if unicode_literal: # XXX Py_UnicodeFlag is ignored for now if encoding is None or encoding == "iso-8859-1": + # 'unicode_escape' expects latin-1 bytes, string is ready. buf = s bufp = ps bufq = q u = None else: - # "\XX" may become "\u005c\uHHLL" (12 bytes) + # String is utf8-encoded, but 'unicode_escape' expects + # latin-1; So multibyte sequences must be escaped. lis = [] # using a list to assemble the value end = q + # Worst case: "\XX" may become "\u005c\uHHLL" (12 bytes) while ps < end: if s[ps] == '\\': lis.append(s[ps]) ps += 1 if ord(s[ps]) & 0x80: + # A multibyte sequence will follow, it will be + # escaped like \u1234. To avoid confusion with + # the backslash we just wrote, we emit "\u005c" + # instead. lis.append("u005c") if ord(s[ps]) & 0x80: # XXX inefficient w, ps = decode_utf8(space, s, ps, end, "utf-16-be") @@ -86,13 +98,11 @@ need_encoding = (encoding is not None and encoding != "utf-8" and encoding != "iso-8859-1") - # XXX add strchr like interface to rtyper assert 0 <= ps <= q substr = s[ps : q] if rawmode or '\\' not in s[ps:]: if need_encoding: w_u = space.wrap(unicodehelper.PyUnicode_DecodeUTF8(space, substr)) - #w_v = space.wrap(space.unwrap(w_u).encode(encoding)) this works w_v = unicodehelper.PyUnicode_AsEncodedString(space, w_u, space.wrap(encoding)) return w_v else: diff --git a/pypy/jit/backend/test/runner_test.py b/pypy/jit/backend/test/runner_test.py --- a/pypy/jit/backend/test/runner_test.py +++ b/pypy/jit/backend/test/runner_test.py @@ -27,6 +27,12 @@ def constfloat(x): return ConstFloat(longlong.getfloatstorage(x)) +def boxlonglong(ll): + if longlong.is_64_bit: + return BoxInt(ll) + else: + return BoxFloat(ll) + class Runner(object): @@ -1623,6 +1629,11 @@ [boxfloat(2.5)], t).value assert res == longlong2float.float2longlong(2.5) + bytes = longlong2float.float2longlong(2.5) + res = self.execute_operation(rop.CONVERT_LONGLONG_BYTES_TO_FLOAT, + [boxlonglong(res)], 'float').value + assert longlong.getrealfloat(res) == 2.5 + def test_ooops_non_gc(self): x = lltype.malloc(lltype.Struct('x'), flavor='raw') v = heaptracker.adr2int(llmemory.cast_ptr_to_adr(x)) diff --git a/pypy/jit/backend/test/test_random.py b/pypy/jit/backend/test/test_random.py --- a/pypy/jit/backend/test/test_random.py +++ b/pypy/jit/backend/test/test_random.py @@ -328,6 +328,15 @@ def produce_into(self, builder, r): self.put(builder, [r.choice(builder.intvars)]) +class CastLongLongToFloatOperation(AbstractFloatOperation): + def produce_into(self, builder, r): + if longlong.is_64_bit: + self.put(builder, [r.choice(builder.intvars)]) + else: + if not builder.floatvars: + raise CannotProduceOperation + self.put(builder, [r.choice(builder.floatvars)]) + class CastFloatToIntOperation(AbstractFloatOperation): def produce_into(self, builder, r): if not builder.floatvars: @@ -450,6 +459,7 @@ OPERATIONS.append(CastFloatToIntOperation(rop.CAST_FLOAT_TO_INT)) OPERATIONS.append(CastIntToFloatOperation(rop.CAST_INT_TO_FLOAT)) OPERATIONS.append(CastFloatToIntOperation(rop.CONVERT_FLOAT_BYTES_TO_LONGLONG)) +OPERATIONS.append(CastLongLongToFloatOperation(rop.CONVERT_LONGLONG_BYTES_TO_FLOAT)) OperationBuilder.OPERATIONS = OPERATIONS diff --git a/pypy/jit/backend/x86/assembler.py b/pypy/jit/backend/x86/assembler.py --- a/pypy/jit/backend/x86/assembler.py +++ b/pypy/jit/backend/x86/assembler.py @@ -1251,6 +1251,15 @@ else: self.mov(loc0, resloc) + def genop_convert_longlong_bytes_to_float(self, op, arglocs, resloc): + loc0, = arglocs + if longlong.is_64_bit: + assert isinstance(resloc, RegLoc) + assert isinstance(loc0, RegLoc) + self.mc.MOVD(resloc, loc0) + else: + self.mov(loc0, resloc) + def genop_guard_int_is_true(self, op, guard_op, guard_token, arglocs, resloc): guard_opnum = guard_op.getopnum() self.mc.CMP(arglocs[0], imm0) diff --git a/pypy/jit/backend/x86/regalloc.py b/pypy/jit/backend/x86/regalloc.py --- a/pypy/jit/backend/x86/regalloc.py +++ b/pypy/jit/backend/x86/regalloc.py @@ -773,10 +773,24 @@ self.Perform(op, [loc0], loc1) self.xrm.possibly_free_var(op.getarg(0)) else: - loc0 = self.xrm.loc(op.getarg(0)) + arg0 = op.getarg(0) + loc0 = self.xrm.loc(arg0) + loc1 = self.xrm.force_allocate_reg(op.result, forbidden_vars=[arg0]) + self.Perform(op, [loc0], loc1) + self.xrm.possibly_free_var(arg0) + + def consider_convert_longlong_bytes_to_float(self, op): + if longlong.is_64_bit: + loc0 = self.rm.make_sure_var_in_reg(op.getarg(0)) loc1 = self.xrm.force_allocate_reg(op.result) self.Perform(op, [loc0], loc1) - self.xrm.possibly_free_var(op.getarg(0)) + self.rm.possibly_free_var(op.getarg(0)) + else: + arg0 = op.getarg(0) + loc0 = self.xrm.make_sure_var_in_reg(arg0) + loc1 = self.xrm.force_allocate_reg(op.result, forbidden_vars=[arg0]) + self.Perform(op, [loc0], loc1) + self.xrm.possibly_free_var(arg0) def _consider_llong_binop_xx(self, op): # must force both arguments into xmm registers, because we don't diff --git a/pypy/jit/codewriter/jtransform.py b/pypy/jit/codewriter/jtransform.py --- a/pypy/jit/codewriter/jtransform.py +++ b/pypy/jit/codewriter/jtransform.py @@ -295,6 +295,7 @@ return op rewrite_op_convert_float_bytes_to_longlong = _noop_rewrite + rewrite_op_convert_longlong_bytes_to_float = _noop_rewrite # ---------- # Various kinds of calls diff --git a/pypy/jit/codewriter/test/test_flatten.py b/pypy/jit/codewriter/test/test_flatten.py --- a/pypy/jit/codewriter/test/test_flatten.py +++ b/pypy/jit/codewriter/test/test_flatten.py @@ -968,20 +968,22 @@ int_return %i2 """, transform=True) - def test_convert_float_bytes_to_int(self): - from pypy.rlib.longlong2float import float2longlong + def test_convert_float_bytes(self): + from pypy.rlib.longlong2float import float2longlong, longlong2float def f(x): - return float2longlong(x) + ll = float2longlong(x) + return longlong2float(ll) if longlong.is_64_bit: - result_var = "%i0" - return_op = "int_return" + tmp_var = "%i0" + result_var = "%f1" else: - result_var = "%f1" - return_op = "float_return" + tmp_var = "%f1" + result_var = "%f2" self.encoding_test(f, [25.0], """ - convert_float_bytes_to_longlong %%f0 -> %(result_var)s - %(return_op)s %(result_var)s - """ % {"result_var": result_var, "return_op": return_op}) + convert_float_bytes_to_longlong %%f0 -> %(tmp_var)s + convert_longlong_bytes_to_float %(tmp_var)s -> %(result_var)s + float_return %(result_var)s + """ % {"result_var": result_var, "tmp_var": tmp_var}, transform=True) def check_force_cast(FROM, TO, operations, value): diff --git a/pypy/jit/metainterp/blackhole.py b/pypy/jit/metainterp/blackhole.py --- a/pypy/jit/metainterp/blackhole.py +++ b/pypy/jit/metainterp/blackhole.py @@ -672,6 +672,11 @@ a = longlong.getrealfloat(a) return longlong2float.float2longlong(a) + @arguments(LONGLONG_TYPECODE, returns="f") + def bhimpl_convert_longlong_bytes_to_float(a): + a = longlong2float.longlong2float(a) + return longlong.getfloatstorage(a) + # ---------- # control flow operations diff --git a/pypy/jit/metainterp/pyjitpl.py b/pypy/jit/metainterp/pyjitpl.py --- a/pypy/jit/metainterp/pyjitpl.py +++ b/pypy/jit/metainterp/pyjitpl.py @@ -224,6 +224,7 @@ 'float_neg', 'float_abs', 'cast_ptr_to_int', 'cast_int_to_ptr', 'convert_float_bytes_to_longlong', + 'convert_longlong_bytes_to_float', ]: exec py.code.Source(''' @arguments("box") diff --git a/pypy/jit/metainterp/resoperation.py b/pypy/jit/metainterp/resoperation.py --- a/pypy/jit/metainterp/resoperation.py +++ b/pypy/jit/metainterp/resoperation.py @@ -420,6 +420,7 @@ 'CAST_FLOAT_TO_SINGLEFLOAT/1', 'CAST_SINGLEFLOAT_TO_FLOAT/1', 'CONVERT_FLOAT_BYTES_TO_LONGLONG/1', + 'CONVERT_LONGLONG_BYTES_TO_FLOAT/1', # 'INT_LT/2b', 'INT_LE/2b', diff --git a/pypy/jit/metainterp/test/test_ajit.py b/pypy/jit/metainterp/test/test_ajit.py --- a/pypy/jit/metainterp/test/test_ajit.py +++ b/pypy/jit/metainterp/test/test_ajit.py @@ -1,3 +1,4 @@ +import math import sys import py @@ -15,7 +16,7 @@ loop_invariant, elidable, promote, jit_debug, assert_green, AssertGreenFailed, unroll_safe, current_trace_length, look_inside_iff, isconstant, isvirtual, promote_string, set_param, record_known_class) -from pypy.rlib.longlong2float import float2longlong +from pypy.rlib.longlong2float import float2longlong, longlong2float from pypy.rlib.rarithmetic import ovfcheck, is_valid_int from pypy.rpython.lltypesystem import lltype, llmemory, rffi from pypy.rpython.ootypesystem import ootype @@ -3795,15 +3796,15 @@ res = self.interp_operations(g, [1]) assert res == 3 - def test_float2longlong(self): + def test_float_bytes(self): def f(n): - return float2longlong(n) + ll = float2longlong(n) + return longlong2float(ll) for x in [2.5, float("nan"), -2.5, float("inf")]: # There are tests elsewhere to verify the correctness of this. - expected = float2longlong(x) res = self.interp_operations(f, [x]) - assert longlong.getfloatstorage(res) == expected + assert res == x or math.isnan(x) and math.isnan(res) class TestLLtype(BaseLLtypeTests, LLJitMixin): diff --git a/pypy/module/array/interp_array.py b/pypy/module/array/interp_array.py --- a/pypy/module/array/interp_array.py +++ b/pypy/module/array/interp_array.py @@ -11,7 +11,7 @@ from pypy.objspace.std.register_all import register_all from pypy.rlib.rarithmetic import ovfcheck from pypy.rlib.unroll import unrolling_iterable -from pypy.rlib.objectmodel import specialize +from pypy.rlib.objectmodel import specialize, keepalive_until_here from pypy.rpython.lltypesystem import lltype, rffi @@ -145,21 +145,27 @@ unroll_typecodes = unrolling_iterable(types.keys()) class ArrayBuffer(RWBuffer): - def __init__(self, data, bytes): - self.data = data - self.len = bytes + def __init__(self, array): + self.array = array def getlength(self): - return self.len + return self.array.len * self.array.itemsize def getitem(self, index): - return self.data[index] + array = self.array + data = array._charbuf_start() + char = data[index] + array._charbuf_stop() + return char def setitem(self, index, char): - self.data[index] = char + array = self.array + data = array._charbuf_start() + data[index] = char + array._charbuf_stop() def get_raw_address(self): - return self.data + return self.array._charbuf_start() def make_array(mytype): @@ -281,9 +287,10 @@ oldlen = self.len new = len(s) / mytype.bytes self.setlen(oldlen + new) - cbuf = self.charbuf() + cbuf = self._charbuf_start() for i in range(len(s)): cbuf[oldlen * mytype.bytes + i] = s[i] + self._charbuf_stop() def fromlist(self, w_lst): s = self.len @@ -313,8 +320,11 @@ else: self.fromsequence(w_iterable) - def charbuf(self): - return rffi.cast(rffi.CCHARP, self.buffer) + def _charbuf_start(self): + return rffi.cast(rffi.CCHARP, self.buffer) + + def _charbuf_stop(self): + keepalive_until_here(self) def w_getitem(self, space, idx): item = self.buffer[idx] @@ -533,8 +543,10 @@ self.fromstring(space.str_w(w_s)) def array_tostring__Array(space, self): - cbuf = self.charbuf() - return self.space.wrap(rffi.charpsize2str(cbuf, self.len * mytype.bytes)) + cbuf = self._charbuf_start() + s = rffi.charpsize2str(cbuf, self.len * mytype.bytes) + self._charbuf_stop() + return self.space.wrap(s) def array_fromfile__Array_ANY_ANY(space, self, w_f, w_n): if not isinstance(w_f, W_File): @@ -616,8 +628,7 @@ # Misc methods def buffer__Array(space, self): - b = ArrayBuffer(self.charbuf(), self.len * mytype.bytes) - return space.wrap(b) + return space.wrap(ArrayBuffer(self)) def array_buffer_info__Array(space, self): w_ptr = space.wrap(rffi.cast(lltype.Unsigned, self.buffer)) @@ -652,7 +663,7 @@ raise OperationError(space.w_RuntimeError, space.wrap(msg)) if self.len == 0: return - bytes = self.charbuf() + bytes = self._charbuf_start() tmp = [bytes[0]] * mytype.bytes for start in range(0, self.len * mytype.bytes, mytype.bytes): stop = start + mytype.bytes - 1 @@ -660,6 +671,7 @@ tmp[i] = bytes[start + i] for i in range(mytype.bytes): bytes[stop - i] = tmp[i] + self._charbuf_stop() def repr__Array(space, self): if self.len == 0: diff --git a/pypy/module/array/test/test_array.py b/pypy/module/array/test/test_array.py --- a/pypy/module/array/test/test_array.py +++ b/pypy/module/array/test/test_array.py @@ -433,7 +433,25 @@ a = self.array('h', 'Hi') buf = buffer(a) assert buf[1] == 'i' - #raises(TypeError, buf.__setitem__, 1, 'o') + + def test_buffer_write(self): + a = self.array('c', 'hello') + buf = buffer(a) + print repr(buf) + try: + buf[3] = 'L' + except TypeError: + skip("buffer(array) returns a read-only buffer on CPython") + assert a.tostring() == 'helLo' + + def test_buffer_keepalive(self): + buf = buffer(self.array('c', 'text')) + assert buf[2] == 'x' + # + a = self.array('c', 'foobarbaz') + buf = buffer(a) + a.fromstring('some extra text') + assert buf[:] == 'foobarbazsome extra text' def test_list_methods(self): assert repr(self.array('i')) == "array('i')" diff --git a/pypy/module/cpyext/bufferobject.py b/pypy/module/cpyext/bufferobject.py --- a/pypy/module/cpyext/bufferobject.py +++ b/pypy/module/cpyext/bufferobject.py @@ -4,6 +4,8 @@ PyObjectFields, PyObject) from pypy.module.cpyext.pyobject import make_typedescr, Py_DecRef from pypy.interpreter.buffer import Buffer, StringBuffer, SubBuffer +from pypy.interpreter.error import OperationError +from pypy.module.array.interp_array import ArrayBuffer PyBufferObjectStruct = lltype.ForwardReference() @@ -43,10 +45,15 @@ if isinstance(w_obj, StringBuffer): py_buf.c_b_base = rffi.cast(PyObject, 0) # space.wrap(w_obj.value) - py_buf.c_b_ptr = rffi.cast(rffi.VOIDP, rffi.str2charp(w_obj.as_str())) + py_buf.c_b_ptr = rffi.cast(rffi.VOIDP, rffi.str2charp(w_obj.value)) + py_buf.c_b_size = w_obj.getlength() + elif isinstance(w_obj, ArrayBuffer): + py_buf.c_b_base = rffi.cast(PyObject, 0) # space.wrap(w_obj.value) + py_buf.c_b_ptr = rffi.cast(rffi.VOIDP, w_obj.data) py_buf.c_b_size = w_obj.getlength() else: - raise Exception("Fail fail fail fail fail") + raise OperationError(space.w_NotImplementedError, space.wrap( + "buffer flavor not supported")) def buffer_realize(space, py_obj): diff --git a/pypy/module/cpyext/test/test_bufferobject.py b/pypy/module/cpyext/test/test_bufferobject.py --- a/pypy/module/cpyext/test/test_bufferobject.py +++ b/pypy/module/cpyext/test/test_bufferobject.py @@ -48,3 +48,17 @@ ]) b = module.buffer_new() raises(AttributeError, getattr, b, 'x') + + def test_array_buffer(self): + module = self.import_extension('foo', [ + ("roundtrip", "METH_O", + """ + PyBufferObject *buf = (PyBufferObject *)args; + return PyString_FromStringAndSize(buf->b_ptr, buf->b_size); + """), + ]) + import array + a = array.array('c', 'text') + b = buffer(a) + assert module.roundtrip(b) == 'text' + diff --git a/pypy/module/micronumpy/__init__.py b/pypy/module/micronumpy/__init__.py --- a/pypy/module/micronumpy/__init__.py +++ b/pypy/module/micronumpy/__init__.py @@ -99,6 +99,8 @@ ("exp2", "exp2"), ("expm1", "expm1"), ("fabs", "fabs"), + ("fmax", "fmax"), + ("fmin", "fmin"), ("fmod", "fmod"), ("floor", "floor"), ("ceil", "ceil"), @@ -122,12 +124,14 @@ ("sinh", "sinh"), ("subtract", "subtract"), ('sqrt', 'sqrt'), + ('square', 'square'), ("tan", "tan"), ("tanh", "tanh"), ('bitwise_and', 'bitwise_and'), ('bitwise_or', 'bitwise_or'), ('bitwise_xor', 'bitwise_xor'), ('bitwise_not', 'invert'), + ('invert', 'invert'), ('isnan', 'isnan'), ('isinf', 'isinf'), ('isneginf', 'isneginf'), diff --git a/pypy/module/micronumpy/interp_ufuncs.py b/pypy/module/micronumpy/interp_ufuncs.py --- a/pypy/module/micronumpy/interp_ufuncs.py +++ b/pypy/module/micronumpy/interp_ufuncs.py @@ -3,9 +3,11 @@ from pypy.interpreter.gateway import interp2app, unwrap_spec, NoneNotWrapped from pypy.interpreter.typedef import TypeDef, GetSetProperty, interp_attrproperty from pypy.module.micronumpy import interp_boxes, interp_dtype, support, loop +from pypy.rlib import jit from pypy.rlib.rarithmetic import LONG_BIT from pypy.tool.sourcetools import func_with_new_name + class W_Ufunc(Wrappable): _attrs_ = ["name", "promote_to_float", "promote_bools", "identity"] _immutable_fields_ = ["promote_to_float", "promote_bools", "name"] @@ -28,7 +30,7 @@ return self.identity def descr_call(self, space, __args__): - from interp_numarray import BaseArray + from interp_numarray import BaseArray args_w, kwds_w = __args__.unpack() # it occurs to me that we don't support any datatypes that # require casting, change it later when we do @@ -179,7 +181,7 @@ elif out.shape != shape: raise operationerrfmt(space.w_ValueError, 'output parameter shape mismatch, expecting [%s]' + - ' , got [%s]', + ' , got [%s]', ",".join([str(x) for x in shape]), ",".join([str(x) for x in out.shape]), ) @@ -204,7 +206,7 @@ else: arr = ReduceArray(self.func, self.name, self.identity, obj, dtype) val = loop.compute(arr) - return val + return val def do_axis_reduce(self, obj, dtype, axis, result): from pypy.module.micronumpy.interp_numarray import AxisReduce @@ -253,7 +255,7 @@ if isinstance(w_obj, Scalar): arr = self.func(calc_dtype, w_obj.value.convert_to(calc_dtype)) if isinstance(out,Scalar): - out.value=arr + out.value = arr elif isinstance(out, BaseArray): out.fill(space, arr) else: @@ -265,7 +267,7 @@ if not broadcast_shape or broadcast_shape != out.shape: raise operationerrfmt(space.w_ValueError, 'output parameter shape mismatch, could not broadcast [%s]' + - ' to [%s]', + ' to [%s]', ",".join([str(x) for x in w_obj.shape]), ",".join([str(x) for x in out.shape]), ) @@ -292,10 +294,11 @@ self.func = func self.comparison_func = comparison_func + @jit.unroll_safe def call(self, space, args_w): from pypy.module.micronumpy.interp_numarray import (Call2, convert_to_array, Scalar, shape_agreement, BaseArray) - if len(args_w)>2: + if len(args_w) > 2: [w_lhs, w_rhs, w_out] = args_w else: [w_lhs, w_rhs] = args_w @@ -326,7 +329,7 @@ w_rhs.value.convert_to(calc_dtype) ) if isinstance(out,Scalar): - out.value=arr + out.value = arr elif isinstance(out, BaseArray): out.fill(space, arr) else: @@ -337,7 +340,7 @@ if out and out.shape != shape_agreement(space, new_shape, out.shape): raise operationerrfmt(space.w_ValueError, 'output parameter shape mismatch, could not broadcast [%s]' + - ' to [%s]', + ' to [%s]', ",".join([str(x) for x in new_shape]), ",".join([str(x) for x in out.shape]), ) @@ -347,7 +350,6 @@ w_lhs.add_invalidates(w_res) w_rhs.add_invalidates(w_res) if out: - #out.add_invalidates(w_res) #causes a recursion loop w_res.get_concrete() return w_res @@ -539,6 +541,8 @@ ("reciprocal", "reciprocal", 1), ("fabs", "fabs", 1, {"promote_to_float": True}), + ("fmax", "fmax", 2, {"promote_to_float": True}), + ("fmin", "fmin", 2, {"promote_to_float": True}), ("fmod", "fmod", 2, {"promote_to_float": True}), ("floor", "floor", 1, {"promote_to_float": True}), ("ceil", "ceil", 1, {"promote_to_float": True}), @@ -547,6 +551,7 @@ ("expm1", "expm1", 1, {"promote_to_float": True}), ('sqrt', 'sqrt', 1, {'promote_to_float': True}), + ('square', 'square', 1, {'promote_to_float': True}), ("sin", "sin", 1, {"promote_to_float": True}), ("cos", "cos", 1, {"promote_to_float": True}), diff --git a/pypy/module/micronumpy/strides.py b/pypy/module/micronumpy/strides.py --- a/pypy/module/micronumpy/strides.py +++ b/pypy/module/micronumpy/strides.py @@ -1,6 +1,7 @@ from pypy.rlib import jit from pypy.interpreter.error import OperationError + at jit.look_inside_iff(lambda chunks: jit.isconstant(len(chunks))) def enumerate_chunks(chunks): result = [] i = -1 @@ -85,9 +86,9 @@ space.isinstance_w(w_item_or_slice, space.w_slice)): raise OperationError(space.w_IndexError, space.wrap('unsupported iterator index')) - + start, stop, step, lngth = space.decode_index4(w_item_or_slice, size) - + coords = [0] * len(shape) i = start if order == 'C': diff --git a/pypy/module/micronumpy/support.py b/pypy/module/micronumpy/support.py --- a/pypy/module/micronumpy/support.py +++ b/pypy/module/micronumpy/support.py @@ -1,5 +1,9 @@ +from pypy.rlib import jit + + + at jit.unroll_safe def product(s): i = 1 for x in s: i *= x - return i \ No newline at end of file + return i diff --git a/pypy/module/micronumpy/test/test_ufuncs.py b/pypy/module/micronumpy/test/test_ufuncs.py --- a/pypy/module/micronumpy/test/test_ufuncs.py +++ b/pypy/module/micronumpy/test/test_ufuncs.py @@ -135,6 +135,38 @@ assert fabs(float('-inf')) == float('inf') assert isnan(fabs(float('nan'))) + def test_fmax(self): + from _numpypy import fmax + import math + + nnan, nan, inf, ninf = float('-nan'), float('nan'), float('inf'), float('-inf') + + a = [ninf, -5, 0, 5, inf] + assert (fmax(a, [ninf]*5) == a).all() + assert (fmax(a, [inf]*5) == [inf]*5).all() + assert (fmax(a, [1]*5) == [1, 1, 1, 5, inf]).all() + assert math.isnan(fmax(nan, 0)) + assert math.isnan(fmax(0, nan)) + assert math.isnan(fmax(nan, nan)) + # The numpy docs specify that the FIRST NaN should be used if both are NaN + assert math.copysign(1.0, fmax(nnan, nan)) == -1.0 + + def test_fmin(self): + from _numpypy import fmin + import math + + nnan, nan, inf, ninf = float('-nan'), float('nan'), float('inf'), float('-inf') + + a = [ninf, -5, 0, 5, inf] + assert (fmin(a, [ninf]*5) == [ninf]*5).all() + assert (fmin(a, [inf]*5) == a).all() + assert (fmin(a, [1]*5) == [ninf, -5, 0, 1, 1]).all() + assert math.isnan(fmin(nan, 0)) + assert math.isnan(fmin(0, nan)) + assert math.isnan(fmin(nan, nan)) + # The numpy docs specify that the FIRST NaN should be used if both are NaN + assert math.copysign(1.0, fmin(nnan, nan)) == -1.0 + def test_fmod(self): from _numpypy import fmod import math @@ -455,6 +487,19 @@ assert math.isnan(sqrt(-1)) assert math.isnan(sqrt(nan)) + def test_square(self): + import math + from _numpypy import square + + nan, inf, ninf = float("nan"), float("inf"), float("-inf") + + assert math.isnan(square(nan)) + assert math.isinf(square(inf)) + assert math.isinf(square(ninf)) + assert square(ninf) > 0 + assert [square(x) for x in range(-5, 5)] == [x*x for x in range(-5, 5)] + assert math.isinf(square(1e300)) + def test_radians(self): import math from _numpypy import radians, array @@ -546,10 +591,11 @@ raises(TypeError, 'array([1.0]) & 1') def test_unary_bitops(self): - from _numpypy import bitwise_not, array + from _numpypy import bitwise_not, invert, array a = array([1, 2, 3, 4]) assert (~a == [-2, -3, -4, -5]).all() assert (bitwise_not(a) == ~a).all() + assert (invert(a) == ~a).all() def test_comparisons(self): import operator diff --git a/pypy/module/micronumpy/types.py b/pypy/module/micronumpy/types.py --- a/pypy/module/micronumpy/types.py +++ b/pypy/module/micronumpy/types.py @@ -631,6 +631,22 @@ return math.fabs(v) @simple_binary_op + def fmax(self, v1, v2): + if math.isnan(v1): + return v1 + elif math.isnan(v2): + return v2 + return max(v1, v2) + + @simple_binary_op + def fmin(self, v1, v2): + if math.isnan(v1): + return v1 + elif math.isnan(v2): + return v2 + return min(v1, v2) + + @simple_binary_op def fmod(self, v1, v2): try: return math.fmod(v1, v2) @@ -741,6 +757,10 @@ except ValueError: return rfloat.NAN + @simple_unary_op + def square(self, v): + return v*v + @raw_unary_op def isnan(self, v): return rfloat.isnan(v) diff --git a/pypy/module/pypyjit/test_pypy_c/test_misc.py b/pypy/module/pypyjit/test_pypy_c/test_misc.py --- a/pypy/module/pypyjit/test_pypy_c/test_misc.py +++ b/pypy/module/pypyjit/test_pypy_c/test_misc.py @@ -351,3 +351,23 @@ # the following assertion fails if the loop was cancelled due # to "abort: vable escape" assert len(log.loops_by_id("eval")) == 1 + + def test_sys_exc_info(self): + def main(): + i = 1 + lst = [i] + while i < 1000: + try: + return lst[i] + except: + e = sys.exc_info()[1] # ID: exc_info + if not isinstance(e, IndexError): + raise + i += 1 + return 42 + + log = self.run(main) + assert log.result == 42 + # the following assertion fails if the loop was cancelled due + # to "abort: vable escape" + assert len(log.loops_by_id("exc_info")) == 1 diff --git a/pypy/module/sys/test/test_sysmodule.py b/pypy/module/sys/test/test_sysmodule.py --- a/pypy/module/sys/test/test_sysmodule.py +++ b/pypy/module/sys/test/test_sysmodule.py @@ -595,3 +595,121 @@ assert len(frames) == 1 _, other_frame = frames.popitem() assert other_frame.f_code.co_name in ('other_thread', '?') + + +class AppTestSysExcInfoDirect: + + def setup_method(self, meth): + self.seen = [] + from pypy.module.sys import vm + def exc_info_with_tb(*args): + self.seen.append("n") # not optimized + return self.old[0](*args) + def exc_info_without_tb(*args): + self.seen.append("y") # optimized + return self.old[1](*args) + self.old = [vm.exc_info_with_tb, vm.exc_info_without_tb] + vm.exc_info_with_tb = exc_info_with_tb + vm.exc_info_without_tb = exc_info_without_tb + # + from pypy.rlib import jit + self.old2 = [jit.we_are_jitted] + jit.we_are_jitted = lambda: True + + def teardown_method(self, meth): + from pypy.module.sys import vm + from pypy.rlib import jit + vm.exc_info_with_tb = self.old[0] + vm.exc_info_without_tb = self.old[1] + jit.we_are_jitted = self.old2[0] + # + assert ''.join(self.seen) == meth.expected + + def test_returns_none(self): + import sys + assert sys.exc_info() == (None, None, None) + assert sys.exc_info()[0] is None + assert sys.exc_info()[1] is None + assert sys.exc_info()[2] is None + assert sys.exc_info()[:2] == (None, None) + assert sys.exc_info()[:3] == (None, None, None) + assert sys.exc_info()[0:2] == (None, None) + assert sys.exc_info()[2:4] == (None,) + test_returns_none.expected = 'nnnnnnnn' + + def test_returns_subscr(self): + import sys + e = KeyError("boom") + try: + raise e + except: + assert sys.exc_info()[0] is KeyError # y + assert sys.exc_info()[1] is e # y + assert sys.exc_info()[2] is not None # n + assert sys.exc_info()[-3] is KeyError # y + assert sys.exc_info()[-2] is e # y + assert sys.exc_info()[-1] is not None # n + test_returns_subscr.expected = 'yynyyn' + + def test_returns_slice_2(self): + import sys + e = KeyError("boom") + try: + raise e + except: + foo = sys.exc_info() # n + assert sys.exc_info()[:0] == () # y + assert sys.exc_info()[:1] == foo[:1] # y + assert sys.exc_info()[:2] == foo[:2] # y + assert sys.exc_info()[:3] == foo # n + assert sys.exc_info()[:4] == foo # n + assert sys.exc_info()[:-1] == foo[:2] # y + assert sys.exc_info()[:-2] == foo[:1] # y + assert sys.exc_info()[:-3] == () # y + test_returns_slice_2.expected = 'nyyynnyyy' + + def test_returns_slice_3(self): + import sys + e = KeyError("boom") + try: + raise e + except: + foo = sys.exc_info() # n + assert sys.exc_info()[2:2] == () # y + assert sys.exc_info()[0:1] == foo[:1] # y + assert sys.exc_info()[1:2] == foo[1:2] # y + assert sys.exc_info()[0:3] == foo # n + assert sys.exc_info()[2:4] == foo[2:] # n + assert sys.exc_info()[0:-1] == foo[:2] # y + assert sys.exc_info()[0:-2] == foo[:1] # y + assert sys.exc_info()[5:-3] == () # y + test_returns_slice_3.expected = 'nyyynnyyy' + + def test_strange_invocation(self): + import sys + e = KeyError("boom") + try: + raise e + except: + a = []; k = {} + assert sys.exc_info(*a)[:0] == () + assert sys.exc_info(**k)[:0] == () + test_strange_invocation.expected = 'nn' + + def test_call_in_subfunction(self): + import sys + def g(): + # this case is not optimized, because we need to search the + # frame chain. it's probably not worth the complications + return sys.exc_info()[1] + e = KeyError("boom") + try: + raise e + except: + assert g() is e + test_call_in_subfunction.expected = 'n' + + +class AppTestSysExcInfoDirectCallMethod(AppTestSysExcInfoDirect): + def setup_class(cls): + cls.space = gettestobjspace(**{"objspace.opcodes.CALL_METHOD": True}) diff --git a/pypy/module/sys/vm.py b/pypy/module/sys/vm.py --- a/pypy/module/sys/vm.py +++ b/pypy/module/sys/vm.py @@ -89,6 +89,9 @@ """Return the (type, value, traceback) of the most recent exception caught by an except clause in the current stack frame or in an older stack frame.""" + return exc_info_with_tb(space) # indirection for the tests + +def exc_info_with_tb(space): operror = space.getexecutioncontext().sys_exc_info() if operror is None: return space.newtuple([space.w_None,space.w_None,space.w_None]) @@ -96,6 +99,59 @@ return space.newtuple([operror.w_type, operror.get_w_value(space), space.wrap(operror.get_traceback())]) +def exc_info_without_tb(space, frame): + operror = frame.last_exception + return space.newtuple([operror.w_type, operror.get_w_value(space), + space.w_None]) + +def exc_info_direct(space, frame): + from pypy.tool import stdlib_opcode + # In order to make the JIT happy, we try to return (exc, val, None) + # instead of (exc, val, tb). We can do that only if we recognize + # the following pattern in the bytecode: + # CALL_FUNCTION/CALL_METHOD <-- invoking me + # LOAD_CONST 0, 1, -2 or -3 + # BINARY_SUBSCR + # or: + # CALL_FUNCTION/CALL_METHOD + # LOAD_CONST <=2 + # SLICE_2 + # or: + # CALL_FUNCTION/CALL_METHOD + # LOAD_CONST any integer + # LOAD_CONST <=2 + # SLICE_3 + need_all_three_args = True + co = frame.getcode().co_code + p = frame.last_instr + if (ord(co[p]) == stdlib_opcode.CALL_FUNCTION or + ord(co[p]) == stdlib_opcode.CALL_METHOD): + if ord(co[p+3]) == stdlib_opcode.LOAD_CONST: + lo = ord(co[p+4]) + hi = ord(co[p+5]) + w_constant = frame.getconstant_w((hi * 256) | lo) + if space.isinstance_w(w_constant, space.w_int): + constant = space.int_w(w_constant) + if ord(co[p+6]) == stdlib_opcode.BINARY_SUBSCR: + if -3 <= constant <= 1 and constant != -1: + need_all_three_args = False + elif ord(co[p+6]) == stdlib_opcode.SLICE+2: + if constant <= 2: + need_all_three_args = False + elif (ord(co[p+6]) == stdlib_opcode.LOAD_CONST and + ord(co[p+9]) == stdlib_opcode.SLICE+3): + lo = ord(co[p+7]) + hi = ord(co[p+8]) + w_constant = frame.getconstant_w((hi * 256) | lo) + if space.isinstance_w(w_constant, space.w_int): + if space.int_w(w_constant) <= 2: + need_all_three_args = False + # + if need_all_three_args or frame.last_exception is None or frame.hide(): + return exc_info_with_tb(space) + else: + return exc_info_without_tb(space, frame) + def exc_clear(space): """Clear global information on the current exception. Subsequent calls to exc_info() will return (None,None,None) until another exception is diff --git a/pypy/rlib/longlong2float.py b/pypy/rlib/longlong2float.py --- a/pypy/rlib/longlong2float.py +++ b/pypy/rlib/longlong2float.py @@ -21,7 +21,7 @@ FLOAT_ARRAY_PTR = lltype.Ptr(lltype.Array(rffi.FLOAT)) # these definitions are used only in tests, when not translated -def longlong2float_emulator(llval): +def longlong2float(llval): with lltype.scoped_alloc(DOUBLE_ARRAY_PTR.TO, 1) as d_array: ll_array = rffi.cast(LONGLONG_ARRAY_PTR, d_array) ll_array[0] = llval @@ -51,12 +51,6 @@ eci = ExternalCompilationInfo(includes=['string.h', 'assert.h'], post_include_bits=[""" -static double pypy__longlong2float(long long x) { - double dd; - assert(sizeof(double) == 8 && sizeof(long long) == 8); - memcpy(&dd, &x, 8); - return dd; -} static float pypy__uint2singlefloat(unsigned int x) { float ff; assert(sizeof(float) == 4 && sizeof(unsigned int) == 4); @@ -71,12 +65,6 @@ } """]) -longlong2float = rffi.llexternal( - "pypy__longlong2float", [rffi.LONGLONG], rffi.DOUBLE, - _callable=longlong2float_emulator, compilation_info=eci, - _nowrapper=True, elidable_function=True, sandboxsafe=True, - oo_primitive="pypy__longlong2float") - uint2singlefloat = rffi.llexternal( "pypy__uint2singlefloat", [rffi.UINT], rffi.FLOAT, _callable=uint2singlefloat_emulator, compilation_info=eci, @@ -99,4 +87,17 @@ def specialize_call(self, hop): [v_float] = hop.inputargs(lltype.Float) - return hop.genop("convert_float_bytes_to_longlong", [v_float], resulttype=hop.r_result) + hop.exception_cannot_occur() + return hop.genop("convert_float_bytes_to_longlong", [v_float], resulttype=lltype.SignedLongLong) + +class LongLong2FloatEntry(ExtRegistryEntry): + _about_ = longlong2float + + def compute_result_annotation(self, s_longlong): + assert annmodel.SomeInteger(knowntype=r_int64).contains(s_longlong) + return annmodel.SomeFloat() + + def specialize_call(self, hop): + [v_longlong] = hop.inputargs(lltype.SignedLongLong) + hop.exception_cannot_occur() + return hop.genop("convert_longlong_bytes_to_float", [v_longlong], resulttype=lltype.Float) diff --git a/pypy/rlib/test/test_longlong2float.py b/pypy/rlib/test/test_longlong2float.py --- a/pypy/rlib/test/test_longlong2float.py +++ b/pypy/rlib/test/test_longlong2float.py @@ -2,6 +2,7 @@ from pypy.rlib.longlong2float import longlong2float, float2longlong from pypy.rlib.longlong2float import uint2singlefloat, singlefloat2uint from pypy.rlib.rarithmetic import r_singlefloat +from pypy.rpython.test.test_llinterp import interpret def fn(f1): @@ -31,6 +32,18 @@ res = fn2(x) assert repr(res) == repr(x) +def test_interpreted(): + def f(f1): + try: + ll = float2longlong(f1) + return longlong2float(ll) + except Exception: + return 500 + + for x in enum_floats(): + res = interpret(f, [x]) + assert repr(res) == repr(x) + # ____________________________________________________________ def fnsingle(f1): diff --git a/pypy/rpython/lltypesystem/lloperation.py b/pypy/rpython/lltypesystem/lloperation.py --- a/pypy/rpython/lltypesystem/lloperation.py +++ b/pypy/rpython/lltypesystem/lloperation.py @@ -350,6 +350,7 @@ 'truncate_longlong_to_int':LLOp(canfold=True), 'force_cast': LLOp(sideeffects=False), # only for rffi.cast() 'convert_float_bytes_to_longlong': LLOp(canfold=True), + 'convert_longlong_bytes_to_float': LLOp(canfold=True), # __________ pointer operations __________ diff --git a/pypy/rpython/lltypesystem/opimpl.py b/pypy/rpython/lltypesystem/opimpl.py --- a/pypy/rpython/lltypesystem/opimpl.py +++ b/pypy/rpython/lltypesystem/opimpl.py @@ -431,6 +431,10 @@ from pypy.rlib.longlong2float import float2longlong return float2longlong(a) +def op_convert_longlong_bytes_to_float(a): + from pypy.rlib.longlong2float import longlong2float + return longlong2float(a) + def op_unichar_eq(x, y): assert isinstance(x, unicode) and len(x) == 1 diff --git a/pypy/rpython/lltypesystem/rstr.py b/pypy/rpython/lltypesystem/rstr.py --- a/pypy/rpython/lltypesystem/rstr.py +++ b/pypy/rpython/lltypesystem/rstr.py @@ -765,7 +765,11 @@ def _ll_stringslice(s1, start, stop): lgt = stop - start assert start >= 0 - assert lgt >= 0 + # If start > stop, return a empty string. This can happen if the start + # is greater than the length of the string. Use < instead of <= to avoid + # creating another path for the JIT when start == stop. + if lgt < 0: + return s1.empty() newstr = s1.malloc(lgt) s1.copy_contents(s1, newstr, start, 0, lgt) return newstr diff --git a/pypy/rpython/ootypesystem/rstr.py b/pypy/rpython/ootypesystem/rstr.py --- a/pypy/rpython/ootypesystem/rstr.py +++ b/pypy/rpython/ootypesystem/rstr.py @@ -222,6 +222,10 @@ length = s.ll_strlen() if stop > length: stop = length + # If start > stop, return a empty string. This can happen if the start + # is greater than the length of the string. + if start > stop: + start = stop return s.ll_substring(start, stop-start) def ll_stringslice_minusone(s): diff --git a/pypy/rpython/test/test_rstr.py b/pypy/rpython/test/test_rstr.py --- a/pypy/rpython/test/test_rstr.py +++ b/pypy/rpython/test/test_rstr.py @@ -477,7 +477,11 @@ s1 = s[:3] s2 = s[3:] s3 = s[3:10] - return s1+s2 == s and s2+s1 == const('lohel') and s1+s3 == s + s4 = s[42:44] + return (s1+s2 == s and + s2+s1 == const('lohel') and + s1+s3 == s and + s4 == const('')) res = self.interpret(fn, [0]) assert res diff --git a/pypy/tool/clean_old_branches.py b/pypy/tool/clean_old_branches.py --- a/pypy/tool/clean_old_branches.py +++ b/pypy/tool/clean_old_branches.py @@ -38,7 +38,7 @@ closed_heads.reverse() for head, branch in closed_heads: - print '\t', branch + print '\t', head, '\t', branch print print 'The branches listed above will be merged to "closed-branches".' print 'You need to run this script in a clean working copy where you' diff --git a/pypy/translator/c/gcc/test/test_asmgcroot.py b/pypy/translator/c/gcc/test/test_asmgcroot.py --- a/pypy/translator/c/gcc/test/test_asmgcroot.py +++ b/pypy/translator/c/gcc/test/test_asmgcroot.py @@ -7,10 +7,17 @@ from pypy import conftest from pypy.translator.tool.cbuild import ExternalCompilationInfo from pypy.translator.platform import platform as compiler +from pypy.rlib.rarithmetic import is_emulated_long from pypy.rpython.lltypesystem import lltype, rffi from pypy.rlib.entrypoint import entrypoint, secondary_entrypoints from pypy.rpython.lltypesystem.lloperation import llop +_MSVC = compiler.name == "msvc" +_MINGW = compiler.name == "mingw32" +_WIN32 = _MSVC or _MINGW +_WIN64 = _WIN32 and is_emulated_long +# XXX get rid of 'is_emulated_long' and have a real config here. + class AbstractTestAsmGCRoot: # the asmgcroot gc transformer doesn't generate gc_reload_possibly_moved # instructions: @@ -18,8 +25,8 @@ @classmethod def make_config(cls): - if compiler.name == "msvc": - py.test.skip("all asmgcroot tests disabled for MSVC") + if _MSVC and _WIN64: + py.test.skip("all asmgcroot tests disabled for MSVC X64") from pypy.config.pypyoption import get_pypy_config config = get_pypy_config(translating=True) config.translation.gc = cls.gcpolicy diff --git a/pypy/translator/c/src/float.h b/pypy/translator/c/src/float.h --- a/pypy/translator/c/src/float.h +++ b/pypy/translator/c/src/float.h @@ -43,5 +43,6 @@ #define OP_CAST_FLOAT_TO_LONGLONG(x,r) r = (long long)(x) #define OP_CAST_FLOAT_TO_ULONGLONG(x,r) r = (unsigned long long)(x) #define OP_CONVERT_FLOAT_BYTES_TO_LONGLONG(x,r) memcpy(&r, &x, sizeof(double)) +#define OP_CONVERT_LONGLONG_BYTES_TO_FLOAT(x,r) memcpy(&r, &x, sizeof(long long)) #endif diff --git a/pypy/translator/jvm/opcodes.py b/pypy/translator/jvm/opcodes.py --- a/pypy/translator/jvm/opcodes.py +++ b/pypy/translator/jvm/opcodes.py @@ -243,4 +243,5 @@ 'force_cast': [PushAllArgs, CastPrimitive, StoreResult], 'convert_float_bytes_to_longlong': jvm.PYPYDOUBLEBYTESTOLONG, + 'convert_longlong_bytes_to_float': jvm.PYPYLONGBYTESTODOUBLE, }) diff --git a/pypy/translator/jvm/typesystem.py b/pypy/translator/jvm/typesystem.py --- a/pypy/translator/jvm/typesystem.py +++ b/pypy/translator/jvm/typesystem.py @@ -942,6 +942,7 @@ PYPYULONGTODOUBLE = Method.s(jPyPy, 'ulong_to_double', (jLong,), jDouble) PYPYLONGBITWISENEGATE = Method.v(jPyPy, 'long_bitwise_negate', (jLong,), jLong) PYPYDOUBLEBYTESTOLONG = Method.v(jPyPy, 'pypy__float2longlong', (jDouble,), jLong) +PYPYLONGBYTESTODOUBLE = Method.v(jPyPy, 'pypy__longlong2float', (jLong,), jDouble) PYPYSTRTOINT = Method.v(jPyPy, 'str_to_int', (jString,), jInt) PYPYSTRTOUINT = Method.v(jPyPy, 'str_to_uint', (jString,), jInt) PYPYSTRTOLONG = Method.v(jPyPy, 'str_to_long', (jString,), jLong) From noreply at buildbot.pypy.org Tue Apr 3 01:43:48 2012 From: noreply at buildbot.pypy.org (wlav) Date: Tue, 3 Apr 2012 01:43:48 +0200 (CEST) Subject: [pypy-commit] pypy reflex-support: merge default Message-ID: <20120402234348.8790046E0EF@wyvern.cs.uni-duesseldorf.de> Author: Wim Lavrijsen Branch: reflex-support Changeset: r54167:748a53a1cc24 Date: 2012-04-02 15:59 -0700 http://bitbucket.org/pypy/pypy/changeset/748a53a1cc24/ Log: merge default diff --git a/dotviewer/graphparse.py b/dotviewer/graphparse.py --- a/dotviewer/graphparse.py +++ b/dotviewer/graphparse.py @@ -93,6 +93,7 @@ return result def parse_plain(graph_id, plaincontent, links={}, fixedfont=False): + plaincontent = plaincontent.replace('\r\n', '\n') # fix Windows EOL lines = plaincontent.splitlines(True) for i in range(len(lines)-2, -1, -1): if lines[i].endswith('\\\n'): # line ending in '\' diff --git a/lib_pypy/datetime.py b/lib_pypy/datetime.py --- a/lib_pypy/datetime.py +++ b/lib_pypy/datetime.py @@ -968,8 +968,7 @@ self._checkOverflow(t.year) result = date(t.year, t.month, t.day) return result - raise TypeError - # XXX Should be 'return NotImplemented', but there's a bug in 2.2... + return NotImplemented # note that this doesn't work on CPython 2.2 __radd__ = __add__ diff --git a/pypy/interpreter/executioncontext.py b/pypy/interpreter/executioncontext.py --- a/pypy/interpreter/executioncontext.py +++ b/pypy/interpreter/executioncontext.py @@ -167,6 +167,11 @@ frame = self.getnextframe_nohidden(frame) return None + def set_sys_exc_info(self, operror): + frame = self.gettopframe_nohidden() + if frame: # else, the exception goes nowhere and is lost + frame.last_exception = operror + def settrace(self, w_func): """Set the global trace function.""" if self.space.is_w(w_func, self.space.w_None): diff --git a/pypy/jit/backend/llgraph/llimpl.py b/pypy/jit/backend/llgraph/llimpl.py --- a/pypy/jit/backend/llgraph/llimpl.py +++ b/pypy/jit/backend/llgraph/llimpl.py @@ -1797,6 +1797,7 @@ if specialize_as_constant: def specialize_call(self, hop): llvalue = func(hop.args_s[0].const) + hop.exception_cannot_occur() return hop.inputconst(lltype.typeOf(llvalue), llvalue) else: # specialize as direct_call @@ -1813,6 +1814,7 @@ sm = ootype._static_meth(FUNCTYPE, _name=func.__name__, _callable=func) cfunc = hop.inputconst(FUNCTYPE, sm) args_v = hop.inputargs(*hop.args_r) + hop.exception_is_here() return hop.genop('direct_call', [cfunc] + args_v, hop.r_result) diff --git a/pypy/module/_ssl/interp_ssl.py b/pypy/module/_ssl/interp_ssl.py --- a/pypy/module/_ssl/interp_ssl.py +++ b/pypy/module/_ssl/interp_ssl.py @@ -432,7 +432,8 @@ raise _ssl_seterror(self.space, self, length) try: # this is actually an immutable bytes sequence - return self.space.wrap(rffi.charp2str(buf_ptr[0])) + return self.space.wrap(rffi.charpsize2str(buf_ptr[0], + length)) finally: libssl_OPENSSL_free(buf_ptr[0]) else: diff --git a/pypy/module/cpyext/bufferobject.py b/pypy/module/cpyext/bufferobject.py --- a/pypy/module/cpyext/bufferobject.py +++ b/pypy/module/cpyext/bufferobject.py @@ -2,7 +2,7 @@ from pypy.module.cpyext.api import ( cpython_api, Py_ssize_t, cpython_struct, bootstrap_function, PyObjectFields, PyObject) -from pypy.module.cpyext.pyobject import make_typedescr, Py_DecRef +from pypy.module.cpyext.pyobject import make_typedescr, Py_DecRef, make_ref from pypy.interpreter.buffer import Buffer, StringBuffer, SubBuffer from pypy.interpreter.error import OperationError from pypy.module.array.interp_array import ArrayBuffer @@ -43,13 +43,18 @@ py_buf.c_b_offset = w_obj.offset w_obj = w_obj.buffer + # If w_obj already allocated a fixed buffer, use it, and keep a + # reference to w_obj. + # Otherwise, b_base stays NULL, and we own the b_ptr. + if isinstance(w_obj, StringBuffer): - py_buf.c_b_base = rffi.cast(PyObject, 0) # space.wrap(w_obj.value) + py_buf.c_b_base = lltype.nullptr(PyObject.TO) py_buf.c_b_ptr = rffi.cast(rffi.VOIDP, rffi.str2charp(w_obj.value)) py_buf.c_b_size = w_obj.getlength() elif isinstance(w_obj, ArrayBuffer): - py_buf.c_b_base = rffi.cast(PyObject, 0) # space.wrap(w_obj.value) - py_buf.c_b_ptr = rffi.cast(rffi.VOIDP, w_obj.data) + w_base = w_obj.array + py_buf.c_b_base = make_ref(space, w_base) + py_buf.c_b_ptr = rffi.cast(rffi.VOIDP, w_obj.array._charbuf_start()) py_buf.c_b_size = w_obj.getlength() else: raise OperationError(space.w_NotImplementedError, space.wrap( @@ -60,14 +65,16 @@ """ Creates the buffer in the PyPy interpreter from a cpyext representation. """ - raise Exception("realize fail fail fail") - + raise OperationError(space.w_NotImplementedError, space.wrap( + "Don't know how to realize a buffer")) @cpython_api([PyObject], lltype.Void, external=False) def buffer_dealloc(space, py_obj): py_buf = rffi.cast(PyBufferObject, py_obj) - Py_DecRef(space, py_buf.c_b_base) - rffi.free_charp(rffi.cast(rffi.CCHARP, py_buf.c_b_ptr)) + if py_buf.c_b_base: + Py_DecRef(space, py_buf.c_b_base) + else: + rffi.free_charp(rffi.cast(rffi.CCHARP, py_buf.c_b_ptr)) from pypy.module.cpyext.object import PyObject_dealloc PyObject_dealloc(space, py_obj) diff --git a/pypy/module/cpyext/pyerrors.py b/pypy/module/cpyext/pyerrors.py --- a/pypy/module/cpyext/pyerrors.py +++ b/pypy/module/cpyext/pyerrors.py @@ -2,6 +2,7 @@ from pypy.rpython.lltypesystem import rffi, lltype from pypy.interpreter.error import OperationError +from pypy.interpreter import pytraceback from pypy.module.cpyext.api import cpython_api, CANNOT_FAIL, CONST_STRING from pypy.module.exceptions.interp_exceptions import W_RuntimeWarning from pypy.module.cpyext.pyobject import ( @@ -315,3 +316,65 @@ It may be called without holding the interpreter lock.""" space.check_signal_action.set_interrupt() + at cpython_api([PyObjectP, PyObjectP, PyObjectP], lltype.Void) +def PyErr_GetExcInfo(space, ptype, pvalue, ptraceback): + """---Cython extension--- + + Retrieve the exception info, as known from ``sys.exc_info()``. This + refers to an exception that was already caught, not to an exception + that was freshly raised. Returns new references for the three + objects, any of which may be *NULL*. Does not modify the exception + info state. + + .. note:: + + This function is not normally used by code that wants to handle + exceptions. Rather, it can be used when code needs to save and + restore the exception state temporarily. Use + :c:func:`PyErr_SetExcInfo` to restore or clear the exception + state. + """ + ec = space.getexecutioncontext() + operror = ec.sys_exc_info() + if operror: + ptype[0] = make_ref(space, operror.w_type) + pvalue[0] = make_ref(space, operror.get_w_value(space)) + ptraceback[0] = make_ref(space, space.wrap(operror.get_traceback())) + else: + ptype[0] = lltype.nullptr(PyObject.TO) + pvalue[0] = lltype.nullptr(PyObject.TO) + ptraceback[0] = lltype.nullptr(PyObject.TO) + + at cpython_api([PyObject, PyObject, PyObject], lltype.Void) +def PyErr_SetExcInfo(space, w_type, w_value, w_traceback): + """---Cython extension--- + + Set the exception info, as known from ``sys.exc_info()``. This refers + to an exception that was already caught, not to an exception that was + freshly raised. This function steals the references of the arguments. + To clear the exception state, pass *NULL* for all three arguments. + For general rules about the three arguments, see :c:func:`PyErr_Restore`. + + .. note:: + + This function is not normally used by code that wants to handle + exceptions. Rather, it can be used when code needs to save and + restore the exception state temporarily. Use + :c:func:`PyErr_GetExcInfo` to read the exception state. + """ + if w_value is None or space.is_w(w_value, space.w_None): + operror = None + else: + tb = None + if w_traceback is not None: + try: + tb = pytraceback.check_traceback(space, w_traceback, '?') + except OperationError: # catch and ignore bogus objects + pass + operror = OperationError(w_type, w_value, tb) + # + ec = space.getexecutioncontext() + ec.set_sys_exc_info(operror) + Py_DecRef(space, w_type) + Py_DecRef(space, w_value) + Py_DecRef(space, w_traceback) diff --git a/pypy/module/cpyext/slotdefs.py b/pypy/module/cpyext/slotdefs.py --- a/pypy/module/cpyext/slotdefs.py +++ b/pypy/module/cpyext/slotdefs.py @@ -167,14 +167,16 @@ if rffi.cast(lltype.Signed, res) == -1: space.fromcache(State).check_and_raise_exception(always=True) +# Warning, confusing function name (like CPython). Used only for sq_contains. def wrap_objobjproc(space, w_self, w_args, func): func_target = rffi.cast(objobjproc, func) check_num_args(space, w_args, 1) w_value, = space.fixedview(w_args) res = generic_cpy_call(space, func_target, w_self, w_value) - if rffi.cast(lltype.Signed, res) == -1: + res = rffi.cast(lltype.Signed, res) + if res == -1: space.fromcache(State).check_and_raise_exception(always=True) - return space.wrap(res) + return space.wrap(bool(res)) def wrap_objobjargproc(space, w_self, w_args, func): func_target = rffi.cast(objobjargproc, func) @@ -183,7 +185,7 @@ res = generic_cpy_call(space, func_target, w_self, w_key, w_value) if rffi.cast(lltype.Signed, res) == -1: space.fromcache(State).check_and_raise_exception(always=True) - return space.wrap(res) + return space.w_None def wrap_delitem(space, w_self, w_args, func): func_target = rffi.cast(objobjargproc, func) diff --git a/pypy/module/cpyext/test/foo.c b/pypy/module/cpyext/test/foo.c --- a/pypy/module/cpyext/test/foo.c +++ b/pypy/module/cpyext/test/foo.c @@ -176,6 +176,8 @@ {NULL} /* Sentinel */ }; +PyDoc_STRVAR(foo_doc, "foo is for testing."); + static PyTypeObject footype = { PyVarObject_HEAD_INIT(NULL, 0) "foo.foo", /*tp_name*/ @@ -198,7 +200,7 @@ (setattrofunc)foo_setattro, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ + foo_doc, /*tp_doc*/ 0, /*tp_traverse*/ 0, /*tp_clear*/ 0, /*tp_richcompare*/ diff --git a/pypy/module/cpyext/test/test_pyerrors.py b/pypy/module/cpyext/test/test_pyerrors.py --- a/pypy/module/cpyext/test/test_pyerrors.py +++ b/pypy/module/cpyext/test/test_pyerrors.py @@ -218,3 +218,51 @@ assert e.filename == "blyf" assert e.errno == errno.EBADF assert e.strerror == os.strerror(errno.EBADF) + + def test_GetSetExcInfo(self): + import sys + module = self.import_extension('foo', [ + ("getset_exc_info", "METH_VARARGS", + r''' + PyObject *type, *val, *tb; + PyObject *new_type, *new_val, *new_tb; + PyObject *result; + + if (!PyArg_ParseTuple(args, "OOO", &new_type, &new_val, &new_tb)) + return NULL; + + PyErr_GetExcInfo(&type, &val, &tb); + + Py_INCREF(new_type); + Py_INCREF(new_val); + Py_INCREF(new_tb); + PyErr_SetExcInfo(new_type, new_val, new_tb); + + result = Py_BuildValue("OOO", + type ? type : Py_None, + val ? val : Py_None, + tb ? tb : Py_None); + Py_XDECREF(type); + Py_XDECREF(val); + Py_XDECREF(tb); + return result; + ''' + ), + ]) + try: + raise ValueError(5) + except ValueError, old_exc: + new_exc = TypeError("TEST") + orig_sys_exc_info = sys.exc_info() + orig_exc_info = module.getset_exc_info(new_exc.__class__, + new_exc, None) + new_sys_exc_info = sys.exc_info() + new_exc_info = module.getset_exc_info(*orig_exc_info) + reset_sys_exc_info = sys.exc_info() + + assert orig_exc_info[0] is old_exc.__class__ + assert orig_exc_info[1] is old_exc + assert orig_exc_info == orig_sys_exc_info + assert orig_exc_info == reset_sys_exc_info + assert new_exc_info == (new_exc.__class__, new_exc, None) + assert new_exc_info == new_sys_exc_info diff --git a/pypy/module/cpyext/test/test_typeobject.py b/pypy/module/cpyext/test/test_typeobject.py --- a/pypy/module/cpyext/test/test_typeobject.py +++ b/pypy/module/cpyext/test/test_typeobject.py @@ -20,6 +20,7 @@ assert type(obj) is module.fooType print "type of obj has type", type(type(obj)) print "type of type of obj has type", type(type(type(obj))) + assert module.fooType.__doc__ == "foo is for testing." def test_typeobject_method_descriptor(self): module = self.import_module(name='foo') @@ -414,8 +415,11 @@ static int mp_ass_subscript(PyObject *self, PyObject *key, PyObject *value) { - PyErr_SetNone(PyExc_ZeroDivisionError); - return -1; + if (PyInt_Check(key)) { + PyErr_SetNone(PyExc_ZeroDivisionError); + return -1; + } + return 0; } PyMappingMethods tp_as_mapping; static PyTypeObject Foo_Type = { @@ -425,6 +429,36 @@ ''') obj = module.new_obj() raises(ZeroDivisionError, obj.__setitem__, 5, None) + res = obj.__setitem__('foo', None) + assert res is None + + def test_sq_contains(self): + module = self.import_extension('foo', [ + ("new_obj", "METH_NOARGS", + ''' + PyObject *obj; + Foo_Type.tp_as_sequence = &tp_as_sequence; + tp_as_sequence.sq_contains = sq_contains; + if (PyType_Ready(&Foo_Type) < 0) return NULL; + obj = PyObject_New(PyObject, &Foo_Type); + return obj; + ''' + )], + ''' + static int + sq_contains(PyObject *self, PyObject *value) + { + return 42; + } + PySequenceMethods tp_as_sequence; + static PyTypeObject Foo_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "foo.foo", + }; + ''') + obj = module.new_obj() + res = "foo" in obj + assert res is True def test_tp_iter(self): module = self.import_extension('foo', [ diff --git a/pypy/module/cpyext/typeobject.py b/pypy/module/cpyext/typeobject.py --- a/pypy/module/cpyext/typeobject.py +++ b/pypy/module/cpyext/typeobject.py @@ -307,6 +307,8 @@ if not space.is_true(space.issubtype(self, space.w_type)): self.flag_cpytype = True self.flag_heaptype = False + if pto.c_tp_doc: + self.w_doc = space.wrap(rffi.charp2str(pto.c_tp_doc)) @bootstrap_function def init_typeobject(space): @@ -624,7 +626,6 @@ Creates an interpreter type from a PyTypeObject structure. """ # missing: - # setting __doc__ if not defined and tp_doc defined # inheriting tp_as_* slots # unsupported: # tp_mro, tp_subclasses diff --git a/pypy/module/micronumpy/__init__.py b/pypy/module/micronumpy/__init__.py --- a/pypy/module/micronumpy/__init__.py +++ b/pypy/module/micronumpy/__init__.py @@ -29,6 +29,7 @@ 'flatiter': 'interp_numarray.W_FlatIterator', 'isna': 'interp_numarray.isna', 'concatenate': 'interp_numarray.concatenate', + 'repeat': 'interp_numarray.repeat', 'set_string_function': 'appbridge.set_string_function', diff --git a/pypy/module/micronumpy/interp_numarray.py b/pypy/module/micronumpy/interp_numarray.py --- a/pypy/module/micronumpy/interp_numarray.py +++ b/pypy/module/micronumpy/interp_numarray.py @@ -673,6 +673,10 @@ def compute_first_step(self, sig, frame): pass + @unwrap_spec(repeats=int) + def descr_repeat(self, space, repeats, w_axis=None): + return repeat(space, self, repeats, w_axis) + def convert_to_array(space, w_obj): if isinstance(w_obj, BaseArray): return w_obj @@ -1261,6 +1265,31 @@ return convert_to_array(space, w_obj2).descr_dot(space, w_arr) return w_arr.descr_dot(space, w_obj2) + at unwrap_spec(repeats=int) +def repeat(space, w_arr, repeats, w_axis=None): + arr = convert_to_array(space, w_arr) + if space.is_w(w_axis, space.w_None): + arr = arr.descr_flatten(space).get_concrete() + orig_size = arr.shape[0] + shape = [arr.shape[0] * repeats] + res = W_NDimArray(shape, arr.find_dtype()) + for i in range(repeats): + Chunks([Chunk(i, shape[0] - repeats + i, repeats, + orig_size)]).apply(res).setslice(space, arr) + else: + arr = arr.get_concrete() + axis = space.int_w(w_axis) + shape = arr.shape[:] + chunks = [Chunk(0, i, 1, i) for i in shape] + orig_size = shape[axis] + shape[axis] *= repeats + res = W_NDimArray(shape, arr.find_dtype()) + for i in range(repeats): + chunks[axis] = Chunk(i, shape[axis] - repeats + i, repeats, + orig_size) + Chunks(chunks).apply(res).setslice(space, arr) + return res + @unwrap_spec(axis=int) def concatenate(space, w_args, axis=0): args_w = space.listview(w_args) @@ -1386,6 +1415,7 @@ tolist = interp2app(BaseArray.descr_tolist), take = interp2app(BaseArray.descr_take), compress = interp2app(BaseArray.descr_compress), + repeat = interp2app(BaseArray.descr_repeat), ) diff --git a/pypy/module/micronumpy/signature.py b/pypy/module/micronumpy/signature.py --- a/pypy/module/micronumpy/signature.py +++ b/pypy/module/micronumpy/signature.py @@ -107,6 +107,10 @@ arr.compute_first_step(self, f) return f + def debug_repr(self): + # should be overridden, but in case it isn't, provide a default + return str(self) + class ConcreteSignature(Signature): _immutable_fields_ = ['dtype'] @@ -207,7 +211,7 @@ def _create_iter(self, iterlist, arraylist, arr, transforms): from pypy.module.micronumpy.interp_numarray import VirtualSlice assert isinstance(arr, VirtualSlice) - transforms = transforms + [ViewTransform(arr.chunks)] + transforms = [ViewTransform(arr.chunks)] + transforms self.child._create_iter(iterlist, arraylist, arr.child, transforms) def eval(self, frame, arr): @@ -215,6 +219,9 @@ assert isinstance(arr, VirtualSlice) return self.child.eval(frame, arr.child) + def debug_repr(self): + return 'VirtualSlice(%s)' % self.child.debug_repr() + class Call1(Signature): _immutable_fields_ = ['unfunc', 'name', 'child', 'res', 'dtype'] @@ -270,7 +277,7 @@ from pypy.module.micronumpy.interp_numarray import Call1 assert isinstance(arr, Call1) - vtransforms = transforms + [BroadcastTransform(arr.values.shape)] + vtransforms = [BroadcastTransform(arr.values.shape)] + transforms self.child._create_iter(iterlist, arraylist, arr.values, vtransforms) self.res._create_iter(iterlist, arraylist, arr.res, transforms) @@ -348,7 +355,7 @@ from pypy.module.micronumpy.interp_numarray import ResultArray assert isinstance(arr, ResultArray) - rtransforms = transforms + [BroadcastTransform(arr.left.shape)] + rtransforms = [BroadcastTransform(arr.left.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, transforms) self.right._create_iter(iterlist, arraylist, arr.right, rtransforms) @@ -375,7 +382,7 @@ from pypy.module.micronumpy.interp_numarray import Call2 assert isinstance(arr, Call2) - ltransforms = transforms + [BroadcastTransform(arr.shape)] + ltransforms = [BroadcastTransform(arr.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, ltransforms) self.right._create_iter(iterlist, arraylist, arr.right, transforms) @@ -388,7 +395,7 @@ from pypy.module.micronumpy.interp_numarray import Call2 assert isinstance(arr, Call2) - rtransforms = transforms + [BroadcastTransform(arr.shape)] + rtransforms = [BroadcastTransform(arr.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, transforms) self.right._create_iter(iterlist, arraylist, arr.right, rtransforms) @@ -401,8 +408,8 @@ from pypy.module.micronumpy.interp_numarray import Call2 assert isinstance(arr, Call2) - rtransforms = transforms + [BroadcastTransform(arr.shape)] - ltransforms = transforms + [BroadcastTransform(arr.shape)] + rtransforms = [BroadcastTransform(arr.shape)] + transforms + ltransforms = [BroadcastTransform(arr.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, ltransforms) self.right._create_iter(iterlist, arraylist, arr.right, rtransforms) @@ -424,7 +431,7 @@ frame.cur_value = self.binfunc(self.calc_dtype, frame.cur_value, rval) def debug_repr(self): - return 'ReduceSig(%s)' % (self.name, self.right.debug_repr()) + return 'ReduceSig(%s, %s)' % (self.name, self.right.debug_repr()) class SliceloopSignature(Call2): def eval(self, frame, arr): @@ -448,7 +455,7 @@ from pypy.module.micronumpy.interp_numarray import SliceArray assert isinstance(arr, SliceArray) - rtransforms = transforms + [BroadcastTransform(arr.shape)] + rtransforms = [BroadcastTransform(arr.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, transforms) self.right._create_iter(iterlist, arraylist, arr.right, rtransforms) diff --git a/pypy/module/micronumpy/test/test_numarray.py b/pypy/module/micronumpy/test/test_numarray.py --- a/pypy/module/micronumpy/test/test_numarray.py +++ b/pypy/module/micronumpy/test/test_numarray.py @@ -395,11 +395,19 @@ assert a[3] == 0. def test_newaxis(self): - from _numpypy import array + import math + from _numpypy import array, cos, zeros from numpypy.core.numeric import newaxis a = array(range(5)) b = array([range(5)]) assert (a[newaxis] == b).all() + a = array(range(3)) + b = array([1, 3]) + expected = zeros((3, 2)) + for x in range(3): + for y in range(2): + expected[x, y] = math.cos(a[x]) * math.cos(b[y]) + assert ((cos(a)[:,newaxis] * cos(b).T) == expected).all() def test_newaxis_slice(self): from _numpypy import array @@ -1338,6 +1346,10 @@ dims_disagree = raises(ValueError, concatenate, (a1, b1), axis=0) assert str(dims_disagree.value) == \ "array dimensions must agree except for axis being concatenated" + a = array([1, 2, 3, 4, 5, 6]) + a = (a + a)[::2] + b = concatenate((a[:3], a[-3:])) + assert (b == [2, 6, 10, 2, 6, 10]).all() def test_std(self): from _numpypy import array @@ -1387,6 +1399,16 @@ assert (ones(1) + ones(1)).nbytes == 8 assert array(3.0).nbytes == 8 + def test_repeat(self): + from _numpypy import repeat, array + assert (repeat([[1, 2], [3, 4]], 3) == [1, 1, 1, 2, 2, 2, + 3, 3, 3, 4, 4, 4]).all() + assert (repeat([[1, 2], [3, 4]], 2, axis=0) == [[1, 2], [1, 2], [3, 4], + [3, 4]]).all() + assert (repeat([[1, 2], [3, 4]], 2, axis=1) == [[1, 1, 2, 2], [3, 3, + 4, 4]]).all() + assert (array([1, 2]).repeat(2) == array([1, 1, 2, 2])).all() + class AppTestMultiDim(BaseNumpyAppTest): def test_init(self): diff --git a/pypy/module/pypyjit/test_pypy_c/test_misc.py b/pypy/module/pypyjit/test_pypy_c/test_misc.py --- a/pypy/module/pypyjit/test_pypy_c/test_misc.py +++ b/pypy/module/pypyjit/test_pypy_c/test_misc.py @@ -212,7 +212,7 @@ i19 = int_add(i12, 1) setfield_gc(p9, i19, descr=) guard_nonnull_class(p17, 146982464, descr=...) - i21 = getfield_gc(p17, descr=) + i21 = getfield_gc(p17, descr=) i23 = int_lt(0, i21) guard_true(i23, descr=...) i24 = getfield_gc(p17, descr=) diff --git a/pypy/module/pypyjit/test_pypy_c/test_string.py b/pypy/module/pypyjit/test_pypy_c/test_string.py --- a/pypy/module/pypyjit/test_pypy_c/test_string.py +++ b/pypy/module/pypyjit/test_pypy_c/test_string.py @@ -198,3 +198,37 @@ i49 = call(ConstClass(_ll_2_str_eq_nonnull__rpy_stringPtr_rpy_stringPtr), p35, ConstPtr(ptr48), descr=) guard_value(i49, 1, descr=...) ''') + + def test_remove_duplicate_method_calls(self): + def main(n): + lst = [] + for i in range(n): + s = 'Hello %d' % i + t = s.lower() # ID: callone + u = s.lower() # ID: calltwo + lst.append(t) + lst.append(u) + return len(','.join(lst)) + log = self.run(main, [1000]) + assert log.result == main(1000) + loops = log.loops_by_filename(self.filepath) + loop, = loops + loop.match_by_id('callone', ''' + p114 = call(ConstClass(ll_lower__rpy_stringPtr), p113, descr=) + guard_no_exception(descr=...) + ''') + loop.match_by_id('calltwo', '') # nothing + + def test_move_method_call_out_of_loop(self): + def main(n): + lst = [] + s = 'Hello %d' % n + for i in range(n): + t = s.lower() # ID: callone + lst.append(t) + return len(','.join(lst)) + log = self.run(main, [1000]) + assert log.result == main(1000) + loops = log.loops_by_filename(self.filepath) + loop, = loops + loop.match_by_id('callone', '') # nothing diff --git a/pypy/module/sys/test/test_sysmodule.py b/pypy/module/sys/test/test_sysmodule.py --- a/pypy/module/sys/test/test_sysmodule.py +++ b/pypy/module/sys/test/test_sysmodule.py @@ -600,30 +600,33 @@ class AppTestSysExcInfoDirect: def setup_method(self, meth): - self.seen = [] - from pypy.module.sys import vm - def exc_info_with_tb(*args): - self.seen.append("n") # not optimized - return self.old[0](*args) - def exc_info_without_tb(*args): - self.seen.append("y") # optimized - return self.old[1](*args) - self.old = [vm.exc_info_with_tb, vm.exc_info_without_tb] - vm.exc_info_with_tb = exc_info_with_tb - vm.exc_info_without_tb = exc_info_without_tb - # - from pypy.rlib import jit - self.old2 = [jit.we_are_jitted] - jit.we_are_jitted = lambda: True + self.checking = not option.runappdirect + if self.checking: + self.seen = [] + from pypy.module.sys import vm + def exc_info_with_tb(*args): + self.seen.append("n") # not optimized + return self.old[0](*args) + def exc_info_without_tb(*args): + self.seen.append("y") # optimized + return self.old[1](*args) + self.old = [vm.exc_info_with_tb, vm.exc_info_without_tb] + vm.exc_info_with_tb = exc_info_with_tb + vm.exc_info_without_tb = exc_info_without_tb + # + from pypy.rlib import jit + self.old2 = [jit.we_are_jitted] + jit.we_are_jitted = lambda: True def teardown_method(self, meth): - from pypy.module.sys import vm - from pypy.rlib import jit - vm.exc_info_with_tb = self.old[0] - vm.exc_info_without_tb = self.old[1] - jit.we_are_jitted = self.old2[0] - # - assert ''.join(self.seen) == meth.expected + if self.checking: + from pypy.module.sys import vm + from pypy.rlib import jit + vm.exc_info_with_tb = self.old[0] + vm.exc_info_without_tb = self.old[1] + jit.we_are_jitted = self.old2[0] + # + assert ''.join(self.seen) == meth.expected def test_returns_none(self): import sys diff --git a/pypy/module/test_lib_pypy/test_datetime.py b/pypy/module/test_lib_pypy/test_datetime.py --- a/pypy/module/test_lib_pypy/test_datetime.py +++ b/pypy/module/test_lib_pypy/test_datetime.py @@ -44,3 +44,9 @@ assert type(dt.microsecond) is int copy.copy(dt) + +def test_radd(): + class X(object): + def __radd__(self, other): + return "radd" + assert datetime.date(10, 10, 10) + X() == "radd" diff --git a/pypy/module/thread/test/test_ll_thread.py b/pypy/module/thread/test/test_ll_thread.py --- a/pypy/module/thread/test/test_ll_thread.py +++ b/pypy/module/thread/test/test_ll_thread.py @@ -66,7 +66,6 @@ def test_gc_locking(self): import time from pypy.rlib.objectmodel import invoke_around_extcall - from pypy.rlib.objectmodel import we_are_translated from pypy.rlib.debug import ll_assert class State: @@ -129,8 +128,6 @@ state.finished = 0 # the next line installs before_extcall() and after_extcall() # to be called automatically around external function calls. - # When not translated it does not work around time.sleep(), - # so we have to call them manually for this test. invoke_around_extcall(before_extcall, after_extcall) g(10, 1) @@ -142,13 +139,9 @@ willing_to_wait_more -= 1 done = len(state.answers) == expected - if not we_are_translated(): before_extcall() time.sleep(0.01) - if not we_are_translated(): after_extcall() - if not we_are_translated(): before_extcall() time.sleep(0.1) - if not we_are_translated(): after_extcall() return len(state.answers) @@ -160,12 +153,11 @@ answers = fn() assert answers == expected -class TestRunDirectly(AbstractThreadTests): - def getcompiled(self, f, argtypes): - return f - - def test_start_new_thread(self): - py.test.skip("deadlocks occasionally -- why???") +#class TestRunDirectly(AbstractThreadTests): +# def getcompiled(self, f, argtypes): +# return f +# These are disabled because they crash occasionally for bad reasons +# related to the fact that ll2ctypes is not at all thread-safe class TestUsingBoehm(AbstractThreadTests): gcpolicy = 'boehm' diff --git a/pypy/objspace/descroperation.py b/pypy/objspace/descroperation.py --- a/pypy/objspace/descroperation.py +++ b/pypy/objspace/descroperation.py @@ -43,6 +43,13 @@ return w_eq type_eq._annspecialcase_ = 'specialize:memo' +def list_iter(space): + "Utility that returns the app-level descriptor list.__iter__." + w_src, w_iter = space.lookup_in_type_where(space.w_list, + '__iter__') + return w_iter +list_iter._annspecialcase_ = 'specialize:memo' + def raiseattrerror(space, w_obj, name, w_descr=None): w_type = space.type(w_obj) typename = w_type.getname(space) diff --git a/pypy/objspace/fake/objspace.py b/pypy/objspace/fake/objspace.py --- a/pypy/objspace/fake/objspace.py +++ b/pypy/objspace/fake/objspace.py @@ -86,6 +86,7 @@ return s_None def specialize_call(self, hop): + hop.exception_cannot_occur() return hop.inputconst(lltype.Void, None) # ____________________________________________________________ diff --git a/pypy/objspace/std/celldict.py b/pypy/objspace/std/celldict.py --- a/pypy/objspace/std/celldict.py +++ b/pypy/objspace/std/celldict.py @@ -163,7 +163,8 @@ class ModuleDictIteratorImplementation(IteratorImplementation): def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) + IteratorImplementation.__init__( + self, space, strategy, dictimplementation) dict_w = strategy.unerase(dictimplementation.dstorage) self.iterator = dict_w.iteritems() diff --git a/pypy/objspace/std/dictmultiobject.py b/pypy/objspace/std/dictmultiobject.py --- a/pypy/objspace/std/dictmultiobject.py +++ b/pypy/objspace/std/dictmultiobject.py @@ -247,7 +247,7 @@ return 0 def iter(self, w_dict): - return EmptyIteratorImplementation(self.space, w_dict) + return EmptyIteratorImplementation(self.space, self, w_dict) def clear(self, w_dict): return @@ -263,8 +263,9 @@ # Iterator Implementation base classes class IteratorImplementation(object): - def __init__(self, space, implementation): + def __init__(self, space, strategy, implementation): self.space = space + self.strategy = strategy self.dictimplementation = implementation self.len = implementation.length() self.pos = 0 @@ -280,7 +281,20 @@ if self.pos < self.len: result = self.next_entry() self.pos += 1 - return result + if self.strategy is self.dictimplementation.strategy: + return result # common case + else: + # waaa, obscure case: the strategy changed, but not the + # length of the dict. The (key, value) pair in 'result' + # might be out-of-date. We try to explicitly look up + # the key in the dict. + w_key = result[0] + w_value = self.dictimplementation.getitem(w_key) + if w_value is None: + self.len = -1 # Make this error state sticky + raise OperationError(self.space.w_RuntimeError, + self.space.wrap("dictionary changed during iteration")) + return (w_key, w_value) # no more entries self.dictimplementation = None return None, None @@ -489,7 +503,7 @@ _mixin_ = True def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) + IteratorImplementation.__init__(self, space, strategy, dictimplementation) self.iterator = strategy.unerase(dictimplementation.dstorage).iteritems() def next_entry(self): @@ -503,7 +517,7 @@ _mixin_ = True def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) + IteratorImplementation.__init__(self, space, strategy, dictimplementation) self.iterator = strategy.unerase(dictimplementation.dstorage).iteritems() def next_entry(self): diff --git a/pypy/objspace/std/dictproxyobject.py b/pypy/objspace/std/dictproxyobject.py --- a/pypy/objspace/std/dictproxyobject.py +++ b/pypy/objspace/std/dictproxyobject.py @@ -20,7 +20,17 @@ def getitem(self, w_dict, w_key): space = self.space w_lookup_type = space.type(w_key) - if space.is_w(w_lookup_type, space.w_str): + if (space.is_w(w_lookup_type, space.w_str) or # Most common path first + space.abstract_issubclass_w(w_lookup_type, space.w_str)): + return self.getitem_str(w_dict, space.str_w(w_key)) + elif space.abstract_issubclass_w(w_lookup_type, space.w_unicode): + try: + w_key = space.str(w_key) + except OperationError, e: + if not e.match(space, space.w_UnicodeEncodeError): + raise + # non-ascii unicode is never equal to a byte string + return None return self.getitem_str(w_dict, space.str_w(w_key)) else: return None diff --git a/pypy/objspace/std/objspace.py b/pypy/objspace/std/objspace.py --- a/pypy/objspace/std/objspace.py +++ b/pypy/objspace/std/objspace.py @@ -439,6 +439,8 @@ t = w_obj.getitems() elif isinstance(w_obj, W_AbstractTupleObject): t = w_obj.getitems_copy() + elif isinstance(w_obj, W_ListObject) and self._uses_list_iter(w_obj): + t = w_obj.getitems() else: return ObjSpace.unpackiterable(self, w_obj, expected_length) if expected_length != -1 and len(t) != expected_length: @@ -456,6 +458,8 @@ return w_obj.listview_str() if isinstance(w_obj, W_StringObject): return w_obj.listview_str() + if isinstance(w_obj, W_ListObject) and self._uses_list_iter(w_obj): + return w_obj.getitems_str() return None def listview_int(self, w_obj): @@ -465,8 +469,14 @@ return w_obj.listview_int() if type(w_obj) is W_SetObject or type(w_obj) is W_FrozensetObject: return w_obj.listview_int() + if isinstance(w_obj, W_ListObject) and self._uses_list_iter(w_obj): + return w_obj.getitems_int() return None + def _uses_list_iter(self, w_obj): + from pypy.objspace.descroperation import list_iter + return self.lookup(w_obj, '__iter__') is list_iter(self) + def sliceindices(self, w_slice, w_length): if isinstance(w_slice, W_SliceObject): a, b, c = w_slice.indices3(self, self.int_w(w_length)) diff --git a/pypy/objspace/std/setobject.py b/pypy/objspace/std/setobject.py --- a/pypy/objspace/std/setobject.py +++ b/pypy/objspace/std/setobject.py @@ -359,7 +359,7 @@ w_set.sstorage = w_other.get_storage_copy() def iter(self, w_set): - return EmptyIteratorImplementation(self.space, w_set) + return EmptyIteratorImplementation(self.space, self, w_set) def popitem(self, w_set): raise OperationError(self.space.w_KeyError, @@ -784,8 +784,9 @@ d_obj[w_item] = None class IteratorImplementation(object): - def __init__(self, space, implementation): + def __init__(self, space, strategy, implementation): self.space = space + self.strategy = strategy self.setimplementation = implementation self.len = implementation.length() self.pos = 0 @@ -801,7 +802,17 @@ if self.pos < self.len: result = self.next_entry() self.pos += 1 - return result + if self.strategy is self.setimplementation.strategy: + return result # common case + else: + # waaa, obscure case: the strategy changed, but not the + # length of the set. The 'result' might be out-of-date. + # We try to explicitly look it up in the set. + if not self.setimplementation.has_key(result): + self.len = -1 # Make this error state sticky + raise OperationError(self.space.w_RuntimeError, + self.space.wrap("dictionary changed during iteration")) + return result # no more entries self.setimplementation = None return None @@ -823,7 +834,7 @@ class StringIteratorImplementation(IteratorImplementation): def __init__(self, space, strategy, w_set): - IteratorImplementation.__init__(self, space, w_set) + IteratorImplementation.__init__(self, space, strategy, w_set) d = strategy.unerase(w_set.sstorage) self.iterator = d.iterkeys() @@ -835,9 +846,9 @@ class IntegerIteratorImplementation(IteratorImplementation): #XXX same implementation in dictmultiobject on dictstrategy-branch - def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) - d = strategy.unerase(dictimplementation.sstorage) + def __init__(self, space, strategy, w_set): + IteratorImplementation.__init__(self, space, strategy, w_set) + d = strategy.unerase(w_set.sstorage) self.iterator = d.iterkeys() def next_entry(self): @@ -848,9 +859,9 @@ return None class RDictIteratorImplementation(IteratorImplementation): - def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) - d = strategy.unerase(dictimplementation.sstorage) + def __init__(self, space, strategy, w_set): + IteratorImplementation.__init__(self, space, strategy, w_set) + d = strategy.unerase(w_set.sstorage) self.iterator = d.iterkeys() def next_entry(self): diff --git a/pypy/objspace/std/test/test_dictmultiobject.py b/pypy/objspace/std/test/test_dictmultiobject.py --- a/pypy/objspace/std/test/test_dictmultiobject.py +++ b/pypy/objspace/std/test/test_dictmultiobject.py @@ -804,6 +804,33 @@ assert "IntDictStrategy" in self.get_strategy(d) assert d[1L] == "hi" + def test_iter_dict_length_change(self): + d = {1: 2, 3: 4, 5: 6} + it = d.iteritems() + d[7] = 8 + # 'd' is now length 4 + raises(RuntimeError, it.next) + + def test_iter_dict_strategy_only_change_1(self): + d = {1: 2, 3: 4, 5: 6} + it = d.iteritems() + class Foo(object): + def __eq__(self, other): + return False + assert d.get(Foo()) is None # this changes the strategy of 'd' + lst = list(it) # but iterating still works + assert sorted(lst) == [(1, 2), (3, 4), (5, 6)] + + def test_iter_dict_strategy_only_change_2(self): + d = {1: 2, 3: 4, 5: 6} + it = d.iteritems() + d['foo'] = 'bar' + del d[1] + # 'd' is still length 3, but its strategy changed. we are + # getting a RuntimeError because iterating over the old storage + # gives us (1, 2), but 1 is not in the dict any longer. + raises(RuntimeError, list, it) + class FakeString(str): hash_count = 0 diff --git a/pypy/objspace/std/test/test_dictproxy.py b/pypy/objspace/std/test/test_dictproxy.py --- a/pypy/objspace/std/test/test_dictproxy.py +++ b/pypy/objspace/std/test/test_dictproxy.py @@ -25,6 +25,16 @@ key, value = NotEmpty.__dict__.popitem() assert (key == 'a' and value == 1) or (key == 'b' and value == 4) + def test_dictproxy_getitem(self): + class NotEmpty(object): + a = 1 + assert 'a' in NotEmpty.__dict__ + class substr(str): pass + assert substr('a') in NotEmpty.__dict__ + assert u'a' in NotEmpty.__dict__ + assert NotEmpty.__dict__[u'a'] == 1 + assert u'\xe9' not in NotEmpty.__dict__ + def test_dictproxyeq(self): class a(object): pass diff --git a/pypy/objspace/std/test/test_listobject.py b/pypy/objspace/std/test/test_listobject.py --- a/pypy/objspace/std/test/test_listobject.py +++ b/pypy/objspace/std/test/test_listobject.py @@ -1186,14 +1186,23 @@ # of dicts, because the OrderedDict in the stdlib relies on this. # we extend the use case to lists and sets, i.e. all types that have # strategies, to avoid surprizes depending on the strategy. - for base, arg in [(list, []), (list, [5]), (list, ['x']), - (set, []), (set, [5]), (set, ['x']), - (dict, []), (dict, [(5,6)]), (dict, [('x',7)])]: + class X: pass + for base, arg in [ + (list, []), (list, [5]), (list, ['x']), (list, [X]), + (set, []), (set, [5]), (set, ['x']), (set, [X]), + (dict, []), (dict, [(5,6)]), (dict, [('x',7)]), (dict, [(X,8)]), + ]: print base, arg class SubClass(base): def __iter__(self): return iter("foobar") assert list(SubClass(arg)) == ['f', 'o', 'o', 'b', 'a', 'r'] + class Sub2(base): + pass + assert list(Sub2(arg)) == list(base(arg)) + s = set() + s.update(Sub2(arg)) + assert s == set(base(arg)) class AppTestForRangeLists(AppTestW_ListObject): diff --git a/pypy/objspace/std/test/test_setobject.py b/pypy/objspace/std/test/test_setobject.py --- a/pypy/objspace/std/test/test_setobject.py +++ b/pypy/objspace/std/test/test_setobject.py @@ -907,3 +907,30 @@ return [5, 3, 4][i] s = set([10,3,2]).intersection(Obj()) assert list(s) == [3] + + def test_iter_set_length_change(self): + s = set([1, 3, 5]) + it = iter(s) + s.add(7) + # 's' is now length 4 + raises(RuntimeError, it.next) + + def test_iter_set_strategy_only_change_1(self): + s = set([1, 3, 5]) + it = iter(s) + class Foo(object): + def __eq__(self, other): + return False + assert Foo() not in s # this changes the strategy of 'd' + lst = list(s) # but iterating still works + assert sorted(lst) == [1, 3, 5] + + def test_iter_set_strategy_only_change_2(self): + s = set([1, 3, 5]) + it = iter(s) + s.add('foo') + s.remove(1) + # 's' is still length 3, but its strategy changed. we are + # getting a RuntimeError because iterating over the old storage + # gives us 1, but 1 is not in the set any longer. + raises(RuntimeError, list, it) diff --git a/pypy/rlib/jit.py b/pypy/rlib/jit.py --- a/pypy/rlib/jit.py +++ b/pypy/rlib/jit.py @@ -382,7 +382,7 @@ pass def specialize_call(self, hop): - pass + hop.exception_cannot_occur() vref_None = non_virtual_ref(None) diff --git a/pypy/rlib/jit_hooks.py b/pypy/rlib/jit_hooks.py --- a/pypy/rlib/jit_hooks.py +++ b/pypy/rlib/jit_hooks.py @@ -22,6 +22,7 @@ c_name = hop.inputconst(lltype.Void, 'access_helper') args_v = [hop.inputarg(arg, arg=i) for i, arg in enumerate(hop.args_r)] + hop.exception_cannot_occur() return hop.genop('jit_marker', [c_name, c_func] + args_v, resulttype=hop.r_result) return helper diff --git a/pypy/rlib/objectmodel.py b/pypy/rlib/objectmodel.py --- a/pypy/rlib/objectmodel.py +++ b/pypy/rlib/objectmodel.py @@ -215,6 +215,7 @@ def specialize_call(self, hop): from pypy.rpython.lltypesystem import lltype + hop.exception_cannot_occur() return hop.inputconst(lltype.Bool, hop.s_result.const) # ____________________________________________________________ @@ -397,6 +398,7 @@ r_obj, = hop.args_r v_obj, = hop.inputargs(r_obj) ll_fn = r_obj.get_ll_hash_function() + hop.exception_is_here() return hop.gendirectcall(ll_fn, v_obj) class Entry(ExtRegistryEntry): @@ -419,6 +421,7 @@ from pypy.rpython.error import TyperError raise TyperError("compute_identity_hash() cannot be applied to" " %r" % (vobj.concretetype,)) + hop.exception_cannot_occur() return hop.genop('gc_identityhash', [vobj], resulttype=lltype.Signed) class Entry(ExtRegistryEntry): @@ -441,6 +444,7 @@ from pypy.rpython.error import TyperError raise TyperError("compute_unique_id() cannot be applied to" " %r" % (vobj.concretetype,)) + hop.exception_cannot_occur() return hop.genop('gc_id', [vobj], resulttype=lltype.Signed) class Entry(ExtRegistryEntry): @@ -452,6 +456,7 @@ def specialize_call(self, hop): vobj, = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() if hop.rtyper.type_system.name == 'lltypesystem': from pypy.rpython.lltypesystem import lltype if isinstance(vobj.concretetype, lltype.Ptr): diff --git a/pypy/rlib/rbigint.py b/pypy/rlib/rbigint.py --- a/pypy/rlib/rbigint.py +++ b/pypy/rlib/rbigint.py @@ -85,7 +85,7 @@ s_DIGIT = self.bookkeeper.valueoftype(type(NULLDIGIT)) assert s_DIGIT.contains(s_list.listdef.listitem.s_value) def specialize_call(self, hop): - pass + hop.exception_cannot_occur() class rbigint(object): diff --git a/pypy/rlib/rerased.py b/pypy/rlib/rerased.py --- a/pypy/rlib/rerased.py +++ b/pypy/rlib/rerased.py @@ -100,6 +100,7 @@ def specialize_call(self, hop): bk = hop.rtyper.annotator.bookkeeper s_obj = identity.get_input_annotation(bk) + hop.exception_cannot_occur() return hop.r_result.rtype_erase(hop, s_obj) class Entry(ExtRegistryEntry): @@ -110,6 +111,7 @@ return identity.leave_tunnel(self.bookkeeper) def specialize_call(self, hop): + hop.exception_cannot_occur() if hop.r_result.lowleveltype is lltype.Void: return hop.inputconst(lltype.Void, None) [v] = hop.inputargs(hop.args_r[0]) @@ -214,6 +216,7 @@ return hop.genop('cast_opaque_ptr', [v], resulttype=hop.r_result) def rtype_unerase_int(self, hop, v): + hop.exception_cannot_occur() return hop.gendirectcall(ll_unerase_int, v) def rtype_erase_int(self, hop): @@ -264,6 +267,7 @@ def rtype_unerase_int(self, hop, v): c_one = hop.inputconst(lltype.Signed, 1) + hop.exception_cannot_occur() v2 = hop.genop('oounbox_int', [v], resulttype=hop.r_result) return hop.genop('int_rshift', [v2, c_one], resulttype=lltype.Signed) diff --git a/pypy/rlib/rgc.py b/pypy/rlib/rgc.py --- a/pypy/rlib/rgc.py +++ b/pypy/rlib/rgc.py @@ -382,6 +382,7 @@ def compute_result_annotation(self): return s_list_of_gcrefs() def specialize_call(self, hop): + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_roots', [], resulttype = hop.r_result) class Entry(ExtRegistryEntry): @@ -392,6 +393,7 @@ return s_list_of_gcrefs() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_referents', vlist, resulttype = hop.r_result) @@ -402,6 +404,7 @@ return annmodel.SomeInteger() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_memory_usage', vlist, resulttype = hop.r_result) @@ -412,6 +415,7 @@ return annmodel.SomeInteger() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_type_index', vlist, resulttype = hop.r_result) @@ -430,6 +434,7 @@ return annmodel.SomeBool() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_is_rpy_instance', vlist, resulttype = hop.r_result) @@ -449,6 +454,7 @@ classrepr = getclassrepr(hop.rtyper, classdef) vtable = classrepr.getvtable() assert lltype.typeOf(vtable) == rclass.CLASSTYPE + hop.exception_cannot_occur() return Constant(vtable, concretetype=rclass.CLASSTYPE) class Entry(ExtRegistryEntry): diff --git a/pypy/rlib/rstring.py b/pypy/rlib/rstring.py --- a/pypy/rlib/rstring.py +++ b/pypy/rlib/rstring.py @@ -245,5 +245,5 @@ raise ValueError("Value is not no_nul") def specialize_call(self, hop): - pass + hop.exception_cannot_occur() diff --git a/pypy/rpython/controllerentry.py b/pypy/rpython/controllerentry.py --- a/pypy/rpython/controllerentry.py +++ b/pypy/rpython/controllerentry.py @@ -201,6 +201,7 @@ def specialize_call(self, hop): from pypy.rpython.lltypesystem import lltype assert hop.s_result.is_constant() + hop.exception_cannot_occur() return hop.inputconst(lltype.Bool, hop.s_result.const) # ____________________________________________________________ diff --git a/pypy/rpython/lltypesystem/lloperation.py b/pypy/rpython/lltypesystem/lloperation.py --- a/pypy/rpython/lltypesystem/lloperation.py +++ b/pypy/rpython/lltypesystem/lloperation.py @@ -130,6 +130,7 @@ def specialize_call(self, hop): from pypy.rpython.lltypesystem import lltype + hop.exception_cannot_occur() return hop.inputconst(lltype.Void, None) def enum_ops_without_sideeffects(raising_is_ok=False): diff --git a/pypy/rpython/lltypesystem/rbuiltin.py b/pypy/rpython/lltypesystem/rbuiltin.py --- a/pypy/rpython/lltypesystem/rbuiltin.py +++ b/pypy/rpython/lltypesystem/rbuiltin.py @@ -9,6 +9,7 @@ from pypy.rpython.rbool import bool_repr def rtype_builtin_isinstance(hop): + hop.exception_cannot_occur() if hop.s_result.is_constant(): return hop.inputconst(lltype.Bool, hop.s_result.const) if hop.args_r[0] == pyobj_repr or hop.args_r[1] == pyobj_repr: @@ -33,6 +34,7 @@ return my_instantiate() def rtype_instantiate(hop): + hop.exception_cannot_occur() s_class = hop.args_s[0] assert isinstance(s_class, annmodel.SomePBC) if len(s_class.descriptions) != 1: @@ -46,6 +48,7 @@ return rclass.rtype_new_instance(hop.rtyper, classdef, hop.llops) def rtype_builtin_hasattr(hop): + hop.exception_cannot_occur() if hop.s_result.is_constant(): return hop.inputconst(lltype.Bool, hop.s_result.const) if hop.args_r[0] == pyobj_repr: @@ -56,6 +59,7 @@ raise TyperError("hasattr is only suported on a constant or on PyObject") def rtype_builtin___import__(hop): + xxx # should not be used any more args_v = hop.inputargs(*[pyobj_repr for ign in hop.args_r]) c = hop.inputconst(pyobj_repr, __import__) return hop.genop('simple_call', [c] + args_v, resulttype = pyobj_repr) diff --git a/pypy/rpython/lltypesystem/rclass.py b/pypy/rpython/lltypesystem/rclass.py --- a/pypy/rpython/lltypesystem/rclass.py +++ b/pypy/rpython/lltypesystem/rclass.py @@ -746,4 +746,5 @@ assert isinstance(TYPE, GcStruct) assert lltype._castdepth(TYPE, OBJECT) > 0 hop.rtyper.set_type_for_typeptr(vtable, TYPE) + hop.exception_cannot_occur() return hop.inputconst(lltype.Void, None) diff --git a/pypy/rpython/lltypesystem/rtuple.py b/pypy/rpython/lltypesystem/rtuple.py --- a/pypy/rpython/lltypesystem/rtuple.py +++ b/pypy/rpython/lltypesystem/rtuple.py @@ -55,6 +55,7 @@ vtup = hop.inputarg(self, 0) LIST = hop.r_result.lowleveltype.TO cno = inputconst(Signed, nitems) + hop.exception_is_here() vlist = hop.gendirectcall(LIST.ll_newlist, cno) v_func = hop.inputconst(Void, rlist.dum_nocheck) for index in range(nitems): diff --git a/pypy/rpython/module/r_os_stat.py b/pypy/rpython/module/r_os_stat.py --- a/pypy/rpython/module/r_os_stat.py +++ b/pypy/rpython/module/r_os_stat.py @@ -65,4 +65,5 @@ r_StatResult = hop.rtyper.getrepr(ll_os_stat.s_StatResult) [v_result] = hop.inputargs(r_StatResult.r_tuple) # no-op conversion from r_StatResult.r_tuple to r_StatResult + hop.exception_cannot_occur() return v_result diff --git a/pypy/rpython/ootypesystem/ooregistry.py b/pypy/rpython/ootypesystem/ooregistry.py --- a/pypy/rpython/ootypesystem/ooregistry.py +++ b/pypy/rpython/ootypesystem/ooregistry.py @@ -22,6 +22,7 @@ annmodel.SomeOOInstance, annmodel.SomeString)) vlist = hop.inputargs(hop.args_r[0], ootype.Signed) + hop.exception_cannot_occur() return hop.genop('oostring', vlist, resulttype = ootype.String) class Entry_oounicode(ExtRegistryEntry): @@ -38,6 +39,7 @@ assert isinstance(hop.args_s[0], (annmodel.SomeUnicodeCodePoint, annmodel.SomeOOInstance)) vlist = hop.inputargs(hop.args_r[0], ootype.Signed) + hop.exception_cannot_occur() return hop.genop('oounicode', vlist, resulttype = ootype.Unicode) diff --git a/pypy/rpython/ootypesystem/rbuiltin.py b/pypy/rpython/ootypesystem/rbuiltin.py --- a/pypy/rpython/ootypesystem/rbuiltin.py +++ b/pypy/rpython/ootypesystem/rbuiltin.py @@ -7,12 +7,14 @@ from pypy.rpython.error import TyperError def rtype_new(hop): + hop.exception_cannot_occur() assert hop.args_s[0].is_constant() vlist = hop.inputargs(ootype.Void) return hop.genop('new', vlist, resulttype = hop.r_result.lowleveltype) def rtype_oonewarray(hop): + hop.exception_cannot_occur() assert hop.args_s[0].is_constant() vlist = hop.inputarg(ootype.Void, arg=0) vlength = hop.inputarg(ootype.Signed, arg=1) @@ -20,23 +22,27 @@ resulttype = hop.r_result.lowleveltype) def rtype_null(hop): + hop.exception_cannot_occur() assert hop.args_s[0].is_constant() TYPE = hop.args_s[0].const nullvalue = ootype.null(TYPE) return hop.inputconst(TYPE, nullvalue) def rtype_classof(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0], annmodel.SomeOOInstance) vlist = hop.inputargs(hop.args_r[0]) return hop.genop('classof', vlist, resulttype = ootype.Class) def rtype_subclassof(hop): + hop.exception_cannot_occur() vlist = hop.inputargs(rootype.ooclass_repr, rootype.ooclass_repr) return hop.genop('subclassof', vlist, resulttype = ootype.Bool) def rtype_instanceof(hop): + hop.exception_cannot_occur() INSTANCE = hop.args_v[1].value v_inst = hop.inputarg(hop.args_r[0], arg=0) c_cls = hop.inputconst(ootype.Void, INSTANCE) @@ -44,23 +50,27 @@ resulttype=ootype.Bool) def rtype_runtimenew(hop): + hop.exception_cannot_occur() vlist = hop.inputargs(rootype.ooclass_repr) return hop.genop('runtimenew', vlist, resulttype = hop.r_result.lowleveltype) def rtype_ooupcast(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0].const, ootype.Instance) assert isinstance(hop.args_s[1], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[1], arg=1) return hop.genop('ooupcast', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_oodowncast(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0].const, ootype.Instance) assert isinstance(hop.args_s[1], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[1], arg=1) return hop.genop('oodowncast', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_cast_to_object(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0], annmodel.SomeOOStaticMeth) or \ isinstance(hop.args_s[0], annmodel.SomeOOClass) or \ isinstance(hop.args_s[0].ootype, ootype.OOType) @@ -68,12 +78,14 @@ return hop.genop('cast_to_object', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_cast_from_object(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0].const, ootype.OOType) assert isinstance(hop.args_s[1], annmodel.SomeOOObject) v_inst = hop.inputarg(hop.args_r[1], arg=1) return hop.genop('cast_from_object', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_builtin_isinstance(hop): + hop.exception_cannot_occur() if hop.s_result.is_constant(): return hop.inputconst(ootype.Bool, hop.s_result.const) @@ -99,6 +111,7 @@ return ootype.subclassof(c1, class_) def rtype_instantiate(hop): + hop.exception_cannot_occur() if hop.args_s[0].is_constant(): ## INSTANCE = hop.s_result.rtyper_makerepr(hop.rtyper).lowleveltype ## v_instance = hop.inputconst(ootype.Void, INSTANCE) diff --git a/pypy/rpython/ootypesystem/rtuple.py b/pypy/rpython/ootypesystem/rtuple.py --- a/pypy/rpython/ootypesystem/rtuple.py +++ b/pypy/rpython/ootypesystem/rtuple.py @@ -39,6 +39,7 @@ RESULT = hop.r_result.lowleveltype c_resulttype = inputconst(ootype.Void, RESULT) c_length = inputconst(ootype.Signed, len(self.items_r)) + hop.exception_is_here() if isinstance(RESULT, ootype.Array): v_list = hop.genop('oonewarray', [c_resulttype, c_length], resulttype=RESULT) else: diff --git a/pypy/rpython/rbool.py b/pypy/rpython/rbool.py --- a/pypy/rpython/rbool.py +++ b/pypy/rpython/rbool.py @@ -34,6 +34,7 @@ def rtype_float(_, hop): vlist = hop.inputargs(Float) + hop.exception_cannot_occur() return vlist[0] # diff --git a/pypy/rpython/rbuiltin.py b/pypy/rpython/rbuiltin.py --- a/pypy/rpython/rbuiltin.py +++ b/pypy/rpython/rbuiltin.py @@ -111,25 +111,32 @@ raise TyperError("don't know about built-in function %r" % ( self.builtinfunc,)) + def _call(self, hop2, **kwds_i): + bltintyper = self.findbltintyper(hop2.rtyper) + hop2.llops._called_exception_is_here_or_cannot_occur = False + v_result = bltintyper(hop2, **kwds_i) + if not hop2.llops._called_exception_is_here_or_cannot_occur: + raise TyperError("missing hop.exception_cannot_occur() or " + "hop.exception_is_here() in %s" % bltintyper) + return v_result + def rtype_simple_call(self, hop): - bltintyper = self.findbltintyper(hop.rtyper) hop2 = hop.copy() hop2.r_s_popfirstarg() - return bltintyper(hop2) + return self._call(hop2) def rtype_call_args(self, hop): # calling a built-in function with keyword arguments: # mostly for rpython.objectmodel.hint() hop, kwds_i = call_args_expand(hop) - bltintyper = self.findbltintyper(hop.rtyper) hop2 = hop.copy() hop2.r_s_popfirstarg() hop2.r_s_popfirstarg() # the RPython-level keyword args are passed with an 'i_' prefix and # the corresponding value is an *index* in the hop2 arguments, # to be used with hop.inputarg(arg=..) - return bltintyper(hop2, **kwds_i) + return self._call(hop2, **kwds_i) class BuiltinMethodRepr(Repr): @@ -198,6 +205,7 @@ # ____________________________________________________________ def rtype_builtin_bool(hop): + # not called any more? assert hop.nb_args == 1 return hop.args_r[0].rtype_is_true(hop) @@ -241,6 +249,7 @@ def rtype_builtin_min(hop): v1, v2 = hop.inputargs(hop.r_result, hop.r_result) + hop.exception_cannot_occur() return hop.gendirectcall(ll_min, v1, v2) def ll_min(i1, i2): @@ -250,6 +259,7 @@ def rtype_builtin_max(hop): v1, v2 = hop.inputargs(hop.r_result, hop.r_result) + hop.exception_cannot_occur() return hop.gendirectcall(ll_max, v1, v2) def ll_max(i1, i2): @@ -264,6 +274,7 @@ pass def rtype_OSError__init__(hop): + hop.exception_cannot_occur() if hop.nb_args == 2: raise TyperError("OSError() should not be called with " "a single argument") @@ -274,6 +285,7 @@ r_self.setfield(v_self, 'errno', v_errno, hop.llops) def rtype_WindowsError__init__(hop): + hop.exception_cannot_occur() if hop.nb_args == 2: raise TyperError("WindowsError() should not be called with " "a single argument") @@ -442,6 +454,7 @@ assert hop.args_s[0].is_constant() TGT = hop.args_s[0].const v_type, v_value = hop.inputargs(lltype.Void, hop.args_r[1]) + hop.exception_cannot_occur() return gen_cast(hop.llops, TGT, v_value) _cast_to_Signed = { @@ -523,11 +536,13 @@ def rtype_identity_hash(hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_identityhash', vlist, resulttype=lltype.Signed) def rtype_runtime_type_info(hop): assert isinstance(hop.args_r[0], rptr.PtrRepr) vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('runtime_type_info', vlist, resulttype = hop.r_result.lowleveltype) @@ -558,6 +573,7 @@ def rtype_raw_malloc(hop): v_size, = hop.inputargs(lltype.Signed) + hop.exception_cannot_occur() return hop.genop('raw_malloc', [v_size], resulttype=llmemory.Address) def rtype_raw_malloc_usage(hop): @@ -586,6 +602,7 @@ if s_addr.is_null_address(): raise TyperError("raw_memclear(x, n) where x is the constant NULL") v_list = hop.inputargs(llmemory.Address, lltype.Signed) + hop.exception_cannot_occur() return hop.genop('raw_memclear', v_list) BUILTIN_TYPER[llmemory.raw_malloc] = rtype_raw_malloc @@ -596,6 +613,7 @@ def rtype_offsetof(hop): TYPE, field = hop.inputargs(lltype.Void, lltype.Void) + hop.exception_cannot_occur() return hop.inputconst(lltype.Signed, llmemory.offsetof(TYPE.value, field.value)) @@ -605,6 +623,7 @@ # non-gc objects def rtype_free_non_gc_object(hop): + hop.exception_cannot_occur() vinst, = hop.inputargs(hop.args_r[0]) flavor = hop.args_r[0].gcflavor assert flavor != 'gc' @@ -617,6 +636,7 @@ # keepalive_until_here def rtype_keepalive_until_here(hop): + hop.exception_cannot_occur() for v in hop.args_v: hop.genop('keepalive', [v], resulttype=lltype.Void) return hop.inputconst(lltype.Void, None) diff --git a/pypy/rpython/rfloat.py b/pypy/rpython/rfloat.py --- a/pypy/rpython/rfloat.py +++ b/pypy/rpython/rfloat.py @@ -136,7 +136,10 @@ hop.exception_cannot_occur() return hop.genop('cast_float_to_int', vlist, resulttype=Signed) - rtype_float = rtype_pos + def rtype_float(_, hop): + vlist = hop.inputargs(Float) + hop.exception_cannot_occur() + return vlist[0] # version picked by specialisation based on which # type system rtyping is using, from .ll_str module diff --git a/pypy/rpython/rint.py b/pypy/rpython/rint.py --- a/pypy/rpython/rint.py +++ b/pypy/rpython/rint.py @@ -310,6 +310,8 @@ if hop.has_implicit_exception(ValueError): hop.exception_is_here() hop.gendirectcall(ll_check_chr, vlist[0]) + else: + hop.exception_cannot_occur() return hop.genop('cast_int_to_char', vlist, resulttype=Char) def rtype_unichr(_, hop): @@ -317,6 +319,8 @@ if hop.has_implicit_exception(ValueError): hop.exception_is_here() hop.gendirectcall(ll_check_unichr, vlist[0]) + else: + hop.exception_cannot_occur() return hop.genop('cast_int_to_unichar', vlist, resulttype=UniChar) def rtype_is_true(self, hop): diff --git a/pypy/rpython/rlist.py b/pypy/rpython/rlist.py --- a/pypy/rpython/rlist.py +++ b/pypy/rpython/rlist.py @@ -115,6 +115,7 @@ def rtype_bltn_list(self, hop): v_lst = hop.inputarg(self, 0) cRESLIST = hop.inputconst(Void, hop.r_result.LIST) + hop.exception_is_here() return hop.gendirectcall(ll_copy, cRESLIST, v_lst) def rtype_len(self, hop): diff --git a/pypy/rpython/rrange.py b/pypy/rpython/rrange.py --- a/pypy/rpython/rrange.py +++ b/pypy/rpython/rrange.py @@ -107,8 +107,10 @@ if isinstance(hop.r_result, AbstractRangeRepr): if hop.r_result.step != 0: c_rng = hop.inputconst(Void, hop.r_result.RANGE) + hop.exception_is_here() return hop.gendirectcall(hop.r_result.ll_newrange, c_rng, vstart, vstop) else: + hop.exception_is_here() return hop.gendirectcall(hop.r_result.ll_newrangest, vstart, vstop, vstep) else: # cannot build a RANGE object, needs a real list @@ -117,6 +119,7 @@ if isinstance(ITEMTYPE, Ptr): ITEMTYPE = ITEMTYPE.TO cLIST = hop.inputconst(Void, ITEMTYPE) + hop.exception_is_here() return hop.gendirectcall(ll_range2list, cLIST, vstart, vstop, vstep) rtype_builtin_xrange = rtype_builtin_range @@ -212,4 +215,5 @@ [v_index, v_item]) def rtype_builtin_enumerate(hop): + hop.exception_cannot_occur() return hop.r_result.r_baseiter.newiter(hop) diff --git a/pypy/rpython/rstr.py b/pypy/rpython/rstr.py --- a/pypy/rpython/rstr.py +++ b/pypy/rpython/rstr.py @@ -288,6 +288,8 @@ def rtype_unicode(self, hop): if hop.args_s[0].is_constant(): + # convertion errors occur during annotation, so cannot any more: + hop.exception_cannot_occur() return hop.inputconst(hop.r_result, hop.s_result.const) repr = hop.args_r[0].repr v_str = hop.inputarg(repr, 0) diff --git a/pypy/rpython/rtyper.py b/pypy/rpython/rtyper.py --- a/pypy/rpython/rtyper.py +++ b/pypy/rpython/rtyper.py @@ -846,6 +846,7 @@ return result def exception_is_here(self): + self.llops._called_exception_is_here_or_cannot_occur = True if self.llops.llop_raising_exceptions is not None: raise TyperError("cannot catch an exception at more than one llop") if not self.exceptionlinks: @@ -861,6 +862,7 @@ self.llops.llop_raising_exceptions = len(self.llops) def exception_cannot_occur(self): + self.llops._called_exception_is_here_or_cannot_occur = True if self.llops.llop_raising_exceptions is not None: raise TyperError("cannot catch an exception at more than one llop") if not self.exceptionlinks: diff --git a/pypy/rpython/test/test_extregistry.py b/pypy/rpython/test/test_extregistry.py --- a/pypy/rpython/test/test_extregistry.py +++ b/pypy/rpython/test/test_extregistry.py @@ -114,6 +114,7 @@ _about_ = dummy_func s_result_annotation = annmodel.SomeInteger() def specialize_call(self, hop): + hop.exception_cannot_occur() return hop.inputconst(lltype.Signed, 42) def func(): diff --git a/pypy/rpython/test/test_rclass.py b/pypy/rpython/test/test_rclass.py --- a/pypy/rpython/test/test_rclass.py +++ b/pypy/rpython/test/test_rclass.py @@ -1085,6 +1085,7 @@ return annmodel.SomeInteger() def specialize_call(self, hop): [v_instance] = hop.inputargs(*hop.args_r) + hop.exception_is_here() return hop.gendirectcall(ll_my_gethash, v_instance) def f(n): diff --git a/pypy/translator/c/test/test_extfunc.py b/pypy/translator/c/test/test_extfunc.py --- a/pypy/translator/c/test/test_extfunc.py +++ b/pypy/translator/c/test/test_extfunc.py @@ -919,4 +919,5 @@ t, cbuilder = self.compile(does_stuff) data = cbuilder.cmdexec('') res = os.nice(0) + 3 + if res > 19: res = 19 # xxx Linux specific, probably assert data.startswith('os.nice returned %d\n' % res) diff --git a/pypy/translator/cli/dotnet.py b/pypy/translator/cli/dotnet.py --- a/pypy/translator/cli/dotnet.py +++ b/pypy/translator/cli/dotnet.py @@ -459,6 +459,7 @@ def specialize_call(self, hop): + hop.exception_cannot_occur() assert hop.args_s[1].is_constant() TYPE = hop.args_s[1].const v_obj = hop.inputarg(hop.args_r[0], arg=0) @@ -507,6 +508,7 @@ def specialize_call(self, hop): v_obj, = hop.inputargs(*hop.args_r) + hop.exception_cannot_occur() return hop.genop('same_as', [v_obj], hop.r_result.lowleveltype) def new_array(type, length): @@ -608,6 +610,7 @@ def specialize_call(self, hop): v_type, = hop.inputargs(*hop.args_r) + hop.exception_cannot_occur() return hop.genop('cli_typeof', [v_type], hop.r_result.lowleveltype) @@ -626,6 +629,7 @@ v_obj, = hop.inputargs(*hop.args_r) methodname = hop.args_r[0].methodname c_methodname = hop.inputconst(ootype.Void, methodname) + hop.exception_cannot_occur() return hop.genop('cli_eventhandler', [v_obj, c_methodname], hop.r_result.lowleveltype) @@ -647,6 +651,7 @@ def specialize_call(self, hop): assert isinstance(hop.args_s[0], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[0], arg=0) + hop.exception_cannot_occur() return hop.genop('oodowncast', [v_inst], resulttype = hop.r_result.lowleveltype) @@ -668,6 +673,7 @@ def specialize_call(self, hop): assert isinstance(hop.args_s[0], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[0], arg=0) + hop.exception_cannot_occur() return hop.genop('ooupcast', [v_inst], resulttype = hop.r_result.lowleveltype) @@ -701,6 +707,7 @@ def specialize_call(self, hop): v_obj = hop.inputarg(hop.args_r[0], arg=0) + hop.exception_cannot_occur() return hop.genop('oodowncast', [v_obj], hop.r_result.lowleveltype) From noreply at buildbot.pypy.org Tue Apr 3 01:43:49 2012 From: noreply at buildbot.pypy.org (wlav) Date: Tue, 3 Apr 2012 01:43:49 +0200 (CEST) Subject: [pypy-commit] pypy reflex-support: merge default into branch; translation now works again Message-ID: <20120402234349.BEA7E46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Wim Lavrijsen Branch: reflex-support Changeset: r54168:374968f4e0af Date: 2012-04-02 16:41 -0700 http://bitbucket.org/pypy/pypy/changeset/374968f4e0af/ Log: merge default into branch; translation now works again diff --git a/pypy/objspace/std/dictproxyobject.py b/pypy/objspace/std/dictproxyobject.py --- a/pypy/objspace/std/dictproxyobject.py +++ b/pypy/objspace/std/dictproxyobject.py @@ -108,7 +108,8 @@ class DictProxyIteratorImplementation(IteratorImplementation): def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) + IteratorImplementation.__init__( + self, space, strategy, dictimplementation) w_type = strategy.unerase(dictimplementation.dstorage) self.iterator = w_type.dict_w.iteritems() diff --git a/pypy/objspace/std/mapdict.py b/pypy/objspace/std/mapdict.py --- a/pypy/objspace/std/mapdict.py +++ b/pypy/objspace/std/mapdict.py @@ -703,7 +703,8 @@ class MapDictIteratorImplementation(IteratorImplementation): def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) + IteratorImplementation.__init__( + self, space, strategy, dictimplementation) w_obj = strategy.unerase(dictimplementation.dstorage) self.w_obj = w_obj self.orig_map = self.curr_map = w_obj._get_mapdict_map() From notifications-noreply at bitbucket.org Tue Apr 3 03:32:45 2012 From: notifications-noreply at bitbucket.org (Bitbucket) Date: Tue, 03 Apr 2012 01:32:45 -0000 Subject: [pypy-commit] Notification: pypy Message-ID: <20120403013245.5585.88664@bitbucket13.managed.contegix.com> You have received a notification from Preston Timmons. Hi, I forked pypy. My fork is at https://bitbucket.org/prestontimmons/pypy. -- Disable notifications at https://bitbucket.org/account/notifications/ From notifications-noreply at bitbucket.org Tue Apr 3 09:13:36 2012 From: notifications-noreply at bitbucket.org (Bitbucket) Date: Tue, 03 Apr 2012 07:13:36 -0000 Subject: [pypy-commit] Notification: jitviewer Message-ID: <20120403071336.3153.20260@bitbucket02.managed.contegix.com> You have received a notification from michielbaird. Hi, I forked jitviewer. My fork is at https://bitbucket.org/michielbaird/jitviewer. -- Disable notifications at https://bitbucket.org/account/notifications/ From noreply at buildbot.pypy.org Tue Apr 3 13:08:27 2012 From: noreply at buildbot.pypy.org (fijal) Date: Tue, 3 Apr 2012 13:08:27 +0200 (CEST) Subject: [pypy-commit] benchmarks default: I think I disabled translate by chance Message-ID: <20120403110827.4EFAD820C7@wyvern.cs.uni-duesseldorf.de> Author: Maciej Fijalkowski Branch: Changeset: r177:54315ab5faa1 Date: 2012-04-03 13:07 +0200 http://bitbucket.org/pypy/benchmarks/changeset/54315ab5faa1/ Log: I think I disabled translate by chance diff --git a/benchmarks.py b/benchmarks.py --- a/benchmarks.py +++ b/benchmarks.py @@ -118,7 +118,7 @@ ('database', 0.4) ] -def XBM_translate(base_python, changed_python, options): +def BM_translate(base_python, changed_python, options): """ Run translate.py and returns a benchmark result for each of the phases. Note that we run it only with ``base_python`` (which corresponds to @@ -148,7 +148,7 @@ data = RawResult([time], None) result.append((name, data)) return result -XBM_translate.benchmark_name = 'trans' +BM_translate.benchmark_name = 'trans' def BM_cpython_doc(base_python, changed_python, options): from unladen_swallow.perf import RawResult From noreply at buildbot.pypy.org Tue Apr 3 18:38:30 2012 From: noreply at buildbot.pypy.org (arigo) Date: Tue, 3 Apr 2012 18:38:30 +0200 (CEST) Subject: [pypy-commit] pypy stm-gc: Starting to rewrite stmgc according to the latest version of Message-ID: <20120403163830.BD98E820C7@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: stm-gc Changeset: r54169:4614cb079670 Date: 2012-04-03 18:09 +0200 http://bitbucket.org/pypy/pypy/changeset/4614cb079670/ Log: Starting to rewrite stmgc according to the latest version of extradoc/planning/stm.txt. Moved stuff around in new files. diff --git a/pypy/rpython/memory/gc/minimark.py b/pypy/rpython/memory/gc/minimark.py --- a/pypy/rpython/memory/gc/minimark.py +++ b/pypy/rpython/memory/gc/minimark.py @@ -47,7 +47,7 @@ from pypy.rpython.lltypesystem.lloperation import llop from pypy.rpython.lltypesystem.llmemory import raw_malloc_usage from pypy.rpython.memory.gc.base import GCBase, MovingGCBase -from pypy.rpython.memory.gc import minimarkpage, env +from pypy.rpython.memory.gc import env from pypy.rpython.memory.support import mangle_hash from pypy.rlib.rarithmetic import ovfcheck, LONG_BIT, intmask, r_uint from pypy.rlib.rarithmetic import LONG_BIT_SHIFT @@ -248,6 +248,7 @@ # # The ArenaCollection() handles the nonmovable objects allocation. if ArenaCollectionClass is None: + from pypy.rpython.memory.gc import minimarkpage ArenaCollectionClass = minimarkpage.ArenaCollection self.ac = ArenaCollectionClass(arena_size, page_size, small_request_threshold) @@ -2026,43 +2027,3 @@ (obj + offset).address[0] = llmemory.NULL self.old_objects_with_weakrefs.delete() self.old_objects_with_weakrefs = new_with_weakref - - -# ____________________________________________________________ - -# For testing, a simple implementation of ArenaCollection. -# This version could be used together with obmalloc.c, but -# it requires an extra word per object in the 'all_objects' -# list. - -class SimpleArenaCollection(object): - - def __init__(self, arena_size, page_size, small_request_threshold): - self.arena_size = arena_size # ignored - self.page_size = page_size - self.small_request_threshold = small_request_threshold - self.all_objects = [] - self.total_memory_used = 0 - - def malloc(self, size): - nsize = raw_malloc_usage(size) - ll_assert(nsize > 0, "malloc: size is null or negative") - ll_assert(nsize <= self.small_request_threshold,"malloc: size too big") - ll_assert((nsize & (WORD-1)) == 0, "malloc: size is not aligned") - # - result = llarena.arena_malloc(nsize, False) - llarena.arena_reserve(result, size) - self.all_objects.append((result, nsize)) - self.total_memory_used += nsize - return result - - def mass_free(self, ok_to_free_func): - objs = self.all_objects - self.all_objects = [] - self.total_memory_used = 0 - for rawobj, nsize in objs: - if ok_to_free_func(rawobj): - llarena.arena_free(rawobj) - else: - self.all_objects.append((rawobj, nsize)) - self.total_memory_used += nsize diff --git a/pypy/rpython/memory/gc/minimarktest.py b/pypy/rpython/memory/gc/minimarktest.py new file mode 100644 --- /dev/null +++ b/pypy/rpython/memory/gc/minimarktest.py @@ -0,0 +1,44 @@ +from pypy.rpython.lltypesystem import llarena +from pypy.rpython.lltypesystem.llmemory import raw_malloc_usage +from pypy.rlib.debug import ll_assert +from pypy.rlib.rarithmetic import LONG_BIT + +# For testing, a simple implementation of ArenaCollection. +# This version could be used together with obmalloc.c, but +# it requires an extra word per object in the 'all_objects' +# list. + +WORD = LONG_BIT // 8 + + +class SimpleArenaCollection(object): + + def __init__(self, arena_size, page_size, small_request_threshold): + self.arena_size = arena_size # ignored + self.page_size = page_size + self.small_request_threshold = small_request_threshold + self.all_objects = [] + self.total_memory_used = 0 + + def malloc(self, size): + nsize = raw_malloc_usage(size) + ll_assert(nsize > 0, "malloc: size is null or negative") + ll_assert(nsize <= self.small_request_threshold,"malloc: size too big") + ll_assert((nsize & (WORD-1)) == 0, "malloc: size is not aligned") + # + result = llarena.arena_malloc(nsize, False) + llarena.arena_reserve(result, size) + self.all_objects.append((result, nsize)) + self.total_memory_used += nsize + return result + + def mass_free(self, ok_to_free_func): + objs = self.all_objects + self.all_objects = [] + self.total_memory_used = 0 + for rawobj, nsize in objs: + if ok_to_free_func(rawobj): + llarena.arena_free(rawobj) + else: + self.all_objects.append((rawobj, nsize)) + self.total_memory_used += nsize diff --git a/pypy/rpython/memory/gc/stmgc.py b/pypy/rpython/memory/gc/stmgc.py --- a/pypy/rpython/memory/gc/stmgc.py +++ b/pypy/rpython/memory/gc/stmgc.py @@ -1,7 +1,7 @@ -from pypy.rpython.lltypesystem import lltype, llmemory, llarena, llgroup, rffi +from pypy.rpython.lltypesystem import lltype, llmemory, llarena, llgroup from pypy.rpython.lltypesystem.lloperation import llop from pypy.rpython.lltypesystem.llmemory import raw_malloc_usage -from pypy.rpython.memory.gc.base import GCBase +from pypy.rpython.memory.gc.base import GCBase, MovingGCBase from pypy.rpython.memory.support import mangle_hash from pypy.rpython.annlowlevel import llhelper from pypy.rlib.rarithmetic import LONG_BIT @@ -15,6 +15,26 @@ first_gcflag = 1 << (LONG_BIT//2) +# Terminology: +# +# - Objects can be LOCAL or GLOBAL. This is what STM is based on. +# Local objects are not visible to any other transaction, whereas +# global objects are, and need care. +# +# - Each object lives either in the shared area, or in a thread-local +# nursery. The shared area contains: +# - the prebuilt objects +# - the small objects allocated via minimarkpage.py +# - the non-small raw-malloced objects +# +# - The GLOBAL objects are all located in the shared area. +# All global objects are non-movable. +# +# - The LOCAL objects might be YOUNG or OLD depending on whether they +# already survived a collection. YOUNG LOCAL objects are either in +# the nursery or, if they are big, raw-malloced. OLD LOCAL objects +# are in the shared area. +# GCFLAG_GLOBAL = first_gcflag << 0 # keep in sync with et.c GCFLAG_WAS_COPIED = first_gcflag << 1 # keep in sync with et.c GCFLAG_HAS_SHADOW = first_gcflag << 2 @@ -30,7 +50,7 @@ return fn -class StmGC(GCBase): +class StmGC(MovingGCBase): _alloc_flavor_ = "raw" inline_simple_malloc = True inline_simple_malloc_varsize = True @@ -43,27 +63,24 @@ typeid_is_in_field = 'tid' withhash_flag_is_in_field = 'tid', GCFLAG_FIXED_HASH - GCTLS = lltype.Struct('GCTLS', ('nursery_free', llmemory.Address), - ('nursery_top', llmemory.Address), - ('nursery_start', llmemory.Address), - ('nursery_size', lltype.Signed), - ('malloc_flags', lltype.Signed), - ('pending_list', llmemory.Address), - ('surviving_weakrefs', llmemory.Address), - ('global_free', llmemory.Address), - ('global_stop', llmemory.Address), - ) - TRANSLATION_PARAMS = { 'stm_operations': 'use_real_one', - 'max_nursery_size': 400*1024*1024, # XXX 400MB - 'tls_page_size': 64*1024, # 64KB + 'nursery_size': 4*1024*1024, # 4 MB + + "page_size": 1024*WORD, # copied from minimark.py + "arena_size": 65536*WORD, # copied from minimark.py + "small_request_threshold": 35*WORD, # copied from minimark.py } - def __init__(self, config, stm_operations='use_emulator', - max_nursery_size=1024, tls_page_size=64, + def __init__(self, config, + stm_operations='use_emulator', + nursery_size=1024, + page_size=16*WORD, + arena_size=64*WORD, + small_request_threshold=5*WORD, + ArenaCollectionClass=None, **kwds): - GCBase.__init__(self, config, **kwds) + MovingGCBase.__init__(self, config, **kwds) # if isinstance(stm_operations, str): assert stm_operations == 'use_real_one', ( @@ -72,10 +89,12 @@ from pypy.translator.stm.stmgcintf import StmOperations stm_operations = StmOperations() # + from pypy.rpython.memory.gc import stmshared self.stm_operations = stm_operations - self.collector = Collector(self) - self.max_nursery_size = max_nursery_size - self.tls_page_size = tls_page_size + self.nursery_size = nursery_size + self.sharedarea = stmshared.StmGCSharedArea(self, + page_size, arena_size, + small_request_threshold) # def _get_size(obj): # indirection to hide 'self' return self.get_size(obj) @@ -87,64 +106,30 @@ def setup(self): """Called at run-time to initialize the GC.""" + # + # Hack: MovingGCBase.setup() sets up stuff related to id(), which + # we implement differently anyway. So directly call GCBase.setup(). GCBase.setup(self) + # self.stm_operations.setup_size_getter( llhelper(self.stm_operations.GETSIZE, self._getsize_fn)) - self.main_thread_tls = self.setup_thread(True) - self.mutex_lock = ll_thread.allocate_ll_lock() + # + self.sharedarea.setup() + # + from pypy.rpython.memory.gc.stmtls import StmGCTLS + self.main_thread_tls = StmGCTLS(self, in_main_thread=True) + self.main_thread_tls.enter() - def _alloc_nursery(self): - nursery = llarena.arena_malloc(self.max_nursery_size, 1) - if not nursery: - raise MemoryError("cannot allocate nursery") - return nursery - - def _free_nursery(self, nursery): - llarena.arena_free(nursery) - - def setup_thread(self, in_main_thread=False): - """Setup a thread. Allocates the thread-local data structures. - Must be called only once per OS-level thread.""" - tls = lltype.malloc(self.GCTLS, zero=True, flavor='raw') - self.stm_operations.set_tls(llmemory.cast_ptr_to_adr(tls), - int(in_main_thread)) - tls.nursery_start = self._alloc_nursery() - tls.nursery_size = self.max_nursery_size - tls.nursery_free = tls.nursery_start - tls.nursery_top = tls.nursery_start + tls.nursery_size - # - # XXX for now, we use as the "global area" the nursery of the - # main thread. So allocation in the main thread is the same as - # allocation in another thread, except that the new objects - # should be immediately marked as GCFLAG_GLOBAL. - if in_main_thread: - tls.malloc_flags = GCFLAG_GLOBAL - else: - tls.malloc_flags = -1 # don't malloc outside a transaction! - return tls - - @staticmethod - def reset_nursery(tls): - """Clear and forget all locally allocated objects.""" - size = tls.nursery_free - tls.nursery_start - llarena.arena_reset(tls.nursery_start, size, 2) - tls.nursery_free = tls.nursery_start - - def teardown_thread(self): - """Teardown a thread. Call this just before the OS-level thread - disappears.""" - tls = self.collector.get_tls() - self.stm_operations.del_tls() - self._free_nursery(tls.nursery_start) - lltype.free(tls, flavor='raw') + def get_tls(self): + from pypy.rpython.memory.gc.stmtls import StmGCTLS + tls = self.stm_operations.get_tls() + return StmGCTLS.cast_address_to_tls_object(tls) # ---------- + @always_inline def allocate_bump_pointer(self, size): - return self._allocate_bump_pointer(self.collector.get_tls(), size) - - @always_inline - def _allocate_bump_pointer(self, tls, size): + tls = self.collector.get_tls() free = tls.nursery_free top = tls.nursery_top if (top - free) < llmemory.raw_malloc_usage(size): @@ -154,7 +139,11 @@ @dont_inline def local_collection(self, size): - raise MemoryError("nursery exhausted") # XXX for now + tls = self.collector.get_tls() + if not tls.nursery_free: + fatalerror("malloc in a non-main thread but outside a transaction") + #... + xxxxxxxxx def malloc_fixedsize_clear(self, typeid, size, @@ -166,19 +155,16 @@ # Check the mode: either in a transactional thread, or in # the main thread. For now we do the same thing in both # modes, but set different flags. - tls = self.collector.get_tls() - flags = tls.malloc_flags - ll_assert(flags != -1, "malloc() in a transactional thread but " - "outside a transaction") # # Get the memory from the nursery. size_gc_header = self.gcheaderbuilder.size_gc_header totalsize = size_gc_header + size - result = self._allocate_bump_pointer(tls, totalsize) + result = self.allocate_bump_pointer(totalsize) # # Build the object. llarena.arena_reserve(result, totalsize) obj = result + size_gc_header + flags = 0 if contains_weakptr: # check constant-folded flags |= GCFLAG_WEAKREF self.init_gc_object(result, typeid, flags=flags) @@ -190,18 +176,14 @@ offset_to_length): # XXX blindly copied from malloc_fixedsize_clear() for now. # XXX Be more subtle, e.g. detecting overflows, at least - tls = self.collector.get_tls() - flags = tls.malloc_flags - ll_assert(flags != -1, "malloc() in a transactional thread but " - "outside a transaction") size_gc_header = self.gcheaderbuilder.size_gc_header nonvarsize = size_gc_header + size totalsize = nonvarsize + itemsize * length totalsize = llarena.round_up_for_allocation(totalsize) - result = self._allocate_bump_pointer(tls, totalsize) + result = self.allocate_bump_pointer(totalsize) llarena.arena_reserve(result, totalsize) obj = result + size_gc_header - self.init_gc_object(result, typeid, flags=flags) + self.init_gc_object(result, typeid, flags=0) (obj + offset_to_length).signed[0] = length return llmemory.cast_adr_to_ptr(obj, llmemory.GCREF) @@ -209,6 +191,7 @@ def _malloc_local_raw(self, tls, size): # for _stm_write_barrier_global(): a version of malloc that does # no initialization of the malloc'ed object + xxxxxxxx size_gc_header = self.gcheaderbuilder.size_gc_header totalsize = size_gc_header + size result = self._allocate_bump_pointer(tls, totalsize) @@ -235,6 +218,7 @@ @dont_inline def _malloc_global_more(self, tls, totalsize): + xxxxxxxxxxxxxxxxx if totalsize < self.tls_page_size: totalsize = self.tls_page_size main_tls = self.main_thread_tls @@ -480,10 +464,6 @@ def _freeze_(self): return True - def get_tls(self): - tls = self.stm_operations.get_tls() - return llmemory.cast_adr_to_ptr(tls, lltype.Ptr(StmGC.GCTLS)) - def is_in_nursery(self, tls, addr): ll_assert(llmemory.cast_adr_to_int(addr) & 1 == 0, "odd-valued (i.e. tagged) pointer unexpected here") @@ -497,7 +477,6 @@ """Start a transaction, by clearing and resetting the tls nursery.""" tls = self.get_tls() self.gc.reset_nursery(tls) - tls.malloc_flags = 0 def commit_transaction(self): @@ -508,7 +487,6 @@ debug_start("gc-collect-commit") # tls = self.get_tls() - tls.malloc_flags = -1 # # Do a mark-and-move minor collection out of the tls' nursery # into the main thread's global area (which is right now also diff --git a/pypy/rpython/memory/gc/stmshared.py b/pypy/rpython/memory/gc/stmshared.py new file mode 100644 --- /dev/null +++ b/pypy/rpython/memory/gc/stmshared.py @@ -0,0 +1,14 @@ + + +class StmGCSharedArea(object): + + def __init__(self, gc, page_size, arena_size, small_request_threshold): + ... + # The ArenaCollection() handles the nonmovable objects allocation. + # It contains all small GCFLAG_GLOBAL objects. The non-small ones + # are directly malloc'ed. + if ArenaCollectionClass is None: + from pypy.rpython.memory.gc import minimarkpage + ArenaCollectionClass = minimarkpage.ArenaCollection + self.ac = ArenaCollectionClass(arena_size, page_size, + small_request_threshold) diff --git a/pypy/rpython/memory/gc/stmtls.py b/pypy/rpython/memory/gc/stmtls.py new file mode 100644 --- /dev/null +++ b/pypy/rpython/memory/gc/stmtls.py @@ -0,0 +1,108 @@ +from pypy.rpython.lltypesystem import lltype, llmemory +from pypy.rpython.annlowlevel import cast_instance_to_base_ptr +from pypy.rpython.annlowlevel import cast_base_ptr_to_instance, base_ptr_lltype +from pypy.rlib import objectmodel +from pypy.rlib.rarithmetic import r_uint +from pypy.rlib.debug import ll_assert + +from pypy.rpython.memory.gc.stmgc import WORD, NULL + + +class StmGCTLS(object): + """The thread-local structure: we have one instance of these per thread, + including one for the main thread.""" + + _alloc_flavor_ = 'raw' + + def __init__(self, gc, in_main_thread): + self.in_main_thread = in_main_thread + self.stm_operations = self.gc.stm_operations + self.null_address_dict = self.gc.null_address_dict + self.AddressStack = self.gc.AddressStack + self.AddressDict = self.gc.AddressDict + # + # --- current position and end of nursery, or NULL when + # mallocs are forbidden + self.nursery_free = NULL + self.nursery_top = NULL + # --- the start and size of the nursery belonging to this thread. + # never changes. + self.nursery_size = self.gc.nursery_size + self.nursery_start = self._alloc_nursery(self.nursery_size) + # + # --- the local raw-malloced objects, young and old + self.rawmalloced_young_objects = self.null_address_dict() + self.rawmalloced_old_objects = self.AddressStack() + self.rawmalloced_total_size = r_uint(0) + # --- the local objects with weakrefs, young and old + self.young_objects_with_weakrefs = self.AddressStack() + self.old_objects_with_weakrefs = self.AddressStack() + # --- support for id and identityhash: maps nursery objects with + # GCFLAG_HAS_SHADOW to their future location at the next + # local collection + self.nursery_objects_shadows = self.AddressDict() + # + self._register_with_C_code() + + def teardown_thread(self): + self._unregister_with_C_code() + self._free_nursery(self.nursery_start) + objectmodel.free_non_gc_object(self) + + def _alloc_nursery(self, nursery_size): + nursery = llarena.arena_malloc(nursery_size, 1) + if not nursery: + raise MemoryError("cannot allocate nursery") + return nursery + + def _free_nursery(self, nursery): + llarena.arena_free(nursery) + + def _register_with_C_code(self): + tls = cast_instance_to_base_ptr(self) + self.stm_operations.set_tls(llmemory.cast_ptr_to_adr(tls), + int(self.in_main_thread)) + + def _unregister_with_C_code(self): + ll_assert(self.gc.get_tls() is self, + "unregister_with_C_code: wrong thread") + self.stm_operations.del_tls() + + @staticmethod + def cast_address_to_tls_object(self, tlsaddr): + tls = llmemory.cast_adr_to_ptr(tlsaddr, base_ptr_lltype()) + return cast_base_ptr_to_instance(tls) + + # ------------------------------------------------------------ + + def enter(self): + """Enter a thread: performs any pending cleanups, and set + up a fresh state for allocating. Called at the start of + transactions, and at the start of the main thread.""" + # Note that the calls to enter() and leave() are not balanced: + # if a transaction is aborted, leave() might never be called. + # Be ready here to clean up any state. + xxx + + def leave(self): + """Leave a thread: no more allocations are allowed. + Called when a transaction finishes.""" + xxx + + # ------------------------------------------------------------ + + def end_of_transaction_collection(self): + """Do an end-of-transaction collection. Finds all surviving + young objects and make them old. This guarantees that the + nursery is empty afterwards. (Even if there are finalizers, XXX) + Assumes that there are no roots from the stack. + """ + xxx + + def local_collection(self): + """Do a local collection. Finds all surviving young objects + and make them old. Also looks for roots from the stack. + """ + xxx + + # ------------------------------------------------------------ diff --git a/pypy/rpython/memory/gc/test/test_direct.py b/pypy/rpython/memory/gc/test/test_direct.py --- a/pypy/rpython/memory/gc/test/test_direct.py +++ b/pypy/rpython/memory/gc/test/test_direct.py @@ -497,7 +497,7 @@ class TestMiniMarkGCSimple(DirectGCTest): from pypy.rpython.memory.gc.minimark import MiniMarkGC as GCClass - from pypy.rpython.memory.gc.minimark import SimpleArenaCollection + from pypy.rpython.memory.gc.minimarktest import SimpleArenaCollection # test the GC itself, providing a simple class for ArenaCollection GC_PARAMS = {'ArenaCollectionClass': SimpleArenaCollection} diff --git a/pypy/rpython/memory/gc/test/test_inspector.py b/pypy/rpython/memory/gc/test/test_inspector.py --- a/pypy/rpython/memory/gc/test/test_inspector.py +++ b/pypy/rpython/memory/gc/test/test_inspector.py @@ -43,6 +43,6 @@ class TestMiniMarkGCSimple(InspectorTest): from pypy.rpython.memory.gc.minimark import MiniMarkGC as GCClass - from pypy.rpython.memory.gc.minimark import SimpleArenaCollection + from pypy.rpython.memory.gc.minimarktest import SimpleArenaCollection GC_PARAMS = {'ArenaCollectionClass': SimpleArenaCollection, "card_page_indices": 4} From noreply at buildbot.pypy.org Tue Apr 3 21:43:31 2012 From: noreply at buildbot.pypy.org (mattip) Date: Tue, 3 Apr 2012 21:43:31 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: adding tests where os.write should fail, add impl of validate_fd Message-ID: <20120403194331.2AF4D820C7@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54170:f89544de8192 Date: 2012-04-03 22:37 +0300 http://bitbucket.org/pypy/pypy/changeset/f89544de8192/ Log: adding tests where os.write should fail, add impl of validate_fd diff --git a/pypy/module/cpyext/api.py b/pypy/module/cpyext/api.py --- a/pypy/module/cpyext/api.py +++ b/pypy/module/cpyext/api.py @@ -81,7 +81,7 @@ FILEP = rffi.COpaquePtr('FILE') fopen = rffi.llexternal('fopen', [CONST_STRING, CONST_STRING], FILEP) -#fclose = rffi.llexternal('fclose', [FILEP], rffi.INT) +_fclose = rffi.llexternal('fclose', [FILEP], rffi.INT) def fclose(fp): try: fd = fileno(fp) diff --git a/pypy/rlib/rposix.py b/pypy/rlib/rposix.py --- a/pypy/rlib/rposix.py +++ b/pypy/rlib/rposix.py @@ -1,5 +1,6 @@ import os -from pypy.rpython.lltypesystem.rffi import CConstant, CExternVariable, INT +from pypy.rpython.lltypesystem.rffi import (CConstant, CExternVariable, + INT, CCHARPP) from pypy.rpython.lltypesystem import lltype, ll2ctypes, rffi from pypy.translator.tool.cbuild import ExternalCompilationInfo from pypy.rlib.rarithmetic import intmask @@ -18,12 +19,68 @@ def __setitem__(self, index, value): assert index == 0 ll2ctypes.TLS.errno = value +if os.name == 'nt': + post_include_bits =[''' + /* Lifted completely from CPython 3.3 Modules/posix_module.c */ + typedef struct { + intptr_t osfhnd; + char osfile; + } my_ioinfo; + extern __declspec(dllimport) char * __pioinfo[]; + #define IOINFO_L2E 5 + #define IOINFO_ARRAY_ELTS (1 << IOINFO_L2E) + #define IOINFO_ARRAYS 64 + #define _NHANDLE_ (IOINFO_ARRAYS * IOINFO_ARRAY_ELTS) + #define FOPEN 0x01 + #define _NO_CONSOLE_FILENO (intptr_t)-2 -errno_eci = ExternalCompilationInfo( - includes=['errno.h'] + /* This function emulates what the windows CRT + does to validate file handles */ + int + _PyVerify_fd(int fd) + { + const int i1 = fd >> IOINFO_L2E; + const int i2 = fd & ((1 << IOINFO_L2E) - 1); + + static size_t sizeof_ioinfo = 0; + + /* Determine the actual size of the ioinfo structure, + * as used by the CRT loaded in memory + */ + if (sizeof_ioinfo == 0 && __pioinfo[0] != NULL) { + sizeof_ioinfo = _msize(__pioinfo[0]) / IOINFO_ARRAY_ELTS; + } + if (sizeof_ioinfo == 0) { + /* This should not happen... */ + goto fail; + } + + /* See that it isn't a special CLEAR fileno */ + if (fd != _NO_CONSOLE_FILENO) { + /* Microsoft CRT would check that 0<=fd<_nhandle but we can't do that. Instead + * we check pointer validity and other info + */ + if (0 <= i1 && i1 < IOINFO_ARRAYS && __pioinfo[i1] != NULL) { + /* finally, check that the file is open */ + my_ioinfo* info = (my_ioinfo*)(__pioinfo[i1] + i2 * sizeof_ioinfo); + if (info->osfile & FOPEN) { + return 1; + } + } + } + fail: + errno = EBADF; + return 0; + } + '''] +else: + post_include_bits = [] +eci = ExternalCompilationInfo( + includes=['errno.h','stdio.h'], + post_include_bits = post_include_bits, ) -_get_errno, _set_errno = CExternVariable(INT, 'errno', errno_eci, +_get_errno, _set_errno = CExternVariable(INT, 'errno', eci, CConstantErrno, sandboxsafe=True, _nowrapper=True, c_type='int') # the default wrapper for set_errno is not suitable for use in critical places @@ -35,12 +92,29 @@ def set_errno(errno): _set_errno(rffi.cast(INT, errno)) +if os.name == 'nt': + def validate_fd_emulator(fd): + try: + os.fstat(fd) + return 1 + except: + return 0 + validate_fd = rffi.llexternal( + "_PyVerify_fd", [rffi.INT], rffi.INT, + _callable=validate_fd_emulator, compilation_info=eci, + _nowrapper=True, elidable_function=True, sandboxsafe=True, + ) +else: + def validate_fd(fd): + return 1 + def closerange(fd_low, fd_high): # this behaves like os.closerange() from Python 2.6. for fd in xrange(fd_low, fd_high): try: - os.close(fd) + if validate_fd(fd): + os.close(fd) except OSError: pass diff --git a/pypy/rlib/test/test_rposix.py b/pypy/rlib/test/test_rposix.py --- a/pypy/rlib/test/test_rposix.py +++ b/pypy/rlib/test/test_rposix.py @@ -131,3 +131,13 @@ os.rmdir(self.ufilename) except Exception: pass + + def test_validate_fd(self): + assert rposix.validate_fd(0) == 1 + fid = open(str(udir.join('validate_test.txt')), 'w') + fd = fid.fileno() + assert rposix.validate_fd(fd) == 1 + fid.close() + assert rposix.validate_fd(fd) == 0 + + diff --git a/pypy/rpython/module/ll_os.py b/pypy/rpython/module/ll_os.py --- a/pypy/rpython/module/ll_os.py +++ b/pypy/rpython/module/ll_os.py @@ -916,6 +916,8 @@ def os_write_llimpl(fd, data): count = len(data) + if not rposix.validate_fd(fd): + raise OSError(rposix.get_errno(), 'Bad file descriptor') buf = rffi.get_nonmovingbuffer(data) try: written = rffi.cast(lltype.Signed, os_write( diff --git a/pypy/rpython/test/test_llinterp.py b/pypy/rpython/test/test_llinterp.py --- a/pypy/rpython/test/test_llinterp.py +++ b/pypy/rpython/test/test_llinterp.py @@ -139,7 +139,7 @@ got = interp.find_exception(info.value) except ValueError: got = None - assert got is exc, "wrong exception type" + assert got is exc, "wrong exception type, expected %r got %r"%(exc, got) #__________________________________________________________________ # tests diff --git a/pypy/rpython/test/test_rbuiltin.py b/pypy/rpython/test/test_rbuiltin.py --- a/pypy/rpython/test/test_rbuiltin.py +++ b/pypy/rpython/test/test_rbuiltin.py @@ -201,6 +201,14 @@ os.close(res) hello = open(tmpdir).read() assert hello == "hello world" + def throws(fname): + fd = os.open(fname, os.O_WRONLY|os.O_CREAT, 777) + os.close(fd) + os.write(fd, "hello world") + return fd + def g(): + return throws(tmpdir) + self.interpret_raises(OSError, g, []) def test_os_write_single_char(self): tmpdir = str(udir.udir.join("os_write_test_char")) diff --git a/pypy/translator/c/src/main.h b/pypy/translator/c/src/main.h --- a/pypy/translator/c/src/main.h +++ b/pypy/translator/c/src/main.h @@ -20,7 +20,7 @@ #endif #ifdef MS_WINDOWS -#include "src/winstuff.c" +/*#include "src/winstuff.c"*/ #endif #ifdef __GNUC__ From noreply at buildbot.pypy.org Tue Apr 3 21:43:32 2012 From: noreply at buildbot.pypy.org (mattip) Date: Tue, 3 Apr 2012 21:43:32 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: finish cleanup of fclose Message-ID: <20120403194333.0005D820C7@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54171:21b9e0657e6f Date: 2012-04-03 22:42 +0300 http://bitbucket.org/pypy/pypy/changeset/21b9e0657e6f/ Log: finish cleanup of fclose diff --git a/pypy/module/cpyext/api.py b/pypy/module/cpyext/api.py --- a/pypy/module/cpyext/api.py +++ b/pypy/module/cpyext/api.py @@ -26,6 +26,7 @@ from pypy.module.__builtin__.interp_classobj import W_ClassObject from pypy.module.__builtin__.interp_memoryview import W_MemoryView from pypy.rlib.entrypoint import entrypoint +from pypy.rlib.rposix import validate_fd from pypy.rlib.unroll import unrolling_iterable from pypy.rlib.objectmodel import specialize from pypy.rlib.exports import export_struct @@ -80,14 +81,18 @@ # FILE* interface FILEP = rffi.COpaquePtr('FILE') +if sys.platform == 'win32': + fileno = rffi.llexternal('_fileno', [FILEP], rffi.INT) +else: + fileno = rffi.llexternal('fileno', [FILEP], rffi.INT) + fopen = rffi.llexternal('fopen', [CONST_STRING, CONST_STRING], FILEP) + _fclose = rffi.llexternal('fclose', [FILEP], rffi.INT) def fclose(fp): - try: - fd = fileno(fp) - return os.close(fd) - except: + if not validate_fd(fileno(fp)): return -1 + return _fclose(fp) fwrite = rffi.llexternal('fwrite', [rffi.VOIDP, rffi.SIZE_T, rffi.SIZE_T, FILEP], @@ -96,10 +101,6 @@ [rffi.VOIDP, rffi.SIZE_T, rffi.SIZE_T, FILEP], rffi.SIZE_T) feof = rffi.llexternal('feof', [FILEP], rffi.INT) -if sys.platform == 'win32': - fileno = rffi.llexternal('_fileno', [FILEP], rffi.INT) -else: - fileno = rffi.llexternal('fileno', [FILEP], rffi.INT) constant_names = """ From noreply at buildbot.pypy.org Wed Apr 4 00:16:18 2012 From: noreply at buildbot.pypy.org (taavi_burns) Date: Wed, 4 Apr 2012 00:16:18 +0200 (CEST) Subject: [pypy-commit] pypy numpy-ufuncs3: Add trunc, and refactor tests for floor and ceil. Message-ID: <20120403221618.F1D53820C7@wyvern.cs.uni-duesseldorf.de> Author: Taavi Burns Branch: numpy-ufuncs3 Changeset: r54172:68288dd9b201 Date: 2012-04-03 18:15 -0400 http://bitbucket.org/pypy/pypy/changeset/68288dd9b201/ Log: Add trunc, and refactor tests for floor and ceil. diff --git a/pypy/module/micronumpy/__init__.py b/pypy/module/micronumpy/__init__.py --- a/pypy/module/micronumpy/__init__.py +++ b/pypy/module/micronumpy/__init__.py @@ -104,6 +104,7 @@ ("fmod", "fmod"), ("floor", "floor"), ("ceil", "ceil"), + ("trunc", "trunc"), ("greater", "greater"), ("greater_equal", "greater_equal"), ("less", "less"), diff --git a/pypy/module/micronumpy/interp_ufuncs.py b/pypy/module/micronumpy/interp_ufuncs.py --- a/pypy/module/micronumpy/interp_ufuncs.py +++ b/pypy/module/micronumpy/interp_ufuncs.py @@ -546,6 +546,7 @@ ("fmod", "fmod", 2, {"promote_to_float": True}), ("floor", "floor", 1, {"promote_to_float": True}), ("ceil", "ceil", 1, {"promote_to_float": True}), + ("trunc", "trunc", 1, {"promote_to_float": True}), ("exp", "exp", 1, {"promote_to_float": True}), ("exp2", "exp2", 1, {"promote_to_float": True}), ("expm1", "expm1", 1, {"promote_to_float": True}), diff --git a/pypy/module/micronumpy/test/test_ufuncs.py b/pypy/module/micronumpy/test/test_ufuncs.py --- a/pypy/module/micronumpy/test/test_ufuncs.py +++ b/pypy/module/micronumpy/test/test_ufuncs.py @@ -253,24 +253,17 @@ for i in range(3): assert c[i] == a[i] - b[i] - def test_floorceil(self): - from _numpypy import array, floor, ceil + def test_floorceiltrunc(self): + from _numpypy import array, floor, ceil, trunc import math - reference = [-2.0, -2.0, -1.0, 0.0, 1.0, 1.0, 0] - a = array([-1.4, -1.5, -1.0, 0.0, 1.0, 1.4, 0.5]) - b = floor(a) - for i in range(5): - assert b[i] == reference[i] - reference = [-1.0, -1.0, -1.0, 0.0, 1.0, 2.0, 1.0] - a = array([-1.4, -1.5, -1.0, 0.0, 1.0, 1.4, 0.5]) - b = ceil(a) - assert (reference == b).all() - inf = float("inf") - data = [1.5, 2.9999, -1.999, inf] - results = [math.floor(x) for x in data] - assert (floor(data) == results).all() - results = [math.ceil(x) for x in data] - assert (ceil(data) == results).all() + ninf, inf = float("-inf"), float("inf") + a = array([ninf, -1.4, -1.5, -1.0, 0.0, 1.0, 1.4, 0.5, inf]) + assert ([ninf, -2.0, -2.0, -1.0, 0.0, 1.0, 1.0, 0.0, inf] == floor(a)).all() + assert ([ninf, -1.0, -1.0, -1.0, 0.0, 1.0, 2.0, 1.0, inf] == ceil(a)).all() + assert ([ninf, -1.0, -1.0, -1.0, 0.0, 1.0, 1.0, 0.0, inf] == trunc(a)).all() + assert all([math.isnan(f(float("nan"))) for f in floor, ceil, trunc]) + assert all([math.copysign(1, f(float("nan"))) == 1 for f in floor, ceil, trunc]) + assert all([math.copysign(1, f(float("-nan"))) == -1 for f in floor, ceil, trunc]) def test_copysign(self): from _numpypy import array, copysign diff --git a/pypy/module/micronumpy/types.py b/pypy/module/micronumpy/types.py --- a/pypy/module/micronumpy/types.py +++ b/pypy/module/micronumpy/types.py @@ -668,6 +668,15 @@ return math.ceil(v) @simple_unary_op + def trunc(self, v): + try: + return int(v) + except OverflowError: + return rfloat.copysign(rfloat.INFINITY, v) + except ValueError: + return v + + @simple_unary_op def exp(self, v): try: return math.exp(v) From noreply at buildbot.pypy.org Wed Apr 4 10:44:35 2012 From: noreply at buildbot.pypy.org (fijal) Date: Wed, 4 Apr 2012 10:44:35 +0200 (CEST) Subject: [pypy-commit] pypy default: an obscure case - hopefully fix the windows annotation Message-ID: <20120404084435.59C61820C7@wyvern.cs.uni-duesseldorf.de> Author: Maciej Fijalkowski Branch: Changeset: r54173:add873f63889 Date: 2012-04-04 10:44 +0200 http://bitbucket.org/pypy/pypy/changeset/add873f63889/ Log: an obscure case - hopefully fix the windows annotation diff --git a/pypy/rpython/annlowlevel.py b/pypy/rpython/annlowlevel.py --- a/pypy/rpython/annlowlevel.py +++ b/pypy/rpython/annlowlevel.py @@ -543,11 +543,11 @@ else: assert False + hop.exception_cannot_occur() if isinstance(hop.args_r[1], rpbc.NoneFrozenPBCRepr): return hop.inputconst(PTR, null) v_arg = hop.inputarg(hop.args_r[1], arg=1) assert isinstance(v_arg.concretetype, T) - hop.exception_cannot_occur() return hop.genop(opname, [v_arg], resulttype = PTR) From noreply at buildbot.pypy.org Wed Apr 4 10:58:52 2012 From: noreply at buildbot.pypy.org (arigo) Date: Wed, 4 Apr 2012 10:58:52 +0200 (CEST) Subject: [pypy-commit] pypy stm-gc: Fixes Message-ID: <20120404085852.0B176820C7@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: stm-gc Changeset: r54174:cae9d28240ae Date: 2012-04-03 22:16 +0200 http://bitbucket.org/pypy/pypy/changeset/cae9d28240ae/ Log: Fixes diff --git a/pypy/rpython/memory/gc/stmgc.py b/pypy/rpython/memory/gc/stmgc.py --- a/pypy/rpython/memory/gc/stmgc.py +++ b/pypy/rpython/memory/gc/stmgc.py @@ -92,7 +92,7 @@ from pypy.rpython.memory.gc import stmshared self.stm_operations = stm_operations self.nursery_size = nursery_size - self.sharedarea = stmshared.StmGCSharedArea(self, + self.sharedarea = stmshared.StmGCSharedArea(self, ArenaCollectionClass, page_size, arena_size, small_request_threshold) # @@ -118,7 +118,7 @@ # from pypy.rpython.memory.gc.stmtls import StmGCTLS self.main_thread_tls = StmGCTLS(self, in_main_thread=True) - self.main_thread_tls.enter() + self.main_thread_tls.start_transaction() def get_tls(self): from pypy.rpython.memory.gc.stmtls import StmGCTLS @@ -234,7 +234,7 @@ debug_print("XXX collect() ignored") def start_transaction(self): - self.collector.start_transaction() + self.get_tls().start_transaction() def commit_transaction(self): self.collector.commit_transaction() diff --git a/pypy/rpython/memory/gc/stmshared.py b/pypy/rpython/memory/gc/stmshared.py --- a/pypy/rpython/memory/gc/stmshared.py +++ b/pypy/rpython/memory/gc/stmshared.py @@ -2,8 +2,9 @@ class StmGCSharedArea(object): - def __init__(self, gc, page_size, arena_size, small_request_threshold): - ... + def __init__(self, gc, ArenaCollectionClass, + page_size, arena_size, small_request_threshold): + self.gc = gc # The ArenaCollection() handles the nonmovable objects allocation. # It contains all small GCFLAG_GLOBAL objects. The non-small ones # are directly malloc'ed. @@ -12,3 +13,6 @@ ArenaCollectionClass = minimarkpage.ArenaCollection self.ac = ArenaCollectionClass(arena_size, page_size, small_request_threshold) + + def setup(self): + pass diff --git a/pypy/rpython/memory/gc/stmtls.py b/pypy/rpython/memory/gc/stmtls.py --- a/pypy/rpython/memory/gc/stmtls.py +++ b/pypy/rpython/memory/gc/stmtls.py @@ -1,7 +1,7 @@ -from pypy.rpython.lltypesystem import lltype, llmemory +from pypy.rpython.lltypesystem import lltype, llmemory, llarena, rffi from pypy.rpython.annlowlevel import cast_instance_to_base_ptr from pypy.rpython.annlowlevel import cast_base_ptr_to_instance, base_ptr_lltype -from pypy.rlib import objectmodel +from pypy.rlib.objectmodel import we_are_translated, free_non_gc_object from pypy.rlib.rarithmetic import r_uint from pypy.rlib.debug import ll_assert @@ -14,7 +14,10 @@ _alloc_flavor_ = 'raw' + nontranslated_dict = {} + def __init__(self, gc, in_main_thread): + self.gc = gc self.in_main_thread = in_main_thread self.stm_operations = self.gc.stm_operations self.null_address_dict = self.gc.null_address_dict @@ -25,6 +28,7 @@ # mallocs are forbidden self.nursery_free = NULL self.nursery_top = NULL + self.nursery_pending_clear = 0 # --- the start and size of the nursery belonging to this thread. # never changes. self.nursery_size = self.gc.nursery_size @@ -32,7 +36,7 @@ # # --- the local raw-malloced objects, young and old self.rawmalloced_young_objects = self.null_address_dict() - self.rawmalloced_old_objects = self.AddressStack() + self.rawmalloced_old_objects = None self.rawmalloced_total_size = r_uint(0) # --- the local objects with weakrefs, young and old self.young_objects_with_weakrefs = self.AddressStack() @@ -47,7 +51,7 @@ def teardown_thread(self): self._unregister_with_C_code() self._free_nursery(self.nursery_start) - objectmodel.free_non_gc_object(self) + free_non_gc_object(self) def _alloc_nursery(self, nursery_size): nursery = llarena.arena_malloc(nursery_size, 1) @@ -59,9 +63,14 @@ llarena.arena_free(nursery) def _register_with_C_code(self): - tls = cast_instance_to_base_ptr(self) - self.stm_operations.set_tls(llmemory.cast_ptr_to_adr(tls), - int(self.in_main_thread)) + if we_are_translated(): + tls = cast_instance_to_base_ptr(self) + tlsaddr = llmemory.cast_ptr_to_adr(tls) + else: + n = 10000 + len(self.nontranslated_dict) + tlsaddr = rffi.cast(llmemory.Address, n) + self.nontranslated_dict[n] = self + self.stm_operations.set_tls(tlsaddr, int(self.in_main_thread)) def _unregister_with_C_code(self): ll_assert(self.gc.get_tls() is self, @@ -70,38 +79,56 @@ @staticmethod def cast_address_to_tls_object(self, tlsaddr): - tls = llmemory.cast_adr_to_ptr(tlsaddr, base_ptr_lltype()) - return cast_base_ptr_to_instance(tls) + if we_are_translated(): + tls = llmemory.cast_adr_to_ptr(tlsaddr, base_ptr_lltype()) + return cast_base_ptr_to_instance(tls) + else: + n = rffi.cast(lltype.Signed, tlsaddr) + return self.nontranslated_dict[n] # ------------------------------------------------------------ - def enter(self): + def start_transaction(self): """Enter a thread: performs any pending cleanups, and set up a fresh state for allocating. Called at the start of - transactions, and at the start of the main thread.""" - # Note that the calls to enter() and leave() are not balanced: - # if a transaction is aborted, leave() might never be called. + each transaction, and at the start of the main thread.""" + # Note that the calls to enter() and + # end_of_transaction_collection() are not balanced: if a + # transaction is aborted, the latter might never be called. # Be ready here to clean up any state. - xxx - - def leave(self): - """Leave a thread: no more allocations are allowed. - Called when a transaction finishes.""" - xxx + if self.nursery_free: + clear_size = self.nursery_free - self.nursery_start + else: + clear_size = self.nursery_pending_clear + if clear_size > 0: + llarena.arena_reset(self.nursery_start, clear_size, 2) + self.nursery_pending_clear = 0 + if self.rawmalloced_young_objects: + xxx + if self.rawmalloced_old_objects: + xxx + self.nursery_free = self.nursery_start + self.nursery_top = self.nursery_start + self.nursery_size # ------------------------------------------------------------ - def end_of_transaction_collection(self): - """Do an end-of-transaction collection. Finds all surviving - young objects and make them old. This guarantees that the - nursery is empty afterwards. (Even if there are finalizers, XXX) - Assumes that there are no roots from the stack. - """ - xxx - def local_collection(self): """Do a local collection. Finds all surviving young objects and make them old. Also looks for roots from the stack. + The flag GCFLAG_WAS_COPIED is kept and the C tree is updated + if the local young object moves. + """ + xxx + + def end_of_transaction_collection(self): + """Do an end-of-transaction collection. Finds all surviving + non-GCFLAG_WAS_COPIED young objects and make them old. Assumes + that there are no roots from the stack. This guarantees that the + nursery will end up empty, apart from GCFLAG_WAS_COPIED objects. + To finish the commit, the C code will need to copy them over the + global objects (or abort in case of conflict, which is still ok). + + No more mallocs are allowed after this is called. """ xxx From pullrequests-noreply at bitbucket.org Wed Apr 4 17:48:00 2012 From: pullrequests-noreply at bitbucket.org (unbit) Date: Wed, 04 Apr 2012 15:48:00 -0000 Subject: [pypy-commit] [pypy/pypy] added implementation of Py_Initialize, Py_Finalize, Py_SetPythonHome, Py_SetProgramName as cpyext (pull request #67) In-Reply-To: References: Message-ID: <20120404154800.26572.80674@bitbucket05.managed.contegix.com> Pull request #67 has been updated by unbit to include new changes. https://bitbucket.org/pypy/pypy/pull-request/67/added-implementation-of-py_initialize Title: added implementation of Py_Initialize,Py_Finalize,Py_SetPythonHome,Py_SetProgramName as cpyext Creator: unbit This commit add support for a series o cpython-compatible api functions required for embedding libpypy in c apps. They are all written in rpython (as cpyext) except for Py_Initialize requiring a call to RPython_StartupCode. Tested with uWSGI tip. Updated list of changes: 7eb44aa590a2 by rob... at mrspurr: "added Py_Initialize,Py_Finalize,Py_SetPythonHome,Py_SetProgramName as cpyext" 4c1e64afbe9a by rob... at goyle: "added implementation of Py_Initialize" -- This is an issue notification from bitbucket.org. You are receiving this either because you are the participating in a pull request, or you are following it. From pullrequests-noreply at bitbucket.org Wed Apr 4 18:38:15 2012 From: pullrequests-noreply at bitbucket.org (Amaury Forgeot d'Arc) Date: Wed, 04 Apr 2012 16:38:15 -0000 Subject: [pypy-commit] [pypy/pypy] added implementation of Py_Initialize, Py_Finalize, Py_SetPythonHome, Py_SetProgramName as cpyext (pull request #67) In-Reply-To: References: Message-ID: <20120404163815.5671.16282@bitbucket03.managed.contegix.com> New comment on pull request: https://bitbucket.org/pypy/pypy/pull-request/67/added-implementation-of-py_initialize#comment-4791 Amaury Forgeot d'Arc (amauryfa) said: Very nice! Especially for the RPython implementation of PyPy_Initialize(). Do the cpyext unit tests still pass? I suspect that RPython_StartupCode is not defined in the C stub library we compile for testing cpyext with an interpreted pypy. -- This is a pull request comment notification from bitbucket.org. You are receiving this either because you are participating in a pull request, or you are following it. From pullrequests-noreply at bitbucket.org Wed Apr 4 19:41:20 2012 From: pullrequests-noreply at bitbucket.org (unbit) Date: Wed, 04 Apr 2012 17:41:20 -0000 Subject: [pypy-commit] [pypy/pypy] added implementation of Py_Initialize, Py_Finalize, Py_SetPythonHome, Py_SetProgramName as cpyext (pull request #67) In-Reply-To: References: Message-ID: <20120404174120.24185.54759@bitbucket05.managed.contegix.com> New comment on pull request: https://bitbucket.org/pypy/pypy/pull-request/67/added-implementation-of-py_initialize#comment-4794 unbit said: yes, it should be defined. I am about to commit PyFile_FromFile support, i will add the fix for the unit test too -- This is a pull request comment notification from bitbucket.org. You are receiving this either because you are participating in a pull request, or you are following it. From pullrequests-noreply at bitbucket.org Wed Apr 4 19:56:23 2012 From: pullrequests-noreply at bitbucket.org (Amaury Forgeot d'Arc) Date: Wed, 04 Apr 2012 17:56:23 -0000 Subject: [pypy-commit] [pypy/pypy] added implementation of Py_Initialize, Py_Finalize, Py_SetPythonHome, Py_SetProgramName as cpyext (pull request #67) In-Reply-To: References: Message-ID: <20120404175623.19485.35891@bitbucket02.managed.contegix.com> New comment on pull request: https://bitbucket.org/pypy/pypy/pull-request/67/added-implementation-of-py_initialize#comment-4795 Amaury Forgeot d'Arc (amauryfa) said: {{{ #!python sys.pypy_initial_path(srcdir) }}} It seems you are trying to reimplement setup_initial_paths() from pypy/translator/goal/app_main.py. Is there a way to reuse this function? -- This is a pull request comment notification from bitbucket.org. You are receiving this either because you are participating in a pull request, or you are following it. From noreply at buildbot.pypy.org Wed Apr 4 20:30:51 2012 From: noreply at buildbot.pypy.org (fijal) Date: Wed, 4 Apr 2012 20:30:51 +0200 (CEST) Subject: [pypy-commit] pypy default: disable some features to make group RPython, a fair bit controversial, maybe I should make rgroup iface or so Message-ID: <20120404183051.8700C820C7@wyvern.cs.uni-duesseldorf.de> Author: Maciej Fijalkowski Branch: Changeset: r54175:52f2110eff80 Date: 2012-04-04 20:30 +0200 http://bitbucket.org/pypy/pypy/changeset/52f2110eff80/ Log: disable some features to make group RPython, a fair bit controversial, maybe I should make rgroup iface or so diff --git a/pypy/rlib/rsre/rsre_re.py b/pypy/rlib/rsre/rsre_re.py --- a/pypy/rlib/rsre/rsre_re.py +++ b/pypy/rlib/rsre/rsre_re.py @@ -172,8 +172,9 @@ self._ctx = ctx def span(self, groupnum=0): - if not isinstance(groupnum, (int, long)): - groupnum = self.re.groupindex[groupnum] +# if not isinstance(groupnum, (int, long)): +# groupnum = self.re.groupindex[groupnum] + return self._ctx.span(groupnum) def start(self, groupnum=0): @@ -182,19 +183,25 @@ def end(self, groupnum=0): return self.span(groupnum)[1] - def group(self, *groups): - groups = groups or (0,) - result = [] - for group in groups: - frm, to = self.span(group) - if 0 <= frm <= to: - result.append(self._ctx._string[frm:to]) - else: - result.append(None) - if len(result) > 1: - return tuple(result) + def group(self, group=0): + frm, to = self.span(group) + if 0 <= frm <= to: + return self._ctx._string[frm:to] else: - return result[0] + return None + +# def group(self, *groups): +# groups = groups or (0,) +# result = [] +# for group in groups: +# frm, to = self.span(group) +# if 0 <= frm <= to: +# result.append(self._ctx._string[frm:to]) +# else: +# result.append(None) +# if len(result) > 1: +# return tuple(result) + def groups(self, default=None): fmarks = self._ctx.flatten_marks() diff --git a/pypy/rlib/rsre/test/test_re.py b/pypy/rlib/rsre/test/test_re.py --- a/pypy/rlib/rsre/test/test_re.py +++ b/pypy/rlib/rsre/test/test_re.py @@ -204,7 +204,7 @@ assert re.match('(a)', 'a').groups() == ('a',) assert re.match(r'(a)', 'a').group(0) == 'a' assert re.match(r'(a)', 'a').group(1) == 'a' - assert re.match(r'(a)', 'a').group(1, 1) == ('a', 'a') + #assert re.match(r'(a)', 'a').group(1, 1) == ('a', 'a') pat = re.compile('((a)|(b))(c)?') assert pat.match('a').groups() == ('a', 'a', None, None) @@ -218,13 +218,13 @@ assert m.group(0) == 'a' assert m.group(0) == 'a' assert m.group(1) == 'a' - assert m.group(1, 1) == ('a', 'a') + #assert m.group(1, 1) == ('a', 'a') pat = re.compile('(?:(?Pa)|(?Pb))(?Pc)?') - assert pat.match('a').group(1, 2, 3) == ('a', None, None) - assert pat.match('b').group('a1', 'b2', 'c3') == ( - (None, 'b', None)) - assert pat.match('ac').group(1, 'b2', 3) == ('a', None, 'c') + #assert pat.match('a').group(1, 2, 3) == ('a', None, None) + #assert pat.match('b').group('a1', 'b2', 'c3') == ( + # (None, 'b', None)) + #assert pat.match('ac').group(1, 'b2', 3) == ('a', None, 'c') def test_bug_923(self): # Issue923: grouping inside optional lookahead problem diff --git a/pypy/rlib/rsre/test/test_zinterp.py b/pypy/rlib/rsre/test/test_zinterp.py --- a/pypy/rlib/rsre/test/test_zinterp.py +++ b/pypy/rlib/rsre/test/test_zinterp.py @@ -1,7 +1,8 @@ # minimal test: just checks that (parts of) rsre can be translated -from pypy.rpython.test.test_llinterp import gengraph +from pypy.rpython.test.test_llinterp import gengraph, interpret from pypy.rlib.rsre import rsre_core +from pypy.rlib.rsre.rsre_re import compile def main(n): assert n >= 0 @@ -19,3 +20,18 @@ def test_gengraph(): t, typer, graph = gengraph(main, [int]) + +m = compile("(a|b)aaaaa") + +def test_match(): + def f(i): + if i: + s = "aaaaaa" + else: + s = "caaaaa" + g = m.match(s) + if g is None: + return 3 + return int("aaaaaa" == g.group(0)) + assert interpret(f, [3]) == 1 + assert interpret(f, [0]) == 3 From noreply at buildbot.pypy.org Wed Apr 4 20:34:17 2012 From: noreply at buildbot.pypy.org (taavi_burns) Date: Wed, 4 Apr 2012 20:34:17 +0200 (CEST) Subject: [pypy-commit] pypy numpy-ufuncs3: Update trunc to use the same implementation as numpy. Works around some "won't work in pypy" issues. Message-ID: <20120404183417.ED7AC820C7@wyvern.cs.uni-duesseldorf.de> Author: Taavi Burns Branch: numpy-ufuncs3 Changeset: r54176:382d1c98cd25 Date: 2012-04-04 14:08 -0400 http://bitbucket.org/pypy/pypy/changeset/382d1c98cd25/ Log: Update trunc to use the same implementation as numpy. Works around some "won't work in pypy" issues. diff --git a/pypy/module/micronumpy/types.py b/pypy/module/micronumpy/types.py --- a/pypy/module/micronumpy/types.py +++ b/pypy/module/micronumpy/types.py @@ -669,12 +669,10 @@ @simple_unary_op def trunc(self, v): - try: - return int(v) - except OverflowError: - return rfloat.copysign(rfloat.INFINITY, v) - except ValueError: - return v + if v < 0: + return math.ceil(v) + else: + return math.floor(v) @simple_unary_op def exp(self, v): From noreply at buildbot.pypy.org Wed Apr 4 20:34:19 2012 From: noreply at buildbot.pypy.org (taavi_burns) Date: Wed, 4 Apr 2012 20:34:19 +0200 (CEST) Subject: [pypy-commit] pypy default: Merge in more ufuncs Message-ID: <20120404183419.C5B31820C7@wyvern.cs.uni-duesseldorf.de> Author: Taavi Burns Branch: Changeset: r54177:7244bbc777dc Date: 2012-04-04 14:33 -0400 http://bitbucket.org/pypy/pypy/changeset/7244bbc777dc/ Log: Merge in more ufuncs diff --git a/pypy/module/micronumpy/__init__.py b/pypy/module/micronumpy/__init__.py --- a/pypy/module/micronumpy/__init__.py +++ b/pypy/module/micronumpy/__init__.py @@ -105,6 +105,7 @@ ("fmod", "fmod"), ("floor", "floor"), ("ceil", "ceil"), + ("trunc", "trunc"), ("greater", "greater"), ("greater_equal", "greater_equal"), ("less", "less"), @@ -132,6 +133,8 @@ ('bitwise_or', 'bitwise_or'), ('bitwise_xor', 'bitwise_xor'), ('bitwise_not', 'invert'), + ('left_shift', 'left_shift'), + ('right_shift', 'right_shift'), ('invert', 'invert'), ('isnan', 'isnan'), ('isinf', 'isinf'), diff --git a/pypy/module/micronumpy/interp_ufuncs.py b/pypy/module/micronumpy/interp_ufuncs.py --- a/pypy/module/micronumpy/interp_ufuncs.py +++ b/pypy/module/micronumpy/interp_ufuncs.py @@ -546,6 +546,7 @@ ("fmod", "fmod", 2, {"promote_to_float": True}), ("floor", "floor", 1, {"promote_to_float": True}), ("ceil", "ceil", 1, {"promote_to_float": True}), + ("trunc", "trunc", 1, {"promote_to_float": True}), ("exp", "exp", 1, {"promote_to_float": True}), ("exp2", "exp2", 1, {"promote_to_float": True}), ("expm1", "expm1", 1, {"promote_to_float": True}), diff --git a/pypy/module/micronumpy/test/test_ufuncs.py b/pypy/module/micronumpy/test/test_ufuncs.py --- a/pypy/module/micronumpy/test/test_ufuncs.py +++ b/pypy/module/micronumpy/test/test_ufuncs.py @@ -253,24 +253,17 @@ for i in range(3): assert c[i] == a[i] - b[i] - def test_floorceil(self): - from _numpypy import array, floor, ceil + def test_floorceiltrunc(self): + from _numpypy import array, floor, ceil, trunc import math - reference = [-2.0, -2.0, -1.0, 0.0, 1.0, 1.0, 0] - a = array([-1.4, -1.5, -1.0, 0.0, 1.0, 1.4, 0.5]) - b = floor(a) - for i in range(5): - assert b[i] == reference[i] - reference = [-1.0, -1.0, -1.0, 0.0, 1.0, 2.0, 1.0] - a = array([-1.4, -1.5, -1.0, 0.0, 1.0, 1.4, 0.5]) - b = ceil(a) - assert (reference == b).all() - inf = float("inf") - data = [1.5, 2.9999, -1.999, inf] - results = [math.floor(x) for x in data] - assert (floor(data) == results).all() - results = [math.ceil(x) for x in data] - assert (ceil(data) == results).all() + ninf, inf = float("-inf"), float("inf") + a = array([ninf, -1.4, -1.5, -1.0, 0.0, 1.0, 1.4, 0.5, inf]) + assert ([ninf, -2.0, -2.0, -1.0, 0.0, 1.0, 1.0, 0.0, inf] == floor(a)).all() + assert ([ninf, -1.0, -1.0, -1.0, 0.0, 1.0, 2.0, 1.0, inf] == ceil(a)).all() + assert ([ninf, -1.0, -1.0, -1.0, 0.0, 1.0, 1.0, 0.0, inf] == trunc(a)).all() + assert all([math.isnan(f(float("nan"))) for f in floor, ceil, trunc]) + assert all([math.copysign(1, f(float("nan"))) == 1 for f in floor, ceil, trunc]) + assert all([math.copysign(1, f(float("-nan"))) == -1 for f in floor, ceil, trunc]) def test_copysign(self): from _numpypy import array, copysign @@ -597,6 +590,13 @@ assert (bitwise_not(a) == ~a).all() assert (invert(a) == ~a).all() + def test_shift(self): + from _numpypy import left_shift, right_shift + import sys + + assert (left_shift([5, 1], [2, 31]) == [20, 2**31]).all() + assert (right_shift(10, range(5)) == [10, 5, 2, 1, 0]).all() + def test_comparisons(self): import operator from _numpypy import equal, not_equal, less, less_equal, greater, greater_equal diff --git a/pypy/module/micronumpy/types.py b/pypy/module/micronumpy/types.py --- a/pypy/module/micronumpy/types.py +++ b/pypy/module/micronumpy/types.py @@ -668,6 +668,13 @@ return math.ceil(v) @simple_unary_op + def trunc(self, v): + if v < 0: + return math.ceil(v) + else: + return math.floor(v) + + @simple_unary_op def exp(self, v): try: return math.exp(v) From pullrequests-noreply at bitbucket.org Wed Apr 4 20:45:13 2012 From: pullrequests-noreply at bitbucket.org (unbit) Date: Wed, 04 Apr 2012 18:45:13 -0000 Subject: [pypy-commit] [pypy/pypy] added implementation of Py_Initialize, Py_Finalize, Py_SetPythonHome, Py_SetProgramName as cpyext (pull request #67) In-Reply-To: References: Message-ID: <20120404184513.11913.53324@bitbucket15.managed.contegix.com> New comment on pull request: https://bitbucket.org/pypy/pypy/pull-request/67/added-implementation-of-py_initialize#comment-4796 unbit said: would be great to resue that code, but i think both will require refactoring to support PYTHONHOME env var and custom sys.prefix (required by Py_SetPythonHome) -- This is a pull request comment notification from bitbucket.org. You are receiving this either because you are participating in a pull request, or you are following it. From pullrequests-noreply at bitbucket.org Wed Apr 4 20:55:15 2012 From: pullrequests-noreply at bitbucket.org (unbit) Date: Wed, 04 Apr 2012 18:55:15 -0000 Subject: [pypy-commit] [pypy/pypy] added implementation of Py_Initialize, Py_Finalize, Py_SetPythonHome, Py_SetProgramName as cpyext (pull request #67) In-Reply-To: References: Message-ID: <20120404185515.2342.73686@bitbucket15.managed.contegix.com> Updated comment on pull request: https://bitbucket.org/pypy/pypy/pull-request/67/added-implementation-of-py_initialize#comment-4796 unbit said: would be great to reuse that code, but i think both will require refactoring to support PYTHONHOME env var and custom sys.prefix (required by Py_SetPythonHome) -- This is a pull request comment notification from bitbucket.org. You are receiving this either because you are participating in a pull request, or you are following it. From pullrequests-noreply at bitbucket.org Wed Apr 4 20:57:21 2012 From: pullrequests-noreply at bitbucket.org (Amaury Forgeot d'Arc) Date: Wed, 04 Apr 2012 18:57:21 -0000 Subject: [pypy-commit] [pypy/pypy] added implementation of Py_Initialize, Py_Finalize, Py_SetPythonHome, Py_SetProgramName as cpyext (pull request #67) In-Reply-To: References: Message-ID: <20120404185721.23121.11178@bitbucket12.managed.contegix.com> New comment on pull request: https://bitbucket.org/pypy/pypy/pull-request/67/added-implementation-of-py_initialize#comment-4799 Amaury Forgeot d'Arc (amauryfa) said: I'm OK to leave it simple to begin with; but pypy.__file__ will hardcode the path of the pypy source tree, at translation time. Is sys.path correctly set if one donwloads the nightly builds and unpack it anywhere? Should the code use sys.executable somehow? -- This is a pull request comment notification from bitbucket.org. You are receiving this either because you are participating in a pull request, or you are following it. From pullrequests-noreply at bitbucket.org Wed Apr 4 21:05:23 2012 From: pullrequests-noreply at bitbucket.org (unbit) Date: Wed, 04 Apr 2012 19:05:23 -0000 Subject: [pypy-commit] [pypy/pypy] added implementation of Py_Initialize, Py_Finalize, Py_SetPythonHome, Py_SetProgramName as cpyext (pull request #67) In-Reply-To: References: Message-ID: <20120404190523.9309.34742@bitbucket02.managed.contegix.com> New comment on pull request: https://bitbucket.org/pypy/pypy/pull-request/67/added-implementation-of-py_initialize#comment-4800 unbit said: honestly it is not clear to me how to make 'srcdir' independent from pypy.__file__ Apps embeding python normally set sys.executable to their binary path, so following that value could lead to problems. Maybe we could take the absolute path of libpypy-c with dladdr() trick, but this is glibc specific. -- This is a pull request comment notification from bitbucket.org. You are receiving this either because you are participating in a pull request, or you are following it. From pullrequests-noreply at bitbucket.org Wed Apr 4 23:37:50 2012 From: pullrequests-noreply at bitbucket.org (Amaury Forgeot d'Arc) Date: Wed, 04 Apr 2012 21:37:50 -0000 Subject: [pypy-commit] [pypy/pypy] added implementation of Py_Initialize, Py_Finalize, Py_SetPythonHome, Py_SetProgramName as cpyext (pull request #67) In-Reply-To: References: Message-ID: <20120404213750.18322.73739@bitbucket12.managed.contegix.com> New comment on pull request: https://bitbucket.org/pypy/pypy/pull-request/67/added-implementation-of-py_initialize#comment-4804 Amaury Forgeot d'Arc (amauryfa) said: You are right of course. I did embed a Python interpreter several times, but I forgot that PythonHome and Path should almost always be overridden. Since pypy does not use sys.prefix, defaulting to the original source path is the right thing to do. -- This is a pull request comment notification from bitbucket.org. You are receiving this either because you are participating in a pull request, or you are following it. From noreply at buildbot.pypy.org Thu Apr 5 00:04:06 2012 From: noreply at buildbot.pypy.org (amauryfa) Date: Thu, 5 Apr 2012 00:04:06 +0200 (CEST) Subject: [pypy-commit] pypy embedded: A branch to define Py_Initialize() and allow embedding pypy Message-ID: <20120404220406.1CAA3820C7@wyvern.cs.uni-duesseldorf.de> Author: Amaury Forgeot d'Arc Branch: embedded Changeset: r54180:8e2e02b0c754 Date: 2012-04-04 23:52 +0200 http://bitbucket.org/pypy/pypy/changeset/8e2e02b0c754/ Log: A branch to define Py_Initialize() and allow embedding pypy From noreply at buildbot.pypy.org Thu Apr 5 00:42:41 2012 From: noreply at buildbot.pypy.org (amauryfa) Date: Thu, 5 Apr 2012 00:42:41 +0200 (CEST) Subject: [pypy-commit] pypy embedded-pypy: Redo the branch for an embedded pypy interpreter Message-ID: <20120404224241.0B961820C7@wyvern.cs.uni-duesseldorf.de> Author: Amaury Forgeot d'Arc Branch: embedded-pypy Changeset: r54183:01c28572ad1b Date: 2012-04-05 00:34 +0200 http://bitbucket.org/pypy/pypy/changeset/01c28572ad1b/ Log: Redo the branch for an embedded pypy interpreter From noreply at buildbot.pypy.org Thu Apr 5 00:42:45 2012 From: noreply at buildbot.pypy.org (amauryfa) Date: Thu, 5 Apr 2012 00:42:45 +0200 (CEST) Subject: [pypy-commit] pypy embedded-pypy: Fix cpyext tests, I hope I did not break Py_Initialize. Message-ID: <20120404224245.2D1AC820C7@wyvern.cs.uni-duesseldorf.de> Author: Amaury Forgeot d'Arc Branch: embedded-pypy Changeset: r54186:e4568fc96f21 Date: 2012-04-05 00:33 +0200 http://bitbucket.org/pypy/pypy/changeset/e4568fc96f21/ Log: Fix cpyext tests, I hope I did not break Py_Initialize. diff --git a/pypy/module/cpyext/pythonrun.py b/pypy/module/cpyext/pythonrun.py --- a/pypy/module/cpyext/pythonrun.py +++ b/pypy/module/cpyext/pythonrun.py @@ -13,8 +13,8 @@ def Py_GetProgramName(space): """ Return the program name set with Py_SetProgramName(), or the default. - The returned string points into static storage; the caller should not modify its - value.""" + The returned string points into static storage; the caller should + not modify its value.""" return space.fromcache(State).get_programname() @cpython_api([rffi.CCHARP], lltype.Void, error=CANNOT_FAIL) @@ -27,7 +27,8 @@ @cpython_api([rffi.CCHARP], lltype.Void, error=CANNOT_FAIL) def Py_SetPythonHome(space, home): """ - Set the default “home” directory, that is, the location of the standard Python libraries. + Set the default "home" directory, that is, the location of the + standard Python libraries. """ space.fromcache(State).set_pythonhome(home) @@ -79,7 +80,7 @@ ''').interphook('pypy_init') @cpython_api([], lltype.Void, error=CANNOT_FAIL) -def PyPy_Initialize(space): +def _PyPy_Initialize(space): srcdir = pypy.__file__ # set pythonhome/virtualenv pyhome = None diff --git a/pypy/module/cpyext/src/pythonrun.c b/pypy/module/cpyext/src/pythonrun.c --- a/pypy/module/cpyext/src/pythonrun.c +++ b/pypy/module/cpyext/src/pythonrun.c @@ -36,15 +36,15 @@ } void Py_Initialize(void) { - +#ifdef PYPY_STANDALONE /* 1 or 0, but defined */ char *errmsg = RPython_StartupCode(); if (errmsg) { fprintf(stderr, "unable to initialize PyPy: %s\n", errmsg); abort(); return; } +#endif - PyPy_Initialize(); - + _PyPy_Initialize(); } From noreply at buildbot.pypy.org Thu Apr 5 00:45:10 2012 From: noreply at buildbot.pypy.org (amauryfa) Date: Thu, 5 Apr 2012 00:45:10 +0200 (CEST) Subject: [pypy-commit] pypy embedded: Close branch that was not correctly created :( Message-ID: <20120404224510.3F5ED820C7@wyvern.cs.uni-duesseldorf.de> Author: Amaury Forgeot d'Arc Branch: embedded Changeset: r54187:97498245b867 Date: 2012-04-05 00:44 +0200 http://bitbucket.org/pypy/pypy/changeset/97498245b867/ Log: Close branch that was not correctly created :( From pullrequests-noreply at bitbucket.org Thu Apr 5 00:47:46 2012 From: pullrequests-noreply at bitbucket.org (Amaury Forgeot d'Arc) Date: Wed, 04 Apr 2012 22:47:46 -0000 Subject: [pypy-commit] [pypy/pypy] added implementation of Py_Initialize, Py_Finalize, Py_SetPythonHome, Py_SetProgramName as cpyext (pull request #67) In-Reply-To: References: Message-ID: <20120404224746.29770.67184@bitbucket13.managed.contegix.com> Pull request #67 has been rejected by Amaury Forgeot d'Arc. unbit/pypy had changes to be pulled into pypy/pypy. https://bitbucket.org/pypy/pypy/pull-request/67/added-implementation-of-py_initialize I actually pulled the changes in a branch: embedded-pypy to continue the developments from here. This is a very good start! Rejected changes: 7eb44aa590a2 by rob... at mrspurr: "added Py_Initialize,Py_Finalize,Py_SetPythonHome,Py_SetProgramName as cpyext" 4c1e64afbe9a by rob... at goyle: "added implementation of Py_Initialize" The pull request has been closed. -- This is an issue notification from bitbucket.org. You are receiving this either because you are the participating in a pull request, or you are following it. From noreply at buildbot.pypy.org Thu Apr 5 01:18:42 2012 From: noreply at buildbot.pypy.org (mattip) Date: Thu, 5 Apr 2012 01:18:42 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: add a test in the proper place, fix failing test Message-ID: <20120404231842.05543820C7@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54188:2eeb44e7e81f Date: 2012-04-05 00:47 +0300 http://bitbucket.org/pypy/pypy/changeset/2eeb44e7e81f/ Log: add a test in the proper place, fix failing test diff --git a/pypy/rpython/module/test/test_ll_os.py b/pypy/rpython/module/test/test_ll_os.py --- a/pypy/rpython/module/test/test_ll_os.py +++ b/pypy/rpython/module/test/test_ll_os.py @@ -84,8 +84,10 @@ if (len == 0) and "WINGDB_PYTHON" in os.environ: # the ctypes call seems not to work in the Wing debugger return - assert str(buf.value).lower() == pwd - # ctypes returns the drive letter in uppercase, os.getcwd does not + assert str(buf.value).lower() == pwd.lower() + # ctypes returns the drive letter in uppercase, + # os.getcwd does not, + # but there may be uppercase in os.getcwd path pwd = os.getcwd() try: @@ -187,7 +189,18 @@ OSError, ll_execve, "/etc/passwd", [], {}) assert info.value.errno == errno.EACCES - +def test_os_write(): + #Same as test in rpython/test/test_rbuiltin + fname = str(udir.join('os_write_test.txt')) + fd = os.open(fname, os.O_WRONLY|os.O_CREAT, 0777) + assert fd >= 0 + os.write(fd, 'Hello world') + os.close(fd) + hello = open(fname).read() + assert hello == "Hello world" + fd = os.open(fname, os.O_WRONLY|os.O_CREAT, 0777) + os.close(fd) + raises(OSError, os.write, fd, 'Hello world') class ExpectTestOs: def setup_class(cls): From noreply at buildbot.pypy.org Thu Apr 5 01:18:43 2012 From: noreply at buildbot.pypy.org (mattip) Date: Thu, 5 Apr 2012 01:18:43 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: make os.utime() subsecond-accurate Message-ID: <20120404231843.56CE8822B2@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54189:571fd39e0a35 Date: 2012-04-05 02:18 +0300 http://bitbucket.org/pypy/pypy/changeset/571fd39e0a35/ Log: make os.utime() subsecond-accurate diff --git a/pypy/rpython/module/ll_os_stat.py b/pypy/rpython/module/ll_os_stat.py --- a/pypy/rpython/module/ll_os_stat.py +++ b/pypy/rpython/module/ll_os_stat.py @@ -455,6 +455,6 @@ return intmask(time), intmask(nsec) def time_t_to_FILE_TIME(time, filetime): - ft = (rffi.r_longlong(time) + secs_between_epochs) * 10000000 + ft = rffi.r_longlong(time * 10000000) + secs_between_epochs * 10000000 filetime.c_dwHighDateTime = rffi.r_uint(ft >> 32) filetime.c_dwLowDateTime = rffi.r_uint(ft) # masking off high bits From noreply at buildbot.pypy.org Thu Apr 5 03:37:16 2012 From: noreply at buildbot.pypy.org (pjenvey) Date: Thu, 5 Apr 2012 03:37:16 +0200 (CEST) Subject: [pypy-commit] pypy length-hint: length_hint basics and some initial iterator support Message-ID: <20120405013716.0F2B3820C7@wyvern.cs.uni-duesseldorf.de> Author: Philip Jenvey Branch: length-hint Changeset: r54190:856f01d18746 Date: 2012-04-04 18:31 -0700 http://bitbucket.org/pypy/pypy/changeset/856f01d18746/ Log: length_hint basics and some initial iterator support diff --git a/pypy/objspace/std/iterobject.py b/pypy/objspace/std/iterobject.py --- a/pypy/objspace/std/iterobject.py +++ b/pypy/objspace/std/iterobject.py @@ -4,6 +4,29 @@ from pypy.objspace.std.register_all import register_all +def length_hint(space, w_obj, default): + """Return the length of an object, consulting its __length_hint__ + method if necessary. + """ + try: + return space.len_w(w_obj) + except OperationError, e: + if not (e.match(space, space.w_TypeError) or + e.match(space, space.w_AttributeError)): + raise + + try: + w_hint = space.call_method(w_obj, '__length_hint__') + except OperationError, e: + if not (e.match(space, space.w_TypeError) or + e.match(space, space.w_AttributeError)): + raise + return default + + hint = space.int_w(w_hint) + return default if hint < 0 else hint + + class W_AbstractIterObject(W_Object): __slots__ = () @@ -71,10 +94,6 @@ w_seqiter.index += 1 return w_item -# XXX __length_hint__() -##def len__SeqIter(space, w_seqiter): -## return w_seqiter.getlength(space) - def iter__FastTupleIter(space, w_seqiter): return w_seqiter @@ -92,10 +111,6 @@ w_seqiter.index = index + 1 return w_item -# XXX __length_hint__() -##def len__FastTupleIter(space, w_seqiter): -## return w_seqiter.getlength(space) - def iter__FastListIter(space, w_seqiter): return w_seqiter @@ -115,10 +130,6 @@ w_seqiter.index = index + 1 return w_item -# XXX __length_hint__() -##def len__FastListIter(space, w_seqiter): -## return w_seqiter.getlength(space) - def iter__ReverseSeqIter(space, w_seqiter): return w_seqiter @@ -136,20 +147,4 @@ raise OperationError(space.w_StopIteration, space.w_None) return w_item -# XXX __length_hint__() -##def len__ReverseSeqIter(space, w_seqiter): -## if w_seqiter.w_seq is None: -## return space.wrap(0) -## index = w_seqiter.index+1 -## w_length = space.len(w_seqiter.w_seq) -## # if length of sequence is less than index :exhaust iterator -## if space.is_true(space.gt(space.wrap(w_seqiter.index), w_length)): -## w_len = space.wrap(0) -## w_seqiter.w_seq = None -## else: -## w_len =space.wrap(index) -## if space.is_true(space.lt(w_len,space.wrap(0))): -## w_len = space.wrap(0) -## return w_len - register_all(vars()) diff --git a/pypy/objspace/std/itertype.py b/pypy/objspace/std/itertype.py --- a/pypy/objspace/std/itertype.py +++ b/pypy/objspace/std/itertype.py @@ -23,6 +23,12 @@ tup = [w_self.w_seq, space.wrap(w_self.index)] return space.newtuple([new_inst, space.newtuple(tup)]) + +def descr_seqiter__length_hint__(space, w_self): + from pypy.objspace.std.iterobject import W_AbstractSeqIterObject + assert isinstance(w_self, W_AbstractSeqIterObject) + return w_self.getlength(space) + # ____________________________________________________________ def descr_reverseseqiter__reduce__(w_self, space): @@ -39,6 +45,24 @@ tup = [w_self.w_seq, space.wrap(w_self.index)] return space.newtuple([new_inst, space.newtuple(tup)]) + +def descr_reverseseqiter__length_hint__(space, w_self): + from pypy.objspace.std.iterobject import W_ReverseSeqIterObject + assert isinstance(w_self, W_ReverseSeqIterObject) + if w_self.w_seq is None: + return space.wrap(0) + index = w_self.index + 1 + w_length = space.len(w_self.w_seq) + # if length of sequence is less than index :exhaust iterator + if space.is_true(space.gt(space.wrap(w_self.index), w_length)): + w_len = space.wrap(0) + w_self.w_seq = None + else: + w_len = space.wrap(index) + if space.is_true(space.lt(w_len, space.wrap(0))): + w_len = space.wrap(0) + return w_len + # ____________________________________________________________ iter_typedef = StdTypeDef("sequenceiterator", __doc__ = '''iter(collection) -> iterator @@ -49,11 +73,13 @@ In the second form, the callable is called until it returns the sentinel.''', __reduce__ = gateway.interp2app(descr_seqiter__reduce__), + __length_hint__ = gateway.interp2app(descr_seqiter__length_hint__), ) iter_typedef.acceptable_as_base_class = False reverse_iter_typedef = StdTypeDef("reversesequenceiterator", __reduce__ = gateway.interp2app(descr_reverseseqiter__reduce__), + __length_hint__ = gateway.interp2app(descr_reverseseqiter__length_hint__), ) reverse_iter_typedef.acceptable_as_base_class = False diff --git a/pypy/objspace/std/test/test_lengthhint.py b/pypy/objspace/std/test/test_lengthhint.py new file mode 100644 --- /dev/null +++ b/pypy/objspace/std/test/test_lengthhint.py @@ -0,0 +1,53 @@ +from pypy.objspace.std.iterobject import length_hint + + +class TestLengthHint: + + SIZE = 4 + ITEMS = range(SIZE) + + def _test_length_hint(self, w_obj): + space = self.space + assert length_hint(space, w_obj, 8) == self.SIZE + + w_iter = space.iter(w_obj) + assert space.int_w( + space.call_method(w_iter, '__length_hint__')) == self.SIZE + assert length_hint(space, w_iter, 8) == self.SIZE + + space.next(w_iter) + assert length_hint(space, w_iter, 8) == self.SIZE - 1 + + def test_list(self): + self._test_length_hint(self.space.newlist(self.ITEMS)) + + def test_tuple(self): + self._test_length_hint(self.space.newtuple(self.ITEMS)) + + def test_reversed(self): + space = self.space + w_reversed = space.call_method(space.builtin, 'reversed', + space.newlist(self.ITEMS)) + assert space.int_w( + space.call_method(w_reversed, '__length_hint__')) == self.SIZE + self._test_length_hint(w_reversed) + + def test_default(self): + space = self.space + assert length_hint(space, space.w_False, 3) == 3 + + def test_exc(self): + from pypy.interpreter.error import OperationError + space = self.space + w_foo = space.appexec([], """(): + class Foo: + def __length_hint__(self): + 1 / 0 + return Foo() + """) + try: + assert length_hint(space, w_foo, 3) + except OperationError, e: + assert e.match(space, space.w_ZeroDivisionError) + else: + assert False, 'ZeroDivisionError expected' From noreply at buildbot.pypy.org Thu Apr 5 04:25:58 2012 From: noreply at buildbot.pypy.org (pjenvey) Date: Thu, 5 Apr 2012 04:25:58 +0200 (CEST) Subject: [pypy-commit] pypy length-hint: consult length_hint for unknown length iterables Message-ID: <20120405022559.013EB820C7@wyvern.cs.uni-duesseldorf.de> Author: Philip Jenvey Branch: length-hint Changeset: r54191:68bdcad461d4 Date: 2012-04-04 19:08 -0700 http://bitbucket.org/pypy/pypy/changeset/68bdcad461d4/ Log: consult length_hint for unknown length iterables diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py --- a/pypy/interpreter/baseobjspace.py +++ b/pypy/interpreter/baseobjspace.py @@ -826,19 +826,12 @@ # contains a loop (made explicit with the decorator above). # # If we can guess the expected length we can preallocate. + from pypy.objspace.std.iterobject import length_hint try: - lgt_estimate = self.len_w(w_iterable) - except OperationError, o: - if (not o.match(self, self.w_AttributeError) and - not o.match(self, self.w_TypeError)): - raise - items = [] - else: - try: - items = newlist_hint(lgt_estimate) - except MemoryError: - items = [] # it might have lied - # + items = newlist_hint(length_hint(self, w_iterable, 0)) + except MemoryError: + items = [] # it might have lied + while True: try: w_item = self.next(w_iterator) From pullrequests-noreply at bitbucket.org Thu Apr 5 05:57:20 2012 From: pullrequests-noreply at bitbucket.org (unbit) Date: Thu, 05 Apr 2012 03:57:20 -0000 Subject: [pypy-commit] [pypy/pypy] PyFile_FromFile implementation (pull request #68) Message-ID: <819aec1e6e3998acaa7b35b136186f8a@bitbucket.org> A new pull request has been opened by unbit. unbit/pypy has changes to be pulled into pypy/pypy. https://bitbucket.org/pypy/pypy/pull-request/68/pyfile_fromfile-implementation Title: PyFile_FromFile implementation PyFile_FromFile() is used by c apps to map a libc FILE object to a Python file object. The implementation make use of the pypy-specific file.fdopen function. Tested with latest uWSGI tip Changes to be pulled: 2afeef0e350b by unbit: "implemed PyFile_FromFile as cpyext" 7eb44aa590a2 by rob... at mrspurr: "added Py_Initialize,Py_Finalize,Py_SetPythonHome,Py_SetProgramName as cpyext" 4c1e64afbe9a by rob... at goyle: "added implementation of Py_Initialize" -- This is an issue notification from bitbucket.org. You are receiving this either because you are the participating in a pull request, or you are following it. From noreply at buildbot.pypy.org Thu Apr 5 07:27:59 2012 From: noreply at buildbot.pypy.org (wlav) Date: Thu, 5 Apr 2012 07:27:59 +0200 (CEST) Subject: [pypy-commit] pypy reflex-support: add return type to function signatures Message-ID: <20120405052759.173638208A@wyvern.cs.uni-duesseldorf.de> Author: Wim Lavrijsen Branch: reflex-support Changeset: r54192:db42c62fffbc Date: 2012-04-04 11:56 -0700 http://bitbucket.org/pypy/pypy/changeset/db42c62fffbc/ Log: add return type to function signatures diff --git a/pypy/module/cppyy/src/cintcwrapper.cxx b/pypy/module/cppyy/src/cintcwrapper.cxx --- a/pypy/module/cppyy/src/cintcwrapper.cxx +++ b/pypy/module/cppyy/src/cintcwrapper.cxx @@ -546,6 +546,9 @@ TFunction* f = type_get_method(handle, method_index); TClassRef cr = type_from_handle(handle); std::ostringstream sig; + if (cr.GetClass() && cr->GetClassInfo() + && strcmp(f->GetName(), ((G__ClassInfo*)cr->GetClassInfo())->Name()) != 0) + sig << f->GetReturnTypeName() << " "; sig << cr.GetClassName() << "::" << f->GetName() << "("; int nArgs = f->GetNargs(); for (int iarg = 0; iarg < nArgs; ++iarg) { diff --git a/pypy/module/cppyy/src/reflexcwrapper.cxx b/pypy/module/cppyy/src/reflexcwrapper.cxx --- a/pypy/module/cppyy/src/reflexcwrapper.cxx +++ b/pypy/module/cppyy/src/reflexcwrapper.cxx @@ -361,6 +361,8 @@ Reflex::Member m = s.FunctionMemberAt(method_index); Reflex::Type mt = m.TypeOf(); std::ostringstream sig; + if (!m.IsConstructor()) + sig << mt.ReturnType().Name() << " "; sig << s.Name(Reflex::SCOPED) << "::" << m.Name() << "("; int nArgs = m.FunctionParameterSize(); for (int iarg = 0; iarg < nArgs; ++iarg) { From noreply at buildbot.pypy.org Thu Apr 5 07:28:00 2012 From: noreply at buildbot.pypy.org (wlav) Date: Thu, 5 Apr 2012 07:28:00 +0200 (CEST) Subject: [pypy-commit] pypy reflex-support: code cleanup Message-ID: <20120405052800.AB5468208A@wyvern.cs.uni-duesseldorf.de> Author: Wim Lavrijsen Branch: reflex-support Changeset: r54193:b7f583abfe01 Date: 2012-04-04 22:28 -0700 http://bitbucket.org/pypy/pypy/changeset/b7f583abfe01/ Log: code cleanup diff --git a/pypy/module/cppyy/__init__.py b/pypy/module/cppyy/__init__.py --- a/pypy/module/cppyy/__init__.py +++ b/pypy/module/cppyy/__init__.py @@ -6,7 +6,7 @@ interpleveldefs = { '_load_dictionary' : 'interp_cppyy.load_dictionary', '_resolve_name' : 'interp_cppyy.resolve_name', - '_type_byname' : 'interp_cppyy.type_byname', + '_scope_byname' : 'interp_cppyy.scope_byname', '_template_byname' : 'interp_cppyy.template_byname', '_set_class_generator' : 'interp_cppyy.set_class_generator', '_register_class' : 'interp_cppyy.register_class', diff --git a/pypy/module/cppyy/capi/__init__.py b/pypy/module/cppyy/capi/__init__.py --- a/pypy/module/cppyy/capi/__init__.py +++ b/pypy/module/cppyy/capi/__init__.py @@ -39,9 +39,9 @@ [rffi.CCHARP], rffi.CCHARP, threadsafe=threadsafe, compilation_info=backend.eci) -def c_resolve_name(cppitem_name): - return charp2str_free(_c_resolve_name(cppitem_name)) -c_get_scope = rffi.llexternal( +def c_resolve_name(name): + return charp2str_free(_c_resolve_name(name)) +c_get_scope_opaque = rffi.llexternal( "cppyy_get_scope", [rffi.CCHARP], C_SCOPE, threadsafe=threadsafe, @@ -51,28 +51,36 @@ [rffi.CCHARP], C_TYPE, threadsafe=threadsafe, compilation_info=backend.eci) -c_get_object_type = rffi.llexternal( - "cppyy_get_object_type", +_c_actual_class = rffi.llexternal( + "cppyy_actual_class", [C_TYPE, C_OBJECT], C_TYPE, threadsafe=threadsafe, compilation_info=backend.eci) +def c_actual_class(cppclass, cppobj): + return _c_actual_class(cppclass.handle, cppobj) # memory management ---------------------------------------------------------- -c_allocate = rffi.llexternal( +_c_allocate = rffi.llexternal( "cppyy_allocate", [C_TYPE], C_OBJECT, threadsafe=threadsafe, compilation_info=backend.eci) -c_deallocate = rffi.llexternal( +def c_allocate(cppclass): + return _c_allocate(cppclass.handle) +_c_deallocate = rffi.llexternal( "cppyy_deallocate", [C_TYPE, C_OBJECT], lltype.Void, threadsafe=threadsafe, compilation_info=backend.eci) -c_destruct = rffi.llexternal( +def c_deallocate(cppclass, cppobject): + _c_deallocate(cppclass.handle, cppobject) +_c_destruct = rffi.llexternal( "cppyy_destruct", [C_TYPE, C_OBJECT], lltype.Void, threadsafe=threadsafe, compilation_info=backend.eci) +def c_destruct(cppclass, cppobject): + _c_destruct(cppclass.handle, cppobject) # method/function dispatching ------------------------------------------------ c_call_v = rffi.llexternal( @@ -133,18 +141,22 @@ threadsafe=threadsafe, compilation_info=backend.eci) -c_call_o = rffi.llexternal( +_c_call_o = rffi.llexternal( "cppyy_call_o", [C_METHOD, C_OBJECT, rffi.INT, rffi.VOIDP, C_TYPE], rffi.LONG, threadsafe=threadsafe, compilation_info=backend.eci) +def c_call_o(method_index, cppobj, nargs, args, cppclass): + return _c_call_o(method_index, cppobj, nargs, args, cppclass.handle) -c_get_methptr_getter = rffi.llexternal( +_c_get_methptr_getter = rffi.llexternal( "cppyy_get_methptr_getter", [C_SCOPE, rffi.INT], C_METHPTRGETTER_PTR, threadsafe=threadsafe, compilation_info=backend.eci, elidable_function=True) +def c_get_methptr_getter(cppscope, method_index): + return _c_get_methptr_getter(cppscope.handle, method_index) # handling of function argument buffer --------------------------------------- c_allocate_function_args = rffi.llexternal( @@ -202,18 +214,20 @@ [C_TYPE], rffi.INT, threadsafe=threadsafe, compilation_info=backend.eci) -c_num_bases = rffi.llexternal( +_c_num_bases = rffi.llexternal( "cppyy_num_bases", [C_TYPE], rffi.INT, threadsafe=threadsafe, compilation_info=backend.eci) +def c_num_bases(cppclass): + return _c_num_bases(cppclass.handle) _c_base_name = rffi.llexternal( "cppyy_base_name", [C_TYPE, rffi.INT], rffi.CCHARP, threadsafe=threadsafe, compilation_info=backend.eci) -def c_base_name(cpptype, base_index): - return charp2str_free(_c_base_name(cpptype, base_index)) +def c_base_name(cppclass, base_index): + return charp2str_free(_c_base_name(cppclass.handle, base_index)) _c_is_subtype = rffi.llexternal( "cppyy_is_subtype", @@ -221,12 +235,11 @@ threadsafe=threadsafe, compilation_info=backend.eci, elidable_function=True) - @jit.elidable_promote() def c_is_subtype(derived, base): if derived == base: return 1 - return _c_is_subtype(derived, base) + return _c_is_subtype(derived.handle, base.handle) _c_base_offset = rffi.llexternal( "cppyy_base_offset", @@ -234,132 +247,155 @@ threadsafe=threadsafe, compilation_info=backend.eci, elidable_function=True) - @jit.elidable_promote() def c_base_offset(derived, base, address): if derived == base: return 0 - return _c_base_offset(derived, base, address) + return _c_base_offset(derived.handle, base.handle, address) # method/function reflection information ------------------------------------- -c_num_methods = rffi.llexternal( +_c_num_methods = rffi.llexternal( "cppyy_num_methods", [C_SCOPE], rffi.INT, threadsafe=threadsafe, compilation_info=backend.eci) +def c_num_methods(cppscope): + return _c_num_methods(cppscope.handle) _c_method_name = rffi.llexternal( "cppyy_method_name", [C_SCOPE, rffi.INT], rffi.CCHARP, threadsafe=threadsafe, compilation_info=backend.eci) def c_method_name(cppscope, method_index): - return charp2str_free(_c_method_name(cppscope, method_index)) + return charp2str_free(_c_method_name(cppscope.handle, method_index)) _c_method_result_type = rffi.llexternal( "cppyy_method_result_type", [C_SCOPE, rffi.INT], rffi.CCHARP, threadsafe=threadsafe, compilation_info=backend.eci) def c_method_result_type(cppscope, method_index): - return charp2str_free(_c_method_result_type(cppscope, method_index)) -c_method_num_args = rffi.llexternal( + return charp2str_free(_c_method_result_type(cppscope.handle, method_index)) +_c_method_num_args = rffi.llexternal( "cppyy_method_num_args", [C_SCOPE, rffi.INT], rffi.INT, threadsafe=threadsafe, compilation_info=backend.eci) -c_method_req_args = rffi.llexternal( +def c_method_num_args(cppscope, method_index): + return _c_method_num_args(cppscope.handle, method_index) +_c_method_req_args = rffi.llexternal( "cppyy_method_req_args", [C_SCOPE, rffi.INT], rffi.INT, threadsafe=threadsafe, compilation_info=backend.eci) +def c_method_req_args(cppscope, method_index): + return _c_method_req_args(cppscope.handle, method_index) _c_method_arg_type = rffi.llexternal( "cppyy_method_arg_type", [C_SCOPE, rffi.INT, rffi.INT], rffi.CCHARP, threadsafe=threadsafe, compilation_info=backend.eci) def c_method_arg_type(cppscope, method_index, arg_index): - return charp2str_free(_c_method_arg_type(cppscope, method_index, arg_index)) + return charp2str_free(_c_method_arg_type(cppscope.handle, method_index, arg_index)) _c_method_arg_default = rffi.llexternal( "cppyy_method_arg_default", [C_SCOPE, rffi.INT, rffi.INT], rffi.CCHARP, threadsafe=threadsafe, compilation_info=backend.eci) def c_method_arg_default(cppscope, method_index, arg_index): - return charp2str_free(_c_method_arg_default(cppscope, method_index, arg_index)) + return charp2str_free(_c_method_arg_default(cppscope.handle, method_index, arg_index)) _c_method_signature = rffi.llexternal( "cppyy_method_signature", [C_SCOPE, rffi.INT], rffi.CCHARP, threadsafe=threadsafe, compilation_info=backend.eci) def c_method_signature(cppscope, method_index): - return charp2str_free(_c_method_signature(cppscope, method_index)) + return charp2str_free(_c_method_signature(cppscope.handle, method_index)) -c_method_index = rffi.llexternal( +_c_method_index = rffi.llexternal( "cppyy_method_index", [C_SCOPE, rffi.CCHARP], rffi.INT, threadsafe=threadsafe, compilation_info=backend.eci) +def c_method_index(cppscope, name): + return _c_method_index(cppscope.handle, name) -c_get_method = rffi.llexternal( +_c_get_method = rffi.llexternal( "cppyy_get_method", [C_SCOPE, rffi.INT], C_METHOD, threadsafe=threadsafe, compilation_info=backend.eci) +def c_get_method(cppscope, method_index): + return _c_get_method(cppscope.handle, method_index) # method properties ---------------------------------------------------------- -c_is_constructor = rffi.llexternal( +_c_is_constructor = rffi.llexternal( "cppyy_is_constructor", [C_TYPE, rffi.INT], rffi.INT, threadsafe=threadsafe, compilation_info=backend.eci) -c_is_staticmethod = rffi.llexternal( +def c_is_constructor(cppclass, method_index): + return _c_is_constructor(cppclass.handle, method_index) +_c_is_staticmethod = rffi.llexternal( "cppyy_is_staticmethod", [C_TYPE, rffi.INT], rffi.INT, threadsafe=threadsafe, compilation_info=backend.eci) +def c_is_staticmethod(cppclass, method_index): + return _c_is_staticmethod(cppclass.handle, method_index) # data member reflection information ----------------------------------------- -c_num_data_members = rffi.llexternal( - "cppyy_num_data_members", +_c_num_datamembers = rffi.llexternal( + "cppyy_num_datamembers", [C_SCOPE], rffi.INT, threadsafe=threadsafe, compilation_info=backend.eci) -_c_data_member_name = rffi.llexternal( - "cppyy_data_member_name", +def c_num_datamembers(cppscope): + return _c_num_datamembers(cppscope.handle) +_c_datamember_name = rffi.llexternal( + "cppyy_datamember_name", [C_SCOPE, rffi.INT], rffi.CCHARP, threadsafe=threadsafe, compilation_info=backend.eci) -def c_data_member_name(cppscope, data_member_index): - return charp2str_free(_c_data_member_name(cppscope, data_member_index)) -_c_data_member_type = rffi.llexternal( - "cppyy_data_member_type", +def c_datamember_name(cppscope, datamember_index): + return charp2str_free(_c_datamember_name(cppscope.handle, datamember_index)) +_c_datamember_type = rffi.llexternal( + "cppyy_datamember_type", [C_SCOPE, rffi.INT], rffi.CCHARP, threadsafe=threadsafe, compilation_info=backend.eci) -def c_data_member_type(cppscope, data_member_index): - return charp2str_free(_c_data_member_type(cppscope, data_member_index)) -c_data_member_offset = rffi.llexternal( - "cppyy_data_member_offset", +def c_datamember_type(cppscope, datamember_index): + return charp2str_free(_c_datamember_type(cppscope.handle, datamember_index)) +_c_datamember_offset = rffi.llexternal( + "cppyy_datamember_offset", [C_SCOPE, rffi.INT], rffi.SIZE_T, threadsafe=threadsafe, compilation_info=backend.eci) +def c_datamember_offset(cppscope, datamember_index): + return _c_datamember_offset(cppscope.handle, datamember_index) -c_data_member_index = rffi.llexternal( - "cppyy_data_member_index", +_c_datamember_index = rffi.llexternal( + "cppyy_datamember_index", [C_SCOPE, rffi.CCHARP], rffi.INT, threadsafe=threadsafe, compilation_info=backend.eci) +def c_datamember_index(cppscope, name): + return _c_datamember_index(cppscope.handle, name) # data member properties ----------------------------------------------------- -c_is_publicdata = rffi.llexternal( +_c_is_publicdata = rffi.llexternal( "cppyy_is_publicdata", [C_SCOPE, rffi.INT], rffi.INT, threadsafe=threadsafe, compilation_info=backend.eci) -c_is_staticdata = rffi.llexternal( +def c_is_publicdata(cppscope, datamember_index): + return _c_is_publicdata(cppscope.handle, datamember_index) +_c_is_staticdata = rffi.llexternal( "cppyy_is_staticdata", [C_SCOPE, rffi.INT], rffi.INT, threadsafe=threadsafe, compilation_info=backend.eci) +def c_is_staticdata(cppscope, datamember_index): + return _c_is_staticdata(cppscope.handle, datamember_index) # misc helpers --------------------------------------------------------------- c_strtoll = rffi.llexternal( diff --git a/pypy/module/cppyy/converter.py b/pypy/module/cppyy/converter.py --- a/pypy/module/cppyy/converter.py +++ b/pypy/module/cppyy/converter.py @@ -64,7 +64,7 @@ from pypy.module.cppyy.interp_cppyy import FastCallNotPossible raise FastCallNotPossible - def from_memory(self, space, w_obj, w_type, offset): + def from_memory(self, space, w_obj, w_pycppclass, offset): self._is_abstract(space) def to_memory(self, space, w_obj, w_value, offset): @@ -98,7 +98,7 @@ else: self.size = array_size - def from_memory(self, space, w_obj, w_type, offset): + def from_memory(self, space, w_obj, w_pycppclass, offset): if hasattr(space, "fake"): raise NotImplementedError # read access, so no copy needed @@ -124,7 +124,7 @@ def __init__(self, space, array_size): self.size = sys.maxint - def from_memory(self, space, w_obj, w_type, offset): + def from_memory(self, space, w_obj, w_pycppclass, offset): # read access, so no copy needed address_value = self._get_raw_address(space, w_obj, offset) address = rffi.cast(rffi.ULONGP, address_value) @@ -154,7 +154,7 @@ def default_argument_libffi(self, space, argchain): argchain.arg(self.default) - def from_memory(self, space, w_obj, w_type, offset): + def from_memory(self, space, w_obj, w_pycppclass, offset): address = self._get_raw_address(space, w_obj, offset) rffiptr = rffi.cast(self.rffiptype, address) return space.wrap(rffiptr[0]) @@ -213,7 +213,7 @@ def convert_argument_libffi(self, space, w_obj, argchain): argchain.arg(self._unwrap_object(space, w_obj)) - def from_memory(self, space, w_obj, w_type, offset): + def from_memory(self, space, w_obj, w_pycppclass, offset): address = rffi.cast(rffi.CCHARP, self._get_raw_address(space, w_obj, offset)) if address[0] == '\x01': return space.w_True @@ -255,7 +255,7 @@ def convert_argument_libffi(self, space, w_obj, argchain): argchain.arg(self._unwrap_object(space, w_obj)) - def from_memory(self, space, w_obj, w_type, offset): + def from_memory(self, space, w_obj, w_pycppclass, offset): address = rffi.cast(rffi.CCHARP, self._get_raw_address(space, w_obj, offset)) return space.wrap(address[0]) @@ -347,7 +347,7 @@ def _unwrap_object(self, space, w_obj): return r_singlefloat(space.float_w(w_obj)) - def from_memory(self, space, w_obj, w_type, offset): + def from_memory(self, space, w_obj, w_pycppclass, offset): address = self._get_raw_address(space, w_obj, offset) rffiptr = rffi.cast(self.rffiptype, address) return space.wrap(float(rffiptr[0])) @@ -378,7 +378,7 @@ ba = rffi.cast(rffi.CCHARP, address) ba[capi.c_function_arg_typeoffset()] = 'o' - def from_memory(self, space, w_obj, w_type, offset): + def from_memory(self, space, w_obj, w_pycppclass, offset): address = self._get_raw_address(space, w_obj, offset) charpptr = rffi.cast(rffi.CCHARPP, address) return space.wrap(rffi.charp2str(charpptr[0])) @@ -505,25 +505,23 @@ class InstancePtrConverter(TypeConverter): _immutable_ = True - def __init__(self, space, cpptype, name): - from pypy.module.cppyy.interp_cppyy import W_CPPType - assert isinstance(cpptype, W_CPPType) - self.cpptype = cpptype - self.name = name + def __init__(self, space, cppclass): + from pypy.module.cppyy.interp_cppyy import W_CPPClass + assert isinstance(cppclass, W_CPPClass) + self.cppclass = cppclass def _unwrap_object(self, space, w_obj): from pypy.module.cppyy.interp_cppyy import W_CPPInstance obj = space.interpclass_w(w_obj) if isinstance(obj, W_CPPInstance): - if capi.c_is_subtype(obj.cppclass.handle, self.cpptype.handle): + if capi.c_is_subtype(obj.cppclass, self.cppclass): rawobject = obj.get_rawobject() - offset = capi.c_base_offset( - obj.cppclass.handle, self.cpptype.handle, rawobject) + offset = capi.c_base_offset(obj.cppclass, self.cppclass, rawobject) obj_address = capi.direct_ptradd(rawobject, offset) return rffi.cast(capi.C_OBJECT, obj_address) raise OperationError(space.w_TypeError, space.wrap("cannot pass %s as %s" % - (space.type(w_obj).getname(space, "?"), self.cpptype.name))) + (space.type(w_obj).getname(space, "?"), self.cppclass.name))) def convert_argument(self, space, w_obj, address): x = rffi.cast(rffi.VOIDPP, address) @@ -535,10 +533,11 @@ def convert_argument_libffi(self, space, w_obj, argchain): argchain.arg(self._unwrap_object(space, w_obj)) - def from_memory(self, space, w_obj, w_type, offset): + def from_memory(self, space, w_obj, w_pycppclass, offset): address = rffi.cast(capi.C_OBJECT, self._get_raw_address(space, w_obj, offset)) from pypy.module.cppyy import interp_cppyy - return interp_cppyy.wrap_cppobject_nocast(space, w_type, self.cpptype, address, True, False) + return interp_cppyy.wrap_cppobject_nocast( + space, w_pycppclass, self.cppclass, address, isref=True, python_owns=False) def to_memory(self, space, w_obj, w_value, offset): address = rffi.cast(rffi.VOIDPP, self._get_raw_address(space, w_obj, offset)) @@ -547,10 +546,11 @@ class InstanceConverter(InstancePtrConverter): _immutable_ = True - def from_memory(self, space, w_obj, w_type, offset): + def from_memory(self, space, w_obj, w_pycppclass, offset): address = rffi.cast(capi.C_OBJECT, self._get_raw_address(space, w_obj, offset)) from pypy.module.cppyy import interp_cppyy - return interp_cppyy.wrap_cppobject_nocast(space, w_type, self.cpptype, address, False, False) + return interp_cppyy.wrap_cppobject_nocast( + space, w_pycppclass, self.cppclass, address, isref=False, python_owns=False) def to_memory(self, space, w_obj, w_value, offset): self._is_abstract(space) @@ -561,8 +561,8 @@ def __init__(self, space, extra): from pypy.module.cppyy import interp_cppyy - cpptype = interp_cppyy.type_byname(space, "std::string") - InstanceConverter.__init__(self, space, cpptype, "std::string") + cppclass = interp_cppyy.scope_byname(space, "std::string") + InstanceConverter.__init__(self, space, cppclass) def _unwrap_object(self, space, w_obj): try: @@ -582,8 +582,8 @@ def __init__(self, space, extra): from pypy.module.cppyy import interp_cppyy - cpptype = interp_cppyy.type_byname(space, "std::string") - InstancePtrConverter.__init__(self, space, cpptype, "std::string") + cppclass = interp_cppyy.scope_byname(space, "std::string") + InstancePtrConverter.__init__(self, space, cppclass) _converters = {} # builtin and custom types @@ -631,15 +631,15 @@ # 5) generalized cases (covers basically all user classes) from pypy.module.cppyy import interp_cppyy - cpptype = interp_cppyy.type_byname(space, clean_name) - if cpptype: + cppclass = interp_cppyy.scope_byname(space, clean_name) + if cppclass: # type check for the benefit of the annotator - from pypy.module.cppyy.interp_cppyy import W_CPPType - cpptype = space.interp_w(W_CPPType, cpptype, can_be_None=False) + from pypy.module.cppyy.interp_cppyy import W_CPPClass + cppclass = space.interp_w(W_CPPClass, cppclass, can_be_None=False) if compound == "*" or compound == "&": - return InstancePtrConverter(space, cpptype, clean_name) + return InstancePtrConverter(space, cppclass) elif compound == "": - return InstanceConverter(space, cpptype, clean_name) + return InstanceConverter(space, cppclass) elif capi.c_is_enum(clean_name): return UnsignedIntConverter(space, default) diff --git a/pypy/module/cppyy/executor.py b/pypy/module/cppyy/executor.py --- a/pypy/module/cppyy/executor.py +++ b/pypy/module/cppyy/executor.py @@ -17,7 +17,7 @@ _immutable_ = True libffitype = NULL - def __init__(self, space, cpptype): + def __init__(self, space, extra): pass def execute(self, space, cppmethod, cppthis, num_args, args): @@ -251,20 +251,22 @@ _immutable_ = True libffitype = libffi.types.pointer - def __init__(self, space, cpptype): - FunctionExecutor.__init__(self, space, cpptype) - self.cpptype = cpptype + def __init__(self, space, cppclass): + FunctionExecutor.__init__(self, space, cppclass) + self.cppclass = cppclass def execute(self, space, cppmethod, cppthis, num_args, args): from pypy.module.cppyy import interp_cppyy long_result = capi.c_call_l(cppmethod, cppthis, num_args, args) ptr_result = rffi.cast(capi.C_OBJECT, long_result) - return interp_cppyy.wrap_cppobject(space, None, self.cpptype, ptr_result, False, False) + return interp_cppyy.wrap_cppobject( + space, space.w_None, self.cppclass, ptr_result, isref=False, python_owns=False) def execute_libffi(self, space, libffifunc, argchain): from pypy.module.cppyy import interp_cppyy ptr_result = rffi.cast(capi.C_OBJECT, libffifunc.call(argchain, rffi.VOIDP)) - return interp_cppyy.wrap_cppobject(space, None, self.cpptype, ptr_result, False, False) + return interp_cppyy.wrap_cppobject( + space, space.w_None, self.cppclass, ptr_result, isref=False, python_owns=False) class InstancePtrPtrExecutor(InstancePtrExecutor): _immutable_ = True @@ -274,7 +276,8 @@ voidp_result = capi.c_call_r(cppmethod, cppthis, num_args, args) ref_address = rffi.cast(rffi.VOIDPP, voidp_result) ptr_result = rffi.cast(capi.C_OBJECT, ref_address[0]) - return interp_cppyy.wrap_cppobject(space, None, self.cpptype, ptr_result, False, False) + return interp_cppyy.wrap_cppobject( + space, space.w_None, self.cppclass, ptr_result, isref=False, python_owns=False) def execute_libffi(self, space, libffifunc, argchain): from pypy.module.cppyy.interp_cppyy import FastCallNotPossible @@ -285,9 +288,10 @@ def execute(self, space, cppmethod, cppthis, num_args, args): from pypy.module.cppyy import interp_cppyy - long_result = capi.c_call_o(cppmethod, cppthis, num_args, args, self.cpptype.handle) + long_result = capi.c_call_o(cppmethod, cppthis, num_args, args, self.cppclass) ptr_result = rffi.cast(capi.C_OBJECT, long_result) - return interp_cppyy.wrap_cppobject(space, None, self.cpptype, ptr_result, False, True) + return interp_cppyy.wrap_cppobject( + space, space.w_None, self.cppclass, ptr_result, isref=False, python_owns=True) def execute_libffi(self, space, libffifunc, argchain): from pypy.module.cppyy.interp_cppyy import FastCallNotPossible @@ -343,17 +347,17 @@ # 3) types/classes, either by ref/ptr or by value from pypy.module.cppyy import interp_cppyy - cpptype = interp_cppyy.type_byname(space, clean_name) - if cpptype: + cppclass = interp_cppyy.scope_byname(space, clean_name) + if cppclass: # type check for the benefit of the annotator - from pypy.module.cppyy.interp_cppyy import W_CPPType - cpptype = space.interp_w(W_CPPType, cpptype, can_be_None=False) + from pypy.module.cppyy.interp_cppyy import W_CPPClass + cppclass = space.interp_w(W_CPPClass, cppclass, can_be_None=False) if compound == "": - return InstanceExecutor(space, cpptype) + return InstanceExecutor(space, cppclass) elif compound == "*" or compound == "&": - return InstancePtrExecutor(space, cpptype) + return InstancePtrExecutor(space, cppclass) elif compound == "**" or compound == "*&": - return InstancePtrPtrExecutor(space, cpptype) + return InstancePtrPtrExecutor(space, cppclass) elif capi.c_is_enum(clean_name): return UnsignedIntExecutor(space, None) diff --git a/pypy/module/cppyy/include/capi.h b/pypy/module/cppyy/include/capi.h --- a/pypy/module/cppyy/include/capi.h +++ b/pypy/module/cppyy/include/capi.h @@ -17,7 +17,7 @@ char* cppyy_resolve_name(const char* cppitem_name); cppyy_scope_t cppyy_get_scope(const char* scope_name); cppyy_type_t cppyy_get_template(const char* template_name); - cppyy_type_t cppyy_get_object_type(cppyy_type_t klass, cppyy_object_t obj); + cppyy_type_t cppyy_actual_class(cppyy_type_t klass, cppyy_object_t obj); /* memory management ------------------------------------------------------ */ cppyy_object_t cppyy_allocate(cppyy_type_t type); @@ -80,16 +80,16 @@ int cppyy_is_staticmethod(cppyy_type_t type, int method_index); /* data member reflection information ------------------------------------ */ - int cppyy_num_data_members(cppyy_scope_t scope); - char* cppyy_data_member_name(cppyy_scope_t scope, int data_member_index); - char* cppyy_data_member_type(cppyy_scope_t scope, int data_member_index); - size_t cppyy_data_member_offset(cppyy_scope_t scope, int data_member_index); + int cppyy_num_datamembers(cppyy_scope_t scope); + char* cppyy_datamember_name(cppyy_scope_t scope, int datamember_index); + char* cppyy_datamember_type(cppyy_scope_t scope, int datamember_index); + size_t cppyy_datamember_offset(cppyy_scope_t scope, int datamember_index); - int cppyy_data_member_index(cppyy_scope_t scope, const char* name); + int cppyy_datamember_index(cppyy_scope_t scope, const char* name); /* data member properties ------------------------------------------------ */ - int cppyy_is_publicdata(cppyy_type_t type, int data_member_index); - int cppyy_is_staticdata(cppyy_type_t type, int data_member_index); + int cppyy_is_publicdata(cppyy_type_t type, int datamember_index); + int cppyy_is_staticdata(cppyy_type_t type, int datamember_index); /* misc helpers ----------------------------------------------------------- */ void cppyy_free(void* ptr); diff --git a/pypy/module/cppyy/interp_cppyy.py b/pypy/module/cppyy/interp_cppyy.py --- a/pypy/module/cppyy/interp_cppyy.py +++ b/pypy/module/cppyy/interp_cppyy.py @@ -27,10 +27,10 @@ class State(object): def __init__(self, space): - self.r_cppscope_cache = { - "void" : W_CPPType(space, "void", capi.C_NULL_TYPE) } - self.r_cpptemplate_cache = {} - self.type_registry = {} + self.cppscope_cache = { + "void" : W_CPPClass(space, "void", capi.C_NULL_TYPE) } + self.cpptemplate_cache = {} + self.cppclass_registry = {} self.w_clgen_callback = None @unwrap_spec(name=str) @@ -38,30 +38,30 @@ return space.wrap(capi.c_resolve_name(name)) @unwrap_spec(name=str) -def type_byname(space, name): +def scope_byname(space, name): true_name = capi.c_resolve_name(name) state = space.fromcache(State) try: - return state.r_cppscope_cache[true_name] + return state.cppscope_cache[true_name] except KeyError: pass - cppscope = capi.c_get_scope(true_name) - assert lltype.typeOf(cppscope) == capi.C_SCOPE - if cppscope: - final_name = capi.c_final_name(cppscope) - if capi.c_is_namespace(cppscope): - r_cppscope = W_CPPNamespace(space, final_name, cppscope) - elif capi.c_has_complex_hierarchy(cppscope): - r_cppscope = W_ComplexCPPType(space, final_name, cppscope) + opaque_handle = capi.c_get_scope_opaque(true_name) + assert lltype.typeOf(opaque_handle) == capi.C_SCOPE + if opaque_handle: + final_name = capi.c_final_name(opaque_handle) + if capi.c_is_namespace(opaque_handle): + cppscope = W_CPPNamespace(space, final_name, opaque_handle) + elif capi.c_has_complex_hierarchy(opaque_handle): + cppscope = W_ComplexCPPClass(space, final_name, opaque_handle) else: - r_cppscope = W_CPPType(space, final_name, cppscope) - state.r_cppscope_cache[name] = r_cppscope + cppscope = W_CPPClass(space, final_name, opaque_handle) + state.cppscope_cache[name] = cppscope - r_cppscope._find_methods() - r_cppscope._find_data_members() - return r_cppscope + cppscope._find_methods() + cppscope._find_datamembers() + return cppscope return None @@ -69,16 +69,16 @@ def template_byname(space, name): state = space.fromcache(State) try: - return state.r_cpptemplate_cache[name] + return state.cpptemplate_cache[name] except KeyError: pass - cpptemplate = capi.c_get_template(name) - assert lltype.typeOf(cpptemplate) == capi.C_TYPE - if cpptemplate: - r_cpptemplate = W_CPPTemplateType(space, name, cpptemplate) - state.r_cpptemplate_cache[name] = r_cpptemplate - return r_cpptemplate + opaque_handle = capi.c_get_template(name) + assert lltype.typeOf(opaque_handle) == capi.C_TYPE + if opaque_handle: + cpptemplate = W_CPPTemplateType(space, name, opaque_handle) + state.cpptemplate_cache[name] = cpptemplate + return cpptemplate return None @@ -87,12 +87,12 @@ state = space.fromcache(State) state.w_clgen_callback = w_callback - at unwrap_spec(w_type=W_Root) -def register_class(space, w_type): - w_cpptype = space.findattr(w_type, space.wrap("_cpp_proxy")) - cpptype = space.interp_w(W_CPPType, w_cpptype, can_be_None=False) + at unwrap_spec(w_pycppclass=W_Root) +def register_class(space, w_pycppclass): + w_cppclass = space.findattr(w_pycppclass, space.wrap("_cpp_proxy")) + cppclass = space.interp_w(W_CPPClass, w_cppclass, can_be_None=False) state = space.fromcache(State) - state.type_registry[cpptype.handle] = w_type + state.cppclass_registry[cppclass.handle] = w_pycppclass class W_CPPLibrary(Wrappable): @@ -112,18 +112,19 @@ """ A concrete function after overloading has been resolved """ _immutable_ = True - def __init__(self, cpptype, method_index, result_type, arg_defs, args_required): - self.space = cpptype.space - self.cpptype = cpptype - self.method_index = method_index - self.cppmethod = capi.c_get_method(self.cpptype.handle, method_index) + def __init__(self, space, containing_scope, method_index, result_type, arg_defs, args_required): + self.space = space + self.scope = containing_scope + self.index = method_index + self.cppmethod = capi.c_get_method(self.scope, method_index) self.arg_defs = arg_defs self.args_required = args_required - self.executor = executor.get_executor(self.space, result_type) + self.result_type = result_type # Setup of the method dispatch's innards is done lazily, i.e. only when - # the method is actually used. TODO: executor should be lazy as well. + # the method is actually used. self.arg_converters = None + self.executor = None self._libffifunc = None @jit.unroll_safe @@ -169,11 +170,12 @@ def _setup(self, cppthis): self.arg_converters = [converter.get_converter(self.space, arg_type, arg_dflt) for arg_type, arg_dflt in self.arg_defs] + self.executor = executor.get_executor(self.space, self.result_type) # Each CPPMethod corresponds one-to-one to a C++ equivalent and cppthis # has been offset to the matching class. Hence, the libffi pointer is # uniquely defined and needs to be setup only once. - methgetter = capi.c_get_methptr_getter(self.cpptype.handle, self.method_index) + methgetter = capi.c_get_methptr_getter(self.scope, self.index) if methgetter and cppthis: # methods only for now funcptr = methgetter(rffi.cast(capi.C_OBJECT, cppthis)) argtypes_libffi = [conv.libffitype for conv in self.arg_converters @@ -217,11 +219,10 @@ capi.c_deallocate_function_args(args) def signature(self): - return capi.c_method_signature(self.cpptype.handle, self.method_index) + return capi.c_method_signature(self.scope, self.index) def __repr__(self): - return "CPPFunction(%s, %s, %r, %s)" % ( - self.cpptype, self.method_index, self.executor, self.arg_defs) + return "CPPMethod: %s" % self.signature() def _freeze_(self): assert 0, "you should never have a pre-built instance of this!" @@ -230,29 +231,34 @@ class CPPFunction(CPPMethod): _immutable_ = True + def __repr__(self): + return "CPPFunction: %s" % self.signature() + class CPPConstructor(CPPMethod): _immutable_ = True def call(self, cppthis, args_w): - newthis = capi.c_allocate(self.cpptype.handle) + newthis = capi.c_allocate(self.scope) assert lltype.typeOf(newthis) == capi.C_OBJECT try: CPPMethod.call(self, newthis, args_w) except: - capi.c_deallocate(self.cpptype.handle, newthis) + capi.c_deallocate(self.scope, newthis) raise - return wrap_new_cppobject_nocast(self.space, None, self.cpptype, newthis, False, True) + return wrap_new_cppobject_nocast( + self.space, self.space.w_None, self.scope, newthis, isref=False, python_owns=True) + + def __repr__(self): + return "CPPConstructor: %s" % self.signature() class W_CPPOverload(Wrappable): _immutable_ = True - def __init__(self, space, scope_handle, func_name, functions): + def __init__(self, space, containing_scope, functions): self.space = space - assert lltype.typeOf(scope_handle) == capi.C_SCOPE - self.scope_handle = scope_handle - self.func_name = func_name + self.scope = containing_scope self.functions = debug.make_sure_not_resized(functions) def is_static(self): @@ -263,8 +269,7 @@ cppinstance = self.space.interp_w(W_CPPInstance, w_cppinstance, can_be_None=True) if cppinstance is not None: cppinstance._nullcheck() - assert isinstance(cppinstance.cppclass, W_CPPType) - cppthis = cppinstance.cppclass.get_cppthis(cppinstance, self.scope_handle) + cppthis = cppinstance.get_cppthis(self.scope) else: cppthis = capi.C_NULL_OBJECT assert lltype.typeOf(cppthis) == capi.C_OBJECT @@ -300,7 +305,7 @@ errmsg += ' '+e.errorstr(self.space) except Exception, e: errmsg += '\n '+cppyyfunc.signature()+' =>\n' - errmsg += ' Exception:'+str(e) + errmsg += ' Exception: '+str(e) raise OperationError(self.space.w_TypeError, self.space.wrap(errmsg)) @@ -311,7 +316,7 @@ return self.space.wrap(sig) def __repr__(self): - return "W_CPPOverload(%s, %s)" % (self.func_name, self.functions) + return "W_CPPOverload(%s)" % [f.signature() for f in self.functions] W_CPPOverload.typedef = TypeDef( 'CPPOverload', @@ -324,10 +329,9 @@ class W_CPPDataMember(Wrappable): _immutable_ = True - def __init__(self, space, scope_handle, type_name, offset, is_static): + def __init__(self, space, containing_scope, type_name, offset, is_static): self.space = space - assert lltype.typeOf(scope_handle) == capi.C_SCOPE - self.scope_handle = scope_handle + self.scope = containing_scope self.converter = converter.get_converter(self.space, type_name, '') self.offset = offset self._is_static = is_static @@ -341,17 +345,17 @@ @jit.elidable_promote() def _get_offset(self, cppinstance): if cppinstance: - assert lltype.typeOf(cppinstance.cppclass.handle) == lltype.typeOf(self.scope_handle) + assert lltype.typeOf(cppinstance.cppclass.handle) == lltype.typeOf(self.scope.handle) offset = self.offset + capi.c_base_offset( - cppinstance.cppclass.handle, self.scope_handle, cppinstance.get_rawobject()) + cppinstance.cppclass, self.scope, cppinstance.get_rawobject()) else: offset = self.offset return offset - def get(self, w_cppinstance, w_type): + def get(self, w_cppinstance, w_pycppclass): cppinstance = self.space.interp_w(W_CPPInstance, w_cppinstance, can_be_None=True) offset = self._get_offset(cppinstance) - return self.converter.from_memory(self.space, w_cppinstance, w_type, offset) + return self.converter.from_memory(self.space, w_cppinstance, w_pycppclass, offset) def set(self, w_cppinstance, w_value): cppinstance = self.space.interp_w(W_CPPInstance, w_cppinstance, can_be_None=True) @@ -371,38 +375,38 @@ class W_CPPScope(Wrappable): _immutable_ = True - _immutable_fields_ = ["methods[*]", "data_members[*]"] + _immutable_fields_ = ["methods[*]", "datamembers[*]"] kind = "scope" - def __init__(self, space, name, handle): + def __init__(self, space, name, opaque_handle): self.space = space self.name = name - assert lltype.typeOf(handle) == capi.C_SCOPE - self.handle = handle + assert lltype.typeOf(opaque_handle) == capi.C_SCOPE + self.handle = opaque_handle self.methods = {} # Do not call "self._find_methods()" here, so that a distinction can # be made between testing for existence (i.e. existence in the cache # of classes) and actual use. Point being that a class can use itself, # e.g. as a return type or an argument to one of its methods. - self.data_members = {} + self.datamembers = {} # Idem self.methods: a type could hold itself by pointer. def _find_methods(self): - num_methods = capi.c_num_methods(self.handle) + num_methods = capi.c_num_methods(self) args_temp = {} for i in range(num_methods): - method_name = capi.c_method_name(self.handle, i) + method_name = capi.c_method_name(self, i) pymethod_name = helper.map_operator_name( - method_name, capi.c_method_num_args(self.handle, i), - capi.c_method_result_type(self.handle, i)) + method_name, capi.c_method_num_args(self, i), + capi.c_method_result_type(self, i)) if not pymethod_name in self.methods: cppfunction = self._make_cppfunction(i) overload = args_temp.setdefault(pymethod_name, []) overload.append(cppfunction) for name, functions in args_temp.iteritems(): - overload = W_CPPOverload(self.space, self.handle, name, functions[:]) + overload = W_CPPOverload(self.space, self, functions[:]) self.methods[name] = overload def get_method_names(self): @@ -414,24 +418,31 @@ return self.methods[name] except KeyError: pass - return self.find_overload(name) + new_method = self.find_overload(name) + self.methods[name] = new_method + return new_method - def get_data_member_names(self): - return self.space.newlist([self.space.wrap(name) for name in self.data_members]) + def get_datamember_names(self): + return self.space.newlist([self.space.wrap(name) for name in self.datamembers]) @jit.elidable_promote('0') - def get_data_member(self, name): + def get_datamember(self, name): try: - return self.data_members[name] + return self.datamembers[name] except KeyError: pass - return self.find_data_member(name) + new_dm = self.find_datamember(name) + self.datamembers[name] = new_dm + return new_dm def missing_attribute_error(self, name): return OperationError( self.space.w_AttributeError, self.space.wrap("%s '%s' has no attribute %s" % (self.kind, self.name, name))) + def __eq__(self, other): + return self.handle == other.handle + # For now, keep namespaces and classes separate as namespaces are extensible # with info from multiple dictionaries and do not need to bother with meta @@ -439,56 +450,54 @@ # may be in order at some point. class W_CPPNamespace(W_CPPScope): _immutable_ = True - kind = "namespace" def _make_cppfunction(self, method_index): - result_type = capi.c_method_result_type(self.handle, method_index) - num_args = capi.c_method_num_args(self.handle, method_index) - args_required = capi.c_method_req_args(self.handle, method_index) + result_type = capi.c_method_result_type(self, method_index) + num_args = capi.c_method_num_args(self, method_index) + args_required = capi.c_method_req_args(self, method_index) arg_defs = [] for i in range(num_args): - arg_type = capi.c_method_arg_type(self.handle, method_index, i) - arg_dflt = capi.c_method_arg_default(self.handle, method_index, i) + arg_type = capi.c_method_arg_type(self, method_index, i) + arg_dflt = capi.c_method_arg_default(self, method_index, i) arg_defs.append((arg_type, arg_dflt)) - return CPPFunction(self, method_index, result_type, arg_defs, args_required) + return CPPFunction(self.space, self, method_index, result_type, arg_defs, args_required) - def _make_data_member(self, dm_name, dm_idx): - type_name = capi.c_data_member_type(self.handle, dm_idx) - offset = capi.c_data_member_offset(self.handle, dm_idx) - data_member = W_CPPDataMember(self.space, self.handle, type_name, offset, True) - self.data_members[dm_name] = data_member - return data_member + def _make_datamember(self, dm_name, dm_idx): + type_name = capi.c_datamember_type(self, dm_idx) + offset = capi.c_datamember_offset(self, dm_idx) + datamember = W_CPPDataMember(self.space, self, type_name, offset, True) + self.datamembers[dm_name] = datamember + return datamember - def _find_data_members(self): - num_data_members = capi.c_num_data_members(self.handle) - for i in range(num_data_members): - if not capi.c_is_publicdata(self.handle, i): + def _find_datamembers(self): + num_datamembers = capi.c_num_datamembers(self) + for i in range(num_datamembers): + if not capi.c_is_publicdata(self, i): continue - data_member_name = capi.c_data_member_name(self.handle, i) - if not data_member_name in self.data_members: - self._make_data_member(data_member_name, i) + datamember_name = capi.c_datamember_name(self, i) + if not datamember_name in self.datamembers: + self._make_datamember(datamember_name, i) def find_overload(self, meth_name): # TODO: collect all overloads, not just the non-overloaded version - meth_idx = capi.c_method_index(self.handle, meth_name) + meth_idx = capi.c_method_index(self, meth_name) if meth_idx < 0: raise self.missing_attribute_error(meth_name) cppfunction = self._make_cppfunction(meth_idx) - overload = W_CPPOverload(self.space, self.handle, meth_name, [cppfunction]) - self.methods[meth_name] = overload + overload = W_CPPOverload(self.space, self, [cppfunction]) return overload - def find_data_member(self, dm_name): - dm_idx = capi.c_data_member_index(self.handle, dm_name) + def find_datamember(self, dm_name): + dm_idx = capi.c_datamember_index(self, dm_name) if dm_idx < 0: raise self.missing_attribute_error(dm_name) - data_member = self._make_data_member(dm_name, dm_idx) - return data_member + datamember = self._make_datamember(dm_name, dm_idx) + return datamember def update(self): self._find_methods() - self._find_data_members() + self._find_datamembers() def is_namespace(self): return self.space.w_True @@ -498,56 +507,55 @@ update = interp2app(W_CPPNamespace.update, unwrap_spec=['self']), get_method_names = interp2app(W_CPPNamespace.get_method_names, unwrap_spec=['self']), get_overload = interp2app(W_CPPNamespace.get_overload, unwrap_spec=['self', str]), - get_data_member_names = interp2app(W_CPPNamespace.get_data_member_names, unwrap_spec=['self']), - get_data_member = interp2app(W_CPPNamespace.get_data_member, unwrap_spec=['self', str]), + get_datamember_names = interp2app(W_CPPNamespace.get_datamember_names, unwrap_spec=['self']), + get_datamember = interp2app(W_CPPNamespace.get_datamember, unwrap_spec=['self', str]), is_namespace = interp2app(W_CPPNamespace.is_namespace, unwrap_spec=['self']), ) W_CPPNamespace.typedef.acceptable_as_base_class = False -class W_CPPType(W_CPPScope): +class W_CPPClass(W_CPPScope): _immutable_ = True - kind = "class" def _make_cppfunction(self, method_index): - result_type = capi.c_method_result_type(self.handle, method_index) - num_args = capi.c_method_num_args(self.handle, method_index) - args_required = capi.c_method_req_args(self.handle, method_index) + result_type = capi.c_method_result_type(self, method_index) + num_args = capi.c_method_num_args(self, method_index) + args_required = capi.c_method_req_args(self, method_index) arg_defs = [] for i in range(num_args): - arg_type = capi.c_method_arg_type(self.handle, method_index, i) - arg_dflt = capi.c_method_arg_default(self.handle, method_index, i) + arg_type = capi.c_method_arg_type(self, method_index, i) + arg_dflt = capi.c_method_arg_default(self, method_index, i) arg_defs.append((arg_type, arg_dflt)) - if capi.c_is_constructor(self.handle, method_index): + if capi.c_is_constructor(self, method_index): result_type = "constructor" cls = CPPConstructor - elif capi.c_is_staticmethod(self.handle, method_index): + elif capi.c_is_staticmethod(self, method_index): cls = CPPFunction else: cls = CPPMethod - return cls(self, method_index, result_type, arg_defs, args_required) + return cls(self.space, self, method_index, result_type, arg_defs, args_required) - def _find_data_members(self): - num_data_members = capi.c_num_data_members(self.handle) - for i in range(num_data_members): - if not capi.c_is_publicdata(self.handle, i): + def _find_datamembers(self): + num_datamembers = capi.c_num_datamembers(self) + for i in range(num_datamembers): + if not capi.c_is_publicdata(self, i): continue - data_member_name = capi.c_data_member_name(self.handle, i) - type_name = capi.c_data_member_type(self.handle, i) - offset = capi.c_data_member_offset(self.handle, i) - is_static = bool(capi.c_is_staticdata(self.handle, i)) - data_member = W_CPPDataMember(self.space, self.handle, type_name, offset, is_static) - self.data_members[data_member_name] = data_member + datamember_name = capi.c_datamember_name(self, i) + type_name = capi.c_datamember_type(self, i) + offset = capi.c_datamember_offset(self, i) + is_static = bool(capi.c_is_staticdata(self, i)) + datamember = W_CPPDataMember(self.space, self, type_name, offset, is_static) + self.datamembers[datamember_name] = datamember def find_overload(self, name): raise self.missing_attribute_error(name) - def find_data_member(self, name): + def find_datamember(self, name): raise self.missing_attribute_error(name) - def get_cppthis(self, cppinstance, scope_handle): - assert self.handle == cppinstance.cppclass.handle + def get_cppthis(self, cppinstance, calling_scope): + assert self == cppinstance.cppclass return cppinstance.get_rawobject() def is_namespace(self): @@ -555,59 +563,59 @@ def get_base_names(self): bases = [] - num_bases = capi.c_num_bases(self.handle) + num_bases = capi.c_num_bases(self) for i in range(num_bases): - base_name = capi.c_base_name(self.handle, i) + base_name = capi.c_base_name(self, i) bases.append(self.space.wrap(base_name)) return self.space.newlist(bases) -W_CPPType.typedef = TypeDef( - 'CPPType', - type_name = interp_attrproperty('name', W_CPPType), - get_base_names = interp2app(W_CPPType.get_base_names, unwrap_spec=['self']), - get_method_names = interp2app(W_CPPType.get_method_names, unwrap_spec=['self']), - get_overload = interp2app(W_CPPType.get_overload, unwrap_spec=['self', str]), - get_data_member_names = interp2app(W_CPPType.get_data_member_names, unwrap_spec=['self']), - get_data_member = interp2app(W_CPPType.get_data_member, unwrap_spec=['self', str]), - is_namespace = interp2app(W_CPPType.is_namespace, unwrap_spec=['self']), +W_CPPClass.typedef = TypeDef( + 'CPPClass', + type_name = interp_attrproperty('name', W_CPPClass), + get_base_names = interp2app(W_CPPClass.get_base_names, unwrap_spec=['self']), + get_method_names = interp2app(W_CPPClass.get_method_names, unwrap_spec=['self']), + get_overload = interp2app(W_CPPClass.get_overload, unwrap_spec=['self', str]), + get_datamember_names = interp2app(W_CPPClass.get_datamember_names, unwrap_spec=['self']), + get_datamember = interp2app(W_CPPClass.get_datamember, unwrap_spec=['self', str]), + is_namespace = interp2app(W_CPPClass.is_namespace, unwrap_spec=['self']), ) -W_CPPType.typedef.acceptable_as_base_class = False +W_CPPClass.typedef.acceptable_as_base_class = False -class W_ComplexCPPType(W_CPPType): +class W_ComplexCPPClass(W_CPPClass): _immutable_ = True - def get_cppthis(self, cppinstance, scope_handle): - assert self.handle == cppinstance.cppclass.handle - offset = capi.c_base_offset(self.handle, scope_handle, cppinstance.get_rawobject()) + def get_cppthis(self, cppinstance, calling_scope): + assert self == cppinstance.cppclass + offset = capi.c_base_offset(self, calling_scope, cppinstance.get_rawobject()) return capi.direct_ptradd(cppinstance.get_rawobject(), offset) -W_ComplexCPPType.typedef = TypeDef( - 'ComplexCPPType', - type_name = interp_attrproperty('name', W_CPPType), - get_base_names = interp2app(W_ComplexCPPType.get_base_names, unwrap_spec=['self']), - get_method_names = interp2app(W_ComplexCPPType.get_method_names, unwrap_spec=['self']), - get_overload = interp2app(W_ComplexCPPType.get_overload, unwrap_spec=['self', str]), - get_data_member_names = interp2app(W_ComplexCPPType.get_data_member_names, unwrap_spec=['self']), - get_data_member = interp2app(W_ComplexCPPType.get_data_member, unwrap_spec=['self', str]), - is_namespace = interp2app(W_ComplexCPPType.is_namespace, unwrap_spec=['self']), +W_ComplexCPPClass.typedef = TypeDef( + 'ComplexCPPClass', + type_name = interp_attrproperty('name', W_CPPClass), + get_base_names = interp2app(W_ComplexCPPClass.get_base_names, unwrap_spec=['self']), + get_method_names = interp2app(W_ComplexCPPClass.get_method_names, unwrap_spec=['self']), + get_overload = interp2app(W_ComplexCPPClass.get_overload, unwrap_spec=['self', str]), + get_datamember_names = interp2app(W_ComplexCPPClass.get_datamember_names, unwrap_spec=['self']), + get_datamember = interp2app(W_ComplexCPPClass.get_datamember, unwrap_spec=['self', str]), + is_namespace = interp2app(W_ComplexCPPClass.is_namespace, unwrap_spec=['self']), ) -W_ComplexCPPType.typedef.acceptable_as_base_class = False +W_ComplexCPPClass.typedef.acceptable_as_base_class = False class W_CPPTemplateType(Wrappable): _immutable_ = True - def __init__(self, space, name, handle): + def __init__(self, space, name, opaque_handle): self.space = space self.name = name - assert lltype.typeOf(handle) == capi.C_TYPE - self.handle = handle + assert lltype.typeOf(opaque_handle) == capi.C_TYPE + self.handle = opaque_handle def __call__(self, args_w): # TODO: this is broken but unused (see pythonify.py) fullname = "".join([self.name, '<', self.space.str_w(args_w[0]), '>']) - return type_byname(self.space, fullname) + return scope_byname(self.space, fullname) W_CPPTemplateType.typedef = TypeDef( 'CPPTemplateType', @@ -621,7 +629,6 @@ def __init__(self, space, cppclass, rawobject, isref, python_owns): self.space = space - assert isinstance(cppclass, W_CPPType) self.cppclass = cppclass assert lltype.typeOf(rawobject) == capi.C_OBJECT assert not isref or rawobject @@ -635,6 +642,9 @@ raise OperationError(self.space.w_ReferenceError, self.space.wrap("trying to access a NULL pointer")) + def get_cppthis(self, calling_scope): + return self.cppclass.get_cppthis(self, calling_scope) + def get_rawobject(self): if not self.isref: return self._rawobject @@ -659,7 +669,7 @@ assert isinstance(self, W_CPPInstance) if self._rawobject and not self.isref: memory_regulator.unregister(self) - capi.c_destruct(self.cppclass.handle, self._rawobject) + capi.c_destruct(self.cppclass, self._rawobject) self._rawobject = capi.C_NULL_OBJECT def __del__(self): @@ -704,40 +714,40 @@ memory_regulator = MemoryRegulator() -def get_wrapped_type(space, handle): +def get_pythonized_cppclass(space, handle): state = space.fromcache(State) try: - w_type = state.type_registry[handle] + w_pycppclass = state.cppclass_registry[handle] except KeyError: final_name = capi.c_scoped_final_name(handle) - w_type = space.call_function(state.w_clgen_callback, space.wrap(final_name)) - return w_type + w_pycppclass = space.call_function(state.w_clgen_callback, space.wrap(final_name)) + return w_pycppclass -def wrap_new_cppobject_nocast(space, w_type, cpptype, rawobject, isref, python_owns): - if w_type is None: - w_type = get_wrapped_type(space, cpptype.handle) - w_cppinstance = space.allocate_instance(W_CPPInstance, w_type) +def wrap_new_cppobject_nocast(space, w_pycppclass, cppclass, rawobject, isref, python_owns): + if space.is_w(w_pycppclass, space.w_None): + w_pycppclass = get_pythonized_cppclass(space, cppclass.handle) + w_cppinstance = space.allocate_instance(W_CPPInstance, w_pycppclass) cppinstance = space.interp_w(W_CPPInstance, w_cppinstance, can_be_None=False) - W_CPPInstance.__init__(cppinstance, space, cpptype, rawobject, isref, python_owns) + W_CPPInstance.__init__(cppinstance, space, cppclass, rawobject, isref, python_owns) memory_regulator.register(cppinstance) return w_cppinstance -def wrap_cppobject_nocast(space, w_type, cpptype, rawobject, isref, python_owns): +def wrap_cppobject_nocast(space, w_pycppclass, cppclass, rawobject, isref, python_owns): obj = memory_regulator.retrieve(rawobject) - if obj and obj.cppclass == cpptype: - return obj - return wrap_new_cppobject_nocast(space, w_type, cpptype, rawobject, isref, python_owns) + if obj and obj.cppclass == cppclass: + return obj + return wrap_new_cppobject_nocast(space, w_pycppclass, cppclass, rawobject, isref, python_owns) -def wrap_cppobject(space, w_type, cpptype, rawobject, isref, python_owns): +def wrap_cppobject(space, w_pycppclass, cppclass, rawobject, isref, python_owns): if rawobject: - actual = capi.c_get_object_type(cpptype.handle, rawobject) - if actual != cpptype.handle: - offset = capi.c_base_offset(actual, cpptype.handle, rawobject) + actual = capi.c_actual_class(cppclass, rawobject) + if actual != cppclass.handle: + offset = capi._c_base_offset(actual, cppclass.handle, rawobject) rawobject = capi.direct_ptradd(rawobject, offset) - w_type = get_wrapped_type(space, actual) - w_cpptype = space.findattr(w_type, space.wrap("_cpp_proxy")) - cpptype = space.interp_w(W_CPPType, w_cpptype, can_be_None=False) - return wrap_cppobject_nocast(space, w_type, cpptype, rawobject, isref, python_owns) + w_pycppclass = get_pythonized_cppclass(space, actual) + w_cppclass = space.findattr(w_pycppclass, space.wrap("_cpp_proxy")) + cppclass = space.interp_w(W_CPPClass, w_cppclass, can_be_None=False) + return wrap_cppobject_nocast(space, w_pycppclass, cppclass, rawobject, isref, python_owns) @unwrap_spec(cppinstance=W_CPPInstance) def addressof(space, cppinstance): @@ -745,8 +755,8 @@ return space.wrap(address) @unwrap_spec(address=int, owns=bool) -def bind_object(space, address, w_type, owns=False): +def bind_object(space, address, w_pycppclass, owns=False): rawobject = rffi.cast(capi.C_OBJECT, address) - w_cpptype = space.findattr(w_type, space.wrap("_cpp_proxy")) - cpptype = space.interp_w(W_CPPType, w_cpptype, can_be_None=False) - return wrap_cppobject_nocast(space, w_type, cpptype, rawobject, False, owns) + w_cppclass = space.findattr(w_pycppclass, space.wrap("_cpp_proxy")) + cppclass = space.interp_w(W_CPPClass, w_cppclass, can_be_None=False) + return wrap_cppobject_nocast(space, w_pycppclass, cppclass, rawobject, False, owns) diff --git a/pypy/module/cppyy/pythonify.py b/pypy/module/cppyy/pythonify.py --- a/pypy/module/cppyy/pythonify.py +++ b/pypy/module/cppyy/pythonify.py @@ -9,8 +9,8 @@ class CppyyScopeMeta(type): def __getattr__(self, name): try: - return get_cppitem(self, name) # will cache on self - except TypeError: + return get_pycppitem(self, name) # will cache on self + except TypeError, t: raise AttributeError("%s object has no attribute '%s'" % (self, name)) class CppyyNamespaceMeta(CppyyScopeMeta): @@ -44,10 +44,10 @@ def clgen_callback(name): - return get_cppclass(name) + return get_pycppclass(name) cppyy._set_class_generator(clgen_callback) -def make_static_function(cpptype, func_name, cppol): +def make_static_function(func_name, cppol): def function(*args): return cppol.call(None, *args) function.__name__ = func_name @@ -61,13 +61,13 @@ return method -def make_data_member(cppdm): +def make_datamember(cppdm): rettype = cppdm.get_returntype() if not rettype: # return builtin type cppclass = None else: # return instance try: - cppclass = get_cppclass(rettype) + cppclass = get_pycppclass(rettype) except AttributeError: import warnings warnings.warn("class %s unknown: no data member access" % rettype, @@ -96,7 +96,7 @@ else: d = dict() def cpp_proxy_loader(cls): - cpp_proxy = cppyy._type_byname(cls.__name__ != '::' and cls.__name__ or '') + cpp_proxy = cppyy._scope_byname(cls.__name__ != '::' and cls.__name__ or '') del cls.__class__._cpp_proxy cls._cpp_proxy = cpp_proxy return cpp_proxy @@ -111,14 +111,14 @@ # insert static methods into the "namespace" dictionary for func_name in cppns.get_method_names(): cppol = cppns.get_overload(func_name) - pyfunc = make_static_function(cppns, func_name, cppol) + pyfunc = make_static_function(func_name, cppol) setattr(pycppns, func_name, pyfunc) # add all data members to the dictionary of the class to be created, and # static ones also to the meta class (needed for property setters) - for dm in cppns.get_data_member_names(): - cppdm = cppns.get_data_member(dm) - pydm = make_data_member(cppdm) + for dm in cppns.get_datamember_names(): + cppdm = cppns.get_datamember(dm) + pydm = make_datamember(cppdm) setattr(pycppns, dm, pydm) setattr(metans, dm, pydm) @@ -133,9 +133,9 @@ break return tuple(bases) -def make_new(class_name, cpptype): +def make_new(class_name, cppclass): try: - constructor_overload = cpptype.get_overload(cpptype.type_name) + constructor_overload = cppclass.get_overload(cppclass.type_name) except AttributeError: msg = "cannot instantiate abstract class '%s'" % class_name def __new__(cls, *args): @@ -145,10 +145,10 @@ return constructor_overload.call(None, *args) return __new__ -def make_cppclass(scope, class_name, final_class_name, cpptype): +def make_pycppclass(scope, class_name, final_class_name, cppclass): # get a list of base classes for class creation - bases = [get_cppclass(base) for base in cpptype.get_base_names()] + bases = [get_pycppclass(base) for base in cppclass.get_base_names()] if not bases: bases = [CPPObject,] else: @@ -164,57 +164,57 @@ metacpp = type(CppyyClass)(class_name+'_meta', _drop_cycles(metabases), {}) # create the python-side C++ class representation - d = {"_cpp_proxy" : cpptype, - "__new__" : make_new(class_name, cpptype), + d = {"_cpp_proxy" : cppclass, + "__new__" : make_new(class_name, cppclass), } - pycpptype = metacpp(class_name, _drop_cycles(bases), d) + pycppclass = metacpp(class_name, _drop_cycles(bases), d) # cache result early so that the class methods can find the class itself - setattr(scope, final_class_name, pycpptype) + setattr(scope, final_class_name, pycppclass) # insert (static) methods into the class dictionary - for meth_name in cpptype.get_method_names(): - cppol = cpptype.get_overload(meth_name) + for meth_name in cppclass.get_method_names(): + cppol = cppclass.get_overload(meth_name) if cppol.is_static(): - setattr(pycpptype, meth_name, make_static_function(cpptype, meth_name, cppol)) + setattr(pycppclass, meth_name, make_static_function(meth_name, cppol)) else: - setattr(pycpptype, meth_name, make_method(meth_name, cppol)) + setattr(pycppclass, meth_name, make_method(meth_name, cppol)) # add all data members to the dictionary of the class to be created, and # static ones also to the meta class (needed for property setters) - for dm_name in cpptype.get_data_member_names(): - cppdm = cpptype.get_data_member(dm_name) - pydm = make_data_member(cppdm) + for dm_name in cppclass.get_datamember_names(): + cppdm = cppclass.get_datamember(dm_name) + pydm = make_datamember(cppdm) - setattr(pycpptype, dm_name, pydm) + setattr(pycppclass, dm_name, pydm) if cppdm.is_static(): setattr(metacpp, dm_name, pydm) - _pythonize(pycpptype) - cppyy._register_class(pycpptype) - return pycpptype + _pythonize(pycppclass) + cppyy._register_class(pycppclass) + return pycppclass def make_cpptemplatetype(scope, template_name): return CppyyTemplateType(scope, template_name) -def get_cppitem(scope, name): +def get_pycppitem(scope, name): # resolve typedefs/aliases full_name = (scope == gbl) and name or (scope.__name__+'::'+name) true_name = cppyy._resolve_name(full_name) if true_name != full_name: - return get_cppclass(true_name) + return get_pycppclass(true_name) pycppitem = None # classes - cppitem = cppyy._type_byname(true_name) + cppitem = cppyy._scope_byname(true_name) if cppitem: if cppitem.is_namespace(): pycppitem = make_cppnamespace(scope, true_name, cppitem) setattr(scope, name, pycppitem) else: - pycppitem = make_cppclass(scope, true_name, name, cppitem) + pycppitem = make_pycppclass(scope, true_name, name, cppitem) # templates if not cppitem: @@ -227,7 +227,7 @@ if not cppitem: try: cppitem = scope._cpp_proxy.get_overload(name) - pycppitem = make_static_function(scope._cpp_proxy, name, cppitem) + pycppitem = make_static_function(name, cppitem) setattr(scope.__class__, name, pycppitem) pycppitem = getattr(scope, name) # binds function as needed except AttributeError: @@ -236,8 +236,8 @@ # data if not cppitem: try: - cppitem = scope._cpp_proxy.get_data_member(name) - pycppitem = make_data_member(cppitem) + cppitem = scope._cpp_proxy.get_datamember(name) + pycppitem = make_datamember(cppitem) setattr(scope, name, pycppitem) if cppitem.is_static(): setattr(scope.__class__, name, pycppitem) @@ -266,7 +266,7 @@ scope += c yield scope -def get_cppclass(name): +def get_pycppclass(name): # break up the name, to walk the scopes and get the class recursively scope = gbl for part in scope_splitter(name): @@ -367,7 +367,7 @@ return dct -# user interface objects (note the two-step of not calling type_byname here: +# user interface objects (note the two-step of not calling scope_byname here: # creation of global functions may cause the creation of classes in the global # namespace, so gbl must exist at that point to cache them) gbl = make_cppnamespace(None, "::", None, False) # global C++ namespace diff --git a/pypy/module/cppyy/src/cintcwrapper.cxx b/pypy/module/cppyy/src/cintcwrapper.cxx --- a/pypy/module/cppyy/src/cintcwrapper.cxx +++ b/pypy/module/cppyy/src/cintcwrapper.cxx @@ -231,7 +231,7 @@ return (cppyy_type_t)sz; } -cppyy_type_t cppyy_get_object_type(cppyy_type_t klass, cppyy_object_t obj) { +cppyy_type_t cppyy_actual_class(cppyy_type_t klass, cppyy_object_t obj) { TClassRef cr = type_from_handle(klass); TClass* clActual = cr->GetActualClass( (void*)obj ); if (clActual && clActual != cr.GetClass()) { @@ -605,7 +605,7 @@ /* data member reflection information ------------------------------------- */ -int cppyy_num_data_members(cppyy_scope_t handle) { +int cppyy_num_datamembers(cppyy_scope_t handle) { TClassRef cr = type_from_handle(handle); if (cr.GetClass() && cr->GetListOfDataMembers()) return cr->GetListOfDataMembers()->GetSize(); @@ -627,20 +627,20 @@ return 0; } -char* cppyy_data_member_name(cppyy_scope_t handle, int data_member_index) { +char* cppyy_datamember_name(cppyy_scope_t handle, int datamember_index) { TClassRef cr = type_from_handle(handle); if (cr.GetClass()) { - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At(data_member_index); + TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At(datamember_index); return cppstring_to_cstring(m->GetName()); } - TGlobal& gbl = g_globalvars[data_member_index]; + TGlobal& gbl = g_globalvars[datamember_index]; return cppstring_to_cstring(gbl.GetName()); } -char* cppyy_data_member_type(cppyy_scope_t handle, int data_member_index) { +char* cppyy_datamember_type(cppyy_scope_t handle, int datamember_index) { TClassRef cr = type_from_handle(handle); if (cr.GetClass()) { - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At(data_member_index); + TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At(datamember_index); std::string fullType = m->GetFullTypeName(); if ((int)m->GetArrayDim() > 1 || (!m->IsBasic() && m->IsaPointer())) fullType.append("*"); @@ -651,21 +651,21 @@ } return cppstring_to_cstring(fullType); } - TGlobal& gbl = g_globalvars[data_member_index]; + TGlobal& gbl = g_globalvars[datamember_index]; return cppstring_to_cstring(gbl.GetFullTypeName()); } -size_t cppyy_data_member_offset(cppyy_scope_t handle, int data_member_index) { +size_t cppyy_datamember_offset(cppyy_scope_t handle, int datamember_index) { TClassRef cr = type_from_handle(handle); if (cr.GetClass()) { - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At(data_member_index); + TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At(datamember_index); return (size_t)m->GetOffsetCint(); } - TGlobal& gbl = g_globalvars[data_member_index]; + TGlobal& gbl = g_globalvars[datamember_index]; return (size_t)gbl.GetAddress(); } -int cppyy_data_member_index(cppyy_scope_t handle, const char* name) { +int cppyy_datamember_index(cppyy_scope_t handle, const char* name) { TClassRef cr = type_from_handle(handle); if (cr.GetClass()) { // called from updates; add a hard reset as the code itself caches in @@ -700,19 +700,19 @@ /* data member properties ------------------------------------------------ */ -int cppyy_is_publicdata(cppyy_scope_t handle, int data_member_index) { +int cppyy_is_publicdata(cppyy_scope_t handle, int datamember_index) { TClassRef cr = type_from_handle(handle); if (cr.GetClass()) { - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At(data_member_index); + TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At(datamember_index); return m->Property() & G__BIT_ISPUBLIC; } return 1; // global data is always public } -int cppyy_is_staticdata(cppyy_scope_t handle, int data_member_index) { +int cppyy_is_staticdata(cppyy_scope_t handle, int datamember_index) { TClassRef cr = type_from_handle(handle); if (cr.GetClass()) { - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At(data_member_index); + TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At(datamember_index); return m->Property() & G__BIT_ISSTATIC; } return 1; // global data is always static diff --git a/pypy/module/cppyy/src/reflexcwrapper.cxx b/pypy/module/cppyy/src/reflexcwrapper.cxx --- a/pypy/module/cppyy/src/reflexcwrapper.cxx +++ b/pypy/module/cppyy/src/reflexcwrapper.cxx @@ -71,7 +71,7 @@ return (cppyy_type_t)tt.Id(); } -cppyy_type_t cppyy_get_object_type(cppyy_type_t klass, cppyy_object_t obj) { +cppyy_type_t cppyy_actual_class(cppyy_type_t klass, cppyy_object_t obj) { Reflex::Type t = type_from_handle(klass); Reflex::Type tActual = t.DynamicType(Reflex::Object(t, (void*)obj)); if (tActual && tActual != t) { @@ -414,7 +414,7 @@ /* data member reflection information ------------------------------------- */ -int cppyy_num_data_members(cppyy_scope_t handle) { +int cppyy_num_datamembers(cppyy_scope_t handle) { Reflex::Scope s = scope_from_handle(handle); // fix enum representation by adding them to the containing scope as per C++ // TODO: this (relatively harmlessly) dupes data members when updating in the @@ -433,34 +433,34 @@ return s.DataMemberSize(); } -char* cppyy_data_member_name(cppyy_scope_t handle, int data_member_index) { +char* cppyy_datamember_name(cppyy_scope_t handle, int datamember_index) { Reflex::Scope s = scope_from_handle(handle); - Reflex::Member m = s.DataMemberAt(data_member_index); + Reflex::Member m = s.DataMemberAt(datamember_index); std::string name = m.Name(); return cppstring_to_cstring(name); } -char* cppyy_data_member_type(cppyy_scope_t handle, int data_member_index) { +char* cppyy_datamember_type(cppyy_scope_t handle, int datamember_index) { Reflex::Scope s = scope_from_handle(handle); - Reflex::Member m = s.DataMemberAt(data_member_index); + Reflex::Member m = s.DataMemberAt(datamember_index); std::string name = m.TypeOf().Name(Reflex::FINAL|Reflex::SCOPED|Reflex::QUALIFIED); return cppstring_to_cstring(name); } -size_t cppyy_data_member_offset(cppyy_scope_t handle, int data_member_index) { +size_t cppyy_datamember_offset(cppyy_scope_t handle, int datamember_index) { Reflex::Scope s = scope_from_handle(handle); - Reflex::Member m = s.DataMemberAt(data_member_index); + Reflex::Member m = s.DataMemberAt(datamember_index); if (m.IsArtificial() && m.TypeOf().IsEnum()) return (size_t)&m.InterpreterOffset(); return m.Offset(); } -int cppyy_data_member_index(cppyy_scope_t handle, const char* name) { +int cppyy_datamember_index(cppyy_scope_t handle, const char* name) { Reflex::Scope s = scope_from_handle(handle); // the following appears dumb, but the internal storage for Reflex is an // unsorted std::vector anyway, so there's no gain to be had in using the // Scope::DataMemberByName() function (which returns Member, not an index) - int num_dm = cppyy_num_data_members(handle); + int num_dm = cppyy_num_datamembers(handle); for (int idm = 0; idm < num_dm; ++idm) { Reflex::Member m = s.DataMemberAt(idm); if (m.Name() == name || m.Name(Reflex::FINAL) == name) { @@ -474,15 +474,15 @@ /* data member properties ------------------------------------------------ */ -int cppyy_is_publicdata(cppyy_scope_t handle, int data_member_index) { +int cppyy_is_publicdata(cppyy_scope_t handle, int datamember_index) { Reflex::Scope s = scope_from_handle(handle); - Reflex::Member m = s.DataMemberAt(data_member_index); + Reflex::Member m = s.DataMemberAt(datamember_index); return m.IsPublic(); } -int cppyy_is_staticdata(cppyy_scope_t handle, int data_member_index) { +int cppyy_is_staticdata(cppyy_scope_t handle, int datamember_index) { Reflex::Scope s = scope_from_handle(handle); - Reflex::Member m = s.DataMemberAt(data_member_index); + Reflex::Member m = s.DataMemberAt(datamember_index); return m.IsStatic(); } diff --git a/pypy/module/cppyy/test/bench1.py b/pypy/module/cppyy/test/bench1.py --- a/pypy/module/cppyy/test/bench1.py +++ b/pypy/module/cppyy/test/bench1.py @@ -54,7 +54,7 @@ import cppyy self.lib = cppyy.load_reflection_info("./example01Dict.so") - self.cls = cppyy._type_byname("example01") + self.cls = cppyy._scope_byname("example01") self.inst = self.cls.get_overload(self.cls.type_name).call(None, 0) def __call__(self): diff --git a/pypy/module/cppyy/test/test_advancedcpp.py b/pypy/module/cppyy/test/test_advancedcpp.py --- a/pypy/module/cppyy/test/test_advancedcpp.py +++ b/pypy/module/cppyy/test/test_advancedcpp.py @@ -228,7 +228,7 @@ assert isinstance(c, gbl.some_concrete_class) assert isinstance(c, gbl.some_abstract_class) - def test06_data_members(self): + def test06_datamembers(self): """Test data member access when using virtual inheritence""" import cppyy diff --git a/pypy/module/cppyy/test/test_cppyy.py b/pypy/module/cppyy/test/test_cppyy.py --- a/pypy/module/cppyy/test/test_cppyy.py +++ b/pypy/module/cppyy/test/test_cppyy.py @@ -16,13 +16,15 @@ raise OSError("'make' failed (see stderr)") class TestCPPYYImplementation: - def test_class_query(self): + def test01_class_query(self): dct = interp_cppyy.load_dictionary(space, test_dct) - w_cppyyclass = interp_cppyy.type_byname(space, "example01") - w_cppyyclass2 = interp_cppyy.type_byname(space, "example01") + w_cppyyclass = interp_cppyy.scope_byname(space, "example01") + w_cppyyclass2 = interp_cppyy.scope_byname(space, "example01") assert space.is_w(w_cppyyclass, w_cppyyclass2) adddouble = w_cppyyclass.methods["staticAddToDouble"] func, = adddouble.functions + assert func.executor is None + func._setup(None) # creates executor assert isinstance(func.executor, executor.DoubleExecutor) assert func.arg_defs == [("double", "")] @@ -34,7 +36,7 @@ cls.w_example01, cls.w_payload = cls.space.unpackiterable(cls.space.appexec([], """(): import cppyy cppyy.load_reflection_info(%r) - return cppyy._type_byname('example01'), cppyy._type_byname('payload')""" % (test_dct, ))) + return cppyy._scope_byname('example01'), cppyy._scope_byname('payload')""" % (test_dct, ))) def test01_static_int(self): """Test passing of an int, returning of an int, and overloading on a diff --git a/pypy/module/cppyy/test/test_fragile.py b/pypy/module/cppyy/test/test_fragile.py --- a/pypy/module/cppyy/test/test_fragile.py +++ b/pypy/module/cppyy/test/test_fragile.py @@ -137,7 +137,7 @@ g = fragile.G() - def test08_unhandled_scoped_data_member(self): + def test08_unhandled_scoped_datamember(self): """Test that an unhandled scoped data member does not cause infinite recursion""" import cppyy @@ -190,4 +190,4 @@ j = fragile.J() assert fragile.J.method1.__doc__ == j.method1.__doc__ - assert j.method1.__doc__ == "fragile::J::method1(int, double)" + assert j.method1.__doc__ == "int fragile::J::method1(int, double)" diff --git a/pypy/module/cppyy/test/test_zjit.py b/pypy/module/cppyy/test/test_zjit.py --- a/pypy/module/cppyy/test/test_zjit.py +++ b/pypy/module/cppyy/test/test_zjit.py @@ -160,7 +160,7 @@ drv = jit.JitDriver(greens=[], reds=["i", "inst", "addDataToInt"]) def f(): lib = interp_cppyy.load_dictionary(space, "./example01Dict.so") - cls = interp_cppyy.type_byname(space, "example01") + cls = interp_cppyy.scope_byname(space, "example01") inst = cls.get_overload("example01").call(None, [FakeInt(0)]) addDataToInt = cls.get_overload("addDataToInt") assert isinstance(inst, interp_cppyy.W_CPPInstance) @@ -185,7 +185,7 @@ drv = jit.JitDriver(greens=[], reds=["i", "inst", "addDataToInt"]) def f(): lib = interp_cppyy.load_dictionary(space, "./example01Dict.so") - cls = interp_cppyy.type_byname(space, "example01") + cls = interp_cppyy.scope_byname(space, "example01") inst = cls.get_overload("example01").call(None, [FakeInt(0)]) addDataToInt = cls.get_overload("overloadedAddDataToInt") assert isinstance(inst, interp_cppyy.W_CPPInstance) From noreply at buildbot.pypy.org Thu Apr 5 10:23:23 2012 From: noreply at buildbot.pypy.org (arigo) Date: Thu, 5 Apr 2012 10:23:23 +0200 (CEST) Subject: [pypy-commit] pypy default: minimark: Tweaks to reduce by one the number of checks in the common Message-ID: <20120405082323.B71C08208A@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: Changeset: r54194:01a73d86d3c7 Date: 2012-04-04 19:23 +0200 http://bitbucket.org/pypy/pypy/changeset/01a73d86d3c7/ Log: minimark: Tweaks to reduce by one the number of checks in the common case. diff --git a/pypy/rpython/memory/gc/minimark.py b/pypy/rpython/memory/gc/minimark.py --- a/pypy/rpython/memory/gc/minimark.py +++ b/pypy/rpython/memory/gc/minimark.py @@ -1426,23 +1426,25 @@ self._visit_young_rawmalloced_object(obj) return # - # If 'obj' was already forwarded, change it to its forwarding address. - if self.is_forwarded(obj): + size_gc_header = self.gcheaderbuilder.size_gc_header + if self.header(obj).tid & GCFLAG_HAS_SHADOW == 0: + # + # Common case: 'obj' was not already forwarded (otherwise + # tid == -42, containing all flags), and it doesn't have the + # HAS_SHADOW flag either. We must move it out of the nursery, + # into a new nonmovable location. + totalsize = size_gc_header + self.get_size(obj) + newhdr = self._malloc_out_of_nursery(totalsize) + # + elif self.is_forwarded(obj): + # + # 'obj' was already forwarded. Change the original reference + # to point to its forwarding address, and we're done. root.address[0] = self.get_forwarding_address(obj) return - # - # First visit to 'obj': we must move it out of the nursery. - size_gc_header = self.gcheaderbuilder.size_gc_header - size = self.get_size(obj) - totalsize = size_gc_header + size - # - if self.header(obj).tid & GCFLAG_HAS_SHADOW == 0: - # - # Common case: allocate a new nonmovable location for it. - newhdr = self._malloc_out_of_nursery(totalsize) # else: - # The object has already a shadow. + # First visit to an object that has already a shadow. newobj = self.nursery_objects_shadows.get(obj) ll_assert(newobj != NULL, "GCFLAG_HAS_SHADOW but no shadow found") newhdr = newobj - size_gc_header @@ -1450,6 +1452,8 @@ # Remove the flag GCFLAG_HAS_SHADOW, so that it doesn't get # copied to the shadow itself. self.header(obj).tid &= ~GCFLAG_HAS_SHADOW + # + totalsize = size_gc_header + self.get_size(obj) # # Copy it. Note that references to other objects in the # nursery are kept unchanged in this step. From noreply at buildbot.pypy.org Thu Apr 5 10:23:25 2012 From: noreply at buildbot.pypy.org (arigo) Date: Thu, 5 Apr 2012 10:23:25 +0200 (CEST) Subject: [pypy-commit] pypy default: merge heads Message-ID: <20120405082325.99FE28208A@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: Changeset: r54195:057219807d73 Date: 2012-04-05 10:14 +0200 http://bitbucket.org/pypy/pypy/changeset/057219807d73/ Log: merge heads diff --git a/pypy/module/micronumpy/__init__.py b/pypy/module/micronumpy/__init__.py --- a/pypy/module/micronumpy/__init__.py +++ b/pypy/module/micronumpy/__init__.py @@ -105,6 +105,7 @@ ("fmod", "fmod"), ("floor", "floor"), ("ceil", "ceil"), + ("trunc", "trunc"), ("greater", "greater"), ("greater_equal", "greater_equal"), ("less", "less"), @@ -132,6 +133,8 @@ ('bitwise_or', 'bitwise_or'), ('bitwise_xor', 'bitwise_xor'), ('bitwise_not', 'invert'), + ('left_shift', 'left_shift'), + ('right_shift', 'right_shift'), ('invert', 'invert'), ('isnan', 'isnan'), ('isinf', 'isinf'), diff --git a/pypy/module/micronumpy/interp_ufuncs.py b/pypy/module/micronumpy/interp_ufuncs.py --- a/pypy/module/micronumpy/interp_ufuncs.py +++ b/pypy/module/micronumpy/interp_ufuncs.py @@ -546,6 +546,7 @@ ("fmod", "fmod", 2, {"promote_to_float": True}), ("floor", "floor", 1, {"promote_to_float": True}), ("ceil", "ceil", 1, {"promote_to_float": True}), + ("trunc", "trunc", 1, {"promote_to_float": True}), ("exp", "exp", 1, {"promote_to_float": True}), ("exp2", "exp2", 1, {"promote_to_float": True}), ("expm1", "expm1", 1, {"promote_to_float": True}), diff --git a/pypy/module/micronumpy/test/test_ufuncs.py b/pypy/module/micronumpy/test/test_ufuncs.py --- a/pypy/module/micronumpy/test/test_ufuncs.py +++ b/pypy/module/micronumpy/test/test_ufuncs.py @@ -253,24 +253,17 @@ for i in range(3): assert c[i] == a[i] - b[i] - def test_floorceil(self): - from _numpypy import array, floor, ceil + def test_floorceiltrunc(self): + from _numpypy import array, floor, ceil, trunc import math - reference = [-2.0, -2.0, -1.0, 0.0, 1.0, 1.0, 0] - a = array([-1.4, -1.5, -1.0, 0.0, 1.0, 1.4, 0.5]) - b = floor(a) - for i in range(5): - assert b[i] == reference[i] - reference = [-1.0, -1.0, -1.0, 0.0, 1.0, 2.0, 1.0] - a = array([-1.4, -1.5, -1.0, 0.0, 1.0, 1.4, 0.5]) - b = ceil(a) - assert (reference == b).all() - inf = float("inf") - data = [1.5, 2.9999, -1.999, inf] - results = [math.floor(x) for x in data] - assert (floor(data) == results).all() - results = [math.ceil(x) for x in data] - assert (ceil(data) == results).all() + ninf, inf = float("-inf"), float("inf") + a = array([ninf, -1.4, -1.5, -1.0, 0.0, 1.0, 1.4, 0.5, inf]) + assert ([ninf, -2.0, -2.0, -1.0, 0.0, 1.0, 1.0, 0.0, inf] == floor(a)).all() + assert ([ninf, -1.0, -1.0, -1.0, 0.0, 1.0, 2.0, 1.0, inf] == ceil(a)).all() + assert ([ninf, -1.0, -1.0, -1.0, 0.0, 1.0, 1.0, 0.0, inf] == trunc(a)).all() + assert all([math.isnan(f(float("nan"))) for f in floor, ceil, trunc]) + assert all([math.copysign(1, f(float("nan"))) == 1 for f in floor, ceil, trunc]) + assert all([math.copysign(1, f(float("-nan"))) == -1 for f in floor, ceil, trunc]) def test_copysign(self): from _numpypy import array, copysign @@ -597,6 +590,13 @@ assert (bitwise_not(a) == ~a).all() assert (invert(a) == ~a).all() + def test_shift(self): + from _numpypy import left_shift, right_shift + import sys + + assert (left_shift([5, 1], [2, 31]) == [20, 2**31]).all() + assert (right_shift(10, range(5)) == [10, 5, 2, 1, 0]).all() + def test_comparisons(self): import operator from _numpypy import equal, not_equal, less, less_equal, greater, greater_equal diff --git a/pypy/module/micronumpy/types.py b/pypy/module/micronumpy/types.py --- a/pypy/module/micronumpy/types.py +++ b/pypy/module/micronumpy/types.py @@ -668,6 +668,13 @@ return math.ceil(v) @simple_unary_op + def trunc(self, v): + if v < 0: + return math.ceil(v) + else: + return math.floor(v) + + @simple_unary_op def exp(self, v): try: return math.exp(v) diff --git a/pypy/rlib/rsre/rsre_re.py b/pypy/rlib/rsre/rsre_re.py --- a/pypy/rlib/rsre/rsre_re.py +++ b/pypy/rlib/rsre/rsre_re.py @@ -172,8 +172,9 @@ self._ctx = ctx def span(self, groupnum=0): - if not isinstance(groupnum, (int, long)): - groupnum = self.re.groupindex[groupnum] +# if not isinstance(groupnum, (int, long)): +# groupnum = self.re.groupindex[groupnum] + return self._ctx.span(groupnum) def start(self, groupnum=0): @@ -182,19 +183,25 @@ def end(self, groupnum=0): return self.span(groupnum)[1] - def group(self, *groups): - groups = groups or (0,) - result = [] - for group in groups: - frm, to = self.span(group) - if 0 <= frm <= to: - result.append(self._ctx._string[frm:to]) - else: - result.append(None) - if len(result) > 1: - return tuple(result) + def group(self, group=0): + frm, to = self.span(group) + if 0 <= frm <= to: + return self._ctx._string[frm:to] else: - return result[0] + return None + +# def group(self, *groups): +# groups = groups or (0,) +# result = [] +# for group in groups: +# frm, to = self.span(group) +# if 0 <= frm <= to: +# result.append(self._ctx._string[frm:to]) +# else: +# result.append(None) +# if len(result) > 1: +# return tuple(result) + def groups(self, default=None): fmarks = self._ctx.flatten_marks() diff --git a/pypy/rlib/rsre/test/test_re.py b/pypy/rlib/rsre/test/test_re.py --- a/pypy/rlib/rsre/test/test_re.py +++ b/pypy/rlib/rsre/test/test_re.py @@ -204,7 +204,7 @@ assert re.match('(a)', 'a').groups() == ('a',) assert re.match(r'(a)', 'a').group(0) == 'a' assert re.match(r'(a)', 'a').group(1) == 'a' - assert re.match(r'(a)', 'a').group(1, 1) == ('a', 'a') + #assert re.match(r'(a)', 'a').group(1, 1) == ('a', 'a') pat = re.compile('((a)|(b))(c)?') assert pat.match('a').groups() == ('a', 'a', None, None) @@ -218,13 +218,13 @@ assert m.group(0) == 'a' assert m.group(0) == 'a' assert m.group(1) == 'a' - assert m.group(1, 1) == ('a', 'a') + #assert m.group(1, 1) == ('a', 'a') pat = re.compile('(?:(?Pa)|(?Pb))(?Pc)?') - assert pat.match('a').group(1, 2, 3) == ('a', None, None) - assert pat.match('b').group('a1', 'b2', 'c3') == ( - (None, 'b', None)) - assert pat.match('ac').group(1, 'b2', 3) == ('a', None, 'c') + #assert pat.match('a').group(1, 2, 3) == ('a', None, None) + #assert pat.match('b').group('a1', 'b2', 'c3') == ( + # (None, 'b', None)) + #assert pat.match('ac').group(1, 'b2', 3) == ('a', None, 'c') def test_bug_923(self): # Issue923: grouping inside optional lookahead problem diff --git a/pypy/rlib/rsre/test/test_zinterp.py b/pypy/rlib/rsre/test/test_zinterp.py --- a/pypy/rlib/rsre/test/test_zinterp.py +++ b/pypy/rlib/rsre/test/test_zinterp.py @@ -1,7 +1,8 @@ # minimal test: just checks that (parts of) rsre can be translated -from pypy.rpython.test.test_llinterp import gengraph +from pypy.rpython.test.test_llinterp import gengraph, interpret from pypy.rlib.rsre import rsre_core +from pypy.rlib.rsre.rsre_re import compile def main(n): assert n >= 0 @@ -19,3 +20,18 @@ def test_gengraph(): t, typer, graph = gengraph(main, [int]) + +m = compile("(a|b)aaaaa") + +def test_match(): + def f(i): + if i: + s = "aaaaaa" + else: + s = "caaaaa" + g = m.match(s) + if g is None: + return 3 + return int("aaaaaa" == g.group(0)) + assert interpret(f, [3]) == 1 + assert interpret(f, [0]) == 3 From noreply at buildbot.pypy.org Thu Apr 5 14:19:02 2012 From: noreply at buildbot.pypy.org (bivab) Date: Thu, 5 Apr 2012 14:19:02 +0200 (CEST) Subject: [pypy-commit] pypy arm-backend-2: combine all the mixins for operation implementations and change the assembler setup Message-ID: <20120405121902.B57E08208B@wyvern.cs.uni-duesseldorf.de> Author: David Schneider Branch: arm-backend-2 Changeset: r54197:3f8b478d181b Date: 2012-03-27 12:21 +0000 http://bitbucket.org/pypy/pypy/changeset/3f8b478d181b/ Log: combine all the mixins for operation implementations and change the assembler setup diff --git a/pypy/jit/backend/arm/assembler.py b/pypy/jit/backend/arm/assembler.py --- a/pypy/jit/backend/arm/assembler.py +++ b/pypy/jit/backend/arm/assembler.py @@ -14,6 +14,7 @@ from pypy.jit.backend.llsupport.asmmemmgr import MachineDataBlockWrapper from pypy.jit.backend.model import CompiledLoopToken from pypy.jit.codewriter import longlong +from pypy.jit.codewriter.effectinfo import EffectInfo from pypy.jit.metainterp.history import AbstractFailDescr, INT, REF, FLOAT from pypy.jit.metainterp.history import BoxInt, ConstInt from pypy.jit.metainterp.resoperation import rop, ResOperation @@ -1264,24 +1265,25 @@ asm_operations = [notimplemented_op] * (rop._LAST + 1) asm_operations_with_guard = [notimplemented_op_with_guard] * (rop._LAST + 1) +asm_math_operations = {} -for key, value in rop.__dict__.items(): - key = key.lower() - if key.startswith('_'): - continue - methname = 'emit_op_%s' % key - if hasattr(AssemblerARM, methname): - func = getattr(AssemblerARM, methname).im_func - asm_operations[value] = func - -for key, value in rop.__dict__.items(): - key = key.lower() - if key.startswith('_'): - continue - methname = 'emit_guard_%s' % key - if hasattr(AssemblerARM, methname): - func = getattr(AssemblerARM, methname).im_func - asm_operations_with_guard[value] = func +for name, value in ResOpAssembler.__dict__.iteritems(): + if name.startswith('emit_guard_'): + opname = name[len('emit_guard_'):] + num = getattr(rop, opname.upper()) + asm_operations_with_guard[num] = value + elif name.startswith('emit_op_llong_'): + opname = name[len('emit_op_llong_'):] + num = getattr(EffectInfo, 'OS_LLONG_' + opname.upper()) + asm_llong_operations[num] = value + elif name.startswith('emit_op_math_'): + opname = name[len('emit_op_math_'):] + num = getattr(EffectInfo, 'OS_MATH_' + opname.upper()) + asm_math_operations[num] = value + elif name.startswith('emit_op_'): + opname = name[len('emit_op_'):] + num = getattr(rop, opname.upper()) + asm_operations[num] = value class BridgeAlreadyCompiled(Exception): diff --git a/pypy/jit/backend/arm/opassembler.py b/pypy/jit/backend/arm/opassembler.py --- a/pypy/jit/backend/arm/opassembler.py +++ b/pypy/jit/backend/arm/opassembler.py @@ -45,9 +45,7 @@ self.fcond = fcond -class IntOpAsslember(object): - - _mixin_ = True +class ResOpAssembler(object): def emit_op_int_add(self, op, arglocs, regalloc, fcond, flags=False): l0, l1, res = arglocs @@ -162,10 +160,6 @@ emit_op_int_sub_ovf = emit_op_int_sub -class UnaryIntOpAssembler(object): - - _mixin_ = True - emit_op_int_is_true = gen_emit_op_unary_cmp('int_is_true', c.NE) emit_op_int_is_zero = gen_emit_op_unary_cmp('int_is_zero', c.EQ) @@ -184,10 +178,6 @@ return fcond -class GuardOpAssembler(object): - - _mixin_ = True - def _emit_guard(self, op, arglocs, fcond, save_exc, is_guard_not_invalidated=False): assert isinstance(save_exc, bool) @@ -291,10 +281,6 @@ is_guard_not_invalidated=True) -class OpAssembler(object): - - _mixin_ = True - def emit_op_jump(self, op, arglocs, regalloc, fcond): # The backend's logic assumes that the target code is in a piece of # assembler that was also called with the same number of arguments, @@ -616,10 +602,6 @@ emit_op_cond_call_gc_wb_array = emit_op_cond_call_gc_wb -class FieldOpAssembler(object): - - _mixin_ = True - def emit_op_setfield_gc(self, op, arglocs, regalloc, fcond): value_loc, base_loc, ofs, size = arglocs if size.value == 8: @@ -760,10 +742,6 @@ emit_op_setinteriorfield_raw = emit_op_setinteriorfield_gc -class ArrayOpAssember(object): - - _mixin_ = True - def emit_op_arraylen_gc(self, op, arglocs, regalloc, fcond): res, base_loc, ofs = arglocs self.mc.LDR_ri(res.value, base_loc.value, ofs.value) @@ -846,10 +824,6 @@ emit_op_getarrayitem_gc_pure = emit_op_getarrayitem_gc -class StrOpAssembler(object): - - _mixin_ = True - def emit_op_strlen(self, op, arglocs, regalloc, fcond): l0, l1, res = arglocs if l1.is_imm(): @@ -998,11 +972,7 @@ raise AssertionError("bad unicode item size") -class UnicodeOpAssembler(object): - - _mixin_ = True - - emit_op_unicodelen = StrOpAssembler.emit_op_strlen + emit_op_unicodelen = emit_op_strlen def emit_op_unicodegetitem(self, op, arglocs, regalloc, fcond): res, base_loc, ofs_loc, scale, basesize, itemsize = arglocs @@ -1032,10 +1002,6 @@ return fcond -class ForceOpAssembler(object): - - _mixin_ = True - def emit_op_force_token(self, op, arglocs, regalloc, fcond): res_loc = arglocs[0] self.mc.MOV_rr(res_loc.value, r.fp.value) @@ -1248,10 +1214,6 @@ self.mc.STR_ri(r.ip.value, r.fp.value) -class AllocOpAssembler(object): - - _mixin_ = True - def emit_op_call_malloc_gc(self, op, arglocs, regalloc, fcond): self.emit_op_call(op, arglocs, regalloc, fcond) self.propagate_memoryerror_if_r0_is_null() @@ -1282,9 +1244,6 @@ self.mc.NOP() -class FloatOpAssemlber(object): - _mixin_ = True - emit_op_float_add = gen_emit_float_op('float_add', 'VADD') emit_op_float_sub = gen_emit_float_op('float_sub', 'VSUB') emit_op_float_mul = gen_emit_float_op('float_mul', 'VMUL') @@ -1324,10 +1283,3 @@ return fcond -class ResOpAssembler(GuardOpAssembler, IntOpAsslember, - OpAssembler, UnaryIntOpAssembler, - FieldOpAssembler, ArrayOpAssember, - StrOpAssembler, UnicodeOpAssembler, - ForceOpAssembler, AllocOpAssembler, - FloatOpAssemlber): - pass diff --git a/pypy/jit/backend/arm/regalloc.py b/pypy/jit/backend/arm/regalloc.py --- a/pypy/jit/backend/arm/regalloc.py +++ b/pypy/jit/backend/arm/regalloc.py @@ -543,7 +543,7 @@ oopspecindex = effectinfo.oopspecindex if oopspecindex == EffectInfo.OS_MATH_SQRT: args = self.prepare_op_math_sqrt(op, fcond) - self.assembler.emit_op_math_sqrt(op, args, self, fcond) + self.perform_math(op, args, fcond) return return self._prepare_call(op) From noreply at buildbot.pypy.org Thu Apr 5 14:19:01 2012 From: noreply at buildbot.pypy.org (bivab) Date: Thu, 5 Apr 2012 14:19:01 +0200 (CEST) Subject: [pypy-commit] pypy arm-backend-2: merge default Message-ID: <20120405121901.6AF608208A@wyvern.cs.uni-duesseldorf.de> Author: David Schneider Branch: arm-backend-2 Changeset: r54196:02a8668f1393 Date: 2012-03-27 08:14 +0000 http://bitbucket.org/pypy/pypy/changeset/02a8668f1393/ Log: merge default diff --git a/lib-python/modified-2.7/test/test_set.py b/lib-python/modified-2.7/test/test_set.py --- a/lib-python/modified-2.7/test/test_set.py +++ b/lib-python/modified-2.7/test/test_set.py @@ -1568,7 +1568,7 @@ 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)) + actual = meth(g(data)) if isinstance(expected, bool): self.assertEqual(actual, expected) else: diff --git a/pypy/doc/discussion/win64_todo.txt b/pypy/doc/discussion/win64_todo.txt new file mode 100644 --- /dev/null +++ b/pypy/doc/discussion/win64_todo.txt @@ -0,0 +1,9 @@ +2011-11-04 +ll_os.py has a problem with the file rwin32.py. +Temporarily disabled for the win64_gborg branch. This needs to be +investigated and re-enabled. +Resolved, enabled. + +2011-11-05 +test_typed.py needs explicit tests to ensure that we +handle word sizes right. \ No newline at end of file diff --git a/pypy/doc/windows.rst b/pypy/doc/windows.rst --- a/pypy/doc/windows.rst +++ b/pypy/doc/windows.rst @@ -18,7 +18,8 @@ Edition. Other configurations may work as well. The translation scripts will set up the appropriate environment variables -for the compiler. They will attempt to locate the same compiler version that +for the compiler, so you do not need to run vcvars before translation. +They will attempt to locate the same compiler version that was used to build the Python interpreter doing the translation. Failing that, they will pick the most recent Visual Studio compiler they can find. In addition, the target architecture @@ -26,7 +27,7 @@ using a 32 bit Python and vice versa. **Note:** PyPy is currently not supported for 64 bit Windows, and translation -will be aborted in this case. +will fail in this case. The compiler is all you need to build pypy-c, but it will miss some modules that relies on third-party libraries. See below how to get @@ -57,7 +58,8 @@ install third-party libraries. We chose to install them in the parent directory of the pypy checkout. For example, if you installed pypy in ``d:\pypy\trunk\`` (This directory contains a README file), the base -directory is ``d:\pypy``. +directory is ``d:\pypy``. You may choose different values by setting the +INCLUDE, LIB and PATH (for DLLs) The Boehm garbage collector ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -126,18 +128,54 @@ ------------------------ You can compile pypy with the mingw compiler, using the --cc=mingw32 option; -mingw.exe must be on the PATH. +gcc.exe must be on the PATH. If the -cc flag does not begin with "ming", it should be +the name of a valid gcc-derivative compiler, i.e. x86_64-w64-mingw32-gcc for the 64 bit +compiler creating a 64 bit target. -libffi for the mingw32 compiler +libffi for the mingw compiler ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To enable the _rawffi (and ctypes) module, you need to compile a mingw32 -version of libffi. I downloaded the `libffi source files`_, and extracted -them in the base directory. Then run:: +To enable the _rawffi (and ctypes) module, you need to compile a mingw +version of libffi. Here is one way to do this, wich should allow you to try +to build for win64 or win32: + +#. Download and unzip a `mingw32 build`_ or `mingw64 build`_, say into c:\mingw +#. If you do not use cygwin, you will need msys to provide make, + autoconf tools and other goodies. + + #. Download and unzip a `msys for mingw`_, say into c:\msys + #. Edit the c:\msys\etc\fstab file to mount c:\mingw + +#. Download and unzip the `libffi source files`_, and extract + them in the base directory. +#. Run c:\msys\msys.bat or a cygwin shell which should make you + feel better since it is a shell prompt with shell tools. +#. From inside the shell, cd to the libffi directory and do:: sh ./configure make cp .libs/libffi-5.dll +If you can't find the dll, and the libtool issued a warning about +"undefined symbols not allowed", you will need to edit the libffi +Makefile in the toplevel directory. Add the flag -no-undefined to +the definition of libffi_la_LDFLAGS + +If you wish to experiment with win64, you must run configure with flags:: + + sh ./configure --build=x86_64-w64-mingw32 --host=x86_64-w64-mingw32 + +or such, depending on your mingw64 download. + +hacking on Pypy with the mingw compiler +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Since hacking on Pypy means running tests, you will need a way to specify +the mingw compiler when hacking (as opposed to translating). As of +March 2012, --cc is not a valid option for pytest.py. However if you set an +environment variable CC it will allow you to choose a compiler. + +.. _'mingw32 build': http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Automated%20Builds +.. _`mingw64 build`: http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Automated%20Builds +.. _`msys for mingw`: http://sourceforge.net/projects/mingw-w64/files/External%20binary%20packages%20%28Win64%20hosted%29/MSYS%20%2832-bit%29 .. _`libffi source files`: http://sourceware.org/libffi/ .. _`RPython translation toolchain`: translation.html diff --git a/pypy/doc/you-want-to-help.rst b/pypy/doc/you-want-to-help.rst new file mode 100644 --- /dev/null +++ b/pypy/doc/you-want-to-help.rst @@ -0,0 +1,86 @@ + +You want to help with PyPy, now what? +===================================== + +PyPy is a very large project that has a reputation of being hard to dive into. +Some of this fame is warranted, some of it is purely accidental. There are three +important lessons that everyone willing to contribute should learn: + +* PyPy has layers. There are many pieces of architecture that are very well + separated from each other. More about this below, but often the manifestation + of this is that things are at a different layer than you would expect them + to be. For example if you are looking for the JIT implementation, you will + not find it in the implementation of the Python programming language. + +* Because of the above, we are very serious about Test Driven Development. + It's not only what we believe in, but also that PyPy's architecture is + working very well with TDD in mind and not so well without it. Often + the development means progressing in an unrelated corner, one unittest + at a time; and then flipping a giant switch, bringing it all together. + (It generally works out of the box. If it doesn't, then we didn't + write enough unit tests.) It's worth repeating - PyPy + approach is great if you do TDD, not so great otherwise. + +* PyPy uses an entirely different set of tools - most of them included + in the PyPy repository. There is no Makefile, nor autoconf. More below + +Architecture +============ + +PyPy has layers. The 100 miles view: + +* `RPython`_ is the language in which we write interpreters. Not the entire + PyPy project is written in RPython, only the parts that are compiled in + the translation process. The interesting point is that RPython has no parser, + it's compiled from the live python objects, which make it possible to do + all kinds of metaprogramming during import time. In short, Python is a meta + programming language for RPython. + + The RPython standard library is to be found in the ``rlib`` subdirectory. + +.. _`RPython`: coding-guide.html#RPython + +* The translation toolchain - this is the part that takes care about translating + RPython to flow graphs and then to C. There is more in the `architecture`_ + document written about it. + + It mostly lives in ``rpython``, ``annotator`` and ``objspace/flow``. + +.. _`architecture`: architecture.html + +* Python Interpreter + + xxx + +* Python modules + + xxx + +* Just-in-Time Compiler (JIT): `we have a tracing JIT`_ that traces the + interpreter written in RPython, rather than the user program that it + interprets. As a result it applies to any interpreter, i.e. any + language. But getting it to work correctly is not trivial: it + requires a small number of precise "hints" and possibly some small + refactorings of the interpreter. The JIT itself also has several + almost-independent parts: the tracer itself in ``jit/metainterp``, the + optimizer in ``jit/metainterp/optimizer`` that optimizes a list of + residual operations, and the backend in ``jit/backend/`` + that turns it into machine code. Writing a new backend is a + traditional way to get into the project. + +.. _`we have a tracing JIT`: jit/index.html + +* Garbage Collectors (GC): as you can notice if you are used to CPython's + C code, there are no ``Py_INCREF/Py_DECREF`` equivalents in RPython code. + `Garbage collection in PyPy`_ is inserted + during translation. Moreover, this is not reference counting; it is a real + GC written as more RPython code. The best one we have so far is in + ``rpython/memory/gc/minimark.py``. + +.. _`Garbage collection in PyPy`: garbage_collection.html + + +Toolset +======= + +xxx diff --git a/pypy/interpreter/astcompiler/test/test_astbuilder.py b/pypy/interpreter/astcompiler/test/test_astbuilder.py --- a/pypy/interpreter/astcompiler/test/test_astbuilder.py +++ b/pypy/interpreter/astcompiler/test/test_astbuilder.py @@ -10,16 +10,6 @@ from pypy.interpreter.astcompiler import ast, consts -try: - all -except NameError: - def all(iterable): - for x in iterable: - if not x: - return False - return True - - class TestAstBuilder: def setup_class(cls): diff --git a/pypy/jit/backend/x86/regalloc.py b/pypy/jit/backend/x86/regalloc.py --- a/pypy/jit/backend/x86/regalloc.py +++ b/pypy/jit/backend/x86/regalloc.py @@ -715,8 +715,9 @@ self.Perform(op, [loc0], loc1) self.xrm.possibly_free_var(op.getarg(0)) else: - loc0 = self.xrm.loc(op.getarg(0)) - loc1 = self.xrm.force_allocate_reg(op.result) + arg0 = op.getarg(0) + loc0 = self.xrm.loc(arg0) + loc1 = self.xrm.force_allocate_reg(op.result, forbidden_vars=[arg0]) self.Perform(op, [loc0], loc1) self.xrm.possibly_free_var(op.getarg(0)) diff --git a/pypy/module/__pypy__/__init__.py b/pypy/module/__pypy__/__init__.py --- a/pypy/module/__pypy__/__init__.py +++ b/pypy/module/__pypy__/__init__.py @@ -16,13 +16,15 @@ appleveldefs = {} interpleveldefs = {} if sys.platform.startswith("linux"): + from pypy.module.__pypy__ import interp_time interpleveldefs["clock_gettime"] = "interp_time.clock_gettime" interpleveldefs["clock_getres"] = "interp_time.clock_getres" for name in [ "CLOCK_REALTIME", "CLOCK_MONOTONIC", "CLOCK_MONOTONIC_RAW", "CLOCK_PROCESS_CPUTIME_ID", "CLOCK_THREAD_CPUTIME_ID" ]: - interpleveldefs[name] = "space.wrap(interp_time.%s)" % name + if getattr(interp_time, name) is not None: + interpleveldefs[name] = "space.wrap(interp_time.%s)" % name class Module(MixedModule): diff --git a/pypy/module/__pypy__/interp_time.py b/pypy/module/__pypy__/interp_time.py --- a/pypy/module/__pypy__/interp_time.py +++ b/pypy/module/__pypy__/interp_time.py @@ -1,3 +1,4 @@ +from __future__ import with_statement import sys from pypy.interpreter.error import exception_from_errno diff --git a/pypy/module/micronumpy/app_numpy.py b/pypy/module/micronumpy/app_numpy.py --- a/pypy/module/micronumpy/app_numpy.py +++ b/pypy/module/micronumpy/app_numpy.py @@ -16,7 +16,7 @@ a[i][i] = 1 return a -def sum(a,axis=None): +def sum(a,axis=None, out=None): '''sum(a, axis=None) Sum of array elements over a given axis. @@ -43,17 +43,17 @@ # TODO: add to doc (once it's implemented): cumsum : Cumulative sum of array elements. if not hasattr(a, "sum"): a = _numpypy.array(a) - return a.sum(axis) + return a.sum(axis=axis, out=out) -def min(a, axis=None): +def min(a, axis=None, out=None): if not hasattr(a, "min"): a = _numpypy.array(a) - return a.min(axis) + return a.min(axis=axis, out=out) -def max(a, axis=None): +def max(a, axis=None, out=None): if not hasattr(a, "max"): a = _numpypy.array(a) - return a.max(axis) + return a.max(axis=axis, out=out) def arange(start, stop=None, step=1, dtype=None): '''arange([start], stop[, step], dtype=None) diff --git a/pypy/module/micronumpy/interp_boxes.py b/pypy/module/micronumpy/interp_boxes.py --- a/pypy/module/micronumpy/interp_boxes.py +++ b/pypy/module/micronumpy/interp_boxes.py @@ -62,21 +62,24 @@ return space.wrap(dtype.itemtype.bool(self)) def _binop_impl(ufunc_name): - def impl(self, space, w_other): + def impl(self, space, w_other, w_out=None): from pypy.module.micronumpy import interp_ufuncs - return getattr(interp_ufuncs.get(space), ufunc_name).call(space, [self, w_other]) + return getattr(interp_ufuncs.get(space), ufunc_name).call(space, + [self, w_other, w_out]) return func_with_new_name(impl, "binop_%s_impl" % ufunc_name) def _binop_right_impl(ufunc_name): - def impl(self, space, w_other): + def impl(self, space, w_other, w_out=None): from pypy.module.micronumpy import interp_ufuncs - return getattr(interp_ufuncs.get(space), ufunc_name).call(space, [w_other, self]) + return getattr(interp_ufuncs.get(space), ufunc_name).call(space, + [w_other, self, w_out]) return func_with_new_name(impl, "binop_right_%s_impl" % ufunc_name) def _unaryop_impl(ufunc_name): - def impl(self, space): + def impl(self, space, w_out=None): from pypy.module.micronumpy import interp_ufuncs - return getattr(interp_ufuncs.get(space), ufunc_name).call(space, [self]) + return getattr(interp_ufuncs.get(space), ufunc_name).call(space, + [self, w_out]) return func_with_new_name(impl, "unaryop_%s_impl" % ufunc_name) descr_add = _binop_impl("add") diff --git a/pypy/module/micronumpy/interp_iter.py b/pypy/module/micronumpy/interp_iter.py --- a/pypy/module/micronumpy/interp_iter.py +++ b/pypy/module/micronumpy/interp_iter.py @@ -269,7 +269,7 @@ def apply_transformations(self, arr, transformations): v = BaseIterator.apply_transformations(self, arr, transformations) - if len(arr.shape) == 1: + if len(arr.shape) == 1 and len(v.res_shape) == 1: return OneDimIterator(self.offset, self.strides[0], self.res_shape[0]) return v diff --git a/pypy/module/micronumpy/interp_numarray.py b/pypy/module/micronumpy/interp_numarray.py --- a/pypy/module/micronumpy/interp_numarray.py +++ b/pypy/module/micronumpy/interp_numarray.py @@ -83,8 +83,9 @@ return space.wrap(W_NDimArray(shape[:], dtype=dtype)) def _unaryop_impl(ufunc_name): - def impl(self, space): - return getattr(interp_ufuncs.get(space), ufunc_name).call(space, [self]) + def impl(self, space, w_out=None): + return getattr(interp_ufuncs.get(space), ufunc_name).call(space, + [self, w_out]) return func_with_new_name(impl, "unaryop_%s_impl" % ufunc_name) descr_pos = _unaryop_impl("positive") @@ -93,8 +94,9 @@ descr_invert = _unaryop_impl("invert") def _binop_impl(ufunc_name): - def impl(self, space, w_other): - return getattr(interp_ufuncs.get(space), ufunc_name).call(space, [self, w_other]) + def impl(self, space, w_other, w_out=None): + return getattr(interp_ufuncs.get(space), ufunc_name).call(space, + [self, w_other, w_out]) return func_with_new_name(impl, "binop_%s_impl" % ufunc_name) descr_add = _binop_impl("add") @@ -124,12 +126,12 @@ return space.newtuple([w_quotient, w_remainder]) def _binop_right_impl(ufunc_name): - def impl(self, space, w_other): + def impl(self, space, w_other, w_out=None): w_other = scalar_w(space, interp_ufuncs.find_dtype_for_scalar(space, w_other, self.find_dtype()), w_other ) - return getattr(interp_ufuncs.get(space), ufunc_name).call(space, [w_other, self]) + return getattr(interp_ufuncs.get(space), ufunc_name).call(space, [w_other, self, w_out]) return func_with_new_name(impl, "binop_right_%s_impl" % ufunc_name) descr_radd = _binop_right_impl("add") @@ -152,13 +154,21 @@ return space.newtuple([w_quotient, w_remainder]) def _reduce_ufunc_impl(ufunc_name, promote_to_largest=False): - def impl(self, space, w_axis=None): + def impl(self, space, w_axis=None, w_out=None): if space.is_w(w_axis, space.w_None): axis = -1 else: axis = space.int_w(w_axis) + if space.is_w(w_out, space.w_None) or not w_out: + out = None + elif not isinstance(w_out, BaseArray): + raise OperationError(space.w_TypeError, space.wrap( + 'output must be an array')) + else: + out = w_out return getattr(interp_ufuncs.get(space), ufunc_name).reduce(space, - self, True, promote_to_largest, axis) + self, True, promote_to_largest, axis, + False, out) return func_with_new_name(impl, "reduce_%s_impl" % ufunc_name) descr_sum = _reduce_ufunc_impl("add") @@ -213,6 +223,7 @@ def descr_dot(self, space, w_other): other = convert_to_array(space, w_other) if isinstance(other, Scalar): + #Note: w_out is not modified, this is numpy compliant. return self.descr_mul(space, other) elif len(self.shape) < 2 and len(other.shape) < 2: w_res = self.descr_mul(space, other) @@ -514,14 +525,14 @@ ) return w_result - def descr_mean(self, space, w_axis=None): + def descr_mean(self, space, w_axis=None, w_out=None): if space.is_w(w_axis, space.w_None): w_axis = space.wrap(-1) w_denom = space.wrap(support.product(self.shape)) else: dim = space.int_w(w_axis) w_denom = space.wrap(self.shape[dim]) - return space.div(self.descr_sum_promote(space, w_axis), w_denom) + return space.div(self.descr_sum_promote(space, w_axis, w_out), w_denom) def descr_var(self, space, w_axis=None): return get_appbridge_cache(space).call_method(space, '_var', self, @@ -714,11 +725,12 @@ """ Class for representing virtual arrays, such as binary ops or ufuncs """ - def __init__(self, name, shape, res_dtype): + def __init__(self, name, shape, res_dtype, out_arg=None): BaseArray.__init__(self, shape) self.forced_result = None self.res_dtype = res_dtype self.name = name + self.res = out_arg self.size = support.product(self.shape) * res_dtype.get_size() def _del_sources(self): @@ -727,13 +739,18 @@ raise NotImplementedError def compute(self): - ra = ResultArray(self, self.shape, self.res_dtype) + ra = ResultArray(self, self.shape, self.res_dtype, self.res) loop.compute(ra) + if self.res: + broadcast_dims = len(self.res.shape) - len(self.shape) + chunks = [Chunk(0,0,0,0)] * broadcast_dims + \ + [Chunk(0, i, 1, i) for i in self.shape] + return Chunks(chunks).apply(self.res) return ra.left def force_if_needed(self): if self.forced_result is None: - self.forced_result = self.compute() + self.forced_result = self.compute().get_concrete() self._del_sources() def get_concrete(self): @@ -773,8 +790,9 @@ class Call1(VirtualArray): - def __init__(self, ufunc, name, shape, calc_dtype, res_dtype, values): - VirtualArray.__init__(self, name, shape, res_dtype) + def __init__(self, ufunc, name, shape, calc_dtype, res_dtype, values, + out_arg=None): + VirtualArray.__init__(self, name, shape, res_dtype, out_arg) self.values = values self.size = values.size self.ufunc = ufunc @@ -786,6 +804,12 @@ def create_sig(self): if self.forced_result is not None: return self.forced_result.create_sig() + if self.shape != self.values.shape: + #This happens if out arg is used + return signature.BroadcastUfunc(self.ufunc, self.name, + self.calc_dtype, + self.values.create_sig(), + self.res.create_sig()) return signature.Call1(self.ufunc, self.name, self.calc_dtype, self.values.create_sig()) @@ -793,8 +817,9 @@ """ Intermediate class for performing binary operations. """ - def __init__(self, ufunc, name, shape, calc_dtype, res_dtype, left, right): - VirtualArray.__init__(self, name, shape, res_dtype) + def __init__(self, ufunc, name, shape, calc_dtype, res_dtype, left, right, + out_arg=None): + VirtualArray.__init__(self, name, shape, res_dtype, out_arg) self.ufunc = ufunc self.left = left self.right = right @@ -832,8 +857,13 @@ Call2.__init__(self, None, 'assign', shape, dtype, dtype, res, child) def create_sig(self): - return signature.ResultSignature(self.res_dtype, self.left.create_sig(), - self.right.create_sig()) + if self.left.shape != self.right.shape: + sig = signature.BroadcastResultSignature(self.res_dtype, + self.left.create_sig(), self.right.create_sig()) + else: + sig = signature.ResultSignature(self.res_dtype, + self.left.create_sig(), self.right.create_sig()) + return sig class ToStringArray(Call1): def __init__(self, child): @@ -842,9 +872,9 @@ self.s = StringBuilder(child.size * self.item_size) Call1.__init__(self, None, 'tostring', child.shape, dtype, dtype, child) - self.res = W_NDimArray([1], dtype, 'C') - self.res_casted = rffi.cast(rffi.CArrayPtr(lltype.Char), - self.res.storage) + self.res_str = W_NDimArray([1], dtype, order='C') + self.res_str_casted = rffi.cast(rffi.CArrayPtr(lltype.Char), + self.res_str.storage) def create_sig(self): return signature.ToStringSignature(self.calc_dtype, @@ -950,7 +980,7 @@ def setitem(self, item, value): self.invalidated() - self.dtype.setitem(self, item, value) + self.dtype.setitem(self, item, value.convert_to(self.dtype)) def calc_strides(self, shape): dtype = self.find_dtype() diff --git a/pypy/module/micronumpy/interp_ufuncs.py b/pypy/module/micronumpy/interp_ufuncs.py --- a/pypy/module/micronumpy/interp_ufuncs.py +++ b/pypy/module/micronumpy/interp_ufuncs.py @@ -28,26 +28,38 @@ return self.identity def descr_call(self, space, __args__): + from interp_numarray import BaseArray args_w, kwds_w = __args__.unpack() # it occurs to me that we don't support any datatypes that # require casting, change it later when we do kwds_w.pop('casting', None) w_subok = kwds_w.pop('subok', None) w_out = kwds_w.pop('out', space.w_None) - if ((w_subok is not None and space.is_true(w_subok)) or - not space.is_w(w_out, space.w_None)): + # Setup a default value for out + if space.is_w(w_out, space.w_None): + out = None + else: + out = w_out + if (w_subok is not None and space.is_true(w_subok)): raise OperationError(space.w_NotImplementedError, space.wrap("parameters unsupported")) if kwds_w or len(args_w) < self.argcount: raise OperationError(space.w_ValueError, space.wrap("invalid number of arguments") ) - elif len(args_w) > self.argcount: - # The extra arguments should actually be the output array, but we - # don't support that yet. + elif (len(args_w) > self.argcount and out is not None) or \ + (len(args_w) > self.argcount + 1): raise OperationError(space.w_TypeError, space.wrap("invalid number of arguments") ) + # Override the default out value, if it has been provided in w_wargs + if len(args_w) > self.argcount: + out = args_w[-1] + else: + args_w = args_w[:] + [out] + if out is not None and not isinstance(out, BaseArray): + raise OperationError(space.w_TypeError, space.wrap( + 'output must be an array')) return self.call(space, args_w) @unwrap_spec(skipna=bool, keepdims=bool) @@ -105,28 +117,33 @@ array([[ 1, 5], [ 9, 13]]) """ - if not space.is_w(w_out, space.w_None): - raise OperationError(space.w_NotImplementedError, space.wrap( - "out not supported")) + from pypy.module.micronumpy.interp_numarray import BaseArray if w_axis is None: axis = 0 elif space.is_w(w_axis, space.w_None): axis = -1 else: axis = space.int_w(w_axis) - return self.reduce(space, w_obj, False, False, axis, keepdims) + if space.is_w(w_out, space.w_None): + out = None + elif not isinstance(w_out, BaseArray): + raise OperationError(space.w_TypeError, space.wrap( + 'output must be an array')) + else: + out = w_out + return self.reduce(space, w_obj, False, False, axis, keepdims, out) - def reduce(self, space, w_obj, multidim, promote_to_largest, dim, - keepdims=False): + def reduce(self, space, w_obj, multidim, promote_to_largest, axis, + keepdims=False, out=None): from pypy.module.micronumpy.interp_numarray import convert_to_array, \ - Scalar, ReduceArray + Scalar, ReduceArray, W_NDimArray if self.argcount != 2: raise OperationError(space.w_ValueError, space.wrap("reduce only " "supported for binary functions")) assert isinstance(self, W_Ufunc2) obj = convert_to_array(space, w_obj) - if dim >= len(obj.shape): - raise OperationError(space.w_ValueError, space.wrap("axis(=%d) out of bounds" % dim)) + if axis >= len(obj.shape): + raise OperationError(space.w_ValueError, space.wrap("axis(=%d) out of bounds" % axis)) if isinstance(obj, Scalar): raise OperationError(space.w_TypeError, space.wrap("cannot reduce " "on a scalar")) @@ -144,21 +161,55 @@ if self.identity is None and size == 0: raise operationerrfmt(space.w_ValueError, "zero-size array to " "%s.reduce without identity", self.name) - if shapelen > 1 and dim >= 0: - return self.do_axis_reduce(obj, dtype, dim, keepdims) - arr = ReduceArray(self.func, self.name, self.identity, obj, dtype) - return loop.compute(arr) + if shapelen > 1 and axis >= 0: + if keepdims: + shape = obj.shape[:axis] + [1] + obj.shape[axis + 1:] + else: + shape = obj.shape[:axis] + obj.shape[axis + 1:] + if out: + #Test for shape agreement + if len(out.shape) > len(shape): + raise operationerrfmt(space.w_ValueError, + 'output parameter for reduction operation %s' + + ' has too many dimensions', self.name) + elif len(out.shape) < len(shape): + raise operationerrfmt(space.w_ValueError, + 'output parameter for reduction operation %s' + + ' does not have enough dimensions', self.name) + elif out.shape != shape: + raise operationerrfmt(space.w_ValueError, + 'output parameter shape mismatch, expecting [%s]' + + ' , got [%s]', + ",".join([str(x) for x in shape]), + ",".join([str(x) for x in out.shape]), + ) + #Test for dtype agreement, perhaps create an itermediate + #if out.dtype != dtype: + # raise OperationError(space.w_TypeError, space.wrap( + # "mismatched dtypes")) + return self.do_axis_reduce(obj, out.find_dtype(), axis, out) + else: + result = W_NDimArray(shape, dtype) + return self.do_axis_reduce(obj, dtype, axis, result) + if out: + if len(out.shape)>0: + raise operationerrfmt(space.w_ValueError, "output parameter " + "for reduction operation %s has too many" + " dimensions",self.name) + arr = ReduceArray(self.func, self.name, self.identity, obj, + out.find_dtype()) + val = loop.compute(arr) + assert isinstance(out, Scalar) + out.value = val + else: + arr = ReduceArray(self.func, self.name, self.identity, obj, dtype) + val = loop.compute(arr) + return val - def do_axis_reduce(self, obj, dtype, dim, keepdims): - from pypy.module.micronumpy.interp_numarray import AxisReduce,\ - W_NDimArray - if keepdims: - shape = obj.shape[:dim] + [1] + obj.shape[dim + 1:] - else: - shape = obj.shape[:dim] + obj.shape[dim + 1:] - result = W_NDimArray(shape, dtype) + def do_axis_reduce(self, obj, dtype, axis, result): + from pypy.module.micronumpy.interp_numarray import AxisReduce arr = AxisReduce(self.func, self.name, self.identity, obj.shape, dtype, - result, obj, dim) + result, obj, axis) loop.compute(arr) return arr.left @@ -176,24 +227,55 @@ self.bool_result = bool_result def call(self, space, args_w): - from pypy.module.micronumpy.interp_numarray import (Call1, - convert_to_array, Scalar) - - [w_obj] = args_w + from pypy.module.micronumpy.interp_numarray import (Call1, BaseArray, + convert_to_array, Scalar, shape_agreement) + if len(args_w)<2: + [w_obj] = args_w + out = None + else: + [w_obj, out] = args_w + if space.is_w(out, space.w_None): + out = None w_obj = convert_to_array(space, w_obj) calc_dtype = find_unaryop_result_dtype(space, w_obj.find_dtype(), promote_to_float=self.promote_to_float, promote_bools=self.promote_bools) - if self.bool_result: + if out: + if not isinstance(out, BaseArray): + raise OperationError(space.w_TypeError, space.wrap( + 'output must be an array')) + res_dtype = out.find_dtype() + elif self.bool_result: res_dtype = interp_dtype.get_dtype_cache(space).w_booldtype else: res_dtype = calc_dtype if isinstance(w_obj, Scalar): - return space.wrap(self.func(calc_dtype, w_obj.value.convert_to(calc_dtype))) - - w_res = Call1(self.func, self.name, w_obj.shape, calc_dtype, res_dtype, - w_obj) + arr = self.func(calc_dtype, w_obj.value.convert_to(calc_dtype)) + if isinstance(out,Scalar): + out.value=arr + elif isinstance(out, BaseArray): + out.fill(space, arr) + else: + out = arr + return space.wrap(out) + if out: + assert isinstance(out, BaseArray) # For translation + broadcast_shape = shape_agreement(space, w_obj.shape, out.shape) + if not broadcast_shape or broadcast_shape != out.shape: + raise operationerrfmt(space.w_ValueError, + 'output parameter shape mismatch, could not broadcast [%s]' + + ' to [%s]', + ",".join([str(x) for x in w_obj.shape]), + ",".join([str(x) for x in out.shape]), + ) + w_res = Call1(self.func, self.name, out.shape, calc_dtype, + res_dtype, w_obj, out) + #Force it immediately + w_res.get_concrete() + else: + w_res = Call1(self.func, self.name, w_obj.shape, calc_dtype, + res_dtype, w_obj) w_obj.add_invalidates(w_res) return w_res @@ -212,32 +294,61 @@ def call(self, space, args_w): from pypy.module.micronumpy.interp_numarray import (Call2, - convert_to_array, Scalar, shape_agreement) - - [w_lhs, w_rhs] = args_w + convert_to_array, Scalar, shape_agreement, BaseArray) + if len(args_w)>2: + [w_lhs, w_rhs, w_out] = args_w + else: + [w_lhs, w_rhs] = args_w + w_out = None w_lhs = convert_to_array(space, w_lhs) w_rhs = convert_to_array(space, w_rhs) - calc_dtype = find_binop_result_dtype(space, - w_lhs.find_dtype(), w_rhs.find_dtype(), - int_only=self.int_only, - promote_to_float=self.promote_to_float, - promote_bools=self.promote_bools, - ) + if space.is_w(w_out, space.w_None) or w_out is None: + out = None + calc_dtype = find_binop_result_dtype(space, + w_lhs.find_dtype(), w_rhs.find_dtype(), + int_only=self.int_only, + promote_to_float=self.promote_to_float, + promote_bools=self.promote_bools, + ) + elif not isinstance(w_out, BaseArray): + raise OperationError(space.w_TypeError, space.wrap( + 'output must be an array')) + else: + out = w_out + calc_dtype = out.find_dtype() if self.comparison_func: res_dtype = interp_dtype.get_dtype_cache(space).w_booldtype else: res_dtype = calc_dtype if isinstance(w_lhs, Scalar) and isinstance(w_rhs, Scalar): - return space.wrap(self.func(calc_dtype, + arr = self.func(calc_dtype, w_lhs.value.convert_to(calc_dtype), w_rhs.value.convert_to(calc_dtype) - )) + ) + if isinstance(out,Scalar): + out.value=arr + elif isinstance(out, BaseArray): + out.fill(space, arr) + else: + out = arr + return space.wrap(out) new_shape = shape_agreement(space, w_lhs.shape, w_rhs.shape) + # Test correctness of out.shape + if out and out.shape != shape_agreement(space, new_shape, out.shape): + raise operationerrfmt(space.w_ValueError, + 'output parameter shape mismatch, could not broadcast [%s]' + + ' to [%s]', + ",".join([str(x) for x in new_shape]), + ",".join([str(x) for x in out.shape]), + ) w_res = Call2(self.func, self.name, new_shape, calc_dtype, - res_dtype, w_lhs, w_rhs) + res_dtype, w_lhs, w_rhs, out) w_lhs.add_invalidates(w_res) w_rhs.add_invalidates(w_res) + if out: + #out.add_invalidates(w_res) #causes a recursion loop + w_res.get_concrete() return w_res diff --git a/pypy/module/micronumpy/signature.py b/pypy/module/micronumpy/signature.py --- a/pypy/module/micronumpy/signature.py +++ b/pypy/module/micronumpy/signature.py @@ -216,13 +216,14 @@ return self.child.eval(frame, arr.child) class Call1(Signature): - _immutable_fields_ = ['unfunc', 'name', 'child', 'dtype'] + _immutable_fields_ = ['unfunc', 'name', 'child', 'res', 'dtype'] - def __init__(self, func, name, dtype, child): + def __init__(self, func, name, dtype, child, res=None): self.unfunc = func self.child = child self.name = name self.dtype = dtype + self.res = res def hash(self): return compute_hash(self.name) ^ intmask(self.child.hash() << 1) @@ -256,6 +257,29 @@ v = self.child.eval(frame, arr.values).convert_to(arr.calc_dtype) return self.unfunc(arr.calc_dtype, v) + +class BroadcastUfunc(Call1): + def _invent_numbering(self, cache, allnumbers): + self.res._invent_numbering(cache, allnumbers) + self.child._invent_numbering(new_cache(), allnumbers) + + def debug_repr(self): + return 'BroadcastUfunc(%s, %s)' % (self.name, self.child.debug_repr()) + + def _create_iter(self, iterlist, arraylist, arr, transforms): + from pypy.module.micronumpy.interp_numarray import Call1 + + assert isinstance(arr, Call1) + vtransforms = transforms + [BroadcastTransform(arr.values.shape)] + self.child._create_iter(iterlist, arraylist, arr.values, vtransforms) + self.res._create_iter(iterlist, arraylist, arr.res, transforms) + + def eval(self, frame, arr): + from pypy.module.micronumpy.interp_numarray import Call1 + assert isinstance(arr, Call1) + v = self.child.eval(frame, arr.values).convert_to(arr.calc_dtype) + return self.unfunc(arr.calc_dtype, v) + class Call2(Signature): _immutable_fields_ = ['binfunc', 'name', 'calc_dtype', 'left', 'right'] @@ -316,7 +340,17 @@ assert isinstance(arr, ResultArray) offset = frame.get_final_iter().offset - arr.left.setitem(offset, self.right.eval(frame, arr.right)) + val = self.right.eval(frame, arr.right) + arr.left.setitem(offset, val) + +class BroadcastResultSignature(ResultSignature): + def _create_iter(self, iterlist, arraylist, arr, transforms): + from pypy.module.micronumpy.interp_numarray import ResultArray + + assert isinstance(arr, ResultArray) + rtransforms = transforms + [BroadcastTransform(arr.left.shape)] + self.left._create_iter(iterlist, arraylist, arr.left, transforms) + self.right._create_iter(iterlist, arraylist, arr.right, rtransforms) class ToStringSignature(Call1): def __init__(self, dtype, child): @@ -327,10 +361,10 @@ from pypy.module.micronumpy.interp_numarray import ToStringArray assert isinstance(arr, ToStringArray) - arr.res.setitem(0, self.child.eval(frame, arr.values).convert_to( + arr.res_str.setitem(0, self.child.eval(frame, arr.values).convert_to( self.dtype)) for i in range(arr.item_size): - arr.s.append(arr.res_casted[i]) + arr.s.append(arr.res_str_casted[i]) class BroadcastLeft(Call2): def _invent_numbering(self, cache, allnumbers): @@ -455,6 +489,5 @@ cur = arr.left.getitem(iterator.offset) value = self.binfunc(self.calc_dtype, cur, v) arr.left.setitem(iterator.offset, value) - def debug_repr(self): return 'AxisReduceSig(%s, %s)' % (self.name, self.right.debug_repr()) diff --git a/pypy/module/micronumpy/test/test_numarray.py b/pypy/module/micronumpy/test/test_numarray.py --- a/pypy/module/micronumpy/test/test_numarray.py +++ b/pypy/module/micronumpy/test/test_numarray.py @@ -995,6 +995,10 @@ assert a.sum() == 5 raises(TypeError, 'a.sum(2, 3)') + d = array(0.) + b = a.sum(out=d) + assert b == d + assert isinstance(b, float) def test_reduce_nd(self): from numpypy import arange, array, multiply @@ -1495,8 +1499,6 @@ a = array([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12], [13, 14]]) b = a[::2] - print a - print b assert (b == [[1, 2], [5, 6], [9, 10], [13, 14]]).all() c = b + b assert c[1][1] == 12 diff --git a/pypy/module/micronumpy/test/test_outarg.py b/pypy/module/micronumpy/test/test_outarg.py new file mode 100644 --- /dev/null +++ b/pypy/module/micronumpy/test/test_outarg.py @@ -0,0 +1,126 @@ +import py +from pypy.module.micronumpy.test.test_base import BaseNumpyAppTest + +class AppTestOutArg(BaseNumpyAppTest): + def test_reduce_out(self): + from numpypy import arange, zeros, array + a = arange(15).reshape(5, 3) + b = arange(12).reshape(4,3) + c = a.sum(0, out=b[1]) + assert (c == [30, 35, 40]).all() + assert (c == b[1]).all() + raises(ValueError, 'a.prod(0, out=arange(10))') + a=arange(12).reshape(3,2,2) + raises(ValueError, 'a.sum(0, out=arange(12).reshape(3,2,2))') + raises(ValueError, 'a.sum(0, out=arange(3))') + c = array([-1, 0, 1]).sum(out=zeros([], dtype=bool)) + #You could argue that this should product False, but + # that would require an itermediate result. Cpython numpy + # gives True. + assert c == True + a = array([[-1, 0, 1], [1, 0, -1]]) + c = a.sum(0, out=zeros((3,), dtype=bool)) + assert (c == [True, False, True]).all() + c = a.sum(1, out=zeros((2,), dtype=bool)) + assert (c == [True, True]).all() + + def test_reduce_intermediary(self): + from numpypy import arange, array + a = arange(15).reshape(5, 3) + b = array(range(3), dtype=bool) + c = a.prod(0, out=b) + assert(b == [False, True, True]).all() + + def test_ufunc_out(self): + from _numpypy import array, negative, zeros, sin + from math import sin as msin + a = array([[1, 2], [3, 4]]) + c = zeros((2,2,2)) + b = negative(a + a, out=c[1]) + #test for view, and also test that forcing out also forces b + assert (c[:, :, 1] == [[0, 0], [-4, -8]]).all() + assert (b == [[-2, -4], [-6, -8]]).all() + #Test broadcast, type promotion + b = negative(3, out=a) + assert (a == -3).all() + c = zeros((2, 2), dtype=float) + b = negative(3, out=c) + assert b.dtype.kind == c.dtype.kind + assert b.shape == c.shape + a = array([1, 2]) + b = sin(a, out=c) + assert(c == [[msin(1), msin(2)]] * 2).all() + b = sin(a, out=c+c) + assert (c == b).all() + + #Test shape agreement + a = zeros((3,4)) + b = zeros((3,5)) + raises(ValueError, 'negative(a, out=b)') + b = zeros((1,4)) + raises(ValueError, 'negative(a, out=b)') + + def test_binfunc_out(self): + from _numpypy import array, add + a = array([[1, 2], [3, 4]]) + out = array([[1, 2], [3, 4]]) + c = add(a, a, out=out) + assert (c == out).all() + assert c.shape == a.shape + assert c.dtype is a.dtype + c[0,0] = 100 + assert out[0, 0] == 100 + out[:] = 100 + raises(ValueError, 'c = add(a, a, out=out[1])') + c = add(a[0], a[1], out=out[1]) + assert (c == out[1]).all() + assert (c == [4, 6]).all() + assert (out[0] == 100).all() + c = add(a[0], a[1], out=out) + assert (c == out[1]).all() + assert (c == out[0]).all() + out = array(16, dtype=int) + b = add(10, 10, out=out) + assert b==out + assert b.dtype == out.dtype + + def test_applevel(self): + from _numpypy import array, sum, max, min + a = array([[1, 2], [3, 4]]) + out = array([[0, 0], [0, 0]]) + c = sum(a, axis=0, out=out[0]) + assert (c == [4, 6]).all() + assert (c == out[0]).all() + assert (c != out[1]).all() + c = max(a, axis=1, out=out[0]) + assert (c == [2, 4]).all() + assert (c == out[0]).all() + assert (c != out[1]).all() + + def test_ufunc_cast(self): + from _numpypy import array, negative, add, sum + a = array(16, dtype = int) + c = array(0, dtype = float) + b = negative(a, out=c) + assert b == c + b = add(a, a, out=c) + assert b == c + d = array([16, 16], dtype=int) + b = sum(d, out=c) + assert b == c + try: + from _numpypy import version + v = version.version.split('.') + except: + v = ['1', '6', '0'] # numpypy is api compatable to what version? + if v[0]<'2': + b = negative(c, out=a) + assert b == a + b = add(c, c, out=a) + assert b == a + b = sum(array([16, 16], dtype=float), out=a) + assert b == a + else: + cast_error = raises(TypeError, negative, c, a) + assert str(cast_error.value) == \ + "Cannot cast ufunc negative output from dtype('float64') to dtype('int64') with casting rule 'same_kind'" diff --git a/pypy/module/micronumpy/test/test_zjit.py b/pypy/module/micronumpy/test/test_zjit.py --- a/pypy/module/micronumpy/test/test_zjit.py +++ b/pypy/module/micronumpy/test/test_zjit.py @@ -131,7 +131,7 @@ # bogus. We need to improve the situation somehow. self.check_simple_loop({'getinteriorfield_raw': 2, 'setinteriorfield_raw': 1, - 'arraylen_gc': 1, + 'arraylen_gc': 2, 'guard_true': 1, 'int_lt': 1, 'jump': 1, diff --git a/pypy/module/pypyjit/test_pypy_c/test_containers.py b/pypy/module/pypyjit/test_pypy_c/test_containers.py --- a/pypy/module/pypyjit/test_pypy_c/test_containers.py +++ b/pypy/module/pypyjit/test_pypy_c/test_containers.py @@ -128,3 +128,82 @@ loop, = log.loops_by_filename(self.filepath) ops = loop.ops_by_id('look') assert 'call' not in log.opnames(ops) + + #XXX the following tests only work with strategies enabled + + def test_should_not_create_intobject_with_sets(self): + def main(n): + i = 0 + s = set() + while i < n: + s.add(i) + i += 1 + log = self.run(main, [1000]) + assert log.result == main(1000) + loop, = log.loops_by_filename(self.filepath) + opnames = log.opnames(loop.allops()) + assert opnames.count('new_with_vtable') == 0 + + def test_should_not_create_stringobject_with_sets(self): + def main(n): + i = 0 + s = set() + while i < n: + s.add(str(i)) + i += 1 + log = self.run(main, [1000]) + assert log.result == main(1000) + loop, = log.loops_by_filename(self.filepath) + opnames = log.opnames(loop.allops()) + assert opnames.count('new_with_vtable') == 0 + + def test_should_not_create_intobject_with_lists(self): + def main(n): + i = 0 + l = [] + while i < n: + l.append(i) + i += 1 + log = self.run(main, [1000]) + assert log.result == main(1000) + loop, = log.loops_by_filename(self.filepath) + opnames = log.opnames(loop.allops()) + assert opnames.count('new_with_vtable') == 0 + + def test_should_not_create_stringobject_with_lists(self): + def main(n): + i = 0 + l = [] + while i < n: + l.append(str(i)) + i += 1 + log = self.run(main, [1000]) + assert log.result == main(1000) + loop, = log.loops_by_filename(self.filepath) + opnames = log.opnames(loop.allops()) + assert opnames.count('new_with_vtable') == 0 + + def test_optimized_create_list_from_string(self): + def main(n): + i = 0 + l = [] + while i < n: + l = list("abc" * i) + i += 1 + log = self.run(main, [1000]) + assert log.result == main(1000) + loop, = log.loops_by_filename(self.filepath) + opnames = log.opnames(loop.allops()) + assert opnames.count('new_with_vtable') == 0 + + def test_optimized_create_set_from_list(self): + def main(n): + i = 0 + while i < n: + s = set([1,2,3]) + i += 1 + log = self.run(main, [1000]) + assert log.result == main(1000) + loop, = log.loops_by_filename(self.filepath) + opnames = log.opnames(loop.allops()) + assert opnames.count('new_with_vtable') == 0 diff --git a/pypy/objspace/flow/test/test_objspace.py b/pypy/objspace/flow/test/test_objspace.py --- a/pypy/objspace/flow/test/test_objspace.py +++ b/pypy/objspace/flow/test/test_objspace.py @@ -1,6 +1,6 @@ from __future__ import with_statement import new -import py +import py, sys from pypy.objspace.flow.model import Constant, Block, Link, Variable from pypy.objspace.flow.model import mkentrymap, c_last_exception from pypy.interpreter.argument import Arguments @@ -893,6 +893,8 @@ """ Tests code generated by pypy-c compiled with BUILD_LIST_FROM_ARG bytecode """ + if sys.version_info < (2, 7): + py.test.skip("2.7 only test") self.patch_opcodes('BUILD_LIST_FROM_ARG') try: def f(): diff --git a/pypy/objspace/std/bytearrayobject.py b/pypy/objspace/std/bytearrayobject.py --- a/pypy/objspace/std/bytearrayobject.py +++ b/pypy/objspace/std/bytearrayobject.py @@ -111,9 +111,15 @@ length = len(data) start, stop, step, slicelength = w_slice.indices4(space, length) assert slicelength >= 0 - newdata = [data[start + i*step] for i in range(slicelength)] + if step == 1 and 0 <= start <= stop: + newdata = data[start:stop] + else: + newdata = _getitem_slice_multistep(data, start, step, slicelength) return W_BytearrayObject(newdata) +def _getitem_slice_multistep(data, start, step, slicelength): + return [data[start + i*step] for i in range(slicelength)] + def contains__Bytearray_Int(space, w_bytearray, w_char): char = space.int_w(w_char) if not 0 <= char < 256: diff --git a/pypy/objspace/std/celldict.py b/pypy/objspace/std/celldict.py --- a/pypy/objspace/std/celldict.py +++ b/pypy/objspace/std/celldict.py @@ -127,10 +127,10 @@ def iter(self, w_dict): return ModuleDictIteratorImplementation(self.space, self, w_dict) - def keys(self, w_dict): + def w_keys(self, w_dict): space = self.space - iterator = self.unerase(w_dict.dstorage).iteritems - return [space.wrap(key) for key, cell in iterator()] + l = self.unerase(w_dict.dstorage).keys() + return space.newlist_str(l) def values(self, w_dict): iterator = self.unerase(w_dict.dstorage).itervalues diff --git a/pypy/objspace/std/dictmultiobject.py b/pypy/objspace/std/dictmultiobject.py --- a/pypy/objspace/std/dictmultiobject.py +++ b/pypy/objspace/std/dictmultiobject.py @@ -90,9 +90,9 @@ def _add_indirections(): dict_methods = "setitem setitem_str getitem \ getitem_str delitem length \ - clear keys values \ + clear w_keys values \ items iter setdefault \ - popitem".split() + popitem listview_str listview_int".split() def make_method(method): def f(self, *args): @@ -113,7 +113,7 @@ def get_empty_storage(self): raise NotImplementedError - def keys(self, w_dict): + def w_keys(self, w_dict): iterator = self.iter(w_dict) result = [] while 1: @@ -121,7 +121,7 @@ if w_key is not None: result.append(w_key) else: - return result + return self.space.newlist(result) def values(self, w_dict): iterator = self.iter(w_dict) @@ -160,6 +160,11 @@ w_dict.strategy = strategy w_dict.dstorage = storage + def listview_str(self, w_dict): + return None + + def listview_int(self, w_dict): + return None class EmptyDictStrategy(DictStrategy): @@ -371,8 +376,9 @@ self.switch_to_object_strategy(w_dict) return w_dict.getitem(w_key) - def keys(self, w_dict): - return [self.wrap(key) for key in self.unerase(w_dict.dstorage).iterkeys()] + def w_keys(self, w_dict): + l = [self.wrap(key) for key in self.unerase(w_dict.dstorage).iterkeys()] + return self.space.newlist(l) def values(self, w_dict): return self.unerase(w_dict.dstorage).values() @@ -425,8 +431,8 @@ def iter(self, w_dict): return ObjectIteratorImplementation(self.space, self, w_dict) - def keys(self, w_dict): - return self.unerase(w_dict.dstorage).keys() + def w_keys(self, w_dict): + return self.space.newlist(self.unerase(w_dict.dstorage).keys()) class StringDictStrategy(AbstractTypedStrategy, DictStrategy): @@ -469,9 +475,15 @@ assert key is not None return self.unerase(w_dict.dstorage).get(key, None) + def listview_str(self, w_dict): + return self.unerase(w_dict.dstorage).keys() + def iter(self, w_dict): return StrIteratorImplementation(self.space, self, w_dict) + def w_keys(self, w_dict): + return self.space.newlist_str(self.listview_str(w_dict)) + class _WrappedIteratorMixin(object): _mixin_ = True @@ -534,6 +546,14 @@ def iter(self, w_dict): return IntIteratorImplementation(self.space, self, w_dict) + def listview_int(self, w_dict): + return self.unerase(w_dict.dstorage).keys() + + def w_keys(self, w_dict): + # XXX there is no space.newlist_int yet + space = self.space + return space.call_function(space.w_list, w_dict) + class IntIteratorImplementation(_WrappedIteratorMixin, IteratorImplementation): pass @@ -688,7 +708,7 @@ return space.newlist(w_self.items()) def dict_keys__DictMulti(space, w_self): - return space.newlist(w_self.keys()) + return w_self.w_keys() def dict_values__DictMulti(space, w_self): return space.newlist(w_self.values()) diff --git a/pypy/objspace/std/dictproxyobject.py b/pypy/objspace/std/dictproxyobject.py --- a/pypy/objspace/std/dictproxyobject.py +++ b/pypy/objspace/std/dictproxyobject.py @@ -76,7 +76,7 @@ def keys(self, w_dict): space = self.space - return [space.wrap(key) for key in self.unerase(w_dict.dstorage).dict_w.iterkeys()] + return space.newlist_str(self.unerase(w_dict.dstorage).dict_w.keys()) def values(self, w_dict): return [unwrap_cell(self.space, w_value) for w_value in self.unerase(w_dict.dstorage).dict_w.itervalues()] diff --git a/pypy/objspace/std/dicttype.py b/pypy/objspace/std/dicttype.py --- a/pypy/objspace/std/dicttype.py +++ b/pypy/objspace/std/dicttype.py @@ -62,8 +62,14 @@ w_fill = space.w_None if space.is_w(w_type, space.w_dict): w_dict = W_DictMultiObject.allocate_and_init_instance(space, w_type) - for w_key in space.listview(w_keys): - w_dict.setitem(w_key, w_fill) + + strlist = space.listview_str(w_keys) + if strlist is not None: + for key in strlist: + w_dict.setitem_str(key, w_fill) + else: + for w_key in space.listview(w_keys): + w_dict.setitem(w_key, w_fill) else: w_dict = space.call_function(w_type) for w_key in space.listview(w_keys): diff --git a/pypy/objspace/std/frozensettype.py b/pypy/objspace/std/frozensettype.py --- a/pypy/objspace/std/frozensettype.py +++ b/pypy/objspace/std/frozensettype.py @@ -39,13 +39,11 @@ def descr__frozenset__new__(space, w_frozensettype, w_iterable=gateway.NoneNotWrapped): from pypy.objspace.std.setobject import W_FrozensetObject - from pypy.objspace.std.setobject import make_setdata_from_w_iterable if (space.is_w(w_frozensettype, space.w_frozenset) and w_iterable is not None and type(w_iterable) is W_FrozensetObject): return w_iterable w_obj = space.allocate_instance(W_FrozensetObject, w_frozensettype) - data = make_setdata_from_w_iterable(space, w_iterable) - W_FrozensetObject.__init__(w_obj, space, data) + W_FrozensetObject.__init__(w_obj, space, w_iterable) return w_obj frozenset_typedef = StdTypeDef("frozenset", diff --git a/pypy/objspace/std/iterobject.py b/pypy/objspace/std/iterobject.py --- a/pypy/objspace/std/iterobject.py +++ b/pypy/objspace/std/iterobject.py @@ -29,9 +29,8 @@ class W_SeqIterObject(W_AbstractSeqIterObject): """Sequence iterator implementation for general sequences.""" -class W_FastListIterObject(W_AbstractSeqIterObject): - """Sequence iterator specialized for lists, accessing directly their - RPython-level list of wrapped objects. +class W_FastListIterObject(W_AbstractSeqIterObject): # XXX still needed + """Sequence iterator specialized for lists. """ class W_FastTupleIterObject(W_AbstractSeqIterObject): diff --git a/pypy/objspace/std/listobject.py b/pypy/objspace/std/listobject.py --- a/pypy/objspace/std/listobject.py +++ b/pypy/objspace/std/listobject.py @@ -139,6 +139,16 @@ new erased object as storage""" self.strategy.init_from_list_w(self, list_w) + def clear(self, space): + """Initializes (or overrides) the listobject as empty.""" + self.space = space + if space.config.objspace.std.withliststrategies: + strategy = space.fromcache(EmptyListStrategy) + else: + strategy = space.fromcache(ObjectListStrategy) + self.strategy = strategy + strategy.clear(self) + def clone(self): """Returns a clone by creating a new listobject with the same strategy and a copy of the storage""" @@ -200,6 +210,11 @@ """ Return the items in the list as unwrapped strings. If the list does not use the list strategy, return None. """ return self.strategy.getitems_str(self) + + def getitems_int(self): + """ Return the items in the list as unwrapped ints. If the list does + not use the list strategy, return None. """ + return self.strategy.getitems_int(self) # ___________________________________________________ @@ -300,6 +315,9 @@ def getitems_str(self, w_list): return None + def getitems_int(self, w_list): + return None + def getstorage_copy(self, w_list): raise NotImplementedError @@ -358,6 +376,9 @@ assert len(list_w) == 0 w_list.lstorage = self.erase(None) + def clear(self, w_list): + w_list.lstorage = self.erase(None) + erase, unerase = rerased.new_erasing_pair("empty") erase = staticmethod(erase) unerase = staticmethod(unerase) @@ -516,6 +537,9 @@ raise IndexError return start + i * step + def getitems_int(self, w_list): + return self._getitems_range(w_list, False) + def getitem(self, w_list, i): return self.wrap(self._getitem_unwrapped(w_list, i)) @@ -696,6 +720,7 @@ for i in l: if i == obj: return True + return False return ListStrategy.contains(self, w_list, w_obj) def length(self, w_list): @@ -937,6 +962,9 @@ def init_from_list_w(self, w_list, list_w): w_list.lstorage = self.erase(list_w) + def clear(self, w_list): + w_list.lstorage = self.erase([]) + def contains(self, w_list, w_obj): return ListStrategy.contains(self, w_list, w_obj) @@ -970,6 +998,9 @@ if reverse: l.reverse() + def getitems_int(self, w_list): + return self.unerase(w_list.lstorage) + class FloatListStrategy(AbstractUnwrappedStrategy, ListStrategy): _none_value = 0.0 _applevel_repr = "float" @@ -1027,37 +1058,49 @@ def getitems_str(self, w_list): return self.unerase(w_list.lstorage) - # _______________________________________________________ init_signature = Signature(['sequence'], None, None) init_defaults = [None] def init__List(space, w_list, __args__): - from pypy.objspace.std.tupleobject import W_TupleObject + from pypy.objspace.std.tupleobject import W_AbstractTupleObject # this is on the silly side w_iterable, = __args__.parse_obj( None, 'list', init_signature, init_defaults) - w_list.__init__(space, []) + w_list.clear(space) if w_iterable is not None: - # unfortunately this is duplicating space.unpackiterable to avoid - # assigning a new RPython list to 'wrappeditems', which defeats the - # W_FastListIterObject optimization. - if isinstance(w_iterable, W_ListObject): - w_list.extend(w_iterable) - elif isinstance(w_iterable, W_TupleObject): - w_list.extend(W_ListObject(space, w_iterable.wrappeditems[:])) - else: - _init_from_iterable(space, w_list, w_iterable) + if type(w_iterable) is W_ListObject: + w_iterable.copy_into(w_list) + return + elif isinstance(w_iterable, W_AbstractTupleObject): + w_list.__init__(space, w_iterable.getitems_copy()) + return + + intlist = space.listview_int(w_iterable) + if intlist is not None: + w_list.strategy = strategy = space.fromcache(IntegerListStrategy) + # need to copy because intlist can share with w_iterable + w_list.lstorage = strategy.erase(intlist[:]) + return + + strlist = space.listview_str(w_iterable) + if strlist is not None: + w_list.strategy = strategy = space.fromcache(StringListStrategy) + # need to copy because intlist can share with w_iterable + w_list.lstorage = strategy.erase(strlist[:]) + return + + # xxx special hack for speed + from pypy.interpreter.generator import GeneratorIterator + if isinstance(w_iterable, GeneratorIterator): + w_iterable.unpack_into_w(w_list) + return + # /xxx + _init_from_iterable(space, w_list, w_iterable) def _init_from_iterable(space, w_list, w_iterable): # in its own function to make the JIT look into init__List - # xxx special hack for speed - from pypy.interpreter.generator import GeneratorIterator - if isinstance(w_iterable, GeneratorIterator): - w_iterable.unpack_into_w(w_list) - return - # /xxx w_iterator = space.iter(w_iterable) while True: try: diff --git a/pypy/objspace/std/listtype.py b/pypy/objspace/std/listtype.py --- a/pypy/objspace/std/listtype.py +++ b/pypy/objspace/std/listtype.py @@ -43,7 +43,7 @@ def descr__new__(space, w_listtype, __args__): from pypy.objspace.std.listobject import W_ListObject w_obj = space.allocate_instance(W_ListObject, w_listtype) - W_ListObject.__init__(w_obj, space, []) + w_obj.clear(space) return w_obj # ____________________________________________________________ diff --git a/pypy/objspace/std/mapdict.py b/pypy/objspace/std/mapdict.py --- a/pypy/objspace/std/mapdict.py +++ b/pypy/objspace/std/mapdict.py @@ -694,6 +694,8 @@ self.delitem(w_dict, w_key) return (w_key, w_value) + # XXX could implement a more efficient w_keys based on space.newlist_str + def materialize_r_dict(space, obj, dict_w): map = obj._get_mapdict_map() new_obj = map.materialize_r_dict(space, obj, dict_w) diff --git a/pypy/objspace/std/objspace.py b/pypy/objspace/std/objspace.py --- a/pypy/objspace/std/objspace.py +++ b/pypy/objspace/std/objspace.py @@ -227,10 +227,7 @@ return W_ComplexObject(x.real, x.imag) if isinstance(x, set): - rdict_w = r_dict(self.eq_w, self.hash_w) - for item in x: - rdict_w[self.wrap(item)] = None - res = W_SetObject(self, rdict_w) + res = W_SetObject(self, self.newlist([self.wrap(item) for item in x])) return res if isinstance(x, frozenset): @@ -325,7 +322,7 @@ def newset(self): from pypy.objspace.std.setobject import newset - return W_SetObject(self, newset(self)) + return W_SetObject(self, None) def newslice(self, w_start, w_end, w_step): return W_SliceObject(w_start, w_end, w_step) @@ -403,7 +400,7 @@ def unpackiterable(self, w_obj, expected_length=-1): if isinstance(w_obj, W_AbstractTupleObject): t = w_obj.getitems_copy() - elif isinstance(w_obj, W_ListObject): + elif type(w_obj) is W_ListObject: t = w_obj.getitems_copy() else: return ObjSpace.unpackiterable(self, w_obj, expected_length) @@ -417,7 +414,7 @@ """ if isinstance(w_obj, W_AbstractTupleObject): t = w_obj.tolist() - elif isinstance(w_obj, W_ListObject): + elif type(w_obj) is W_ListObject: if unroll: t = w_obj.getitems_unroll() else: @@ -438,7 +435,7 @@ return self.fixedview(w_obj, expected_length, unroll=True) def listview(self, w_obj, expected_length=-1): - if isinstance(w_obj, W_ListObject): + if type(w_obj) is W_ListObject: t = w_obj.getitems() elif isinstance(w_obj, W_AbstractTupleObject): t = w_obj.getitems_copy() @@ -449,8 +446,25 @@ return t def listview_str(self, w_obj): - if isinstance(w_obj, W_ListObject): + # note: uses exact type checking for objects with strategies, + # and isinstance() for others. See test_listobject.test_uses_custom... + if type(w_obj) is W_ListObject: return w_obj.getitems_str() + if type(w_obj) is W_DictMultiObject: + return w_obj.listview_str() + if type(w_obj) is W_SetObject or type(w_obj) is W_FrozensetObject: + return w_obj.listview_str() + if isinstance(w_obj, W_StringObject): + return w_obj.listview_str() + return None + + def listview_int(self, w_obj): + if type(w_obj) is W_ListObject: + return w_obj.getitems_int() + if type(w_obj) is W_DictMultiObject: + return w_obj.listview_int() + if type(w_obj) is W_SetObject or type(w_obj) is W_FrozensetObject: + return w_obj.listview_int() return None def sliceindices(self, w_slice, w_length): diff --git a/pypy/objspace/std/setobject.py b/pypy/objspace/std/setobject.py --- a/pypy/objspace/std/setobject.py +++ b/pypy/objspace/std/setobject.py @@ -7,6 +7,12 @@ from pypy.interpreter.argument import Signature from pypy.objspace.std.settype import set_typedef as settypedef from pypy.objspace.std.frozensettype import frozenset_typedef as frozensettypedef +from pypy.rlib import rerased +from pypy.rlib.objectmodel import instantiate +from pypy.interpreter.generator import GeneratorIterator +from pypy.objspace.std.listobject import W_ListObject +from pypy.objspace.std.intobject import W_IntObject +from pypy.objspace.std.stringobject import W_StringObject class W_BaseSetObject(W_Object): typedef = None @@ -20,88 +26,859 @@ return True return False - - def __init__(w_self, space, setdata): + def __init__(w_self, space, w_iterable=None): """Initialize the set by taking ownership of 'setdata'.""" - assert setdata is not None - w_self.setdata = setdata + w_self.space = space + set_strategy_and_setdata(space, w_self, w_iterable) def __repr__(w_self): """representation for debugging purposes""" - reprlist = [repr(w_item) for w_item in w_self.setdata.keys()] + reprlist = [repr(w_item) for w_item in w_self.getkeys()] return "<%s(%s)>" % (w_self.__class__.__name__, ', '.join(reprlist)) + def from_storage_and_strategy(w_self, storage, strategy): + obj = w_self._newobj(w_self.space, None) + assert isinstance(obj, W_BaseSetObject) + obj.strategy = strategy + obj.sstorage = storage + return obj + _lifeline_ = None def getweakref(self): return self._lifeline_ + def setweakref(self, space, weakreflifeline): self._lifeline_ = weakreflifeline def delweakref(self): self._lifeline_ = None + def switch_to_object_strategy(self, space): + d = self.strategy.getdict_w(self) + self.strategy = strategy = space.fromcache(ObjectSetStrategy) + self.sstorage = strategy.erase(d) + + def switch_to_empty_strategy(self): + self.strategy = strategy = self.space.fromcache(EmptySetStrategy) + self.sstorage = strategy.get_empty_storage() + + # _____________ strategy methods ________________ + + + def clear(self): + """ Removes all elements from the set. """ + self.strategy.clear(self) + + def copy_real(self): + """ Returns a clone of the set. Frozensets storages are also copied.""" + return self.strategy.copy_real(self) + + def length(self): + """ Returns the number of items inside the set. """ + return self.strategy.length(self) + + def add(self, w_key): + """ Adds an element to the set. The element must be wrapped. """ + self.strategy.add(self, w_key) + + def remove(self, w_item): + """ Removes the given element from the set. Element must be wrapped. """ + return self.strategy.remove(self, w_item) + + def getdict_w(self): + """ Returns a dict with all elements of the set. Needed only for switching to ObjectSetStrategy. """ + return self.strategy.getdict_w(self) + + def listview_str(self): + """ If this is a string set return its contents as a list of uwnrapped strings. Otherwise return None. """ + return self.strategy.listview_str(self) + + def listview_int(self): + """ If this is an int set return its contents as a list of uwnrapped ints. Otherwise return None. """ + return self.strategy.listview_int(self) + + def get_storage_copy(self): + """ Returns a copy of the storage. Needed when we want to clone all elements from one set and + put them into another. """ + return self.strategy.get_storage_copy(self) + + def getkeys(self): + """ Returns a list of all elements inside the set. Only used in __repr__. Use as less as possible.""" + return self.strategy.getkeys(self) + + def difference(self, w_other): + """ Returns a set with all items that are in this set, but not in w_other. W_other must be a set.""" + return self.strategy.difference(self, w_other) + + def difference_update(self, w_other): + """ As difference but overwrites the sets content with the result. W_other must be a set.""" + self.strategy.difference_update(self, w_other) + + def symmetric_difference(self, w_other): + """ Returns a set with all items that are either in this set or in w_other, but not in both. W_other must be a set. """ + return self.strategy.symmetric_difference(self, w_other) + + def symmetric_difference_update(self, w_other): + """ As symmetric_difference but overwrites the content of the set with the result. W_other must be a set.""" + self.strategy.symmetric_difference_update(self, w_other) + + def intersect(self, w_other): + """ Returns a set with all items that exists in both sets, this set and in w_other. W_other must be a set. """ + return self.strategy.intersect(self, w_other) + + def intersect_update(self, w_other): + """ Keeps only those elements found in both sets, removing all other elements. W_other must be a set.""" + self.strategy.intersect_update(self, w_other) + + def issubset(self, w_other): + """ Checks wether this set is a subset of w_other. W_other must be a set. """ + return self.strategy.issubset(self, w_other) + + def isdisjoint(self, w_other): + """ Checks wether this set and the w_other are completly different, i.e. have no equal elements. W_other must be a set.""" + return self.strategy.isdisjoint(self, w_other) + + def update(self, w_other): + """ Appends all elements from the given set to this set. W_other must be a set.""" + self.strategy.update(self, w_other) + + def has_key(self, w_key): + """ Checks wether this set contains the given wrapped key.""" + return self.strategy.has_key(self, w_key) + + def equals(self, w_other): + """ Checks wether this set and the given set are equal, i.e. contain the same elements. W_other must be a set.""" + return self.strategy.equals(self, w_other) + + def iter(self): + """ Returns an iterator of the elements from this set. """ + return self.strategy.iter(self) + + def popitem(self): + """ Removes an arbitrary element from the set. May raise KeyError if set is empty.""" + return self.strategy.popitem(self) + class W_SetObject(W_BaseSetObject): from pypy.objspace.std.settype import set_typedef as typedef - def _newobj(w_self, space, rdict_w): - """Make a new set by taking ownership of 'rdict_w'.""" + def _newobj(w_self, space, w_iterable): + """Make a new set by taking ownership of 'w_iterable'.""" if type(w_self) is W_SetObject: - return W_SetObject(space, rdict_w) + return W_SetObject(space, w_iterable) w_type = space.type(w_self) w_obj = space.allocate_instance(W_SetObject, w_type) - W_SetObject.__init__(w_obj, space, rdict_w) + W_SetObject.__init__(w_obj, space, w_iterable) return w_obj class W_FrozensetObject(W_BaseSetObject): from pypy.objspace.std.frozensettype import frozenset_typedef as typedef hash = 0 - def _newobj(w_self, space, rdict_w): - """Make a new frozenset by taking ownership of 'rdict_w'.""" + def _newobj(w_self, space, w_iterable): + """Make a new frozenset by taking ownership of 'w_iterable'.""" if type(w_self) is W_FrozensetObject: - return W_FrozensetObject(space, rdict_w) + return W_FrozensetObject(space, w_iterable) w_type = space.type(w_self) w_obj = space.allocate_instance(W_FrozensetObject, w_type) - W_FrozensetObject.__init__(w_obj, space, rdict_w) + W_FrozensetObject.__init__(w_obj, space, w_iterable) return w_obj registerimplementation(W_BaseSetObject) registerimplementation(W_SetObject) registerimplementation(W_FrozensetObject) -class W_SetIterObject(W_Object): - from pypy.objspace.std.settype import setiter_typedef as typedef +class SetStrategy(object): + def __init__(self, space): + self.space = space - def __init__(w_self, setdata): - w_self.content = content = setdata - w_self.len = len(content) - w_self.pos = 0 - w_self.iterator = w_self.content.iterkeys() + def get_empty_dict(self): + """ Returns an empty dictionary depending on the strategy. Used to initalize a new storage. """ + raise NotImplementedError - def next_entry(w_self): - for w_key in w_self.iterator: + def get_empty_storage(self): + """ Returns an empty storage (erased) object. Used to initialize an empty set.""" + raise NotImplementedError + + def listview_str(self, w_set): + return None + + def listview_int(self, w_set): + return None + + #def erase(self, storage): + # raise NotImplementedError + + #def unerase(self, storage): + # raise NotImplementedError + + # __________________ methods called on W_SetObject _________________ + + def clear(self, w_set): + raise NotImplementedError + + def copy_real(self, w_set): + raise NotImplementedError + + def length(self, w_set): + raise NotImplementedError + + def add(self, w_set, w_key): + raise NotImplementedError + + def remove(self, w_set, w_item): + raise NotImplementedError + + def getdict_w(self, w_set): + raise NotImplementedError + + def get_storage_copy(self, w_set): + raise NotImplementedError + + def getkeys(self, w_set): + raise NotImplementedError + + def difference(self, w_set, w_other): + raise NotImplementedError + + def difference_update(self, w_set, w_other): + raise NotImplementedError + + def symmetric_difference(self, w_set, w_other): + raise NotImplementedError + + def symmetric_difference_update(self, w_set, w_other): + raise NotImplementedError + + def intersect(self, w_set, w_other): + raise NotImplementedError + + def intersect_update(self, w_set, w_other): + raise NotImplementedError + + def issubset(self, w_set, w_other): + raise NotImplementedError + + def isdisjoint(self, w_set, w_other): + raise NotImplementedError + + def update(self, w_set, w_other): + raise NotImplementedError + + def has_key(self, w_set, w_key): + raise NotImplementedError + + def equals(self, w_set, w_other): + raise NotImplementedError + + def iter(self, w_set): + raise NotImplementedError + + def popitem(self, w_set): + raise NotImplementedError + +class EmptySetStrategy(SetStrategy): + + erase, unerase = rerased.new_erasing_pair("empty") + erase = staticmethod(erase) + unerase = staticmethod(unerase) + + def get_empty_storage(self): + return self.erase(None) + + def is_correct_type(self, w_key): + return False + + def length(self, w_set): + return 0 + + def clear(self, w_set): + pass + + def copy_real(self, w_set): + storage = self.erase(None) + clone = w_set.from_storage_and_strategy(storage, self) + return clone + + def add(self, w_set, w_key): + if type(w_key) is W_IntObject: + strategy = self.space.fromcache(IntegerSetStrategy) + elif type(w_key) is W_StringObject: + strategy = self.space.fromcache(StringSetStrategy) + else: + strategy = self.space.fromcache(ObjectSetStrategy) + w_set.strategy = strategy + w_set.sstorage = strategy.get_empty_storage() + w_set.add(w_key) + + def remove(self, w_set, w_item): + return False + + def getdict_w(self, w_set): + return newset(self.space) + + def get_storage_copy(self, w_set): + return w_set.sstorage + + def getkeys(self, w_set): + return [] + + def has_key(self, w_set, w_key): + return False + + def equals(self, w_set, w_other): + if w_other.strategy is self or w_other.length() == 0: + return True + return False + + def difference(self, w_set, w_other): + return w_set.copy_real() + + def difference_update(self, w_set, w_other): + pass + + def intersect(self, w_set, w_other): + return w_set.copy_real() + + def intersect_update(self, w_set, w_other): + pass + + def isdisjoint(self, w_set, w_other): + return True + + def issubset(self, w_set, w_other): + return True + + def symmetric_difference(self, w_set, w_other): + return w_other.copy_real() + + def symmetric_difference_update(self, w_set, w_other): + w_set.strategy = w_other.strategy + w_set.sstorage = w_other.get_storage_copy() + + def update(self, w_set, w_other): + w_set.strategy = w_other.strategy + w_set.sstorage = w_other.get_storage_copy() + + def iter(self, w_set): + return EmptyIteratorImplementation(self.space, w_set) + + def popitem(self, w_set): + raise OperationError(self.space.w_KeyError, + self.space.wrap('pop from an empty set')) + +class AbstractUnwrappedSetStrategy(object): + _mixin_ = True + + def is_correct_type(self, w_key): + """ Checks wether the given wrapped key fits this strategy.""" + raise NotImplementedError + + def unwrap(self, w_item): + """ Returns the unwrapped value of the given wrapped item.""" + raise NotImplementedError + + def wrap(self, item): + """ Returns a wrapped version of the given unwrapped item. """ + raise NotImplementedError + + def get_storage_from_list(self, list_w): + setdata = self.get_empty_dict() + for w_item in list_w: + setdata[self.unwrap(w_item)] = None + return self.erase(setdata) + + def get_storage_from_unwrapped_list(self, items): + setdata = self.get_empty_dict() + for item in items: + setdata[item] = None + return self.erase(setdata) + + def length(self, w_set): + return len(self.unerase(w_set.sstorage)) + + def clear(self, w_set): + w_set.switch_to_empty_strategy() + + def copy_real(self, w_set): + # may be used internally on frozen sets, although frozenset().copy() + # returns self in frozenset_copy__Frozenset. + strategy = w_set.strategy + d = self.unerase(w_set.sstorage) + storage = self.erase(d.copy()) + clone = w_set.from_storage_and_strategy(storage, strategy) + return clone + + def add(self, w_set, w_key): + if self.is_correct_type(w_key): + d = self.unerase(w_set.sstorage) + d[self.unwrap(w_key)] = None + else: + w_set.switch_to_object_strategy(self.space) + w_set.add(w_key) + + def remove(self, w_set, w_item): + from pypy.objspace.std.dictmultiobject import _never_equal_to_string + d = self.unerase(w_set.sstorage) + if not self.is_correct_type(w_item): + #XXX check type of w_item and immediately return False in some cases + w_set.switch_to_object_strategy(self.space) + return w_set.remove(w_item) + + key = self.unwrap(w_item) + try: + del d[key] + return True + except KeyError: + return False + + def getdict_w(self, w_set): + result = newset(self.space) + keys = self.unerase(w_set.sstorage).keys() + for key in keys: + result[self.wrap(key)] = None + return result + + def get_storage_copy(self, w_set): + d = self.unerase(w_set.sstorage) + copy = self.erase(d.copy()) + return copy + + def getkeys(self, w_set): + keys = self.unerase(w_set.sstorage).keys() + keys_w = [self.wrap(key) for key in keys] + return keys_w + + def has_key(self, w_set, w_key): + from pypy.objspace.std.dictmultiobject import _never_equal_to_string + if not self.is_correct_type(w_key): + #XXX check type of w_item and immediately return False in some cases + w_set.switch_to_object_strategy(self.space) + return w_set.has_key(w_key) + d = self.unerase(w_set.sstorage) + return self.unwrap(w_key) in d + + def equals(self, w_set, w_other): + if w_set.length() != w_other.length(): + return False + items = self.unerase(w_set.sstorage).keys() + for key in items: + if not w_other.has_key(self.wrap(key)): + return False + return True + + def _difference_wrapped(self, w_set, w_other): + strategy = self.space.fromcache(ObjectSetStrategy) + + d_new = strategy.get_empty_dict() + for obj in self.unerase(w_set.sstorage): + w_item = self.wrap(obj) + if not w_other.has_key(w_item): + d_new[w_item] = None + + return strategy.erase(d_new) + + def _difference_unwrapped(self, w_set, w_other): + iterator = self.unerase(w_set.sstorage).iterkeys() + other_dict = self.unerase(w_other.sstorage) + result_dict = self.get_empty_dict() + for key in iterator: + if key not in other_dict: + result_dict[key] = None + return self.erase(result_dict) + + def _difference_base(self, w_set, w_other): + if self is w_other.strategy: + strategy = w_set.strategy + storage = self._difference_unwrapped(w_set, w_other) + elif not w_set.strategy.may_contain_equal_elements(w_other.strategy): + strategy = w_set.strategy + storage = w_set.sstorage + else: + strategy = self.space.fromcache(ObjectSetStrategy) + storage = self._difference_wrapped(w_set, w_other) + return storage, strategy + + def difference(self, w_set, w_other): + storage, strategy = self._difference_base(w_set, w_other) + w_newset = w_set.from_storage_and_strategy(storage, strategy) + return w_newset + + def difference_update(self, w_set, w_other): + storage, strategy = self._difference_base(w_set, w_other) + w_set.strategy = strategy + w_set.sstorage = storage + + def _symmetric_difference_unwrapped(self, w_set, w_other): + d_new = self.get_empty_dict() + d_this = self.unerase(w_set.sstorage) + d_other = self.unerase(w_other.sstorage) + for key in d_other.keys(): + if not key in d_this: + d_new[key] = None + for key in d_this.keys(): + if not key in d_other: + d_new[key] = None + + storage = self.erase(d_new) + return storage + + def _symmetric_difference_wrapped(self, w_set, w_other): + newsetdata = newset(self.space) + for obj in self.unerase(w_set.sstorage): + w_item = self.wrap(obj) + if not w_other.has_key(w_item): + newsetdata[w_item] = None + + w_iterator = w_other.iter() + while True: + w_item = w_iterator.next_entry() + if w_item is None: + break + if not w_set.has_key(w_item): + newsetdata[w_item] = None + + strategy = self.space.fromcache(ObjectSetStrategy) + return strategy.erase(newsetdata) + + def _symmetric_difference_base(self, w_set, w_other): + if self is w_other.strategy: + strategy = w_set.strategy + storage = self._symmetric_difference_unwrapped(w_set, w_other) + else: + strategy = self.space.fromcache(ObjectSetStrategy) + storage = self._symmetric_difference_wrapped(w_set, w_other) + return storage, strategy + + def symmetric_difference(self, w_set, w_other): + storage, strategy = self._symmetric_difference_base(w_set, w_other) + return w_set.from_storage_and_strategy(storage, strategy) + + def symmetric_difference_update(self, w_set, w_other): + storage, strategy = self._symmetric_difference_base(w_set, w_other) + w_set.strategy = strategy + w_set.sstorage = storage + + def _intersect_base(self, w_set, w_other): + if self is w_other.strategy: + strategy = w_set.strategy + storage = strategy._intersect_unwrapped(w_set, w_other) + elif not w_set.strategy.may_contain_equal_elements(w_other.strategy): + strategy = self.space.fromcache(EmptySetStrategy) + storage = strategy.get_empty_storage() + else: + strategy = self.space.fromcache(ObjectSetStrategy) + storage = self._intersect_wrapped(w_set, w_other) + return storage, strategy + + def _intersect_wrapped(self, w_set, w_other): + result = newset(self.space) + for key in self.unerase(w_set.sstorage): + w_key = self.wrap(key) + if w_other.has_key(w_key): + result[w_key] = None + + strategy = self.space.fromcache(ObjectSetStrategy) + return strategy.erase(result) + + def _intersect_unwrapped(self, w_set, w_other): + result = self.get_empty_dict() + d_this = self.unerase(w_set.sstorage) + d_other = self.unerase(w_other.sstorage) + for key in d_this: + if key in d_other: + result[key] = None + return self.erase(result) + + def intersect(self, w_set, w_other): + if w_set.length() > w_other.length(): + return w_other.intersect(w_set) + + storage, strategy = self._intersect_base(w_set, w_other) + return w_set.from_storage_and_strategy(storage, strategy) + + def intersect_update(self, w_set, w_other): + if w_set.length() > w_other.length(): + w_intersection = w_other.intersect(w_set) + strategy = w_intersection.strategy + storage = w_intersection.sstorage + else: + storage, strategy = self._intersect_base(w_set, w_other) + w_set.strategy = strategy + w_set.sstorage = storage + + def _issubset_unwrapped(self, w_set, w_other): + d_other = self.unerase(w_other.sstorage) + for item in self.unerase(w_set.sstorage): + if not item in d_other: + return False + return True + + def _issubset_wrapped(self, w_set, w_other): + for obj in self.unerase(w_set.sstorage): + w_item = self.wrap(obj) + if not w_other.has_key(w_item): + return False + return True + + def issubset(self, w_set, w_other): + if w_set.length() == 0: + return True + + if w_set.strategy is w_other.strategy: + return self._issubset_unwrapped(w_set, w_other) + elif not w_set.strategy.may_contain_equal_elements(w_other.strategy): + return False + else: + return self._issubset_wrapped(w_set, w_other) + + def _isdisjoint_unwrapped(self, w_set, w_other): + d_set = self.unerase(w_set.sstorage) + d_other = self.unerase(w_other.sstorage) + for key in d_set: + if key in d_other: + return False + return True + + def _isdisjoint_wrapped(self, w_set, w_other): + d = self.unerase(w_set.sstorage) + for key in d: + if w_other.has_key(self.wrap(key)): + return False + return True + + def isdisjoint(self, w_set, w_other): + if w_other.length() == 0: + return True + if w_set.length() > w_other.length(): + return w_other.isdisjoint(w_set) + + if w_set.strategy is w_other.strategy: + return self._isdisjoint_unwrapped(w_set, w_other) + elif not w_set.strategy.may_contain_equal_elements(w_other.strategy): + return True + else: + return self._isdisjoint_wrapped(w_set, w_other) + + def update(self, w_set, w_other): + if self is w_other.strategy: + d_set = self.unerase(w_set.sstorage) + d_other = self.unerase(w_other.sstorage) + d_set.update(d_other) + return + + w_set.switch_to_object_strategy(self.space) + w_set.update(w_other) + + def popitem(self, w_set): + storage = self.unerase(w_set.sstorage) + try: + # this returns a tuple because internally sets are dicts + result = storage.popitem() + except KeyError: + # strategy may still be the same even if dict is empty + raise OperationError(self.space.w_KeyError, + self.space.wrap('pop from an empty set')) + return self.wrap(result[0]) + +class StringSetStrategy(AbstractUnwrappedSetStrategy, SetStrategy): + erase, unerase = rerased.new_erasing_pair("string") + erase = staticmethod(erase) + unerase = staticmethod(unerase) + + def get_empty_storage(self): + return self.erase({}) + + def get_empty_dict(self): + return {} + + def listview_str(self, w_set): + return self.unerase(w_set.sstorage).keys() + + def is_correct_type(self, w_key): + return type(w_key) is W_StringObject + + def may_contain_equal_elements(self, strategy): + if strategy is self.space.fromcache(IntegerSetStrategy): + return False + if strategy is self.space.fromcache(EmptySetStrategy): + return False + return True + + def unwrap(self, w_item): + return self.space.str_w(w_item) + + def wrap(self, item): + return self.space.wrap(item) + + def iter(self, w_set): + return StringIteratorImplementation(self.space, self, w_set) + +class IntegerSetStrategy(AbstractUnwrappedSetStrategy, SetStrategy): + erase, unerase = rerased.new_erasing_pair("integer") + erase = staticmethod(erase) + unerase = staticmethod(unerase) + + def get_empty_storage(self): + return self.erase({}) + + def get_empty_dict(self): + return {} + + def listview_int(self, w_set): + return self.unerase(w_set.sstorage).keys() + + def is_correct_type(self, w_key): + from pypy.objspace.std.intobject import W_IntObject + return type(w_key) is W_IntObject + + def may_contain_equal_elements(self, strategy): + if strategy is self.space.fromcache(StringSetStrategy): + return False + if strategy is self.space.fromcache(EmptySetStrategy): + return False + return True + + def unwrap(self, w_item): + return self.space.int_w(w_item) + + def wrap(self, item): + return self.space.wrap(item) + + def iter(self, w_set): + return IntegerIteratorImplementation(self.space, self, w_set) + +class ObjectSetStrategy(AbstractUnwrappedSetStrategy, SetStrategy): + erase, unerase = rerased.new_erasing_pair("object") + erase = staticmethod(erase) + unerase = staticmethod(unerase) + + def get_empty_storage(self): + return self.erase(self.get_empty_dict()) + + def get_empty_dict(self): + return newset(self.space) + + def is_correct_type(self, w_key): + return True + + def may_contain_equal_elements(self, strategy): + if strategy is self.space.fromcache(EmptySetStrategy): + return False + return True + + def unwrap(self, w_item): + return w_item + + def wrap(self, item): + return item + + def iter(self, w_set): + return RDictIteratorImplementation(self.space, self, w_set) + + def update(self, w_set, w_other): + d_obj = self.unerase(w_set.sstorage) + w_iterator = w_other.iter() + while True: + w_item = w_iterator.next_entry() + if w_item is None: + break + d_obj[w_item] = None + +class IteratorImplementation(object): + def __init__(self, space, implementation): + self.space = space + self.setimplementation = implementation + self.len = implementation.length() + self.pos = 0 + + def next(self): + if self.setimplementation is None: + return None + if self.len != self.setimplementation.length(): + self.len = -1 # Make this error state sticky + raise OperationError(self.space.w_RuntimeError, + self.space.wrap("set changed size during iteration")) + # look for the next entry + if self.pos < self.len: + result = self.next_entry() + self.pos += 1 + return result + # no more entries + self.setimplementation = None + return None + + def next_entry(self): + """ Purely abstract method + """ + raise NotImplementedError + + def length(self): + if self.setimplementation is not None: + return self.len - self.pos + return 0 + +class EmptyIteratorImplementation(IteratorImplementation): + def next_entry(self): + return None + + +class StringIteratorImplementation(IteratorImplementation): + def __init__(self, space, strategy, w_set): + IteratorImplementation.__init__(self, space, w_set) + d = strategy.unerase(w_set.sstorage) + self.iterator = d.iterkeys() + + def next_entry(self): + for key in self.iterator: + return self.space.wrap(key) + else: + return None + +class IntegerIteratorImplementation(IteratorImplementation): + #XXX same implementation in dictmultiobject on dictstrategy-branch + def __init__(self, space, strategy, dictimplementation): + IteratorImplementation.__init__(self, space, dictimplementation) + d = strategy.unerase(dictimplementation.sstorage) + self.iterator = d.iterkeys() + + def next_entry(self): + # note that this 'for' loop only runs once, at most + for key in self.iterator: + return self.space.wrap(key) + else: + return None + +class RDictIteratorImplementation(IteratorImplementation): + def __init__(self, space, strategy, dictimplementation): + IteratorImplementation.__init__(self, space, dictimplementation) + d = strategy.unerase(dictimplementation.sstorage) + self.iterator = d.iterkeys() + + def next_entry(self): + # note that this 'for' loop only runs once, at most + for w_key in self.iterator: return w_key else: return None +class W_SetIterObject(W_Object): + from pypy.objspace.std.settype import setiter_typedef as typedef + # XXX this class should be killed, and the various + # iterimplementations should be W_Objects directly. + + def __init__(w_self, space, iterimplementation): + w_self.space = space + w_self.iterimplementation = iterimplementation + registerimplementation(W_SetIterObject) def iter__SetIterObject(space, w_setiter): return w_setiter def next__SetIterObject(space, w_setiter): - content = w_setiter.content - if content is not None: - if w_setiter.len != len(content): - w_setiter.len = -1 # Make this error state sticky - raise OperationError(space.w_RuntimeError, - space.wrap("Set changed size during iteration")) - # look for the next entry - w_result = w_setiter.next_entry() - if w_result is not None: - w_setiter.pos += 1 - return w_result - # no more entries - w_setiter.content = None + iterimplementation = w_setiter.iterimplementation + w_key = iterimplementation.next() + if w_key is not None: + return w_key raise OperationError(space.w_StopIteration, space.w_None) # XXX __length_hint__() @@ -116,107 +893,91 @@ def newset(space): return r_dict(space.eq_w, space.hash_w, force_non_null=True) -def make_setdata_from_w_iterable(space, w_iterable=None): - """Return a new r_dict with the content of w_iterable.""" +def set_strategy_and_setdata(space, w_set, w_iterable): + from pypy.objspace.std.intobject import W_IntObject + if w_iterable is None : + w_set.strategy = strategy = space.fromcache(EmptySetStrategy) + w_set.sstorage = strategy.get_empty_storage() + return + if isinstance(w_iterable, W_BaseSetObject): - return w_iterable.setdata.copy() - data = newset(space) - if w_iterable is not None: - for w_item in space.listview(w_iterable): - data[w_item] = None - return data + w_set.strategy = w_iterable.strategy + w_set.sstorage = w_iterable.get_storage_copy() + return + + stringlist = space.listview_str(w_iterable) + if stringlist is not None: + strategy = space.fromcache(StringSetStrategy) + w_set.strategy = strategy + w_set.sstorage = strategy.get_storage_from_unwrapped_list(stringlist) + return + + intlist = space.listview_int(w_iterable) + if intlist is not None: + strategy = space.fromcache(IntegerSetStrategy) + w_set.strategy = strategy + w_set.sstorage = strategy.get_storage_from_unwrapped_list(intlist) + return + + iterable_w = space.listview(w_iterable) + + if len(iterable_w) == 0: + w_set.strategy = strategy = space.fromcache(EmptySetStrategy) + w_set.sstorage = strategy.get_empty_storage() + return + + _pick_correct_strategy(space, w_set, iterable_w) + +def _pick_correct_strategy(space, w_set, iterable_w): + # check for integers + for w_item in iterable_w: + if type(w_item) is not W_IntObject: + break + else: + w_set.strategy = space.fromcache(IntegerSetStrategy) + w_set.sstorage = w_set.strategy.get_storage_from_list(iterable_w) + return + + # check for strings + for w_item in iterable_w: + if type(w_item) is not W_StringObject: + break + else: + w_set.strategy = space.fromcache(StringSetStrategy) + w_set.sstorage = w_set.strategy.get_storage_from_list(iterable_w) + return + + w_set.strategy = space.fromcache(ObjectSetStrategy) + w_set.sstorage = w_set.strategy.get_storage_from_list(iterable_w) def _initialize_set(space, w_obj, w_iterable=None): - w_obj.setdata.clear() - if w_iterable is not None: - w_obj.setdata = make_setdata_from_w_iterable(space, w_iterable) + w_obj.clear() + set_strategy_and_setdata(space, w_obj, w_iterable) def _convert_set_to_frozenset(space, w_obj): - if space.isinstance_w(w_obj, space.w_set): - return W_FrozensetObject(space, - make_setdata_from_w_iterable(space, w_obj)) + if isinstance(w_obj, W_SetObject): + w_frozen = W_FrozensetObject(space, None) + w_frozen.strategy = w_obj.strategy + w_frozen.sstorage = w_obj.sstorage + return w_frozen + elif space.isinstance_w(w_obj, space.w_set): + w_frz = space.allocate_instance(W_FrozensetObject, space.w_frozenset) + W_FrozensetObject.__init__(w_frz, space, w_obj) + return w_frz else: return None -# helper functions for set operation on dicts - -def _is_eq(ld, rd): - if len(ld) != len(rd): - return False - for w_key in ld: - if w_key not in rd: - return False - return True - -def _difference_dict(space, ld, rd): - result = newset(space) - for w_key in ld: - if w_key not in rd: - result[w_key] = None - return result - -def _difference_dict_update(space, ld, rd): - if ld is rd: - ld.clear() # for the case 'a.difference_update(a)' - else: - for w_key in rd: - try: - del ld[w_key] - except KeyError: - pass - -def _intersection_dict(space, ld, rd): - result = newset(space) - if len(ld) > len(rd): - ld, rd = rd, ld # loop over the smaller dict - for w_key in ld: - if w_key in rd: - result[w_key] = None - return result - -def _isdisjoint_dict(ld, rd): - if len(ld) > len(rd): - ld, rd = rd, ld # loop over the smaller dict - for w_key in ld: - if w_key in rd: - return False - return True - -def _symmetric_difference_dict(space, ld, rd): - result = newset(space) - for w_key in ld: - if w_key not in rd: - result[w_key] = None - for w_key in rd: - if w_key not in ld: - result[w_key] = None - return result - -def _issubset_dict(ldict, rdict): - if len(ldict) > len(rdict): - return False - - for w_key in ldict: - if w_key not in rdict: - return False - return True - - -#end helper functions - def set_update__Set(space, w_left, others_w): """Update a set with the union of itself and another.""" - ld = w_left.setdata for w_other in others_w: if isinstance(w_other, W_BaseSetObject): - ld.update(w_other.setdata) # optimization only + w_left.update(w_other) # optimization only else: for w_key in space.listview(w_other): - ld[w_key] = None + w_left.add(w_key) def inplace_or__Set_Set(space, w_left, w_other): - ld, rd = w_left.setdata, w_other.setdata - ld.update(rd) + w_left.update(w_other) return w_left inplace_or__Set_Frozenset = inplace_or__Set_Set @@ -226,10 +987,10 @@ This has no effect if the element is already present. """ - w_left.setdata[w_other] = None + w_left.add(w_other) def set_copy__Set(space, w_set): - return w_set._newobj(space, w_set.setdata.copy()) + return w_set.copy_real() def frozenset_copy__Frozenset(space, w_left): if type(w_left) is W_FrozensetObject: @@ -238,63 +999,51 @@ return set_copy__Set(space, w_left) def set_clear__Set(space, w_left): - w_left.setdata.clear() + w_left.clear() def sub__Set_Set(space, w_left, w_other): - ld, rd = w_left.setdata, w_other.setdata - new_ld = _difference_dict(space, ld, rd) - return w_left._newobj(space, new_ld) + return w_left.difference(w_other) sub__Set_Frozenset = sub__Set_Set sub__Frozenset_Set = sub__Set_Set sub__Frozenset_Frozenset = sub__Set_Set def set_difference__Set(space, w_left, others_w): - result = w_left.setdata - if len(others_w) == 0: - result = result.copy() - for w_other in others_w: - if isinstance(w_other, W_BaseSetObject): - rd = w_other.setdata # optimization only - else: - rd = make_setdata_from_w_iterable(space, w_other) - result = _difference_dict(space, result, rd) - return w_left._newobj(space, result) + result = w_left.copy_real() + set_difference_update__Set(space, result, others_w) + return result frozenset_difference__Frozenset = set_difference__Set def set_difference_update__Set(space, w_left, others_w): - ld = w_left.setdata for w_other in others_w: if isinstance(w_other, W_BaseSetObject): # optimization only - _difference_dict_update(space, ld, w_other.setdata) + w_left.difference_update(w_other) else: - for w_key in space.listview(w_other): - try: - del ld[w_key] - except KeyError: - pass + w_other_as_set = w_left._newobj(space, w_other) + w_left.difference_update(w_other_as_set) def inplace_sub__Set_Set(space, w_left, w_other): - ld, rd = w_left.setdata, w_other.setdata - _difference_dict_update(space, ld, rd) + w_left.difference_update(w_other) return w_left inplace_sub__Set_Frozenset = inplace_sub__Set_Set def eq__Set_Set(space, w_left, w_other): # optimization only (the general case is eq__Set_settypedef) - return space.wrap(_is_eq(w_left.setdata, w_other.setdata)) + return space.wrap(w_left.equals(w_other)) eq__Set_Frozenset = eq__Set_Set eq__Frozenset_Frozenset = eq__Set_Set eq__Frozenset_Set = eq__Set_Set def eq__Set_settypedef(space, w_left, w_other): - rd = make_setdata_from_w_iterable(space, w_other) - return space.wrap(_is_eq(w_left.setdata, rd)) + # tested in test_buildinshortcut.py + #XXX do not make new setobject here + w_other_as_set = w_left._newobj(space, w_other) + return space.wrap(w_left.equals(w_other_as_set)) eq__Set_frozensettypedef = eq__Set_settypedef eq__Frozenset_settypedef = eq__Set_settypedef @@ -308,15 +1057,16 @@ eq__Frozenset_ANY = eq__Set_ANY def ne__Set_Set(space, w_left, w_other): - return space.wrap(not _is_eq(w_left.setdata, w_other.setdata)) + return space.wrap(not w_left.equals(w_other)) ne__Set_Frozenset = ne__Set_Set ne__Frozenset_Frozenset = ne__Set_Set ne__Frozenset_Set = ne__Set_Set def ne__Set_settypedef(space, w_left, w_other): - rd = make_setdata_from_w_iterable(space, w_other) - return space.wrap(not _is_eq(w_left.setdata, rd)) + #XXX this is not tested + w_other_as_set = w_left._newobj(space, w_other) + return space.wrap(not w_left.equals(w_other_as_set)) ne__Set_frozensettypedef = ne__Set_settypedef ne__Frozenset_settypedef = ne__Set_settypedef @@ -331,12 +1081,12 @@ def contains__Set_ANY(space, w_left, w_other): try: - return space.newbool(w_other in w_left.setdata) + return space.newbool(w_left.has_key(w_other)) except OperationError, e: if e.match(space, space.w_TypeError): w_f = _convert_set_to_frozenset(space, w_other) if w_f is not None: - return space.newbool(w_f in w_left.setdata) + return space.newbool(w_left.has_key(w_f)) raise contains__Frozenset_ANY = contains__Set_ANY @@ -345,19 +1095,23 @@ # optimization only (the general case works too) if space.is_w(w_left, w_other): return space.w_True - ld, rd = w_left.setdata, w_other.setdata - return space.wrap(_issubset_dict(ld, rd)) + if w_left.length() > w_other.length(): + return space.w_False + return space.wrap(w_left.issubset(w_other)) set_issubset__Set_Frozenset = set_issubset__Set_Set frozenset_issubset__Frozenset_Set = set_issubset__Set_Set frozenset_issubset__Frozenset_Frozenset = set_issubset__Set_Set def set_issubset__Set_ANY(space, w_left, w_other): - if space.is_w(w_left, w_other): - return space.w_True + # not checking whether w_left is w_other here, because if that were the + # case the more precise multimethod would have applied. - ld, rd = w_left.setdata, make_setdata_from_w_iterable(space, w_other) - return space.wrap(_issubset_dict(ld, rd)) + w_other_as_set = w_left._newobj(space, w_other) + + if w_left.length() > w_other_as_set.length(): + return space.w_False + return space.wrap(w_left.issubset(w_other_as_set)) frozenset_issubset__Frozenset_ANY = set_issubset__Set_ANY @@ -370,9 +1124,9 @@ # optimization only (the general case works too) if space.is_w(w_left, w_other): return space.w_True - - ld, rd = w_left.setdata, w_other.setdata - return space.wrap(_issubset_dict(rd, ld)) + if w_left.length() < w_other.length(): + return space.w_False + return space.wrap(w_other.issubset(w_left)) set_issuperset__Set_Frozenset = set_issuperset__Set_Set set_issuperset__Frozenset_Set = set_issuperset__Set_Set @@ -382,8 +1136,11 @@ if space.is_w(w_left, w_other): return space.w_True - ld, rd = w_left.setdata, make_setdata_from_w_iterable(space, w_other) - return space.wrap(_issubset_dict(rd, ld)) + w_other_as_set = w_left._newobj(space, w_other) + + if w_left.length() < w_other_as_set.length(): + return space.w_False + return space.wrap(w_other_as_set.issubset(w_left)) frozenset_issuperset__Frozenset_ANY = set_issuperset__Set_ANY @@ -395,7 +1152,7 @@ # automatic registration of "lt(x, y)" as "not ge(y, x)" would not give the # correct answer here! def lt__Set_Set(space, w_left, w_other): - if len(w_left.setdata) >= len(w_other.setdata): + if w_left.length() >= w_other.length(): return space.w_False else: return le__Set_Set(space, w_left, w_other) @@ -405,7 +1162,7 @@ lt__Frozenset_Frozenset = lt__Set_Set def gt__Set_Set(space, w_left, w_other): - if len(w_left.setdata) <= len(w_other.setdata): + if w_left.length() <= w_other.length(): return space.w_False else: return ge__Set_Set(space, w_left, w_other) @@ -421,26 +1178,19 @@ Returns True if successfully removed. """ try: - del w_left.setdata[w_item] - return True - except KeyError: - return False + deleted = w_left.remove(w_item) except OperationError, e: if not e.match(space, space.w_TypeError): raise - w_f = _convert_set_to_frozenset(space, w_item) - if w_f is None: - raise + else: + w_f = _convert_set_to_frozenset(space, w_item) + if w_f is None: + raise + deleted = w_left.remove(w_f) - try: - del w_left.setdata[w_f] - return True - except KeyError: - return False - except OperationError, e: - if not e.match(space, space.w_TypeError): - raise - return False + if w_left.length() == 0: + w_left.switch_to_empty_strategy() + return deleted def set_discard__Set_ANY(space, w_left, w_item): _discard_from_set(space, w_left, w_item) @@ -454,8 +1204,12 @@ if w_set.hash != 0: return space.wrap(w_set.hash) hash = r_uint(1927868237) - hash *= r_uint(len(w_set.setdata) + 1) - for w_item in w_set.setdata: + hash *= r_uint(w_set.length() + 1) + w_iterator = w_set.iter() + while True: + w_item = w_iterator.next_entry() + if w_item is None: + break h = space.hash_w(w_item) value = (r_uint(h ^ (h << 16) ^ 89869747) * multi) hash = hash ^ value @@ -468,71 +1222,75 @@ return space.wrap(hash) def set_pop__Set(space, w_left): - try: - w_key, _ = w_left.setdata.popitem() - except KeyError: - raise OperationError(space.w_KeyError, - space.wrap('pop from an empty set')) - return w_key + return w_left.popitem() def and__Set_Set(space, w_left, w_other): - ld, rd = w_left.setdata, w_other.setdata - new_ld = _intersection_dict(space, ld, rd) - return w_left._newobj(space, new_ld) + new_set = w_left.intersect(w_other) + return new_set and__Set_Frozenset = and__Set_Set and__Frozenset_Set = and__Set_Set and__Frozenset_Frozenset = and__Set_Set -def _intersection_multiple(space, w_left, others_w): - result = w_left.setdata - for w_other in others_w: +def set_intersection__Set(space, w_left, others_w): + #XXX find smarter implementations + others_w = [w_left] + others_w + + # find smallest set in others_w to reduce comparisons + startindex, startlength = 0, -1 + for i in range(len(others_w)): + w_other = others_w[i] + try: + length = space.int_w(space.len(w_other)) + except OperationError, e: + if (e.match(space, space.w_TypeError) or + e.match(space, space.w_AttributeError)): + continue + raise + + if startlength == -1 or length < startlength: + startindex = i + startlength = length + + others_w[startindex], others_w[0] = others_w[0], others_w[startindex] + + result = w_left._newobj(space, others_w[0]) + for i in range(1,len(others_w)): + w_other = others_w[i] if isinstance(w_other, W_BaseSetObject): # optimization only - result = _intersection_dict(space, result, w_other.setdata) + result.intersect_update(w_other) else: - result2 = newset(space) - for w_key in space.listview(w_other): - if w_key in result: - result2[w_key] = None - result = result2 + w_other_as_set = w_left._newobj(space, w_other) + result.intersect_update(w_other_as_set) return result -def set_intersection__Set(space, w_left, others_w): - if len(others_w) == 0: - result = w_left.setdata.copy() - else: - result = _intersection_multiple(space, w_left, others_w) - return w_left._newobj(space, result) - frozenset_intersection__Frozenset = set_intersection__Set def set_intersection_update__Set(space, w_left, others_w): - result = _intersection_multiple(space, w_left, others_w) - w_left.setdata = result + result = set_intersection__Set(space, w_left, others_w) + w_left.strategy = result.strategy + w_left.sstorage = result.sstorage + return def inplace_and__Set_Set(space, w_left, w_other): - ld, rd = w_left.setdata, w_other.setdata - new_ld = _intersection_dict(space, ld, rd) - w_left.setdata = new_ld + w_left.intersect_update(w_other) return w_left inplace_and__Set_Frozenset = inplace_and__Set_Set def set_isdisjoint__Set_Set(space, w_left, w_other): # optimization only (the general case works too) - ld, rd = w_left.setdata, w_other.setdata - disjoint = _isdisjoint_dict(ld, rd) - return space.newbool(disjoint) + return space.newbool(w_left.isdisjoint(w_other)) set_isdisjoint__Set_Frozenset = set_isdisjoint__Set_Set set_isdisjoint__Frozenset_Frozenset = set_isdisjoint__Set_Set set_isdisjoint__Frozenset_Set = set_isdisjoint__Set_Set def set_isdisjoint__Set_ANY(space, w_left, w_other): - ld = w_left.setdata + #XXX may be optimized when other strategies are added for w_key in space.listview(w_other): - if w_key in ld: + if w_left.has_key(w_key): return space.w_False return space.w_True @@ -540,9 +1298,8 @@ def set_symmetric_difference__Set_Set(space, w_left, w_other): # optimization only (the general case works too) - ld, rd = w_left.setdata, w_other.setdata - new_ld = _symmetric_difference_dict(space, ld, rd) - return w_left._newobj(space, new_ld) + w_result = w_left.symmetric_difference(w_other) + return w_result set_symmetric_difference__Set_Frozenset = set_symmetric_difference__Set_Set set_symmetric_difference__Frozenset_Set = set_symmetric_difference__Set_Set @@ -556,26 +1313,23 @@ def set_symmetric_difference__Set_ANY(space, w_left, w_other): - ld, rd = w_left.setdata, make_setdata_from_w_iterable(space, w_other) - new_ld = _symmetric_difference_dict(space, ld, rd) - return w_left._newobj(space, new_ld) + w_other_as_set = w_left._newobj(space, w_other) + w_result = w_left.symmetric_difference(w_other_as_set) + return w_result frozenset_symmetric_difference__Frozenset_ANY = \ set_symmetric_difference__Set_ANY def set_symmetric_difference_update__Set_Set(space, w_left, w_other): # optimization only (the general case works too) - ld, rd = w_left.setdata, w_other.setdata - new_ld = _symmetric_difference_dict(space, ld, rd) - w_left.setdata = new_ld + w_left.symmetric_difference_update(w_other) set_symmetric_difference_update__Set_Frozenset = \ set_symmetric_difference_update__Set_Set def set_symmetric_difference_update__Set_ANY(space, w_left, w_other): - ld, rd = w_left.setdata, make_setdata_from_w_iterable(space, w_other) - new_ld = _symmetric_difference_dict(space, ld, rd) - w_left.setdata = new_ld + w_other_as_set = w_left._newobj(space, w_other) + w_left.symmetric_difference_update(w_other_as_set) def inplace_xor__Set_Set(space, w_left, w_other): set_symmetric_difference_update__Set_Set(space, w_left, w_other) @@ -584,34 +1338,33 @@ inplace_xor__Set_Frozenset = inplace_xor__Set_Set def or__Set_Set(space, w_left, w_other): - ld, rd = w_left.setdata, w_other.setdata - result = ld.copy() - result.update(rd) - return w_left._newobj(space, result) + w_copy = w_left.copy_real() + w_copy.update(w_other) + return w_copy or__Set_Frozenset = or__Set_Set or__Frozenset_Set = or__Set_Set or__Frozenset_Frozenset = or__Set_Set def set_union__Set(space, w_left, others_w): - result = w_left.setdata.copy() + result = w_left.copy_real() for w_other in others_w: if isinstance(w_other, W_BaseSetObject): - result.update(w_other.setdata) # optimization only + result.update(w_other) # optimization only else: for w_key in space.listview(w_other): - result[w_key] = None - return w_left._newobj(space, result) + result.add(w_key) + return result frozenset_union__Frozenset = set_union__Set def len__Set(space, w_left): - return space.newint(len(w_left.setdata)) + return space.newint(w_left.length()) len__Frozenset = len__Set def iter__Set(space, w_left): - return W_SetIterObject(w_left.setdata) + return W_SetIterObject(space, w_left.iter()) iter__Frozenset = iter__Set diff --git a/pypy/objspace/std/settype.py b/pypy/objspace/std/settype.py --- a/pypy/objspace/std/settype.py +++ b/pypy/objspace/std/settype.py @@ -68,7 +68,7 @@ def descr__new__(space, w_settype, __args__): from pypy.objspace.std.setobject import W_SetObject, newset w_obj = space.allocate_instance(W_SetObject, w_settype) - W_SetObject.__init__(w_obj, space, newset(space)) + W_SetObject.__init__(w_obj, space) return w_obj set_typedef = StdTypeDef("set", diff --git a/pypy/objspace/std/stringobject.py b/pypy/objspace/std/stringobject.py --- a/pypy/objspace/std/stringobject.py +++ b/pypy/objspace/std/stringobject.py @@ -69,6 +69,14 @@ def str_w(w_self, space): return w_self._value + def listview_str(w_self): + return _create_list_from_string(w_self._value) + +def _create_list_from_string(value): + # need this helper function to allow the jit to look inside and inline + # listview_str + return [s for s in value] + registerimplementation(W_StringObject) W_StringObject.EMPTY = W_StringObject('') diff --git a/pypy/objspace/std/test/test_builtinshortcut.py b/pypy/objspace/std/test/test_builtinshortcut.py --- a/pypy/objspace/std/test/test_builtinshortcut.py +++ b/pypy/objspace/std/test/test_builtinshortcut.py @@ -85,6 +85,20 @@ def setup_class(cls): from pypy import conftest cls.space = conftest.gettestobjspace(**WITH_BUILTINSHORTCUT) + w_fakeint = cls.space.appexec([], """(): + class FakeInt(object): + def __init__(self, value): + self.value = value + def __hash__(self): + return hash(self.value) + + def __eq__(self, other): + if other == self.value: + return True + return False + return FakeInt + """) + cls.w_FakeInt = w_fakeint class AppTestString(test_stringobject.AppTestStringObject): def setup_class(cls): diff --git a/pypy/objspace/std/test/test_dictmultiobject.py b/pypy/objspace/std/test/test_dictmultiobject.py --- a/pypy/objspace/std/test/test_dictmultiobject.py +++ b/pypy/objspace/std/test/test_dictmultiobject.py @@ -131,6 +131,45 @@ assert self.space.eq_w(space.call_function(get, w("33")), w(None)) assert self.space.eq_w(space.call_function(get, w("33"), w(44)), w(44)) + def test_fromkeys_fastpath(self): + space = self.space + w = space.wrap + + w_l = self.space.newlist([w("a"),w("b")]) + w_l.getitems = None + w_d = space.call_method(space.w_dict, "fromkeys", w_l) + + assert space.eq_w(w_d.getitem_str("a"), space.w_None) + assert space.eq_w(w_d.getitem_str("b"), space.w_None) + + def test_listview_str_dict(self): + w = self.space.wrap + + w_d = self.space.newdict() + w_d.initialize_content([(w("a"), w(1)), (w("b"), w(2))]) + + assert self.space.listview_str(w_d) == ["a", "b"] + + def test_listview_int_dict(self): + w = self.space.wrap + w_d = self.space.newdict() + w_d.initialize_content([(w(1), w("a")), (w(2), w("b"))]) + + assert self.space.listview_int(w_d) == [1, 2] + + def test_keys_on_string_int_dict(self): + w = self.space.wrap + w_d = self.space.newdict() + w_d.initialize_content([(w(1), w("a")), (w(2), w("b"))]) + + w_l = self.space.call_method(w_d, "keys") + assert sorted(self.space.listview_int(w_l)) == [1,2] + + w_d = self.space.newdict() + w_d.initialize_content([(w("a"), w(1)), (w("b"), w(6))]) + + w_l = self.space.call_method(w_d, "keys") + assert sorted(self.space.listview_str(w_l)) == ["a", "b"] class AppTest_DictObject: def setup_class(cls): @@ -793,7 +832,9 @@ return x == y eq_w = eq def newlist(self, l): - return [] + return l + def newlist_str(self, l): + return l DictObjectCls = W_DictMultiObject def type(self, w_obj): if isinstance(w_obj, FakeString): @@ -933,7 +974,7 @@ def test_keys(self): self.fill_impl() - keys = self.impl.keys() + keys = self.impl.w_keys() # wrapped lists = lists in the fake space keys.sort() assert keys == [self.string, self.string2] self.check_not_devolved() @@ -1011,8 +1052,8 @@ d.setitem("s", 12) d.delitem(F()) - assert "s" not in d.keys() - assert F() not in d.keys() + assert "s" not in d.w_keys() + assert F() not in d.w_keys() class TestStrDictImplementation(BaseTestRDictImplementation): StrategyClass = StringDictStrategy diff --git a/pypy/objspace/std/test/test_listobject.py b/pypy/objspace/std/test/test_listobject.py --- a/pypy/objspace/std/test/test_listobject.py +++ b/pypy/objspace/std/test/test_listobject.py @@ -486,6 +486,14 @@ list.__init__(l, ['a', 'b', 'c']) assert l is l0 assert l == ['a', 'b', 'c'] + list.__init__(l) + assert l == [] + + def test_explicit_new_init_more_cases(self): + for assignment in [[], (), [3], ["foo"]]: + l = [1, 2] + l.__init__(assignment) + assert l == list(assignment) def test_extend_list(self): l = l0 = [1] @@ -1173,6 +1181,20 @@ assert l == [] assert list(g) == [] + def test_uses_custom_iterator(self): + # obscure corner case: space.listview*() must not shortcut subclasses + # of dicts, because the OrderedDict in the stdlib relies on this. + # we extend the use case to lists and sets, i.e. all types that have + # strategies, to avoid surprizes depending on the strategy. + for base, arg in [(list, []), (list, [5]), (list, ['x']), + (set, []), (set, [5]), (set, ['x']), + (dict, []), (dict, [(5,6)]), (dict, [('x',7)])]: + print base, arg + class SubClass(base): + def __iter__(self): + return iter("foobar") + assert list(SubClass(arg)) == ['f', 'o', 'o', 'b', 'a', 'r'] + class AppTestForRangeLists(AppTestW_ListObject): def setup_class(cls): diff --git a/pypy/objspace/std/test/test_liststrategies.py b/pypy/objspace/std/test/test_liststrategies.py --- a/pypy/objspace/std/test/test_liststrategies.py +++ b/pypy/objspace/std/test/test_liststrategies.py @@ -420,7 +420,7 @@ def test_listview_str(self): space = self.space - assert space.listview_str(space.wrap("a")) is None + assert space.listview_str(space.wrap(1)) == None w_l = self.space.newlist([self.space.wrap('a'), self.space.wrap('b')]) assert space.listview_str(w_l) == ["a", "b"] @@ -463,6 +463,44 @@ w_res = listobject.list_pop__List_ANY(space, w_l, space.w_None) # does not crash assert space.unwrap(w_res) == 3 + def test_create_list_from_set(self): + from pypy.objspace.std.setobject import W_SetObject + from pypy.objspace.std.setobject import _initialize_set + + space = self.space + w = space.wrap + + w_l = W_ListObject(space, [space.wrap(1), space.wrap(2), space.wrap(3)]) + + w_set = W_SetObject(self.space) + _initialize_set(self.space, w_set, w_l) + w_set.iter = None # make sure fast path is used + + w_l2 = W_ListObject(space, []) + space.call_method(w_l2, "__init__", w_set) + + w_l2.sort(False) + assert space.eq_w(w_l, w_l2) + + w_l = W_ListObject(space, [space.wrap("a"), space.wrap("b"), space.wrap("c")]) + _initialize_set(self.space, w_set, w_l) + + space.call_method(w_l2, "__init__", w_set) + + w_l2.sort(False) + assert space.eq_w(w_l, w_l2) + + + def test_listview_str_list(self): + space = self.space + w_l = W_ListObject(space, [space.wrap("a"), space.wrap("b")]) + assert self.space.listview_str(w_l) == ["a", "b"] + + def test_listview_int_list(self): + space = self.space + w_l = W_ListObject(space, [space.wrap(1), space.wrap(2), space.wrap(3)]) + assert self.space.listview_int(w_l) == [1, 2, 3] + class TestW_ListStrategiesDisabled: def setup_class(cls): diff --git a/pypy/objspace/std/test/test_setobject.py b/pypy/objspace/std/test/test_setobject.py --- a/pypy/objspace/std/test/test_setobject.py +++ b/pypy/objspace/std/test/test_setobject.py @@ -8,12 +8,14 @@ is not too wrong. """ import py.test -from pypy.objspace.std.setobject import W_SetObject, W_FrozensetObject +from pypy.objspace.std.setobject import W_SetObject, W_FrozensetObject, IntegerSetStrategy from pypy.objspace.std.setobject import _initialize_set -from pypy.objspace.std.setobject import newset, make_setdata_from_w_iterable +from pypy.objspace.std.setobject import newset from pypy.objspace.std.setobject import and__Set_Set from pypy.objspace.std.setobject import set_intersection__Set from pypy.objspace.std.setobject import eq__Set_Set +from pypy.conftest import gettestobjspace +from pypy.objspace.std.listobject import W_ListObject letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' @@ -29,12 +31,11 @@ self.false = self.space.w_False def test_and(self): - s = W_SetObject(self.space, newset(self.space)) + s = W_SetObject(self.space) _initialize_set(self.space, s, self.word) - t0 = W_SetObject(self.space, newset(self.space)) + t0 = W_SetObject(self.space) _initialize_set(self.space, t0, self.otherword) - t1 = W_FrozensetObject(self.space, - make_setdata_from_w_iterable(self.space, self.otherword)) + t1 = W_FrozensetObject(self.space, self.otherword) r0 = and__Set_Set(self.space, s, t0) r1 = and__Set_Set(self.space, s, t1) assert eq__Set_Set(self.space, r0, r1) == self.true @@ -42,9 +43,9 @@ assert eq__Set_Set(self.space, r0, sr) == self.true def test_compare(self): - s = W_SetObject(self.space, newset(self.space)) + s = W_SetObject(self.space) _initialize_set(self.space, s, self.word) - t = W_SetObject(self.space, newset(self.space)) + t = W_SetObject(self.space) _initialize_set(self.space, t, self.word) assert self.space.eq_w(s,t) u = self.space.wrap(set('simsalabim')) @@ -54,7 +55,247 @@ s = self.space.newset() assert self.space.str_w(self.space.repr(s)) == 'set([])' + def test_intersection_order(self): + # theses tests make sure that intersection is done in the correct order + # (smallest first) + space = self.space + a = W_SetObject(self.space) + _initialize_set(self.space, a, self.space.wrap("abcdefg")) + a.intersect = None + + b = W_SetObject(self.space) + _initialize_set(self.space, b, self.space.wrap("abc")) + + result = set_intersection__Set(space, a, [b]) + assert space.is_true(self.space.eq(result, W_SetObject(space, self.space.wrap("abc")))) + + c = W_SetObject(self.space) + _initialize_set(self.space, c, self.space.wrap("e")) + + d = W_SetObject(self.space) + _initialize_set(self.space, d, self.space.wrap("ab")) + + # if ordering works correct we should start with set e + a.get_storage_copy = None + b.get_storage_copy = None + d.get_storage_copy = None + + result = set_intersection__Set(space, a, [d,c,b]) + assert space.is_true(self.space.eq(result, W_SetObject(space, self.space.wrap("")))) + + def test_create_set_from_list(self): + from pypy.objspace.std.setobject import ObjectSetStrategy, StringSetStrategy + from pypy.objspace.std.floatobject import W_FloatObject + from pypy.objspace.std.model import W_Object + + w = self.space.wrap + intstr = self.space.fromcache(IntegerSetStrategy) + tmp_func = intstr.get_storage_from_list + # test if get_storage_from_list is no longer used + intstr.get_storage_from_list = None + + w_list = W_ListObject(self.space, [w(1), w(2), w(3)]) + w_set = W_SetObject(self.space) + _initialize_set(self.space, w_set, w_list) + assert w_set.strategy is intstr + assert intstr.unerase(w_set.sstorage) == {1:None, 2:None, 3:None} + + w_list = W_ListObject(self.space, [w("1"), w("2"), w("3")]) + w_set = W_SetObject(self.space) + _initialize_set(self.space, w_set, w_list) + assert w_set.strategy is self.space.fromcache(StringSetStrategy) + assert w_set.strategy.unerase(w_set.sstorage) == {"1":None, "2":None, "3":None} + + w_list = W_ListObject(self.space, [w("1"), w(2), w("3")]) + w_set = W_SetObject(self.space) + _initialize_set(self.space, w_set, w_list) + assert w_set.strategy is self.space.fromcache(ObjectSetStrategy) + for item in w_set.strategy.unerase(w_set.sstorage): + assert isinstance(item, W_Object) + + w_list = W_ListObject(self.space, [w(1.0), w(2.0), w(3.0)]) + w_set = W_SetObject(self.space) + _initialize_set(self.space, w_set, w_list) + assert w_set.strategy is self.space.fromcache(ObjectSetStrategy) + for item in w_set.strategy.unerase(w_set.sstorage): + assert isinstance(item, W_FloatObject) + + # changed cached object, need to change it back for other tests to pass + intstr.get_storage_from_list = tmp_func + + def test_listview_str_int_on_set(self): + w = self.space.wrap + + w_a = W_SetObject(self.space) + _initialize_set(self.space, w_a, w("abcdefg")) + assert sorted(self.space.listview_str(w_a)) == list("abcdefg") + assert self.space.listview_int(w_a) is None + + w_b = W_SetObject(self.space) + _initialize_set(self.space, w_b, self.space.newlist([w(1),w(2),w(3),w(4),w(5)])) + assert sorted(self.space.listview_int(w_b)) == [1,2,3,4,5] + assert self.space.listview_str(w_b) is None + class AppTestAppSetTest: + + def setup_class(self): + self.space = gettestobjspace() + w_fakeint = self.space.appexec([], """(): + class FakeInt(object): + def __init__(self, value): + self.value = value + def __hash__(self): + return hash(self.value) + + def __eq__(self, other): + if other == self.value: + return True + return False + return FakeInt + """) + self.w_FakeInt = w_fakeint + + def test_fakeint(self): + f1 = self.FakeInt(4) + assert f1 == 4 + assert hash(f1) == hash(4) + + def test_simple(self): + a = set([1,2,3]) + b = set() + b.add(4) + c = a.union(b) + assert c == set([1,2,3,4]) + + def test_generator(self): + def foo(): + for i in [1,2,3,4,5]: + yield i + b = set(foo()) + assert b == set([1,2,3,4,5]) + + a = set(x for x in [1,2,3]) + assert a == set([1,2,3]) + + def test_generator2(self): + def foo(): + for i in [1,2,3]: + yield i + class A(set): + pass + a = A([1,2,3,4,5]) + b = a.difference(foo()) + assert b == set([4,5]) + + def test_or(self): + a = set([0,1,2]) + b = a | set([1,2,3]) + assert b == set([0,1,2,3]) + + # test inplace or + a |= set([1,2,3]) + assert a == b + + def test_clear(self): + a = set([1,2,3]) + a.clear() + assert a == set() + + def test_sub(self): + a = set([1,2,3,4,5]) + b = set([2,3,4]) + a - b == [1,5] + a.__sub__(b) == [1,5] + + #inplace sub + a = set([1,2,3,4]) + b = set([1,4]) + a -= b + assert a == set([2,3]) + + def test_issubset(self): + a = set([1,2,3,4]) + b = set([2,3]) + assert b.issubset(a) + c = [1,2,3,4] + assert b.issubset(c) + + a = set([1,2,3,4]) + b = set(['1','2']) + assert not b.issubset(a) + + def test_issuperset(self): + a = set([1,2,3,4]) + b = set([2,3]) + assert a.issuperset(b) + c = [2,3] + assert a.issuperset(c) + + c = [1,1,1,1,1] + assert a.issuperset(c) + assert set([1,1,1,1,1]).issubset(a) + + a = set([1,2,3]) + assert a.issuperset(a) + assert not a.issuperset(set([1,2,3,4,5])) + + def test_inplace_and(test): + a = set([1,2,3,4]) + b = set([0,2,3,5,6]) + a &= b + assert a == set([2,3]) + + def test_discard_remove(self): + a = set([1,2,3,4,5]) + a.remove(1) + assert a == set([2,3,4,5]) + a.discard(2) + assert a == set([3,4,5]) + + raises(KeyError, "a.remove(6)") + + def test_pop(self): + b = set() + raises(KeyError, "b.pop()") + + a = set([1,2,3,4,5]) + for i in xrange(5): + a.pop() + assert a == set() + raises(KeyError, "a.pop()") + + def test_symmetric_difference(self): + a = set([1,2,3]) + b = set([3,4,5]) + c = a.symmetric_difference(b) + assert c == set([1,2,4,5]) + + a = set([1,2,3]) + b = [3,4,5] + c = a.symmetric_difference(b) + assert c == set([1,2,4,5]) + + a = set([1,2,3]) + b = set('abc') + c = a.symmetric_difference(b) + assert c == set([1,2,3,'a','b','c']) + + def test_symmetric_difference_update(self): + a = set([1,2,3]) + b = set([3,4,5]) + a.symmetric_difference_update(b) + assert a == set([1,2,4,5]) + + a = set([1,2,3]) + b = [3,4,5] + a.symmetric_difference_update(b) + assert a == set([1,2,4,5]) + + a = set([1,2,3]) + b = set([3,4,5]) + a ^= b + assert a == set([1,2,4,5]) + def test_subtype(self): class subset(set):pass a = subset() @@ -131,6 +372,8 @@ assert (set('abc') != set('abcd')) assert (frozenset('abc') != frozenset('abcd')) assert (frozenset('abc') != set('abcd')) + assert set() != set('abc') + assert set('abc') != set('abd') def test_libpython_equality(self): for thetype in [frozenset, set]: @@ -178,6 +421,9 @@ s1 = set('abc') s1.update('d', 'ef', frozenset('g')) assert s1 == set('abcdefg') + s1 = set() + s1.update(set('abcd')) + assert s1 == set('abcd') def test_recursive_repr(self): class A(object): @@ -330,6 +576,7 @@ assert not set([1,2,5]).isdisjoint(frozenset([4,5,6])) assert not set([1,2,5]).isdisjoint([4,5,6]) assert not set([1,2,5]).isdisjoint((4,5,6)) + assert set([1,2,3]).isdisjoint(set([3.5,4.0])) def test_intersection(self): assert set([1,2,3]).intersection(set([2,3,4])) == set([2,3]) @@ -347,6 +594,35 @@ assert s.intersection() == s assert s.intersection() is not s + def test_intersection_swap(self): + s1 = s3 = set([1,2,3,4,5]) + s2 = set([2,3,6,7]) + s1 &= s2 + assert s1 == set([2,3]) + assert s3 == set([2,3]) + + def test_intersection_generator(self): + def foo(): + for i in range(5): + yield i + + s1 = s2 = set([1,2,3,4,5,6]) + assert s1.intersection(foo()) == set([1,2,3,4]) + s1.intersection_update(foo()) + assert s1 == set([1,2,3,4]) + assert s2 == set([1,2,3,4]) + + def test_intersection_string(self): + s = set([1,2,3]) + o = 'abc' + assert s.intersection(o) == set() + + def test_intersection_float(self): + a = set([1,2,3]) + b = set([3.0,4.0,5.0]) + c = a.intersection(b) + assert c == set([3.0]) + def test_difference(self): assert set([1,2,3]).difference(set([2,3,4])) == set([1]) assert set([1,2,3]).difference(frozenset([2,3,4])) == set([1]) @@ -361,6 +637,9 @@ s = set([1,2,3]) assert s.difference() == s assert s.difference() is not s + assert set([1,2,3]).difference(set([2,3,4,'5'])) == set([1]) + assert set([1,2,3,'5']).difference(set([2,3,4])) == set([1,'5']) + assert set().difference(set([1,2,3])) == set() def test_intersection_update(self): s = set([1,2,3,4,7]) @@ -381,3 +660,250 @@ assert s == set([2,3]) s.difference_update(s) assert s == set([]) + + def test_empty_empty(self): + assert set() == set([]) + + def test_empty_difference(self): + e = set() + x = set([1,2,3]) + assert e.difference(x) == set() + assert x.difference(e) == x + + e.difference_update(x) + assert e == set() + x.difference_update(e) + assert x == set([1,2,3]) + + assert e.symmetric_difference(x) == x + assert x.symmetric_difference(e) == x + + e.symmetric_difference_update(e) + assert e == e + e.symmetric_difference_update(x) + assert e == x + + x.symmetric_difference_update(set()) + assert x == set([1,2,3]) + + def test_fastpath_with_strategies(self): + a = set([1,2,3]) + b = set(["a","b","c"]) + assert a.difference(b) == a + assert b.difference(a) == b + + a = set([1,2,3]) + b = set(["a","b","c"]) + assert a.intersection(b) == set() + assert b.intersection(a) == set() + + a = set([1,2,3]) + b = set(["a","b","c"]) + assert not a.issubset(b) + assert not b.issubset(a) + + a = set([1,2,3]) + b = set(["a","b","c"]) + assert a.isdisjoint(b) + assert b.isdisjoint(a) + + def test_empty_intersect(self): + e = set() + x = set([1,2,3]) + assert e.intersection(x) == e + assert x.intersection(e) == e + assert e & x == e + assert x & e == e + + e.intersection_update(x) + assert e == set() + e &= x + assert e == set() + x.intersection_update(e) + assert x == set() + + def test_empty_issuper(self): + e = set() + x = set([1,2,3]) + assert e.issuperset(e) == True + assert e.issuperset(x) == False + assert x.issuperset(e) == True + + assert e.issuperset(set()) + assert e.issuperset([]) + + def test_empty_issubset(self): + e = set() + x = set([1,2,3]) + assert e.issubset(e) == True + assert e.issubset(x) == True + assert x.issubset(e) == False + assert e.issubset([]) + + def test_empty_isdisjoint(self): + e = set() + x = set([1,2,3]) + assert e.isdisjoint(e) == True + assert e.isdisjoint(x) == True + assert x.isdisjoint(e) == True + + def test_empty_unhashable(self): + s = set() + raises(TypeError, s.difference, [[]]) + raises(TypeError, s.difference_update, [[]]) + raises(TypeError, s.intersection, [[]]) + raises(TypeError, s.intersection_update, [[]]) + raises(TypeError, s.symmetric_difference, [[]]) + raises(TypeError, s.symmetric_difference_update, [[]]) + raises(TypeError, s.update, [[]]) + + def test_super_with_generator(self): + def foo(): + for i in [1,2,3]: + yield i + set([1,2,3,4,5]).issuperset(foo()) + + def test_isdisjoint_with_generator(self): + def foo(): + for i in [1,2,3]: + yield i + set([1,2,3,4,5]).isdisjoint(foo()) + + def test_fakeint_and_equals(self): + s1 = set([1,2,3,4]) + s2 = set([1,2,self.FakeInt(3), 4]) + assert s1 == s2 + + def test_fakeint_and_discard(self): + # test with object strategy + s = set([1, 2, 'three', 'four']) + s.discard(self.FakeInt(2)) + assert s == set([1, 'three', 'four']) + + s.remove(self.FakeInt(1)) + assert s == set(['three', 'four']) + raises(KeyError, s.remove, self.FakeInt(16)) + + # test with int strategy + s = set([1,2,3,4]) + s.discard(self.FakeInt(4)) + assert s == set([1,2,3]) + s.remove(self.FakeInt(3)) + assert s == set([1,2]) + raises(KeyError, s.remove, self.FakeInt(16)) + + def test_fakeobject_and_has_key(self): + s = set([1,2,3,4,5]) + assert 5 in s + assert self.FakeInt(5) in s + + def test_fakeobject_and_pop(self): + s = set([1,2,3,self.FakeInt(4),5]) + assert s.pop() + assert s.pop() + assert s.pop() + assert s.pop() + assert s.pop() + assert s == set([]) + + def test_fakeobject_and_difference(self): + s = set([1,2,'3',4]) + s.difference_update([self.FakeInt(1), self.FakeInt(2)]) + assert s == set(['3',4]) + + s = set([1,2,3,4]) + s.difference_update([self.FakeInt(1), self.FakeInt(2)]) + assert s == set([3,4]) + + def test_frozenset_behavior(self): + s = set([1,2,3,frozenset([4])]) + raises(TypeError, s.difference_update, [1,2,3,set([4])]) + + s = set([1,2,3,frozenset([4])]) + s.discard(set([4])) + assert s == set([1,2,3]) + + def test_discard_unhashable(self): + s = set([1,2,3,4]) + raises(TypeError, s.discard, [1]) + + def test_discard_evil_compare(self): + class Evil(object): + def __init__(self, value): + self.value = value + def __hash__(self): + return hash(self.value) + def __eq__(self, other): + if isinstance(other, frozenset): + raise TypeError + if other == self.value: + return True + return False + s = set([1,2, Evil(frozenset([1]))]) + raises(TypeError, s.discard, set([1])) + + def test_create_set_from_set(self): + # no sharing + x = set([1,2,3]) + y = set(x) + a = x.pop() + assert y == set([1,2,3]) + assert len(x) == 2 + assert x.union(set([a])) == y + + def test_never_change_frozenset(self): + a = frozenset([1,2]) + b = a.copy() + assert a is b + + a = frozenset([1,2]) + b = a.union(set([3,4])) + assert b == set([1,2,3,4]) + assert a == set([1,2]) + + a = frozenset() + b = a.union(set([3,4])) + assert b == set([3,4]) + assert a == set() + + a = frozenset([1,2])#multiple + b = a.union(set([3,4]),[5,6]) + assert b == set([1,2,3,4,5,6]) + assert a == set([1,2]) + + a = frozenset([1,2,3]) + b = a.difference(set([3,4,5])) + assert b == set([1,2]) + assert a == set([1,2,3]) + + a = frozenset([1,2,3])#multiple + b = a.difference(set([3]), [2]) + assert b == set([1]) + assert a == set([1,2,3]) + + a = frozenset([1,2,3]) + b = a.symmetric_difference(set([3,4,5])) + assert b == set([1,2,4,5]) + assert a == set([1,2,3]) + + a = frozenset([1,2,3]) + b = a.intersection(set([3,4,5])) + assert b == set([3]) + assert a == set([1,2,3]) + + a = frozenset([1,2,3])#multiple + b = a.intersection(set([2,3,4]), [2]) + assert b == set([2]) + assert a == set([1,2,3]) + + raises(AttributeError, "frozenset().update()") + raises(AttributeError, "frozenset().difference_update()") + raises(AttributeError, "frozenset().symmetric_difference_update()") + raises(AttributeError, "frozenset().intersection_update()") + + def test_intersection_obj(self): + class Obj: + def __getitem__(self, i): + return [5, 3, 4][i] + s = set([10,3,2]).intersection(Obj()) + assert list(s) == [3] diff --git a/pypy/objspace/std/test/test_setstrategies.py b/pypy/objspace/std/test/test_setstrategies.py new file mode 100644 --- /dev/null +++ b/pypy/objspace/std/test/test_setstrategies.py @@ -0,0 +1,107 @@ +from pypy.objspace.std.setobject import W_SetObject +from pypy.objspace.std.setobject import IntegerSetStrategy, ObjectSetStrategy, EmptySetStrategy +from pypy.objspace.std.listobject import W_ListObject + +class TestW_SetStrategies: + + def wrapped(self, l): + return W_ListObject(self.space, [self.space.wrap(x) for x in l]) + + def test_from_list(self): + s = W_SetObject(self.space, self.wrapped([1,2,3,4,5])) + assert s.strategy is self.space.fromcache(IntegerSetStrategy) + + s = W_SetObject(self.space, self.wrapped([1,"two",3,"four",5])) + assert s.strategy is self.space.fromcache(ObjectSetStrategy) + + s = W_SetObject(self.space) + assert s.strategy is self.space.fromcache(EmptySetStrategy) + + s = W_SetObject(self.space, self.wrapped([])) + assert s.strategy is self.space.fromcache(EmptySetStrategy) + + def test_switch_to_object(self): + s = W_SetObject(self.space, self.wrapped([1,2,3,4,5])) + s.add(self.space.wrap("six")) + assert s.strategy is self.space.fromcache(ObjectSetStrategy) + + s1 = W_SetObject(self.space, self.wrapped([1,2,3,4,5])) + s2 = W_SetObject(self.space, self.wrapped(["six", "seven"])) + s1.update(s2) + assert s1.strategy is self.space.fromcache(ObjectSetStrategy) + + def test_symmetric_difference(self): + s1 = W_SetObject(self.space, self.wrapped([1,2,3,4,5])) + s2 = W_SetObject(self.space, self.wrapped(["six", "seven"])) + s1.symmetric_difference_update(s2) + assert s1.strategy is self.space.fromcache(ObjectSetStrategy) + + def test_intersection(self): + s1 = W_SetObject(self.space, self.wrapped([1,2,3,4,5])) + s2 = W_SetObject(self.space, self.wrapped([4,5, "six", "seven"])) + s3 = s1.intersect(s2) + skip("for now intersection with ObjectStrategy always results in another ObjectStrategy") + assert s3.strategy is self.space.fromcache(IntegerSetStrategy) + + def test_clear(self): + s1 = W_SetObject(self.space, self.wrapped([1,2,3,4,5])) + s1.clear() + assert s1.strategy is self.space.fromcache(EmptySetStrategy) + + def test_remove(self): + from pypy.objspace.std.setobject import set_remove__Set_ANY + s1 = W_SetObject(self.space, self.wrapped([1])) + set_remove__Set_ANY(self.space, s1, self.space.wrap(1)) + assert s1.strategy is self.space.fromcache(EmptySetStrategy) + + def test_union(self): + from pypy.objspace.std.setobject import set_union__Set + s1 = W_SetObject(self.space, self.wrapped([1,2,3,4,5])) + s2 = W_SetObject(self.space, self.wrapped([4,5,6,7])) + s3 = W_SetObject(self.space, self.wrapped([4,'5','6',7])) + s4 = set_union__Set(self.space, s1, [s2]) + s5 = set_union__Set(self.space, s1, [s3]) + assert s4.strategy is self.space.fromcache(IntegerSetStrategy) + assert s5.strategy is self.space.fromcache(ObjectSetStrategy) + + def test_discard(self): + class FakeInt(object): + def __init__(self, value): + self.value = value + def __hash__(self): + return hash(self.value) + def __eq__(self, other): + if other == self.value: + return True + return False + + from pypy.objspace.std.setobject import set_discard__Set_ANY + + s1 = W_SetObject(self.space, self.wrapped([1,2,3,4,5])) + set_discard__Set_ANY(self.space, s1, self.space.wrap("five")) + skip("currently not supported") + assert s1.strategy is self.space.fromcache(IntegerSetStrategy) + + set_discard__Set_ANY(self.space, s1, self.space.wrap(FakeInt(5))) + assert s1.strategy is self.space.fromcache(ObjectSetStrategy) + + def test_has_key(self): + class FakeInt(object): + def __init__(self, value): + self.value = value + def __hash__(self): + return hash(self.value) + def __eq__(self, other): + if other == self.value: + return True + return False + + from pypy.objspace.std.setobject import set_discard__Set_ANY + + s1 = W_SetObject(self.space, self.wrapped([1,2,3,4,5])) + assert not s1.has_key(self.space.wrap("five")) + skip("currently not supported") + assert s1.strategy is self.space.fromcache(IntegerSetStrategy) + + assert s1.has_key(self.space.wrap(FakeInt(2))) + assert s1.strategy is self.space.fromcache(ObjectSetStrategy) diff --git a/pypy/objspace/std/test/test_stringobject.py b/pypy/objspace/std/test/test_stringobject.py --- a/pypy/objspace/std/test/test_stringobject.py +++ b/pypy/objspace/std/test/test_stringobject.py @@ -85,6 +85,10 @@ w_slice = space.newslice(w(1), w_None, w(2)) assert self.space.eq_w(space.getitem(w_str, w_slice), w('el')) + def test_listview_str(self): + w_str = self.space.wrap('abcd') + assert self.space.listview_str(w_str) == list("abcd") + class AppTestStringObject: def test_format_wrongchar(self): diff --git a/pypy/objspace/std/test/test_userobject.py b/pypy/objspace/std/test/test_userobject.py --- a/pypy/objspace/std/test/test_userobject.py +++ b/pypy/objspace/std/test/test_userobject.py @@ -244,8 +244,12 @@ skip("disabled") if self.runappdirect: total = 500000 + def rand(): + import random + return random.randrange(0, 5) else: total = 50 + rand = self.rand # class A(object): hash = None @@ -256,7 +260,7 @@ a = A() a.next = tail.next tail.next = a - for j in range(self.rand()): + for j in range(rand()): any = any.next if any.hash is None: any.hash = hash(any) diff --git a/pypy/rlib/_rffi_stacklet.py b/pypy/rlib/_rffi_stacklet.py --- a/pypy/rlib/_rffi_stacklet.py +++ b/pypy/rlib/_rffi_stacklet.py @@ -14,7 +14,7 @@ includes = ['src/stacklet/stacklet.h'], separate_module_sources = ['#include "src/stacklet/stacklet.c"\n'], ) -if sys.platform == 'win32': +if 'masm' in dir(eci.platform): # Microsoft compiler if is_emulated_long: asmsrc = 'switch_x64_msvc.asm' else: diff --git a/pypy/rlib/_rsocket_rffi.py b/pypy/rlib/_rsocket_rffi.py --- a/pypy/rlib/_rsocket_rffi.py +++ b/pypy/rlib/_rsocket_rffi.py @@ -58,12 +58,12 @@ header_lines = [ '#include ', '#include ', + '#include ', # winsock2 defines AF_UNIX, but not sockaddr_un '#undef AF_UNIX', ] if _MSVC: header_lines.extend([ - '#include ', # these types do not exist on microsoft compilers 'typedef int ssize_t;', 'typedef unsigned __int16 uint16_t;', @@ -71,6 +71,7 @@ ]) else: # MINGW includes = ('stdint.h',) + """ header_lines.extend([ '''\ #ifndef _WIN32_WINNT @@ -88,6 +89,7 @@ u_long keepaliveinterval; };''' ]) + """ HEADER = '\n'.join(header_lines) COND_HEADER = '' constants = {} diff --git a/pypy/rlib/clibffi.py b/pypy/rlib/clibffi.py --- a/pypy/rlib/clibffi.py +++ b/pypy/rlib/clibffi.py @@ -114,9 +114,10 @@ ) eci = rffi_platform.configure_external_library( - 'libffi', eci, + 'libffi-5', eci, [dict(prefix='libffi-', include_dir='include', library_dir='.libs'), + dict(prefix=r'c:\mingw64', include_dir='include', library_dir='lib'), ]) else: libffidir = py.path.local(pypydir).join('translator', 'c', 'src', 'libffi_msvc') diff --git a/pypy/rlib/longlong2float.py b/pypy/rlib/longlong2float.py --- a/pypy/rlib/longlong2float.py +++ b/pypy/rlib/longlong2float.py @@ -6,6 +6,7 @@ in which it does not work. """ +from __future__ import with_statement from pypy.annotation import model as annmodel from pypy.rlib.rarithmetic import r_int64 from pypy.rpython.lltypesystem import lltype, rffi diff --git a/pypy/rlib/parsing/pypackrat.py b/pypy/rlib/parsing/pypackrat.py --- a/pypy/rlib/parsing/pypackrat.py +++ b/pypy/rlib/parsing/pypackrat.py @@ -1,6 +1,8 @@ from pypy.rlib.parsing.tree import Nonterminal, Symbol -from makepackrat import PackratParser, BacktrackException, Status +from pypy.rlib.parsing.makepackrat import PackratParser, BacktrackException, Status + + class Parser(object): def NAME(self): return self._NAME().result diff --git a/pypy/rlib/rmmap.py b/pypy/rlib/rmmap.py --- a/pypy/rlib/rmmap.py +++ b/pypy/rlib/rmmap.py @@ -713,9 +713,9 @@ free = c_munmap_safe elif _MS_WINDOWS: - def mmap(fileno, length, flags=0, tagname="", access=_ACCESS_DEFAULT, offset=0): + def mmap(fileno, length, tagname="", access=_ACCESS_DEFAULT, offset=0): # XXX flags is or-ed into access by now. - + flags = 0 # check size boundaries _check_map_size(length) map_size = length diff --git a/pypy/rlib/rwin32.py b/pypy/rlib/rwin32.py --- a/pypy/rlib/rwin32.py +++ b/pypy/rlib/rwin32.py @@ -141,6 +141,10 @@ cfile = udir.join('dosmaperr.c') cfile.write(r''' #include + #include + #ifdef __GNUC__ + #define _dosmaperr mingw_dosmaperr + #endif int main() { int i; diff --git a/pypy/rlib/test/autopath.py b/pypy/rlib/test/autopath.py new file mode 100644 --- /dev/null +++ b/pypy/rlib/test/autopath.py @@ -0,0 +1,131 @@ +""" +self cloning, automatic path configuration + +copy this into any subdirectory of pypy from which scripts need +to be run, typically all of the test subdirs. +The idea is that any such script simply issues + + import autopath + +and this will make sure that the parent directory containing "pypy" +is in sys.path. + +If you modify the master "autopath.py" version (in pypy/tool/autopath.py) +you can directly run it which will copy itself on all autopath.py files +it finds under the pypy root directory. + +This module always provides these attributes: + + pypydir pypy root directory path + this_dir directory where this autopath.py resides + +""" + +def __dirinfo(part): + """ return (partdir, this_dir) and insert parent of partdir + into sys.path. If the parent directories don't have the part + an EnvironmentError is raised.""" + + import sys, os + try: + head = this_dir = os.path.realpath(os.path.dirname(__file__)) + except NameError: + head = this_dir = os.path.realpath(os.path.dirname(sys.argv[0])) + + error = None + while head: + partdir = head + head, tail = os.path.split(head) + if tail == part: + checkfile = os.path.join(partdir, os.pardir, 'pypy', '__init__.py') + if not os.path.exists(checkfile): + error = "Cannot find %r" % (os.path.normpath(checkfile),) + break + else: + error = "Cannot find the parent directory %r of the path %r" % ( + partdir, this_dir) + if not error: + # check for bogus end-of-line style (e.g. files checked out on + # Windows and moved to Unix) + f = open(__file__.replace('.pyc', '.py'), 'r') + data = f.read() + f.close() + if data.endswith('\r\n') or data.endswith('\r'): + error = ("Bad end-of-line style in the .py files. Typically " + "caused by a zip file or a checkout done on Windows and " + "moved to Unix or vice-versa.") + if error: + raise EnvironmentError("Invalid source tree - bogus checkout! " + + error) + + pypy_root = os.path.join(head, '') + try: + sys.path.remove(head) + except ValueError: + pass + sys.path.insert(0, head) + + munged = {} + for name, mod in sys.modules.items(): + if '.' in name: + continue + fn = getattr(mod, '__file__', None) + if not isinstance(fn, str): + continue + newname = os.path.splitext(os.path.basename(fn))[0] + if not newname.startswith(part + '.'): + continue + path = os.path.join(os.path.dirname(os.path.realpath(fn)), '') + if path.startswith(pypy_root) and newname != part: + modpaths = os.path.normpath(path[len(pypy_root):]).split(os.sep) + if newname != '__init__': + modpaths.append(newname) + modpath = '.'.join(modpaths) + if modpath not in sys.modules: + munged[modpath] = mod + + for name, mod in munged.iteritems(): + if name not in sys.modules: + sys.modules[name] = mod + if '.' in name: + prename = name[:name.rfind('.')] + postname = name[len(prename)+1:] + if prename not in sys.modules: + __import__(prename) + if not hasattr(sys.modules[prename], postname): + setattr(sys.modules[prename], postname, mod) + + return partdir, this_dir + +def __clone(): + """ clone master version of autopath.py into all subdirs """ + from os.path import join, walk + if not this_dir.endswith(join('pypy','tool')): + raise EnvironmentError("can only clone master version " + "'%s'" % join(pypydir, 'tool',_myname)) + + + def sync_walker(arg, dirname, fnames): + if _myname in fnames: + fn = join(dirname, _myname) + f = open(fn, 'rwb+') + try: + if f.read() == arg: + print "checkok", fn + else: + print "syncing", fn + f = open(fn, 'w') + f.write(arg) + finally: + f.close() + s = open(join(pypydir, 'tool', _myname), 'rb').read() + walk(pypydir, sync_walker, s) + +_myname = 'autopath.py' + +# set guaranteed attributes + +pypydir, this_dir = __dirinfo('pypy') + +if __name__ == '__main__': + __clone() diff --git a/pypy/rpython/lltypesystem/opimpl.py b/pypy/rpython/lltypesystem/opimpl.py --- a/pypy/rpython/lltypesystem/opimpl.py +++ b/pypy/rpython/lltypesystem/opimpl.py @@ -427,6 +427,10 @@ ## assert type(x) is int ## return llmemory.cast_int_to_adr(x) +def op_convert_float_bytes_to_longlong(a): + from pypy.rlib.longlong2float import float2longlong + return float2longlong(a) + def op_unichar_eq(x, y): assert isinstance(x, unicode) and len(x) == 1 diff --git a/pypy/rpython/rstr.py b/pypy/rpython/rstr.py --- a/pypy/rpython/rstr.py +++ b/pypy/rpython/rstr.py @@ -165,6 +165,7 @@ v_char = hop.inputarg(rstr.char_repr, arg=1) v_left = hop.inputconst(Bool, left) v_right = hop.inputconst(Bool, right) + hop.exception_is_here() return hop.gendirectcall(self.ll.ll_strip, v_str, v_char, v_left, v_right) def rtype_method_lstrip(self, hop): diff --git a/pypy/rpython/test/test_rstr.py b/pypy/rpython/test/test_rstr.py --- a/pypy/rpython/test/test_rstr.py +++ b/pypy/rpython/test/test_rstr.py @@ -637,13 +637,16 @@ def _make_split_test(self, split_fn): const = self.const def fn(i): - s = [const(''), const('0.1.2.4.8'), const('.1.2'), const('1.2.'), const('.1.2.4.')][i] - l = getattr(s, split_fn)(const('.')) - sum = 0 - for num in l: - if len(num): - sum += ord(num[0]) - ord(const('0')[0]) - return sum + len(l) * 100 + try: + s = [const(''), const('0.1.2.4.8'), const('.1.2'), const('1.2.'), const('.1.2.4.')][i] + l = getattr(s, split_fn)(const('.')) + sum = 0 + for num in l: + if len(num): + sum += ord(num[0]) - ord(const('0')[0]) + return sum + len(l) * 100 + except MemoryError: + return 42 return fn def test_split(self): diff --git a/pypy/rpython/tool/rffi_platform.py b/pypy/rpython/tool/rffi_platform.py --- a/pypy/rpython/tool/rffi_platform.py +++ b/pypy/rpython/tool/rffi_platform.py @@ -660,8 +660,8 @@ if isinstance(fieldtype, lltype.FixedSizeArray): size, _ = expected_size_and_sign return lltype.FixedSizeArray(fieldtype.OF, size/_sizeof(fieldtype.OF)) - raise TypeError("conflicting field type %r for %r" % (fieldtype, - fieldname)) + raise TypeError("conflict between translating python and compiler field" + " type %r for %r" % (fieldtype, fieldname)) def expose_value_as_rpython(value): if intmask(value) == value: diff --git a/pypy/tool/release/package.py b/pypy/tool/release/package.py --- a/pypy/tool/release/package.py +++ b/pypy/tool/release/package.py @@ -58,9 +58,13 @@ binaries = [(pypy_c, rename_pypy_c)] # if sys.platform == 'win32': + #Don't include a mscvrXX.dll, users should get their own. + #Instructions are provided on the website. + # Can't rename a DLL: it is always called 'libpypy-c.dll' + for extra in ['libpypy-c.dll', - 'libexpat.dll', 'sqlite3.dll', 'msvcr100.dll', + 'libexpat.dll', 'sqlite3.dll', 'libeay32.dll', 'ssleay32.dll']: p = pypy_c.dirpath().join(extra) if not p.check(): diff --git a/pypy/translator/c/gcc/test/test_asmgcroot.py b/pypy/translator/c/gcc/test/test_asmgcroot.py --- a/pypy/translator/c/gcc/test/test_asmgcroot.py +++ b/pypy/translator/c/gcc/test/test_asmgcroot.py @@ -6,6 +6,7 @@ from pypy.annotation.listdef import s_list_of_strings from pypy import conftest from pypy.translator.tool.cbuild import ExternalCompilationInfo +from pypy.translator.platform import platform as compiler from pypy.rpython.lltypesystem import lltype, rffi from pypy.rlib.entrypoint import entrypoint, secondary_entrypoints from pypy.rpython.lltypesystem.lloperation import llop @@ -17,6 +18,8 @@ @classmethod def make_config(cls): + if compiler.name == "msvc": + py.test.skip("all asmgcroot tests disabled for MSVC") from pypy.config.pypyoption import get_pypy_config config = get_pypy_config(translating=True) config.translation.gc = cls.gcpolicy diff --git a/pypy/translator/c/gcc/trackgcroot.py b/pypy/translator/c/gcc/trackgcroot.py --- a/pypy/translator/c/gcc/trackgcroot.py +++ b/pypy/translator/c/gcc/trackgcroot.py @@ -484,7 +484,9 @@ 'shl', 'shr', 'sal', 'sar', 'rol', 'ror', 'mul', 'imul', 'div', 'idiv', 'bswap', 'bt', 'rdtsc', 'punpck', 'pshufd', 'pcmp', 'pand', 'psllw', 'pslld', 'psllq', - 'paddq', 'pinsr', 'pmul', 'psrl', 'vmul', + 'paddq', 'pinsr', 'pmul', 'psrl', + # all vectors don't produce pointers + 'v', # sign-extending moves should not produce GC pointers 'cbtw', 'cwtl', 'cwtd', 'cltd', 'cltq', 'cqto', # zero-extending moves should not produce GC pointers diff --git a/pypy/translator/c/src/libffi_msvc/win64.asm b/pypy/translator/c/src/libffi_msvc/win64.asm new file mode 100644 --- /dev/null +++ b/pypy/translator/c/src/libffi_msvc/win64.asm @@ -0,0 +1,156 @@ +PUBLIC ffi_call_AMD64 + +EXTRN __chkstk:NEAR +EXTRN ffi_closure_SYSV:NEAR + +_TEXT SEGMENT + +;;; ffi_closure_OUTER will be called with these registers set: +;;; rax points to 'closure' +;;; r11 contains a bit mask that specifies which of the +;;; first four parameters are float or double +;;; +;;; It must move the parameters passed in registers to their stack location, +;;; call ffi_closure_SYSV for the actual work, then return the result. +;;; +ffi_closure_OUTER PROC FRAME + ;; save actual arguments to their stack space. + test r11, 1 + jne first_is_float + mov QWORD PTR [rsp+8], rcx + jmp second +first_is_float: + movlpd QWORD PTR [rsp+8], xmm0 + +second: + test r11, 2 + jne second_is_float + mov QWORD PTR [rsp+16], rdx + jmp third +second_is_float: + movlpd QWORD PTR [rsp+16], xmm1 + +third: + test r11, 4 + jne third_is_float + mov QWORD PTR [rsp+24], r8 + jmp forth +third_is_float: + movlpd QWORD PTR [rsp+24], xmm2 + +forth: + test r11, 8 + jne forth_is_float + mov QWORD PTR [rsp+32], r9 + jmp done +forth_is_float: + movlpd QWORD PTR [rsp+32], xmm3 + +done: +.ALLOCSTACK 40 + sub rsp, 40 +.ENDPROLOG + mov rcx, rax ; context is first parameter + mov rdx, rsp ; stack is second parameter + add rdx, 40 ; correct our own area + mov rax, ffi_closure_SYSV + call rax ; call the real closure function + ;; Here, code is missing that handles float return values + add rsp, 40 + movd xmm0, rax ; In case the closure returned a float. + ret 0 +ffi_closure_OUTER ENDP + + +;;; ffi_call_AMD64 + +stack$ = 0 +prepfunc$ = 32 +ecif$ = 40 +bytes$ = 48 +flags$ = 56 +rvalue$ = 64 +fn$ = 72 + +ffi_call_AMD64 PROC FRAME + + mov QWORD PTR [rsp+32], r9 + mov QWORD PTR [rsp+24], r8 + mov QWORD PTR [rsp+16], rdx + mov QWORD PTR [rsp+8], rcx +.PUSHREG rbp + push rbp +.ALLOCSTACK 48 + sub rsp, 48 ; 00000030H +.SETFRAME rbp, 32 + lea rbp, QWORD PTR [rsp+32] +.ENDPROLOG + + mov eax, DWORD PTR bytes$[rbp] + add rax, 15 + and rax, -16 + call __chkstk + sub rsp, rax + lea rax, QWORD PTR [rsp+32] + mov QWORD PTR stack$[rbp], rax + + mov rdx, QWORD PTR ecif$[rbp] + mov rcx, QWORD PTR stack$[rbp] + call QWORD PTR prepfunc$[rbp] + + mov rsp, QWORD PTR stack$[rbp] + + movlpd xmm3, QWORD PTR [rsp+24] + movd r9, xmm3 + + movlpd xmm2, QWORD PTR [rsp+16] + movd r8, xmm2 + + movlpd xmm1, QWORD PTR [rsp+8] + movd rdx, xmm1 + + movlpd xmm0, QWORD PTR [rsp] + movd rcx, xmm0 + + call QWORD PTR fn$[rbp] +ret_int$: + cmp DWORD PTR flags$[rbp], 1 ; FFI_TYPE_INT + jne ret_float$ + + mov rcx, QWORD PTR rvalue$[rbp] + mov DWORD PTR [rcx], eax + jmp SHORT ret_nothing$ + +ret_float$: + cmp DWORD PTR flags$[rbp], 2 ; FFI_TYPE_FLOAT + jne SHORT ret_double$ + + mov rax, QWORD PTR rvalue$[rbp] + movlpd QWORD PTR [rax], xmm0 + jmp SHORT ret_nothing$ + +ret_double$: + cmp DWORD PTR flags$[rbp], 3 ; FFI_TYPE_DOUBLE + jne SHORT ret_int64$ + + mov rax, QWORD PTR rvalue$[rbp] + movlpd QWORD PTR [rax], xmm0 + jmp SHORT ret_nothing$ + +ret_int64$: + cmp DWORD PTR flags$[rbp], 12 ; FFI_TYPE_SINT64 + jne ret_nothing$ + + mov rcx, QWORD PTR rvalue$[rbp] + mov QWORD PTR [rcx], rax + jmp SHORT ret_nothing$ + +ret_nothing$: + xor eax, eax + + lea rsp, QWORD PTR [rbp+16] + pop rbp + ret 0 +ffi_call_AMD64 ENDP +_TEXT ENDS +END diff --git a/pypy/translator/platform/__init__.py b/pypy/translator/platform/__init__.py --- a/pypy/translator/platform/__init__.py +++ b/pypy/translator/platform/__init__.py @@ -304,6 +304,8 @@ global platform log.msg("Setting platform to %r cc=%s" % (new_platform,cc)) platform = pick_platform(new_platform, cc) + if not platform: + raise ValueError("pick_platform failed") if new_platform == 'host': global host diff --git a/pypy/translator/platform/windows.py b/pypy/translator/platform/windows.py --- a/pypy/translator/platform/windows.py +++ b/pypy/translator/platform/windows.py @@ -7,15 +7,27 @@ from pypy.translator.platform import log, _run_subprocess from pypy.translator.platform import Platform, posix +def _get_compiler_type(cc, x64_flag): + import subprocess + if not cc: + cc = os.environ.get('CC','') + if not cc: + return MsvcPlatform(cc=cc, x64=x64_flag) + elif cc.startswith('mingw'): + return MingwPlatform(cc) + try: + subprocess.check_output([cc, '--version']) + except: + raise ValueError,"Could not find compiler specified by cc option" + \ + " '%s', it must be a valid exe file on your path"%cc + return MingwPlatform(cc) + def Windows(cc=None): - if cc == 'mingw32': - return MingwPlatform(cc) - else: - return MsvcPlatform(cc, False) + return _get_compiler_type(cc, False) + +def Windows_x64(cc=None): + return _get_compiler_type(cc, True) -def Windows_x64(cc=None): - return MsvcPlatform(cc, True) - def _get_msvc_env(vsver, x64flag): try: toolsdir = os.environ['VS%sCOMNTOOLS' % vsver] @@ -31,14 +43,16 @@ vcvars = os.path.join(toolsdir, 'vsvars32.bat') import subprocess - popen = subprocess.Popen('"%s" & set' % (vcvars,), + try: + popen = subprocess.Popen('"%s" & set' % (vcvars,), stdout=subprocess.PIPE, stderr=subprocess.PIPE) - stdout, stderr = popen.communicate() - if popen.wait() != 0: - return - + stdout, stderr = popen.communicate() + if popen.wait() != 0: + return None + except: + return None env = {} stdout = stdout.replace("\r\n", "\n") @@ -395,7 +409,9 @@ so_ext = 'dll' def __init__(self, cc=None): - Platform.__init__(self, 'gcc') + if not cc: + cc = 'gcc' + Platform.__init__(self, cc) def _args_for_shared(self, args): return ['-shared'] + args diff --git a/pypy/translator/test/test_unsimplify.py b/pypy/translator/test/test_unsimplify.py --- a/pypy/translator/test/test_unsimplify.py +++ b/pypy/translator/test/test_unsimplify.py @@ -78,7 +78,7 @@ return x * 6 def hello_world(): if we_are_translated(): - fd = os.open(tmpfile, os.O_WRONLY | os.O_CREAT, 0) + fd = os.open(tmpfile, os.O_WRONLY | os.O_CREAT, 0644) os.close(fd) graph, t = translate(f, [int], type_system) call_initial_function(t, hello_world) @@ -97,7 +97,7 @@ return x * 6 def goodbye_world(): if we_are_translated(): - fd = os.open(tmpfile, os.O_WRONLY | os.O_CREAT, 0) + fd = os.open(tmpfile, os.O_WRONLY | os.O_CREAT, 0644) os.close(fd) graph, t = translate(f, [int], type_system) call_final_function(t, goodbye_world) From noreply at buildbot.pypy.org Thu Apr 5 14:19:04 2012 From: noreply at buildbot.pypy.org (bivab) Date: Thu, 5 Apr 2012 14:19:04 +0200 (CEST) Subject: [pypy-commit] pypy arm-backend-2: start implementing the long long support Message-ID: <20120405121904.009178208A@wyvern.cs.uni-duesseldorf.de> Author: David Schneider Branch: arm-backend-2 Changeset: r54198:7dbbed165924 Date: 2012-03-27 12:23 +0000 http://bitbucket.org/pypy/pypy/changeset/7dbbed165924/ Log: start implementing the long long support diff --git a/pypy/jit/backend/arm/assembler.py b/pypy/jit/backend/arm/assembler.py --- a/pypy/jit/backend/arm/assembler.py +++ b/pypy/jit/backend/arm/assembler.py @@ -925,6 +925,19 @@ return False return True + def regalloc_emit_llong(self, op, arglocs, fcond, regalloc): + effectinfo = op.getdescr().get_extra_info() + oopspecindex = effectinfo.oopspecindex + asm_llong_operations[oopspecindex](self, op, arglocs, regalloc, fcond) + return fcond + + def regalloc_emit_math(self, op, arglocs, fcond, regalloc): + effectinfo = op.getdescr().get_extra_info() + oopspecindex = effectinfo.oopspecindex + asm_math_operations[oopspecindex](self, op, arglocs, resloc) + return fcond + + def _insert_checks(self, mc=None): if not we_are_translated() and self._debug: if mc is None: @@ -1265,6 +1278,7 @@ asm_operations = [notimplemented_op] * (rop._LAST + 1) asm_operations_with_guard = [notimplemented_op_with_guard] * (rop._LAST + 1) +asm_llong_operations = {} asm_math_operations = {} for name, value in ResOpAssembler.__dict__.iteritems(): diff --git a/pypy/jit/backend/arm/instruction_builder.py b/pypy/jit/backend/arm/instruction_builder.py --- a/pypy/jit/backend/arm/instruction_builder.py +++ b/pypy/jit/backend/arm/instruction_builder.py @@ -350,6 +350,36 @@ self.write32(instr) return f +def define_simd_instructions_3regs_func(name, table): + n = 0x79 << 25 + if 'A' in table: + n |= (table['A'] & 0xF) << 8 + if 'B' in table: + n |= (table['B'] & 0x1) << 4 + if 'U' in table: + n |= (table['U'] & 0x1) << 24 + if 'C' in table: + n |= (table['C'] & 0x3) << 20 + if name == 'VADD_i64' or name == 'VSUB_i64': + size = 0x3 + n |= size << 20 + def f(self, dd, dn, dm): + N = (dn >> 4) & 0x1 + M = (dm >> 4) & 0x1 + D = (dd >> 4) & 0x1 + Q = 0 # we want doubleword regs + instr = (n + | D << 22 + | (dn & 0xf) << 16 + | (dd & 0xf) << 12 + | N << 7 + | Q << 6 + | M << 5 + | (dm & 0xf)) + + self.write32(instr) + return f + def imm_operation(rt, rn, imm): return ((rn & 0xFF) << 16 @@ -368,6 +398,7 @@ def define_instruction(builder, key, val, target): f = builder(key, val) + f.__name__ = key setattr(target, key, f) @@ -378,7 +409,6 @@ continue try: func = globals()['define_%s_func' % name] - func.__name__ = name except KeyError: print 'No instr generator for %s instructions' % name continue diff --git a/pypy/jit/backend/arm/instructions.py b/pypy/jit/backend/arm/instructions.py --- a/pypy/jit/backend/arm/instructions.py +++ b/pypy/jit/backend/arm/instructions.py @@ -140,3 +140,11 @@ 'VSQRT' : {'opc1':0xB, 'opc2':0x1, 'opc3':0x3, 'base': False}, #'VCVT' : {'opc1':0xB, 'opc2':0xE, 'opc3':0x1, 'base': False}, } + +simd_instructions_3regs = { + 'VADD_i64': {'A': 0x8, 'B': 0, 'U': 0}, + 'VSUB_i64': {'A': 0x8, 'B': 0, 'U': 1}, + 'VAND_i64': {'A': 0x1, 'B': 1, 'U': 0, 'C': 0}, + 'VORR_i64': {'A': 0x1, 'B': 1, 'U': 0, 'C': 0x10}, + 'VEOR_i64': {'A': 0x1, 'B': 1, 'U': 1, 'C': 0x0}, +} diff --git a/pypy/jit/backend/arm/opassembler.py b/pypy/jit/backend/arm/opassembler.py --- a/pypy/jit/backend/arm/opassembler.py +++ b/pypy/jit/backend/arm/opassembler.py @@ -1282,4 +1282,9 @@ self.mc.VCVT_int_to_float(res.value, temp.value) return fcond + emit_op_llong_add = gen_emit_float_op('llong_add', 'VADD_i64') + emit_op_llong_sub = gen_emit_float_op('llong_sub', 'VSUB_i64') + emit_op_llong_and = gen_emit_float_op('llong_and', 'VAND_i64') + emit_op_llong_or = gen_emit_float_op('llong_or', 'VORR_i64') + emit_op_llong_xor = gen_emit_float_op('llong_xor', 'VEOR_i64') diff --git a/pypy/jit/backend/arm/regalloc.py b/pypy/jit/backend/arm/regalloc.py --- a/pypy/jit/backend/arm/regalloc.py +++ b/pypy/jit/backend/arm/regalloc.py @@ -374,6 +374,12 @@ # is also used on op args, which is a non-resizable list self.possibly_free_vars(list(inputargs)) + def perform_llong(self, op, args, fcond): + return self.assembler.regalloc_emit_llong(op, args, fcond, self) + + def perform_math(self, op, args, fcond): + return self.assembler.regalloc_emit_math(op, args, self, fcond) + def force_spill_var(self, var): if var.type == FLOAT: self.vfprm.force_spill_var(var) @@ -541,6 +547,18 @@ effectinfo = op.getdescr().get_extra_info() if effectinfo is not None: oopspecindex = effectinfo.oopspecindex + if oopspecindex in (EffectInfo.OS_LLONG_ADD, + EffectInfo.OS_LLONG_SUB, + EffectInfo.OS_LLONG_AND, + EffectInfo.OS_LLONG_OR, + EffectInfo.OS_LLONG_XOR): + args = self._prepare_llong_binop_xx(op, fcond) + self.perform_llong(op, args, fcond) + return + if oopspecindex == EffectInfo.OS_LLONG_TO_INT: + args = self._prepare_llong_to_int(op, fcond) + self.perform_llong(op, args, fcond) + return if oopspecindex == EffectInfo.OS_MATH_SQRT: args = self.prepare_op_math_sqrt(op, fcond) self.perform_math(op, args, fcond) @@ -568,6 +586,15 @@ def prepare_op_call_malloc_gc(self, op, fcond): return self._prepare_call(op) + def _prepare_llong_binop_xx(self, op, fcond): + # arg 0 is the address of the function + loc0 = self._ensure_value_is_boxed(op.getarg(1)) + loc1 = self._ensure_value_is_boxed(op.getarg(2)) + self.possibly_free_vars_for_op(op) + self.free_temp_vars() + res = self.vfprm.force_allocate_reg(op.result) + return [loc0, loc1, res] + def _prepare_guard(self, op, args=None): if args is None: args = [] diff --git a/pypy/jit/backend/arm/runner.py b/pypy/jit/backend/arm/runner.py --- a/pypy/jit/backend/arm/runner.py +++ b/pypy/jit/backend/arm/runner.py @@ -9,6 +9,7 @@ class ArmCPU(AbstractLLCPU): supports_floats = True + supports_longlong = True def __init__(self, rtyper, stats, opts=None, translate_support_code=False, gcdescr=None): From noreply at buildbot.pypy.org Thu Apr 5 14:19:05 2012 From: noreply at buildbot.pypy.org (bivab) Date: Thu, 5 Apr 2012 14:19:05 +0200 (CEST) Subject: [pypy-commit] pypy arm-backend-2: refactor cast_float_to_int and cast_int_to_float using the VMOV operation Message-ID: <20120405121905.A4C898208A@wyvern.cs.uni-duesseldorf.de> Author: David Schneider Branch: arm-backend-2 Changeset: r54199:cf3f78de7344 Date: 2012-03-27 17:02 +0000 http://bitbucket.org/pypy/pypy/changeset/cf3f78de7344/ Log: refactor cast_float_to_int and cast_int_to_float using the VMOV operation diff --git a/pypy/jit/backend/arm/opassembler.py b/pypy/jit/backend/arm/opassembler.py --- a/pypy/jit/backend/arm/opassembler.py +++ b/pypy/jit/backend/arm/opassembler.py @@ -1268,18 +1268,20 @@ emit_guard_float_ge = gen_emit_float_cmp_op_guard('float_ge', c.GE) def emit_op_cast_float_to_int(self, op, arglocs, regalloc, fcond): - arg, temp, res = arglocs - self.mc.VCVT_float_to_int(temp.value, arg.value) - self.mc.VPUSH([temp.value]) - # res is lower register than r.ip - self.mc.POP([res.value, r.ip.value]) + arg, res = arglocs + assert arg.is_vfp_reg() + assert res.is_reg() + self.mc.VCVT_float_to_int(r.vfp_ip.value, arg.value) + self.mc.VMOV_rc(res.value, r.ip.value, r.vfp_ip.value) return fcond def emit_op_cast_int_to_float(self, op, arglocs, regalloc, fcond): - arg, temp, res = arglocs - self.mc.PUSH([arg.value, r.ip.value]) - self.mc.VPOP([temp.value]) - self.mc.VCVT_int_to_float(res.value, temp.value) + arg, res = arglocs + assert res.is_vfp_reg() + assert arg.is_reg() + self.mc.MOV_ri(r.ip.value, 0) + self.mc.VMOV_cr(res.value, arg.value, r.ip.value) + self.mc.VCVT_int_to_float(res.value, res.value) return fcond emit_op_llong_add = gen_emit_float_op('llong_add', 'VADD_i64') diff --git a/pypy/jit/backend/arm/regalloc.py b/pypy/jit/backend/arm/regalloc.py --- a/pypy/jit/backend/arm/regalloc.py +++ b/pypy/jit/backend/arm/regalloc.py @@ -1186,19 +1186,13 @@ def prepare_op_cast_float_to_int(self, op, fcond): loc1 = self._ensure_value_is_boxed(op.getarg(0)) - temp_loc = self.get_scratch_reg(FLOAT) - self.possibly_free_vars_for_op(op) - self.free_temp_vars() res = self.rm.force_allocate_reg(op.result) - return [loc1, temp_loc, res] + return [loc1, res] def prepare_op_cast_int_to_float(self, op, fcond): loc1 = self._ensure_value_is_boxed(op.getarg(0)) - temp_loc = self.get_scratch_reg(FLOAT) - self.possibly_free_vars_for_op(op) - self.free_temp_vars() res = self.vfprm.force_allocate_reg(op.result) - return [loc1, temp_loc, res] + return [loc1, res] def prepare_force_spill(self, op, fcond): self.force_spill_var(op.getarg(0)) From noreply at buildbot.pypy.org Thu Apr 5 14:19:07 2012 From: noreply at buildbot.pypy.org (bivab) Date: Thu, 5 Apr 2012 14:19:07 +0200 (CEST) Subject: [pypy-commit] pypy arm-backend-2: Add tests for SIMD instructions and fix VORR Message-ID: <20120405121907.090658208A@wyvern.cs.uni-duesseldorf.de> Author: David Schneider Branch: arm-backend-2 Changeset: r54200:563b0debb79e Date: 2012-03-28 10:32 +0000 http://bitbucket.org/pypy/pypy/changeset/563b0debb79e/ Log: Add tests for SIMD instructions and fix VORR diff --git a/pypy/jit/backend/arm/instructions.py b/pypy/jit/backend/arm/instructions.py --- a/pypy/jit/backend/arm/instructions.py +++ b/pypy/jit/backend/arm/instructions.py @@ -145,6 +145,6 @@ 'VADD_i64': {'A': 0x8, 'B': 0, 'U': 0}, 'VSUB_i64': {'A': 0x8, 'B': 0, 'U': 1}, 'VAND_i64': {'A': 0x1, 'B': 1, 'U': 0, 'C': 0}, - 'VORR_i64': {'A': 0x1, 'B': 1, 'U': 0, 'C': 0x10}, + 'VORR_i64': {'A': 0x1, 'B': 1, 'U': 0, 'C': 0x2}, 'VEOR_i64': {'A': 0x1, 'B': 1, 'U': 1, 'C': 0x0}, } diff --git a/pypy/jit/backend/arm/test/test_instr_codebuilder.py b/pypy/jit/backend/arm/test/test_instr_codebuilder.py --- a/pypy/jit/backend/arm/test/test_instr_codebuilder.py +++ b/pypy/jit/backend/arm/test/test_instr_codebuilder.py @@ -284,6 +284,10 @@ tests.append((asm, (r.r3.value, range(regs+1)))) return tests +def gen_test_simd_instructions_3regs_func(name, table): + op_name = name[:name.index('_')] + return [('d1, d2, d3', (r.d1.value, r.d2.value, r.d3.value), {}, '.i64')] + def build_tests(): cls = TestInstrCodeBuilderForGeneratedInstr test_name = 'test_generated_%s' From noreply at buildbot.pypy.org Thu Apr 5 14:19:08 2012 From: noreply at buildbot.pypy.org (bivab) Date: Thu, 5 Apr 2012 14:19:08 +0200 (CEST) Subject: [pypy-commit] pypy arm-backend-2: add llong_to_int implementation Message-ID: <20120405121908.541518208A@wyvern.cs.uni-duesseldorf.de> Author: David Schneider Branch: arm-backend-2 Changeset: r54201:9f746b2b318e Date: 2012-03-28 11:22 +0000 http://bitbucket.org/pypy/pypy/changeset/9f746b2b318e/ Log: add llong_to_int implementation diff --git a/pypy/jit/backend/arm/opassembler.py b/pypy/jit/backend/arm/opassembler.py --- a/pypy/jit/backend/arm/opassembler.py +++ b/pypy/jit/backend/arm/opassembler.py @@ -1290,3 +1290,10 @@ emit_op_llong_or = gen_emit_float_op('llong_or', 'VORR_i64') emit_op_llong_xor = gen_emit_float_op('llong_xor', 'VEOR_i64') + def emit_op_llong_to_int(self, op, arglocs, regalloc, fcond): + loc = arglocs[0] + res = arglocs[1] + assert loc.is_vfp_reg() + assert res.is_reg() + self.mc.VMOV_rc(res.value, r.ip.value, loc.value) + return fcond diff --git a/pypy/jit/backend/arm/regalloc.py b/pypy/jit/backend/arm/regalloc.py --- a/pypy/jit/backend/arm/regalloc.py +++ b/pypy/jit/backend/arm/regalloc.py @@ -595,6 +595,12 @@ res = self.vfprm.force_allocate_reg(op.result) return [loc0, loc1, res] + def _prepare_llong_to_int(self, op, fcond): + loc0 = self._ensure_value_is_boxed(op.getarg(1)) + res = self.force_allocate_reg(op.result) + return [loc0, res] + + def _prepare_guard(self, op, args=None): if args is None: args = [] From noreply at buildbot.pypy.org Thu Apr 5 14:19:09 2012 From: noreply at buildbot.pypy.org (bivab) Date: Thu, 5 Apr 2012 14:19:09 +0200 (CEST) Subject: [pypy-commit] pypy arm-backend-2: implement convert_float_bytes_to_longlong Message-ID: <20120405121909.909B18208A@wyvern.cs.uni-duesseldorf.de> Author: David Schneider Branch: arm-backend-2 Changeset: r54202:fdde7a2ddb35 Date: 2012-03-28 11:35 +0000 http://bitbucket.org/pypy/pypy/changeset/fdde7a2ddb35/ Log: implement convert_float_bytes_to_longlong diff --git a/pypy/jit/backend/arm/opassembler.py b/pypy/jit/backend/arm/opassembler.py --- a/pypy/jit/backend/arm/opassembler.py +++ b/pypy/jit/backend/arm/opassembler.py @@ -1297,3 +1297,5 @@ assert res.is_reg() self.mc.VMOV_rc(res.value, r.ip.value, loc.value) return fcond + + emit_op_convert_float_bytes_to_longlong = gen_emit_unary_float_op('float_bytes_to_longlong', 'VMOV_cc') diff --git a/pypy/jit/backend/arm/regalloc.py b/pypy/jit/backend/arm/regalloc.py --- a/pypy/jit/backend/arm/regalloc.py +++ b/pypy/jit/backend/arm/regalloc.py @@ -1204,6 +1204,9 @@ self.force_spill_var(op.getarg(0)) return [] + prepare_op_convert_float_bytes_to_longlong = prepare_float_op(base=False, + name='prepare_op_convert_float_bytes_to_longlong') + def add_none_argument(fn): return lambda self, op, fcond: fn(self, op, None, fcond) From noreply at buildbot.pypy.org Thu Apr 5 14:19:10 2012 From: noreply at buildbot.pypy.org (bivab) Date: Thu, 5 Apr 2012 14:19:10 +0200 (CEST) Subject: [pypy-commit] pypy arm-backend-2: mark test_convert_float_bytes as requiring longlong support Message-ID: <20120405121910.D10898208A@wyvern.cs.uni-duesseldorf.de> Author: David Schneider Branch: arm-backend-2 Changeset: r54203:20eca32afd29 Date: 2012-03-28 11:37 +0000 http://bitbucket.org/pypy/pypy/changeset/20eca32afd29/ Log: mark test_convert_float_bytes as requiring longlong support diff --git a/pypy/jit/backend/test/runner_test.py b/pypy/jit/backend/test/runner_test.py --- a/pypy/jit/backend/test/runner_test.py +++ b/pypy/jit/backend/test/runner_test.py @@ -1776,6 +1776,8 @@ assert res == -19 def test_convert_float_bytes(self): + if not self.cpu.supports_longlong: + py.test.skip("longlong test") t = 'int' if longlong.is_64_bit else 'float' res = self.execute_operation(rop.CONVERT_FLOAT_BYTES_TO_LONGLONG, [boxfloat(2.5)], t).value From noreply at buildbot.pypy.org Thu Apr 5 14:19:12 2012 From: noreply at buildbot.pypy.org (bivab) Date: Thu, 5 Apr 2012 14:19:12 +0200 (CEST) Subject: [pypy-commit] pypy arm-backend-2: fix merge Message-ID: <20120405121912.26EF78208A@wyvern.cs.uni-duesseldorf.de> Author: David Schneider Branch: arm-backend-2 Changeset: r54204:43c292b41c22 Date: 2012-03-28 11:40 +0000 http://bitbucket.org/pypy/pypy/changeset/43c292b41c22/ Log: fix merge diff --git a/pypy/jit/backend/test/runner_test.py b/pypy/jit/backend/test/runner_test.py --- a/pypy/jit/backend/test/runner_test.py +++ b/pypy/jit/backend/test/runner_test.py @@ -1657,6 +1657,7 @@ def test_read_timestamp(self): if not self.cpu.supports_longlong: py.test.skip("longlong test") + if sys.platform == 'win32': # so we stretch the time a little bit. # On my virtual Parallels machine in a 2GHz Core i7 Mac Mini, # the test starts working at delay == 21670 and stops at 20600000. From noreply at buildbot.pypy.org Thu Apr 5 14:19:13 2012 From: noreply at buildbot.pypy.org (bivab) Date: Thu, 5 Apr 2012 14:19:13 +0200 (CEST) Subject: [pypy-commit] pypy arm-backend-2: implement the read timestamp operation. But it only works in privileged mode. Bah. Message-ID: <20120405121913.8F6CA8208A@wyvern.cs.uni-duesseldorf.de> Author: David Schneider Branch: arm-backend-2 Changeset: r54205:81fad4c1e587 Date: 2012-04-05 12:07 +0000 http://bitbucket.org/pypy/pypy/changeset/81fad4c1e587/ Log: implement the read timestamp operation. But it only works in privileged mode. Bah. diff --git a/pypy/jit/backend/arm/instruction_builder.py b/pypy/jit/backend/arm/instruction_builder.py --- a/pypy/jit/backend/arm/instruction_builder.py +++ b/pypy/jit/backend/arm/instruction_builder.py @@ -223,6 +223,7 @@ n = (0x3 << 26 | (table['op1'] & 0x3F) << 20 | (table['op'] & 0x1) << 4) def f(self, coproc, opc1, rt, crn, crm, opc2=0, cond=cond.AL): + assert coproc & 0xE != 0xA self.write32(n | cond << 28 | (opc1 & 0x7) << 21 diff --git a/pypy/jit/backend/arm/instructions.py b/pypy/jit/backend/arm/instructions.py --- a/pypy/jit/backend/arm/instructions.py +++ b/pypy/jit/backend/arm/instructions.py @@ -93,6 +93,7 @@ supervisor_and_coproc = { 'MCR': {'op1': 0x20, 'op': 1, 'rn':0, 'coproc':0}, + 'MRC': {'op1': 0x21, 'op': 1, 'rn':0, 'coproc':0}, } block_data = { diff --git a/pypy/jit/backend/arm/opassembler.py b/pypy/jit/backend/arm/opassembler.py --- a/pypy/jit/backend/arm/opassembler.py +++ b/pypy/jit/backend/arm/opassembler.py @@ -1299,3 +1299,11 @@ return fcond emit_op_convert_float_bytes_to_longlong = gen_emit_unary_float_op('float_bytes_to_longlong', 'VMOV_cc') + + def emit_op_read_timestamp(self, op, arglocs, regalloc, fcond): + tmp = arglocs[0] + res = arglocs[1] + self.mc.MRC(15, 0, tmp.value, 15, 12, 1) + self.mc.MOV_ri(r.ip.value, 0) + self.mc.VMOV_cr(res.value, tmp.value, r.ip.value) + return fcond diff --git a/pypy/jit/backend/arm/regalloc.py b/pypy/jit/backend/arm/regalloc.py --- a/pypy/jit/backend/arm/regalloc.py +++ b/pypy/jit/backend/arm/regalloc.py @@ -1207,6 +1207,11 @@ prepare_op_convert_float_bytes_to_longlong = prepare_float_op(base=False, name='prepare_op_convert_float_bytes_to_longlong') + def prepare_op_read_timestamp(self, op, fcond): + loc = self.get_scratch_reg(INT) + res = self.vfprm.force_allocate_reg(op.result) + return [loc, res] + def add_none_argument(fn): return lambda self, op, fcond: fn(self, op, None, fcond) From noreply at buildbot.pypy.org Thu Apr 5 14:19:14 2012 From: noreply at buildbot.pypy.org (bivab) Date: Thu, 5 Apr 2012 14:19:14 +0200 (CEST) Subject: [pypy-commit] pypy arm-backend-2: disable longlong support until I find a way to implement read_timestamp on ARM Message-ID: <20120405121914.D54FD8208A@wyvern.cs.uni-duesseldorf.de> Author: David Schneider Branch: arm-backend-2 Changeset: r54206:b3ce03aac72d Date: 2012-04-05 12:14 +0000 http://bitbucket.org/pypy/pypy/changeset/b3ce03aac72d/ Log: disable longlong support until I find a way to implement read_timestamp on ARM diff --git a/pypy/jit/backend/arm/runner.py b/pypy/jit/backend/arm/runner.py --- a/pypy/jit/backend/arm/runner.py +++ b/pypy/jit/backend/arm/runner.py @@ -9,7 +9,8 @@ class ArmCPU(AbstractLLCPU): supports_floats = True - supports_longlong = True + supports_longlong = False # XXX requires an implementation of + # read_timestamp that works in user mode def __init__(self, rtyper, stats, opts=None, translate_support_code=False, gcdescr=None): From noreply at buildbot.pypy.org Thu Apr 5 14:19:16 2012 From: noreply at buildbot.pypy.org (bivab) Date: Thu, 5 Apr 2012 14:19:16 +0200 (CEST) Subject: [pypy-commit] pypy arm-backend-2: backout 20eca32afd29 Message-ID: <20120405121916.1CE668208A@wyvern.cs.uni-duesseldorf.de> Author: David Schneider Branch: arm-backend-2 Changeset: r54207:30c3a37d897a Date: 2012-04-05 12:17 +0000 http://bitbucket.org/pypy/pypy/changeset/30c3a37d897a/ Log: backout 20eca32afd29 diff --git a/pypy/jit/backend/test/runner_test.py b/pypy/jit/backend/test/runner_test.py --- a/pypy/jit/backend/test/runner_test.py +++ b/pypy/jit/backend/test/runner_test.py @@ -1777,8 +1777,6 @@ assert res == -19 def test_convert_float_bytes(self): - if not self.cpu.supports_longlong: - py.test.skip("longlong test") t = 'int' if longlong.is_64_bit else 'float' res = self.execute_operation(rop.CONVERT_FLOAT_BYTES_TO_LONGLONG, [boxfloat(2.5)], t).value From pullrequests-noreply at bitbucket.org Thu Apr 5 15:59:34 2012 From: pullrequests-noreply at bitbucket.org (Amaury Forgeot d'Arc) Date: Thu, 05 Apr 2012 13:59:34 -0000 Subject: [pypy-commit] [pypy/pypy] PyFile_FromFile implementation (pull request #68) In-Reply-To: <819aec1e6e3998acaa7b35b136186f8a@bitbucket.org> References: <819aec1e6e3998acaa7b35b136186f8a@bitbucket.org> Message-ID: <20120405135934.7050.41402@bitbucket12.managed.contegix.com> New comment on pull request: https://bitbucket.org/pypy/pypy/pull-request/68/pyfile_fromfile-implementation#comment-4832 Amaury Forgeot d'Arc (amauryfa) said: Looks good. I'd fflush() the FILE* though, to avoid a potential loss of data. -- This is a pull request comment notification from bitbucket.org. You are receiving this either because you are participating in a pull request, or you are following it. From noreply at buildbot.pypy.org Thu Apr 5 16:55:29 2012 From: noreply at buildbot.pypy.org (mattip) Date: Thu, 5 Apr 2012 16:55:29 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: simplify Message-ID: <20120405145529.8A75E8208A@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54208:f1aae6ad9af7 Date: 2012-04-05 17:53 +0300 http://bitbucket.org/pypy/pypy/changeset/f1aae6ad9af7/ Log: simplify diff --git a/pypy/rpython/module/ll_os_stat.py b/pypy/rpython/module/ll_os_stat.py --- a/pypy/rpython/module/ll_os_stat.py +++ b/pypy/rpython/module/ll_os_stat.py @@ -455,6 +455,6 @@ return intmask(time), intmask(nsec) def time_t_to_FILE_TIME(time, filetime): - ft = rffi.r_longlong(time * 10000000) + secs_between_epochs * 10000000 + ft = rffi.r_longlong((time + secs_between_epochs) * 10000000) filetime.c_dwHighDateTime = rffi.r_uint(ft >> 32) filetime.c_dwLowDateTime = rffi.r_uint(ft) # masking off high bits From noreply at buildbot.pypy.org Thu Apr 5 16:55:30 2012 From: noreply at buildbot.pypy.org (mattip) Date: Thu, 5 Apr 2012 16:55:30 +0200 (CEST) Subject: [pypy-commit] pypy default: fix float-to-time conversion in windows Message-ID: <20120405145530.C374A8208A@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: Changeset: r54209:a7b45b1ec178 Date: 2012-04-05 17:54 +0300 http://bitbucket.org/pypy/pypy/changeset/a7b45b1ec178/ Log: fix float-to-time conversion in windows diff --git a/pypy/rpython/module/ll_os_stat.py b/pypy/rpython/module/ll_os_stat.py --- a/pypy/rpython/module/ll_os_stat.py +++ b/pypy/rpython/module/ll_os_stat.py @@ -455,6 +455,6 @@ return intmask(time), intmask(nsec) def time_t_to_FILE_TIME(time, filetime): - ft = (rffi.r_longlong(time) + secs_between_epochs) * 10000000 + ft = rffi.r_longlong((time + secs_between_epochs) * 10000000) filetime.c_dwHighDateTime = rffi.r_uint(ft >> 32) filetime.c_dwLowDateTime = rffi.r_uint(ft) # masking off high bits From pullrequests-noreply at bitbucket.org Thu Apr 5 17:00:42 2012 From: pullrequests-noreply at bitbucket.org (Roberto De Ioris) Date: Thu, 05 Apr 2012 15:00:42 -0000 Subject: [pypy-commit] [pypy/pypy] PyFile_FromFile implementation (pull request #68) In-Reply-To: <819aec1e6e3998acaa7b35b136186f8a@bitbucket.org> References: <819aec1e6e3998acaa7b35b136186f8a@bitbucket.org> Message-ID: <20120405150042.13043.27165@bitbucket15.managed.contegix.com> New comment on pull request: https://bitbucket.org/pypy/pypy/pull-request/68/pyfile_fromfile-implementation#comment-4836 Roberto De Ioris (unbit) said: that should be enough: https://bitbucket.org/unbit/pypy/changeset/d356d169e770 i will remake the pull-request later as i am about to commit another couple of functions -- This is a pull request comment notification from bitbucket.org. You are receiving this either because you are participating in a pull request, or you are following it. From noreply at buildbot.pypy.org Thu Apr 5 18:27:22 2012 From: noreply at buildbot.pypy.org (ctismer) Date: Thu, 5 Apr 2012 18:27:22 +0200 (CEST) Subject: [pypy-commit] pypy win64-stage1: Closed branch win64-stage1 Message-ID: <20120405162722.C5E238208A@wyvern.cs.uni-duesseldorf.de> Author: Christian Tismer Branch: win64-stage1 Changeset: r54210:e37b6d1dd329 Date: 2012-04-05 18:25 +0200 http://bitbucket.org/pypy/pypy/changeset/e37b6d1dd329/ Log: Closed branch win64-stage1 From noreply at buildbot.pypy.org Thu Apr 5 23:39:04 2012 From: noreply at buildbot.pypy.org (amauryfa) Date: Thu, 5 Apr 2012 23:39:04 +0200 (CEST) Subject: [pypy-commit] pypy default: Try to better support -fPIC compiler option in trackgcroot. Message-ID: <20120405213904.7F41F8208A@wyvern.cs.uni-duesseldorf.de> Author: Amaury Forgeot d'Arc Branch: Changeset: r54211:10f1ff48184b Date: 2012-04-05 23:38 +0200 http://bitbucket.org/pypy/pypy/changeset/10f1ff48184b/ Log: Try to better support -fPIC compiler option in trackgcroot. diff --git a/pypy/translator/c/gcc/trackgcroot.py b/pypy/translator/c/gcc/trackgcroot.py --- a/pypy/translator/c/gcc/trackgcroot.py +++ b/pypy/translator/c/gcc/trackgcroot.py @@ -847,6 +847,10 @@ if sources: target, = sources + if target.endswith('@PLT'): + # In -fPIC mode, all functions calls have this suffix + target = target[:-4] + if target in self.FUNCTIONS_NOT_RETURNING: return [InsnStop(target)] if self.format == 'mingw32' and target == '__alloca': @@ -1137,7 +1141,7 @@ r_jump_rel_label = re.compile(r"\tj\w+\s+"+"(\d+)f"+"\s*$") r_unaryinsn_star= re.compile(r"\t[a-z]\w*\s+[*]("+OPERAND+")\s*$") - r_jmptable_item = re.compile(r"\t.quad\t"+LABEL+"(-\"[A-Za-z0-9$]+\")?\s*$") + r_jmptable_item = re.compile(r"\t.(?:quad|long)\t"+LABEL+"(-\"[A-Za-z0-9$]+\"|-"+LABEL+")?\s*$") r_jmptable_end = re.compile(r"\t.text|\t.section\s+.text|\t\.align|"+LABEL) r_gcroot_marker = re.compile(r"\t/[*] GCROOT ("+LOCALVARFP+") [*]/") From noreply at buildbot.pypy.org Fri Apr 6 09:02:42 2012 From: noreply at buildbot.pypy.org (mattip) Date: Fri, 6 Apr 2012 09:02:42 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: merge from default Message-ID: <20120406070242.485108208A@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54212:b06aa6be5f37 Date: 2012-04-05 18:05 +0300 http://bitbucket.org/pypy/pypy/changeset/b06aa6be5f37/ Log: merge from default diff --git a/dotviewer/graphparse.py b/dotviewer/graphparse.py --- a/dotviewer/graphparse.py +++ b/dotviewer/graphparse.py @@ -93,6 +93,7 @@ return result def parse_plain(graph_id, plaincontent, links={}, fixedfont=False): + plaincontent = plaincontent.replace('\r\n', '\n') # fix Windows EOL lines = plaincontent.splitlines(True) for i in range(len(lines)-2, -1, -1): if lines[i].endswith('\\\n'): # line ending in '\' diff --git a/lib_pypy/datetime.py b/lib_pypy/datetime.py --- a/lib_pypy/datetime.py +++ b/lib_pypy/datetime.py @@ -968,8 +968,7 @@ self._checkOverflow(t.year) result = date(t.year, t.month, t.day) return result - raise TypeError - # XXX Should be 'return NotImplemented', but there's a bug in 2.2... + return NotImplemented # note that this doesn't work on CPython 2.2 __radd__ = __add__ diff --git a/pypy/doc/project-ideas.rst b/pypy/doc/project-ideas.rst --- a/pypy/doc/project-ideas.rst +++ b/pypy/doc/project-ideas.rst @@ -149,6 +149,22 @@ exported. This would give us a one-size-fits-all generic .so file to be imported by any application that wants to load .so files :-) +Optimising cpyext (CPython C-API compatibility layer) +----------------------------------------------------- + +A lot of work has gone into PyPy's implementation of CPython's C-API over +the last years to let it reach a practical level of compatibility, so that +C extensions for CPython work on PyPy without major rewrites. However, +there are still many edges and corner cases where it misbehaves, and it has +not received any substantial optimisation so far. + +The objective of this project is to fix bugs in cpyext and to optimise +several performance critical parts of it, such as the reference counting +support and other heavily used C-API functions. The net result would be to +have CPython extensions run much faster on PyPy than they currently do, or +to make them work at all if they currently don't. A part of this work would +be to get cpyext into a shape where it supports running Cython generated +extensions. .. _`issue tracker`: http://bugs.pypy.org .. _`mailing list`: http://mail.python.org/mailman/listinfo/pypy-dev diff --git a/pypy/doc/stackless.rst b/pypy/doc/stackless.rst --- a/pypy/doc/stackless.rst +++ b/pypy/doc/stackless.rst @@ -199,17 +199,11 @@ The following features (present in some past Stackless version of PyPy) are for the time being not supported any more: -* Tasklets and channels (currently ``stackless.py`` seems to import, - but you have tasklets on top of coroutines on top of greenlets on - top of continulets on top of stacklets, and it's probably not too - hard to cut two of these levels by adapting ``stackless.py`` to - use directly continulets) - * Coroutines (could be rewritten at app-level) -* Pickling and unpickling continulets (*) - -* Continuing execution of a continulet in a different thread (*) +* Continuing execution of a continulet in a different thread + (but if it is "simple enough", you can pickle it and unpickle it + in the other thread). * Automatic unlimited stack (must be emulated__ so far) @@ -217,15 +211,6 @@ .. __: `recursion depth limit`_ -(*) Pickling, as well as changing threads, could be implemented by using -a "soft" stack switching mode again. We would get either "hard" or -"soft" switches, similarly to Stackless Python 3rd version: you get a -"hard" switch (like now) when the C stack contains non-trivial C frames -to save, and a "soft" switch (like previously) when it contains only -simple calls from Python to Python. Soft-switched continulets would -also consume a bit less RAM, and the switch might be a bit faster too -(unsure about that; what is the Stackless Python experience?). - Recursion depth limit +++++++++++++++++++++ diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py --- a/pypy/interpreter/baseobjspace.py +++ b/pypy/interpreter/baseobjspace.py @@ -296,6 +296,7 @@ self.check_signal_action = None # changed by the signal module self.user_del_action = UserDelAction(self) self.frame_trace_action = FrameTraceAction(self) + self._code_of_sys_exc_info = None from pypy.interpreter.pycode import cpython_magic, default_magic self.our_magic = default_magic @@ -467,9 +468,9 @@ if name not in modules: modules.append(name) - # a bit of custom logic: time2 or rctime take precedence over time + # a bit of custom logic: rctime take precedence over time # XXX this could probably be done as a "requires" in the config - if ('time2' in modules or 'rctime' in modules) and 'time' in modules: + if 'rctime' in modules and 'time' in modules: modules.remove('time') if not self.config.objspace.nofaking: diff --git a/pypy/interpreter/executioncontext.py b/pypy/interpreter/executioncontext.py --- a/pypy/interpreter/executioncontext.py +++ b/pypy/interpreter/executioncontext.py @@ -154,6 +154,7 @@ #operationerr.print_detailed_traceback(self.space) def _convert_exc(self, operr): + # Only for the flow object space return operr def sys_exc_info(self): # attn: the result is not the wrapped sys.exc_info() !!! @@ -166,6 +167,11 @@ frame = self.getnextframe_nohidden(frame) return None + def set_sys_exc_info(self, operror): + frame = self.gettopframe_nohidden() + if frame: # else, the exception goes nowhere and is lost + frame.last_exception = operror + def settrace(self, w_func): """Set the global trace function.""" if self.space.is_w(w_func, self.space.w_None): diff --git a/pypy/interpreter/function.py b/pypy/interpreter/function.py --- a/pypy/interpreter/function.py +++ b/pypy/interpreter/function.py @@ -113,6 +113,12 @@ from pypy.interpreter.pycode import PyCode code = self.getcode() # hook for the jit + # + if (jit.we_are_jitted() and code is self.space._code_of_sys_exc_info + and nargs == 0): + from pypy.module.sys.vm import exc_info_direct + return exc_info_direct(self.space, frame) + # fast_natural_arity = code.fast_natural_arity if nargs == fast_natural_arity: if nargs == 0: diff --git a/pypy/interpreter/gateway.py b/pypy/interpreter/gateway.py --- a/pypy/interpreter/gateway.py +++ b/pypy/interpreter/gateway.py @@ -874,6 +874,12 @@ fn.add_to_table() if gateway.as_classmethod: fn = ClassMethod(space.wrap(fn)) + # + from pypy.module.sys.vm import exc_info + if code._bltin is exc_info: + assert space._code_of_sys_exc_info is None + space._code_of_sys_exc_info = code + # return fn diff --git a/pypy/interpreter/pyparser/parsestring.py b/pypy/interpreter/pyparser/parsestring.py --- a/pypy/interpreter/pyparser/parsestring.py +++ b/pypy/interpreter/pyparser/parsestring.py @@ -2,34 +2,39 @@ from pypy.interpreter import unicodehelper from pypy.rlib.rstring import StringBuilder -def parsestr(space, encoding, s, unicode_literals=False): - # compiler.transformer.Transformer.decode_literal depends on what - # might seem like minor details of this function -- changes here - # must be reflected there. +def parsestr(space, encoding, s, unicode_literal=False): + """Parses a string or unicode literal, and return a wrapped value. + + If encoding=iso8859-1, the source string is also in this encoding. + If encoding=None, the source string is ascii only. + In other cases, the source string is in utf-8 encoding. + + When a bytes string is returned, it will be encoded with the + original encoding. + + Yes, it's very inefficient. + Yes, CPython has very similar code. + """ # we use ps as "pointer to s" # q is the virtual last char index of the string ps = 0 quote = s[ps] rawmode = False - unicode = unicode_literals # string decoration handling - o = ord(quote) - isalpha = (o>=97 and o<=122) or (o>=65 and o<=90) - if isalpha or quote == '_': - if quote == 'b' or quote == 'B': - ps += 1 - quote = s[ps] - unicode = False - elif quote == 'u' or quote == 'U': - ps += 1 - quote = s[ps] - unicode = True - if quote == 'r' or quote == 'R': - ps += 1 - quote = s[ps] - rawmode = True + if quote == 'b' or quote == 'B': + ps += 1 + quote = s[ps] + unicode_literal = False + elif quote == 'u' or quote == 'U': + ps += 1 + quote = s[ps] + unicode_literal = True + if quote == 'r' or quote == 'R': + ps += 1 + quote = s[ps] + rawmode = True if quote != "'" and quote != '"': raise_app_valueerror(space, 'Internal error: parser passed unquoted literal') @@ -46,21 +51,28 @@ 'unmatched triple quotes in literal') q -= 2 - if unicode: # XXX Py_UnicodeFlag is ignored for now + if unicode_literal: # XXX Py_UnicodeFlag is ignored for now if encoding is None or encoding == "iso-8859-1": + # 'unicode_escape' expects latin-1 bytes, string is ready. buf = s bufp = ps bufq = q u = None else: - # "\XX" may become "\u005c\uHHLL" (12 bytes) + # String is utf8-encoded, but 'unicode_escape' expects + # latin-1; So multibyte sequences must be escaped. lis = [] # using a list to assemble the value end = q + # Worst case: "\XX" may become "\u005c\uHHLL" (12 bytes) while ps < end: if s[ps] == '\\': lis.append(s[ps]) ps += 1 if ord(s[ps]) & 0x80: + # A multibyte sequence will follow, it will be + # escaped like \u1234. To avoid confusion with + # the backslash we just wrote, we emit "\u005c" + # instead. lis.append("u005c") if ord(s[ps]) & 0x80: # XXX inefficient w, ps = decode_utf8(space, s, ps, end, "utf-16-be") @@ -86,13 +98,11 @@ need_encoding = (encoding is not None and encoding != "utf-8" and encoding != "iso-8859-1") - # XXX add strchr like interface to rtyper assert 0 <= ps <= q substr = s[ps : q] if rawmode or '\\' not in s[ps:]: if need_encoding: w_u = space.wrap(unicodehelper.PyUnicode_DecodeUTF8(space, substr)) - #w_v = space.wrap(space.unwrap(w_u).encode(encoding)) this works w_v = unicodehelper.PyUnicode_AsEncodedString(space, w_u, space.wrap(encoding)) return w_v else: diff --git a/pypy/jit/backend/llgraph/llimpl.py b/pypy/jit/backend/llgraph/llimpl.py --- a/pypy/jit/backend/llgraph/llimpl.py +++ b/pypy/jit/backend/llgraph/llimpl.py @@ -1797,6 +1797,7 @@ if specialize_as_constant: def specialize_call(self, hop): llvalue = func(hop.args_s[0].const) + hop.exception_cannot_occur() return hop.inputconst(lltype.typeOf(llvalue), llvalue) else: # specialize as direct_call @@ -1813,6 +1814,7 @@ sm = ootype._static_meth(FUNCTYPE, _name=func.__name__, _callable=func) cfunc = hop.inputconst(FUNCTYPE, sm) args_v = hop.inputargs(*hop.args_r) + hop.exception_is_here() return hop.genop('direct_call', [cfunc] + args_v, hop.r_result) diff --git a/pypy/jit/backend/test/runner_test.py b/pypy/jit/backend/test/runner_test.py --- a/pypy/jit/backend/test/runner_test.py +++ b/pypy/jit/backend/test/runner_test.py @@ -27,6 +27,12 @@ def constfloat(x): return ConstFloat(longlong.getfloatstorage(x)) +def boxlonglong(ll): + if longlong.is_64_bit: + return BoxInt(ll) + else: + return BoxFloat(ll) + class Runner(object): @@ -1623,6 +1629,11 @@ [boxfloat(2.5)], t).value assert res == longlong2float.float2longlong(2.5) + bytes = longlong2float.float2longlong(2.5) + res = self.execute_operation(rop.CONVERT_LONGLONG_BYTES_TO_FLOAT, + [boxlonglong(res)], 'float').value + assert longlong.getrealfloat(res) == 2.5 + def test_ooops_non_gc(self): x = lltype.malloc(lltype.Struct('x'), flavor='raw') v = heaptracker.adr2int(llmemory.cast_ptr_to_adr(x)) diff --git a/pypy/jit/backend/test/test_random.py b/pypy/jit/backend/test/test_random.py --- a/pypy/jit/backend/test/test_random.py +++ b/pypy/jit/backend/test/test_random.py @@ -328,6 +328,15 @@ def produce_into(self, builder, r): self.put(builder, [r.choice(builder.intvars)]) +class CastLongLongToFloatOperation(AbstractFloatOperation): + def produce_into(self, builder, r): + if longlong.is_64_bit: + self.put(builder, [r.choice(builder.intvars)]) + else: + if not builder.floatvars: + raise CannotProduceOperation + self.put(builder, [r.choice(builder.floatvars)]) + class CastFloatToIntOperation(AbstractFloatOperation): def produce_into(self, builder, r): if not builder.floatvars: @@ -450,6 +459,7 @@ OPERATIONS.append(CastFloatToIntOperation(rop.CAST_FLOAT_TO_INT)) OPERATIONS.append(CastIntToFloatOperation(rop.CAST_INT_TO_FLOAT)) OPERATIONS.append(CastFloatToIntOperation(rop.CONVERT_FLOAT_BYTES_TO_LONGLONG)) +OPERATIONS.append(CastLongLongToFloatOperation(rop.CONVERT_LONGLONG_BYTES_TO_FLOAT)) OperationBuilder.OPERATIONS = OPERATIONS diff --git a/pypy/jit/backend/x86/assembler.py b/pypy/jit/backend/x86/assembler.py --- a/pypy/jit/backend/x86/assembler.py +++ b/pypy/jit/backend/x86/assembler.py @@ -1251,6 +1251,15 @@ else: self.mov(loc0, resloc) + def genop_convert_longlong_bytes_to_float(self, op, arglocs, resloc): + loc0, = arglocs + if longlong.is_64_bit: + assert isinstance(resloc, RegLoc) + assert isinstance(loc0, RegLoc) + self.mc.MOVD(resloc, loc0) + else: + self.mov(loc0, resloc) + def genop_guard_int_is_true(self, op, guard_op, guard_token, arglocs, resloc): guard_opnum = guard_op.getopnum() self.mc.CMP(arglocs[0], imm0) diff --git a/pypy/jit/backend/x86/regalloc.py b/pypy/jit/backend/x86/regalloc.py --- a/pypy/jit/backend/x86/regalloc.py +++ b/pypy/jit/backend/x86/regalloc.py @@ -777,7 +777,20 @@ loc0 = self.xrm.loc(arg0) loc1 = self.xrm.force_allocate_reg(op.result, forbidden_vars=[arg0]) self.Perform(op, [loc0], loc1) - self.xrm.possibly_free_var(op.getarg(0)) + self.xrm.possibly_free_var(arg0) + + def consider_convert_longlong_bytes_to_float(self, op): + if longlong.is_64_bit: + loc0 = self.rm.make_sure_var_in_reg(op.getarg(0)) + loc1 = self.xrm.force_allocate_reg(op.result) + self.Perform(op, [loc0], loc1) + self.rm.possibly_free_var(op.getarg(0)) + else: + arg0 = op.getarg(0) + loc0 = self.xrm.make_sure_var_in_reg(arg0) + loc1 = self.xrm.force_allocate_reg(op.result, forbidden_vars=[arg0]) + self.Perform(op, [loc0], loc1) + self.xrm.possibly_free_var(arg0) def _consider_llong_binop_xx(self, op): # must force both arguments into xmm registers, because we don't diff --git a/pypy/jit/codewriter/jtransform.py b/pypy/jit/codewriter/jtransform.py --- a/pypy/jit/codewriter/jtransform.py +++ b/pypy/jit/codewriter/jtransform.py @@ -295,6 +295,7 @@ return op rewrite_op_convert_float_bytes_to_longlong = _noop_rewrite + rewrite_op_convert_longlong_bytes_to_float = _noop_rewrite # ---------- # Various kinds of calls diff --git a/pypy/jit/codewriter/test/test_flatten.py b/pypy/jit/codewriter/test/test_flatten.py --- a/pypy/jit/codewriter/test/test_flatten.py +++ b/pypy/jit/codewriter/test/test_flatten.py @@ -968,20 +968,22 @@ int_return %i2 """, transform=True) - def test_convert_float_bytes_to_int(self): - from pypy.rlib.longlong2float import float2longlong + def test_convert_float_bytes(self): + from pypy.rlib.longlong2float import float2longlong, longlong2float def f(x): - return float2longlong(x) + ll = float2longlong(x) + return longlong2float(ll) if longlong.is_64_bit: - result_var = "%i0" - return_op = "int_return" + tmp_var = "%i0" + result_var = "%f1" else: - result_var = "%f1" - return_op = "float_return" + tmp_var = "%f1" + result_var = "%f2" self.encoding_test(f, [25.0], """ - convert_float_bytes_to_longlong %%f0 -> %(result_var)s - %(return_op)s %(result_var)s - """ % {"result_var": result_var, "return_op": return_op}) + convert_float_bytes_to_longlong %%f0 -> %(tmp_var)s + convert_longlong_bytes_to_float %(tmp_var)s -> %(result_var)s + float_return %(result_var)s + """ % {"result_var": result_var, "tmp_var": tmp_var}, transform=True) def check_force_cast(FROM, TO, operations, value): diff --git a/pypy/jit/metainterp/blackhole.py b/pypy/jit/metainterp/blackhole.py --- a/pypy/jit/metainterp/blackhole.py +++ b/pypy/jit/metainterp/blackhole.py @@ -672,6 +672,11 @@ a = longlong.getrealfloat(a) return longlong2float.float2longlong(a) + @arguments(LONGLONG_TYPECODE, returns="f") + def bhimpl_convert_longlong_bytes_to_float(a): + a = longlong2float.longlong2float(a) + return longlong.getfloatstorage(a) + # ---------- # control flow operations diff --git a/pypy/jit/metainterp/pyjitpl.py b/pypy/jit/metainterp/pyjitpl.py --- a/pypy/jit/metainterp/pyjitpl.py +++ b/pypy/jit/metainterp/pyjitpl.py @@ -224,6 +224,7 @@ 'float_neg', 'float_abs', 'cast_ptr_to_int', 'cast_int_to_ptr', 'convert_float_bytes_to_longlong', + 'convert_longlong_bytes_to_float', ]: exec py.code.Source(''' @arguments("box") diff --git a/pypy/jit/metainterp/resoperation.py b/pypy/jit/metainterp/resoperation.py --- a/pypy/jit/metainterp/resoperation.py +++ b/pypy/jit/metainterp/resoperation.py @@ -420,6 +420,7 @@ 'CAST_FLOAT_TO_SINGLEFLOAT/1', 'CAST_SINGLEFLOAT_TO_FLOAT/1', 'CONVERT_FLOAT_BYTES_TO_LONGLONG/1', + 'CONVERT_LONGLONG_BYTES_TO_FLOAT/1', # 'INT_LT/2b', 'INT_LE/2b', diff --git a/pypy/jit/metainterp/test/test_ajit.py b/pypy/jit/metainterp/test/test_ajit.py --- a/pypy/jit/metainterp/test/test_ajit.py +++ b/pypy/jit/metainterp/test/test_ajit.py @@ -1,3 +1,4 @@ +import math import sys import py @@ -15,7 +16,7 @@ loop_invariant, elidable, promote, jit_debug, assert_green, AssertGreenFailed, unroll_safe, current_trace_length, look_inside_iff, isconstant, isvirtual, promote_string, set_param, record_known_class) -from pypy.rlib.longlong2float import float2longlong +from pypy.rlib.longlong2float import float2longlong, longlong2float from pypy.rlib.rarithmetic import ovfcheck, is_valid_int from pypy.rpython.lltypesystem import lltype, llmemory, rffi from pypy.rpython.ootypesystem import ootype @@ -3795,15 +3796,15 @@ res = self.interp_operations(g, [1]) assert res == 3 - def test_float2longlong(self): + def test_float_bytes(self): def f(n): - return float2longlong(n) + ll = float2longlong(n) + return longlong2float(ll) for x in [2.5, float("nan"), -2.5, float("inf")]: # There are tests elsewhere to verify the correctness of this. - expected = float2longlong(x) res = self.interp_operations(f, [x]) - assert longlong.getfloatstorage(res) == expected + assert res == x or math.isnan(x) and math.isnan(res) class TestLLtype(BaseLLtypeTests, LLJitMixin): diff --git a/pypy/module/_ssl/interp_ssl.py b/pypy/module/_ssl/interp_ssl.py --- a/pypy/module/_ssl/interp_ssl.py +++ b/pypy/module/_ssl/interp_ssl.py @@ -432,7 +432,8 @@ raise _ssl_seterror(self.space, self, length) try: # this is actually an immutable bytes sequence - return self.space.wrap(rffi.charp2str(buf_ptr[0])) + return self.space.wrap(rffi.charpsize2str(buf_ptr[0], + length)) finally: libssl_OPENSSL_free(buf_ptr[0]) else: diff --git a/pypy/module/array/interp_array.py b/pypy/module/array/interp_array.py --- a/pypy/module/array/interp_array.py +++ b/pypy/module/array/interp_array.py @@ -11,7 +11,7 @@ from pypy.objspace.std.register_all import register_all from pypy.rlib.rarithmetic import ovfcheck from pypy.rlib.unroll import unrolling_iterable -from pypy.rlib.objectmodel import specialize +from pypy.rlib.objectmodel import specialize, keepalive_until_here from pypy.rpython.lltypesystem import lltype, rffi @@ -145,18 +145,24 @@ unroll_typecodes = unrolling_iterable(types.keys()) class ArrayBuffer(RWBuffer): - def __init__(self, data, bytes): - self.data = data - self.len = bytes + def __init__(self, array): + self.array = array def getlength(self): - return self.len + return self.array.len * self.array.itemsize def getitem(self, index): - return self.data[index] + array = self.array + data = array._charbuf_start() + char = data[index] + array._charbuf_stop() + return char def setitem(self, index, char): - self.data[index] = char + array = self.array + data = array._charbuf_start() + data[index] = char + array._charbuf_stop() def make_array(mytype): @@ -278,9 +284,10 @@ oldlen = self.len new = len(s) / mytype.bytes self.setlen(oldlen + new) - cbuf = self.charbuf() + cbuf = self._charbuf_start() for i in range(len(s)): cbuf[oldlen * mytype.bytes + i] = s[i] + self._charbuf_stop() def fromlist(self, w_lst): s = self.len @@ -310,8 +317,11 @@ else: self.fromsequence(w_iterable) - def charbuf(self): - return rffi.cast(rffi.CCHARP, self.buffer) + def _charbuf_start(self): + return rffi.cast(rffi.CCHARP, self.buffer) + + def _charbuf_stop(self): + keepalive_until_here(self) def w_getitem(self, space, idx): item = self.buffer[idx] @@ -530,8 +540,10 @@ self.fromstring(space.str_w(w_s)) def array_tostring__Array(space, self): - cbuf = self.charbuf() - return self.space.wrap(rffi.charpsize2str(cbuf, self.len * mytype.bytes)) + cbuf = self._charbuf_start() + s = rffi.charpsize2str(cbuf, self.len * mytype.bytes) + self._charbuf_stop() + return self.space.wrap(s) def array_fromfile__Array_ANY_ANY(space, self, w_f, w_n): if not isinstance(w_f, W_File): @@ -613,8 +625,7 @@ # Misc methods def buffer__Array(space, self): - b = ArrayBuffer(self.charbuf(), self.len * mytype.bytes) - return space.wrap(b) + return space.wrap(ArrayBuffer(self)) def array_buffer_info__Array(space, self): w_ptr = space.wrap(rffi.cast(lltype.Unsigned, self.buffer)) @@ -649,7 +660,7 @@ raise OperationError(space.w_RuntimeError, space.wrap(msg)) if self.len == 0: return - bytes = self.charbuf() + bytes = self._charbuf_start() tmp = [bytes[0]] * mytype.bytes for start in range(0, self.len * mytype.bytes, mytype.bytes): stop = start + mytype.bytes - 1 @@ -657,6 +668,7 @@ tmp[i] = bytes[start + i] for i in range(mytype.bytes): bytes[stop - i] = tmp[i] + self._charbuf_stop() def repr__Array(space, self): if self.len == 0: diff --git a/pypy/module/array/test/test_array.py b/pypy/module/array/test/test_array.py --- a/pypy/module/array/test/test_array.py +++ b/pypy/module/array/test/test_array.py @@ -433,7 +433,25 @@ a = self.array('h', 'Hi') buf = buffer(a) assert buf[1] == 'i' - #raises(TypeError, buf.__setitem__, 1, 'o') + + def test_buffer_write(self): + a = self.array('c', 'hello') + buf = buffer(a) + print repr(buf) + try: + buf[3] = 'L' + except TypeError: + skip("buffer(array) returns a read-only buffer on CPython") + assert a.tostring() == 'helLo' + + def test_buffer_keepalive(self): + buf = buffer(self.array('c', 'text')) + assert buf[2] == 'x' + # + a = self.array('c', 'foobarbaz') + buf = buffer(a) + a.fromstring('some extra text') + assert buf[:] == 'foobarbazsome extra text' def test_list_methods(self): assert repr(self.array('i')) == "array('i')" diff --git a/pypy/module/cpyext/bufferobject.py b/pypy/module/cpyext/bufferobject.py --- a/pypy/module/cpyext/bufferobject.py +++ b/pypy/module/cpyext/bufferobject.py @@ -2,8 +2,10 @@ from pypy.module.cpyext.api import ( cpython_api, Py_ssize_t, cpython_struct, bootstrap_function, PyObjectFields, PyObject) -from pypy.module.cpyext.pyobject import make_typedescr, Py_DecRef +from pypy.module.cpyext.pyobject import make_typedescr, Py_DecRef, make_ref from pypy.interpreter.buffer import Buffer, StringBuffer, SubBuffer +from pypy.interpreter.error import OperationError +from pypy.module.array.interp_array import ArrayBuffer PyBufferObjectStruct = lltype.ForwardReference() @@ -41,26 +43,38 @@ py_buf.c_b_offset = w_obj.offset w_obj = w_obj.buffer + # If w_obj already allocated a fixed buffer, use it, and keep a + # reference to w_obj. + # Otherwise, b_base stays NULL, and we own the b_ptr. + if isinstance(w_obj, StringBuffer): - py_buf.c_b_base = rffi.cast(PyObject, 0) # space.wrap(w_obj.value) - py_buf.c_b_ptr = rffi.cast(rffi.VOIDP, rffi.str2charp(w_obj.as_str())) + py_buf.c_b_base = lltype.nullptr(PyObject.TO) + py_buf.c_b_ptr = rffi.cast(rffi.VOIDP, rffi.str2charp(w_obj.value)) + py_buf.c_b_size = w_obj.getlength() + elif isinstance(w_obj, ArrayBuffer): + w_base = w_obj.array + py_buf.c_b_base = make_ref(space, w_base) + py_buf.c_b_ptr = rffi.cast(rffi.VOIDP, w_obj.array._charbuf_start()) py_buf.c_b_size = w_obj.getlength() else: - raise Exception("Fail fail fail fail fail") + raise OperationError(space.w_NotImplementedError, space.wrap( + "buffer flavor not supported")) def buffer_realize(space, py_obj): """ Creates the buffer in the PyPy interpreter from a cpyext representation. """ - raise Exception("realize fail fail fail") - + raise OperationError(space.w_NotImplementedError, space.wrap( + "Don't know how to realize a buffer")) @cpython_api([PyObject], lltype.Void, external=False) def buffer_dealloc(space, py_obj): py_buf = rffi.cast(PyBufferObject, py_obj) - Py_DecRef(space, py_buf.c_b_base) - rffi.free_charp(rffi.cast(rffi.CCHARP, py_buf.c_b_ptr)) + if py_buf.c_b_base: + Py_DecRef(space, py_buf.c_b_base) + else: + rffi.free_charp(rffi.cast(rffi.CCHARP, py_buf.c_b_ptr)) from pypy.module.cpyext.object import PyObject_dealloc PyObject_dealloc(space, py_obj) diff --git a/pypy/module/cpyext/pyerrors.py b/pypy/module/cpyext/pyerrors.py --- a/pypy/module/cpyext/pyerrors.py +++ b/pypy/module/cpyext/pyerrors.py @@ -2,6 +2,7 @@ from pypy.rpython.lltypesystem import rffi, lltype from pypy.interpreter.error import OperationError +from pypy.interpreter import pytraceback from pypy.module.cpyext.api import cpython_api, CANNOT_FAIL, CONST_STRING from pypy.module.exceptions.interp_exceptions import W_RuntimeWarning from pypy.module.cpyext.pyobject import ( @@ -315,3 +316,65 @@ It may be called without holding the interpreter lock.""" space.check_signal_action.set_interrupt() + at cpython_api([PyObjectP, PyObjectP, PyObjectP], lltype.Void) +def PyErr_GetExcInfo(space, ptype, pvalue, ptraceback): + """---Cython extension--- + + Retrieve the exception info, as known from ``sys.exc_info()``. This + refers to an exception that was already caught, not to an exception + that was freshly raised. Returns new references for the three + objects, any of which may be *NULL*. Does not modify the exception + info state. + + .. note:: + + This function is not normally used by code that wants to handle + exceptions. Rather, it can be used when code needs to save and + restore the exception state temporarily. Use + :c:func:`PyErr_SetExcInfo` to restore or clear the exception + state. + """ + ec = space.getexecutioncontext() + operror = ec.sys_exc_info() + if operror: + ptype[0] = make_ref(space, operror.w_type) + pvalue[0] = make_ref(space, operror.get_w_value(space)) + ptraceback[0] = make_ref(space, space.wrap(operror.get_traceback())) + else: + ptype[0] = lltype.nullptr(PyObject.TO) + pvalue[0] = lltype.nullptr(PyObject.TO) + ptraceback[0] = lltype.nullptr(PyObject.TO) + + at cpython_api([PyObject, PyObject, PyObject], lltype.Void) +def PyErr_SetExcInfo(space, w_type, w_value, w_traceback): + """---Cython extension--- + + Set the exception info, as known from ``sys.exc_info()``. This refers + to an exception that was already caught, not to an exception that was + freshly raised. This function steals the references of the arguments. + To clear the exception state, pass *NULL* for all three arguments. + For general rules about the three arguments, see :c:func:`PyErr_Restore`. + + .. note:: + + This function is not normally used by code that wants to handle + exceptions. Rather, it can be used when code needs to save and + restore the exception state temporarily. Use + :c:func:`PyErr_GetExcInfo` to read the exception state. + """ + if w_value is None or space.is_w(w_value, space.w_None): + operror = None + else: + tb = None + if w_traceback is not None: + try: + tb = pytraceback.check_traceback(space, w_traceback, '?') + except OperationError: # catch and ignore bogus objects + pass + operror = OperationError(w_type, w_value, tb) + # + ec = space.getexecutioncontext() + ec.set_sys_exc_info(operror) + Py_DecRef(space, w_type) + Py_DecRef(space, w_value) + Py_DecRef(space, w_traceback) diff --git a/pypy/module/cpyext/slotdefs.py b/pypy/module/cpyext/slotdefs.py --- a/pypy/module/cpyext/slotdefs.py +++ b/pypy/module/cpyext/slotdefs.py @@ -167,14 +167,16 @@ if rffi.cast(lltype.Signed, res) == -1: space.fromcache(State).check_and_raise_exception(always=True) +# Warning, confusing function name (like CPython). Used only for sq_contains. def wrap_objobjproc(space, w_self, w_args, func): func_target = rffi.cast(objobjproc, func) check_num_args(space, w_args, 1) w_value, = space.fixedview(w_args) res = generic_cpy_call(space, func_target, w_self, w_value) - if rffi.cast(lltype.Signed, res) == -1: + res = rffi.cast(lltype.Signed, res) + if res == -1: space.fromcache(State).check_and_raise_exception(always=True) - return space.wrap(res) + return space.wrap(bool(res)) def wrap_objobjargproc(space, w_self, w_args, func): func_target = rffi.cast(objobjargproc, func) @@ -183,7 +185,7 @@ res = generic_cpy_call(space, func_target, w_self, w_key, w_value) if rffi.cast(lltype.Signed, res) == -1: space.fromcache(State).check_and_raise_exception(always=True) - return space.wrap(res) + return space.w_None def wrap_delitem(space, w_self, w_args, func): func_target = rffi.cast(objobjargproc, func) diff --git a/pypy/module/cpyext/test/foo.c b/pypy/module/cpyext/test/foo.c --- a/pypy/module/cpyext/test/foo.c +++ b/pypy/module/cpyext/test/foo.c @@ -176,6 +176,8 @@ {NULL} /* Sentinel */ }; +PyDoc_STRVAR(foo_doc, "foo is for testing."); + static PyTypeObject footype = { PyVarObject_HEAD_INIT(NULL, 0) "foo.foo", /*tp_name*/ @@ -198,7 +200,7 @@ (setattrofunc)foo_setattro, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ + foo_doc, /*tp_doc*/ 0, /*tp_traverse*/ 0, /*tp_clear*/ 0, /*tp_richcompare*/ diff --git a/pypy/module/cpyext/test/test_bufferobject.py b/pypy/module/cpyext/test/test_bufferobject.py --- a/pypy/module/cpyext/test/test_bufferobject.py +++ b/pypy/module/cpyext/test/test_bufferobject.py @@ -48,3 +48,17 @@ ]) b = module.buffer_new() raises(AttributeError, getattr, b, 'x') + + def test_array_buffer(self): + module = self.import_extension('foo', [ + ("roundtrip", "METH_O", + """ + PyBufferObject *buf = (PyBufferObject *)args; + return PyString_FromStringAndSize(buf->b_ptr, buf->b_size); + """), + ]) + import array + a = array.array('c', 'text') + b = buffer(a) + assert module.roundtrip(b) == 'text' + diff --git a/pypy/module/cpyext/test/test_pyerrors.py b/pypy/module/cpyext/test/test_pyerrors.py --- a/pypy/module/cpyext/test/test_pyerrors.py +++ b/pypy/module/cpyext/test/test_pyerrors.py @@ -218,3 +218,51 @@ assert e.filename == "blyf" assert e.errno == errno.EBADF assert e.strerror == os.strerror(errno.EBADF) + + def test_GetSetExcInfo(self): + import sys + module = self.import_extension('foo', [ + ("getset_exc_info", "METH_VARARGS", + r''' + PyObject *type, *val, *tb; + PyObject *new_type, *new_val, *new_tb; + PyObject *result; + + if (!PyArg_ParseTuple(args, "OOO", &new_type, &new_val, &new_tb)) + return NULL; + + PyErr_GetExcInfo(&type, &val, &tb); + + Py_INCREF(new_type); + Py_INCREF(new_val); + Py_INCREF(new_tb); + PyErr_SetExcInfo(new_type, new_val, new_tb); + + result = Py_BuildValue("OOO", + type ? type : Py_None, + val ? val : Py_None, + tb ? tb : Py_None); + Py_XDECREF(type); + Py_XDECREF(val); + Py_XDECREF(tb); + return result; + ''' + ), + ]) + try: + raise ValueError(5) + except ValueError, old_exc: + new_exc = TypeError("TEST") + orig_sys_exc_info = sys.exc_info() + orig_exc_info = module.getset_exc_info(new_exc.__class__, + new_exc, None) + new_sys_exc_info = sys.exc_info() + new_exc_info = module.getset_exc_info(*orig_exc_info) + reset_sys_exc_info = sys.exc_info() + + assert orig_exc_info[0] is old_exc.__class__ + assert orig_exc_info[1] is old_exc + assert orig_exc_info == orig_sys_exc_info + assert orig_exc_info == reset_sys_exc_info + assert new_exc_info == (new_exc.__class__, new_exc, None) + assert new_exc_info == new_sys_exc_info diff --git a/pypy/module/cpyext/test/test_typeobject.py b/pypy/module/cpyext/test/test_typeobject.py --- a/pypy/module/cpyext/test/test_typeobject.py +++ b/pypy/module/cpyext/test/test_typeobject.py @@ -20,6 +20,7 @@ assert type(obj) is module.fooType print "type of obj has type", type(type(obj)) print "type of type of obj has type", type(type(type(obj))) + assert module.fooType.__doc__ == "foo is for testing." def test_typeobject_method_descriptor(self): module = self.import_module(name='foo') @@ -414,8 +415,11 @@ static int mp_ass_subscript(PyObject *self, PyObject *key, PyObject *value) { - PyErr_SetNone(PyExc_ZeroDivisionError); - return -1; + if (PyInt_Check(key)) { + PyErr_SetNone(PyExc_ZeroDivisionError); + return -1; + } + return 0; } PyMappingMethods tp_as_mapping; static PyTypeObject Foo_Type = { @@ -425,6 +429,36 @@ ''') obj = module.new_obj() raises(ZeroDivisionError, obj.__setitem__, 5, None) + res = obj.__setitem__('foo', None) + assert res is None + + def test_sq_contains(self): + module = self.import_extension('foo', [ + ("new_obj", "METH_NOARGS", + ''' + PyObject *obj; + Foo_Type.tp_as_sequence = &tp_as_sequence; + tp_as_sequence.sq_contains = sq_contains; + if (PyType_Ready(&Foo_Type) < 0) return NULL; + obj = PyObject_New(PyObject, &Foo_Type); + return obj; + ''' + )], + ''' + static int + sq_contains(PyObject *self, PyObject *value) + { + return 42; + } + PySequenceMethods tp_as_sequence; + static PyTypeObject Foo_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "foo.foo", + }; + ''') + obj = module.new_obj() + res = "foo" in obj + assert res is True def test_tp_iter(self): module = self.import_extension('foo', [ diff --git a/pypy/module/cpyext/typeobject.py b/pypy/module/cpyext/typeobject.py --- a/pypy/module/cpyext/typeobject.py +++ b/pypy/module/cpyext/typeobject.py @@ -307,6 +307,8 @@ if not space.is_true(space.issubtype(self, space.w_type)): self.flag_cpytype = True self.flag_heaptype = False + if pto.c_tp_doc: + self.w_doc = space.wrap(rffi.charp2str(pto.c_tp_doc)) @bootstrap_function def init_typeobject(space): @@ -624,7 +626,6 @@ Creates an interpreter type from a PyTypeObject structure. """ # missing: - # setting __doc__ if not defined and tp_doc defined # inheriting tp_as_* slots # unsupported: # tp_mro, tp_subclasses diff --git a/pypy/module/micronumpy/__init__.py b/pypy/module/micronumpy/__init__.py --- a/pypy/module/micronumpy/__init__.py +++ b/pypy/module/micronumpy/__init__.py @@ -29,6 +29,7 @@ 'flatiter': 'interp_numarray.W_FlatIterator', 'isna': 'interp_numarray.isna', 'concatenate': 'interp_numarray.concatenate', + 'repeat': 'interp_numarray.repeat', 'set_string_function': 'appbridge.set_string_function', @@ -99,9 +100,12 @@ ("exp2", "exp2"), ("expm1", "expm1"), ("fabs", "fabs"), + ("fmax", "fmax"), + ("fmin", "fmin"), ("fmod", "fmod"), ("floor", "floor"), ("ceil", "ceil"), + ("trunc", "trunc"), ("greater", "greater"), ("greater_equal", "greater_equal"), ("less", "less"), @@ -122,12 +126,16 @@ ("sinh", "sinh"), ("subtract", "subtract"), ('sqrt', 'sqrt'), + ('square', 'square'), ("tan", "tan"), ("tanh", "tanh"), ('bitwise_and', 'bitwise_and'), ('bitwise_or', 'bitwise_or'), ('bitwise_xor', 'bitwise_xor'), ('bitwise_not', 'invert'), + ('left_shift', 'left_shift'), + ('right_shift', 'right_shift'), + ('invert', 'invert'), ('isnan', 'isnan'), ('isinf', 'isinf'), ('isneginf', 'isneginf'), diff --git a/pypy/module/micronumpy/interp_numarray.py b/pypy/module/micronumpy/interp_numarray.py --- a/pypy/module/micronumpy/interp_numarray.py +++ b/pypy/module/micronumpy/interp_numarray.py @@ -673,6 +673,10 @@ def compute_first_step(self, sig, frame): pass + @unwrap_spec(repeats=int) + def descr_repeat(self, space, repeats, w_axis=None): + return repeat(space, self, repeats, w_axis) + def convert_to_array(space, w_obj): if isinstance(w_obj, BaseArray): return w_obj @@ -1261,6 +1265,31 @@ return convert_to_array(space, w_obj2).descr_dot(space, w_arr) return w_arr.descr_dot(space, w_obj2) + at unwrap_spec(repeats=int) +def repeat(space, w_arr, repeats, w_axis=None): + arr = convert_to_array(space, w_arr) + if space.is_w(w_axis, space.w_None): + arr = arr.descr_flatten(space).get_concrete() + orig_size = arr.shape[0] + shape = [arr.shape[0] * repeats] + res = W_NDimArray(shape, arr.find_dtype()) + for i in range(repeats): + Chunks([Chunk(i, shape[0] - repeats + i, repeats, + orig_size)]).apply(res).setslice(space, arr) + else: + arr = arr.get_concrete() + axis = space.int_w(w_axis) + shape = arr.shape[:] + chunks = [Chunk(0, i, 1, i) for i in shape] + orig_size = shape[axis] + shape[axis] *= repeats + res = W_NDimArray(shape, arr.find_dtype()) + for i in range(repeats): + chunks[axis] = Chunk(i, shape[axis] - repeats + i, repeats, + orig_size) + Chunks(chunks).apply(res).setslice(space, arr) + return res + @unwrap_spec(axis=int) def concatenate(space, w_args, axis=0): args_w = space.listview(w_args) @@ -1386,6 +1415,7 @@ tolist = interp2app(BaseArray.descr_tolist), take = interp2app(BaseArray.descr_take), compress = interp2app(BaseArray.descr_compress), + repeat = interp2app(BaseArray.descr_repeat), ) diff --git a/pypy/module/micronumpy/interp_ufuncs.py b/pypy/module/micronumpy/interp_ufuncs.py --- a/pypy/module/micronumpy/interp_ufuncs.py +++ b/pypy/module/micronumpy/interp_ufuncs.py @@ -3,9 +3,11 @@ from pypy.interpreter.gateway import interp2app, unwrap_spec, NoneNotWrapped from pypy.interpreter.typedef import TypeDef, GetSetProperty, interp_attrproperty from pypy.module.micronumpy import interp_boxes, interp_dtype, support, loop +from pypy.rlib import jit from pypy.rlib.rarithmetic import LONG_BIT from pypy.tool.sourcetools import func_with_new_name + class W_Ufunc(Wrappable): _attrs_ = ["name", "promote_to_float", "promote_bools", "identity"] _immutable_fields_ = ["promote_to_float", "promote_bools", "name"] @@ -28,7 +30,7 @@ return self.identity def descr_call(self, space, __args__): - from interp_numarray import BaseArray + from interp_numarray import BaseArray args_w, kwds_w = __args__.unpack() # it occurs to me that we don't support any datatypes that # require casting, change it later when we do @@ -179,7 +181,7 @@ elif out.shape != shape: raise operationerrfmt(space.w_ValueError, 'output parameter shape mismatch, expecting [%s]' + - ' , got [%s]', + ' , got [%s]', ",".join([str(x) for x in shape]), ",".join([str(x) for x in out.shape]), ) @@ -204,7 +206,7 @@ else: arr = ReduceArray(self.func, self.name, self.identity, obj, dtype) val = loop.compute(arr) - return val + return val def do_axis_reduce(self, obj, dtype, axis, result): from pypy.module.micronumpy.interp_numarray import AxisReduce @@ -253,7 +255,7 @@ if isinstance(w_obj, Scalar): arr = self.func(calc_dtype, w_obj.value.convert_to(calc_dtype)) if isinstance(out,Scalar): - out.value=arr + out.value = arr elif isinstance(out, BaseArray): out.fill(space, arr) else: @@ -265,7 +267,7 @@ if not broadcast_shape or broadcast_shape != out.shape: raise operationerrfmt(space.w_ValueError, 'output parameter shape mismatch, could not broadcast [%s]' + - ' to [%s]', + ' to [%s]', ",".join([str(x) for x in w_obj.shape]), ",".join([str(x) for x in out.shape]), ) @@ -292,10 +294,11 @@ self.func = func self.comparison_func = comparison_func + @jit.unroll_safe def call(self, space, args_w): from pypy.module.micronumpy.interp_numarray import (Call2, convert_to_array, Scalar, shape_agreement, BaseArray) - if len(args_w)>2: + if len(args_w) > 2: [w_lhs, w_rhs, w_out] = args_w else: [w_lhs, w_rhs] = args_w @@ -326,7 +329,7 @@ w_rhs.value.convert_to(calc_dtype) ) if isinstance(out,Scalar): - out.value=arr + out.value = arr elif isinstance(out, BaseArray): out.fill(space, arr) else: @@ -337,7 +340,7 @@ if out and out.shape != shape_agreement(space, new_shape, out.shape): raise operationerrfmt(space.w_ValueError, 'output parameter shape mismatch, could not broadcast [%s]' + - ' to [%s]', + ' to [%s]', ",".join([str(x) for x in new_shape]), ",".join([str(x) for x in out.shape]), ) @@ -347,7 +350,6 @@ w_lhs.add_invalidates(w_res) w_rhs.add_invalidates(w_res) if out: - #out.add_invalidates(w_res) #causes a recursion loop w_res.get_concrete() return w_res @@ -539,14 +541,18 @@ ("reciprocal", "reciprocal", 1), ("fabs", "fabs", 1, {"promote_to_float": True}), + ("fmax", "fmax", 2, {"promote_to_float": True}), + ("fmin", "fmin", 2, {"promote_to_float": True}), ("fmod", "fmod", 2, {"promote_to_float": True}), ("floor", "floor", 1, {"promote_to_float": True}), ("ceil", "ceil", 1, {"promote_to_float": True}), + ("trunc", "trunc", 1, {"promote_to_float": True}), ("exp", "exp", 1, {"promote_to_float": True}), ("exp2", "exp2", 1, {"promote_to_float": True}), ("expm1", "expm1", 1, {"promote_to_float": True}), ('sqrt', 'sqrt', 1, {'promote_to_float': True}), + ('square', 'square', 1, {'promote_to_float': True}), ("sin", "sin", 1, {"promote_to_float": True}), ("cos", "cos", 1, {"promote_to_float": True}), diff --git a/pypy/module/micronumpy/signature.py b/pypy/module/micronumpy/signature.py --- a/pypy/module/micronumpy/signature.py +++ b/pypy/module/micronumpy/signature.py @@ -107,6 +107,10 @@ arr.compute_first_step(self, f) return f + def debug_repr(self): + # should be overridden, but in case it isn't, provide a default + return str(self) + class ConcreteSignature(Signature): _immutable_fields_ = ['dtype'] @@ -207,7 +211,7 @@ def _create_iter(self, iterlist, arraylist, arr, transforms): from pypy.module.micronumpy.interp_numarray import VirtualSlice assert isinstance(arr, VirtualSlice) - transforms = transforms + [ViewTransform(arr.chunks)] + transforms = [ViewTransform(arr.chunks)] + transforms self.child._create_iter(iterlist, arraylist, arr.child, transforms) def eval(self, frame, arr): @@ -215,6 +219,9 @@ assert isinstance(arr, VirtualSlice) return self.child.eval(frame, arr.child) + def debug_repr(self): + return 'VirtualSlice(%s)' % self.child.debug_repr() + class Call1(Signature): _immutable_fields_ = ['unfunc', 'name', 'child', 'res', 'dtype'] @@ -270,7 +277,7 @@ from pypy.module.micronumpy.interp_numarray import Call1 assert isinstance(arr, Call1) - vtransforms = transforms + [BroadcastTransform(arr.values.shape)] + vtransforms = [BroadcastTransform(arr.values.shape)] + transforms self.child._create_iter(iterlist, arraylist, arr.values, vtransforms) self.res._create_iter(iterlist, arraylist, arr.res, transforms) @@ -348,7 +355,7 @@ from pypy.module.micronumpy.interp_numarray import ResultArray assert isinstance(arr, ResultArray) - rtransforms = transforms + [BroadcastTransform(arr.left.shape)] + rtransforms = [BroadcastTransform(arr.left.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, transforms) self.right._create_iter(iterlist, arraylist, arr.right, rtransforms) @@ -375,7 +382,7 @@ from pypy.module.micronumpy.interp_numarray import Call2 assert isinstance(arr, Call2) - ltransforms = transforms + [BroadcastTransform(arr.shape)] + ltransforms = [BroadcastTransform(arr.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, ltransforms) self.right._create_iter(iterlist, arraylist, arr.right, transforms) @@ -388,7 +395,7 @@ from pypy.module.micronumpy.interp_numarray import Call2 assert isinstance(arr, Call2) - rtransforms = transforms + [BroadcastTransform(arr.shape)] + rtransforms = [BroadcastTransform(arr.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, transforms) self.right._create_iter(iterlist, arraylist, arr.right, rtransforms) @@ -401,8 +408,8 @@ from pypy.module.micronumpy.interp_numarray import Call2 assert isinstance(arr, Call2) - rtransforms = transforms + [BroadcastTransform(arr.shape)] - ltransforms = transforms + [BroadcastTransform(arr.shape)] + rtransforms = [BroadcastTransform(arr.shape)] + transforms + ltransforms = [BroadcastTransform(arr.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, ltransforms) self.right._create_iter(iterlist, arraylist, arr.right, rtransforms) @@ -424,7 +431,7 @@ frame.cur_value = self.binfunc(self.calc_dtype, frame.cur_value, rval) def debug_repr(self): - return 'ReduceSig(%s)' % (self.name, self.right.debug_repr()) + return 'ReduceSig(%s, %s)' % (self.name, self.right.debug_repr()) class SliceloopSignature(Call2): def eval(self, frame, arr): @@ -448,7 +455,7 @@ from pypy.module.micronumpy.interp_numarray import SliceArray assert isinstance(arr, SliceArray) - rtransforms = transforms + [BroadcastTransform(arr.shape)] + rtransforms = [BroadcastTransform(arr.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, transforms) self.right._create_iter(iterlist, arraylist, arr.right, rtransforms) diff --git a/pypy/module/micronumpy/strides.py b/pypy/module/micronumpy/strides.py --- a/pypy/module/micronumpy/strides.py +++ b/pypy/module/micronumpy/strides.py @@ -1,6 +1,7 @@ from pypy.rlib import jit from pypy.interpreter.error import OperationError + at jit.look_inside_iff(lambda chunks: jit.isconstant(len(chunks))) def enumerate_chunks(chunks): result = [] i = -1 @@ -85,9 +86,9 @@ space.isinstance_w(w_item_or_slice, space.w_slice)): raise OperationError(space.w_IndexError, space.wrap('unsupported iterator index')) - + start, stop, step, lngth = space.decode_index4(w_item_or_slice, size) - + coords = [0] * len(shape) i = start if order == 'C': diff --git a/pypy/module/micronumpy/support.py b/pypy/module/micronumpy/support.py --- a/pypy/module/micronumpy/support.py +++ b/pypy/module/micronumpy/support.py @@ -1,5 +1,9 @@ +from pypy.rlib import jit + + + at jit.unroll_safe def product(s): i = 1 for x in s: i *= x - return i \ No newline at end of file + return i diff --git a/pypy/module/micronumpy/test/test_numarray.py b/pypy/module/micronumpy/test/test_numarray.py --- a/pypy/module/micronumpy/test/test_numarray.py +++ b/pypy/module/micronumpy/test/test_numarray.py @@ -395,11 +395,19 @@ assert a[3] == 0. def test_newaxis(self): - from _numpypy import array + import math + from _numpypy import array, cos, zeros from numpypy.core.numeric import newaxis a = array(range(5)) b = array([range(5)]) assert (a[newaxis] == b).all() + a = array(range(3)) + b = array([1, 3]) + expected = zeros((3, 2)) + for x in range(3): + for y in range(2): + expected[x, y] = math.cos(a[x]) * math.cos(b[y]) + assert ((cos(a)[:,newaxis] * cos(b).T) == expected).all() def test_newaxis_slice(self): from _numpypy import array @@ -1338,6 +1346,10 @@ dims_disagree = raises(ValueError, concatenate, (a1, b1), axis=0) assert str(dims_disagree.value) == \ "array dimensions must agree except for axis being concatenated" + a = array([1, 2, 3, 4, 5, 6]) + a = (a + a)[::2] + b = concatenate((a[:3], a[-3:])) + assert (b == [2, 6, 10, 2, 6, 10]).all() def test_std(self): from _numpypy import array @@ -1387,6 +1399,16 @@ assert (ones(1) + ones(1)).nbytes == 8 assert array(3.0).nbytes == 8 + def test_repeat(self): + from _numpypy import repeat, array + assert (repeat([[1, 2], [3, 4]], 3) == [1, 1, 1, 2, 2, 2, + 3, 3, 3, 4, 4, 4]).all() + assert (repeat([[1, 2], [3, 4]], 2, axis=0) == [[1, 2], [1, 2], [3, 4], + [3, 4]]).all() + assert (repeat([[1, 2], [3, 4]], 2, axis=1) == [[1, 1, 2, 2], [3, 3, + 4, 4]]).all() + assert (array([1, 2]).repeat(2) == array([1, 1, 2, 2])).all() + class AppTestMultiDim(BaseNumpyAppTest): def test_init(self): diff --git a/pypy/module/micronumpy/test/test_ufuncs.py b/pypy/module/micronumpy/test/test_ufuncs.py --- a/pypy/module/micronumpy/test/test_ufuncs.py +++ b/pypy/module/micronumpy/test/test_ufuncs.py @@ -135,6 +135,38 @@ assert fabs(float('-inf')) == float('inf') assert isnan(fabs(float('nan'))) + def test_fmax(self): + from _numpypy import fmax + import math + + nnan, nan, inf, ninf = float('-nan'), float('nan'), float('inf'), float('-inf') + + a = [ninf, -5, 0, 5, inf] + assert (fmax(a, [ninf]*5) == a).all() + assert (fmax(a, [inf]*5) == [inf]*5).all() + assert (fmax(a, [1]*5) == [1, 1, 1, 5, inf]).all() + assert math.isnan(fmax(nan, 0)) + assert math.isnan(fmax(0, nan)) + assert math.isnan(fmax(nan, nan)) + # The numpy docs specify that the FIRST NaN should be used if both are NaN + assert math.copysign(1.0, fmax(nnan, nan)) == -1.0 + + def test_fmin(self): + from _numpypy import fmin + import math + + nnan, nan, inf, ninf = float('-nan'), float('nan'), float('inf'), float('-inf') + + a = [ninf, -5, 0, 5, inf] + assert (fmin(a, [ninf]*5) == [ninf]*5).all() + assert (fmin(a, [inf]*5) == a).all() + assert (fmin(a, [1]*5) == [ninf, -5, 0, 1, 1]).all() + assert math.isnan(fmin(nan, 0)) + assert math.isnan(fmin(0, nan)) + assert math.isnan(fmin(nan, nan)) + # The numpy docs specify that the FIRST NaN should be used if both are NaN + assert math.copysign(1.0, fmin(nnan, nan)) == -1.0 + def test_fmod(self): from _numpypy import fmod import math @@ -221,24 +253,17 @@ for i in range(3): assert c[i] == a[i] - b[i] - def test_floorceil(self): - from _numpypy import array, floor, ceil + def test_floorceiltrunc(self): + from _numpypy import array, floor, ceil, trunc import math - reference = [-2.0, -2.0, -1.0, 0.0, 1.0, 1.0, 0] - a = array([-1.4, -1.5, -1.0, 0.0, 1.0, 1.4, 0.5]) - b = floor(a) - for i in range(5): - assert b[i] == reference[i] - reference = [-1.0, -1.0, -1.0, 0.0, 1.0, 2.0, 1.0] - a = array([-1.4, -1.5, -1.0, 0.0, 1.0, 1.4, 0.5]) - b = ceil(a) - assert (reference == b).all() - inf = float("inf") - data = [1.5, 2.9999, -1.999, inf] - results = [math.floor(x) for x in data] - assert (floor(data) == results).all() - results = [math.ceil(x) for x in data] - assert (ceil(data) == results).all() + ninf, inf = float("-inf"), float("inf") + a = array([ninf, -1.4, -1.5, -1.0, 0.0, 1.0, 1.4, 0.5, inf]) + assert ([ninf, -2.0, -2.0, -1.0, 0.0, 1.0, 1.0, 0.0, inf] == floor(a)).all() + assert ([ninf, -1.0, -1.0, -1.0, 0.0, 1.0, 2.0, 1.0, inf] == ceil(a)).all() + assert ([ninf, -1.0, -1.0, -1.0, 0.0, 1.0, 1.0, 0.0, inf] == trunc(a)).all() + assert all([math.isnan(f(float("nan"))) for f in floor, ceil, trunc]) + assert all([math.copysign(1, f(float("nan"))) == 1 for f in floor, ceil, trunc]) + assert all([math.copysign(1, f(float("-nan"))) == -1 for f in floor, ceil, trunc]) def test_copysign(self): from _numpypy import array, copysign @@ -455,6 +480,19 @@ assert math.isnan(sqrt(-1)) assert math.isnan(sqrt(nan)) + def test_square(self): + import math + from _numpypy import square + + nan, inf, ninf = float("nan"), float("inf"), float("-inf") + + assert math.isnan(square(nan)) + assert math.isinf(square(inf)) + assert math.isinf(square(ninf)) + assert square(ninf) > 0 + assert [square(x) for x in range(-5, 5)] == [x*x for x in range(-5, 5)] + assert math.isinf(square(1e300)) + def test_radians(self): import math from _numpypy import radians, array @@ -546,10 +584,18 @@ raises(TypeError, 'array([1.0]) & 1') def test_unary_bitops(self): - from _numpypy import bitwise_not, array + from _numpypy import bitwise_not, invert, array a = array([1, 2, 3, 4]) assert (~a == [-2, -3, -4, -5]).all() assert (bitwise_not(a) == ~a).all() + assert (invert(a) == ~a).all() + + def test_shift(self): + from _numpypy import left_shift, right_shift + import sys + + assert (left_shift([5, 1], [2, 31]) == [20, 2**31]).all() + assert (right_shift(10, range(5)) == [10, 5, 2, 1, 0]).all() def test_comparisons(self): import operator diff --git a/pypy/module/micronumpy/types.py b/pypy/module/micronumpy/types.py --- a/pypy/module/micronumpy/types.py +++ b/pypy/module/micronumpy/types.py @@ -631,6 +631,22 @@ return math.fabs(v) @simple_binary_op + def fmax(self, v1, v2): + if math.isnan(v1): + return v1 + elif math.isnan(v2): + return v2 + return max(v1, v2) + + @simple_binary_op + def fmin(self, v1, v2): + if math.isnan(v1): + return v1 + elif math.isnan(v2): + return v2 + return min(v1, v2) + + @simple_binary_op def fmod(self, v1, v2): try: return math.fmod(v1, v2) @@ -652,6 +668,13 @@ return math.ceil(v) @simple_unary_op + def trunc(self, v): + if v < 0: + return math.ceil(v) + else: + return math.floor(v) + + @simple_unary_op def exp(self, v): try: return math.exp(v) @@ -741,6 +764,10 @@ except ValueError: return rfloat.NAN + @simple_unary_op + def square(self, v): + return v*v + @raw_unary_op def isnan(self, v): return rfloat.isnan(v) diff --git a/pypy/module/pypyjit/test_pypy_c/test_misc.py b/pypy/module/pypyjit/test_pypy_c/test_misc.py --- a/pypy/module/pypyjit/test_pypy_c/test_misc.py +++ b/pypy/module/pypyjit/test_pypy_c/test_misc.py @@ -212,7 +212,7 @@ i19 = int_add(i12, 1) setfield_gc(p9, i19, descr=) guard_nonnull_class(p17, 146982464, descr=...) - i21 = getfield_gc(p17, descr=) + i21 = getfield_gc(p17, descr=) i23 = int_lt(0, i21) guard_true(i23, descr=...) i24 = getfield_gc(p17, descr=) @@ -351,3 +351,23 @@ # the following assertion fails if the loop was cancelled due # to "abort: vable escape" assert len(log.loops_by_id("eval")) == 1 + + def test_sys_exc_info(self): + def main(): + i = 1 + lst = [i] + while i < 1000: + try: + return lst[i] + except: + e = sys.exc_info()[1] # ID: exc_info + if not isinstance(e, IndexError): + raise + i += 1 + return 42 + + log = self.run(main) + assert log.result == 42 + # the following assertion fails if the loop was cancelled due + # to "abort: vable escape" + assert len(log.loops_by_id("exc_info")) == 1 diff --git a/pypy/module/pypyjit/test_pypy_c/test_string.py b/pypy/module/pypyjit/test_pypy_c/test_string.py --- a/pypy/module/pypyjit/test_pypy_c/test_string.py +++ b/pypy/module/pypyjit/test_pypy_c/test_string.py @@ -198,3 +198,37 @@ i49 = call(ConstClass(_ll_2_str_eq_nonnull__rpy_stringPtr_rpy_stringPtr), p35, ConstPtr(ptr48), descr=) guard_value(i49, 1, descr=...) ''') + + def test_remove_duplicate_method_calls(self): + def main(n): + lst = [] + for i in range(n): + s = 'Hello %d' % i + t = s.lower() # ID: callone + u = s.lower() # ID: calltwo + lst.append(t) + lst.append(u) + return len(','.join(lst)) + log = self.run(main, [1000]) + assert log.result == main(1000) + loops = log.loops_by_filename(self.filepath) + loop, = loops + loop.match_by_id('callone', ''' + p114 = call(ConstClass(ll_lower__rpy_stringPtr), p113, descr=) + guard_no_exception(descr=...) + ''') + loop.match_by_id('calltwo', '') # nothing + + def test_move_method_call_out_of_loop(self): + def main(n): + lst = [] + s = 'Hello %d' % n + for i in range(n): + t = s.lower() # ID: callone + lst.append(t) + return len(','.join(lst)) + log = self.run(main, [1000]) + assert log.result == main(1000) + loops = log.loops_by_filename(self.filepath) + loop, = loops + loop.match_by_id('callone', '') # nothing diff --git a/pypy/module/sys/test/test_sysmodule.py b/pypy/module/sys/test/test_sysmodule.py --- a/pypy/module/sys/test/test_sysmodule.py +++ b/pypy/module/sys/test/test_sysmodule.py @@ -595,3 +595,124 @@ assert len(frames) == 1 _, other_frame = frames.popitem() assert other_frame.f_code.co_name in ('other_thread', '?') + + +class AppTestSysExcInfoDirect: + + def setup_method(self, meth): + self.checking = not option.runappdirect + if self.checking: + self.seen = [] + from pypy.module.sys import vm + def exc_info_with_tb(*args): + self.seen.append("n") # not optimized + return self.old[0](*args) + def exc_info_without_tb(*args): + self.seen.append("y") # optimized + return self.old[1](*args) + self.old = [vm.exc_info_with_tb, vm.exc_info_without_tb] + vm.exc_info_with_tb = exc_info_with_tb + vm.exc_info_without_tb = exc_info_without_tb + # + from pypy.rlib import jit + self.old2 = [jit.we_are_jitted] + jit.we_are_jitted = lambda: True + + def teardown_method(self, meth): + if self.checking: + from pypy.module.sys import vm + from pypy.rlib import jit + vm.exc_info_with_tb = self.old[0] + vm.exc_info_without_tb = self.old[1] + jit.we_are_jitted = self.old2[0] + # + assert ''.join(self.seen) == meth.expected + + def test_returns_none(self): + import sys + assert sys.exc_info() == (None, None, None) + assert sys.exc_info()[0] is None + assert sys.exc_info()[1] is None + assert sys.exc_info()[2] is None + assert sys.exc_info()[:2] == (None, None) + assert sys.exc_info()[:3] == (None, None, None) + assert sys.exc_info()[0:2] == (None, None) + assert sys.exc_info()[2:4] == (None,) + test_returns_none.expected = 'nnnnnnnn' + + def test_returns_subscr(self): + import sys + e = KeyError("boom") + try: + raise e + except: + assert sys.exc_info()[0] is KeyError # y + assert sys.exc_info()[1] is e # y + assert sys.exc_info()[2] is not None # n + assert sys.exc_info()[-3] is KeyError # y + assert sys.exc_info()[-2] is e # y + assert sys.exc_info()[-1] is not None # n + test_returns_subscr.expected = 'yynyyn' + + def test_returns_slice_2(self): + import sys + e = KeyError("boom") + try: + raise e + except: + foo = sys.exc_info() # n + assert sys.exc_info()[:0] == () # y + assert sys.exc_info()[:1] == foo[:1] # y + assert sys.exc_info()[:2] == foo[:2] # y + assert sys.exc_info()[:3] == foo # n + assert sys.exc_info()[:4] == foo # n + assert sys.exc_info()[:-1] == foo[:2] # y + assert sys.exc_info()[:-2] == foo[:1] # y + assert sys.exc_info()[:-3] == () # y + test_returns_slice_2.expected = 'nyyynnyyy' + + def test_returns_slice_3(self): + import sys + e = KeyError("boom") + try: + raise e + except: + foo = sys.exc_info() # n + assert sys.exc_info()[2:2] == () # y + assert sys.exc_info()[0:1] == foo[:1] # y + assert sys.exc_info()[1:2] == foo[1:2] # y + assert sys.exc_info()[0:3] == foo # n + assert sys.exc_info()[2:4] == foo[2:] # n + assert sys.exc_info()[0:-1] == foo[:2] # y + assert sys.exc_info()[0:-2] == foo[:1] # y + assert sys.exc_info()[5:-3] == () # y + test_returns_slice_3.expected = 'nyyynnyyy' + + def test_strange_invocation(self): + import sys + e = KeyError("boom") + try: + raise e + except: + a = []; k = {} + assert sys.exc_info(*a)[:0] == () + assert sys.exc_info(**k)[:0] == () + test_strange_invocation.expected = 'nn' + + def test_call_in_subfunction(self): + import sys + def g(): + # this case is not optimized, because we need to search the + # frame chain. it's probably not worth the complications + return sys.exc_info()[1] + e = KeyError("boom") + try: + raise e + except: + assert g() is e + test_call_in_subfunction.expected = 'n' + + +class AppTestSysExcInfoDirectCallMethod(AppTestSysExcInfoDirect): + def setup_class(cls): + cls.space = gettestobjspace(**{"objspace.opcodes.CALL_METHOD": True}) diff --git a/pypy/module/sys/vm.py b/pypy/module/sys/vm.py --- a/pypy/module/sys/vm.py +++ b/pypy/module/sys/vm.py @@ -89,6 +89,9 @@ """Return the (type, value, traceback) of the most recent exception caught by an except clause in the current stack frame or in an older stack frame.""" + return exc_info_with_tb(space) # indirection for the tests + +def exc_info_with_tb(space): operror = space.getexecutioncontext().sys_exc_info() if operror is None: return space.newtuple([space.w_None,space.w_None,space.w_None]) @@ -96,6 +99,59 @@ return space.newtuple([operror.w_type, operror.get_w_value(space), space.wrap(operror.get_traceback())]) +def exc_info_without_tb(space, frame): + operror = frame.last_exception + return space.newtuple([operror.w_type, operror.get_w_value(space), + space.w_None]) + +def exc_info_direct(space, frame): + from pypy.tool import stdlib_opcode + # In order to make the JIT happy, we try to return (exc, val, None) + # instead of (exc, val, tb). We can do that only if we recognize + # the following pattern in the bytecode: + # CALL_FUNCTION/CALL_METHOD <-- invoking me + # LOAD_CONST 0, 1, -2 or -3 + # BINARY_SUBSCR + # or: + # CALL_FUNCTION/CALL_METHOD + # LOAD_CONST <=2 + # SLICE_2 + # or: + # CALL_FUNCTION/CALL_METHOD + # LOAD_CONST any integer + # LOAD_CONST <=2 + # SLICE_3 + need_all_three_args = True + co = frame.getcode().co_code + p = frame.last_instr + if (ord(co[p]) == stdlib_opcode.CALL_FUNCTION or + ord(co[p]) == stdlib_opcode.CALL_METHOD): + if ord(co[p+3]) == stdlib_opcode.LOAD_CONST: + lo = ord(co[p+4]) + hi = ord(co[p+5]) + w_constant = frame.getconstant_w((hi * 256) | lo) + if space.isinstance_w(w_constant, space.w_int): + constant = space.int_w(w_constant) + if ord(co[p+6]) == stdlib_opcode.BINARY_SUBSCR: + if -3 <= constant <= 1 and constant != -1: + need_all_three_args = False + elif ord(co[p+6]) == stdlib_opcode.SLICE+2: + if constant <= 2: + need_all_three_args = False + elif (ord(co[p+6]) == stdlib_opcode.LOAD_CONST and + ord(co[p+9]) == stdlib_opcode.SLICE+3): + lo = ord(co[p+7]) + hi = ord(co[p+8]) + w_constant = frame.getconstant_w((hi * 256) | lo) + if space.isinstance_w(w_constant, space.w_int): + if space.int_w(w_constant) <= 2: + need_all_three_args = False + # + if need_all_three_args or frame.last_exception is None or frame.hide(): + return exc_info_with_tb(space) + else: + return exc_info_without_tb(space, frame) + def exc_clear(space): """Clear global information on the current exception. Subsequent calls to exc_info() will return (None,None,None) until another exception is diff --git a/pypy/module/test_lib_pypy/test_datetime.py b/pypy/module/test_lib_pypy/test_datetime.py --- a/pypy/module/test_lib_pypy/test_datetime.py +++ b/pypy/module/test_lib_pypy/test_datetime.py @@ -44,3 +44,9 @@ assert type(dt.microsecond) is int copy.copy(dt) + +def test_radd(): + class X(object): + def __radd__(self, other): + return "radd" + assert datetime.date(10, 10, 10) + X() == "radd" diff --git a/pypy/module/thread/test/test_ll_thread.py b/pypy/module/thread/test/test_ll_thread.py --- a/pypy/module/thread/test/test_ll_thread.py +++ b/pypy/module/thread/test/test_ll_thread.py @@ -66,7 +66,6 @@ def test_gc_locking(self): import time from pypy.rlib.objectmodel import invoke_around_extcall - from pypy.rlib.objectmodel import we_are_translated from pypy.rlib.debug import ll_assert class State: @@ -129,8 +128,6 @@ state.finished = 0 # the next line installs before_extcall() and after_extcall() # to be called automatically around external function calls. - # When not translated it does not work around time.sleep(), - # so we have to call them manually for this test. invoke_around_extcall(before_extcall, after_extcall) g(10, 1) @@ -142,13 +139,9 @@ willing_to_wait_more -= 1 done = len(state.answers) == expected - if not we_are_translated(): before_extcall() time.sleep(0.01) - if not we_are_translated(): after_extcall() - if not we_are_translated(): before_extcall() time.sleep(0.1) - if not we_are_translated(): after_extcall() return len(state.answers) @@ -160,12 +153,11 @@ answers = fn() assert answers == expected -class TestRunDirectly(AbstractThreadTests): - def getcompiled(self, f, argtypes): - return f - - def test_start_new_thread(self): - py.test.skip("deadlocks occasionally -- why???") +#class TestRunDirectly(AbstractThreadTests): +# def getcompiled(self, f, argtypes): +# return f +# These are disabled because they crash occasionally for bad reasons +# related to the fact that ll2ctypes is not at all thread-safe class TestUsingBoehm(AbstractThreadTests): gcpolicy = 'boehm' diff --git a/pypy/objspace/descroperation.py b/pypy/objspace/descroperation.py --- a/pypy/objspace/descroperation.py +++ b/pypy/objspace/descroperation.py @@ -43,6 +43,13 @@ return w_eq type_eq._annspecialcase_ = 'specialize:memo' +def list_iter(space): + "Utility that returns the app-level descriptor list.__iter__." + w_src, w_iter = space.lookup_in_type_where(space.w_list, + '__iter__') + return w_iter +list_iter._annspecialcase_ = 'specialize:memo' + def raiseattrerror(space, w_obj, name, w_descr=None): w_type = space.type(w_obj) typename = w_type.getname(space) diff --git a/pypy/objspace/fake/objspace.py b/pypy/objspace/fake/objspace.py --- a/pypy/objspace/fake/objspace.py +++ b/pypy/objspace/fake/objspace.py @@ -86,6 +86,7 @@ return s_None def specialize_call(self, hop): + hop.exception_cannot_occur() return hop.inputconst(lltype.Void, None) # ____________________________________________________________ diff --git a/pypy/objspace/std/celldict.py b/pypy/objspace/std/celldict.py --- a/pypy/objspace/std/celldict.py +++ b/pypy/objspace/std/celldict.py @@ -163,7 +163,8 @@ class ModuleDictIteratorImplementation(IteratorImplementation): def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) + IteratorImplementation.__init__( + self, space, strategy, dictimplementation) dict_w = strategy.unerase(dictimplementation.dstorage) self.iterator = dict_w.iteritems() diff --git a/pypy/objspace/std/dictmultiobject.py b/pypy/objspace/std/dictmultiobject.py --- a/pypy/objspace/std/dictmultiobject.py +++ b/pypy/objspace/std/dictmultiobject.py @@ -247,7 +247,7 @@ return 0 def iter(self, w_dict): - return EmptyIteratorImplementation(self.space, w_dict) + return EmptyIteratorImplementation(self.space, self, w_dict) def clear(self, w_dict): return @@ -263,8 +263,9 @@ # Iterator Implementation base classes class IteratorImplementation(object): - def __init__(self, space, implementation): + def __init__(self, space, strategy, implementation): self.space = space + self.strategy = strategy self.dictimplementation = implementation self.len = implementation.length() self.pos = 0 @@ -280,7 +281,20 @@ if self.pos < self.len: result = self.next_entry() self.pos += 1 - return result + if self.strategy is self.dictimplementation.strategy: + return result # common case + else: + # waaa, obscure case: the strategy changed, but not the + # length of the dict. The (key, value) pair in 'result' + # might be out-of-date. We try to explicitly look up + # the key in the dict. + w_key = result[0] + w_value = self.dictimplementation.getitem(w_key) + if w_value is None: + self.len = -1 # Make this error state sticky + raise OperationError(self.space.w_RuntimeError, + self.space.wrap("dictionary changed during iteration")) + return (w_key, w_value) # no more entries self.dictimplementation = None return None, None @@ -489,7 +503,7 @@ _mixin_ = True def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) + IteratorImplementation.__init__(self, space, strategy, dictimplementation) self.iterator = strategy.unerase(dictimplementation.dstorage).iteritems() def next_entry(self): @@ -503,7 +517,7 @@ _mixin_ = True def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) + IteratorImplementation.__init__(self, space, strategy, dictimplementation) self.iterator = strategy.unerase(dictimplementation.dstorage).iteritems() def next_entry(self): diff --git a/pypy/objspace/std/dictproxyobject.py b/pypy/objspace/std/dictproxyobject.py --- a/pypy/objspace/std/dictproxyobject.py +++ b/pypy/objspace/std/dictproxyobject.py @@ -20,7 +20,17 @@ def getitem(self, w_dict, w_key): space = self.space w_lookup_type = space.type(w_key) - if space.is_w(w_lookup_type, space.w_str): + if (space.is_w(w_lookup_type, space.w_str) or # Most common path first + space.abstract_issubclass_w(w_lookup_type, space.w_str)): + return self.getitem_str(w_dict, space.str_w(w_key)) + elif space.abstract_issubclass_w(w_lookup_type, space.w_unicode): + try: + w_key = space.str(w_key) + except OperationError, e: + if not e.match(space, space.w_UnicodeEncodeError): + raise + # non-ascii unicode is never equal to a byte string + return None return self.getitem_str(w_dict, space.str_w(w_key)) else: return None @@ -98,7 +108,8 @@ class DictProxyIteratorImplementation(IteratorImplementation): def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) + IteratorImplementation.__init__( + self, space, strategy, dictimplementation) w_type = strategy.unerase(dictimplementation.dstorage) self.iterator = w_type.dict_w.iteritems() diff --git a/pypy/objspace/std/mapdict.py b/pypy/objspace/std/mapdict.py --- a/pypy/objspace/std/mapdict.py +++ b/pypy/objspace/std/mapdict.py @@ -703,7 +703,8 @@ class MapDictIteratorImplementation(IteratorImplementation): def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) + IteratorImplementation.__init__( + self, space, strategy, dictimplementation) w_obj = strategy.unerase(dictimplementation.dstorage) self.w_obj = w_obj self.orig_map = self.curr_map = w_obj._get_mapdict_map() diff --git a/pypy/objspace/std/objspace.py b/pypy/objspace/std/objspace.py --- a/pypy/objspace/std/objspace.py +++ b/pypy/objspace/std/objspace.py @@ -439,6 +439,8 @@ t = w_obj.getitems() elif isinstance(w_obj, W_AbstractTupleObject): t = w_obj.getitems_copy() + elif isinstance(w_obj, W_ListObject) and self._uses_list_iter(w_obj): + t = w_obj.getitems() else: return ObjSpace.unpackiterable(self, w_obj, expected_length) if expected_length != -1 and len(t) != expected_length: @@ -456,6 +458,8 @@ return w_obj.listview_str() if isinstance(w_obj, W_StringObject): return w_obj.listview_str() + if isinstance(w_obj, W_ListObject) and self._uses_list_iter(w_obj): + return w_obj.getitems_str() return None def listview_int(self, w_obj): @@ -465,8 +469,14 @@ return w_obj.listview_int() if type(w_obj) is W_SetObject or type(w_obj) is W_FrozensetObject: return w_obj.listview_int() + if isinstance(w_obj, W_ListObject) and self._uses_list_iter(w_obj): + return w_obj.getitems_int() return None + def _uses_list_iter(self, w_obj): + from pypy.objspace.descroperation import list_iter + return self.lookup(w_obj, '__iter__') is list_iter(self) + def sliceindices(self, w_slice, w_length): if isinstance(w_slice, W_SliceObject): a, b, c = w_slice.indices3(self, self.int_w(w_length)) diff --git a/pypy/objspace/std/setobject.py b/pypy/objspace/std/setobject.py --- a/pypy/objspace/std/setobject.py +++ b/pypy/objspace/std/setobject.py @@ -359,7 +359,7 @@ w_set.sstorage = w_other.get_storage_copy() def iter(self, w_set): - return EmptyIteratorImplementation(self.space, w_set) + return EmptyIteratorImplementation(self.space, self, w_set) def popitem(self, w_set): raise OperationError(self.space.w_KeyError, @@ -784,8 +784,9 @@ d_obj[w_item] = None class IteratorImplementation(object): - def __init__(self, space, implementation): + def __init__(self, space, strategy, implementation): self.space = space + self.strategy = strategy self.setimplementation = implementation self.len = implementation.length() self.pos = 0 @@ -801,7 +802,17 @@ if self.pos < self.len: result = self.next_entry() self.pos += 1 - return result + if self.strategy is self.setimplementation.strategy: + return result # common case + else: + # waaa, obscure case: the strategy changed, but not the + # length of the set. The 'result' might be out-of-date. + # We try to explicitly look it up in the set. + if not self.setimplementation.has_key(result): + self.len = -1 # Make this error state sticky + raise OperationError(self.space.w_RuntimeError, + self.space.wrap("dictionary changed during iteration")) + return result # no more entries self.setimplementation = None return None @@ -823,7 +834,7 @@ class StringIteratorImplementation(IteratorImplementation): def __init__(self, space, strategy, w_set): - IteratorImplementation.__init__(self, space, w_set) + IteratorImplementation.__init__(self, space, strategy, w_set) d = strategy.unerase(w_set.sstorage) self.iterator = d.iterkeys() @@ -835,9 +846,9 @@ class IntegerIteratorImplementation(IteratorImplementation): #XXX same implementation in dictmultiobject on dictstrategy-branch - def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) - d = strategy.unerase(dictimplementation.sstorage) + def __init__(self, space, strategy, w_set): + IteratorImplementation.__init__(self, space, strategy, w_set) + d = strategy.unerase(w_set.sstorage) self.iterator = d.iterkeys() def next_entry(self): @@ -848,9 +859,9 @@ return None class RDictIteratorImplementation(IteratorImplementation): - def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) - d = strategy.unerase(dictimplementation.sstorage) + def __init__(self, space, strategy, w_set): + IteratorImplementation.__init__(self, space, strategy, w_set) + d = strategy.unerase(w_set.sstorage) self.iterator = d.iterkeys() def next_entry(self): diff --git a/pypy/objspace/std/test/test_dictmultiobject.py b/pypy/objspace/std/test/test_dictmultiobject.py --- a/pypy/objspace/std/test/test_dictmultiobject.py +++ b/pypy/objspace/std/test/test_dictmultiobject.py @@ -804,6 +804,33 @@ assert "IntDictStrategy" in self.get_strategy(d) assert d[1L] == "hi" + def test_iter_dict_length_change(self): + d = {1: 2, 3: 4, 5: 6} + it = d.iteritems() + d[7] = 8 + # 'd' is now length 4 + raises(RuntimeError, it.next) + + def test_iter_dict_strategy_only_change_1(self): + d = {1: 2, 3: 4, 5: 6} + it = d.iteritems() + class Foo(object): + def __eq__(self, other): + return False + assert d.get(Foo()) is None # this changes the strategy of 'd' + lst = list(it) # but iterating still works + assert sorted(lst) == [(1, 2), (3, 4), (5, 6)] + + def test_iter_dict_strategy_only_change_2(self): + d = {1: 2, 3: 4, 5: 6} + it = d.iteritems() + d['foo'] = 'bar' + del d[1] + # 'd' is still length 3, but its strategy changed. we are + # getting a RuntimeError because iterating over the old storage + # gives us (1, 2), but 1 is not in the dict any longer. + raises(RuntimeError, list, it) + class FakeString(str): hash_count = 0 diff --git a/pypy/objspace/std/test/test_dictproxy.py b/pypy/objspace/std/test/test_dictproxy.py --- a/pypy/objspace/std/test/test_dictproxy.py +++ b/pypy/objspace/std/test/test_dictproxy.py @@ -25,6 +25,16 @@ key, value = NotEmpty.__dict__.popitem() assert (key == 'a' and value == 1) or (key == 'b' and value == 4) + def test_dictproxy_getitem(self): + class NotEmpty(object): + a = 1 + assert 'a' in NotEmpty.__dict__ + class substr(str): pass + assert substr('a') in NotEmpty.__dict__ + assert u'a' in NotEmpty.__dict__ + assert NotEmpty.__dict__[u'a'] == 1 + assert u'\xe9' not in NotEmpty.__dict__ + def test_dictproxyeq(self): class a(object): pass diff --git a/pypy/objspace/std/test/test_listobject.py b/pypy/objspace/std/test/test_listobject.py --- a/pypy/objspace/std/test/test_listobject.py +++ b/pypy/objspace/std/test/test_listobject.py @@ -1186,14 +1186,23 @@ # of dicts, because the OrderedDict in the stdlib relies on this. # we extend the use case to lists and sets, i.e. all types that have # strategies, to avoid surprizes depending on the strategy. - for base, arg in [(list, []), (list, [5]), (list, ['x']), - (set, []), (set, [5]), (set, ['x']), - (dict, []), (dict, [(5,6)]), (dict, [('x',7)])]: + class X: pass + for base, arg in [ + (list, []), (list, [5]), (list, ['x']), (list, [X]), + (set, []), (set, [5]), (set, ['x']), (set, [X]), + (dict, []), (dict, [(5,6)]), (dict, [('x',7)]), (dict, [(X,8)]), + ]: print base, arg class SubClass(base): def __iter__(self): return iter("foobar") assert list(SubClass(arg)) == ['f', 'o', 'o', 'b', 'a', 'r'] + class Sub2(base): + pass + assert list(Sub2(arg)) == list(base(arg)) + s = set() + s.update(Sub2(arg)) + assert s == set(base(arg)) class AppTestForRangeLists(AppTestW_ListObject): diff --git a/pypy/objspace/std/test/test_setobject.py b/pypy/objspace/std/test/test_setobject.py --- a/pypy/objspace/std/test/test_setobject.py +++ b/pypy/objspace/std/test/test_setobject.py @@ -907,3 +907,30 @@ return [5, 3, 4][i] s = set([10,3,2]).intersection(Obj()) assert list(s) == [3] + + def test_iter_set_length_change(self): + s = set([1, 3, 5]) + it = iter(s) + s.add(7) + # 's' is now length 4 + raises(RuntimeError, it.next) + + def test_iter_set_strategy_only_change_1(self): + s = set([1, 3, 5]) + it = iter(s) + class Foo(object): + def __eq__(self, other): + return False + assert Foo() not in s # this changes the strategy of 'd' + lst = list(s) # but iterating still works + assert sorted(lst) == [1, 3, 5] + + def test_iter_set_strategy_only_change_2(self): + s = set([1, 3, 5]) + it = iter(s) + s.add('foo') + s.remove(1) + # 's' is still length 3, but its strategy changed. we are + # getting a RuntimeError because iterating over the old storage + # gives us 1, but 1 is not in the set any longer. + raises(RuntimeError, list, it) diff --git a/pypy/rlib/jit.py b/pypy/rlib/jit.py --- a/pypy/rlib/jit.py +++ b/pypy/rlib/jit.py @@ -382,7 +382,7 @@ pass def specialize_call(self, hop): - pass + hop.exception_cannot_occur() vref_None = non_virtual_ref(None) diff --git a/pypy/rlib/jit_hooks.py b/pypy/rlib/jit_hooks.py --- a/pypy/rlib/jit_hooks.py +++ b/pypy/rlib/jit_hooks.py @@ -22,6 +22,7 @@ c_name = hop.inputconst(lltype.Void, 'access_helper') args_v = [hop.inputarg(arg, arg=i) for i, arg in enumerate(hop.args_r)] + hop.exception_cannot_occur() return hop.genop('jit_marker', [c_name, c_func] + args_v, resulttype=hop.r_result) return helper diff --git a/pypy/rlib/longlong2float.py b/pypy/rlib/longlong2float.py --- a/pypy/rlib/longlong2float.py +++ b/pypy/rlib/longlong2float.py @@ -21,7 +21,7 @@ FLOAT_ARRAY_PTR = lltype.Ptr(lltype.Array(rffi.FLOAT)) # these definitions are used only in tests, when not translated -def longlong2float_emulator(llval): +def longlong2float(llval): with lltype.scoped_alloc(DOUBLE_ARRAY_PTR.TO, 1) as d_array: ll_array = rffi.cast(LONGLONG_ARRAY_PTR, d_array) ll_array[0] = llval @@ -51,12 +51,6 @@ eci = ExternalCompilationInfo(includes=['string.h', 'assert.h'], post_include_bits=[""" -static double pypy__longlong2float(long long x) { - double dd; - assert(sizeof(double) == 8 && sizeof(long long) == 8); - memcpy(&dd, &x, 8); - return dd; -} static float pypy__uint2singlefloat(unsigned int x) { float ff; assert(sizeof(float) == 4 && sizeof(unsigned int) == 4); @@ -71,12 +65,6 @@ } """]) -longlong2float = rffi.llexternal( - "pypy__longlong2float", [rffi.LONGLONG], rffi.DOUBLE, - _callable=longlong2float_emulator, compilation_info=eci, - _nowrapper=True, elidable_function=True, sandboxsafe=True, - oo_primitive="pypy__longlong2float") - uint2singlefloat = rffi.llexternal( "pypy__uint2singlefloat", [rffi.UINT], rffi.FLOAT, _callable=uint2singlefloat_emulator, compilation_info=eci, @@ -99,4 +87,17 @@ def specialize_call(self, hop): [v_float] = hop.inputargs(lltype.Float) - return hop.genop("convert_float_bytes_to_longlong", [v_float], resulttype=hop.r_result) + hop.exception_cannot_occur() + return hop.genop("convert_float_bytes_to_longlong", [v_float], resulttype=lltype.SignedLongLong) + +class LongLong2FloatEntry(ExtRegistryEntry): + _about_ = longlong2float + + def compute_result_annotation(self, s_longlong): + assert annmodel.SomeInteger(knowntype=r_int64).contains(s_longlong) + return annmodel.SomeFloat() + + def specialize_call(self, hop): + [v_longlong] = hop.inputargs(lltype.SignedLongLong) + hop.exception_cannot_occur() + return hop.genop("convert_longlong_bytes_to_float", [v_longlong], resulttype=lltype.Float) diff --git a/pypy/rlib/objectmodel.py b/pypy/rlib/objectmodel.py --- a/pypy/rlib/objectmodel.py +++ b/pypy/rlib/objectmodel.py @@ -215,6 +215,7 @@ def specialize_call(self, hop): from pypy.rpython.lltypesystem import lltype + hop.exception_cannot_occur() return hop.inputconst(lltype.Bool, hop.s_result.const) # ____________________________________________________________ @@ -397,6 +398,7 @@ r_obj, = hop.args_r v_obj, = hop.inputargs(r_obj) ll_fn = r_obj.get_ll_hash_function() + hop.exception_is_here() return hop.gendirectcall(ll_fn, v_obj) class Entry(ExtRegistryEntry): @@ -419,6 +421,7 @@ from pypy.rpython.error import TyperError raise TyperError("compute_identity_hash() cannot be applied to" " %r" % (vobj.concretetype,)) + hop.exception_cannot_occur() return hop.genop('gc_identityhash', [vobj], resulttype=lltype.Signed) class Entry(ExtRegistryEntry): @@ -441,6 +444,7 @@ from pypy.rpython.error import TyperError raise TyperError("compute_unique_id() cannot be applied to" " %r" % (vobj.concretetype,)) + hop.exception_cannot_occur() return hop.genop('gc_id', [vobj], resulttype=lltype.Signed) class Entry(ExtRegistryEntry): @@ -452,6 +456,7 @@ def specialize_call(self, hop): vobj, = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() if hop.rtyper.type_system.name == 'lltypesystem': from pypy.rpython.lltypesystem import lltype if isinstance(vobj.concretetype, lltype.Ptr): diff --git a/pypy/rlib/rbigint.py b/pypy/rlib/rbigint.py --- a/pypy/rlib/rbigint.py +++ b/pypy/rlib/rbigint.py @@ -85,7 +85,7 @@ s_DIGIT = self.bookkeeper.valueoftype(type(NULLDIGIT)) assert s_DIGIT.contains(s_list.listdef.listitem.s_value) def specialize_call(self, hop): - pass + hop.exception_cannot_occur() class rbigint(object): diff --git a/pypy/rlib/rerased.py b/pypy/rlib/rerased.py --- a/pypy/rlib/rerased.py +++ b/pypy/rlib/rerased.py @@ -100,6 +100,7 @@ def specialize_call(self, hop): bk = hop.rtyper.annotator.bookkeeper s_obj = identity.get_input_annotation(bk) + hop.exception_cannot_occur() return hop.r_result.rtype_erase(hop, s_obj) class Entry(ExtRegistryEntry): @@ -110,6 +111,7 @@ return identity.leave_tunnel(self.bookkeeper) def specialize_call(self, hop): + hop.exception_cannot_occur() if hop.r_result.lowleveltype is lltype.Void: return hop.inputconst(lltype.Void, None) [v] = hop.inputargs(hop.args_r[0]) @@ -214,6 +216,7 @@ return hop.genop('cast_opaque_ptr', [v], resulttype=hop.r_result) def rtype_unerase_int(self, hop, v): + hop.exception_cannot_occur() return hop.gendirectcall(ll_unerase_int, v) def rtype_erase_int(self, hop): @@ -264,6 +267,7 @@ def rtype_unerase_int(self, hop, v): c_one = hop.inputconst(lltype.Signed, 1) + hop.exception_cannot_occur() v2 = hop.genop('oounbox_int', [v], resulttype=hop.r_result) return hop.genop('int_rshift', [v2, c_one], resulttype=lltype.Signed) diff --git a/pypy/rlib/rgc.py b/pypy/rlib/rgc.py --- a/pypy/rlib/rgc.py +++ b/pypy/rlib/rgc.py @@ -382,6 +382,7 @@ def compute_result_annotation(self): return s_list_of_gcrefs() def specialize_call(self, hop): + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_roots', [], resulttype = hop.r_result) class Entry(ExtRegistryEntry): @@ -392,6 +393,7 @@ return s_list_of_gcrefs() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_referents', vlist, resulttype = hop.r_result) @@ -402,6 +404,7 @@ return annmodel.SomeInteger() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_memory_usage', vlist, resulttype = hop.r_result) @@ -412,6 +415,7 @@ return annmodel.SomeInteger() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_type_index', vlist, resulttype = hop.r_result) @@ -430,6 +434,7 @@ return annmodel.SomeBool() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_is_rpy_instance', vlist, resulttype = hop.r_result) @@ -449,6 +454,7 @@ classrepr = getclassrepr(hop.rtyper, classdef) vtable = classrepr.getvtable() assert lltype.typeOf(vtable) == rclass.CLASSTYPE + hop.exception_cannot_occur() return Constant(vtable, concretetype=rclass.CLASSTYPE) class Entry(ExtRegistryEntry): diff --git a/pypy/rlib/rsre/rsre_re.py b/pypy/rlib/rsre/rsre_re.py --- a/pypy/rlib/rsre/rsre_re.py +++ b/pypy/rlib/rsre/rsre_re.py @@ -172,8 +172,9 @@ self._ctx = ctx def span(self, groupnum=0): - if not isinstance(groupnum, (int, long)): - groupnum = self.re.groupindex[groupnum] +# if not isinstance(groupnum, (int, long)): +# groupnum = self.re.groupindex[groupnum] + return self._ctx.span(groupnum) def start(self, groupnum=0): @@ -182,19 +183,25 @@ def end(self, groupnum=0): return self.span(groupnum)[1] - def group(self, *groups): - groups = groups or (0,) - result = [] - for group in groups: - frm, to = self.span(group) - if 0 <= frm <= to: - result.append(self._ctx._string[frm:to]) - else: - result.append(None) - if len(result) > 1: - return tuple(result) + def group(self, group=0): + frm, to = self.span(group) + if 0 <= frm <= to: + return self._ctx._string[frm:to] else: - return result[0] + return None + +# def group(self, *groups): +# groups = groups or (0,) +# result = [] +# for group in groups: +# frm, to = self.span(group) +# if 0 <= frm <= to: +# result.append(self._ctx._string[frm:to]) +# else: +# result.append(None) +# if len(result) > 1: +# return tuple(result) + def groups(self, default=None): fmarks = self._ctx.flatten_marks() diff --git a/pypy/rlib/rsre/test/test_re.py b/pypy/rlib/rsre/test/test_re.py --- a/pypy/rlib/rsre/test/test_re.py +++ b/pypy/rlib/rsre/test/test_re.py @@ -204,7 +204,7 @@ assert re.match('(a)', 'a').groups() == ('a',) assert re.match(r'(a)', 'a').group(0) == 'a' assert re.match(r'(a)', 'a').group(1) == 'a' - assert re.match(r'(a)', 'a').group(1, 1) == ('a', 'a') + #assert re.match(r'(a)', 'a').group(1, 1) == ('a', 'a') pat = re.compile('((a)|(b))(c)?') assert pat.match('a').groups() == ('a', 'a', None, None) @@ -218,13 +218,13 @@ assert m.group(0) == 'a' assert m.group(0) == 'a' assert m.group(1) == 'a' - assert m.group(1, 1) == ('a', 'a') + #assert m.group(1, 1) == ('a', 'a') pat = re.compile('(?:(?Pa)|(?Pb))(?Pc)?') - assert pat.match('a').group(1, 2, 3) == ('a', None, None) - assert pat.match('b').group('a1', 'b2', 'c3') == ( - (None, 'b', None)) - assert pat.match('ac').group(1, 'b2', 3) == ('a', None, 'c') + #assert pat.match('a').group(1, 2, 3) == ('a', None, None) + #assert pat.match('b').group('a1', 'b2', 'c3') == ( + # (None, 'b', None)) + #assert pat.match('ac').group(1, 'b2', 3) == ('a', None, 'c') def test_bug_923(self): # Issue923: grouping inside optional lookahead problem diff --git a/pypy/rlib/rsre/test/test_zinterp.py b/pypy/rlib/rsre/test/test_zinterp.py --- a/pypy/rlib/rsre/test/test_zinterp.py +++ b/pypy/rlib/rsre/test/test_zinterp.py @@ -1,7 +1,8 @@ # minimal test: just checks that (parts of) rsre can be translated -from pypy.rpython.test.test_llinterp import gengraph +from pypy.rpython.test.test_llinterp import gengraph, interpret from pypy.rlib.rsre import rsre_core +from pypy.rlib.rsre.rsre_re import compile def main(n): assert n >= 0 @@ -19,3 +20,18 @@ def test_gengraph(): t, typer, graph = gengraph(main, [int]) + +m = compile("(a|b)aaaaa") + +def test_match(): + def f(i): + if i: + s = "aaaaaa" + else: + s = "caaaaa" + g = m.match(s) + if g is None: + return 3 + return int("aaaaaa" == g.group(0)) + assert interpret(f, [3]) == 1 + assert interpret(f, [0]) == 3 diff --git a/pypy/rlib/rstring.py b/pypy/rlib/rstring.py --- a/pypy/rlib/rstring.py +++ b/pypy/rlib/rstring.py @@ -245,5 +245,5 @@ raise ValueError("Value is not no_nul") def specialize_call(self, hop): - pass + hop.exception_cannot_occur() diff --git a/pypy/rlib/test/test_longlong2float.py b/pypy/rlib/test/test_longlong2float.py --- a/pypy/rlib/test/test_longlong2float.py +++ b/pypy/rlib/test/test_longlong2float.py @@ -2,6 +2,7 @@ from pypy.rlib.longlong2float import longlong2float, float2longlong from pypy.rlib.longlong2float import uint2singlefloat, singlefloat2uint from pypy.rlib.rarithmetic import r_singlefloat +from pypy.rpython.test.test_llinterp import interpret def fn(f1): @@ -31,6 +32,18 @@ res = fn2(x) assert repr(res) == repr(x) +def test_interpreted(): + def f(f1): + try: + ll = float2longlong(f1) + return longlong2float(ll) + except Exception: + return 500 + + for x in enum_floats(): + res = interpret(f, [x]) + assert repr(res) == repr(x) + # ____________________________________________________________ def fnsingle(f1): diff --git a/pypy/rpython/annlowlevel.py b/pypy/rpython/annlowlevel.py --- a/pypy/rpython/annlowlevel.py +++ b/pypy/rpython/annlowlevel.py @@ -543,11 +543,11 @@ else: assert False + hop.exception_cannot_occur() if isinstance(hop.args_r[1], rpbc.NoneFrozenPBCRepr): return hop.inputconst(PTR, null) v_arg = hop.inputarg(hop.args_r[1], arg=1) assert isinstance(v_arg.concretetype, T) - hop.exception_cannot_occur() return hop.genop(opname, [v_arg], resulttype = PTR) diff --git a/pypy/rpython/controllerentry.py b/pypy/rpython/controllerentry.py --- a/pypy/rpython/controllerentry.py +++ b/pypy/rpython/controllerentry.py @@ -201,6 +201,7 @@ def specialize_call(self, hop): from pypy.rpython.lltypesystem import lltype assert hop.s_result.is_constant() + hop.exception_cannot_occur() return hop.inputconst(lltype.Bool, hop.s_result.const) # ____________________________________________________________ diff --git a/pypy/rpython/lltypesystem/lloperation.py b/pypy/rpython/lltypesystem/lloperation.py --- a/pypy/rpython/lltypesystem/lloperation.py +++ b/pypy/rpython/lltypesystem/lloperation.py @@ -130,6 +130,7 @@ def specialize_call(self, hop): from pypy.rpython.lltypesystem import lltype + hop.exception_cannot_occur() return hop.inputconst(lltype.Void, None) def enum_ops_without_sideeffects(raising_is_ok=False): @@ -350,6 +351,7 @@ 'truncate_longlong_to_int':LLOp(canfold=True), 'force_cast': LLOp(sideeffects=False), # only for rffi.cast() 'convert_float_bytes_to_longlong': LLOp(canfold=True), + 'convert_longlong_bytes_to_float': LLOp(canfold=True), # __________ pointer operations __________ diff --git a/pypy/rpython/lltypesystem/opimpl.py b/pypy/rpython/lltypesystem/opimpl.py --- a/pypy/rpython/lltypesystem/opimpl.py +++ b/pypy/rpython/lltypesystem/opimpl.py @@ -431,6 +431,10 @@ from pypy.rlib.longlong2float import float2longlong return float2longlong(a) +def op_convert_longlong_bytes_to_float(a): + from pypy.rlib.longlong2float import longlong2float + return longlong2float(a) + def op_unichar_eq(x, y): assert isinstance(x, unicode) and len(x) == 1 diff --git a/pypy/rpython/lltypesystem/rbuiltin.py b/pypy/rpython/lltypesystem/rbuiltin.py --- a/pypy/rpython/lltypesystem/rbuiltin.py +++ b/pypy/rpython/lltypesystem/rbuiltin.py @@ -9,6 +9,7 @@ from pypy.rpython.rbool import bool_repr def rtype_builtin_isinstance(hop): + hop.exception_cannot_occur() if hop.s_result.is_constant(): return hop.inputconst(lltype.Bool, hop.s_result.const) if hop.args_r[0] == pyobj_repr or hop.args_r[1] == pyobj_repr: @@ -33,6 +34,7 @@ return my_instantiate() def rtype_instantiate(hop): + hop.exception_cannot_occur() s_class = hop.args_s[0] assert isinstance(s_class, annmodel.SomePBC) if len(s_class.descriptions) != 1: @@ -46,6 +48,7 @@ return rclass.rtype_new_instance(hop.rtyper, classdef, hop.llops) def rtype_builtin_hasattr(hop): + hop.exception_cannot_occur() if hop.s_result.is_constant(): return hop.inputconst(lltype.Bool, hop.s_result.const) if hop.args_r[0] == pyobj_repr: @@ -56,6 +59,7 @@ raise TyperError("hasattr is only suported on a constant or on PyObject") def rtype_builtin___import__(hop): + xxx # should not be used any more args_v = hop.inputargs(*[pyobj_repr for ign in hop.args_r]) c = hop.inputconst(pyobj_repr, __import__) return hop.genop('simple_call', [c] + args_v, resulttype = pyobj_repr) diff --git a/pypy/rpython/lltypesystem/rclass.py b/pypy/rpython/lltypesystem/rclass.py --- a/pypy/rpython/lltypesystem/rclass.py +++ b/pypy/rpython/lltypesystem/rclass.py @@ -746,4 +746,5 @@ assert isinstance(TYPE, GcStruct) assert lltype._castdepth(TYPE, OBJECT) > 0 hop.rtyper.set_type_for_typeptr(vtable, TYPE) + hop.exception_cannot_occur() return hop.inputconst(lltype.Void, None) diff --git a/pypy/rpython/lltypesystem/rstr.py b/pypy/rpython/lltypesystem/rstr.py --- a/pypy/rpython/lltypesystem/rstr.py +++ b/pypy/rpython/lltypesystem/rstr.py @@ -765,7 +765,11 @@ def _ll_stringslice(s1, start, stop): lgt = stop - start assert start >= 0 - assert lgt >= 0 + # If start > stop, return a empty string. This can happen if the start + # is greater than the length of the string. Use < instead of <= to avoid + # creating another path for the JIT when start == stop. + if lgt < 0: + return s1.empty() newstr = s1.malloc(lgt) s1.copy_contents(s1, newstr, start, 0, lgt) return newstr diff --git a/pypy/rpython/lltypesystem/rtuple.py b/pypy/rpython/lltypesystem/rtuple.py --- a/pypy/rpython/lltypesystem/rtuple.py +++ b/pypy/rpython/lltypesystem/rtuple.py @@ -55,6 +55,7 @@ vtup = hop.inputarg(self, 0) LIST = hop.r_result.lowleveltype.TO cno = inputconst(Signed, nitems) + hop.exception_is_here() vlist = hop.gendirectcall(LIST.ll_newlist, cno) v_func = hop.inputconst(Void, rlist.dum_nocheck) for index in range(nitems): diff --git a/pypy/rpython/memory/gc/minimark.py b/pypy/rpython/memory/gc/minimark.py --- a/pypy/rpython/memory/gc/minimark.py +++ b/pypy/rpython/memory/gc/minimark.py @@ -1426,23 +1426,25 @@ self._visit_young_rawmalloced_object(obj) return # - # If 'obj' was already forwarded, change it to its forwarding address. - if self.is_forwarded(obj): + size_gc_header = self.gcheaderbuilder.size_gc_header + if self.header(obj).tid & GCFLAG_HAS_SHADOW == 0: + # + # Common case: 'obj' was not already forwarded (otherwise + # tid == -42, containing all flags), and it doesn't have the + # HAS_SHADOW flag either. We must move it out of the nursery, + # into a new nonmovable location. + totalsize = size_gc_header + self.get_size(obj) + newhdr = self._malloc_out_of_nursery(totalsize) + # + elif self.is_forwarded(obj): + # + # 'obj' was already forwarded. Change the original reference + # to point to its forwarding address, and we're done. root.address[0] = self.get_forwarding_address(obj) return - # - # First visit to 'obj': we must move it out of the nursery. - size_gc_header = self.gcheaderbuilder.size_gc_header - size = self.get_size(obj) - totalsize = size_gc_header + size - # - if self.header(obj).tid & GCFLAG_HAS_SHADOW == 0: - # - # Common case: allocate a new nonmovable location for it. - newhdr = self._malloc_out_of_nursery(totalsize) # else: - # The object has already a shadow. + # First visit to an object that has already a shadow. newobj = self.nursery_objects_shadows.get(obj) ll_assert(newobj != NULL, "GCFLAG_HAS_SHADOW but no shadow found") newhdr = newobj - size_gc_header @@ -1450,6 +1452,8 @@ # Remove the flag GCFLAG_HAS_SHADOW, so that it doesn't get # copied to the shadow itself. self.header(obj).tid &= ~GCFLAG_HAS_SHADOW + # + totalsize = size_gc_header + self.get_size(obj) # # Copy it. Note that references to other objects in the # nursery are kept unchanged in this step. diff --git a/pypy/rpython/module/r_os_stat.py b/pypy/rpython/module/r_os_stat.py --- a/pypy/rpython/module/r_os_stat.py +++ b/pypy/rpython/module/r_os_stat.py @@ -65,4 +65,5 @@ r_StatResult = hop.rtyper.getrepr(ll_os_stat.s_StatResult) [v_result] = hop.inputargs(r_StatResult.r_tuple) # no-op conversion from r_StatResult.r_tuple to r_StatResult + hop.exception_cannot_occur() return v_result diff --git a/pypy/rpython/ootypesystem/ooregistry.py b/pypy/rpython/ootypesystem/ooregistry.py --- a/pypy/rpython/ootypesystem/ooregistry.py +++ b/pypy/rpython/ootypesystem/ooregistry.py @@ -22,6 +22,7 @@ annmodel.SomeOOInstance, annmodel.SomeString)) vlist = hop.inputargs(hop.args_r[0], ootype.Signed) + hop.exception_cannot_occur() return hop.genop('oostring', vlist, resulttype = ootype.String) class Entry_oounicode(ExtRegistryEntry): @@ -38,6 +39,7 @@ assert isinstance(hop.args_s[0], (annmodel.SomeUnicodeCodePoint, annmodel.SomeOOInstance)) vlist = hop.inputargs(hop.args_r[0], ootype.Signed) + hop.exception_cannot_occur() return hop.genop('oounicode', vlist, resulttype = ootype.Unicode) diff --git a/pypy/rpython/ootypesystem/rbuiltin.py b/pypy/rpython/ootypesystem/rbuiltin.py --- a/pypy/rpython/ootypesystem/rbuiltin.py +++ b/pypy/rpython/ootypesystem/rbuiltin.py @@ -7,12 +7,14 @@ from pypy.rpython.error import TyperError def rtype_new(hop): + hop.exception_cannot_occur() assert hop.args_s[0].is_constant() vlist = hop.inputargs(ootype.Void) return hop.genop('new', vlist, resulttype = hop.r_result.lowleveltype) def rtype_oonewarray(hop): + hop.exception_cannot_occur() assert hop.args_s[0].is_constant() vlist = hop.inputarg(ootype.Void, arg=0) vlength = hop.inputarg(ootype.Signed, arg=1) @@ -20,23 +22,27 @@ resulttype = hop.r_result.lowleveltype) def rtype_null(hop): + hop.exception_cannot_occur() assert hop.args_s[0].is_constant() TYPE = hop.args_s[0].const nullvalue = ootype.null(TYPE) return hop.inputconst(TYPE, nullvalue) def rtype_classof(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0], annmodel.SomeOOInstance) vlist = hop.inputargs(hop.args_r[0]) return hop.genop('classof', vlist, resulttype = ootype.Class) def rtype_subclassof(hop): + hop.exception_cannot_occur() vlist = hop.inputargs(rootype.ooclass_repr, rootype.ooclass_repr) return hop.genop('subclassof', vlist, resulttype = ootype.Bool) def rtype_instanceof(hop): + hop.exception_cannot_occur() INSTANCE = hop.args_v[1].value v_inst = hop.inputarg(hop.args_r[0], arg=0) c_cls = hop.inputconst(ootype.Void, INSTANCE) @@ -44,23 +50,27 @@ resulttype=ootype.Bool) def rtype_runtimenew(hop): + hop.exception_cannot_occur() vlist = hop.inputargs(rootype.ooclass_repr) return hop.genop('runtimenew', vlist, resulttype = hop.r_result.lowleveltype) def rtype_ooupcast(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0].const, ootype.Instance) assert isinstance(hop.args_s[1], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[1], arg=1) return hop.genop('ooupcast', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_oodowncast(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0].const, ootype.Instance) assert isinstance(hop.args_s[1], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[1], arg=1) return hop.genop('oodowncast', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_cast_to_object(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0], annmodel.SomeOOStaticMeth) or \ isinstance(hop.args_s[0], annmodel.SomeOOClass) or \ isinstance(hop.args_s[0].ootype, ootype.OOType) @@ -68,12 +78,14 @@ return hop.genop('cast_to_object', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_cast_from_object(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0].const, ootype.OOType) assert isinstance(hop.args_s[1], annmodel.SomeOOObject) v_inst = hop.inputarg(hop.args_r[1], arg=1) return hop.genop('cast_from_object', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_builtin_isinstance(hop): + hop.exception_cannot_occur() if hop.s_result.is_constant(): return hop.inputconst(ootype.Bool, hop.s_result.const) @@ -99,6 +111,7 @@ return ootype.subclassof(c1, class_) def rtype_instantiate(hop): + hop.exception_cannot_occur() if hop.args_s[0].is_constant(): ## INSTANCE = hop.s_result.rtyper_makerepr(hop.rtyper).lowleveltype ## v_instance = hop.inputconst(ootype.Void, INSTANCE) diff --git a/pypy/rpython/ootypesystem/rstr.py b/pypy/rpython/ootypesystem/rstr.py --- a/pypy/rpython/ootypesystem/rstr.py +++ b/pypy/rpython/ootypesystem/rstr.py @@ -222,6 +222,10 @@ length = s.ll_strlen() if stop > length: stop = length + # If start > stop, return a empty string. This can happen if the start + # is greater than the length of the string. + if start > stop: + start = stop return s.ll_substring(start, stop-start) def ll_stringslice_minusone(s): diff --git a/pypy/rpython/ootypesystem/rtuple.py b/pypy/rpython/ootypesystem/rtuple.py --- a/pypy/rpython/ootypesystem/rtuple.py +++ b/pypy/rpython/ootypesystem/rtuple.py @@ -39,6 +39,7 @@ RESULT = hop.r_result.lowleveltype c_resulttype = inputconst(ootype.Void, RESULT) c_length = inputconst(ootype.Signed, len(self.items_r)) + hop.exception_is_here() if isinstance(RESULT, ootype.Array): v_list = hop.genop('oonewarray', [c_resulttype, c_length], resulttype=RESULT) else: diff --git a/pypy/rpython/rbool.py b/pypy/rpython/rbool.py --- a/pypy/rpython/rbool.py +++ b/pypy/rpython/rbool.py @@ -34,6 +34,7 @@ def rtype_float(_, hop): vlist = hop.inputargs(Float) + hop.exception_cannot_occur() return vlist[0] # diff --git a/pypy/rpython/rbuiltin.py b/pypy/rpython/rbuiltin.py --- a/pypy/rpython/rbuiltin.py +++ b/pypy/rpython/rbuiltin.py @@ -111,25 +111,32 @@ raise TyperError("don't know about built-in function %r" % ( self.builtinfunc,)) + def _call(self, hop2, **kwds_i): + bltintyper = self.findbltintyper(hop2.rtyper) + hop2.llops._called_exception_is_here_or_cannot_occur = False + v_result = bltintyper(hop2, **kwds_i) + if not hop2.llops._called_exception_is_here_or_cannot_occur: + raise TyperError("missing hop.exception_cannot_occur() or " + "hop.exception_is_here() in %s" % bltintyper) + return v_result + def rtype_simple_call(self, hop): - bltintyper = self.findbltintyper(hop.rtyper) hop2 = hop.copy() hop2.r_s_popfirstarg() - return bltintyper(hop2) + return self._call(hop2) def rtype_call_args(self, hop): # calling a built-in function with keyword arguments: # mostly for rpython.objectmodel.hint() hop, kwds_i = call_args_expand(hop) - bltintyper = self.findbltintyper(hop.rtyper) hop2 = hop.copy() hop2.r_s_popfirstarg() hop2.r_s_popfirstarg() # the RPython-level keyword args are passed with an 'i_' prefix and # the corresponding value is an *index* in the hop2 arguments, # to be used with hop.inputarg(arg=..) - return bltintyper(hop2, **kwds_i) + return self._call(hop2, **kwds_i) class BuiltinMethodRepr(Repr): @@ -198,6 +205,7 @@ # ____________________________________________________________ def rtype_builtin_bool(hop): + # not called any more? assert hop.nb_args == 1 return hop.args_r[0].rtype_is_true(hop) @@ -241,6 +249,7 @@ def rtype_builtin_min(hop): v1, v2 = hop.inputargs(hop.r_result, hop.r_result) + hop.exception_cannot_occur() return hop.gendirectcall(ll_min, v1, v2) def ll_min(i1, i2): @@ -250,6 +259,7 @@ def rtype_builtin_max(hop): v1, v2 = hop.inputargs(hop.r_result, hop.r_result) + hop.exception_cannot_occur() return hop.gendirectcall(ll_max, v1, v2) def ll_max(i1, i2): @@ -264,6 +274,7 @@ pass def rtype_OSError__init__(hop): + hop.exception_cannot_occur() if hop.nb_args == 2: raise TyperError("OSError() should not be called with " "a single argument") @@ -274,6 +285,7 @@ r_self.setfield(v_self, 'errno', v_errno, hop.llops) def rtype_WindowsError__init__(hop): + hop.exception_cannot_occur() if hop.nb_args == 2: raise TyperError("WindowsError() should not be called with " "a single argument") @@ -442,6 +454,7 @@ assert hop.args_s[0].is_constant() TGT = hop.args_s[0].const v_type, v_value = hop.inputargs(lltype.Void, hop.args_r[1]) + hop.exception_cannot_occur() return gen_cast(hop.llops, TGT, v_value) _cast_to_Signed = { @@ -523,11 +536,13 @@ def rtype_identity_hash(hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_identityhash', vlist, resulttype=lltype.Signed) def rtype_runtime_type_info(hop): assert isinstance(hop.args_r[0], rptr.PtrRepr) vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('runtime_type_info', vlist, resulttype = hop.r_result.lowleveltype) @@ -558,6 +573,7 @@ def rtype_raw_malloc(hop): v_size, = hop.inputargs(lltype.Signed) + hop.exception_cannot_occur() return hop.genop('raw_malloc', [v_size], resulttype=llmemory.Address) def rtype_raw_malloc_usage(hop): @@ -586,6 +602,7 @@ if s_addr.is_null_address(): raise TyperError("raw_memclear(x, n) where x is the constant NULL") v_list = hop.inputargs(llmemory.Address, lltype.Signed) + hop.exception_cannot_occur() return hop.genop('raw_memclear', v_list) BUILTIN_TYPER[llmemory.raw_malloc] = rtype_raw_malloc @@ -596,6 +613,7 @@ def rtype_offsetof(hop): TYPE, field = hop.inputargs(lltype.Void, lltype.Void) + hop.exception_cannot_occur() return hop.inputconst(lltype.Signed, llmemory.offsetof(TYPE.value, field.value)) @@ -605,6 +623,7 @@ # non-gc objects def rtype_free_non_gc_object(hop): + hop.exception_cannot_occur() vinst, = hop.inputargs(hop.args_r[0]) flavor = hop.args_r[0].gcflavor assert flavor != 'gc' @@ -617,6 +636,7 @@ # keepalive_until_here def rtype_keepalive_until_here(hop): + hop.exception_cannot_occur() for v in hop.args_v: hop.genop('keepalive', [v], resulttype=lltype.Void) return hop.inputconst(lltype.Void, None) diff --git a/pypy/rpython/rfloat.py b/pypy/rpython/rfloat.py --- a/pypy/rpython/rfloat.py +++ b/pypy/rpython/rfloat.py @@ -136,7 +136,10 @@ hop.exception_cannot_occur() return hop.genop('cast_float_to_int', vlist, resulttype=Signed) - rtype_float = rtype_pos + def rtype_float(_, hop): + vlist = hop.inputargs(Float) + hop.exception_cannot_occur() + return vlist[0] # version picked by specialisation based on which # type system rtyping is using, from .ll_str module diff --git a/pypy/rpython/rint.py b/pypy/rpython/rint.py --- a/pypy/rpython/rint.py +++ b/pypy/rpython/rint.py @@ -310,6 +310,8 @@ if hop.has_implicit_exception(ValueError): hop.exception_is_here() hop.gendirectcall(ll_check_chr, vlist[0]) + else: + hop.exception_cannot_occur() return hop.genop('cast_int_to_char', vlist, resulttype=Char) def rtype_unichr(_, hop): @@ -317,6 +319,8 @@ if hop.has_implicit_exception(ValueError): hop.exception_is_here() hop.gendirectcall(ll_check_unichr, vlist[0]) + else: + hop.exception_cannot_occur() return hop.genop('cast_int_to_unichar', vlist, resulttype=UniChar) def rtype_is_true(self, hop): diff --git a/pypy/rpython/rlist.py b/pypy/rpython/rlist.py --- a/pypy/rpython/rlist.py +++ b/pypy/rpython/rlist.py @@ -115,6 +115,7 @@ def rtype_bltn_list(self, hop): v_lst = hop.inputarg(self, 0) cRESLIST = hop.inputconst(Void, hop.r_result.LIST) + hop.exception_is_here() return hop.gendirectcall(ll_copy, cRESLIST, v_lst) def rtype_len(self, hop): diff --git a/pypy/rpython/rrange.py b/pypy/rpython/rrange.py --- a/pypy/rpython/rrange.py +++ b/pypy/rpython/rrange.py @@ -107,8 +107,10 @@ if isinstance(hop.r_result, AbstractRangeRepr): if hop.r_result.step != 0: c_rng = hop.inputconst(Void, hop.r_result.RANGE) + hop.exception_is_here() return hop.gendirectcall(hop.r_result.ll_newrange, c_rng, vstart, vstop) else: + hop.exception_is_here() return hop.gendirectcall(hop.r_result.ll_newrangest, vstart, vstop, vstep) else: # cannot build a RANGE object, needs a real list @@ -117,6 +119,7 @@ if isinstance(ITEMTYPE, Ptr): ITEMTYPE = ITEMTYPE.TO cLIST = hop.inputconst(Void, ITEMTYPE) + hop.exception_is_here() return hop.gendirectcall(ll_range2list, cLIST, vstart, vstop, vstep) rtype_builtin_xrange = rtype_builtin_range @@ -212,4 +215,5 @@ [v_index, v_item]) def rtype_builtin_enumerate(hop): + hop.exception_cannot_occur() return hop.r_result.r_baseiter.newiter(hop) diff --git a/pypy/rpython/rstr.py b/pypy/rpython/rstr.py --- a/pypy/rpython/rstr.py +++ b/pypy/rpython/rstr.py @@ -288,6 +288,8 @@ def rtype_unicode(self, hop): if hop.args_s[0].is_constant(): + # convertion errors occur during annotation, so cannot any more: + hop.exception_cannot_occur() return hop.inputconst(hop.r_result, hop.s_result.const) repr = hop.args_r[0].repr v_str = hop.inputarg(repr, 0) diff --git a/pypy/rpython/rtyper.py b/pypy/rpython/rtyper.py --- a/pypy/rpython/rtyper.py +++ b/pypy/rpython/rtyper.py @@ -846,6 +846,7 @@ return result def exception_is_here(self): + self.llops._called_exception_is_here_or_cannot_occur = True if self.llops.llop_raising_exceptions is not None: raise TyperError("cannot catch an exception at more than one llop") if not self.exceptionlinks: @@ -861,6 +862,7 @@ self.llops.llop_raising_exceptions = len(self.llops) def exception_cannot_occur(self): + self.llops._called_exception_is_here_or_cannot_occur = True if self.llops.llop_raising_exceptions is not None: raise TyperError("cannot catch an exception at more than one llop") if not self.exceptionlinks: diff --git a/pypy/rpython/test/test_extregistry.py b/pypy/rpython/test/test_extregistry.py --- a/pypy/rpython/test/test_extregistry.py +++ b/pypy/rpython/test/test_extregistry.py @@ -114,6 +114,7 @@ _about_ = dummy_func s_result_annotation = annmodel.SomeInteger() def specialize_call(self, hop): + hop.exception_cannot_occur() return hop.inputconst(lltype.Signed, 42) def func(): diff --git a/pypy/rpython/test/test_rclass.py b/pypy/rpython/test/test_rclass.py --- a/pypy/rpython/test/test_rclass.py +++ b/pypy/rpython/test/test_rclass.py @@ -1085,6 +1085,7 @@ return annmodel.SomeInteger() def specialize_call(self, hop): [v_instance] = hop.inputargs(*hop.args_r) + hop.exception_is_here() return hop.gendirectcall(ll_my_gethash, v_instance) def f(n): diff --git a/pypy/rpython/test/test_rstr.py b/pypy/rpython/test/test_rstr.py --- a/pypy/rpython/test/test_rstr.py +++ b/pypy/rpython/test/test_rstr.py @@ -477,7 +477,11 @@ s1 = s[:3] s2 = s[3:] s3 = s[3:10] - return s1+s2 == s and s2+s1 == const('lohel') and s1+s3 == s + s4 = s[42:44] + return (s1+s2 == s and + s2+s1 == const('lohel') and + s1+s3 == s and + s4 == const('')) res = self.interpret(fn, [0]) assert res diff --git a/pypy/tool/clean_old_branches.py b/pypy/tool/clean_old_branches.py --- a/pypy/tool/clean_old_branches.py +++ b/pypy/tool/clean_old_branches.py @@ -38,7 +38,7 @@ closed_heads.reverse() for head, branch in closed_heads: - print '\t', branch + print '\t', head, '\t', branch print print 'The branches listed above will be merged to "closed-branches".' print 'You need to run this script in a clean working copy where you' diff --git a/pypy/translator/c/gcc/test/test_asmgcroot.py b/pypy/translator/c/gcc/test/test_asmgcroot.py --- a/pypy/translator/c/gcc/test/test_asmgcroot.py +++ b/pypy/translator/c/gcc/test/test_asmgcroot.py @@ -7,10 +7,17 @@ from pypy import conftest from pypy.translator.tool.cbuild import ExternalCompilationInfo from pypy.translator.platform import platform as compiler +from pypy.rlib.rarithmetic import is_emulated_long from pypy.rpython.lltypesystem import lltype, rffi from pypy.rlib.entrypoint import entrypoint, secondary_entrypoints from pypy.rpython.lltypesystem.lloperation import llop +_MSVC = compiler.name == "msvc" +_MINGW = compiler.name == "mingw32" +_WIN32 = _MSVC or _MINGW +_WIN64 = _WIN32 and is_emulated_long +# XXX get rid of 'is_emulated_long' and have a real config here. + class AbstractTestAsmGCRoot: # the asmgcroot gc transformer doesn't generate gc_reload_possibly_moved # instructions: @@ -18,8 +25,8 @@ @classmethod def make_config(cls): - if compiler.name == "msvc": - py.test.skip("all asmgcroot tests disabled for MSVC") + if _MSVC and _WIN64: + py.test.skip("all asmgcroot tests disabled for MSVC X64") from pypy.config.pypyoption import get_pypy_config config = get_pypy_config(translating=True) config.translation.gc = cls.gcpolicy diff --git a/pypy/translator/c/src/float.h b/pypy/translator/c/src/float.h --- a/pypy/translator/c/src/float.h +++ b/pypy/translator/c/src/float.h @@ -43,5 +43,6 @@ #define OP_CAST_FLOAT_TO_LONGLONG(x,r) r = (long long)(x) #define OP_CAST_FLOAT_TO_ULONGLONG(x,r) r = (unsigned long long)(x) #define OP_CONVERT_FLOAT_BYTES_TO_LONGLONG(x,r) memcpy(&r, &x, sizeof(double)) +#define OP_CONVERT_LONGLONG_BYTES_TO_FLOAT(x,r) memcpy(&r, &x, sizeof(long long)) #endif diff --git a/pypy/translator/c/test/test_extfunc.py b/pypy/translator/c/test/test_extfunc.py --- a/pypy/translator/c/test/test_extfunc.py +++ b/pypy/translator/c/test/test_extfunc.py @@ -919,4 +919,5 @@ t, cbuilder = self.compile(does_stuff) data = cbuilder.cmdexec('') res = os.nice(0) + 3 + if res > 19: res = 19 # xxx Linux specific, probably assert data.startswith('os.nice returned %d\n' % res) diff --git a/pypy/translator/cli/dotnet.py b/pypy/translator/cli/dotnet.py --- a/pypy/translator/cli/dotnet.py +++ b/pypy/translator/cli/dotnet.py @@ -459,6 +459,7 @@ def specialize_call(self, hop): + hop.exception_cannot_occur() assert hop.args_s[1].is_constant() TYPE = hop.args_s[1].const v_obj = hop.inputarg(hop.args_r[0], arg=0) @@ -507,6 +508,7 @@ def specialize_call(self, hop): v_obj, = hop.inputargs(*hop.args_r) + hop.exception_cannot_occur() return hop.genop('same_as', [v_obj], hop.r_result.lowleveltype) def new_array(type, length): @@ -608,6 +610,7 @@ def specialize_call(self, hop): v_type, = hop.inputargs(*hop.args_r) + hop.exception_cannot_occur() return hop.genop('cli_typeof', [v_type], hop.r_result.lowleveltype) @@ -626,6 +629,7 @@ v_obj, = hop.inputargs(*hop.args_r) methodname = hop.args_r[0].methodname c_methodname = hop.inputconst(ootype.Void, methodname) + hop.exception_cannot_occur() return hop.genop('cli_eventhandler', [v_obj, c_methodname], hop.r_result.lowleveltype) @@ -647,6 +651,7 @@ def specialize_call(self, hop): assert isinstance(hop.args_s[0], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[0], arg=0) + hop.exception_cannot_occur() return hop.genop('oodowncast', [v_inst], resulttype = hop.r_result.lowleveltype) @@ -668,6 +673,7 @@ def specialize_call(self, hop): assert isinstance(hop.args_s[0], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[0], arg=0) + hop.exception_cannot_occur() return hop.genop('ooupcast', [v_inst], resulttype = hop.r_result.lowleveltype) @@ -701,6 +707,7 @@ def specialize_call(self, hop): v_obj = hop.inputarg(hop.args_r[0], arg=0) + hop.exception_cannot_occur() return hop.genop('oodowncast', [v_obj], hop.r_result.lowleveltype) diff --git a/pypy/translator/jvm/opcodes.py b/pypy/translator/jvm/opcodes.py --- a/pypy/translator/jvm/opcodes.py +++ b/pypy/translator/jvm/opcodes.py @@ -243,4 +243,5 @@ 'force_cast': [PushAllArgs, CastPrimitive, StoreResult], 'convert_float_bytes_to_longlong': jvm.PYPYDOUBLEBYTESTOLONG, + 'convert_longlong_bytes_to_float': jvm.PYPYLONGBYTESTODOUBLE, }) diff --git a/pypy/translator/jvm/typesystem.py b/pypy/translator/jvm/typesystem.py --- a/pypy/translator/jvm/typesystem.py +++ b/pypy/translator/jvm/typesystem.py @@ -942,6 +942,7 @@ PYPYULONGTODOUBLE = Method.s(jPyPy, 'ulong_to_double', (jLong,), jDouble) PYPYLONGBITWISENEGATE = Method.v(jPyPy, 'long_bitwise_negate', (jLong,), jLong) PYPYDOUBLEBYTESTOLONG = Method.v(jPyPy, 'pypy__float2longlong', (jDouble,), jLong) +PYPYLONGBYTESTODOUBLE = Method.v(jPyPy, 'pypy__longlong2float', (jLong,), jDouble) PYPYSTRTOINT = Method.v(jPyPy, 'str_to_int', (jString,), jInt) PYPYSTRTOUINT = Method.v(jPyPy, 'str_to_uint', (jString,), jInt) PYPYSTRTOLONG = Method.v(jPyPy, 'str_to_long', (jString,), jLong) From noreply at buildbot.pypy.org Fri Apr 6 09:02:43 2012 From: noreply at buildbot.pypy.org (mattip) Date: Fri, 6 Apr 2012 09:02:43 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: translation fixes Message-ID: <20120406070243.8A6308208A@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54213:8664eeb9165d Date: 2012-04-06 07:56 +0300 http://bitbucket.org/pypy/pypy/changeset/8664eeb9165d/ Log: translation fixes diff --git a/pypy/rlib/rposix.py b/pypy/rlib/rposix.py --- a/pypy/rlib/rposix.py +++ b/pypy/rlib/rposix.py @@ -20,8 +20,9 @@ assert index == 0 ll2ctypes.TLS.errno = value if os.name == 'nt': - post_include_bits =[''' + separate_module_sources =[''' /* Lifted completely from CPython 3.3 Modules/posix_module.c */ + #include /* for _msize */ typedef struct { intptr_t osfhnd; char osfile; @@ -72,12 +73,15 @@ errno = EBADF; return 0; } - '''] + ''',] + export_symbols = ['_PyVerify_fd'] else: - post_include_bits = [] + separate_module_sources = [] + export_symbols = [] eci = ExternalCompilationInfo( includes=['errno.h','stdio.h'], - post_include_bits = post_include_bits, + separate_module_sources = separate_module_sources, + export_symbols = export_symbols, ) _get_errno, _set_errno = CExternVariable(INT, 'errno', eci, diff --git a/pypy/translator/c/src/main.h b/pypy/translator/c/src/main.h --- a/pypy/translator/c/src/main.h +++ b/pypy/translator/c/src/main.h @@ -20,7 +20,7 @@ #endif #ifdef MS_WINDOWS -/*#include "src/winstuff.c"*/ +#include "src/winstuff.c" #endif #ifdef __GNUC__ diff --git a/pypy/translator/c/src/winstuff.c b/pypy/translator/c/src/winstuff.c --- a/pypy/translator/c/src/winstuff.c +++ b/pypy/translator/c/src/winstuff.c @@ -27,7 +27,7 @@ void pypy_Windows_startup(void) { -#if defined _MSC_VER && _MSC_VER >= 1400 && defined(__STDC_SECURE_LIB__) +#if 0 && defined _MSC_VER && _MSC_VER >= 1400 && defined(__STDC_SECURE_LIB__) /* Set CRT argument error handler */ _set_invalid_parameter_handler(InvalidParameterHandler); /* turn off assertions within CRT in debug mode; From noreply at buildbot.pypy.org Fri Apr 6 09:02:44 2012 From: noreply at buildbot.pypy.org (mattip) Date: Fri, 6 Apr 2012 09:02:44 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: fix for clock_gettime Message-ID: <20120406070244.BCB8B8208A@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54214:3b96efd18ff6 Date: 2012-04-06 10:00 +0300 http://bitbucket.org/pypy/pypy/changeset/3b96efd18ff6/ Log: fix for clock_gettime diff --git a/pypy/module/__pypy__/interp_time.py b/pypy/module/__pypy__/interp_time.py --- a/pypy/module/__pypy__/interp_time.py +++ b/pypy/module/__pypy__/interp_time.py @@ -1,5 +1,5 @@ from __future__ import with_statement -import sys +import os from pypy.interpreter.error import exception_from_errno from pypy.interpreter.gateway import unwrap_spec @@ -7,11 +7,15 @@ from pypy.rpython.tool import rffi_platform from pypy.translator.tool.cbuild import ExternalCompilationInfo +if os.name == 'nt': + libraries = [] +else: + libraries = ["rt"] class CConfig: _compilation_info_ = ExternalCompilationInfo( includes=["time.h"], - libraries=["rt"], + libraries=libraries, ) HAS_CLOCK_GETTIME = rffi_platform.Has('clock_gettime') @@ -22,11 +26,6 @@ CLOCK_PROCESS_CPUTIME_ID = rffi_platform.DefinedConstantInteger("CLOCK_PROCESS_CPUTIME_ID") CLOCK_THREAD_CPUTIME_ID = rffi_platform.DefinedConstantInteger("CLOCK_THREAD_CPUTIME_ID") - TIMESPEC = rffi_platform.Struct("struct timespec", [ - ("tv_sec", rffi.TIME_T), - ("tv_nsec", rffi.LONG), - ]) - cconfig = rffi_platform.configure(CConfig) HAS_CLOCK_GETTIME = cconfig["HAS_CLOCK_GETTIME"] @@ -37,29 +36,33 @@ CLOCK_PROCESS_CPUTIME_ID = cconfig["CLOCK_PROCESS_CPUTIME_ID"] CLOCK_THREAD_CPUTIME_ID = cconfig["CLOCK_THREAD_CPUTIME_ID"] -TIMESPEC = cconfig["TIMESPEC"] +if HAS_CLOCK_GETTIME: + TIMESPEC = lltype.Struct("struct timespec", + ("tv_sec", rffi.TIME_T), + ("tv_nsec", rffi.LONG), + ) -c_clock_gettime = rffi.llexternal("clock_gettime", - [lltype.Signed, lltype.Ptr(TIMESPEC)], rffi.INT, - compilation_info=CConfig._compilation_info_, threadsafe=False -) -c_clock_getres = rffi.llexternal("clock_getres", - [lltype.Signed, lltype.Ptr(TIMESPEC)], rffi.INT, - compilation_info=CConfig._compilation_info_, threadsafe=False -) + c_clock_gettime = rffi.llexternal("clock_gettime", + [lltype.Signed, lltype.Ptr(TIMESPEC)], rffi.INT, + compilation_info=CConfig._compilation_info_, threadsafe=False + ) + c_clock_getres = rffi.llexternal("clock_getres", + [lltype.Signed, lltype.Ptr(TIMESPEC)], rffi.INT, + compilation_info=CConfig._compilation_info_, threadsafe=False + ) - at unwrap_spec(clk_id="c_int") -def clock_gettime(space, clk_id): - with lltype.scoped_alloc(TIMESPEC) as tp: - ret = c_clock_gettime(clk_id, tp) - if ret != 0: - raise exception_from_errno(space, space.w_IOError) - return space.wrap(tp.c_tv_sec + tp.c_tv_nsec * 1e-9) + @unwrap_spec(clk_id="c_int") + def clock_gettime(space, clk_id): + with lltype.scoped_alloc(TIMESPEC) as tp: + ret = c_clock_gettime(clk_id, tp) + if ret != 0: + raise exception_from_errno(space, space.w_IOError) + return space.wrap(tp.c_tv_sec + tp.c_tv_nsec * 1e-9) - at unwrap_spec(clk_id="c_int") -def clock_getres(space, clk_id): - with lltype.scoped_alloc(TIMESPEC) as tp: - ret = c_clock_getres(clk_id, tp) - if ret != 0: - raise exception_from_errno(space, space.w_IOError) - return space.wrap(tp.c_tv_sec + tp.c_tv_nsec * 1e-9) + @unwrap_spec(clk_id="c_int") + def clock_getres(space, clk_id): + with lltype.scoped_alloc(TIMESPEC) as tp: + ret = c_clock_getres(clk_id, tp) + if ret != 0: + raise exception_from_errno(space, space.w_IOError) + return space.wrap(tp.c_tv_sec + tp.c_tv_nsec * 1e-9) From pullrequests-noreply at bitbucket.org Fri Apr 6 11:35:32 2012 From: pullrequests-noreply at bitbucket.org (Roberto De Ioris) Date: Fri, 06 Apr 2012 09:35:32 -0000 Subject: [pypy-commit] [pypy/pypy] PyFile_FromFile, PySys_SetArgv and PyThreadState_New implementation (pull request #68) In-Reply-To: <819aec1e6e3998acaa7b35b136186f8a@bitbucket.org> References: <819aec1e6e3998acaa7b35b136186f8a@bitbucket.org> Message-ID: <20120406093532.2950.65072@bitbucket05.managed.contegix.com> Pull request #68 has been updated by Roberto De Ioris to include new changes. https://bitbucket.org/pypy/pypy/pull-request/68/pyfile_fromfile-pysys_setargv-and Title: PyFile_FromFile, PySys_SetArgv and PyThreadState_New implementation Creator: Roberto De Ioris PyFile_FromFile() is used by c apps to map a libc FILE object to a Python file object. The implementation make use of the pypy-specific file.fdopen function. PySys_SetArgv is used to set sys.argv. Both Tested with latest uWSGI tip. PyThreadState_New is only a prototype, i suspect the different internal threading usage in PyPy will require a bigger effort. Updated list of changes: 5b28c054a739 by Roberto De Ioris: "added PySys_SetArgv and PyThreadState_New" 7bd1b6f657a8 by Roberto De Ioris: "merge with official pypy" d356d169e770 by Roberto De Ioris: "add fflush to cpyext pyfile implementation" 1f3baa6ce8b1 by rob... at mrsurr: "merge" 2afeef0e350b by Roberto De Ioris: "implemed PyFile_FromFile as cpyext" 7eb44aa590a2 by Roberto De Ioris: "added Py_Initialize,Py_Finalize,Py_SetPythonHome,Py_SetProgramName as cpyext" 4c1e64afbe9a by Roberto De Ioris: "added implementation of Py_Initialize" -- This is an issue notification from bitbucket.org. You are receiving this either because you are the participating in a pull request, or you are following it. From noreply at buildbot.pypy.org Fri Apr 6 12:06:33 2012 From: noreply at buildbot.pypy.org (mattip) Date: Fri, 6 Apr 2012 12:06:33 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: fix for non-windows Message-ID: <20120406100633.F34728208A@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54215:a6e9220f391d Date: 2012-04-06 13:05 +0300 http://bitbucket.org/pypy/pypy/changeset/a6e9220f391d/ Log: fix for non-windows diff --git a/pypy/module/__pypy__/interp_time.py b/pypy/module/__pypy__/interp_time.py --- a/pypy/module/__pypy__/interp_time.py +++ b/pypy/module/__pypy__/interp_time.py @@ -37,10 +37,13 @@ CLOCK_THREAD_CPUTIME_ID = cconfig["CLOCK_THREAD_CPUTIME_ID"] if HAS_CLOCK_GETTIME: - TIMESPEC = lltype.Struct("struct timespec", + #redo it for timespec + CConfig.TIMESPEC = rffi_platform.Struct("struct timespec", [ ("tv_sec", rffi.TIME_T), ("tv_nsec", rffi.LONG), - ) + ]) + cconfig = rffi_platform.configure(CConfig) + TIMESPEC = cconfig['TIMESPEC'] c_clock_gettime = rffi.llexternal("clock_gettime", [lltype.Signed, lltype.Ptr(TIMESPEC)], rffi.INT, From noreply at buildbot.pypy.org Fri Apr 6 16:41:57 2012 From: noreply at buildbot.pypy.org (antocuni) Date: Fri, 6 Apr 2012 16:41:57 +0200 (CEST) Subject: [pypy-commit] extradoc extradoc: draft for another py3k-status-update blog post Message-ID: <20120406144157.D366446E0EF@wyvern.cs.uni-duesseldorf.de> Author: Antonio Cuni Branch: extradoc Changeset: r4178:9899005dd47d Date: 2012-04-06 16:41 +0200 http://bitbucket.org/pypy/extradoc/changeset/9899005dd47d/ Log: draft for another py3k-status-update blog post diff --git a/blog/draft/py3k-activity-march.png b/blog/draft/py3k-activity-march.png new file mode 100644 index 0000000000000000000000000000000000000000..23b5b2a2aa5a7284a06c01328c6c1b4561d2a063 GIT binary patch [cut] diff --git a/blog/draft/py3k-status-update-3.rst b/blog/draft/py3k-status-update-3.rst new file mode 100644 --- /dev/null +++ b/blog/draft/py3k-status-update-3.rst @@ -0,0 +1,61 @@ +Py3k status update #3 +--------------------- + +This is the third status update about my work on the `py3k branch`_, which I +can work on thanks to all of the people who donated_ to the `py3k proposal`_. + +A lot of work has been done during the last month: as usual, the list of +changes is too big to be reported in a detalied way, so this is just a summary +of what happened. + +One of the most active areas was killing old and deprecated features. In +particular, we killed support for the ``__cmp__`` special method and its +counsins, the ``cmp`` builtin function and keyword argument for +``list.sort()`` and ``sorted()``. Killing is easy, but then you have to fix +all the places which breaks because of this, including all the types which +relied on ``__cmp__`` to be comparable,, fixing all the tests which tried to +order objects which are no longer ordeable now, or implementing new behavior +like forbidding calling ``hash()`` on objects which implement ``__eq__`` but +not ``__hash__``. + +Among the other features, we killed lots of now-gone functions in the +``operator`` module, the builtins ``apply()``, ``reduce()`` and ``buffer``, +and the ``os.*`` functions to deal with temporary files, which has been +deprecated in favour of the new ``tempfile`` module. + +The other topic which can't miss in a py3k status update is, as usual, +string-vs-unicode. At this round, we fixed bugs in string formatting (in +particular to teach ``format()`` to always use unicode strings) and various +corner cases about when calling the (possibly overridden) ``__str__`` method +on subclasses of ``str``. Believe me, you don't want to know the precise rules +:-). + +Other features which we worked on and fixed tests include, but are not limited +to, ``marshal``, ``hashlib``, ``zipimport``, ``_socket`` and ``itertools``, +plus the habitual endless lists of tests which fail for shallow reasons such +as the syntactic differences, ``int`` vs ``long``, ``range()`` vs +``list(range())`` etc. As a result, the number of failing tests dropped_ from +650 to 235: we are beginning to see the light at the end of the tunnel :-) + +Finally, we did some RPython fixes, so that it is possible again to translate +PyPy in the py3k branch. However, the resuling binary is a strange beast which +mixes python 2 and python 3 semantics, so it is unusable for anything but +`showing friends how cool it's that`_. + +.. image:: py3k-activity-march.png + +I would like to underline that I was not alone in doing all this work. In +particular, a lot of people joined the PyPy sprint at Pycon and worked on the +branch, as you can clearly see in this activity graph. I would like to thank +all who helped! + +XXX: benjamin, could you write a quick summary of what you did? + +cheers, +Antonio + +.. _donated: http://morepypy.blogspot.com/2012/01/py3k-and-numpy-first-stage-thanks-to.html +.. _`py3k proposal`: http://pypy.org/py3donate.html +.. _`py3k branch`: https://bitbucket.org/pypy/pypy/src/py3k +.. _`showing friends how cool it's that`: http://paste.pocoo.org/show/577006/ +.. _dropped: http://buildbot.pypy.org/summary?category=linux32&branch=py3k&recentrev=53956:3c8ac35c653a From noreply at buildbot.pypy.org Fri Apr 6 19:15:24 2012 From: noreply at buildbot.pypy.org (gutworth) Date: Fri, 6 Apr 2012 19:15:24 +0200 (CEST) Subject: [pypy-commit] extradoc extradoc: mention what I did with syntax Message-ID: <20120406171524.3F51346E0EF@wyvern.cs.uni-duesseldorf.de> Author: Benjamin Peterson Branch: extradoc Changeset: r4179:976290076952 Date: 2012-04-06 13:15 -0400 http://bitbucket.org/pypy/extradoc/changeset/976290076952/ Log: mention what I did with syntax diff --git a/blog/draft/py3k-status-update-3.rst b/blog/draft/py3k-status-update-3.rst --- a/blog/draft/py3k-status-update-3.rst +++ b/blog/draft/py3k-status-update-3.rst @@ -37,6 +37,12 @@ ``list(range())`` etc. As a result, the number of failing tests dropped_ from 650 to 235: we are beginning to see the light at the end of the tunnel :-) +Benjamin finished implementing Python 3 syntax. Most of it was small cleanups +and tweaks to be compatible with CPython such as making ``True`` and ``False`` +keywords and preventing ``. . .`` from being parsed as ``Ellipsis``. Larger +features syntax additions included keyword only arguments and function +annotations. + Finally, we did some RPython fixes, so that it is possible again to translate PyPy in the py3k branch. However, the resuling binary is a strange beast which mixes python 2 and python 3 semantics, so it is unusable for anything but @@ -49,10 +55,8 @@ branch, as you can clearly see in this activity graph. I would like to thank all who helped! -XXX: benjamin, could you write a quick summary of what you did? - cheers, -Antonio +Antonio and Benjamin .. _donated: http://morepypy.blogspot.com/2012/01/py3k-and-numpy-first-stage-thanks-to.html .. _`py3k proposal`: http://pypy.org/py3donate.html From noreply at buildbot.pypy.org Fri Apr 6 19:15:52 2012 From: noreply at buildbot.pypy.org (gutworth) Date: Fri, 6 Apr 2012 19:15:52 +0200 (CEST) Subject: [pypy-commit] extradoc extradoc: kill extra word Message-ID: <20120406171552.79CF346E0EF@wyvern.cs.uni-duesseldorf.de> Author: Benjamin Peterson Branch: extradoc Changeset: r4180:ade4b357fc44 Date: 2012-04-06 13:15 -0400 http://bitbucket.org/pypy/extradoc/changeset/ade4b357fc44/ Log: kill extra word diff --git a/blog/draft/py3k-status-update-3.rst b/blog/draft/py3k-status-update-3.rst --- a/blog/draft/py3k-status-update-3.rst +++ b/blog/draft/py3k-status-update-3.rst @@ -40,8 +40,7 @@ Benjamin finished implementing Python 3 syntax. Most of it was small cleanups and tweaks to be compatible with CPython such as making ``True`` and ``False`` keywords and preventing ``. . .`` from being parsed as ``Ellipsis``. Larger -features syntax additions included keyword only arguments and function -annotations. +syntax additions included keyword only arguments and function annotations. Finally, we did some RPython fixes, so that it is possible again to translate PyPy in the py3k branch. However, the resuling binary is a strange beast which From noreply at buildbot.pypy.org Sat Apr 7 15:01:44 2012 From: noreply at buildbot.pypy.org (antocuni) Date: Sat, 7 Apr 2012 15:01:44 +0200 (CEST) Subject: [pypy-commit] pypy default: bump the version number; this checkin will be cherry-picked in the release branch Message-ID: <20120407130144.C1AEC8208A@wyvern.cs.uni-duesseldorf.de> Author: Antonio Cuni Branch: Changeset: r54216:8fafaa971ea2 Date: 2012-04-07 14:40 +0200 http://bitbucket.org/pypy/pypy/changeset/8fafaa971ea2/ Log: bump the version number; this checkin will be cherry-picked in the release branch diff --git a/pypy/module/cpyext/include/patchlevel.h b/pypy/module/cpyext/include/patchlevel.h --- a/pypy/module/cpyext/include/patchlevel.h +++ b/pypy/module/cpyext/include/patchlevel.h @@ -29,7 +29,7 @@ #define PY_VERSION "2.7.2" /* PyPy version as a string */ -#define PYPY_VERSION "1.8.1" +#define PYPY_VERSION "1.9.0" /* Subversion Revision number of this file (not of the repository). * Empty since Mercurial migration. */ diff --git a/pypy/module/sys/version.py b/pypy/module/sys/version.py --- a/pypy/module/sys/version.py +++ b/pypy/module/sys/version.py @@ -10,7 +10,7 @@ CPYTHON_VERSION = (2, 7, 2, "final", 42) #XXX # sync patchlevel.h CPYTHON_API_VERSION = 1013 #XXX # sync with include/modsupport.h -PYPY_VERSION = (1, 8, 1, "dev", 0) #XXX # sync patchlevel.h +PYPY_VERSION = (1, 9, 0, "final", 0) #XXX # sync patchlevel.h if platform.name == 'msvc': COMPILER_INFO = 'MSC v.%d 32 bit' % (platform.version * 10 + 600) From noreply at buildbot.pypy.org Sat Apr 7 15:01:46 2012 From: noreply at buildbot.pypy.org (antocuni) Date: Sat, 7 Apr 2012 15:01:46 +0200 (CEST) Subject: [pypy-commit] pypy default: bump the version number to continue the development on default Message-ID: <20120407130146.30B148208B@wyvern.cs.uni-duesseldorf.de> Author: Antonio Cuni Branch: Changeset: r54217:571bc0fbf7d1 Date: 2012-04-07 14:42 +0200 http://bitbucket.org/pypy/pypy/changeset/571bc0fbf7d1/ Log: bump the version number to continue the development on default diff --git a/pypy/module/cpyext/include/patchlevel.h b/pypy/module/cpyext/include/patchlevel.h --- a/pypy/module/cpyext/include/patchlevel.h +++ b/pypy/module/cpyext/include/patchlevel.h @@ -29,7 +29,7 @@ #define PY_VERSION "2.7.2" /* PyPy version as a string */ -#define PYPY_VERSION "1.9.0" +#define PYPY_VERSION "1.9.1" /* Subversion Revision number of this file (not of the repository). * Empty since Mercurial migration. */ diff --git a/pypy/module/sys/version.py b/pypy/module/sys/version.py --- a/pypy/module/sys/version.py +++ b/pypy/module/sys/version.py @@ -10,7 +10,7 @@ CPYTHON_VERSION = (2, 7, 2, "final", 42) #XXX # sync patchlevel.h CPYTHON_API_VERSION = 1013 #XXX # sync with include/modsupport.h -PYPY_VERSION = (1, 9, 0, "final", 0) #XXX # sync patchlevel.h +PYPY_VERSION = (1, 9, 1, "dev", 0) #XXX # sync patchlevel.h if platform.name == 'msvc': COMPILER_INFO = 'MSC v.%d 32 bit' % (platform.version * 10 + 600) From noreply at buildbot.pypy.org Sat Apr 7 15:01:47 2012 From: noreply at buildbot.pypy.org (antocuni) Date: Sat, 7 Apr 2012 15:01:47 +0200 (CEST) Subject: [pypy-commit] pypy release-1.9.x: create a release branch Message-ID: <20120407130147.BD005820C7@wyvern.cs.uni-duesseldorf.de> Author: Antonio Cuni Branch: release-1.9.x Changeset: r54218:77dd5ae278aa Date: 2012-04-07 14:50 +0200 http://bitbucket.org/pypy/pypy/changeset/77dd5ae278aa/ Log: create a release branch From noreply at buildbot.pypy.org Sat Apr 7 15:01:49 2012 From: noreply at buildbot.pypy.org (antocuni) Date: Sat, 7 Apr 2012 15:01:49 +0200 (CEST) Subject: [pypy-commit] pypy release-1.9.x: bump the version number; this checkin will be cherry-picked in the release branch Message-ID: <20120407130149.1301D820D9@wyvern.cs.uni-duesseldorf.de> Author: Antonio Cuni Branch: release-1.9.x Changeset: r54219:82161531c3d5 Date: 2012-04-07 14:40 +0200 http://bitbucket.org/pypy/pypy/changeset/82161531c3d5/ Log: bump the version number; this checkin will be cherry-picked in the release branch diff --git a/pypy/module/cpyext/include/patchlevel.h b/pypy/module/cpyext/include/patchlevel.h --- a/pypy/module/cpyext/include/patchlevel.h +++ b/pypy/module/cpyext/include/patchlevel.h @@ -29,7 +29,7 @@ #define PY_VERSION "2.7.2" /* PyPy version as a string */ -#define PYPY_VERSION "1.8.1" +#define PYPY_VERSION "1.9.0" /* Subversion Revision number of this file (not of the repository). * Empty since Mercurial migration. */ diff --git a/pypy/module/sys/version.py b/pypy/module/sys/version.py --- a/pypy/module/sys/version.py +++ b/pypy/module/sys/version.py @@ -10,7 +10,7 @@ CPYTHON_VERSION = (2, 7, 2, "final", 42) #XXX # sync patchlevel.h CPYTHON_API_VERSION = 1013 #XXX # sync with include/modsupport.h -PYPY_VERSION = (1, 8, 1, "dev", 0) #XXX # sync patchlevel.h +PYPY_VERSION = (1, 9, 0, "final", 0) #XXX # sync patchlevel.h if platform.name == 'msvc': COMPILER_INFO = 'MSC v.%d 32 bit' % (platform.version * 10 + 600) From noreply at buildbot.pypy.org Sat Apr 7 15:04:09 2012 From: noreply at buildbot.pypy.org (cfbolz) Date: Sat, 7 Apr 2012 15:04:09 +0200 (CEST) Subject: [pypy-commit] pypy default: typo Message-ID: <20120407130409.428008208A@wyvern.cs.uni-duesseldorf.de> Author: Carl Friedrich Bolz Branch: Changeset: r54220:ab5974a8f615 Date: 2012-04-05 22:05 +0200 http://bitbucket.org/pypy/pypy/changeset/ab5974a8f615/ Log: typo diff --git a/pypy/objspace/std/identitydict.py b/pypy/objspace/std/identitydict.py --- a/pypy/objspace/std/identitydict.py +++ b/pypy/objspace/std/identitydict.py @@ -1,5 +1,5 @@ ## ---------------------------------------------------------------------------- -## dict strategy (see dict_multiobject.py) +## dict strategy (see dictmultiobject.py) from pypy.rlib import rerased from pypy.rlib.debug import mark_dict_non_null From noreply at buildbot.pypy.org Sat Apr 7 15:04:10 2012 From: noreply at buildbot.pypy.org (cfbolz) Date: Sat, 7 Apr 2012 15:04:10 +0200 (CEST) Subject: [pypy-commit] pypy default: remove inefficient implementation of IntDictStrategy.w_keys and rely on default Message-ID: <20120407130410.915548208A@wyvern.cs.uni-duesseldorf.de> Author: Carl Friedrich Bolz Branch: Changeset: r54221:057f65a41c8e Date: 2012-04-05 22:10 +0200 http://bitbucket.org/pypy/pypy/changeset/057f65a41c8e/ Log: remove inefficient implementation of IntDictStrategy.w_keys and rely on default implementation diff --git a/pypy/objspace/std/dictmultiobject.py b/pypy/objspace/std/dictmultiobject.py --- a/pypy/objspace/std/dictmultiobject.py +++ b/pypy/objspace/std/dictmultiobject.py @@ -563,10 +563,7 @@ def listview_int(self, w_dict): return self.unerase(w_dict.dstorage).keys() - def w_keys(self, w_dict): - # XXX there is no space.newlist_int yet - space = self.space - return space.call_function(space.w_list, w_dict) + # XXX there is no space.newlist_int yet to implement w_keys more efficiently class IntIteratorImplementation(_WrappedIteratorMixin, IteratorImplementation): pass From noreply at buildbot.pypy.org Sat Apr 7 15:04:11 2012 From: noreply at buildbot.pypy.org (cfbolz) Date: Sat, 7 Apr 2012 15:04:11 +0200 (CEST) Subject: [pypy-commit] pypy default: remvoe unused argument that was only passed in mostly disabled code path Message-ID: <20120407130411.EFF008208A@wyvern.cs.uni-duesseldorf.de> Author: Carl Friedrich Bolz Branch: Changeset: r54222:4fa7e4646fd4 Date: 2012-04-05 22:19 +0200 http://bitbucket.org/pypy/pypy/changeset/4fa7e4646fd4/ Log: remvoe unused argument that was only passed in mostly disabled code path diff --git a/pypy/interpreter/typedef.py b/pypy/interpreter/typedef.py --- a/pypy/interpreter/typedef.py +++ b/pypy/interpreter/typedef.py @@ -321,7 +321,7 @@ def user_setup(self, space, w_subtype): self.w__dict__ = space.newdict( - instance=True, classofinstance=w_subtype) + instance=True) base_user_setup(self, space, w_subtype) def setclass(self, space, w_subtype): diff --git a/pypy/objspace/fake/objspace.py b/pypy/objspace/fake/objspace.py --- a/pypy/objspace/fake/objspace.py +++ b/pypy/objspace/fake/objspace.py @@ -110,7 +110,7 @@ "NOT_RPYTHON" raise NotImplementedError - def newdict(self, module=False, instance=False, classofinstance=None, + def newdict(self, module=False, instance=False, strdict=False): return w_some_obj() diff --git a/pypy/objspace/std/dictmultiobject.py b/pypy/objspace/std/dictmultiobject.py --- a/pypy/objspace/std/dictmultiobject.py +++ b/pypy/objspace/std/dictmultiobject.py @@ -33,8 +33,7 @@ @staticmethod def allocate_and_init_instance(space, w_type=None, module=False, - instance=False, classofinstance=None, - strdict=False): + instance=False, strdict=False): if space.config.objspace.std.withcelldict and module: from pypy.objspace.std.celldict import ModuleDictStrategy diff --git a/pypy/objspace/std/objspace.py b/pypy/objspace/std/objspace.py --- a/pypy/objspace/std/objspace.py +++ b/pypy/objspace/std/objspace.py @@ -313,11 +313,10 @@ def newlist_str(self, list_s): return W_ListObject.newlist_str(self, list_s) - def newdict(self, module=False, instance=False, classofinstance=None, + def newdict(self, module=False, instance=False, strdict=False): return W_DictMultiObject.allocate_and_init_instance( self, module=module, instance=instance, - classofinstance=classofinstance, strdict=strdict) def newset(self): diff --git a/pypy/objspace/std/test/test_dictmultiobject.py b/pypy/objspace/std/test/test_dictmultiobject.py --- a/pypy/objspace/std/test/test_dictmultiobject.py +++ b/pypy/objspace/std/test/test_dictmultiobject.py @@ -885,10 +885,9 @@ def newtuple(self, l): return tuple(l) - def newdict(self, module=False, instance=False, classofinstance=None): + def newdict(self, module=False, instance=False): return W_DictMultiObject.allocate_and_init_instance( - self, module=module, instance=instance, - classofinstance=classofinstance) + self, module=module, instance=instance) def finditem_str(self, w_dict, s): return w_dict.getitem_str(s) # assume it's a multidict From noreply at buildbot.pypy.org Sat Apr 7 15:04:13 2012 From: noreply at buildbot.pypy.org (cfbolz) Date: Sat, 7 Apr 2012 15:04:13 +0200 (CEST) Subject: [pypy-commit] pypy default: the keys method no longer exists Message-ID: <20120407130413.48E3C8208A@wyvern.cs.uni-duesseldorf.de> Author: Carl Friedrich Bolz Branch: Changeset: r54223:02d0ae2e0c67 Date: 2012-04-05 22:45 +0200 http://bitbucket.org/pypy/pypy/changeset/02d0ae2e0c67/ Log: the keys method no longer exists diff --git a/pypy/objspace/std/identitydict.py b/pypy/objspace/std/identitydict.py --- a/pypy/objspace/std/identitydict.py +++ b/pypy/objspace/std/identitydict.py @@ -80,8 +80,8 @@ def iter(self, w_dict): return IdentityDictIteratorImplementation(self.space, self, w_dict) - def keys(self, w_dict): - return self.unerase(w_dict.dstorage).keys() + def w_keys(self, w_dict): + return self.space.newlist(self.unerase(w_dict.dstorage).keys()) class IdentityDictIteratorImplementation(_UnwrappedIteratorMixin, IteratorImplementation): From noreply at buildbot.pypy.org Sat Apr 7 15:04:14 2012 From: noreply at buildbot.pypy.org (cfbolz) Date: Sat, 7 Apr 2012 15:04:14 +0200 (CEST) Subject: [pypy-commit] pypy default: add a generic popitem test to the fake space test class as well Message-ID: <20120407130414.AA1EB8208A@wyvern.cs.uni-duesseldorf.de> Author: Carl Friedrich Bolz Branch: Changeset: r54224:69d52bf3335c Date: 2012-04-05 23:07 +0200 http://bitbucket.org/pypy/pypy/changeset/69d52bf3335c/ Log: add a generic popitem test to the fake space test class as well diff --git a/pypy/objspace/std/test/test_dictmultiobject.py b/pypy/objspace/std/test/test_dictmultiobject.py --- a/pypy/objspace/std/test/test_dictmultiobject.py +++ b/pypy/objspace/std/test/test_dictmultiobject.py @@ -967,6 +967,20 @@ assert type(self.impl.strategy) is self.StrategyClass #assert self.impl.r_dict_content is None + def test_popitem(self): + self.fill_impl() + assert self.impl.length() == 2 + a, b = self.impl.popitem() + assert self.impl.length() == 1 + if a == self.string: + assert b == 1000 + assert self.impl.getitem(self.string2) == 2000 + else: + assert a == self.string2 + assert b == 2000 + assert self.impl.getitem_str(self.string) == 1000 + self.check_not_devolved() + def test_setitem(self): self.impl.setitem(self.string, 1000) assert self.impl.length() == 1 From noreply at buildbot.pypy.org Sat Apr 7 15:04:15 2012 From: noreply at buildbot.pypy.org (cfbolz) Date: Sat, 7 Apr 2012 15:04:15 +0200 (CEST) Subject: [pypy-commit] pypy kwargsdict-strategy: a branch to try to write a special strategy for kwarg-dicts Message-ID: <20120407130415.EA8128208A@wyvern.cs.uni-duesseldorf.de> Author: Carl Friedrich Bolz Branch: kwargsdict-strategy Changeset: r54225:6fe24eaa5861 Date: 2012-04-05 23:10 +0200 http://bitbucket.org/pypy/pypy/changeset/6fe24eaa5861/ Log: a branch to try to write a special strategy for kwarg-dicts From noreply at buildbot.pypy.org Sat Apr 7 15:04:17 2012 From: noreply at buildbot.pypy.org (cfbolz) Date: Sat, 7 Apr 2012 15:04:17 +0200 (CEST) Subject: [pypy-commit] pypy kwargsdict-strategy: add a kwargs dict strategy based on two lists Message-ID: <20120407130417.459F08208A@wyvern.cs.uni-duesseldorf.de> Author: Carl Friedrich Bolz Branch: kwargsdict-strategy Changeset: r54226:3275a7dffd64 Date: 2012-04-05 23:20 +0200 http://bitbucket.org/pypy/pypy/changeset/3275a7dffd64/ Log: add a kwargs dict strategy based on two lists diff --git a/pypy/objspace/std/kwargsdict.py b/pypy/objspace/std/kwargsdict.py new file mode 100644 --- /dev/null +++ b/pypy/objspace/std/kwargsdict.py @@ -0,0 +1,136 @@ +## ---------------------------------------------------------------------------- +## dict strategy (see dictmultiobject.py) + +from pypy.rlib import rerased, jit +from pypy.objspace.std.dictmultiobject import (DictStrategy, + IteratorImplementation, + ObjectDictStrategy) + + +class KwargsDictStrategy(DictStrategy): + erase, unerase = rerased.new_erasing_pair("kwargsdict") + erase = staticmethod(erase) + unerase = staticmethod(unerase) + + def wrap(self, key): + return self.space.wrap(key) + + def unwrap(self, wrapped): + return self.space.str_w(wrapped) + + def get_empty_storage(self): + d = ([], []) + return self.erase(d) + + def is_correct_type(self, w_obj): + space = self.space + return space.is_w(space.type(w_obj), space.w_str) + + def _never_equal_to(self, w_lookup_type): + return False + + def iter(self, w_dict): + return KwargsDictIterator(self.space, self, w_dict) + + def w_keys(self, w_dict): + return self.space.newlist([self.space.wrap(key) for key in self.unerase(w_dict.dstorage)[0]]) + + def setitem(self, w_dict, w_key, w_value): + space = self.space + if self.is_correct_type(w_key): + self.setitem_str(w_dict, self.unwrap(w_key), w_value) + return + else: + self.switch_to_object_strategy(w_dict) + w_dict.setitem(w_key, w_value) + + @jit.look_inside_iff(lambda self, w_dict, key, w_value: + jit.is_constant(self.length(w_dict)) and jit.is_constant(key)) + def setitem_str(self, w_dict, key, w_value): + keys, values_w = self.unerase(w_dict.dstorage) + result = [] + for i in range(len(keys)): + if keys[i] == key: + values_w[i] = w_value + break + else: + keys.append(key) + values_w.append(w_value) + + def setdefault(self, w_dict, w_key, w_default): + # XXX could do better, but is it worth it? + self.switch_to_object_strategy(w_dict) + return w_dict.setdefault(w_key, w_default) + + def delitem(self, w_dict, w_key): + # XXX could do better, but is it worth it? + self.switch_to_object_strategy(w_dict) + return w_dict.delitem(w_key) + + def length(self, w_dict): + return len(self.unerase(w_dict.dstorage)[0]) + + @jit.look_inside_iff(lambda self, w_dict, key: jit.is_constant(self.length(w_dict)) and jit.is_constant(key)) + def getitem_str(self, w_dict, key): + keys, values_w = self.unerase(w_dict.dstorage) + result = [] + for i in range(len(keys)): + if keys[i] == key: + return values_w[i] + return None + + def getitem(self, w_dict, w_key): + space = self.space + if self.is_correct_type(w_key): + return self.getitem_str(w_dict, self.unwrap(w_key)) + elif self._never_equal_to(space.type(w_key)): + return None + else: + self.switch_to_object_strategy(w_dict) + return w_dict.getitem(w_key) + + def w_keys(self, w_dict): + l = [self.wrap(key) for key in self.unerase(w_dict.dstorage)[0]] + return self.space.newlist(l) + + def values(self, w_dict): + return self.unerase(w_dict.dstorage)[1][:] # to make non-resizable + + def items(self, w_dict): + space = self.space + keys, values_w = self.unerase(w_dict.dstorage) + result = [] + for i in range(len(keys)): + result.append(space.newtuple([self.wrap(keys[i]), values_w[i]])) + return result + + def popitem(self, w_dict): + keys, values_w = self.unerase(w_dict.dstorage) + key = keys.pop() + w_value = values_w.pop() + return (self.wrap(key), w_value) + + def clear(self, w_dict): + w_dict.dstorage = self.get_empty_storage() + + def switch_to_object_strategy(self, w_dict): + strategy = self.space.fromcache(ObjectDictStrategy) + keys, values_w = self.unerase(w_dict.dstorage) + d_new = strategy.unerase(strategy.get_empty_storage()) + for i in range(len(keys)): + d_new[self.wrap(keys[i])] = values_w[i] + w_dict.strategy = strategy + w_dict.dstorage = strategy.erase(d_new) + +class KwargsDictIterator(IteratorImplementation): + def __init__(self, space, strategy, dictimplementation): + IteratorImplementation.__init__(self, space, strategy, dictimplementation) + self.iterator = iter(range(len(strategy.unerase(dictimplementation.dstorage)[0]))) + + def next_entry(self): + # note that this 'for' loop only runs once, at most + keys, values_w = self.strategy.unerase(self.dictimplementation.dstorage) + for i in self.iterator: + return self.space.wrap(keys[i]), values_w[i] + else: + return None, None diff --git a/pypy/objspace/std/test/test_kwargsdict.py b/pypy/objspace/std/test/test_kwargsdict.py new file mode 100644 --- /dev/null +++ b/pypy/objspace/std/test/test_kwargsdict.py @@ -0,0 +1,84 @@ +from pypy.conftest import gettestobjspace, option +from pypy.objspace.std.test.test_dictmultiobject import FakeSpace, W_DictMultiObject +from pypy.objspace.std.kwargsdict import * + +space = FakeSpace() +strategy = KwargsDictStrategy(space) + +def test_create(): + keys = ["a", "b", "c"] + values = [1, 2, 3] + storage = strategy.erase((keys, values)) + d = W_DictMultiObject(space, strategy, storage) + assert d.getitem_str("a") == 1 + assert d.getitem_str("b") == 2 + assert d.getitem_str("c") == 3 + assert d.getitem(space.wrap("a")) == 1 + assert d.getitem(space.wrap("b")) == 2 + assert d.getitem(space.wrap("c")) == 3 + assert d.w_keys() == keys + assert d.values() == values + +def test_set_existing(): + keys = ["a", "b", "c"] + values = [1, 2, 3] + storage = strategy.erase((keys, values)) + d = W_DictMultiObject(space, strategy, storage) + assert d.getitem_str("a") == 1 + assert d.getitem_str("b") == 2 + assert d.getitem_str("c") == 3 + assert d.setitem_str("a", 4) is None + assert d.getitem_str("a") == 4 + assert d.getitem_str("b") == 2 + assert d.getitem_str("c") == 3 + assert d.setitem_str("b", 5) is None + assert d.getitem_str("a") == 4 + assert d.getitem_str("b") == 5 + assert d.getitem_str("c") == 3 + assert d.setitem_str("c", 6) is None + assert d.getitem_str("a") == 4 + assert d.getitem_str("b") == 5 + assert d.getitem_str("c") == 6 + assert d.getitem(space.wrap("a")) == 4 + assert d.getitem(space.wrap("b")) == 5 + assert d.getitem(space.wrap("c")) == 6 + assert d.w_keys() == keys + assert d.values() == values + assert keys == ["a", "b", "c"] + assert values == [4, 5, 6] + + +def test_set_new(): + keys = ["a", "b", "c"] + values = [1, 2, 3] + storage = strategy.erase((keys, values)) + d = W_DictMultiObject(space, strategy, storage) + assert d.getitem_str("a") == 1 + assert d.getitem_str("b") == 2 + assert d.getitem_str("c") == 3 + assert d.getitem_str("d") is None + assert d.setitem_str("d", 4) is None + assert d.getitem_str("a") == 1 + assert d.getitem_str("b") == 2 + assert d.getitem_str("c") == 3 + assert d.getitem_str("d") == 4 + assert d.w_keys() == keys + assert d.values() == values + assert keys == ["a", "b", "c", "d"] + assert values == [1, 2, 3, 4] + + +from pypy.objspace.std.test.test_dictmultiobject import BaseTestRDictImplementation, BaseTestDevolvedDictImplementation +def get_impl(self): + storage = strategy.erase(([], [])) + return W_DictMultiObject(space, strategy, storage) +class TestKwargsDictImplementation(BaseTestRDictImplementation): + StrategyClass = KwargsDictStrategy + get_impl = get_impl + def test_delitem(self): + pass # delitem devolves for now + +class TestDevolvedKwargsDictImplementation(BaseTestDevolvedDictImplementation): + get_impl = get_impl + StrategyClass = KwargsDictStrategy + From noreply at buildbot.pypy.org Sat Apr 7 15:04:18 2012 From: noreply at buildbot.pypy.org (cfbolz) Date: Sat, 7 Apr 2012 15:04:18 +0200 (CEST) Subject: [pypy-commit] pypy kwargsdict-strategy: make a kwargs dict when calling a function that takes **args Message-ID: <20120407130418.926F88208A@wyvern.cs.uni-duesseldorf.de> Author: Carl Friedrich Bolz Branch: kwargsdict-strategy Changeset: r54227:0cfe5878d642 Date: 2012-04-05 23:22 +0200 http://bitbucket.org/pypy/pypy/changeset/0cfe5878d642/ Log: make a kwargs dict when calling a function that takes **args diff --git a/pypy/interpreter/argument.py b/pypy/interpreter/argument.py --- a/pypy/interpreter/argument.py +++ b/pypy/interpreter/argument.py @@ -385,7 +385,7 @@ # collect extra keyword arguments into the **kwarg if has_kwarg: - w_kwds = self.space.newdict() + w_kwds = self.space.newdict(kwargs=True) if num_remainingkwds: # limit = len(keywords) diff --git a/pypy/objspace/fake/objspace.py b/pypy/objspace/fake/objspace.py --- a/pypy/objspace/fake/objspace.py +++ b/pypy/objspace/fake/objspace.py @@ -110,7 +110,7 @@ "NOT_RPYTHON" raise NotImplementedError - def newdict(self, module=False, instance=False, + def newdict(self, module=False, instance=False, kwargs=False, strdict=False): return w_some_obj() diff --git a/pypy/objspace/std/dictmultiobject.py b/pypy/objspace/std/dictmultiobject.py --- a/pypy/objspace/std/dictmultiobject.py +++ b/pypy/objspace/std/dictmultiobject.py @@ -33,7 +33,7 @@ @staticmethod def allocate_and_init_instance(space, w_type=None, module=False, - instance=False, strdict=False): + instance=False, strdict=False, kwargs=False): if space.config.objspace.std.withcelldict and module: from pypy.objspace.std.celldict import ModuleDictStrategy @@ -46,11 +46,15 @@ assert w_type is None strategy = space.fromcache(StringDictStrategy) + elif kwargs: + assert w_type is None + from pypy.objspace.std.kwargsdict import KwargsDictStrategy + strategy = space.fromcache(KwargsDictStrategy) else: strategy = space.fromcache(EmptyDictStrategy) - if w_type is None: w_type = space.w_dict + storage = strategy.get_empty_storage() w_self = space.allocate_instance(W_DictMultiObject, w_type) W_DictMultiObject.__init__(w_self, space, strategy, storage) diff --git a/pypy/objspace/std/objspace.py b/pypy/objspace/std/objspace.py --- a/pypy/objspace/std/objspace.py +++ b/pypy/objspace/std/objspace.py @@ -313,11 +313,11 @@ def newlist_str(self, list_s): return W_ListObject.newlist_str(self, list_s) - def newdict(self, module=False, instance=False, + def newdict(self, module=False, instance=False, kwargs=False, strdict=False): return W_DictMultiObject.allocate_and_init_instance( self, module=module, instance=instance, - strdict=strdict) + strdict=strdict, kwargs=kwargs) def newset(self): from pypy.objspace.std.setobject import newset diff --git a/pypy/objspace/std/test/test_kwargsdict.py b/pypy/objspace/std/test/test_kwargsdict.py --- a/pypy/objspace/std/test/test_kwargsdict.py +++ b/pypy/objspace/std/test/test_kwargsdict.py @@ -82,3 +82,20 @@ get_impl = get_impl StrategyClass = KwargsDictStrategy + +class AppTestKwargsDictStrategy(object): + def setup_class(cls): + if option.runappdirect: + py.test.skip("__repr__ doesn't work on appdirect") + + def w_get_strategy(self, obj): + import __pypy__ + r = __pypy__.internal_repr(obj) + return r[r.find("(") + 1: r.find(")")] + + def test_create(self): + def f(**args): + return args + d = f(a=1) + assert "KwargsDictStrategy" in self.get_strategy(d) + From noreply at buildbot.pypy.org Sat Apr 7 15:04:19 2012 From: noreply at buildbot.pypy.org (cfbolz) Date: Sat, 7 Apr 2012 15:04:19 +0200 (CEST) Subject: [pypy-commit] pypy kwargsdict-strategy: limit size of the kwargsdict to (for now) 16 Message-ID: <20120407130419.ED7C88208A@wyvern.cs.uni-duesseldorf.de> Author: Carl Friedrich Bolz Branch: kwargsdict-strategy Changeset: r54228:dfac680a26f9 Date: 2012-04-06 10:43 +0200 http://bitbucket.org/pypy/pypy/changeset/dfac680a26f9/ Log: limit size of the kwargsdict to (for now) 16 diff --git a/pypy/objspace/std/kwargsdict.py b/pypy/objspace/std/kwargsdict.py --- a/pypy/objspace/std/kwargsdict.py +++ b/pypy/objspace/std/kwargsdict.py @@ -4,7 +4,8 @@ from pypy.rlib import rerased, jit from pypy.objspace.std.dictmultiobject import (DictStrategy, IteratorImplementation, - ObjectDictStrategy) + ObjectDictStrategy, + StringDictStrategy) class KwargsDictStrategy(DictStrategy): @@ -54,8 +55,13 @@ values_w[i] = w_value break else: - keys.append(key) - values_w.append(w_value) + # limit the size so that the linear searches don't become too long + if len(keys) >= 16: + self.switch_to_string_strategy(w_dict) + w_dict.setitem_str(key, w_value) + else: + keys.append(key) + values_w.append(w_value) def setdefault(self, w_dict, w_key, w_default): # XXX could do better, but is it worth it? @@ -122,6 +128,17 @@ w_dict.strategy = strategy w_dict.dstorage = strategy.erase(d_new) + def switch_to_string_strategy(self, w_dict): + strategy = self.space.fromcache(StringDictStrategy) + keys, values_w = self.unerase(w_dict.dstorage) + storage = strategy.get_empty_storage() + d_new = strategy.unerase(storage) + for i in range(len(keys)): + d_new[keys[i]] = values_w[i] + w_dict.strategy = strategy + w_dict.dstorage = storage + + class KwargsDictIterator(IteratorImplementation): def __init__(self, space, strategy, dictimplementation): IteratorImplementation.__init__(self, space, strategy, dictimplementation) diff --git a/pypy/objspace/std/test/test_kwargsdict.py b/pypy/objspace/std/test/test_kwargsdict.py --- a/pypy/objspace/std/test/test_kwargsdict.py +++ b/pypy/objspace/std/test/test_kwargsdict.py @@ -67,6 +67,13 @@ assert keys == ["a", "b", "c", "d"] assert values == [1, 2, 3, 4] +def test_limit_size(): + storage = strategy.get_empty_storage() + d = W_DictMultiObject(space, strategy, storage) + for i in range(100): + assert d.setitem_str("d%s" % i, 4) is None + assert d.strategy is not strategy + assert "StringDictStrategy" == d.strategy.__class__.__name__ from pypy.objspace.std.test.test_dictmultiobject import BaseTestRDictImplementation, BaseTestDevolvedDictImplementation def get_impl(self): From noreply at buildbot.pypy.org Sat Apr 7 15:04:21 2012 From: noreply at buildbot.pypy.org (cfbolz) Date: Sat, 7 Apr 2012 15:04:21 +0200 (CEST) Subject: [pypy-commit] pypy kwargsdict-strategy: add a new space method view_as_kwargs which efficiently unwraps a KwargsDict and use it in the argument parser Message-ID: <20120407130421.5182C8208A@wyvern.cs.uni-duesseldorf.de> Author: Carl Friedrich Bolz Branch: kwargsdict-strategy Changeset: r54229:166c64714bac Date: 2012-04-06 11:08 +0200 http://bitbucket.org/pypy/pypy/changeset/166c64714bac/ Log: add a new space method view_as_kwargs which efficiently unwraps a KwargsDict and use it in the argument parser diff --git a/pypy/interpreter/argument.py b/pypy/interpreter/argument.py --- a/pypy/interpreter/argument.py +++ b/pypy/interpreter/argument.py @@ -169,6 +169,10 @@ def _combine_starstarargs_wrapped(self, w_starstararg): # unpack the ** arguments space = self.space + keywords, values_w = space.view_as_kwargs(w_starstararg) + if keywords is not None: + self._add_keywordargs_no_unwrapping(keywords, values_w) + return jit.isconstant(len(self.keywords)) if space.isinstance_w(w_starstararg, space.w_dict): if not space.is_true(w_starstararg): return False # don't call unpackiterable - it's jit-opaque @@ -227,6 +231,26 @@ self.keywords_w = self.keywords_w + keywords_w self.keyword_names_w = keys_w + @jit.look_inside_iff(lambda self, keywords, keywords_w: + jit.isconstant(len(keywords) and + jit.isconstant(self.keywords))) + def _add_keywordargs_no_unwrapping(self, keywords, keywords_w): + if self.keywords is None: + self.keywords = keywords + self.keywords_w = keywords_w + else: + # looks quadratic, but the JIT should remove all of it nicely. + # Also, all the lists should be small + for key in keywords: + for otherkey in self.keywords: + if otherkey == key: + raise operationerrfmt(self.space.w_TypeError, + "got multiple values " + "for keyword argument " + "'%s'", key) + self.keywords = self.keywords + keywords + self.keywords_w = self.keywords_w + keywords_w + def fixedunpack(self, argcount): """The simplest argument parsing: get the 'argcount' arguments, or raise a real ValueError if the length is wrong.""" diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py --- a/pypy/interpreter/baseobjspace.py +++ b/pypy/interpreter/baseobjspace.py @@ -914,6 +914,12 @@ """ return None + def view_as_kwargs(self, w_dict): + """ if w_dict is a kwargs-dict, return two lists, one of unwrapped + strings and one of wrapped values. otherwise return (None, None) + """ + return (None, None) + def newlist_str(self, list_s): return self.newlist([self.wrap(s) for s in list_s]) diff --git a/pypy/interpreter/test/test_argument.py b/pypy/interpreter/test/test_argument.py --- a/pypy/interpreter/test/test_argument.py +++ b/pypy/interpreter/test/test_argument.py @@ -75,7 +75,10 @@ def unpackiterable(self, it): return list(it) - def newdict(self): + def view_as_kwargs(self, x): + return None, None + + def newdict(self, kwargs=False): return {} def newlist(self, l=[]): @@ -488,6 +491,57 @@ assert len(l) == 1 assert l[0] == space.wrap(5) + def test_starstarargs_special(self): + class kwargs(object): + def __init__(self, k, v): + self.k = k + self.v = v + class MyDummySpace(DummySpace): + def view_as_kwargs(self, kw): + if isinstance(kw, kwargs): + return kw.k, kw.v + return None, None + space = MyDummySpace() + for i in range(3): + kwds = [("c", 3)] + kwds_w = dict(kwds[:i]) + keywords = kwds_w.keys() + keywords_w = kwds_w.values() + rest = dict(kwds[i:]) + w_kwds = kwargs(rest.keys(), rest.values()) + if i == 2: + w_kwds = None + assert len(keywords) == len(keywords_w) + args = Arguments(space, [1, 2], keywords[:], keywords_w[:], w_starstararg=w_kwds) + l = [None, None, None] + args._match_signature(None, l, Signature(["a", "b", "c"]), defaults_w=[4]) + assert l == [1, 2, 3] + args = Arguments(space, [1, 2], keywords[:], keywords_w[:], w_starstararg=w_kwds) + l = [None, None, None, None] + args._match_signature(None, l, Signature(["a", "b", "b1", "c"]), defaults_w=[4, 5]) + assert l == [1, 2, 4, 3] + args = Arguments(space, [1, 2], keywords[:], keywords_w[:], w_starstararg=w_kwds) + l = [None, None, None, None] + args._match_signature(None, l, Signature(["a", "b", "c", "d"]), defaults_w=[4, 5]) + assert l == [1, 2, 3, 5] + args = Arguments(space, [1, 2], keywords[:], keywords_w[:], w_starstararg=w_kwds) + l = [None, None, None, None] + py.test.raises(ArgErr, args._match_signature, None, l, + Signature(["c", "b", "a", "d"]), defaults_w=[4, 5]) + args = Arguments(space, [1, 2], keywords[:], keywords_w[:], w_starstararg=w_kwds) + l = [None, None, None, None] + py.test.raises(ArgErr, args._match_signature, None, l, + Signature(["a", "b", "c1", "d"]), defaults_w=[4, 5]) + args = Arguments(space, [1, 2], keywords[:], keywords_w[:], w_starstararg=w_kwds) + l = [None, None, None] + args._match_signature(None, l, Signature(["a", "b"], None, "**")) + assert l == [1, 2, {'c': 3}] + excinfo = py.test.raises(OperationError, Arguments, space, [], ["a"], + [1], w_starstararg=kwargs(["a"], [2])) + assert excinfo.value.w_type is TypeError + + + class TestErrorHandling(object): def test_missing_args(self): # got_nargs, nkwds, expected_nargs, has_vararg, has_kwarg, diff --git a/pypy/objspace/std/dictmultiobject.py b/pypy/objspace/std/dictmultiobject.py --- a/pypy/objspace/std/dictmultiobject.py +++ b/pypy/objspace/std/dictmultiobject.py @@ -95,7 +95,8 @@ getitem_str delitem length \ clear w_keys values \ items iter setdefault \ - popitem listview_str listview_int".split() + popitem listview_str listview_int \ + view_as_kwargs".split() def make_method(method): def f(self, *args): @@ -169,6 +170,9 @@ def listview_int(self, w_dict): return None + def view_as_kwargs(self, w_dict): + return (None, None) + class EmptyDictStrategy(DictStrategy): erase, unerase = rerased.new_erasing_pair("empty") diff --git a/pypy/objspace/std/kwargsdict.py b/pypy/objspace/std/kwargsdict.py --- a/pypy/objspace/std/kwargsdict.py +++ b/pypy/objspace/std/kwargsdict.py @@ -138,6 +138,9 @@ w_dict.strategy = strategy w_dict.dstorage = storage + def view_as_kwargs(self, w_dict): + return self.unerase(w_dict.dstorage) + class KwargsDictIterator(IteratorImplementation): def __init__(self, space, strategy, dictimplementation): diff --git a/pypy/objspace/std/objspace.py b/pypy/objspace/std/objspace.py --- a/pypy/objspace/std/objspace.py +++ b/pypy/objspace/std/objspace.py @@ -472,6 +472,12 @@ return w_obj.getitems_int() return None + def view_as_kwargs(self, w_dict): + return (None, None) + if type(w_obj) is W_DictMultiObject: + return w_obj.view_as_kwargs() + return (None, None) + def _uses_list_iter(self, w_obj): from pypy.objspace.descroperation import list_iter return self.lookup(w_obj, '__iter__') is list_iter(self) From noreply at buildbot.pypy.org Sat Apr 7 15:04:22 2012 From: noreply at buildbot.pypy.org (cfbolz) Date: Sat, 7 Apr 2012 15:04:22 +0200 (CEST) Subject: [pypy-commit] pypy kwargsdict-strategy: make empty dicts use this path as well Message-ID: <20120407130422.9C3078208A@wyvern.cs.uni-duesseldorf.de> Author: Carl Friedrich Bolz Branch: kwargsdict-strategy Changeset: r54230:5bad845d3944 Date: 2012-04-06 11:08 +0200 http://bitbucket.org/pypy/pypy/changeset/5bad845d3944/ Log: make empty dicts use this path as well diff --git a/pypy/interpreter/argument.py b/pypy/interpreter/argument.py --- a/pypy/interpreter/argument.py +++ b/pypy/interpreter/argument.py @@ -170,12 +170,10 @@ # unpack the ** arguments space = self.space keywords, values_w = space.view_as_kwargs(w_starstararg) - if keywords is not None: + if keywords is not None: # this path also taken for empty dicts self._add_keywordargs_no_unwrapping(keywords, values_w) return jit.isconstant(len(self.keywords)) if space.isinstance_w(w_starstararg, space.w_dict): - if not space.is_true(w_starstararg): - return False # don't call unpackiterable - it's jit-opaque keys_w = space.unpackiterable(w_starstararg) else: try: @@ -190,11 +188,8 @@ "a mapping, not %s" % (typename,))) raise keys_w = space.unpackiterable(w_keys) - if keys_w: - self._do_combine_starstarargs_wrapped(keys_w, w_starstararg) - return True - else: - return False # empty dict; don't disable the JIT + self._do_combine_starstarargs_wrapped(keys_w, w_starstararg) + return True def _do_combine_starstarargs_wrapped(self, keys_w, w_starstararg): space = self.space diff --git a/pypy/objspace/std/dictmultiobject.py b/pypy/objspace/std/dictmultiobject.py --- a/pypy/objspace/std/dictmultiobject.py +++ b/pypy/objspace/std/dictmultiobject.py @@ -262,6 +262,9 @@ def popitem(self, w_dict): raise KeyError + def view_as_kwargs(self, w_dict): + return ([], []) + registerimplementation(W_DictMultiObject) # DictImplementation lattice From noreply at buildbot.pypy.org Sat Apr 7 15:04:23 2012 From: noreply at buildbot.pypy.org (cfbolz) Date: Sat, 7 Apr 2012 15:04:23 +0200 (CEST) Subject: [pypy-commit] pypy kwargsdict-strategy: translation fixes Message-ID: <20120407130423.E19C88208A@wyvern.cs.uni-duesseldorf.de> Author: Carl Friedrich Bolz Branch: kwargsdict-strategy Changeset: r54231:065d2eb0c85e Date: 2012-04-06 11:41 +0200 http://bitbucket.org/pypy/pypy/changeset/065d2eb0c85e/ Log: translation fixes diff --git a/pypy/objspace/std/kwargsdict.py b/pypy/objspace/std/kwargsdict.py --- a/pypy/objspace/std/kwargsdict.py +++ b/pypy/objspace/std/kwargsdict.py @@ -46,7 +46,7 @@ w_dict.setitem(w_key, w_value) @jit.look_inside_iff(lambda self, w_dict, key, w_value: - jit.is_constant(self.length(w_dict)) and jit.is_constant(key)) + jit.isconstant(self.length(w_dict)) and jit.isconstant(key)) def setitem_str(self, w_dict, key, w_value): keys, values_w = self.unerase(w_dict.dstorage) result = [] @@ -76,7 +76,7 @@ def length(self, w_dict): return len(self.unerase(w_dict.dstorage)[0]) - @jit.look_inside_iff(lambda self, w_dict, key: jit.is_constant(self.length(w_dict)) and jit.is_constant(key)) + @jit.look_inside_iff(lambda self, w_dict, key: jit.isconstant(self.length(w_dict)) and jit.isconstant(key)) def getitem_str(self, w_dict, key): keys, values_w = self.unerase(w_dict.dstorage) result = [] @@ -145,12 +145,15 @@ class KwargsDictIterator(IteratorImplementation): def __init__(self, space, strategy, dictimplementation): IteratorImplementation.__init__(self, space, strategy, dictimplementation) - self.iterator = iter(range(len(strategy.unerase(dictimplementation.dstorage)[0]))) + keys, values_w = strategy.unerase(self.dictimplementation.dstorage) + self.iterator = iter(range(len(keys))) + # XXX this potentially leaks + self.keys = keys + self.values_w = values_w def next_entry(self): # note that this 'for' loop only runs once, at most - keys, values_w = self.strategy.unerase(self.dictimplementation.dstorage) for i in self.iterator: - return self.space.wrap(keys[i]), values_w[i] + return self.space.wrap(self.keys[i]), self.values_w[i] else: return None, None From noreply at buildbot.pypy.org Sat Apr 7 15:04:25 2012 From: noreply at buildbot.pypy.org (cfbolz) Date: Sat, 7 Apr 2012 15:04:25 +0200 (CEST) Subject: [pypy-commit] pypy kwargsdict-strategy: more translation fixes Message-ID: <20120407130425.384128208A@wyvern.cs.uni-duesseldorf.de> Author: Carl Friedrich Bolz Branch: kwargsdict-strategy Changeset: r54232:6fce95c0e36e Date: 2012-04-06 16:40 +0200 http://bitbucket.org/pypy/pypy/changeset/6fce95c0e36e/ Log: more translation fixes diff --git a/pypy/objspace/std/kwargsdict.py b/pypy/objspace/std/kwargsdict.py --- a/pypy/objspace/std/kwargsdict.py +++ b/pypy/objspace/std/kwargsdict.py @@ -45,9 +45,12 @@ self.switch_to_object_strategy(w_dict) w_dict.setitem(w_key, w_value) + def setitem_str(self, w_dict, key, w_value): + self._setitem_str_indirection(w_dict, key, w_value) + @jit.look_inside_iff(lambda self, w_dict, key, w_value: jit.isconstant(self.length(w_dict)) and jit.isconstant(key)) - def setitem_str(self, w_dict, key, w_value): + def _setitem_str_indirection(self, w_dict, key, w_value): keys, values_w = self.unerase(w_dict.dstorage) result = [] for i in range(len(keys)): @@ -76,8 +79,11 @@ def length(self, w_dict): return len(self.unerase(w_dict.dstorage)[0]) + def getitem_str(self, w_dict, key): + return self._getitem_str_indirection(w_dict, key) + @jit.look_inside_iff(lambda self, w_dict, key: jit.isconstant(self.length(w_dict)) and jit.isconstant(key)) - def getitem_str(self, w_dict, key): + def _getitem_str_indirection(self, w_dict, key): keys, values_w = self.unerase(w_dict.dstorage) result = [] for i in range(len(keys)): From noreply at buildbot.pypy.org Sat Apr 7 15:04:26 2012 From: noreply at buildbot.pypy.org (cfbolz) Date: Sat, 7 Apr 2012 15:04:26 +0200 (CEST) Subject: [pypy-commit] pypy kwargsdict-strategy: - gah, actually enable view_as_kwargs Message-ID: <20120407130426.821A48208A@wyvern.cs.uni-duesseldorf.de> Author: Carl Friedrich Bolz Branch: kwargsdict-strategy Changeset: r54233:1ce73e4161df Date: 2012-04-07 10:56 +0200 http://bitbucket.org/pypy/pypy/changeset/1ce73e4161df/ Log: - gah, actually enable view_as_kwargs - also, fix a resizing problem - fix logic when to look into argument matching diff --git a/pypy/interpreter/argument.py b/pypy/interpreter/argument.py --- a/pypy/interpreter/argument.py +++ b/pypy/interpreter/argument.py @@ -172,7 +172,7 @@ keywords, values_w = space.view_as_kwargs(w_starstararg) if keywords is not None: # this path also taken for empty dicts self._add_keywordargs_no_unwrapping(keywords, values_w) - return jit.isconstant(len(self.keywords)) + return not jit.isconstant(len(self.keywords)) if space.isinstance_w(w_starstararg, space.w_dict): keys_w = space.unpackiterable(w_starstararg) else: @@ -231,8 +231,8 @@ jit.isconstant(self.keywords))) def _add_keywordargs_no_unwrapping(self, keywords, keywords_w): if self.keywords is None: - self.keywords = keywords - self.keywords_w = keywords_w + self.keywords = keywords[:] # copy to make non-resizable + self.keywords_w = keywords_w[:] else: # looks quadratic, but the JIT should remove all of it nicely. # Also, all the lists should be small diff --git a/pypy/objspace/std/objspace.py b/pypy/objspace/std/objspace.py --- a/pypy/objspace/std/objspace.py +++ b/pypy/objspace/std/objspace.py @@ -473,9 +473,8 @@ return None def view_as_kwargs(self, w_dict): - return (None, None) - if type(w_obj) is W_DictMultiObject: - return w_obj.view_as_kwargs() + if type(w_dict) is W_DictMultiObject: + return w_dict.view_as_kwargs() return (None, None) def _uses_list_iter(self, w_obj): From noreply at buildbot.pypy.org Sat Apr 7 15:04:28 2012 From: noreply at buildbot.pypy.org (cfbolz) Date: Sat, 7 Apr 2012 15:04:28 +0200 (CEST) Subject: [pypy-commit] pypy kwargsdict-strategy: test that kwargs virtualize nicely Message-ID: <20120407130428.3708A8208A@wyvern.cs.uni-duesseldorf.de> Author: Carl Friedrich Bolz Branch: kwargsdict-strategy Changeset: r54234:0019961d9e80 Date: 2012-04-07 14:58 +0200 http://bitbucket.org/pypy/pypy/changeset/0019961d9e80/ Log: test that kwargs virtualize nicely diff --git a/pypy/module/pypyjit/test_pypy_c/test_call.py b/pypy/module/pypyjit/test_pypy_c/test_call.py --- a/pypy/module/pypyjit/test_pypy_c/test_call.py +++ b/pypy/module/pypyjit/test_pypy_c/test_call.py @@ -244,6 +244,7 @@ print guards assert len(guards) <= 20 + def test_stararg_virtual(self): def main(x): def g(*args): @@ -486,3 +487,38 @@ --TICK-- jump(..., descr=...) """) + + def test_kwargs_virtual2(self): + log = self.run(""" + def f(*args, **kwargs): + kwargs['a'] = kwargs['z'] * 0 + return g(1, *args, **kwargs) + + def g(x, y, z=2, a=1): + return x - y + z + a + + def main(stop): + res = 0 + i = 0 + while i < stop: + res = f(res, z=i) # ID: call + i += 1 + return res""", [1000]) + assert log.result == 500 + loop, = log.loops_by_id('call') + print loop.ops_by_id('call') + assert loop.match(""" + i65 = int_lt(i58, i29) + guard_true(i65, descr=...) + guard_not_invalidated(..., descr=...) + i66 = force_token() + i67 = force_token() + i69 = int_sub_ovf(1, i56) + guard_no_overflow(..., descr=...) + i70 = int_add_ovf(i69, i58) + guard_no_overflow(..., descr=...) + i71 = int_add(i58, 1) + --TICK-- + jump(..., descr=...) + """) + From noreply at buildbot.pypy.org Sat Apr 7 15:04:29 2012 From: noreply at buildbot.pypy.org (cfbolz) Date: Sat, 7 Apr 2012 15:04:29 +0200 (CEST) Subject: [pypy-commit] pypy default: merge Message-ID: <20120407130429.88E0C8208A@wyvern.cs.uni-duesseldorf.de> Author: Carl Friedrich Bolz Branch: Changeset: r54235:f016b819e011 Date: 2012-04-07 15:03 +0200 http://bitbucket.org/pypy/pypy/changeset/f016b819e011/ Log: merge diff --git a/pypy/interpreter/typedef.py b/pypy/interpreter/typedef.py --- a/pypy/interpreter/typedef.py +++ b/pypy/interpreter/typedef.py @@ -321,7 +321,7 @@ def user_setup(self, space, w_subtype): self.w__dict__ = space.newdict( - instance=True, classofinstance=w_subtype) + instance=True) base_user_setup(self, space, w_subtype) def setclass(self, space, w_subtype): diff --git a/pypy/objspace/fake/objspace.py b/pypy/objspace/fake/objspace.py --- a/pypy/objspace/fake/objspace.py +++ b/pypy/objspace/fake/objspace.py @@ -110,7 +110,7 @@ "NOT_RPYTHON" raise NotImplementedError - def newdict(self, module=False, instance=False, classofinstance=None, + def newdict(self, module=False, instance=False, strdict=False): return w_some_obj() diff --git a/pypy/objspace/std/dictmultiobject.py b/pypy/objspace/std/dictmultiobject.py --- a/pypy/objspace/std/dictmultiobject.py +++ b/pypy/objspace/std/dictmultiobject.py @@ -33,8 +33,7 @@ @staticmethod def allocate_and_init_instance(space, w_type=None, module=False, - instance=False, classofinstance=None, - strdict=False): + instance=False, strdict=False): if space.config.objspace.std.withcelldict and module: from pypy.objspace.std.celldict import ModuleDictStrategy @@ -563,10 +562,7 @@ def listview_int(self, w_dict): return self.unerase(w_dict.dstorage).keys() - def w_keys(self, w_dict): - # XXX there is no space.newlist_int yet - space = self.space - return space.call_function(space.w_list, w_dict) + # XXX there is no space.newlist_int yet to implement w_keys more efficiently class IntIteratorImplementation(_WrappedIteratorMixin, IteratorImplementation): pass diff --git a/pypy/objspace/std/identitydict.py b/pypy/objspace/std/identitydict.py --- a/pypy/objspace/std/identitydict.py +++ b/pypy/objspace/std/identitydict.py @@ -1,5 +1,5 @@ ## ---------------------------------------------------------------------------- -## dict strategy (see dict_multiobject.py) +## dict strategy (see dictmultiobject.py) from pypy.rlib import rerased from pypy.rlib.debug import mark_dict_non_null @@ -80,8 +80,8 @@ def iter(self, w_dict): return IdentityDictIteratorImplementation(self.space, self, w_dict) - def keys(self, w_dict): - return self.unerase(w_dict.dstorage).keys() + def w_keys(self, w_dict): + return self.space.newlist(self.unerase(w_dict.dstorage).keys()) class IdentityDictIteratorImplementation(_UnwrappedIteratorMixin, IteratorImplementation): diff --git a/pypy/objspace/std/objspace.py b/pypy/objspace/std/objspace.py --- a/pypy/objspace/std/objspace.py +++ b/pypy/objspace/std/objspace.py @@ -313,11 +313,10 @@ def newlist_str(self, list_s): return W_ListObject.newlist_str(self, list_s) - def newdict(self, module=False, instance=False, classofinstance=None, + def newdict(self, module=False, instance=False, strdict=False): return W_DictMultiObject.allocate_and_init_instance( self, module=module, instance=instance, - classofinstance=classofinstance, strdict=strdict) def newset(self): diff --git a/pypy/objspace/std/test/test_dictmultiobject.py b/pypy/objspace/std/test/test_dictmultiobject.py --- a/pypy/objspace/std/test/test_dictmultiobject.py +++ b/pypy/objspace/std/test/test_dictmultiobject.py @@ -885,10 +885,9 @@ def newtuple(self, l): return tuple(l) - def newdict(self, module=False, instance=False, classofinstance=None): + def newdict(self, module=False, instance=False): return W_DictMultiObject.allocate_and_init_instance( - self, module=module, instance=instance, - classofinstance=classofinstance) + self, module=module, instance=instance) def finditem_str(self, w_dict, s): return w_dict.getitem_str(s) # assume it's a multidict @@ -968,6 +967,20 @@ assert type(self.impl.strategy) is self.StrategyClass #assert self.impl.r_dict_content is None + def test_popitem(self): + self.fill_impl() + assert self.impl.length() == 2 + a, b = self.impl.popitem() + assert self.impl.length() == 1 + if a == self.string: + assert b == 1000 + assert self.impl.getitem(self.string2) == 2000 + else: + assert a == self.string2 + assert b == 2000 + assert self.impl.getitem_str(self.string) == 1000 + self.check_not_devolved() + def test_setitem(self): self.impl.setitem(self.string, 1000) assert self.impl.length() == 1 From noreply at buildbot.pypy.org Sat Apr 7 20:47:47 2012 From: noreply at buildbot.pypy.org (alex_gaynor) Date: Sat, 7 Apr 2012 20:47:47 +0200 (CEST) Subject: [pypy-commit] pypy dynamic-specialized-tuple: Merged default in. Message-ID: <20120407184747.2FD398208A@wyvern.cs.uni-duesseldorf.de> Author: Alex Gaynor Branch: dynamic-specialized-tuple Changeset: r54236:48aba87044b4 Date: 2012-04-07 14:47 -0400 http://bitbucket.org/pypy/pypy/changeset/48aba87044b4/ Log: Merged default in. diff --git a/dotviewer/graphparse.py b/dotviewer/graphparse.py --- a/dotviewer/graphparse.py +++ b/dotviewer/graphparse.py @@ -93,6 +93,7 @@ return result def parse_plain(graph_id, plaincontent, links={}, fixedfont=False): + plaincontent = plaincontent.replace('\r\n', '\n') # fix Windows EOL lines = plaincontent.splitlines(True) for i in range(len(lines)-2, -1, -1): if lines[i].endswith('\\\n'): # line ending in '\' diff --git a/lib_pypy/datetime.py b/lib_pypy/datetime.py --- a/lib_pypy/datetime.py +++ b/lib_pypy/datetime.py @@ -968,8 +968,7 @@ self._checkOverflow(t.year) result = date(t.year, t.month, t.day) return result - raise TypeError - # XXX Should be 'return NotImplemented', but there's a bug in 2.2... + return NotImplemented # note that this doesn't work on CPython 2.2 __radd__ = __add__ diff --git a/pypy/doc/project-ideas.rst b/pypy/doc/project-ideas.rst --- a/pypy/doc/project-ideas.rst +++ b/pypy/doc/project-ideas.rst @@ -149,6 +149,22 @@ exported. This would give us a one-size-fits-all generic .so file to be imported by any application that wants to load .so files :-) +Optimising cpyext (CPython C-API compatibility layer) +----------------------------------------------------- + +A lot of work has gone into PyPy's implementation of CPython's C-API over +the last years to let it reach a practical level of compatibility, so that +C extensions for CPython work on PyPy without major rewrites. However, +there are still many edges and corner cases where it misbehaves, and it has +not received any substantial optimisation so far. + +The objective of this project is to fix bugs in cpyext and to optimise +several performance critical parts of it, such as the reference counting +support and other heavily used C-API functions. The net result would be to +have CPython extensions run much faster on PyPy than they currently do, or +to make them work at all if they currently don't. A part of this work would +be to get cpyext into a shape where it supports running Cython generated +extensions. .. _`issue tracker`: http://bugs.pypy.org .. _`mailing list`: http://mail.python.org/mailman/listinfo/pypy-dev diff --git a/pypy/doc/stackless.rst b/pypy/doc/stackless.rst --- a/pypy/doc/stackless.rst +++ b/pypy/doc/stackless.rst @@ -199,17 +199,11 @@ The following features (present in some past Stackless version of PyPy) are for the time being not supported any more: -* Tasklets and channels (currently ``stackless.py`` seems to import, - but you have tasklets on top of coroutines on top of greenlets on - top of continulets on top of stacklets, and it's probably not too - hard to cut two of these levels by adapting ``stackless.py`` to - use directly continulets) - * Coroutines (could be rewritten at app-level) -* Pickling and unpickling continulets (*) - -* Continuing execution of a continulet in a different thread (*) +* Continuing execution of a continulet in a different thread + (but if it is "simple enough", you can pickle it and unpickle it + in the other thread). * Automatic unlimited stack (must be emulated__ so far) @@ -217,15 +211,6 @@ .. __: `recursion depth limit`_ -(*) Pickling, as well as changing threads, could be implemented by using -a "soft" stack switching mode again. We would get either "hard" or -"soft" switches, similarly to Stackless Python 3rd version: you get a -"hard" switch (like now) when the C stack contains non-trivial C frames -to save, and a "soft" switch (like previously) when it contains only -simple calls from Python to Python. Soft-switched continulets would -also consume a bit less RAM, and the switch might be a bit faster too -(unsure about that; what is the Stackless Python experience?). - Recursion depth limit +++++++++++++++++++++ diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py --- a/pypy/interpreter/baseobjspace.py +++ b/pypy/interpreter/baseobjspace.py @@ -296,6 +296,7 @@ self.check_signal_action = None # changed by the signal module self.user_del_action = UserDelAction(self) self.frame_trace_action = FrameTraceAction(self) + self._code_of_sys_exc_info = None from pypy.interpreter.pycode import cpython_magic, default_magic self.our_magic = default_magic @@ -467,9 +468,9 @@ if name not in modules: modules.append(name) - # a bit of custom logic: time2 or rctime take precedence over time + # a bit of custom logic: rctime take precedence over time # XXX this could probably be done as a "requires" in the config - if ('time2' in modules or 'rctime' in modules) and 'time' in modules: + if 'rctime' in modules and 'time' in modules: modules.remove('time') if not self.config.objspace.nofaking: diff --git a/pypy/interpreter/executioncontext.py b/pypy/interpreter/executioncontext.py --- a/pypy/interpreter/executioncontext.py +++ b/pypy/interpreter/executioncontext.py @@ -154,6 +154,7 @@ #operationerr.print_detailed_traceback(self.space) def _convert_exc(self, operr): + # Only for the flow object space return operr def sys_exc_info(self): # attn: the result is not the wrapped sys.exc_info() !!! @@ -166,6 +167,11 @@ frame = self.getnextframe_nohidden(frame) return None + def set_sys_exc_info(self, operror): + frame = self.gettopframe_nohidden() + if frame: # else, the exception goes nowhere and is lost + frame.last_exception = operror + def settrace(self, w_func): """Set the global trace function.""" if self.space.is_w(w_func, self.space.w_None): diff --git a/pypy/interpreter/function.py b/pypy/interpreter/function.py --- a/pypy/interpreter/function.py +++ b/pypy/interpreter/function.py @@ -113,6 +113,12 @@ from pypy.interpreter.pycode import PyCode code = self.getcode() # hook for the jit + # + if (jit.we_are_jitted() and code is self.space._code_of_sys_exc_info + and nargs == 0): + from pypy.module.sys.vm import exc_info_direct + return exc_info_direct(self.space, frame) + # fast_natural_arity = code.fast_natural_arity if nargs == fast_natural_arity: if nargs == 0: diff --git a/pypy/interpreter/gateway.py b/pypy/interpreter/gateway.py --- a/pypy/interpreter/gateway.py +++ b/pypy/interpreter/gateway.py @@ -874,6 +874,12 @@ fn.add_to_table() if gateway.as_classmethod: fn = ClassMethod(space.wrap(fn)) + # + from pypy.module.sys.vm import exc_info + if code._bltin is exc_info: + assert space._code_of_sys_exc_info is None + space._code_of_sys_exc_info = code + # return fn diff --git a/pypy/interpreter/pyparser/parsestring.py b/pypy/interpreter/pyparser/parsestring.py --- a/pypy/interpreter/pyparser/parsestring.py +++ b/pypy/interpreter/pyparser/parsestring.py @@ -2,34 +2,39 @@ from pypy.interpreter import unicodehelper from pypy.rlib.rstring import StringBuilder -def parsestr(space, encoding, s, unicode_literals=False): - # compiler.transformer.Transformer.decode_literal depends on what - # might seem like minor details of this function -- changes here - # must be reflected there. +def parsestr(space, encoding, s, unicode_literal=False): + """Parses a string or unicode literal, and return a wrapped value. + + If encoding=iso8859-1, the source string is also in this encoding. + If encoding=None, the source string is ascii only. + In other cases, the source string is in utf-8 encoding. + + When a bytes string is returned, it will be encoded with the + original encoding. + + Yes, it's very inefficient. + Yes, CPython has very similar code. + """ # we use ps as "pointer to s" # q is the virtual last char index of the string ps = 0 quote = s[ps] rawmode = False - unicode = unicode_literals # string decoration handling - o = ord(quote) - isalpha = (o>=97 and o<=122) or (o>=65 and o<=90) - if isalpha or quote == '_': - if quote == 'b' or quote == 'B': - ps += 1 - quote = s[ps] - unicode = False - elif quote == 'u' or quote == 'U': - ps += 1 - quote = s[ps] - unicode = True - if quote == 'r' or quote == 'R': - ps += 1 - quote = s[ps] - rawmode = True + if quote == 'b' or quote == 'B': + ps += 1 + quote = s[ps] + unicode_literal = False + elif quote == 'u' or quote == 'U': + ps += 1 + quote = s[ps] + unicode_literal = True + if quote == 'r' or quote == 'R': + ps += 1 + quote = s[ps] + rawmode = True if quote != "'" and quote != '"': raise_app_valueerror(space, 'Internal error: parser passed unquoted literal') @@ -46,21 +51,28 @@ 'unmatched triple quotes in literal') q -= 2 - if unicode: # XXX Py_UnicodeFlag is ignored for now + if unicode_literal: # XXX Py_UnicodeFlag is ignored for now if encoding is None or encoding == "iso-8859-1": + # 'unicode_escape' expects latin-1 bytes, string is ready. buf = s bufp = ps bufq = q u = None else: - # "\XX" may become "\u005c\uHHLL" (12 bytes) + # String is utf8-encoded, but 'unicode_escape' expects + # latin-1; So multibyte sequences must be escaped. lis = [] # using a list to assemble the value end = q + # Worst case: "\XX" may become "\u005c\uHHLL" (12 bytes) while ps < end: if s[ps] == '\\': lis.append(s[ps]) ps += 1 if ord(s[ps]) & 0x80: + # A multibyte sequence will follow, it will be + # escaped like \u1234. To avoid confusion with + # the backslash we just wrote, we emit "\u005c" + # instead. lis.append("u005c") if ord(s[ps]) & 0x80: # XXX inefficient w, ps = decode_utf8(space, s, ps, end, "utf-16-be") @@ -86,13 +98,11 @@ need_encoding = (encoding is not None and encoding != "utf-8" and encoding != "iso-8859-1") - # XXX add strchr like interface to rtyper assert 0 <= ps <= q substr = s[ps : q] if rawmode or '\\' not in s[ps:]: if need_encoding: w_u = space.wrap(unicodehelper.PyUnicode_DecodeUTF8(space, substr)) - #w_v = space.wrap(space.unwrap(w_u).encode(encoding)) this works w_v = unicodehelper.PyUnicode_AsEncodedString(space, w_u, space.wrap(encoding)) return w_v else: diff --git a/pypy/interpreter/typedef.py b/pypy/interpreter/typedef.py --- a/pypy/interpreter/typedef.py +++ b/pypy/interpreter/typedef.py @@ -321,7 +321,7 @@ def user_setup(self, space, w_subtype): self.w__dict__ = space.newdict( - instance=True, classofinstance=w_subtype) + instance=True) base_user_setup(self, space, w_subtype) def setclass(self, space, w_subtype): diff --git a/pypy/jit/backend/llgraph/llimpl.py b/pypy/jit/backend/llgraph/llimpl.py --- a/pypy/jit/backend/llgraph/llimpl.py +++ b/pypy/jit/backend/llgraph/llimpl.py @@ -1796,6 +1796,7 @@ if specialize_as_constant: def specialize_call(self, hop): llvalue = func(hop.args_s[0].const) + hop.exception_cannot_occur() return hop.inputconst(lltype.typeOf(llvalue), llvalue) else: # specialize as direct_call @@ -1812,6 +1813,7 @@ sm = ootype._static_meth(FUNCTYPE, _name=func.__name__, _callable=func) cfunc = hop.inputconst(FUNCTYPE, sm) args_v = hop.inputargs(*hop.args_r) + hop.exception_is_here() return hop.genop('direct_call', [cfunc] + args_v, hop.r_result) diff --git a/pypy/module/_ssl/interp_ssl.py b/pypy/module/_ssl/interp_ssl.py --- a/pypy/module/_ssl/interp_ssl.py +++ b/pypy/module/_ssl/interp_ssl.py @@ -432,7 +432,8 @@ raise _ssl_seterror(self.space, self, length) try: # this is actually an immutable bytes sequence - return self.space.wrap(rffi.charp2str(buf_ptr[0])) + return self.space.wrap(rffi.charpsize2str(buf_ptr[0], + length)) finally: libssl_OPENSSL_free(buf_ptr[0]) else: diff --git a/pypy/module/array/interp_array.py b/pypy/module/array/interp_array.py --- a/pypy/module/array/interp_array.py +++ b/pypy/module/array/interp_array.py @@ -11,7 +11,7 @@ from pypy.objspace.std.register_all import register_all from pypy.rlib.rarithmetic import ovfcheck from pypy.rlib.unroll import unrolling_iterable -from pypy.rlib.objectmodel import specialize +from pypy.rlib.objectmodel import specialize, keepalive_until_here from pypy.rpython.lltypesystem import lltype, rffi @@ -145,18 +145,24 @@ unroll_typecodes = unrolling_iterable(types.keys()) class ArrayBuffer(RWBuffer): - def __init__(self, data, bytes): - self.data = data - self.len = bytes + def __init__(self, array): + self.array = array def getlength(self): - return self.len + return self.array.len * self.array.itemsize def getitem(self, index): - return self.data[index] + array = self.array + data = array._charbuf_start() + char = data[index] + array._charbuf_stop() + return char def setitem(self, index, char): - self.data[index] = char + array = self.array + data = array._charbuf_start() + data[index] = char + array._charbuf_stop() def make_array(mytype): @@ -278,9 +284,10 @@ oldlen = self.len new = len(s) / mytype.bytes self.setlen(oldlen + new) - cbuf = self.charbuf() + cbuf = self._charbuf_start() for i in range(len(s)): cbuf[oldlen * mytype.bytes + i] = s[i] + self._charbuf_stop() def fromlist(self, w_lst): s = self.len @@ -310,8 +317,11 @@ else: self.fromsequence(w_iterable) - def charbuf(self): - return rffi.cast(rffi.CCHARP, self.buffer) + def _charbuf_start(self): + return rffi.cast(rffi.CCHARP, self.buffer) + + def _charbuf_stop(self): + keepalive_until_here(self) def w_getitem(self, space, idx): item = self.buffer[idx] @@ -530,8 +540,10 @@ self.fromstring(space.str_w(w_s)) def array_tostring__Array(space, self): - cbuf = self.charbuf() - return self.space.wrap(rffi.charpsize2str(cbuf, self.len * mytype.bytes)) + cbuf = self._charbuf_start() + s = rffi.charpsize2str(cbuf, self.len * mytype.bytes) + self._charbuf_stop() + return self.space.wrap(s) def array_fromfile__Array_ANY_ANY(space, self, w_f, w_n): if not isinstance(w_f, W_File): @@ -613,8 +625,7 @@ # Misc methods def buffer__Array(space, self): - b = ArrayBuffer(self.charbuf(), self.len * mytype.bytes) - return space.wrap(b) + return space.wrap(ArrayBuffer(self)) def array_buffer_info__Array(space, self): w_ptr = space.wrap(rffi.cast(lltype.Unsigned, self.buffer)) @@ -649,7 +660,7 @@ raise OperationError(space.w_RuntimeError, space.wrap(msg)) if self.len == 0: return - bytes = self.charbuf() + bytes = self._charbuf_start() tmp = [bytes[0]] * mytype.bytes for start in range(0, self.len * mytype.bytes, mytype.bytes): stop = start + mytype.bytes - 1 @@ -657,6 +668,7 @@ tmp[i] = bytes[start + i] for i in range(mytype.bytes): bytes[stop - i] = tmp[i] + self._charbuf_stop() def repr__Array(space, self): if self.len == 0: diff --git a/pypy/module/array/test/test_array.py b/pypy/module/array/test/test_array.py --- a/pypy/module/array/test/test_array.py +++ b/pypy/module/array/test/test_array.py @@ -433,7 +433,25 @@ a = self.array('h', 'Hi') buf = buffer(a) assert buf[1] == 'i' - #raises(TypeError, buf.__setitem__, 1, 'o') + + def test_buffer_write(self): + a = self.array('c', 'hello') + buf = buffer(a) + print repr(buf) + try: + buf[3] = 'L' + except TypeError: + skip("buffer(array) returns a read-only buffer on CPython") + assert a.tostring() == 'helLo' + + def test_buffer_keepalive(self): + buf = buffer(self.array('c', 'text')) + assert buf[2] == 'x' + # + a = self.array('c', 'foobarbaz') + buf = buffer(a) + a.fromstring('some extra text') + assert buf[:] == 'foobarbazsome extra text' def test_list_methods(self): assert repr(self.array('i')) == "array('i')" diff --git a/pypy/module/cpyext/bufferobject.py b/pypy/module/cpyext/bufferobject.py --- a/pypy/module/cpyext/bufferobject.py +++ b/pypy/module/cpyext/bufferobject.py @@ -2,8 +2,10 @@ from pypy.module.cpyext.api import ( cpython_api, Py_ssize_t, cpython_struct, bootstrap_function, PyObjectFields, PyObject) -from pypy.module.cpyext.pyobject import make_typedescr, Py_DecRef +from pypy.module.cpyext.pyobject import make_typedescr, Py_DecRef, make_ref from pypy.interpreter.buffer import Buffer, StringBuffer, SubBuffer +from pypy.interpreter.error import OperationError +from pypy.module.array.interp_array import ArrayBuffer PyBufferObjectStruct = lltype.ForwardReference() @@ -41,26 +43,38 @@ py_buf.c_b_offset = w_obj.offset w_obj = w_obj.buffer + # If w_obj already allocated a fixed buffer, use it, and keep a + # reference to w_obj. + # Otherwise, b_base stays NULL, and we own the b_ptr. + if isinstance(w_obj, StringBuffer): - py_buf.c_b_base = rffi.cast(PyObject, 0) # space.wrap(w_obj.value) - py_buf.c_b_ptr = rffi.cast(rffi.VOIDP, rffi.str2charp(w_obj.as_str())) + py_buf.c_b_base = lltype.nullptr(PyObject.TO) + py_buf.c_b_ptr = rffi.cast(rffi.VOIDP, rffi.str2charp(w_obj.value)) + py_buf.c_b_size = w_obj.getlength() + elif isinstance(w_obj, ArrayBuffer): + w_base = w_obj.array + py_buf.c_b_base = make_ref(space, w_base) + py_buf.c_b_ptr = rffi.cast(rffi.VOIDP, w_obj.array._charbuf_start()) py_buf.c_b_size = w_obj.getlength() else: - raise Exception("Fail fail fail fail fail") + raise OperationError(space.w_NotImplementedError, space.wrap( + "buffer flavor not supported")) def buffer_realize(space, py_obj): """ Creates the buffer in the PyPy interpreter from a cpyext representation. """ - raise Exception("realize fail fail fail") - + raise OperationError(space.w_NotImplementedError, space.wrap( + "Don't know how to realize a buffer")) @cpython_api([PyObject], lltype.Void, external=False) def buffer_dealloc(space, py_obj): py_buf = rffi.cast(PyBufferObject, py_obj) - Py_DecRef(space, py_buf.c_b_base) - rffi.free_charp(rffi.cast(rffi.CCHARP, py_buf.c_b_ptr)) + if py_buf.c_b_base: + Py_DecRef(space, py_buf.c_b_base) + else: + rffi.free_charp(rffi.cast(rffi.CCHARP, py_buf.c_b_ptr)) from pypy.module.cpyext.object import PyObject_dealloc PyObject_dealloc(space, py_obj) diff --git a/pypy/module/cpyext/include/patchlevel.h b/pypy/module/cpyext/include/patchlevel.h --- a/pypy/module/cpyext/include/patchlevel.h +++ b/pypy/module/cpyext/include/patchlevel.h @@ -29,7 +29,7 @@ #define PY_VERSION "2.7.2" /* PyPy version as a string */ -#define PYPY_VERSION "1.8.1" +#define PYPY_VERSION "1.9.1" /* Subversion Revision number of this file (not of the repository). * Empty since Mercurial migration. */ diff --git a/pypy/module/cpyext/pyerrors.py b/pypy/module/cpyext/pyerrors.py --- a/pypy/module/cpyext/pyerrors.py +++ b/pypy/module/cpyext/pyerrors.py @@ -2,6 +2,7 @@ from pypy.rpython.lltypesystem import rffi, lltype from pypy.interpreter.error import OperationError +from pypy.interpreter import pytraceback from pypy.module.cpyext.api import cpython_api, CANNOT_FAIL, CONST_STRING from pypy.module.exceptions.interp_exceptions import W_RuntimeWarning from pypy.module.cpyext.pyobject import ( @@ -315,3 +316,65 @@ It may be called without holding the interpreter lock.""" space.check_signal_action.set_interrupt() + at cpython_api([PyObjectP, PyObjectP, PyObjectP], lltype.Void) +def PyErr_GetExcInfo(space, ptype, pvalue, ptraceback): + """---Cython extension--- + + Retrieve the exception info, as known from ``sys.exc_info()``. This + refers to an exception that was already caught, not to an exception + that was freshly raised. Returns new references for the three + objects, any of which may be *NULL*. Does not modify the exception + info state. + + .. note:: + + This function is not normally used by code that wants to handle + exceptions. Rather, it can be used when code needs to save and + restore the exception state temporarily. Use + :c:func:`PyErr_SetExcInfo` to restore or clear the exception + state. + """ + ec = space.getexecutioncontext() + operror = ec.sys_exc_info() + if operror: + ptype[0] = make_ref(space, operror.w_type) + pvalue[0] = make_ref(space, operror.get_w_value(space)) + ptraceback[0] = make_ref(space, space.wrap(operror.get_traceback())) + else: + ptype[0] = lltype.nullptr(PyObject.TO) + pvalue[0] = lltype.nullptr(PyObject.TO) + ptraceback[0] = lltype.nullptr(PyObject.TO) + + at cpython_api([PyObject, PyObject, PyObject], lltype.Void) +def PyErr_SetExcInfo(space, w_type, w_value, w_traceback): + """---Cython extension--- + + Set the exception info, as known from ``sys.exc_info()``. This refers + to an exception that was already caught, not to an exception that was + freshly raised. This function steals the references of the arguments. + To clear the exception state, pass *NULL* for all three arguments. + For general rules about the three arguments, see :c:func:`PyErr_Restore`. + + .. note:: + + This function is not normally used by code that wants to handle + exceptions. Rather, it can be used when code needs to save and + restore the exception state temporarily. Use + :c:func:`PyErr_GetExcInfo` to read the exception state. + """ + if w_value is None or space.is_w(w_value, space.w_None): + operror = None + else: + tb = None + if w_traceback is not None: + try: + tb = pytraceback.check_traceback(space, w_traceback, '?') + except OperationError: # catch and ignore bogus objects + pass + operror = OperationError(w_type, w_value, tb) + # + ec = space.getexecutioncontext() + ec.set_sys_exc_info(operror) + Py_DecRef(space, w_type) + Py_DecRef(space, w_value) + Py_DecRef(space, w_traceback) diff --git a/pypy/module/cpyext/slotdefs.py b/pypy/module/cpyext/slotdefs.py --- a/pypy/module/cpyext/slotdefs.py +++ b/pypy/module/cpyext/slotdefs.py @@ -167,14 +167,16 @@ if rffi.cast(lltype.Signed, res) == -1: space.fromcache(State).check_and_raise_exception(always=True) +# Warning, confusing function name (like CPython). Used only for sq_contains. def wrap_objobjproc(space, w_self, w_args, func): func_target = rffi.cast(objobjproc, func) check_num_args(space, w_args, 1) w_value, = space.fixedview(w_args) res = generic_cpy_call(space, func_target, w_self, w_value) - if rffi.cast(lltype.Signed, res) == -1: + res = rffi.cast(lltype.Signed, res) + if res == -1: space.fromcache(State).check_and_raise_exception(always=True) - return space.wrap(res) + return space.wrap(bool(res)) def wrap_objobjargproc(space, w_self, w_args, func): func_target = rffi.cast(objobjargproc, func) @@ -183,7 +185,7 @@ res = generic_cpy_call(space, func_target, w_self, w_key, w_value) if rffi.cast(lltype.Signed, res) == -1: space.fromcache(State).check_and_raise_exception(always=True) - return space.wrap(res) + return space.w_None def wrap_delitem(space, w_self, w_args, func): func_target = rffi.cast(objobjargproc, func) diff --git a/pypy/module/cpyext/test/foo.c b/pypy/module/cpyext/test/foo.c --- a/pypy/module/cpyext/test/foo.c +++ b/pypy/module/cpyext/test/foo.c @@ -176,6 +176,8 @@ {NULL} /* Sentinel */ }; +PyDoc_STRVAR(foo_doc, "foo is for testing."); + static PyTypeObject footype = { PyVarObject_HEAD_INIT(NULL, 0) "foo.foo", /*tp_name*/ @@ -198,7 +200,7 @@ (setattrofunc)foo_setattro, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ + foo_doc, /*tp_doc*/ 0, /*tp_traverse*/ 0, /*tp_clear*/ 0, /*tp_richcompare*/ diff --git a/pypy/module/cpyext/test/test_bufferobject.py b/pypy/module/cpyext/test/test_bufferobject.py --- a/pypy/module/cpyext/test/test_bufferobject.py +++ b/pypy/module/cpyext/test/test_bufferobject.py @@ -48,3 +48,17 @@ ]) b = module.buffer_new() raises(AttributeError, getattr, b, 'x') + + def test_array_buffer(self): + module = self.import_extension('foo', [ + ("roundtrip", "METH_O", + """ + PyBufferObject *buf = (PyBufferObject *)args; + return PyString_FromStringAndSize(buf->b_ptr, buf->b_size); + """), + ]) + import array + a = array.array('c', 'text') + b = buffer(a) + assert module.roundtrip(b) == 'text' + diff --git a/pypy/module/cpyext/test/test_pyerrors.py b/pypy/module/cpyext/test/test_pyerrors.py --- a/pypy/module/cpyext/test/test_pyerrors.py +++ b/pypy/module/cpyext/test/test_pyerrors.py @@ -218,3 +218,51 @@ assert e.filename == "blyf" assert e.errno == errno.EBADF assert e.strerror == os.strerror(errno.EBADF) + + def test_GetSetExcInfo(self): + import sys + module = self.import_extension('foo', [ + ("getset_exc_info", "METH_VARARGS", + r''' + PyObject *type, *val, *tb; + PyObject *new_type, *new_val, *new_tb; + PyObject *result; + + if (!PyArg_ParseTuple(args, "OOO", &new_type, &new_val, &new_tb)) + return NULL; + + PyErr_GetExcInfo(&type, &val, &tb); + + Py_INCREF(new_type); + Py_INCREF(new_val); + Py_INCREF(new_tb); + PyErr_SetExcInfo(new_type, new_val, new_tb); + + result = Py_BuildValue("OOO", + type ? type : Py_None, + val ? val : Py_None, + tb ? tb : Py_None); + Py_XDECREF(type); + Py_XDECREF(val); + Py_XDECREF(tb); + return result; + ''' + ), + ]) + try: + raise ValueError(5) + except ValueError, old_exc: + new_exc = TypeError("TEST") + orig_sys_exc_info = sys.exc_info() + orig_exc_info = module.getset_exc_info(new_exc.__class__, + new_exc, None) + new_sys_exc_info = sys.exc_info() + new_exc_info = module.getset_exc_info(*orig_exc_info) + reset_sys_exc_info = sys.exc_info() + + assert orig_exc_info[0] is old_exc.__class__ + assert orig_exc_info[1] is old_exc + assert orig_exc_info == orig_sys_exc_info + assert orig_exc_info == reset_sys_exc_info + assert new_exc_info == (new_exc.__class__, new_exc, None) + assert new_exc_info == new_sys_exc_info diff --git a/pypy/module/cpyext/test/test_typeobject.py b/pypy/module/cpyext/test/test_typeobject.py --- a/pypy/module/cpyext/test/test_typeobject.py +++ b/pypy/module/cpyext/test/test_typeobject.py @@ -20,6 +20,7 @@ assert type(obj) is module.fooType print "type of obj has type", type(type(obj)) print "type of type of obj has type", type(type(type(obj))) + assert module.fooType.__doc__ == "foo is for testing." def test_typeobject_method_descriptor(self): module = self.import_module(name='foo') @@ -414,8 +415,11 @@ static int mp_ass_subscript(PyObject *self, PyObject *key, PyObject *value) { - PyErr_SetNone(PyExc_ZeroDivisionError); - return -1; + if (PyInt_Check(key)) { + PyErr_SetNone(PyExc_ZeroDivisionError); + return -1; + } + return 0; } PyMappingMethods tp_as_mapping; static PyTypeObject Foo_Type = { @@ -425,6 +429,36 @@ ''') obj = module.new_obj() raises(ZeroDivisionError, obj.__setitem__, 5, None) + res = obj.__setitem__('foo', None) + assert res is None + + def test_sq_contains(self): + module = self.import_extension('foo', [ + ("new_obj", "METH_NOARGS", + ''' + PyObject *obj; + Foo_Type.tp_as_sequence = &tp_as_sequence; + tp_as_sequence.sq_contains = sq_contains; + if (PyType_Ready(&Foo_Type) < 0) return NULL; + obj = PyObject_New(PyObject, &Foo_Type); + return obj; + ''' + )], + ''' + static int + sq_contains(PyObject *self, PyObject *value) + { + return 42; + } + PySequenceMethods tp_as_sequence; + static PyTypeObject Foo_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "foo.foo", + }; + ''') + obj = module.new_obj() + res = "foo" in obj + assert res is True def test_tp_iter(self): module = self.import_extension('foo', [ diff --git a/pypy/module/cpyext/typeobject.py b/pypy/module/cpyext/typeobject.py --- a/pypy/module/cpyext/typeobject.py +++ b/pypy/module/cpyext/typeobject.py @@ -307,6 +307,8 @@ if not space.is_true(space.issubtype(self, space.w_type)): self.flag_cpytype = True self.flag_heaptype = False + if pto.c_tp_doc: + self.w_doc = space.wrap(rffi.charp2str(pto.c_tp_doc)) @bootstrap_function def init_typeobject(space): @@ -624,7 +626,6 @@ Creates an interpreter type from a PyTypeObject structure. """ # missing: - # setting __doc__ if not defined and tp_doc defined # inheriting tp_as_* slots # unsupported: # tp_mro, tp_subclasses diff --git a/pypy/module/micronumpy/__init__.py b/pypy/module/micronumpy/__init__.py --- a/pypy/module/micronumpy/__init__.py +++ b/pypy/module/micronumpy/__init__.py @@ -29,6 +29,7 @@ 'flatiter': 'interp_numarray.W_FlatIterator', 'isna': 'interp_numarray.isna', 'concatenate': 'interp_numarray.concatenate', + 'repeat': 'interp_numarray.repeat', 'set_string_function': 'appbridge.set_string_function', @@ -99,9 +100,12 @@ ("exp2", "exp2"), ("expm1", "expm1"), ("fabs", "fabs"), + ("fmax", "fmax"), + ("fmin", "fmin"), ("fmod", "fmod"), ("floor", "floor"), ("ceil", "ceil"), + ("trunc", "trunc"), ("greater", "greater"), ("greater_equal", "greater_equal"), ("less", "less"), @@ -122,12 +126,16 @@ ("sinh", "sinh"), ("subtract", "subtract"), ('sqrt', 'sqrt'), + ('square', 'square'), ("tan", "tan"), ("tanh", "tanh"), ('bitwise_and', 'bitwise_and'), ('bitwise_or', 'bitwise_or'), ('bitwise_xor', 'bitwise_xor'), ('bitwise_not', 'invert'), + ('left_shift', 'left_shift'), + ('right_shift', 'right_shift'), + ('invert', 'invert'), ('isnan', 'isnan'), ('isinf', 'isinf'), ('isneginf', 'isneginf'), diff --git a/pypy/module/micronumpy/interp_numarray.py b/pypy/module/micronumpy/interp_numarray.py --- a/pypy/module/micronumpy/interp_numarray.py +++ b/pypy/module/micronumpy/interp_numarray.py @@ -673,6 +673,10 @@ def compute_first_step(self, sig, frame): pass + @unwrap_spec(repeats=int) + def descr_repeat(self, space, repeats, w_axis=None): + return repeat(space, self, repeats, w_axis) + def convert_to_array(space, w_obj): if isinstance(w_obj, BaseArray): return w_obj @@ -1261,6 +1265,31 @@ return convert_to_array(space, w_obj2).descr_dot(space, w_arr) return w_arr.descr_dot(space, w_obj2) + at unwrap_spec(repeats=int) +def repeat(space, w_arr, repeats, w_axis=None): + arr = convert_to_array(space, w_arr) + if space.is_w(w_axis, space.w_None): + arr = arr.descr_flatten(space).get_concrete() + orig_size = arr.shape[0] + shape = [arr.shape[0] * repeats] + res = W_NDimArray(shape, arr.find_dtype()) + for i in range(repeats): + Chunks([Chunk(i, shape[0] - repeats + i, repeats, + orig_size)]).apply(res).setslice(space, arr) + else: + arr = arr.get_concrete() + axis = space.int_w(w_axis) + shape = arr.shape[:] + chunks = [Chunk(0, i, 1, i) for i in shape] + orig_size = shape[axis] + shape[axis] *= repeats + res = W_NDimArray(shape, arr.find_dtype()) + for i in range(repeats): + chunks[axis] = Chunk(i, shape[axis] - repeats + i, repeats, + orig_size) + Chunks(chunks).apply(res).setslice(space, arr) + return res + @unwrap_spec(axis=int) def concatenate(space, w_args, axis=0): args_w = space.listview(w_args) @@ -1386,6 +1415,7 @@ tolist = interp2app(BaseArray.descr_tolist), take = interp2app(BaseArray.descr_take), compress = interp2app(BaseArray.descr_compress), + repeat = interp2app(BaseArray.descr_repeat), ) diff --git a/pypy/module/micronumpy/interp_ufuncs.py b/pypy/module/micronumpy/interp_ufuncs.py --- a/pypy/module/micronumpy/interp_ufuncs.py +++ b/pypy/module/micronumpy/interp_ufuncs.py @@ -3,9 +3,11 @@ from pypy.interpreter.gateway import interp2app, unwrap_spec, NoneNotWrapped from pypy.interpreter.typedef import TypeDef, GetSetProperty, interp_attrproperty from pypy.module.micronumpy import interp_boxes, interp_dtype, support, loop +from pypy.rlib import jit from pypy.rlib.rarithmetic import LONG_BIT from pypy.tool.sourcetools import func_with_new_name + class W_Ufunc(Wrappable): _attrs_ = ["name", "promote_to_float", "promote_bools", "identity"] _immutable_fields_ = ["promote_to_float", "promote_bools", "name"] @@ -28,7 +30,7 @@ return self.identity def descr_call(self, space, __args__): - from interp_numarray import BaseArray + from interp_numarray import BaseArray args_w, kwds_w = __args__.unpack() # it occurs to me that we don't support any datatypes that # require casting, change it later when we do @@ -179,7 +181,7 @@ elif out.shape != shape: raise operationerrfmt(space.w_ValueError, 'output parameter shape mismatch, expecting [%s]' + - ' , got [%s]', + ' , got [%s]', ",".join([str(x) for x in shape]), ",".join([str(x) for x in out.shape]), ) @@ -204,7 +206,7 @@ else: arr = ReduceArray(self.func, self.name, self.identity, obj, dtype) val = loop.compute(arr) - return val + return val def do_axis_reduce(self, obj, dtype, axis, result): from pypy.module.micronumpy.interp_numarray import AxisReduce @@ -253,7 +255,7 @@ if isinstance(w_obj, Scalar): arr = self.func(calc_dtype, w_obj.value.convert_to(calc_dtype)) if isinstance(out,Scalar): - out.value=arr + out.value = arr elif isinstance(out, BaseArray): out.fill(space, arr) else: @@ -265,7 +267,7 @@ if not broadcast_shape or broadcast_shape != out.shape: raise operationerrfmt(space.w_ValueError, 'output parameter shape mismatch, could not broadcast [%s]' + - ' to [%s]', + ' to [%s]', ",".join([str(x) for x in w_obj.shape]), ",".join([str(x) for x in out.shape]), ) @@ -292,10 +294,11 @@ self.func = func self.comparison_func = comparison_func + @jit.unroll_safe def call(self, space, args_w): from pypy.module.micronumpy.interp_numarray import (Call2, convert_to_array, Scalar, shape_agreement, BaseArray) - if len(args_w)>2: + if len(args_w) > 2: [w_lhs, w_rhs, w_out] = args_w else: [w_lhs, w_rhs] = args_w @@ -326,7 +329,7 @@ w_rhs.value.convert_to(calc_dtype) ) if isinstance(out,Scalar): - out.value=arr + out.value = arr elif isinstance(out, BaseArray): out.fill(space, arr) else: @@ -337,7 +340,7 @@ if out and out.shape != shape_agreement(space, new_shape, out.shape): raise operationerrfmt(space.w_ValueError, 'output parameter shape mismatch, could not broadcast [%s]' + - ' to [%s]', + ' to [%s]', ",".join([str(x) for x in new_shape]), ",".join([str(x) for x in out.shape]), ) @@ -347,7 +350,6 @@ w_lhs.add_invalidates(w_res) w_rhs.add_invalidates(w_res) if out: - #out.add_invalidates(w_res) #causes a recursion loop w_res.get_concrete() return w_res @@ -539,14 +541,18 @@ ("reciprocal", "reciprocal", 1), ("fabs", "fabs", 1, {"promote_to_float": True}), + ("fmax", "fmax", 2, {"promote_to_float": True}), + ("fmin", "fmin", 2, {"promote_to_float": True}), ("fmod", "fmod", 2, {"promote_to_float": True}), ("floor", "floor", 1, {"promote_to_float": True}), ("ceil", "ceil", 1, {"promote_to_float": True}), + ("trunc", "trunc", 1, {"promote_to_float": True}), ("exp", "exp", 1, {"promote_to_float": True}), ("exp2", "exp2", 1, {"promote_to_float": True}), ("expm1", "expm1", 1, {"promote_to_float": True}), ('sqrt', 'sqrt', 1, {'promote_to_float': True}), + ('square', 'square', 1, {'promote_to_float': True}), ("sin", "sin", 1, {"promote_to_float": True}), ("cos", "cos", 1, {"promote_to_float": True}), diff --git a/pypy/module/micronumpy/signature.py b/pypy/module/micronumpy/signature.py --- a/pypy/module/micronumpy/signature.py +++ b/pypy/module/micronumpy/signature.py @@ -107,6 +107,10 @@ arr.compute_first_step(self, f) return f + def debug_repr(self): + # should be overridden, but in case it isn't, provide a default + return str(self) + class ConcreteSignature(Signature): _immutable_fields_ = ['dtype'] @@ -207,7 +211,7 @@ def _create_iter(self, iterlist, arraylist, arr, transforms): from pypy.module.micronumpy.interp_numarray import VirtualSlice assert isinstance(arr, VirtualSlice) - transforms = transforms + [ViewTransform(arr.chunks)] + transforms = [ViewTransform(arr.chunks)] + transforms self.child._create_iter(iterlist, arraylist, arr.child, transforms) def eval(self, frame, arr): @@ -215,6 +219,9 @@ assert isinstance(arr, VirtualSlice) return self.child.eval(frame, arr.child) + def debug_repr(self): + return 'VirtualSlice(%s)' % self.child.debug_repr() + class Call1(Signature): _immutable_fields_ = ['unfunc', 'name', 'child', 'res', 'dtype'] @@ -270,7 +277,7 @@ from pypy.module.micronumpy.interp_numarray import Call1 assert isinstance(arr, Call1) - vtransforms = transforms + [BroadcastTransform(arr.values.shape)] + vtransforms = [BroadcastTransform(arr.values.shape)] + transforms self.child._create_iter(iterlist, arraylist, arr.values, vtransforms) self.res._create_iter(iterlist, arraylist, arr.res, transforms) @@ -348,7 +355,7 @@ from pypy.module.micronumpy.interp_numarray import ResultArray assert isinstance(arr, ResultArray) - rtransforms = transforms + [BroadcastTransform(arr.left.shape)] + rtransforms = [BroadcastTransform(arr.left.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, transforms) self.right._create_iter(iterlist, arraylist, arr.right, rtransforms) @@ -375,7 +382,7 @@ from pypy.module.micronumpy.interp_numarray import Call2 assert isinstance(arr, Call2) - ltransforms = transforms + [BroadcastTransform(arr.shape)] + ltransforms = [BroadcastTransform(arr.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, ltransforms) self.right._create_iter(iterlist, arraylist, arr.right, transforms) @@ -388,7 +395,7 @@ from pypy.module.micronumpy.interp_numarray import Call2 assert isinstance(arr, Call2) - rtransforms = transforms + [BroadcastTransform(arr.shape)] + rtransforms = [BroadcastTransform(arr.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, transforms) self.right._create_iter(iterlist, arraylist, arr.right, rtransforms) @@ -401,8 +408,8 @@ from pypy.module.micronumpy.interp_numarray import Call2 assert isinstance(arr, Call2) - rtransforms = transforms + [BroadcastTransform(arr.shape)] - ltransforms = transforms + [BroadcastTransform(arr.shape)] + rtransforms = [BroadcastTransform(arr.shape)] + transforms + ltransforms = [BroadcastTransform(arr.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, ltransforms) self.right._create_iter(iterlist, arraylist, arr.right, rtransforms) @@ -424,7 +431,7 @@ frame.cur_value = self.binfunc(self.calc_dtype, frame.cur_value, rval) def debug_repr(self): - return 'ReduceSig(%s)' % (self.name, self.right.debug_repr()) + return 'ReduceSig(%s, %s)' % (self.name, self.right.debug_repr()) class SliceloopSignature(Call2): def eval(self, frame, arr): @@ -448,7 +455,7 @@ from pypy.module.micronumpy.interp_numarray import SliceArray assert isinstance(arr, SliceArray) - rtransforms = transforms + [BroadcastTransform(arr.shape)] + rtransforms = [BroadcastTransform(arr.shape)] + transforms self.left._create_iter(iterlist, arraylist, arr.left, transforms) self.right._create_iter(iterlist, arraylist, arr.right, rtransforms) diff --git a/pypy/module/micronumpy/strides.py b/pypy/module/micronumpy/strides.py --- a/pypy/module/micronumpy/strides.py +++ b/pypy/module/micronumpy/strides.py @@ -1,6 +1,7 @@ from pypy.rlib import jit from pypy.interpreter.error import OperationError + at jit.look_inside_iff(lambda chunks: jit.isconstant(len(chunks))) def enumerate_chunks(chunks): result = [] i = -1 @@ -85,9 +86,9 @@ space.isinstance_w(w_item_or_slice, space.w_slice)): raise OperationError(space.w_IndexError, space.wrap('unsupported iterator index')) - + start, stop, step, lngth = space.decode_index4(w_item_or_slice, size) - + coords = [0] * len(shape) i = start if order == 'C': diff --git a/pypy/module/micronumpy/support.py b/pypy/module/micronumpy/support.py --- a/pypy/module/micronumpy/support.py +++ b/pypy/module/micronumpy/support.py @@ -1,5 +1,9 @@ +from pypy.rlib import jit + + + at jit.unroll_safe def product(s): i = 1 for x in s: i *= x - return i \ No newline at end of file + return i diff --git a/pypy/module/micronumpy/test/test_numarray.py b/pypy/module/micronumpy/test/test_numarray.py --- a/pypy/module/micronumpy/test/test_numarray.py +++ b/pypy/module/micronumpy/test/test_numarray.py @@ -395,11 +395,19 @@ assert a[3] == 0. def test_newaxis(self): - from _numpypy import array + import math + from _numpypy import array, cos, zeros from numpypy.core.numeric import newaxis a = array(range(5)) b = array([range(5)]) assert (a[newaxis] == b).all() + a = array(range(3)) + b = array([1, 3]) + expected = zeros((3, 2)) + for x in range(3): + for y in range(2): + expected[x, y] = math.cos(a[x]) * math.cos(b[y]) + assert ((cos(a)[:,newaxis] * cos(b).T) == expected).all() def test_newaxis_slice(self): from _numpypy import array @@ -1338,6 +1346,10 @@ dims_disagree = raises(ValueError, concatenate, (a1, b1), axis=0) assert str(dims_disagree.value) == \ "array dimensions must agree except for axis being concatenated" + a = array([1, 2, 3, 4, 5, 6]) + a = (a + a)[::2] + b = concatenate((a[:3], a[-3:])) + assert (b == [2, 6, 10, 2, 6, 10]).all() def test_std(self): from _numpypy import array @@ -1387,6 +1399,16 @@ assert (ones(1) + ones(1)).nbytes == 8 assert array(3.0).nbytes == 8 + def test_repeat(self): + from _numpypy import repeat, array + assert (repeat([[1, 2], [3, 4]], 3) == [1, 1, 1, 2, 2, 2, + 3, 3, 3, 4, 4, 4]).all() + assert (repeat([[1, 2], [3, 4]], 2, axis=0) == [[1, 2], [1, 2], [3, 4], + [3, 4]]).all() + assert (repeat([[1, 2], [3, 4]], 2, axis=1) == [[1, 1, 2, 2], [3, 3, + 4, 4]]).all() + assert (array([1, 2]).repeat(2) == array([1, 1, 2, 2])).all() + class AppTestMultiDim(BaseNumpyAppTest): def test_init(self): diff --git a/pypy/module/micronumpy/test/test_ufuncs.py b/pypy/module/micronumpy/test/test_ufuncs.py --- a/pypy/module/micronumpy/test/test_ufuncs.py +++ b/pypy/module/micronumpy/test/test_ufuncs.py @@ -135,6 +135,38 @@ assert fabs(float('-inf')) == float('inf') assert isnan(fabs(float('nan'))) + def test_fmax(self): + from _numpypy import fmax + import math + + nnan, nan, inf, ninf = float('-nan'), float('nan'), float('inf'), float('-inf') + + a = [ninf, -5, 0, 5, inf] + assert (fmax(a, [ninf]*5) == a).all() + assert (fmax(a, [inf]*5) == [inf]*5).all() + assert (fmax(a, [1]*5) == [1, 1, 1, 5, inf]).all() + assert math.isnan(fmax(nan, 0)) + assert math.isnan(fmax(0, nan)) + assert math.isnan(fmax(nan, nan)) + # The numpy docs specify that the FIRST NaN should be used if both are NaN + assert math.copysign(1.0, fmax(nnan, nan)) == -1.0 + + def test_fmin(self): + from _numpypy import fmin + import math + + nnan, nan, inf, ninf = float('-nan'), float('nan'), float('inf'), float('-inf') + + a = [ninf, -5, 0, 5, inf] + assert (fmin(a, [ninf]*5) == [ninf]*5).all() + assert (fmin(a, [inf]*5) == a).all() + assert (fmin(a, [1]*5) == [ninf, -5, 0, 1, 1]).all() + assert math.isnan(fmin(nan, 0)) + assert math.isnan(fmin(0, nan)) + assert math.isnan(fmin(nan, nan)) + # The numpy docs specify that the FIRST NaN should be used if both are NaN + assert math.copysign(1.0, fmin(nnan, nan)) == -1.0 + def test_fmod(self): from _numpypy import fmod import math @@ -221,24 +253,17 @@ for i in range(3): assert c[i] == a[i] - b[i] - def test_floorceil(self): - from _numpypy import array, floor, ceil + def test_floorceiltrunc(self): + from _numpypy import array, floor, ceil, trunc import math - reference = [-2.0, -2.0, -1.0, 0.0, 1.0, 1.0, 0] - a = array([-1.4, -1.5, -1.0, 0.0, 1.0, 1.4, 0.5]) - b = floor(a) - for i in range(5): - assert b[i] == reference[i] - reference = [-1.0, -1.0, -1.0, 0.0, 1.0, 2.0, 1.0] - a = array([-1.4, -1.5, -1.0, 0.0, 1.0, 1.4, 0.5]) - b = ceil(a) - assert (reference == b).all() - inf = float("inf") - data = [1.5, 2.9999, -1.999, inf] - results = [math.floor(x) for x in data] - assert (floor(data) == results).all() - results = [math.ceil(x) for x in data] - assert (ceil(data) == results).all() + ninf, inf = float("-inf"), float("inf") + a = array([ninf, -1.4, -1.5, -1.0, 0.0, 1.0, 1.4, 0.5, inf]) + assert ([ninf, -2.0, -2.0, -1.0, 0.0, 1.0, 1.0, 0.0, inf] == floor(a)).all() + assert ([ninf, -1.0, -1.0, -1.0, 0.0, 1.0, 2.0, 1.0, inf] == ceil(a)).all() + assert ([ninf, -1.0, -1.0, -1.0, 0.0, 1.0, 1.0, 0.0, inf] == trunc(a)).all() + assert all([math.isnan(f(float("nan"))) for f in floor, ceil, trunc]) + assert all([math.copysign(1, f(float("nan"))) == 1 for f in floor, ceil, trunc]) + assert all([math.copysign(1, f(float("-nan"))) == -1 for f in floor, ceil, trunc]) def test_copysign(self): from _numpypy import array, copysign @@ -455,6 +480,19 @@ assert math.isnan(sqrt(-1)) assert math.isnan(sqrt(nan)) + def test_square(self): + import math + from _numpypy import square + + nan, inf, ninf = float("nan"), float("inf"), float("-inf") + + assert math.isnan(square(nan)) + assert math.isinf(square(inf)) + assert math.isinf(square(ninf)) + assert square(ninf) > 0 + assert [square(x) for x in range(-5, 5)] == [x*x for x in range(-5, 5)] + assert math.isinf(square(1e300)) + def test_radians(self): import math from _numpypy import radians, array @@ -546,10 +584,18 @@ raises(TypeError, 'array([1.0]) & 1') def test_unary_bitops(self): - from _numpypy import bitwise_not, array + from _numpypy import bitwise_not, invert, array a = array([1, 2, 3, 4]) assert (~a == [-2, -3, -4, -5]).all() assert (bitwise_not(a) == ~a).all() + assert (invert(a) == ~a).all() + + def test_shift(self): + from _numpypy import left_shift, right_shift + import sys + + assert (left_shift([5, 1], [2, 31]) == [20, 2**31]).all() + assert (right_shift(10, range(5)) == [10, 5, 2, 1, 0]).all() def test_comparisons(self): import operator diff --git a/pypy/module/micronumpy/types.py b/pypy/module/micronumpy/types.py --- a/pypy/module/micronumpy/types.py +++ b/pypy/module/micronumpy/types.py @@ -631,6 +631,22 @@ return math.fabs(v) @simple_binary_op + def fmax(self, v1, v2): + if math.isnan(v1): + return v1 + elif math.isnan(v2): + return v2 + return max(v1, v2) + + @simple_binary_op + def fmin(self, v1, v2): + if math.isnan(v1): + return v1 + elif math.isnan(v2): + return v2 + return min(v1, v2) + + @simple_binary_op def fmod(self, v1, v2): try: return math.fmod(v1, v2) @@ -652,6 +668,13 @@ return math.ceil(v) @simple_unary_op + def trunc(self, v): + if v < 0: + return math.ceil(v) + else: + return math.floor(v) + + @simple_unary_op def exp(self, v): try: return math.exp(v) @@ -741,6 +764,10 @@ except ValueError: return rfloat.NAN + @simple_unary_op + def square(self, v): + return v*v + @raw_unary_op def isnan(self, v): return rfloat.isnan(v) diff --git a/pypy/module/pypyjit/test_pypy_c/test_misc.py b/pypy/module/pypyjit/test_pypy_c/test_misc.py --- a/pypy/module/pypyjit/test_pypy_c/test_misc.py +++ b/pypy/module/pypyjit/test_pypy_c/test_misc.py @@ -212,7 +212,7 @@ i19 = int_add(i12, 1) setfield_gc(p9, i19, descr=) guard_nonnull_class(p17, 146982464, descr=...) - i21 = getfield_gc(p17, descr=) + i21 = getfield_gc(p17, descr=) i23 = int_lt(0, i21) guard_true(i23, descr=...) i24 = getfield_gc(p17, descr=) @@ -351,3 +351,23 @@ # the following assertion fails if the loop was cancelled due # to "abort: vable escape" assert len(log.loops_by_id("eval")) == 1 + + def test_sys_exc_info(self): + def main(): + i = 1 + lst = [i] + while i < 1000: + try: + return lst[i] + except: + e = sys.exc_info()[1] # ID: exc_info + if not isinstance(e, IndexError): + raise + i += 1 + return 42 + + log = self.run(main) + assert log.result == 42 + # the following assertion fails if the loop was cancelled due + # to "abort: vable escape" + assert len(log.loops_by_id("exc_info")) == 1 diff --git a/pypy/module/pypyjit/test_pypy_c/test_string.py b/pypy/module/pypyjit/test_pypy_c/test_string.py --- a/pypy/module/pypyjit/test_pypy_c/test_string.py +++ b/pypy/module/pypyjit/test_pypy_c/test_string.py @@ -198,3 +198,37 @@ i49 = call(ConstClass(_ll_2_str_eq_nonnull__rpy_stringPtr_rpy_stringPtr), p35, ConstPtr(ptr48), descr=) guard_value(i49, 1, descr=...) ''') + + def test_remove_duplicate_method_calls(self): + def main(n): + lst = [] + for i in range(n): + s = 'Hello %d' % i + t = s.lower() # ID: callone + u = s.lower() # ID: calltwo + lst.append(t) + lst.append(u) + return len(','.join(lst)) + log = self.run(main, [1000]) + assert log.result == main(1000) + loops = log.loops_by_filename(self.filepath) + loop, = loops + loop.match_by_id('callone', ''' + p114 = call(ConstClass(ll_lower__rpy_stringPtr), p113, descr=) + guard_no_exception(descr=...) + ''') + loop.match_by_id('calltwo', '') # nothing + + def test_move_method_call_out_of_loop(self): + def main(n): + lst = [] + s = 'Hello %d' % n + for i in range(n): + t = s.lower() # ID: callone + lst.append(t) + return len(','.join(lst)) + log = self.run(main, [1000]) + assert log.result == main(1000) + loops = log.loops_by_filename(self.filepath) + loop, = loops + loop.match_by_id('callone', '') # nothing diff --git a/pypy/module/sys/test/test_sysmodule.py b/pypy/module/sys/test/test_sysmodule.py --- a/pypy/module/sys/test/test_sysmodule.py +++ b/pypy/module/sys/test/test_sysmodule.py @@ -595,3 +595,124 @@ assert len(frames) == 1 _, other_frame = frames.popitem() assert other_frame.f_code.co_name in ('other_thread', '?') + + +class AppTestSysExcInfoDirect: + + def setup_method(self, meth): + self.checking = not option.runappdirect + if self.checking: + self.seen = [] + from pypy.module.sys import vm + def exc_info_with_tb(*args): + self.seen.append("n") # not optimized + return self.old[0](*args) + def exc_info_without_tb(*args): + self.seen.append("y") # optimized + return self.old[1](*args) + self.old = [vm.exc_info_with_tb, vm.exc_info_without_tb] + vm.exc_info_with_tb = exc_info_with_tb + vm.exc_info_without_tb = exc_info_without_tb + # + from pypy.rlib import jit + self.old2 = [jit.we_are_jitted] + jit.we_are_jitted = lambda: True + + def teardown_method(self, meth): + if self.checking: + from pypy.module.sys import vm + from pypy.rlib import jit + vm.exc_info_with_tb = self.old[0] + vm.exc_info_without_tb = self.old[1] + jit.we_are_jitted = self.old2[0] + # + assert ''.join(self.seen) == meth.expected + + def test_returns_none(self): + import sys + assert sys.exc_info() == (None, None, None) + assert sys.exc_info()[0] is None + assert sys.exc_info()[1] is None + assert sys.exc_info()[2] is None + assert sys.exc_info()[:2] == (None, None) + assert sys.exc_info()[:3] == (None, None, None) + assert sys.exc_info()[0:2] == (None, None) + assert sys.exc_info()[2:4] == (None,) + test_returns_none.expected = 'nnnnnnnn' + + def test_returns_subscr(self): + import sys + e = KeyError("boom") + try: + raise e + except: + assert sys.exc_info()[0] is KeyError # y + assert sys.exc_info()[1] is e # y + assert sys.exc_info()[2] is not None # n + assert sys.exc_info()[-3] is KeyError # y + assert sys.exc_info()[-2] is e # y + assert sys.exc_info()[-1] is not None # n + test_returns_subscr.expected = 'yynyyn' + + def test_returns_slice_2(self): + import sys + e = KeyError("boom") + try: + raise e + except: + foo = sys.exc_info() # n + assert sys.exc_info()[:0] == () # y + assert sys.exc_info()[:1] == foo[:1] # y + assert sys.exc_info()[:2] == foo[:2] # y + assert sys.exc_info()[:3] == foo # n + assert sys.exc_info()[:4] == foo # n + assert sys.exc_info()[:-1] == foo[:2] # y + assert sys.exc_info()[:-2] == foo[:1] # y + assert sys.exc_info()[:-3] == () # y + test_returns_slice_2.expected = 'nyyynnyyy' + + def test_returns_slice_3(self): + import sys + e = KeyError("boom") + try: + raise e + except: + foo = sys.exc_info() # n + assert sys.exc_info()[2:2] == () # y + assert sys.exc_info()[0:1] == foo[:1] # y + assert sys.exc_info()[1:2] == foo[1:2] # y + assert sys.exc_info()[0:3] == foo # n + assert sys.exc_info()[2:4] == foo[2:] # n + assert sys.exc_info()[0:-1] == foo[:2] # y + assert sys.exc_info()[0:-2] == foo[:1] # y + assert sys.exc_info()[5:-3] == () # y + test_returns_slice_3.expected = 'nyyynnyyy' + + def test_strange_invocation(self): + import sys + e = KeyError("boom") + try: + raise e + except: + a = []; k = {} + assert sys.exc_info(*a)[:0] == () + assert sys.exc_info(**k)[:0] == () + test_strange_invocation.expected = 'nn' + + def test_call_in_subfunction(self): + import sys + def g(): + # this case is not optimized, because we need to search the + # frame chain. it's probably not worth the complications + return sys.exc_info()[1] + e = KeyError("boom") + try: + raise e + except: + assert g() is e + test_call_in_subfunction.expected = 'n' + + +class AppTestSysExcInfoDirectCallMethod(AppTestSysExcInfoDirect): + def setup_class(cls): + cls.space = gettestobjspace(**{"objspace.opcodes.CALL_METHOD": True}) diff --git a/pypy/module/sys/version.py b/pypy/module/sys/version.py --- a/pypy/module/sys/version.py +++ b/pypy/module/sys/version.py @@ -10,7 +10,7 @@ CPYTHON_VERSION = (2, 7, 2, "final", 42) #XXX # sync patchlevel.h CPYTHON_API_VERSION = 1013 #XXX # sync with include/modsupport.h -PYPY_VERSION = (1, 8, 1, "dev", 0) #XXX # sync patchlevel.h +PYPY_VERSION = (1, 9, 1, "dev", 0) #XXX # sync patchlevel.h if platform.name == 'msvc': COMPILER_INFO = 'MSC v.%d 32 bit' % (platform.version * 10 + 600) diff --git a/pypy/module/sys/vm.py b/pypy/module/sys/vm.py --- a/pypy/module/sys/vm.py +++ b/pypy/module/sys/vm.py @@ -89,6 +89,9 @@ """Return the (type, value, traceback) of the most recent exception caught by an except clause in the current stack frame or in an older stack frame.""" + return exc_info_with_tb(space) # indirection for the tests + +def exc_info_with_tb(space): operror = space.getexecutioncontext().sys_exc_info() if operror is None: return space.newtuple([space.w_None,space.w_None,space.w_None]) @@ -96,6 +99,59 @@ return space.newtuple([operror.w_type, operror.get_w_value(space), space.wrap(operror.get_traceback())]) +def exc_info_without_tb(space, frame): + operror = frame.last_exception + return space.newtuple([operror.w_type, operror.get_w_value(space), + space.w_None]) + +def exc_info_direct(space, frame): + from pypy.tool import stdlib_opcode + # In order to make the JIT happy, we try to return (exc, val, None) + # instead of (exc, val, tb). We can do that only if we recognize + # the following pattern in the bytecode: + # CALL_FUNCTION/CALL_METHOD <-- invoking me + # LOAD_CONST 0, 1, -2 or -3 + # BINARY_SUBSCR + # or: + # CALL_FUNCTION/CALL_METHOD + # LOAD_CONST <=2 + # SLICE_2 + # or: + # CALL_FUNCTION/CALL_METHOD + # LOAD_CONST any integer + # LOAD_CONST <=2 + # SLICE_3 + need_all_three_args = True + co = frame.getcode().co_code + p = frame.last_instr + if (ord(co[p]) == stdlib_opcode.CALL_FUNCTION or + ord(co[p]) == stdlib_opcode.CALL_METHOD): + if ord(co[p+3]) == stdlib_opcode.LOAD_CONST: + lo = ord(co[p+4]) + hi = ord(co[p+5]) + w_constant = frame.getconstant_w((hi * 256) | lo) + if space.isinstance_w(w_constant, space.w_int): + constant = space.int_w(w_constant) + if ord(co[p+6]) == stdlib_opcode.BINARY_SUBSCR: + if -3 <= constant <= 1 and constant != -1: + need_all_three_args = False + elif ord(co[p+6]) == stdlib_opcode.SLICE+2: + if constant <= 2: + need_all_three_args = False + elif (ord(co[p+6]) == stdlib_opcode.LOAD_CONST and + ord(co[p+9]) == stdlib_opcode.SLICE+3): + lo = ord(co[p+7]) + hi = ord(co[p+8]) + w_constant = frame.getconstant_w((hi * 256) | lo) + if space.isinstance_w(w_constant, space.w_int): + if space.int_w(w_constant) <= 2: + need_all_three_args = False + # + if need_all_three_args or frame.last_exception is None or frame.hide(): + return exc_info_with_tb(space) + else: + return exc_info_without_tb(space, frame) + def exc_clear(space): """Clear global information on the current exception. Subsequent calls to exc_info() will return (None,None,None) until another exception is diff --git a/pypy/module/test_lib_pypy/test_datetime.py b/pypy/module/test_lib_pypy/test_datetime.py --- a/pypy/module/test_lib_pypy/test_datetime.py +++ b/pypy/module/test_lib_pypy/test_datetime.py @@ -44,3 +44,9 @@ assert type(dt.microsecond) is int copy.copy(dt) + +def test_radd(): + class X(object): + def __radd__(self, other): + return "radd" + assert datetime.date(10, 10, 10) + X() == "radd" diff --git a/pypy/module/thread/test/test_ll_thread.py b/pypy/module/thread/test/test_ll_thread.py --- a/pypy/module/thread/test/test_ll_thread.py +++ b/pypy/module/thread/test/test_ll_thread.py @@ -66,7 +66,6 @@ def test_gc_locking(self): import time from pypy.rlib.objectmodel import invoke_around_extcall - from pypy.rlib.objectmodel import we_are_translated from pypy.rlib.debug import ll_assert class State: @@ -129,8 +128,6 @@ state.finished = 0 # the next line installs before_extcall() and after_extcall() # to be called automatically around external function calls. - # When not translated it does not work around time.sleep(), - # so we have to call them manually for this test. invoke_around_extcall(before_extcall, after_extcall) g(10, 1) @@ -142,13 +139,9 @@ willing_to_wait_more -= 1 done = len(state.answers) == expected - if not we_are_translated(): before_extcall() time.sleep(0.01) - if not we_are_translated(): after_extcall() - if not we_are_translated(): before_extcall() time.sleep(0.1) - if not we_are_translated(): after_extcall() return len(state.answers) @@ -160,12 +153,11 @@ answers = fn() assert answers == expected -class TestRunDirectly(AbstractThreadTests): - def getcompiled(self, f, argtypes): - return f - - def test_start_new_thread(self): - py.test.skip("deadlocks occasionally -- why???") +#class TestRunDirectly(AbstractThreadTests): +# def getcompiled(self, f, argtypes): +# return f +# These are disabled because they crash occasionally for bad reasons +# related to the fact that ll2ctypes is not at all thread-safe class TestUsingBoehm(AbstractThreadTests): gcpolicy = 'boehm' diff --git a/pypy/objspace/descroperation.py b/pypy/objspace/descroperation.py --- a/pypy/objspace/descroperation.py +++ b/pypy/objspace/descroperation.py @@ -43,6 +43,13 @@ return w_eq type_eq._annspecialcase_ = 'specialize:memo' +def list_iter(space): + "Utility that returns the app-level descriptor list.__iter__." + w_src, w_iter = space.lookup_in_type_where(space.w_list, + '__iter__') + return w_iter +list_iter._annspecialcase_ = 'specialize:memo' + def raiseattrerror(space, w_obj, name, w_descr=None): w_type = space.type(w_obj) typename = w_type.getname(space) diff --git a/pypy/objspace/fake/objspace.py b/pypy/objspace/fake/objspace.py --- a/pypy/objspace/fake/objspace.py +++ b/pypy/objspace/fake/objspace.py @@ -86,6 +86,7 @@ return s_None def specialize_call(self, hop): + hop.exception_cannot_occur() return hop.inputconst(lltype.Void, None) # ____________________________________________________________ @@ -109,7 +110,7 @@ "NOT_RPYTHON" raise NotImplementedError - def newdict(self, module=False, instance=False, classofinstance=None, + def newdict(self, module=False, instance=False, strdict=False): return w_some_obj() diff --git a/pypy/objspace/std/celldict.py b/pypy/objspace/std/celldict.py --- a/pypy/objspace/std/celldict.py +++ b/pypy/objspace/std/celldict.py @@ -163,7 +163,8 @@ class ModuleDictIteratorImplementation(IteratorImplementation): def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) + IteratorImplementation.__init__( + self, space, strategy, dictimplementation) dict_w = strategy.unerase(dictimplementation.dstorage) self.iterator = dict_w.iteritems() diff --git a/pypy/objspace/std/dictmultiobject.py b/pypy/objspace/std/dictmultiobject.py --- a/pypy/objspace/std/dictmultiobject.py +++ b/pypy/objspace/std/dictmultiobject.py @@ -33,8 +33,7 @@ @staticmethod def allocate_and_init_instance(space, w_type=None, module=False, - instance=False, classofinstance=None, - strdict=False): + instance=False, strdict=False): if space.config.objspace.std.withcelldict and module: from pypy.objspace.std.celldict import ModuleDictStrategy @@ -247,7 +246,7 @@ return 0 def iter(self, w_dict): - return EmptyIteratorImplementation(self.space, w_dict) + return EmptyIteratorImplementation(self.space, self, w_dict) def clear(self, w_dict): return @@ -263,8 +262,9 @@ # Iterator Implementation base classes class IteratorImplementation(object): - def __init__(self, space, implementation): + def __init__(self, space, strategy, implementation): self.space = space + self.strategy = strategy self.dictimplementation = implementation self.len = implementation.length() self.pos = 0 @@ -280,7 +280,20 @@ if self.pos < self.len: result = self.next_entry() self.pos += 1 - return result + if self.strategy is self.dictimplementation.strategy: + return result # common case + else: + # waaa, obscure case: the strategy changed, but not the + # length of the dict. The (key, value) pair in 'result' + # might be out-of-date. We try to explicitly look up + # the key in the dict. + w_key = result[0] + w_value = self.dictimplementation.getitem(w_key) + if w_value is None: + self.len = -1 # Make this error state sticky + raise OperationError(self.space.w_RuntimeError, + self.space.wrap("dictionary changed during iteration")) + return (w_key, w_value) # no more entries self.dictimplementation = None return None, None @@ -489,7 +502,7 @@ _mixin_ = True def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) + IteratorImplementation.__init__(self, space, strategy, dictimplementation) self.iterator = strategy.unerase(dictimplementation.dstorage).iteritems() def next_entry(self): @@ -503,7 +516,7 @@ _mixin_ = True def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) + IteratorImplementation.__init__(self, space, strategy, dictimplementation) self.iterator = strategy.unerase(dictimplementation.dstorage).iteritems() def next_entry(self): @@ -549,10 +562,7 @@ def listview_int(self, w_dict): return self.unerase(w_dict.dstorage).keys() - def w_keys(self, w_dict): - # XXX there is no space.newlist_int yet - space = self.space - return space.call_function(space.w_list, w_dict) + # XXX there is no space.newlist_int yet to implement w_keys more efficiently class IntIteratorImplementation(_WrappedIteratorMixin, IteratorImplementation): pass diff --git a/pypy/objspace/std/dictproxyobject.py b/pypy/objspace/std/dictproxyobject.py --- a/pypy/objspace/std/dictproxyobject.py +++ b/pypy/objspace/std/dictproxyobject.py @@ -20,7 +20,17 @@ def getitem(self, w_dict, w_key): space = self.space w_lookup_type = space.type(w_key) - if space.is_w(w_lookup_type, space.w_str): + if (space.is_w(w_lookup_type, space.w_str) or # Most common path first + space.abstract_issubclass_w(w_lookup_type, space.w_str)): + return self.getitem_str(w_dict, space.str_w(w_key)) + elif space.abstract_issubclass_w(w_lookup_type, space.w_unicode): + try: + w_key = space.str(w_key) + except OperationError, e: + if not e.match(space, space.w_UnicodeEncodeError): + raise + # non-ascii unicode is never equal to a byte string + return None return self.getitem_str(w_dict, space.str_w(w_key)) else: return None @@ -98,7 +108,8 @@ class DictProxyIteratorImplementation(IteratorImplementation): def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) + IteratorImplementation.__init__( + self, space, strategy, dictimplementation) w_type = strategy.unerase(dictimplementation.dstorage) self.iterator = w_type.dict_w.iteritems() diff --git a/pypy/objspace/std/identitydict.py b/pypy/objspace/std/identitydict.py --- a/pypy/objspace/std/identitydict.py +++ b/pypy/objspace/std/identitydict.py @@ -1,5 +1,5 @@ ## ---------------------------------------------------------------------------- -## dict strategy (see dict_multiobject.py) +## dict strategy (see dictmultiobject.py) from pypy.rlib import rerased from pypy.rlib.debug import mark_dict_non_null @@ -80,8 +80,8 @@ def iter(self, w_dict): return IdentityDictIteratorImplementation(self.space, self, w_dict) - def keys(self, w_dict): - return self.unerase(w_dict.dstorage).keys() + def w_keys(self, w_dict): + return self.space.newlist(self.unerase(w_dict.dstorage).keys()) class IdentityDictIteratorImplementation(_UnwrappedIteratorMixin, IteratorImplementation): diff --git a/pypy/objspace/std/mapdict.py b/pypy/objspace/std/mapdict.py --- a/pypy/objspace/std/mapdict.py +++ b/pypy/objspace/std/mapdict.py @@ -703,7 +703,8 @@ class MapDictIteratorImplementation(IteratorImplementation): def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) + IteratorImplementation.__init__( + self, space, strategy, dictimplementation) w_obj = strategy.unerase(dictimplementation.dstorage) self.w_obj = w_obj self.orig_map = self.curr_map = w_obj._get_mapdict_map() diff --git a/pypy/objspace/std/objspace.py b/pypy/objspace/std/objspace.py --- a/pypy/objspace/std/objspace.py +++ b/pypy/objspace/std/objspace.py @@ -313,11 +313,10 @@ def newlist_str(self, list_s): return W_ListObject.newlist_str(self, list_s) - def newdict(self, module=False, instance=False, classofinstance=None, + def newdict(self, module=False, instance=False, strdict=False): return W_DictMultiObject.allocate_and_init_instance( self, module=module, instance=instance, - classofinstance=classofinstance, strdict=strdict) def newset(self): @@ -439,6 +438,8 @@ t = w_obj.getitems() elif isinstance(w_obj, W_AbstractTupleObject): t = w_obj.getitems_copy(self) + elif isinstance(w_obj, W_ListObject) and self._uses_list_iter(w_obj): + t = w_obj.getitems() else: return ObjSpace.unpackiterable(self, w_obj, expected_length) if expected_length != -1 and len(t) != expected_length: @@ -456,6 +457,8 @@ return w_obj.listview_str() if isinstance(w_obj, W_StringObject): return w_obj.listview_str() + if isinstance(w_obj, W_ListObject) and self._uses_list_iter(w_obj): + return w_obj.getitems_str() return None def listview_int(self, w_obj): @@ -465,8 +468,14 @@ return w_obj.listview_int() if type(w_obj) is W_SetObject or type(w_obj) is W_FrozensetObject: return w_obj.listview_int() + if isinstance(w_obj, W_ListObject) and self._uses_list_iter(w_obj): + return w_obj.getitems_int() return None + def _uses_list_iter(self, w_obj): + from pypy.objspace.descroperation import list_iter + return self.lookup(w_obj, '__iter__') is list_iter(self) + def sliceindices(self, w_slice, w_length): if isinstance(w_slice, W_SliceObject): a, b, c = w_slice.indices3(self, self.int_w(w_length)) diff --git a/pypy/objspace/std/setobject.py b/pypy/objspace/std/setobject.py --- a/pypy/objspace/std/setobject.py +++ b/pypy/objspace/std/setobject.py @@ -359,7 +359,7 @@ w_set.sstorage = w_other.get_storage_copy() def iter(self, w_set): - return EmptyIteratorImplementation(self.space, w_set) + return EmptyIteratorImplementation(self.space, self, w_set) def popitem(self, w_set): raise OperationError(self.space.w_KeyError, @@ -784,8 +784,9 @@ d_obj[w_item] = None class IteratorImplementation(object): - def __init__(self, space, implementation): + def __init__(self, space, strategy, implementation): self.space = space + self.strategy = strategy self.setimplementation = implementation self.len = implementation.length() self.pos = 0 @@ -801,7 +802,17 @@ if self.pos < self.len: result = self.next_entry() self.pos += 1 - return result + if self.strategy is self.setimplementation.strategy: + return result # common case + else: + # waaa, obscure case: the strategy changed, but not the + # length of the set. The 'result' might be out-of-date. + # We try to explicitly look it up in the set. + if not self.setimplementation.has_key(result): + self.len = -1 # Make this error state sticky + raise OperationError(self.space.w_RuntimeError, + self.space.wrap("dictionary changed during iteration")) + return result # no more entries self.setimplementation = None return None @@ -823,7 +834,7 @@ class StringIteratorImplementation(IteratorImplementation): def __init__(self, space, strategy, w_set): - IteratorImplementation.__init__(self, space, w_set) + IteratorImplementation.__init__(self, space, strategy, w_set) d = strategy.unerase(w_set.sstorage) self.iterator = d.iterkeys() @@ -835,9 +846,9 @@ class IntegerIteratorImplementation(IteratorImplementation): #XXX same implementation in dictmultiobject on dictstrategy-branch - def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) - d = strategy.unerase(dictimplementation.sstorage) + def __init__(self, space, strategy, w_set): + IteratorImplementation.__init__(self, space, strategy, w_set) + d = strategy.unerase(w_set.sstorage) self.iterator = d.iterkeys() def next_entry(self): @@ -848,9 +859,9 @@ return None class RDictIteratorImplementation(IteratorImplementation): - def __init__(self, space, strategy, dictimplementation): - IteratorImplementation.__init__(self, space, dictimplementation) - d = strategy.unerase(dictimplementation.sstorage) + def __init__(self, space, strategy, w_set): + IteratorImplementation.__init__(self, space, strategy, w_set) + d = strategy.unerase(w_set.sstorage) self.iterator = d.iterkeys() def next_entry(self): diff --git a/pypy/objspace/std/test/test_dictmultiobject.py b/pypy/objspace/std/test/test_dictmultiobject.py --- a/pypy/objspace/std/test/test_dictmultiobject.py +++ b/pypy/objspace/std/test/test_dictmultiobject.py @@ -804,6 +804,33 @@ assert "IntDictStrategy" in self.get_strategy(d) assert d[1L] == "hi" + def test_iter_dict_length_change(self): + d = {1: 2, 3: 4, 5: 6} + it = d.iteritems() + d[7] = 8 + # 'd' is now length 4 + raises(RuntimeError, it.next) + + def test_iter_dict_strategy_only_change_1(self): + d = {1: 2, 3: 4, 5: 6} + it = d.iteritems() + class Foo(object): + def __eq__(self, other): + return False + assert d.get(Foo()) is None # this changes the strategy of 'd' + lst = list(it) # but iterating still works + assert sorted(lst) == [(1, 2), (3, 4), (5, 6)] + + def test_iter_dict_strategy_only_change_2(self): + d = {1: 2, 3: 4, 5: 6} + it = d.iteritems() + d['foo'] = 'bar' + del d[1] + # 'd' is still length 3, but its strategy changed. we are + # getting a RuntimeError because iterating over the old storage + # gives us (1, 2), but 1 is not in the dict any longer. + raises(RuntimeError, list, it) + class FakeString(str): hash_count = 0 @@ -858,10 +885,9 @@ def newtuple(self, l): return tuple(l) - def newdict(self, module=False, instance=False, classofinstance=None): + def newdict(self, module=False, instance=False): return W_DictMultiObject.allocate_and_init_instance( - self, module=module, instance=instance, - classofinstance=classofinstance) + self, module=module, instance=instance) def finditem_str(self, w_dict, s): return w_dict.getitem_str(s) # assume it's a multidict @@ -941,6 +967,20 @@ assert type(self.impl.strategy) is self.StrategyClass #assert self.impl.r_dict_content is None + def test_popitem(self): + self.fill_impl() + assert self.impl.length() == 2 + a, b = self.impl.popitem() + assert self.impl.length() == 1 + if a == self.string: + assert b == 1000 + assert self.impl.getitem(self.string2) == 2000 + else: + assert a == self.string2 + assert b == 2000 + assert self.impl.getitem_str(self.string) == 1000 + self.check_not_devolved() + def test_setitem(self): self.impl.setitem(self.string, 1000) assert self.impl.length() == 1 diff --git a/pypy/objspace/std/test/test_dictproxy.py b/pypy/objspace/std/test/test_dictproxy.py --- a/pypy/objspace/std/test/test_dictproxy.py +++ b/pypy/objspace/std/test/test_dictproxy.py @@ -25,6 +25,16 @@ key, value = NotEmpty.__dict__.popitem() assert (key == 'a' and value == 1) or (key == 'b' and value == 4) + def test_dictproxy_getitem(self): + class NotEmpty(object): + a = 1 + assert 'a' in NotEmpty.__dict__ + class substr(str): pass + assert substr('a') in NotEmpty.__dict__ + assert u'a' in NotEmpty.__dict__ + assert NotEmpty.__dict__[u'a'] == 1 + assert u'\xe9' not in NotEmpty.__dict__ + def test_dictproxyeq(self): class a(object): pass diff --git a/pypy/objspace/std/test/test_listobject.py b/pypy/objspace/std/test/test_listobject.py --- a/pypy/objspace/std/test/test_listobject.py +++ b/pypy/objspace/std/test/test_listobject.py @@ -1186,14 +1186,23 @@ # of dicts, because the OrderedDict in the stdlib relies on this. # we extend the use case to lists and sets, i.e. all types that have # strategies, to avoid surprizes depending on the strategy. - for base, arg in [(list, []), (list, [5]), (list, ['x']), - (set, []), (set, [5]), (set, ['x']), - (dict, []), (dict, [(5,6)]), (dict, [('x',7)])]: + class X: pass + for base, arg in [ + (list, []), (list, [5]), (list, ['x']), (list, [X]), + (set, []), (set, [5]), (set, ['x']), (set, [X]), + (dict, []), (dict, [(5,6)]), (dict, [('x',7)]), (dict, [(X,8)]), + ]: print base, arg class SubClass(base): def __iter__(self): return iter("foobar") assert list(SubClass(arg)) == ['f', 'o', 'o', 'b', 'a', 'r'] + class Sub2(base): + pass + assert list(Sub2(arg)) == list(base(arg)) + s = set() + s.update(Sub2(arg)) + assert s == set(base(arg)) class AppTestForRangeLists(AppTestW_ListObject): diff --git a/pypy/objspace/std/test/test_setobject.py b/pypy/objspace/std/test/test_setobject.py --- a/pypy/objspace/std/test/test_setobject.py +++ b/pypy/objspace/std/test/test_setobject.py @@ -907,3 +907,30 @@ return [5, 3, 4][i] s = set([10,3,2]).intersection(Obj()) assert list(s) == [3] + + def test_iter_set_length_change(self): + s = set([1, 3, 5]) + it = iter(s) + s.add(7) + # 's' is now length 4 + raises(RuntimeError, it.next) + + def test_iter_set_strategy_only_change_1(self): + s = set([1, 3, 5]) + it = iter(s) + class Foo(object): + def __eq__(self, other): + return False + assert Foo() not in s # this changes the strategy of 'd' + lst = list(s) # but iterating still works + assert sorted(lst) == [1, 3, 5] + + def test_iter_set_strategy_only_change_2(self): + s = set([1, 3, 5]) + it = iter(s) + s.add('foo') + s.remove(1) + # 's' is still length 3, but its strategy changed. we are + # getting a RuntimeError because iterating over the old storage + # gives us 1, but 1 is not in the set any longer. + raises(RuntimeError, list, it) diff --git a/pypy/rlib/jit.py b/pypy/rlib/jit.py --- a/pypy/rlib/jit.py +++ b/pypy/rlib/jit.py @@ -382,7 +382,7 @@ pass def specialize_call(self, hop): - pass + hop.exception_cannot_occur() vref_None = non_virtual_ref(None) diff --git a/pypy/rlib/jit_hooks.py b/pypy/rlib/jit_hooks.py --- a/pypy/rlib/jit_hooks.py +++ b/pypy/rlib/jit_hooks.py @@ -22,6 +22,7 @@ c_name = hop.inputconst(lltype.Void, 'access_helper') args_v = [hop.inputarg(arg, arg=i) for i, arg in enumerate(hop.args_r)] + hop.exception_cannot_occur() return hop.genop('jit_marker', [c_name, c_func] + args_v, resulttype=hop.r_result) return helper diff --git a/pypy/rlib/objectmodel.py b/pypy/rlib/objectmodel.py --- a/pypy/rlib/objectmodel.py +++ b/pypy/rlib/objectmodel.py @@ -215,6 +215,7 @@ def specialize_call(self, hop): from pypy.rpython.lltypesystem import lltype + hop.exception_cannot_occur() return hop.inputconst(lltype.Bool, hop.s_result.const) # ____________________________________________________________ @@ -397,6 +398,7 @@ r_obj, = hop.args_r v_obj, = hop.inputargs(r_obj) ll_fn = r_obj.get_ll_hash_function() + hop.exception_is_here() return hop.gendirectcall(ll_fn, v_obj) class Entry(ExtRegistryEntry): @@ -419,6 +421,7 @@ from pypy.rpython.error import TyperError raise TyperError("compute_identity_hash() cannot be applied to" " %r" % (vobj.concretetype,)) + hop.exception_cannot_occur() return hop.genop('gc_identityhash', [vobj], resulttype=lltype.Signed) class Entry(ExtRegistryEntry): @@ -441,6 +444,7 @@ from pypy.rpython.error import TyperError raise TyperError("compute_unique_id() cannot be applied to" " %r" % (vobj.concretetype,)) + hop.exception_cannot_occur() return hop.genop('gc_id', [vobj], resulttype=lltype.Signed) class Entry(ExtRegistryEntry): @@ -452,6 +456,7 @@ def specialize_call(self, hop): vobj, = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() if hop.rtyper.type_system.name == 'lltypesystem': from pypy.rpython.lltypesystem import lltype if isinstance(vobj.concretetype, lltype.Ptr): diff --git a/pypy/rlib/rbigint.py b/pypy/rlib/rbigint.py --- a/pypy/rlib/rbigint.py +++ b/pypy/rlib/rbigint.py @@ -85,7 +85,7 @@ s_DIGIT = self.bookkeeper.valueoftype(type(NULLDIGIT)) assert s_DIGIT.contains(s_list.listdef.listitem.s_value) def specialize_call(self, hop): - pass + hop.exception_cannot_occur() class rbigint(object): diff --git a/pypy/rlib/rerased.py b/pypy/rlib/rerased.py --- a/pypy/rlib/rerased.py +++ b/pypy/rlib/rerased.py @@ -100,6 +100,7 @@ def specialize_call(self, hop): bk = hop.rtyper.annotator.bookkeeper s_obj = identity.get_input_annotation(bk) + hop.exception_cannot_occur() return hop.r_result.rtype_erase(hop, s_obj) class Entry(ExtRegistryEntry): @@ -110,6 +111,7 @@ return identity.leave_tunnel(self.bookkeeper) def specialize_call(self, hop): + hop.exception_cannot_occur() if hop.r_result.lowleveltype is lltype.Void: return hop.inputconst(lltype.Void, None) [v] = hop.inputargs(hop.args_r[0]) @@ -214,6 +216,7 @@ return hop.genop('cast_opaque_ptr', [v], resulttype=hop.r_result) def rtype_unerase_int(self, hop, v): + hop.exception_cannot_occur() return hop.gendirectcall(ll_unerase_int, v) def rtype_erase_int(self, hop): @@ -264,6 +267,7 @@ def rtype_unerase_int(self, hop, v): c_one = hop.inputconst(lltype.Signed, 1) + hop.exception_cannot_occur() v2 = hop.genop('oounbox_int', [v], resulttype=hop.r_result) return hop.genop('int_rshift', [v2, c_one], resulttype=lltype.Signed) diff --git a/pypy/rlib/rgc.py b/pypy/rlib/rgc.py --- a/pypy/rlib/rgc.py +++ b/pypy/rlib/rgc.py @@ -382,6 +382,7 @@ def compute_result_annotation(self): return s_list_of_gcrefs() def specialize_call(self, hop): + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_roots', [], resulttype = hop.r_result) class Entry(ExtRegistryEntry): @@ -392,6 +393,7 @@ return s_list_of_gcrefs() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_referents', vlist, resulttype = hop.r_result) @@ -402,6 +404,7 @@ return annmodel.SomeInteger() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_memory_usage', vlist, resulttype = hop.r_result) @@ -412,6 +415,7 @@ return annmodel.SomeInteger() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_get_rpy_type_index', vlist, resulttype = hop.r_result) @@ -430,6 +434,7 @@ return annmodel.SomeBool() def specialize_call(self, hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_is_rpy_instance', vlist, resulttype = hop.r_result) @@ -449,6 +454,7 @@ classrepr = getclassrepr(hop.rtyper, classdef) vtable = classrepr.getvtable() assert lltype.typeOf(vtable) == rclass.CLASSTYPE + hop.exception_cannot_occur() return Constant(vtable, concretetype=rclass.CLASSTYPE) class Entry(ExtRegistryEntry): diff --git a/pypy/rlib/rsre/rsre_re.py b/pypy/rlib/rsre/rsre_re.py --- a/pypy/rlib/rsre/rsre_re.py +++ b/pypy/rlib/rsre/rsre_re.py @@ -172,8 +172,9 @@ self._ctx = ctx def span(self, groupnum=0): - if not isinstance(groupnum, (int, long)): - groupnum = self.re.groupindex[groupnum] +# if not isinstance(groupnum, (int, long)): +# groupnum = self.re.groupindex[groupnum] + return self._ctx.span(groupnum) def start(self, groupnum=0): @@ -182,19 +183,25 @@ def end(self, groupnum=0): return self.span(groupnum)[1] - def group(self, *groups): - groups = groups or (0,) - result = [] - for group in groups: - frm, to = self.span(group) - if 0 <= frm <= to: - result.append(self._ctx._string[frm:to]) - else: - result.append(None) - if len(result) > 1: - return tuple(result) + def group(self, group=0): + frm, to = self.span(group) + if 0 <= frm <= to: + return self._ctx._string[frm:to] else: - return result[0] + return None + +# def group(self, *groups): +# groups = groups or (0,) +# result = [] +# for group in groups: +# frm, to = self.span(group) +# if 0 <= frm <= to: +# result.append(self._ctx._string[frm:to]) +# else: +# result.append(None) +# if len(result) > 1: +# return tuple(result) + def groups(self, default=None): fmarks = self._ctx.flatten_marks() diff --git a/pypy/rlib/rsre/test/test_re.py b/pypy/rlib/rsre/test/test_re.py --- a/pypy/rlib/rsre/test/test_re.py +++ b/pypy/rlib/rsre/test/test_re.py @@ -204,7 +204,7 @@ assert re.match('(a)', 'a').groups() == ('a',) assert re.match(r'(a)', 'a').group(0) == 'a' assert re.match(r'(a)', 'a').group(1) == 'a' - assert re.match(r'(a)', 'a').group(1, 1) == ('a', 'a') + #assert re.match(r'(a)', 'a').group(1, 1) == ('a', 'a') pat = re.compile('((a)|(b))(c)?') assert pat.match('a').groups() == ('a', 'a', None, None) @@ -218,13 +218,13 @@ assert m.group(0) == 'a' assert m.group(0) == 'a' assert m.group(1) == 'a' - assert m.group(1, 1) == ('a', 'a') + #assert m.group(1, 1) == ('a', 'a') pat = re.compile('(?:(?Pa)|(?Pb))(?Pc)?') - assert pat.match('a').group(1, 2, 3) == ('a', None, None) - assert pat.match('b').group('a1', 'b2', 'c3') == ( - (None, 'b', None)) - assert pat.match('ac').group(1, 'b2', 3) == ('a', None, 'c') + #assert pat.match('a').group(1, 2, 3) == ('a', None, None) + #assert pat.match('b').group('a1', 'b2', 'c3') == ( + # (None, 'b', None)) + #assert pat.match('ac').group(1, 'b2', 3) == ('a', None, 'c') def test_bug_923(self): # Issue923: grouping inside optional lookahead problem diff --git a/pypy/rlib/rsre/test/test_zinterp.py b/pypy/rlib/rsre/test/test_zinterp.py --- a/pypy/rlib/rsre/test/test_zinterp.py +++ b/pypy/rlib/rsre/test/test_zinterp.py @@ -1,7 +1,8 @@ # minimal test: just checks that (parts of) rsre can be translated -from pypy.rpython.test.test_llinterp import gengraph +from pypy.rpython.test.test_llinterp import gengraph, interpret from pypy.rlib.rsre import rsre_core +from pypy.rlib.rsre.rsre_re import compile def main(n): assert n >= 0 @@ -19,3 +20,18 @@ def test_gengraph(): t, typer, graph = gengraph(main, [int]) + +m = compile("(a|b)aaaaa") + +def test_match(): + def f(i): + if i: + s = "aaaaaa" + else: + s = "caaaaa" + g = m.match(s) + if g is None: + return 3 + return int("aaaaaa" == g.group(0)) + assert interpret(f, [3]) == 1 + assert interpret(f, [0]) == 3 diff --git a/pypy/rlib/rstring.py b/pypy/rlib/rstring.py --- a/pypy/rlib/rstring.py +++ b/pypy/rlib/rstring.py @@ -245,5 +245,5 @@ raise ValueError("Value is not no_nul") def specialize_call(self, hop): - pass + hop.exception_cannot_occur() diff --git a/pypy/rpython/annlowlevel.py b/pypy/rpython/annlowlevel.py --- a/pypy/rpython/annlowlevel.py +++ b/pypy/rpython/annlowlevel.py @@ -543,11 +543,11 @@ else: assert False + hop.exception_cannot_occur() if isinstance(hop.args_r[1], rpbc.NoneFrozenPBCRepr): return hop.inputconst(PTR, null) v_arg = hop.inputarg(hop.args_r[1], arg=1) assert isinstance(v_arg.concretetype, T) - hop.exception_cannot_occur() return hop.genop(opname, [v_arg], resulttype = PTR) diff --git a/pypy/rpython/controllerentry.py b/pypy/rpython/controllerentry.py --- a/pypy/rpython/controllerentry.py +++ b/pypy/rpython/controllerentry.py @@ -201,6 +201,7 @@ def specialize_call(self, hop): from pypy.rpython.lltypesystem import lltype assert hop.s_result.is_constant() + hop.exception_cannot_occur() return hop.inputconst(lltype.Bool, hop.s_result.const) # ____________________________________________________________ diff --git a/pypy/rpython/lltypesystem/lloperation.py b/pypy/rpython/lltypesystem/lloperation.py --- a/pypy/rpython/lltypesystem/lloperation.py +++ b/pypy/rpython/lltypesystem/lloperation.py @@ -130,6 +130,7 @@ def specialize_call(self, hop): from pypy.rpython.lltypesystem import lltype + hop.exception_cannot_occur() return hop.inputconst(lltype.Void, None) def enum_ops_without_sideeffects(raising_is_ok=False): diff --git a/pypy/rpython/lltypesystem/rbuiltin.py b/pypy/rpython/lltypesystem/rbuiltin.py --- a/pypy/rpython/lltypesystem/rbuiltin.py +++ b/pypy/rpython/lltypesystem/rbuiltin.py @@ -9,6 +9,7 @@ from pypy.rpython.rbool import bool_repr def rtype_builtin_isinstance(hop): + hop.exception_cannot_occur() if hop.s_result.is_constant(): return hop.inputconst(lltype.Bool, hop.s_result.const) if hop.args_r[0] == pyobj_repr or hop.args_r[1] == pyobj_repr: @@ -33,6 +34,7 @@ return my_instantiate() def rtype_instantiate(hop): + hop.exception_cannot_occur() s_class = hop.args_s[0] assert isinstance(s_class, annmodel.SomePBC) if len(s_class.descriptions) != 1: @@ -46,6 +48,7 @@ return rclass.rtype_new_instance(hop.rtyper, classdef, hop.llops) def rtype_builtin_hasattr(hop): + hop.exception_cannot_occur() if hop.s_result.is_constant(): return hop.inputconst(lltype.Bool, hop.s_result.const) if hop.args_r[0] == pyobj_repr: @@ -56,6 +59,7 @@ raise TyperError("hasattr is only suported on a constant or on PyObject") def rtype_builtin___import__(hop): + xxx # should not be used any more args_v = hop.inputargs(*[pyobj_repr for ign in hop.args_r]) c = hop.inputconst(pyobj_repr, __import__) return hop.genop('simple_call', [c] + args_v, resulttype = pyobj_repr) diff --git a/pypy/rpython/lltypesystem/rclass.py b/pypy/rpython/lltypesystem/rclass.py --- a/pypy/rpython/lltypesystem/rclass.py +++ b/pypy/rpython/lltypesystem/rclass.py @@ -746,4 +746,5 @@ assert isinstance(TYPE, GcStruct) assert lltype._castdepth(TYPE, OBJECT) > 0 hop.rtyper.set_type_for_typeptr(vtable, TYPE) + hop.exception_cannot_occur() return hop.inputconst(lltype.Void, None) diff --git a/pypy/rpython/lltypesystem/rstr.py b/pypy/rpython/lltypesystem/rstr.py --- a/pypy/rpython/lltypesystem/rstr.py +++ b/pypy/rpython/lltypesystem/rstr.py @@ -765,7 +765,11 @@ def _ll_stringslice(s1, start, stop): lgt = stop - start assert start >= 0 - assert lgt >= 0 + # If start > stop, return a empty string. This can happen if the start + # is greater than the length of the string. Use < instead of <= to avoid + # creating another path for the JIT when start == stop. + if lgt < 0: + return s1.empty() newstr = s1.malloc(lgt) s1.copy_contents(s1, newstr, start, 0, lgt) return newstr diff --git a/pypy/rpython/lltypesystem/rtuple.py b/pypy/rpython/lltypesystem/rtuple.py --- a/pypy/rpython/lltypesystem/rtuple.py +++ b/pypy/rpython/lltypesystem/rtuple.py @@ -55,6 +55,7 @@ vtup = hop.inputarg(self, 0) LIST = hop.r_result.lowleveltype.TO cno = inputconst(Signed, nitems) + hop.exception_is_here() vlist = hop.gendirectcall(LIST.ll_newlist, cno) v_func = hop.inputconst(Void, rlist.dum_nocheck) for index in range(nitems): diff --git a/pypy/rpython/memory/gc/minimark.py b/pypy/rpython/memory/gc/minimark.py --- a/pypy/rpython/memory/gc/minimark.py +++ b/pypy/rpython/memory/gc/minimark.py @@ -1426,23 +1426,25 @@ self._visit_young_rawmalloced_object(obj) return # - # If 'obj' was already forwarded, change it to its forwarding address. - if self.is_forwarded(obj): + size_gc_header = self.gcheaderbuilder.size_gc_header + if self.header(obj).tid & GCFLAG_HAS_SHADOW == 0: + # + # Common case: 'obj' was not already forwarded (otherwise + # tid == -42, containing all flags), and it doesn't have the + # HAS_SHADOW flag either. We must move it out of the nursery, + # into a new nonmovable location. + totalsize = size_gc_header + self.get_size(obj) + newhdr = self._malloc_out_of_nursery(totalsize) + # + elif self.is_forwarded(obj): + # + # 'obj' was already forwarded. Change the original reference + # to point to its forwarding address, and we're done. root.address[0] = self.get_forwarding_address(obj) return - # - # First visit to 'obj': we must move it out of the nursery. - size_gc_header = self.gcheaderbuilder.size_gc_header - size = self.get_size(obj) - totalsize = size_gc_header + size - # - if self.header(obj).tid & GCFLAG_HAS_SHADOW == 0: - # - # Common case: allocate a new nonmovable location for it. - newhdr = self._malloc_out_of_nursery(totalsize) # else: - # The object has already a shadow. + # First visit to an object that has already a shadow. newobj = self.nursery_objects_shadows.get(obj) ll_assert(newobj != NULL, "GCFLAG_HAS_SHADOW but no shadow found") newhdr = newobj - size_gc_header @@ -1450,6 +1452,8 @@ # Remove the flag GCFLAG_HAS_SHADOW, so that it doesn't get # copied to the shadow itself. self.header(obj).tid &= ~GCFLAG_HAS_SHADOW + # + totalsize = size_gc_header + self.get_size(obj) # # Copy it. Note that references to other objects in the # nursery are kept unchanged in this step. diff --git a/pypy/rpython/module/ll_os_stat.py b/pypy/rpython/module/ll_os_stat.py --- a/pypy/rpython/module/ll_os_stat.py +++ b/pypy/rpython/module/ll_os_stat.py @@ -455,6 +455,6 @@ return intmask(time), intmask(nsec) def time_t_to_FILE_TIME(time, filetime): - ft = (rffi.r_longlong(time) + secs_between_epochs) * 10000000 + ft = rffi.r_longlong((time + secs_between_epochs) * 10000000) filetime.c_dwHighDateTime = rffi.r_uint(ft >> 32) filetime.c_dwLowDateTime = rffi.r_uint(ft) # masking off high bits diff --git a/pypy/rpython/module/r_os_stat.py b/pypy/rpython/module/r_os_stat.py --- a/pypy/rpython/module/r_os_stat.py +++ b/pypy/rpython/module/r_os_stat.py @@ -65,4 +65,5 @@ r_StatResult = hop.rtyper.getrepr(ll_os_stat.s_StatResult) [v_result] = hop.inputargs(r_StatResult.r_tuple) # no-op conversion from r_StatResult.r_tuple to r_StatResult + hop.exception_cannot_occur() return v_result diff --git a/pypy/rpython/ootypesystem/ooregistry.py b/pypy/rpython/ootypesystem/ooregistry.py --- a/pypy/rpython/ootypesystem/ooregistry.py +++ b/pypy/rpython/ootypesystem/ooregistry.py @@ -22,6 +22,7 @@ annmodel.SomeOOInstance, annmodel.SomeString)) vlist = hop.inputargs(hop.args_r[0], ootype.Signed) + hop.exception_cannot_occur() return hop.genop('oostring', vlist, resulttype = ootype.String) class Entry_oounicode(ExtRegistryEntry): @@ -38,6 +39,7 @@ assert isinstance(hop.args_s[0], (annmodel.SomeUnicodeCodePoint, annmodel.SomeOOInstance)) vlist = hop.inputargs(hop.args_r[0], ootype.Signed) + hop.exception_cannot_occur() return hop.genop('oounicode', vlist, resulttype = ootype.Unicode) diff --git a/pypy/rpython/ootypesystem/rbuiltin.py b/pypy/rpython/ootypesystem/rbuiltin.py --- a/pypy/rpython/ootypesystem/rbuiltin.py +++ b/pypy/rpython/ootypesystem/rbuiltin.py @@ -7,12 +7,14 @@ from pypy.rpython.error import TyperError def rtype_new(hop): + hop.exception_cannot_occur() assert hop.args_s[0].is_constant() vlist = hop.inputargs(ootype.Void) return hop.genop('new', vlist, resulttype = hop.r_result.lowleveltype) def rtype_oonewarray(hop): + hop.exception_cannot_occur() assert hop.args_s[0].is_constant() vlist = hop.inputarg(ootype.Void, arg=0) vlength = hop.inputarg(ootype.Signed, arg=1) @@ -20,23 +22,27 @@ resulttype = hop.r_result.lowleveltype) def rtype_null(hop): + hop.exception_cannot_occur() assert hop.args_s[0].is_constant() TYPE = hop.args_s[0].const nullvalue = ootype.null(TYPE) return hop.inputconst(TYPE, nullvalue) def rtype_classof(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0], annmodel.SomeOOInstance) vlist = hop.inputargs(hop.args_r[0]) return hop.genop('classof', vlist, resulttype = ootype.Class) def rtype_subclassof(hop): + hop.exception_cannot_occur() vlist = hop.inputargs(rootype.ooclass_repr, rootype.ooclass_repr) return hop.genop('subclassof', vlist, resulttype = ootype.Bool) def rtype_instanceof(hop): + hop.exception_cannot_occur() INSTANCE = hop.args_v[1].value v_inst = hop.inputarg(hop.args_r[0], arg=0) c_cls = hop.inputconst(ootype.Void, INSTANCE) @@ -44,23 +50,27 @@ resulttype=ootype.Bool) def rtype_runtimenew(hop): + hop.exception_cannot_occur() vlist = hop.inputargs(rootype.ooclass_repr) return hop.genop('runtimenew', vlist, resulttype = hop.r_result.lowleveltype) def rtype_ooupcast(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0].const, ootype.Instance) assert isinstance(hop.args_s[1], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[1], arg=1) return hop.genop('ooupcast', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_oodowncast(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0].const, ootype.Instance) assert isinstance(hop.args_s[1], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[1], arg=1) return hop.genop('oodowncast', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_cast_to_object(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0], annmodel.SomeOOStaticMeth) or \ isinstance(hop.args_s[0], annmodel.SomeOOClass) or \ isinstance(hop.args_s[0].ootype, ootype.OOType) @@ -68,12 +78,14 @@ return hop.genop('cast_to_object', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_cast_from_object(hop): + hop.exception_cannot_occur() assert isinstance(hop.args_s[0].const, ootype.OOType) assert isinstance(hop.args_s[1], annmodel.SomeOOObject) v_inst = hop.inputarg(hop.args_r[1], arg=1) return hop.genop('cast_from_object', [v_inst], resulttype = hop.r_result.lowleveltype) def rtype_builtin_isinstance(hop): + hop.exception_cannot_occur() if hop.s_result.is_constant(): return hop.inputconst(ootype.Bool, hop.s_result.const) @@ -99,6 +111,7 @@ return ootype.subclassof(c1, class_) def rtype_instantiate(hop): + hop.exception_cannot_occur() if hop.args_s[0].is_constant(): ## INSTANCE = hop.s_result.rtyper_makerepr(hop.rtyper).lowleveltype ## v_instance = hop.inputconst(ootype.Void, INSTANCE) diff --git a/pypy/rpython/ootypesystem/rstr.py b/pypy/rpython/ootypesystem/rstr.py --- a/pypy/rpython/ootypesystem/rstr.py +++ b/pypy/rpython/ootypesystem/rstr.py @@ -222,6 +222,10 @@ length = s.ll_strlen() if stop > length: stop = length + # If start > stop, return a empty string. This can happen if the start + # is greater than the length of the string. + if start > stop: + start = stop return s.ll_substring(start, stop-start) def ll_stringslice_minusone(s): diff --git a/pypy/rpython/ootypesystem/rtuple.py b/pypy/rpython/ootypesystem/rtuple.py --- a/pypy/rpython/ootypesystem/rtuple.py +++ b/pypy/rpython/ootypesystem/rtuple.py @@ -39,6 +39,7 @@ RESULT = hop.r_result.lowleveltype c_resulttype = inputconst(ootype.Void, RESULT) c_length = inputconst(ootype.Signed, len(self.items_r)) + hop.exception_is_here() if isinstance(RESULT, ootype.Array): v_list = hop.genop('oonewarray', [c_resulttype, c_length], resulttype=RESULT) else: diff --git a/pypy/rpython/rbool.py b/pypy/rpython/rbool.py --- a/pypy/rpython/rbool.py +++ b/pypy/rpython/rbool.py @@ -34,6 +34,7 @@ def rtype_float(_, hop): vlist = hop.inputargs(Float) + hop.exception_cannot_occur() return vlist[0] # diff --git a/pypy/rpython/rbuiltin.py b/pypy/rpython/rbuiltin.py --- a/pypy/rpython/rbuiltin.py +++ b/pypy/rpython/rbuiltin.py @@ -111,25 +111,32 @@ raise TyperError("don't know about built-in function %r" % ( self.builtinfunc,)) + def _call(self, hop2, **kwds_i): + bltintyper = self.findbltintyper(hop2.rtyper) + hop2.llops._called_exception_is_here_or_cannot_occur = False + v_result = bltintyper(hop2, **kwds_i) + if not hop2.llops._called_exception_is_here_or_cannot_occur: + raise TyperError("missing hop.exception_cannot_occur() or " + "hop.exception_is_here() in %s" % bltintyper) + return v_result + def rtype_simple_call(self, hop): - bltintyper = self.findbltintyper(hop.rtyper) hop2 = hop.copy() hop2.r_s_popfirstarg() - return bltintyper(hop2) + return self._call(hop2) def rtype_call_args(self, hop): # calling a built-in function with keyword arguments: # mostly for rpython.objectmodel.hint() hop, kwds_i = call_args_expand(hop) - bltintyper = self.findbltintyper(hop.rtyper) hop2 = hop.copy() hop2.r_s_popfirstarg() hop2.r_s_popfirstarg() # the RPython-level keyword args are passed with an 'i_' prefix and # the corresponding value is an *index* in the hop2 arguments, # to be used with hop.inputarg(arg=..) - return bltintyper(hop2, **kwds_i) + return self._call(hop2, **kwds_i) class BuiltinMethodRepr(Repr): @@ -198,6 +205,7 @@ # ____________________________________________________________ def rtype_builtin_bool(hop): + # not called any more? assert hop.nb_args == 1 return hop.args_r[0].rtype_is_true(hop) @@ -241,6 +249,7 @@ def rtype_builtin_min(hop): v1, v2 = hop.inputargs(hop.r_result, hop.r_result) + hop.exception_cannot_occur() return hop.gendirectcall(ll_min, v1, v2) def ll_min(i1, i2): @@ -250,6 +259,7 @@ def rtype_builtin_max(hop): v1, v2 = hop.inputargs(hop.r_result, hop.r_result) + hop.exception_cannot_occur() return hop.gendirectcall(ll_max, v1, v2) def ll_max(i1, i2): @@ -264,6 +274,7 @@ pass def rtype_OSError__init__(hop): + hop.exception_cannot_occur() if hop.nb_args == 2: raise TyperError("OSError() should not be called with " "a single argument") @@ -274,6 +285,7 @@ r_self.setfield(v_self, 'errno', v_errno, hop.llops) def rtype_WindowsError__init__(hop): + hop.exception_cannot_occur() if hop.nb_args == 2: raise TyperError("WindowsError() should not be called with " "a single argument") @@ -442,6 +454,7 @@ assert hop.args_s[0].is_constant() TGT = hop.args_s[0].const v_type, v_value = hop.inputargs(lltype.Void, hop.args_r[1]) + hop.exception_cannot_occur() return gen_cast(hop.llops, TGT, v_value) _cast_to_Signed = { @@ -523,11 +536,13 @@ def rtype_identity_hash(hop): vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('gc_identityhash', vlist, resulttype=lltype.Signed) def rtype_runtime_type_info(hop): assert isinstance(hop.args_r[0], rptr.PtrRepr) vlist = hop.inputargs(hop.args_r[0]) + hop.exception_cannot_occur() return hop.genop('runtime_type_info', vlist, resulttype = hop.r_result.lowleveltype) @@ -558,6 +573,7 @@ def rtype_raw_malloc(hop): v_size, = hop.inputargs(lltype.Signed) + hop.exception_cannot_occur() return hop.genop('raw_malloc', [v_size], resulttype=llmemory.Address) def rtype_raw_malloc_usage(hop): @@ -586,6 +602,7 @@ if s_addr.is_null_address(): raise TyperError("raw_memclear(x, n) where x is the constant NULL") v_list = hop.inputargs(llmemory.Address, lltype.Signed) + hop.exception_cannot_occur() return hop.genop('raw_memclear', v_list) BUILTIN_TYPER[llmemory.raw_malloc] = rtype_raw_malloc @@ -596,6 +613,7 @@ def rtype_offsetof(hop): TYPE, field = hop.inputargs(lltype.Void, lltype.Void) + hop.exception_cannot_occur() return hop.inputconst(lltype.Signed, llmemory.offsetof(TYPE.value, field.value)) @@ -605,6 +623,7 @@ # non-gc objects def rtype_free_non_gc_object(hop): + hop.exception_cannot_occur() vinst, = hop.inputargs(hop.args_r[0]) flavor = hop.args_r[0].gcflavor assert flavor != 'gc' @@ -617,6 +636,7 @@ # keepalive_until_here def rtype_keepalive_until_here(hop): + hop.exception_cannot_occur() for v in hop.args_v: hop.genop('keepalive', [v], resulttype=lltype.Void) return hop.inputconst(lltype.Void, None) diff --git a/pypy/rpython/rfloat.py b/pypy/rpython/rfloat.py --- a/pypy/rpython/rfloat.py +++ b/pypy/rpython/rfloat.py @@ -136,7 +136,10 @@ hop.exception_cannot_occur() return hop.genop('cast_float_to_int', vlist, resulttype=Signed) - rtype_float = rtype_pos + def rtype_float(_, hop): + vlist = hop.inputargs(Float) + hop.exception_cannot_occur() + return vlist[0] # version picked by specialisation based on which # type system rtyping is using, from .ll_str module diff --git a/pypy/rpython/rint.py b/pypy/rpython/rint.py --- a/pypy/rpython/rint.py +++ b/pypy/rpython/rint.py @@ -310,6 +310,8 @@ if hop.has_implicit_exception(ValueError): hop.exception_is_here() hop.gendirectcall(ll_check_chr, vlist[0]) + else: + hop.exception_cannot_occur() return hop.genop('cast_int_to_char', vlist, resulttype=Char) def rtype_unichr(_, hop): @@ -317,6 +319,8 @@ if hop.has_implicit_exception(ValueError): hop.exception_is_here() hop.gendirectcall(ll_check_unichr, vlist[0]) + else: + hop.exception_cannot_occur() return hop.genop('cast_int_to_unichar', vlist, resulttype=UniChar) def rtype_is_true(self, hop): diff --git a/pypy/rpython/rlist.py b/pypy/rpython/rlist.py --- a/pypy/rpython/rlist.py +++ b/pypy/rpython/rlist.py @@ -115,6 +115,7 @@ def rtype_bltn_list(self, hop): v_lst = hop.inputarg(self, 0) cRESLIST = hop.inputconst(Void, hop.r_result.LIST) + hop.exception_is_here() return hop.gendirectcall(ll_copy, cRESLIST, v_lst) def rtype_len(self, hop): diff --git a/pypy/rpython/rrange.py b/pypy/rpython/rrange.py --- a/pypy/rpython/rrange.py +++ b/pypy/rpython/rrange.py @@ -107,8 +107,10 @@ if isinstance(hop.r_result, AbstractRangeRepr): if hop.r_result.step != 0: c_rng = hop.inputconst(Void, hop.r_result.RANGE) + hop.exception_is_here() return hop.gendirectcall(hop.r_result.ll_newrange, c_rng, vstart, vstop) else: + hop.exception_is_here() return hop.gendirectcall(hop.r_result.ll_newrangest, vstart, vstop, vstep) else: # cannot build a RANGE object, needs a real list @@ -117,6 +119,7 @@ if isinstance(ITEMTYPE, Ptr): ITEMTYPE = ITEMTYPE.TO cLIST = hop.inputconst(Void, ITEMTYPE) + hop.exception_is_here() return hop.gendirectcall(ll_range2list, cLIST, vstart, vstop, vstep) rtype_builtin_xrange = rtype_builtin_range @@ -212,4 +215,5 @@ [v_index, v_item]) def rtype_builtin_enumerate(hop): + hop.exception_cannot_occur() return hop.r_result.r_baseiter.newiter(hop) diff --git a/pypy/rpython/rstr.py b/pypy/rpython/rstr.py --- a/pypy/rpython/rstr.py +++ b/pypy/rpython/rstr.py @@ -288,6 +288,8 @@ def rtype_unicode(self, hop): if hop.args_s[0].is_constant(): + # convertion errors occur during annotation, so cannot any more: + hop.exception_cannot_occur() return hop.inputconst(hop.r_result, hop.s_result.const) repr = hop.args_r[0].repr v_str = hop.inputarg(repr, 0) diff --git a/pypy/rpython/rtyper.py b/pypy/rpython/rtyper.py --- a/pypy/rpython/rtyper.py +++ b/pypy/rpython/rtyper.py @@ -846,6 +846,7 @@ return result def exception_is_here(self): + self.llops._called_exception_is_here_or_cannot_occur = True if self.llops.llop_raising_exceptions is not None: raise TyperError("cannot catch an exception at more than one llop") if not self.exceptionlinks: @@ -861,6 +862,7 @@ self.llops.llop_raising_exceptions = len(self.llops) def exception_cannot_occur(self): + self.llops._called_exception_is_here_or_cannot_occur = True if self.llops.llop_raising_exceptions is not None: raise TyperError("cannot catch an exception at more than one llop") if not self.exceptionlinks: diff --git a/pypy/rpython/test/test_extregistry.py b/pypy/rpython/test/test_extregistry.py --- a/pypy/rpython/test/test_extregistry.py +++ b/pypy/rpython/test/test_extregistry.py @@ -114,6 +114,7 @@ _about_ = dummy_func s_result_annotation = annmodel.SomeInteger() def specialize_call(self, hop): + hop.exception_cannot_occur() return hop.inputconst(lltype.Signed, 42) def func(): diff --git a/pypy/rpython/test/test_rclass.py b/pypy/rpython/test/test_rclass.py --- a/pypy/rpython/test/test_rclass.py +++ b/pypy/rpython/test/test_rclass.py @@ -1085,6 +1085,7 @@ return annmodel.SomeInteger() def specialize_call(self, hop): [v_instance] = hop.inputargs(*hop.args_r) + hop.exception_is_here() return hop.gendirectcall(ll_my_gethash, v_instance) def f(n): diff --git a/pypy/rpython/test/test_rstr.py b/pypy/rpython/test/test_rstr.py --- a/pypy/rpython/test/test_rstr.py +++ b/pypy/rpython/test/test_rstr.py @@ -477,7 +477,11 @@ s1 = s[:3] s2 = s[3:] s3 = s[3:10] - return s1+s2 == s and s2+s1 == const('lohel') and s1+s3 == s + s4 = s[42:44] + return (s1+s2 == s and + s2+s1 == const('lohel') and + s1+s3 == s and + s4 == const('')) res = self.interpret(fn, [0]) assert res diff --git a/pypy/tool/clean_old_branches.py b/pypy/tool/clean_old_branches.py --- a/pypy/tool/clean_old_branches.py +++ b/pypy/tool/clean_old_branches.py @@ -38,7 +38,7 @@ closed_heads.reverse() for head, branch in closed_heads: - print '\t', branch + print '\t', head, '\t', branch print print 'The branches listed above will be merged to "closed-branches".' print 'You need to run this script in a clean working copy where you' diff --git a/pypy/translator/c/gcc/trackgcroot.py b/pypy/translator/c/gcc/trackgcroot.py --- a/pypy/translator/c/gcc/trackgcroot.py +++ b/pypy/translator/c/gcc/trackgcroot.py @@ -847,6 +847,10 @@ if sources: target, = sources + if target.endswith('@PLT'): + # In -fPIC mode, all functions calls have this suffix + target = target[:-4] + if target in self.FUNCTIONS_NOT_RETURNING: return [InsnStop(target)] if self.format == 'mingw32' and target == '__alloca': @@ -1137,7 +1141,7 @@ r_jump_rel_label = re.compile(r"\tj\w+\s+"+"(\d+)f"+"\s*$") r_unaryinsn_star= re.compile(r"\t[a-z]\w*\s+[*]("+OPERAND+")\s*$") - r_jmptable_item = re.compile(r"\t.quad\t"+LABEL+"(-\"[A-Za-z0-9$]+\")?\s*$") + r_jmptable_item = re.compile(r"\t.(?:quad|long)\t"+LABEL+"(-\"[A-Za-z0-9$]+\"|-"+LABEL+")?\s*$") r_jmptable_end = re.compile(r"\t.text|\t.section\s+.text|\t\.align|"+LABEL) r_gcroot_marker = re.compile(r"\t/[*] GCROOT ("+LOCALVARFP+") [*]/") diff --git a/pypy/translator/c/test/test_extfunc.py b/pypy/translator/c/test/test_extfunc.py --- a/pypy/translator/c/test/test_extfunc.py +++ b/pypy/translator/c/test/test_extfunc.py @@ -919,4 +919,5 @@ t, cbuilder = self.compile(does_stuff) data = cbuilder.cmdexec('') res = os.nice(0) + 3 + if res > 19: res = 19 # xxx Linux specific, probably assert data.startswith('os.nice returned %d\n' % res) diff --git a/pypy/translator/cli/dotnet.py b/pypy/translator/cli/dotnet.py --- a/pypy/translator/cli/dotnet.py +++ b/pypy/translator/cli/dotnet.py @@ -459,6 +459,7 @@ def specialize_call(self, hop): + hop.exception_cannot_occur() assert hop.args_s[1].is_constant() TYPE = hop.args_s[1].const v_obj = hop.inputarg(hop.args_r[0], arg=0) @@ -507,6 +508,7 @@ def specialize_call(self, hop): v_obj, = hop.inputargs(*hop.args_r) + hop.exception_cannot_occur() return hop.genop('same_as', [v_obj], hop.r_result.lowleveltype) def new_array(type, length): @@ -608,6 +610,7 @@ def specialize_call(self, hop): v_type, = hop.inputargs(*hop.args_r) + hop.exception_cannot_occur() return hop.genop('cli_typeof', [v_type], hop.r_result.lowleveltype) @@ -626,6 +629,7 @@ v_obj, = hop.inputargs(*hop.args_r) methodname = hop.args_r[0].methodname c_methodname = hop.inputconst(ootype.Void, methodname) + hop.exception_cannot_occur() return hop.genop('cli_eventhandler', [v_obj, c_methodname], hop.r_result.lowleveltype) @@ -647,6 +651,7 @@ def specialize_call(self, hop): assert isinstance(hop.args_s[0], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[0], arg=0) + hop.exception_cannot_occur() return hop.genop('oodowncast', [v_inst], resulttype = hop.r_result.lowleveltype) @@ -668,6 +673,7 @@ def specialize_call(self, hop): assert isinstance(hop.args_s[0], annmodel.SomeOOInstance) v_inst = hop.inputarg(hop.args_r[0], arg=0) + hop.exception_cannot_occur() return hop.genop('ooupcast', [v_inst], resulttype = hop.r_result.lowleveltype) @@ -701,6 +707,7 @@ def specialize_call(self, hop): v_obj = hop.inputarg(hop.args_r[0], arg=0) + hop.exception_cannot_occur() return hop.genop('oodowncast', [v_obj], hop.r_result.lowleveltype) From noreply at buildbot.pypy.org Sat Apr 7 21:01:57 2012 From: noreply at buildbot.pypy.org (antocuni) Date: Sat, 7 Apr 2012 21:01:57 +0200 (CEST) Subject: [pypy-commit] pypy default: a script to update the contributors list Message-ID: <20120407190157.E34B58208A@wyvern.cs.uni-duesseldorf.de> Author: Antonio Cuni Branch: Changeset: r54237:df57ca3b7687 Date: 2012-04-07 19:56 +0200 http://bitbucket.org/pypy/pypy/changeset/df57ca3b7687/ Log: a script to update the contributors list diff --git a/pypy/doc/tool/makecontributor.py b/pypy/doc/tool/makecontributor.py new file mode 100644 --- /dev/null +++ b/pypy/doc/tool/makecontributor.py @@ -0,0 +1,64 @@ +import py +import sys +from collections import defaultdict +import operator +import re +import mercurial.localrepo +import mercurial.ui + +ROOT = py.path.local(__file__).join('..', '..', '..', '..') +author_re = re.compile('(.*) <.*>') + +excluded = set(["pypy" "convert-repo"]) + +alias = { + 'arigo': 'Armin Rigo', + 'lac': 'Laura Creighton', + 'fijal': 'Maciej Fijalkowski', + 'tismer at christia-wjtqxl.localdomain': 'Christian Tismer', + 'holger krekel': 'Holger Krekel', + 'hager': 'Sven Hager', + 'mattip': 'Matti Picus', + 'mattip>': 'Matti Picus', + 'matthp': 'Matti Picus', + 'Matti Picus matti.picus at gmail.com': 'Matti Picus', + 'edelsohn': 'David Edelsohn', + 'edelsoh': 'David Edelsohn', + 'l.diekmann': 'Lukas Diekmann', + 'ldiekmann': 'Lukas Diekmann', + 'aliles': 'Aaron Iles', + 'mikefc': 'Michael Cheng', + 'cocoatomo': 'Tomo Cocoa', + 'roberto at goyle': 'Roberto De Ioris', + 'roberto at mrspurr': 'Roberto De Ioris', + 'landtuna at gmail.com': 'Jim Hunziker', + 'kristjan at kristjan-lp.ccp.ad.local': 'Kristjan Valur Jonsson', + } + +def get_canonical_author(name): + match = author_re.match(name) + if match: + name = match.group(1) + return alias.get(name, name) + +def main(show_numbers): + ui = mercurial.ui.ui() + repo = mercurial.localrepo.localrepository(ui, str(ROOT)) + authors = defaultdict(int) + for i in repo: + ctx = repo[i] + author = get_canonical_author(ctx.user()) + if author not in excluded: + authors[author] += 1 + # + items = authors.items() + items.sort(key=operator.itemgetter(1), reverse=True) + for name, n in items: + if show_numbers: + print '%5d %s' % (n, name) + else: + print name + +if __name__ == '__main__': + show_numbers = '-n' in sys.argv + main(show_numbers) From noreply at buildbot.pypy.org Sat Apr 7 21:01:59 2012 From: noreply at buildbot.pypy.org (antocuni) Date: Sat, 7 Apr 2012 21:01:59 +0200 (CEST) Subject: [pypy-commit] pypy default: be advanced: try hard to parse the pair programming markers in commit logs, and count them in the commit count :-) Message-ID: <20120407190159.768828208A@wyvern.cs.uni-duesseldorf.de> Author: Antonio Cuni Branch: Changeset: r54238:97275a06775e Date: 2012-04-07 21:00 +0200 http://bitbucket.org/pypy/pypy/changeset/97275a06775e/ Log: be advanced: try hard to parse the pair programming markers in commit logs, and count them in the commit count :-) diff --git a/pypy/doc/tool/makecontributor.py b/pypy/doc/tool/makecontributor.py --- a/pypy/doc/tool/makecontributor.py +++ b/pypy/doc/tool/makecontributor.py @@ -8,50 +8,119 @@ ROOT = py.path.local(__file__).join('..', '..', '..', '..') author_re = re.compile('(.*) <.*>') - -excluded = set(["pypy" "convert-repo"]) +pair_programming_re = re.compile(r'^\((.*?)\)') +excluded = set(["pypy", "convert-repo"]) alias = { - 'arigo': 'Armin Rigo', - 'lac': 'Laura Creighton', - 'fijal': 'Maciej Fijalkowski', - 'tismer at christia-wjtqxl.localdomain': 'Christian Tismer', - 'holger krekel': 'Holger Krekel', - 'hager': 'Sven Hager', - 'mattip': 'Matti Picus', - 'mattip>': 'Matti Picus', - 'matthp': 'Matti Picus', - 'Matti Picus matti.picus at gmail.com': 'Matti Picus', - 'edelsohn': 'David Edelsohn', - 'edelsoh': 'David Edelsohn', - 'l.diekmann': 'Lukas Diekmann', - 'ldiekmann': 'Lukas Diekmann', - 'aliles': 'Aaron Iles', - 'mikefc': 'Michael Cheng', - 'cocoatomo': 'Tomo Cocoa', - 'roberto at goyle': 'Roberto De Ioris', - 'roberto at mrspurr': 'Roberto De Ioris', - 'landtuna at gmail.com': 'Jim Hunziker', - 'kristjan at kristjan-lp.ccp.ad.local': 'Kristjan Valur Jonsson', + 'Anders Chrigstrom': ['arre'], + 'Antonio Cuni': ['antocuni', 'anto'], + 'Armin Rigo': ['arigo', 'arfigo', 'armin', 'arigato'], + 'Maciej Fijalkowski': ['fijal'], + 'Carl Friedrich Bolz': ['cfbolz', 'cf'], + 'Samuele Pedroni': ['pedronis', 'samuele', 'samule'], + 'Michael Hudson': ['mwh'], + 'Holger Krekel': ['hpk', 'holger krekel', 'holger', 'hufpk'], + "Amaury Forgeot d'Arc": ['afa'], + 'Alex Gaynor': ['alex', 'agaynor'], + 'David Schneider': ['bivab', 'david'], + 'Christian Tismer': ['chris', 'christian', 'tismer', + 'tismer at christia-wjtqxl.localdomain'], + 'Benjamin Peterson': ['benjamin'], + 'Hakan Ardo': ['hakan', 'hakanardo'], + 'Niklaus Haldimann': ['nik'], + 'Alexander Schremmer': ['xoraxax'], + 'Anders Hammarquist': ['iko'], + 'David Edelsohn': ['edelsoh', 'edelsohn'], + 'Niko Matsakis': ['niko'], + 'Jakub Gustak': ['jlg'], + 'Guido Wesdorp': ['guido'], + 'Michael Foord': ['mfoord'], + 'Mark Pearse': ['mwp'], + 'Toon Verwaest': ['tverwaes'], + 'Eric van Riet Paap': ['ericvrp'], + 'Jacob Hallen': ['jacob', 'jakob'], + 'Anders Lehmann': ['ale', 'anders'], + 'Bert Freudenberg': ['bert'], + 'Boris Feigin': ['boris', 'boria'], + 'Valentino Volonghi': ['valentino', 'dialtone'], + 'Aurelien Campeas': ['aurelien', 'aureliene'], + 'Adrien Di Mascio': ['adim'], + 'Jacek Generowicz': ['Jacek', 'jacek'], + 'Jim Hunziker': ['landtuna at gmail.com'], + 'Kristjan Valur Jonsson': ['kristjan at kristjan-lp.ccp.ad.local'], + 'Laura Creighton': ['lac'], + 'Aaron Iles': ['aliles'], + 'Ludovic Aubry': ['ludal', 'ludovic'], + 'Lukas Diekmann': ['l.diekmann', 'ldiekmann'], + 'Matti Picus': ['Matti Picus matti.picus at gmail.com', + 'matthp', 'mattip', 'mattip>'], + 'Michael Cheng': ['mikefc'], + 'Richard Emslie': ['rxe'], + 'Roberto De Ioris': ['roberto at goyle'], + 'Roberto De Ioris': ['roberto at mrspurr'], + 'Sven Hager': ['hager'], + 'Tomo Cocoa': ['cocoatomo'], } +alias_map = {} +for name, nicks in alias.iteritems(): + for nick in nicks: + alias_map[nick] = name + def get_canonical_author(name): match = author_re.match(name) if match: name = match.group(1) - return alias.get(name, name) + return alias_map.get(name, name) + +ignored_nicknames = defaultdict(int) + +def get_more_authors(log): + match = pair_programming_re.match(log) + if not match: + return set() + ignore_words = ['around', 'consulting', 'yesterday', 'for a bit', 'thanks', + 'in-progress', 'bits of', 'even a little', 'floating',] + sep_words = ['and', ';', '+', '/', 'with special by'] + nicknames = match.group(1) + for word in ignore_words: + nicknames = nicknames.replace(word, '') + for word in sep_words: + nicknames = nicknames.replace(word, ',') + nicknames = [nick.strip().lower() for nick in nicknames.split(',')] + authors = set() + for nickname in nicknames: + author = alias_map.get(nickname) + if not author: + ignored_nicknames[nickname] += 1 + else: + authors.add(author) + return authors def main(show_numbers): ui = mercurial.ui.ui() repo = mercurial.localrepo.localrepository(ui, str(ROOT)) - authors = defaultdict(int) + authors_count = defaultdict(int) for i in repo: ctx = repo[i] - author = get_canonical_author(ctx.user()) - if author not in excluded: - authors[author] += 1 - # - items = authors.items() + authors = set() + authors.add(get_canonical_author(ctx.user())) + authors.update(get_more_authors(ctx.description())) + for author in authors: + if author not in excluded: + authors_count[author] += 1 + + # uncomment the next lines to get the list of nicknamed which could not be + # parsed from commit logs + ## items = ignored_nicknames.items() + ## items.sort(key=operator.itemgetter(1), reverse=True) + ## for name, n in items: + ## if show_numbers: + ## print '%5d %s' % (n, name) + ## else: + ## print name + + items = authors_count.items() items.sort(key=operator.itemgetter(1), reverse=True) for name, n in items: if show_numbers: From noreply at buildbot.pypy.org Sat Apr 7 21:02:00 2012 From: noreply at buildbot.pypy.org (antocuni) Date: Sat, 7 Apr 2012 21:02:00 +0200 (CEST) Subject: [pypy-commit] pypy default: merge Message-ID: <20120407190200.B804C8208A@wyvern.cs.uni-duesseldorf.de> Author: Antonio Cuni Branch: Changeset: r54239:df6b4266c2e9 Date: 2012-04-07 21:01 +0200 http://bitbucket.org/pypy/pypy/changeset/df6b4266c2e9/ Log: merge diff --git a/pypy/interpreter/typedef.py b/pypy/interpreter/typedef.py --- a/pypy/interpreter/typedef.py +++ b/pypy/interpreter/typedef.py @@ -321,7 +321,7 @@ def user_setup(self, space, w_subtype): self.w__dict__ = space.newdict( - instance=True, classofinstance=w_subtype) + instance=True) base_user_setup(self, space, w_subtype) def setclass(self, space, w_subtype): diff --git a/pypy/objspace/fake/objspace.py b/pypy/objspace/fake/objspace.py --- a/pypy/objspace/fake/objspace.py +++ b/pypy/objspace/fake/objspace.py @@ -110,7 +110,7 @@ "NOT_RPYTHON" raise NotImplementedError - def newdict(self, module=False, instance=False, classofinstance=None, + def newdict(self, module=False, instance=False, strdict=False): return w_some_obj() diff --git a/pypy/objspace/std/dictmultiobject.py b/pypy/objspace/std/dictmultiobject.py --- a/pypy/objspace/std/dictmultiobject.py +++ b/pypy/objspace/std/dictmultiobject.py @@ -33,8 +33,7 @@ @staticmethod def allocate_and_init_instance(space, w_type=None, module=False, - instance=False, classofinstance=None, - strdict=False): + instance=False, strdict=False): if space.config.objspace.std.withcelldict and module: from pypy.objspace.std.celldict import ModuleDictStrategy @@ -563,10 +562,7 @@ def listview_int(self, w_dict): return self.unerase(w_dict.dstorage).keys() - def w_keys(self, w_dict): - # XXX there is no space.newlist_int yet - space = self.space - return space.call_function(space.w_list, w_dict) + # XXX there is no space.newlist_int yet to implement w_keys more efficiently class IntIteratorImplementation(_WrappedIteratorMixin, IteratorImplementation): pass diff --git a/pypy/objspace/std/identitydict.py b/pypy/objspace/std/identitydict.py --- a/pypy/objspace/std/identitydict.py +++ b/pypy/objspace/std/identitydict.py @@ -1,5 +1,5 @@ ## ---------------------------------------------------------------------------- -## dict strategy (see dict_multiobject.py) +## dict strategy (see dictmultiobject.py) from pypy.rlib import rerased from pypy.rlib.debug import mark_dict_non_null @@ -80,8 +80,8 @@ def iter(self, w_dict): return IdentityDictIteratorImplementation(self.space, self, w_dict) - def keys(self, w_dict): - return self.unerase(w_dict.dstorage).keys() + def w_keys(self, w_dict): + return self.space.newlist(self.unerase(w_dict.dstorage).keys()) class IdentityDictIteratorImplementation(_UnwrappedIteratorMixin, IteratorImplementation): diff --git a/pypy/objspace/std/objspace.py b/pypy/objspace/std/objspace.py --- a/pypy/objspace/std/objspace.py +++ b/pypy/objspace/std/objspace.py @@ -313,11 +313,10 @@ def newlist_str(self, list_s): return W_ListObject.newlist_str(self, list_s) - def newdict(self, module=False, instance=False, classofinstance=None, + def newdict(self, module=False, instance=False, strdict=False): return W_DictMultiObject.allocate_and_init_instance( self, module=module, instance=instance, - classofinstance=classofinstance, strdict=strdict) def newset(self): diff --git a/pypy/objspace/std/test/test_dictmultiobject.py b/pypy/objspace/std/test/test_dictmultiobject.py --- a/pypy/objspace/std/test/test_dictmultiobject.py +++ b/pypy/objspace/std/test/test_dictmultiobject.py @@ -885,10 +885,9 @@ def newtuple(self, l): return tuple(l) - def newdict(self, module=False, instance=False, classofinstance=None): + def newdict(self, module=False, instance=False): return W_DictMultiObject.allocate_and_init_instance( - self, module=module, instance=instance, - classofinstance=classofinstance) + self, module=module, instance=instance) def finditem_str(self, w_dict, s): return w_dict.getitem_str(s) # assume it's a multidict @@ -968,6 +967,20 @@ assert type(self.impl.strategy) is self.StrategyClass #assert self.impl.r_dict_content is None + def test_popitem(self): + self.fill_impl() + assert self.impl.length() == 2 + a, b = self.impl.popitem() + assert self.impl.length() == 1 + if a == self.string: + assert b == 1000 + assert self.impl.getitem(self.string2) == 2000 + else: + assert a == self.string2 + assert b == 2000 + assert self.impl.getitem_str(self.string) == 1000 + self.check_not_devolved() + def test_setitem(self): self.impl.setitem(self.string, 1000) assert self.impl.length() == 1 From noreply at buildbot.pypy.org Sun Apr 8 00:46:00 2012 From: noreply at buildbot.pypy.org (mattip) Date: Sun, 8 Apr 2012 00:46:00 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: windows exception on get linenumber, linecolumn after error Message-ID: <20120407224600.4536E8208A@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54240:0e4763b18703 Date: 2012-04-06 17:22 +0300 http://bitbucket.org/pypy/pypy/changeset/0e4763b18703/ Log: windows exception on get linenumber, linecolumn after error diff --git a/pypy/module/pyexpat/interp_pyexpat.py b/pypy/module/pyexpat/interp_pyexpat.py --- a/pypy/module/pyexpat/interp_pyexpat.py +++ b/pypy/module/pyexpat/interp_pyexpat.py @@ -10,6 +10,7 @@ from pypy.translator.platform import platform import sys +import os import weakref import py @@ -677,8 +678,12 @@ def set_error(self, space, code): err = rffi.charp2strn(XML_ErrorString(code), 200) - lineno = XML_GetCurrentLineNumber(self.itself) - colno = XML_GetCurrentColumnNumber(self.itself) + if os.name == 'nt': + lineno = -1 + colno = -1 + else: + lineno = XML_GetCurrentLineNumber(self.itself) + colno = XML_GetCurrentColumnNumber(self.itself) msg = "%s: line %d, column %d" % (err, lineno, colno) w_errorcls = space.fromcache(Cache).w_error w_error = space.call_function(w_errorcls, space.wrap(msg)) From noreply at buildbot.pypy.org Sun Apr 8 00:46:01 2012 From: noreply at buildbot.pypy.org (mattip) Date: Sun, 8 Apr 2012 00:46:01 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: fix for float('nan') returning -NAN, Microsoft should try to adhere to standards Message-ID: <20120407224601.7B03D8208A@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54241:da56cef5777d Date: 2012-04-08 01:44 +0300 http://bitbucket.org/pypy/pypy/changeset/da56cef5777d/ Log: fix for float('nan') returning -NAN, Microsoft should try to adhere to standards diff --git a/pypy/translator/c/primitive.py b/pypy/translator/c/primitive.py --- a/pypy/translator/c/primitive.py +++ b/pypy/translator/c/primitive.py @@ -11,6 +11,7 @@ GCHeaderOffset, GCREF, AddressAsInt from pypy.rpython.lltypesystem.llarena import RoundedUpForAllocation from pypy.translator.c.support import cdecl, barebonearray +from pypy.translator.platform import platform as target_platform # ____________________________________________________________ # @@ -98,11 +99,19 @@ else: return '%dLL' % value -def is_positive_nan(value): - # bah. we don't have math.copysign() if we're running Python 2.5 - import struct - c = struct.pack("!d", value)[0] - return {'\x7f': True, '\xff': False}[c] +if target_platform.name == 'msvc': + def is_positive_nan(value): + # Microsoft decided that NAN should have the sign bit set, + # so the values are reversed! + import struct + c = struct.pack("!d", value)[0] + return {'\x7f': False, '\xff': True}[c] +else: + def is_positive_nan(value): + # bah. we don't have math.copysign() if we're running Python 2.5 + import struct + c = struct.pack("!d", value)[0] + return {'\x7f': True, '\xff': False}[c] def name_float(value, db): if isinf(value): From noreply at buildbot.pypy.org Sun Apr 8 00:46:02 2012 From: noreply at buildbot.pypy.org (mattip) Date: Sun, 8 Apr 2012 00:46:02 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: revert console handling to run tests on this version Message-ID: <20120407224602.AF7958208A@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54242:05ab3be91dfd Date: 2012-04-08 01:45 +0300 http://bitbucket.org/pypy/pypy/changeset/05ab3be91dfd/ Log: revert console handling to run tests on this version diff --git a/pypy/translator/c/src/winstuff.c b/pypy/translator/c/src/winstuff.c --- a/pypy/translator/c/src/winstuff.c +++ b/pypy/translator/c/src/winstuff.c @@ -27,7 +27,7 @@ void pypy_Windows_startup(void) { -#if 0 && defined _MSC_VER && _MSC_VER >= 1400 && defined(__STDC_SECURE_LIB__) +#if defined _MSC_VER && _MSC_VER >= 1400 && defined(__STDC_SECURE_LIB__) /* Set CRT argument error handler */ _set_invalid_parameter_handler(InvalidParameterHandler); /* turn off assertions within CRT in debug mode; From pullrequests-noreply at bitbucket.org Sun Apr 8 06:57:25 2012 From: pullrequests-noreply at bitbucket.org (Roberto De Ioris) Date: Sun, 08 Apr 2012 04:57:25 -0000 Subject: [pypy-commit] [pypy/pypy] PyFile_FromFile, PySys_SetArgv and PyThreadState_New implementation (pull request #68) Message-ID: <819aec1e6e3998acaa7b35b136186f8a@bitbucket.org> A new pull request has been opened by Roberto De Ioris. unbit/pypy/default has changes to be pulled into pypy/pypy/embedded-pypy. https://bitbucket.org/pypy/pypy/pull-request/68/pyfile_fromfile-pysys_setargv-and Title: PyFile_FromFile, PySys_SetArgv and PyThreadState_New implementation PyFile_FromFile() is used by c apps to map a libc FILE object to a Python file object. The implementation make use of the pypy-specific file.fdopen function. PySys_SetArgv is used to set sys.argv. Both Tested with latest uWSGI tip. PyThreadState_New is only a prototype, i suspect the different internal threading usage in PyPy will require a bigger effort. Changes to be pulled: 5b28c054a739 by Roberto De Ioris: "added PySys_SetArgv and PyThreadState_New" 7bd1b6f657a8 by Roberto De Ioris: "merge with official pypy" a7b45b1ec178 by Matti Picus: "fix float-to-time conversion in windows" 057219807d73 by arigo: "merge heads" 01a73d86d3c7 by arigo: "minimark: Tweaks to reduce by one the number of checks in the common case." d356d169e770 by Roberto De Ioris: "add fflush to cpyext pyfile implementation" 1f3baa6ce8b1 by rob... at mrsurr: "merge" 2afeef0e350b by Roberto De Ioris: "implemed PyFile_FromFile as cpyext" 7eb44aa590a2 by Roberto De Ioris: "added Py_Initialize,Py_Finalize,Py_SetPythonHome,Py_SetProgramName as cpyext" 4c1e64afbe9a by Roberto De Ioris: "added implementation of Py_Initialize" -- This is an issue notification from bitbucket.org. You are receiving this either because you are the participating in a pull request, or you are following it. From noreply at buildbot.pypy.org Sun Apr 8 13:08:55 2012 From: noreply at buildbot.pypy.org (mattip) Date: Sun, 8 Apr 2012 13:08:55 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: Backed out changeset: da56cef5777d, pypy is OK problem is with underlying cpython Message-ID: <20120408110855.09E8C82111@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54243:0d96f6934f8f Date: 2012-04-08 10:15 +0300 http://bitbucket.org/pypy/pypy/changeset/0d96f6934f8f/ Log: Backed out changeset: da56cef5777d, pypy is OK problem is with underlying cpython diff --git a/pypy/translator/c/primitive.py b/pypy/translator/c/primitive.py --- a/pypy/translator/c/primitive.py +++ b/pypy/translator/c/primitive.py @@ -11,7 +11,6 @@ GCHeaderOffset, GCREF, AddressAsInt from pypy.rpython.lltypesystem.llarena import RoundedUpForAllocation from pypy.translator.c.support import cdecl, barebonearray -from pypy.translator.platform import platform as target_platform # ____________________________________________________________ # @@ -99,19 +98,11 @@ else: return '%dLL' % value -if target_platform.name == 'msvc': - def is_positive_nan(value): - # Microsoft decided that NAN should have the sign bit set, - # so the values are reversed! - import struct - c = struct.pack("!d", value)[0] - return {'\x7f': False, '\xff': True}[c] -else: - def is_positive_nan(value): - # bah. we don't have math.copysign() if we're running Python 2.5 - import struct - c = struct.pack("!d", value)[0] - return {'\x7f': True, '\xff': False}[c] +def is_positive_nan(value): + # bah. we don't have math.copysign() if we're running Python 2.5 + import struct + c = struct.pack("!d", value)[0] + return {'\x7f': True, '\xff': False}[c] def name_float(value, db): if isinf(value): From noreply at buildbot.pypy.org Sun Apr 8 13:08:56 2012 From: noreply at buildbot.pypy.org (mattip) Date: Sun, 8 Apr 2012 13:08:56 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: add more validate_fd Message-ID: <20120408110856.55A7D82111@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54244:10ccfa6466f5 Date: 2012-04-08 13:02 +0300 http://bitbucket.org/pypy/pypy/changeset/10ccfa6466f5/ Log: add more validate_fd diff --git a/pypy/rpython/module/ll_os.py b/pypy/rpython/module/ll_os.py --- a/pypy/rpython/module/ll_os.py +++ b/pypy/rpython/module/ll_os.py @@ -942,6 +942,8 @@ rffi.INT, threadsafe=False) def close_llimpl(fd): + if not rposix.validate_fd(fd): + raise OSError(rposix.get_errno(), 'Bad file descriptor') error = rffi.cast(lltype.Signed, os_close(rffi.cast(rffi.INT, fd))) if error == -1: raise OSError(rposix.get_errno(), "close failed") @@ -977,6 +979,8 @@ rffi.LONGLONG) def lseek_llimpl(fd, pos, how): + if not rposix.validate_fd(fd): + raise OSError(rposix.get_errno(), 'Bad file descriptor') how = fix_seek_arg(how) res = os_lseek(rffi.cast(rffi.INT, fd), rffi.cast(rffi.LONGLONG, pos), @@ -1002,6 +1006,8 @@ [rffi.INT, rffi.LONGLONG], rffi.INT) def ftruncate_llimpl(fd, length): + if not rposix.validate_fd(fd): + raise OSError(rposix.get_errno(), 'Bad file descriptor') res = rffi.cast(rffi.LONG, os_ftruncate(rffi.cast(rffi.INT, fd), rffi.cast(rffi.LONGLONG, length))) @@ -1020,6 +1026,8 @@ os_fsync = self.llexternal('_commit', [rffi.INT], rffi.INT) def fsync_llimpl(fd): + if not rposix.validate_fd(fd): + raise OSError(rposix.get_errno(), 'Bad file descriptor') res = rffi.cast(rffi.SIGNED, os_fsync(rffi.cast(rffi.INT, fd))) if res < 0: raise OSError(rposix.get_errno(), "fsync failed") @@ -1032,6 +1040,8 @@ os_fdatasync = self.llexternal('fdatasync', [rffi.INT], rffi.INT) def fdatasync_llimpl(fd): + if not rposix.validate_fd(fd): + raise OSError(rposix.get_errno(), 'Bad file descriptor') res = rffi.cast(rffi.SIGNED, os_fdatasync(rffi.cast(rffi.INT, fd))) if res < 0: raise OSError(rposix.get_errno(), "fdatasync failed") diff --git a/pypy/rpython/module/test/test_ll_os.py b/pypy/rpython/module/test/test_ll_os.py --- a/pypy/rpython/module/test/test_ll_os.py +++ b/pypy/rpython/module/test/test_ll_os.py @@ -191,7 +191,7 @@ def test_os_write(): #Same as test in rpython/test/test_rbuiltin - fname = str(udir.join('os_write_test.txt')) + fname = str(udir.join('os_test.txt')) fd = os.open(fname, os.O_WRONLY|os.O_CREAT, 0777) assert fd >= 0 os.write(fd, 'Hello world') @@ -202,6 +202,46 @@ os.close(fd) raises(OSError, os.write, fd, 'Hello world') +def test_os_close(): + fname = str(udir.join('os_test.txt')) + fd = os.open(fname, os.O_WRONLY|os.O_CREAT, 0777) + assert fd >= 0 + os.write(fd, 'Hello world') + os.close(fd) + raises(OSError, os.close, fd) + +def test_os_lseek(): + fname = str(udir.join('os_test.txt')) + fd = os.open(fname, os.O_WRONLY|os.O_CREAT, 0777) + assert fd >= 0 + os.write(fd, 'Hello world') + os.lseek(fd,0,0) + assert os.tell(fd) == 0 + os.close(fd) + raises(OSError, os.lseek, fd, 0, 0) + +def test_os_fsync(): + fname = str(udir.join('os_test.txt')) + fd = os.open(fname, os.O_WRONLY|os.O_CREAT, 0777) + assert fd >= 0 + os.write(fd, 'Hello world') + os.fsync(fd) + fid = open(fname) + assert fid.read() == 'Hello world' + os.close(fd) + raises(OSError, os.fsync, fd) + +def test_os_fdatasync(): + fname = str(udir.join('os_test.txt')) + fd = os.open(fname, os.O_WRONLY|os.O_CREAT, 0777) + assert fd >= 0 + os.write(fd, 'Hello world') + os.fdatasync(fd) + fid = open(fname) + assert fid.read() == 'Hello world' + os.close(fd) + raises(OSError, os.fdatasync, fd) + class ExpectTestOs: def setup_class(cls): if not hasattr(os, 'ttyname'): From noreply at buildbot.pypy.org Sun Apr 8 13:08:57 2012 From: noreply at buildbot.pypy.org (mattip) Date: Sun, 8 Apr 2012 13:08:57 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: skip test if unable to run Message-ID: <20120408110857.909A382111@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54245:49cc32603e11 Date: 2012-04-08 13:21 +0300 http://bitbucket.org/pypy/pypy/changeset/49cc32603e11/ Log: skip test if unable to run diff --git a/pypy/module/_multiprocessing/test/test_connection.py b/pypy/module/_multiprocessing/test/test_connection.py --- a/pypy/module/_multiprocessing/test/test_connection.py +++ b/pypy/module/_multiprocessing/test/test_connection.py @@ -157,13 +157,15 @@ raises(IOError, _multiprocessing.Connection, -15) def test_byte_order(self): + import socket + if not 'fromfd' in dir(socket): + skip('No fromfd in socket') # The exact format of net strings (length in network byte # order) is important for interoperation with others # implementations. rhandle, whandle = self.make_pair() whandle.send_bytes("abc") whandle.send_bytes("defg") - import socket sock = socket.fromfd(rhandle.fileno(), socket.AF_INET, socket.SOCK_STREAM) data1 = sock.recv(7) From noreply at buildbot.pypy.org Sun Apr 8 13:08:58 2012 From: noreply at buildbot.pypy.org (mattip) Date: Sun, 8 Apr 2012 13:08:58 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: fix test, catching raised exception with 'raises' fails on win32 Message-ID: <20120408110858.E5CD482111@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54246:36ff8218c07d Date: 2012-04-08 13:30 +0300 http://bitbucket.org/pypy/pypy/changeset/36ff8218c07d/ Log: fix test, catching raised exception with 'raises' fails on win32 diff --git a/pypy/module/_socket/test/test_sock_app.py b/pypy/module/_socket/test/test_sock_app.py --- a/pypy/module/_socket/test/test_sock_app.py +++ b/pypy/module/_socket/test/test_sock_app.py @@ -617,7 +617,10 @@ except timeout: pass # test sendall() timeout - raises(timeout, cli.sendall, 'foobar' * 70) + try: + cli.sendall('foobar'*70) + except: + pass # done cli.close() t.close() From noreply at buildbot.pypy.org Sun Apr 8 13:09:00 2012 From: noreply at buildbot.pypy.org (mattip) Date: Sun, 8 Apr 2012 13:09:00 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: windir can be lower case on windows 7 Message-ID: <20120408110900.26A1E82111@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54247:10aecff7bec5 Date: 2012-04-08 13:45 +0300 http://bitbucket.org/pypy/pypy/changeset/10aecff7bec5/ Log: windir can be lower case on windows 7 diff --git a/pypy/module/_winreg/test/test_winreg.py b/pypy/module/_winreg/test/test_winreg.py --- a/pypy/module/_winreg/test/test_winreg.py +++ b/pypy/module/_winreg/test/test_winreg.py @@ -198,7 +198,10 @@ import nt r = ExpandEnvironmentStrings(u"%windir%\\test") assert isinstance(r, unicode) - assert r == nt.environ["WINDIR"] + "\\test" + if 'WINDIR' in nt.environ.keys(): + assert r == nt.environ["WINDIR"] + "\\test" + else: + assert r == nt.environ["windir"] + "\\test" def test_long_key(self): from _winreg import ( From noreply at buildbot.pypy.org Sun Apr 8 13:09:01 2012 From: noreply at buildbot.pypy.org (mattip) Date: Sun, 8 Apr 2012 13:09:01 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: sign of nan is not defined, skip test Message-ID: <20120408110901.60BA882111@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54248:d31590b69d3c Date: 2012-04-08 13:49 +0300 http://bitbucket.org/pypy/pypy/changeset/d31590b69d3c/ Log: sign of nan is not defined, skip test diff --git a/pypy/module/math/test/test_math.py b/pypy/module/math/test/test_math.py --- a/pypy/module/math/test/test_math.py +++ b/pypy/module/math/test/test_math.py @@ -271,5 +271,6 @@ assert math.trunc(foo()) == "truncated" def test_copysign_nan(self): + skip('sign of nan is undefined') import math assert math.copysign(1.0, float('-nan')) == -1.0 From noreply at buildbot.pypy.org Sun Apr 8 13:09:02 2012 From: noreply at buildbot.pypy.org (mattip) Date: Sun, 8 Apr 2012 13:09:02 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: do not use copysign in testing nan Message-ID: <20120408110902.9FA4A82111@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54249:281bca084dae Date: 2012-04-08 13:58 +0300 http://bitbucket.org/pypy/pypy/changeset/281bca084dae/ Log: do not use copysign in testing nan diff --git a/pypy/module/micronumpy/test/test_ufuncs.py b/pypy/module/micronumpy/test/test_ufuncs.py --- a/pypy/module/micronumpy/test/test_ufuncs.py +++ b/pypy/module/micronumpy/test/test_ufuncs.py @@ -149,7 +149,11 @@ assert math.isnan(fmax(0, nan)) assert math.isnan(fmax(nan, nan)) # The numpy docs specify that the FIRST NaN should be used if both are NaN - assert math.copysign(1.0, fmax(nnan, nan)) == -1.0 + try: + import struct + except: + skip('no struct available, and copysign is unreliable. How to compare?') + assert struct.pack('d', fmax(nnan, nan)) == struct.pack('d', nnan) def test_fmin(self): from _numpypy import fmin @@ -165,7 +169,11 @@ assert math.isnan(fmin(0, nan)) assert math.isnan(fmin(nan, nan)) # The numpy docs specify that the FIRST NaN should be used if both are NaN - assert math.copysign(1.0, fmin(nnan, nan)) == -1.0 + try: + import struct + except: + skip('no struct available, and copysign is unreliable. How to compare?') + assert struct.pack('d', fmin(nan, nnan)) == struct.pack('d', nan) def test_fmod(self): from _numpypy import fmod From noreply at buildbot.pypy.org Sun Apr 8 14:11:49 2012 From: noreply at buildbot.pypy.org (mattip) Date: Sun, 8 Apr 2012 14:11:49 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: force sign to be dfined for testing with float('nan') Message-ID: <20120408121149.4384182111@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54250:c5f3e2ef91fd Date: 2012-04-08 14:29 +0300 http://bitbucket.org/pypy/pypy/changeset/c5f3e2ef91fd/ Log: force sign to be dfined for testing with float('nan') diff --git a/pypy/module/micronumpy/test/test_ufuncs.py b/pypy/module/micronumpy/test/test_ufuncs.py --- a/pypy/module/micronumpy/test/test_ufuncs.py +++ b/pypy/module/micronumpy/test/test_ufuncs.py @@ -270,8 +270,8 @@ assert ([ninf, -1.0, -1.0, -1.0, 0.0, 1.0, 2.0, 1.0, inf] == ceil(a)).all() assert ([ninf, -1.0, -1.0, -1.0, 0.0, 1.0, 1.0, 0.0, inf] == trunc(a)).all() assert all([math.isnan(f(float("nan"))) for f in floor, ceil, trunc]) - assert all([math.copysign(1, f(float("nan"))) == 1 for f in floor, ceil, trunc]) - assert all([math.copysign(1, f(float("-nan"))) == -1 for f in floor, ceil, trunc]) + assert all([math.copysign(1, f(abs(float("nan")))) == 1 for f in floor, ceil, trunc]) + assert all([math.copysign(1, f(-abs(float("nan")))) == -1 for f in floor, ceil, trunc]) def test_copysign(self): from _numpypy import array, copysign From noreply at buildbot.pypy.org Sun Apr 8 14:11:50 2012 From: noreply at buildbot.pypy.org (mattip) Date: Sun, 8 Apr 2012 14:11:50 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: low lying fruit Message-ID: <20120408121150.7E87882287@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54251:bacd1ee8cfaa Date: 2012-04-08 14:33 +0300 http://bitbucket.org/pypy/pypy/changeset/bacd1ee8cfaa/ Log: low lying fruit diff --git a/pypy/module/mmap/test/test_mmap.py b/pypy/module/mmap/test/test_mmap.py --- a/pypy/module/mmap/test/test_mmap.py +++ b/pypy/module/mmap/test/test_mmap.py @@ -596,7 +596,7 @@ import sys size = 0x14FFFFFFF if sys.platform.startswith('win') or sys.platform == 'darwin': - self.skip('test requires %s bytes and a long time to run' % size) + skip('test requires %s bytes and a long time to run' % size) with open(self.tmpname, "w+b") as f: f.seek(size) @@ -618,7 +618,7 @@ import sys size = 0x17FFFFFFF if sys.platform.startswith('win') or sys.platform == 'darwin': - self.skip('test requires %s bytes and a long time to run' % size) + skip('test requires %s bytes and a long time to run' % size) with open(self.tmpname, "w+b") as f: f.seek(size) From noreply at buildbot.pypy.org Sun Apr 8 14:11:51 2012 From: noreply at buildbot.pypy.org (mattip) Date: Sun, 8 Apr 2012 14:11:51 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: add check for offset validiy Message-ID: <20120408121151.B4DE782111@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54252:be83d722b0fb Date: 2012-04-08 15:04 +0300 http://bitbucket.org/pypy/pypy/changeset/be83d722b0fb/ Log: add check for offset validiy diff --git a/pypy/rlib/rmmap.py b/pypy/rlib/rmmap.py --- a/pypy/rlib/rmmap.py +++ b/pypy/rlib/rmmap.py @@ -747,6 +747,25 @@ # SEEK_SET = 0 # libc._lseek(fileno, 0, SEEK_SET) + # check file size + try: + st = os.fstat(fileno) + except OSError: + pass # ignore errors and trust map_size + else: + mode = st[stat.ST_MODE] + size = st[stat.ST_SIZE] + if stat.S_ISREG(mode): + if map_size == 0: + if offset > size: + raise RValueError( + "mmap offset is greater than file size") + map_size = int(size - offset) + if map_size != size - offset: + raise RValueError("mmap length is too large") + elif offset + map_size > size: + raise RValueError("mmap length is greater than file size") + m = MMap(access, offset) m.file_handle = INVALID_HANDLE m.map_handle = INVALID_HANDLE From noreply at buildbot.pypy.org Sun Apr 8 14:46:59 2012 From: noreply at buildbot.pypy.org (mattip) Date: Sun, 8 Apr 2012 14:46:59 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: make sure float('nan') does not set the sign bit Message-ID: <20120408124659.571B682111@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54253:7a8139560192 Date: 2012-04-08 15:42 +0300 http://bitbucket.org/pypy/pypy/changeset/7a8139560192/ Log: make sure float('nan') does not set the sign bit diff --git a/pypy/module/rctime/test/test_rctime.py b/pypy/module/rctime/test/test_rctime.py --- a/pypy/module/rctime/test/test_rctime.py +++ b/pypy/module/rctime/test/test_rctime.py @@ -108,7 +108,10 @@ assert long(rctime.mktime(rctime.gmtime(t))) - rctime.timezone == long(t) ltime = rctime.localtime() assert rctime.mktime(tuple(ltime)) == rctime.mktime(ltime) - + try: + rctime.localtime(-1) + except: + skip('localtime cannot be negative on this platform') assert rctime.mktime(rctime.localtime(-1)) == -1 def test_asctime(self): diff --git a/pypy/translator/c/src/ll_math.c b/pypy/translator/c/src/ll_math.c --- a/pypy/translator/c/src/ll_math.c +++ b/pypy/translator/c/src/ll_math.c @@ -20,7 +20,7 @@ #define PyPy_IS_INFINITY(X) ((X) && \ (Py_FORCE_DOUBLE(X)*0.5 == Py_FORCE_DOUBLE(X))) #endif -#define PyPy_NAN (HUGE_VAL * 0.) +#define PyPy_NAN fabs(HUGE_VAL * 0.) /* The following copyright notice applies to the original implementations of acosh, asinh and atanh. */ From noreply at buildbot.pypy.org Sun Apr 8 14:47:01 2012 From: noreply at buildbot.pypy.org (mattip) Date: Sun, 8 Apr 2012 14:47:01 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: remove invalid_parameter_handler Message-ID: <20120408124701.61C0082111@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54254:e3349e81f0e9 Date: 2012-04-08 15:42 +0300 http://bitbucket.org/pypy/pypy/changeset/e3349e81f0e9/ Log: remove invalid_parameter_handler diff --git a/pypy/translator/c/src/winstuff.c b/pypy/translator/c/src/winstuff.c --- a/pypy/translator/c/src/winstuff.c +++ b/pypy/translator/c/src/winstuff.c @@ -27,7 +27,7 @@ void pypy_Windows_startup(void) { -#if defined _MSC_VER && _MSC_VER >= 1400 && defined(__STDC_SECURE_LIB__) +#if 0 &&defined _MSC_VER && _MSC_VER >= 1400 && defined(__STDC_SECURE_LIB__) /* Set CRT argument error handler */ _set_invalid_parameter_handler(InvalidParameterHandler); /* turn off assertions within CRT in debug mode; From noreply at buildbot.pypy.org Sun Apr 8 15:56:00 2012 From: noreply at buildbot.pypy.org (mattip) Date: Sun, 8 Apr 2012 15:56:00 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: more precise exception handling (antocuni) Message-ID: <20120408135600.2EFB382111@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54255:c0ded41a61e0 Date: 2012-04-08 16:55 +0300 http://bitbucket.org/pypy/pypy/changeset/c0ded41a61e0/ Log: more precise exception handling (antocuni) diff --git a/pypy/module/_socket/test/test_sock_app.py b/pypy/module/_socket/test/test_sock_app.py --- a/pypy/module/_socket/test/test_sock_app.py +++ b/pypy/module/_socket/test/test_sock_app.py @@ -619,7 +619,7 @@ # test sendall() timeout try: cli.sendall('foobar'*70) - except: + except timeout: pass # done cli.close() From noreply at buildbot.pypy.org Sun Apr 8 18:02:58 2012 From: noreply at buildbot.pypy.org (fijal) Date: Sun, 8 Apr 2012 18:02:58 +0200 (CEST) Subject: [pypy-commit] pypy dynamic-specialized-tuple: typo Message-ID: <20120408160258.4BA0D82111@wyvern.cs.uni-duesseldorf.de> Author: Maciej Fijalkowski Branch: dynamic-specialized-tuple Changeset: r54256:d6ba957b6cb5 Date: 2012-04-08 17:04 +0200 http://bitbucket.org/pypy/pypy/changeset/d6ba957b6cb5/ Log: typo diff --git a/pypy/jit/backend/x86/test/test_basic.py b/pypy/jit/backend/x86/test/test_basic.py --- a/pypy/jit/backend/x86/test/test_basic.py +++ b/pypy/jit/backend/x86/test/test_basic.py @@ -14,7 +14,7 @@ class TestBasic(Jit386Mixin, test_ajit.BaseLLtypeTests): # for the individual tests see - # ====> ../../../metainterp/test/test_basic.py + # ====> ../../../metainterp/test/test_ajit.py def test_bug(self): jitdriver = JitDriver(greens = [], reds = ['n']) class X(object): From noreply at buildbot.pypy.org Sun Apr 8 18:02:59 2012 From: noreply at buildbot.pypy.org (fijal) Date: Sun, 8 Apr 2012 18:02:59 +0200 (CEST) Subject: [pypy-commit] pypy dynamic-specialized-tuple: fix the test on x86 Message-ID: <20120408160259.8AB7682111@wyvern.cs.uni-duesseldorf.de> Author: Maciej Fijalkowski Branch: dynamic-specialized-tuple Changeset: r54257:7a3a9c99b85d Date: 2012-04-08 18:02 +0200 http://bitbucket.org/pypy/pypy/changeset/7a3a9c99b85d/ Log: fix the test on x86 diff --git a/pypy/jit/backend/llsupport/llmodel.py b/pypy/jit/backend/llsupport/llmodel.py --- a/pypy/jit/backend/llsupport/llmodel.py +++ b/pypy/jit/backend/llsupport/llmodel.py @@ -22,6 +22,7 @@ gcdescr=None): assert type(opts) is not bool self.opts = opts + self.arraydescr_cache = {} from pypy.jit.backend.llsupport.gc import get_ll_description AbstractCPU.__init__(self) @@ -292,7 +293,14 @@ extrainfo, ffi_flags) def copy_and_change_descr_typeinfo_to_ptr(self, descr): - return ArrayDescr(descr.basesize, descr.itemsize, descr.lendescr, FLAG_POINTER) + key = (descr.basesize, descr.itemsize, descr.lendescr) + try: + return self.arraydescr_cache[key] + except KeyError: + new_descr = ArrayDescr(descr.basesize, descr.itemsize, + descr.lendescr, FLAG_POINTER) + self.arraydescr_cache[key] = new_descr + return new_descr def get_overflow_error(self): ovf_vtable = self.cast_adr_to_int(self._ovf_error_vtable) diff --git a/pypy/jit/codewriter/jtransform.py b/pypy/jit/codewriter/jtransform.py --- a/pypy/jit/codewriter/jtransform.py +++ b/pypy/jit/codewriter/jtransform.py @@ -1,4 +1,3 @@ -import copy import py From noreply at buildbot.pypy.org Sun Apr 8 19:07:26 2012 From: noreply at buildbot.pypy.org (mattip) Date: Sun, 8 Apr 2012 19:07:26 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: sidestep copysign bug in underlying python for test Message-ID: <20120408170726.A89DE82111@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54258:9ced0e490fa8 Date: 2012-04-08 19:51 +0300 http://bitbucket.org/pypy/pypy/changeset/9ced0e490fa8/ Log: sidestep copysign bug in underlying python for test diff --git a/pypy/module/micronumpy/test/test_ufuncs.py b/pypy/module/micronumpy/test/test_ufuncs.py --- a/pypy/module/micronumpy/test/test_ufuncs.py +++ b/pypy/module/micronumpy/test/test_ufuncs.py @@ -148,12 +148,11 @@ assert math.isnan(fmax(nan, 0)) assert math.isnan(fmax(0, nan)) assert math.isnan(fmax(nan, nan)) - # The numpy docs specify that the FIRST NaN should be used if both are NaN - try: - import struct - except: - skip('no struct available, and copysign is unreliable. How to compare?') - assert struct.pack('d', fmax(nnan, nan)) == struct.pack('d', nnan) + # The numpy docs specify that + # the FIRST NaN should be used if both are NaN + # use copysign on both sides to sidestep bug in nan representaion + # on Microsoft win32 + assert math.copysign(1., fmax(nnan, nan)) == math.copysign(1., nnan) def test_fmin(self): from _numpypy import fmin @@ -167,13 +166,11 @@ assert (fmin(a, [1]*5) == [ninf, -5, 0, 1, 1]).all() assert math.isnan(fmin(nan, 0)) assert math.isnan(fmin(0, nan)) - assert math.isnan(fmin(nan, nan)) - # The numpy docs specify that the FIRST NaN should be used if both are NaN - try: - import struct - except: - skip('no struct available, and copysign is unreliable. How to compare?') - assert struct.pack('d', fmin(nan, nnan)) == struct.pack('d', nan) + # The numpy docs specify that + # the FIRST NaN should be used if both are NaN + # use copysign on both sides to sidestep bug in nan representaion + # on Microsoft win32 + assert math.copysign(1., fmin(nnan, nan)) == math.copysign(1., nnan) def test_fmod(self): from _numpypy import fmod From noreply at buildbot.pypy.org Sun Apr 8 19:07:27 2012 From: noreply at buildbot.pypy.org (mattip) Date: Sun, 8 Apr 2012 19:07:27 +0200 (CEST) Subject: [pypy-commit] pypy default: fix test for 32 bit platforms Message-ID: <20120408170727.E2ADC82111@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: Changeset: r54259:aaa93c295404 Date: 2012-04-08 20:06 +0300 http://bitbucket.org/pypy/pypy/changeset/aaa93c295404/ Log: fix test for 32 bit platforms diff --git a/pypy/module/micronumpy/test/test_ufuncs.py b/pypy/module/micronumpy/test/test_ufuncs.py --- a/pypy/module/micronumpy/test/test_ufuncs.py +++ b/pypy/module/micronumpy/test/test_ufuncs.py @@ -592,9 +592,8 @@ def test_shift(self): from _numpypy import left_shift, right_shift - import sys - assert (left_shift([5, 1], [2, 31]) == [20, 2**31]).all() + assert (left_shift([5, 1], [2, 13]) == [20, 2**13]).all() assert (right_shift(10, range(5)) == [10, 5, 2, 1, 0]).all() def test_comparisons(self): From noreply at buildbot.pypy.org Mon Apr 9 04:15:21 2012 From: noreply at buildbot.pypy.org (taavi_burns) Date: Mon, 9 Apr 2012 04:15:21 +0200 (CEST) Subject: [pypy-commit] pypy numpy-ufuncs3: Update logaddexp(2) tests to run correctly under cpython/numpy. The current micronumpy implementation is wrong; fix in next commit. Message-ID: <20120409021521.D9C8982111@wyvern.cs.uni-duesseldorf.de> Author: Taavi Burns Branch: numpy-ufuncs3 Changeset: r54260:2a1a61463bd1 Date: 2012-04-08 22:14 -0400 http://bitbucket.org/pypy/pypy/changeset/2a1a61463bd1/ Log: Update logaddexp(2) tests to run correctly under cpython/numpy. The current micronumpy implementation is wrong; fix in next commit. diff --git a/pypy/module/micronumpy/test/test_ufuncs.py b/pypy/module/micronumpy/test/test_ufuncs.py --- a/pypy/module/micronumpy/test/test_ufuncs.py +++ b/pypy/module/micronumpy/test/test_ufuncs.py @@ -763,6 +763,7 @@ def test_logaddexp(self): import math + import sys from _numpypy import logaddexp # From the numpy documentation @@ -773,7 +774,8 @@ assert logaddexp(0, 0) == math.log(2) assert logaddexp(float('-inf'), 0) == 0 - assert logaddexp(12345678, 12345678) == float('inf') + assert logaddexp(sys.float_info.max, sys.float_info.max) == sys.float_info.max + assert logaddexp(sys.float_info.min, sys.float_info.min) == math.log(2) assert math.isnan(logaddexp(float('nan'), 1)) assert math.isnan(logaddexp(1, float('nan'))) @@ -786,6 +788,7 @@ def test_logaddexp2(self): import math + import sys from _numpypy import logaddexp2 log2 = math.log(2) @@ -797,7 +800,8 @@ assert logaddexp2(0, 0) == 1 assert logaddexp2(float('-inf'), 0) == 0 - assert logaddexp2(12345678, 12345678) == float('inf') + assert logaddexp2(sys.float_info.max, sys.float_info.max) == sys.float_info.max + assert logaddexp2(sys.float_info.min, sys.float_info.min) == 1.0 assert math.isnan(logaddexp2(float('nan'), 1)) assert math.isnan(logaddexp2(1, float('nan'))) From noreply at buildbot.pypy.org Mon Apr 9 10:19:17 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 9 Apr 2012 10:19:17 +0200 (CEST) Subject: [pypy-commit] pypy stm-gc: Add enter/leave_transactional_mode operations. Message-ID: <20120409081917.CC6E246E0F0@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: stm-gc Changeset: r54262:f7e7bb5d8f56 Date: 2012-04-05 12:12 +0200 http://bitbucket.org/pypy/pypy/changeset/f7e7bb5d8f56/ Log: Add enter/leave_transactional_mode operations. diff --git a/pypy/module/transaction/interp_transaction.py b/pypy/module/transaction/interp_transaction.py --- a/pypy/module/transaction/interp_transaction.py +++ b/pypy/module/transaction/interp_transaction.py @@ -269,10 +269,14 @@ @rgc.no_collect def _run(): # --- start the threads --- don't use the GC here any more! --- + rstm.enter_transactional_mode() + # for i in range(state.num_threads): threadintf.start_new_thread(_run_thread) # state.lock_unfinished() # wait for all threads to finish + # + rstm.leave_transactional_mode() # --- done, we can use the GC again --- diff --git a/pypy/rlib/rstm.py b/pypy/rlib/rstm.py --- a/pypy/rlib/rstm.py +++ b/pypy/rlib/rstm.py @@ -50,6 +50,12 @@ if not we_are_translated(): _global_lock.release() +def enter_transactional_mode(): + llop.stm_enter_transactional_mode(lltype.Void) + +def leave_transactional_mode(): + llop.stm_leave_transactional_mode(lltype.Void) + def descriptor_init(): if not we_are_translated(): _global_lock.acquire() llop.stm_descriptor_init(lltype.Void) diff --git a/pypy/rpython/lltypesystem/lloperation.py b/pypy/rpython/lltypesystem/lloperation.py --- a/pypy/rpython/lltypesystem/lloperation.py +++ b/pypy/rpython/lltypesystem/lloperation.py @@ -403,6 +403,8 @@ 'stm_become_inevitable': LLOp(), 'stm_descriptor_init': LLOp(canrun=True), 'stm_descriptor_done': LLOp(canrun=True), + 'stm_enter_transactional_mode': LLOp(), + 'stm_leave_transactional_mode': LLOp(), 'stm_writebarrier': LLOp(sideeffects=False), 'stm_normalize_global': LLOp(), 'stm_start_transaction': LLOp(canrun=True), diff --git a/pypy/rpython/memory/gctransform/stmframework.py b/pypy/rpython/memory/gctransform/stmframework.py --- a/pypy/rpython/memory/gctransform/stmframework.py +++ b/pypy/rpython/memory/gctransform/stmframework.py @@ -32,6 +32,12 @@ self.stm_normalize_global_ptr = getfn( self.gcdata.gc.stm_normalize_global, [annmodel.SomeAddress()], annmodel.SomeAddress()) + self.stm_enter_transactional_mode_ptr = getfn( + self.gcdata.gc.enter_transactional_mode.im_func + [s_sc], annmodel.s_None) + self.stm_leave_transactional_mode_ptr = getfn( + self.gcdata.gc.leave_transactional_mode.im_func + [s_sc], annmodel.s_None) self.stm_start_ptr = getfn( self.gcdata.gc.start_transaction.im_func, [s_gc], annmodel.s_None) From noreply at buildbot.pypy.org Mon Apr 9 10:19:19 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 9 Apr 2012 10:19:19 +0200 (CEST) Subject: [pypy-commit] pypy stm-gc: A comment Message-ID: <20120409081919.3145346E0F0@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: stm-gc Changeset: r54263:2d60ce23a2ec Date: 2012-04-09 10:18 +0200 http://bitbucket.org/pypy/pypy/changeset/2d60ce23a2ec/ Log: A comment diff --git a/pypy/translator/stm/src_stm/et.c b/pypy/translator/stm/src_stm/et.c --- a/pypy/translator/stm/src_stm/et.c +++ b/pypy/translator/stm/src_stm/et.c @@ -155,7 +155,8 @@ ((char *)localobj) + sizeof(orec_t), size - sizeof(orec_t)); /* but we must only unlock the orec if it's the last time it - appears in the redolog list. If it's not, then p == -1. */ + appears in the redolog list. If it's not, then p == -1. + XXX I think that duplicate orecs are not possible any more. */ if (p != -1) { volatile orec_t* o = get_orec(globalobj); From noreply at buildbot.pypy.org Mon Apr 9 10:19:16 2012 From: noreply at buildbot.pypy.org (arigo) Date: Mon, 9 Apr 2012 10:19:16 +0200 (CEST) Subject: [pypy-commit] pypy stm-gc: Crash with a clearer error message when not translated. Message-ID: <20120409081916.8C1FC46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: stm-gc Changeset: r54261:f170650418ee Date: 2012-04-05 12:10 +0200 http://bitbucket.org/pypy/pypy/changeset/f170650418ee/ Log: Crash with a clearer error message when not translated. diff --git a/pypy/rlib/debug.py b/pypy/rlib/debug.py --- a/pypy/rlib/debug.py +++ b/pypy/rlib/debug.py @@ -1,5 +1,6 @@ import sys, time from pypy.rpython.extregistry import ExtRegistryEntry +from pypy.rlib.objectmodel import we_are_translated from pypy.rlib.rarithmetic import is_valid_int @@ -21,8 +22,13 @@ hop.exception_cannot_occur() hop.genop('debug_assert', vlist) +class FatalError(Exception): + pass + def fatalerror(msg): # print the RPython traceback and abort with a fatal error + if not we_are_translated(): + raise FatalError(msg) from pypy.rpython.lltypesystem import lltype from pypy.rpython.lltypesystem.lloperation import llop llop.debug_print_traceback(lltype.Void) @@ -33,6 +39,8 @@ def fatalerror_notb(msg): # a variant of fatalerror() that doesn't print the RPython traceback + if not we_are_translated(): + raise FatalError(msg) from pypy.rpython.lltypesystem import lltype from pypy.rpython.lltypesystem.lloperation import llop llop.debug_fatalerror(lltype.Void, msg) From noreply at buildbot.pypy.org Mon Apr 9 11:40:14 2012 From: noreply at buildbot.pypy.org (RonnyPfannschmidt) Date: Mon, 9 Apr 2012 11:40:14 +0200 (CEST) Subject: [pypy-commit] pypy pytest: upgrade py and _pytest to current tip Message-ID: <20120409094014.3A8C746E0EF@wyvern.cs.uni-duesseldorf.de> Author: Ronny Pfannschmidt Branch: pytest Changeset: r54264:f1fdc8c6ed6a Date: 2012-04-09 11:38 +0200 http://bitbucket.org/pypy/pypy/changeset/f1fdc8c6ed6a/ Log: upgrade py and _pytest to current tip diff --git a/_pytest/__init__.py b/_pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.2.3' +__version__ = '2.2.4.dev2' diff --git a/_pytest/assertion/rewrite.py b/_pytest/assertion/rewrite.py --- a/_pytest/assertion/rewrite.py +++ b/_pytest/assertion/rewrite.py @@ -1,7 +1,6 @@ """Rewrite assertion AST to produce nice error messages""" import ast -import collections import errno import itertools import imp diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -34,15 +34,21 @@ # this dynamically instead of hardcoding it. The spec range of valid # chars is: Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] # | [#x10000-#x10FFFF] -_illegal_unichrs = [(0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x19), - (0xD800, 0xDFFF), (0xFDD0, 0xFFFF)] -_illegal_ranges = [unicode("%s-%s") % (unichr(low), unichr(high)) - for (low, high) in _illegal_unichrs +_legal_chars = (0x09, 0x0A, 0x0d) +_legal_ranges = ( + (0x20, 0xD7FF), + (0xE000, 0xFFFD), + (0x10000, 0x10FFFF), +) +_legal_xml_re = [unicode("%s-%s") % (unichr(low), unichr(high)) + for (low, high) in _legal_ranges if low < sys.maxunicode] -illegal_xml_re = re.compile(unicode('[%s]') % - unicode('').join(_illegal_ranges)) -del _illegal_unichrs -del _illegal_ranges +_legal_xml_re = [unichr(x) for x in _legal_chars] + _legal_xml_re +illegal_xml_re = re.compile(unicode('[^%s]') % + unicode('').join(_legal_xml_re)) +del _legal_chars +del _legal_ranges +del _legal_xml_re def bin_xml_escape(arg): def repl(matchobj): diff --git a/_pytest/skipping.py b/_pytest/skipping.py --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -132,6 +132,14 @@ def pytest_runtest_makereport(__multicall__, item, call): if not isinstance(item, pytest.Function): return + # unitttest special case, see setting of _unexpectedsuccess + if hasattr(item, '_unexpectedsuccess'): + rep = __multicall__.execute() + if rep.when == "call": + # we need to translate into how py.test encodes xpass + rep.keywords['xfail'] = "reason: " + item._unexpectedsuccess + rep.outcome = "failed" + return rep if not (call.excinfo and call.excinfo.errisinstance(py.test.xfail.Exception)): evalxfail = getattr(item, '_evalxfail', None) diff --git a/_pytest/unittest.py b/_pytest/unittest.py --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -91,22 +91,28 @@ self._addexcinfo(rawexcinfo) def addFailure(self, testcase, rawexcinfo): self._addexcinfo(rawexcinfo) + def addSkip(self, testcase, reason): try: pytest.skip(reason) except pytest.skip.Exception: self._addexcinfo(sys.exc_info()) - def addExpectedFailure(self, testcase, rawexcinfo, reason): + + def addExpectedFailure(self, testcase, rawexcinfo, reason=""): try: pytest.xfail(str(reason)) except pytest.xfail.Exception: self._addexcinfo(sys.exc_info()) - def addUnexpectedSuccess(self, testcase, reason): - pass + + def addUnexpectedSuccess(self, testcase, reason=""): + self._unexpectedsuccess = reason + def addSuccess(self, testcase): pass + def stopTest(self, testcase): pass + def runtest(self): self._testcase(result=self) diff --git a/py/_builtin.py b/py/_builtin.py --- a/py/_builtin.py +++ b/py/_builtin.py @@ -113,9 +113,12 @@ # some backward compatibility helpers _basestring = str - def _totext(obj, encoding=None): + def _totext(obj, encoding=None, errors=None): if isinstance(obj, bytes): - obj = obj.decode(encoding) + if errors is None: + obj = obj.decode(encoding) + else: + obj = obj.decode(encoding, errors) elif not isinstance(obj, str): obj = str(obj) return obj diff --git a/py/_error.py b/py/_error.py --- a/py/_error.py +++ b/py/_error.py @@ -23,6 +23,7 @@ 2: errno.ENOENT, 3: errno.ENOENT, 17: errno.EEXIST, + 13: errno.EBUSY, # empty cd drive, but ENOMEDIUM seems unavailiable 22: errno.ENOTDIR, 267: errno.ENOTDIR, 5: errno.EACCES, # anything better? diff --git a/py/_io/capture.py b/py/_io/capture.py --- a/py/_io/capture.py +++ b/py/_io/capture.py @@ -12,7 +12,7 @@ class TextIO(StringIO): def write(self, data): if not isinstance(data, unicode): - data = unicode(data, getattr(self, '_encoding', 'UTF-8')) + data = unicode(data, getattr(self, '_encoding', 'UTF-8'), 'replace') StringIO.write(self, data) else: TextIO = StringIO @@ -260,7 +260,7 @@ res = f.read() enc = getattr(f, 'encoding', None) if enc: - res = py.builtin._totext(res, enc) + res = py.builtin._totext(res, enc, 'replace') f.truncate(0) f.seek(0) l.append(res) diff --git a/py/_path/common.py b/py/_path/common.py --- a/py/_path/common.py +++ b/py/_path/common.py @@ -64,7 +64,10 @@ else: if bool(value) ^ bool(meth()) ^ invert: return False - except (py.error.ENOENT, py.error.ENOTDIR): + except (py.error.ENOENT, py.error.ENOTDIR, py.error.EBUSY): + # EBUSY feels not entirely correct, + # but its kind of necessary since ENOMEDIUM + # is not accessible in python for name in self._depend_on_existence: if name in kw: if kw.get(name): diff --git a/py/_xmlgen.py b/py/_xmlgen.py --- a/py/_xmlgen.py +++ b/py/_xmlgen.py @@ -52,7 +52,7 @@ def unicode(self, indent=2): l = [] SimpleUnicodeVisitor(l.append, indent).visit(self) - return "".join(l) + return u("").join(l) def __repr__(self): name = self.__class__.__name__ @@ -122,11 +122,13 @@ if visitmethod is not None: break else: - visitmethod = self.object + visitmethod = self.__object self.cache[cls] = visitmethod visitmethod(node) - def object(self, obj): + # the default fallback handler is marked private + # to avoid clashes with the tag name object + def __object(self, obj): #self.write(obj) self.write(escape(unicode(obj))) @@ -182,7 +184,11 @@ value = getattr(attrs, name) if name.endswith('_'): name = name[:-1] - return ' %s="%s"' % (name, escape(unicode(value))) + if isinstance(value, raw): + insert = value.uniobj + else: + insert = escape(unicode(value)) + return ' %s="%s"' % (name, insert) def getstyle(self, tag): """ return attribute list suitable for styling. """ From noreply at buildbot.pypy.org Mon Apr 9 11:40:31 2012 From: noreply at buildbot.pypy.org (RonnyPfannschmidt) Date: Mon, 9 Apr 2012 11:40:31 +0200 (CEST) Subject: [pypy-commit] pypy pytest: merge from default Message-ID: <20120409094031.A548C46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Ronny Pfannschmidt Branch: pytest Changeset: r54265:026db0b5babb Date: 2012-04-09 11:39 +0200 http://bitbucket.org/pypy/pypy/changeset/026db0b5babb/ Log: merge from default diff too long, truncating to 10000 out of 28906 lines diff --git a/dotviewer/graphparse.py b/dotviewer/graphparse.py --- a/dotviewer/graphparse.py +++ b/dotviewer/graphparse.py @@ -93,6 +93,7 @@ return result def parse_plain(graph_id, plaincontent, links={}, fixedfont=False): + plaincontent = plaincontent.replace('\r\n', '\n') # fix Windows EOL lines = plaincontent.splitlines(True) for i in range(len(lines)-2, -1, -1): if lines[i].endswith('\\\n'): # line ending in '\' diff --git a/lib-python/2.7/SimpleXMLRPCServer.py b/lib-python/2.7/SimpleXMLRPCServer.py --- a/lib-python/2.7/SimpleXMLRPCServer.py +++ b/lib-python/2.7/SimpleXMLRPCServer.py @@ -486,7 +486,10 @@ L = [] while size_remaining: chunk_size = min(size_remaining, max_chunk_size) - L.append(self.rfile.read(chunk_size)) + chunk = self.rfile.read(chunk_size) + if not chunk: + break + L.append(chunk) size_remaining -= len(L[-1]) data = ''.join(L) diff --git a/lib-python/2.7/test/test_xmlrpc.py b/lib-python/2.7/test/test_xmlrpc.py --- a/lib-python/2.7/test/test_xmlrpc.py +++ b/lib-python/2.7/test/test_xmlrpc.py @@ -308,7 +308,7 @@ global ADDR, PORT, URL ADDR, PORT = serv.socket.getsockname() #connect to IP address directly. This avoids socket.create_connection() - #trying to connect to to "localhost" using all address families, which + #trying to connect to "localhost" using all address families, which #causes slowdown e.g. on vista which supports AF_INET6. The server listens #on AF_INET only. URL = "http://%s:%d"%(ADDR, PORT) @@ -367,7 +367,7 @@ global ADDR, PORT, URL ADDR, PORT = serv.socket.getsockname() #connect to IP address directly. This avoids socket.create_connection() - #trying to connect to to "localhost" using all address families, which + #trying to connect to "localhost" using all address families, which #causes slowdown e.g. on vista which supports AF_INET6. The server listens #on AF_INET only. URL = "http://%s:%d"%(ADDR, PORT) @@ -472,6 +472,9 @@ # protocol error; provide additional information in test output self.fail("%s\n%s" % (e, getattr(e, "headers", ""))) + def test_unicode_host(self): + server = xmlrpclib.ServerProxy(u"http://%s:%d/RPC2"%(ADDR, PORT)) + self.assertEqual(server.add("a", u"\xe9"), u"a\xe9") # [ch] The test 404 is causing lots of false alarms. def XXXtest_404(self): @@ -586,6 +589,12 @@ # This avoids waiting for the socket timeout. self.test_simple1() + def test_partial_post(self): + # Check that a partial POST doesn't make the server loop: issue #14001. + conn = httplib.HTTPConnection(ADDR, PORT) + conn.request('POST', '/RPC2 HTTP/1.0\r\nContent-Length: 100\r\n\r\nbye') + conn.close() + class MultiPathServerTestCase(BaseServerTestCase): threadFunc = staticmethod(http_multi_server) request_count = 2 diff --git a/lib-python/conftest.py b/lib-python/conftest.py --- a/lib-python/conftest.py +++ b/lib-python/conftest.py @@ -311,7 +311,7 @@ RegrTest('test_mimetypes.py'), RegrTest('test_MimeWriter.py', core=False), RegrTest('test_minidom.py'), - RegrTest('test_mmap.py'), + RegrTest('test_mmap.py', usemodules="mmap"), RegrTest('test_module.py', core=True), RegrTest('test_modulefinder.py'), RegrTest('test_msilib.py', skip=only_win32), diff --git a/lib-python/modified-2.7/distutils/command/bdist_wininst.py b/lib-python/modified-2.7/distutils/command/bdist_wininst.py --- a/lib-python/modified-2.7/distutils/command/bdist_wininst.py +++ b/lib-python/modified-2.7/distutils/command/bdist_wininst.py @@ -298,7 +298,8 @@ bitmaplen, # number of bytes in bitmap ) file.write(header) - file.write(open(arcname, "rb").read()) + with open(arcname, "rb") as arcfile: + file.write(arcfile.read()) # create_exe() diff --git a/lib-python/modified-2.7/distutils/sysconfig_pypy.py b/lib-python/modified-2.7/distutils/sysconfig_pypy.py --- a/lib-python/modified-2.7/distutils/sysconfig_pypy.py +++ b/lib-python/modified-2.7/distutils/sysconfig_pypy.py @@ -60,6 +60,7 @@ g['EXE'] = "" g['SO'] = _get_so_extension() or ".so" g['SOABI'] = g['SO'].rsplit('.')[0] + g['LIBDIR'] = os.path.join(sys.prefix, 'lib') global _config_vars _config_vars = g diff --git a/lib-python/modified-2.7/opcode.py b/lib-python/modified-2.7/opcode.py --- a/lib-python/modified-2.7/opcode.py +++ b/lib-python/modified-2.7/opcode.py @@ -192,5 +192,6 @@ def_op('LOOKUP_METHOD', 201) # Index in name list hasname.append(201) def_op('CALL_METHOD', 202) # #args not including 'self' +def_op('BUILD_LIST_FROM_ARG', 203) del def_op, name_op, jrel_op, jabs_op diff --git a/lib-python/modified-2.7/site.py b/lib-python/modified-2.7/site.py --- a/lib-python/modified-2.7/site.py +++ b/lib-python/modified-2.7/site.py @@ -550,9 +550,18 @@ "'import usercustomize' failed; use -v for traceback" +def import_builtin_stuff(): + """PyPy specific: pre-import a few built-in modules, because + some programs actually rely on them to be in sys.modules :-(""" + import exceptions + if 'zipimport' in sys.builtin_module_names: + import zipimport + + def main(): global ENABLE_USER_SITE + import_builtin_stuff() abs__file__() known_paths = removeduppaths() if (os.name == "posix" and sys.path and diff --git a/lib-python/modified-2.7/test/test_dis.py b/lib-python/modified-2.7/test/test_dis.py new file mode 100644 --- /dev/null +++ b/lib-python/modified-2.7/test/test_dis.py @@ -0,0 +1,150 @@ +# Minimal tests for dis module + +from test.test_support import run_unittest +import unittest +import sys +import dis +import StringIO + + +def _f(a): + print a + return 1 + +dis_f = """\ + %-4d 0 LOAD_FAST 0 (a) + 3 PRINT_ITEM + 4 PRINT_NEWLINE + + %-4d 5 LOAD_CONST 1 (1) + 8 RETURN_VALUE +"""%(_f.func_code.co_firstlineno + 1, + _f.func_code.co_firstlineno + 2) + + +def bug708901(): + for res in range(1, + 10): + pass + +dis_bug708901 = """\ + %-4d 0 SETUP_LOOP 23 (to 26) + 3 LOAD_GLOBAL 0 (range) + 6 LOAD_CONST 1 (1) + + %-4d 9 LOAD_CONST 2 (10) + 12 CALL_FUNCTION 2 + 15 GET_ITER + >> 16 FOR_ITER 6 (to 25) + 19 STORE_FAST 0 (res) + + %-4d 22 JUMP_ABSOLUTE 16 + >> 25 POP_BLOCK + >> 26 LOAD_CONST 0 (None) + 29 RETURN_VALUE +"""%(bug708901.func_code.co_firstlineno + 1, + bug708901.func_code.co_firstlineno + 2, + bug708901.func_code.co_firstlineno + 3) + + +def bug1333982(x=[]): + assert 0, ([s for s in x] + + 1) + pass + +dis_bug1333982 = """\ + %-4d 0 LOAD_CONST 1 (0) + 3 POP_JUMP_IF_TRUE 38 + 6 LOAD_GLOBAL 0 (AssertionError) + 9 LOAD_FAST 0 (x) + 12 BUILD_LIST_FROM_ARG 0 + 15 GET_ITER + >> 16 FOR_ITER 12 (to 31) + 19 STORE_FAST 1 (s) + 22 LOAD_FAST 1 (s) + 25 LIST_APPEND 2 + 28 JUMP_ABSOLUTE 16 + + %-4d >> 31 LOAD_CONST 2 (1) + 34 BINARY_ADD + 35 RAISE_VARARGS 2 + + %-4d >> 38 LOAD_CONST 0 (None) + 41 RETURN_VALUE +"""%(bug1333982.func_code.co_firstlineno + 1, + bug1333982.func_code.co_firstlineno + 2, + bug1333982.func_code.co_firstlineno + 3) + +_BIG_LINENO_FORMAT = """\ +%3d 0 LOAD_GLOBAL 0 (spam) + 3 POP_TOP + 4 LOAD_CONST 0 (None) + 7 RETURN_VALUE +""" + +class DisTests(unittest.TestCase): + def do_disassembly_test(self, func, expected): + s = StringIO.StringIO() + save_stdout = sys.stdout + sys.stdout = s + dis.dis(func) + sys.stdout = save_stdout + got = s.getvalue() + # Trim trailing blanks (if any). + lines = got.split('\n') + lines = [line.rstrip() for line in lines] + expected = expected.split("\n") + import difflib + if expected != lines: + self.fail( + "events did not match expectation:\n" + + "\n".join(difflib.ndiff(expected, + lines))) + + def test_opmap(self): + self.assertEqual(dis.opmap["STOP_CODE"], 0) + self.assertIn(dis.opmap["LOAD_CONST"], dis.hasconst) + self.assertIn(dis.opmap["STORE_NAME"], dis.hasname) + + def test_opname(self): + self.assertEqual(dis.opname[dis.opmap["LOAD_FAST"]], "LOAD_FAST") + + def test_boundaries(self): + self.assertEqual(dis.opmap["EXTENDED_ARG"], dis.EXTENDED_ARG) + self.assertEqual(dis.opmap["STORE_NAME"], dis.HAVE_ARGUMENT) + + def test_dis(self): + self.do_disassembly_test(_f, dis_f) + + def test_bug_708901(self): + self.do_disassembly_test(bug708901, dis_bug708901) + + def test_bug_1333982(self): + # This one is checking bytecodes generated for an `assert` statement, + # so fails if the tests are run with -O. Skip this test then. + if __debug__: + self.do_disassembly_test(bug1333982, dis_bug1333982) + + def test_big_linenos(self): + def func(count): + namespace = {} + func = "def foo():\n " + "".join(["\n "] * count + ["spam\n"]) + exec func in namespace + return namespace['foo'] + + # Test all small ranges + for i in xrange(1, 300): + expected = _BIG_LINENO_FORMAT % (i + 2) + self.do_disassembly_test(func(i), expected) + + # Test some larger ranges too + for i in xrange(300, 5000, 10): + expected = _BIG_LINENO_FORMAT % (i + 2) + self.do_disassembly_test(func(i), expected) + +def test_main(): + run_unittest(DisTests) + + +if __name__ == "__main__": + test_main() diff --git a/lib-python/modified-2.7/test/test_set.py b/lib-python/modified-2.7/test/test_set.py --- a/lib-python/modified-2.7/test/test_set.py +++ b/lib-python/modified-2.7/test/test_set.py @@ -1568,7 +1568,7 @@ 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)) + actual = meth(g(data)) if isinstance(expected, bool): self.assertEqual(actual, expected) else: diff --git a/lib_pypy/_csv.py b/lib_pypy/_csv.py --- a/lib_pypy/_csv.py +++ b/lib_pypy/_csv.py @@ -414,7 +414,7 @@ def _parse_add_char(self, c): if len(self.field) + len(c) > _field_limit: - raise Error("field larget than field limit (%d)" % (_field_limit)) + raise Error("field larger than field limit (%d)" % (_field_limit)) self.field += c diff --git a/lib_pypy/_ctypes/builtin.py b/lib_pypy/_ctypes/builtin.py --- a/lib_pypy/_ctypes/builtin.py +++ b/lib_pypy/_ctypes/builtin.py @@ -31,24 +31,20 @@ arg = cobj._get_buffer_value() return _rawffi.wcharp2rawunicode(arg, lgt) -class ErrorObject(local): - def __init__(self): - self.errno = 0 - self.winerror = 0 -_error_object = ErrorObject() +_err = local() def get_errno(): - return _error_object.errno + return getattr(_err, "errno", 0) def set_errno(errno): - old_errno = _error_object.errno - _error_object.errno = errno + old_errno = get_errno() + _err.errno = errno return old_errno def get_last_error(): - return _error_object.winerror + return getattr(_err, "winerror", 0) def set_last_error(winerror): - old_winerror = _error_object.winerror - _error_object.winerror = winerror + old_winerror = get_last_error() + _err.winerror = winerror return old_winerror diff --git a/lib_pypy/_ctypes/function.py b/lib_pypy/_ctypes/function.py --- a/lib_pypy/_ctypes/function.py +++ b/lib_pypy/_ctypes/function.py @@ -3,7 +3,7 @@ from _ctypes.primitive import SimpleType, _SimpleCData from _ctypes.basics import ArgumentError, keepalive_key from _ctypes.basics import is_struct_shape -from _ctypes.builtin import set_errno, set_last_error +from _ctypes.builtin import get_errno, set_errno, get_last_error, set_last_error import _rawffi import _ffi import sys @@ -350,16 +350,24 @@ def _call_funcptr(self, funcptr, *newargs): if self._flags_ & _rawffi.FUNCFLAG_USE_ERRNO: - set_errno(_rawffi.get_errno()) + tmp = _rawffi.get_errno() + _rawffi.set_errno(get_errno()) + set_errno(tmp) if self._flags_ & _rawffi.FUNCFLAG_USE_LASTERROR: - set_last_error(_rawffi.get_last_error()) + tmp = _rawffi.get_last_error() + _rawffi.set_last_error(get_last_error()) + set_last_error(tmp) try: result = funcptr(*newargs) finally: if self._flags_ & _rawffi.FUNCFLAG_USE_ERRNO: - set_errno(_rawffi.get_errno()) + tmp = _rawffi.get_errno() + _rawffi.set_errno(get_errno()) + set_errno(tmp) if self._flags_ & _rawffi.FUNCFLAG_USE_LASTERROR: - set_last_error(_rawffi.get_last_error()) + tmp = _rawffi.get_last_error() + _rawffi.set_last_error(get_last_error()) + set_last_error(tmp) # try: return self._build_result(self._restype_, result, newargs) diff --git a/lib_pypy/_locale.py b/lib_pypy/_locale.py deleted file mode 100644 --- a/lib_pypy/_locale.py +++ /dev/null @@ -1,337 +0,0 @@ -# ctypes implementation of _locale module by Victor Stinner, 2008-03-27 - -# ------------------------------------------------------------ -# Note that we also have our own interp-level implementation -# ------------------------------------------------------------ - -""" -Support for POSIX locales. -""" - -from ctypes import (Structure, POINTER, create_string_buffer, - c_ubyte, c_int, c_char_p, c_wchar_p, c_size_t) -from ctypes_support import standard_c_lib as libc -from ctypes_support import get_errno - -# load the platform-specific cache made by running locale.ctc.py -from ctypes_config_cache._locale_cache import * - -try: from __pypy__ import builtinify -except ImportError: builtinify = lambda f: f - - -# Ubuntu Gusty i386 structure -class lconv(Structure): - _fields_ = ( - # Numeric (non-monetary) information. - ("decimal_point", c_char_p), # Decimal point character. - ("thousands_sep", c_char_p), # Thousands separator. - - # Each element is the number of digits in each group; - # elements with higher indices are farther left. - # An element with value CHAR_MAX means that no further grouping is done. - # An element with value 0 means that the previous element is used - # for all groups farther left. */ - ("grouping", c_char_p), - - # Monetary information. - - # First three chars are a currency symbol from ISO 4217. - # Fourth char is the separator. Fifth char is '\0'. - ("int_curr_symbol", c_char_p), - ("currency_symbol", c_char_p), # Local currency symbol. - ("mon_decimal_point", c_char_p), # Decimal point character. - ("mon_thousands_sep", c_char_p), # Thousands separator. - ("mon_grouping", c_char_p), # Like `grouping' element (above). - ("positive_sign", c_char_p), # Sign for positive values. - ("negative_sign", c_char_p), # Sign for negative values. - ("int_frac_digits", c_ubyte), # Int'l fractional digits. - ("frac_digits", c_ubyte), # Local fractional digits. - # 1 if currency_symbol precedes a positive value, 0 if succeeds. - ("p_cs_precedes", c_ubyte), - # 1 iff a space separates currency_symbol from a positive value. - ("p_sep_by_space", c_ubyte), - # 1 if currency_symbol precedes a negative value, 0 if succeeds. - ("n_cs_precedes", c_ubyte), - # 1 iff a space separates currency_symbol from a negative value. - ("n_sep_by_space", c_ubyte), - - # Positive and negative sign positions: - # 0 Parentheses surround the quantity and currency_symbol. - # 1 The sign string precedes the quantity and currency_symbol. - # 2 The sign string follows the quantity and currency_symbol. - # 3 The sign string immediately precedes the currency_symbol. - # 4 The sign string immediately follows the currency_symbol. - ("p_sign_posn", c_ubyte), - ("n_sign_posn", c_ubyte), - # 1 if int_curr_symbol precedes a positive value, 0 if succeeds. - ("int_p_cs_precedes", c_ubyte), - # 1 iff a space separates int_curr_symbol from a positive value. - ("int_p_sep_by_space", c_ubyte), - # 1 if int_curr_symbol precedes a negative value, 0 if succeeds. - ("int_n_cs_precedes", c_ubyte), - # 1 iff a space separates int_curr_symbol from a negative value. - ("int_n_sep_by_space", c_ubyte), - # Positive and negative sign positions: - # 0 Parentheses surround the quantity and int_curr_symbol. - # 1 The sign string precedes the quantity and int_curr_symbol. - # 2 The sign string follows the quantity and int_curr_symbol. - # 3 The sign string immediately precedes the int_curr_symbol. - # 4 The sign string immediately follows the int_curr_symbol. - ("int_p_sign_posn", c_ubyte), - ("int_n_sign_posn", c_ubyte), - ) - -_setlocale = libc.setlocale -_setlocale.argtypes = (c_int, c_char_p) -_setlocale.restype = c_char_p - -_localeconv = libc.localeconv -_localeconv.argtypes = None -_localeconv.restype = POINTER(lconv) - -_strcoll = libc.strcoll -_strcoll.argtypes = (c_char_p, c_char_p) -_strcoll.restype = c_int - -_wcscoll = libc.wcscoll -_wcscoll.argtypes = (c_wchar_p, c_wchar_p) -_wcscoll.restype = c_int - -_strxfrm = libc.strxfrm -_strxfrm.argtypes = (c_char_p, c_char_p, c_size_t) -_strxfrm.restype = c_size_t - -HAS_LIBINTL = hasattr(libc, 'gettext') -if HAS_LIBINTL: - _gettext = libc.gettext - _gettext.argtypes = (c_char_p,) - _gettext.restype = c_char_p - - _dgettext = libc.dgettext - _dgettext.argtypes = (c_char_p, c_char_p) - _dgettext.restype = c_char_p - - _dcgettext = libc.dcgettext - _dcgettext.argtypes = (c_char_p, c_char_p, c_int) - _dcgettext.restype = c_char_p - - _textdomain = libc.textdomain - _textdomain.argtypes = (c_char_p,) - _textdomain.restype = c_char_p - - _bindtextdomain = libc.bindtextdomain - _bindtextdomain.argtypes = (c_char_p, c_char_p) - _bindtextdomain.restype = c_char_p - - HAS_BIND_TEXTDOMAIN_CODESET = hasattr(libc, 'bindtextdomain_codeset') - if HAS_BIND_TEXTDOMAIN_CODESET: - _bind_textdomain_codeset = libc.bindtextdomain_codeset - _bind_textdomain_codeset.argtypes = (c_char_p, c_char_p) - _bind_textdomain_codeset.restype = c_char_p - -class Error(Exception): - pass - -def fixup_ulcase(): - import string - #import strop - - # create uppercase map string - ul = [] - for c in xrange(256): - c = chr(c) - if c.isupper(): - ul.append(c) - ul = ''.join(ul) - string.uppercase = ul - #strop.uppercase = ul - - # create lowercase string - ul = [] - for c in xrange(256): - c = chr(c) - if c.islower(): - ul.append(c) - ul = ''.join(ul) - string.lowercase = ul - #strop.lowercase = ul - - # create letters string - ul = [] - for c in xrange(256): - c = chr(c) - if c.isalpha(): - ul.append(c) - ul = ''.join(ul) - string.letters = ul - - at builtinify -def setlocale(category, locale=None): - "(integer,string=None) -> string. Activates/queries locale processing." - if locale: - # set locale - result = _setlocale(category, locale) - if not result: - raise Error("unsupported locale setting") - - # record changes to LC_CTYPE - if category in (LC_CTYPE, LC_ALL): - fixup_ulcase() - else: - # get locale - result = _setlocale(category, None) - if not result: - raise Error("locale query failed") - return result - -def _copy_grouping(text): - groups = [ ord(group) for group in text ] - if groups: - groups.append(0) - return groups - - at builtinify -def localeconv(): - "() -> dict. Returns numeric and monetary locale-specific parameters." - - # if LC_NUMERIC is different in the C library, use saved value - lp = _localeconv() - l = lp.contents - - # hopefully, the localeconv result survives the C library calls - # involved herein - - # Numeric information - result = { - "decimal_point": l.decimal_point, - "thousands_sep": l.thousands_sep, - "grouping": _copy_grouping(l.grouping), - "int_curr_symbol": l.int_curr_symbol, - "currency_symbol": l.currency_symbol, - "mon_decimal_point": l.mon_decimal_point, - "mon_thousands_sep": l.mon_thousands_sep, - "mon_grouping": _copy_grouping(l.mon_grouping), - "positive_sign": l.positive_sign, - "negative_sign": l.negative_sign, - "int_frac_digits": l.int_frac_digits, - "frac_digits": l.frac_digits, - "p_cs_precedes": l.p_cs_precedes, - "p_sep_by_space": l.p_sep_by_space, - "n_cs_precedes": l.n_cs_precedes, - "n_sep_by_space": l.n_sep_by_space, - "p_sign_posn": l.p_sign_posn, - "n_sign_posn": l.n_sign_posn, - } - return result - - at builtinify -def strcoll(s1, s2): - "string,string -> int. Compares two strings according to the locale." - - # If both arguments are byte strings, use strcoll. - if isinstance(s1, str) and isinstance(s2, str): - return _strcoll(s1, s2) - - # If neither argument is unicode, it's an error. - if not isinstance(s1, unicode) and not isinstance(s2, unicode): - raise ValueError("strcoll arguments must be strings") - - # Convert the non-unicode argument to unicode. - s1 = unicode(s1) - s2 = unicode(s2) - - # Collate the strings. - return _wcscoll(s1, s2) - - at builtinify -def strxfrm(s): - "string -> string. Returns a string that behaves for cmp locale-aware." - - # assume no change in size, first - n1 = len(s) + 1 - buf = create_string_buffer(n1) - n2 = _strxfrm(buf, s, n1) + 1 - if n2 > n1: - # more space needed - buf = create_string_buffer(n2) - _strxfrm(buf, s, n2) - return buf.value - - at builtinify -def getdefaultlocale(): - # TODO: Port code from CPython for Windows and Mac OS - raise NotImplementedError() - -if HAS_LANGINFO: - _nl_langinfo = libc.nl_langinfo - _nl_langinfo.argtypes = (nl_item,) - _nl_langinfo.restype = c_char_p - - def nl_langinfo(key): - """nl_langinfo(key) -> string - Return the value for the locale information associated with key.""" - # Check whether this is a supported constant. GNU libc sometimes - # returns numeric values in the char* return value, which would - # crash PyString_FromString. - result = _nl_langinfo(key) - if result is not None: - return result - raise ValueError("unsupported langinfo constant") - -if HAS_LIBINTL: - @builtinify - def gettext(msg): - """gettext(msg) -> string - Return translation of msg.""" - return _gettext(msg) - - @builtinify - def dgettext(domain, msg): - """dgettext(domain, msg) -> string - Return translation of msg in domain.""" - return _dgettext(domain, msg) - - @builtinify - def dcgettext(domain, msg, category): - """dcgettext(domain, msg, category) -> string - Return translation of msg in domain and category.""" - return _dcgettext(domain, msg, category) - - @builtinify - def textdomain(domain): - """textdomain(domain) -> string - Set the C library's textdomain to domain, returning the new domain.""" - return _textdomain(domain) - - @builtinify - def bindtextdomain(domain, dir): - """bindtextdomain(domain, dir) -> string - Bind the C library's domain to dir.""" - dirname = _bindtextdomain(domain, dir) - if not dirname: - errno = get_errno() - raise OSError(errno) - return dirname - - if HAS_BIND_TEXTDOMAIN_CODESET: - @builtinify - def bind_textdomain_codeset(domain, codeset): - """bind_textdomain_codeset(domain, codeset) -> string - Bind the C library's domain to codeset.""" - codeset = _bind_textdomain_codeset(domain, codeset) - if codeset: - return codeset - return None - -__all__ = ( - 'Error', - 'setlocale', 'localeconv', 'strxfrm', 'strcoll', -) + ALL_CONSTANTS -if HAS_LIBINTL: - __all__ += ('gettext', 'dgettext', 'dcgettext', 'textdomain', - 'bindtextdomain') - if HAS_BIND_TEXTDOMAIN_CODESET: - __all__ += ('bind_textdomain_codeset',) -if HAS_LANGINFO: - __all__ += ('nl_langinfo',) diff --git a/lib_pypy/array.py b/lib_pypy/array.py deleted file mode 100644 --- a/lib_pypy/array.py +++ /dev/null @@ -1,531 +0,0 @@ -"""This module defines an object type which can efficiently represent -an array of basic values: characters, integers, floating point -numbers. Arrays are sequence types and behave very much like lists, -except that the type of objects stored in them is constrained. The -type is specified at object creation time by using a type code, which -is a single character. The following type codes are defined: - - Type code C Type Minimum size in bytes - 'c' character 1 - 'b' signed integer 1 - 'B' unsigned integer 1 - 'u' Unicode character 2 - 'h' signed integer 2 - 'H' unsigned integer 2 - 'i' signed integer 2 - 'I' unsigned integer 2 - 'l' signed integer 4 - 'L' unsigned integer 4 - 'f' floating point 4 - 'd' floating point 8 - -The constructor is: - -array(typecode [, initializer]) -- create a new array -""" - -from struct import calcsize, pack, pack_into, unpack_from -import operator - -# the buffer-like object to use internally: trying from -# various places in order... -try: - import _rawffi # a reasonable implementation based - _RAWARRAY = _rawffi.Array('c') # on raw_malloc, and providing a - def bytebuffer(size): # real address - return _RAWARRAY(size, autofree=True) - def getbufaddress(buf): - return buf.buffer -except ImportError: - try: - from __pypy__ import bytebuffer # a reasonable implementation - def getbufaddress(buf): # compatible with oo backends, - return 0 # but no address - except ImportError: - # not running on PyPy. Fall back to ctypes... - import ctypes - bytebuffer = ctypes.create_string_buffer - def getbufaddress(buf): - voidp = ctypes.cast(ctypes.pointer(buf), ctypes.c_void_p) - return voidp.value - -# ____________________________________________________________ - -TYPECODES = "cbBuhHiIlLfd" - -class array(object): - """array(typecode [, initializer]) -> array - - Return a new array whose items are restricted by typecode, and - initialized from the optional initializer value, which must be a list, - string. or iterable over elements of the appropriate type. - - Arrays represent basic values and behave very much like lists, except - the type of objects stored in them is constrained. - - Methods: - - append() -- append a new item to the end of the array - buffer_info() -- return information giving the current memory info - byteswap() -- byteswap all the items of the array - count() -- return number of occurences of an object - extend() -- extend array by appending multiple elements from an iterable - fromfile() -- read items from a file object - fromlist() -- append items from the list - fromstring() -- append items from the string - index() -- return index of first occurence of an object - insert() -- insert a new item into the array at a provided position - pop() -- remove and return item (default last) - read() -- DEPRECATED, use fromfile() - remove() -- remove first occurence of an object - reverse() -- reverse the order of the items in the array - tofile() -- write all items to a file object - tolist() -- return the array converted to an ordinary list - tostring() -- return the array converted to a string - write() -- DEPRECATED, use tofile() - - Attributes: - - typecode -- the typecode character used to create the array - itemsize -- the length in bytes of one array item - """ - __slots__ = ["typecode", "itemsize", "_data", "_descriptor", "__weakref__"] - - def __new__(cls, typecode, initializer=[], **extrakwds): - self = object.__new__(cls) - if cls is array and extrakwds: - raise TypeError("array() does not take keyword arguments") - if not isinstance(typecode, str) or len(typecode) != 1: - raise TypeError( - "array() argument 1 must be char, not %s" % type(typecode)) - if typecode not in TYPECODES: - raise ValueError( - "bad typecode (must be one of %s)" % ', '.join(TYPECODES)) - self._data = bytebuffer(0) - self.typecode = typecode - self.itemsize = calcsize(typecode) - if isinstance(initializer, list): - self.fromlist(initializer) - elif isinstance(initializer, str): - self.fromstring(initializer) - elif isinstance(initializer, unicode) and self.typecode == "u": - self.fromunicode(initializer) - else: - self.extend(initializer) - return self - - def _clear(self): - self._data = bytebuffer(0) - - ##### array-specific operations - - def fromfile(self, f, n): - """Read n objects from the file object f and append them to the end of - the array. Also called as read.""" - if not isinstance(f, file): - raise TypeError("arg1 must be open file") - size = self.itemsize * n - item = f.read(size) - if len(item) < size: - raise EOFError("not enough items in file") - self.fromstring(item) - - def fromlist(self, l): - """Append items to array from list.""" - if not isinstance(l, list): - raise TypeError("arg must be list") - self._fromiterable(l) - - def fromstring(self, s): - """Appends items from the string, interpreting it as an array of machine - values, as if it had been read from a file using the fromfile() - method.""" - if isinstance(s, unicode): - s = str(s) - self._frombuffer(s) - - def _frombuffer(self, s): - length = len(s) - if length % self.itemsize != 0: - raise ValueError("string length not a multiple of item size") - boundary = len(self._data) - newdata = bytebuffer(boundary + length) - newdata[:boundary] = self._data - newdata[boundary:] = s - self._data = newdata - - def fromunicode(self, ustr): - """Extends this array with data from the unicode string ustr. The array - must be a type 'u' array; otherwise a ValueError is raised. Use - array.fromstring(ustr.encode(...)) to append Unicode data to an array of - some other type.""" - if not self.typecode == "u": - raise ValueError( - "fromunicode() may only be called on type 'u' arrays") - # XXX the following probable bug is not emulated: - # CPython accepts a non-unicode string or a buffer, and then - # behaves just like fromstring(), except that it strangely truncates - # string arguments at multiples of the unicode byte size. - # Let's only accept unicode arguments for now. - if not isinstance(ustr, unicode): - raise TypeError("fromunicode() argument should probably be " - "a unicode string") - # _frombuffer() does the currect thing using - # the buffer behavior of unicode objects - self._frombuffer(buffer(ustr)) - - def tofile(self, f): - """Write all items (as machine values) to the file object f. Also - called as write.""" - if not isinstance(f, file): - raise TypeError("arg must be open file") - f.write(self.tostring()) - - def tolist(self): - """Convert array to an ordinary list with the same items.""" - count = len(self._data) // self.itemsize - return list(unpack_from('%d%s' % (count, self.typecode), self._data)) - - def tostring(self): - return self._data[:] - - def __buffer__(self): - return buffer(self._data) - - def tounicode(self): - """Convert the array to a unicode string. The array must be a type 'u' - array; otherwise a ValueError is raised. Use array.tostring().decode() - to obtain a unicode string from an array of some other type.""" - if self.typecode != "u": - raise ValueError("tounicode() may only be called on type 'u' arrays") - # XXX performance is not too good - return u"".join(self.tolist()) - - def byteswap(self): - """Byteswap all items of the array. If the items in the array are not - 1, 2, 4, or 8 bytes in size, RuntimeError is raised.""" - if self.itemsize not in [1, 2, 4, 8]: - raise RuntimeError("byteswap not supported for this array") - # XXX slowish - itemsize = self.itemsize - bytes = self._data - for start in range(0, len(bytes), itemsize): - stop = start + itemsize - bytes[start:stop] = bytes[start:stop][::-1] - - def buffer_info(self): - """Return a tuple (address, length) giving the current memory address - and the length in items of the buffer used to hold array's contents. The - length should be multiplied by the itemsize attribute to calculate the - buffer length in bytes. On PyPy the address might be meaningless - (returned as 0), depending on the available modules.""" - return (getbufaddress(self._data), len(self)) - - read = fromfile - - write = tofile - - ##### general object protocol - - def __repr__(self): - if len(self._data) == 0: - return "array('%s')" % self.typecode - elif self.typecode == "c": - return "array('%s', %s)" % (self.typecode, repr(self.tostring())) - elif self.typecode == "u": - return "array('%s', %s)" % (self.typecode, repr(self.tounicode())) - else: - return "array('%s', %s)" % (self.typecode, repr(self.tolist())) - - def __copy__(self): - a = array(self.typecode) - a._data = bytebuffer(len(self._data)) - a._data[:] = self._data - return a - - def __eq__(self, other): - if not isinstance(other, array): - return NotImplemented - if self.typecode == 'c': - return buffer(self._data) == buffer(other._data) - else: - return self.tolist() == other.tolist() - - def __ne__(self, other): - if not isinstance(other, array): - return NotImplemented - if self.typecode == 'c': - return buffer(self._data) != buffer(other._data) - else: - return self.tolist() != other.tolist() - - def __lt__(self, other): - if not isinstance(other, array): - return NotImplemented - if self.typecode == 'c': - return buffer(self._data) < buffer(other._data) - else: - return self.tolist() < other.tolist() - - def __gt__(self, other): - if not isinstance(other, array): - return NotImplemented - if self.typecode == 'c': - return buffer(self._data) > buffer(other._data) - else: - return self.tolist() > other.tolist() - - def __le__(self, other): - if not isinstance(other, array): - return NotImplemented - if self.typecode == 'c': - return buffer(self._data) <= buffer(other._data) - else: - return self.tolist() <= other.tolist() - - def __ge__(self, other): - if not isinstance(other, array): - return NotImplemented - if self.typecode == 'c': - return buffer(self._data) >= buffer(other._data) - else: - return self.tolist() >= other.tolist() - - def __reduce__(self): - dict = getattr(self, '__dict__', None) - data = self.tostring() - if data: - initargs = (self.typecode, data) - else: - initargs = (self.typecode,) - return (type(self), initargs, dict) - - ##### list methods - - def append(self, x): - """Append new value x to the end of the array.""" - self._frombuffer(pack(self.typecode, x)) - - def count(self, x): - """Return number of occurences of x in the array.""" - return operator.countOf(self, x) - - def extend(self, iterable): - """Append items to the end of the array.""" - if isinstance(iterable, array) \ - and not self.typecode == iterable.typecode: - raise TypeError("can only extend with array of same kind") - self._fromiterable(iterable) - - def index(self, x): - """Return index of first occurence of x in the array.""" - return operator.indexOf(self, x) - - def insert(self, i, x): - """Insert a new item x into the array before position i.""" - seqlength = len(self) - if i < 0: - i += seqlength - if i < 0: - i = 0 - elif i > seqlength: - i = seqlength - boundary = i * self.itemsize - data = pack(self.typecode, x) - newdata = bytebuffer(len(self._data) + len(data)) - newdata[:boundary] = self._data[:boundary] - newdata[boundary:boundary+self.itemsize] = data - newdata[boundary+self.itemsize:] = self._data[boundary:] - self._data = newdata - - def pop(self, i=-1): - """Return the i-th element and delete it from the array. i defaults to - -1.""" - seqlength = len(self) - if i < 0: - i += seqlength - if not (0 <= i < seqlength): - raise IndexError(i) - boundary = i * self.itemsize - result = unpack_from(self.typecode, self._data, boundary)[0] - newdata = bytebuffer(len(self._data) - self.itemsize) - newdata[:boundary] = self._data[:boundary] - newdata[boundary:] = self._data[boundary+self.itemsize:] - self._data = newdata - return result - - def remove(self, x): - """Remove the first occurence of x in the array.""" - self.pop(self.index(x)) - - def reverse(self): - """Reverse the order of the items in the array.""" - lst = self.tolist() - lst.reverse() - self._clear() - self.fromlist(lst) - - ##### list protocol - - def __len__(self): - return len(self._data) // self.itemsize - - def __add__(self, other): - if not isinstance(other, array): - raise TypeError("can only append array to array") - if self.typecode != other.typecode: - raise TypeError("bad argument type for built-in operation") - return array(self.typecode, buffer(self._data) + buffer(other._data)) - - def __mul__(self, repeat): - return array(self.typecode, buffer(self._data) * repeat) - - __rmul__ = __mul__ - - def __getitem__(self, i): - seqlength = len(self) - if isinstance(i, slice): - start, stop, step = i.indices(seqlength) - if step != 1: - sublist = self.tolist()[i] # fall-back - return array(self.typecode, sublist) - if start < 0: - start = 0 - if stop < start: - stop = start - assert stop <= seqlength - return array(self.typecode, self._data[start * self.itemsize : - stop * self.itemsize]) - else: - if i < 0: - i += seqlength - if self.typecode == 'c': # speed trick - return self._data[i] - if not (0 <= i < seqlength): - raise IndexError(i) - boundary = i * self.itemsize - return unpack_from(self.typecode, self._data, boundary)[0] - - def __getslice__(self, i, j): - return self.__getitem__(slice(i, j)) - - def __setitem__(self, i, x): - if isinstance(i, slice): - if (not isinstance(x, array) - or self.typecode != x.typecode): - raise TypeError("can only assign array of same kind" - " to array slice") - seqlength = len(self) - start, stop, step = i.indices(seqlength) - if step != 1: - sublist = self.tolist() # fall-back - sublist[i] = x.tolist() - self._clear() - self.fromlist(sublist) - return - if start < 0: - start = 0 - if stop < start: - stop = start - assert stop <= seqlength - boundary1 = start * self.itemsize - boundary2 = stop * self.itemsize - boundary2new = boundary1 + len(x._data) - if boundary2 == boundary2new: - self._data[boundary1:boundary2] = x._data - else: - newdata = bytebuffer(len(self._data) + boundary2new-boundary2) - newdata[:boundary1] = self._data[:boundary1] - newdata[boundary1:boundary2new] = x._data - newdata[boundary2new:] = self._data[boundary2:] - self._data = newdata - else: - seqlength = len(self) - if i < 0: - i += seqlength - if self.typecode == 'c': # speed trick - self._data[i] = x - return - if not (0 <= i < seqlength): - raise IndexError(i) - boundary = i * self.itemsize - pack_into(self.typecode, self._data, boundary, x) - - def __setslice__(self, i, j, x): - self.__setitem__(slice(i, j), x) - - def __delitem__(self, i): - if isinstance(i, slice): - seqlength = len(self) - start, stop, step = i.indices(seqlength) - if start < 0: - start = 0 - if stop < start: - stop = start - assert stop <= seqlength - if step != 1: - sublist = self.tolist() # fall-back - del sublist[i] - self._clear() - self.fromlist(sublist) - return - dellength = stop - start - boundary1 = start * self.itemsize - boundary2 = stop * self.itemsize - newdata = bytebuffer(len(self._data) - (boundary2-boundary1)) - newdata[:boundary1] = self._data[:boundary1] - newdata[boundary1:] = self._data[boundary2:] - self._data = newdata - else: - seqlength = len(self) - if i < 0: - i += seqlength - if not (0 <= i < seqlength): - raise IndexError(i) - boundary = i * self.itemsize - newdata = bytebuffer(len(self._data) - self.itemsize) - newdata[:boundary] = self._data[:boundary] - newdata[boundary:] = self._data[boundary+self.itemsize:] - self._data = newdata - - def __delslice__(self, i, j): - self.__delitem__(slice(i, j)) - - def __contains__(self, item): - for x in self: - if x == item: - return True - return False - - def __iadd__(self, other): - if not isinstance(other, array): - raise TypeError("can only extend array with array") - self.extend(other) - return self - - def __imul__(self, repeat): - newdata = buffer(self._data) * repeat - self._data = bytebuffer(len(newdata)) - self._data[:] = newdata - return self - - def __iter__(self): - p = 0 - typecode = self.typecode - itemsize = self.itemsize - while p < len(self._data): - yield unpack_from(typecode, self._data, p)[0] - p += itemsize - - ##### internal methods - - def _fromiterable(self, iterable): - iterable = tuple(iterable) - n = len(iterable) - boundary = len(self._data) - newdata = bytebuffer(boundary + n * self.itemsize) - newdata[:boundary] = self._data - pack_into('%d%s' % (n, self.typecode), newdata, boundary, *iterable) - self._data = newdata - -ArrayType = array diff --git a/lib_pypy/binascii.py b/lib_pypy/binascii.py deleted file mode 100644 --- a/lib_pypy/binascii.py +++ /dev/null @@ -1,720 +0,0 @@ -"""A pure Python implementation of binascii. - -Rather slow and buggy in corner cases. -PyPy provides an RPython version too. -""" - -class Error(Exception): - pass - -class Done(Exception): - pass - -class Incomplete(Exception): - pass - -def a2b_uu(s): - if not s: - return '' - - length = (ord(s[0]) - 0x20) % 64 - - def quadruplets_gen(s): - while s: - try: - yield ord(s[0]), ord(s[1]), ord(s[2]), ord(s[3]) - except IndexError: - s += ' ' - yield ord(s[0]), ord(s[1]), ord(s[2]), ord(s[3]) - return - s = s[4:] - - try: - result = [''.join( - [chr((A - 0x20) << 2 | (((B - 0x20) >> 4) & 0x3)), - chr(((B - 0x20) & 0xf) << 4 | (((C - 0x20) >> 2) & 0xf)), - chr(((C - 0x20) & 0x3) << 6 | ((D - 0x20) & 0x3f)) - ]) for A, B, C, D in quadruplets_gen(s[1:].rstrip())] - except ValueError: - raise Error('Illegal char') - result = ''.join(result) - trailingdata = result[length:] - if trailingdata.strip('\x00'): - raise Error('Trailing garbage') - result = result[:length] - if len(result) < length: - result += ((length - len(result)) * '\x00') - return result - - -def b2a_uu(s): - length = len(s) - if length > 45: - raise Error('At most 45 bytes at once') - - def triples_gen(s): - while s: - try: - yield ord(s[0]), ord(s[1]), ord(s[2]) - except IndexError: - s += '\0\0' - yield ord(s[0]), ord(s[1]), ord(s[2]) - return - s = s[3:] - - result = [''.join( - [chr(0x20 + (( A >> 2 ) & 0x3F)), - chr(0x20 + (((A << 4) | ((B >> 4) & 0xF)) & 0x3F)), - chr(0x20 + (((B << 2) | ((C >> 6) & 0x3)) & 0x3F)), - chr(0x20 + (( C ) & 0x3F))]) - for A, B, C in triples_gen(s)] - return chr(ord(' ') + (length & 077)) + ''.join(result) + '\n' - - -table_a2b_base64 = { - 'A': 0, - 'B': 1, - 'C': 2, - 'D': 3, - 'E': 4, - 'F': 5, - 'G': 6, - 'H': 7, - 'I': 8, - 'J': 9, - 'K': 10, - 'L': 11, - 'M': 12, - 'N': 13, - 'O': 14, - 'P': 15, - 'Q': 16, - 'R': 17, - 'S': 18, - 'T': 19, - 'U': 20, - 'V': 21, - 'W': 22, - 'X': 23, - 'Y': 24, - 'Z': 25, - 'a': 26, - 'b': 27, - 'c': 28, - 'd': 29, - 'e': 30, - 'f': 31, - 'g': 32, - 'h': 33, - 'i': 34, - 'j': 35, - 'k': 36, - 'l': 37, - 'm': 38, - 'n': 39, - 'o': 40, - 'p': 41, - 'q': 42, - 'r': 43, - 's': 44, - 't': 45, - 'u': 46, - 'v': 47, - 'w': 48, - 'x': 49, - 'y': 50, - 'z': 51, - '0': 52, - '1': 53, - '2': 54, - '3': 55, - '4': 56, - '5': 57, - '6': 58, - '7': 59, - '8': 60, - '9': 61, - '+': 62, - '/': 63, - '=': 0, -} - - -def a2b_base64(s): - if not isinstance(s, (str, unicode)): - raise TypeError("expected string or unicode, got %r" % (s,)) - s = s.rstrip() - # clean out all invalid characters, this also strips the final '=' padding - # check for correct padding - - def next_valid_char(s, pos): - for i in range(pos + 1, len(s)): - c = s[i] - if c < '\x7f': - try: - table_a2b_base64[c] - return c - except KeyError: - pass - return None - - quad_pos = 0 - leftbits = 0 - leftchar = 0 - res = [] - for i, c in enumerate(s): - if c > '\x7f' or c == '\n' or c == '\r' or c == ' ': - continue - if c == '=': - if quad_pos < 2 or (quad_pos == 2 and next_valid_char(s, i) != '='): - continue - else: - leftbits = 0 - break - try: - next_c = table_a2b_base64[c] - except KeyError: - continue - quad_pos = (quad_pos + 1) & 0x03 - leftchar = (leftchar << 6) | next_c - leftbits += 6 - if leftbits >= 8: - leftbits -= 8 - res.append((leftchar >> leftbits & 0xff)) - leftchar &= ((1 << leftbits) - 1) - if leftbits != 0: - raise Error('Incorrect padding') - - return ''.join([chr(i) for i in res]) - -table_b2a_base64 = \ -"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" - -def b2a_base64(s): - length = len(s) - final_length = length % 3 - - def triples_gen(s): - while s: - try: - yield ord(s[0]), ord(s[1]), ord(s[2]) - except IndexError: - s += '\0\0' - yield ord(s[0]), ord(s[1]), ord(s[2]) - return - s = s[3:] - - - a = triples_gen(s[ :length - final_length]) - - result = [''.join( - [table_b2a_base64[( A >> 2 ) & 0x3F], - table_b2a_base64[((A << 4) | ((B >> 4) & 0xF)) & 0x3F], - table_b2a_base64[((B << 2) | ((C >> 6) & 0x3)) & 0x3F], - table_b2a_base64[( C ) & 0x3F]]) - for A, B, C in a] - - final = s[length - final_length:] - if final_length == 0: - snippet = '' - elif final_length == 1: - a = ord(final[0]) - snippet = table_b2a_base64[(a >> 2 ) & 0x3F] + \ - table_b2a_base64[(a << 4 ) & 0x3F] + '==' - else: - a = ord(final[0]) - b = ord(final[1]) - snippet = table_b2a_base64[(a >> 2) & 0x3F] + \ - table_b2a_base64[((a << 4) | (b >> 4) & 0xF) & 0x3F] + \ - table_b2a_base64[(b << 2) & 0x3F] + '=' - return ''.join(result) + snippet + '\n' - -def a2b_qp(s, header=False): - inp = 0 - odata = [] - while inp < len(s): - if s[inp] == '=': - inp += 1 - if inp >= len(s): - break - # Soft line breaks - if (s[inp] == '\n') or (s[inp] == '\r'): - if s[inp] != '\n': - while inp < len(s) and s[inp] != '\n': - inp += 1 - if inp < len(s): - inp += 1 - elif s[inp] == '=': - # broken case from broken python qp - odata.append('=') - inp += 1 - elif s[inp] in hex_numbers and s[inp + 1] in hex_numbers: - ch = chr(int(s[inp:inp+2], 16)) - inp += 2 - odata.append(ch) - else: - odata.append('=') - elif header and s[inp] == '_': - odata.append(' ') - inp += 1 - else: - odata.append(s[inp]) - inp += 1 - return ''.join(odata) - -def b2a_qp(data, quotetabs=False, istext=True, header=False): - """quotetabs=True means that tab and space characters are always - quoted. - istext=False means that \r and \n are treated as regular characters - header=True encodes space characters with '_' and requires - real '_' characters to be quoted. - """ - MAXLINESIZE = 76 - - # See if this string is using CRLF line ends - lf = data.find('\n') - crlf = lf > 0 and data[lf-1] == '\r' - - inp = 0 - linelen = 0 - odata = [] - while inp < len(data): - c = data[inp] - if (c > '~' or - c == '=' or - (header and c == '_') or - (c == '.' and linelen == 0 and (inp+1 == len(data) or - data[inp+1] == '\n' or - data[inp+1] == '\r')) or - (not istext and (c == '\r' or c == '\n')) or - ((c == '\t' or c == ' ') and (inp + 1 == len(data))) or - (c <= ' ' and c != '\r' and c != '\n' and - (quotetabs or (not quotetabs and (c != '\t' and c != ' '))))): - linelen += 3 - if linelen >= MAXLINESIZE: - odata.append('=') - if crlf: odata.append('\r') - odata.append('\n') - linelen = 3 - odata.append('=' + two_hex_digits(ord(c))) - inp += 1 - else: - if (istext and - (c == '\n' or (inp+1 < len(data) and c == '\r' and - data[inp+1] == '\n'))): - linelen = 0 - # Protect against whitespace on end of line - if (len(odata) > 0 and - (odata[-1] == ' ' or odata[-1] == '\t')): - ch = ord(odata[-1]) - odata[-1] = '=' - odata.append(two_hex_digits(ch)) - - if crlf: odata.append('\r') - odata.append('\n') - if c == '\r': - inp += 2 - else: - inp += 1 - else: - if (inp + 1 < len(data) and - data[inp+1] != '\n' and - (linelen + 1) >= MAXLINESIZE): - odata.append('=') - if crlf: odata.append('\r') - odata.append('\n') - linelen = 0 - - linelen += 1 - if header and c == ' ': - c = '_' - odata.append(c) - inp += 1 - return ''.join(odata) - -hex_numbers = '0123456789ABCDEF' -def hex(n): - if n == 0: - return '0' - - if n < 0: - n = -n - sign = '-' - else: - sign = '' - arr = [] - - def hex_gen(n): - """ Yield a nibble at a time. """ - while n: - yield n % 0x10 - n = n / 0x10 - - for nibble in hex_gen(n): - arr = [hex_numbers[nibble]] + arr - return sign + ''.join(arr) - -def two_hex_digits(n): - return hex_numbers[n / 0x10] + hex_numbers[n % 0x10] - - -def strhex_to_int(s): - i = 0 - for c in s: - i = i * 0x10 + hex_numbers.index(c) - return i - -hqx_encoding = '!"#$%&\'()*+,-012345689 at ABCDEFGHIJKLMNPQRSTUVXYZ[`abcdefhijklmpqr' - -DONE = 0x7f -SKIP = 0x7e -FAIL = 0x7d - -table_a2b_hqx = [ - #^@ ^A ^B ^C ^D ^E ^F ^G - FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, - #\b \t \n ^K ^L \r ^N ^O - FAIL, FAIL, SKIP, FAIL, FAIL, SKIP, FAIL, FAIL, - #^P ^Q ^R ^S ^T ^U ^V ^W - FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, - #^X ^Y ^Z ^[ ^\ ^] ^^ ^_ - FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, - # ! " # $ % & ' - FAIL, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, - #( ) * + , - . / - 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, FAIL, FAIL, - #0 1 2 3 4 5 6 7 - 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, FAIL, - #8 9 : ; < = > ? - 0x14, 0x15, DONE, FAIL, FAIL, FAIL, FAIL, FAIL, - #@ A B C D E F G - 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, - #H I J K L M N O - 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, FAIL, - #P Q R S T U V W - 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, FAIL, - #X Y Z [ \ ] ^ _ - 0x2C, 0x2D, 0x2E, 0x2F, FAIL, FAIL, FAIL, FAIL, - #` a b c d e f g - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, FAIL, - #h i j k l m n o - 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, FAIL, FAIL, - #p q r s t u v w - 0x3D, 0x3E, 0x3F, FAIL, FAIL, FAIL, FAIL, FAIL, - #x y z { | } ~ ^? - FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, - FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, - FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, - FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, - FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, - FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, - FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, - FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, - FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, - FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, - FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, - FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, - FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, - FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, - FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, - FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, - FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, FAIL, -] - -def a2b_hqx(s): - result = [] - - def quadruples_gen(s): - t = [] - for c in s: - res = table_a2b_hqx[ord(c)] - if res == SKIP: - continue - elif res == FAIL: - raise Error('Illegal character') - elif res == DONE: - yield t - raise Done - else: - t.append(res) - if len(t) == 4: - yield t - t = [] - yield t - - done = 0 - try: - for snippet in quadruples_gen(s): - length = len(snippet) - if length == 4: - result.append(chr(((snippet[0] & 0x3f) << 2) | (snippet[1] >> 4))) - result.append(chr(((snippet[1] & 0x0f) << 4) | (snippet[2] >> 2))) - result.append(chr(((snippet[2] & 0x03) << 6) | (snippet[3]))) - elif length == 3: - result.append(chr(((snippet[0] & 0x3f) << 2) | (snippet[1] >> 4))) - result.append(chr(((snippet[1] & 0x0f) << 4) | (snippet[2] >> 2))) - elif length == 2: - result.append(chr(((snippet[0] & 0x3f) << 2) | (snippet[1] >> 4))) - except Done: - done = 1 - except Error: - raise - return (''.join(result), done) - -def b2a_hqx(s): - result =[] - - def triples_gen(s): - while s: - try: - yield ord(s[0]), ord(s[1]), ord(s[2]) - except IndexError: - yield tuple([ord(c) for c in s]) - s = s[3:] - - for snippet in triples_gen(s): - length = len(snippet) - if length == 3: - result.append( - hqx_encoding[(snippet[0] & 0xfc) >> 2]) - result.append(hqx_encoding[ - ((snippet[0] & 0x03) << 4) | ((snippet[1] & 0xf0) >> 4)]) - result.append(hqx_encoding[ - (snippet[1] & 0x0f) << 2 | ((snippet[2] & 0xc0) >> 6)]) - result.append(hqx_encoding[snippet[2] & 0x3f]) - elif length == 2: - result.append( - hqx_encoding[(snippet[0] & 0xfc) >> 2]) - result.append(hqx_encoding[ - ((snippet[0] & 0x03) << 4) | ((snippet[1] & 0xf0) >> 4)]) - result.append(hqx_encoding[ - (snippet[1] & 0x0f) << 2]) - elif length == 1: - result.append( - hqx_encoding[(snippet[0] & 0xfc) >> 2]) - result.append(hqx_encoding[ - ((snippet[0] & 0x03) << 4)]) - return ''.join(result) - -crctab_hqx = [ - 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, - 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, - 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, - 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, - 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, - 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, - 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, - 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, - 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, - 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, - 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, - 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, - 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, - 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, - 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, - 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, - 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, - 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, - 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, - 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, - 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, - 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, - 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, - 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, - 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, - 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, - 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, - 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, - 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, - 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, - 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, - 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0, -] - -def crc_hqx(s, crc): - for c in s: - crc = ((crc << 8) & 0xff00) ^ crctab_hqx[((crc >> 8) & 0xff) ^ ord(c)] - - return crc - -def rlecode_hqx(s): - """ - Run length encoding for binhex4. - The CPython implementation does not do run length encoding - of \x90 characters. This implementation does. - """ - if not s: - return '' - result = [] - prev = s[0] - count = 1 - # Add a dummy character to get the loop to go one extra round. - # The dummy must be different from the last character of s. - # In the same step we remove the first character, which has - # already been stored in prev. - if s[-1] == '!': - s = s[1:] + '?' - else: - s = s[1:] + '!' - - for c in s: - if c == prev and count < 255: - count += 1 - else: - if count == 1: - if prev != '\x90': - result.append(prev) - else: - result.extend(['\x90', '\x00']) - elif count < 4: - if prev != '\x90': - result.extend([prev] * count) - else: - result.extend(['\x90', '\x00'] * count) - else: - if prev != '\x90': - result.extend([prev, '\x90', chr(count)]) - else: - result.extend(['\x90', '\x00', '\x90', chr(count)]) - count = 1 - prev = c - - return ''.join(result) - -def rledecode_hqx(s): - s = s.split('\x90') - result = [s[0]] - prev = s[0] - for snippet in s[1:]: - count = ord(snippet[0]) - if count > 0: - result.append(prev[-1] * (count-1)) - prev = snippet - else: - result. append('\x90') - prev = '\x90' - result.append(snippet[1:]) - - return ''.join(result) - -crc_32_tab = [ - 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, - 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L, - 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, - 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, - 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, - 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, - 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, - 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, - 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, - 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, - 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, - 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, - 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L, - 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, - 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, - 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, - 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, - 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, - 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, - 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, - 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, - 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, - 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, - 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, - 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, - 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, - 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, - 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, - 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L, - 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, - 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, - 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, - 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, - 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, - 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, - 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, - 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, - 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, - 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, - 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, - 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, - 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, - 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, - 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, - 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, - 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, - 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, - 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, - 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, - 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, - 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, - 0x2d02ef8dL -] - -def crc32(s, crc=0): - result = 0 - crc = ~long(crc) & 0xffffffffL - for c in s: - crc = crc_32_tab[(crc ^ long(ord(c))) & 0xffL] ^ (crc >> 8) - #/* Note: (crc >> 8) MUST zero fill on left - - result = crc ^ 0xffffffffL - - if result > 2**31: - result = ((result + 2**31) % 2**32) - 2**31 - - return result - -def b2a_hex(s): - result = [] - for char in s: - c = (ord(char) >> 4) & 0xf - if c > 9: - c = c + ord('a') - 10 - else: - c = c + ord('0') - result.append(chr(c)) - c = ord(char) & 0xf - if c > 9: - c = c + ord('a') - 10 - else: - c = c + ord('0') - result.append(chr(c)) - return ''.join(result) - -hexlify = b2a_hex - -table_hex = [ - -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, - -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, - -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1, -1,-1,-1,-1, - -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1, - -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, - -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1, - -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1 -] - - -def a2b_hex(t): - result = [] - - def pairs_gen(s): - while s: - try: - yield table_hex[ord(s[0])], table_hex[ord(s[1])] - except IndexError: - if len(s): - raise TypeError('Odd-length string') - return - s = s[2:] - - for a, b in pairs_gen(t): - if a < 0 or b < 0: - raise TypeError('Non-hexadecimal digit found') - result.append(chr((a << 4) + b)) - return ''.join(result) - - -unhexlify = a2b_hex diff --git a/lib_pypy/cPickle.py b/lib_pypy/cPickle.py --- a/lib_pypy/cPickle.py +++ b/lib_pypy/cPickle.py @@ -2,16 +2,95 @@ # One-liner implementation of cPickle # -from pickle import * +from pickle import Pickler, dump, dumps, PickleError, PicklingError, UnpicklingError, _EmptyClass from pickle import __doc__, __version__, format_version, compatible_formats +from types import * +from copy_reg import dispatch_table +from copy_reg import _extension_registry, _inverted_registry, _extension_cache +import marshal, struct, sys try: from __pypy__ import builtinify except ImportError: builtinify = lambda f: f +# These are purely informational; no code uses these. +format_version = "2.0" # File format version we write +compatible_formats = ["1.0", # Original protocol 0 + "1.1", # Protocol 0 with INST added + "1.2", # Original protocol 1 + "1.3", # Protocol 1 with BINFLOAT added + "2.0", # Protocol 2 + ] # Old format versions we can read + +# Keep in synch with cPickle. This is the highest protocol number we +# know how to read. +HIGHEST_PROTOCOL = 2 BadPickleGet = KeyError UnpickleableError = PicklingError +MARK = ord('(') # push special markobject on stack +STOP = ord('.') # every pickle ends with STOP +POP = ord('0') # discard topmost stack item +POP_MARK = ord('1') # discard stack top through topmost markobject +DUP = ord('2') # duplicate top stack item +FLOAT = ord('F') # push float object; decimal string argument +INT = ord('I') # push integer or bool; decimal string argument +BININT = ord('J') # push four-byte signed int +BININT1 = ord('K') # push 1-byte unsigned int +LONG = ord('L') # push long; decimal string argument +BININT2 = ord('M') # push 2-byte unsigned int +NONE = ord('N') # push None +PERSID = ord('P') # push persistent object; id is taken from string arg +BINPERSID = ord('Q') # " " " ; " " " " stack +REDUCE = ord('R') # apply callable to argtuple, both on stack +STRING = ord('S') # push string; NL-terminated string argument +BINSTRING = ord('T') # push string; counted binary string argument +SHORT_BINSTRING = ord('U') # " " ; " " " " < 256 bytes +UNICODE = ord('V') # push Unicode string; raw-unicode-escaped'd argument +BINUNICODE = ord('X') # " " " ; counted UTF-8 string argument +APPEND = ord('a') # append stack top to list below it +BUILD = ord('b') # call __setstate__ or __dict__.update() +GLOBAL = ord('c') # push self.find_class(modname, name); 2 string args +DICT = ord('d') # build a dict from stack items +EMPTY_DICT = ord('}') # push empty dict +APPENDS = ord('e') # extend list on stack by topmost stack slice +GET = ord('g') # push item from memo on stack; index is string arg +BINGET = ord('h') # " " " " " " ; " " 1-byte arg +INST = ord('i') # build & push class instance +LONG_BINGET = ord('j') # push item from memo on stack; index is 4-byte arg +LIST = ord('l') # build list from topmost stack items +EMPTY_LIST = ord(']') # push empty list +OBJ = ord('o') # build & push class instance +PUT = ord('p') # store stack top in memo; index is string arg +BINPUT = ord('q') # " " " " " ; " " 1-byte arg +LONG_BINPUT = ord('r') # " " " " " ; " " 4-byte arg +SETITEM = ord('s') # add key+value pair to dict +TUPLE = ord('t') # build tuple from topmost stack items +EMPTY_TUPLE = ord(')') # push empty tuple +SETITEMS = ord('u') # modify dict by adding topmost key+value pairs +BINFLOAT = ord('G') # push float; arg is 8-byte float encoding + +TRUE = 'I01\n' # not an opcode; see INT docs in pickletools.py +FALSE = 'I00\n' # not an opcode; see INT docs in pickletools.py + +# Protocol 2 + +PROTO = ord('\x80') # identify pickle protocol +NEWOBJ = ord('\x81') # build object by applying cls.__new__ to argtuple +EXT1 = ord('\x82') # push object from extension registry; 1-byte index +EXT2 = ord('\x83') # ditto, but 2-byte index +EXT4 = ord('\x84') # ditto, but 4-byte index +TUPLE1 = ord('\x85') # build 1-tuple from stack top +TUPLE2 = ord('\x86') # build 2-tuple from two topmost stack items +TUPLE3 = ord('\x87') # build 3-tuple from three topmost stack items +NEWTRUE = ord('\x88') # push True +NEWFALSE = ord('\x89') # push False +LONG1 = ord('\x8a') # push long from < 256 bytes +LONG4 = ord('\x8b') # push really big long + +_tuplesize2code = [EMPTY_TUPLE, TUPLE1, TUPLE2, TUPLE3] + + # ____________________________________________________________ # XXX some temporary dark magic to produce pickled dumps that are # closer to the ones produced by cPickle in CPython @@ -44,3 +123,474 @@ file = StringIO() Pickler(file, protocol).dump(obj) return file.getvalue() + +# Why use struct.pack() for pickling but marshal.loads() for +# unpickling? struct.pack() is 40% faster than marshal.dumps(), but +# marshal.loads() is twice as fast as struct.unpack()! +mloads = marshal.loads + +# Unpickling machinery + +class Unpickler(object): + + def __init__(self, file): + """This takes a file-like object for reading a pickle data stream. + + The protocol version of the pickle is detected automatically, so no + proto argument is needed. + + The file-like object must have two methods, a read() method that + takes an integer argument, and a readline() method that requires no + arguments. Both methods should return a string. Thus file-like + object can be a file object opened for reading, a StringIO object, + or any other custom object that meets this interface. + """ + self.readline = file.readline + self.read = file.read + self.memo = {} + + def load(self): + """Read a pickled object representation from the open file. + + Return the reconstituted object hierarchy specified in the file. + """ + self.mark = object() # any new unique object + self.stack = [] + self.append = self.stack.append + try: + key = ord(self.read(1)) + while key != STOP: + self.dispatch[key](self) + key = ord(self.read(1)) + except TypeError: + if self.read(1) == '': + raise EOFError + raise + return self.stack.pop() + + # Return largest index k such that self.stack[k] is self.mark. + # If the stack doesn't contain a mark, eventually raises IndexError. + # This could be sped by maintaining another stack, of indices at which + # the mark appears. For that matter, the latter stack would suffice, + # and we wouldn't need to push mark objects on self.stack at all. + # Doing so is probably a good thing, though, since if the pickle is + # corrupt (or hostile) we may get a clue from finding self.mark embedded + # in unpickled objects. + def marker(self): + k = len(self.stack)-1 + while self.stack[k] is not self.mark: k -= 1 + return k + + dispatch = {} + + def load_proto(self): + proto = ord(self.read(1)) + if not 0 <= proto <= 2: + raise ValueError, "unsupported pickle protocol: %d" % proto + dispatch[PROTO] = load_proto + + def load_persid(self): + pid = self.readline()[:-1] + self.append(self.persistent_load(pid)) + dispatch[PERSID] = load_persid + + def load_binpersid(self): + pid = self.stack.pop() + self.append(self.persistent_load(pid)) + dispatch[BINPERSID] = load_binpersid + + def load_none(self): + self.append(None) + dispatch[NONE] = load_none + + def load_false(self): + self.append(False) + dispatch[NEWFALSE] = load_false + + def load_true(self): + self.append(True) + dispatch[NEWTRUE] = load_true + + def load_int(self): + data = self.readline() + if data == FALSE[1:]: + val = False + elif data == TRUE[1:]: + val = True + else: + try: + val = int(data) + except ValueError: + val = long(data) + self.append(val) + dispatch[INT] = load_int + + def load_binint(self): + self.append(mloads('i' + self.read(4))) + dispatch[BININT] = load_binint + + def load_binint1(self): + self.append(ord(self.read(1))) + dispatch[BININT1] = load_binint1 + + def load_binint2(self): + self.append(mloads('i' + self.read(2) + '\000\000')) + dispatch[BININT2] = load_binint2 + + def load_long(self): + self.append(long(self.readline()[:-1], 0)) + dispatch[LONG] = load_long + + def load_long1(self): + n = ord(self.read(1)) + bytes = self.read(n) + self.append(decode_long(bytes)) + dispatch[LONG1] = load_long1 + + def load_long4(self): + n = mloads('i' + self.read(4)) + bytes = self.read(n) + self.append(decode_long(bytes)) + dispatch[LONG4] = load_long4 + + def load_float(self): + self.append(float(self.readline()[:-1])) + dispatch[FLOAT] = load_float + + def load_binfloat(self, unpack=struct.unpack): + self.append(unpack('>d', self.read(8))[0]) + dispatch[BINFLOAT] = load_binfloat + + def load_string(self): + rep = self.readline() + if len(rep) < 3: + raise ValueError, "insecure string pickle" + if rep[0] == "'" == rep[-2]: + rep = rep[1:-2] + elif rep[0] == '"' == rep[-2]: + rep = rep[1:-2] + else: + raise ValueError, "insecure string pickle" + self.append(rep.decode("string-escape")) + dispatch[STRING] = load_string + + def load_binstring(self): + L = mloads('i' + self.read(4)) + self.append(self.read(L)) + dispatch[BINSTRING] = load_binstring + + def load_unicode(self): + self.append(unicode(self.readline()[:-1],'raw-unicode-escape')) + dispatch[UNICODE] = load_unicode + + def load_binunicode(self): + L = mloads('i' + self.read(4)) + self.append(unicode(self.read(L),'utf-8')) + dispatch[BINUNICODE] = load_binunicode + + def load_short_binstring(self): + L = ord(self.read(1)) + self.append(self.read(L)) + dispatch[SHORT_BINSTRING] = load_short_binstring + + def load_tuple(self): + k = self.marker() + self.stack[k:] = [tuple(self.stack[k+1:])] + dispatch[TUPLE] = load_tuple + + def load_empty_tuple(self): + self.stack.append(()) + dispatch[EMPTY_TUPLE] = load_empty_tuple + + def load_tuple1(self): + self.stack[-1] = (self.stack[-1],) + dispatch[TUPLE1] = load_tuple1 + + def load_tuple2(self): + self.stack[-2:] = [(self.stack[-2], self.stack[-1])] + dispatch[TUPLE2] = load_tuple2 + + def load_tuple3(self): + self.stack[-3:] = [(self.stack[-3], self.stack[-2], self.stack[-1])] + dispatch[TUPLE3] = load_tuple3 + + def load_empty_list(self): + self.stack.append([]) + dispatch[EMPTY_LIST] = load_empty_list + + def load_empty_dictionary(self): + self.stack.append({}) + dispatch[EMPTY_DICT] = load_empty_dictionary + + def load_list(self): + k = self.marker() + self.stack[k:] = [self.stack[k+1:]] + dispatch[LIST] = load_list + + def load_dict(self): + k = self.marker() + d = {} + items = self.stack[k+1:] + for i in range(0, len(items), 2): + key = items[i] + value = items[i+1] + d[key] = value + self.stack[k:] = [d] + dispatch[DICT] = load_dict + + # INST and OBJ differ only in how they get a class object. It's not + # only sensible to do the rest in a common routine, the two routines + # previously diverged and grew different bugs. + # klass is the class to instantiate, and k points to the topmost mark + # object, following which are the arguments for klass.__init__. + def _instantiate(self, klass, k): + args = tuple(self.stack[k+1:]) + del self.stack[k:] + instantiated = 0 + if (not args and + type(klass) is ClassType and + not hasattr(klass, "__getinitargs__")): + try: + value = _EmptyClass() + value.__class__ = klass + instantiated = 1 + except RuntimeError: + # In restricted execution, assignment to inst.__class__ is + # prohibited + pass + if not instantiated: + try: + value = klass(*args) + except TypeError, err: + raise TypeError, "in constructor for %s: %s" % ( + klass.__name__, str(err)), sys.exc_info()[2] + self.append(value) + + def load_inst(self): + module = self.readline()[:-1] + name = self.readline()[:-1] + klass = self.find_class(module, name) + self._instantiate(klass, self.marker()) + dispatch[INST] = load_inst + + def load_obj(self): + # Stack is ... markobject classobject arg1 arg2 ... + k = self.marker() + klass = self.stack.pop(k+1) + self._instantiate(klass, k) + dispatch[OBJ] = load_obj + + def load_newobj(self): + args = self.stack.pop() + cls = self.stack[-1] + obj = cls.__new__(cls, *args) + self.stack[-1] = obj + dispatch[NEWOBJ] = load_newobj + + def load_global(self): + module = self.readline()[:-1] + name = self.readline()[:-1] + klass = self.find_class(module, name) + self.append(klass) + dispatch[GLOBAL] = load_global + + def load_ext1(self): + code = ord(self.read(1)) + self.get_extension(code) + dispatch[EXT1] = load_ext1 + + def load_ext2(self): + code = mloads('i' + self.read(2) + '\000\000') + self.get_extension(code) + dispatch[EXT2] = load_ext2 + + def load_ext4(self): + code = mloads('i' + self.read(4)) + self.get_extension(code) + dispatch[EXT4] = load_ext4 + + def get_extension(self, code): + nil = [] + obj = _extension_cache.get(code, nil) + if obj is not nil: + self.append(obj) + return + key = _inverted_registry.get(code) + if not key: + raise ValueError("unregistered extension code %d" % code) + obj = self.find_class(*key) + _extension_cache[code] = obj + self.append(obj) + + def find_class(self, module, name): + # Subclasses may override this + __import__(module) + mod = sys.modules[module] + klass = getattr(mod, name) + return klass + + def load_reduce(self): + args = self.stack.pop() + func = self.stack[-1] + value = self.stack[-1](*args) + self.stack[-1] = value + dispatch[REDUCE] = load_reduce + + def load_pop(self): + del self.stack[-1] + dispatch[POP] = load_pop + + def load_pop_mark(self): + k = self.marker() + del self.stack[k:] + dispatch[POP_MARK] = load_pop_mark + + def load_dup(self): + self.append(self.stack[-1]) + dispatch[DUP] = load_dup + + def load_get(self): + self.append(self.memo[self.readline()[:-1]]) + dispatch[GET] = load_get + + def load_binget(self): + i = ord(self.read(1)) + self.append(self.memo[repr(i)]) + dispatch[BINGET] = load_binget + + def load_long_binget(self): + i = mloads('i' + self.read(4)) + self.append(self.memo[repr(i)]) + dispatch[LONG_BINGET] = load_long_binget + + def load_put(self): + self.memo[self.readline()[:-1]] = self.stack[-1] + dispatch[PUT] = load_put + + def load_binput(self): + i = ord(self.read(1)) + self.memo[repr(i)] = self.stack[-1] + dispatch[BINPUT] = load_binput + + def load_long_binput(self): + i = mloads('i' + self.read(4)) + self.memo[repr(i)] = self.stack[-1] + dispatch[LONG_BINPUT] = load_long_binput + + def load_append(self): + value = self.stack.pop() + self.stack[-1].append(value) + dispatch[APPEND] = load_append + + def load_appends(self): + stack = self.stack + mark = self.marker() + lst = stack[mark - 1] + lst.extend(stack[mark + 1:]) + del stack[mark:] + dispatch[APPENDS] = load_appends + + def load_setitem(self): + stack = self.stack + value = stack.pop() + key = stack.pop() + dict = stack[-1] + dict[key] = value + dispatch[SETITEM] = load_setitem + + def load_setitems(self): + stack = self.stack + mark = self.marker() + dict = stack[mark - 1] + for i in range(mark + 1, len(stack), 2): + dict[stack[i]] = stack[i + 1] + + del stack[mark:] + dispatch[SETITEMS] = load_setitems + + def load_build(self): + stack = self.stack + state = stack.pop() + inst = stack[-1] + setstate = getattr(inst, "__setstate__", None) + if setstate: + setstate(state) + return + slotstate = None + if isinstance(state, tuple) and len(state) == 2: + state, slotstate = state + if state: + try: + d = inst.__dict__ + try: + for k, v in state.iteritems(): + d[intern(k)] = v + # keys in state don't have to be strings + # don't blow up, but don't go out of our way + except TypeError: + d.update(state) + + except RuntimeError: + # XXX In restricted execution, the instance's __dict__ + # is not accessible. Use the old way of unpickling + # the instance variables. This is a semantic + # difference when unpickling in restricted + # vs. unrestricted modes. + # Note, however, that cPickle has never tried to do the + # .update() business, and always uses + # PyObject_SetItem(inst.__dict__, key, value) in a + # loop over state.items(). + for k, v in state.items(): + setattr(inst, k, v) + if slotstate: + for k, v in slotstate.items(): + setattr(inst, k, v) + dispatch[BUILD] = load_build + + def load_mark(self): + self.append(self.mark) + dispatch[MARK] = load_mark + +#from pickle import decode_long + +def decode_long(data): + r"""Decode a long from a two's complement little-endian binary string. + + >>> decode_long('') + 0L + >>> decode_long("\xff\x00") + 255L + >>> decode_long("\xff\x7f") + 32767L + >>> decode_long("\x00\xff") + -256L + >>> decode_long("\x00\x80") + -32768L + >>> decode_long("\x80") + -128L + >>> decode_long("\x7f") + 127L + """ + + nbytes = len(data) + if nbytes == 0: + return 0L + ind = nbytes - 1 + while ind and ord(data[ind]) == 0: + ind -= 1 + n = ord(data[ind]) + while ind: + n <<= 8 + ind -= 1 + if ord(data[ind]): + n += ord(data[ind]) + if ord(data[nbytes - 1]) >= 128: + n -= 1L << (nbytes << 3) + return n + +def load(f): + return Unpickler(f).load() + +def loads(str): + f = StringIO(str) + return Unpickler(f).load() diff --git a/lib_pypy/datetime.py b/lib_pypy/datetime.py --- a/lib_pypy/datetime.py +++ b/lib_pypy/datetime.py @@ -968,8 +968,7 @@ self._checkOverflow(t.year) result = date(t.year, t.month, t.day) return result - raise TypeError - # XXX Should be 'return NotImplemented', but there's a bug in 2.2... + return NotImplemented # note that this doesn't work on CPython 2.2 __radd__ = __add__ @@ -1032,8 +1031,8 @@ def __setstate(self, string): if len(string) != 4 or not (1 <= ord(string[2]) <= 12): raise TypeError("not enough arguments") - yhi, ylo, self._month, self._day = map(ord, string) - self._year = yhi * 256 + ylo + self._month, self._day = ord(string[2]), ord(string[3]) + self._year = ord(string[0]) * 256 + ord(string[1]) def __reduce__(self): return (self.__class__, self._getstate()) @@ -1421,9 +1420,10 @@ def __setstate(self, string, tzinfo): if len(string) != 6 or ord(string[0]) >= 24: raise TypeError("an integer is required") - self._hour, self._minute, self._second, us1, us2, us3 = \ - map(ord, string) - self._microsecond = (((us1 << 8) | us2) << 8) | us3 + self._hour, self._minute, self._second = ord(string[0]), \ + ord(string[1]), ord(string[2]) + self._microsecond = (((ord(string[3]) << 8) | \ + ord(string[4])) << 8) | ord(string[5]) self._tzinfo = tzinfo def __reduce__(self): @@ -1903,10 +1903,11 @@ return (basestate, self._tzinfo) def __setstate(self, string, tzinfo): - (yhi, ylo, self._month, self._day, self._hour, - self._minute, self._second, us1, us2, us3) = map(ord, string) - self._year = yhi * 256 + ylo - self._microsecond = (((us1 << 8) | us2) << 8) | us3 + (self._month, self._day, self._hour, self._minute, + self._second) = (ord(string[2]), ord(string[3]), ord(string[4]), + ord(string[5]), ord(string[6])) + self._year = ord(string[0]) * 256 + ord(string[1]) + self._microsecond = (((ord(string[7]) << 8) | ord(string[8])) << 8) | ord(string[9]) self._tzinfo = tzinfo def __reduce__(self): diff --git a/lib_pypy/numpypy/core/numeric.py b/lib_pypy/numpypy/core/numeric.py --- a/lib_pypy/numpypy/core/numeric.py +++ b/lib_pypy/numpypy/core/numeric.py @@ -6,7 +6,7 @@ import _numpypy as multiarray # ARGH from numpypy.core.arrayprint import array2string - +newaxis = None def asanyarray(a, dtype=None, order=None, maskna=None, ownmaskna=False): """ @@ -306,6 +306,125 @@ else: return multiarray.set_string_function(f, repr) +def array_equal(a1, a2): + """ + True if two arrays have the same shape and elements, False otherwise. + + Parameters + ---------- + a1, a2 : array_like + Input arrays. + + Returns + ------- + b : bool + Returns True if the arrays are equal. + + See Also + -------- + allclose: Returns True if two arrays are element-wise equal within a + tolerance. + array_equiv: Returns True if input arrays are shape consistent and all + elements equal. + + Examples + -------- + >>> np.array_equal([1, 2], [1, 2]) + True + >>> np.array_equal(np.array([1, 2]), np.array([1, 2])) + True + >>> np.array_equal([1, 2], [1, 2, 3]) + False + >>> np.array_equal([1, 2], [1, 4]) + False + + """ + try: + a1, a2 = asarray(a1), asarray(a2) + except: + return False + if a1.shape != a2.shape: + return False + return bool((a1 == a2).all()) + +def asarray(a, dtype=None, order=None, maskna=None, ownmaskna=False): + """ + Convert the input to an array. + + Parameters + ---------- + a : array_like + Input data, in any form that can be converted to an array. This + includes lists, lists of tuples, tuples, tuples of tuples, tuples + of lists and ndarrays. + dtype : data-type, optional + By default, the data-type is inferred from the input data. + order : {'C', 'F'}, optional + Whether to use row-major ('C') or column-major ('F' for FORTRAN) + memory representation. Defaults to 'C'. + maskna : bool or None, optional + If this is set to True, it forces the array to have an NA mask. + If this is set to False, it forces the array to not have an NA + mask. + ownmaskna : bool, optional + If this is set to True, forces the array to have a mask which + it owns. + + Returns + ------- + out : ndarray + Array interpretation of `a`. No copy is performed if the input + is already an ndarray. If `a` is a subclass of ndarray, a base + class ndarray is returned. + + See Also + -------- + asanyarray : Similar function which passes through subclasses. + ascontiguousarray : Convert input to a contiguous array. + asfarray : Convert input to a floating point ndarray. + asfortranarray : Convert input to an ndarray with column-major + memory order. + asarray_chkfinite : Similar function which checks input for NaNs and Infs. + fromiter : Create an array from an iterator. + fromfunction : Construct an array by executing a function on grid + positions. + + Examples + -------- + Convert a list into an array: + + >>> a = [1, 2] + >>> np.asarray(a) + array([1, 2]) + + Existing arrays are not copied: + + >>> a = np.array([1, 2]) + >>> np.asarray(a) is a + True + + If `dtype` is set, array is copied only if dtype does not match: + + >>> a = np.array([1, 2], dtype=np.float32) + >>> np.asarray(a, dtype=np.float32) is a + True + >>> np.asarray(a, dtype=np.float64) is a + False + + Contrary to `asanyarray`, ndarray subclasses are not passed through: + + >>> issubclass(np.matrix, np.ndarray) + True + >>> a = np.matrix([[1, 2]]) + >>> np.asarray(a) is a + False + >>> np.asanyarray(a) is a + True + + """ + return array(a, dtype, copy=False, order=order, + maskna=maskna, ownmaskna=ownmaskna) + set_string_function(array_str, 0) set_string_function(array_repr, 1) @@ -319,4 +438,4 @@ False_ = bool_(False) True_ = bool_(True) e = math.e -pi = math.pi \ No newline at end of file +pi = math.pi diff --git a/lib_pypy/pypy_test/test_binascii.py b/lib_pypy/pypy_test/test_binascii.py deleted file mode 100644 --- a/lib_pypy/pypy_test/test_binascii.py +++ /dev/null @@ -1,168 +0,0 @@ -from __future__ import absolute_import -import py -from lib_pypy import binascii - -# Create binary test data -data = "The quick brown fox jumps over the lazy dog.\r\n" -# Be slow so we don't depend on other modules -data += "".join(map(chr, xrange(256))) -data += "\r\nHello world.\n" - -def test_exceptions(): - # Check module exceptions - assert issubclass(binascii.Error, Exception) - assert issubclass(binascii.Incomplete, Exception) - -def test_functions(): - # Check presence of all functions - funcs = [] - for suffix in "base64", "hqx", "uu", "hex": - prefixes = ["a2b_", "b2a_"] - if suffix == "hqx": - prefixes.extend(["crc_", "rlecode_", "rledecode_"]) - for prefix in prefixes: - name = prefix + suffix - assert callable(getattr(binascii, name)) - py.test.raises(TypeError, getattr(binascii, name)) - for name in ("hexlify", "unhexlify"): - assert callable(getattr(binascii, name)) - py.test.raises(TypeError, getattr(binascii, name)) - -def test_base64valid(): - # Test base64 with valid data - MAX_BASE64 = 57 - lines = [] - for i in range(0, len(data), MAX_BASE64): - b = data[i:i+MAX_BASE64] - a = binascii.b2a_base64(b) - lines.append(a) - res = "" - for line in lines: - b = binascii.a2b_base64(line) - res = res + b - assert res == data - -def test_base64invalid(): - # Test base64 with random invalid characters sprinkled throughout - # (This requires a new version of binascii.) - MAX_BASE64 = 57 - lines = [] - for i in range(0, len(data), MAX_BASE64): - b = data[i:i+MAX_BASE64] - a = binascii.b2a_base64(b) - lines.append(a) - - fillers = "" - valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/" - for i in xrange(256): - c = chr(i) - if c not in valid: - fillers += c - def addnoise(line): - noise = fillers - ratio = len(line) // len(noise) - res = "" - while line and noise: - if len(line) // len(noise) > ratio: - c, line = line[0], line[1:] - else: - c, noise = noise[0], noise[1:] - res += c - return res + noise + line - res = "" - for line in map(addnoise, lines): - b = binascii.a2b_base64(line) - res += b - assert res == data - - # Test base64 with just invalid characters, which should return - # empty strings. TBD: shouldn't it raise an exception instead ? - assert binascii.a2b_base64(fillers) == '' - -def test_uu(): - MAX_UU = 45 - lines = [] - for i in range(0, len(data), MAX_UU): - b = data[i:i+MAX_UU] - a = binascii.b2a_uu(b) - lines.append(a) - res = "" - for line in lines: - b = binascii.a2b_uu(line) - res += b - assert res == data - - assert binascii.a2b_uu("\x7f") == "\x00"*31 - assert binascii.a2b_uu("\x80") == "\x00"*32 - assert binascii.a2b_uu("\xff") == "\x00"*31 - py.test.raises(binascii.Error, binascii.a2b_uu, "\xff\x00") - py.test.raises(binascii.Error, binascii.a2b_uu, "!!!!") - - py.test.raises(binascii.Error, binascii.b2a_uu, 46*"!") - -def test_crc32(): - crc = binascii.crc32("Test the CRC-32 of") - crc = binascii.crc32(" this string.", crc) - assert crc == 1571220330 - - crc = binascii.crc32('frotz\n', 0) - assert crc == -372923920 - - py.test.raises(TypeError, binascii.crc32) - -def test_hex(): - # test hexlification - s = '{s\005\000\000\000worldi\002\000\000\000s\005\000\000\000helloi\001\000\000\0000' - t = binascii.b2a_hex(s) - u = binascii.a2b_hex(t) - assert s == u - py.test.raises(TypeError, binascii.a2b_hex, t[:-1]) - py.test.raises(TypeError, binascii.a2b_hex, t[:-1] + 'q') - - # Verify the treatment of Unicode strings - assert binascii.hexlify(unicode('a', 'ascii')) == '61' - -def test_qp(): - # A test for SF bug 534347 (segfaults without the proper fix) - try: - binascii.a2b_qp("", **{1:1}) - except TypeError: - pass - else: - fail("binascii.a2b_qp(**{1:1}) didn't raise TypeError") - assert binascii.a2b_qp("= ") == "= " - assert binascii.a2b_qp("==") == "=" - assert binascii.a2b_qp("=AX") == "=AX" - py.test.raises(TypeError, binascii.b2a_qp, foo="bar") - assert binascii.a2b_qp("=00\r\n=00") == "\x00\r\n\x00" - assert binascii.b2a_qp("\xff\r\n\xff\n\xff") == "=FF\r\n=FF\r\n=FF" - target = "0"*75+"=\r\n=FF\r\n=FF\r\n=FF" - assert binascii.b2a_qp("0"*75+"\xff\r\n\xff\r\n\xff") == target - -def test_empty_string(): - # A test for SF bug #1022953. Make sure SystemError is not raised. - for n in ['b2a_qp', 'a2b_hex', 'b2a_base64', 'a2b_uu', 'a2b_qp', - 'b2a_hex', 'unhexlify', 'hexlify', 'crc32', 'b2a_hqx', - 'a2b_hqx', 'a2b_base64', 'rlecode_hqx', 'b2a_uu', - 'rledecode_hqx']: - f = getattr(binascii, n) - f('') - binascii.crc_hqx('', 0) - -def test_qp_bug_case(): - assert binascii.b2a_qp('y'*77, False, False) == 'y'*75 + '=\nyy' - assert binascii.b2a_qp(' '*77, False, False) == ' '*75 + '=\n =20' - assert binascii.b2a_qp('y'*76, False, False) == 'y'*76 - assert binascii.b2a_qp(' '*76, False, False) == ' '*75 + '=\n=20' - -def test_wrong_padding(): - s = 'CSixpLDtKSC/7Liuvsax4iC6uLmwMcijIKHaILzSwd/H0SC8+LCjwLsgv7W/+Mj3IQ' - py.test.raises(binascii.Error, binascii.a2b_base64, s) - -def test_crap_after_padding(): - s = 'xxx=axxxx' - assert binascii.a2b_base64(s) == '\xc7\x1c' - -def test_wrong_args(): - # this should grow as a way longer list - py.test.raises(TypeError, binascii.a2b_base64, 42) diff --git a/lib_pypy/pypy_test/test_locale.py b/lib_pypy/pypy_test/test_locale.py deleted file mode 100644 --- a/lib_pypy/pypy_test/test_locale.py +++ /dev/null @@ -1,79 +0,0 @@ -from __future__ import absolute_import -import py -import sys - -from lib_pypy.ctypes_config_cache import rebuild -rebuild.rebuild_one('locale.ctc.py') - -from lib_pypy import _locale - - -def setup_module(mod): - if sys.platform == 'darwin': - py.test.skip("Locale support on MacOSX is minimal and cannot be tested") - -class TestLocale: - def setup_class(cls): - cls.oldlocale = _locale.setlocale(_locale.LC_NUMERIC) - if sys.platform.startswith("win"): - cls.tloc = "en" - elif sys.platform.startswith("freebsd"): - cls.tloc = "en_US.US-ASCII" - else: - cls.tloc = "en_US.UTF8" - try: - _locale.setlocale(_locale.LC_NUMERIC, cls.tloc) - except _locale.Error: - py.test.skip("test locale %s not supported" % cls.tloc) - - def teardown_class(cls): - _locale.setlocale(_locale.LC_NUMERIC, cls.oldlocale) - - def test_format(self): - py.test.skip("XXX fix or kill me") - - def testformat(formatstr, value, grouping = 0, output=None): - if output: - print "%s %% %s =? %s ..." %\ - (repr(formatstr), repr(value), repr(output)), - else: - print "%s %% %s works? ..." % (repr(formatstr), repr(value)), - result = locale.format(formatstr, value, grouping = grouping) - assert result == output - - testformat("%f", 1024, grouping=1, output='1,024.000000') - testformat("%f", 102, grouping=1, output='102.000000') - testformat("%f", -42, grouping=1, output='-42.000000') - testformat("%+f", -42, grouping=1, output='-42.000000') - testformat("%20.f", -42, grouping=1, output=' -42') - testformat("%+10.f", -4200, grouping=1, output=' -4,200') - testformat("%-10.f", 4200, grouping=1, output='4,200 ') - - def test_getpreferredencoding(self): - py.test.skip("XXX fix or kill me") - # Invoke getpreferredencoding to make sure it does not cause exceptions - _locale.getpreferredencoding() - - # Test BSD Rune locale's bug for isctype functions. - def test_bsd_bug(self): - def teststrop(s, method, output): - print "%s.%s() =? %s ..." % (repr(s), method, repr(output)), - result = getattr(s, method)() - assert result == output - - oldlocale = _locale.setlocale(_locale.LC_CTYPE) - _locale.setlocale(_locale.LC_CTYPE, self.tloc) - try: - teststrop('\x20', 'isspace', True) - teststrop('\xa0', 'isspace', False) - teststrop('\xa1', 'isspace', False) - teststrop('\xc0', 'isalpha', False) - teststrop('\xc0', 'isalnum', False) - teststrop('\xc0', 'isupper', False) - teststrop('\xc0', 'islower', False) - teststrop('\xec\xa0\xbc', 'split', ['\xec\xa0\xbc']) - teststrop('\xed\x95\xa0', 'strip', '\xed\x95\xa0') - teststrop('\xcc\x85', 'lower', '\xcc\x85') - teststrop('\xed\x95\xa0', 'upper', '\xed\x95\xa0') - finally: - _locale.setlocale(_locale.LC_CTYPE, oldlocale) diff --git a/lib_pypy/pypy_test/test_site_extra.py b/lib_pypy/pypy_test/test_site_extra.py new file mode 100644 --- /dev/null +++ b/lib_pypy/pypy_test/test_site_extra.py @@ -0,0 +1,13 @@ +import sys, os + + +def test_preimported_modules(): + lst = ['__builtin__', '_codecs', '_warnings', 'codecs', 'encodings', + 'exceptions', 'signal', 'sys', 'zipimport'] + g = os.popen("'%s' -c 'import sys; print sorted(sys.modules)'" % + (sys.executable,)) + real_data = g.read() + g.close() + for name in lst: + quoted_name = repr(name) + assert quoted_name in real_data diff --git a/lib_pypy/pypy_test/test_struct_extra.py b/lib_pypy/pypy_test/test_struct_extra.py deleted file mode 100644 --- a/lib_pypy/pypy_test/test_struct_extra.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import absolute_import -from lib_pypy import struct - -def test_simple(): - morezeros = '\x00' * (struct.calcsize('l')-4) - assert struct.pack(': big-endian, std. size & alignment - !: same as > - -The remaining chars indicate types of args and must match exactly; -these can be preceded by a decimal repeat count: - x: pad byte (no data); - c:char; - b:signed byte; - B:unsigned byte; - h:short; - H:unsigned short; - i:int; - I:unsigned int; - l:long; - L:unsigned long; - f:float; - d:double. -Special cases (preceding decimal count indicates length): - s:string (array of char); p: pascal string (with count byte). -Special case (only available in native format): - P:an integer type that is wide enough to hold a pointer. -Special case (not in native mode unless 'long long' in platform C): - q:long long; - Q:unsigned long long -Whitespace between formats is ignored. - -The variable struct.error is an exception raised on errors.""" - -import math, sys - -# TODO: XXX Find a way to get information on native sizes and alignments -class StructError(Exception): - pass -error = StructError -def unpack_int(data,index,size,le): - bytes = [ord(b) for b in data[index:index+size]] - if le == 'little': - bytes.reverse() - number = 0L - for b in bytes: - number = number << 8 | b - return int(number) - -def unpack_signed_int(data,index,size,le): - number = unpack_int(data,index,size,le) - max = 2**(size*8) - if number > 2**(size*8 - 1) - 1: - number = int(-1*(max - number)) - return number - -INFINITY = 1e200 * 1e200 -NAN = INFINITY / INFINITY - -def unpack_char(data,index,size,le): - return data[index:index+size] - -def pack_int(number,size,le): - x=number - res=[] - for i in range(size): - res.append(chr(x&0xff)) - x >>= 8 - if le == 'big': - res.reverse() - return ''.join(res) - -def pack_signed_int(number,size,le): - if not isinstance(number, (int,long)): - raise StructError,"argument for i,I,l,L,q,Q,h,H must be integer" - if number > 2**(8*size-1)-1 or number < -1*2**(8*size-1): - raise OverflowError,"Number:%i too large to convert" % number - return pack_int(number,size,le) - -def pack_unsigned_int(number,size,le): - if not isinstance(number, (int,long)): - raise StructError,"argument for i,I,l,L,q,Q,h,H must be integer" - if number < 0: - raise TypeError,"can't convert negative long to unsigned" - if number > 2**(8*size)-1: - raise OverflowError,"Number:%i too large to convert" % number - return pack_int(number,size,le) - -def pack_char(char,size,le): - return str(char) - -def isinf(x): - return x != 0.0 and x / 2 == x -def isnan(v): - return v != v*1.0 or (v == 1.0 and v == 2.0) - -def pack_float(x, size, le): - unsigned = float_pack(x, size) - result = [] - for i in range(8): - result.append(chr((unsigned >> (i * 8)) & 0xFF)) - if le == "big": - result.reverse() - return ''.join(result) - -def unpack_float(data, index, size, le): - binary = [data[i] for i in range(index, index + 8)] - if le == "big": - binary.reverse() - unsigned = 0 - for i in range(8): - unsigned |= ord(binary[i]) << (i * 8) - return float_unpack(unsigned, size, le) - -def round_to_nearest(x): - """Python 3 style round: round a float x to the nearest int, but - unlike the builtin Python 2.x round function: - - - return an int, not a float - - do round-half-to-even, not round-half-away-from-zero. - - We assume that x is finite and nonnegative; except wrong results - if you use this for negative x. - - """ - int_part = int(x) - frac_part = x - int_part - if frac_part > 0.5 or frac_part == 0.5 and int_part & 1 == 1: - int_part += 1 - return int_part - -def float_unpack(Q, size, le): - """Convert a 32-bit or 64-bit integer created - by float_pack into a Python float.""" - - if size == 8: - MIN_EXP = -1021 # = sys.float_info.min_exp - MAX_EXP = 1024 # = sys.float_info.max_exp - MANT_DIG = 53 # = sys.float_info.mant_dig - BITS = 64 - elif size == 4: - MIN_EXP = -125 # C's FLT_MIN_EXP - MAX_EXP = 128 # FLT_MAX_EXP - MANT_DIG = 24 # FLT_MANT_DIG - BITS = 32 - else: - raise ValueError("invalid size value") - - if Q >> BITS: - raise ValueError("input out of range") - - # extract pieces - sign = Q >> BITS - 1 - exp = (Q & ((1 << BITS - 1) - (1 << MANT_DIG - 1))) >> MANT_DIG - 1 - mant = Q & ((1 << MANT_DIG - 1) - 1) - - if exp == MAX_EXP - MIN_EXP + 2: - # nan or infinity - result = float('nan') if mant else float('inf') - elif exp == 0: - # subnormal or zero - result = math.ldexp(float(mant), MIN_EXP - MANT_DIG) - else: - # normal - mant += 1 << MANT_DIG - 1 - result = math.ldexp(float(mant), exp + MIN_EXP - MANT_DIG - 1) - return -result if sign else result - - -def float_pack(x, size): - """Convert a Python float x into a 64-bit unsigned integer - with the same byte representation.""" - - if size == 8: - MIN_EXP = -1021 # = sys.float_info.min_exp - MAX_EXP = 1024 # = sys.float_info.max_exp - MANT_DIG = 53 # = sys.float_info.mant_dig - BITS = 64 - elif size == 4: - MIN_EXP = -125 # C's FLT_MIN_EXP - MAX_EXP = 128 # FLT_MAX_EXP - MANT_DIG = 24 # FLT_MANT_DIG - BITS = 32 - else: - raise ValueError("invalid size value") - - sign = math.copysign(1.0, x) < 0.0 - if math.isinf(x): - mant = 0 - exp = MAX_EXP - MIN_EXP + 2 - elif math.isnan(x): - mant = 1 << (MANT_DIG-2) # other values possible - exp = MAX_EXP - MIN_EXP + 2 - elif x == 0.0: - mant = 0 - exp = 0 - else: - m, e = math.frexp(abs(x)) # abs(x) == m * 2**e - exp = e - (MIN_EXP - 1) - if exp > 0: - # Normal case. - mant = round_to_nearest(m * (1 << MANT_DIG)) - mant -= 1 << MANT_DIG - 1 - else: - # Subnormal case. - if exp + MANT_DIG - 1 >= 0: - mant = round_to_nearest(m * (1 << exp + MANT_DIG - 1)) - else: - mant = 0 - exp = 0 - - # Special case: rounding produced a MANT_DIG-bit mantissa. - assert 0 <= mant <= 1 << MANT_DIG - 1 - if mant == 1 << MANT_DIG - 1: - mant = 0 - exp += 1 - - # Raise on overflow (in some circumstances, may want to return - # infinity instead). - if exp >= MAX_EXP - MIN_EXP + 2: - raise OverflowError("float too large to pack in this format") - - # check constraints - assert 0 <= mant < 1 << MANT_DIG - 1 - assert 0 <= exp <= MAX_EXP - MIN_EXP + 2 - assert 0 <= sign <= 1 - return ((sign << BITS - 1) | (exp << MANT_DIG - 1)) | mant - - -big_endian_format = { - 'x':{ 'size' : 1, 'alignment' : 0, 'pack' : None, 'unpack' : None}, - 'b':{ 'size' : 1, 'alignment' : 0, 'pack' : pack_signed_int, 'unpack' : unpack_signed_int}, - 'B':{ 'size' : 1, 'alignment' : 0, 'pack' : pack_unsigned_int, 'unpack' : unpack_int}, - 'c':{ 'size' : 1, 'alignment' : 0, 'pack' : pack_char, 'unpack' : unpack_char}, - 's':{ 'size' : 1, 'alignment' : 0, 'pack' : None, 'unpack' : None}, - 'p':{ 'size' : 1, 'alignment' : 0, 'pack' : None, 'unpack' : None}, - 'h':{ 'size' : 2, 'alignment' : 0, 'pack' : pack_signed_int, 'unpack' : unpack_signed_int}, - 'H':{ 'size' : 2, 'alignment' : 0, 'pack' : pack_unsigned_int, 'unpack' : unpack_int}, - 'i':{ 'size' : 4, 'alignment' : 0, 'pack' : pack_signed_int, 'unpack' : unpack_signed_int}, - 'I':{ 'size' : 4, 'alignment' : 0, 'pack' : pack_unsigned_int, 'unpack' : unpack_int}, - 'l':{ 'size' : 4, 'alignment' : 0, 'pack' : pack_signed_int, 'unpack' : unpack_signed_int}, - 'L':{ 'size' : 4, 'alignment' : 0, 'pack' : pack_unsigned_int, 'unpack' : unpack_int}, - 'q':{ 'size' : 8, 'alignment' : 0, 'pack' : pack_signed_int, 'unpack' : unpack_signed_int}, - 'Q':{ 'size' : 8, 'alignment' : 0, 'pack' : pack_unsigned_int, 'unpack' : unpack_int}, - 'f':{ 'size' : 4, 'alignment' : 0, 'pack' : pack_float, 'unpack' : unpack_float}, - 'd':{ 'size' : 8, 'alignment' : 0, 'pack' : pack_float, 'unpack' : unpack_float}, - } -default = big_endian_format -formatmode={ '<' : (default, 'little'), - '>' : (default, 'big'), - '!' : (default, 'big'), - '=' : (default, sys.byteorder), - '@' : (default, sys.byteorder) - } - -def getmode(fmt): - try: - formatdef,endianness = formatmode[fmt[0]] - index = 1 - except KeyError: - formatdef,endianness = formatmode['@'] - index = 0 - return formatdef,endianness,index -def getNum(fmt,i): - num=None - cur = fmt[i] - while ('0'<= cur ) and ( cur <= '9'): - if num == None: - num = int(cur) - else: - num = 10*num + int(cur) - i += 1 - cur = fmt[i] - return num,i - -def calcsize(fmt): - """calcsize(fmt) -> int - Return size of C struct described by format string fmt. - See struct.__doc__ for more on format strings.""" - - formatdef,endianness,i = getmode(fmt) - num = 0 - result = 0 - while i string - Return string containing values v1, v2, ... packed according to fmt. - See struct.__doc__ for more on format strings.""" - formatdef,endianness,i = getmode(fmt) - args = list(args) - n_args = len(args) - result = [] - while i 0: - result += [chr(len(args[0])) + args[0][:num-1] + '\0'*padding] - else: - if num<255: - result += [chr(num-1) + args[0][:num-1]] - else: - result += [chr(255) + args[0][:num-1]] - args.pop(0) - else: - raise StructError,"arg for string format not a string" - - else: - if len(args) < num: - raise StructError,"insufficient arguments to pack" - for var in args[:num]: - result += [format['pack'](var,format['size'],endianness)] - args=args[num:] - num = None - i += 1 - if len(args) != 0: - raise StructError,"too many arguments for pack format" - return ''.join(result) - -def unpack(fmt,data): - """unpack(fmt, string) -> (v1, v2, ...) - Unpack the string, containing packed C structure data, according - to fmt. Requires len(string)==calcsize(fmt). - See struct.__doc__ for more on format strings.""" - formatdef,endianness,i = getmode(fmt) - j = 0 - num = 0 - result = [] - length= calcsize(fmt) - if length != len (data): - raise StructError,"unpack str size does not match format" - while i= num: - n = num-1 - result.append(data[j+1:j+n+1]) - j += num - else: - for n in range(num): - result += [format['unpack'](data,j,format['size'],endianness)] - j += format['size'] - - return tuple(result) - -def pack_into(fmt, buf, offset, *args): - data = pack(fmt, *args) - buffer(buf)[offset:offset+len(data)] = data - -def unpack_from(fmt, buf, offset=0): - size = calcsize(fmt) - data = buffer(buf)[offset:offset+size] - if len(data) != size: - raise error("unpack_from requires a buffer of at least %d bytes" - % (size,)) - return unpack(fmt, data) diff --git a/pypy/__init__.py b/pypy/__init__.py --- a/pypy/__init__.py +++ b/pypy/__init__.py @@ -1,1 +1,16 @@ # Empty + +# XXX Should be empty again, soon. +# XXX hack for win64: +# This patch must stay here until the END OF STAGE 1 +# When all tests work, this branch will be merged +# and the branch stage 2 is started, where we remove this patch. +import sys +if hasattr(sys, "maxsize"): + if sys.maxint != sys.maxsize: + sys.maxint = sys.maxsize + import warnings + warnings.warn("""\n +---> This win64 port is now in stage 1: sys.maxint was modified. +---> When pypy/__init__.py becomes empty again, we have reached stage 2. +""") diff --git a/pypy/annotation/builtin.py b/pypy/annotation/builtin.py --- a/pypy/annotation/builtin.py +++ b/pypy/annotation/builtin.py @@ -37,7 +37,11 @@ try: realresult = func(*args) except (ValueError, OverflowError): - return s_ImpossibleValue # no possible answer for this precise input + # no possible answer for this precise input. Be conservative + # and keep the computation non-constant. Example: + # unichr(constant-that-doesn't-fit-16-bits) on platforms where + # the underlying Python has sys.maxunicode == 0xffff. + return s_result s_realresult = immutablevalue(realresult) if not s_result.contains(s_realresult): raise Exception("%s%r returned %r, which is not contained in %s" % ( @@ -163,7 +167,7 @@ r.const = False return r - assert not issubclass(typ, (int,long)) or typ in (bool, int), ( + assert not issubclass(typ, (int, long)) or typ in (bool, int, long), ( "for integers only isinstance(.,int|r_uint) are supported") if s_obj.is_constant(): @@ -297,7 +301,7 @@ def robjmodel_instantiate(s_clspbc): assert isinstance(s_clspbc, SomePBC) clsdef = None - more_than_one = len(s_clspbc.descriptions) + more_than_one = len(s_clspbc.descriptions) > 1 for desc in s_clspbc.descriptions: cdef = desc.getuniqueclassdef() if more_than_one: diff --git a/pypy/annotation/classdef.py b/pypy/annotation/classdef.py --- a/pypy/annotation/classdef.py +++ b/pypy/annotation/classdef.py @@ -134,13 +134,19 @@ if self.name not in homedef.classdesc.all_enforced_attrs: self.attr_allowed = False if not self.readonly: - raise NoSuchAttrError(homedef, self.name) + raise NoSuchAttrError( + "setting forbidden attribute %r on %r" % ( + self.name, homedef)) def modified(self, classdef='?'): self.readonly = False if not self.attr_allowed: - raise NoSuchAttrError(classdef, self.name) - + raise NoSuchAttrError( + "Attribute %r on %r should be read-only.\n" % (self.name, + classdef) + + "This error can be caused by another 'getattr' that promoted\n" + "the attribute here; the list of read locations is:\n" + + '\n'.join([str(loc[0]) for loc in self.read_locations])) class ClassDef(object): "Wraps a user class." diff --git a/pypy/annotation/description.py b/pypy/annotation/description.py --- a/pypy/annotation/description.py +++ b/pypy/annotation/description.py @@ -398,7 +398,6 @@ cls = pyobj base = object baselist = list(cls.__bases__) - baselist.reverse() # special case: skip BaseException in Python 2.5, and pretend # that all exceptions ultimately inherit from Exception instead @@ -408,17 +407,27 @@ elif baselist == [py.builtin.BaseException]: baselist = [Exception] + mixins_before = [] + mixins_after = [] for b1 in baselist: if b1 is object: continue if b1.__dict__.get('_mixin_', False): - self.add_mixin(b1) + if base is object: + mixins_before.append(b1) + else: + mixins_after.append(b1) else: assert base is object, ("multiple inheritance only supported " "with _mixin_: %r" % (cls,)) base = b1 + if mixins_before and mixins_after: + raise Exception("unsupported: class %r has mixin bases both" + " before and after the regular base" % (self,)) + self.add_mixins(mixins_after, check_not_in=base) + self.add_mixins(mixins_before) + self.add_sources_for_class(cls) - self.add_sources_for_class(cls) if base is not object: self.basedesc = bookkeeper.getdesc(base) @@ -480,14 +489,30 @@ return self.classdict[name] = Constant(value) - def add_mixin(self, base): - for subbase in base.__bases__: - if subbase is object: - continue - assert subbase.__dict__.get("_mixin_", False), ("Mixin class %r has non" - "mixin base class %r" % (base, subbase)) - self.add_mixin(subbase) - self.add_sources_for_class(base, mixin=True) + def add_mixins(self, mixins, check_not_in=object): + if not mixins: + return + A = type('tmp', tuple(mixins) + (object,), {}) + mro = A.__mro__ + assert mro[0] is A and mro[-1] is object + mro = mro[1:-1] + # + skip = set() + def add(cls): + if cls is not object: + for base in cls.__bases__: + add(base) + for name in cls.__dict__: + skip.add(name) + add(check_not_in) + # + for base in reversed(mro): + assert base.__dict__.get("_mixin_", False), ("Mixin class %r has non" + "mixin base class %r" % (mixins, base)) + for name, value in base.__dict__.items(): + if name in skip: + continue + self.add_source_attribute(name, value, mixin=True) def add_sources_for_class(self, cls, mixin=False): for name, value in cls.__dict__.items(): diff --git a/pypy/annotation/model.py b/pypy/annotation/model.py --- a/pypy/annotation/model.py +++ b/pypy/annotation/model.py @@ -786,12 +786,15 @@ # # safety check that no-one is trying to make annotation and translation # faster by providing the -O option to Python. -try: - assert False -except AssertionError: - pass # fine -else: - raise RuntimeError("The annotator relies on 'assert' statements from the\n" +import os +if "WINGDB_PYTHON" not in os.environ: + # ...but avoiding this boring check in the IDE + try: + assert False + except AssertionError: + pass # fine + else: + raise RuntimeError("The annotator relies on 'assert' statements from the\n" "\tannotated program: you cannot run it with 'python -O'.") # this has the side-effect of registering the unary and binary operations diff --git a/pypy/annotation/test/test_annrpython.py b/pypy/annotation/test/test_annrpython.py --- a/pypy/annotation/test/test_annrpython.py +++ b/pypy/annotation/test/test_annrpython.py @@ -1,15 +1,12 @@ from __future__ import with_statement -import autopath import py.test import sys from pypy import conftest -from pypy.tool.udir import udir from pypy.annotation import model as annmodel from pypy.annotation.annrpython import RPythonAnnotator as _RPythonAnnotator from pypy.translator.translator import graphof as tgraphof from pypy.annotation import policy -from pypy.annotation import specialize from pypy.annotation.listdef import ListDef, ListChangeUnallowed from pypy.annotation.dictdef import DictDef from pypy.objspace.flow.model import * @@ -2431,6 +2428,93 @@ assert isinstance(s.items[1], annmodel.SomeChar) assert isinstance(s.items[2], annmodel.SomeChar) + def test_mixin_first(self): + class Mixin(object): + _mixin_ = True + def foo(self): return 4 + class Base(object): + def foo(self): return 5 + class Concrete(Mixin, Base): + pass + def f(): + return Concrete().foo() + + assert f() == 4 + a = self.RPythonAnnotator() + s = a.build_types(f, []) + assert s.const == 4 + + def test_mixin_last(self): + class Mixin(object): + _mixin_ = True + def foo(self): return 4 + class Base(object): + def foo(self): return 5 + class Concrete(Base, Mixin): + pass + def f(): + return Concrete().foo() + + assert f() == 5 + a = self.RPythonAnnotator() + s = a.build_types(f, []) + assert s.const == 5 + + def test_mixin_concrete(self): + class Mixin(object): + _mixin_ = True + def foo(self): return 4 + class Concrete(Mixin): + def foo(self): return 5 + def f(): + return Concrete().foo() + + assert f() == 5 + a = self.RPythonAnnotator() + s = a.build_types(f, []) + assert s.const == 5 + + def test_multiple_mixins_mro(self): + # an obscure situation, but it occurred in module/micronumpy/types.py + class A(object): + _mixin_ = True + def foo(self): return 1 + class B(A): + _mixin_ = True + def foo(self): return 2 + class C(A): + _mixin_ = True + class D(B, C): + _mixin_ = True + class Concrete(D): + pass + def f(): + return Concrete().foo() + + assert f() == 2 + a = self.RPythonAnnotator() + s = a.build_types(f, []) + assert s.const == 2 + + def test_multiple_mixins_mro_2(self): + class A(object): + _mixin_ = True + def foo(self): return 1 + class B(A): + _mixin_ = True + def foo(self): return 2 + class C(A): + _mixin_ = True + class Concrete(C, B): + pass + def f(): + return Concrete().foo() + + assert f() == 2 + a = self.RPythonAnnotator() + s = a.build_types(f, []) + assert s.const == 2 + def test___class___attribute(self): class Base(object): pass class A(Base): pass @@ -2469,6 +2553,26 @@ s = a.build_types(f, [int]) assert s.knowntype == int + def test_slots_reads(self): + class A(object): + __slots__ = () + class B(A): + def __init__(self, x): + self.x = x + def f(x): + if x: + a = A() + else: + a = B(x) + return a.x # should explode here + + a = self.RPythonAnnotator() + e = py.test.raises(Exception, a.build_types, f, [int]) + # this should explode on reading the attribute 'a.x', but it can + # sometimes explode on 'self.x = x', which does not make much sense. + # But it looks hard to fix in general: we don't know yet during 'a.x' + # if the attribute x will be read-only or read-write. + def test_unboxed_value(self): class A(object): __slots__ = () diff --git a/pypy/bin/rpython b/pypy/bin/rpython new file mode 100755 --- /dev/null +++ b/pypy/bin/rpython @@ -0,0 +1,18 @@ +#!/usr/bin/env pypy + +"""RPython translation usage: + +rpython target + +run with --help for more information +""" + +import sys +from pypy.translator.goal.translate import main + +# no implicit targets +if len(sys.argv) == 1: + print __doc__ + sys.exit(1) + +main() diff --git a/pypy/config/pypyoption.py b/pypy/config/pypyoption.py --- a/pypy/config/pypyoption.py +++ b/pypy/config/pypyoption.py @@ -176,9 +176,6 @@ cmdline="--translationmodules", suggests=[("objspace.allworkingmodules", False)]), - BoolOption("geninterp", "specify whether geninterp should be used", - default=False), - BoolOption("logbytecodes", "keep track of bytecode usage", default=False), @@ -392,10 +389,6 @@ config.objspace.std.suggest(withsmalllong=True) # xxx other options? ropes maybe? - # completely disable geninterp in a level 0 translation - if level == '0': - config.objspace.suggest(geninterp=False) - # some optimizations have different effects depending on the typesystem if type_system == 'ootype': config.objspace.std.suggest(multimethods="doubledispatch") diff --git a/pypy/config/translationoption.py b/pypy/config/translationoption.py --- a/pypy/config/translationoption.py +++ b/pypy/config/translationoption.py @@ -182,11 +182,6 @@ # Flags of the TranslationContext: BoolOption("simplifying", "Simplify flow graphs", default=True), - BoolOption("builtins_can_raise_exceptions", - "When true, assume any call to a 'simple' builtin such as " - "'hex' can raise an arbitrary exception", - default=False, - cmdline=None), BoolOption("list_comprehension_operations", "When true, look for and special-case the sequence of " "operations that results from a list comprehension and " diff --git a/pypy/doc/discussion/win64_todo.txt b/pypy/doc/discussion/win64_todo.txt new file mode 100644 --- /dev/null +++ b/pypy/doc/discussion/win64_todo.txt @@ -0,0 +1,9 @@ +2011-11-04 +ll_os.py has a problem with the file rwin32.py. +Temporarily disabled for the win64_gborg branch. This needs to be +investigated and re-enabled. +Resolved, enabled. + +2011-11-05 +test_typed.py needs explicit tests to ensure that we +handle word sizes right. \ No newline at end of file diff --git a/pypy/doc/project-ideas.rst b/pypy/doc/project-ideas.rst --- a/pypy/doc/project-ideas.rst +++ b/pypy/doc/project-ideas.rst @@ -103,21 +103,13 @@ * A concurrent garbage collector (a lot of work) -Remove the GIL --------------- +STM, a.k.a. "remove the GIL" +---------------------------- -This is a major task that requires lots of thinking. However, few subprojects -can be potentially specified, unless a better plan can be thought out: +Removing the GIL --- or more precisely, a GIL-less thread-less solution --- +is `now work in progress.`__ Contributions welcome. -* A thread-aware garbage collector - -* Better RPython primitives for dealing with concurrency - -* JIT passes to remove locks on objects - -* (maybe) implement locking in Python interpreter - -* alternatively, look at Software Transactional Memory +.. __: http://pypy.org/tmdonate.html Introduce new benchmarks ------------------------ @@ -157,6 +149,22 @@ exported. This would give us a one-size-fits-all generic .so file to be imported by any application that wants to load .so files :-) +Optimising cpyext (CPython C-API compatibility layer) +----------------------------------------------------- + +A lot of work has gone into PyPy's implementation of CPython's C-API over +the last years to let it reach a practical level of compatibility, so that +C extensions for CPython work on PyPy without major rewrites. However, +there are still many edges and corner cases where it misbehaves, and it has +not received any substantial optimisation so far. + +The objective of this project is to fix bugs in cpyext and to optimise +several performance critical parts of it, such as the reference counting +support and other heavily used C-API functions. The net result would be to +have CPython extensions run much faster on PyPy than they currently do, or +to make them work at all if they currently don't. A part of this work would +be to get cpyext into a shape where it supports running Cython generated +extensions. .. _`issue tracker`: http://bugs.pypy.org .. _`mailing list`: http://mail.python.org/mailman/listinfo/pypy-dev diff --git a/pypy/doc/sandbox.rst b/pypy/doc/sandbox.rst --- a/pypy/doc/sandbox.rst +++ b/pypy/doc/sandbox.rst @@ -82,7 +82,10 @@ In pypy/translator/goal:: - ./translate.py --sandbox targetpypystandalone.py + ./translate.py -O2 --sandbox targetpypystandalone.py + +If you don't have a regular PyPy installed, you should, because it's +faster to translate, but you can also run ``python translate.py`` instead. To run it, use the tools in the pypy/translator/sandbox directory:: diff --git a/pypy/doc/stackless.rst b/pypy/doc/stackless.rst --- a/pypy/doc/stackless.rst +++ b/pypy/doc/stackless.rst @@ -199,17 +199,11 @@ The following features (present in some past Stackless version of PyPy) are for the time being not supported any more: -* Tasklets and channels (currently ``stackless.py`` seems to import, - but you have tasklets on top of coroutines on top of greenlets on - top of continulets on top of stacklets, and it's probably not too - hard to cut two of these levels by adapting ``stackless.py`` to - use directly continulets) - * Coroutines (could be rewritten at app-level) -* Pickling and unpickling continulets (*) - -* Continuing execution of a continulet in a different thread (*) +* Continuing execution of a continulet in a different thread + (but if it is "simple enough", you can pickle it and unpickle it + in the other thread). * Automatic unlimited stack (must be emulated__ so far) @@ -217,15 +211,6 @@ .. __: `recursion depth limit`_ -(*) Pickling, as well as changing threads, could be implemented by using -a "soft" stack switching mode again. We would get either "hard" or -"soft" switches, similarly to Stackless Python 3rd version: you get a -"hard" switch (like now) when the C stack contains non-trivial C frames -to save, and a "soft" switch (like previously) when it contains only -simple calls from Python to Python. Soft-switched continulets would -also consume a bit less RAM, and the switch might be a bit faster too -(unsure about that; what is the Stackless Python experience?). - Recursion depth limit +++++++++++++++++++++ diff --git a/pypy/doc/tool/makecontributor.py b/pypy/doc/tool/makecontributor.py new file mode 100644 --- /dev/null +++ b/pypy/doc/tool/makecontributor.py @@ -0,0 +1,133 @@ +import py +import sys +from collections import defaultdict +import operator +import re +import mercurial.localrepo +import mercurial.ui + +ROOT = py.path.local(__file__).join('..', '..', '..', '..') +author_re = re.compile('(.*) <.*>') +pair_programming_re = re.compile(r'^\((.*?)\)') +excluded = set(["pypy", "convert-repo"]) + +alias = { + 'Anders Chrigstrom': ['arre'], + 'Antonio Cuni': ['antocuni', 'anto'], + 'Armin Rigo': ['arigo', 'arfigo', 'armin', 'arigato'], + 'Maciej Fijalkowski': ['fijal'], + 'Carl Friedrich Bolz': ['cfbolz', 'cf'], + 'Samuele Pedroni': ['pedronis', 'samuele', 'samule'], + 'Michael Hudson': ['mwh'], + 'Holger Krekel': ['hpk', 'holger krekel', 'holger', 'hufpk'], + "Amaury Forgeot d'Arc": ['afa'], + 'Alex Gaynor': ['alex', 'agaynor'], + 'David Schneider': ['bivab', 'david'], + 'Christian Tismer': ['chris', 'christian', 'tismer', + 'tismer at christia-wjtqxl.localdomain'], + 'Benjamin Peterson': ['benjamin'], + 'Hakan Ardo': ['hakan', 'hakanardo'], + 'Niklaus Haldimann': ['nik'], + 'Alexander Schremmer': ['xoraxax'], + 'Anders Hammarquist': ['iko'], + 'David Edelsohn': ['edelsoh', 'edelsohn'], + 'Niko Matsakis': ['niko'], + 'Jakub Gustak': ['jlg'], + 'Guido Wesdorp': ['guido'], + 'Michael Foord': ['mfoord'], + 'Mark Pearse': ['mwp'], + 'Toon Verwaest': ['tverwaes'], + 'Eric van Riet Paap': ['ericvrp'], + 'Jacob Hallen': ['jacob', 'jakob'], + 'Anders Lehmann': ['ale', 'anders'], + 'Bert Freudenberg': ['bert'], + 'Boris Feigin': ['boris', 'boria'], + 'Valentino Volonghi': ['valentino', 'dialtone'], + 'Aurelien Campeas': ['aurelien', 'aureliene'], + 'Adrien Di Mascio': ['adim'], + 'Jacek Generowicz': ['Jacek', 'jacek'], + 'Jim Hunziker': ['landtuna at gmail.com'], + 'Kristjan Valur Jonsson': ['kristjan at kristjan-lp.ccp.ad.local'], + 'Laura Creighton': ['lac'], + 'Aaron Iles': ['aliles'], + 'Ludovic Aubry': ['ludal', 'ludovic'], + 'Lukas Diekmann': ['l.diekmann', 'ldiekmann'], + 'Matti Picus': ['Matti Picus matti.picus at gmail.com', + 'matthp', 'mattip', 'mattip>'], + 'Michael Cheng': ['mikefc'], + 'Richard Emslie': ['rxe'], + 'Roberto De Ioris': ['roberto at goyle'], + 'Roberto De Ioris': ['roberto at mrspurr'], + 'Sven Hager': ['hager'], + 'Tomo Cocoa': ['cocoatomo'], + } + +alias_map = {} +for name, nicks in alias.iteritems(): + for nick in nicks: + alias_map[nick] = name + +def get_canonical_author(name): + match = author_re.match(name) + if match: + name = match.group(1) + return alias_map.get(name, name) + +ignored_nicknames = defaultdict(int) + +def get_more_authors(log): + match = pair_programming_re.match(log) + if not match: + return set() + ignore_words = ['around', 'consulting', 'yesterday', 'for a bit', 'thanks', + 'in-progress', 'bits of', 'even a little', 'floating',] + sep_words = ['and', ';', '+', '/', 'with special by'] + nicknames = match.group(1) + for word in ignore_words: + nicknames = nicknames.replace(word, '') + for word in sep_words: + nicknames = nicknames.replace(word, ',') + nicknames = [nick.strip().lower() for nick in nicknames.split(',')] + authors = set() + for nickname in nicknames: + author = alias_map.get(nickname) + if not author: + ignored_nicknames[nickname] += 1 + else: + authors.add(author) + return authors + +def main(show_numbers): + ui = mercurial.ui.ui() + repo = mercurial.localrepo.localrepository(ui, str(ROOT)) + authors_count = defaultdict(int) + for i in repo: + ctx = repo[i] + authors = set() + authors.add(get_canonical_author(ctx.user())) + authors.update(get_more_authors(ctx.description())) + for author in authors: + if author not in excluded: + authors_count[author] += 1 + + # uncomment the next lines to get the list of nicknamed which could not be + # parsed from commit logs + ## items = ignored_nicknames.items() + ## items.sort(key=operator.itemgetter(1), reverse=True) + ## for name, n in items: + ## if show_numbers: + ## print '%5d %s' % (n, name) + ## else: + ## print name + + items = authors_count.items() + items.sort(key=operator.itemgetter(1), reverse=True) + for name, n in items: + if show_numbers: + print '%5d %s' % (n, name) + else: + print name + +if __name__ == '__main__': + show_numbers = '-n' in sys.argv + main(show_numbers) diff --git a/pypy/doc/windows.rst b/pypy/doc/windows.rst --- a/pypy/doc/windows.rst +++ b/pypy/doc/windows.rst @@ -18,7 +18,8 @@ Edition. Other configurations may work as well. The translation scripts will set up the appropriate environment variables -for the compiler. They will attempt to locate the same compiler version that +for the compiler, so you do not need to run vcvars before translation. +They will attempt to locate the same compiler version that was used to build the Python interpreter doing the translation. Failing that, they will pick the most recent Visual Studio compiler they can find. In addition, the target architecture @@ -26,7 +27,7 @@ using a 32 bit Python and vice versa. **Note:** PyPy is currently not supported for 64 bit Windows, and translation -will be aborted in this case. +will fail in this case. The compiler is all you need to build pypy-c, but it will miss some modules that relies on third-party libraries. See below how to get @@ -57,7 +58,8 @@ install third-party libraries. We chose to install them in the parent directory of the pypy checkout. For example, if you installed pypy in ``d:\pypy\trunk\`` (This directory contains a README file), the base -directory is ``d:\pypy``. +directory is ``d:\pypy``. You may choose different values by setting the +INCLUDE, LIB and PATH (for DLLs) The Boehm garbage collector ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -126,18 +128,54 @@ ------------------------ You can compile pypy with the mingw compiler, using the --cc=mingw32 option; -mingw.exe must be on the PATH. +gcc.exe must be on the PATH. If the -cc flag does not begin with "ming", it should be +the name of a valid gcc-derivative compiler, i.e. x86_64-w64-mingw32-gcc for the 64 bit +compiler creating a 64 bit target. -libffi for the mingw32 compiler +libffi for the mingw compiler ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To enable the _rawffi (and ctypes) module, you need to compile a mingw32 -version of libffi. I downloaded the `libffi source files`_, and extracted -them in the base directory. Then run:: +To enable the _rawffi (and ctypes) module, you need to compile a mingw +version of libffi. Here is one way to do this, wich should allow you to try +to build for win64 or win32: + +#. Download and unzip a `mingw32 build`_ or `mingw64 build`_, say into c:\mingw +#. If you do not use cygwin, you will need msys to provide make, + autoconf tools and other goodies. + + #. Download and unzip a `msys for mingw`_, say into c:\msys + #. Edit the c:\msys\etc\fstab file to mount c:\mingw + +#. Download and unzip the `libffi source files`_, and extract + them in the base directory. +#. Run c:\msys\msys.bat or a cygwin shell which should make you + feel better since it is a shell prompt with shell tools. +#. From inside the shell, cd to the libffi directory and do:: sh ./configure make cp .libs/libffi-5.dll +If you can't find the dll, and the libtool issued a warning about +"undefined symbols not allowed", you will need to edit the libffi +Makefile in the toplevel directory. Add the flag -no-undefined to +the definition of libffi_la_LDFLAGS + +If you wish to experiment with win64, you must run configure with flags:: + + sh ./configure --build=x86_64-w64-mingw32 --host=x86_64-w64-mingw32 + +or such, depending on your mingw64 download. + +hacking on Pypy with the mingw compiler +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Since hacking on Pypy means running tests, you will need a way to specify +the mingw compiler when hacking (as opposed to translating). As of +March 2012, --cc is not a valid option for pytest.py. However if you set an +environment variable CC it will allow you to choose a compiler. + +.. _'mingw32 build': http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Automated%20Builds +.. _`mingw64 build`: http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Automated%20Builds +.. _`msys for mingw`: http://sourceforge.net/projects/mingw-w64/files/External%20binary%20packages%20%28Win64%20hosted%29/MSYS%20%2832-bit%29 .. _`libffi source files`: http://sourceware.org/libffi/ .. _`RPython translation toolchain`: translation.html diff --git a/pypy/doc/you-want-to-help.rst b/pypy/doc/you-want-to-help.rst new file mode 100644 --- /dev/null +++ b/pypy/doc/you-want-to-help.rst @@ -0,0 +1,86 @@ + +You want to help with PyPy, now what? +===================================== + +PyPy is a very large project that has a reputation of being hard to dive into. +Some of this fame is warranted, some of it is purely accidental. There are three +important lessons that everyone willing to contribute should learn: + +* PyPy has layers. There are many pieces of architecture that are very well + separated from each other. More about this below, but often the manifestation + of this is that things are at a different layer than you would expect them + to be. For example if you are looking for the JIT implementation, you will + not find it in the implementation of the Python programming language. + +* Because of the above, we are very serious about Test Driven Development. + It's not only what we believe in, but also that PyPy's architecture is + working very well with TDD in mind and not so well without it. Often + the development means progressing in an unrelated corner, one unittest + at a time; and then flipping a giant switch, bringing it all together. + (It generally works out of the box. If it doesn't, then we didn't + write enough unit tests.) It's worth repeating - PyPy + approach is great if you do TDD, not so great otherwise. + +* PyPy uses an entirely different set of tools - most of them included + in the PyPy repository. There is no Makefile, nor autoconf. More below + +Architecture +============ + +PyPy has layers. The 100 miles view: + +* `RPython`_ is the language in which we write interpreters. Not the entire + PyPy project is written in RPython, only the parts that are compiled in + the translation process. The interesting point is that RPython has no parser, + it's compiled from the live python objects, which make it possible to do + all kinds of metaprogramming during import time. In short, Python is a meta + programming language for RPython. + + The RPython standard library is to be found in the ``rlib`` subdirectory. + +.. _`RPython`: coding-guide.html#RPython + +* The translation toolchain - this is the part that takes care about translating + RPython to flow graphs and then to C. There is more in the `architecture`_ + document written about it. + + It mostly lives in ``rpython``, ``annotator`` and ``objspace/flow``. + +.. _`architecture`: architecture.html + +* Python Interpreter + + xxx + +* Python modules + + xxx + +* Just-in-Time Compiler (JIT): `we have a tracing JIT`_ that traces the + interpreter written in RPython, rather than the user program that it + interprets. As a result it applies to any interpreter, i.e. any + language. But getting it to work correctly is not trivial: it + requires a small number of precise "hints" and possibly some small + refactorings of the interpreter. The JIT itself also has several + almost-independent parts: the tracer itself in ``jit/metainterp``, the + optimizer in ``jit/metainterp/optimizer`` that optimizes a list of + residual operations, and the backend in ``jit/backend/`` + that turns it into machine code. Writing a new backend is a + traditional way to get into the project. + +.. _`we have a tracing JIT`: jit/index.html + +* Garbage Collectors (GC): as you can notice if you are used to CPython's + C code, there are no ``Py_INCREF/Py_DECREF`` equivalents in RPython code. + `Garbage collection in PyPy`_ is inserted + during translation. Moreover, this is not reference counting; it is a real + GC written as more RPython code. The best one we have so far is in + ``rpython/memory/gc/minimark.py``. + +.. _`Garbage collection in PyPy`: garbage_collection.html + + +Toolset +======= + +xxx diff --git a/pypy/interpreter/astcompiler/assemble.py b/pypy/interpreter/astcompiler/assemble.py --- a/pypy/interpreter/astcompiler/assemble.py +++ b/pypy/interpreter/astcompiler/assemble.py @@ -610,6 +610,8 @@ ops.JUMP_IF_FALSE_OR_POP : 0, ops.POP_JUMP_IF_TRUE : -1, ops.POP_JUMP_IF_FALSE : -1, + + ops.BUILD_LIST_FROM_ARG: 1, } diff --git a/pypy/interpreter/astcompiler/codegen.py b/pypy/interpreter/astcompiler/codegen.py --- a/pypy/interpreter/astcompiler/codegen.py +++ b/pypy/interpreter/astcompiler/codegen.py @@ -965,7 +965,7 @@ self.emit_op_arg(ops.CALL_METHOD, (kwarg_count << 8) | arg_count) return True - def _listcomp_generator(self, gens, gen_index, elt): + def _listcomp_generator(self, gens, gen_index, elt, single=False): start = self.new_block() skip = self.new_block() if_cleanup = self.new_block() @@ -973,6 +973,8 @@ gen = gens[gen_index] assert isinstance(gen, ast.comprehension) gen.iter.walkabout(self) + if single: + self.emit_op_arg(ops.BUILD_LIST_FROM_ARG, 0) self.emit_op(ops.GET_ITER) self.use_next_block(start) self.emit_jump(ops.FOR_ITER, anchor) @@ -998,8 +1000,12 @@ def visit_ListComp(self, lc): self.update_position(lc.lineno) - self.emit_op_arg(ops.BUILD_LIST, 0) - self._listcomp_generator(lc.generators, 0, lc.elt) + if len(lc.generators) != 1 or lc.generators[0].ifs: + single = False + self.emit_op_arg(ops.BUILD_LIST, 0) + else: + single = True + self._listcomp_generator(lc.generators, 0, lc.elt, single=single) def _comp_generator(self, node, generators, gen_index): start = self.new_block() diff --git a/pypy/interpreter/astcompiler/test/test_astbuilder.py b/pypy/interpreter/astcompiler/test/test_astbuilder.py --- a/pypy/interpreter/astcompiler/test/test_astbuilder.py +++ b/pypy/interpreter/astcompiler/test/test_astbuilder.py @@ -10,16 +10,6 @@ from pypy.interpreter.astcompiler import ast, consts -try: - all -except NameError: - def all(iterable): - for x in iterable: - if not x: - return False - return True - - class TestAstBuilder: def setup_class(cls): diff --git a/pypy/interpreter/astcompiler/test/test_compiler.py b/pypy/interpreter/astcompiler/test/test_compiler.py --- a/pypy/interpreter/astcompiler/test/test_compiler.py +++ b/pypy/interpreter/astcompiler/test/test_compiler.py @@ -58,7 +58,8 @@ w_res = pyco_expr.exec_host_bytecode(w_dict, w_dict) res = space.str_w(space.repr(w_res)) if not isinstance(expected, float): - assert res == repr(expected) + noL = lambda expr: expr.replace('L', '') + assert noL(res) == noL(repr(expected)) else: # Float representation can vary a bit between interpreter # versions, compare the numbers instead. @@ -908,3 +909,17 @@ return d['f'](5) """) assert 'generator' in space.str_w(space.repr(w_generator)) + + def test_list_comprehension(self): + source = "def f(): [i for i in l]" + source2 = "def f(): [i for i in l for j in l]" + source3 = "def f(): [i for i in l if i]" + counts = self.count_instructions(source) + assert ops.BUILD_LIST not in counts + assert counts[ops.BUILD_LIST_FROM_ARG] == 1 + counts = self.count_instructions(source2) + assert counts[ops.BUILD_LIST] == 1 + assert ops.BUILD_LIST_FROM_ARG not in counts + counts = self.count_instructions(source3) + assert counts[ops.BUILD_LIST] == 1 + assert ops.BUILD_LIST_FROM_ARG not in counts diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py --- a/pypy/interpreter/baseobjspace.py +++ b/pypy/interpreter/baseobjspace.py @@ -7,7 +7,8 @@ from pypy.interpreter.miscutils import ThreadLocals from pypy.tool.cache import Cache from pypy.tool.uid import HUGEVAL_BYTES -from pypy.rlib.objectmodel import we_are_translated, newlist, compute_unique_id +from pypy.rlib.objectmodel import we_are_translated, newlist_hint,\ + compute_unique_id from pypy.rlib.debug import make_sure_not_resized from pypy.rlib.timer import DummyTimer, Timer from pypy.rlib.rarithmetic import r_uint @@ -295,6 +296,7 @@ self.check_signal_action = None # changed by the signal module self.user_del_action = UserDelAction(self) self.frame_trace_action = FrameTraceAction(self) + self._code_of_sys_exc_info = None from pypy.interpreter.pycode import cpython_magic, default_magic self.our_magic = default_magic @@ -466,9 +468,9 @@ if name not in modules: modules.append(name) - # a bit of custom logic: time2 or rctime take precedence over time + # a bit of custom logic: rctime take precedence over time # XXX this could probably be done as a "requires" in the config - if ('time2' in modules or 'rctime' in modules) and 'time' in modules: + if 'rctime' in modules and 'time' in modules: modules.remove('time') if not self.config.objspace.nofaking: @@ -833,7 +835,7 @@ items = [] else: try: - items = newlist(lgt_estimate) + items = newlist_hint(lgt_estimate) except MemoryError: items = [] # it might have lied # @@ -1335,7 +1337,7 @@ if not self.is_true(self.isinstance(w_obj, self.w_str)): raise OperationError(self.w_TypeError, self.wrap('argument must be a string')) - return self.str_w(w_obj) + return self.str_w(w_obj) def unicode_w(self, w_obj): return w_obj.unicode_w(self) diff --git a/pypy/interpreter/buffer.py b/pypy/interpreter/buffer.py --- a/pypy/interpreter/buffer.py +++ b/pypy/interpreter/buffer.py @@ -20,6 +20,7 @@ from pypy.interpreter.gateway import interp2app, unwrap_spec from pypy.interpreter.error import OperationError from pypy.rlib.objectmodel import compute_hash +from pypy.rlib.rstring import StringBuilder class Buffer(Wrappable): @@ -152,12 +153,13 @@ if space.isinstance_w(w_object, space.w_unicode): # unicode objects support the old buffer interface # but not the new buffer interface (change in python 2.7) - from pypy.rlib.rstruct.unichar import pack_unichar - charlist = [] - for unich in space.unicode_w(w_object): - pack_unichar(unich, charlist) + from pypy.rlib.rstruct.unichar import pack_unichar, UNICODE_SIZE + unistr = space.unicode_w(w_object) + builder = StringBuilder(len(unistr) * UNICODE_SIZE) + for unich in unistr: + pack_unichar(unich, builder) from pypy.interpreter.buffer import StringBuffer - w_buffer = space.wrap(StringBuffer(''.join(charlist))) + w_buffer = space.wrap(StringBuffer(builder.build())) else: w_buffer = space.buffer(w_object) diff --git a/pypy/interpreter/error.py b/pypy/interpreter/error.py --- a/pypy/interpreter/error.py +++ b/pypy/interpreter/error.py @@ -47,6 +47,11 @@ def async(self, space): "Check if this is an exception that should better not be caught." + if not space.full_exceptions: + # flow objspace does not support such exceptions and more + # importantly, raises KeyboardInterrupt if you try to access + # space.w_KeyboardInterrupt + return False return (self.match(space, space.w_SystemExit) or self.match(space, space.w_KeyboardInterrupt)) diff --git a/pypy/interpreter/executioncontext.py b/pypy/interpreter/executioncontext.py --- a/pypy/interpreter/executioncontext.py +++ b/pypy/interpreter/executioncontext.py @@ -154,6 +154,7 @@ #operationerr.print_detailed_traceback(self.space) def _convert_exc(self, operr): + # Only for the flow object space return operr def sys_exc_info(self): # attn: the result is not the wrapped sys.exc_info() !!! @@ -166,6 +167,11 @@ frame = self.getnextframe_nohidden(frame) return None + def set_sys_exc_info(self, operror): + frame = self.gettopframe_nohidden() + if frame: # else, the exception goes nowhere and is lost + frame.last_exception = operror + def settrace(self, w_func): """Set the global trace function.""" if self.space.is_w(w_func, self.space.w_None): diff --git a/pypy/interpreter/function.py b/pypy/interpreter/function.py --- a/pypy/interpreter/function.py +++ b/pypy/interpreter/function.py @@ -113,6 +113,12 @@ from pypy.interpreter.pycode import PyCode code = self.getcode() # hook for the jit + # + if (jit.we_are_jitted() and code is self.space._code_of_sys_exc_info + and nargs == 0): + from pypy.module.sys.vm import exc_info_direct + return exc_info_direct(self.space, frame) + # fast_natural_arity = code.fast_natural_arity if nargs == fast_natural_arity: if nargs == 0: diff --git a/pypy/interpreter/gateway.py b/pypy/interpreter/gateway.py --- a/pypy/interpreter/gateway.py +++ b/pypy/interpreter/gateway.py @@ -874,6 +874,12 @@ fn.add_to_table() if gateway.as_classmethod: fn = ClassMethod(space.wrap(fn)) + # + from pypy.module.sys.vm import exc_info + if code._bltin is exc_info: + assert space._code_of_sys_exc_info is None + space._code_of_sys_exc_info = code + # return fn @@ -901,24 +907,20 @@ def __init__(self, source, filename=None, modname='__builtin__'): # HAAACK (but a good one) + self.filename = filename + self.source = str(py.code.Source(source).deindent()) + self.modname = modname if filename is None: f = sys._getframe(1) filename = '<%s:%d>' % (f.f_code.co_filename, f.f_lineno) + if not os.path.exists(filename): + # make source code available for tracebacks + lines = [x + "\n" for x in source.split("\n")] + py.std.linecache.cache[filename] = (1, None, lines, filename) self.filename = filename - self.source = str(py.code.Source(source).deindent()) - self.modname = modname - # look at the first three lines for a NOT_RPYTHON tag - first = "\n".join(source.split("\n", 3)[:3]) - if "NOT_RPYTHON" in first: - self.can_use_geninterp = False - else: - self.can_use_geninterp = True - # make source code available for tracebacks - lines = [x + "\n" for x in source.split("\n")] - py.std.linecache.cache[filename] = (1, None, lines, filename) def __repr__(self): - return "" % (self.filename, self.can_use_geninterp) + return "" % (self.filename,) def getwdict(self, space): return space.fromcache(ApplevelCache).getorbuild(self) @@ -979,10 +981,7 @@ def build(self, app): "NOT_RPYTHON. Called indirectly by Applevel.getwdict()." - if self.space.config.objspace.geninterp and app.can_use_geninterp: - return PyPyCacheDir.build_applevelinterp_dict(app, self.space) - else: - return build_applevel_dict(app, self.space) + return build_applevel_dict(app, self.space) # __________ pure applevel version __________ @@ -996,157 +995,6 @@ filename=self.filename) return w_glob -# __________ geninterplevel version __________ - -class PyPyCacheDir: - "NOT_RPYTHON" - # similar to applevel, but using translation to interp-level. - # This version maintains a cache folder with single files. - - def build_applevelinterp_dict(cls, self, space): - "NOT_RPYTHON" - # N.B. 'self' is the ApplevelInterp; this is a class method, - # just so that we have a convenient place to store the global state. - if not cls._setup_done: - cls._setup() - - from pypy.translator.geninterplevel import translate_as_module - import marshal - scramble = md5(cls.seed) - scramble.update(marshal.dumps(self.source)) - key = scramble.hexdigest() - initfunc = cls.known_code.get(key) - if not initfunc: - # try to get it from file - name = key - if self.filename: - prename = os.path.splitext(os.path.basename(self.filename))[0] - else: - prename = 'zznoname' - name = "%s_%s" % (prename, name) - try: - __import__("pypy._cache."+name) - except ImportError, x: - # print x - pass - else: - initfunc = cls.known_code[key] - if not initfunc: - # build it and put it into a file - initfunc, newsrc = translate_as_module( - self.source, self.filename, self.modname) - fname = cls.cache_path.join(name+".py").strpath - f = file(get_tmp_file_name(fname), "w") - print >> f, """\ -# self-destruct on double-click: -if __name__ == "__main__": - from pypy import _cache - import os - namestart = os.path.join(os.path.split(_cache.__file__)[0], '%s') - for ending in ('.py', '.pyc', '.pyo'): - try: - os.unlink(namestart+ending) - except os.error: - pass""" % name - print >> f - print >> f, newsrc - print >> f, "from pypy._cache import known_code" - print >> f, "known_code[%r] = %s" % (key, initfunc.__name__) - f.close() - rename_tmp_to_eventual_file_name(fname) - w_glob = initfunc(space) - return w_glob - build_applevelinterp_dict = classmethod(build_applevelinterp_dict) - - _setup_done = False - - def _setup(cls): - """NOT_RPYTHON""" - lp = py.path.local - import pypy, os - p = lp(pypy.__file__).new(basename='_cache').ensure(dir=1) - cls.cache_path = p - ini = p.join('__init__.py') - try: - if not ini.check(): - raise ImportError # don't import if only a .pyc file left!!! - from pypy._cache import known_code, \ - GI_VERSION_RENDERED - except ImportError: - GI_VERSION_RENDERED = 0 - from pypy.translator.geninterplevel import GI_VERSION - cls.seed = md5(str(GI_VERSION)).digest() - if GI_VERSION != GI_VERSION_RENDERED or GI_VERSION is None: - for pth in p.listdir(): - if pth.check(file=1): - try: - pth.remove() - except: pass - f = file(get_tmp_file_name(str(ini)), "w") - f.write("""\ -# This folder acts as a cache for code snippets which have been -# compiled by compile_as_module(). -# It will get a new entry for every piece of code that has -# not been seen, yet. -# -# Caution! Only the code snippet is checked. If something -# is imported, changes are not detected. Also, changes -# to geninterplevel or gateway are also not checked. -# Exception: There is a checked version number in geninterplevel.py -# -# If in doubt, remove this file from time to time. - -GI_VERSION_RENDERED = %r - -known_code = {} - -# self-destruct on double-click: -def harakiri(): - import pypy._cache as _c - import py - lp = py.path.local - for pth in lp(_c.__file__).dirpath().listdir(): - try: - pth.remove() - except: pass - -if __name__ == "__main__": - harakiri() - -del harakiri -""" % GI_VERSION) - f.close() - rename_tmp_to_eventual_file_name(str(ini)) - import pypy._cache - cls.known_code = pypy._cache.known_code - cls._setup_done = True - _setup = classmethod(_setup) - - -def gethostname(_cache=[]): - if not _cache: - try: - import socket - hostname = socket.gethostname() - except: - hostname = '' - _cache.append(hostname) - return _cache[0] - -def get_tmp_file_name(fname): - return '%s~%s~%d' % (fname, gethostname(), os.getpid()) - -def rename_tmp_to_eventual_file_name(fname): - # generated files are first written to the host- and process-specific - # file 'tmpname', and then atomically moved to their final 'fname' - # to avoid problems if py.py is started several times in parallel - tmpname = get_tmp_file_name(fname) - try: - os.rename(tmpname, fname) - except (OSError, IOError): - os.unlink(fname) # necessary on Windows - os.rename(tmpname, fname) - # ____________________________________________________________ def appdef(source, applevel=ApplevelClass, filename=None): @@ -1184,11 +1032,6 @@ return build_applevel_dict(self, space) -class applevelinterp_temp(ApplevelClass): - hidden_applevel = False - def getwdict(self, space): # no cache - return PyPyCacheDir.build_applevelinterp_dict(self, space) - # app2interp_temp is used for testing mainly def app2interp_temp(func, applevel_temp=applevel_temp, filename=None): """ NOT_RPYTHON """ diff --git a/pypy/interpreter/pyopcode.py b/pypy/interpreter/pyopcode.py --- a/pypy/interpreter/pyopcode.py +++ b/pypy/interpreter/pyopcode.py @@ -15,9 +15,8 @@ from pypy.rlib.rarithmetic import r_uint, intmask from pypy.rlib.unroll import unrolling_iterable from pypy.rlib.debug import check_nonneg -from pypy.tool.stdlib_opcode import (bytecode_spec, host_bytecode_spec, - unrolling_all_opcode_descs, opmap, - host_opmap) +from pypy.tool.stdlib_opcode import (bytecode_spec, + unrolling_all_opcode_descs) def unaryoperation(operationname): """NOT_RPYTHON""" @@ -713,6 +712,19 @@ w_list = self.space.newlist(items) self.pushvalue(w_list) + def BUILD_LIST_FROM_ARG(self, _, next_instr): + # this is a little dance, because list has to be before the + # value + last_val = self.popvalue() + try: + lgt = self.space.len_w(last_val) + except OperationError, e: + if e.async(self.space): + raise + lgt = 0 # oh well + self.pushvalue(self.space.newlist([], sizehint=lgt)) + self.pushvalue(last_val) + def LOAD_ATTR(self, nameindex, next_instr): "obj.attributename" w_obj = self.popvalue() @@ -1419,11 +1431,9 @@ if lastchar.isspace() and lastchar != ' ': return file_softspace(stream, True) - print_item_to._annspecialcase_ = "specialize:argtype(0)" def print_item(x): print_item_to(x, sys_stdout()) - print_item._annspecialcase_ = "flowspace:print_item" def print_newline_to(stream): stream.write("\n") @@ -1431,7 +1441,6 @@ def print_newline(): print_newline_to(sys_stdout()) - print_newline._annspecialcase_ = "flowspace:print_newline" def file_softspace(file, newflag): try: diff --git a/pypy/interpreter/pyparser/parsestring.py b/pypy/interpreter/pyparser/parsestring.py --- a/pypy/interpreter/pyparser/parsestring.py +++ b/pypy/interpreter/pyparser/parsestring.py @@ -2,34 +2,39 @@ from pypy.interpreter import unicodehelper from pypy.rlib.rstring import StringBuilder -def parsestr(space, encoding, s, unicode_literals=False): - # compiler.transformer.Transformer.decode_literal depends on what - # might seem like minor details of this function -- changes here - # must be reflected there. +def parsestr(space, encoding, s, unicode_literal=False): + """Parses a string or unicode literal, and return a wrapped value. + + If encoding=iso8859-1, the source string is also in this encoding. + If encoding=None, the source string is ascii only. + In other cases, the source string is in utf-8 encoding. + + When a bytes string is returned, it will be encoded with the + original encoding. + + Yes, it's very inefficient. + Yes, CPython has very similar code. + """ # we use ps as "pointer to s" # q is the virtual last char index of the string ps = 0 quote = s[ps] rawmode = False - unicode = unicode_literals # string decoration handling - o = ord(quote) - isalpha = (o>=97 and o<=122) or (o>=65 and o<=90) - if isalpha or quote == '_': - if quote == 'b' or quote == 'B': - ps += 1 - quote = s[ps] - unicode = False - elif quote == 'u' or quote == 'U': - ps += 1 - quote = s[ps] - unicode = True - if quote == 'r' or quote == 'R': - ps += 1 - quote = s[ps] - rawmode = True + if quote == 'b' or quote == 'B': + ps += 1 + quote = s[ps] + unicode_literal = False + elif quote == 'u' or quote == 'U': + ps += 1 + quote = s[ps] + unicode_literal = True + if quote == 'r' or quote == 'R': + ps += 1 + quote = s[ps] + rawmode = True if quote != "'" and quote != '"': raise_app_valueerror(space, 'Internal error: parser passed unquoted literal') @@ -46,21 +51,28 @@ 'unmatched triple quotes in literal') q -= 2 - if unicode: # XXX Py_UnicodeFlag is ignored for now + if unicode_literal: # XXX Py_UnicodeFlag is ignored for now if encoding is None or encoding == "iso-8859-1": + # 'unicode_escape' expects latin-1 bytes, string is ready. buf = s bufp = ps bufq = q u = None else: - # "\XX" may become "\u005c\uHHLL" (12 bytes) + # String is utf8-encoded, but 'unicode_escape' expects + # latin-1; So multibyte sequences must be escaped. lis = [] # using a list to assemble the value end = q + # Worst case: "\XX" may become "\u005c\uHHLL" (12 bytes) while ps < end: if s[ps] == '\\': lis.append(s[ps]) ps += 1 if ord(s[ps]) & 0x80: + # A multibyte sequence will follow, it will be + # escaped like \u1234. To avoid confusion with + # the backslash we just wrote, we emit "\u005c" + # instead. lis.append("u005c") if ord(s[ps]) & 0x80: # XXX inefficient w, ps = decode_utf8(space, s, ps, end, "utf-16-be") @@ -86,13 +98,11 @@ need_encoding = (encoding is not None and encoding != "utf-8" and encoding != "iso-8859-1") - # XXX add strchr like interface to rtyper assert 0 <= ps <= q substr = s[ps : q] if rawmode or '\\' not in s[ps:]: if need_encoding: w_u = space.wrap(unicodehelper.PyUnicode_DecodeUTF8(space, substr)) - #w_v = space.wrap(space.unwrap(w_u).encode(encoding)) this works w_v = unicodehelper.PyUnicode_AsEncodedString(space, w_u, space.wrap(encoding)) return w_v else: diff --git a/pypy/interpreter/test/test_appinterp.py b/pypy/interpreter/test/test_appinterp.py --- a/pypy/interpreter/test/test_appinterp.py +++ b/pypy/interpreter/test/test_appinterp.py @@ -1,6 +1,6 @@ import py -from pypy.interpreter.gateway import appdef, ApplevelClass, applevel_temp, applevelinterp_temp +from pypy.interpreter.gateway import appdef, ApplevelClass, applevel_temp from pypy.interpreter.error import OperationError def test_execwith_novars(space): @@ -82,9 +82,6 @@ w_res = g(space, space.wrap(10), space.wrap(1)) assert space.eq_w(w_res, space.wrap(-9)) -def test_applevelinterp_functions(space): - test_applevel_functions(space, applevel_temp = applevelinterp_temp) - def test_applevel_class(space, applevel_temp = applevel_temp): app = applevel_temp(''' class C(object): @@ -99,9 +96,6 @@ w_clsattr = space.getattr(c, space.wrap('attr')) assert space.eq_w(w_clsattr, space.wrap(17)) -def test_applevelinterp_class(space): - test_applevel_class(space, applevel_temp = applevelinterp_temp) - def app_test_something_at_app_level(): x = 2 assert x/2 == 1 @@ -161,7 +155,7 @@ w_str = space1.getattr(w_mymod1, space1.wrap("hi")) assert space1.str_w(w_str) == "hello" - def test_geninterp_can_unfreeze(self): + def test_random_stuff_can_unfreeze(self): # When a module contains an "import" statement in applevel code, the # imported module is initialized, possibly after it has been already # frozen. diff --git a/pypy/interpreter/test/test_gateway.py b/pypy/interpreter/test/test_gateway.py --- a/pypy/interpreter/test/test_gateway.py +++ b/pypy/interpreter/test/test_gateway.py @@ -101,14 +101,6 @@ g3 = gateway.app2interp_temp(noapp_g3, gateway.applevel_temp) assert self.space.eq_w(g3(self.space, w('foo'), w('bar')), w('foobar')) - def test_app2interp2(self): - """same but using transformed code""" - w = self.space.wrap - def noapp_g3(a, b): - return a+b - g3 = gateway.app2interp_temp(noapp_g3, gateway.applevelinterp_temp) - assert self.space.eq_w(g3(self.space, w('foo'), w('bar')), w('foobar')) - def test_app2interp_general_args(self): w = self.space.wrap def app_general(x, *args, **kwds): diff --git a/pypy/interpreter/test/test_objspace.py b/pypy/interpreter/test/test_objspace.py --- a/pypy/interpreter/test/test_objspace.py +++ b/pypy/interpreter/test/test_objspace.py @@ -312,8 +312,8 @@ mods = space.get_builtinmodule_to_install() assert '__pypy__' in mods # real builtin - assert 'array' not in mods # in lib_pypy - assert 'faked+array' not in mods # in lib_pypy + assert '_functools' not in mods # in lib_pypy + assert 'faked+_functools' not in mods # in lib_pypy assert 'this_doesnt_exist' not in mods # not in lib_pypy assert 'faked+this_doesnt_exist' in mods # not in lib_pypy, but in # ALL_BUILTIN_MODULES diff --git a/pypy/interpreter/test/test_zzpickle_and_slow.py b/pypy/interpreter/test/test_zzpickle_and_slow.py --- a/pypy/interpreter/test/test_zzpickle_and_slow.py +++ b/pypy/interpreter/test/test_zzpickle_and_slow.py @@ -75,6 +75,7 @@ class AppTestInterpObjectPickling: pytestmark = py.test.mark.skipif("config.option.runappdirect") def setup_class(cls): + cls.space = gettestobjspace(usemodules=['struct']) _attach_helpers(cls.space) def teardown_class(cls): diff --git a/pypy/interpreter/typedef.py b/pypy/interpreter/typedef.py --- a/pypy/interpreter/typedef.py +++ b/pypy/interpreter/typedef.py @@ -321,7 +321,7 @@ def user_setup(self, space, w_subtype): self.w__dict__ = space.newdict( - instance=True, classofinstance=w_subtype) + instance=True) base_user_setup(self, space, w_subtype) def setclass(self, space, w_subtype): diff --git a/pypy/jit/backend/llgraph/llimpl.py b/pypy/jit/backend/llgraph/llimpl.py --- a/pypy/jit/backend/llgraph/llimpl.py +++ b/pypy/jit/backend/llgraph/llimpl.py @@ -171,7 +171,7 @@ 'unicodesetitem' : (('ref', 'int', 'int'), 'int'), 'cast_ptr_to_int' : (('ref',), 'int'), 'cast_int_to_ptr' : (('int',), 'ref'), - 'debug_merge_point': (('ref', 'int'), None), + 'debug_merge_point': (('ref', 'int', 'int'), None), 'force_token' : ((), 'int'), 'call_may_force' : (('int', 'varargs'), 'intorptr'), 'guard_not_forced': ((), None), @@ -1797,6 +1797,7 @@ if specialize_as_constant: def specialize_call(self, hop): llvalue = func(hop.args_s[0].const) + hop.exception_cannot_occur() return hop.inputconst(lltype.typeOf(llvalue), llvalue) else: # specialize as direct_call @@ -1813,6 +1814,7 @@ sm = ootype._static_meth(FUNCTYPE, _name=func.__name__, _callable=func) cfunc = hop.inputconst(FUNCTYPE, sm) args_v = hop.inputargs(*hop.args_r) + hop.exception_is_here() return hop.genop('direct_call', [cfunc] + args_v, hop.r_result) diff --git a/pypy/jit/backend/llsupport/gc.py b/pypy/jit/backend/llsupport/gc.py --- a/pypy/jit/backend/llsupport/gc.py +++ b/pypy/jit/backend/llsupport/gc.py @@ -208,6 +208,7 @@ This is the class supporting --gcrootfinder=asmgcc. """ is_shadow_stack = False + is_64_bit = (WORD == 8) LOC_REG = 0 LOC_ESP_PLUS = 1 @@ -336,17 +337,17 @@ self._gcmap_deadentries += 1 item += asmgcroot.arrayitemsize - def get_basic_shape(self, is_64_bit=False): + def get_basic_shape(self): # XXX: Should this code even really know about stack frame layout of # the JIT? - if is_64_bit: - return [chr(self.LOC_EBP_PLUS | 8), - chr(self.LOC_EBP_MINUS | 8), - chr(self.LOC_EBP_MINUS | 16), - chr(self.LOC_EBP_MINUS | 24), - chr(self.LOC_EBP_MINUS | 32), - chr(self.LOC_EBP_MINUS | 40), - chr(self.LOC_EBP_PLUS | 0), + if self.is_64_bit: + return [chr(self.LOC_EBP_PLUS | 4), # return addr: at 8(%rbp) + chr(self.LOC_EBP_MINUS | 4), # saved %rbx: at -8(%rbp) + chr(self.LOC_EBP_MINUS | 8), # saved %r12: at -16(%rbp) + chr(self.LOC_EBP_MINUS | 12), # saved %r13: at -24(%rbp) + chr(self.LOC_EBP_MINUS | 16), # saved %r14: at -32(%rbp) + chr(self.LOC_EBP_MINUS | 20), # saved %r15: at -40(%rbp) + chr(self.LOC_EBP_PLUS | 0), # saved %rbp: at (%rbp) chr(0)] else: return [chr(self.LOC_EBP_PLUS | 4), # return addr: at 4(%ebp) @@ -366,7 +367,11 @@ shape.append(chr(number | flag)) def add_frame_offset(self, shape, offset): - assert (offset & 3) == 0 + if self.is_64_bit: + assert (offset & 7) == 0 + offset >>= 1 + else: + assert (offset & 3) == 0 if offset >= 0: num = self.LOC_EBP_PLUS | offset else: @@ -518,7 +523,7 @@ def initialize(self): pass - def get_basic_shape(self, is_64_bit=False): + def get_basic_shape(self): return [] def add_frame_offset(self, shape, offset): @@ -594,7 +599,7 @@ # if convenient for the backend, we compute the info about # the flag as (byte-offset, single-byte-flag). import struct - value = struct.pack("l", flag_word) + value = struct.pack(lltype.SignedFmt, flag_word) assert value.count('\x00') == len(value) - 1 # only one byte is != 0 i = 0 while value[i] == '\x00': i += 1 diff --git a/pypy/jit/backend/llsupport/regalloc.py b/pypy/jit/backend/llsupport/regalloc.py --- a/pypy/jit/backend/llsupport/regalloc.py +++ b/pypy/jit/backend/llsupport/regalloc.py @@ -321,7 +321,7 @@ except KeyError: pass # 'var' is already not in a register - def loc(self, box): + def loc(self, box, must_exist=False): """ Return the location of 'box'. """ self._check_type(box) @@ -332,6 +332,8 @@ except KeyError: if box in self.bindings_to_frame_reg: return self.frame_reg + if must_exist: + return self.frame_manager.bindings[box] return self.frame_manager.loc(box) def return_constant(self, v, forbidden_vars=[], selected_reg=None): @@ -360,7 +362,7 @@ self._check_type(v) if isinstance(v, Const): return self.return_constant(v, forbidden_vars, selected_reg) - prev_loc = self.loc(v) + prev_loc = self.loc(v, must_exist=True) if prev_loc is self.frame_reg and selected_reg is None: return prev_loc loc = self.force_allocate_reg(v, forbidden_vars, selected_reg, diff --git a/pypy/jit/backend/llsupport/test/test_descr.py b/pypy/jit/backend/llsupport/test/test_descr.py --- a/pypy/jit/backend/llsupport/test/test_descr.py +++ b/pypy/jit/backend/llsupport/test/test_descr.py @@ -148,7 +148,7 @@ # def get_alignment(code): # Retrieve default alignment for the compiler/platform - return struct.calcsize('l' + code) - struct.calcsize(code) + return struct.calcsize(lltype.SignedFmt + code) - struct.calcsize(code) assert descr1.basesize == get_alignment('c') assert descr2.basesize == get_alignment('p') assert descr3.basesize == get_alignment('p') diff --git a/pypy/jit/backend/llsupport/test/test_ffisupport.py b/pypy/jit/backend/llsupport/test/test_ffisupport.py --- a/pypy/jit/backend/llsupport/test/test_ffisupport.py +++ b/pypy/jit/backend/llsupport/test/test_ffisupport.py @@ -2,6 +2,7 @@ from pypy.jit.codewriter.longlong import is_64_bit from pypy.jit.backend.llsupport.descr import * from pypy.jit.backend.llsupport.ffisupport import * +from pypy.rlib.rarithmetic import is_emulated_long class FakeCPU: @@ -43,7 +44,7 @@ assert descr.result_flag == FLAG_UNSIGNED assert descr.is_result_signed() == False - if not is_64_bit: + if not is_64_bit or is_emulated_long: descr = get_call_descr_dynamic(FakeCPU(), [], types.slonglong, None, 42) assert descr is None # missing longlongs diff --git a/pypy/jit/backend/llsupport/test/test_gc.py b/pypy/jit/backend/llsupport/test/test_gc.py --- a/pypy/jit/backend/llsupport/test/test_gc.py +++ b/pypy/jit/backend/llsupport/test/test_gc.py @@ -11,6 +11,7 @@ from pypy.jit.tool.oparser import parse from pypy.rpython.lltypesystem.rclass import OBJECT, OBJECT_VTABLE from pypy.jit.metainterp.optimizeopt.util import equaloplists +from pypy.rlib.rarithmetic import is_valid_int def test_boehm(): gc_ll_descr = GcLLDescr_boehm(None, None, None) @@ -57,6 +58,7 @@ def frame_pos(n): return -4*(4+n) gcrootmap = GcRootMap_asmgcc() + gcrootmap.is_64_bit = False num1 = frame_pos(-5) num1a = num1|2 num2 = frame_pos(55) @@ -102,7 +104,7 @@ gcrootmap.put(retaddr, shapeaddr) assert gcrootmap._gcmap[0] == retaddr assert gcrootmap._gcmap[1] == shapeaddr - p = rffi.cast(rffi.LONGP, gcrootmap.gcmapstart()) + p = rffi.cast(rffi.SIGNEDP, gcrootmap.gcmapstart()) assert p[0] == retaddr assert (gcrootmap.gcmapend() == gcrootmap.gcmapstart() + rffi.sizeof(lltype.Signed) * 2) @@ -418,9 +420,9 @@ assert newops[0].getarg(1) == v_value assert newops[0].result is None wbdescr = newops[0].getdescr() - assert isinstance(wbdescr.jit_wb_if_flag, int) - assert isinstance(wbdescr.jit_wb_if_flag_byteofs, int) - assert isinstance(wbdescr.jit_wb_if_flag_singlebyte, int) + assert is_valid_int(wbdescr.jit_wb_if_flag) + assert is_valid_int(wbdescr.jit_wb_if_flag_byteofs) + assert is_valid_int(wbdescr.jit_wb_if_flag_singlebyte) def test_get_rid_of_debug_merge_point(self): operations = [ diff --git a/pypy/jit/backend/llsupport/test/test_regalloc.py b/pypy/jit/backend/llsupport/test/test_regalloc.py --- a/pypy/jit/backend/llsupport/test/test_regalloc.py +++ b/pypy/jit/backend/llsupport/test/test_regalloc.py @@ -1,4 +1,4 @@ - +import py from pypy.jit.metainterp.history import BoxInt, ConstInt, BoxFloat, INT, FLOAT from pypy.jit.backend.llsupport.regalloc import FrameManager from pypy.jit.backend.llsupport.regalloc import RegisterManager as BaseRegMan @@ -236,6 +236,16 @@ assert isinstance(loc, FakeFramePos) assert len(asm.moves) == 1 + def test_bogus_make_sure_var_in_reg(self): + b0, = newboxes(0) + longevity = {b0: (0, 1)} + fm = TFrameManager() + asm = MockAsm() + rm = RegisterManager(longevity, frame_manager=fm, assembler=asm) + rm.next_instruction() + # invalid call to make_sure_var_in_reg(): box unknown so far + py.test.raises(KeyError, rm.make_sure_var_in_reg, b0) + def test_return_constant(self): asm = MockAsm() boxes, longevity = boxes_and_longevity(5) diff --git a/pypy/jit/backend/test/runner_test.py b/pypy/jit/backend/test/runner_test.py --- a/pypy/jit/backend/test/runner_test.py +++ b/pypy/jit/backend/test/runner_test.py @@ -16,15 +16,23 @@ from pypy.rpython.annlowlevel import llhelper from pypy.rpython.llinterp import LLException from pypy.jit.codewriter import heaptracker, longlong -from pypy.rlib.rarithmetic import intmask +from pypy.rlib import longlong2float +from pypy.rlib.rarithmetic import intmask, is_valid_int from pypy.jit.backend.detect_cpu import autodetect_main_model_and_size + def boxfloat(x): return BoxFloat(longlong.getfloatstorage(x)) def constfloat(x): return ConstFloat(longlong.getfloatstorage(x)) +def boxlonglong(ll): + if longlong.is_64_bit: + return BoxInt(ll) + else: + return BoxFloat(ll) + class Runner(object): @@ -493,7 +501,7 @@ if cpu.supports_floats: def func(f, i): assert isinstance(f, float) - assert isinstance(i, int) + assert is_valid_int(i) return f - float(i) FPTR = self.Ptr(self.FuncType([lltype.Float, lltype.Signed], lltype.Float)) @@ -604,7 +612,7 @@ [funcbox, BoxInt(arg1), BoxInt(arg2)], 'int', descr=calldescr) assert res.getint() == f(arg1, arg2) - + def test_call_stack_alignment(self): # test stack alignment issues, notably for Mac OS/X. # also test the ordering of the arguments. @@ -1490,18 +1498,36 @@ def test_noops(self): c_box = self.alloc_string("hi there").constbox() c_nest = ConstInt(0) - self.execute_operation(rop.DEBUG_MERGE_POINT, [c_box, c_nest], 'void') + c_id = ConstInt(0) + self.execute_operation(rop.DEBUG_MERGE_POINT, [c_box, c_nest, c_id], 'void') self.execute_operation(rop.JIT_DEBUG, [c_box, c_nest, c_nest, c_nest, c_nest], 'void') def test_read_timestamp(self): + if sys.platform == 'win32': + # windows quite often is very inexact (like the old Intel 8259 PIC), + # so we stretch the time a little bit. + # On my virtual Parallels machine in a 2GHz Core i7 Mac Mini, + # the test starts working at delay == 21670 and stops at 20600000. + # We take the geometric mean value. + from math import log, exp + delay_min = 21670 + delay_max = 20600000 + delay = int(exp((log(delay_min)+log(delay_max))/2)) + def wait_a_bit(): + for i in xrange(delay): pass + else: + def wait_a_bit(): + pass if longlong.is_64_bit: got1 = self.execute_operation(rop.READ_TIMESTAMP, [], 'int') + wait_a_bit() got2 = self.execute_operation(rop.READ_TIMESTAMP, [], 'int') res1 = got1.getint() res2 = got2.getint() else: got1 = self.execute_operation(rop.READ_TIMESTAMP, [], 'float') + wait_a_bit() got2 = self.execute_operation(rop.READ_TIMESTAMP, [], 'float') res1 = got1.getlonglong() res2 = got2.getlonglong() @@ -1597,6 +1623,17 @@ [BoxPtr(x)], 'int').value assert res == -19 + def test_convert_float_bytes(self): + t = 'int' if longlong.is_64_bit else 'float' + res = self.execute_operation(rop.CONVERT_FLOAT_BYTES_TO_LONGLONG, + [boxfloat(2.5)], t).value + assert res == longlong2float.float2longlong(2.5) + + bytes = longlong2float.float2longlong(2.5) + res = self.execute_operation(rop.CONVERT_LONGLONG_BYTES_TO_FLOAT, + [boxlonglong(res)], 'float').value + assert longlong.getrealfloat(res) == 2.5 + def test_ooops_non_gc(self): x = lltype.malloc(lltype.Struct('x'), flavor='raw') v = heaptracker.adr2int(llmemory.cast_ptr_to_adr(x)) @@ -3061,7 +3098,7 @@ ResOperation(rop.JUMP, [i2], None, descr=targettoken2), ] self.cpu.compile_bridge(faildescr, inputargs, operations, looptoken) - + fail = self.cpu.execute_token(looptoken, 2) assert fail.identifier == 3 res = self.cpu.get_latest_value_int(0) @@ -3106,7 +3143,7 @@ assert len(mc) == len(ops) for i in range(len(mc)): assert mc[i].split("\t")[-1].startswith(ops[i]) - + data = ctypes.string_at(info.asmaddr, info.asmlen) mc = list(machine_code_dump(data, info.asmaddr, cpuname)) lines = [line for line in mc if line.count('\t') == 2] diff --git a/pypy/jit/backend/test/support.py b/pypy/jit/backend/test/support.py --- a/pypy/jit/backend/test/support.py +++ b/pypy/jit/backend/test/support.py @@ -3,6 +3,7 @@ from pypy.rlib.debug import debug_print from pypy.translator.translator import TranslationContext, graphof from pypy.jit.metainterp.optimizeopt import ALL_OPTS_NAMES +from pypy.rlib.rarithmetic import is_valid_int class BaseCompiledMixin(object): @@ -24,7 +25,7 @@ from pypy.annotation import model as annmodel for arg in args: - assert isinstance(arg, int) + assert is_valid_int(arg) self.pre_translation_hook() t = self._get_TranslationContext() diff --git a/pypy/jit/backend/test/test_random.py b/pypy/jit/backend/test/test_random.py --- a/pypy/jit/backend/test/test_random.py +++ b/pypy/jit/backend/test/test_random.py @@ -328,6 +328,15 @@ def produce_into(self, builder, r): self.put(builder, [r.choice(builder.intvars)]) +class CastLongLongToFloatOperation(AbstractFloatOperation): + def produce_into(self, builder, r): + if longlong.is_64_bit: + self.put(builder, [r.choice(builder.intvars)]) + else: + if not builder.floatvars: + raise CannotProduceOperation + self.put(builder, [r.choice(builder.floatvars)]) + class CastFloatToIntOperation(AbstractFloatOperation): def produce_into(self, builder, r): if not builder.floatvars: @@ -449,6 +458,8 @@ OPERATIONS.append(CastFloatToIntOperation(rop.CAST_FLOAT_TO_INT)) OPERATIONS.append(CastIntToFloatOperation(rop.CAST_INT_TO_FLOAT)) +OPERATIONS.append(CastFloatToIntOperation(rop.CONVERT_FLOAT_BYTES_TO_LONGLONG)) +OPERATIONS.append(CastLongLongToFloatOperation(rop.CONVERT_LONGLONG_BYTES_TO_FLOAT)) OperationBuilder.OPERATIONS = OPERATIONS @@ -502,11 +513,11 @@ else: assert 0, "unknown backend %r" % pytest.config.option.backend -# ____________________________________________________________ +# ____________________________________________________________ class RandomLoop(object): dont_generate_more = False - + def __init__(self, cpu, builder_factory, r, startvars=None): self.cpu = cpu if startvars is None: diff --git a/pypy/jit/backend/x86/assembler.py b/pypy/jit/backend/x86/assembler.py --- a/pypy/jit/backend/x86/assembler.py +++ b/pypy/jit/backend/x86/assembler.py @@ -88,7 +88,6 @@ self._debug = False self.debug_counter_descr = cpu.fielddescrof(DEBUG_COUNTER, 'i') self.fail_boxes_count = 0 - self._current_depths_cache = (0, 0) self.datablockwrapper = None self.stack_check_slowpath = 0 self.propagate_exception_path = 0 @@ -442,10 +441,8 @@ looppos = self.mc.get_relative_pos() looptoken._x86_loop_code = looppos clt.frame_depth = -1 # temporarily - clt.param_depth = -1 # temporarily - frame_depth, param_depth = self._assemble(regalloc, operations) + frame_depth = self._assemble(regalloc, operations) clt.frame_depth = frame_depth - clt.param_depth = param_depth # size_excluding_failure_stuff = self.mc.get_relative_pos() self.write_pending_failure_recoveries() @@ -459,8 +456,7 @@ rawstart + size_excluding_failure_stuff, rawstart)) debug_stop("jit-backend-addr") - self._patch_stackadjust(rawstart + stackadjustpos, - frame_depth + param_depth) + self._patch_stackadjust(rawstart + stackadjustpos, frame_depth) self.patch_pending_failure_recoveries(rawstart) # ops_offset = self.mc.ops_offset @@ -500,14 +496,13 @@ assert ([loc.assembler() for loc in arglocs] == [loc.assembler() for loc in faildescr._x86_debug_faillocs]) regalloc = RegAlloc(self, self.cpu.translate_support_code) - fail_depths = faildescr._x86_current_depths startpos = self.mc.get_relative_pos() - operations = regalloc.prepare_bridge(fail_depths, inputargs, arglocs, + operations = regalloc.prepare_bridge(inputargs, arglocs, operations, self.current_clt.allgcrefs) stackadjustpos = self._patchable_stackadjust() - frame_depth, param_depth = self._assemble(regalloc, operations) + frame_depth = self._assemble(regalloc, operations) codeendpos = self.mc.get_relative_pos() self.write_pending_failure_recoveries() fullsize = self.mc.get_relative_pos() @@ -517,19 +512,16 @@ debug_print("bridge out of Guard %d has address %x to %x" % (descr_number, rawstart, rawstart + codeendpos)) debug_stop("jit-backend-addr") - self._patch_stackadjust(rawstart + stackadjustpos, - frame_depth + param_depth) + self._patch_stackadjust(rawstart + stackadjustpos, frame_depth) self.patch_pending_failure_recoveries(rawstart) if not we_are_translated(): # for the benefit of tests faildescr._x86_bridge_frame_depth = frame_depth - faildescr._x86_bridge_param_depth = param_depth # patch the jump from original guard self.patch_jump_for_descr(faildescr, rawstart) ops_offset = self.mc.ops_offset self.fixup_target_tokens(rawstart) self.current_clt.frame_depth = max(self.current_clt.frame_depth, frame_depth) - self.current_clt.param_depth = max(self.current_clt.param_depth, param_depth) self.teardown() # oprofile support if self.cpu.profile_agent is not None: @@ -614,7 +606,7 @@ else: assert token struct.number = compute_unique_id(token) - self.loop_run_counters.append(struct) + self.loop_run_counters.append(struct) return struct def _find_failure_recovery_bytecode(self, faildescr): @@ -673,7 +665,7 @@ ResOperation(rop.SETFIELD_RAW, [c_adr, box2], None, descr=self.debug_counter_descr)] operations.extend(ops) - + @specialize.argtype(1) def _inject_debugging_code(self, looptoken, operations, tp, number): if self._debug: @@ -700,15 +692,12 @@ regalloc.walk_operations(operations) if we_are_translated() or self.cpu.dont_keepalive_stuff: self._regalloc = None # else keep it around for debugging - frame_depth = regalloc.fm.get_frame_depth() - param_depth = regalloc.param_depth + frame_depth = regalloc.get_final_frame_depth() jump_target_descr = regalloc.jump_target_descr if jump_target_descr is not None: target_frame_depth = jump_target_descr._x86_clt.frame_depth - target_param_depth = jump_target_descr._x86_clt.param_depth frame_depth = max(frame_depth, target_frame_depth) - param_depth = max(param_depth, target_param_depth) - return frame_depth, param_depth + return frame_depth def _patchable_stackadjust(self): # stack adjustment LEA @@ -847,8 +836,8 @@ self.mc.MOVSD_sx(0, loc.value) elif WORD == 4 and isinstance(loc, StackLoc) and loc.get_width() == 8: # XXX evil trick - self.mc.PUSH_b(get_ebp_ofs(loc.position)) - self.mc.PUSH_b(get_ebp_ofs(loc.position + 1)) + self.mc.PUSH_b(loc.value + 4) + self.mc.PUSH_b(loc.value) else: self.mc.PUSH(loc) @@ -858,8 +847,8 @@ self.mc.ADD_ri(esp.value, 8) # = size of doubles elif WORD == 4 and isinstance(loc, StackLoc) and loc.get_width() == 8: # XXX evil trick - self.mc.POP_b(get_ebp_ofs(loc.position + 1)) - self.mc.POP_b(get_ebp_ofs(loc.position)) + self.mc.POP_b(loc.value) + self.mc.POP_b(loc.value + 4) else: self.mc.POP(loc) @@ -892,10 +881,9 @@ genop_math_list[oopspecindex](self, op, arglocs, resloc) def regalloc_perform_with_guard(self, op, guard_op, faillocs, - arglocs, resloc, current_depths): + arglocs, resloc): faildescr = guard_op.getdescr() assert isinstance(faildescr, AbstractFailDescr) - faildescr._x86_current_depths = current_depths failargs = guard_op.getfailargs() guard_opnum = guard_op.getopnum() guard_token = self.implement_guard_recovery(guard_opnum, @@ -911,10 +899,9 @@ # must be added by the genop_guard_list[]() assert guard_token is self.pending_guard_tokens[-1] - def regalloc_perform_guard(self, guard_op, faillocs, arglocs, resloc, - current_depths): + def regalloc_perform_guard(self, guard_op, faillocs, arglocs, resloc): self.regalloc_perform_with_guard(None, guard_op, faillocs, arglocs, - resloc, current_depths) + resloc) def load_effective_addr(self, sizereg, baseofs, scale, result, frm=imm0): self.mc.LEA(result, addr_add(frm, sizereg, baseofs, scale)) @@ -1038,13 +1025,14 @@ self.mc.MOV(tmp, loc) self.mc.MOV_sr(p, tmp.value) p += loc.get_width() - self._regalloc.reserve_param(p//WORD) # x is a location self.mc.CALL(x) self.mark_gc_roots(force_index) # if callconv != FFI_DEFAULT_ABI: self._fix_stdcall(callconv, p) + # + self._regalloc.needed_extra_stack_locations(p//WORD) def _fix_stdcall(self, callconv, p): from pypy.rlib.clibffi import FFI_STDCALL @@ -1127,9 +1115,9 @@ x = r10 remap_frame_layout(self, src_locs, dst_locs, X86_64_SCRATCH_REG) - self._regalloc.reserve_param(len(pass_on_stack)) self.mc.CALL(x) self.mark_gc_roots(force_index) + self._regalloc.needed_extra_stack_locations(len(pass_on_stack)) def call(self, addr, args, res): force_index = self.write_new_force_index() @@ -1254,6 +1242,24 @@ self.mc.MOVD_xr(resloc.value, loc0.value) self.mc.CVTSS2SD_xx(resloc.value, resloc.value) + def genop_convert_float_bytes_to_longlong(self, op, arglocs, resloc): + loc0, = arglocs + if longlong.is_64_bit: + assert isinstance(resloc, RegLoc) + assert isinstance(loc0, RegLoc) + self.mc.MOVD(resloc, loc0) + else: + self.mov(loc0, resloc) + + def genop_convert_longlong_bytes_to_float(self, op, arglocs, resloc): + loc0, = arglocs + if longlong.is_64_bit: + assert isinstance(resloc, RegLoc) + assert isinstance(loc0, RegLoc) + self.mc.MOVD(resloc, loc0) + else: + self.mov(loc0, resloc) + def genop_guard_int_is_true(self, op, guard_op, guard_token, arglocs, resloc): guard_opnum = guard_op.getopnum() self.mc.CMP(arglocs[0], imm0) @@ -1966,8 +1972,6 @@ mc.PUSH_r(ebx.value) elif IS_X86_64: mc.MOV_rr(edi.value, ebx.value) - # XXX: Correct to only align the stack on 64-bit? - mc.AND_ri(esp.value, -16) else: raise AssertionError("Shouldn't happen") @@ -2129,14 +2133,16 @@ # First, we need to save away the registers listed in # 'save_registers' that are not callee-save. XXX We assume that # the XMM registers won't be modified. We store them in - # [ESP+4], [ESP+8], etc., leaving enough room in [ESP] for the - # single argument to closestack_addr below. - p = WORD + # [ESP+4], [ESP+8], etc.; on x86-32 we leave enough room in [ESP] + # for the single argument to closestack_addr below. + if IS_X86_32: + p = WORD + elif IS_X86_64: + p = 0 for reg in self._regalloc.rm.save_around_call_regs: if reg in save_registers: self.mc.MOV_sr(p, reg.value) p += WORD - self._regalloc.reserve_param(p//WORD) # if gcrootmap.is_shadow_stack: args = [] @@ -2187,11 +2193,15 @@ # self._emit_call(-1, imm(self.releasegil_addr), args) # Finally, restore the registers saved above. - p = WORD + if IS_X86_32: + p = WORD + elif IS_X86_64: + p = 0 for reg in self._regalloc.rm.save_around_call_regs: if reg in save_registers: self.mc.MOV_rs(reg.value, p) p += WORD + self._regalloc.needed_extra_stack_locations(p//WORD) def call_reacquire_gil(self, gcrootmap, save_loc): # save the previous result (eax/xmm0) into the stack temporarily. @@ -2199,7 +2209,6 @@ # to save xmm0 in this case. if isinstance(save_loc, RegLoc) and not save_loc.is_xmm: self.mc.MOV_sr(WORD, save_loc.value) - self._regalloc.reserve_param(2) # call the reopenstack() function (also reacquiring the GIL) if gcrootmap.is_shadow_stack: args = [] @@ -2219,6 +2228,7 @@ # restore the result from the stack if isinstance(save_loc, RegLoc) and not save_loc.is_xmm: self.mc.MOV_rs(save_loc.value, WORD) + self._regalloc.needed_extra_stack_locations(2) def genop_guard_call_assembler(self, op, guard_op, guard_token, arglocs, result_loc): @@ -2495,11 +2505,6 @@ # copy of heap(nursery_free_adr), so that the final MOV below is # a no-op. - # reserve room for the argument to the real malloc and the - # saved XMM regs (on 32 bit: 8 * 2 words; on 64 bit: 16 * 1 - # word) - self._regalloc.reserve_param(1+16) - gcrootmap = self.cpu.gc_ll_descr.gcrootmap shadow_stack = (gcrootmap is not None and gcrootmap.is_shadow_stack) if not shadow_stack: @@ -2510,6 +2515,11 @@ slowpath_addr2 = self.malloc_slowpath2 self.mc.CALL(imm(slowpath_addr2)) + # reserve room for the argument to the real malloc and the + # saved XMM regs (on 32 bit: 8 * 2 words; on 64 bit: 16 * 1 + # word) + self._regalloc.needed_extra_stack_locations(1+16) + offset = self.mc.get_relative_pos() - jmp_adr assert 0 < offset <= 127 self.mc.overwrite(jmp_adr-1, chr(offset)) diff --git a/pypy/jit/backend/x86/codebuf.py b/pypy/jit/backend/x86/codebuf.py --- a/pypy/jit/backend/x86/codebuf.py +++ b/pypy/jit/backend/x86/codebuf.py @@ -19,8 +19,8 @@ class MachineCodeBlockWrapper(BlockBuilderMixin, - codebuilder_cls, - LocationCodeBuilder): + LocationCodeBuilder, + codebuilder_cls): def __init__(self): self.init_block_builder() # a list of relative positions; for each position p, the bytes diff --git a/pypy/jit/backend/x86/regalloc.py b/pypy/jit/backend/x86/regalloc.py --- a/pypy/jit/backend/x86/regalloc.py +++ b/pypy/jit/backend/x86/regalloc.py @@ -168,7 +168,7 @@ def _prepare(self, inputargs, operations, allgcrefs): self.fm = X86FrameManager() - self.param_depth = 0 + self.min_frame_depth = 0 cpu = self.assembler.cpu operations = cpu.gc_ll_descr.rewrite_assembler(cpu, operations, allgcrefs) @@ -193,11 +193,9 @@ self.min_bytes_before_label = 13 return operations - def prepare_bridge(self, prev_depths, inputargs, arglocs, operations, - allgcrefs): + def prepare_bridge(self, inputargs, arglocs, operations, allgcrefs): operations = self._prepare(inputargs, operations, allgcrefs) self._update_bindings(arglocs, inputargs) - self.param_depth = prev_depths[1] self.min_bytes_before_label = 0 return operations @@ -205,8 +203,15 @@ self.min_bytes_before_label = max(self.min_bytes_before_label, at_least_position) - def reserve_param(self, n): - self.param_depth = max(self.param_depth, n) + def needed_extra_stack_locations(self, n): + # call *after* you needed extra stack locations: (%esp), (%esp+4)... + min_frame_depth = self.fm.get_frame_depth() + n + if min_frame_depth > self.min_frame_depth: + self.min_frame_depth = min_frame_depth + + def get_final_frame_depth(self): + self.needed_extra_stack_locations(0) # update min_frame_depth + return self.min_frame_depth def _set_initial_bindings(self, inputargs): if IS_X86_64: @@ -376,25 +381,12 @@ def locs_for_fail(self, guard_op): return [self.loc(v) for v in guard_op.getfailargs()] - def get_current_depth(self): - # return (self.fm.frame_depth, self.param_depth), but trying to share - # the resulting tuple among several calls - arg0 = self.fm.get_frame_depth() - arg1 = self.param_depth - result = self.assembler._current_depths_cache - if result[0] != arg0 or result[1] != arg1: - result = (arg0, arg1) - self.assembler._current_depths_cache = result - return result - def perform_with_guard(self, op, guard_op, arglocs, result_loc): faillocs = self.locs_for_fail(guard_op) self.rm.position += 1 self.xrm.position += 1 - current_depths = self.get_current_depth() self.assembler.regalloc_perform_with_guard(op, guard_op, faillocs, - arglocs, result_loc, - current_depths) + arglocs, result_loc) if op.result is not None: self.possibly_free_var(op.result) self.possibly_free_vars(guard_op.getfailargs()) @@ -407,10 +399,8 @@ arglocs)) else: self.assembler.dump('%s(%s)' % (guard_op, arglocs)) - current_depths = self.get_current_depth() self.assembler.regalloc_perform_guard(guard_op, faillocs, arglocs, - result_loc, - current_depths) + result_loc) self.possibly_free_vars(guard_op.getfailargs()) def PerformDiscard(self, op, arglocs): @@ -776,6 +766,32 @@ consider_cast_singlefloat_to_float = consider_cast_int_to_float + def consider_convert_float_bytes_to_longlong(self, op): + if longlong.is_64_bit: + loc0 = self.xrm.make_sure_var_in_reg(op.getarg(0)) + loc1 = self.rm.force_allocate_reg(op.result) + self.Perform(op, [loc0], loc1) + self.xrm.possibly_free_var(op.getarg(0)) + else: + arg0 = op.getarg(0) + loc0 = self.xrm.loc(arg0) + loc1 = self.xrm.force_allocate_reg(op.result, forbidden_vars=[arg0]) + self.Perform(op, [loc0], loc1) + self.xrm.possibly_free_var(arg0) + + def consider_convert_longlong_bytes_to_float(self, op): + if longlong.is_64_bit: + loc0 = self.rm.make_sure_var_in_reg(op.getarg(0)) + loc1 = self.xrm.force_allocate_reg(op.result) + self.Perform(op, [loc0], loc1) + self.rm.possibly_free_var(op.getarg(0)) + else: + arg0 = op.getarg(0) + loc0 = self.xrm.make_sure_var_in_reg(arg0) + loc1 = self.xrm.force_allocate_reg(op.result, forbidden_vars=[arg0]) + self.Perform(op, [loc0], loc1) + self.xrm.possibly_free_var(arg0) + def _consider_llong_binop_xx(self, op): # must force both arguments into xmm registers, because we don't # know if they will be suitably aligned. Exception: if the second @@ -1393,7 +1409,7 @@ self.force_spill_var(op.getarg(0)) def get_mark_gc_roots(self, gcrootmap, use_copy_area=False): - shape = gcrootmap.get_basic_shape(IS_X86_64) + shape = gcrootmap.get_basic_shape() for v, val in self.fm.bindings.items(): if (isinstance(v, BoxPtr) and self.rm.stays_alive(v)): assert isinstance(val, StackLoc) diff --git a/pypy/jit/backend/x86/rx86.py b/pypy/jit/backend/x86/rx86.py --- a/pypy/jit/backend/x86/rx86.py +++ b/pypy/jit/backend/x86/rx86.py @@ -601,9 +601,12 @@ CVTSS2SD_xb = xmminsn('\xF3', rex_nw, '\x0F\x5A', register(1, 8), stack_bp(2)) - MOVD_rx = xmminsn('\x66', rex_nw, '\x0F\x7E', register(2, 8), register(1), '\xC0') - MOVD_xr = xmminsn('\x66', rex_nw, '\x0F\x6E', register(1, 8), register(2), '\xC0') - MOVD_xb = xmminsn('\x66', rex_nw, '\x0F\x6E', register(1, 8), stack_bp(2)) + # These work on machine sized registers, so MOVD is actually MOVQ + # when running on 64 bits. Note a bug in the Intel documentation: + # http://lists.gnu.org/archive/html/bug-binutils/2007-07/msg00095.html + MOVD_rx = xmminsn('\x66', rex_w, '\x0F\x7E', register(2, 8), register(1), '\xC0') + MOVD_xr = xmminsn('\x66', rex_w, '\x0F\x6E', register(1, 8), register(2), '\xC0') + MOVD_xb = xmminsn('\x66', rex_w, '\x0F\x6E', register(1, 8), stack_bp(2)) PSRAD_xi = xmminsn('\x66', rex_nw, '\x0F\x72', register(1), '\xE0', immediate(2, 'b')) diff --git a/pypy/jit/backend/x86/support.py b/pypy/jit/backend/x86/support.py --- a/pypy/jit/backend/x86/support.py +++ b/pypy/jit/backend/x86/support.py @@ -36,15 +36,15 @@ # ____________________________________________________________ -if sys.platform == 'win32': - ensure_sse2_floats = lambda : None - # XXX check for SSE2 on win32 too +if WORD == 4: + extra = ['-DPYPY_X86_CHECK_SSE2'] else: - if WORD == 4: - extra = ['-DPYPY_X86_CHECK_SSE2'] - else: - extra = [] - ensure_sse2_floats = rffi.llexternal_use_eci(ExternalCompilationInfo( - compile_extra = ['-msse2', '-mfpmath=sse', - '-DPYPY_CPU_HAS_STANDARD_PRECISION'] + extra, - )) + extra = [] + +if sys.platform != 'win32': + extra = ['-msse2', '-mfpmath=sse', + '-DPYPY_CPU_HAS_STANDARD_PRECISION'] + extra + +ensure_sse2_floats = rffi.llexternal_use_eci(ExternalCompilationInfo( + compile_extra = extra, +)) diff --git a/pypy/jit/backend/x86/test/conftest.py b/pypy/jit/backend/x86/test/conftest.py --- a/pypy/jit/backend/x86/test/conftest.py +++ b/pypy/jit/backend/x86/test/conftest.py @@ -1,4 +1,4 @@ -import py +import py, os from pypy.jit.backend import detect_cpu cpu = detect_cpu.autodetect() @@ -6,5 +6,7 @@ if cpu not in ('x86', 'x86_64'): py.test.skip("x86/x86_64 tests skipped: cpu is %r" % (cpu,)) if cpu == 'x86_64': + if os.name == "nt": + py.test.skip("Windows cannot allocate non-reserved memory") from pypy.rpython.lltypesystem import ll2ctypes ll2ctypes.do_allocation_in_far_regions() diff --git a/pypy/jit/backend/x86/test/test_gc_integration.py b/pypy/jit/backend/x86/test/test_gc_integration.py --- a/pypy/jit/backend/x86/test/test_gc_integration.py +++ b/pypy/jit/backend/x86/test/test_gc_integration.py @@ -28,7 +28,7 @@ class MockGcRootMap(object): is_shadow_stack = False - def get_basic_shape(self, is_64_bit): + def get_basic_shape(self): return ['shape'] def add_frame_offset(self, shape, offset): shape.append(offset) diff --git a/pypy/jit/backend/x86/test/test_recompilation.py b/pypy/jit/backend/x86/test/test_recompilation.py --- a/pypy/jit/backend/x86/test/test_recompilation.py +++ b/pypy/jit/backend/x86/test/test_recompilation.py @@ -34,7 +34,6 @@ ''' loop = self.interpret(ops, [0]) previous = loop._jitcelltoken.compiled_loop_token.frame_depth - assert loop._jitcelltoken.compiled_loop_token.param_depth == 0 assert self.getint(0) == 20 ops = ''' [i1] @@ -51,7 +50,6 @@ bridge = self.attach_bridge(ops, loop, -2) descr = loop.operations[3].getdescr() new = descr._x86_bridge_frame_depth - assert descr._x86_bridge_param_depth == 0 # the force_spill() forces the stack to grow assert new > previous fail = self.run(loop, 0) @@ -116,10 +114,8 @@ loop_frame_depth = loop._jitcelltoken.compiled_loop_token.frame_depth bridge = self.attach_bridge(ops, loop, 6) guard_op = loop.operations[6] - assert loop._jitcelltoken.compiled_loop_token.param_depth == 0 # the force_spill() forces the stack to grow assert guard_op.getdescr()._x86_bridge_frame_depth > loop_frame_depth - assert guard_op.getdescr()._x86_bridge_param_depth == 0 self.run(loop, 0, 0, 0, 0, 0, 0) assert self.getint(0) == 1 assert self.getint(1) == 20 diff --git a/pypy/jit/backend/x86/test/test_regalloc.py b/pypy/jit/backend/x86/test/test_regalloc.py --- a/pypy/jit/backend/x86/test/test_regalloc.py +++ b/pypy/jit/backend/x86/test/test_regalloc.py @@ -606,23 +606,37 @@ assert self.getints(9) == [0, 1, 1, 1, 1, 1, 1, 1, 1] class TestRegAllocCallAndStackDepth(BaseTestRegalloc): - def expected_param_depth(self, num_args): + def expected_frame_depth(self, num_call_args, num_pushed_input_args=0): # Assumes the arguments are all non-float if IS_X86_32: - return num_args + extra_esp = num_call_args + return extra_esp elif IS_X86_64: - return max(num_args - 6, 0) + # 'num_pushed_input_args' is for X86_64 only + extra_esp = max(num_call_args - 6, 0) + return num_pushed_input_args + extra_esp def test_one_call(self): ops = ''' - [i0, i1, i2, i3, i4, i5, i6, i7, i8, i9] + [i0, i1, i2, i3, i4, i5, i6, i7, i8, i9, i9b] i10 = call(ConstClass(f1ptr), i0, descr=f1_calldescr) - finish(i10, i1, i2, i3, i4, i5, i6, i7, i8, i9) + finish(i10, i1, i2, i3, i4, i5, i6, i7, i8, i9, i9b) ''' - loop = self.interpret(ops, [4, 7, 9, 9 ,9, 9, 9, 9, 9, 9]) - assert self.getints(10) == [5, 7, 9, 9, 9, 9, 9, 9, 9, 9] + loop = self.interpret(ops, [4, 7, 9, 9 ,9, 9, 9, 9, 9, 9, 8]) + assert self.getints(11) == [5, 7, 9, 9, 9, 9, 9, 9, 9, 9, 8] clt = loop._jitcelltoken.compiled_loop_token - assert clt.param_depth == self.expected_param_depth(1) + assert clt.frame_depth == self.expected_frame_depth(1, 5) + + def test_one_call_reverse(self): + ops = ''' + [i1, i2, i3, i4, i5, i6, i7, i8, i9, i9b, i0] + i10 = call(ConstClass(f1ptr), i0, descr=f1_calldescr) + finish(i10, i1, i2, i3, i4, i5, i6, i7, i8, i9, i9b) + ''' + loop = self.interpret(ops, [7, 9, 9 ,9, 9, 9, 9, 9, 9, 8, 4]) + assert self.getints(11) == [5, 7, 9, 9, 9, 9, 9, 9, 9, 9, 8] + clt = loop._jitcelltoken.compiled_loop_token + assert clt.frame_depth == self.expected_frame_depth(1, 6) def test_two_calls(self): ops = ''' @@ -634,7 +648,7 @@ loop = self.interpret(ops, [4, 7, 9, 9 ,9, 9, 9, 9, 9, 9]) assert self.getints(10) == [5*7, 7, 9, 9, 9, 9, 9, 9, 9, 9] clt = loop._jitcelltoken.compiled_loop_token - assert clt.param_depth == self.expected_param_depth(2) + assert clt.frame_depth == self.expected_frame_depth(2, 5) def test_call_many_arguments(self): # NB: The first and last arguments in the call are constants. This @@ -648,25 +662,31 @@ loop = self.interpret(ops, [2, 3, 4, 5, 6, 7, 8, 9]) assert self.getint(0) == 55 clt = loop._jitcelltoken.compiled_loop_token - assert clt.param_depth == self.expected_param_depth(10) + assert clt.frame_depth == self.expected_frame_depth(10) def test_bridge_calls_1(self): ops = ''' [i0, i1] i2 = call(ConstClass(f1ptr), i0, descr=f1_calldescr) - guard_value(i2, 0, descr=fdescr1) [i2, i1] + guard_value(i2, 0, descr=fdescr1) [i2, i0, i1] finish(i1) ''' loop = self.interpret(ops, [4, 7]) assert self.getint(0) == 5 + clt = loop._jitcelltoken.compiled_loop_token + orgdepth = clt.frame_depth + assert orgdepth == self.expected_frame_depth(1, 2) + ops = ''' - [i2, i1] + [i2, i0, i1] i3 = call(ConstClass(f2ptr), i2, i1, descr=f2_calldescr) - finish(i3, descr=fdescr2) + finish(i3, i0, descr=fdescr2) ''' bridge = self.attach_bridge(ops, loop, -2) - assert loop.operations[-2].getdescr()._x86_bridge_param_depth == self.expected_param_depth(2) + assert clt.frame_depth == max(orgdepth, self.expected_frame_depth(2, 2)) + assert loop.operations[-2].getdescr()._x86_bridge_frame_depth == \ + self.expected_frame_depth(2, 2) self.run(loop, 4, 7) assert self.getint(0) == 5*7 @@ -676,10 +696,14 @@ [i0, i1] i2 = call(ConstClass(f2ptr), i0, i1, descr=f2_calldescr) guard_value(i2, 0, descr=fdescr1) [i2] - finish(i1) + finish(i2) ''' loop = self.interpret(ops, [4, 7]) assert self.getint(0) == 4*7 + clt = loop._jitcelltoken.compiled_loop_token + orgdepth = clt.frame_depth + assert orgdepth == self.expected_frame_depth(2) + ops = ''' [i2] i3 = call(ConstClass(f1ptr), i2, descr=f1_calldescr) @@ -687,7 +711,9 @@ ''' bridge = self.attach_bridge(ops, loop, -2) - assert loop.operations[-2].getdescr()._x86_bridge_param_depth == self.expected_param_depth(2) + assert clt.frame_depth == max(orgdepth, self.expected_frame_depth(1)) + assert loop.operations[-2].getdescr()._x86_bridge_frame_depth == \ + self.expected_frame_depth(1) self.run(loop, 4, 7) assert self.getint(0) == 29 diff --git a/pypy/jit/backend/x86/test/test_runner.py b/pypy/jit/backend/x86/test/test_runner.py --- a/pypy/jit/backend/x86/test/test_runner.py +++ b/pypy/jit/backend/x86/test/test_runner.py @@ -371,7 +371,7 @@ operations = [ ResOperation(rop.LABEL, [i0], None, descr=targettoken), - ResOperation(rop.DEBUG_MERGE_POINT, [FakeString("hello"), 0], None), + ResOperation(rop.DEBUG_MERGE_POINT, [FakeString("hello"), 0, 0], None), ResOperation(rop.INT_ADD, [i0, ConstInt(1)], i1), ResOperation(rop.INT_LE, [i1, ConstInt(9)], i2), ResOperation(rop.GUARD_TRUE, [i2], None, descr=faildescr1), @@ -390,7 +390,7 @@ bridge = [ ResOperation(rop.INT_LE, [i1b, ConstInt(19)], i3), ResOperation(rop.GUARD_TRUE, [i3], None, descr=faildescr2), - ResOperation(rop.DEBUG_MERGE_POINT, [FakeString("bye"), 0], None), + ResOperation(rop.DEBUG_MERGE_POINT, [FakeString("bye"), 0, 0], None), ResOperation(rop.JUMP, [i1b], None, descr=targettoken), ] bridge[1].setfailargs([i1b]) @@ -531,12 +531,12 @@ loop = """ [i0] label(i0, descr=preambletoken) - debug_merge_point('xyz', 0) + debug_merge_point('xyz', 0, 0) i1 = int_add(i0, 1) i2 = int_ge(i1, 10) guard_false(i2) [] label(i1, descr=targettoken) - debug_merge_point('xyz', 0) + debug_merge_point('xyz', 0, 0) i11 = int_add(i1, 1) i12 = int_ge(i11, 10) guard_false(i12) [] @@ -569,7 +569,7 @@ loop = """ [i0] label(i0, descr=targettoken) - debug_merge_point('xyz', 0) + debug_merge_point('xyz', 0, 0) i1 = int_add(i0, 1) i2 = int_ge(i1, 10) guard_false(i2) [] diff --git a/pypy/jit/backend/x86/test/test_rx86_32_auto_encoding.py b/pypy/jit/backend/x86/test/test_rx86_32_auto_encoding.py --- a/pypy/jit/backend/x86/test/test_rx86_32_auto_encoding.py +++ b/pypy/jit/backend/x86/test/test_rx86_32_auto_encoding.py @@ -182,6 +182,12 @@ filename = str(testdir.join(FILENAME % methname)) g = open(inputname, 'w') g.write('\x09.string "%s"\n' % BEGIN_TAG) + # + if instrname == 'MOVD' and self.WORD == 8: + instrname = 'MOVQ' + if argmodes == 'xb': + py.test.skip('"as" uses an undocumented alternate encoding??') + # for args in args_lists: suffix = "" ## all = instr.as_all_suffixes @@ -229,9 +235,6 @@ # movq $xxx, %rax => movl $xxx, %eax suffix = 'l' ops[1] = reduce_to_32bit(ops[1]) - if instrname.lower() == 'movd': - ops[0] = reduce_to_32bit(ops[0]) - ops[1] = reduce_to_32bit(ops[1]) # op = '\t%s%s %s%s' % (instrname.lower(), suffix, ', '.join(ops), following) diff --git a/pypy/jit/backend/x86/test/test_zmath.py b/pypy/jit/backend/x86/test/test_zmath.py --- a/pypy/jit/backend/x86/test/test_zmath.py +++ b/pypy/jit/backend/x86/test/test_zmath.py @@ -6,6 +6,8 @@ from pypy.translator.c.test.test_genc import compile from pypy.jit.backend.x86.support import ensure_sse2_floats from pypy.rlib import rfloat +from pypy.rlib.unroll import unrolling_iterable +from pypy.rlib.debug import debug_print def get_test_case((fnname, args, expected)): @@ -16,16 +18,32 @@ expect_valueerror = (expected == ValueError) expect_overflowerror = (expected == OverflowError) check = test_direct.get_tester(expected) + unroll_args = unrolling_iterable(args) # def testfn(): + debug_print('calling', fnname, 'with arguments:') + for arg in unroll_args: + debug_print('\t', arg) try: got = fn(*args) except ValueError: - return expect_valueerror + if expect_valueerror: + return True + else: + debug_print('unexpected ValueError!') + return False except OverflowError: - return expect_overflowerror + if expect_overflowerror: + return True + else: + debug_print('unexpected OverflowError!') + return False else: - return check(got) + if check(got): + return True + else: + debug_print('unexpected result:', got) + return False # testfn.func_name = 'test_' + fnname return testfn diff --git a/pypy/jit/backend/x86/tool/viewcode.py b/pypy/jit/backend/x86/tool/viewcode.py --- a/pypy/jit/backend/x86/tool/viewcode.py +++ b/pypy/jit/backend/x86/tool/viewcode.py @@ -34,7 +34,7 @@ # I am porting it in a lazy fashion... See py-utils/xam.py if sys.platform == "win32": - XXX # lots more in Psyco + pass # lots more in Psyco def machine_code_dump(data, originaddr, backend_name, label_list=None): objdump_backend_option = { diff --git a/pypy/jit/codewriter/jtransform.py b/pypy/jit/codewriter/jtransform.py --- a/pypy/jit/codewriter/jtransform.py +++ b/pypy/jit/codewriter/jtransform.py @@ -291,6 +291,12 @@ op1 = SpaceOperation('-live-', [], None) return [op, op1] + def _noop_rewrite(self, op): + return op + + rewrite_op_convert_float_bytes_to_longlong = _noop_rewrite + rewrite_op_convert_longlong_bytes_to_float = _noop_rewrite + # ---------- # Various kinds of calls @@ -365,7 +371,7 @@ def handle_builtin_call(self, op): oopspec_name, args = support.decode_builtin_call(op) # dispatch to various implementations depending on the oopspec_name - if oopspec_name.startswith('list.') or oopspec_name == 'newlist': + if oopspec_name.startswith('list.') or oopspec_name.startswith('newlist'): prepare = self._handle_list_call elif oopspec_name.startswith('stroruni.'): prepare = self._handle_stroruni_call @@ -1494,6 +1500,14 @@ arraydescr, v_length], op.result) + def do_resizable_newlist_hint(self, op, args, arraydescr, lengthdescr, + itemsdescr, structdescr): + v_hint = self._get_initial_newlist_length(op, args) + return SpaceOperation('newlist_hint', + [structdescr, lengthdescr, itemsdescr, + arraydescr, v_hint], + op.result) + def do_resizable_list_getitem(self, op, args, arraydescr, lengthdescr, itemsdescr, structdescr): v_index, extraop = self._prepare_list_getset(op, lengthdescr, args, diff --git a/pypy/jit/codewriter/support.py b/pypy/jit/codewriter/support.py --- a/pypy/jit/codewriter/support.py +++ b/pypy/jit/codewriter/support.py @@ -144,6 +144,10 @@ _ll_1_newlist.need_result_type = True _ll_2_newlist.need_result_type = True +def _ll_1_newlist_hint(LIST, hint): + return LIST.ll_newlist_hint(hint) +_ll_1_newlist_hint.need_result_type = True + def _ll_1_list_len(l): return l.ll_length() def _ll_2_list_getitem(l, index): diff --git a/pypy/jit/codewriter/test/test_flatten.py b/pypy/jit/codewriter/test/test_flatten.py --- a/pypy/jit/codewriter/test/test_flatten.py +++ b/pypy/jit/codewriter/test/test_flatten.py @@ -968,6 +968,23 @@ int_return %i2 """, transform=True) + def test_convert_float_bytes(self): + from pypy.rlib.longlong2float import float2longlong, longlong2float + def f(x): + ll = float2longlong(x) + return longlong2float(ll) + if longlong.is_64_bit: + tmp_var = "%i0" + result_var = "%f1" + else: + tmp_var = "%f1" + result_var = "%f2" + self.encoding_test(f, [25.0], """ + convert_float_bytes_to_longlong %%f0 -> %(tmp_var)s + convert_longlong_bytes_to_float %(tmp_var)s -> %(result_var)s + float_return %(result_var)s + """ % {"result_var": result_var, "tmp_var": tmp_var}, transform=True) + def check_force_cast(FROM, TO, operations, value): """Check that the test is correctly written...""" diff --git a/pypy/jit/codewriter/test/test_longlong.py b/pypy/jit/codewriter/test/test_longlong.py --- a/pypy/jit/codewriter/test/test_longlong.py +++ b/pypy/jit/codewriter/test/test_longlong.py @@ -1,6 +1,6 @@ import py, sys -from pypy.rlib.rarithmetic import r_longlong, intmask +from pypy.rlib.rarithmetic import r_longlong, intmask, is_valid_int from pypy.objspace.flow.model import SpaceOperation, Variable, Constant from pypy.objspace.flow.model import Block, Link from pypy.translator.unsimplify import varoftype @@ -32,7 +32,7 @@ def test_functions(): xll = longlong.getfloatstorage(3.5) assert longlong.getrealfloat(xll) == 3.5 - assert isinstance(longlong.gethash(xll), int) + assert is_valid_int(longlong.gethash(xll)) class TestLongLong: diff --git a/pypy/jit/metainterp/blackhole.py b/pypy/jit/metainterp/blackhole.py --- a/pypy/jit/metainterp/blackhole.py +++ b/pypy/jit/metainterp/blackhole.py @@ -1,15 +1,16 @@ +from pypy.jit.codewriter import heaptracker, longlong +from pypy.jit.codewriter.jitcode import JitCode, SwitchDictDescr +from pypy.jit.metainterp.compile import ResumeAtPositionDescr +from pypy.jit.metainterp.jitexc import JitException, get_llexception, reraise +from pypy.rlib import longlong2float +from pypy.rlib.debug import debug_start, debug_stop, ll_assert, make_sure_not_resized +from pypy.rlib.objectmodel import we_are_translated +from pypy.rlib.rarithmetic import intmask, LONG_BIT, r_uint, ovfcheck +from pypy.rlib.rtimer import read_timestamp from pypy.rlib.unroll import unrolling_iterable -from pypy.rlib.rtimer import read_timestamp -from pypy.rlib.rarithmetic import intmask, LONG_BIT, r_uint, ovfcheck -from pypy.rlib.objectmodel import we_are_translated -from pypy.rlib.debug import debug_start, debug_stop, ll_assert -from pypy.rlib.debug import make_sure_not_resized from pypy.rpython.lltypesystem import lltype, llmemory, rclass from pypy.rpython.lltypesystem.lloperation import llop -from pypy.jit.codewriter.jitcode import JitCode, SwitchDictDescr -from pypy.jit.codewriter import heaptracker, longlong -from pypy.jit.metainterp.jitexc import JitException, get_llexception, reraise -from pypy.jit.metainterp.compile import ResumeAtPositionDescr + def arguments(*argtypes, **kwds): resulttype = kwds.pop('returns', None) @@ -20,6 +21,9 @@ return function return decorate +LONGLONG_TYPECODE = 'i' if longlong.is_64_bit else 'f' + + class LeaveFrame(JitException): pass @@ -663,6 +667,16 @@ a = float(a) return longlong.getfloatstorage(a) + @arguments("f", returns=LONGLONG_TYPECODE) + def bhimpl_convert_float_bytes_to_longlong(a): + a = longlong.getrealfloat(a) + return longlong2float.float2longlong(a) + + @arguments(LONGLONG_TYPECODE, returns="f") + def bhimpl_convert_longlong_bytes_to_float(a): + a = longlong2float.longlong2float(a) + return longlong.getfloatstorage(a) + # ---------- # control flow operations @@ -982,6 +996,15 @@ cpu.bh_setfield_gc_r(result, itemsdescr, items) return result + @arguments("cpu", "d", "d", "d", "d", "i", returns="r") + def bhimpl_newlist_hint(cpu, structdescr, lengthdescr, itemsdescr, + arraydescr, lengthhint): + result = cpu.bh_new(structdescr) + cpu.bh_setfield_gc_i(result, lengthdescr, 0) + items = cpu.bh_new_array(arraydescr, lengthhint) + cpu.bh_setfield_gc_r(result, itemsdescr, items) + return result + @arguments("cpu", "r", "d", "d", "i", returns="i") def bhimpl_getlistitem_gc_i(cpu, lst, itemsdescr, arraydescr, index): items = cpu.bh_getfield_gc_r(lst, itemsdescr) @@ -1176,14 +1199,14 @@ def bhimpl_getinteriorfield_gc_f(cpu, array, index, descr): return cpu.bh_getinteriorfield_gc_f(array, index, descr) - @arguments("cpu", "r", "i", "d", "i") - def bhimpl_setinteriorfield_gc_i(cpu, array, index, descr, value): + @arguments("cpu", "r", "i", "i", "d") + def bhimpl_setinteriorfield_gc_i(cpu, array, index, value, descr): cpu.bh_setinteriorfield_gc_i(array, index, descr, value) - @arguments("cpu", "r", "i", "d", "r") - def bhimpl_setinteriorfield_gc_r(cpu, array, index, descr, value): + @arguments("cpu", "r", "i", "r", "d") + def bhimpl_setinteriorfield_gc_r(cpu, array, index, value, descr): cpu.bh_setinteriorfield_gc_r(array, index, descr, value) - @arguments("cpu", "r", "i", "d", "f") - def bhimpl_setinteriorfield_gc_f(cpu, array, index, descr, value): + @arguments("cpu", "r", "i", "f", "d") + def bhimpl_setinteriorfield_gc_f(cpu, array, index, value, descr): cpu.bh_setinteriorfield_gc_f(array, index, descr, value) @arguments("cpu", "r", "d", returns="i") @@ -1300,7 +1323,7 @@ def bhimpl_copyunicodecontent(cpu, src, dst, srcstart, dststart, length): cpu.bh_copyunicodecontent(src, dst, srcstart, dststart, length) - @arguments(returns=(longlong.is_64_bit and "i" or "f")) + @arguments(returns=LONGLONG_TYPECODE) def bhimpl_ll_read_timestamp(): return read_timestamp() diff --git a/pypy/jit/metainterp/executor.py b/pypy/jit/metainterp/executor.py --- a/pypy/jit/metainterp/executor.py +++ b/pypy/jit/metainterp/executor.py @@ -2,7 +2,7 @@ """ from pypy.rpython.lltypesystem import lltype, rstr -from pypy.rlib.rarithmetic import ovfcheck, r_longlong +from pypy.rlib.rarithmetic import ovfcheck, r_longlong, is_valid_int from pypy.rlib.rtimer import read_timestamp from pypy.rlib.unroll import unrolling_iterable from pypy.jit.metainterp.history import BoxInt, BoxPtr, BoxFloat, check_descr @@ -248,7 +248,7 @@ def do_read_timestamp(cpu, _): x = read_timestamp() if longlong.is_64_bit: - assert isinstance(x, int) # 64-bit + assert is_valid_int(x) # 64-bit return BoxInt(x) else: assert isinstance(x, r_longlong) # 32-bit diff --git a/pypy/jit/metainterp/graphpage.py b/pypy/jit/metainterp/graphpage.py --- a/pypy/jit/metainterp/graphpage.py +++ b/pypy/jit/metainterp/graphpage.py @@ -169,9 +169,9 @@ if op.getopnum() == rop.DEBUG_MERGE_POINT: jd_sd = self.metainterp_sd.jitdrivers_sd[op.getarg(0).getint()] if jd_sd._get_printable_location_ptr: - s = jd_sd.warmstate.get_location_str(op.getarglist()[2:]) + s = jd_sd.warmstate.get_location_str(op.getarglist()[3:]) s = s.replace(',', '.') # we use comma for argument splitting - op_repr = "debug_merge_point(%d, '%s')" % (op.getarg(1).getint(), s) + op_repr = "debug_merge_point(%d, %d, '%s')" % (op.getarg(1).getint(), op.getarg(2).getint(), s) lines.append(op_repr) if is_interesting_guard(op): tgt = op.getdescr()._debug_suboperations[0] diff --git a/pypy/jit/metainterp/history.py b/pypy/jit/metainterp/history.py --- a/pypy/jit/metainterp/history.py +++ b/pypy/jit/metainterp/history.py @@ -4,7 +4,8 @@ from pypy.rpython.ootypesystem import ootype from pypy.rlib.objectmodel import we_are_translated, Symbolic from pypy.rlib.objectmodel import compute_unique_id -from pypy.rlib.rarithmetic import r_int64 +from pypy.rlib.rarithmetic import r_int64, is_valid_int + from pypy.conftest import option from pypy.jit.metainterp.resoperation import ResOperation, rop @@ -213,7 +214,7 @@ def __init__(self, value): if not we_are_translated(): - if isinstance(value, int): + if is_valid_int(value): value = int(value) # bool -> int else: assert isinstance(value, Symbolic) @@ -448,7 +449,7 @@ def __init__(self, value=0): if not we_are_translated(): - if isinstance(value, int): + if is_valid_int(value): value = int(value) # bool -> int else: assert isinstance(value, Symbolic) diff --git a/pypy/jit/metainterp/logger.py b/pypy/jit/metainterp/logger.py --- a/pypy/jit/metainterp/logger.py +++ b/pypy/jit/metainterp/logger.py @@ -110,9 +110,9 @@ def repr_of_resop(self, op, ops_offset=None): if op.getopnum() == rop.DEBUG_MERGE_POINT: jd_sd = self.metainterp_sd.jitdrivers_sd[op.getarg(0).getint()] - s = jd_sd.warmstate.get_location_str(op.getarglist()[2:]) + s = jd_sd.warmstate.get_location_str(op.getarglist()[3:]) s = s.replace(',', '.') # we use comma for argument splitting - return "debug_merge_point(%d, '%s')" % (op.getarg(1).getint(), s) + return "debug_merge_point(%d, %d, '%s')" % (op.getarg(1).getint(), op.getarg(2).getint(), s) if ops_offset is None: offset = -1 else: @@ -149,7 +149,7 @@ if target_token.exported_state: for op in target_token.exported_state.inputarg_setup_ops: debug_print(' ' + self.repr_of_resop(op)) - + def _log_operations(self, inputargs, operations, ops_offset): if not have_debug_prints(): return diff --git a/pypy/jit/metainterp/optimizeopt/__init__.py b/pypy/jit/metainterp/optimizeopt/__init__.py --- a/pypy/jit/metainterp/optimizeopt/__init__.py +++ b/pypy/jit/metainterp/optimizeopt/__init__.py @@ -9,7 +9,7 @@ from pypy.jit.metainterp.optimizeopt.simplify import OptSimplify from pypy.jit.metainterp.optimizeopt.pure import OptPure from pypy.jit.metainterp.optimizeopt.earlyforce import OptEarlyForce -from pypy.rlib.jit import PARAMETERS +from pypy.rlib.jit import PARAMETERS, ENABLE_ALL_OPTS from pypy.rlib.unroll import unrolling_iterable from pypy.rlib.debug import debug_start, debug_stop, debug_print @@ -30,6 +30,9 @@ ALL_OPTS_LIST = [name for name, _ in ALL_OPTS] ALL_OPTS_NAMES = ':'.join([name for name, _ in ALL_OPTS]) +assert ENABLE_ALL_OPTS == ALL_OPTS_NAMES, ( + 'please fix rlib/jit.py to say ENABLE_ALL_OPTS = %r' % (ALL_OPTS_NAMES,)) + def build_opt_chain(metainterp_sd, enable_opts): config = metainterp_sd.config optimizations = [] diff --git a/pypy/jit/metainterp/optimizeopt/intutils.py b/pypy/jit/metainterp/optimizeopt/intutils.py --- a/pypy/jit/metainterp/optimizeopt/intutils.py +++ b/pypy/jit/metainterp/optimizeopt/intutils.py @@ -1,10 +1,9 @@ -from pypy.rlib.rarithmetic import ovfcheck, LONG_BIT +from pypy.rlib.rarithmetic import ovfcheck, LONG_BIT, maxint, is_valid_int from pypy.rlib.objectmodel import we_are_translated from pypy.jit.metainterp.resoperation import rop, ResOperation from pypy.jit.metainterp.history import BoxInt, ConstInt -import sys -MAXINT = sys.maxint -MININT = -sys.maxint - 1 +MAXINT = maxint +MININT = -maxint - 1 class IntBound(object): _attrs_ = ('has_upper', 'has_lower', 'upper', 'lower') @@ -16,8 +15,8 @@ self.lower = lower # check for unexpected overflows: if not we_are_translated(): - assert type(upper) is not long - assert type(lower) is not long + assert type(upper) is not long or is_valid_int(upper) + assert type(lower) is not long or is_valid_int(lower) # Returns True if the bound was updated def make_le(self, other): diff --git a/pypy/jit/metainterp/optimizeopt/test/test_optimizebasic.py b/pypy/jit/metainterp/optimizeopt/test/test_optimizebasic.py --- a/pypy/jit/metainterp/optimizeopt/test/test_optimizebasic.py +++ b/pypy/jit/metainterp/optimizeopt/test/test_optimizebasic.py @@ -5031,6 +5031,42 @@ """ self.optimize_loop(ops, expected) + def test_str_copy_virtual(self): + ops = """ + [i0] + p0 = newstr(8) + strsetitem(p0, 0, i0) + strsetitem(p0, 1, i0) + strsetitem(p0, 2, i0) + strsetitem(p0, 3, i0) + strsetitem(p0, 4, i0) + strsetitem(p0, 5, i0) + strsetitem(p0, 6, i0) + strsetitem(p0, 7, i0) + p1 = newstr(12) + copystrcontent(p0, p1, 0, 0, 8) + strsetitem(p1, 8, 3) + strsetitem(p1, 9, 0) + strsetitem(p1, 10, 0) + strsetitem(p1, 11, 0) + finish(p1) + """ + expected = """ + [i0] + p1 = newstr(12) + strsetitem(p1, 0, i0) + strsetitem(p1, 1, i0) + strsetitem(p1, 2, i0) + strsetitem(p1, 3, i0) + strsetitem(p1, 4, i0) + strsetitem(p1, 5, i0) + strsetitem(p1, 6, i0) + strsetitem(p1, 7, i0) + strsetitem(p1, 8, 3) + finish(p1) + """ + self.optimize_strunicode_loop(ops, expected) + class TestLLtype(BaseTestOptimizeBasic, LLtypeMixin): pass diff --git a/pypy/jit/metainterp/optimizeopt/unroll.py b/pypy/jit/metainterp/optimizeopt/unroll.py --- a/pypy/jit/metainterp/optimizeopt/unroll.py +++ b/pypy/jit/metainterp/optimizeopt/unroll.py @@ -9,7 +9,6 @@ from pypy.jit.metainterp.inliner import Inliner from pypy.jit.metainterp.resoperation import rop, ResOperation from pypy.jit.metainterp.resume import Snapshot -from pypy.rlib.debug import debug_print import sys, os # FIXME: Introduce some VirtualOptimizer super class instead @@ -121,9 +120,9 @@ limit = self.optimizer.metainterp_sd.warmrunnerdesc.memory_manager.retrace_limit if cell_token.retraced_count < limit: cell_token.retraced_count += 1 - debug_print('Retracing (%d/%d)' % (cell_token.retraced_count, limit)) + #debug_print('Retracing (%d/%d)' % (cell_token.retraced_count, limit)) else: - debug_print("Retrace count reached, jumping to preamble") + #debug_print("Retrace count reached, jumping to preamble") assert cell_token.target_tokens[0].virtual_state is None jumpop.setdescr(cell_token.target_tokens[0]) self.optimizer.send_extra_operation(jumpop) @@ -273,9 +272,9 @@ not newvalue.is_constant(): op = ResOperation(rop.SAME_AS, [op.result], newresult) self.optimizer._newoperations.append(op) - if self.optimizer.loop.logops: - debug_print(' Falling back to add extra: ' + - self.optimizer.loop.logops.repr_of_resop(op)) + #if self.optimizer.loop.logops: + # debug_print(' Falling back to add extra: ' + + # self.optimizer.loop.logops.repr_of_resop(op)) self.optimizer.flush() self.optimizer.emitting_dissabled = False @@ -341,8 +340,8 @@ if i == len(newoperations): while j < len(jumpargs): a = jumpargs[j] - if self.optimizer.loop.logops: - debug_print('J: ' + self.optimizer.loop.logops.repr_of_arg(a)) + #if self.optimizer.loop.logops: + # debug_print('J: ' + self.optimizer.loop.logops.repr_of_arg(a)) self.import_box(a, inputargs, short_jumpargs, jumpargs) j += 1 else: @@ -353,11 +352,11 @@ if op.is_guard(): args = args + op.getfailargs() - if self.optimizer.loop.logops: - debug_print('OP: ' + self.optimizer.loop.logops.repr_of_resop(op)) + #if self.optimizer.loop.logops: + # debug_print('OP: ' + self.optimizer.loop.logops.repr_of_resop(op)) for a in args: - if self.optimizer.loop.logops: - debug_print('A: ' + self.optimizer.loop.logops.repr_of_arg(a)) + #if self.optimizer.loop.logops: + # debug_print('A: ' + self.optimizer.loop.logops.repr_of_arg(a)) self.import_box(a, inputargs, short_jumpargs, jumpargs) i += 1 newoperations = self.optimizer.get_newoperations() @@ -370,18 +369,18 @@ # that is compatible with the virtual state at the start of the loop modifier = VirtualStateAdder(self.optimizer) final_virtual_state = modifier.get_virtual_state(original_jumpargs) - debug_start('jit-log-virtualstate') - virtual_state.debug_print('Closed loop with ') + #debug_start('jit-log-virtualstate') + #virtual_state.debug_print('Closed loop with ') bad = {} if not virtual_state.generalization_of(final_virtual_state, bad): # We ended up with a virtual state that is not compatible # and we are thus unable to jump to the start of the loop - final_virtual_state.debug_print("Bad virtual state at end of loop, ", - bad) - debug_stop('jit-log-virtualstate') + #final_virtual_state.debug_print("Bad virtual state at end of loop, ", + # bad) + #debug_stop('jit-log-virtualstate') raise InvalidLoop - debug_stop('jit-log-virtualstate') + #debug_stop('jit-log-virtualstate') maxguards = self.optimizer.metainterp_sd.warmrunnerdesc.memory_manager.max_retrace_guards if self.optimizer.emitted_guards > maxguards: @@ -444,9 +443,9 @@ self.ensure_short_op_emitted(self.short_boxes.producer(a), optimizer, seen) - if self.optimizer.loop.logops: - debug_print(' Emitting short op: ' + - self.optimizer.loop.logops.repr_of_resop(op)) + #if self.optimizer.loop.logops: + # debug_print(' Emitting short op: ' + + # self.optimizer.loop.logops.repr_of_resop(op)) optimizer.send_extra_operation(op) seen[op.result] = True @@ -527,8 +526,8 @@ args = jumpop.getarglist() modifier = VirtualStateAdder(self.optimizer) virtual_state = modifier.get_virtual_state(args) - debug_start('jit-log-virtualstate') - virtual_state.debug_print("Looking for ") + #debug_start('jit-log-virtualstate') + #virtual_state.debug_print("Looking for ") for target in cell_token.target_tokens: if not target.virtual_state: @@ -537,10 +536,10 @@ extra_guards = [] bad = {} - debugmsg = 'Did not match ' + #debugmsg = 'Did not match ' if target.virtual_state.generalization_of(virtual_state, bad): ok = True - debugmsg = 'Matched ' + #debugmsg = 'Matched ' else: try: cpu = self.optimizer.cpu @@ -549,13 +548,13 @@ extra_guards) ok = True - debugmsg = 'Guarded to match ' + #debugmsg = 'Guarded to match ' except InvalidLoop: pass - target.virtual_state.debug_print(debugmsg, bad) + #target.virtual_state.debug_print(debugmsg, bad) if ok: - debug_stop('jit-log-virtualstate') + #debug_stop('jit-log-virtualstate') values = [self.getvalue(arg) for arg in jumpop.getarglist()] @@ -576,13 +575,13 @@ newop = inliner.inline_op(shop) self.optimizer.send_extra_operation(newop) except InvalidLoop: - debug_print("Inlining failed unexpectedly", - "jumping to preamble instead") + #debug_print("Inlining failed unexpectedly", + # "jumping to preamble instead") assert cell_token.target_tokens[0].virtual_state is None jumpop.setdescr(cell_token.target_tokens[0]) self.optimizer.send_extra_operation(jumpop) return True - debug_stop('jit-log-virtualstate') + #debug_stop('jit-log-virtualstate') return False class ValueImporter(object): diff --git a/pypy/jit/metainterp/optimizeopt/virtualstate.py b/pypy/jit/metainterp/optimizeopt/virtualstate.py --- a/pypy/jit/metainterp/optimizeopt/virtualstate.py +++ b/pypy/jit/metainterp/optimizeopt/virtualstate.py @@ -681,13 +681,14 @@ self.synthetic[op] = True def debug_print(self, logops): - debug_start('jit-short-boxes') - for box, op in self.short_boxes.items(): - if op: - debug_print(logops.repr_of_arg(box) + ': ' + logops.repr_of_resop(op)) - else: - debug_print(logops.repr_of_arg(box) + ': None') - debug_stop('jit-short-boxes') + if 0: + debug_start('jit-short-boxes') + for box, op in self.short_boxes.items(): + if op: + debug_print(logops.repr_of_arg(box) + ': ' + logops.repr_of_resop(op)) + else: + debug_print(logops.repr_of_arg(box) + ': None') + debug_stop('jit-short-boxes') def operations(self): if not we_are_translated(): # For tests diff --git a/pypy/jit/metainterp/optimizeopt/vstring.py b/pypy/jit/metainterp/optimizeopt/vstring.py --- a/pypy/jit/metainterp/optimizeopt/vstring.py +++ b/pypy/jit/metainterp/optimizeopt/vstring.py @@ -10,6 +10,8 @@ from pypy.rlib.unroll import unrolling_iterable from pypy.rpython import annlowlevel from pypy.rpython.lltypesystem import lltype, rstr +from pypy.rlib.rarithmetic import is_valid_int + class StrOrUnicode(object): @@ -505,14 +507,23 @@ if length.is_constant() and length.box.getint() == 0: return - copy_str_content(self, - src.force_box(self), - dst.force_box(self), - srcstart.force_box(self), - dststart.force_box(self), - length.force_box(self), - mode, need_next_offset=False - ) + elif (src.is_virtual() and dst.is_virtual() and srcstart.is_constant() and + dststart.is_constant() and length.is_constant()): + + src_start = srcstart.force_box(self).getint() + dst_start = dststart.force_box(self).getint() + for index in range(length.force_box(self).getint()): + vresult = self.strgetitem(src, optimizer.ConstantValue(ConstInt(index + src_start)), mode) + dst.setitem(index + dst_start, vresult) + else: + copy_str_content(self, + src.force_box(self), + dst.force_box(self), + srcstart.force_box(self), + dststart.force_box(self), + length.force_box(self), + mode, need_next_offset=False + ) def optimize_CALL(self, op): # dispatch based on 'oopspecindex' to a method that handles @@ -721,7 +732,7 @@ for name in dir(OptString): if name.startswith(prefix): value = getattr(EffectInfo, 'OS_' + name[len(prefix):]) - assert isinstance(value, int) and value != 0 + assert is_valid_int(value) and value != 0 result.append((value, getattr(OptString, name))) return unrolling_iterable(result) opt_call_oopspec_ops = _findall_call_oopspec() diff --git a/pypy/jit/metainterp/pyjitpl.py b/pypy/jit/metainterp/pyjitpl.py --- a/pypy/jit/metainterp/pyjitpl.py +++ b/pypy/jit/metainterp/pyjitpl.py @@ -223,6 +223,8 @@ 'cast_float_to_singlefloat', 'cast_singlefloat_to_float', 'float_neg', 'float_abs', 'cast_ptr_to_int', 'cast_int_to_ptr', + 'convert_float_bytes_to_longlong', + 'convert_longlong_bytes_to_float', ]: exec py.code.Source(''' @arguments("box") @@ -509,6 +511,15 @@ self._opimpl_setfield_gc_any(sbox, itemsdescr, abox) return sbox + @arguments("descr", "descr", "descr", "descr", "box") + def opimpl_newlist_hint(self, structdescr, lengthdescr, itemsdescr, + arraydescr, sizehintbox): + sbox = self.opimpl_new(structdescr) + self._opimpl_setfield_gc_any(sbox, lengthdescr, history.CONST_FALSE) + abox = self.opimpl_new_array(arraydescr, sizehintbox) + self._opimpl_setfield_gc_any(sbox, itemsdescr, abox) + return sbox + @arguments("box", "descr", "descr", "box") def _opimpl_getlistitem_gc_any(self, listbox, itemsdescr, arraydescr, indexbox): @@ -974,9 +985,11 @@ any_operation = len(self.metainterp.history.operations) > 0 jitdriver_sd = self.metainterp.staticdata.jitdrivers_sd[jdindex] self.verify_green_args(jitdriver_sd, greenboxes) - self.debug_merge_point(jitdriver_sd, jdindex, self.metainterp.portal_call_depth, + self.debug_merge_point(jitdriver_sd, jdindex, + self.metainterp.portal_call_depth, + self.metainterp.call_ids[-1], greenboxes) - + if self.metainterp.seen_loop_header_for_jdindex < 0: if not any_operation: return @@ -1028,11 +1041,11 @@ assembler_call=True) raise ChangeFrame - def debug_merge_point(self, jitdriver_sd, jd_index, portal_call_depth, greenkey): + def debug_merge_point(self, jitdriver_sd, jd_index, portal_call_depth, current_call_id, greenkey): # debugging: produce a DEBUG_MERGE_POINT operation loc = jitdriver_sd.warmstate.get_location_str(greenkey) debug_print(loc) - args = [ConstInt(jd_index), ConstInt(portal_call_depth)] + greenkey + args = [ConstInt(jd_index), ConstInt(portal_call_depth), ConstInt(current_call_id)] + greenkey self.metainterp.history.record(rop.DEBUG_MERGE_POINT, args, None) @arguments("box", "label") @@ -1574,11 +1587,14 @@ self.call_pure_results = args_dict_box() self.heapcache = HeapCache() + self.call_ids = [] + self.current_call_id = 0 + def retrace_needed(self, trace): self.partial_trace = trace self.retracing_from = len(self.history.operations) - 1 self.heapcache.reset() - + def perform_call(self, jitcode, boxes, greenkey=None): # causes the metainterp to enter the given subfunction @@ -1592,6 +1608,8 @@ def newframe(self, jitcode, greenkey=None): if jitcode.is_portal: self.portal_call_depth += 1 + self.call_ids.append(self.current_call_id) + self.current_call_id += 1 if greenkey is not None and self.is_main_jitcode(jitcode): self.portal_trace_positions.append( (greenkey, len(self.history.operations))) @@ -1608,6 +1626,7 @@ jitcode = frame.jitcode if jitcode.is_portal: self.portal_call_depth -= 1 + self.call_ids.pop() if frame.greenkey is not None and self.is_main_jitcode(jitcode): self.portal_trace_positions.append( (None, len(self.history.operations))) @@ -1976,7 +1995,7 @@ # Found! Compile it as a loop. # raises in case it works -- which is the common case if self.partial_trace: - if start != self.retracing_from: + if start != self.retracing_from: raise SwitchToBlackhole(ABORT_BAD_LOOP) # For now self.compile_loop(original_boxes, live_arg_boxes, start, resumedescr) # creation of the loop was cancelled! @@ -2085,7 +2104,7 @@ if not token.target_tokens: return None return token - + def compile_loop(self, original_boxes, live_arg_boxes, start, resume_at_jump_descr): num_green_args = self.jitdriver_sd.num_green_args greenkey = original_boxes[:num_green_args] diff --git a/pypy/jit/metainterp/resoperation.py b/pypy/jit/metainterp/resoperation.py --- a/pypy/jit/metainterp/resoperation.py +++ b/pypy/jit/metainterp/resoperation.py @@ -419,6 +419,8 @@ 'CAST_INT_TO_FLOAT/1', # need some messy code in the backend 'CAST_FLOAT_TO_SINGLEFLOAT/1', 'CAST_SINGLEFLOAT_TO_FLOAT/1', + 'CONVERT_FLOAT_BYTES_TO_LONGLONG/1', + 'CONVERT_LONGLONG_BYTES_TO_FLOAT/1', # 'INT_LT/2b', 'INT_LE/2b', diff --git a/pypy/jit/metainterp/test/test_ajit.py b/pypy/jit/metainterp/test/test_ajit.py --- a/pypy/jit/metainterp/test/test_ajit.py +++ b/pypy/jit/metainterp/test/test_ajit.py @@ -1,8 +1,10 @@ +import math import sys import py from pypy import conftest +from pypy.jit.codewriter import longlong from pypy.jit.codewriter.policy import JitPolicy, StopAtXPolicy from pypy.jit.metainterp import pyjitpl, history from pypy.jit.metainterp.optimizeopt import ALL_OPTS_DICT @@ -14,7 +16,8 @@ loop_invariant, elidable, promote, jit_debug, assert_green, AssertGreenFailed, unroll_safe, current_trace_length, look_inside_iff, isconstant, isvirtual, promote_string, set_param, record_known_class) -from pypy.rlib.rarithmetic import ovfcheck +from pypy.rlib.longlong2float import float2longlong, longlong2float +from pypy.rlib.rarithmetic import ovfcheck, is_valid_int from pypy.rpython.lltypesystem import lltype, llmemory, rffi from pypy.rpython.ootypesystem import ootype @@ -292,7 +295,7 @@ assert res == f(6, sys.maxint, 32, 48) res = self.meta_interp(f, [sys.maxint, 6, 32, 48]) assert res == f(sys.maxint, 6, 32, 48) - + def test_loop_invariant_intbox(self): myjitdriver = JitDriver(greens = [], reds = ['y', 'res', 'x']) @@ -953,7 +956,7 @@ self.meta_interp(f, [20], repeat=7) # the loop and the entry path as a single trace self.check_jitcell_token_count(1) - + # we get: # ENTER - compile the new loop and the entry bridge # ENTER - compile the leaving path @@ -1470,7 +1473,7 @@ assert res == f(299) self.check_resops(guard_class=0, guard_nonnull=4, guard_nonnull_class=4, guard_isnull=2) - + def test_merge_guardnonnull_guardvalue(self): from pypy.rlib.objectmodel import instantiate @@ -1499,7 +1502,7 @@ assert res == f(299) self.check_resops(guard_value=4, guard_class=0, guard_nonnull=4, guard_nonnull_class=0, guard_isnull=2) - + def test_merge_guardnonnull_guardvalue_2(self): from pypy.rlib.objectmodel import instantiate @@ -1528,7 +1531,7 @@ assert res == f(299) self.check_resops(guard_value=4, guard_class=0, guard_nonnull=4, guard_nonnull_class=0, guard_isnull=2) - + def test_merge_guardnonnull_guardclass_guardvalue(self): from pypy.rlib.objectmodel import instantiate @@ -2296,7 +2299,7 @@ self.check_resops(int_rshift=3) bigval = 1 - while (bigval << 3).__class__ is int: + while is_valid_int(bigval << 3): bigval = bigval << 1 assert self.meta_interp(f, [bigval, 5]) == 0 @@ -2341,7 +2344,7 @@ self.check_resops(int_rshift=3) bigval = 1 - while (bigval << 3).__class__ is int: + while is_valid_int(bigval << 3): bigval = bigval << 1 assert self.meta_interp(f, [bigval, 5]) == 0 @@ -2636,7 +2639,7 @@ return sa assert self.meta_interp(f, [20]) == f(20) self.check_resops(int_lt=6, int_le=2, int_ge=4, int_gt=3) - + def test_intbounds_not_generalized2(self): myjitdriver = JitDriver(greens = [], reds = ['n', 'i', 'sa', 'node']) @@ -2677,7 +2680,7 @@ assert self.meta_interp(f, [20, 3]) == f(20, 3) self.check_jitcell_token_count(1) self.check_target_token_count(5) - + def test_max_retrace_guards(self): myjitdriver = JitDriver(greens = [], reds = ['n', 'i', 'sa', 'a']) @@ -2815,7 +2818,7 @@ for cell in get_stats().get_all_jitcell_tokens(): # Initialal trace with two labels and 5 retraces assert len(cell.target_tokens) <= 7 - + def test_nested_retrace(self): myjitdriver = JitDriver(greens = ['pc'], reds = ['n', 'a', 'i', 'j', 'sa']) @@ -3784,6 +3787,25 @@ assert res == 11 * 12 * 13 self.check_operations_history(int_add=3, int_mul=2) + def test_setinteriorfield(self): + A = lltype.GcArray(lltype.Struct('S', ('x', lltype.Signed))) + a = lltype.malloc(A, 5, immortal=True) + def g(n): + a[n].x = n + 2 + return a[n].x + res = self.interp_operations(g, [1]) + assert res == 3 + + def test_float_bytes(self): + def f(n): + ll = float2longlong(n) + return longlong2float(ll) + + for x in [2.5, float("nan"), -2.5, float("inf")]: + # There are tests elsewhere to verify the correctness of this. + res = self.interp_operations(f, [x]) + assert res == x or math.isnan(x) and math.isnan(res) + class TestLLtype(BaseLLtypeTests, LLJitMixin): def test_tagged(self): diff --git a/pypy/jit/metainterp/test/test_compile.py b/pypy/jit/metainterp/test/test_compile.py --- a/pypy/jit/metainterp/test/test_compile.py +++ b/pypy/jit/metainterp/test/test_compile.py @@ -14,7 +14,7 @@ ts = typesystem.llhelper def __init__(self): self.seen = [] - def compile_loop(self, inputargs, operations, token, name=''): + def compile_loop(self, inputargs, operations, token, log=True, name=''): self.seen.append((inputargs, operations, token)) class FakeLogger(object): diff --git a/pypy/jit/metainterp/test/test_list.py b/pypy/jit/metainterp/test/test_list.py --- a/pypy/jit/metainterp/test/test_list.py +++ b/pypy/jit/metainterp/test/test_list.py @@ -1,4 +1,5 @@ import py +from pypy.rlib.objectmodel import newlist_hint from pypy.rlib.jit import JitDriver from pypy.jit.metainterp.test.support import LLJitMixin, OOJitMixin @@ -228,6 +229,28 @@ self.check_resops({'jump': 1, 'int_gt': 2, 'int_add': 2, 'guard_true': 2, 'int_sub': 2}) + def test_newlist_hint(self): + def f(i): + l = newlist_hint(i) + l[0] = 55 + return len(l) + + r = self.interp_operations(f, [3]) + assert r == 0 + + def test_newlist_hint_optimized(self): + driver = JitDriver(greens = [], reds = ['i']) + + def f(i): + while i > 0: + driver.jit_merge_point(i=i) + l = newlist_hint(5) + l.append(1) + i -= l[0] + + self.meta_interp(f, [10], listops=True) + self.check_resops(new_array=0, call=0) + class TestOOtype(ListTests, OOJitMixin): pass diff --git a/pypy/jit/metainterp/test/test_logger.py b/pypy/jit/metainterp/test/test_logger.py --- a/pypy/jit/metainterp/test/test_logger.py +++ b/pypy/jit/metainterp/test/test_logger.py @@ -54,7 +54,7 @@ class FakeJitDriver(object): class warmstate(object): get_location_str = staticmethod(lambda args: "dupa") - + class FakeMetaInterpSd: cpu = AbstractCPU() cpu.ts = self.ts @@ -77,7 +77,7 @@ equaloplists(loop.operations, oloop.operations) assert oloop.inputargs == loop.inputargs return logger, loop, oloop - + def test_simple(self): inp = ''' [i0, i1, i2, p3, p4, p5] @@ -116,12 +116,13 @@ def test_debug_merge_point(self): inp = ''' [] - debug_merge_point(0, 0) + debug_merge_point(0, 0, 0) ''' _, loop, oloop = self.reparse(inp, check_equal=False) assert loop.operations[0].getarg(1).getint() == 0 - assert oloop.operations[0].getarg(1)._get_str() == "dupa" - + assert loop.operations[0].getarg(2).getint() == 0 + assert oloop.operations[0].getarg(2)._get_str() == "dupa" + def test_floats(self): inp = ''' [f0] @@ -142,7 +143,7 @@ output = logger.log_loop(loop) assert output.splitlines()[-1] == "jump(i0, descr=)" pure_parse(output) - + def test_guard_descr(self): namespace = {'fdescr': BasicFailDescr()} inp = ''' @@ -154,7 +155,7 @@ output = logger.log_loop(loop) assert output.splitlines()[-1] == "guard_true(i0, descr=) [i0]" pure_parse(output) - + logger = Logger(self.make_metainterp_sd(), guard_number=False) output = logger.log_loop(loop) lastline = output.splitlines()[-1] diff --git a/pypy/jit/metainterp/test/test_warmspot.py b/pypy/jit/metainterp/test/test_warmspot.py --- a/pypy/jit/metainterp/test/test_warmspot.py +++ b/pypy/jit/metainterp/test/test_warmspot.py @@ -13,7 +13,7 @@ class WarmspotTests(object): - + def test_basic(self): mydriver = JitDriver(reds=['a'], greens=['i']) @@ -77,16 +77,16 @@ self.meta_interp(f, [123, 10]) assert len(get_stats().locations) >= 4 for loc in get_stats().locations: - assert loc == (0, 123) + assert loc == (0, 0, 123) def test_set_param_enable_opts(self): from pypy.rpython.annlowlevel import llstr, hlstr - + myjitdriver = JitDriver(greens = [], reds = ['n']) class A(object): def m(self, n): return n-1 - + def g(n): while n > 0: myjitdriver.can_enter_jit(n=n) @@ -332,7 +332,7 @@ ts = llhelper translate_support_code = False stats = "stats" - + def get_fail_descr_number(self, d): return -1 @@ -352,7 +352,7 @@ return "not callable" driver = JitDriver(reds = ['red'], greens = ['green']) - + def f(green): red = 0 while red < 10: diff --git a/pypy/jit/tl/tlc.py b/pypy/jit/tl/tlc.py --- a/pypy/jit/tl/tlc.py +++ b/pypy/jit/tl/tlc.py @@ -6,6 +6,8 @@ from pypy.jit.tl.tlopcode import * from pypy.jit.tl import tlopcode from pypy.rlib.jit import JitDriver, elidable +from pypy.rlib.rarithmetic import is_valid_int + class Obj(object): @@ -219,7 +221,7 @@ class Frame(object): def __init__(self, args, pc): - assert isinstance(pc, int) + assert is_valid_int(pc) self.args = args self.pc = pc self.stack = [] @@ -239,7 +241,7 @@ return interp_eval(code, pc, args, pool).int_o() def interp_eval(code, pc, args, pool): - assert isinstance(pc, int) + assert is_valid_int(pc) frame = Frame(args, pc) pc = frame.pc diff --git a/pypy/jit/tool/test/test_oparser.py b/pypy/jit/tool/test/test_oparser.py --- a/pypy/jit/tool/test/test_oparser.py +++ b/pypy/jit/tool/test/test_oparser.py @@ -146,16 +146,18 @@ def test_debug_merge_point(self): x = ''' [] - debug_merge_point(0, "info") - debug_merge_point(0, 'info') - debug_merge_point(1, ' info') - debug_merge_point(0, '(stuff) #1') + debug_merge_point(0, 0, "info") + debug_merge_point(0, 0, 'info') + debug_merge_point(1, 1, ' info') + debug_merge_point(0, 0, '(stuff) #1') ''' loop = self.parse(x) - assert loop.operations[0].getarg(1)._get_str() == 'info' - assert loop.operations[1].getarg(1)._get_str() == 'info' - assert loop.operations[2].getarg(1)._get_str() == " info" - assert loop.operations[3].getarg(1)._get_str() == "(stuff) #1" + assert loop.operations[0].getarg(2)._get_str() == 'info' + assert loop.operations[0].getarg(1).value == 0 + assert loop.operations[1].getarg(2)._get_str() == 'info' + assert loop.operations[2].getarg(2)._get_str() == " info" + assert loop.operations[2].getarg(1).value == 1 + assert loop.operations[3].getarg(2)._get_str() == "(stuff) #1" def test_descr_with_obj_print(self): diff --git a/pypy/module/__builtin__/app_inspect.py b/pypy/module/__builtin__/app_inspect.py --- a/pypy/module/__builtin__/app_inspect.py +++ b/pypy/module/__builtin__/app_inspect.py @@ -8,8 +8,6 @@ from __pypy__ import lookup_special def _caller_locals(): - # note: the reason why this is working is because the functions in here are - # compiled by geninterp, so they don't have a frame return sys._getframe(0).f_locals def vars(*obj): @@ -26,17 +24,6 @@ except AttributeError: raise TypeError, "vars() argument must have __dict__ attribute" -# Replaced by the interp-level helper space.callable(): -##def callable(ob): -## import __builtin__ # XXX this is insane but required for now for geninterp -## for c in type(ob).__mro__: -## if '__call__' in c.__dict__: -## if isinstance(ob, __builtin__._instance): # old style instance! -## return getattr(ob, '__call__', None) is not None -## return True -## else: -## return False - def dir(*args): """dir([object]) -> list of strings diff --git a/pypy/module/__builtin__/interp_memoryview.py b/pypy/module/__builtin__/interp_memoryview.py --- a/pypy/module/__builtin__/interp_memoryview.py +++ b/pypy/module/__builtin__/interp_memoryview.py @@ -69,6 +69,10 @@ return W_MemoryView(buf) def descr_buffer(self, space): + """Note that memoryview() objects in PyPy support buffer(), whereas + not in CPython; but CPython supports passing memoryview() to most + built-in functions that accept buffers, with the notable exception + of the buffer() built-in.""" return space.wrap(self.buf) def descr_tobytes(self, space): diff --git a/pypy/module/__pypy__/__init__.py b/pypy/module/__pypy__/__init__.py --- a/pypy/module/__pypy__/__init__.py +++ b/pypy/module/__pypy__/__init__.py @@ -1,5 +1,5 @@ +import sys -# Package initialisation from pypy.interpreter.mixedmodule import MixedModule from pypy.module.imp.importing import get_pyc_magic @@ -12,6 +12,21 @@ "UnicodeBuilder": "interp_builders.W_UnicodeBuilder", } +class TimeModule(MixedModule): + appleveldefs = {} + interpleveldefs = {} + if sys.platform.startswith("linux"): + from pypy.module.__pypy__ import interp_time + interpleveldefs["clock_gettime"] = "interp_time.clock_gettime" + interpleveldefs["clock_getres"] = "interp_time.clock_getres" + for name in [ + "CLOCK_REALTIME", "CLOCK_MONOTONIC", "CLOCK_MONOTONIC_RAW", + "CLOCK_PROCESS_CPUTIME_ID", "CLOCK_THREAD_CPUTIME_ID" + ]: + if getattr(interp_time, name) is not None: + interpleveldefs[name] = "space.wrap(interp_time.%s)" % name + + class Module(MixedModule): appleveldefs = { } @@ -32,6 +47,7 @@ submodules = { "builders": BuildersModule, + "time": TimeModule, } def setup_after_space_initialization(self): diff --git a/pypy/module/__pypy__/interp_time.py b/pypy/module/__pypy__/interp_time.py new file mode 100644 --- /dev/null +++ b/pypy/module/__pypy__/interp_time.py @@ -0,0 +1,65 @@ +from __future__ import with_statement +import sys + +from pypy.interpreter.error import exception_from_errno +from pypy.interpreter.gateway import unwrap_spec +from pypy.rpython.lltypesystem import rffi, lltype +from pypy.rpython.tool import rffi_platform +from pypy.translator.tool.cbuild import ExternalCompilationInfo + + +class CConfig: + _compilation_info_ = ExternalCompilationInfo( + includes=["time.h"], + libraries=["rt"], + ) + + HAS_CLOCK_GETTIME = rffi_platform.Has('clock_gettime') + + CLOCK_REALTIME = rffi_platform.DefinedConstantInteger("CLOCK_REALTIME") + CLOCK_MONOTONIC = rffi_platform.DefinedConstantInteger("CLOCK_MONOTONIC") + CLOCK_MONOTONIC_RAW = rffi_platform.DefinedConstantInteger("CLOCK_MONOTONIC_RAW") + CLOCK_PROCESS_CPUTIME_ID = rffi_platform.DefinedConstantInteger("CLOCK_PROCESS_CPUTIME_ID") + CLOCK_THREAD_CPUTIME_ID = rffi_platform.DefinedConstantInteger("CLOCK_THREAD_CPUTIME_ID") + + TIMESPEC = rffi_platform.Struct("struct timespec", [ + ("tv_sec", rffi.TIME_T), + ("tv_nsec", rffi.LONG), + ]) + +cconfig = rffi_platform.configure(CConfig) + +HAS_CLOCK_GETTIME = cconfig["HAS_CLOCK_GETTIME"] + +CLOCK_REALTIME = cconfig["CLOCK_REALTIME"] +CLOCK_MONOTONIC = cconfig["CLOCK_MONOTONIC"] +CLOCK_MONOTONIC_RAW = cconfig["CLOCK_MONOTONIC_RAW"] +CLOCK_PROCESS_CPUTIME_ID = cconfig["CLOCK_PROCESS_CPUTIME_ID"] +CLOCK_THREAD_CPUTIME_ID = cconfig["CLOCK_THREAD_CPUTIME_ID"] + +TIMESPEC = cconfig["TIMESPEC"] + +c_clock_gettime = rffi.llexternal("clock_gettime", + [lltype.Signed, lltype.Ptr(TIMESPEC)], rffi.INT, + compilation_info=CConfig._compilation_info_, threadsafe=False +) +c_clock_getres = rffi.llexternal("clock_getres", + [lltype.Signed, lltype.Ptr(TIMESPEC)], rffi.INT, + compilation_info=CConfig._compilation_info_, threadsafe=False +) + + at unwrap_spec(clk_id="c_int") +def clock_gettime(space, clk_id): + with lltype.scoped_alloc(TIMESPEC) as tp: + ret = c_clock_gettime(clk_id, tp) + if ret != 0: + raise exception_from_errno(space, space.w_IOError) + return space.wrap(tp.c_tv_sec + tp.c_tv_nsec * 1e-9) + + at unwrap_spec(clk_id="c_int") +def clock_getres(space, clk_id): + with lltype.scoped_alloc(TIMESPEC) as tp: + ret = c_clock_getres(clk_id, tp) + if ret != 0: + raise exception_from_errno(space, space.w_IOError) + return space.wrap(tp.c_tv_sec + tp.c_tv_nsec * 1e-9) diff --git a/pypy/module/__pypy__/test/test_time.py b/pypy/module/__pypy__/test/test_time.py new file mode 100644 --- /dev/null +++ b/pypy/module/__pypy__/test/test_time.py @@ -0,0 +1,26 @@ +import py + +from pypy.module.__pypy__.interp_time import HAS_CLOCK_GETTIME + + +class AppTestTime(object): + def setup_class(cls): + if not HAS_CLOCK_GETTIME: + py.test.skip("need time.clock_gettime") + + def test_clock_realtime(self): + from __pypy__ import time + res = time.clock_gettime(time.CLOCK_REALTIME) + assert isinstance(res, float) + + def test_clock_monotonic(self): + from __pypy__ import time + a = time.clock_gettime(time.CLOCK_MONOTONIC) + b = time.clock_gettime(time.CLOCK_MONOTONIC) + assert a <= b + + def test_clock_getres(self): + from __pypy__ import time + res = time.clock_getres(time.CLOCK_REALTIME) + assert res > 0.0 + assert res <= 1.0 diff --git a/pypy/module/_ast/test/test_ast.py b/pypy/module/_ast/test/test_ast.py --- a/pypy/module/_ast/test/test_ast.py +++ b/pypy/module/_ast/test/test_ast.py @@ -1,9 +1,10 @@ import py - +from pypy.conftest import gettestobjspace class AppTestAST: def setup_class(cls): + cls.space = gettestobjspace(usemodules=['struct']) cls.w_ast = cls.space.appexec([], """(): import _ast return _ast""") diff --git a/pypy/module/_codecs/test/test_codecs.py b/pypy/module/_codecs/test/test_codecs.py --- a/pypy/module/_codecs/test/test_codecs.py +++ b/pypy/module/_codecs/test/test_codecs.py @@ -4,7 +4,7 @@ class AppTestCodecs: def setup_class(cls): - space = gettestobjspace(usemodules=('unicodedata',)) + space = gettestobjspace(usemodules=('unicodedata', 'struct')) cls.space = space def test_register_noncallable(self): diff --git a/pypy/module/_continuation/test/test_zpickle.py b/pypy/module/_continuation/test/test_zpickle.py --- a/pypy/module/_continuation/test/test_zpickle.py +++ b/pypy/module/_continuation/test/test_zpickle.py @@ -106,8 +106,9 @@ version = 0 def setup_class(cls): - cls.space = gettestobjspace(usemodules=('_continuation',), + cls.space = gettestobjspace(usemodules=('_continuation', 'struct'), CALL_METHOD=True) + cls.space.config.translation.continuation = True cls.space.appexec([], """(): global continulet, A, __name__ diff --git a/pypy/module/_ffi/test/test__ffi.py b/pypy/module/_ffi/test/test__ffi.py --- a/pypy/module/_ffi/test/test__ffi.py +++ b/pypy/module/_ffi/test/test__ffi.py @@ -100,7 +100,10 @@ from _ffi import CDLL, types libm = CDLL(self.libm_name) pow_addr = libm.getaddressindll('pow') - assert pow_addr == self.pow_addr & (sys.maxint*2-1) + fff = sys.maxint*2-1 + if sys.platform == 'win32': + fff = sys.maxint*2+1 + assert pow_addr == self.pow_addr & fff def test_func_fromaddr(self): import sys diff --git a/pypy/module/_hashlib/test/test_hashlib.py b/pypy/module/_hashlib/test/test_hashlib.py --- a/pypy/module/_hashlib/test/test_hashlib.py +++ b/pypy/module/_hashlib/test/test_hashlib.py @@ -3,7 +3,7 @@ class AppTestHashlib: def setup_class(cls): - cls.space = gettestobjspace(usemodules=['_hashlib']) + cls.space = gettestobjspace(usemodules=['_hashlib', 'array', 'struct']) def test_simple(self): import _hashlib diff --git a/pypy/module/_io/test/test_io.py b/pypy/module/_io/test/test_io.py --- a/pypy/module/_io/test/test_io.py +++ b/pypy/module/_io/test/test_io.py @@ -158,7 +158,7 @@ class AppTestOpen: def setup_class(cls): - cls.space = gettestobjspace(usemodules=['_io', '_locale']) + cls.space = gettestobjspace(usemodules=['_io', '_locale', 'array', 'struct']) tmpfile = udir.join('tmpfile').ensure() cls.w_tmpfile = cls.space.wrap(str(tmpfile)) diff --git a/pypy/module/_lsprof/interp_lsprof.py b/pypy/module/_lsprof/interp_lsprof.py --- a/pypy/module/_lsprof/interp_lsprof.py +++ b/pypy/module/_lsprof/interp_lsprof.py @@ -22,7 +22,7 @@ eci = ExternalCompilationInfo( separate_module_files=[srcdir.join('profiling.c')], export_symbols=['pypy_setup_profiling', 'pypy_teardown_profiling']) - + c_setup_profiling = rffi.llexternal('pypy_setup_profiling', [], lltype.Void, compilation_info = eci) @@ -228,7 +228,7 @@ if w_self.builtins: key = create_spec(space, w_arg) w_self._enter_builtin_call(key) - elif event == 'c_return': + elif event == 'c_return' or event == 'c_exception': if w_self.builtins: key = create_spec(space, w_arg) w_self._enter_builtin_return(key) @@ -237,7 +237,7 @@ pass class W_Profiler(Wrappable): - + def __init__(self, space, w_callable, time_unit, subcalls, builtins): self.subcalls = subcalls self.builtins = builtins diff --git a/pypy/module/_lsprof/test/test_cprofile.py b/pypy/module/_lsprof/test/test_cprofile.py --- a/pypy/module/_lsprof/test/test_cprofile.py +++ b/pypy/module/_lsprof/test/test_cprofile.py @@ -117,6 +117,20 @@ assert 0.9 < subentry.totaltime < 2.9 #assert 0.9 < subentry.inlinetime < 2.9 + def test_builtin_exception(self): + import math + import _lsprof + + prof = _lsprof.Profiler() + prof.enable() + try: + math.sqrt("a") + except TypeError: + pass + prof.disable() + stats = prof.getstats() + assert len(stats) == 2 + def test_use_cprofile(self): import sys, os # XXX this is evil trickery to walk around the fact that we don't diff --git a/pypy/module/_md5/test/test_md5.py b/pypy/module/_md5/test/test_md5.py --- a/pypy/module/_md5/test/test_md5.py +++ b/pypy/module/_md5/test/test_md5.py @@ -28,7 +28,7 @@ assert self.md5.digest_size == 16 #assert self.md5.digestsize == 16 -- not on CPython assert self.md5.md5().digest_size == 16 - if sys.version >= (2, 5): + if sys.version_info >= (2, 5): assert self.md5.blocksize == 1 assert self.md5.md5().digestsize == 16 diff --git a/pypy/module/_multiprocessing/test/test_connection.py b/pypy/module/_multiprocessing/test/test_connection.py --- a/pypy/module/_multiprocessing/test/test_connection.py +++ b/pypy/module/_multiprocessing/test/test_connection.py @@ -92,7 +92,8 @@ class AppTestSocketConnection(BaseConnectionTest): def setup_class(cls): - space = gettestobjspace(usemodules=('_multiprocessing', 'thread', 'signal')) + space = gettestobjspace(usemodules=('_multiprocessing', 'thread', 'signal', + 'struct', 'array')) cls.space = space cls.w_connections = space.newlist([]) diff --git a/pypy/module/_multiprocessing/test/test_semaphore.py b/pypy/module/_multiprocessing/test/test_semaphore.py --- a/pypy/module/_multiprocessing/test/test_semaphore.py +++ b/pypy/module/_multiprocessing/test/test_semaphore.py @@ -2,6 +2,7 @@ from pypy.module._multiprocessing.interp_semaphore import ( RECURSIVE_MUTEX, SEMAPHORE) + class AppTestSemaphore: def setup_class(cls): space = gettestobjspace(usemodules=('_multiprocessing', 'thread')) diff --git a/pypy/module/_socket/test/test_sock_app.py b/pypy/module/_socket/test/test_sock_app.py --- a/pypy/module/_socket/test/test_sock_app.py +++ b/pypy/module/_socket/test/test_sock_app.py @@ -6,7 +6,7 @@ from pypy.rpython.lltypesystem import lltype, rffi def setup_module(mod): - mod.space = gettestobjspace(usemodules=['_socket', 'array']) + mod.space = gettestobjspace(usemodules=['_socket', 'array', 'struct']) global socket import socket mod.w_socket = space.appexec([], "(): import _socket as m; return m") @@ -372,10 +372,9 @@ def test_socket_connect(self): import _socket, os s = _socket.socket(_socket.AF_INET, _socket.SOCK_STREAM, 0) - # XXX temporarily we use python.org to test, will have more robust tests - # in the absence of a network connection later when more parts of the - # socket API are implemented. Currently skip the test if there is no - # connection. + # it would be nice to have a test which works even if there is no + # network connection. However, this one is "good enough" for now. Skip + # it if there is no connection. try: s.connect(("www.python.org", 80)) except _socket.gaierror, ex: diff --git a/pypy/module/_ssl/interp_ssl.py b/pypy/module/_ssl/interp_ssl.py --- a/pypy/module/_ssl/interp_ssl.py +++ b/pypy/module/_ssl/interp_ssl.py @@ -432,7 +432,8 @@ raise _ssl_seterror(self.space, self, length) try: # this is actually an immutable bytes sequence - return self.space.wrap(rffi.charp2str(buf_ptr[0])) + return self.space.wrap(rffi.charpsize2str(buf_ptr[0], + length)) finally: libssl_OPENSSL_free(buf_ptr[0]) else: diff --git a/pypy/module/_ssl/test/test_ssl.py b/pypy/module/_ssl/test/test_ssl.py --- a/pypy/module/_ssl/test/test_ssl.py +++ b/pypy/module/_ssl/test/test_ssl.py @@ -2,6 +2,7 @@ import os import py + class AppTestSSL: def setup_class(cls): space = gettestobjspace(usemodules=('_ssl', '_socket')) @@ -29,7 +30,6 @@ assert isinstance(_ssl.SSL_ERROR_EOF, int) assert isinstance(_ssl.SSL_ERROR_INVALID_ERROR_CODE, int) - assert isinstance(_ssl.OPENSSL_VERSION_NUMBER, (int, long)) assert isinstance(_ssl.OPENSSL_VERSION_INFO, tuple) assert len(_ssl.OPENSSL_VERSION_INFO) == 5 assert isinstance(_ssl.OPENSSL_VERSION, str) @@ -90,7 +90,7 @@ class AppTestConnectedSSL: def setup_class(cls): - space = gettestobjspace(usemodules=('_ssl', '_socket')) + space = gettestobjspace(usemodules=('_ssl', '_socket', 'struct')) cls.space = space def setup_method(self, method): @@ -179,7 +179,7 @@ # to exercise the poll() calls def setup_class(cls): - space = gettestobjspace(usemodules=('_ssl', '_socket')) + space = gettestobjspace(usemodules=('_ssl', '_socket', 'struct')) cls.space = space cls.space.appexec([], """(): import socket; socket.setdefaulttimeout(1) diff --git a/pypy/module/array/interp_array.py b/pypy/module/array/interp_array.py --- a/pypy/module/array/interp_array.py +++ b/pypy/module/array/interp_array.py @@ -11,6 +11,7 @@ from pypy.objspace.std.register_all import register_all from pypy.rlib.rarithmetic import ovfcheck from pypy.rlib.unroll import unrolling_iterable +from pypy.rlib.objectmodel import specialize, keepalive_until_here from pypy.rpython.lltypesystem import lltype, rffi @@ -144,28 +145,36 @@ unroll_typecodes = unrolling_iterable(types.keys()) class ArrayBuffer(RWBuffer): - def __init__(self, data, bytes): - self.data = data - self.len = bytes + def __init__(self, array): + self.array = array def getlength(self): - return self.len + return self.array.len * self.array.itemsize def getitem(self, index): - return self.data[index] + array = self.array + data = array._charbuf_start() + char = data[index] + array._charbuf_stop() + return char def setitem(self, index, char): - self.data[index] = char + array = self.array + data = array._charbuf_start() + data[index] = char + array._charbuf_stop() def make_array(mytype): + W_ArrayBase = globals()['W_ArrayBase'] + class W_Array(W_ArrayBase): itemsize = mytype.bytes typecode = mytype.typecode @staticmethod def register(typeorder): - typeorder[W_Array] = [] + typeorder[W_Array] = [(W_ArrayBase, None)] def __init__(self, space): self.space = space @@ -275,9 +284,10 @@ oldlen = self.len new = len(s) / mytype.bytes self.setlen(oldlen + new) - cbuf = self.charbuf() + cbuf = self._charbuf_start() for i in range(len(s)): cbuf[oldlen * mytype.bytes + i] = s[i] + self._charbuf_stop() def fromlist(self, w_lst): s = self.len @@ -307,8 +317,11 @@ else: self.fromsequence(w_iterable) - def charbuf(self): - return rffi.cast(rffi.CCHARP, self.buffer) + def _charbuf_start(self): + return rffi.cast(rffi.CCHARP, self.buffer) + + def _charbuf_stop(self): + keepalive_until_here(self) def w_getitem(self, space, idx): item = self.buffer[idx] @@ -527,8 +540,10 @@ self.fromstring(space.str_w(w_s)) def array_tostring__Array(space, self): - cbuf = self.charbuf() - return self.space.wrap(rffi.charpsize2str(cbuf, self.len * mytype.bytes)) + cbuf = self._charbuf_start() + s = rffi.charpsize2str(cbuf, self.len * mytype.bytes) + self._charbuf_stop() + return self.space.wrap(s) def array_fromfile__Array_ANY_ANY(space, self, w_f, w_n): if not isinstance(w_f, W_File): @@ -583,37 +598,34 @@ raise OperationError(space.w_ValueError, space.wrap(msg)) # Compare methods + @specialize.arg(3) def _cmp_impl(space, self, other, space_fn): - if isinstance(other, W_ArrayBase): - w_lst1 = array_tolist__Array(space, self) - w_lst2 = space.call_method(other, 'tolist') - return space_fn(w_lst1, w_lst2) - else: - return space.w_NotImplemented + w_lst1 = array_tolist__Array(space, self) + w_lst2 = space.call_method(other, 'tolist') + return space_fn(w_lst1, w_lst2) - def eq__Array_ANY(space, self, other): + def eq__Array_ArrayBase(space, self, other): return _cmp_impl(space, self, other, space.eq) - def ne__Array_ANY(space, self, other): + def ne__Array_ArrayBase(space, self, other): return _cmp_impl(space, self, other, space.ne) - def lt__Array_ANY(space, self, other): + def lt__Array_ArrayBase(space, self, other): return _cmp_impl(space, self, other, space.lt) - def le__Array_ANY(space, self, other): + def le__Array_ArrayBase(space, self, other): return _cmp_impl(space, self, other, space.le) - def gt__Array_ANY(space, self, other): + def gt__Array_ArrayBase(space, self, other): return _cmp_impl(space, self, other, space.gt) - def ge__Array_ANY(space, self, other): + def ge__Array_ArrayBase(space, self, other): return _cmp_impl(space, self, other, space.ge) # Misc methods def buffer__Array(space, self): - b = ArrayBuffer(self.charbuf(), self.len * mytype.bytes) - return space.wrap(b) + return space.wrap(ArrayBuffer(self)) def array_buffer_info__Array(space, self): w_ptr = space.wrap(rffi.cast(lltype.Unsigned, self.buffer)) @@ -648,7 +660,7 @@ raise OperationError(space.w_RuntimeError, space.wrap(msg)) if self.len == 0: return - bytes = self.charbuf() + bytes = self._charbuf_start() tmp = [bytes[0]] * mytype.bytes for start in range(0, self.len * mytype.bytes, mytype.bytes): stop = start + mytype.bytes - 1 @@ -656,6 +668,7 @@ tmp[i] = bytes[start + i] for i in range(mytype.bytes): bytes[stop - i] = tmp[i] + self._charbuf_stop() def repr__Array(space, self): if self.len == 0: diff --git a/pypy/module/array/test/test_array.py b/pypy/module/array/test/test_array.py --- a/pypy/module/array/test/test_array.py +++ b/pypy/module/array/test/test_array.py @@ -433,7 +433,25 @@ a = self.array('h', 'Hi') buf = buffer(a) assert buf[1] == 'i' - #raises(TypeError, buf.__setitem__, 1, 'o') + + def test_buffer_write(self): + a = self.array('c', 'hello') + buf = buffer(a) + print repr(buf) + try: + buf[3] = 'L' + except TypeError: + skip("buffer(array) returns a read-only buffer on CPython") + assert a.tostring() == 'helLo' + + def test_buffer_keepalive(self): + buf = buffer(self.array('c', 'text')) + assert buf[2] == 'x' + # + a = self.array('c', 'foobarbaz') + buf = buffer(a) + a.fromstring('some extra text') + assert buf[:] == 'foobarbazsome extra text' def test_list_methods(self): assert repr(self.array('i')) == "array('i')" @@ -845,8 +863,11 @@ cls.maxint = sys.maxint class AppTestArray(BaseArrayTests): + OPTIONS = {} + def setup_class(cls): - cls.space = gettestobjspace(usemodules=('array', 'struct', '_rawffi')) + cls.space = gettestobjspace(usemodules=('array', 'struct', '_rawffi'), + **cls.OPTIONS) cls.w_array = cls.space.appexec([], """(): import array return array.array @@ -868,3 +889,7 @@ a = self.array('b', range(4)) a[::-1] = a assert a == self.array('b', [3, 2, 1, 0]) + + +class AppTestArrayBuiltinShortcut(AppTestArray): + OPTIONS = {'objspace.std.builtinshortcut': True} diff --git a/pypy/module/cpyext/api.py b/pypy/module/cpyext/api.py --- a/pypy/module/cpyext/api.py +++ b/pypy/module/cpyext/api.py @@ -352,6 +352,9 @@ 'PyObject_AsReadBuffer', 'PyObject_AsWriteBuffer', 'PyObject_CheckReadBuffer', 'PyOS_getsig', 'PyOS_setsig', + 'PyThread_create_key', 'PyThread_delete_key', 'PyThread_set_key_value', + 'PyThread_get_key_value', 'PyThread_delete_key_value', + 'PyThread_ReInitTLS', 'PyStructSequence_InitType', 'PyStructSequence_New', ] @@ -617,6 +620,10 @@ lambda space: init_pycobject(), lambda space: init_capsule(), ]) + from pypy.module.posix.interp_posix import add_fork_hook + reinit_tls = rffi.llexternal('PyThread_ReInitTLS', [], lltype.Void, + compilation_info=eci) + add_fork_hook('child', reinit_tls) def init_function(func): INIT_FUNCTIONS.append(func) @@ -817,6 +824,8 @@ pypy_decls.append("#ifdef __cplusplus") pypy_decls.append("extern \"C\" {") pypy_decls.append("#endif\n") + pypy_decls.append('#define Signed long /* xxx temporary fix */\n') + pypy_decls.append('#define Unsigned unsigned long /* xxx temporary fix */\n') for decl in FORWARD_DECLS: pypy_decls.append("%s;" % (decl,)) @@ -848,6 +857,8 @@ typ = 'PyObject*' pypy_decls.append('PyAPI_DATA(%s) %s;' % (typ, name)) + pypy_decls.append('#undef Signed /* xxx temporary fix */\n') + pypy_decls.append('#undef Unsigned /* xxx temporary fix */\n') pypy_decls.append("#ifdef __cplusplus") pypy_decls.append("}") pypy_decls.append("#endif") @@ -926,6 +937,7 @@ source_dir / "structseq.c", source_dir / "capsule.c", source_dir / "pysignals.c", + source_dir / "thread.c", ], separate_module_sources=separate_module_sources, export_symbols=export_symbols_eci, diff --git a/pypy/module/cpyext/bufferobject.py b/pypy/module/cpyext/bufferobject.py --- a/pypy/module/cpyext/bufferobject.py +++ b/pypy/module/cpyext/bufferobject.py @@ -2,8 +2,10 @@ from pypy.module.cpyext.api import ( cpython_api, Py_ssize_t, cpython_struct, bootstrap_function, PyObjectFields, PyObject) -from pypy.module.cpyext.pyobject import make_typedescr, Py_DecRef +from pypy.module.cpyext.pyobject import make_typedescr, Py_DecRef, make_ref from pypy.interpreter.buffer import Buffer, StringBuffer, SubBuffer +from pypy.interpreter.error import OperationError +from pypy.module.array.interp_array import ArrayBuffer PyBufferObjectStruct = lltype.ForwardReference() @@ -41,26 +43,38 @@ py_buf.c_b_offset = w_obj.offset w_obj = w_obj.buffer + # If w_obj already allocated a fixed buffer, use it, and keep a + # reference to w_obj. + # Otherwise, b_base stays NULL, and we own the b_ptr. + if isinstance(w_obj, StringBuffer): - py_buf.c_b_base = rffi.cast(PyObject, 0) # space.wrap(w_obj.value) - py_buf.c_b_ptr = rffi.cast(rffi.VOIDP, rffi.str2charp(w_obj.as_str())) + py_buf.c_b_base = lltype.nullptr(PyObject.TO) + py_buf.c_b_ptr = rffi.cast(rffi.VOIDP, rffi.str2charp(w_obj.value)) + py_buf.c_b_size = w_obj.getlength() + elif isinstance(w_obj, ArrayBuffer): + w_base = w_obj.array + py_buf.c_b_base = make_ref(space, w_base) + py_buf.c_b_ptr = rffi.cast(rffi.VOIDP, w_obj.array._charbuf_start()) py_buf.c_b_size = w_obj.getlength() else: - raise Exception("Fail fail fail fail fail") + raise OperationError(space.w_NotImplementedError, space.wrap( + "buffer flavor not supported")) def buffer_realize(space, py_obj): """ Creates the buffer in the PyPy interpreter from a cpyext representation. """ - raise Exception("realize fail fail fail") - + raise OperationError(space.w_NotImplementedError, space.wrap( + "Don't know how to realize a buffer")) @cpython_api([PyObject], lltype.Void, external=False) def buffer_dealloc(space, py_obj): py_buf = rffi.cast(PyBufferObject, py_obj) - Py_DecRef(space, py_buf.c_b_base) - rffi.free_charp(rffi.cast(rffi.CCHARP, py_buf.c_b_ptr)) + if py_buf.c_b_base: + Py_DecRef(space, py_buf.c_b_base) + else: + rffi.free_charp(rffi.cast(rffi.CCHARP, py_buf.c_b_ptr)) from pypy.module.cpyext.object import PyObject_dealloc PyObject_dealloc(space, py_obj) diff --git a/pypy/module/cpyext/include/patchlevel.h b/pypy/module/cpyext/include/patchlevel.h --- a/pypy/module/cpyext/include/patchlevel.h +++ b/pypy/module/cpyext/include/patchlevel.h @@ -29,7 +29,7 @@ #define PY_VERSION "2.7.2" /* PyPy version as a string */ -#define PYPY_VERSION "1.8.1" +#define PYPY_VERSION "1.9.1" /* Subversion Revision number of this file (not of the repository). * Empty since Mercurial migration. */ diff --git a/pypy/module/cpyext/include/pythread.h b/pypy/module/cpyext/include/pythread.h --- a/pypy/module/cpyext/include/pythread.h +++ b/pypy/module/cpyext/include/pythread.h @@ -3,8 +3,26 @@ #define WITH_THREAD +#ifdef __cplusplus +extern "C" { +#endif + typedef void *PyThread_type_lock; #define WAIT_LOCK 1 #define NOWAIT_LOCK 0 +/* Thread Local Storage (TLS) API */ +PyAPI_FUNC(int) PyThread_create_key(void); +PyAPI_FUNC(void) PyThread_delete_key(int); +PyAPI_FUNC(int) PyThread_set_key_value(int, void *); +PyAPI_FUNC(void *) PyThread_get_key_value(int); +PyAPI_FUNC(void) PyThread_delete_key_value(int key); + +/* Cleanup after a fork */ +PyAPI_FUNC(void) PyThread_ReInitTLS(void); + +#ifdef __cplusplus +} #endif + +#endif diff --git a/pypy/module/cpyext/pyerrors.py b/pypy/module/cpyext/pyerrors.py --- a/pypy/module/cpyext/pyerrors.py +++ b/pypy/module/cpyext/pyerrors.py @@ -2,6 +2,7 @@ from pypy.rpython.lltypesystem import rffi, lltype from pypy.interpreter.error import OperationError +from pypy.interpreter import pytraceback from pypy.module.cpyext.api import cpython_api, CANNOT_FAIL, CONST_STRING from pypy.module.exceptions.interp_exceptions import W_RuntimeWarning from pypy.module.cpyext.pyobject import ( @@ -315,3 +316,65 @@ It may be called without holding the interpreter lock.""" space.check_signal_action.set_interrupt() + at cpython_api([PyObjectP, PyObjectP, PyObjectP], lltype.Void) +def PyErr_GetExcInfo(space, ptype, pvalue, ptraceback): + """---Cython extension--- + + Retrieve the exception info, as known from ``sys.exc_info()``. This + refers to an exception that was already caught, not to an exception + that was freshly raised. Returns new references for the three + objects, any of which may be *NULL*. Does not modify the exception + info state. + + .. note:: + + This function is not normally used by code that wants to handle + exceptions. Rather, it can be used when code needs to save and + restore the exception state temporarily. Use + :c:func:`PyErr_SetExcInfo` to restore or clear the exception + state. + """ + ec = space.getexecutioncontext() + operror = ec.sys_exc_info() + if operror: + ptype[0] = make_ref(space, operror.w_type) + pvalue[0] = make_ref(space, operror.get_w_value(space)) + ptraceback[0] = make_ref(space, space.wrap(operror.get_traceback())) + else: + ptype[0] = lltype.nullptr(PyObject.TO) + pvalue[0] = lltype.nullptr(PyObject.TO) + ptraceback[0] = lltype.nullptr(PyObject.TO) + + at cpython_api([PyObject, PyObject, PyObject], lltype.Void) +def PyErr_SetExcInfo(space, w_type, w_value, w_traceback): + """---Cython extension--- + + Set the exception info, as known from ``sys.exc_info()``. This refers + to an exception that was already caught, not to an exception that was + freshly raised. This function steals the references of the arguments. + To clear the exception state, pass *NULL* for all three arguments. + For general rules about the three arguments, see :c:func:`PyErr_Restore`. + + .. note:: + + This function is not normally used by code that wants to handle + exceptions. Rather, it can be used when code needs to save and + restore the exception state temporarily. Use + :c:func:`PyErr_GetExcInfo` to read the exception state. + """ + if w_value is None or space.is_w(w_value, space.w_None): + operror = None + else: + tb = None + if w_traceback is not None: + try: + tb = pytraceback.check_traceback(space, w_traceback, '?') + except OperationError: # catch and ignore bogus objects + pass + operror = OperationError(w_type, w_value, tb) + # + ec = space.getexecutioncontext() + ec.set_sys_exc_info(operror) + Py_DecRef(space, w_type) + Py_DecRef(space, w_value) + Py_DecRef(space, w_traceback) diff --git a/pypy/module/cpyext/pystate.py b/pypy/module/cpyext/pystate.py --- a/pypy/module/cpyext/pystate.py +++ b/pypy/module/cpyext/pystate.py @@ -10,7 +10,7 @@ [('next', PyInterpreterState)], PyInterpreterStateStruct) PyThreadState = lltype.Ptr(cpython_struct( - "PyThreadState", + "PyThreadState", [('interp', PyInterpreterState), ('dict', PyObject), ])) @@ -19,12 +19,15 @@ def PyEval_SaveThread(space): """Release the global interpreter lock (if it has been created and thread support is enabled) and reset the thread state to NULL, returning the - previous thread state (which is not NULL except in PyPy). If the lock has been created, + previous thread state. If the lock has been created, the current thread must have acquired it. (This function is available even when thread support is disabled at compile time.)""" + state = space.fromcache(InterpreterState) if rffi.aroundstate.before: rffi.aroundstate.before() - return lltype.nullptr(PyThreadState.TO) + tstate = state.swap_thread_state( + space, lltype.nullptr(PyThreadState.TO)) + return tstate @cpython_api([PyThreadState], lltype.Void) def PyEval_RestoreThread(space, tstate): @@ -35,6 +38,8 @@ when thread support is disabled at compile time.)""" if rffi.aroundstate.after: rffi.aroundstate.after() + state = space.fromcache(InterpreterState) + state.swap_thread_state(space, tstate) @cpython_api([], lltype.Void) def PyEval_InitThreads(space): @@ -67,28 +72,91 @@ dealloc=ThreadState_dealloc) from pypy.interpreter.executioncontext import ExecutionContext + +# Keep track of the ThreadStateCapsule for a particular execution context. The +# default is for new execution contexts not to have one; it is allocated on the +# first cpyext-based request for it. ExecutionContext.cpyext_threadstate = ThreadStateCapsule(None) +# Also keep track of whether it has been initialized yet or not (None is a valid +# PyThreadState for an execution context to have, when the GIL has been +# released, so a check against that can't be used to determine the need for +# initialization). +ExecutionContext.cpyext_initialized_threadstate = False + +def cleanup_cpyext_state(self): + try: + del self.cpyext_threadstate + except AttributeError: + pass + self.cpyext_initialized_threadstate = False +ExecutionContext.cleanup_cpyext_state = cleanup_cpyext_state + class InterpreterState(object): def __init__(self, space): self.interpreter_state = lltype.malloc( PyInterpreterState.TO, flavor='raw', zero=True, immortal=True) def new_thread_state(self, space): + """ + Create a new ThreadStateCapsule to hold the PyThreadState for a + particular execution context. + + :param space: A space. + + :returns: A new ThreadStateCapsule holding a newly allocated + PyThreadState and referring to this interpreter state. + """ capsule = ThreadStateCapsule(space) ts = capsule.memory ts.c_interp = self.interpreter_state ts.c_dict = make_ref(space, space.newdict()) return capsule + def get_thread_state(self, space): + """ + Get the current PyThreadState for the current execution context. + + :param space: A space. + + :returns: The current PyThreadState for the current execution context, + or None if it does not have one. + """ ec = space.getexecutioncontext() return self._get_thread_state(space, ec).memory + + def swap_thread_state(self, space, tstate): + """ + Replace the current thread state of the current execution context with a + new thread state. + + :param space: The space. + + :param tstate: The new PyThreadState for the current execution context. + + :returns: The old thread state for the current execution context, either + None or a PyThreadState. + """ + ec = space.getexecutioncontext() + capsule = self._get_thread_state(space, ec) + old_tstate = capsule.memory + capsule.memory = tstate + return old_tstate + def _get_thread_state(self, space, ec): - if ec.cpyext_threadstate.memory == lltype.nullptr(PyThreadState.TO): + """ + Get the ThreadStateCapsule for the given execution context, possibly + creating a new one if it does not already have one. + + :param space: The space. + :param ec: The ExecutionContext of which to get the thread state. + :returns: The ThreadStateCapsule for the given execution context. + """ + if not ec.cpyext_initialized_threadstate: ec.cpyext_threadstate = self.new_thread_state(space) - + ec.cpyext_initialized_threadstate = True return ec.cpyext_threadstate @cpython_api([], PyThreadState, error=CANNOT_FAIL) @@ -105,13 +173,8 @@ def PyThreadState_Swap(space, tstate): """Swap the current thread state with the thread state given by the argument tstate, which may be NULL. The global interpreter lock must be held.""" - # All cpyext calls release and acquire the GIL, so this function has no - # side-effects - if tstate: - return lltype.nullptr(PyThreadState.TO) - else: - state = space.fromcache(InterpreterState) - return state.get_thread_state(space) + state = space.fromcache(InterpreterState) + return state.swap_thread_state(space, tstate) @cpython_api([PyThreadState], lltype.Void) def PyEval_AcquireThread(space, tstate): diff --git a/pypy/module/cpyext/slotdefs.py b/pypy/module/cpyext/slotdefs.py --- a/pypy/module/cpyext/slotdefs.py +++ b/pypy/module/cpyext/slotdefs.py @@ -167,14 +167,16 @@ if rffi.cast(lltype.Signed, res) == -1: space.fromcache(State).check_and_raise_exception(always=True) +# Warning, confusing function name (like CPython). Used only for sq_contains. def wrap_objobjproc(space, w_self, w_args, func): func_target = rffi.cast(objobjproc, func) check_num_args(space, w_args, 1) w_value, = space.fixedview(w_args) res = generic_cpy_call(space, func_target, w_self, w_value) - if rffi.cast(lltype.Signed, res) == -1: + res = rffi.cast(lltype.Signed, res) + if res == -1: space.fromcache(State).check_and_raise_exception(always=True) - return space.wrap(res) + return space.wrap(bool(res)) def wrap_objobjargproc(space, w_self, w_args, func): func_target = rffi.cast(objobjargproc, func) @@ -183,7 +185,7 @@ res = generic_cpy_call(space, func_target, w_self, w_key, w_value) if rffi.cast(lltype.Signed, res) == -1: space.fromcache(State).check_and_raise_exception(always=True) - return space.wrap(res) + return space.w_None def wrap_delitem(space, w_self, w_args, func): func_target = rffi.cast(objobjargproc, func) diff --git a/pypy/module/cpyext/src/getargs.c b/pypy/module/cpyext/src/getargs.c --- a/pypy/module/cpyext/src/getargs.c +++ b/pypy/module/cpyext/src/getargs.c @@ -23,16 +23,33 @@ #define FLAG_COMPAT 1 #define FLAG_SIZE_T 2 +typedef int (*destr_t)(PyObject *, void *); + + +/* Keep track of "objects" that have been allocated or initialized and + which will need to be deallocated or cleaned up somehow if overall + parsing fails. +*/ +typedef struct { + void *item; + destr_t destructor; +} freelistentry_t; + +typedef struct { + int first_available; + freelistentry_t *entries; +} freelist_t; + /* Forward */ static int vgetargs1(PyObject *, const char *, va_list *, int); static void seterror(int, const char *, int *, const char *, const char *); static char *convertitem(PyObject *, const char **, va_list *, int, int *, - char *, size_t, PyObject **); + char *, size_t, freelist_t *); static char *converttuple(PyObject *, const char **, va_list *, int, - int *, char *, size_t, int, PyObject **); + int *, char *, size_t, int, freelist_t *); static char *convertsimple(PyObject *, const char **, va_list *, int, char *, - size_t, PyObject **); + size_t, freelist_t *); static Py_ssize_t convertbuffer(PyObject *, void **p, char **); static int getbuffer(PyObject *, Py_buffer *, char**); @@ -129,57 +146,56 @@ /* Handle cleanup of allocated memory in case of exception */ -static void -cleanup_ptr(void *ptr) +static int +cleanup_ptr(PyObject *self, void *ptr) { - PyMem_FREE(ptr); -} - -static void -cleanup_buffer(void *ptr) -{ - PyBuffer_Release((Py_buffer *) ptr); + if (ptr) { + PyMem_FREE(ptr); + } + return 0; } static int -addcleanup(void *ptr, PyObject **freelist, void (*destr)(void *)) +cleanup_buffer(PyObject *self, void *ptr) { - PyObject *cobj; - if (!*freelist) { - *freelist = PyList_New(0); - if (!*freelist) { - destr(ptr); - return -1; - } - } - cobj = PyCObject_FromVoidPtr(ptr, destr); - if (!cobj) { - destr(ptr); - return -1; - } - if (PyList_Append(*freelist, cobj)) { - Py_DECREF(cobj); - return -1; - } - Py_DECREF(cobj); - return 0; + Py_buffer *buf = (Py_buffer *)ptr; + if (buf) { + PyBuffer_Release(buf); + } + return 0; } static int -cleanreturn(int retval, PyObject *freelist) +addcleanup(void *ptr, freelist_t *freelist, destr_t destructor) { - if (freelist && retval != 0) { - /* We were successful, reset the destructors so that they - don't get called. */ - Py_ssize_t len = PyList_GET_SIZE(freelist), i; - for (i = 0; i < len; i++) - ((PyCObject *) PyList_GET_ITEM(freelist, i)) - ->destructor = NULL; - } - Py_XDECREF(freelist); - return retval; + int index; + + index = freelist->first_available; + freelist->first_available += 1; + + freelist->entries[index].item = ptr; + freelist->entries[index].destructor = destructor; + + return 0; } +static int +cleanreturn(int retval, freelist_t *freelist) +{ + int index; + + if (retval == 0) { + /* A failure occurred, therefore execute all of the cleanup + functions. + */ + for (index = 0; index < freelist->first_available; ++index) { + freelist->entries[index].destructor(NULL, + freelist->entries[index].item); + } + } + PyMem_Free(freelist->entries); + return retval; +} static int vgetargs1(PyObject *args, const char *format, va_list *p_va, int flags) @@ -195,7 +211,7 @@ const char *formatsave = format; Py_ssize_t i, len; char *msg; - PyObject *freelist = NULL; + freelist_t freelist = {0, NULL}; int compat = flags & FLAG_COMPAT; assert(compat || (args != (PyObject*)NULL)); @@ -251,16 +267,18 @@ format = formatsave; + freelist.entries = PyMem_New(freelistentry_t, max); + if (compat) { if (max == 0) { if (args == NULL) - return 1; + return cleanreturn(1, &freelist); PyOS_snprintf(msgbuf, sizeof(msgbuf), "%.200s%s takes no arguments", fname==NULL ? "function" : fname, fname==NULL ? "" : "()"); PyErr_SetString(PyExc_TypeError, msgbuf); - return 0; + return cleanreturn(0, &freelist); } else if (min == 1 && max == 1) { if (args == NULL) { @@ -269,26 +287,26 @@ fname==NULL ? "function" : fname, fname==NULL ? "" : "()"); PyErr_SetString(PyExc_TypeError, msgbuf); - return 0; + return cleanreturn(0, &freelist); } msg = convertitem(args, &format, p_va, flags, levels, msgbuf, sizeof(msgbuf), &freelist); if (msg == NULL) - return cleanreturn(1, freelist); + return cleanreturn(1, &freelist); seterror(levels[0], msg, levels+1, fname, message); - return cleanreturn(0, freelist); + return cleanreturn(0, &freelist); } else { PyErr_SetString(PyExc_SystemError, "old style getargs format uses new features"); - return 0; + return cleanreturn(0, &freelist); } } if (!PyTuple_Check(args)) { PyErr_SetString(PyExc_SystemError, "new style getargs format but argument is not a tuple"); - return 0; + return cleanreturn(0, &freelist); } len = PyTuple_GET_SIZE(args); @@ -308,7 +326,7 @@ message = msgbuf; } PyErr_SetString(PyExc_TypeError, message); - return 0; + return cleanreturn(0, &freelist); } for (i = 0; i < len; i++) { @@ -319,7 +337,7 @@ sizeof(msgbuf), &freelist); if (msg) { seterror(i+1, msg, levels, fname, message); - return cleanreturn(0, freelist); + return cleanreturn(0, &freelist); } } @@ -328,10 +346,10 @@ *format != '|' && *format != ':' && *format != ';') { PyErr_Format(PyExc_SystemError, "bad format string: %.200s", formatsave); - return cleanreturn(0, freelist); + return cleanreturn(0, &freelist); } - return cleanreturn(1, freelist); + return cleanreturn(1, &freelist); } @@ -395,7 +413,7 @@ static char * converttuple(PyObject *arg, const char **p_format, va_list *p_va, int flags, int *levels, char *msgbuf, size_t bufsize, int toplevel, - PyObject **freelist) + freelist_t *freelist) { int level = 0; int n = 0; @@ -472,7 +490,7 @@ static char * convertitem(PyObject *arg, const char **p_format, va_list *p_va, int flags, - int *levels, char *msgbuf, size_t bufsize, PyObject **freelist) + int *levels, char *msgbuf, size_t bufsize, freelist_t *freelist) { char *msg; const char *format = *p_format; @@ -539,7 +557,7 @@ static char * convertsimple(PyObject *arg, const char **p_format, va_list *p_va, int flags, - char *msgbuf, size_t bufsize, PyObject **freelist) + char *msgbuf, size_t bufsize, freelist_t *freelist) { /* For # codes */ #define FETCH_SIZE int *q=NULL;Py_ssize_t *q2=NULL;\ @@ -1501,7 +1519,9 @@ const char *fname, *msg, *custom_msg, *keyword; int min = INT_MAX; int i, len, nargs, nkeywords; - PyObject *freelist = NULL, *current_arg; + PyObject *current_arg; + freelist_t freelist = {0, NULL}; + assert(args != NULL && PyTuple_Check(args)); assert(keywords == NULL || PyDict_Check(keywords)); @@ -1525,6 +1545,8 @@ for (len=0; kwlist[len]; len++) continue; + freelist.entries = PyMem_New(freelistentry_t, len); + nargs = PyTuple_GET_SIZE(args); nkeywords = (keywords == NULL) ? 0 : PyDict_Size(keywords); if (nargs + nkeywords > len) { @@ -1535,7 +1557,7 @@ len, (len == 1) ? "" : "s", nargs + nkeywords); - return 0; + return cleanreturn(0, &freelist); } /* convert tuple args and keyword args in same loop, using kwlist to drive process */ @@ -1549,7 +1571,7 @@ PyErr_Format(PyExc_RuntimeError, "More keyword list entries (%d) than " "format specifiers (%d)", len, i); - return cleanreturn(0, freelist); + return cleanreturn(0, &freelist); } current_arg = NULL; if (nkeywords) { @@ -1563,11 +1585,11 @@ "Argument given by name ('%s') " "and position (%d)", keyword, i+1); - return cleanreturn(0, freelist); + return cleanreturn(0, &freelist); } } else if (nkeywords && PyErr_Occurred()) - return cleanreturn(0, freelist); + return cleanreturn(0, &freelist); else if (i < nargs) current_arg = PyTuple_GET_ITEM(args, i); @@ -1576,7 +1598,7 @@ levels, msgbuf, sizeof(msgbuf), &freelist); if (msg) { seterror(i+1, msg, levels, fname, custom_msg); - return cleanreturn(0, freelist); + return cleanreturn(0, &freelist); } continue; } @@ -1585,14 +1607,14 @@ PyErr_Format(PyExc_TypeError, "Required argument " "'%s' (pos %d) not found", keyword, i+1); - return cleanreturn(0, freelist); + return cleanreturn(0, &freelist); } /* current code reports success when all required args * fulfilled and no keyword args left, with no further * validation. XXX Maybe skip this in debug build ? */ if (!nkeywords) - return cleanreturn(1, freelist); + return cleanreturn(1, &freelist); /* We are into optional args, skip thru to any remaining * keyword args */ @@ -1600,7 +1622,7 @@ if (msg) { PyErr_Format(PyExc_RuntimeError, "%s: '%s'", msg, format); - return cleanreturn(0, freelist); + return cleanreturn(0, &freelist); } } @@ -1608,7 +1630,7 @@ PyErr_Format(PyExc_RuntimeError, "more argument specifiers than keyword list entries " "(remaining format:'%s')", format); - return cleanreturn(0, freelist); + return cleanreturn(0, &freelist); } /* make sure there are no extraneous keyword arguments */ @@ -1621,7 +1643,7 @@ if (!PyString_Check(key)) { PyErr_SetString(PyExc_TypeError, "keywords must be strings"); - return cleanreturn(0, freelist); + return cleanreturn(0, &freelist); } ks = PyString_AsString(key); for (i = 0; i < len; i++) { @@ -1635,12 +1657,12 @@ "'%s' is an invalid keyword " "argument for this function", ks); - return cleanreturn(0, freelist); + return cleanreturn(0, &freelist); } } } - return cleanreturn(1, freelist); + return cleanreturn(1, &freelist); } diff --git a/pypy/module/cpyext/src/thread.c b/pypy/module/cpyext/src/thread.c new file mode 100644 --- /dev/null +++ b/pypy/module/cpyext/src/thread.c @@ -0,0 +1,313 @@ +#include +#include "pythread.h" + +/* ------------------------------------------------------------------------ +Per-thread data ("key") support. + +Use PyThread_create_key() to create a new key. This is typically shared +across threads. + +Use PyThread_set_key_value(thekey, value) to associate void* value with +thekey in the current thread. Each thread has a distinct mapping of thekey +to a void* value. Caution: if the current thread already has a mapping +for thekey, value is ignored. + +Use PyThread_get_key_value(thekey) to retrieve the void* value associated +with thekey in the current thread. This returns NULL if no value is +associated with thekey in the current thread. + +Use PyThread_delete_key_value(thekey) to forget the current thread's associated +value for thekey. PyThread_delete_key(thekey) forgets the values associated +with thekey across *all* threads. + +While some of these functions have error-return values, none set any +Python exception. + +None of the functions does memory management on behalf of the void* values. +You need to allocate and deallocate them yourself. If the void* values +happen to be PyObject*, these functions don't do refcount operations on +them either. + +The GIL does not need to be held when calling these functions; they supply +their own locking. This isn't true of PyThread_create_key(), though (see +next paragraph). + +There's a hidden assumption that PyThread_create_key() will be called before +any of the other functions are called. There's also a hidden assumption +that calls to PyThread_create_key() are serialized externally. +------------------------------------------------------------------------ */ + +#ifdef MS_WINDOWS +#include + +/* use native Windows TLS functions */ +#define Py_HAVE_NATIVE_TLS + +int +PyThread_create_key(void) +{ + return (int) TlsAlloc(); +} + +void +PyThread_delete_key(int key) +{ + TlsFree(key); +} + +/* We must be careful to emulate the strange semantics implemented in thread.c, + * where the value is only set if it hasn't been set before. + */ +int +PyThread_set_key_value(int key, void *value) +{ + BOOL ok; + void *oldvalue; + + assert(value != NULL); + oldvalue = TlsGetValue(key); + if (oldvalue != NULL) + /* ignore value if already set */ + return 0; + ok = TlsSetValue(key, value); + if (!ok) + return -1; + return 0; +} + +void * +PyThread_get_key_value(int key) +{ + /* because TLS is used in the Py_END_ALLOW_THREAD macro, + * it is necessary to preserve the windows error state, because + * it is assumed to be preserved across the call to the macro. + * Ideally, the macro should be fixed, but it is simpler to + * do it here. + */ + DWORD error = GetLastError(); + void *result = TlsGetValue(key); + SetLastError(error); + return result; +} + +void +PyThread_delete_key_value(int key) +{ + /* NULL is used as "key missing", and it is also the default + * given by TlsGetValue() if nothing has been set yet. + */ + TlsSetValue(key, NULL); +} + +/* reinitialization of TLS is not necessary after fork when using + * the native TLS functions. And forking isn't supported on Windows either. + */ +void +PyThread_ReInitTLS(void) +{} + +#else /* MS_WINDOWS */ + +/* A singly-linked list of struct key objects remembers all the key->value + * associations. File static keyhead heads the list. keymutex is used + * to enforce exclusion internally. + */ +struct key { + /* Next record in the list, or NULL if this is the last record. */ + struct key *next; + + /* The thread id, according to PyThread_get_thread_ident(). */ + long id; + + /* The key and its associated value. */ + int key; + void *value; +}; + +static struct key *keyhead = NULL; +static PyThread_type_lock keymutex = NULL; +static int nkeys = 0; /* PyThread_create_key() hands out nkeys+1 next */ + +/* Internal helper. + * If the current thread has a mapping for key, the appropriate struct key* + * is returned. NB: value is ignored in this case! + * If there is no mapping for key in the current thread, then: + * If value is NULL, NULL is returned. + * Else a mapping of key to value is created for the current thread, + * and a pointer to a new struct key* is returned; except that if + * malloc() can't find room for a new struct key*, NULL is returned. + * So when value==NULL, this acts like a pure lookup routine, and when + * value!=NULL, this acts like dict.setdefault(), returning an existing + * mapping if one exists, else creating a new mapping. + * + * Caution: this used to be too clever, trying to hold keymutex only + * around the "p->next = keyhead; keyhead = p" pair. That allowed + * another thread to mutate the list, via key deletion, concurrent with + * find_key() crawling over the list. Hilarity ensued. For example, when + * the for-loop here does "p = p->next", p could end up pointing at a + * record that PyThread_delete_key_value() was concurrently free()'ing. + * That could lead to anything, from failing to find a key that exists, to + * segfaults. Now we lock the whole routine. + */ +static struct key * +find_key(int key, void *value) +{ + struct key *p, *prev_p; + long id = PyThread_get_thread_ident(); + + if (!keymutex) + return NULL; + PyThread_acquire_lock(keymutex, 1); + prev_p = NULL; + for (p = keyhead; p != NULL; p = p->next) { + if (p->id == id && p->key == key) + goto Done; + /* Sanity check. These states should never happen but if + * they do we must abort. Otherwise we'll end up spinning in + * in a tight loop with the lock held. A similar check is done + * in pystate.c tstate_delete_common(). */ + if (p == prev_p) + Py_FatalError("tls find_key: small circular list(!)"); + prev_p = p; + if (p->next == keyhead) + Py_FatalError("tls find_key: circular list(!)"); + } + if (value == NULL) { + assert(p == NULL); + goto Done; + } + p = (struct key *)malloc(sizeof(struct key)); + if (p != NULL) { + p->id = id; + p->key = key; + p->value = value; + p->next = keyhead; + keyhead = p; + } + Done: + PyThread_release_lock(keymutex); + return p; +} + +/* Return a new key. This must be called before any other functions in + * this family, and callers must arrange to serialize calls to this + * function. No violations are detected. + */ +int +PyThread_create_key(void) +{ + /* All parts of this function are wrong if it's called by multiple + * threads simultaneously. + */ + if (keymutex == NULL) + keymutex = PyThread_allocate_lock(); + return ++nkeys; +} + +/* Forget the associations for key across *all* threads. */ +void +PyThread_delete_key(int key) +{ + struct key *p, **q; + + PyThread_acquire_lock(keymutex, 1); + q = &keyhead; + while ((p = *q) != NULL) { + if (p->key == key) { + *q = p->next; + free((void *)p); + /* NB This does *not* free p->value! */ + } + else + q = &p->next; + } + PyThread_release_lock(keymutex); +} + +/* Confusing: If the current thread has an association for key, + * value is ignored, and 0 is returned. Else an attempt is made to create + * an association of key to value for the current thread. 0 is returned + * if that succeeds, but -1 is returned if there's not enough memory + * to create the association. value must not be NULL. + */ +int +PyThread_set_key_value(int key, void *value) +{ + struct key *p; + + assert(value != NULL); + p = find_key(key, value); + if (p == NULL) + return -1; + else + return 0; +} + +/* Retrieve the value associated with key in the current thread, or NULL + * if the current thread doesn't have an association for key. + */ +void * +PyThread_get_key_value(int key) +{ + struct key *p = find_key(key, NULL); + + if (p == NULL) + return NULL; + else + return p->value; +} + +/* Forget the current thread's association for key, if any. */ +void +PyThread_delete_key_value(int key) +{ + long id = PyThread_get_thread_ident(); + struct key *p, **q; + + PyThread_acquire_lock(keymutex, 1); + q = &keyhead; + while ((p = *q) != NULL) { + if (p->key == key && p->id == id) { + *q = p->next; + free((void *)p); + /* NB This does *not* free p->value! */ + break; + } + else + q = &p->next; + } + PyThread_release_lock(keymutex); +} + +/* Forget everything not associated with the current thread id. + * This function is called from PyOS_AfterFork(). It is necessary + * because other thread ids which were in use at the time of the fork + * may be reused for new threads created in the forked process. + */ +void +PyThread_ReInitTLS(void) +{ + long id = PyThread_get_thread_ident(); + struct key *p, **q; + + if (!keymutex) + return; + + /* As with interpreter_lock in PyEval_ReInitThreads() + we just create a new lock without freeing the old one */ + keymutex = PyThread_allocate_lock(); + + /* Delete all keys which do not match the current thread id */ + q = &keyhead; + while ((p = *q) != NULL) { + if (p->id != id) { + *q = p->next; + free((void *)p); + /* NB This does *not* free p->value! */ + } + else + q = &p->next; + } +} + +#endif /* !MS_WINDOWS */ diff --git a/pypy/module/cpyext/stringobject.py b/pypy/module/cpyext/stringobject.py --- a/pypy/module/cpyext/stringobject.py +++ b/pypy/module/cpyext/stringobject.py @@ -130,6 +130,11 @@ @cpython_api([PyObject], rffi.CCHARP, error=0) def PyString_AsString(space, ref): + if from_ref(space, rffi.cast(PyObject, ref.c_ob_type)) is space.w_str: + pass # typecheck returned "ok" without forcing 'ref' at all + elif not PyString_Check(space, ref): # otherwise, use the alternate way + raise OperationError(space.w_TypeError, space.wrap( + "PyString_AsString only support strings")) ref_str = rffi.cast(PyStringObject, ref) if not ref_str.c_buffer: # copy string buffer diff --git a/pypy/module/cpyext/test/conftest.py b/pypy/module/cpyext/test/conftest.py --- a/pypy/module/cpyext/test/conftest.py +++ b/pypy/module/cpyext/test/conftest.py @@ -10,7 +10,7 @@ return False def pytest_funcarg__space(request): - return gettestobjspace(usemodules=['cpyext', 'thread', '_rawffi']) + return gettestobjspace(usemodules=['cpyext', 'thread', '_rawffi', 'array']) def pytest_funcarg__api(request): return request.cls.api diff --git a/pypy/module/cpyext/test/foo.c b/pypy/module/cpyext/test/foo.c --- a/pypy/module/cpyext/test/foo.c +++ b/pypy/module/cpyext/test/foo.c @@ -176,6 +176,8 @@ {NULL} /* Sentinel */ }; +PyDoc_STRVAR(foo_doc, "foo is for testing."); + static PyTypeObject footype = { PyVarObject_HEAD_INIT(NULL, 0) "foo.foo", /*tp_name*/ @@ -198,7 +200,7 @@ (setattrofunc)foo_setattro, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ - 0, /*tp_doc*/ + foo_doc, /*tp_doc*/ 0, /*tp_traverse*/ 0, /*tp_clear*/ 0, /*tp_richcompare*/ diff --git a/pypy/module/cpyext/test/test_api.py b/pypy/module/cpyext/test/test_api.py --- a/pypy/module/cpyext/test/test_api.py +++ b/pypy/module/cpyext/test/test_api.py @@ -19,7 +19,8 @@ class BaseApiTest(LeakCheckingTest): def setup_class(cls): - cls.space = space = gettestobjspace(usemodules=['cpyext', 'thread', '_rawffi']) + cls.space = space = gettestobjspace(usemodules=['cpyext', 'thread', '_rawffi', + 'array']) # warm up reference counts: # - the posix module allocates a HCRYPTPROV on Windows diff --git a/pypy/module/cpyext/test/test_arraymodule.py b/pypy/module/cpyext/test/test_arraymodule.py --- a/pypy/module/cpyext/test/test_arraymodule.py +++ b/pypy/module/cpyext/test/test_arraymodule.py @@ -1,3 +1,4 @@ +from pypy.conftest import gettestobjspace from pypy.module.cpyext.test.test_cpyext import AppTestCpythonExtensionBase import py diff --git a/pypy/module/cpyext/test/test_bufferobject.py b/pypy/module/cpyext/test/test_bufferobject.py --- a/pypy/module/cpyext/test/test_bufferobject.py +++ b/pypy/module/cpyext/test/test_bufferobject.py @@ -48,3 +48,17 @@ ]) b = module.buffer_new() raises(AttributeError, getattr, b, 'x') + + def test_array_buffer(self): + module = self.import_extension('foo', [ + ("roundtrip", "METH_O", + """ + PyBufferObject *buf = (PyBufferObject *)args; + return PyString_FromStringAndSize(buf->b_ptr, buf->b_size); + """), + ]) + import array + a = array.array('c', 'text') + b = buffer(a) + assert module.roundtrip(b) == 'text' + diff --git a/pypy/module/cpyext/test/test_cpyext.py b/pypy/module/cpyext/test/test_cpyext.py --- a/pypy/module/cpyext/test/test_cpyext.py +++ b/pypy/module/cpyext/test/test_cpyext.py @@ -35,7 +35,7 @@ class AppTestApi: def setup_class(cls): - cls.space = gettestobjspace(usemodules=['cpyext', 'thread', '_rawffi']) + cls.space = gettestobjspace(usemodules=['cpyext', 'thread', '_rawffi', 'array']) from pypy.rlib.libffi import get_libc_name cls.w_libc = cls.space.wrap(get_libc_name()) @@ -106,10 +106,7 @@ del obj import gc; gc.collect() - try: - del space.getexecutioncontext().cpyext_threadstate - except AttributeError: - pass + space.getexecutioncontext().cleanup_cpyext_state() for w_obj in state.non_heaptypes_w: Py_DecRef(space, w_obj) @@ -168,8 +165,9 @@ return leaking class AppTestCpythonExtensionBase(LeakCheckingTest): + def setup_class(cls): - cls.space = gettestobjspace(usemodules=['cpyext', 'thread', '_rawffi']) + cls.space = gettestobjspace(usemodules=['cpyext', 'thread', '_rawffi', 'array']) cls.space.getbuiltinmodule("cpyext") from pypy.module.imp.importing import importhook importhook(cls.space, "os") # warm up reference counts diff --git a/pypy/module/cpyext/test/test_import.py b/pypy/module/cpyext/test/test_import.py --- a/pypy/module/cpyext/test/test_import.py +++ b/pypy/module/cpyext/test/test_import.py @@ -19,7 +19,7 @@ space.wrap('__name__'))) == 'foobar' def test_getmoduledict(self, space, api): - testmod = "binascii" + testmod = "_functools" w_pre_dict = api.PyImport_GetModuleDict() assert not space.is_true(space.contains(w_pre_dict, space.wrap(testmod))) diff --git a/pypy/module/cpyext/test/test_longobject.py b/pypy/module/cpyext/test/test_longobject.py --- a/pypy/module/cpyext/test/test_longobject.py +++ b/pypy/module/cpyext/test/test_longobject.py @@ -101,9 +101,9 @@ space.wrap((2, 7)))): py.test.skip("unsupported before Python 2.7") - assert api._PyLong_Sign(space.wrap(0L)) == 0 - assert api._PyLong_Sign(space.wrap(2L)) == 1 - assert api._PyLong_Sign(space.wrap(-2L)) == -1 + assert api._PyLong_Sign(space.wraplong(0L)) == 0 + assert api._PyLong_Sign(space.wraplong(2L)) == 1 + assert api._PyLong_Sign(space.wraplong(-2L)) == -1 assert api._PyLong_NumBits(space.wrap(0)) == 0 assert api._PyLong_NumBits(space.wrap(1)) == 1 diff --git a/pypy/module/cpyext/test/test_number.py b/pypy/module/cpyext/test/test_number.py --- a/pypy/module/cpyext/test/test_number.py +++ b/pypy/module/cpyext/test/test_number.py @@ -6,12 +6,12 @@ class TestIterator(BaseApiTest): def test_check(self, space, api): assert api.PyIndex_Check(space.wrap(12)) - assert api.PyIndex_Check(space.wrap(-12L)) + assert api.PyIndex_Check(space.wraplong(-12L)) assert not api.PyIndex_Check(space.wrap(12.1)) assert not api.PyIndex_Check(space.wrap('12')) assert api.PyNumber_Check(space.wrap(12)) - assert api.PyNumber_Check(space.wrap(-12L)) + assert api.PyNumber_Check(space.wraplong(-12L)) assert api.PyNumber_Check(space.wrap(12.1)) assert not api.PyNumber_Check(space.wrap('12')) assert not api.PyNumber_Check(space.wrap(1+3j)) @@ -21,7 +21,7 @@ assert api.PyLong_CheckExact(w_l) def test_number_int(self, space, api): - w_l = api.PyNumber_Int(space.wrap(123L)) + w_l = api.PyNumber_Int(space.wraplong(123L)) assert api.PyInt_CheckExact(w_l) w_l = api.PyNumber_Int(space.wrap(2 << 65)) assert api.PyLong_CheckExact(w_l) @@ -29,7 +29,7 @@ assert api.PyInt_CheckExact(w_l) def test_number_index(self, space, api): - w_l = api.PyNumber_Index(space.wrap(123L)) + w_l = api.PyNumber_Index(space.wraplong(123L)) assert api.PyLong_CheckExact(w_l) w_l = api.PyNumber_Index(space.wrap(42.3)) assert w_l is None diff --git a/pypy/module/cpyext/test/test_pyerrors.py b/pypy/module/cpyext/test/test_pyerrors.py --- a/pypy/module/cpyext/test/test_pyerrors.py +++ b/pypy/module/cpyext/test/test_pyerrors.py @@ -218,3 +218,51 @@ assert e.filename == "blyf" assert e.errno == errno.EBADF assert e.strerror == os.strerror(errno.EBADF) + + def test_GetSetExcInfo(self): + import sys + module = self.import_extension('foo', [ + ("getset_exc_info", "METH_VARARGS", + r''' + PyObject *type, *val, *tb; + PyObject *new_type, *new_val, *new_tb; + PyObject *result; + + if (!PyArg_ParseTuple(args, "OOO", &new_type, &new_val, &new_tb)) + return NULL; + + PyErr_GetExcInfo(&type, &val, &tb); + + Py_INCREF(new_type); + Py_INCREF(new_val); + Py_INCREF(new_tb); + PyErr_SetExcInfo(new_type, new_val, new_tb); + + result = Py_BuildValue("OOO", + type ? type : Py_None, + val ? val : Py_None, + tb ? tb : Py_None); + Py_XDECREF(type); + Py_XDECREF(val); + Py_XDECREF(tb); + return result; + ''' + ), + ]) + try: + raise ValueError(5) + except ValueError, old_exc: + new_exc = TypeError("TEST") + orig_sys_exc_info = sys.exc_info() + orig_exc_info = module.getset_exc_info(new_exc.__class__, + new_exc, None) + new_sys_exc_info = sys.exc_info() + new_exc_info = module.getset_exc_info(*orig_exc_info) + reset_sys_exc_info = sys.exc_info() + + assert orig_exc_info[0] is old_exc.__class__ + assert orig_exc_info[1] is old_exc + assert orig_exc_info == orig_sys_exc_info + assert orig_exc_info == reset_sys_exc_info + assert new_exc_info == (new_exc.__class__, new_exc, None) + assert new_exc_info == new_sys_exc_info diff --git a/pypy/module/cpyext/test/test_pystate.py b/pypy/module/cpyext/test/test_pystate.py --- a/pypy/module/cpyext/test/test_pystate.py +++ b/pypy/module/cpyext/test/test_pystate.py @@ -3,6 +3,10 @@ from pypy.rpython.lltypesystem.lltype import nullptr from pypy.module.cpyext.pystate import PyInterpreterState, PyThreadState from pypy.module.cpyext.pyobject import from_ref +from pypy.rpython.lltypesystem import lltype +from pypy.module.cpyext.test.test_cpyext import LeakCheckingTest, freeze_refcnts +from pypy.module.cpyext.pystate import PyThreadState_Get, PyInterpreterState_Head +from pypy.tool import leakfinder class AppTestThreads(AppTestCpythonExtensionBase): def test_allow_threads(self): @@ -21,6 +25,93 @@ # Should compile at least module.test() + + def test_thread_state_get(self): + module = self.import_extension('foo', [ + ("get", "METH_NOARGS", + """ + PyThreadState *tstate = PyThreadState_Get(); + if (tstate == NULL) { + return PyLong_FromLong(0); + } + if (tstate->interp != PyInterpreterState_Head()) { + return PyLong_FromLong(1); + } + if (tstate->interp->next != NULL) { + return PyLong_FromLong(2); + } + return PyLong_FromLong(3); + """), + ]) + assert module.get() == 3 + + def test_basic_threadstate_dance(self): + module = self.import_extension('foo', [ + ("dance", "METH_NOARGS", + """ + PyThreadState *old_tstate, *new_tstate; + + old_tstate = PyThreadState_Swap(NULL); + if (old_tstate == NULL) { + return PyLong_FromLong(0); + } + + new_tstate = PyThreadState_Get(); + if (new_tstate != NULL) { + return PyLong_FromLong(1); + } + + new_tstate = PyThreadState_Swap(old_tstate); + if (new_tstate != NULL) { + return PyLong_FromLong(2); + } + + new_tstate = PyThreadState_Get(); + if (new_tstate != old_tstate) { + return PyLong_FromLong(3); + } + + return PyLong_FromLong(4); + """), + ]) + assert module.dance() == 4 + + def test_threadstate_dict(self): + module = self.import_extension('foo', [ + ("getdict", "METH_NOARGS", + """ + PyObject *dict = PyThreadState_GetDict(); + Py_INCREF(dict); + return dict; + """), + ]) + assert isinstance(module.getdict(), dict) + + def test_savethread(self): + module = self.import_extension('foo', [ + ("bounce", "METH_NOARGS", + """ + PyThreadState *tstate = PyEval_SaveThread(); + if (tstate == NULL) { + return PyLong_FromLong(0); + } + + if (PyThreadState_Get() != NULL) { + return PyLong_FromLong(1); + } + + PyEval_RestoreThread(tstate); + + if (PyThreadState_Get() != tstate) { + return PyLong_FromLong(2); + } + + return PyLong_FromLong(3); + """), + ]) + + + class TestInterpreterState(BaseApiTest): def test_interpreter_head(self, space, api): state = api.PyInterpreterState_Head() @@ -29,31 +120,3 @@ def test_interpreter_next(self, space, api): state = api.PyInterpreterState_Head() assert nullptr(PyInterpreterState.TO) == api.PyInterpreterState_Next(state) - -class TestThreadState(BaseApiTest): - def test_thread_state_get(self, space, api): - ts = api.PyThreadState_Get() - assert ts != nullptr(PyThreadState.TO) - - def test_thread_state_interp(self, space, api): - ts = api.PyThreadState_Get() - assert ts.c_interp == api.PyInterpreterState_Head() - assert ts.c_interp.c_next == nullptr(PyInterpreterState.TO) - - def test_basic_threadstate_dance(self, space, api): - # Let extension modules call these functions, - # Not sure of the semantics in pypy though. - # (cpyext always acquires and releases the GIL around calls) - tstate = api.PyThreadState_Swap(None) - assert tstate is not None - assert not api.PyThreadState_Swap(tstate) - - api.PyEval_AcquireThread(tstate) - api.PyEval_ReleaseThread(tstate) - - def test_threadstate_dict(self, space, api): - ts = api.PyThreadState_Get() - ref = ts.c_dict - assert ref == api.PyThreadState_GetDict() - w_obj = from_ref(space, ref) - assert space.isinstance_w(w_obj, space.w_dict) diff --git a/pypy/module/cpyext/test/test_stringobject.py b/pypy/module/cpyext/test/test_stringobject.py --- a/pypy/module/cpyext/test/test_stringobject.py +++ b/pypy/module/cpyext/test/test_stringobject.py @@ -105,6 +105,15 @@ )]) assert module.string_as_string("huheduwe") == "huhe" + def test_py_string_as_string_None(self): + module = self.import_extension('foo', [ + ("string_None", "METH_VARARGS", + ''' + return PyString_AsString(Py_None); + ''' + )]) + raises(TypeError, module.string_None) + def test_AsStringAndSize(self): module = self.import_extension('foo', [ ("getstring", "METH_NOARGS", diff --git a/pypy/module/cpyext/test/test_thread.py b/pypy/module/cpyext/test/test_thread.py --- a/pypy/module/cpyext/test/test_thread.py +++ b/pypy/module/cpyext/test/test_thread.py @@ -5,6 +5,7 @@ from pypy.module.thread.ll_thread import allocate_ll_lock from pypy.module.cpyext.test.test_api import BaseApiTest +from pypy.module.cpyext.test.test_cpyext import AppTestCpythonExtensionBase class TestPyThread(BaseApiTest): @@ -38,3 +39,51 @@ api.PyThread_release_lock(lock) assert api.PyThread_acquire_lock(lock, 0) == 1 api.PyThread_free_lock(lock) + + +class AppTestThread(AppTestCpythonExtensionBase): + def test_tls(self): + module = self.import_extension('foo', [ + ("create_key", "METH_NOARGS", + """ + return PyInt_FromLong(PyThread_create_key()); + """), + ("test_key", "METH_O", + """ + int key = PyInt_AsLong(args); + if (PyThread_get_key_value(key) != NULL) { + PyErr_SetNone(PyExc_ValueError); + return NULL; + } + if (PyThread_set_key_value(key, (void*)123) < 0) { + PyErr_SetNone(PyExc_ValueError); + return NULL; + } + if (PyThread_get_key_value(key) != (void*)123) { + PyErr_SetNone(PyExc_ValueError); + return NULL; + } + Py_RETURN_NONE; + """), + ]) + key = module.create_key() + assert key > 0 + # Test value in main thread. + module.test_key(key) + raises(ValueError, module.test_key, key) + # Same test, in another thread. + result = [] + import thread, time + def in_thread(): + try: + module.test_key(key) + raises(ValueError, module.test_key, key) + except Exception, e: + result.append(e) + else: + result.append(True) + thread.start_new_thread(in_thread, ()) + while not result: + print "." + time.sleep(.5) + assert result == [True] diff --git a/pypy/module/cpyext/test/test_typeobject.py b/pypy/module/cpyext/test/test_typeobject.py --- a/pypy/module/cpyext/test/test_typeobject.py +++ b/pypy/module/cpyext/test/test_typeobject.py @@ -20,6 +20,7 @@ assert type(obj) is module.fooType print "type of obj has type", type(type(obj)) print "type of type of obj has type", type(type(type(obj))) + assert module.fooType.__doc__ == "foo is for testing." def test_typeobject_method_descriptor(self): module = self.import_module(name='foo') @@ -414,8 +415,11 @@ static int mp_ass_subscript(PyObject *self, PyObject *key, PyObject *value) { - PyErr_SetNone(PyExc_ZeroDivisionError); - return -1; + if (PyInt_Check(key)) { + PyErr_SetNone(PyExc_ZeroDivisionError); + return -1; + } + return 0; } PyMappingMethods tp_as_mapping; static PyTypeObject Foo_Type = { @@ -425,6 +429,36 @@ ''') obj = module.new_obj() raises(ZeroDivisionError, obj.__setitem__, 5, None) + res = obj.__setitem__('foo', None) + assert res is None + + def test_sq_contains(self): + module = self.import_extension('foo', [ + ("new_obj", "METH_NOARGS", + ''' + PyObject *obj; + Foo_Type.tp_as_sequence = &tp_as_sequence; + tp_as_sequence.sq_contains = sq_contains; + if (PyType_Ready(&Foo_Type) < 0) return NULL; + obj = PyObject_New(PyObject, &Foo_Type); + return obj; + ''' + )], + ''' + static int + sq_contains(PyObject *self, PyObject *value) + { + return 42; + } + PySequenceMethods tp_as_sequence; + static PyTypeObject Foo_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "foo.foo", + }; + ''') + obj = module.new_obj() + res = "foo" in obj + assert res is True def test_tp_iter(self): module = self.import_extension('foo', [ diff --git a/pypy/module/cpyext/typeobject.py b/pypy/module/cpyext/typeobject.py --- a/pypy/module/cpyext/typeobject.py +++ b/pypy/module/cpyext/typeobject.py @@ -307,6 +307,8 @@ if not space.is_true(space.issubtype(self, space.w_type)): self.flag_cpytype = True self.flag_heaptype = False + if pto.c_tp_doc: + self.w_doc = space.wrap(rffi.charp2str(pto.c_tp_doc)) @bootstrap_function def init_typeobject(space): @@ -624,7 +626,6 @@ Creates an interpreter type from a PyTypeObject structure. """ # missing: - # setting __doc__ if not defined and tp_doc defined # inheriting tp_as_* slots # unsupported: # tp_mro, tp_subclasses diff --git a/pypy/module/cpyext/unicodeobject.py b/pypy/module/cpyext/unicodeobject.py --- a/pypy/module/cpyext/unicodeobject.py +++ b/pypy/module/cpyext/unicodeobject.py @@ -178,7 +178,7 @@ @cpython_api([], Py_UNICODE, error=CANNOT_FAIL) def PyUnicode_GetMax(space): """Get the maximum ordinal for a Unicode character.""" - return unichr(runicode.MAXUNICODE) + return runicode.UNICHR(runicode.MAXUNICODE) @cpython_api([PyObject], rffi.CCHARP, error=CANNOT_FAIL) def PyUnicode_AS_DATA(space, ref): diff --git a/pypy/module/fcntl/test/test_fcntl.py b/pypy/module/fcntl/test/test_fcntl.py --- a/pypy/module/fcntl/test/test_fcntl.py +++ b/pypy/module/fcntl/test/test_fcntl.py @@ -13,7 +13,7 @@ class AppTestFcntl: def setup_class(cls): - space = gettestobjspace(usemodules=('fcntl', 'array')) + space = gettestobjspace(usemodules=('fcntl', 'array', 'struct')) cls.space = space tmpprefix = str(udir.ensure('test_fcntl', dir=1).join('tmp_')) cls.w_tmp = space.wrap(tmpprefix) diff --git a/pypy/module/imp/test/test_import.py b/pypy/module/imp/test/test_import.py --- a/pypy/module/imp/test/test_import.py +++ b/pypy/module/imp/test/test_import.py @@ -987,6 +987,10 @@ os.environ['LANG'] = oldlang class AppTestImportHooks(object): + + def setup_class(cls): + cls.space = gettestobjspace(usemodules=('struct',)) + def test_meta_path(self): tried_imports = [] class Importer(object): diff --git a/pypy/module/itertools/test/test_itertools.py b/pypy/module/itertools/test/test_itertools.py --- a/pypy/module/itertools/test/test_itertools.py +++ b/pypy/module/itertools/test/test_itertools.py @@ -891,7 +891,7 @@ class AppTestItertools27: def setup_class(cls): - cls.space = gettestobjspace(usemodules=['itertools']) + cls.space = gettestobjspace(usemodules=['itertools', 'struct']) if cls.space.is_true(cls.space.appexec([], """(): import sys; return sys.version_info < (2, 7) """)): diff --git a/pypy/module/marshal/interp_marshal.py b/pypy/module/marshal/interp_marshal.py --- a/pypy/module/marshal/interp_marshal.py +++ b/pypy/module/marshal/interp_marshal.py @@ -327,8 +327,10 @@ # %r not supported in rpython #u.raise_exc('invalid typecode in unmarshal: %r' % tc) c = ord(tc) - if c < 32 or c > 126: - s = '\\x' + hex(c) + if c < 16: + s = '\\x0%x' % c + elif c < 32 or c > 126: + s = '\\x%x' % c elif tc == '\\': s = r'\\' else: diff --git a/pypy/module/marshal/test/make_test_marshal.py b/pypy/module/marshal/test/make_test_marshal.py deleted file mode 100644 --- a/pypy/module/marshal/test/make_test_marshal.py +++ /dev/null @@ -1,78 +0,0 @@ - -TESTCASES = """\ - None - False - True - StopIteration - Ellipsis - 42 - -17 - sys.maxint - -1.25 - -1.25 #2 - 2+5j - 2+5j #2 - 42L - -1234567890123456789012345678901234567890L - hello # not interned - "hello" - () - (1, 2) - [] - [3, 4] - {} - {5: 6, 7: 8} - func.func_code - scopefunc.func_code - u'hello' - set() - set([1, 2]) - frozenset() - frozenset([3, 4]) -""".strip().split('\n') - -def readable(s): - for c, repl in ( - ("'", '_quote_'), ('"', '_Quote_'), (':', '_colon_'), ('.', '_dot_'), - ('[', '_list_'), (']', '_tsil_'), ('{', '_dict_'), ('}', '_tcid_'), - ('-', '_minus_'), ('+', '_plus_'), - (',', '_comma_'), ('(', '_brace_'), (')', '_ecarb_') ): - s = s.replace(c, repl) - lis = list(s) - for i, c in enumerate(lis): - if c.isalnum() or c == '_': - continue - lis[i] = '_' - return ''.join(lis) - -print """class AppTestMarshal: -""" -for line in TESTCASES: - line = line.strip() - name = readable(line) - version = '' - extra = '' - if line.endswith('#2'): - version = ', 2' - extra = '; assert len(s) in (9, 17)' - src = '''\ - def test_%(name)s(self): - import sys - hello = "he" - hello += "llo" - def func(x): - return lambda y: x+y - scopefunc = func(42) - import marshal, StringIO - case = %(line)s - print "case: %%-30s func=%(name)s" %% (case, ) - s = marshal.dumps(case%(version)s)%(extra)s - x = marshal.loads(s) - assert x == case - f = StringIO.StringIO() - marshal.dump(case, f) - f.seek(0) - x = marshal.load(f) - assert x == case -''' % {'name': name, 'line': line, 'version' : version, 'extra': extra} - print src diff --git a/pypy/module/marshal/test/test_marshal.py b/pypy/module/marshal/test/test_marshal.py --- a/pypy/module/marshal/test/test_marshal.py +++ b/pypy/module/marshal/test/test_marshal.py @@ -174,6 +174,11 @@ pass raises(ValueError, marshal.dumps, subtype) + def test_bad_typecode(self): + import marshal + exc = raises(ValueError, marshal.loads, chr(1)) + assert r"'\x01'" in exc.value.message + class AppTestRope(AppTestMarshal): def setup_class(cls): diff --git a/pypy/module/math/test/test_direct.py b/pypy/module/math/test/test_direct.py --- a/pypy/module/math/test/test_direct.py +++ b/pypy/module/math/test/test_direct.py @@ -55,6 +55,15 @@ ('frexp', (-1.25,), lambda x: x == (-0.625, 1)), ('modf', (4.25,), lambda x: x == (0.25, 4.0)), ('modf', (-4.25,), lambda x: x == (-0.25, -4.0)), + ('copysign', (1.5, 0.0), 1.5), + ('copysign', (1.5, -0.0), -1.5), + ('copysign', (1.5, INFINITY), 1.5), + ('copysign', (1.5, -INFINITY), -1.5), + ] + if sys.platform != 'win32': # all NaNs seem to be negative there...? + IRREGCASES += [ + ('copysign', (1.5, NAN), 1.5), + ('copysign', (1.75, -NAN), -1.75), # special case for -NAN here ] OVFCASES = [ diff --git a/pypy/module/math/test/test_math.py b/pypy/module/math/test/test_math.py --- a/pypy/module/math/test/test_math.py +++ b/pypy/module/math/test/test_math.py @@ -1,3 +1,4 @@ +from __future__ import with_statement import sys from pypy.conftest import gettestobjspace from pypy.module.math.test import test_direct @@ -5,7 +6,7 @@ class AppTestMath: def setup_class(cls): - cls.space = gettestobjspace(usemodules=['math']) + cls.space = gettestobjspace(usemodules=['math', 'struct']) cls.w_cases = cls.space.wrap(test_direct.MathTests.TESTCASES) cls.w_consistent_host = cls.space.wrap(test_direct.consistent_host) @@ -268,3 +269,7 @@ def __trunc__(self): return "truncated" assert math.trunc(foo()) == "truncated" + + def test_copysign_nan(self): + import math + assert math.copysign(1.0, float('-nan')) == -1.0 diff --git a/pypy/module/micronumpy/__init__.py b/pypy/module/micronumpy/__init__.py --- a/pypy/module/micronumpy/__init__.py +++ b/pypy/module/micronumpy/__init__.py @@ -29,6 +29,7 @@ 'flatiter': 'interp_numarray.W_FlatIterator', 'isna': 'interp_numarray.isna', 'concatenate': 'interp_numarray.concatenate', + 'repeat': 'interp_numarray.repeat', 'set_string_function': 'appbridge.set_string_function', @@ -37,26 +38,44 @@ 'True_': 'types.Bool.True', 'False_': 'types.Bool.False', + 'typeinfo': 'interp_dtype.get_dtype_cache(space).w_typeinfo', + 'generic': 'interp_boxes.W_GenericBox', 'number': 'interp_boxes.W_NumberBox', 'integer': 'interp_boxes.W_IntegerBox', 'signedinteger': 'interp_boxes.W_SignedIntegerBox', 'unsignedinteger': 'interp_boxes.W_UnsignedIntegerBox', 'bool_': 'interp_boxes.W_BoolBox', + 'bool8': 'interp_boxes.W_BoolBox', 'int8': 'interp_boxes.W_Int8Box', + 'byte': 'interp_boxes.W_Int8Box', 'uint8': 'interp_boxes.W_UInt8Box', + 'ubyte': 'interp_boxes.W_UInt8Box', 'int16': 'interp_boxes.W_Int16Box', + 'short': 'interp_boxes.W_Int16Box', 'uint16': 'interp_boxes.W_UInt16Box', + 'ushort': 'interp_boxes.W_UInt16Box', 'int32': 'interp_boxes.W_Int32Box', + 'intc': 'interp_boxes.W_Int32Box', 'uint32': 'interp_boxes.W_UInt32Box', + 'uintc': 'interp_boxes.W_UInt32Box', 'int64': 'interp_boxes.W_Int64Box', 'uint64': 'interp_boxes.W_UInt64Box', + 'longlong': 'interp_boxes.W_LongLongBox', + 'ulonglong': 'interp_boxes.W_ULongLongBox', 'int_': 'interp_boxes.W_LongBox', 'inexact': 'interp_boxes.W_InexactBox', 'floating': 'interp_boxes.W_FloatingBox', 'float_': 'interp_boxes.W_Float64Box', 'float32': 'interp_boxes.W_Float32Box', 'float64': 'interp_boxes.W_Float64Box', + 'intp': 'types.IntP.BoxType', + 'uintp': 'types.UIntP.BoxType', + 'flexible': 'interp_boxes.W_FlexibleBox', + 'character': 'interp_boxes.W_CharacterBox', + 'str_': 'interp_boxes.W_StringBox', + 'unicode_': 'interp_boxes.W_UnicodeBox', + 'void': 'interp_boxes.W_VoidBox', } # ufuncs @@ -67,6 +86,7 @@ ("arccos", "arccos"), ("arcsin", "arcsin"), ("arctan", "arctan"), + ("arctan2", "arctan2"), ("arccosh", "arccosh"), ("arcsinh", "arcsinh"), ("arctanh", "arctanh"), @@ -77,9 +97,15 @@ ("true_divide", "true_divide"), ("equal", "equal"), ("exp", "exp"), + ("exp2", "exp2"), + ("expm1", "expm1"), ("fabs", "fabs"), + ("fmax", "fmax"), + ("fmin", "fmin"), + ("fmod", "fmod"), ("floor", "floor"), ("ceil", "ceil"), + ("trunc", "trunc"), ("greater", "greater"), ("greater_equal", "greater_equal"), ("less", "less"), @@ -89,24 +115,44 @@ ("multiply", "multiply"), ("negative", "negative"), ("not_equal", "not_equal"), + ("radians", "radians"), + ("degrees", "degrees"), + ("deg2rad", "radians"), + ("rad2deg", "degrees"), ("reciprocal", "reciprocal"), ("sign", "sign"), + ("signbit", "signbit"), ("sin", "sin"), ("sinh", "sinh"), ("subtract", "subtract"), ('sqrt', 'sqrt'), + ('square', 'square'), ("tan", "tan"), ("tanh", "tanh"), ('bitwise_and', 'bitwise_and'), ('bitwise_or', 'bitwise_or'), ('bitwise_xor', 'bitwise_xor'), ('bitwise_not', 'invert'), + ('left_shift', 'left_shift'), + ('right_shift', 'right_shift'), + ('invert', 'invert'), ('isnan', 'isnan'), ('isinf', 'isinf'), + ('isneginf', 'isneginf'), + ('isposinf', 'isposinf'), + ('isfinite', 'isfinite'), ('logical_and', 'logical_and'), ('logical_xor', 'logical_xor'), ('logical_not', 'logical_not'), ('logical_or', 'logical_or'), + ('log', 'log'), + ('log2', 'log2'), + ('log10', 'log10'), + ('log1p', 'log1p'), + ('power', 'power'), + ('floor_divide', 'floor_divide'), + ('logaddexp', 'logaddexp'), + ('logaddexp2', 'logaddexp2'), ]: interpleveldefs[exposed] = "interp_ufuncs.get(space).%s" % impl diff --git a/pypy/module/micronumpy/app_numpy.py b/pypy/module/micronumpy/app_numpy.py --- a/pypy/module/micronumpy/app_numpy.py +++ b/pypy/module/micronumpy/app_numpy.py @@ -16,7 +16,7 @@ a[i][i] = 1 return a -def sum(a,axis=None): +def sum(a,axis=None, out=None): '''sum(a, axis=None) Sum of array elements over a given axis. @@ -43,17 +43,17 @@ # TODO: add to doc (once it's implemented): cumsum : Cumulative sum of array elements. if not hasattr(a, "sum"): a = _numpypy.array(a) - return a.sum(axis) + return a.sum(axis=axis, out=out) From noreply at buildbot.pypy.org Mon Apr 9 13:40:42 2012 From: noreply at buildbot.pypy.org (fijal) Date: Mon, 9 Apr 2012 13:40:42 +0200 (CEST) Subject: [pypy-commit] benchmarks default: hopefully the minimal change to make benchmark run again Message-ID: <20120409114042.A032646E0EF@wyvern.cs.uni-duesseldorf.de> Author: Maciej Fijalkowski Branch: Changeset: r178:d92ba6094dd0 Date: 2012-04-09 13:40 +0200 http://bitbucket.org/pypy/benchmarks/changeset/d92ba6094dd0/ Log: hopefully the minimal change to make benchmark run again diff --git a/lib/pypy/lib-python/modified-2.7/opcode.py b/lib/pypy/lib-python/modified-2.7/opcode.py --- a/lib/pypy/lib-python/modified-2.7/opcode.py +++ b/lib/pypy/lib-python/modified-2.7/opcode.py @@ -192,5 +192,6 @@ def_op('LOOKUP_METHOD', 201) # Index in name list hasname.append(201) def_op('CALL_METHOD', 202) # #args not including 'self' +def_op('BUILD_LIST_FROM_ARG', 203) del def_op, name_op, jrel_op, jabs_op diff --git a/lib/pypy/pypy/interpreter/pyopcode.py b/lib/pypy/pypy/interpreter/pyopcode.py --- a/lib/pypy/pypy/interpreter/pyopcode.py +++ b/lib/pypy/pypy/interpreter/pyopcode.py @@ -713,6 +713,17 @@ w_list = self.space.newlist(items) self.pushvalue(w_list) + def BUILD_LIST_FROM_ARG(self, _, next_instr): + # this is a little dance, because list has to be before the + # value + last_val = self.popvalue() + try: + lgt = self.space.int_w(self.space.len(last_val)) + except OperationError: + lgt = 0 # oh well + self.pushvalue(self.space.newlist([])) + self.pushvalue(last_val) + def LOAD_ATTR(self, nameindex, next_instr): "obj.attributename" w_obj = self.popvalue() diff --git a/lib/pypy/pypy/objspace/flow/test/test_objspace.py b/lib/pypy/pypy/objspace/flow/test/test_objspace.py --- a/lib/pypy/pypy/objspace/flow/test/test_objspace.py +++ b/lib/pypy/pypy/objspace/flow/test/test_objspace.py @@ -1,6 +1,6 @@ from __future__ import with_statement import new -import py +import py, sys from pypy.objspace.flow.model import Constant, Block, Link, Variable from pypy.objspace.flow.model import mkentrymap, c_last_exception from pypy.interpreter.argument import Arguments @@ -831,16 +831,25 @@ c.co_filename, c.co_name, c.co_firstlineno, c.co_lnotab) + def patch_opcodes(self, *opcodes): + flow_meth_names = flowcontext.FlowSpaceFrame.opcode_method_names + pyframe_meth_names = PyFrame.opcode_method_names + for name in opcodes: + num = bytecode_spec.opmap[name] + setattr(self, 'old_' + name, flow_meth_names[num]) + flow_meth_names[num] = pyframe_meth_names[num] + + def unpatch_opcodes(self, *opcodes): + flow_meth_names = flowcontext.FlowSpaceFrame.opcode_method_names + for name in opcodes: + num = bytecode_spec.opmap[name] + flow_meth_names[num] = getattr(self, 'old_' + name) + def test_callmethod_opcode(self): """ Tests code generated by pypy-c compiled with CALL_METHOD bytecode """ - flow_meth_names = flowcontext.FlowSpaceFrame.opcode_method_names - pyframe_meth_names = PyFrame.opcode_method_names - for name in ['CALL_METHOD', 'LOOKUP_METHOD']: - num = bytecode_spec.opmap[name] - locals()['old_' + name] = flow_meth_names[num] - flow_meth_names[num] = pyframe_meth_names[num] + self.patch_opcodes('CALL_METHOD', 'LOOKUP_METHOD') try: class X: def m(self): @@ -860,9 +869,31 @@ assert all_ops['simple_call'] == 2 assert all_ops['getattr'] == 1 finally: - for name in ['CALL_METHOD', 'LOOKUP_METHOD']: - num = bytecode_spec.opmap[name] - flow_meth_names[num] = locals()['old_' + name] + self.unpatch_opcodes('CALL_METHOD', 'LOOKUP_METHOD') + + def test_build_list_from_arg_opcode(self): + """ Tests code generated by pypy-c compiled with BUILD_LIST_FROM_ARG + bytecode + """ + if sys.version_info < (2, 7): + py.test.skip("2.7 only test") + self.patch_opcodes('BUILD_LIST_FROM_ARG') + try: + def f(): + return [i for i in "abc"] + + # this code is generated by pypy-c when compiling above f + pypy_code = 'd\x01\x00\xcb\x00\x00D]\x0c\x00}\x00\x00|\x00\x00^\x02\x00q\x07\x00S' + new_c = self.monkey_patch_code(f.func_code, 3, 67, pypy_code, (), + ('i',)) + f2 = new.function(new_c, locals(), 'f') + + graph = self.codetest(f2) + all_ops = self.all_operations(graph) + assert all_ops == {'newlist': 1, 'getattr': 1, 'simple_call': 1, + 'iter': 1, 'next': 1} + finally: + self.unpatch_opcodes('BUILD_LIST_FROM_ARG') def test_generator(self): def f(): From noreply at buildbot.pypy.org Mon Apr 9 14:54:14 2012 From: noreply at buildbot.pypy.org (RonnyPfannschmidt) Date: Mon, 9 Apr 2012 14:54:14 +0200 (CEST) Subject: [pypy-commit] pypy default: merge pylib/pytest updates & bugfixes Message-ID: <20120409125414.8103A46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Ronny Pfannschmidt Branch: Changeset: r54266:e87a047ee66f Date: 2012-04-09 14:53 +0200 http://bitbucket.org/pypy/pypy/changeset/e87a047ee66f/ Log: merge pylib/pytest updates & bugfixes diff --git a/_pytest/__init__.py b/_pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.1.0.dev4' +__version__ = '2.2.4.dev2' diff --git a/_pytest/assertion/__init__.py b/_pytest/assertion/__init__.py --- a/_pytest/assertion/__init__.py +++ b/_pytest/assertion/__init__.py @@ -2,35 +2,25 @@ support for presenting detailed information in failing assertions. """ import py -import imp -import marshal -import struct import sys import pytest from _pytest.monkeypatch import monkeypatch -from _pytest.assertion import reinterpret, util - -try: - from _pytest.assertion.rewrite import rewrite_asserts -except ImportError: - rewrite_asserts = None -else: - import ast +from _pytest.assertion import util def pytest_addoption(parser): group = parser.getgroup("debugconfig") - group.addoption('--assertmode', action="store", dest="assertmode", - choices=("on", "old", "off", "default"), default="default", - metavar="on|old|off", + group.addoption('--assert', action="store", dest="assertmode", + choices=("rewrite", "reinterp", "plain",), + default="rewrite", metavar="MODE", help="""control assertion debugging tools. -'off' performs no assertion debugging. -'old' reinterprets the expressions in asserts to glean information. -'on' (the default) rewrites the assert statements in test modules to provide -sub-expression results.""") +'plain' performs no assertion debugging. +'reinterp' reinterprets assert statements after they failed to provide assertion expression information. +'rewrite' (the default) rewrites assert statements in test modules on import +to provide assert expression information. """) group.addoption('--no-assert', action="store_true", default=False, - dest="noassert", help="DEPRECATED equivalent to --assertmode=off") + dest="noassert", help="DEPRECATED equivalent to --assert=plain") group.addoption('--nomagic', action="store_true", default=False, - dest="nomagic", help="DEPRECATED equivalent to --assertmode=off") + dest="nomagic", help="DEPRECATED equivalent to --assert=plain") class AssertionState: """State for the assertion plugin.""" @@ -40,89 +30,90 @@ self.trace = config.trace.root.get("assertion") def pytest_configure(config): - warn_about_missing_assertion() mode = config.getvalue("assertmode") if config.getvalue("noassert") or config.getvalue("nomagic"): - if mode not in ("off", "default"): - raise pytest.UsageError("assertion options conflict") - mode = "off" - elif mode == "default": - mode = "on" - if mode != "off": - def callbinrepr(op, left, right): - hook_result = config.hook.pytest_assertrepr_compare( - config=config, op=op, left=left, right=right) - for new_expl in hook_result: - if new_expl: - return '\n~'.join(new_expl) + mode = "plain" + if mode == "rewrite": + try: + import ast + except ImportError: + mode = "reinterp" + else: + if sys.platform.startswith('java'): + mode = "reinterp" + if mode != "plain": + _load_modules(mode) m = monkeypatch() config._cleanup.append(m.undo) m.setattr(py.builtin.builtins, 'AssertionError', reinterpret.AssertionError) - m.setattr(util, '_reprcompare', callbinrepr) - if mode == "on" and rewrite_asserts is None: - mode = "old" + hook = None + if mode == "rewrite": + hook = rewrite.AssertionRewritingHook() + sys.meta_path.append(hook) + warn_about_missing_assertion(mode) config._assertstate = AssertionState(config, mode) + config._assertstate.hook = hook config._assertstate.trace("configured with mode set to %r" % (mode,)) -def _write_pyc(co, source_path): - if hasattr(imp, "cache_from_source"): - # Handle PEP 3147 pycs. - pyc = py.path.local(imp.cache_from_source(str(source_path))) - pyc.ensure() - else: - pyc = source_path + "c" - mtime = int(source_path.mtime()) - fp = pyc.open("wb") - try: - fp.write(imp.get_magic()) - fp.write(struct.pack(" 0 and - item.identifier != "__future__"): + elif (not isinstance(item, ast.ImportFrom) or item.level > 0 or + item.module != "__future__"): lineno = item.lineno break pos += 1 @@ -118,9 +357,9 @@ for alias in aliases] mod.body[pos:pos] = imports # Collect asserts. - nodes = collections.deque([mod]) + nodes = [mod] while nodes: - node = nodes.popleft() + node = nodes.pop() for name, field in ast.iter_fields(node): if isinstance(field, list): new = [] @@ -143,7 +382,7 @@ """Get a new variable.""" # Use a character invalid in python identifiers to avoid clashing. name = "@py_assert" + str(next(self.variable_counter)) - self.variables.add(name) + self.variables.append(name) return name def assign(self, expr): @@ -198,7 +437,8 @@ # There's already a message. Don't mess with it. return [assert_] self.statements = [] - self.variables = set() + self.cond_chain = () + self.variables = [] self.variable_counter = itertools.count() self.stack = [] self.on_failure = [] @@ -220,11 +460,11 @@ else: raise_ = ast.Raise(exc, None, None) body.append(raise_) - # Delete temporary variables. - names = [ast.Name(name, ast.Del()) for name in self.variables] - if names: - delete = ast.Delete(names) - self.statements.append(delete) + # Clear temporary variables by setting them to None. + if self.variables: + variables = [ast.Name(name, ast.Store()) for name in self.variables] + clear = ast.Assign(variables, ast.Name("None", ast.Load())) + self.statements.append(clear) # Fix line numbers. for stmt in self.statements: set_location(stmt, assert_.lineno, assert_.col_offset) @@ -240,21 +480,38 @@ return name, self.explanation_param(expr) def visit_BoolOp(self, boolop): - operands = [] - explanations = [] + res_var = self.variable() + expl_list = self.assign(ast.List([], ast.Load())) + app = ast.Attribute(expl_list, "append", ast.Load()) + is_or = int(isinstance(boolop.op, ast.Or)) + body = save = self.statements + fail_save = self.on_failure + levels = len(boolop.values) - 1 self.push_format_context() - for operand in boolop.values: - res, explanation = self.visit(operand) - operands.append(res) - explanations.append(explanation) - expls = ast.Tuple([ast.Str(expl) for expl in explanations], ast.Load()) - is_or = ast.Num(isinstance(boolop.op, ast.Or)) - expl_template = self.helper("format_boolop", - ast.Tuple(operands, ast.Load()), expls, - is_or) + # Process each operand, short-circuting if needed. + for i, v in enumerate(boolop.values): + if i: + fail_inner = [] + self.on_failure.append(ast.If(cond, fail_inner, [])) + self.on_failure = fail_inner + self.push_format_context() + res, expl = self.visit(v) + body.append(ast.Assign([ast.Name(res_var, ast.Store())], res)) + expl_format = self.pop_format_context(ast.Str(expl)) + call = ast.Call(app, [expl_format], [], None, None) + self.on_failure.append(ast.Expr(call)) + if i < levels: + cond = res + if is_or: + cond = ast.UnaryOp(ast.Not(), cond) + inner = [] + self.statements.append(ast.If(cond, inner, [])) + self.statements = body = inner + self.statements = save + self.on_failure = fail_save + expl_template = self.helper("format_boolop", expl_list, ast.Num(is_or)) expl = self.pop_format_context(expl_template) - res = self.assign(ast.BoolOp(boolop.op, operands)) - return res, self.explanation_param(expl) + return ast.Name(res_var, ast.Load()), self.explanation_param(expl) def visit_UnaryOp(self, unary): pattern = unary_map[unary.op.__class__] @@ -288,7 +545,7 @@ new_star, expl = self.visit(call.starargs) arg_expls.append("*" + expl) if call.kwargs: - new_kwarg, expl = self.visit(call.kwarg) + new_kwarg, expl = self.visit(call.kwargs) arg_expls.append("**" + expl) expl = "%s(%s)" % (func_expl, ', '.join(arg_expls)) new_call = ast.Call(new_func, new_args, new_kwargs, new_star, new_kwarg) diff --git a/_pytest/capture.py b/_pytest/capture.py --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -11,22 +11,22 @@ group._addoption('-s', action="store_const", const="no", dest="capture", help="shortcut for --capture=no.") + at pytest.mark.tryfirst +def pytest_cmdline_parse(pluginmanager, args): + # we want to perform capturing already for plugin/conftest loading + if '-s' in args or "--capture=no" in args: + method = "no" + elif hasattr(os, 'dup') and '--capture=sys' not in args: + method = "fd" + else: + method = "sys" + capman = CaptureManager(method) + pluginmanager.register(capman, "capturemanager") + def addouterr(rep, outerr): - repr = getattr(rep, 'longrepr', None) - if not hasattr(repr, 'addsection'): - return for secname, content in zip(["out", "err"], outerr): if content: - repr.addsection("Captured std%s" % secname, content.rstrip()) - -def pytest_unconfigure(config): - # registered in config.py during early conftest.py loading - capman = config.pluginmanager.getplugin('capturemanager') - while capman._method2capture: - name, cap = capman._method2capture.popitem() - # XXX logging module may wants to close it itself on process exit - # otherwise we could do finalization here and call "reset()". - cap.suspend() + rep.sections.append(("Captured std%s" % secname, content)) class NoCapture: def startall(self): @@ -39,8 +39,9 @@ return "", "" class CaptureManager: - def __init__(self): + def __init__(self, defaultmethod=None): self._method2capture = {} + self._defaultmethod = defaultmethod def _maketempfile(self): f = py.std.tempfile.TemporaryFile() @@ -65,14 +66,6 @@ else: raise ValueError("unknown capturing method: %r" % method) - def _getmethod_preoptionparse(self, args): - if '-s' in args or "--capture=no" in args: - return "no" - elif hasattr(os, 'dup') and '--capture=sys' not in args: - return "fd" - else: - return "sys" - def _getmethod(self, config, fspath): if config.option.capture: method = config.option.capture @@ -85,16 +78,22 @@ method = "sys" return method + def reset_capturings(self): + for name, cap in self._method2capture.items(): + cap.reset() + def resumecapture_item(self, item): method = self._getmethod(item.config, item.fspath) if not hasattr(item, 'outerr'): item.outerr = ('', '') # we accumulate outerr on the item return self.resumecapture(method) - def resumecapture(self, method): + def resumecapture(self, method=None): if hasattr(self, '_capturing'): raise ValueError("cannot resume, already capturing with %r" % (self._capturing,)) + if method is None: + method = self._defaultmethod cap = self._method2capture.get(method) self._capturing = method if cap is None: @@ -164,17 +163,6 @@ def pytest_runtest_teardown(self, item): self.resumecapture_item(item) - def pytest__teardown_final(self, __multicall__, session): - method = self._getmethod(session.config, None) - self.resumecapture(method) - try: - rep = __multicall__.execute() - finally: - outerr = self.suspendcapture() - if rep: - addouterr(rep, outerr) - return rep - def pytest_keyboard_interrupt(self, excinfo): if hasattr(self, '_capturing'): self.suspendcapture() diff --git a/_pytest/config.py b/_pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -8,13 +8,15 @@ def pytest_cmdline_parse(pluginmanager, args): config = Config(pluginmanager) config.parse(args) - if config.option.debug: - config.trace.root.setwriter(sys.stderr.write) return config def pytest_unconfigure(config): - for func in config._cleanup: - func() + while 1: + try: + fin = config._cleanup.pop() + except IndexError: + break + fin() class Parser: """ Parser for command line arguments. """ @@ -81,6 +83,7 @@ self._inidict[name] = (help, type, default) self._ininames.append(name) + class OptionGroup: def __init__(self, name, description="", parser=None): self.name = name @@ -256,11 +259,14 @@ self.hook = self.pluginmanager.hook self._inicache = {} self._cleanup = [] - + @classmethod def fromdictargs(cls, option_dict, args): """ constructor useable for subprocesses. """ config = cls() + # XXX slightly crude way to initialize capturing + import _pytest.capture + _pytest.capture.pytest_cmdline_parse(config.pluginmanager, args) config._preparse(args, addopts=False) config.option.__dict__.update(option_dict) for x in config.option.plugins: @@ -285,11 +291,10 @@ def _setinitialconftest(self, args): # capture output during conftest init (#issue93) - from _pytest.capture import CaptureManager - capman = CaptureManager() - self.pluginmanager.register(capman, 'capturemanager') - # will be unregistered in capture.py's unconfigure() - capman.resumecapture(capman._getmethod_preoptionparse(args)) + # XXX introduce load_conftest hook to avoid needing to know + # about capturing plugin here + capman = self.pluginmanager.getplugin("capturemanager") + capman.resumecapture() try: try: self._conftest.setinitial(args) @@ -334,6 +339,7 @@ # Note that this can only be called once per testing process. assert not hasattr(self, 'args'), ( "can only parse cmdline args at most once per Config object") + self._origargs = args self._preparse(args) self._parser.hints.extend(self.pluginmanager._hints) args = self._parser.parse_setoption(args, self.option) @@ -341,6 +347,14 @@ args.append(py.std.os.getcwd()) self.args = args + def addinivalue_line(self, name, line): + """ add a line to an ini-file option. The option must have been + declared but might not yet be set in which case the line becomes the + the first line in its value. """ + x = self.getini(name) + assert isinstance(x, list) + x.append(line) # modifies the cached list inline + def getini(self, name): """ return configuration value from an ini file. If the specified name hasn't been registered through a prior ``parse.addini`` @@ -422,7 +436,7 @@ def getcfg(args, inibasenames): - args = [x for x in args if str(x)[0] != "-"] + args = [x for x in args if not str(x).startswith("-")] if not args: args = [py.path.local()] for arg in args: diff --git a/_pytest/core.py b/_pytest/core.py --- a/_pytest/core.py +++ b/_pytest/core.py @@ -16,11 +16,10 @@ "junitxml resultlog doctest").split() class TagTracer: - def __init__(self, prefix="[pytest] "): + def __init__(self): self._tag2proc = {} self.writer = None self.indent = 0 - self.prefix = prefix def get(self, name): return TagTracerSub(self, (name,)) @@ -30,7 +29,7 @@ if args: indent = " " * self.indent content = " ".join(map(str, args)) - self.writer("%s%s%s\n" %(self.prefix, indent, content)) + self.writer("%s%s [%s]\n" %(indent, content, ":".join(tags))) try: self._tag2proc[tags](tags, args) except KeyError: @@ -212,6 +211,14 @@ self.register(mod, modname) self.consider_module(mod) + def pytest_configure(self, config): + config.addinivalue_line("markers", + "tryfirst: mark a hook implementation function such that the " + "plugin machinery will try to call it first/as early as possible.") + config.addinivalue_line("markers", + "trylast: mark a hook implementation function such that the " + "plugin machinery will try to call it last/as late as possible.") + def pytest_plugin_registered(self, plugin): import pytest dic = self.call_plugin(plugin, "pytest_namespace", {}) or {} @@ -432,10 +439,7 @@ def _preloadplugins(): _preinit.append(PluginManager(load=True)) -def main(args=None, plugins=None): - """ returned exit code integer, after an in-process testing run - with the given command line arguments, preloading an optional list - of passed in plugin objects. """ +def _prepareconfig(args=None, plugins=None): if args is None: args = sys.argv[1:] elif isinstance(args, py.path.local): @@ -449,13 +453,19 @@ else: # subsequent calls to main will create a fresh instance _pluginmanager = PluginManager(load=True) hook = _pluginmanager.hook + if plugins: + for plugin in plugins: + _pluginmanager.register(plugin) + return hook.pytest_cmdline_parse( + pluginmanager=_pluginmanager, args=args) + +def main(args=None, plugins=None): + """ returned exit code integer, after an in-process testing run + with the given command line arguments, preloading an optional list + of passed in plugin objects. """ try: - if plugins: - for plugin in plugins: - _pluginmanager.register(plugin) - config = hook.pytest_cmdline_parse( - pluginmanager=_pluginmanager, args=args) - exitstatus = hook.pytest_cmdline_main(config=config) + config = _prepareconfig(args, plugins) + exitstatus = config.hook.pytest_cmdline_main(config=config) except UsageError: e = sys.exc_info()[1] sys.stderr.write("ERROR: %s\n" %(e.args[0],)) diff --git a/_pytest/helpconfig.py b/_pytest/helpconfig.py --- a/_pytest/helpconfig.py +++ b/_pytest/helpconfig.py @@ -1,7 +1,7 @@ """ version info, help messages, tracing configuration. """ import py import pytest -import inspect, sys +import os, inspect, sys from _pytest.core import varnames def pytest_addoption(parser): @@ -18,7 +18,29 @@ help="trace considerations of conftest.py files."), group.addoption('--debug', action="store_true", dest="debug", default=False, - help="generate and show internal debugging information.") + help="store internal tracing debug information in 'pytestdebug.log'.") + + +def pytest_cmdline_parse(__multicall__): + config = __multicall__.execute() + if config.option.debug: + path = os.path.abspath("pytestdebug.log") + f = open(path, 'w') + config._debugfile = f + f.write("versions pytest-%s, py-%s, python-%s\ncwd=%s\nargs=%s\n\n" %( + pytest.__version__, py.__version__, ".".join(map(str, sys.version_info)), + os.getcwd(), config._origargs)) + config.trace.root.setwriter(f.write) + sys.stderr.write("writing pytestdebug information to %s\n" % path) + return config + + at pytest.mark.trylast +def pytest_unconfigure(config): + if hasattr(config, '_debugfile'): + config._debugfile.close() + sys.stderr.write("wrote pytestdebug information to %s\n" % + config._debugfile.name) + config.trace.root.setwriter(None) def pytest_cmdline_main(config): @@ -34,6 +56,7 @@ elif config.option.help: config.pluginmanager.do_configure(config) showhelp(config) + config.pluginmanager.do_unconfigure(config) return 0 def showhelp(config): @@ -91,7 +114,7 @@ verinfo = getpluginversioninfo(config) if verinfo: lines.extend(verinfo) - + if config.option.traceconfig: lines.append("active plugins:") plugins = [] diff --git a/_pytest/hookspec.py b/_pytest/hookspec.py --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -121,16 +121,23 @@ def pytest_itemstart(item, node=None): """ (deprecated, use pytest_runtest_logstart). """ -def pytest_runtest_protocol(item): - """ implements the standard runtest_setup/call/teardown protocol including - capturing exceptions and calling reporting hooks on the results accordingly. +def pytest_runtest_protocol(item, nextitem): + """ implements the runtest_setup/call/teardown protocol for + the given test item, including capturing exceptions and calling + reporting hooks. + + :arg item: test item for which the runtest protocol is performed. + + :arg nexitem: the scheduled-to-be-next test item (or None if this + is the end my friend). This argument is passed on to + :py:func:`pytest_runtest_teardown`. :return boolean: True if no further hook implementations should be invoked. """ pytest_runtest_protocol.firstresult = True def pytest_runtest_logstart(nodeid, location): - """ signal the start of a test run. """ + """ signal the start of running a single test item. """ def pytest_runtest_setup(item): """ called before ``pytest_runtest_call(item)``. """ @@ -138,8 +145,14 @@ def pytest_runtest_call(item): """ called to execute the test ``item``. """ -def pytest_runtest_teardown(item): - """ called after ``pytest_runtest_call``. """ +def pytest_runtest_teardown(item, nextitem): + """ called after ``pytest_runtest_call``. + + :arg nexitem: the scheduled-to-be-next test item (None if no further + test item is scheduled). This argument can be used to + perform exact teardowns, i.e. calling just enough finalizers + so that nextitem only needs to call setup-functions. + """ def pytest_runtest_makereport(item, call): """ return a :py:class:`_pytest.runner.TestReport` object @@ -149,15 +162,8 @@ pytest_runtest_makereport.firstresult = True def pytest_runtest_logreport(report): - """ process item test report. """ - -# special handling for final teardown - somewhat internal for now -def pytest__teardown_final(session): - """ called before test session finishes. """ -pytest__teardown_final.firstresult = True - -def pytest__teardown_final_logerror(report, session): - """ called if runtest_teardown_final failed. """ + """ process a test setup/call/teardown report relating to + the respective phase of executing a test. """ # ------------------------------------------------------------------------- # test session related hooks diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -25,21 +25,39 @@ long = int +class Junit(py.xml.Namespace): + pass + + # We need to get the subset of the invalid unicode ranges according to # XML 1.0 which are valid in this python build. Hence we calculate # this dynamically instead of hardcoding it. The spec range of valid # chars is: Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] # | [#x10000-#x10FFFF] -_illegal_unichrs = [(0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x19), - (0xD800, 0xDFFF), (0xFDD0, 0xFFFF)] -_illegal_ranges = [unicode("%s-%s") % (unichr(low), unichr(high)) - for (low, high) in _illegal_unichrs +_legal_chars = (0x09, 0x0A, 0x0d) +_legal_ranges = ( + (0x20, 0xD7FF), + (0xE000, 0xFFFD), + (0x10000, 0x10FFFF), +) +_legal_xml_re = [unicode("%s-%s") % (unichr(low), unichr(high)) + for (low, high) in _legal_ranges if low < sys.maxunicode] -illegal_xml_re = re.compile(unicode('[%s]') % - unicode('').join(_illegal_ranges)) -del _illegal_unichrs -del _illegal_ranges +_legal_xml_re = [unichr(x) for x in _legal_chars] + _legal_xml_re +illegal_xml_re = re.compile(unicode('[^%s]') % + unicode('').join(_legal_xml_re)) +del _legal_chars +del _legal_ranges +del _legal_xml_re +def bin_xml_escape(arg): + def repl(matchobj): + i = ord(matchobj.group()) + if i <= 0xFF: + return unicode('#x%02X') % i + else: + return unicode('#x%04X') % i + return illegal_xml_re.sub(repl, py.xml.escape(arg)) def pytest_addoption(parser): group = parser.getgroup("terminal reporting") @@ -68,117 +86,97 @@ logfile = os.path.expanduser(os.path.expandvars(logfile)) self.logfile = os.path.normpath(logfile) self.prefix = prefix - self.test_logs = [] + self.tests = [] self.passed = self.skipped = 0 self.failed = self.errors = 0 - self._durations = {} def _opentestcase(self, report): names = report.nodeid.split("::") names[0] = names[0].replace("/", '.') - names = tuple(names) - d = {'time': self._durations.pop(report.nodeid, "0")} names = [x.replace(".py", "") for x in names if x != "()"] classnames = names[:-1] if self.prefix: classnames.insert(0, self.prefix) - d['classname'] = ".".join(classnames) - d['name'] = py.xml.escape(names[-1]) - attrs = ['%s="%s"' % item for item in sorted(d.items())] - self.test_logs.append("\n" % " ".join(attrs)) + self.tests.append(Junit.testcase( + classname=".".join(classnames), + name=names[-1], + time=getattr(report, 'duration', 0) + )) - def _closetestcase(self): - self.test_logs.append("") - - def appendlog(self, fmt, *args): - def repl(matchobj): - i = ord(matchobj.group()) - if i <= 0xFF: - return unicode('#x%02X') % i - else: - return unicode('#x%04X') % i - args = tuple([illegal_xml_re.sub(repl, py.xml.escape(arg)) - for arg in args]) - self.test_logs.append(fmt % args) + def append(self, obj): + self.tests[-1].append(obj) def append_pass(self, report): self.passed += 1 - self._opentestcase(report) - self._closetestcase() def append_failure(self, report): - self._opentestcase(report) #msg = str(report.longrepr.reprtraceback.extraline) if "xfail" in report.keywords: - self.appendlog( - '') + self.append( + Junit.skipped(message="xfail-marked test passes unexpectedly")) self.skipped += 1 else: - self.appendlog('%s', - report.longrepr) + sec = dict(report.sections) + fail = Junit.failure(message="test failure") + fail.append(str(report.longrepr)) + self.append(fail) + for name in ('out', 'err'): + content = sec.get("Captured std%s" % name) + if content: + tag = getattr(Junit, 'system-'+name) + self.append(tag(bin_xml_escape(content))) self.failed += 1 - self._closetestcase() def append_collect_failure(self, report): - self._opentestcase(report) #msg = str(report.longrepr.reprtraceback.extraline) - self.appendlog('%s', - report.longrepr) - self._closetestcase() + self.append(Junit.failure(str(report.longrepr), + message="collection failure")) self.errors += 1 def append_collect_skipped(self, report): - self._opentestcase(report) #msg = str(report.longrepr.reprtraceback.extraline) - self.appendlog('%s', - report.longrepr) - self._closetestcase() + self.append(Junit.skipped(str(report.longrepr), + message="collection skipped")) self.skipped += 1 def append_error(self, report): - self._opentestcase(report) - self.appendlog('%s', - report.longrepr) - self._closetestcase() + self.append(Junit.error(str(report.longrepr), + message="test setup failure")) self.errors += 1 def append_skipped(self, report): - self._opentestcase(report) if "xfail" in report.keywords: - self.appendlog( - '%s', - report.keywords['xfail']) + self.append(Junit.skipped(str(report.keywords['xfail']), + message="expected test failure")) else: filename, lineno, skipreason = report.longrepr if skipreason.startswith("Skipped: "): skipreason = skipreason[9:] - self.appendlog('%s', - skipreason, "%s:%s: %s" % report.longrepr, - ) - self._closetestcase() + self.append( + Junit.skipped("%s:%s: %s" % report.longrepr, + type="pytest.skip", + message=skipreason + )) self.skipped += 1 def pytest_runtest_logreport(self, report): if report.passed: - self.append_pass(report) + if report.when == "call": # ignore setup/teardown + self._opentestcase(report) + self.append_pass(report) elif report.failed: + self._opentestcase(report) if report.when != "call": self.append_error(report) else: self.append_failure(report) elif report.skipped: + self._opentestcase(report) self.append_skipped(report) - def pytest_runtest_call(self, item, __multicall__): - start = time.time() - try: - return __multicall__.execute() - finally: - self._durations[item.nodeid] = time.time() - start - def pytest_collectreport(self, report): if not report.passed: + self._opentestcase(report) if report.failed: self.append_collect_failure(report) else: @@ -187,10 +185,11 @@ def pytest_internalerror(self, excrepr): self.errors += 1 data = py.xml.escape(excrepr) - self.test_logs.append( - '\n' - ' ' - '%s' % data) + self.tests.append( + Junit.testcase( + Junit.error(data, message="internal error"), + classname="pytest", + name="internal")) def pytest_sessionstart(self, session): self.suite_start_time = time.time() @@ -204,17 +203,17 @@ suite_stop_time = time.time() suite_time_delta = suite_stop_time - self.suite_start_time numtests = self.passed + self.failed + logfile.write('') - logfile.write('') - logfile.writelines(self.test_logs) - logfile.write('') + logfile.write(Junit.testsuite( + self.tests, + name="", + errors=self.errors, + failures=self.failed, + skips=self.skipped, + tests=numtests, + time="%.3f" % suite_time_delta, + ).unicode(indent=0)) logfile.close() def pytest_terminal_summary(self, terminalreporter): diff --git a/_pytest/main.py b/_pytest/main.py --- a/_pytest/main.py +++ b/_pytest/main.py @@ -2,7 +2,7 @@ import py import pytest, _pytest -import os, sys +import os, sys, imp tracebackcutdir = py.path.local(_pytest.__file__).dirpath() # exitcodes for the command line @@ -11,6 +11,8 @@ EXIT_INTERRUPTED = 2 EXIT_INTERNALERROR = 3 +name_re = py.std.re.compile("^[a-zA-Z_]\w*$") + def pytest_addoption(parser): parser.addini("norecursedirs", "directory patterns to avoid for recursion", type="args", default=('.*', 'CVS', '_darcs', '{arch}')) @@ -27,6 +29,9 @@ action="store", type="int", dest="maxfail", default=0, help="exit after first num failures or errors.") + group._addoption('--strict', action="store_true", + help="run pytest in strict mode, warnings become errors.") + group = parser.getgroup("collect", "collection") group.addoption('--collectonly', action="store_true", dest="collectonly", @@ -48,7 +53,7 @@ def pytest_namespace(): collect = dict(Item=Item, Collector=Collector, File=File, Session=Session) return dict(collect=collect) - + def pytest_configure(config): py.test.config = config # compatibiltiy if config.option.exitfirst: @@ -77,11 +82,11 @@ session.exitstatus = EXIT_INTERNALERROR if excinfo.errisinstance(SystemExit): sys.stderr.write("mainloop: caught Spurious SystemExit!\n") + if initstate >= 2: + config.hook.pytest_sessionfinish(session=session, + exitstatus=session.exitstatus or (session._testsfailed and 1)) if not session.exitstatus and session._testsfailed: session.exitstatus = EXIT_TESTSFAILED - if initstate >= 2: - config.hook.pytest_sessionfinish(session=session, - exitstatus=session.exitstatus) if initstate >= 1: config.pluginmanager.do_unconfigure(config) return session.exitstatus @@ -101,8 +106,12 @@ def pytest_runtestloop(session): if session.config.option.collectonly: return True - for item in session.session.items: - item.config.hook.pytest_runtest_protocol(item=item) + for i, item in enumerate(session.items): + try: + nextitem = session.items[i+1] + except IndexError: + nextitem = None + item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem) if session.shouldstop: raise session.Interrupted(session.shouldstop) return True @@ -132,7 +141,7 @@ return getattr(pytest, name) return property(fget, None, None, "deprecated attribute %r, use pytest.%s" % (name,name)) - + class Node(object): """ base class for all Nodes in the collection tree. Collector subclasses have children, Items are terminal nodes.""" @@ -143,13 +152,13 @@ #: the parent collector node. self.parent = parent - + #: the test config object self.config = config or parent.config #: the collection this node is part of self.session = session or parent.session - + #: filesystem path where this node was collected from self.fspath = getattr(parent, 'fspath', None) self.ihook = self.session.gethookproxy(self.fspath) @@ -224,13 +233,13 @@ def listchain(self): """ return list of all parent collectors up to self, starting from root of collection tree. """ - l = [self] - while 1: - x = l[0] - if x.parent is not None: # and x.parent.parent is not None: - l.insert(0, x.parent) - else: - return l + chain = [] + item = self + while item is not None: + chain.append(item) + item = item.parent + chain.reverse() + return chain def listnames(self): return [x.name for x in self.listchain()] @@ -325,6 +334,8 @@ """ a basic test invocation item. Note that for a single function there might be multiple test invocation items. """ + nextitem = None + def reportinfo(self): return self.fspath, None, "" @@ -399,6 +410,7 @@ self._notfound = [] self._initialpaths = set() self._initialparts = [] + self.items = items = [] for arg in args: parts = self._parsearg(arg) self._initialparts.append(parts) @@ -414,7 +426,6 @@ if not genitems: return rep.result else: - self.items = items = [] if rep.passed: for node in rep.result: self.items.extend(self.genitems(node)) @@ -469,16 +480,29 @@ return True def _tryconvertpyarg(self, x): - try: - mod = __import__(x, None, None, ['__doc__']) - except (ValueError, ImportError): - return x - p = py.path.local(mod.__file__) - if p.purebasename == "__init__": - p = p.dirpath() - else: - p = p.new(basename=p.purebasename+".py") - return str(p) + mod = None + path = [os.path.abspath('.')] + sys.path + for name in x.split('.'): + # ignore anything that's not a proper name here + # else something like --pyargs will mess up '.' + # since imp.find_module will actually sometimes work for it + # but it's supposed to be considered a filesystem path + # not a package + if name_re.match(name) is None: + return x + try: + fd, mod, type_ = imp.find_module(name, path) + except ImportError: + return x + else: + if fd is not None: + fd.close() + + if type_[2] != imp.PKG_DIRECTORY: + path = [os.path.dirname(mod)] + else: + path = [mod] + return mod def _parsearg(self, arg): """ return (fspath, names) tuple after checking the file exists. """ @@ -496,7 +520,7 @@ raise pytest.UsageError(msg + arg) parts[0] = path return parts - + def matchnodes(self, matching, names): self.trace("matchnodes", matching, names) self.trace.root.indent += 1 diff --git a/_pytest/mark.py b/_pytest/mark.py --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -14,12 +14,37 @@ "Terminate expression with ':' to make the first match match " "all subsequent tests (usually file-order). ") + group._addoption("-m", + action="store", dest="markexpr", default="", metavar="MARKEXPR", + help="only run tests matching given mark expression. " + "example: -m 'mark1 and not mark2'." + ) + + group.addoption("--markers", action="store_true", help= + "show markers (builtin, plugin and per-project ones).") + + parser.addini("markers", "markers for test functions", 'linelist') + +def pytest_cmdline_main(config): + if config.option.markers: + config.pluginmanager.do_configure(config) + tw = py.io.TerminalWriter() + for line in config.getini("markers"): + name, rest = line.split(":", 1) + tw.write("@pytest.mark.%s:" % name, bold=True) + tw.line(rest) + tw.line() + config.pluginmanager.do_unconfigure(config) + return 0 +pytest_cmdline_main.tryfirst = True + def pytest_collection_modifyitems(items, config): keywordexpr = config.option.keyword - if not keywordexpr: + matchexpr = config.option.markexpr + if not keywordexpr and not matchexpr: return selectuntil = False - if keywordexpr[-1] == ":": + if keywordexpr[-1:] == ":": selectuntil = True keywordexpr = keywordexpr[:-1] @@ -29,21 +54,38 @@ if keywordexpr and skipbykeyword(colitem, keywordexpr): deselected.append(colitem) else: - remaining.append(colitem) if selectuntil: keywordexpr = None + if matchexpr: + if not matchmark(colitem, matchexpr): + deselected.append(colitem) + continue + remaining.append(colitem) if deselected: config.hook.pytest_deselected(items=deselected) items[:] = remaining +class BoolDict: + def __init__(self, mydict): + self._mydict = mydict + def __getitem__(self, name): + return name in self._mydict + +def matchmark(colitem, matchexpr): + return eval(matchexpr, {}, BoolDict(colitem.obj.__dict__)) + +def pytest_configure(config): + if config.option.strict: + pytest.mark._config = config + def skipbykeyword(colitem, keywordexpr): """ return True if they given keyword expression means to skip this collector/item. """ if not keywordexpr: return - + itemkeywords = getkeywords(colitem) for key in filter(None, keywordexpr.split()): eor = key[:1] == '-' @@ -77,15 +119,31 @@ @py.test.mark.slowtest def test_function(): pass - + will set a 'slowtest' :class:`MarkInfo` object on the ``test_function`` object. """ def __getattr__(self, name): if name[0] == "_": raise AttributeError(name) + if hasattr(self, '_config'): + self._check(name) return MarkDecorator(name) + def _check(self, name): + try: + if name in self._markers: + return + except AttributeError: + pass + self._markers = l = set() + for line in self._config.getini("markers"): + beginning = line.split(":", 1) + x = beginning[0].split("(", 1)[0] + l.add(x) + if name not in self._markers: + raise AttributeError("%r not a registered marker" % (name,)) + class MarkDecorator: """ A decorator for test functions and test classes. When applied it will create :class:`MarkInfo` objects which may be @@ -133,8 +191,7 @@ holder = MarkInfo(self.markname, self.args, self.kwargs) setattr(func, self.markname, holder) else: - holder.kwargs.update(self.kwargs) - holder.args += self.args + holder.add(self.args, self.kwargs) return func kw = self.kwargs.copy() kw.update(kwargs) @@ -150,27 +207,20 @@ self.args = args #: keyword argument dictionary, empty if nothing specified self.kwargs = kwargs + self._arglist = [(args, kwargs.copy())] def __repr__(self): return "" % ( self.name, self.args, self.kwargs) -def pytest_itemcollected(item): - if not isinstance(item, pytest.Function): - return - try: - func = item.obj.__func__ - except AttributeError: - func = getattr(item.obj, 'im_func', item.obj) - pyclasses = (pytest.Class, pytest.Module) - for node in item.listchain(): - if isinstance(node, pyclasses): - marker = getattr(node.obj, 'pytestmark', None) - if marker is not None: - if isinstance(marker, list): - for mark in marker: - mark(func) - else: - marker(func) - node = node.parent - item.keywords.update(py.builtin._getfuncdict(func)) + def add(self, args, kwargs): + """ add a MarkInfo with the given args and kwargs. """ + self._arglist.append((args, kwargs)) + self.args += args + self.kwargs.update(kwargs) + + def __iter__(self): + """ yield MarkInfo objects each relating to a marking-call. """ + for args, kwargs in self._arglist: + yield MarkInfo(self.name, args, kwargs) + diff --git a/_pytest/monkeypatch.py b/_pytest/monkeypatch.py --- a/_pytest/monkeypatch.py +++ b/_pytest/monkeypatch.py @@ -13,6 +13,7 @@ monkeypatch.setenv(name, value, prepend=False) monkeypatch.delenv(name, value, raising=True) monkeypatch.syspath_prepend(path) + monkeypatch.chdir(path) All modifications will be undone after the requesting test function has finished. The ``raising`` @@ -30,6 +31,7 @@ def __init__(self): self._setattr = [] self._setitem = [] + self._cwd = None def setattr(self, obj, name, value, raising=True): """ set attribute ``name`` on ``obj`` to ``value``, by default @@ -83,6 +85,17 @@ self._savesyspath = sys.path[:] sys.path.insert(0, str(path)) + def chdir(self, path): + """ change the current working directory to the specified path + path can be a string or a py.path.local object + """ + if self._cwd is None: + self._cwd = os.getcwd() + if hasattr(path, "chdir"): + path.chdir() + else: + os.chdir(path) + def undo(self): """ undo previous changes. This call consumes the undo stack. Calling it a second time has no effect unless @@ -95,9 +108,17 @@ self._setattr[:] = [] for dictionary, name, value in self._setitem: if value is notset: - del dictionary[name] + try: + del dictionary[name] + except KeyError: + pass # was already deleted, so we have the desired state else: dictionary[name] = value self._setitem[:] = [] if hasattr(self, '_savesyspath'): sys.path[:] = self._savesyspath + del self._savesyspath + + if self._cwd is not None: + os.chdir(self._cwd) + self._cwd = None diff --git a/_pytest/nose.py b/_pytest/nose.py --- a/_pytest/nose.py +++ b/_pytest/nose.py @@ -13,6 +13,7 @@ call.excinfo = call2.excinfo + at pytest.mark.trylast def pytest_runtest_setup(item): if isinstance(item, (pytest.Function)): if isinstance(item.parent, pytest.Generator): diff --git a/_pytest/pastebin.py b/_pytest/pastebin.py --- a/_pytest/pastebin.py +++ b/_pytest/pastebin.py @@ -38,7 +38,11 @@ del tr._tw.__dict__['write'] def getproxy(): - return py.std.xmlrpclib.ServerProxy(url.xmlrpc).pastes + if sys.version_info < (3, 0): + from xmlrpclib import ServerProxy + else: + from xmlrpc.client import ServerProxy + return ServerProxy(url.xmlrpc).pastes def pytest_terminal_summary(terminalreporter): if terminalreporter.config.option.pastebin != "failed": diff --git a/_pytest/pdb.py b/_pytest/pdb.py --- a/_pytest/pdb.py +++ b/_pytest/pdb.py @@ -19,11 +19,13 @@ class pytestPDB: """ Pseudo PDB that defers to the real pdb. """ item = None + collector = None def set_trace(self): """ invoke PDB set_trace debugging, dropping any IO capturing. """ frame = sys._getframe().f_back - item = getattr(self, 'item', None) + item = self.item or self.collector + if item is not None: capman = item.config.pluginmanager.getplugin("capturemanager") out, err = capman.suspendcapture() @@ -38,6 +40,14 @@ pytestPDB.item = item pytest_runtest_setup = pytest_runtest_call = pytest_runtest_teardown = pdbitem + at pytest.mark.tryfirst +def pytest_make_collect_report(__multicall__, collector): + try: + pytestPDB.collector = collector + return __multicall__.execute() + finally: + pytestPDB.collector = None + def pytest_runtest_makereport(): pytestPDB.item = None @@ -60,7 +70,13 @@ tw.sep(">", "traceback") rep.toterminal(tw) tw.sep(">", "entering PDB") - post_mortem(call.excinfo._excinfo[2]) + # A doctest.UnexpectedException is not useful for post_mortem. + # Use the underlying exception instead: + if isinstance(call.excinfo.value, py.std.doctest.UnexpectedException): + tb = call.excinfo.value.exc_info[2] + else: + tb = call.excinfo._excinfo[2] + post_mortem(tb) rep._pdbshown = True return rep diff --git a/_pytest/pytester.py b/_pytest/pytester.py --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -25,6 +25,7 @@ _pytest_fullpath except NameError: _pytest_fullpath = os.path.abspath(pytest.__file__.rstrip("oc")) + _pytest_fullpath = _pytest_fullpath.replace("$py.class", ".py") def pytest_funcarg___pytest(request): return PytestArg(request) @@ -313,16 +314,6 @@ result.extend(session.genitems(colitem)) return result - def inline_genitems(self, *args): - #config = self.parseconfig(*args) - config = self.parseconfigure(*args) - rec = self.getreportrecorder(config) - session = Session(config) - config.hook.pytest_sessionstart(session=session) - session.perform_collect() - config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK) - return session.items, rec - def runitem(self, source): # used from runner functional tests item = self.getitem(source) @@ -343,64 +334,57 @@ l = list(args) + [p] reprec = self.inline_run(*l) reports = reprec.getreports("pytest_runtest_logreport") - assert len(reports) == 1, reports - return reports[0] + assert len(reports) == 3, reports # setup/call/teardown + return reports[1] + + def inline_genitems(self, *args): + return self.inprocess_run(list(args) + ['--collectonly']) def inline_run(self, *args): - args = ("-s", ) + args # otherwise FD leakage - config = self.parseconfig(*args) - reprec = self.getreportrecorder(config) - #config.pluginmanager.do_configure(config) - config.hook.pytest_cmdline_main(config=config) - #config.pluginmanager.do_unconfigure(config) - return reprec + items, rec = self.inprocess_run(args) + return rec - def config_preparse(self): - config = self.Config() - for plugin in self.plugins: - if isinstance(plugin, str): - config.pluginmanager.import_plugin(plugin) - else: - if isinstance(plugin, dict): - plugin = PseudoPlugin(plugin) - if not config.pluginmanager.isregistered(plugin): - config.pluginmanager.register(plugin) - return config + def inprocess_run(self, args, plugins=None): + rec = [] + items = [] + class Collect: + def pytest_configure(x, config): + rec.append(self.getreportrecorder(config)) + def pytest_itemcollected(self, item): + items.append(item) + if not plugins: + plugins = [] + plugins.append(Collect()) + ret = self.pytestmain(list(args), plugins=[Collect()]) + reprec = rec[0] + reprec.ret = ret + assert len(rec) == 1 + return items, reprec def parseconfig(self, *args): - if not args: - args = (self.tmpdir,) - config = self.config_preparse() - args = list(args) + args = [str(x) for x in args] for x in args: if str(x).startswith('--basetemp'): break else: args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp')) - config.parse(args) + import _pytest.core + config = _pytest.core._prepareconfig(args, self.plugins) + # the in-process pytest invocation needs to avoid leaking FDs + # so we register a "reset_capturings" callmon the capturing manager + # and make sure it gets called + config._cleanup.append( + config.pluginmanager.getplugin("capturemanager").reset_capturings) + import _pytest.config + self.request.addfinalizer( + lambda: _pytest.config.pytest_unconfigure(config)) return config - def reparseconfig(self, args=None): - """ this is used from tests that want to re-invoke parse(). """ - if not args: - args = [self.tmpdir] - oldconfig = getattr(py.test, 'config', None) - try: - c = py.test.config = self.Config() - c.basetemp = py.path.local.make_numbered_dir(prefix="reparse", - keep=0, rootdir=self.tmpdir, lock_timeout=None) - c.parse(args) - c.pluginmanager.do_configure(c) - self.request.addfinalizer(lambda: c.pluginmanager.do_unconfigure(c)) - return c - finally: - py.test.config = oldconfig - def parseconfigure(self, *args): config = self.parseconfig(*args) config.pluginmanager.do_configure(config) self.request.addfinalizer(lambda: - config.pluginmanager.do_unconfigure(config)) + config.pluginmanager.do_unconfigure(config)) return config def getitem(self, source, funcname="test_func"): @@ -420,7 +404,6 @@ self.makepyfile(__init__ = "#") self.config = config = self.parseconfigure(path, *configargs) node = self.getnode(config, path) - #config.pluginmanager.do_unconfigure(config) return node def collect_by_name(self, modcol, name): @@ -437,9 +420,16 @@ return py.std.subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw) def pytestmain(self, *args, **kwargs): - ret = pytest.main(*args, **kwargs) - if ret == 2: - raise KeyboardInterrupt() + class ResetCapturing: + @pytest.mark.trylast + def pytest_unconfigure(self, config): + capman = config.pluginmanager.getplugin("capturemanager") + capman.reset_capturings() + plugins = kwargs.setdefault("plugins", []) + rc = ResetCapturing() + plugins.append(rc) + return pytest.main(*args, **kwargs) + def run(self, *cmdargs): return self._run(*cmdargs) @@ -528,6 +518,8 @@ pexpect = py.test.importorskip("pexpect", "2.4") if hasattr(sys, 'pypy_version_info') and '64' in py.std.platform.machine(): pytest.skip("pypy-64 bit not supported") + if sys.platform == "darwin": + pytest.xfail("pexpect does not work reliably on darwin?!") logfile = self.tmpdir.join("spawn.out") child = pexpect.spawn(cmd, logfile=logfile.open("w")) child.timeout = expect_timeout @@ -540,10 +532,6 @@ return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % ( py.io.saferepr(out),) -class PseudoPlugin: - def __init__(self, vars): - self.__dict__.update(vars) - class ReportRecorder(object): def __init__(self, hook): self.hook = hook @@ -565,10 +553,17 @@ def getreports(self, names="pytest_runtest_logreport pytest_collectreport"): return [x.report for x in self.getcalls(names)] - def matchreport(self, inamepart="", names="pytest_runtest_logreport pytest_collectreport", when=None): + def matchreport(self, inamepart="", + names="pytest_runtest_logreport pytest_collectreport", when=None): """ return a testreport whose dotted import path matches """ l = [] for rep in self.getreports(names=names): + try: + if not when and rep.when != "call" and rep.passed: + # setup/teardown passing reports - let's ignore those + continue + except AttributeError: + pass if when and getattr(rep, 'when', None) != when: continue if not inamepart or inamepart in rep.nodeid.split("::"): diff --git a/_pytest/python.py b/_pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -4,6 +4,7 @@ import sys import pytest from py._code.code import TerminalRepr +from _pytest.monkeypatch import monkeypatch import _pytest cutdir = py.path.local(_pytest.__file__).dirpath() @@ -26,6 +27,24 @@ showfuncargs(config) return 0 + +def pytest_generate_tests(metafunc): + try: + param = metafunc.function.parametrize + except AttributeError: + return + for p in param: + metafunc.parametrize(*p.args, **p.kwargs) + +def pytest_configure(config): + config.addinivalue_line("markers", + "parametrize(argnames, argvalues): call a test function multiple " + "times passing in multiple different argument value sets. Example: " + "@parametrize('arg1', [1,2]) would lead to two calls of the decorated " + "test function, one with arg1=1 and another with arg1=2." + ) + + @pytest.mark.trylast def pytest_namespace(): raises.Exception = pytest.fail.Exception @@ -138,6 +157,7 @@ obj = obj.place_as self._fslineno = py.code.getfslineno(obj) + assert isinstance(self._fslineno[1], int), obj return self._fslineno def reportinfo(self): @@ -155,6 +175,7 @@ else: fspath, lineno = self._getfslineno() modpath = self.getmodpath() + assert isinstance(lineno, int) return fspath, lineno, modpath class PyCollectorMixin(PyobjMixin, pytest.Collector): @@ -200,6 +221,7 @@ module = self.getparent(Module).obj clscol = self.getparent(Class) cls = clscol and clscol.obj or None + transfer_markers(funcobj, cls, module) metafunc = Metafunc(funcobj, config=self.config, cls=cls, module=module) gentesthook = self.config.hook.pytest_generate_tests @@ -219,6 +241,19 @@ l.append(function) return l +def transfer_markers(funcobj, cls, mod): + # XXX this should rather be code in the mark plugin or the mark + # plugin should merge with the python plugin. + for holder in (cls, mod): + try: + pytestmark = holder.pytestmark + except AttributeError: + continue + if isinstance(pytestmark, list): + for mark in pytestmark: + mark(funcobj) + else: + pytestmark(funcobj) class Module(pytest.File, PyCollectorMixin): def _getobj(self): @@ -226,13 +261,8 @@ def _importtestmodule(self): # we assume we are only called once per module - from _pytest import assertion - assertion.before_module_import(self) try: - try: - mod = self.fspath.pyimport(ensuresyspath=True) - finally: - assertion.after_module_import(self) + mod = self.fspath.pyimport(ensuresyspath=True) except SyntaxError: excinfo = py.code.ExceptionInfo() raise self.CollectError(excinfo.getrepr(style="short")) @@ -244,7 +274,8 @@ " %s\n" "which is not the same as the test file we want to collect:\n" " %s\n" - "HINT: use a unique basename for your test file modules" + "HINT: remove __pycache__ / .pyc files and/or use a " + "unique basename for your test file modules" % e.args ) #print "imported test module", mod @@ -374,6 +405,7 @@ tw.line() tw.line("%s:%d" % (self.filename, self.firstlineno+1)) + class Generator(FunctionMixin, PyCollectorMixin, pytest.Collector): def collect(self): # test generators are seen as collectors but they also @@ -430,6 +462,7 @@ "yielded functions (deprecated) cannot have funcargs") else: if callspec is not None: + self.callspec = callspec self.funcargs = callspec.funcargs or {} self._genid = callspec.id if hasattr(callspec, "param"): @@ -506,15 +539,59 @@ request._fillfuncargs() _notexists = object() -class CallSpec: - def __init__(self, funcargs, id, param): - self.funcargs = funcargs - self.id = id + +class CallSpec2(object): + def __init__(self, metafunc): + self.metafunc = metafunc + self.funcargs = {} + self._idlist = [] + self.params = {} + self._globalid = _notexists + self._globalid_args = set() + self._globalparam = _notexists + + def copy(self, metafunc): + cs = CallSpec2(self.metafunc) + cs.funcargs.update(self.funcargs) + cs.params.update(self.params) + cs._idlist = list(self._idlist) + cs._globalid = self._globalid + cs._globalid_args = self._globalid_args + cs._globalparam = self._globalparam + return cs + + def _checkargnotcontained(self, arg): + if arg in self.params or arg in self.funcargs: + raise ValueError("duplicate %r" %(arg,)) + + def getparam(self, name): + try: + return self.params[name] + except KeyError: + if self._globalparam is _notexists: + raise ValueError(name) + return self._globalparam + + @property + def id(self): + return "-".join(map(str, filter(None, self._idlist))) + + def setmulti(self, valtype, argnames, valset, id): + for arg,val in zip(argnames, valset): + self._checkargnotcontained(arg) + getattr(self, valtype)[arg] = val + self._idlist.append(id) + + def setall(self, funcargs, id, param): + for x in funcargs: + self._checkargnotcontained(x) + self.funcargs.update(funcargs) + if id is not _notexists: + self._idlist.append(id) if param is not _notexists: - self.param = param - def __repr__(self): - return "" %( - self.id, getattr(self, 'param', '?'), self.funcargs) + assert self._globalparam is _notexists + self._globalparam = param + class Metafunc: def __init__(self, function, config=None, cls=None, module=None): @@ -528,31 +605,71 @@ self._calls = [] self._ids = py.builtin.set() + def parametrize(self, argnames, argvalues, indirect=False, ids=None): + """ Add new invocations to the underlying test function using the list + of argvalues for the given argnames. Parametrization is performed + during the collection phase. If you need to setup expensive resources + you may pass indirect=True and implement a funcarg factory which can + perform the expensive setup just before a test is actually run. + + :arg argnames: an argument name or a list of argument names + + :arg argvalues: a list of values for the argname or a list of tuples of + values for the list of argument names. + + :arg indirect: if True each argvalue corresponding to an argument will + be passed as request.param to its respective funcarg factory so + that it can perform more expensive setups during the setup phase of + a test rather than at collection time. + + :arg ids: list of string ids each corresponding to the argvalues so + that they are part of the test id. If no ids are provided they will + be generated automatically from the argvalues. + """ + if not isinstance(argnames, (tuple, list)): + argnames = (argnames,) + argvalues = [(val,) for val in argvalues] + if not indirect: + #XXX should we also check for the opposite case? + for arg in argnames: + if arg not in self.funcargnames: + raise ValueError("%r has no argument %r" %(self.function, arg)) + valtype = indirect and "params" or "funcargs" + if not ids: + idmaker = IDMaker() + ids = list(map(idmaker, argvalues)) + newcalls = [] + for callspec in self._calls or [CallSpec2(self)]: + for i, valset in enumerate(argvalues): + assert len(valset) == len(argnames) + newcallspec = callspec.copy(self) + newcallspec.setmulti(valtype, argnames, valset, ids[i]) + newcalls.append(newcallspec) + self._calls = newcalls + def addcall(self, funcargs=None, id=_notexists, param=_notexists): - """ add a new call to the underlying test function during the - collection phase of a test run. Note that request.addcall() is - called during the test collection phase prior and independently - to actual test execution. Therefore you should perform setup - of resources in a funcarg factory which can be instrumented - with the ``param``. + """ (deprecated, use parametrize) Add a new call to the underlying + test function during the collection phase of a test run. Note that + request.addcall() is called during the test collection phase prior and + independently to actual test execution. You should only use addcall() + if you need to specify multiple arguments of a test function. :arg funcargs: argument keyword dictionary used when invoking the test function. :arg id: used for reporting and identification purposes. If you - don't supply an `id` the length of the currently - list of calls to the test function will be used. + don't supply an `id` an automatic unique id will be generated. - :arg param: will be exposed to a later funcarg factory invocation - through the ``request.param`` attribute. It allows to - defer test fixture setup activities to when an actual - test is run. + :arg param: a parameter which will be exposed to a later funcarg factory + invocation through the ``request.param`` attribute. """ assert funcargs is None or isinstance(funcargs, dict) if funcargs is not None: for name in funcargs: if name not in self.funcargnames: pytest.fail("funcarg %r not used in this function." % name) + else: + funcargs = {} if id is None: raise ValueError("id=None not allowed") if id is _notexists: @@ -561,11 +678,26 @@ if id in self._ids: raise ValueError("duplicate id %r" % id) self._ids.add(id) - self._calls.append(CallSpec(funcargs, id, param)) + + cs = CallSpec2(self) + cs.setall(funcargs, id, param) + self._calls.append(cs) + +class IDMaker: + def __init__(self): + self.counter = 0 + def __call__(self, valset): + l = [] + for val in valset: + if not isinstance(val, (int, str)): + val = "."+str(self.counter) + self.counter += 1 + l.append(str(val)) + return "-".join(l) class FuncargRequest: """ A request for function arguments from a test function. - + Note that there is an optional ``param`` attribute in case there was an invocation to metafunc.addcall(param=...). If no such call was done in a ``pytest_generate_tests`` @@ -637,7 +769,7 @@ def applymarker(self, marker): - """ apply a marker to a single test function invocation. + """ Apply a marker to a single test function invocation. This method is useful if you don't want to have a keyword/marker on all function invocations. @@ -649,7 +781,7 @@ self._pyfuncitem.keywords[marker.markname] = marker def cached_setup(self, setup, teardown=None, scope="module", extrakey=None): - """ return a testing resource managed by ``setup`` & + """ Return a testing resource managed by ``setup`` & ``teardown`` calls. ``scope`` and ``extrakey`` determine when the ``teardown`` function will be called so that subsequent calls to ``setup`` would recreate the resource. @@ -698,11 +830,18 @@ self._raiselookupfailed(argname) funcargfactory = self._name2factory[argname].pop() oldarg = self._currentarg - self._currentarg = argname + mp = monkeypatch() + mp.setattr(self, '_currentarg', argname) + try: + param = self._pyfuncitem.callspec.getparam(argname) + except (AttributeError, ValueError): + pass + else: + mp.setattr(self, 'param', param, raising=False) try: self._funcargs[argname] = res = funcargfactory(request=self) finally: - self._currentarg = oldarg + mp.undo() return res def _getscopeitem(self, scope): @@ -817,8 +956,7 @@ >>> raises(ZeroDivisionError, f, x=0) - A third possibility is to use a string which which will - be executed:: + A third possibility is to use a string to be executed:: >>> raises(ZeroDivisionError, "f(0)") diff --git a/_pytest/resultlog.py b/_pytest/resultlog.py --- a/_pytest/resultlog.py +++ b/_pytest/resultlog.py @@ -63,6 +63,8 @@ self.write_log_entry(testpath, lettercode, longrepr) def pytest_runtest_logreport(self, report): + if report.when != "call" and report.passed: + return res = self.config.hook.pytest_report_teststatus(report=report) code = res[1] if code == 'x': @@ -89,5 +91,8 @@ self.log_outcome(report, code, longrepr) def pytest_internalerror(self, excrepr): - path = excrepr.reprcrash.path + reprcrash = getattr(excrepr, 'reprcrash', None) + path = getattr(reprcrash, "path", None) + if path is None: + path = "cwd:%s" % py.path.local() self.write_log_entry(path, '!', str(excrepr)) diff --git a/_pytest/runner.py b/_pytest/runner.py --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -1,6 +1,6 @@ """ basic collect and runtest protocol implementations """ -import py, sys +import py, sys, time from py._code.code import TerminalRepr def pytest_namespace(): @@ -14,33 +14,60 @@ # # pytest plugin hooks +def pytest_addoption(parser): + group = parser.getgroup("terminal reporting", "reporting", after="general") + group.addoption('--durations', + action="store", type="int", default=None, metavar="N", + help="show N slowest setup/test durations (N=0 for all)."), + +def pytest_terminal_summary(terminalreporter): + durations = terminalreporter.config.option.durations + if durations is None: + return + tr = terminalreporter + dlist = [] + for replist in tr.stats.values(): + for rep in replist: + if hasattr(rep, 'duration'): + dlist.append(rep) + if not dlist: + return + dlist.sort(key=lambda x: x.duration) + dlist.reverse() + if not durations: + tr.write_sep("=", "slowest test durations") + else: + tr.write_sep("=", "slowest %s test durations" % durations) + dlist = dlist[:durations] + + for rep in dlist: + nodeid = rep.nodeid.replace("::()::", "::") + tr.write_line("%02.2fs %-8s %s" % + (rep.duration, rep.when, nodeid)) + def pytest_sessionstart(session): session._setupstate = SetupState() - -def pytest_sessionfinish(session, exitstatus): - hook = session.config.hook - rep = hook.pytest__teardown_final(session=session) - if rep: - hook.pytest__teardown_final_logerror(session=session, report=rep) - session.exitstatus = 1 +def pytest_sessionfinish(session): + session._setupstate.teardown_all() class NodeInfo: def __init__(self, location): self.location = location -def pytest_runtest_protocol(item): +def pytest_runtest_protocol(item, nextitem): item.ihook.pytest_runtest_logstart( nodeid=item.nodeid, location=item.location, ) - runtestprotocol(item) + runtestprotocol(item, nextitem=nextitem) return True -def runtestprotocol(item, log=True): +def runtestprotocol(item, log=True, nextitem=None): rep = call_and_report(item, "setup", log) reports = [rep] if rep.passed: reports.append(call_and_report(item, "call", log)) - reports.append(call_and_report(item, "teardown", log)) + reports.append(call_and_report(item, "teardown", log, + nextitem=nextitem)) return reports def pytest_runtest_setup(item): @@ -49,16 +76,8 @@ def pytest_runtest_call(item): item.runtest() -def pytest_runtest_teardown(item): - item.session._setupstate.teardown_exact(item) - -def pytest__teardown_final(session): - call = CallInfo(session._setupstate.teardown_all, when="teardown") - if call.excinfo: - ntraceback = call.excinfo.traceback .cut(excludepath=py._pydir) - call.excinfo.traceback = ntraceback.filter() - longrepr = call.excinfo.getrepr(funcargs=True) - return TeardownErrorReport(longrepr) +def pytest_runtest_teardown(item, nextitem): + item.session._setupstate.teardown_exact(item, nextitem) def pytest_report_teststatus(report): if report.when in ("setup", "teardown"): @@ -74,18 +93,18 @@ # # Implementation -def call_and_report(item, when, log=True): - call = call_runtest_hook(item, when) +def call_and_report(item, when, log=True, **kwds): + call = call_runtest_hook(item, when, **kwds) hook = item.ihook report = hook.pytest_runtest_makereport(item=item, call=call) - if log and (when == "call" or not report.passed): + if log: hook.pytest_runtest_logreport(report=report) return report -def call_runtest_hook(item, when): +def call_runtest_hook(item, when, **kwds): hookname = "pytest_runtest_" + when ihook = getattr(item.ihook, hookname) - return CallInfo(lambda: ihook(item=item), when=when) + return CallInfo(lambda: ihook(item=item, **kwds), when=when) class CallInfo: """ Result/Exception info a function invocation. """ @@ -95,12 +114,16 @@ #: context of invocation: one of "setup", "call", #: "teardown", "memocollect" self.when = when + self.start = time.time() try: - self.result = func() - except KeyboardInterrupt: - raise - except: - self.excinfo = py.code.ExceptionInfo() + try: + self.result = func() + except KeyboardInterrupt: + raise + except: + self.excinfo = py.code.ExceptionInfo() + finally: + self.stop = time.time() def __repr__(self): if self.excinfo: @@ -120,6 +143,10 @@ return s class BaseReport(object): + + def __init__(self, **kw): + self.__dict__.update(kw) + def toterminal(self, out): longrepr = self.longrepr if hasattr(self, 'node'): @@ -139,6 +166,7 @@ def pytest_runtest_makereport(item, call): when = call.when + duration = call.stop-call.start keywords = dict([(x,1) for x in item.keywords]) excinfo = call.excinfo if not call.excinfo: @@ -160,14 +188,15 @@ else: # exception in setup or teardown longrepr = item._repr_failure_py(excinfo) return TestReport(item.nodeid, item.location, - keywords, outcome, longrepr, when) + keywords, outcome, longrepr, when, + duration=duration) class TestReport(BaseReport): """ Basic test report object (also used for setup and teardown calls if they fail). """ def __init__(self, nodeid, location, - keywords, outcome, longrepr, when): + keywords, outcome, longrepr, when, sections=(), duration=0, **extra): #: normalized collection node id self.nodeid = nodeid @@ -179,16 +208,25 @@ #: a name -> value dictionary containing all keywords and #: markers associated with a test invocation. self.keywords = keywords - + #: test outcome, always one of "passed", "failed", "skipped". self.outcome = outcome #: None or a failure representation. self.longrepr = longrepr - + #: one of 'setup', 'call', 'teardown' to indicate runtest phase. self.when = when + #: list of (secname, data) extra information which needs to + #: marshallable + self.sections = list(sections) + + #: time it took to run just the test + self.duration = duration + + self.__dict__.update(extra) + def __repr__(self): return "" % ( self.nodeid, self.when, self.outcome) @@ -196,8 +234,10 @@ class TeardownErrorReport(BaseReport): outcome = "failed" when = "teardown" - def __init__(self, longrepr): + def __init__(self, longrepr, **extra): self.longrepr = longrepr + self.sections = [] + self.__dict__.update(extra) def pytest_make_collect_report(collector): call = CallInfo(collector._memocollect, "memocollect") @@ -219,11 +259,13 @@ getattr(call, 'result', None)) class CollectReport(BaseReport): - def __init__(self, nodeid, outcome, longrepr, result): + def __init__(self, nodeid, outcome, longrepr, result, sections=(), **extra): self.nodeid = nodeid self.outcome = outcome self.longrepr = longrepr self.result = result or [] + self.sections = list(sections) + self.__dict__.update(extra) @property def location(self): @@ -277,20 +319,22 @@ self._teardown_with_finalization(None) assert not self._finalizers - def teardown_exact(self, item): - if self.stack and item == self.stack[-1]: + def teardown_exact(self, item, nextitem): + needed_collectors = nextitem and nextitem.listchain() or [] + self._teardown_towards(needed_collectors) + + def _teardown_towards(self, needed_collectors): + while self.stack: + if self.stack == needed_collectors[:len(self.stack)]: + break self._pop_and_teardown() - else: - self._callfinalizers(item) def prepare(self, colitem): """ setup objects along the collector chain to the test-method and teardown previously setup objects.""" needed_collectors = colitem.listchain() - while self.stack: - if self.stack == needed_collectors[:len(self.stack)]: - break - self._pop_and_teardown() + self._teardown_towards(needed_collectors) + # check if the last collection node has raised an error for col in self.stack: if hasattr(col, '_prepare_exc'): diff --git a/_pytest/skipping.py b/_pytest/skipping.py --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -9,6 +9,21 @@ action="store_true", dest="runxfail", default=False, help="run tests even if they are marked xfail") +def pytest_configure(config): + config.addinivalue_line("markers", + "skipif(*conditions): skip the given test function if evaluation " + "of all conditions has a True value. Evaluation happens within the " + "module global context. Example: skipif('sys.platform == \"win32\"') " + "skips the test if we are on the win32 platform. " + ) + config.addinivalue_line("markers", + "xfail(*conditions, reason=None, run=True): mark the the test function " + "as an expected failure. Optionally specify a reason and run=False " + "if you don't even want to execute the test function. Any positional " + "condition strings will be evaluated (like with skipif) and if one is " + "False the marker will not be applied." + ) + def pytest_namespace(): return dict(xfail=xfail) @@ -117,6 +132,14 @@ def pytest_runtest_makereport(__multicall__, item, call): if not isinstance(item, pytest.Function): return + # unitttest special case, see setting of _unexpectedsuccess + if hasattr(item, '_unexpectedsuccess'): + rep = __multicall__.execute() + if rep.when == "call": + # we need to translate into how py.test encodes xpass + rep.keywords['xfail'] = "reason: " + item._unexpectedsuccess + rep.outcome = "failed" + return rep if not (call.excinfo and call.excinfo.errisinstance(py.test.xfail.Exception)): evalxfail = getattr(item, '_evalxfail', None) @@ -169,21 +192,23 @@ elif char == "X": show_xpassed(terminalreporter, lines) elif char in "fF": - show_failed(terminalreporter, lines) + show_simple(terminalreporter, lines, 'failed', "FAIL %s") elif char in "sS": show_skipped(terminalreporter, lines) + elif char == "E": + show_simple(terminalreporter, lines, 'error', "ERROR %s") if lines: tr._tw.sep("=", "short test summary info") for line in lines: tr._tw.line(line) -def show_failed(terminalreporter, lines): +def show_simple(terminalreporter, lines, stat, format): tw = terminalreporter._tw - failed = terminalreporter.stats.get("failed") + failed = terminalreporter.stats.get(stat) if failed: for rep in failed: pos = rep.nodeid - lines.append("FAIL %s" %(pos, )) + lines.append(format %(pos, )) def show_xfailed(terminalreporter, lines): xfailed = terminalreporter.stats.get("xfailed") diff --git a/_pytest/terminal.py b/_pytest/terminal.py --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -15,7 +15,7 @@ group._addoption('-r', action="store", dest="reportchars", default=None, metavar="chars", help="show extra test summary info as specified by chars (f)ailed, " - "(s)skipped, (x)failed, (X)passed.") + "(E)error, (s)skipped, (x)failed, (X)passed.") group._addoption('-l', '--showlocals', action="store_true", dest="showlocals", default=False, help="show locals in tracebacks (disabled by default).") @@ -43,7 +43,8 @@ pass else: stdout = os.fdopen(newfd, stdout.mode, 1) - config._toclose = stdout + config._cleanup.append(lambda: stdout.close()) + reporter = TerminalReporter(config, stdout) config.pluginmanager.register(reporter, 'terminalreporter') if config.option.debug or config.option.traceconfig: @@ -52,11 +53,6 @@ reporter.write_line("[traceconfig] " + msg) config.trace.root.setprocessor("pytest:config", mywriter) -def pytest_unconfigure(config): - if hasattr(config, '_toclose'): - #print "closing", config._toclose, config._toclose.fileno() - config._toclose.close() - def getreportopt(config): reportopts = "" optvalue = config.option.report @@ -165,9 +161,6 @@ def pytest_deselected(self, items): self.stats.setdefault('deselected', []).extend(items) - def pytest__teardown_final_logerror(self, report): - self.stats.setdefault("error", []).append(report) - def pytest_runtest_logstart(self, nodeid, location): # ensure that the path is printed before the # 1st test of a module starts running @@ -259,7 +252,7 @@ msg = "platform %s -- Python %s" % (sys.platform, verinfo) if hasattr(sys, 'pypy_version_info'): verinfo = ".".join(map(str, sys.pypy_version_info[:3])) - msg += "[pypy-%s]" % verinfo + msg += "[pypy-%s-%s]" % (verinfo, sys.pypy_version_info[3]) msg += " -- pytest-%s" % (py.test.__version__) if self.verbosity > 0 or self.config.option.debug or \ getattr(self.config.option, 'pastebin', None): @@ -289,10 +282,18 @@ # we take care to leave out Instances aka () # because later versions are going to get rid of them anyway if self.config.option.verbose < 0: - for item in items: - nodeid = item.nodeid - nodeid = nodeid.replace("::()::", "::") - self._tw.line(nodeid) + if self.config.option.verbose < -1: + counts = {} + for item in items: + name = item.nodeid.split('::', 1)[0] + counts[name] = counts.get(name, 0) + 1 + for name, count in sorted(counts.items()): + self._tw.line("%s: %d" % (name, count)) + else: + for item in items: + nodeid = item.nodeid + nodeid = nodeid.replace("::()::", "::") + self._tw.line(nodeid) return stack = [] indent = "" @@ -318,12 +319,17 @@ self.config.hook.pytest_terminal_summary(terminalreporter=self) if exitstatus == 2: self._report_keyboardinterrupt() + del self._keyboardinterrupt_memo self.summary_deselected() self.summary_stats() def pytest_keyboard_interrupt(self, excinfo): self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True) + def pytest_unconfigure(self): + if hasattr(self, '_keyboardinterrupt_memo'): + self._report_keyboardinterrupt() + def _report_keyboardinterrupt(self): excrepr = self._keyboardinterrupt_memo msg = excrepr.reprcrash.message @@ -388,7 +394,7 @@ else: msg = self._getfailureheadline(rep) self.write_sep("_", msg) - rep.toterminal(self._tw) + self._outrep_summary(rep) def summary_errors(self): if self.config.option.tbstyle != "no": @@ -406,7 +412,15 @@ elif rep.when == "teardown": msg = "ERROR at teardown of " + msg self.write_sep("_", msg) - rep.toterminal(self._tw) + self._outrep_summary(rep) + + def _outrep_summary(self, rep): + rep.toterminal(self._tw) + for secname, content in rep.sections: + self._tw.sep("-", secname) + if content[-1:] == "\n": + content = content[:-1] + self._tw.line(content) def summary_stats(self): session_duration = py.std.time.time() - self._sessionstarttime @@ -417,9 +431,10 @@ keys.append(key) parts = [] for key in keys: - val = self.stats.get(key, None) - if val: - parts.append("%d %s" %(len(val), key)) + if key: # setup/teardown reports have an empty key, ignore them + val = self.stats.get(key, None) + if val: + parts.append("%d %s" %(len(val), key)) line = ", ".join(parts) # XXX coloring msg = "%s in %.2f seconds" %(line, session_duration) @@ -430,8 +445,15 @@ def summary_deselected(self): if 'deselected' in self.stats: + l = [] + k = self.config.option.keyword + if k: + l.append("-k%s" % k) + m = self.config.option.markexpr + if m: + l.append("-m %r" % m) self.write_sep("=", "%d tests deselected by %r" %( - len(self.stats['deselected']), self.config.option.keyword), bold=True) + len(self.stats['deselected']), " ".join(l)), bold=True) def repr_pythonversion(v=None): if v is None: diff --git a/_pytest/tmpdir.py b/_pytest/tmpdir.py --- a/_pytest/tmpdir.py +++ b/_pytest/tmpdir.py @@ -46,7 +46,7 @@ def finish(self): self.trace("finish") - + def pytest_configure(config): mp = monkeypatch() t = TempdirHandler(config) @@ -64,5 +64,5 @@ name = request._pyfuncitem.name name = py.std.re.sub("[\W]", "_", name) x = request.config._tmpdirhandler.mktemp(name, numbered=True) - return x.realpath() + return x diff --git a/_pytest/unittest.py b/_pytest/unittest.py --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -2,6 +2,9 @@ import pytest, py import sys, pdb +# for transfering markers +from _pytest.python import transfer_markers + def pytest_pycollect_makeitem(collector, name, obj): unittest = sys.modules.get('unittest') if unittest is None: @@ -19,7 +22,14 @@ class UnitTestCase(pytest.Class): def collect(self): loader = py.std.unittest.TestLoader() + module = self.getparent(pytest.Module).obj + cls = self.obj for name in loader.getTestCaseNames(self.obj): + x = getattr(self.obj, name) + funcobj = getattr(x, 'im_func', x) + transfer_markers(funcobj, cls, module) + if hasattr(funcobj, 'todo'): + pytest.mark.xfail(reason=str(funcobj.todo))(funcobj) yield TestCaseFunction(name, parent=self) def setup(self): @@ -37,15 +47,13 @@ class TestCaseFunction(pytest.Function): _excinfo = None - def __init__(self, name, parent): - super(TestCaseFunction, self).__init__(name, parent) - if hasattr(self._obj, 'todo'): - getattr(self._obj, 'im_func', self._obj).xfail = \ - pytest.mark.xfail(reason=str(self._obj.todo)) - def setup(self): self._testcase = self.parent.obj(self.name) self._obj = getattr(self._testcase, self.name) + if hasattr(self._testcase, 'skip'): + pytest.skip(self._testcase.skip) + if hasattr(self._obj, 'skip'): + pytest.skip(self._obj.skip) if hasattr(self._testcase, 'setup_method'): self._testcase.setup_method(self._obj) @@ -83,28 +91,37 @@ self._addexcinfo(rawexcinfo) def addFailure(self, testcase, rawexcinfo): self._addexcinfo(rawexcinfo) + def addSkip(self, testcase, reason): try: pytest.skip(reason) except pytest.skip.Exception: self._addexcinfo(sys.exc_info()) - def addExpectedFailure(self, testcase, rawexcinfo, reason): + + def addExpectedFailure(self, testcase, rawexcinfo, reason=""): try: pytest.xfail(str(reason)) except pytest.xfail.Exception: self._addexcinfo(sys.exc_info()) - def addUnexpectedSuccess(self, testcase, reason): - pass + + def addUnexpectedSuccess(self, testcase, reason=""): + self._unexpectedsuccess = reason + def addSuccess(self, testcase): pass + def stopTest(self, testcase): pass + def runtest(self): self._testcase(result=self) def _prunetraceback(self, excinfo): pytest.Function._prunetraceback(self, excinfo) - excinfo.traceback = excinfo.traceback.filter(lambda x:not x.frame.f_globals.get('__unittest')) + traceback = excinfo.traceback.filter( + lambda x:not x.frame.f_globals.get('__unittest')) + if traceback: + excinfo.traceback = traceback @pytest.mark.tryfirst def pytest_runtest_makereport(item, call): @@ -120,14 +137,19 @@ ut = sys.modules['twisted.python.failure'] Failure__init__ = ut.Failure.__init__.im_func check_testcase_implements_trial_reporter() - def excstore(self, exc_value=None, exc_type=None, exc_tb=None): + def excstore(self, exc_value=None, exc_type=None, exc_tb=None, + captureVars=None): if exc_value is None: self._rawexcinfo = sys.exc_info() else: if exc_type is None: exc_type = type(exc_value) self._rawexcinfo = (exc_type, exc_value, exc_tb) - Failure__init__(self, exc_value, exc_type, exc_tb) + try: + Failure__init__(self, exc_value, exc_type, exc_tb, + captureVars=captureVars) + except TypeError: + Failure__init__(self, exc_value, exc_type, exc_tb) ut.Failure.__init__ = excstore try: return __multicall__.execute() diff --git a/py/__init__.py b/py/__init__.py --- a/py/__init__.py +++ b/py/__init__.py @@ -8,7 +8,7 @@ (c) Holger Krekel and others, 2004-2010 """ -__version__ = '1.4.4.dev1' +__version__ = '1.4.7' from py import _apipkg @@ -70,6 +70,11 @@ 'getrawcode' : '._code.code:getrawcode', 'patch_builtins' : '._code.code:patch_builtins', 'unpatch_builtins' : '._code.code:unpatch_builtins', + '_AssertionError' : '._code.assertion:AssertionError', + '_reinterpret_old' : '._code.assertion:reinterpret_old', + '_reinterpret' : '._code.assertion:reinterpret', + '_reprcompare' : '._code.assertion:_reprcompare', + '_format_explanation' : '._code.assertion:_format_explanation', }, # backports and additions of builtins diff --git a/py/_builtin.py b/py/_builtin.py --- a/py/_builtin.py +++ b/py/_builtin.py @@ -113,9 +113,12 @@ # some backward compatibility helpers _basestring = str - def _totext(obj, encoding=None): + def _totext(obj, encoding=None, errors=None): if isinstance(obj, bytes): - obj = obj.decode(encoding) + if errors is None: + obj = obj.decode(encoding) + else: + obj = obj.decode(encoding, errors) elif not isinstance(obj, str): obj = str(obj) return obj @@ -142,7 +145,7 @@ del back elif locs is None: locs = globs - fp = open(fn, "rb") + fp = open(fn, "r") try: source = fp.read() finally: diff --git a/py/_code/_assertionnew.py b/py/_code/_assertionnew.py new file mode 100644 --- /dev/null +++ b/py/_code/_assertionnew.py @@ -0,0 +1,339 @@ +""" +Find intermediate evalutation results in assert statements through builtin AST. +This should replace _assertionold.py eventually. +""" + +import sys +import ast + +import py +from py._code.assertion import _format_explanation, BuiltinAssertionError + + +if sys.platform.startswith("java") and sys.version_info < (2, 5, 2): + # See http://bugs.jython.org/issue1497 + _exprs = ("BoolOp", "BinOp", "UnaryOp", "Lambda", "IfExp", "Dict", + "ListComp", "GeneratorExp", "Yield", "Compare", "Call", + "Repr", "Num", "Str", "Attribute", "Subscript", "Name", + "List", "Tuple") + _stmts = ("FunctionDef", "ClassDef", "Return", "Delete", "Assign", + "AugAssign", "Print", "For", "While", "If", "With", "Raise", + "TryExcept", "TryFinally", "Assert", "Import", "ImportFrom", + "Exec", "Global", "Expr", "Pass", "Break", "Continue") + _expr_nodes = set(getattr(ast, name) for name in _exprs) + _stmt_nodes = set(getattr(ast, name) for name in _stmts) + def _is_ast_expr(node): + return node.__class__ in _expr_nodes + def _is_ast_stmt(node): + return node.__class__ in _stmt_nodes +else: + def _is_ast_expr(node): + return isinstance(node, ast.expr) + def _is_ast_stmt(node): + return isinstance(node, ast.stmt) + + +class Failure(Exception): + """Error found while interpreting AST.""" + + def __init__(self, explanation=""): + self.cause = sys.exc_info() + self.explanation = explanation + + +def interpret(source, frame, should_fail=False): + mod = ast.parse(source) + visitor = DebugInterpreter(frame) + try: + visitor.visit(mod) + except Failure: + failure = sys.exc_info()[1] + return getfailure(failure) + if should_fail: + return ("(assertion failed, but when it was re-run for " + "printing intermediate values, it did not fail. Suggestions: " + "compute assert expression before the assert or use --no-assert)") + +def run(offending_line, frame=None): + if frame is None: + frame = py.code.Frame(sys._getframe(1)) + return interpret(offending_line, frame) + +def getfailure(failure): + explanation = _format_explanation(failure.explanation) + value = failure.cause[1] + if str(value): + lines = explanation.splitlines() + if not lines: + lines.append("") + lines[0] += " << %s" % (value,) + explanation = "\n".join(lines) + text = "%s: %s" % (failure.cause[0].__name__, explanation) + if text.startswith("AssertionError: assert "): + text = text[16:] + return text + + +operator_map = { + ast.BitOr : "|", + ast.BitXor : "^", + ast.BitAnd : "&", + ast.LShift : "<<", + ast.RShift : ">>", + ast.Add : "+", + ast.Sub : "-", + ast.Mult : "*", + ast.Div : "/", + ast.FloorDiv : "//", + ast.Mod : "%", + ast.Eq : "==", + ast.NotEq : "!=", + ast.Lt : "<", + ast.LtE : "<=", + ast.Gt : ">", + ast.GtE : ">=", + ast.Pow : "**", + ast.Is : "is", + ast.IsNot : "is not", + ast.In : "in", + ast.NotIn : "not in" +} + +unary_map = { + ast.Not : "not %s", + ast.Invert : "~%s", + ast.USub : "-%s", + ast.UAdd : "+%s" +} + + +class DebugInterpreter(ast.NodeVisitor): + """Interpret AST nodes to gleam useful debugging information. """ + + def __init__(self, frame): + self.frame = frame + + def generic_visit(self, node): + # Fallback when we don't have a special implementation. + if _is_ast_expr(node): + mod = ast.Expression(node) + co = self._compile(mod) + try: + result = self.frame.eval(co) + except Exception: + raise Failure() + explanation = self.frame.repr(result) + return explanation, result + elif _is_ast_stmt(node): + mod = ast.Module([node]) + co = self._compile(mod, "exec") + try: + self.frame.exec_(co) + except Exception: + raise Failure() + return None, None + else: + raise AssertionError("can't handle %s" %(node,)) + + def _compile(self, source, mode="eval"): + return compile(source, "", mode) + + def visit_Expr(self, expr): + return self.visit(expr.value) + + def visit_Module(self, mod): + for stmt in mod.body: + self.visit(stmt) + + def visit_Name(self, name): + explanation, result = self.generic_visit(name) + # See if the name is local. + source = "%r in locals() is not globals()" % (name.id,) + co = self._compile(source) + try: + local = self.frame.eval(co) + except Exception: + # have to assume it isn't + local = False + if not local: + return name.id, result + return explanation, result + + def visit_Compare(self, comp): + left = comp.left + left_explanation, left_result = self.visit(left) + for op, next_op in zip(comp.ops, comp.comparators): + next_explanation, next_result = self.visit(next_op) + op_symbol = operator_map[op.__class__] + explanation = "%s %s %s" % (left_explanation, op_symbol, + next_explanation) + source = "__exprinfo_left %s __exprinfo_right" % (op_symbol,) + co = self._compile(source) + try: + result = self.frame.eval(co, __exprinfo_left=left_result, + __exprinfo_right=next_result) + except Exception: + raise Failure(explanation) + try: + if not result: + break + except KeyboardInterrupt: + raise + except: + break + left_explanation, left_result = next_explanation, next_result + + rcomp = py.code._reprcompare + if rcomp: + res = rcomp(op_symbol, left_result, next_result) + if res: + explanation = res + return explanation, result + + def visit_BoolOp(self, boolop): + is_or = isinstance(boolop.op, ast.Or) + explanations = [] + for operand in boolop.values: + explanation, result = self.visit(operand) + explanations.append(explanation) + if result == is_or: + break + name = is_or and " or " or " and " + explanation = "(" + name.join(explanations) + ")" + return explanation, result + + def visit_UnaryOp(self, unary): + pattern = unary_map[unary.op.__class__] + operand_explanation, operand_result = self.visit(unary.operand) + explanation = pattern % (operand_explanation,) + co = self._compile(pattern % ("__exprinfo_expr",)) + try: + result = self.frame.eval(co, __exprinfo_expr=operand_result) + except Exception: + raise Failure(explanation) + return explanation, result + + def visit_BinOp(self, binop): + left_explanation, left_result = self.visit(binop.left) + right_explanation, right_result = self.visit(binop.right) + symbol = operator_map[binop.op.__class__] + explanation = "(%s %s %s)" % (left_explanation, symbol, + right_explanation) + source = "__exprinfo_left %s __exprinfo_right" % (symbol,) + co = self._compile(source) + try: + result = self.frame.eval(co, __exprinfo_left=left_result, + __exprinfo_right=right_result) + except Exception: + raise Failure(explanation) + return explanation, result + + def visit_Call(self, call): + func_explanation, func = self.visit(call.func) + arg_explanations = [] + ns = {"__exprinfo_func" : func} + arguments = [] + for arg in call.args: + arg_explanation, arg_result = self.visit(arg) + arg_name = "__exprinfo_%s" % (len(ns),) + ns[arg_name] = arg_result + arguments.append(arg_name) + arg_explanations.append(arg_explanation) + for keyword in call.keywords: + arg_explanation, arg_result = self.visit(keyword.value) + arg_name = "__exprinfo_%s" % (len(ns),) + ns[arg_name] = arg_result + keyword_source = "%s=%%s" % (keyword.arg) + arguments.append(keyword_source % (arg_name,)) + arg_explanations.append(keyword_source % (arg_explanation,)) + if call.starargs: + arg_explanation, arg_result = self.visit(call.starargs) + arg_name = "__exprinfo_star" + ns[arg_name] = arg_result + arguments.append("*%s" % (arg_name,)) + arg_explanations.append("*%s" % (arg_explanation,)) + if call.kwargs: + arg_explanation, arg_result = self.visit(call.kwargs) + arg_name = "__exprinfo_kwds" + ns[arg_name] = arg_result + arguments.append("**%s" % (arg_name,)) + arg_explanations.append("**%s" % (arg_explanation,)) + args_explained = ", ".join(arg_explanations) + explanation = "%s(%s)" % (func_explanation, args_explained) + args = ", ".join(arguments) + source = "__exprinfo_func(%s)" % (args,) + co = self._compile(source) + try: + result = self.frame.eval(co, **ns) + except Exception: + raise Failure(explanation) + pattern = "%s\n{%s = %s\n}" + rep = self.frame.repr(result) + explanation = pattern % (rep, rep, explanation) + return explanation, result + + def _is_builtin_name(self, name): + pattern = "%r not in globals() and %r not in locals()" + source = pattern % (name.id, name.id) + co = self._compile(source) + try: + return self.frame.eval(co) + except Exception: + return False + + def visit_Attribute(self, attr): + if not isinstance(attr.ctx, ast.Load): + return self.generic_visit(attr) + source_explanation, source_result = self.visit(attr.value) + explanation = "%s.%s" % (source_explanation, attr.attr) + source = "__exprinfo_expr.%s" % (attr.attr,) + co = self._compile(source) + try: + result = self.frame.eval(co, __exprinfo_expr=source_result) + except Exception: + raise Failure(explanation) + explanation = "%s\n{%s = %s.%s\n}" % (self.frame.repr(result), + self.frame.repr(result), + source_explanation, attr.attr) + # Check if the attr is from an instance. + source = "%r in getattr(__exprinfo_expr, '__dict__', {})" + source = source % (attr.attr,) + co = self._compile(source) + try: + from_instance = self.frame.eval(co, __exprinfo_expr=source_result) + except Exception: + from_instance = True + if from_instance: + rep = self.frame.repr(result) + pattern = "%s\n{%s = %s\n}" + explanation = pattern % (rep, rep, explanation) + return explanation, result + + def visit_Assert(self, assrt): + test_explanation, test_result = self.visit(assrt.test) + if test_explanation.startswith("False\n{False =") and \ + test_explanation.endswith("\n"): + test_explanation = test_explanation[15:-2] + explanation = "assert %s" % (test_explanation,) + if not test_result: + try: + raise BuiltinAssertionError + except Exception: + raise Failure(explanation) + return explanation, test_result + + def visit_Assign(self, assign): + value_explanation, value_result = self.visit(assign.value) + explanation = "... = %s" % (value_explanation,) + name = ast.Name("__exprinfo_expr", ast.Load(), + lineno=assign.value.lineno, + col_offset=assign.value.col_offset) + new_assign = ast.Assign(assign.targets, name, lineno=assign.lineno, + col_offset=assign.col_offset) + mod = ast.Module([new_assign]) + co = self._compile(mod, "exec") + try: + self.frame.exec_(co, __exprinfo_expr=value_result) + except Exception: + raise Failure(explanation) + return explanation, value_result diff --git a/py/_code/_assertionold.py b/py/_code/_assertionold.py new file mode 100644 --- /dev/null +++ b/py/_code/_assertionold.py @@ -0,0 +1,555 @@ +import py +import sys, inspect +from compiler import parse, ast, pycodegen +from py._code.assertion import BuiltinAssertionError, _format_explanation + +passthroughex = py.builtin._sysex + +class Failure: + def __init__(self, node): + self.exc, self.value, self.tb = sys.exc_info() + self.node = node + +class View(object): + """View base class. + + If C is a subclass of View, then C(x) creates a proxy object around + the object x. The actual class of the proxy is not C in general, + but a *subclass* of C determined by the rules below. To avoid confusion + we call view class the class of the proxy (a subclass of C, so of View) + and object class the class of x. + + Attributes and methods not found in the proxy are automatically read on x. + Other operations like setting attributes are performed on the proxy, as + determined by its view class. The object x is available from the proxy + as its __obj__ attribute. + + The view class selection is determined by the __view__ tuples and the + optional __viewkey__ method. By default, the selected view class is the + most specific subclass of C whose __view__ mentions the class of x. + If no such subclass is found, the search proceeds with the parent + object classes. For example, C(True) will first look for a subclass + of C with __view__ = (..., bool, ...) and only if it doesn't find any + look for one with __view__ = (..., int, ...), and then ..., object,... + If everything fails the class C itself is considered to be the default. + + Alternatively, the view class selection can be driven by another aspect + of the object x, instead of the class of x, by overriding __viewkey__. + See last example at the end of this module. + """ + + _viewcache = {} + __view__ = () + + def __new__(rootclass, obj, *args, **kwds): + self = object.__new__(rootclass) + self.__obj__ = obj + self.__rootclass__ = rootclass + key = self.__viewkey__() + try: + self.__class__ = self._viewcache[key] + except KeyError: + self.__class__ = self._selectsubclass(key) + return self + + def __getattr__(self, attr): + # attributes not found in the normal hierarchy rooted on View + # are looked up in the object's real class + return getattr(self.__obj__, attr) + + def __viewkey__(self): + return self.__obj__.__class__ + + def __matchkey__(self, key, subclasses): + if inspect.isclass(key): + keys = inspect.getmro(key) + else: + keys = [key] + for key in keys: + result = [C for C in subclasses if key in C.__view__] + if result: + return result + return [] + + def _selectsubclass(self, key): + subclasses = list(enumsubclasses(self.__rootclass__)) + for C in subclasses: + if not isinstance(C.__view__, tuple): + C.__view__ = (C.__view__,) + choices = self.__matchkey__(key, subclasses) + if not choices: + return self.__rootclass__ + elif len(choices) == 1: + return choices[0] + else: + # combine the multiple choices + return type('?', tuple(choices), {}) + + def __repr__(self): + return '%s(%r)' % (self.__rootclass__.__name__, self.__obj__) + + +def enumsubclasses(cls): + for subcls in cls.__subclasses__(): + for subsubclass in enumsubclasses(subcls): + yield subsubclass + yield cls + + +class Interpretable(View): + """A parse tree node with a few extra methods.""" + explanation = None + + def is_builtin(self, frame): + return False + + def eval(self, frame): + # fall-back for unknown expression nodes + try: + expr = ast.Expression(self.__obj__) + expr.filename = '' + self.__obj__.filename = '' + co = pycodegen.ExpressionCodeGenerator(expr).getCode() + result = frame.eval(co) + except passthroughex: + raise + except: + raise Failure(self) + self.result = result + self.explanation = self.explanation or frame.repr(self.result) + + def run(self, frame): + # fall-back for unknown statement nodes + try: + expr = ast.Module(None, ast.Stmt([self.__obj__])) + expr.filename = '' + co = pycodegen.ModuleCodeGenerator(expr).getCode() + frame.exec_(co) + except passthroughex: + raise + except: + raise Failure(self) + + def nice_explanation(self): + return _format_explanation(self.explanation) + + +class Name(Interpretable): + __view__ = ast.Name + + def is_local(self, frame): + source = '%r in locals() is not globals()' % self.name + try: + return frame.is_true(frame.eval(source)) + except passthroughex: + raise + except: + return False + + def is_global(self, frame): + source = '%r in globals()' % self.name + try: + return frame.is_true(frame.eval(source)) + except passthroughex: + raise + except: + return False + + def is_builtin(self, frame): + source = '%r not in locals() and %r not in globals()' % ( + self.name, self.name) + try: + return frame.is_true(frame.eval(source)) + except passthroughex: + raise + except: + return False + + def eval(self, frame): + super(Name, self).eval(frame) + if not self.is_local(frame): + self.explanation = self.name + +class Compare(Interpretable): + __view__ = ast.Compare + + def eval(self, frame): + expr = Interpretable(self.expr) + expr.eval(frame) + for operation, expr2 in self.ops: + if hasattr(self, 'result'): + # shortcutting in chained expressions + if not frame.is_true(self.result): + break + expr2 = Interpretable(expr2) + expr2.eval(frame) + self.explanation = "%s %s %s" % ( + expr.explanation, operation, expr2.explanation) + source = "__exprinfo_left %s __exprinfo_right" % operation + try: + self.result = frame.eval(source, + __exprinfo_left=expr.result, + __exprinfo_right=expr2.result) + except passthroughex: + raise + except: + raise Failure(self) + expr = expr2 + +class And(Interpretable): + __view__ = ast.And + + def eval(self, frame): + explanations = [] + for expr in self.nodes: + expr = Interpretable(expr) + expr.eval(frame) + explanations.append(expr.explanation) + self.result = expr.result + if not frame.is_true(expr.result): + break + self.explanation = '(' + ' and '.join(explanations) + ')' + +class Or(Interpretable): + __view__ = ast.Or + + def eval(self, frame): + explanations = [] + for expr in self.nodes: + expr = Interpretable(expr) + expr.eval(frame) + explanations.append(expr.explanation) + self.result = expr.result + if frame.is_true(expr.result): + break + self.explanation = '(' + ' or '.join(explanations) + ')' + + +# == Unary operations == +keepalive = [] +for astclass, astpattern in { + ast.Not : 'not __exprinfo_expr', + ast.Invert : '(~__exprinfo_expr)', + }.items(): + + class UnaryArith(Interpretable): + __view__ = astclass + + def eval(self, frame, astpattern=astpattern): + expr = Interpretable(self.expr) + expr.eval(frame) + self.explanation = astpattern.replace('__exprinfo_expr', + expr.explanation) + try: + self.result = frame.eval(astpattern, + __exprinfo_expr=expr.result) + except passthroughex: + raise + except: + raise Failure(self) + + keepalive.append(UnaryArith) + +# == Binary operations == +for astclass, astpattern in { + ast.Add : '(__exprinfo_left + __exprinfo_right)', + ast.Sub : '(__exprinfo_left - __exprinfo_right)', + ast.Mul : '(__exprinfo_left * __exprinfo_right)', + ast.Div : '(__exprinfo_left / __exprinfo_right)', + ast.Mod : '(__exprinfo_left % __exprinfo_right)', + ast.Power : '(__exprinfo_left ** __exprinfo_right)', + }.items(): + + class BinaryArith(Interpretable): + __view__ = astclass + + def eval(self, frame, astpattern=astpattern): + left = Interpretable(self.left) + left.eval(frame) + right = Interpretable(self.right) + right.eval(frame) + self.explanation = (astpattern + .replace('__exprinfo_left', left .explanation) + .replace('__exprinfo_right', right.explanation)) + try: + self.result = frame.eval(astpattern, + __exprinfo_left=left.result, + __exprinfo_right=right.result) + except passthroughex: + raise + except: + raise Failure(self) + + keepalive.append(BinaryArith) + + +class CallFunc(Interpretable): + __view__ = ast.CallFunc + + def is_bool(self, frame): + source = 'isinstance(__exprinfo_value, bool)' + try: + return frame.is_true(frame.eval(source, + __exprinfo_value=self.result)) + except passthroughex: + raise + except: + return False + + def eval(self, frame): + node = Interpretable(self.node) + node.eval(frame) + explanations = [] + vars = {'__exprinfo_fn': node.result} + source = '__exprinfo_fn(' + for a in self.args: + if isinstance(a, ast.Keyword): + keyword = a.name + a = a.expr + else: + keyword = None + a = Interpretable(a) + a.eval(frame) + argname = '__exprinfo_%d' % len(vars) + vars[argname] = a.result + if keyword is None: + source += argname + ',' + explanations.append(a.explanation) + else: + source += '%s=%s,' % (keyword, argname) + explanations.append('%s=%s' % (keyword, a.explanation)) + if self.star_args: + star_args = Interpretable(self.star_args) + star_args.eval(frame) + argname = '__exprinfo_star' + vars[argname] = star_args.result + source += '*' + argname + ',' + explanations.append('*' + star_args.explanation) + if self.dstar_args: + dstar_args = Interpretable(self.dstar_args) + dstar_args.eval(frame) + argname = '__exprinfo_kwds' + vars[argname] = dstar_args.result + source += '**' + argname + ',' + explanations.append('**' + dstar_args.explanation) + self.explanation = "%s(%s)" % ( + node.explanation, ', '.join(explanations)) + if source.endswith(','): + source = source[:-1] + source += ')' + try: + self.result = frame.eval(source, **vars) + except passthroughex: + raise + except: + raise Failure(self) + if not node.is_builtin(frame) or not self.is_bool(frame): + r = frame.repr(self.result) + self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation) + +class Getattr(Interpretable): + __view__ = ast.Getattr + + def eval(self, frame): + expr = Interpretable(self.expr) + expr.eval(frame) + source = '__exprinfo_expr.%s' % self.attrname + try: + self.result = frame.eval(source, __exprinfo_expr=expr.result) + except passthroughex: + raise + except: + raise Failure(self) + self.explanation = '%s.%s' % (expr.explanation, self.attrname) + # if the attribute comes from the instance, its value is interesting + source = ('hasattr(__exprinfo_expr, "__dict__") and ' + '%r in __exprinfo_expr.__dict__' % self.attrname) + try: + from_instance = frame.is_true( + frame.eval(source, __exprinfo_expr=expr.result)) + except passthroughex: + raise + except: + from_instance = True + if from_instance: + r = frame.repr(self.result) + self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation) + +# == Re-interpretation of full statements == + +class Assert(Interpretable): + __view__ = ast.Assert + + def run(self, frame): + test = Interpretable(self.test) + test.eval(frame) + # simplify 'assert False where False = ...' + if (test.explanation.startswith('False\n{False = ') and + test.explanation.endswith('\n}')): + test.explanation = test.explanation[15:-2] + # print the result as 'assert ' + self.result = test.result + self.explanation = 'assert ' + test.explanation + if not frame.is_true(test.result): + try: + raise BuiltinAssertionError + except passthroughex: + raise + except: + raise Failure(self) + +class Assign(Interpretable): + __view__ = ast.Assign + + def run(self, frame): + expr = Interpretable(self.expr) + expr.eval(frame) + self.result = expr.result + self.explanation = '... = ' + expr.explanation + # fall-back-run the rest of the assignment + ass = ast.Assign(self.nodes, ast.Name('__exprinfo_expr')) + mod = ast.Module(None, ast.Stmt([ass])) + mod.filename = '' + co = pycodegen.ModuleCodeGenerator(mod).getCode() + try: + frame.exec_(co, __exprinfo_expr=expr.result) + except passthroughex: + raise + except: + raise Failure(self) + +class Discard(Interpretable): + __view__ = ast.Discard + + def run(self, frame): + expr = Interpretable(self.expr) + expr.eval(frame) + self.result = expr.result + self.explanation = expr.explanation + +class Stmt(Interpretable): + __view__ = ast.Stmt + + def run(self, frame): + for stmt in self.nodes: + stmt = Interpretable(stmt) + stmt.run(frame) + + +def report_failure(e): + explanation = e.node.nice_explanation() + if explanation: + explanation = ", in: " + explanation + else: + explanation = "" + sys.stdout.write("%s: %s%s\n" % (e.exc.__name__, e.value, explanation)) + +def check(s, frame=None): + if frame is None: + frame = sys._getframe(1) + frame = py.code.Frame(frame) + expr = parse(s, 'eval') + assert isinstance(expr, ast.Expression) + node = Interpretable(expr.node) + try: + node.eval(frame) + except passthroughex: + raise + except Failure: + e = sys.exc_info()[1] + report_failure(e) + else: + if not frame.is_true(node.result): + sys.stderr.write("assertion failed: %s\n" % node.nice_explanation()) + + +########################################################### +# API / Entry points +# ######################################################### + +def interpret(source, frame, should_fail=False): + module = Interpretable(parse(source, 'exec').node) + #print "got module", module + if isinstance(frame, py.std.types.FrameType): + frame = py.code.Frame(frame) + try: + module.run(frame) + except Failure: + e = sys.exc_info()[1] + return getfailure(e) + except passthroughex: + raise + except: + import traceback + traceback.print_exc() + if should_fail: + return ("(assertion failed, but when it was re-run for " + "printing intermediate values, it did not fail. Suggestions: " + "compute assert expression before the assert or use --nomagic)") + else: + return None + +def getmsg(excinfo): + if isinstance(excinfo, tuple): + excinfo = py.code.ExceptionInfo(excinfo) + #frame, line = gettbline(tb) + #frame = py.code.Frame(frame) + #return interpret(line, frame) + + tb = excinfo.traceback[-1] + source = str(tb.statement).strip() + x = interpret(source, tb.frame, should_fail=True) + if not isinstance(x, str): + raise TypeError("interpret returned non-string %r" % (x,)) + return x + +def getfailure(e): + explanation = e.node.nice_explanation() + if str(e.value): + lines = explanation.split('\n') + lines[0] += " << %s" % (e.value,) + explanation = '\n'.join(lines) + text = "%s: %s" % (e.exc.__name__, explanation) + if text.startswith('AssertionError: assert '): + text = text[16:] + return text + +def run(s, frame=None): + if frame is None: + frame = sys._getframe(1) + frame = py.code.Frame(frame) + module = Interpretable(parse(s, 'exec').node) + try: + module.run(frame) + except Failure: + e = sys.exc_info()[1] + report_failure(e) + + +if __name__ == '__main__': + # example: + def f(): + return 5 + def g(): + return 3 + def h(x): + return 'never' + check("f() * g() == 5") + check("not f()") + check("not (f() and g() or 0)") + check("f() == g()") + i = 4 + check("i == f()") + check("len(f()) == 0") + check("isinstance(2+3+4, float)") + + run("x = i") + check("x == 5") + + run("assert not f(), 'oops'") + run("a, b, c = 1, 2") + run("a, b, c = f()") + + check("max([f(),g()]) == 4") + check("'hello'[g()] == 'h'") + run("'guk%d' % h(f())") diff --git a/py/_code/assertion.py b/py/_code/assertion.py new file mode 100644 --- /dev/null +++ b/py/_code/assertion.py @@ -0,0 +1,94 @@ +import sys +import py + +BuiltinAssertionError = py.builtin.builtins.AssertionError + +_reprcompare = None # if set, will be called by assert reinterp for comparison ops + +def _format_explanation(explanation): + """This formats an explanation + + Normally all embedded newlines are escaped, however there are + three exceptions: \n{, \n} and \n~. The first two are intended + cover nested explanations, see function and attribute explanations + for examples (.visit_Call(), visit_Attribute()). The last one is + for when one explanation needs to span multiple lines, e.g. when + displaying diffs. + """ + raw_lines = (explanation or '').split('\n') + # escape newlines not followed by {, } and ~ + lines = [raw_lines[0]] + for l in raw_lines[1:]: + if l.startswith('{') or l.startswith('}') or l.startswith('~'): + lines.append(l) + else: + lines[-1] += '\\n' + l + + result = lines[:1] + stack = [0] + stackcnt = [0] + for line in lines[1:]: + if line.startswith('{'): + if stackcnt[-1]: + s = 'and ' + else: + s = 'where ' + stack.append(len(result)) + stackcnt[-1] += 1 + stackcnt.append(0) + result.append(' +' + ' '*(len(stack)-1) + s + line[1:]) + elif line.startswith('}'): + assert line.startswith('}') + stack.pop() + stackcnt.pop() + result[stack[-1]] += line[1:] + else: + assert line.startswith('~') + result.append(' '*len(stack) + line[1:]) + assert len(stack) == 1 + return '\n'.join(result) + + +class AssertionError(BuiltinAssertionError): + def __init__(self, *args): + BuiltinAssertionError.__init__(self, *args) + if args: + try: + self.msg = str(args[0]) + except py.builtin._sysex: + raise + except: + self.msg = "<[broken __repr__] %s at %0xd>" %( + args[0].__class__, id(args[0])) + else: + f = py.code.Frame(sys._getframe(1)) + try: + source = f.code.fullsource + if source is not None: + try: + source = source.getstatement(f.lineno, assertion=True) + except IndexError: + source = None + else: + source = str(source.deindent()).strip() + except py.error.ENOENT: + source = None + # this can also occur during reinterpretation, when the + # co_filename is set to "". + if source: + self.msg = reinterpret(source, f, should_fail=True) + else: + self.msg = "" + if not self.args: + self.args = (self.msg,) + +if sys.version_info > (3, 0): + AssertionError.__module__ = "builtins" + reinterpret_old = "old reinterpretation not available for py3" +else: + from py._code._assertionold import interpret as reinterpret_old +if sys.version_info >= (2, 6) or (sys.platform.startswith("java")): + from py._code._assertionnew import interpret as reinterpret +else: + reinterpret = reinterpret_old + diff --git a/py/_code/code.py b/py/_code/code.py --- a/py/_code/code.py +++ b/py/_code/code.py @@ -145,6 +145,17 @@ return self.frame.f_locals locals = property(getlocals, None, None, "locals of underlaying frame") + def reinterpret(self): + """Reinterpret the failing statement and returns a detailed information + about what operations are performed.""" + if self.exprinfo is None: + source = str(self.statement).strip() + x = py.code._reinterpret(source, self.frame, should_fail=True) + if not isinstance(x, str): + raise TypeError("interpret returned non-string %r" % (x,)) + self.exprinfo = x + return self.exprinfo + def getfirstlinesource(self): # on Jython this firstlineno can be -1 apparently return max(self.frame.code.firstlineno, 0) @@ -158,13 +169,12 @@ end = self.lineno try: _, end = source.getstatementrange(end) - except IndexError: + except (IndexError, ValueError): end = self.lineno + 1 # heuristic to stop displaying source on e.g. # if something: # assume this causes a NameError # # _this_ lines and the one # below we don't want from entry.getsource() - end = min(end, len(source)) for i in range(self.lineno, end): if source[i].rstrip().endswith(':'): end = i + 1 @@ -273,7 +283,11 @@ """ cache = {} for i, entry in enumerate(self): - key = entry.frame.code.path, entry.lineno + # id for the code.raw is needed to work around + # the strange metaprogramming in the decorator lib from pypi + # which generates code objects that have hash/value equality + #XXX needs a test + key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno #print "checking for recursion at", key l = cache.setdefault(key, []) if l: @@ -308,7 +322,7 @@ self._striptext = 'AssertionError: ' self._excinfo = tup self.type, self.value, tb = self._excinfo - self.typename = getattr(self.type, "__name__", "???") + self.typename = self.type.__name__ self.traceback = py.code.Traceback(tb) def __repr__(self): @@ -347,14 +361,16 @@ showlocals: show locals per traceback entry style: long|short|no|native traceback style tbfilter: hide entries (where __tracebackhide__ is true) + + in case of style==native, tbfilter and showlocals is ignored. """ if style == 'native': - import traceback - return ''.join(traceback.format_exception( - self.type, - self.value, - self.traceback[0]._rawentry, - )) + return ReprExceptionInfo(ReprTracebackNative( + py.std.traceback.format_exception( + self.type, + self.value, + self.traceback[0]._rawentry, + )), self._getreprcrash()) fmt = FormattedExcinfo(showlocals=showlocals, style=style, abspath=abspath, tbfilter=tbfilter, funcargs=funcargs) @@ -452,7 +468,7 @@ def repr_locals(self, locals): if self.showlocals: lines = [] - keys = list(locals) + keys = [loc for loc in locals if loc[0] != "@"] keys.sort() for name in keys: value = locals[name] @@ -506,7 +522,10 @@ def _makepath(self, path): if not self.abspath: - np = py.path.local().bestrelpath(path) + try: + np = py.path.local().bestrelpath(path) + except OSError: + return path if len(np) < len(str(path)): path = np return path @@ -595,6 +614,19 @@ if self.extraline: tw.line(self.extraline) +class ReprTracebackNative(ReprTraceback): + def __init__(self, tblines): + self.style = "native" + self.reprentries = [ReprEntryNative(tblines)] + self.extraline = None + +class ReprEntryNative(TerminalRepr): + def __init__(self, tblines): + self.lines = tblines + + def toterminal(self, tw): + tw.write("".join(self.lines)) + class ReprEntry(TerminalRepr): localssep = "_ " @@ -680,19 +712,26 @@ oldbuiltins = {} -def patch_builtins(compile=True): - """ put compile builtins to Python's builtins. """ +def patch_builtins(assertion=True, compile=True): + """ put compile and AssertionError builtins to Python's builtins. """ + if assertion: + from py._code import assertion + l = oldbuiltins.setdefault('AssertionError', []) + l.append(py.builtin.builtins.AssertionError) + py.builtin.builtins.AssertionError = assertion.AssertionError if compile: l = oldbuiltins.setdefault('compile', []) l.append(py.builtin.builtins.compile) py.builtin.builtins.compile = py.code.compile -def unpatch_builtins(compile=True): +def unpatch_builtins(assertion=True, compile=True): """ remove compile and AssertionError builtins from Python builtins. """ + if assertion: + py.builtin.builtins.AssertionError = oldbuiltins['AssertionError'].pop() if compile: py.builtin.builtins.compile = oldbuiltins['compile'].pop() -def getrawcode(obj): +def getrawcode(obj, trycall=True): """ return code object for given function. """ try: return obj.__code__ @@ -701,5 +740,10 @@ obj = getattr(obj, 'func_code', obj) obj = getattr(obj, 'f_code', obj) obj = getattr(obj, '__code__', obj) + if trycall and not hasattr(obj, 'co_firstlineno'): + if hasattr(obj, '__call__') and not py.std.inspect.isclass(obj): + x = getrawcode(obj.__call__, trycall=False) + if hasattr(x, 'co_firstlineno'): + return x return obj diff --git a/py/_code/source.py b/py/_code/source.py --- a/py/_code/source.py +++ b/py/_code/source.py @@ -108,6 +108,7 @@ def getstatementrange(self, lineno, assertion=False): """ return (start, end) tuple which spans the minimal statement region which containing the given lineno. + raise an IndexError if no such statementrange can be found. """ # XXX there must be a better than these heuristic ways ... # XXX there may even be better heuristics :-) @@ -116,6 +117,7 @@ # 1. find the start of the statement from codeop import compile_command + end = None for start in range(lineno, -1, -1): if assertion: line = self.lines[start] @@ -139,7 +141,9 @@ trysource = self[start:end] if trysource.isparseable(): return start, end - return start, len(self) + if end is None: + raise IndexError("no valid source range around line %d " % (lineno,)) + return start, end def getblockend(self, lineno): # XXX @@ -257,23 +261,29 @@ def getfslineno(obj): + """ Return source location (path, lineno) for the given object. + If the source cannot be determined return ("", -1) + """ try: code = py.code.Code(obj) except TypeError: - # fallback to - fn = (py.std.inspect.getsourcefile(obj) or - py.std.inspect.getfile(obj)) + try: + fn = (py.std.inspect.getsourcefile(obj) or + py.std.inspect.getfile(obj)) + except TypeError: + return "", -1 + fspath = fn and py.path.local(fn) or None + lineno = -1 if fspath: try: _, lineno = findsource(obj) except IOError: - lineno = None - else: - lineno = None + pass else: fspath = code.path lineno = code.firstlineno + assert isinstance(lineno, int) return fspath, lineno # @@ -286,7 +296,7 @@ except py.builtin._sysex: raise except: - return None, None + return None, -1 source = Source() source.lines = [line.rstrip() for line in sourcelines] return source, lineno diff --git a/py/_error.py b/py/_error.py --- a/py/_error.py +++ b/py/_error.py @@ -23,6 +23,7 @@ 2: errno.ENOENT, 3: errno.ENOENT, 17: errno.EEXIST, + 13: errno.EBUSY, # empty cd drive, but ENOMEDIUM seems unavailiable 22: errno.ENOTDIR, 267: errno.ENOTDIR, 5: errno.EACCES, # anything better? diff --git a/py/_iniconfig.py b/py/_iniconfig.py --- a/py/_iniconfig.py +++ b/py/_iniconfig.py @@ -103,6 +103,7 @@ def _parseline(self, line, lineno): # comments line = line.split('#')[0].rstrip() + line = line.split(';')[0].rstrip() # blank lines if not line: return None, None diff --git a/py/_io/capture.py b/py/_io/capture.py --- a/py/_io/capture.py +++ b/py/_io/capture.py @@ -12,7 +12,7 @@ class TextIO(StringIO): def write(self, data): if not isinstance(data, unicode): - data = unicode(data, getattr(self, '_encoding', 'UTF-8')) + data = unicode(data, getattr(self, '_encoding', 'UTF-8'), 'replace') StringIO.write(self, data) else: TextIO = StringIO @@ -258,6 +258,9 @@ f = getattr(self, name).tmpfile f.seek(0) res = f.read() + enc = getattr(f, 'encoding', None) + if enc: + res = py.builtin._totext(res, enc, 'replace') f.truncate(0) f.seek(0) l.append(res) diff --git a/py/_io/terminalwriter.py b/py/_io/terminalwriter.py --- a/py/_io/terminalwriter.py +++ b/py/_io/terminalwriter.py @@ -105,6 +105,8 @@ Blue=44, Purple=45, Cyan=46, White=47, bold=1, light=2, blink=5, invert=7) + _newline = None # the last line printed + # XXX deprecate stringio argument def __init__(self, file=None, stringio=False, encoding=None): if file is None: @@ -112,11 +114,9 @@ self.stringio = file = py.io.TextIO() else: file = py.std.sys.stdout - if hasattr(file, 'encoding'): - encoding = file.encoding elif hasattr(file, '__call__'): file = WriteFile(file, encoding=encoding) - self.encoding = encoding + self.encoding = encoding or getattr(file, 'encoding', "utf-8") self._file = file self.fullwidth = get_terminal_width() self.hasmarkup = should_do_markup(file) @@ -182,8 +182,31 @@ return s def line(self, s='', **kw): + if self._newline == False: + self.write("\n") self.write(s, **kw) self.write('\n') + self._newline = True + + def reline(self, line, **opts): + if not self.hasmarkup: + raise ValueError("cannot use rewrite-line without terminal") + if not self._newline: + self.write("\r") + self.write(line, **opts) + # see if we need to fill up some spaces at the end + # xxx have a more exact lastlinelen working from self.write? + lenline = len(line) + try: + lastlen = self._lastlinelen + except AttributeError: + pass + else: + if lenline < lastlen: + self.write(" " * (lastlen - lenline + 1)) + self._lastlinelen = lenline + self._newline = False + class Win32ConsoleWriter(TerminalWriter): def write(self, s, **kw): @@ -280,10 +303,10 @@ SetConsoleTextAttribute = ctypes.windll.kernel32.SetConsoleTextAttribute SetConsoleTextAttribute.argtypes = [wintypes.HANDLE, wintypes.WORD] SetConsoleTextAttribute.restype = wintypes.BOOL - + _GetConsoleScreenBufferInfo = \ ctypes.windll.kernel32.GetConsoleScreenBufferInfo - _GetConsoleScreenBufferInfo.argtypes = [wintypes.HANDLE, + _GetConsoleScreenBufferInfo.argtypes = [wintypes.HANDLE, ctypes.POINTER(CONSOLE_SCREEN_BUFFER_INFO)] _GetConsoleScreenBufferInfo.restype = wintypes.BOOL def GetConsoleInfo(handle): diff --git a/py/_path/common.py b/py/_path/common.py --- a/py/_path/common.py +++ b/py/_path/common.py @@ -64,7 +64,10 @@ else: if bool(value) ^ bool(meth()) ^ invert: return False - except (py.error.ENOENT, py.error.ENOTDIR): + except (py.error.ENOENT, py.error.ENOTDIR, py.error.EBUSY): + # EBUSY feels not entirely correct, + # but its kind of necessary since ENOMEDIUM + # is not accessible in python for name in self._depend_on_existence: if name in kw: if kw.get(name): @@ -368,6 +371,5 @@ else: name = str(path) # path.strpath # XXX svn? pattern = '*' + path.sep + pattern - from fnmatch import fnmatch - return fnmatch(name, pattern) + return py.std.fnmatch.fnmatch(name, pattern) diff --git a/py/_path/local.py b/py/_path/local.py --- a/py/_path/local.py +++ b/py/_path/local.py @@ -157,14 +157,16 @@ return str(self) < str(other) def samefile(self, other): - """ return True if 'other' references the same file as 'self'. """ - if not iswin32: - return py.error.checked_call( - os.path.samefile, str(self), str(other)) + """ return True if 'other' references the same file as 'self'. + """ + if not isinstance(other, py.path.local): + other = os.path.abspath(str(other)) if self == other: return True - other = os.path.abspath(str(other)) - return self == other + if iswin32: + return False # ther is no samefile + return py.error.checked_call( + os.path.samefile, str(self), str(other)) def remove(self, rec=1, ignore_errors=False): """ remove a file or directory (or a directory tree if rec=1). @@ -539,7 +541,11 @@ if self.basename != "__init__.py": modfile = modfile[:-12] - if not self.samefile(modfile): + try: + issame = self.samefile(modfile) + except py.error.ENOENT: + issame = False + if not issame: raise self.ImportMismatchError(modname, modfile, self) return mod else: diff --git a/py/_path/svnurl.py b/py/_path/svnurl.py --- a/py/_path/svnurl.py +++ b/py/_path/svnurl.py @@ -233,6 +233,8 @@ e = sys.exc_info()[1] if e.err.find('non-existent in that revision') != -1: raise py.error.ENOENT(self, e.err) + elif e.err.find("E200009:") != -1: + raise py.error.ENOENT(self, e.err) elif e.err.find('File not found') != -1: raise py.error.ENOENT(self, e.err) elif e.err.find('not part of a repository')!=-1: diff --git a/py/_path/svnwc.py b/py/_path/svnwc.py --- a/py/_path/svnwc.py +++ b/py/_path/svnwc.py @@ -482,10 +482,13 @@ except py.process.cmdexec.Error: e = sys.exc_info()[1] strerr = e.err.lower() - if strerr.find('file not found') != -1: + if strerr.find('not found') != -1: + raise py.error.ENOENT(self) + elif strerr.find("E200009:") != -1: raise py.error.ENOENT(self) if (strerr.find('file exists') != -1 or strerr.find('file already exists') != -1 or + strerr.find('w150002:') != -1 or strerr.find("can't create directory") != -1): raise py.error.EEXIST(self) raise @@ -593,7 +596,7 @@ out = self._authsvn('lock').strip() if not out: # warning or error, raise exception - raise Exception(out[4:]) + raise ValueError("unknown error in svn lock command") def unlock(self): """ unset a previously set lock """ @@ -1066,6 +1069,8 @@ modrev = '?' author = '?' date = '' + elif itemstatus == "replaced": + pass else: #print entryel.toxml() commitel = entryel.getElementsByTagName('commit')[0] @@ -1148,7 +1153,11 @@ raise ValueError("Not a versioned resource") #raise ValueError, "Not a versioned resource %r" % path self.kind = d['nodekind'] == 'directory' and 'dir' or d['nodekind'] - self.rev = int(d['revision']) + try: + self.rev = int(d['revision']) + except KeyError: + self.rev = None + self.path = py.path.local(d['path']) self.size = self.path.size() if 'lastchangedrev' in d: diff --git a/py/_xmlgen.py b/py/_xmlgen.py --- a/py/_xmlgen.py +++ b/py/_xmlgen.py @@ -52,7 +52,7 @@ def unicode(self, indent=2): l = [] SimpleUnicodeVisitor(l.append, indent).visit(self) - return "".join(l) + return u("").join(l) def __repr__(self): name = self.__class__.__name__ @@ -122,11 +122,13 @@ if visitmethod is not None: break else: - visitmethod = self.object + visitmethod = self.__object self.cache[cls] = visitmethod visitmethod(node) - def object(self, obj): + # the default fallback handler is marked private + # to avoid clashes with the tag name object + def __object(self, obj): #self.write(obj) self.write(escape(unicode(obj))) @@ -136,7 +138,8 @@ def list(self, obj): assert id(obj) not in self.visited self.visited[id(obj)] = 1 - map(self.visit, obj) + for elem in obj: + self.visit(elem) def Tag(self, tag): assert id(tag) not in self.visited @@ -181,7 +184,11 @@ value = getattr(attrs, name) if name.endswith('_'): name = name[:-1] - return ' %s="%s"' % (name, escape(unicode(value))) + if isinstance(value, raw): + insert = value.uniobj + else: + insert = escape(unicode(value)) + return ' %s="%s"' % (name, insert) def getstyle(self, tag): """ return attribute list suitable for styling. """ diff --git a/py/bin/_findpy.py b/py/bin/_findpy.py deleted file mode 100644 --- a/py/bin/_findpy.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python - -# -# find and import a version of 'py' -# -import sys -import os -from os.path import dirname as opd, exists, join, basename, abspath - -def searchpy(current): - while 1: - last = current - initpy = join(current, '__init__.py') - if not exists(initpy): - pydir = join(current, 'py') - # recognize py-package and ensure it is importable - if exists(pydir) and exists(join(pydir, '__init__.py')): - #for p in sys.path: - # if p == current: - # return True - if current != sys.path[0]: # if we are already first, then ok - sys.stderr.write("inserting into sys.path: %s\n" % current) - sys.path.insert(0, current) - return True - current = opd(current) - if last == current: - return False - -if not searchpy(abspath(os.curdir)): - if not searchpy(opd(abspath(sys.argv[0]))): - if not searchpy(opd(__file__)): - pass # let's hope it is just on sys.path - -import py -import pytest - -if __name__ == '__main__': - print ("py lib is at %s" % py.__file__) diff --git a/py/bin/py.test b/py/bin/py.test deleted file mode 100755 --- a/py/bin/py.test +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env python -from _findpy import pytest -raise SystemExit(pytest.main()) diff --git a/pypy/module/pyexpat/test/__init__.py b/pypy/module/pyexpat/test/__init__.py new file mode 100644 diff --git a/pypy/pytest.ini b/pypy/pytest.ini --- a/pypy/pytest.ini +++ b/pypy/pytest.ini @@ -1,2 +1,2 @@ [pytest] -addopts = --assertmode=old -rf +addopts = --assert=plain -rf diff --git a/pypy/test_all.py b/pypy/test_all.py old mode 100755 new mode 100644 --- a/pypy/test_all.py +++ b/pypy/test_all.py @@ -11,11 +11,12 @@ """ import sys, os -if len(sys.argv) == 1 and os.path.dirname(sys.argv[0]) in '.': - print >> sys.stderr, __doc__ - sys.exit(2) if __name__ == '__main__': + if len(sys.argv) == 1 and os.path.dirname(sys.argv[0]) in '.': + print >> sys.stderr, __doc__ + sys.exit(2) + import tool.autopath import pytest import pytest_cov diff --git a/pypy/tool/jitlogparser/test/__init__.py b/pypy/tool/jitlogparser/test/__init__.py new file mode 100644 diff --git a/pypy/tool/pytest/test/test_pytestsupport.py b/pypy/tool/pytest/test/test_pytestsupport.py --- a/pypy/tool/pytest/test/test_pytestsupport.py +++ b/pypy/tool/pytest/test/test_pytestsupport.py @@ -165,7 +165,10 @@ def test_one(self): exec 'blow' """) - ev, = sorter.getreports("pytest_runtest_logreport") + reports = sorter.getreports("pytest_runtest_logreport") + setup, ev, teardown = reports assert ev.failed + assert setup.passed + assert teardown.passed assert 'NameError' in ev.longrepr.reprcrash.message assert 'blow' in ev.longrepr.reprcrash.message diff --git a/testrunner/runner.py b/testrunner/runner.py --- a/testrunner/runner.py +++ b/testrunner/runner.py @@ -110,7 +110,10 @@ do_dry_run=False, timeout=None, _win32=(sys.platform=='win32')): args = interp + test_driver - args += ['-p', 'resultlog', '--resultlog=%s' % logfname, test] + args += ['-p', 'resultlog', + '--resultlog=%s' % logfname, + '--junitxml=%s.junit' % logfname, + test] args = map(str, args) interp0 = args[0] diff --git a/testrunner/scratchbox_runner.py b/testrunner/scratchbox_runner.py --- a/testrunner/scratchbox_runner.py +++ b/testrunner/scratchbox_runner.py @@ -14,14 +14,14 @@ def dry_run_scratchbox(args, cwd, out, timeout=None): return dry_run(args_for_scratchbox(cwd, args), cwd, out, timeout) -import runner -# XXX hack hack hack -dry_run = runner.dry_run -run = runner.run +if __name__ == '__main__': + import runner + # XXX hack hack hack + dry_run = runner.dry_run + run = runner.run -runner.dry_run = dry_run_scratchbox -runner.run = run_scratchbox + runner.dry_run = dry_run_scratchbox + runner.run = run_scratchbox -if __name__ == '__main__': import sys runner.main(sys.argv) diff --git a/testrunner/test/conftest.py b/testrunner/test/conftest.py new file mode 100644 --- /dev/null +++ b/testrunner/test/conftest.py @@ -0,0 +1,6 @@ + +def pytest_runtest_makereport(__multicall__, item): + report = __multicall__.execute() + if 'out' in item.funcargs: + report.sections.append(('out', item.funcargs['out'].read())) + return report diff --git a/testrunner/test/test_runner.py b/testrunner/test/test_runner.py --- a/testrunner/test/test_runner.py +++ b/testrunner/test/test_runner.py @@ -53,49 +53,44 @@ assert not should_report_failure("F Def\n. Ghi\n. Jkl\n") + class TestRunHelper(object): + def pytest_funcarg__out(self, request): + tmpdir = request.getfuncargvalue('tmpdir') + return tmpdir.ensure('out') - def setup_method(self, meth): - h, self.fn = tempfile.mkstemp() - os.close(h) + def test_run(self, out): + res = runner.run([sys.executable, "-c", "print 42"], '.', out) + assert res == 0 + assert out.read() == "42\n" - def teardown_method(self, meth): - os.unlink(self.fn) - - def test_run(self): - res = runner.run([sys.executable, "-c", "print 42"], '.', - py.path.local(self.fn)) - assert res == 0 - out = py.path.local(self.fn).read('r') - assert out == "42\n" - - def test_error(self): - res = runner.run([sys.executable, "-c", "import sys; sys.exit(3)"], '.', py.path.local(self.fn)) + def test_error(self, out): + res = runner.run([sys.executable, "-c", "import sys; sys.exit(3)"], '.', out) assert res == 3 - def test_signal(self): + def test_signal(self, out): if sys.platform == 'win32': py.test.skip("no death by signal on windows") - res = runner.run([sys.executable, "-c", "import os; os.kill(os.getpid(), 9)"], '.', py.path.local(self.fn)) + res = runner.run([sys.executable, "-c", "import os; os.kill(os.getpid(), 9)"], '.', out) assert res == -9 - def test_timeout(self): - res = runner.run([sys.executable, "-c", "while True: pass"], '.', py.path.local(self.fn), timeout=3) + def test_timeout(self, out): + res = runner.run([sys.executable, "-c", "while True: pass"], '.', out, timeout=3) assert res == -999 - def test_timeout_lock(self): - res = runner.run([sys.executable, "-c", "import threading; l=threading.Lock(); l.acquire(); l.acquire()"], '.', py.path.local(self.fn), timeout=3) + def test_timeout_lock(self, out): + res = runner.run([sys.executable, "-c", "import threading; l=threading.Lock(); l.acquire(); l.acquire()"], '.', out, timeout=3) assert res == -999 - def test_timeout_syscall(self): - res = runner.run([sys.executable, "-c", "import socket; s=s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); s.bind(('', 0)); s.recv(1000)"], '.', py.path.local(self.fn), timeout=3) + def test_timeout_syscall(self, out): + res = runner.run([sys.executable, "-c", "import socket; s=s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); s.bind(('', 0)); s.recv(1000)"], '.', out, timeout=3) assert res == -999 - def test_timeout_success(self): + def test_timeout_success(self, out): res = runner.run([sys.executable, "-c", "print 42"], '.', - py.path.local(self.fn), timeout=2) + out, timeout=2) assert res == 0 - out = py.path.local(self.fn).read('r') + out = out.read() assert out == "42\n" @@ -122,7 +117,10 @@ expected = ['INTERP', 'IARG', 'driver', 'darg', + '-p', 'resultlog', '--resultlog=LOGFILE', + '--junitxml=LOGFILE.junit', + 'test_one'] assert self.called == (expected, '/wd', 'out', 'secs') @@ -138,9 +136,11 @@ expected = ['/wd' + os.sep + './INTERP', 'IARG', 'driver', 'darg', + '-p', 'resultlog', '--resultlog=LOGFILE', + '--junitxml=LOGFILE.junit', 'test_one'] - + assert self.called[0] == expected assert self.called == (expected, '/wd', 'out', 'secs') assert res == 0 @@ -251,7 +251,7 @@ assert '\n' in log log_lines = log.splitlines() - assert log_lines[0] == ". test_normal/test_example.py:test_one" + assert ". test_normal/test_example.py::test_one" in log_lines nfailures = 0 noutcomes = 0 for line in log_lines: From noreply at buildbot.pypy.org Mon Apr 9 18:22:28 2012 From: noreply at buildbot.pypy.org (amauryfa) Date: Mon, 9 Apr 2012 18:22:28 +0200 (CEST) Subject: [pypy-commit] benchmarks default: Simplify BUILD_LIST_FROM_ARG again, and hope that benchmarks pass Message-ID: <20120409162228.DA2BD46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Amaury Forgeot d'Arc Branch: Changeset: r179:d9436ef872ff Date: 2012-04-09 18:21 +0200 http://bitbucket.org/pypy/benchmarks/changeset/d9436ef872ff/ Log: Simplify BUILD_LIST_FROM_ARG again, and hope that benchmarks pass diff --git a/lib/pypy/pypy/interpreter/pyopcode.py b/lib/pypy/pypy/interpreter/pyopcode.py --- a/lib/pypy/pypy/interpreter/pyopcode.py +++ b/lib/pypy/pypy/interpreter/pyopcode.py @@ -717,10 +717,6 @@ # this is a little dance, because list has to be before the # value last_val = self.popvalue() - try: - lgt = self.space.int_w(self.space.len(last_val)) - except OperationError: - lgt = 0 # oh well self.pushvalue(self.space.newlist([])) self.pushvalue(last_val) From noreply at buildbot.pypy.org Mon Apr 9 18:41:28 2012 From: noreply at buildbot.pypy.org (mattip) Date: Mon, 9 Apr 2012 18:41:28 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: merge from default Message-ID: <20120409164128.78C3B46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54267:94df3eb8e759 Date: 2012-04-08 20:08 +0300 http://bitbucket.org/pypy/pypy/changeset/94df3eb8e759/ Log: merge from default diff --git a/pypy/doc/tool/makecontributor.py b/pypy/doc/tool/makecontributor.py new file mode 100644 --- /dev/null +++ b/pypy/doc/tool/makecontributor.py @@ -0,0 +1,133 @@ +import py +import sys +from collections import defaultdict +import operator +import re +import mercurial.localrepo +import mercurial.ui + +ROOT = py.path.local(__file__).join('..', '..', '..', '..') +author_re = re.compile('(.*) <.*>') +pair_programming_re = re.compile(r'^\((.*?)\)') +excluded = set(["pypy", "convert-repo"]) + +alias = { + 'Anders Chrigstrom': ['arre'], + 'Antonio Cuni': ['antocuni', 'anto'], + 'Armin Rigo': ['arigo', 'arfigo', 'armin', 'arigato'], + 'Maciej Fijalkowski': ['fijal'], + 'Carl Friedrich Bolz': ['cfbolz', 'cf'], + 'Samuele Pedroni': ['pedronis', 'samuele', 'samule'], + 'Michael Hudson': ['mwh'], + 'Holger Krekel': ['hpk', 'holger krekel', 'holger', 'hufpk'], + "Amaury Forgeot d'Arc": ['afa'], + 'Alex Gaynor': ['alex', 'agaynor'], + 'David Schneider': ['bivab', 'david'], + 'Christian Tismer': ['chris', 'christian', 'tismer', + 'tismer at christia-wjtqxl.localdomain'], + 'Benjamin Peterson': ['benjamin'], + 'Hakan Ardo': ['hakan', 'hakanardo'], + 'Niklaus Haldimann': ['nik'], + 'Alexander Schremmer': ['xoraxax'], + 'Anders Hammarquist': ['iko'], + 'David Edelsohn': ['edelsoh', 'edelsohn'], + 'Niko Matsakis': ['niko'], + 'Jakub Gustak': ['jlg'], + 'Guido Wesdorp': ['guido'], + 'Michael Foord': ['mfoord'], + 'Mark Pearse': ['mwp'], + 'Toon Verwaest': ['tverwaes'], + 'Eric van Riet Paap': ['ericvrp'], + 'Jacob Hallen': ['jacob', 'jakob'], + 'Anders Lehmann': ['ale', 'anders'], + 'Bert Freudenberg': ['bert'], + 'Boris Feigin': ['boris', 'boria'], + 'Valentino Volonghi': ['valentino', 'dialtone'], + 'Aurelien Campeas': ['aurelien', 'aureliene'], + 'Adrien Di Mascio': ['adim'], + 'Jacek Generowicz': ['Jacek', 'jacek'], + 'Jim Hunziker': ['landtuna at gmail.com'], + 'Kristjan Valur Jonsson': ['kristjan at kristjan-lp.ccp.ad.local'], + 'Laura Creighton': ['lac'], + 'Aaron Iles': ['aliles'], + 'Ludovic Aubry': ['ludal', 'ludovic'], + 'Lukas Diekmann': ['l.diekmann', 'ldiekmann'], + 'Matti Picus': ['Matti Picus matti.picus at gmail.com', + 'matthp', 'mattip', 'mattip>'], + 'Michael Cheng': ['mikefc'], + 'Richard Emslie': ['rxe'], + 'Roberto De Ioris': ['roberto at goyle'], + 'Roberto De Ioris': ['roberto at mrspurr'], + 'Sven Hager': ['hager'], + 'Tomo Cocoa': ['cocoatomo'], + } + +alias_map = {} +for name, nicks in alias.iteritems(): + for nick in nicks: + alias_map[nick] = name + +def get_canonical_author(name): + match = author_re.match(name) + if match: + name = match.group(1) + return alias_map.get(name, name) + +ignored_nicknames = defaultdict(int) + +def get_more_authors(log): + match = pair_programming_re.match(log) + if not match: + return set() + ignore_words = ['around', 'consulting', 'yesterday', 'for a bit', 'thanks', + 'in-progress', 'bits of', 'even a little', 'floating',] + sep_words = ['and', ';', '+', '/', 'with special by'] + nicknames = match.group(1) + for word in ignore_words: + nicknames = nicknames.replace(word, '') + for word in sep_words: + nicknames = nicknames.replace(word, ',') + nicknames = [nick.strip().lower() for nick in nicknames.split(',')] + authors = set() + for nickname in nicknames: + author = alias_map.get(nickname) + if not author: + ignored_nicknames[nickname] += 1 + else: + authors.add(author) + return authors + +def main(show_numbers): + ui = mercurial.ui.ui() + repo = mercurial.localrepo.localrepository(ui, str(ROOT)) + authors_count = defaultdict(int) + for i in repo: + ctx = repo[i] + authors = set() + authors.add(get_canonical_author(ctx.user())) + authors.update(get_more_authors(ctx.description())) + for author in authors: + if author not in excluded: + authors_count[author] += 1 + + # uncomment the next lines to get the list of nicknamed which could not be + # parsed from commit logs + ## items = ignored_nicknames.items() + ## items.sort(key=operator.itemgetter(1), reverse=True) + ## for name, n in items: + ## if show_numbers: + ## print '%5d %s' % (n, name) + ## else: + ## print name + + items = authors_count.items() + items.sort(key=operator.itemgetter(1), reverse=True) + for name, n in items: + if show_numbers: + print '%5d %s' % (n, name) + else: + print name + +if __name__ == '__main__': + show_numbers = '-n' in sys.argv + main(show_numbers) diff --git a/pypy/interpreter/typedef.py b/pypy/interpreter/typedef.py --- a/pypy/interpreter/typedef.py +++ b/pypy/interpreter/typedef.py @@ -321,7 +321,7 @@ def user_setup(self, space, w_subtype): self.w__dict__ = space.newdict( - instance=True, classofinstance=w_subtype) + instance=True) base_user_setup(self, space, w_subtype) def setclass(self, space, w_subtype): diff --git a/pypy/module/cpyext/include/patchlevel.h b/pypy/module/cpyext/include/patchlevel.h --- a/pypy/module/cpyext/include/patchlevel.h +++ b/pypy/module/cpyext/include/patchlevel.h @@ -29,7 +29,7 @@ #define PY_VERSION "2.7.2" /* PyPy version as a string */ -#define PYPY_VERSION "1.8.1" +#define PYPY_VERSION "1.9.1" /* Subversion Revision number of this file (not of the repository). * Empty since Mercurial migration. */ diff --git a/pypy/module/micronumpy/test/test_ufuncs.py b/pypy/module/micronumpy/test/test_ufuncs.py --- a/pypy/module/micronumpy/test/test_ufuncs.py +++ b/pypy/module/micronumpy/test/test_ufuncs.py @@ -597,9 +597,8 @@ def test_shift(self): from _numpypy import left_shift, right_shift - import sys - assert (left_shift([5, 1], [2, 31]) == [20, 2**31]).all() + assert (left_shift([5, 1], [2, 13]) == [20, 2**13]).all() assert (right_shift(10, range(5)) == [10, 5, 2, 1, 0]).all() def test_comparisons(self): diff --git a/pypy/module/sys/version.py b/pypy/module/sys/version.py --- a/pypy/module/sys/version.py +++ b/pypy/module/sys/version.py @@ -10,7 +10,7 @@ CPYTHON_VERSION = (2, 7, 2, "final", 42) #XXX # sync patchlevel.h CPYTHON_API_VERSION = 1013 #XXX # sync with include/modsupport.h -PYPY_VERSION = (1, 8, 1, "dev", 0) #XXX # sync patchlevel.h +PYPY_VERSION = (1, 9, 1, "dev", 0) #XXX # sync patchlevel.h if platform.name == 'msvc': COMPILER_INFO = 'MSC v.%d 32 bit' % (platform.version * 10 + 600) diff --git a/pypy/objspace/fake/objspace.py b/pypy/objspace/fake/objspace.py --- a/pypy/objspace/fake/objspace.py +++ b/pypy/objspace/fake/objspace.py @@ -110,7 +110,7 @@ "NOT_RPYTHON" raise NotImplementedError - def newdict(self, module=False, instance=False, classofinstance=None, + def newdict(self, module=False, instance=False, strdict=False): return w_some_obj() diff --git a/pypy/objspace/std/dictmultiobject.py b/pypy/objspace/std/dictmultiobject.py --- a/pypy/objspace/std/dictmultiobject.py +++ b/pypy/objspace/std/dictmultiobject.py @@ -33,8 +33,7 @@ @staticmethod def allocate_and_init_instance(space, w_type=None, module=False, - instance=False, classofinstance=None, - strdict=False): + instance=False, strdict=False): if space.config.objspace.std.withcelldict and module: from pypy.objspace.std.celldict import ModuleDictStrategy @@ -563,10 +562,7 @@ def listview_int(self, w_dict): return self.unerase(w_dict.dstorage).keys() - def w_keys(self, w_dict): - # XXX there is no space.newlist_int yet - space = self.space - return space.call_function(space.w_list, w_dict) + # XXX there is no space.newlist_int yet to implement w_keys more efficiently class IntIteratorImplementation(_WrappedIteratorMixin, IteratorImplementation): pass diff --git a/pypy/objspace/std/identitydict.py b/pypy/objspace/std/identitydict.py --- a/pypy/objspace/std/identitydict.py +++ b/pypy/objspace/std/identitydict.py @@ -1,5 +1,5 @@ ## ---------------------------------------------------------------------------- -## dict strategy (see dict_multiobject.py) +## dict strategy (see dictmultiobject.py) from pypy.rlib import rerased from pypy.rlib.debug import mark_dict_non_null @@ -80,8 +80,8 @@ def iter(self, w_dict): return IdentityDictIteratorImplementation(self.space, self, w_dict) - def keys(self, w_dict): - return self.unerase(w_dict.dstorage).keys() + def w_keys(self, w_dict): + return self.space.newlist(self.unerase(w_dict.dstorage).keys()) class IdentityDictIteratorImplementation(_UnwrappedIteratorMixin, IteratorImplementation): diff --git a/pypy/objspace/std/objspace.py b/pypy/objspace/std/objspace.py --- a/pypy/objspace/std/objspace.py +++ b/pypy/objspace/std/objspace.py @@ -313,11 +313,10 @@ def newlist_str(self, list_s): return W_ListObject.newlist_str(self, list_s) - def newdict(self, module=False, instance=False, classofinstance=None, + def newdict(self, module=False, instance=False, strdict=False): return W_DictMultiObject.allocate_and_init_instance( self, module=module, instance=instance, - classofinstance=classofinstance, strdict=strdict) def newset(self): diff --git a/pypy/objspace/std/test/test_dictmultiobject.py b/pypy/objspace/std/test/test_dictmultiobject.py --- a/pypy/objspace/std/test/test_dictmultiobject.py +++ b/pypy/objspace/std/test/test_dictmultiobject.py @@ -885,10 +885,9 @@ def newtuple(self, l): return tuple(l) - def newdict(self, module=False, instance=False, classofinstance=None): + def newdict(self, module=False, instance=False): return W_DictMultiObject.allocate_and_init_instance( - self, module=module, instance=instance, - classofinstance=classofinstance) + self, module=module, instance=instance) def finditem_str(self, w_dict, s): return w_dict.getitem_str(s) # assume it's a multidict @@ -968,6 +967,20 @@ assert type(self.impl.strategy) is self.StrategyClass #assert self.impl.r_dict_content is None + def test_popitem(self): + self.fill_impl() + assert self.impl.length() == 2 + a, b = self.impl.popitem() + assert self.impl.length() == 1 + if a == self.string: + assert b == 1000 + assert self.impl.getitem(self.string2) == 2000 + else: + assert a == self.string2 + assert b == 2000 + assert self.impl.getitem_str(self.string) == 1000 + self.check_not_devolved() + def test_setitem(self): self.impl.setitem(self.string, 1000) assert self.impl.length() == 1 diff --git a/pypy/translator/c/gcc/trackgcroot.py b/pypy/translator/c/gcc/trackgcroot.py --- a/pypy/translator/c/gcc/trackgcroot.py +++ b/pypy/translator/c/gcc/trackgcroot.py @@ -847,6 +847,10 @@ if sources: target, = sources + if target.endswith('@PLT'): + # In -fPIC mode, all functions calls have this suffix + target = target[:-4] + if target in self.FUNCTIONS_NOT_RETURNING: return [InsnStop(target)] if self.format == 'mingw32' and target == '__alloca': @@ -1137,7 +1141,7 @@ r_jump_rel_label = re.compile(r"\tj\w+\s+"+"(\d+)f"+"\s*$") r_unaryinsn_star= re.compile(r"\t[a-z]\w*\s+[*]("+OPERAND+")\s*$") - r_jmptable_item = re.compile(r"\t.quad\t"+LABEL+"(-\"[A-Za-z0-9$]+\")?\s*$") + r_jmptable_item = re.compile(r"\t.(?:quad|long)\t"+LABEL+"(-\"[A-Za-z0-9$]+\"|-"+LABEL+")?\s*$") r_jmptable_end = re.compile(r"\t.text|\t.section\s+.text|\t\.align|"+LABEL) r_gcroot_marker = re.compile(r"\t/[*] GCROOT ("+LOCALVARFP+") [*]/") From noreply at buildbot.pypy.org Mon Apr 9 18:41:29 2012 From: noreply at buildbot.pypy.org (mattip) Date: Mon, 9 Apr 2012 18:41:29 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: remove poll() from select for cpython compatability Message-ID: <20120409164129.C2A8F46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54268:774652376acf Date: 2012-04-09 19:33 +0300 http://bitbucket.org/pypy/pypy/changeset/774652376acf/ Log: remove poll() from select for cpython compatability diff --git a/pypy/module/select/__init__.py b/pypy/module/select/__init__.py --- a/pypy/module/select/__init__.py +++ b/pypy/module/select/__init__.py @@ -7,12 +7,17 @@ class Module(MixedModule): appleveldefs = { } - - interpleveldefs = { - 'poll' : 'interp_select.poll', - 'select': 'interp_select.select', - 'error' : 'space.fromcache(interp_select.Cache).w_error' - } + if sys.platform.startswith('win'): + interpleveldefs = { + 'select': 'interp_select.select', + 'error' : 'space.fromcache(interp_select.Cache).w_error' + } + else: + interpleveldefs = { + 'poll' : 'interp_select.poll', + 'select': 'interp_select.select', + 'error' : 'space.fromcache(interp_select.Cache).w_error' + } if sys.platform.startswith('linux'): interpleveldefs['epoll'] = 'interp_epoll.W_Epoll' diff --git a/pypy/module/select/interp_select.py b/pypy/module/select/interp_select.py --- a/pypy/module/select/interp_select.py +++ b/pypy/module/select/interp_select.py @@ -53,7 +53,6 @@ except (OverflowError, ValueError): raise OperationError(space.w_ValueError, space.wrap("math range error")) - try: retval = rpoll.poll(self.fddict, timeout) except rpoll.PollError, e: diff --git a/pypy/module/select/test/test_select.py b/pypy/module/select/test/test_select.py --- a/pypy/module/select/test/test_select.py +++ b/pypy/module/select/test/test_select.py @@ -214,6 +214,8 @@ def test_poll(self): import select + if 'poll' not in dir(select): + skip('select.poll not available') readend, writeend = self.getpair() try: class A(object): From noreply at buildbot.pypy.org Mon Apr 9 18:41:31 2012 From: noreply at buildbot.pypy.org (mattip) Date: Mon, 9 Apr 2012 18:41:31 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: merge default into branch Message-ID: <20120409164131.ECF8E46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54269:f6da3cc07d42 Date: 2012-04-09 19:34 +0300 http://bitbucket.org/pypy/pypy/changeset/f6da3cc07d42/ Log: merge default into branch diff --git a/_pytest/__init__.py b/_pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.1.0.dev4' +__version__ = '2.2.4.dev2' diff --git a/_pytest/assertion/__init__.py b/_pytest/assertion/__init__.py --- a/_pytest/assertion/__init__.py +++ b/_pytest/assertion/__init__.py @@ -2,35 +2,25 @@ support for presenting detailed information in failing assertions. """ import py -import imp -import marshal -import struct import sys import pytest from _pytest.monkeypatch import monkeypatch -from _pytest.assertion import reinterpret, util - -try: - from _pytest.assertion.rewrite import rewrite_asserts -except ImportError: - rewrite_asserts = None -else: - import ast +from _pytest.assertion import util def pytest_addoption(parser): group = parser.getgroup("debugconfig") - group.addoption('--assertmode', action="store", dest="assertmode", - choices=("on", "old", "off", "default"), default="default", - metavar="on|old|off", + group.addoption('--assert', action="store", dest="assertmode", + choices=("rewrite", "reinterp", "plain",), + default="rewrite", metavar="MODE", help="""control assertion debugging tools. -'off' performs no assertion debugging. -'old' reinterprets the expressions in asserts to glean information. -'on' (the default) rewrites the assert statements in test modules to provide -sub-expression results.""") +'plain' performs no assertion debugging. +'reinterp' reinterprets assert statements after they failed to provide assertion expression information. +'rewrite' (the default) rewrites assert statements in test modules on import +to provide assert expression information. """) group.addoption('--no-assert', action="store_true", default=False, - dest="noassert", help="DEPRECATED equivalent to --assertmode=off") + dest="noassert", help="DEPRECATED equivalent to --assert=plain") group.addoption('--nomagic', action="store_true", default=False, - dest="nomagic", help="DEPRECATED equivalent to --assertmode=off") + dest="nomagic", help="DEPRECATED equivalent to --assert=plain") class AssertionState: """State for the assertion plugin.""" @@ -40,89 +30,90 @@ self.trace = config.trace.root.get("assertion") def pytest_configure(config): - warn_about_missing_assertion() mode = config.getvalue("assertmode") if config.getvalue("noassert") or config.getvalue("nomagic"): - if mode not in ("off", "default"): - raise pytest.UsageError("assertion options conflict") - mode = "off" - elif mode == "default": - mode = "on" - if mode != "off": - def callbinrepr(op, left, right): - hook_result = config.hook.pytest_assertrepr_compare( - config=config, op=op, left=left, right=right) - for new_expl in hook_result: - if new_expl: - return '\n~'.join(new_expl) + mode = "plain" + if mode == "rewrite": + try: + import ast + except ImportError: + mode = "reinterp" + else: + if sys.platform.startswith('java'): + mode = "reinterp" + if mode != "plain": + _load_modules(mode) m = monkeypatch() config._cleanup.append(m.undo) m.setattr(py.builtin.builtins, 'AssertionError', reinterpret.AssertionError) - m.setattr(util, '_reprcompare', callbinrepr) - if mode == "on" and rewrite_asserts is None: - mode = "old" + hook = None + if mode == "rewrite": + hook = rewrite.AssertionRewritingHook() + sys.meta_path.append(hook) + warn_about_missing_assertion(mode) config._assertstate = AssertionState(config, mode) + config._assertstate.hook = hook config._assertstate.trace("configured with mode set to %r" % (mode,)) -def _write_pyc(co, source_path): - if hasattr(imp, "cache_from_source"): - # Handle PEP 3147 pycs. - pyc = py.path.local(imp.cache_from_source(str(source_path))) - pyc.ensure() - else: - pyc = source_path + "c" - mtime = int(source_path.mtime()) - fp = pyc.open("wb") - try: - fp.write(imp.get_magic()) - fp.write(struct.pack(" 0 and - item.identifier != "__future__"): + elif (not isinstance(item, ast.ImportFrom) or item.level > 0 or + item.module != "__future__"): lineno = item.lineno break pos += 1 @@ -118,9 +357,9 @@ for alias in aliases] mod.body[pos:pos] = imports # Collect asserts. - nodes = collections.deque([mod]) + nodes = [mod] while nodes: - node = nodes.popleft() + node = nodes.pop() for name, field in ast.iter_fields(node): if isinstance(field, list): new = [] @@ -143,7 +382,7 @@ """Get a new variable.""" # Use a character invalid in python identifiers to avoid clashing. name = "@py_assert" + str(next(self.variable_counter)) - self.variables.add(name) + self.variables.append(name) return name def assign(self, expr): @@ -198,7 +437,8 @@ # There's already a message. Don't mess with it. return [assert_] self.statements = [] - self.variables = set() + self.cond_chain = () + self.variables = [] self.variable_counter = itertools.count() self.stack = [] self.on_failure = [] @@ -220,11 +460,11 @@ else: raise_ = ast.Raise(exc, None, None) body.append(raise_) - # Delete temporary variables. - names = [ast.Name(name, ast.Del()) for name in self.variables] - if names: - delete = ast.Delete(names) - self.statements.append(delete) + # Clear temporary variables by setting them to None. + if self.variables: + variables = [ast.Name(name, ast.Store()) for name in self.variables] + clear = ast.Assign(variables, ast.Name("None", ast.Load())) + self.statements.append(clear) # Fix line numbers. for stmt in self.statements: set_location(stmt, assert_.lineno, assert_.col_offset) @@ -240,21 +480,38 @@ return name, self.explanation_param(expr) def visit_BoolOp(self, boolop): - operands = [] - explanations = [] + res_var = self.variable() + expl_list = self.assign(ast.List([], ast.Load())) + app = ast.Attribute(expl_list, "append", ast.Load()) + is_or = int(isinstance(boolop.op, ast.Or)) + body = save = self.statements + fail_save = self.on_failure + levels = len(boolop.values) - 1 self.push_format_context() - for operand in boolop.values: - res, explanation = self.visit(operand) - operands.append(res) - explanations.append(explanation) - expls = ast.Tuple([ast.Str(expl) for expl in explanations], ast.Load()) - is_or = ast.Num(isinstance(boolop.op, ast.Or)) - expl_template = self.helper("format_boolop", - ast.Tuple(operands, ast.Load()), expls, - is_or) + # Process each operand, short-circuting if needed. + for i, v in enumerate(boolop.values): + if i: + fail_inner = [] + self.on_failure.append(ast.If(cond, fail_inner, [])) + self.on_failure = fail_inner + self.push_format_context() + res, expl = self.visit(v) + body.append(ast.Assign([ast.Name(res_var, ast.Store())], res)) + expl_format = self.pop_format_context(ast.Str(expl)) + call = ast.Call(app, [expl_format], [], None, None) + self.on_failure.append(ast.Expr(call)) + if i < levels: + cond = res + if is_or: + cond = ast.UnaryOp(ast.Not(), cond) + inner = [] + self.statements.append(ast.If(cond, inner, [])) + self.statements = body = inner + self.statements = save + self.on_failure = fail_save + expl_template = self.helper("format_boolop", expl_list, ast.Num(is_or)) expl = self.pop_format_context(expl_template) - res = self.assign(ast.BoolOp(boolop.op, operands)) - return res, self.explanation_param(expl) + return ast.Name(res_var, ast.Load()), self.explanation_param(expl) def visit_UnaryOp(self, unary): pattern = unary_map[unary.op.__class__] @@ -288,7 +545,7 @@ new_star, expl = self.visit(call.starargs) arg_expls.append("*" + expl) if call.kwargs: - new_kwarg, expl = self.visit(call.kwarg) + new_kwarg, expl = self.visit(call.kwargs) arg_expls.append("**" + expl) expl = "%s(%s)" % (func_expl, ', '.join(arg_expls)) new_call = ast.Call(new_func, new_args, new_kwargs, new_star, new_kwarg) diff --git a/_pytest/capture.py b/_pytest/capture.py --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -11,22 +11,22 @@ group._addoption('-s', action="store_const", const="no", dest="capture", help="shortcut for --capture=no.") + at pytest.mark.tryfirst +def pytest_cmdline_parse(pluginmanager, args): + # we want to perform capturing already for plugin/conftest loading + if '-s' in args or "--capture=no" in args: + method = "no" + elif hasattr(os, 'dup') and '--capture=sys' not in args: + method = "fd" + else: + method = "sys" + capman = CaptureManager(method) + pluginmanager.register(capman, "capturemanager") + def addouterr(rep, outerr): - repr = getattr(rep, 'longrepr', None) - if not hasattr(repr, 'addsection'): - return for secname, content in zip(["out", "err"], outerr): if content: - repr.addsection("Captured std%s" % secname, content.rstrip()) - -def pytest_unconfigure(config): - # registered in config.py during early conftest.py loading - capman = config.pluginmanager.getplugin('capturemanager') - while capman._method2capture: - name, cap = capman._method2capture.popitem() - # XXX logging module may wants to close it itself on process exit - # otherwise we could do finalization here and call "reset()". - cap.suspend() + rep.sections.append(("Captured std%s" % secname, content)) class NoCapture: def startall(self): @@ -39,8 +39,9 @@ return "", "" class CaptureManager: - def __init__(self): + def __init__(self, defaultmethod=None): self._method2capture = {} + self._defaultmethod = defaultmethod def _maketempfile(self): f = py.std.tempfile.TemporaryFile() @@ -65,14 +66,6 @@ else: raise ValueError("unknown capturing method: %r" % method) - def _getmethod_preoptionparse(self, args): - if '-s' in args or "--capture=no" in args: - return "no" - elif hasattr(os, 'dup') and '--capture=sys' not in args: - return "fd" - else: - return "sys" - def _getmethod(self, config, fspath): if config.option.capture: method = config.option.capture @@ -85,16 +78,22 @@ method = "sys" return method + def reset_capturings(self): + for name, cap in self._method2capture.items(): + cap.reset() + def resumecapture_item(self, item): method = self._getmethod(item.config, item.fspath) if not hasattr(item, 'outerr'): item.outerr = ('', '') # we accumulate outerr on the item return self.resumecapture(method) - def resumecapture(self, method): + def resumecapture(self, method=None): if hasattr(self, '_capturing'): raise ValueError("cannot resume, already capturing with %r" % (self._capturing,)) + if method is None: + method = self._defaultmethod cap = self._method2capture.get(method) self._capturing = method if cap is None: @@ -164,17 +163,6 @@ def pytest_runtest_teardown(self, item): self.resumecapture_item(item) - def pytest__teardown_final(self, __multicall__, session): - method = self._getmethod(session.config, None) - self.resumecapture(method) - try: - rep = __multicall__.execute() - finally: - outerr = self.suspendcapture() - if rep: - addouterr(rep, outerr) - return rep - def pytest_keyboard_interrupt(self, excinfo): if hasattr(self, '_capturing'): self.suspendcapture() diff --git a/_pytest/config.py b/_pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -8,13 +8,15 @@ def pytest_cmdline_parse(pluginmanager, args): config = Config(pluginmanager) config.parse(args) - if config.option.debug: - config.trace.root.setwriter(sys.stderr.write) return config def pytest_unconfigure(config): - for func in config._cleanup: - func() + while 1: + try: + fin = config._cleanup.pop() + except IndexError: + break + fin() class Parser: """ Parser for command line arguments. """ @@ -81,6 +83,7 @@ self._inidict[name] = (help, type, default) self._ininames.append(name) + class OptionGroup: def __init__(self, name, description="", parser=None): self.name = name @@ -256,11 +259,14 @@ self.hook = self.pluginmanager.hook self._inicache = {} self._cleanup = [] - + @classmethod def fromdictargs(cls, option_dict, args): """ constructor useable for subprocesses. """ config = cls() + # XXX slightly crude way to initialize capturing + import _pytest.capture + _pytest.capture.pytest_cmdline_parse(config.pluginmanager, args) config._preparse(args, addopts=False) config.option.__dict__.update(option_dict) for x in config.option.plugins: @@ -285,11 +291,10 @@ def _setinitialconftest(self, args): # capture output during conftest init (#issue93) - from _pytest.capture import CaptureManager - capman = CaptureManager() - self.pluginmanager.register(capman, 'capturemanager') - # will be unregistered in capture.py's unconfigure() - capman.resumecapture(capman._getmethod_preoptionparse(args)) + # XXX introduce load_conftest hook to avoid needing to know + # about capturing plugin here + capman = self.pluginmanager.getplugin("capturemanager") + capman.resumecapture() try: try: self._conftest.setinitial(args) @@ -334,6 +339,7 @@ # Note that this can only be called once per testing process. assert not hasattr(self, 'args'), ( "can only parse cmdline args at most once per Config object") + self._origargs = args self._preparse(args) self._parser.hints.extend(self.pluginmanager._hints) args = self._parser.parse_setoption(args, self.option) @@ -341,6 +347,14 @@ args.append(py.std.os.getcwd()) self.args = args + def addinivalue_line(self, name, line): + """ add a line to an ini-file option. The option must have been + declared but might not yet be set in which case the line becomes the + the first line in its value. """ + x = self.getini(name) + assert isinstance(x, list) + x.append(line) # modifies the cached list inline + def getini(self, name): """ return configuration value from an ini file. If the specified name hasn't been registered through a prior ``parse.addini`` @@ -422,7 +436,7 @@ def getcfg(args, inibasenames): - args = [x for x in args if str(x)[0] != "-"] + args = [x for x in args if not str(x).startswith("-")] if not args: args = [py.path.local()] for arg in args: diff --git a/_pytest/core.py b/_pytest/core.py --- a/_pytest/core.py +++ b/_pytest/core.py @@ -16,11 +16,10 @@ "junitxml resultlog doctest").split() class TagTracer: - def __init__(self, prefix="[pytest] "): + def __init__(self): self._tag2proc = {} self.writer = None self.indent = 0 - self.prefix = prefix def get(self, name): return TagTracerSub(self, (name,)) @@ -30,7 +29,7 @@ if args: indent = " " * self.indent content = " ".join(map(str, args)) - self.writer("%s%s%s\n" %(self.prefix, indent, content)) + self.writer("%s%s [%s]\n" %(indent, content, ":".join(tags))) try: self._tag2proc[tags](tags, args) except KeyError: @@ -212,6 +211,14 @@ self.register(mod, modname) self.consider_module(mod) + def pytest_configure(self, config): + config.addinivalue_line("markers", + "tryfirst: mark a hook implementation function such that the " + "plugin machinery will try to call it first/as early as possible.") + config.addinivalue_line("markers", + "trylast: mark a hook implementation function such that the " + "plugin machinery will try to call it last/as late as possible.") + def pytest_plugin_registered(self, plugin): import pytest dic = self.call_plugin(plugin, "pytest_namespace", {}) or {} @@ -432,10 +439,7 @@ def _preloadplugins(): _preinit.append(PluginManager(load=True)) -def main(args=None, plugins=None): - """ returned exit code integer, after an in-process testing run - with the given command line arguments, preloading an optional list - of passed in plugin objects. """ +def _prepareconfig(args=None, plugins=None): if args is None: args = sys.argv[1:] elif isinstance(args, py.path.local): @@ -449,13 +453,19 @@ else: # subsequent calls to main will create a fresh instance _pluginmanager = PluginManager(load=True) hook = _pluginmanager.hook + if plugins: + for plugin in plugins: + _pluginmanager.register(plugin) + return hook.pytest_cmdline_parse( + pluginmanager=_pluginmanager, args=args) + +def main(args=None, plugins=None): + """ returned exit code integer, after an in-process testing run + with the given command line arguments, preloading an optional list + of passed in plugin objects. """ try: - if plugins: - for plugin in plugins: - _pluginmanager.register(plugin) - config = hook.pytest_cmdline_parse( - pluginmanager=_pluginmanager, args=args) - exitstatus = hook.pytest_cmdline_main(config=config) + config = _prepareconfig(args, plugins) + exitstatus = config.hook.pytest_cmdline_main(config=config) except UsageError: e = sys.exc_info()[1] sys.stderr.write("ERROR: %s\n" %(e.args[0],)) diff --git a/_pytest/helpconfig.py b/_pytest/helpconfig.py --- a/_pytest/helpconfig.py +++ b/_pytest/helpconfig.py @@ -1,7 +1,7 @@ """ version info, help messages, tracing configuration. """ import py import pytest -import inspect, sys +import os, inspect, sys from _pytest.core import varnames def pytest_addoption(parser): @@ -18,7 +18,29 @@ help="trace considerations of conftest.py files."), group.addoption('--debug', action="store_true", dest="debug", default=False, - help="generate and show internal debugging information.") + help="store internal tracing debug information in 'pytestdebug.log'.") + + +def pytest_cmdline_parse(__multicall__): + config = __multicall__.execute() + if config.option.debug: + path = os.path.abspath("pytestdebug.log") + f = open(path, 'w') + config._debugfile = f + f.write("versions pytest-%s, py-%s, python-%s\ncwd=%s\nargs=%s\n\n" %( + pytest.__version__, py.__version__, ".".join(map(str, sys.version_info)), + os.getcwd(), config._origargs)) + config.trace.root.setwriter(f.write) + sys.stderr.write("writing pytestdebug information to %s\n" % path) + return config + + at pytest.mark.trylast +def pytest_unconfigure(config): + if hasattr(config, '_debugfile'): + config._debugfile.close() + sys.stderr.write("wrote pytestdebug information to %s\n" % + config._debugfile.name) + config.trace.root.setwriter(None) def pytest_cmdline_main(config): @@ -34,6 +56,7 @@ elif config.option.help: config.pluginmanager.do_configure(config) showhelp(config) + config.pluginmanager.do_unconfigure(config) return 0 def showhelp(config): @@ -91,7 +114,7 @@ verinfo = getpluginversioninfo(config) if verinfo: lines.extend(verinfo) - + if config.option.traceconfig: lines.append("active plugins:") plugins = [] diff --git a/_pytest/hookspec.py b/_pytest/hookspec.py --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -121,16 +121,23 @@ def pytest_itemstart(item, node=None): """ (deprecated, use pytest_runtest_logstart). """ -def pytest_runtest_protocol(item): - """ implements the standard runtest_setup/call/teardown protocol including - capturing exceptions and calling reporting hooks on the results accordingly. +def pytest_runtest_protocol(item, nextitem): + """ implements the runtest_setup/call/teardown protocol for + the given test item, including capturing exceptions and calling + reporting hooks. + + :arg item: test item for which the runtest protocol is performed. + + :arg nexitem: the scheduled-to-be-next test item (or None if this + is the end my friend). This argument is passed on to + :py:func:`pytest_runtest_teardown`. :return boolean: True if no further hook implementations should be invoked. """ pytest_runtest_protocol.firstresult = True def pytest_runtest_logstart(nodeid, location): - """ signal the start of a test run. """ + """ signal the start of running a single test item. """ def pytest_runtest_setup(item): """ called before ``pytest_runtest_call(item)``. """ @@ -138,8 +145,14 @@ def pytest_runtest_call(item): """ called to execute the test ``item``. """ -def pytest_runtest_teardown(item): - """ called after ``pytest_runtest_call``. """ +def pytest_runtest_teardown(item, nextitem): + """ called after ``pytest_runtest_call``. + + :arg nexitem: the scheduled-to-be-next test item (None if no further + test item is scheduled). This argument can be used to + perform exact teardowns, i.e. calling just enough finalizers + so that nextitem only needs to call setup-functions. + """ def pytest_runtest_makereport(item, call): """ return a :py:class:`_pytest.runner.TestReport` object @@ -149,15 +162,8 @@ pytest_runtest_makereport.firstresult = True def pytest_runtest_logreport(report): - """ process item test report. """ - -# special handling for final teardown - somewhat internal for now -def pytest__teardown_final(session): - """ called before test session finishes. """ -pytest__teardown_final.firstresult = True - -def pytest__teardown_final_logerror(report, session): - """ called if runtest_teardown_final failed. """ + """ process a test setup/call/teardown report relating to + the respective phase of executing a test. """ # ------------------------------------------------------------------------- # test session related hooks diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -25,21 +25,39 @@ long = int +class Junit(py.xml.Namespace): + pass + + # We need to get the subset of the invalid unicode ranges according to # XML 1.0 which are valid in this python build. Hence we calculate # this dynamically instead of hardcoding it. The spec range of valid # chars is: Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] # | [#x10000-#x10FFFF] -_illegal_unichrs = [(0x00, 0x08), (0x0B, 0x0C), (0x0E, 0x19), - (0xD800, 0xDFFF), (0xFDD0, 0xFFFF)] -_illegal_ranges = [unicode("%s-%s") % (unichr(low), unichr(high)) - for (low, high) in _illegal_unichrs +_legal_chars = (0x09, 0x0A, 0x0d) +_legal_ranges = ( + (0x20, 0xD7FF), + (0xE000, 0xFFFD), + (0x10000, 0x10FFFF), +) +_legal_xml_re = [unicode("%s-%s") % (unichr(low), unichr(high)) + for (low, high) in _legal_ranges if low < sys.maxunicode] -illegal_xml_re = re.compile(unicode('[%s]') % - unicode('').join(_illegal_ranges)) -del _illegal_unichrs -del _illegal_ranges +_legal_xml_re = [unichr(x) for x in _legal_chars] + _legal_xml_re +illegal_xml_re = re.compile(unicode('[^%s]') % + unicode('').join(_legal_xml_re)) +del _legal_chars +del _legal_ranges +del _legal_xml_re +def bin_xml_escape(arg): + def repl(matchobj): + i = ord(matchobj.group()) + if i <= 0xFF: + return unicode('#x%02X') % i + else: + return unicode('#x%04X') % i + return illegal_xml_re.sub(repl, py.xml.escape(arg)) def pytest_addoption(parser): group = parser.getgroup("terminal reporting") @@ -68,117 +86,97 @@ logfile = os.path.expanduser(os.path.expandvars(logfile)) self.logfile = os.path.normpath(logfile) self.prefix = prefix - self.test_logs = [] + self.tests = [] self.passed = self.skipped = 0 self.failed = self.errors = 0 - self._durations = {} def _opentestcase(self, report): names = report.nodeid.split("::") names[0] = names[0].replace("/", '.') - names = tuple(names) - d = {'time': self._durations.pop(report.nodeid, "0")} names = [x.replace(".py", "") for x in names if x != "()"] classnames = names[:-1] if self.prefix: classnames.insert(0, self.prefix) - d['classname'] = ".".join(classnames) - d['name'] = py.xml.escape(names[-1]) - attrs = ['%s="%s"' % item for item in sorted(d.items())] - self.test_logs.append("\n" % " ".join(attrs)) + self.tests.append(Junit.testcase( + classname=".".join(classnames), + name=names[-1], + time=getattr(report, 'duration', 0) + )) - def _closetestcase(self): - self.test_logs.append("") - - def appendlog(self, fmt, *args): - def repl(matchobj): - i = ord(matchobj.group()) - if i <= 0xFF: - return unicode('#x%02X') % i - else: - return unicode('#x%04X') % i - args = tuple([illegal_xml_re.sub(repl, py.xml.escape(arg)) - for arg in args]) - self.test_logs.append(fmt % args) + def append(self, obj): + self.tests[-1].append(obj) def append_pass(self, report): self.passed += 1 - self._opentestcase(report) - self._closetestcase() def append_failure(self, report): - self._opentestcase(report) #msg = str(report.longrepr.reprtraceback.extraline) if "xfail" in report.keywords: - self.appendlog( - '') + self.append( + Junit.skipped(message="xfail-marked test passes unexpectedly")) self.skipped += 1 else: - self.appendlog('%s', - report.longrepr) + sec = dict(report.sections) + fail = Junit.failure(message="test failure") + fail.append(str(report.longrepr)) + self.append(fail) + for name in ('out', 'err'): + content = sec.get("Captured std%s" % name) + if content: + tag = getattr(Junit, 'system-'+name) + self.append(tag(bin_xml_escape(content))) self.failed += 1 - self._closetestcase() def append_collect_failure(self, report): - self._opentestcase(report) #msg = str(report.longrepr.reprtraceback.extraline) - self.appendlog('%s', - report.longrepr) - self._closetestcase() + self.append(Junit.failure(str(report.longrepr), + message="collection failure")) self.errors += 1 def append_collect_skipped(self, report): - self._opentestcase(report) #msg = str(report.longrepr.reprtraceback.extraline) - self.appendlog('%s', - report.longrepr) - self._closetestcase() + self.append(Junit.skipped(str(report.longrepr), + message="collection skipped")) self.skipped += 1 def append_error(self, report): - self._opentestcase(report) - self.appendlog('%s', - report.longrepr) - self._closetestcase() + self.append(Junit.error(str(report.longrepr), + message="test setup failure")) self.errors += 1 def append_skipped(self, report): - self._opentestcase(report) if "xfail" in report.keywords: - self.appendlog( - '%s', - report.keywords['xfail']) + self.append(Junit.skipped(str(report.keywords['xfail']), + message="expected test failure")) else: filename, lineno, skipreason = report.longrepr if skipreason.startswith("Skipped: "): skipreason = skipreason[9:] - self.appendlog('%s', - skipreason, "%s:%s: %s" % report.longrepr, - ) - self._closetestcase() + self.append( + Junit.skipped("%s:%s: %s" % report.longrepr, + type="pytest.skip", + message=skipreason + )) self.skipped += 1 def pytest_runtest_logreport(self, report): if report.passed: - self.append_pass(report) + if report.when == "call": # ignore setup/teardown + self._opentestcase(report) + self.append_pass(report) elif report.failed: + self._opentestcase(report) if report.when != "call": self.append_error(report) else: self.append_failure(report) elif report.skipped: + self._opentestcase(report) self.append_skipped(report) - def pytest_runtest_call(self, item, __multicall__): - start = time.time() - try: - return __multicall__.execute() - finally: - self._durations[item.nodeid] = time.time() - start - def pytest_collectreport(self, report): if not report.passed: + self._opentestcase(report) if report.failed: self.append_collect_failure(report) else: @@ -187,10 +185,11 @@ def pytest_internalerror(self, excrepr): self.errors += 1 data = py.xml.escape(excrepr) - self.test_logs.append( - '\n' - ' ' - '%s' % data) + self.tests.append( + Junit.testcase( + Junit.error(data, message="internal error"), + classname="pytest", + name="internal")) def pytest_sessionstart(self, session): self.suite_start_time = time.time() @@ -204,17 +203,17 @@ suite_stop_time = time.time() suite_time_delta = suite_stop_time - self.suite_start_time numtests = self.passed + self.failed + logfile.write('') - logfile.write('') - logfile.writelines(self.test_logs) - logfile.write('') + logfile.write(Junit.testsuite( + self.tests, + name="", + errors=self.errors, + failures=self.failed, + skips=self.skipped, + tests=numtests, + time="%.3f" % suite_time_delta, + ).unicode(indent=0)) logfile.close() def pytest_terminal_summary(self, terminalreporter): diff --git a/_pytest/main.py b/_pytest/main.py --- a/_pytest/main.py +++ b/_pytest/main.py @@ -2,7 +2,7 @@ import py import pytest, _pytest -import os, sys +import os, sys, imp tracebackcutdir = py.path.local(_pytest.__file__).dirpath() # exitcodes for the command line @@ -11,6 +11,8 @@ EXIT_INTERRUPTED = 2 EXIT_INTERNALERROR = 3 +name_re = py.std.re.compile("^[a-zA-Z_]\w*$") + def pytest_addoption(parser): parser.addini("norecursedirs", "directory patterns to avoid for recursion", type="args", default=('.*', 'CVS', '_darcs', '{arch}')) @@ -27,6 +29,9 @@ action="store", type="int", dest="maxfail", default=0, help="exit after first num failures or errors.") + group._addoption('--strict', action="store_true", + help="run pytest in strict mode, warnings become errors.") + group = parser.getgroup("collect", "collection") group.addoption('--collectonly', action="store_true", dest="collectonly", @@ -48,7 +53,7 @@ def pytest_namespace(): collect = dict(Item=Item, Collector=Collector, File=File, Session=Session) return dict(collect=collect) - + def pytest_configure(config): py.test.config = config # compatibiltiy if config.option.exitfirst: @@ -77,11 +82,11 @@ session.exitstatus = EXIT_INTERNALERROR if excinfo.errisinstance(SystemExit): sys.stderr.write("mainloop: caught Spurious SystemExit!\n") + if initstate >= 2: + config.hook.pytest_sessionfinish(session=session, + exitstatus=session.exitstatus or (session._testsfailed and 1)) if not session.exitstatus and session._testsfailed: session.exitstatus = EXIT_TESTSFAILED - if initstate >= 2: - config.hook.pytest_sessionfinish(session=session, - exitstatus=session.exitstatus) if initstate >= 1: config.pluginmanager.do_unconfigure(config) return session.exitstatus @@ -101,8 +106,12 @@ def pytest_runtestloop(session): if session.config.option.collectonly: return True - for item in session.session.items: - item.config.hook.pytest_runtest_protocol(item=item) + for i, item in enumerate(session.items): + try: + nextitem = session.items[i+1] + except IndexError: + nextitem = None + item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem) if session.shouldstop: raise session.Interrupted(session.shouldstop) return True @@ -132,7 +141,7 @@ return getattr(pytest, name) return property(fget, None, None, "deprecated attribute %r, use pytest.%s" % (name,name)) - + class Node(object): """ base class for all Nodes in the collection tree. Collector subclasses have children, Items are terminal nodes.""" @@ -143,13 +152,13 @@ #: the parent collector node. self.parent = parent - + #: the test config object self.config = config or parent.config #: the collection this node is part of self.session = session or parent.session - + #: filesystem path where this node was collected from self.fspath = getattr(parent, 'fspath', None) self.ihook = self.session.gethookproxy(self.fspath) @@ -224,13 +233,13 @@ def listchain(self): """ return list of all parent collectors up to self, starting from root of collection tree. """ - l = [self] - while 1: - x = l[0] - if x.parent is not None: # and x.parent.parent is not None: - l.insert(0, x.parent) - else: - return l + chain = [] + item = self + while item is not None: + chain.append(item) + item = item.parent + chain.reverse() + return chain def listnames(self): return [x.name for x in self.listchain()] @@ -325,6 +334,8 @@ """ a basic test invocation item. Note that for a single function there might be multiple test invocation items. """ + nextitem = None + def reportinfo(self): return self.fspath, None, "" @@ -399,6 +410,7 @@ self._notfound = [] self._initialpaths = set() self._initialparts = [] + self.items = items = [] for arg in args: parts = self._parsearg(arg) self._initialparts.append(parts) @@ -414,7 +426,6 @@ if not genitems: return rep.result else: - self.items = items = [] if rep.passed: for node in rep.result: self.items.extend(self.genitems(node)) @@ -469,16 +480,29 @@ return True def _tryconvertpyarg(self, x): - try: - mod = __import__(x, None, None, ['__doc__']) - except (ValueError, ImportError): - return x - p = py.path.local(mod.__file__) - if p.purebasename == "__init__": - p = p.dirpath() - else: - p = p.new(basename=p.purebasename+".py") - return str(p) + mod = None + path = [os.path.abspath('.')] + sys.path + for name in x.split('.'): + # ignore anything that's not a proper name here + # else something like --pyargs will mess up '.' + # since imp.find_module will actually sometimes work for it + # but it's supposed to be considered a filesystem path + # not a package + if name_re.match(name) is None: + return x + try: + fd, mod, type_ = imp.find_module(name, path) + except ImportError: + return x + else: + if fd is not None: + fd.close() + + if type_[2] != imp.PKG_DIRECTORY: + path = [os.path.dirname(mod)] + else: + path = [mod] + return mod def _parsearg(self, arg): """ return (fspath, names) tuple after checking the file exists. """ @@ -496,7 +520,7 @@ raise pytest.UsageError(msg + arg) parts[0] = path return parts - + def matchnodes(self, matching, names): self.trace("matchnodes", matching, names) self.trace.root.indent += 1 diff --git a/_pytest/mark.py b/_pytest/mark.py --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -14,12 +14,37 @@ "Terminate expression with ':' to make the first match match " "all subsequent tests (usually file-order). ") + group._addoption("-m", + action="store", dest="markexpr", default="", metavar="MARKEXPR", + help="only run tests matching given mark expression. " + "example: -m 'mark1 and not mark2'." + ) + + group.addoption("--markers", action="store_true", help= + "show markers (builtin, plugin and per-project ones).") + + parser.addini("markers", "markers for test functions", 'linelist') + +def pytest_cmdline_main(config): + if config.option.markers: + config.pluginmanager.do_configure(config) + tw = py.io.TerminalWriter() + for line in config.getini("markers"): + name, rest = line.split(":", 1) + tw.write("@pytest.mark.%s:" % name, bold=True) + tw.line(rest) + tw.line() + config.pluginmanager.do_unconfigure(config) + return 0 +pytest_cmdline_main.tryfirst = True + def pytest_collection_modifyitems(items, config): keywordexpr = config.option.keyword - if not keywordexpr: + matchexpr = config.option.markexpr + if not keywordexpr and not matchexpr: return selectuntil = False - if keywordexpr[-1] == ":": + if keywordexpr[-1:] == ":": selectuntil = True keywordexpr = keywordexpr[:-1] @@ -29,21 +54,38 @@ if keywordexpr and skipbykeyword(colitem, keywordexpr): deselected.append(colitem) else: - remaining.append(colitem) if selectuntil: keywordexpr = None + if matchexpr: + if not matchmark(colitem, matchexpr): + deselected.append(colitem) + continue + remaining.append(colitem) if deselected: config.hook.pytest_deselected(items=deselected) items[:] = remaining +class BoolDict: + def __init__(self, mydict): + self._mydict = mydict + def __getitem__(self, name): + return name in self._mydict + +def matchmark(colitem, matchexpr): + return eval(matchexpr, {}, BoolDict(colitem.obj.__dict__)) + +def pytest_configure(config): + if config.option.strict: + pytest.mark._config = config + def skipbykeyword(colitem, keywordexpr): """ return True if they given keyword expression means to skip this collector/item. """ if not keywordexpr: return - + itemkeywords = getkeywords(colitem) for key in filter(None, keywordexpr.split()): eor = key[:1] == '-' @@ -77,15 +119,31 @@ @py.test.mark.slowtest def test_function(): pass - + will set a 'slowtest' :class:`MarkInfo` object on the ``test_function`` object. """ def __getattr__(self, name): if name[0] == "_": raise AttributeError(name) + if hasattr(self, '_config'): + self._check(name) return MarkDecorator(name) + def _check(self, name): + try: + if name in self._markers: + return + except AttributeError: + pass + self._markers = l = set() + for line in self._config.getini("markers"): + beginning = line.split(":", 1) + x = beginning[0].split("(", 1)[0] + l.add(x) + if name not in self._markers: + raise AttributeError("%r not a registered marker" % (name,)) + class MarkDecorator: """ A decorator for test functions and test classes. When applied it will create :class:`MarkInfo` objects which may be @@ -133,8 +191,7 @@ holder = MarkInfo(self.markname, self.args, self.kwargs) setattr(func, self.markname, holder) else: - holder.kwargs.update(self.kwargs) - holder.args += self.args + holder.add(self.args, self.kwargs) return func kw = self.kwargs.copy() kw.update(kwargs) @@ -150,27 +207,20 @@ self.args = args #: keyword argument dictionary, empty if nothing specified self.kwargs = kwargs + self._arglist = [(args, kwargs.copy())] def __repr__(self): return "" % ( self.name, self.args, self.kwargs) -def pytest_itemcollected(item): - if not isinstance(item, pytest.Function): - return - try: - func = item.obj.__func__ - except AttributeError: - func = getattr(item.obj, 'im_func', item.obj) - pyclasses = (pytest.Class, pytest.Module) - for node in item.listchain(): - if isinstance(node, pyclasses): - marker = getattr(node.obj, 'pytestmark', None) - if marker is not None: - if isinstance(marker, list): - for mark in marker: - mark(func) - else: - marker(func) - node = node.parent - item.keywords.update(py.builtin._getfuncdict(func)) + def add(self, args, kwargs): + """ add a MarkInfo with the given args and kwargs. """ + self._arglist.append((args, kwargs)) + self.args += args + self.kwargs.update(kwargs) + + def __iter__(self): + """ yield MarkInfo objects each relating to a marking-call. """ + for args, kwargs in self._arglist: + yield MarkInfo(self.name, args, kwargs) + diff --git a/_pytest/monkeypatch.py b/_pytest/monkeypatch.py --- a/_pytest/monkeypatch.py +++ b/_pytest/monkeypatch.py @@ -13,6 +13,7 @@ monkeypatch.setenv(name, value, prepend=False) monkeypatch.delenv(name, value, raising=True) monkeypatch.syspath_prepend(path) + monkeypatch.chdir(path) All modifications will be undone after the requesting test function has finished. The ``raising`` @@ -30,6 +31,7 @@ def __init__(self): self._setattr = [] self._setitem = [] + self._cwd = None def setattr(self, obj, name, value, raising=True): """ set attribute ``name`` on ``obj`` to ``value``, by default @@ -83,6 +85,17 @@ self._savesyspath = sys.path[:] sys.path.insert(0, str(path)) + def chdir(self, path): + """ change the current working directory to the specified path + path can be a string or a py.path.local object + """ + if self._cwd is None: + self._cwd = os.getcwd() + if hasattr(path, "chdir"): + path.chdir() + else: + os.chdir(path) + def undo(self): """ undo previous changes. This call consumes the undo stack. Calling it a second time has no effect unless @@ -95,9 +108,17 @@ self._setattr[:] = [] for dictionary, name, value in self._setitem: if value is notset: - del dictionary[name] + try: + del dictionary[name] + except KeyError: + pass # was already deleted, so we have the desired state else: dictionary[name] = value self._setitem[:] = [] if hasattr(self, '_savesyspath'): sys.path[:] = self._savesyspath + del self._savesyspath + + if self._cwd is not None: + os.chdir(self._cwd) + self._cwd = None diff --git a/_pytest/nose.py b/_pytest/nose.py --- a/_pytest/nose.py +++ b/_pytest/nose.py @@ -13,6 +13,7 @@ call.excinfo = call2.excinfo + at pytest.mark.trylast def pytest_runtest_setup(item): if isinstance(item, (pytest.Function)): if isinstance(item.parent, pytest.Generator): diff --git a/_pytest/pastebin.py b/_pytest/pastebin.py --- a/_pytest/pastebin.py +++ b/_pytest/pastebin.py @@ -38,7 +38,11 @@ del tr._tw.__dict__['write'] def getproxy(): - return py.std.xmlrpclib.ServerProxy(url.xmlrpc).pastes + if sys.version_info < (3, 0): + from xmlrpclib import ServerProxy + else: + from xmlrpc.client import ServerProxy + return ServerProxy(url.xmlrpc).pastes def pytest_terminal_summary(terminalreporter): if terminalreporter.config.option.pastebin != "failed": diff --git a/_pytest/pdb.py b/_pytest/pdb.py --- a/_pytest/pdb.py +++ b/_pytest/pdb.py @@ -19,11 +19,13 @@ class pytestPDB: """ Pseudo PDB that defers to the real pdb. """ item = None + collector = None def set_trace(self): """ invoke PDB set_trace debugging, dropping any IO capturing. """ frame = sys._getframe().f_back - item = getattr(self, 'item', None) + item = self.item or self.collector + if item is not None: capman = item.config.pluginmanager.getplugin("capturemanager") out, err = capman.suspendcapture() @@ -38,6 +40,14 @@ pytestPDB.item = item pytest_runtest_setup = pytest_runtest_call = pytest_runtest_teardown = pdbitem + at pytest.mark.tryfirst +def pytest_make_collect_report(__multicall__, collector): + try: + pytestPDB.collector = collector + return __multicall__.execute() + finally: + pytestPDB.collector = None + def pytest_runtest_makereport(): pytestPDB.item = None @@ -60,7 +70,13 @@ tw.sep(">", "traceback") rep.toterminal(tw) tw.sep(">", "entering PDB") - post_mortem(call.excinfo._excinfo[2]) + # A doctest.UnexpectedException is not useful for post_mortem. + # Use the underlying exception instead: + if isinstance(call.excinfo.value, py.std.doctest.UnexpectedException): + tb = call.excinfo.value.exc_info[2] + else: + tb = call.excinfo._excinfo[2] + post_mortem(tb) rep._pdbshown = True return rep diff --git a/_pytest/pytester.py b/_pytest/pytester.py --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -25,6 +25,7 @@ _pytest_fullpath except NameError: _pytest_fullpath = os.path.abspath(pytest.__file__.rstrip("oc")) + _pytest_fullpath = _pytest_fullpath.replace("$py.class", ".py") def pytest_funcarg___pytest(request): return PytestArg(request) @@ -313,16 +314,6 @@ result.extend(session.genitems(colitem)) return result - def inline_genitems(self, *args): - #config = self.parseconfig(*args) - config = self.parseconfigure(*args) - rec = self.getreportrecorder(config) - session = Session(config) - config.hook.pytest_sessionstart(session=session) - session.perform_collect() - config.hook.pytest_sessionfinish(session=session, exitstatus=EXIT_OK) - return session.items, rec - def runitem(self, source): # used from runner functional tests item = self.getitem(source) @@ -343,64 +334,57 @@ l = list(args) + [p] reprec = self.inline_run(*l) reports = reprec.getreports("pytest_runtest_logreport") - assert len(reports) == 1, reports - return reports[0] + assert len(reports) == 3, reports # setup/call/teardown + return reports[1] + + def inline_genitems(self, *args): + return self.inprocess_run(list(args) + ['--collectonly']) def inline_run(self, *args): - args = ("-s", ) + args # otherwise FD leakage - config = self.parseconfig(*args) - reprec = self.getreportrecorder(config) - #config.pluginmanager.do_configure(config) - config.hook.pytest_cmdline_main(config=config) - #config.pluginmanager.do_unconfigure(config) - return reprec + items, rec = self.inprocess_run(args) + return rec - def config_preparse(self): - config = self.Config() - for plugin in self.plugins: - if isinstance(plugin, str): - config.pluginmanager.import_plugin(plugin) - else: - if isinstance(plugin, dict): - plugin = PseudoPlugin(plugin) - if not config.pluginmanager.isregistered(plugin): - config.pluginmanager.register(plugin) - return config + def inprocess_run(self, args, plugins=None): + rec = [] + items = [] + class Collect: + def pytest_configure(x, config): + rec.append(self.getreportrecorder(config)) + def pytest_itemcollected(self, item): + items.append(item) + if not plugins: + plugins = [] + plugins.append(Collect()) + ret = self.pytestmain(list(args), plugins=[Collect()]) + reprec = rec[0] + reprec.ret = ret + assert len(rec) == 1 + return items, reprec def parseconfig(self, *args): - if not args: - args = (self.tmpdir,) - config = self.config_preparse() - args = list(args) + args = [str(x) for x in args] for x in args: if str(x).startswith('--basetemp'): break else: args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp')) - config.parse(args) + import _pytest.core + config = _pytest.core._prepareconfig(args, self.plugins) + # the in-process pytest invocation needs to avoid leaking FDs + # so we register a "reset_capturings" callmon the capturing manager + # and make sure it gets called + config._cleanup.append( + config.pluginmanager.getplugin("capturemanager").reset_capturings) + import _pytest.config + self.request.addfinalizer( + lambda: _pytest.config.pytest_unconfigure(config)) return config - def reparseconfig(self, args=None): - """ this is used from tests that want to re-invoke parse(). """ - if not args: - args = [self.tmpdir] - oldconfig = getattr(py.test, 'config', None) - try: - c = py.test.config = self.Config() - c.basetemp = py.path.local.make_numbered_dir(prefix="reparse", - keep=0, rootdir=self.tmpdir, lock_timeout=None) - c.parse(args) - c.pluginmanager.do_configure(c) - self.request.addfinalizer(lambda: c.pluginmanager.do_unconfigure(c)) - return c - finally: - py.test.config = oldconfig - def parseconfigure(self, *args): config = self.parseconfig(*args) config.pluginmanager.do_configure(config) self.request.addfinalizer(lambda: - config.pluginmanager.do_unconfigure(config)) + config.pluginmanager.do_unconfigure(config)) return config def getitem(self, source, funcname="test_func"): @@ -420,7 +404,6 @@ self.makepyfile(__init__ = "#") self.config = config = self.parseconfigure(path, *configargs) node = self.getnode(config, path) - #config.pluginmanager.do_unconfigure(config) return node def collect_by_name(self, modcol, name): @@ -437,9 +420,16 @@ return py.std.subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw) def pytestmain(self, *args, **kwargs): - ret = pytest.main(*args, **kwargs) - if ret == 2: - raise KeyboardInterrupt() + class ResetCapturing: + @pytest.mark.trylast + def pytest_unconfigure(self, config): + capman = config.pluginmanager.getplugin("capturemanager") + capman.reset_capturings() + plugins = kwargs.setdefault("plugins", []) + rc = ResetCapturing() + plugins.append(rc) + return pytest.main(*args, **kwargs) + def run(self, *cmdargs): return self._run(*cmdargs) @@ -528,6 +518,8 @@ pexpect = py.test.importorskip("pexpect", "2.4") if hasattr(sys, 'pypy_version_info') and '64' in py.std.platform.machine(): pytest.skip("pypy-64 bit not supported") + if sys.platform == "darwin": + pytest.xfail("pexpect does not work reliably on darwin?!") logfile = self.tmpdir.join("spawn.out") child = pexpect.spawn(cmd, logfile=logfile.open("w")) child.timeout = expect_timeout @@ -540,10 +532,6 @@ return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % ( py.io.saferepr(out),) -class PseudoPlugin: - def __init__(self, vars): - self.__dict__.update(vars) - class ReportRecorder(object): def __init__(self, hook): self.hook = hook @@ -565,10 +553,17 @@ def getreports(self, names="pytest_runtest_logreport pytest_collectreport"): return [x.report for x in self.getcalls(names)] - def matchreport(self, inamepart="", names="pytest_runtest_logreport pytest_collectreport", when=None): + def matchreport(self, inamepart="", + names="pytest_runtest_logreport pytest_collectreport", when=None): """ return a testreport whose dotted import path matches """ l = [] for rep in self.getreports(names=names): + try: + if not when and rep.when != "call" and rep.passed: + # setup/teardown passing reports - let's ignore those + continue + except AttributeError: + pass if when and getattr(rep, 'when', None) != when: continue if not inamepart or inamepart in rep.nodeid.split("::"): diff --git a/_pytest/python.py b/_pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -4,6 +4,7 @@ import sys import pytest from py._code.code import TerminalRepr +from _pytest.monkeypatch import monkeypatch import _pytest cutdir = py.path.local(_pytest.__file__).dirpath() @@ -26,6 +27,24 @@ showfuncargs(config) return 0 + +def pytest_generate_tests(metafunc): + try: + param = metafunc.function.parametrize + except AttributeError: + return + for p in param: + metafunc.parametrize(*p.args, **p.kwargs) + +def pytest_configure(config): + config.addinivalue_line("markers", + "parametrize(argnames, argvalues): call a test function multiple " + "times passing in multiple different argument value sets. Example: " + "@parametrize('arg1', [1,2]) would lead to two calls of the decorated " + "test function, one with arg1=1 and another with arg1=2." + ) + + @pytest.mark.trylast def pytest_namespace(): raises.Exception = pytest.fail.Exception @@ -138,6 +157,7 @@ obj = obj.place_as self._fslineno = py.code.getfslineno(obj) + assert isinstance(self._fslineno[1], int), obj return self._fslineno def reportinfo(self): @@ -155,6 +175,7 @@ else: fspath, lineno = self._getfslineno() modpath = self.getmodpath() + assert isinstance(lineno, int) return fspath, lineno, modpath class PyCollectorMixin(PyobjMixin, pytest.Collector): @@ -200,6 +221,7 @@ module = self.getparent(Module).obj clscol = self.getparent(Class) cls = clscol and clscol.obj or None + transfer_markers(funcobj, cls, module) metafunc = Metafunc(funcobj, config=self.config, cls=cls, module=module) gentesthook = self.config.hook.pytest_generate_tests @@ -219,6 +241,19 @@ l.append(function) return l +def transfer_markers(funcobj, cls, mod): + # XXX this should rather be code in the mark plugin or the mark + # plugin should merge with the python plugin. + for holder in (cls, mod): + try: + pytestmark = holder.pytestmark + except AttributeError: + continue + if isinstance(pytestmark, list): + for mark in pytestmark: + mark(funcobj) + else: + pytestmark(funcobj) class Module(pytest.File, PyCollectorMixin): def _getobj(self): @@ -226,13 +261,8 @@ def _importtestmodule(self): # we assume we are only called once per module - from _pytest import assertion - assertion.before_module_import(self) try: - try: - mod = self.fspath.pyimport(ensuresyspath=True) - finally: - assertion.after_module_import(self) + mod = self.fspath.pyimport(ensuresyspath=True) except SyntaxError: excinfo = py.code.ExceptionInfo() raise self.CollectError(excinfo.getrepr(style="short")) @@ -244,7 +274,8 @@ " %s\n" "which is not the same as the test file we want to collect:\n" " %s\n" - "HINT: use a unique basename for your test file modules" + "HINT: remove __pycache__ / .pyc files and/or use a " + "unique basename for your test file modules" % e.args ) #print "imported test module", mod @@ -374,6 +405,7 @@ tw.line() tw.line("%s:%d" % (self.filename, self.firstlineno+1)) + class Generator(FunctionMixin, PyCollectorMixin, pytest.Collector): def collect(self): # test generators are seen as collectors but they also @@ -430,6 +462,7 @@ "yielded functions (deprecated) cannot have funcargs") else: if callspec is not None: + self.callspec = callspec self.funcargs = callspec.funcargs or {} self._genid = callspec.id if hasattr(callspec, "param"): @@ -506,15 +539,59 @@ request._fillfuncargs() _notexists = object() -class CallSpec: - def __init__(self, funcargs, id, param): - self.funcargs = funcargs - self.id = id + +class CallSpec2(object): + def __init__(self, metafunc): + self.metafunc = metafunc + self.funcargs = {} + self._idlist = [] + self.params = {} + self._globalid = _notexists + self._globalid_args = set() + self._globalparam = _notexists + + def copy(self, metafunc): + cs = CallSpec2(self.metafunc) + cs.funcargs.update(self.funcargs) + cs.params.update(self.params) + cs._idlist = list(self._idlist) + cs._globalid = self._globalid + cs._globalid_args = self._globalid_args + cs._globalparam = self._globalparam + return cs + + def _checkargnotcontained(self, arg): + if arg in self.params or arg in self.funcargs: + raise ValueError("duplicate %r" %(arg,)) + + def getparam(self, name): + try: + return self.params[name] + except KeyError: + if self._globalparam is _notexists: + raise ValueError(name) + return self._globalparam + + @property + def id(self): + return "-".join(map(str, filter(None, self._idlist))) + + def setmulti(self, valtype, argnames, valset, id): + for arg,val in zip(argnames, valset): + self._checkargnotcontained(arg) + getattr(self, valtype)[arg] = val + self._idlist.append(id) + + def setall(self, funcargs, id, param): + for x in funcargs: + self._checkargnotcontained(x) + self.funcargs.update(funcargs) + if id is not _notexists: + self._idlist.append(id) if param is not _notexists: - self.param = param - def __repr__(self): - return "" %( - self.id, getattr(self, 'param', '?'), self.funcargs) + assert self._globalparam is _notexists + self._globalparam = param + class Metafunc: def __init__(self, function, config=None, cls=None, module=None): @@ -528,31 +605,71 @@ self._calls = [] self._ids = py.builtin.set() + def parametrize(self, argnames, argvalues, indirect=False, ids=None): + """ Add new invocations to the underlying test function using the list + of argvalues for the given argnames. Parametrization is performed + during the collection phase. If you need to setup expensive resources + you may pass indirect=True and implement a funcarg factory which can + perform the expensive setup just before a test is actually run. + + :arg argnames: an argument name or a list of argument names + + :arg argvalues: a list of values for the argname or a list of tuples of + values for the list of argument names. + + :arg indirect: if True each argvalue corresponding to an argument will + be passed as request.param to its respective funcarg factory so + that it can perform more expensive setups during the setup phase of + a test rather than at collection time. + + :arg ids: list of string ids each corresponding to the argvalues so + that they are part of the test id. If no ids are provided they will + be generated automatically from the argvalues. + """ + if not isinstance(argnames, (tuple, list)): + argnames = (argnames,) + argvalues = [(val,) for val in argvalues] + if not indirect: + #XXX should we also check for the opposite case? + for arg in argnames: + if arg not in self.funcargnames: + raise ValueError("%r has no argument %r" %(self.function, arg)) + valtype = indirect and "params" or "funcargs" + if not ids: + idmaker = IDMaker() + ids = list(map(idmaker, argvalues)) + newcalls = [] + for callspec in self._calls or [CallSpec2(self)]: + for i, valset in enumerate(argvalues): + assert len(valset) == len(argnames) + newcallspec = callspec.copy(self) + newcallspec.setmulti(valtype, argnames, valset, ids[i]) + newcalls.append(newcallspec) + self._calls = newcalls + def addcall(self, funcargs=None, id=_notexists, param=_notexists): - """ add a new call to the underlying test function during the - collection phase of a test run. Note that request.addcall() is - called during the test collection phase prior and independently - to actual test execution. Therefore you should perform setup - of resources in a funcarg factory which can be instrumented - with the ``param``. + """ (deprecated, use parametrize) Add a new call to the underlying + test function during the collection phase of a test run. Note that + request.addcall() is called during the test collection phase prior and + independently to actual test execution. You should only use addcall() + if you need to specify multiple arguments of a test function. :arg funcargs: argument keyword dictionary used when invoking the test function. :arg id: used for reporting and identification purposes. If you - don't supply an `id` the length of the currently - list of calls to the test function will be used. + don't supply an `id` an automatic unique id will be generated. - :arg param: will be exposed to a later funcarg factory invocation - through the ``request.param`` attribute. It allows to - defer test fixture setup activities to when an actual - test is run. + :arg param: a parameter which will be exposed to a later funcarg factory + invocation through the ``request.param`` attribute. """ assert funcargs is None or isinstance(funcargs, dict) if funcargs is not None: for name in funcargs: if name not in self.funcargnames: pytest.fail("funcarg %r not used in this function." % name) + else: + funcargs = {} if id is None: raise ValueError("id=None not allowed") if id is _notexists: @@ -561,11 +678,26 @@ if id in self._ids: raise ValueError("duplicate id %r" % id) self._ids.add(id) - self._calls.append(CallSpec(funcargs, id, param)) + + cs = CallSpec2(self) + cs.setall(funcargs, id, param) + self._calls.append(cs) + +class IDMaker: + def __init__(self): + self.counter = 0 + def __call__(self, valset): + l = [] + for val in valset: + if not isinstance(val, (int, str)): + val = "."+str(self.counter) + self.counter += 1 + l.append(str(val)) + return "-".join(l) class FuncargRequest: """ A request for function arguments from a test function. - + Note that there is an optional ``param`` attribute in case there was an invocation to metafunc.addcall(param=...). If no such call was done in a ``pytest_generate_tests`` @@ -637,7 +769,7 @@ def applymarker(self, marker): - """ apply a marker to a single test function invocation. + """ Apply a marker to a single test function invocation. This method is useful if you don't want to have a keyword/marker on all function invocations. @@ -649,7 +781,7 @@ self._pyfuncitem.keywords[marker.markname] = marker def cached_setup(self, setup, teardown=None, scope="module", extrakey=None): - """ return a testing resource managed by ``setup`` & + """ Return a testing resource managed by ``setup`` & ``teardown`` calls. ``scope`` and ``extrakey`` determine when the ``teardown`` function will be called so that subsequent calls to ``setup`` would recreate the resource. @@ -698,11 +830,18 @@ self._raiselookupfailed(argname) funcargfactory = self._name2factory[argname].pop() oldarg = self._currentarg - self._currentarg = argname + mp = monkeypatch() + mp.setattr(self, '_currentarg', argname) + try: + param = self._pyfuncitem.callspec.getparam(argname) + except (AttributeError, ValueError): + pass + else: + mp.setattr(self, 'param', param, raising=False) try: self._funcargs[argname] = res = funcargfactory(request=self) finally: - self._currentarg = oldarg + mp.undo() return res def _getscopeitem(self, scope): @@ -817,8 +956,7 @@ >>> raises(ZeroDivisionError, f, x=0) - A third possibility is to use a string which which will - be executed:: + A third possibility is to use a string to be executed:: >>> raises(ZeroDivisionError, "f(0)") diff --git a/_pytest/resultlog.py b/_pytest/resultlog.py --- a/_pytest/resultlog.py +++ b/_pytest/resultlog.py @@ -63,6 +63,8 @@ self.write_log_entry(testpath, lettercode, longrepr) def pytest_runtest_logreport(self, report): + if report.when != "call" and report.passed: + return res = self.config.hook.pytest_report_teststatus(report=report) code = res[1] if code == 'x': @@ -89,5 +91,8 @@ self.log_outcome(report, code, longrepr) def pytest_internalerror(self, excrepr): - path = excrepr.reprcrash.path + reprcrash = getattr(excrepr, 'reprcrash', None) + path = getattr(reprcrash, "path", None) + if path is None: + path = "cwd:%s" % py.path.local() self.write_log_entry(path, '!', str(excrepr)) diff --git a/_pytest/runner.py b/_pytest/runner.py --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -1,6 +1,6 @@ """ basic collect and runtest protocol implementations """ -import py, sys +import py, sys, time from py._code.code import TerminalRepr def pytest_namespace(): @@ -14,33 +14,60 @@ # # pytest plugin hooks +def pytest_addoption(parser): + group = parser.getgroup("terminal reporting", "reporting", after="general") + group.addoption('--durations', + action="store", type="int", default=None, metavar="N", + help="show N slowest setup/test durations (N=0 for all)."), + +def pytest_terminal_summary(terminalreporter): + durations = terminalreporter.config.option.durations + if durations is None: + return + tr = terminalreporter + dlist = [] + for replist in tr.stats.values(): + for rep in replist: + if hasattr(rep, 'duration'): + dlist.append(rep) + if not dlist: + return + dlist.sort(key=lambda x: x.duration) + dlist.reverse() + if not durations: + tr.write_sep("=", "slowest test durations") + else: + tr.write_sep("=", "slowest %s test durations" % durations) + dlist = dlist[:durations] + + for rep in dlist: + nodeid = rep.nodeid.replace("::()::", "::") + tr.write_line("%02.2fs %-8s %s" % + (rep.duration, rep.when, nodeid)) + def pytest_sessionstart(session): session._setupstate = SetupState() - -def pytest_sessionfinish(session, exitstatus): - hook = session.config.hook - rep = hook.pytest__teardown_final(session=session) - if rep: - hook.pytest__teardown_final_logerror(session=session, report=rep) - session.exitstatus = 1 +def pytest_sessionfinish(session): + session._setupstate.teardown_all() class NodeInfo: def __init__(self, location): self.location = location -def pytest_runtest_protocol(item): +def pytest_runtest_protocol(item, nextitem): item.ihook.pytest_runtest_logstart( nodeid=item.nodeid, location=item.location, ) - runtestprotocol(item) + runtestprotocol(item, nextitem=nextitem) return True -def runtestprotocol(item, log=True): +def runtestprotocol(item, log=True, nextitem=None): rep = call_and_report(item, "setup", log) reports = [rep] if rep.passed: reports.append(call_and_report(item, "call", log)) - reports.append(call_and_report(item, "teardown", log)) + reports.append(call_and_report(item, "teardown", log, + nextitem=nextitem)) return reports def pytest_runtest_setup(item): @@ -49,16 +76,8 @@ def pytest_runtest_call(item): item.runtest() -def pytest_runtest_teardown(item): - item.session._setupstate.teardown_exact(item) - -def pytest__teardown_final(session): - call = CallInfo(session._setupstate.teardown_all, when="teardown") - if call.excinfo: - ntraceback = call.excinfo.traceback .cut(excludepath=py._pydir) - call.excinfo.traceback = ntraceback.filter() - longrepr = call.excinfo.getrepr(funcargs=True) - return TeardownErrorReport(longrepr) +def pytest_runtest_teardown(item, nextitem): + item.session._setupstate.teardown_exact(item, nextitem) def pytest_report_teststatus(report): if report.when in ("setup", "teardown"): @@ -74,18 +93,18 @@ # # Implementation -def call_and_report(item, when, log=True): - call = call_runtest_hook(item, when) +def call_and_report(item, when, log=True, **kwds): + call = call_runtest_hook(item, when, **kwds) hook = item.ihook report = hook.pytest_runtest_makereport(item=item, call=call) - if log and (when == "call" or not report.passed): + if log: hook.pytest_runtest_logreport(report=report) return report -def call_runtest_hook(item, when): +def call_runtest_hook(item, when, **kwds): hookname = "pytest_runtest_" + when ihook = getattr(item.ihook, hookname) - return CallInfo(lambda: ihook(item=item), when=when) + return CallInfo(lambda: ihook(item=item, **kwds), when=when) class CallInfo: """ Result/Exception info a function invocation. """ @@ -95,12 +114,16 @@ #: context of invocation: one of "setup", "call", #: "teardown", "memocollect" self.when = when + self.start = time.time() try: - self.result = func() - except KeyboardInterrupt: - raise - except: - self.excinfo = py.code.ExceptionInfo() + try: + self.result = func() + except KeyboardInterrupt: + raise + except: + self.excinfo = py.code.ExceptionInfo() + finally: + self.stop = time.time() def __repr__(self): if self.excinfo: @@ -120,6 +143,10 @@ return s class BaseReport(object): + + def __init__(self, **kw): + self.__dict__.update(kw) + def toterminal(self, out): longrepr = self.longrepr if hasattr(self, 'node'): @@ -139,6 +166,7 @@ def pytest_runtest_makereport(item, call): when = call.when + duration = call.stop-call.start keywords = dict([(x,1) for x in item.keywords]) excinfo = call.excinfo if not call.excinfo: @@ -160,14 +188,15 @@ else: # exception in setup or teardown longrepr = item._repr_failure_py(excinfo) return TestReport(item.nodeid, item.location, - keywords, outcome, longrepr, when) + keywords, outcome, longrepr, when, + duration=duration) class TestReport(BaseReport): """ Basic test report object (also used for setup and teardown calls if they fail). """ def __init__(self, nodeid, location, - keywords, outcome, longrepr, when): + keywords, outcome, longrepr, when, sections=(), duration=0, **extra): #: normalized collection node id self.nodeid = nodeid @@ -179,16 +208,25 @@ #: a name -> value dictionary containing all keywords and #: markers associated with a test invocation. self.keywords = keywords - + #: test outcome, always one of "passed", "failed", "skipped". self.outcome = outcome #: None or a failure representation. self.longrepr = longrepr - + #: one of 'setup', 'call', 'teardown' to indicate runtest phase. self.when = when + #: list of (secname, data) extra information which needs to + #: marshallable + self.sections = list(sections) + + #: time it took to run just the test + self.duration = duration + + self.__dict__.update(extra) + def __repr__(self): return "" % ( self.nodeid, self.when, self.outcome) @@ -196,8 +234,10 @@ class TeardownErrorReport(BaseReport): outcome = "failed" when = "teardown" - def __init__(self, longrepr): + def __init__(self, longrepr, **extra): self.longrepr = longrepr + self.sections = [] + self.__dict__.update(extra) def pytest_make_collect_report(collector): call = CallInfo(collector._memocollect, "memocollect") @@ -219,11 +259,13 @@ getattr(call, 'result', None)) class CollectReport(BaseReport): - def __init__(self, nodeid, outcome, longrepr, result): + def __init__(self, nodeid, outcome, longrepr, result, sections=(), **extra): self.nodeid = nodeid self.outcome = outcome self.longrepr = longrepr self.result = result or [] + self.sections = list(sections) + self.__dict__.update(extra) @property def location(self): @@ -277,20 +319,22 @@ self._teardown_with_finalization(None) assert not self._finalizers - def teardown_exact(self, item): - if self.stack and item == self.stack[-1]: + def teardown_exact(self, item, nextitem): + needed_collectors = nextitem and nextitem.listchain() or [] + self._teardown_towards(needed_collectors) + + def _teardown_towards(self, needed_collectors): + while self.stack: + if self.stack == needed_collectors[:len(self.stack)]: + break self._pop_and_teardown() - else: - self._callfinalizers(item) def prepare(self, colitem): """ setup objects along the collector chain to the test-method and teardown previously setup objects.""" needed_collectors = colitem.listchain() - while self.stack: - if self.stack == needed_collectors[:len(self.stack)]: - break - self._pop_and_teardown() + self._teardown_towards(needed_collectors) + # check if the last collection node has raised an error for col in self.stack: if hasattr(col, '_prepare_exc'): diff --git a/_pytest/skipping.py b/_pytest/skipping.py --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -9,6 +9,21 @@ action="store_true", dest="runxfail", default=False, help="run tests even if they are marked xfail") +def pytest_configure(config): + config.addinivalue_line("markers", + "skipif(*conditions): skip the given test function if evaluation " + "of all conditions has a True value. Evaluation happens within the " + "module global context. Example: skipif('sys.platform == \"win32\"') " + "skips the test if we are on the win32 platform. " + ) + config.addinivalue_line("markers", + "xfail(*conditions, reason=None, run=True): mark the the test function " + "as an expected failure. Optionally specify a reason and run=False " + "if you don't even want to execute the test function. Any positional " + "condition strings will be evaluated (like with skipif) and if one is " + "False the marker will not be applied." + ) + def pytest_namespace(): return dict(xfail=xfail) @@ -117,6 +132,14 @@ def pytest_runtest_makereport(__multicall__, item, call): if not isinstance(item, pytest.Function): return + # unitttest special case, see setting of _unexpectedsuccess + if hasattr(item, '_unexpectedsuccess'): + rep = __multicall__.execute() + if rep.when == "call": + # we need to translate into how py.test encodes xpass + rep.keywords['xfail'] = "reason: " + item._unexpectedsuccess + rep.outcome = "failed" + return rep if not (call.excinfo and call.excinfo.errisinstance(py.test.xfail.Exception)): evalxfail = getattr(item, '_evalxfail', None) @@ -169,21 +192,23 @@ elif char == "X": show_xpassed(terminalreporter, lines) elif char in "fF": - show_failed(terminalreporter, lines) + show_simple(terminalreporter, lines, 'failed', "FAIL %s") elif char in "sS": show_skipped(terminalreporter, lines) + elif char == "E": + show_simple(terminalreporter, lines, 'error', "ERROR %s") if lines: tr._tw.sep("=", "short test summary info") for line in lines: tr._tw.line(line) -def show_failed(terminalreporter, lines): +def show_simple(terminalreporter, lines, stat, format): tw = terminalreporter._tw - failed = terminalreporter.stats.get("failed") + failed = terminalreporter.stats.get(stat) if failed: for rep in failed: pos = rep.nodeid - lines.append("FAIL %s" %(pos, )) + lines.append(format %(pos, )) def show_xfailed(terminalreporter, lines): xfailed = terminalreporter.stats.get("xfailed") diff --git a/_pytest/terminal.py b/_pytest/terminal.py --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -15,7 +15,7 @@ group._addoption('-r', action="store", dest="reportchars", default=None, metavar="chars", help="show extra test summary info as specified by chars (f)ailed, " - "(s)skipped, (x)failed, (X)passed.") + "(E)error, (s)skipped, (x)failed, (X)passed.") group._addoption('-l', '--showlocals', action="store_true", dest="showlocals", default=False, help="show locals in tracebacks (disabled by default).") @@ -43,7 +43,8 @@ pass else: stdout = os.fdopen(newfd, stdout.mode, 1) - config._toclose = stdout + config._cleanup.append(lambda: stdout.close()) + reporter = TerminalReporter(config, stdout) config.pluginmanager.register(reporter, 'terminalreporter') if config.option.debug or config.option.traceconfig: @@ -52,11 +53,6 @@ reporter.write_line("[traceconfig] " + msg) config.trace.root.setprocessor("pytest:config", mywriter) -def pytest_unconfigure(config): - if hasattr(config, '_toclose'): - #print "closing", config._toclose, config._toclose.fileno() - config._toclose.close() - def getreportopt(config): reportopts = "" optvalue = config.option.report @@ -165,9 +161,6 @@ def pytest_deselected(self, items): self.stats.setdefault('deselected', []).extend(items) - def pytest__teardown_final_logerror(self, report): - self.stats.setdefault("error", []).append(report) - def pytest_runtest_logstart(self, nodeid, location): # ensure that the path is printed before the # 1st test of a module starts running @@ -259,7 +252,7 @@ msg = "platform %s -- Python %s" % (sys.platform, verinfo) if hasattr(sys, 'pypy_version_info'): verinfo = ".".join(map(str, sys.pypy_version_info[:3])) - msg += "[pypy-%s]" % verinfo + msg += "[pypy-%s-%s]" % (verinfo, sys.pypy_version_info[3]) msg += " -- pytest-%s" % (py.test.__version__) if self.verbosity > 0 or self.config.option.debug or \ getattr(self.config.option, 'pastebin', None): @@ -289,10 +282,18 @@ # we take care to leave out Instances aka () # because later versions are going to get rid of them anyway if self.config.option.verbose < 0: - for item in items: - nodeid = item.nodeid - nodeid = nodeid.replace("::()::", "::") - self._tw.line(nodeid) + if self.config.option.verbose < -1: + counts = {} + for item in items: + name = item.nodeid.split('::', 1)[0] + counts[name] = counts.get(name, 0) + 1 + for name, count in sorted(counts.items()): + self._tw.line("%s: %d" % (name, count)) + else: + for item in items: + nodeid = item.nodeid + nodeid = nodeid.replace("::()::", "::") + self._tw.line(nodeid) return stack = [] indent = "" @@ -318,12 +319,17 @@ self.config.hook.pytest_terminal_summary(terminalreporter=self) if exitstatus == 2: self._report_keyboardinterrupt() + del self._keyboardinterrupt_memo self.summary_deselected() self.summary_stats() def pytest_keyboard_interrupt(self, excinfo): self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True) + def pytest_unconfigure(self): + if hasattr(self, '_keyboardinterrupt_memo'): + self._report_keyboardinterrupt() + def _report_keyboardinterrupt(self): excrepr = self._keyboardinterrupt_memo msg = excrepr.reprcrash.message @@ -388,7 +394,7 @@ else: msg = self._getfailureheadline(rep) self.write_sep("_", msg) - rep.toterminal(self._tw) + self._outrep_summary(rep) def summary_errors(self): if self.config.option.tbstyle != "no": @@ -406,7 +412,15 @@ elif rep.when == "teardown": msg = "ERROR at teardown of " + msg self.write_sep("_", msg) - rep.toterminal(self._tw) + self._outrep_summary(rep) + + def _outrep_summary(self, rep): + rep.toterminal(self._tw) + for secname, content in rep.sections: + self._tw.sep("-", secname) + if content[-1:] == "\n": + content = content[:-1] + self._tw.line(content) def summary_stats(self): session_duration = py.std.time.time() - self._sessionstarttime @@ -417,9 +431,10 @@ keys.append(key) parts = [] for key in keys: - val = self.stats.get(key, None) - if val: - parts.append("%d %s" %(len(val), key)) + if key: # setup/teardown reports have an empty key, ignore them + val = self.stats.get(key, None) + if val: + parts.append("%d %s" %(len(val), key)) line = ", ".join(parts) # XXX coloring msg = "%s in %.2f seconds" %(line, session_duration) @@ -430,8 +445,15 @@ def summary_deselected(self): if 'deselected' in self.stats: + l = [] + k = self.config.option.keyword + if k: + l.append("-k%s" % k) + m = self.config.option.markexpr + if m: + l.append("-m %r" % m) self.write_sep("=", "%d tests deselected by %r" %( - len(self.stats['deselected']), self.config.option.keyword), bold=True) + len(self.stats['deselected']), " ".join(l)), bold=True) def repr_pythonversion(v=None): if v is None: diff --git a/_pytest/tmpdir.py b/_pytest/tmpdir.py --- a/_pytest/tmpdir.py +++ b/_pytest/tmpdir.py @@ -46,7 +46,7 @@ def finish(self): self.trace("finish") - + def pytest_configure(config): mp = monkeypatch() t = TempdirHandler(config) @@ -64,5 +64,5 @@ name = request._pyfuncitem.name name = py.std.re.sub("[\W]", "_", name) x = request.config._tmpdirhandler.mktemp(name, numbered=True) - return x.realpath() + return x diff --git a/_pytest/unittest.py b/_pytest/unittest.py --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -2,6 +2,9 @@ import pytest, py import sys, pdb +# for transfering markers +from _pytest.python import transfer_markers + def pytest_pycollect_makeitem(collector, name, obj): unittest = sys.modules.get('unittest') if unittest is None: @@ -19,7 +22,14 @@ class UnitTestCase(pytest.Class): def collect(self): loader = py.std.unittest.TestLoader() + module = self.getparent(pytest.Module).obj + cls = self.obj for name in loader.getTestCaseNames(self.obj): + x = getattr(self.obj, name) + funcobj = getattr(x, 'im_func', x) + transfer_markers(funcobj, cls, module) + if hasattr(funcobj, 'todo'): + pytest.mark.xfail(reason=str(funcobj.todo))(funcobj) yield TestCaseFunction(name, parent=self) def setup(self): @@ -37,15 +47,13 @@ class TestCaseFunction(pytest.Function): _excinfo = None - def __init__(self, name, parent): - super(TestCaseFunction, self).__init__(name, parent) - if hasattr(self._obj, 'todo'): - getattr(self._obj, 'im_func', self._obj).xfail = \ - pytest.mark.xfail(reason=str(self._obj.todo)) - def setup(self): self._testcase = self.parent.obj(self.name) self._obj = getattr(self._testcase, self.name) + if hasattr(self._testcase, 'skip'): + pytest.skip(self._testcase.skip) + if hasattr(self._obj, 'skip'): + pytest.skip(self._obj.skip) if hasattr(self._testcase, 'setup_method'): self._testcase.setup_method(self._obj) @@ -83,28 +91,37 @@ self._addexcinfo(rawexcinfo) def addFailure(self, testcase, rawexcinfo): self._addexcinfo(rawexcinfo) + def addSkip(self, testcase, reason): try: pytest.skip(reason) except pytest.skip.Exception: self._addexcinfo(sys.exc_info()) - def addExpectedFailure(self, testcase, rawexcinfo, reason): + + def addExpectedFailure(self, testcase, rawexcinfo, reason=""): try: pytest.xfail(str(reason)) except pytest.xfail.Exception: self._addexcinfo(sys.exc_info()) - def addUnexpectedSuccess(self, testcase, reason): - pass + + def addUnexpectedSuccess(self, testcase, reason=""): + self._unexpectedsuccess = reason + def addSuccess(self, testcase): pass + def stopTest(self, testcase): pass + def runtest(self): self._testcase(result=self) def _prunetraceback(self, excinfo): pytest.Function._prunetraceback(self, excinfo) - excinfo.traceback = excinfo.traceback.filter(lambda x:not x.frame.f_globals.get('__unittest')) + traceback = excinfo.traceback.filter( + lambda x:not x.frame.f_globals.get('__unittest')) + if traceback: + excinfo.traceback = traceback @pytest.mark.tryfirst def pytest_runtest_makereport(item, call): @@ -120,14 +137,19 @@ ut = sys.modules['twisted.python.failure'] Failure__init__ = ut.Failure.__init__.im_func check_testcase_implements_trial_reporter() - def excstore(self, exc_value=None, exc_type=None, exc_tb=None): + def excstore(self, exc_value=None, exc_type=None, exc_tb=None, + captureVars=None): if exc_value is None: self._rawexcinfo = sys.exc_info() else: if exc_type is None: exc_type = type(exc_value) self._rawexcinfo = (exc_type, exc_value, exc_tb) - Failure__init__(self, exc_value, exc_type, exc_tb) + try: + Failure__init__(self, exc_value, exc_type, exc_tb, + captureVars=captureVars) + except TypeError: + Failure__init__(self, exc_value, exc_type, exc_tb) ut.Failure.__init__ = excstore try: return __multicall__.execute() diff --git a/py/__init__.py b/py/__init__.py --- a/py/__init__.py +++ b/py/__init__.py @@ -8,7 +8,7 @@ (c) Holger Krekel and others, 2004-2010 """ -__version__ = '1.4.4.dev1' +__version__ = '1.4.7' from py import _apipkg @@ -70,6 +70,11 @@ 'getrawcode' : '._code.code:getrawcode', 'patch_builtins' : '._code.code:patch_builtins', 'unpatch_builtins' : '._code.code:unpatch_builtins', + '_AssertionError' : '._code.assertion:AssertionError', + '_reinterpret_old' : '._code.assertion:reinterpret_old', + '_reinterpret' : '._code.assertion:reinterpret', + '_reprcompare' : '._code.assertion:_reprcompare', + '_format_explanation' : '._code.assertion:_format_explanation', }, # backports and additions of builtins diff --git a/py/_builtin.py b/py/_builtin.py --- a/py/_builtin.py +++ b/py/_builtin.py @@ -113,9 +113,12 @@ # some backward compatibility helpers _basestring = str - def _totext(obj, encoding=None): + def _totext(obj, encoding=None, errors=None): if isinstance(obj, bytes): - obj = obj.decode(encoding) + if errors is None: + obj = obj.decode(encoding) + else: + obj = obj.decode(encoding, errors) elif not isinstance(obj, str): obj = str(obj) return obj @@ -142,7 +145,7 @@ del back elif locs is None: locs = globs - fp = open(fn, "rb") + fp = open(fn, "r") try: source = fp.read() finally: diff --git a/py/_code/_assertionnew.py b/py/_code/_assertionnew.py new file mode 100644 --- /dev/null +++ b/py/_code/_assertionnew.py @@ -0,0 +1,339 @@ +""" +Find intermediate evalutation results in assert statements through builtin AST. +This should replace _assertionold.py eventually. +""" + +import sys +import ast + +import py +from py._code.assertion import _format_explanation, BuiltinAssertionError + + +if sys.platform.startswith("java") and sys.version_info < (2, 5, 2): + # See http://bugs.jython.org/issue1497 + _exprs = ("BoolOp", "BinOp", "UnaryOp", "Lambda", "IfExp", "Dict", + "ListComp", "GeneratorExp", "Yield", "Compare", "Call", + "Repr", "Num", "Str", "Attribute", "Subscript", "Name", + "List", "Tuple") + _stmts = ("FunctionDef", "ClassDef", "Return", "Delete", "Assign", + "AugAssign", "Print", "For", "While", "If", "With", "Raise", + "TryExcept", "TryFinally", "Assert", "Import", "ImportFrom", + "Exec", "Global", "Expr", "Pass", "Break", "Continue") + _expr_nodes = set(getattr(ast, name) for name in _exprs) + _stmt_nodes = set(getattr(ast, name) for name in _stmts) + def _is_ast_expr(node): + return node.__class__ in _expr_nodes + def _is_ast_stmt(node): + return node.__class__ in _stmt_nodes +else: + def _is_ast_expr(node): + return isinstance(node, ast.expr) + def _is_ast_stmt(node): + return isinstance(node, ast.stmt) + + +class Failure(Exception): + """Error found while interpreting AST.""" + + def __init__(self, explanation=""): + self.cause = sys.exc_info() + self.explanation = explanation + + +def interpret(source, frame, should_fail=False): + mod = ast.parse(source) + visitor = DebugInterpreter(frame) + try: + visitor.visit(mod) + except Failure: + failure = sys.exc_info()[1] + return getfailure(failure) + if should_fail: + return ("(assertion failed, but when it was re-run for " + "printing intermediate values, it did not fail. Suggestions: " + "compute assert expression before the assert or use --no-assert)") + +def run(offending_line, frame=None): + if frame is None: + frame = py.code.Frame(sys._getframe(1)) + return interpret(offending_line, frame) + +def getfailure(failure): + explanation = _format_explanation(failure.explanation) + value = failure.cause[1] + if str(value): + lines = explanation.splitlines() + if not lines: + lines.append("") + lines[0] += " << %s" % (value,) + explanation = "\n".join(lines) + text = "%s: %s" % (failure.cause[0].__name__, explanation) + if text.startswith("AssertionError: assert "): + text = text[16:] + return text + + +operator_map = { + ast.BitOr : "|", + ast.BitXor : "^", + ast.BitAnd : "&", + ast.LShift : "<<", + ast.RShift : ">>", + ast.Add : "+", + ast.Sub : "-", + ast.Mult : "*", + ast.Div : "/", + ast.FloorDiv : "//", + ast.Mod : "%", + ast.Eq : "==", + ast.NotEq : "!=", + ast.Lt : "<", + ast.LtE : "<=", + ast.Gt : ">", + ast.GtE : ">=", + ast.Pow : "**", + ast.Is : "is", + ast.IsNot : "is not", + ast.In : "in", + ast.NotIn : "not in" +} + +unary_map = { + ast.Not : "not %s", + ast.Invert : "~%s", + ast.USub : "-%s", + ast.UAdd : "+%s" +} + + +class DebugInterpreter(ast.NodeVisitor): + """Interpret AST nodes to gleam useful debugging information. """ + + def __init__(self, frame): + self.frame = frame + + def generic_visit(self, node): + # Fallback when we don't have a special implementation. + if _is_ast_expr(node): + mod = ast.Expression(node) + co = self._compile(mod) + try: + result = self.frame.eval(co) + except Exception: + raise Failure() + explanation = self.frame.repr(result) + return explanation, result + elif _is_ast_stmt(node): + mod = ast.Module([node]) + co = self._compile(mod, "exec") + try: + self.frame.exec_(co) + except Exception: + raise Failure() + return None, None + else: + raise AssertionError("can't handle %s" %(node,)) + + def _compile(self, source, mode="eval"): + return compile(source, "", mode) + + def visit_Expr(self, expr): + return self.visit(expr.value) + + def visit_Module(self, mod): + for stmt in mod.body: + self.visit(stmt) + + def visit_Name(self, name): + explanation, result = self.generic_visit(name) + # See if the name is local. + source = "%r in locals() is not globals()" % (name.id,) + co = self._compile(source) + try: + local = self.frame.eval(co) + except Exception: + # have to assume it isn't + local = False + if not local: + return name.id, result + return explanation, result + + def visit_Compare(self, comp): + left = comp.left + left_explanation, left_result = self.visit(left) + for op, next_op in zip(comp.ops, comp.comparators): + next_explanation, next_result = self.visit(next_op) + op_symbol = operator_map[op.__class__] + explanation = "%s %s %s" % (left_explanation, op_symbol, + next_explanation) + source = "__exprinfo_left %s __exprinfo_right" % (op_symbol,) + co = self._compile(source) + try: + result = self.frame.eval(co, __exprinfo_left=left_result, + __exprinfo_right=next_result) + except Exception: + raise Failure(explanation) + try: + if not result: + break + except KeyboardInterrupt: + raise + except: + break + left_explanation, left_result = next_explanation, next_result + + rcomp = py.code._reprcompare + if rcomp: + res = rcomp(op_symbol, left_result, next_result) + if res: + explanation = res + return explanation, result + + def visit_BoolOp(self, boolop): + is_or = isinstance(boolop.op, ast.Or) + explanations = [] + for operand in boolop.values: + explanation, result = self.visit(operand) + explanations.append(explanation) + if result == is_or: + break + name = is_or and " or " or " and " + explanation = "(" + name.join(explanations) + ")" + return explanation, result + + def visit_UnaryOp(self, unary): + pattern = unary_map[unary.op.__class__] + operand_explanation, operand_result = self.visit(unary.operand) + explanation = pattern % (operand_explanation,) + co = self._compile(pattern % ("__exprinfo_expr",)) + try: + result = self.frame.eval(co, __exprinfo_expr=operand_result) + except Exception: + raise Failure(explanation) + return explanation, result + + def visit_BinOp(self, binop): + left_explanation, left_result = self.visit(binop.left) + right_explanation, right_result = self.visit(binop.right) + symbol = operator_map[binop.op.__class__] + explanation = "(%s %s %s)" % (left_explanation, symbol, + right_explanation) + source = "__exprinfo_left %s __exprinfo_right" % (symbol,) + co = self._compile(source) + try: + result = self.frame.eval(co, __exprinfo_left=left_result, + __exprinfo_right=right_result) + except Exception: + raise Failure(explanation) + return explanation, result + + def visit_Call(self, call): + func_explanation, func = self.visit(call.func) + arg_explanations = [] + ns = {"__exprinfo_func" : func} + arguments = [] + for arg in call.args: + arg_explanation, arg_result = self.visit(arg) + arg_name = "__exprinfo_%s" % (len(ns),) + ns[arg_name] = arg_result + arguments.append(arg_name) + arg_explanations.append(arg_explanation) + for keyword in call.keywords: + arg_explanation, arg_result = self.visit(keyword.value) + arg_name = "__exprinfo_%s" % (len(ns),) + ns[arg_name] = arg_result + keyword_source = "%s=%%s" % (keyword.arg) + arguments.append(keyword_source % (arg_name,)) + arg_explanations.append(keyword_source % (arg_explanation,)) + if call.starargs: + arg_explanation, arg_result = self.visit(call.starargs) + arg_name = "__exprinfo_star" + ns[arg_name] = arg_result + arguments.append("*%s" % (arg_name,)) + arg_explanations.append("*%s" % (arg_explanation,)) + if call.kwargs: + arg_explanation, arg_result = self.visit(call.kwargs) + arg_name = "__exprinfo_kwds" + ns[arg_name] = arg_result + arguments.append("**%s" % (arg_name,)) + arg_explanations.append("**%s" % (arg_explanation,)) + args_explained = ", ".join(arg_explanations) + explanation = "%s(%s)" % (func_explanation, args_explained) + args = ", ".join(arguments) + source = "__exprinfo_func(%s)" % (args,) + co = self._compile(source) + try: + result = self.frame.eval(co, **ns) + except Exception: + raise Failure(explanation) + pattern = "%s\n{%s = %s\n}" + rep = self.frame.repr(result) + explanation = pattern % (rep, rep, explanation) + return explanation, result + + def _is_builtin_name(self, name): + pattern = "%r not in globals() and %r not in locals()" + source = pattern % (name.id, name.id) + co = self._compile(source) + try: + return self.frame.eval(co) + except Exception: + return False + + def visit_Attribute(self, attr): + if not isinstance(attr.ctx, ast.Load): + return self.generic_visit(attr) + source_explanation, source_result = self.visit(attr.value) + explanation = "%s.%s" % (source_explanation, attr.attr) + source = "__exprinfo_expr.%s" % (attr.attr,) + co = self._compile(source) + try: + result = self.frame.eval(co, __exprinfo_expr=source_result) + except Exception: + raise Failure(explanation) + explanation = "%s\n{%s = %s.%s\n}" % (self.frame.repr(result), + self.frame.repr(result), + source_explanation, attr.attr) + # Check if the attr is from an instance. + source = "%r in getattr(__exprinfo_expr, '__dict__', {})" + source = source % (attr.attr,) + co = self._compile(source) + try: + from_instance = self.frame.eval(co, __exprinfo_expr=source_result) + except Exception: + from_instance = True + if from_instance: + rep = self.frame.repr(result) + pattern = "%s\n{%s = %s\n}" + explanation = pattern % (rep, rep, explanation) + return explanation, result + + def visit_Assert(self, assrt): + test_explanation, test_result = self.visit(assrt.test) + if test_explanation.startswith("False\n{False =") and \ + test_explanation.endswith("\n"): + test_explanation = test_explanation[15:-2] + explanation = "assert %s" % (test_explanation,) + if not test_result: + try: + raise BuiltinAssertionError + except Exception: + raise Failure(explanation) + return explanation, test_result + + def visit_Assign(self, assign): + value_explanation, value_result = self.visit(assign.value) + explanation = "... = %s" % (value_explanation,) + name = ast.Name("__exprinfo_expr", ast.Load(), + lineno=assign.value.lineno, + col_offset=assign.value.col_offset) + new_assign = ast.Assign(assign.targets, name, lineno=assign.lineno, + col_offset=assign.col_offset) + mod = ast.Module([new_assign]) + co = self._compile(mod, "exec") + try: + self.frame.exec_(co, __exprinfo_expr=value_result) + except Exception: + raise Failure(explanation) + return explanation, value_result diff --git a/py/_code/_assertionold.py b/py/_code/_assertionold.py new file mode 100644 --- /dev/null +++ b/py/_code/_assertionold.py @@ -0,0 +1,555 @@ +import py +import sys, inspect +from compiler import parse, ast, pycodegen +from py._code.assertion import BuiltinAssertionError, _format_explanation + +passthroughex = py.builtin._sysex + +class Failure: + def __init__(self, node): + self.exc, self.value, self.tb = sys.exc_info() + self.node = node + +class View(object): + """View base class. + + If C is a subclass of View, then C(x) creates a proxy object around + the object x. The actual class of the proxy is not C in general, + but a *subclass* of C determined by the rules below. To avoid confusion + we call view class the class of the proxy (a subclass of C, so of View) + and object class the class of x. + + Attributes and methods not found in the proxy are automatically read on x. + Other operations like setting attributes are performed on the proxy, as + determined by its view class. The object x is available from the proxy + as its __obj__ attribute. + + The view class selection is determined by the __view__ tuples and the + optional __viewkey__ method. By default, the selected view class is the + most specific subclass of C whose __view__ mentions the class of x. + If no such subclass is found, the search proceeds with the parent + object classes. For example, C(True) will first look for a subclass + of C with __view__ = (..., bool, ...) and only if it doesn't find any + look for one with __view__ = (..., int, ...), and then ..., object,... + If everything fails the class C itself is considered to be the default. + + Alternatively, the view class selection can be driven by another aspect + of the object x, instead of the class of x, by overriding __viewkey__. + See last example at the end of this module. + """ + + _viewcache = {} + __view__ = () + + def __new__(rootclass, obj, *args, **kwds): + self = object.__new__(rootclass) + self.__obj__ = obj + self.__rootclass__ = rootclass + key = self.__viewkey__() + try: + self.__class__ = self._viewcache[key] + except KeyError: + self.__class__ = self._selectsubclass(key) + return self + + def __getattr__(self, attr): + # attributes not found in the normal hierarchy rooted on View + # are looked up in the object's real class + return getattr(self.__obj__, attr) + + def __viewkey__(self): + return self.__obj__.__class__ + + def __matchkey__(self, key, subclasses): + if inspect.isclass(key): + keys = inspect.getmro(key) + else: + keys = [key] + for key in keys: + result = [C for C in subclasses if key in C.__view__] + if result: + return result + return [] + + def _selectsubclass(self, key): + subclasses = list(enumsubclasses(self.__rootclass__)) + for C in subclasses: + if not isinstance(C.__view__, tuple): + C.__view__ = (C.__view__,) + choices = self.__matchkey__(key, subclasses) + if not choices: + return self.__rootclass__ + elif len(choices) == 1: + return choices[0] + else: + # combine the multiple choices + return type('?', tuple(choices), {}) + + def __repr__(self): + return '%s(%r)' % (self.__rootclass__.__name__, self.__obj__) + + +def enumsubclasses(cls): + for subcls in cls.__subclasses__(): + for subsubclass in enumsubclasses(subcls): + yield subsubclass + yield cls + + +class Interpretable(View): + """A parse tree node with a few extra methods.""" + explanation = None + + def is_builtin(self, frame): + return False + + def eval(self, frame): + # fall-back for unknown expression nodes + try: + expr = ast.Expression(self.__obj__) + expr.filename = '' + self.__obj__.filename = '' + co = pycodegen.ExpressionCodeGenerator(expr).getCode() + result = frame.eval(co) + except passthroughex: + raise + except: + raise Failure(self) + self.result = result + self.explanation = self.explanation or frame.repr(self.result) + + def run(self, frame): + # fall-back for unknown statement nodes + try: + expr = ast.Module(None, ast.Stmt([self.__obj__])) + expr.filename = '' + co = pycodegen.ModuleCodeGenerator(expr).getCode() + frame.exec_(co) + except passthroughex: + raise + except: + raise Failure(self) + + def nice_explanation(self): + return _format_explanation(self.explanation) + + +class Name(Interpretable): + __view__ = ast.Name + + def is_local(self, frame): + source = '%r in locals() is not globals()' % self.name + try: + return frame.is_true(frame.eval(source)) + except passthroughex: + raise + except: + return False + + def is_global(self, frame): + source = '%r in globals()' % self.name + try: + return frame.is_true(frame.eval(source)) + except passthroughex: + raise + except: + return False + + def is_builtin(self, frame): + source = '%r not in locals() and %r not in globals()' % ( + self.name, self.name) + try: + return frame.is_true(frame.eval(source)) + except passthroughex: + raise + except: + return False + + def eval(self, frame): + super(Name, self).eval(frame) + if not self.is_local(frame): + self.explanation = self.name + +class Compare(Interpretable): + __view__ = ast.Compare + + def eval(self, frame): + expr = Interpretable(self.expr) + expr.eval(frame) + for operation, expr2 in self.ops: + if hasattr(self, 'result'): + # shortcutting in chained expressions + if not frame.is_true(self.result): + break + expr2 = Interpretable(expr2) + expr2.eval(frame) + self.explanation = "%s %s %s" % ( + expr.explanation, operation, expr2.explanation) + source = "__exprinfo_left %s __exprinfo_right" % operation + try: + self.result = frame.eval(source, + __exprinfo_left=expr.result, + __exprinfo_right=expr2.result) + except passthroughex: + raise + except: + raise Failure(self) + expr = expr2 + +class And(Interpretable): + __view__ = ast.And + + def eval(self, frame): + explanations = [] + for expr in self.nodes: + expr = Interpretable(expr) + expr.eval(frame) + explanations.append(expr.explanation) + self.result = expr.result + if not frame.is_true(expr.result): + break + self.explanation = '(' + ' and '.join(explanations) + ')' + +class Or(Interpretable): + __view__ = ast.Or + + def eval(self, frame): + explanations = [] + for expr in self.nodes: + expr = Interpretable(expr) + expr.eval(frame) + explanations.append(expr.explanation) + self.result = expr.result + if frame.is_true(expr.result): + break + self.explanation = '(' + ' or '.join(explanations) + ')' + + +# == Unary operations == +keepalive = [] +for astclass, astpattern in { + ast.Not : 'not __exprinfo_expr', + ast.Invert : '(~__exprinfo_expr)', + }.items(): + + class UnaryArith(Interpretable): + __view__ = astclass + + def eval(self, frame, astpattern=astpattern): + expr = Interpretable(self.expr) + expr.eval(frame) + self.explanation = astpattern.replace('__exprinfo_expr', + expr.explanation) + try: + self.result = frame.eval(astpattern, + __exprinfo_expr=expr.result) + except passthroughex: + raise + except: + raise Failure(self) + + keepalive.append(UnaryArith) + +# == Binary operations == +for astclass, astpattern in { + ast.Add : '(__exprinfo_left + __exprinfo_right)', + ast.Sub : '(__exprinfo_left - __exprinfo_right)', + ast.Mul : '(__exprinfo_left * __exprinfo_right)', + ast.Div : '(__exprinfo_left / __exprinfo_right)', + ast.Mod : '(__exprinfo_left % __exprinfo_right)', + ast.Power : '(__exprinfo_left ** __exprinfo_right)', + }.items(): + + class BinaryArith(Interpretable): + __view__ = astclass + + def eval(self, frame, astpattern=astpattern): + left = Interpretable(self.left) + left.eval(frame) + right = Interpretable(self.right) + right.eval(frame) + self.explanation = (astpattern + .replace('__exprinfo_left', left .explanation) + .replace('__exprinfo_right', right.explanation)) + try: + self.result = frame.eval(astpattern, + __exprinfo_left=left.result, + __exprinfo_right=right.result) + except passthroughex: + raise + except: + raise Failure(self) + + keepalive.append(BinaryArith) + + +class CallFunc(Interpretable): + __view__ = ast.CallFunc + + def is_bool(self, frame): + source = 'isinstance(__exprinfo_value, bool)' + try: + return frame.is_true(frame.eval(source, + __exprinfo_value=self.result)) + except passthroughex: + raise + except: + return False + + def eval(self, frame): + node = Interpretable(self.node) + node.eval(frame) + explanations = [] + vars = {'__exprinfo_fn': node.result} + source = '__exprinfo_fn(' + for a in self.args: + if isinstance(a, ast.Keyword): + keyword = a.name + a = a.expr + else: + keyword = None + a = Interpretable(a) + a.eval(frame) + argname = '__exprinfo_%d' % len(vars) + vars[argname] = a.result + if keyword is None: + source += argname + ',' + explanations.append(a.explanation) + else: + source += '%s=%s,' % (keyword, argname) + explanations.append('%s=%s' % (keyword, a.explanation)) + if self.star_args: + star_args = Interpretable(self.star_args) + star_args.eval(frame) + argname = '__exprinfo_star' + vars[argname] = star_args.result + source += '*' + argname + ',' + explanations.append('*' + star_args.explanation) + if self.dstar_args: + dstar_args = Interpretable(self.dstar_args) + dstar_args.eval(frame) + argname = '__exprinfo_kwds' + vars[argname] = dstar_args.result + source += '**' + argname + ',' + explanations.append('**' + dstar_args.explanation) + self.explanation = "%s(%s)" % ( + node.explanation, ', '.join(explanations)) + if source.endswith(','): + source = source[:-1] + source += ')' + try: + self.result = frame.eval(source, **vars) + except passthroughex: + raise + except: + raise Failure(self) + if not node.is_builtin(frame) or not self.is_bool(frame): + r = frame.repr(self.result) + self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation) + +class Getattr(Interpretable): + __view__ = ast.Getattr + + def eval(self, frame): + expr = Interpretable(self.expr) + expr.eval(frame) + source = '__exprinfo_expr.%s' % self.attrname + try: + self.result = frame.eval(source, __exprinfo_expr=expr.result) + except passthroughex: + raise + except: + raise Failure(self) + self.explanation = '%s.%s' % (expr.explanation, self.attrname) + # if the attribute comes from the instance, its value is interesting + source = ('hasattr(__exprinfo_expr, "__dict__") and ' + '%r in __exprinfo_expr.__dict__' % self.attrname) + try: + from_instance = frame.is_true( + frame.eval(source, __exprinfo_expr=expr.result)) + except passthroughex: + raise + except: + from_instance = True + if from_instance: + r = frame.repr(self.result) + self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation) + +# == Re-interpretation of full statements == + +class Assert(Interpretable): + __view__ = ast.Assert + + def run(self, frame): + test = Interpretable(self.test) + test.eval(frame) + # simplify 'assert False where False = ...' + if (test.explanation.startswith('False\n{False = ') and + test.explanation.endswith('\n}')): + test.explanation = test.explanation[15:-2] + # print the result as 'assert ' + self.result = test.result + self.explanation = 'assert ' + test.explanation + if not frame.is_true(test.result): + try: + raise BuiltinAssertionError + except passthroughex: + raise + except: + raise Failure(self) + +class Assign(Interpretable): + __view__ = ast.Assign + + def run(self, frame): + expr = Interpretable(self.expr) + expr.eval(frame) + self.result = expr.result + self.explanation = '... = ' + expr.explanation + # fall-back-run the rest of the assignment + ass = ast.Assign(self.nodes, ast.Name('__exprinfo_expr')) + mod = ast.Module(None, ast.Stmt([ass])) + mod.filename = '' + co = pycodegen.ModuleCodeGenerator(mod).getCode() + try: + frame.exec_(co, __exprinfo_expr=expr.result) + except passthroughex: + raise + except: + raise Failure(self) + +class Discard(Interpretable): + __view__ = ast.Discard + + def run(self, frame): + expr = Interpretable(self.expr) + expr.eval(frame) + self.result = expr.result + self.explanation = expr.explanation + +class Stmt(Interpretable): + __view__ = ast.Stmt + + def run(self, frame): + for stmt in self.nodes: + stmt = Interpretable(stmt) + stmt.run(frame) + + +def report_failure(e): + explanation = e.node.nice_explanation() + if explanation: + explanation = ", in: " + explanation + else: + explanation = "" + sys.stdout.write("%s: %s%s\n" % (e.exc.__name__, e.value, explanation)) + +def check(s, frame=None): + if frame is None: + frame = sys._getframe(1) + frame = py.code.Frame(frame) + expr = parse(s, 'eval') + assert isinstance(expr, ast.Expression) + node = Interpretable(expr.node) + try: + node.eval(frame) + except passthroughex: + raise + except Failure: + e = sys.exc_info()[1] + report_failure(e) + else: + if not frame.is_true(node.result): + sys.stderr.write("assertion failed: %s\n" % node.nice_explanation()) + + +########################################################### +# API / Entry points +# ######################################################### + +def interpret(source, frame, should_fail=False): + module = Interpretable(parse(source, 'exec').node) + #print "got module", module + if isinstance(frame, py.std.types.FrameType): + frame = py.code.Frame(frame) + try: + module.run(frame) + except Failure: + e = sys.exc_info()[1] + return getfailure(e) + except passthroughex: + raise + except: + import traceback + traceback.print_exc() + if should_fail: + return ("(assertion failed, but when it was re-run for " + "printing intermediate values, it did not fail. Suggestions: " + "compute assert expression before the assert or use --nomagic)") + else: + return None + +def getmsg(excinfo): + if isinstance(excinfo, tuple): + excinfo = py.code.ExceptionInfo(excinfo) + #frame, line = gettbline(tb) + #frame = py.code.Frame(frame) + #return interpret(line, frame) + + tb = excinfo.traceback[-1] + source = str(tb.statement).strip() + x = interpret(source, tb.frame, should_fail=True) + if not isinstance(x, str): + raise TypeError("interpret returned non-string %r" % (x,)) + return x + +def getfailure(e): + explanation = e.node.nice_explanation() + if str(e.value): + lines = explanation.split('\n') + lines[0] += " << %s" % (e.value,) + explanation = '\n'.join(lines) + text = "%s: %s" % (e.exc.__name__, explanation) + if text.startswith('AssertionError: assert '): + text = text[16:] + return text + +def run(s, frame=None): + if frame is None: + frame = sys._getframe(1) + frame = py.code.Frame(frame) + module = Interpretable(parse(s, 'exec').node) + try: + module.run(frame) + except Failure: + e = sys.exc_info()[1] + report_failure(e) + + +if __name__ == '__main__': + # example: + def f(): + return 5 + def g(): + return 3 + def h(x): + return 'never' + check("f() * g() == 5") + check("not f()") + check("not (f() and g() or 0)") + check("f() == g()") + i = 4 + check("i == f()") + check("len(f()) == 0") + check("isinstance(2+3+4, float)") + + run("x = i") + check("x == 5") + + run("assert not f(), 'oops'") + run("a, b, c = 1, 2") + run("a, b, c = f()") + + check("max([f(),g()]) == 4") + check("'hello'[g()] == 'h'") + run("'guk%d' % h(f())") diff --git a/py/_code/assertion.py b/py/_code/assertion.py new file mode 100644 --- /dev/null +++ b/py/_code/assertion.py @@ -0,0 +1,94 @@ +import sys +import py + +BuiltinAssertionError = py.builtin.builtins.AssertionError + +_reprcompare = None # if set, will be called by assert reinterp for comparison ops + +def _format_explanation(explanation): + """This formats an explanation + + Normally all embedded newlines are escaped, however there are + three exceptions: \n{, \n} and \n~. The first two are intended + cover nested explanations, see function and attribute explanations + for examples (.visit_Call(), visit_Attribute()). The last one is + for when one explanation needs to span multiple lines, e.g. when + displaying diffs. + """ + raw_lines = (explanation or '').split('\n') + # escape newlines not followed by {, } and ~ + lines = [raw_lines[0]] + for l in raw_lines[1:]: + if l.startswith('{') or l.startswith('}') or l.startswith('~'): + lines.append(l) + else: + lines[-1] += '\\n' + l + + result = lines[:1] + stack = [0] + stackcnt = [0] + for line in lines[1:]: + if line.startswith('{'): + if stackcnt[-1]: + s = 'and ' + else: + s = 'where ' + stack.append(len(result)) + stackcnt[-1] += 1 + stackcnt.append(0) + result.append(' +' + ' '*(len(stack)-1) + s + line[1:]) + elif line.startswith('}'): + assert line.startswith('}') + stack.pop() + stackcnt.pop() + result[stack[-1]] += line[1:] + else: + assert line.startswith('~') + result.append(' '*len(stack) + line[1:]) + assert len(stack) == 1 + return '\n'.join(result) + + +class AssertionError(BuiltinAssertionError): + def __init__(self, *args): + BuiltinAssertionError.__init__(self, *args) + if args: + try: + self.msg = str(args[0]) + except py.builtin._sysex: + raise + except: + self.msg = "<[broken __repr__] %s at %0xd>" %( + args[0].__class__, id(args[0])) + else: + f = py.code.Frame(sys._getframe(1)) + try: + source = f.code.fullsource + if source is not None: + try: + source = source.getstatement(f.lineno, assertion=True) + except IndexError: + source = None + else: + source = str(source.deindent()).strip() + except py.error.ENOENT: + source = None + # this can also occur during reinterpretation, when the + # co_filename is set to "". + if source: + self.msg = reinterpret(source, f, should_fail=True) + else: + self.msg = "" + if not self.args: + self.args = (self.msg,) + +if sys.version_info > (3, 0): + AssertionError.__module__ = "builtins" + reinterpret_old = "old reinterpretation not available for py3" +else: + from py._code._assertionold import interpret as reinterpret_old +if sys.version_info >= (2, 6) or (sys.platform.startswith("java")): + from py._code._assertionnew import interpret as reinterpret +else: + reinterpret = reinterpret_old + diff --git a/py/_code/code.py b/py/_code/code.py --- a/py/_code/code.py +++ b/py/_code/code.py @@ -145,6 +145,17 @@ return self.frame.f_locals locals = property(getlocals, None, None, "locals of underlaying frame") + def reinterpret(self): + """Reinterpret the failing statement and returns a detailed information + about what operations are performed.""" + if self.exprinfo is None: + source = str(self.statement).strip() + x = py.code._reinterpret(source, self.frame, should_fail=True) + if not isinstance(x, str): + raise TypeError("interpret returned non-string %r" % (x,)) + self.exprinfo = x + return self.exprinfo + def getfirstlinesource(self): # on Jython this firstlineno can be -1 apparently return max(self.frame.code.firstlineno, 0) @@ -158,13 +169,12 @@ end = self.lineno try: _, end = source.getstatementrange(end) - except IndexError: + except (IndexError, ValueError): end = self.lineno + 1 # heuristic to stop displaying source on e.g. # if something: # assume this causes a NameError # # _this_ lines and the one # below we don't want from entry.getsource() - end = min(end, len(source)) for i in range(self.lineno, end): if source[i].rstrip().endswith(':'): end = i + 1 @@ -273,7 +283,11 @@ """ cache = {} for i, entry in enumerate(self): - key = entry.frame.code.path, entry.lineno + # id for the code.raw is needed to work around + # the strange metaprogramming in the decorator lib from pypi + # which generates code objects that have hash/value equality + #XXX needs a test + key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno #print "checking for recursion at", key l = cache.setdefault(key, []) if l: @@ -308,7 +322,7 @@ self._striptext = 'AssertionError: ' self._excinfo = tup self.type, self.value, tb = self._excinfo - self.typename = getattr(self.type, "__name__", "???") + self.typename = self.type.__name__ self.traceback = py.code.Traceback(tb) def __repr__(self): @@ -347,14 +361,16 @@ showlocals: show locals per traceback entry style: long|short|no|native traceback style tbfilter: hide entries (where __tracebackhide__ is true) + + in case of style==native, tbfilter and showlocals is ignored. """ if style == 'native': - import traceback - return ''.join(traceback.format_exception( - self.type, - self.value, - self.traceback[0]._rawentry, - )) + return ReprExceptionInfo(ReprTracebackNative( + py.std.traceback.format_exception( + self.type, + self.value, + self.traceback[0]._rawentry, + )), self._getreprcrash()) fmt = FormattedExcinfo(showlocals=showlocals, style=style, abspath=abspath, tbfilter=tbfilter, funcargs=funcargs) @@ -452,7 +468,7 @@ def repr_locals(self, locals): if self.showlocals: lines = [] - keys = list(locals) + keys = [loc for loc in locals if loc[0] != "@"] keys.sort() for name in keys: value = locals[name] @@ -506,7 +522,10 @@ def _makepath(self, path): if not self.abspath: - np = py.path.local().bestrelpath(path) + try: + np = py.path.local().bestrelpath(path) + except OSError: + return path if len(np) < len(str(path)): path = np return path @@ -595,6 +614,19 @@ if self.extraline: tw.line(self.extraline) +class ReprTracebackNative(ReprTraceback): + def __init__(self, tblines): + self.style = "native" + self.reprentries = [ReprEntryNative(tblines)] + self.extraline = None + +class ReprEntryNative(TerminalRepr): + def __init__(self, tblines): + self.lines = tblines + + def toterminal(self, tw): + tw.write("".join(self.lines)) + class ReprEntry(TerminalRepr): localssep = "_ " @@ -680,19 +712,26 @@ oldbuiltins = {} -def patch_builtins(compile=True): - """ put compile builtins to Python's builtins. """ +def patch_builtins(assertion=True, compile=True): + """ put compile and AssertionError builtins to Python's builtins. """ + if assertion: + from py._code import assertion + l = oldbuiltins.setdefault('AssertionError', []) + l.append(py.builtin.builtins.AssertionError) + py.builtin.builtins.AssertionError = assertion.AssertionError if compile: l = oldbuiltins.setdefault('compile', []) l.append(py.builtin.builtins.compile) py.builtin.builtins.compile = py.code.compile -def unpatch_builtins(compile=True): +def unpatch_builtins(assertion=True, compile=True): """ remove compile and AssertionError builtins from Python builtins. """ + if assertion: + py.builtin.builtins.AssertionError = oldbuiltins['AssertionError'].pop() if compile: py.builtin.builtins.compile = oldbuiltins['compile'].pop() -def getrawcode(obj): +def getrawcode(obj, trycall=True): """ return code object for given function. """ try: return obj.__code__ @@ -701,5 +740,10 @@ obj = getattr(obj, 'func_code', obj) obj = getattr(obj, 'f_code', obj) obj = getattr(obj, '__code__', obj) + if trycall and not hasattr(obj, 'co_firstlineno'): + if hasattr(obj, '__call__') and not py.std.inspect.isclass(obj): + x = getrawcode(obj.__call__, trycall=False) + if hasattr(x, 'co_firstlineno'): + return x return obj diff --git a/py/_code/source.py b/py/_code/source.py --- a/py/_code/source.py +++ b/py/_code/source.py @@ -108,6 +108,7 @@ def getstatementrange(self, lineno, assertion=False): """ return (start, end) tuple which spans the minimal statement region which containing the given lineno. + raise an IndexError if no such statementrange can be found. """ # XXX there must be a better than these heuristic ways ... # XXX there may even be better heuristics :-) @@ -116,6 +117,7 @@ # 1. find the start of the statement from codeop import compile_command + end = None for start in range(lineno, -1, -1): if assertion: line = self.lines[start] @@ -139,7 +141,9 @@ trysource = self[start:end] if trysource.isparseable(): return start, end - return start, len(self) + if end is None: + raise IndexError("no valid source range around line %d " % (lineno,)) + return start, end def getblockend(self, lineno): # XXX @@ -257,23 +261,29 @@ def getfslineno(obj): + """ Return source location (path, lineno) for the given object. + If the source cannot be determined return ("", -1) + """ try: code = py.code.Code(obj) except TypeError: - # fallback to - fn = (py.std.inspect.getsourcefile(obj) or - py.std.inspect.getfile(obj)) + try: + fn = (py.std.inspect.getsourcefile(obj) or + py.std.inspect.getfile(obj)) + except TypeError: + return "", -1 + fspath = fn and py.path.local(fn) or None + lineno = -1 if fspath: try: _, lineno = findsource(obj) except IOError: - lineno = None - else: - lineno = None + pass else: fspath = code.path lineno = code.firstlineno + assert isinstance(lineno, int) return fspath, lineno # @@ -286,7 +296,7 @@ except py.builtin._sysex: raise except: - return None, None + return None, -1 source = Source() source.lines = [line.rstrip() for line in sourcelines] return source, lineno diff --git a/py/_error.py b/py/_error.py --- a/py/_error.py +++ b/py/_error.py @@ -23,6 +23,7 @@ 2: errno.ENOENT, 3: errno.ENOENT, 17: errno.EEXIST, + 13: errno.EBUSY, # empty cd drive, but ENOMEDIUM seems unavailiable 22: errno.ENOTDIR, 267: errno.ENOTDIR, 5: errno.EACCES, # anything better? diff --git a/py/_iniconfig.py b/py/_iniconfig.py --- a/py/_iniconfig.py +++ b/py/_iniconfig.py @@ -103,6 +103,7 @@ def _parseline(self, line, lineno): # comments line = line.split('#')[0].rstrip() + line = line.split(';')[0].rstrip() # blank lines if not line: return None, None diff --git a/py/_io/capture.py b/py/_io/capture.py --- a/py/_io/capture.py +++ b/py/_io/capture.py @@ -12,7 +12,7 @@ class TextIO(StringIO): def write(self, data): if not isinstance(data, unicode): - data = unicode(data, getattr(self, '_encoding', 'UTF-8')) + data = unicode(data, getattr(self, '_encoding', 'UTF-8'), 'replace') StringIO.write(self, data) else: TextIO = StringIO @@ -258,6 +258,9 @@ f = getattr(self, name).tmpfile f.seek(0) res = f.read() + enc = getattr(f, 'encoding', None) + if enc: + res = py.builtin._totext(res, enc, 'replace') f.truncate(0) f.seek(0) l.append(res) diff --git a/py/_io/terminalwriter.py b/py/_io/terminalwriter.py --- a/py/_io/terminalwriter.py +++ b/py/_io/terminalwriter.py @@ -105,6 +105,8 @@ Blue=44, Purple=45, Cyan=46, White=47, bold=1, light=2, blink=5, invert=7) + _newline = None # the last line printed + # XXX deprecate stringio argument def __init__(self, file=None, stringio=False, encoding=None): if file is None: @@ -112,11 +114,9 @@ self.stringio = file = py.io.TextIO() else: file = py.std.sys.stdout - if hasattr(file, 'encoding'): - encoding = file.encoding elif hasattr(file, '__call__'): file = WriteFile(file, encoding=encoding) - self.encoding = encoding + self.encoding = encoding or getattr(file, 'encoding', "utf-8") self._file = file self.fullwidth = get_terminal_width() self.hasmarkup = should_do_markup(file) @@ -182,8 +182,31 @@ return s def line(self, s='', **kw): + if self._newline == False: + self.write("\n") self.write(s, **kw) self.write('\n') + self._newline = True + + def reline(self, line, **opts): + if not self.hasmarkup: + raise ValueError("cannot use rewrite-line without terminal") + if not self._newline: + self.write("\r") + self.write(line, **opts) + # see if we need to fill up some spaces at the end + # xxx have a more exact lastlinelen working from self.write? + lenline = len(line) + try: + lastlen = self._lastlinelen + except AttributeError: + pass + else: + if lenline < lastlen: + self.write(" " * (lastlen - lenline + 1)) + self._lastlinelen = lenline + self._newline = False + class Win32ConsoleWriter(TerminalWriter): def write(self, s, **kw): @@ -280,10 +303,10 @@ SetConsoleTextAttribute = ctypes.windll.kernel32.SetConsoleTextAttribute SetConsoleTextAttribute.argtypes = [wintypes.HANDLE, wintypes.WORD] SetConsoleTextAttribute.restype = wintypes.BOOL - + _GetConsoleScreenBufferInfo = \ ctypes.windll.kernel32.GetConsoleScreenBufferInfo - _GetConsoleScreenBufferInfo.argtypes = [wintypes.HANDLE, + _GetConsoleScreenBufferInfo.argtypes = [wintypes.HANDLE, ctypes.POINTER(CONSOLE_SCREEN_BUFFER_INFO)] _GetConsoleScreenBufferInfo.restype = wintypes.BOOL def GetConsoleInfo(handle): diff --git a/py/_path/common.py b/py/_path/common.py --- a/py/_path/common.py +++ b/py/_path/common.py @@ -64,7 +64,10 @@ else: if bool(value) ^ bool(meth()) ^ invert: return False - except (py.error.ENOENT, py.error.ENOTDIR): + except (py.error.ENOENT, py.error.ENOTDIR, py.error.EBUSY): + # EBUSY feels not entirely correct, + # but its kind of necessary since ENOMEDIUM + # is not accessible in python for name in self._depend_on_existence: if name in kw: if kw.get(name): @@ -368,6 +371,5 @@ else: name = str(path) # path.strpath # XXX svn? pattern = '*' + path.sep + pattern - from fnmatch import fnmatch - return fnmatch(name, pattern) + return py.std.fnmatch.fnmatch(name, pattern) diff --git a/py/_path/local.py b/py/_path/local.py --- a/py/_path/local.py +++ b/py/_path/local.py @@ -157,14 +157,16 @@ return str(self) < str(other) def samefile(self, other): - """ return True if 'other' references the same file as 'self'. """ - if not iswin32: - return py.error.checked_call( - os.path.samefile, str(self), str(other)) + """ return True if 'other' references the same file as 'self'. + """ + if not isinstance(other, py.path.local): + other = os.path.abspath(str(other)) if self == other: return True - other = os.path.abspath(str(other)) - return self == other + if iswin32: + return False # ther is no samefile + return py.error.checked_call( + os.path.samefile, str(self), str(other)) def remove(self, rec=1, ignore_errors=False): """ remove a file or directory (or a directory tree if rec=1). @@ -539,7 +541,11 @@ if self.basename != "__init__.py": modfile = modfile[:-12] - if not self.samefile(modfile): + try: + issame = self.samefile(modfile) + except py.error.ENOENT: + issame = False + if not issame: raise self.ImportMismatchError(modname, modfile, self) return mod else: diff --git a/py/_path/svnurl.py b/py/_path/svnurl.py --- a/py/_path/svnurl.py +++ b/py/_path/svnurl.py @@ -233,6 +233,8 @@ e = sys.exc_info()[1] if e.err.find('non-existent in that revision') != -1: raise py.error.ENOENT(self, e.err) + elif e.err.find("E200009:") != -1: + raise py.error.ENOENT(self, e.err) elif e.err.find('File not found') != -1: raise py.error.ENOENT(self, e.err) elif e.err.find('not part of a repository')!=-1: diff --git a/py/_path/svnwc.py b/py/_path/svnwc.py --- a/py/_path/svnwc.py +++ b/py/_path/svnwc.py @@ -482,10 +482,13 @@ except py.process.cmdexec.Error: e = sys.exc_info()[1] strerr = e.err.lower() - if strerr.find('file not found') != -1: + if strerr.find('not found') != -1: + raise py.error.ENOENT(self) + elif strerr.find("E200009:") != -1: raise py.error.ENOENT(self) if (strerr.find('file exists') != -1 or strerr.find('file already exists') != -1 or + strerr.find('w150002:') != -1 or strerr.find("can't create directory") != -1): raise py.error.EEXIST(self) raise @@ -593,7 +596,7 @@ out = self._authsvn('lock').strip() if not out: # warning or error, raise exception - raise Exception(out[4:]) + raise ValueError("unknown error in svn lock command") def unlock(self): """ unset a previously set lock """ @@ -1066,6 +1069,8 @@ modrev = '?' author = '?' date = '' + elif itemstatus == "replaced": + pass else: #print entryel.toxml() commitel = entryel.getElementsByTagName('commit')[0] @@ -1148,7 +1153,11 @@ raise ValueError("Not a versioned resource") #raise ValueError, "Not a versioned resource %r" % path self.kind = d['nodekind'] == 'directory' and 'dir' or d['nodekind'] - self.rev = int(d['revision']) + try: + self.rev = int(d['revision']) + except KeyError: + self.rev = None + self.path = py.path.local(d['path']) self.size = self.path.size() if 'lastchangedrev' in d: diff --git a/py/_xmlgen.py b/py/_xmlgen.py --- a/py/_xmlgen.py +++ b/py/_xmlgen.py @@ -52,7 +52,7 @@ def unicode(self, indent=2): l = [] SimpleUnicodeVisitor(l.append, indent).visit(self) - return "".join(l) + return u("").join(l) def __repr__(self): name = self.__class__.__name__ @@ -122,11 +122,13 @@ if visitmethod is not None: break else: - visitmethod = self.object + visitmethod = self.__object self.cache[cls] = visitmethod visitmethod(node) - def object(self, obj): + # the default fallback handler is marked private + # to avoid clashes with the tag name object + def __object(self, obj): #self.write(obj) self.write(escape(unicode(obj))) @@ -136,7 +138,8 @@ def list(self, obj): assert id(obj) not in self.visited self.visited[id(obj)] = 1 - map(self.visit, obj) + for elem in obj: + self.visit(elem) def Tag(self, tag): assert id(tag) not in self.visited @@ -181,7 +184,11 @@ value = getattr(attrs, name) if name.endswith('_'): name = name[:-1] - return ' %s="%s"' % (name, escape(unicode(value))) + if isinstance(value, raw): + insert = value.uniobj + else: + insert = escape(unicode(value)) + return ' %s="%s"' % (name, insert) def getstyle(self, tag): """ return attribute list suitable for styling. """ diff --git a/py/bin/_findpy.py b/py/bin/_findpy.py deleted file mode 100644 --- a/py/bin/_findpy.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python - -# -# find and import a version of 'py' -# -import sys -import os -from os.path import dirname as opd, exists, join, basename, abspath - -def searchpy(current): - while 1: - last = current - initpy = join(current, '__init__.py') - if not exists(initpy): - pydir = join(current, 'py') - # recognize py-package and ensure it is importable - if exists(pydir) and exists(join(pydir, '__init__.py')): - #for p in sys.path: - # if p == current: - # return True - if current != sys.path[0]: # if we are already first, then ok - sys.stderr.write("inserting into sys.path: %s\n" % current) - sys.path.insert(0, current) - return True - current = opd(current) - if last == current: - return False - -if not searchpy(abspath(os.curdir)): - if not searchpy(opd(abspath(sys.argv[0]))): - if not searchpy(opd(__file__)): - pass # let's hope it is just on sys.path - -import py -import pytest - -if __name__ == '__main__': - print ("py lib is at %s" % py.__file__) diff --git a/py/bin/py.test b/py/bin/py.test deleted file mode 100755 --- a/py/bin/py.test +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env python -from _findpy import pytest -raise SystemExit(pytest.main()) diff --git a/pypy/module/pyexpat/test/__init__.py b/pypy/module/pyexpat/test/__init__.py new file mode 100644 diff --git a/pypy/pytest.ini b/pypy/pytest.ini --- a/pypy/pytest.ini +++ b/pypy/pytest.ini @@ -1,2 +1,2 @@ [pytest] -addopts = --assertmode=old -rf +addopts = --assert=plain -rf diff --git a/pypy/test_all.py b/pypy/test_all.py old mode 100755 new mode 100644 --- a/pypy/test_all.py +++ b/pypy/test_all.py @@ -11,11 +11,12 @@ """ import sys, os -if len(sys.argv) == 1 and os.path.dirname(sys.argv[0]) in '.': - print >> sys.stderr, __doc__ - sys.exit(2) if __name__ == '__main__': + if len(sys.argv) == 1 and os.path.dirname(sys.argv[0]) in '.': + print >> sys.stderr, __doc__ + sys.exit(2) + import tool.autopath import pytest import pytest_cov diff --git a/pypy/tool/jitlogparser/test/__init__.py b/pypy/tool/jitlogparser/test/__init__.py new file mode 100644 diff --git a/pypy/tool/pytest/test/test_pytestsupport.py b/pypy/tool/pytest/test/test_pytestsupport.py --- a/pypy/tool/pytest/test/test_pytestsupport.py +++ b/pypy/tool/pytest/test/test_pytestsupport.py @@ -165,7 +165,10 @@ def test_one(self): exec 'blow' """) - ev, = sorter.getreports("pytest_runtest_logreport") + reports = sorter.getreports("pytest_runtest_logreport") + setup, ev, teardown = reports assert ev.failed + assert setup.passed + assert teardown.passed assert 'NameError' in ev.longrepr.reprcrash.message assert 'blow' in ev.longrepr.reprcrash.message diff --git a/testrunner/runner.py b/testrunner/runner.py --- a/testrunner/runner.py +++ b/testrunner/runner.py @@ -110,7 +110,10 @@ do_dry_run=False, timeout=None, _win32=(sys.platform=='win32')): args = interp + test_driver - args += ['-p', 'resultlog', '--resultlog=%s' % logfname, test] + args += ['-p', 'resultlog', + '--resultlog=%s' % logfname, + '--junitxml=%s.junit' % logfname, + test] args = map(str, args) interp0 = args[0] diff --git a/testrunner/scratchbox_runner.py b/testrunner/scratchbox_runner.py --- a/testrunner/scratchbox_runner.py +++ b/testrunner/scratchbox_runner.py @@ -14,14 +14,14 @@ def dry_run_scratchbox(args, cwd, out, timeout=None): return dry_run(args_for_scratchbox(cwd, args), cwd, out, timeout) -import runner -# XXX hack hack hack -dry_run = runner.dry_run -run = runner.run +if __name__ == '__main__': + import runner + # XXX hack hack hack + dry_run = runner.dry_run + run = runner.run -runner.dry_run = dry_run_scratchbox -runner.run = run_scratchbox + runner.dry_run = dry_run_scratchbox + runner.run = run_scratchbox -if __name__ == '__main__': import sys runner.main(sys.argv) diff --git a/testrunner/test/conftest.py b/testrunner/test/conftest.py new file mode 100644 --- /dev/null +++ b/testrunner/test/conftest.py @@ -0,0 +1,6 @@ + +def pytest_runtest_makereport(__multicall__, item): + report = __multicall__.execute() + if 'out' in item.funcargs: + report.sections.append(('out', item.funcargs['out'].read())) + return report diff --git a/testrunner/test/test_runner.py b/testrunner/test/test_runner.py --- a/testrunner/test/test_runner.py +++ b/testrunner/test/test_runner.py @@ -53,49 +53,44 @@ assert not should_report_failure("F Def\n. Ghi\n. Jkl\n") + class TestRunHelper(object): + def pytest_funcarg__out(self, request): + tmpdir = request.getfuncargvalue('tmpdir') + return tmpdir.ensure('out') - def setup_method(self, meth): - h, self.fn = tempfile.mkstemp() - os.close(h) + def test_run(self, out): + res = runner.run([sys.executable, "-c", "print 42"], '.', out) + assert res == 0 + assert out.read() == "42\n" - def teardown_method(self, meth): - os.unlink(self.fn) - - def test_run(self): - res = runner.run([sys.executable, "-c", "print 42"], '.', - py.path.local(self.fn)) - assert res == 0 - out = py.path.local(self.fn).read('r') - assert out == "42\n" - - def test_error(self): - res = runner.run([sys.executable, "-c", "import sys; sys.exit(3)"], '.', py.path.local(self.fn)) + def test_error(self, out): + res = runner.run([sys.executable, "-c", "import sys; sys.exit(3)"], '.', out) assert res == 3 - def test_signal(self): + def test_signal(self, out): if sys.platform == 'win32': py.test.skip("no death by signal on windows") - res = runner.run([sys.executable, "-c", "import os; os.kill(os.getpid(), 9)"], '.', py.path.local(self.fn)) + res = runner.run([sys.executable, "-c", "import os; os.kill(os.getpid(), 9)"], '.', out) assert res == -9 - def test_timeout(self): - res = runner.run([sys.executable, "-c", "while True: pass"], '.', py.path.local(self.fn), timeout=3) + def test_timeout(self, out): + res = runner.run([sys.executable, "-c", "while True: pass"], '.', out, timeout=3) assert res == -999 - def test_timeout_lock(self): - res = runner.run([sys.executable, "-c", "import threading; l=threading.Lock(); l.acquire(); l.acquire()"], '.', py.path.local(self.fn), timeout=3) + def test_timeout_lock(self, out): + res = runner.run([sys.executable, "-c", "import threading; l=threading.Lock(); l.acquire(); l.acquire()"], '.', out, timeout=3) assert res == -999 - def test_timeout_syscall(self): - res = runner.run([sys.executable, "-c", "import socket; s=s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); s.bind(('', 0)); s.recv(1000)"], '.', py.path.local(self.fn), timeout=3) + def test_timeout_syscall(self, out): + res = runner.run([sys.executable, "-c", "import socket; s=s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); s.bind(('', 0)); s.recv(1000)"], '.', out, timeout=3) assert res == -999 - def test_timeout_success(self): + def test_timeout_success(self, out): res = runner.run([sys.executable, "-c", "print 42"], '.', - py.path.local(self.fn), timeout=2) + out, timeout=2) assert res == 0 - out = py.path.local(self.fn).read('r') + out = out.read() assert out == "42\n" @@ -122,7 +117,10 @@ expected = ['INTERP', 'IARG', 'driver', 'darg', + '-p', 'resultlog', '--resultlog=LOGFILE', + '--junitxml=LOGFILE.junit', + 'test_one'] assert self.called == (expected, '/wd', 'out', 'secs') @@ -138,9 +136,11 @@ expected = ['/wd' + os.sep + './INTERP', 'IARG', 'driver', 'darg', + '-p', 'resultlog', '--resultlog=LOGFILE', + '--junitxml=LOGFILE.junit', 'test_one'] - + assert self.called[0] == expected assert self.called == (expected, '/wd', 'out', 'secs') assert res == 0 @@ -251,7 +251,7 @@ assert '\n' in log log_lines = log.splitlines() - assert log_lines[0] == ". test_normal/test_example.py:test_one" + assert ". test_normal/test_example.py::test_one" in log_lines nfailures = 0 noutcomes = 0 for line in log_lines: From noreply at buildbot.pypy.org Mon Apr 9 20:34:19 2012 From: noreply at buildbot.pypy.org (fijal) Date: Mon, 9 Apr 2012 20:34:19 +0200 (CEST) Subject: [pypy-commit] benchmarks default: yet another necessary fix (this is getting obscure) Message-ID: <20120409183419.A1F6F46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Maciej Fijalkowski Branch: Changeset: r180:9946cdcf97a2 Date: 2012-04-09 20:34 +0200 http://bitbucket.org/pypy/benchmarks/changeset/9946cdcf97a2/ Log: yet another necessary fix (this is getting obscure) diff --git a/lib/pypy/pypy/interpreter/astcompiler/assemble.py b/lib/pypy/pypy/interpreter/astcompiler/assemble.py --- a/lib/pypy/pypy/interpreter/astcompiler/assemble.py +++ b/lib/pypy/pypy/interpreter/astcompiler/assemble.py @@ -610,6 +610,8 @@ ops.JUMP_IF_FALSE_OR_POP : 0, ops.POP_JUMP_IF_TRUE : -1, ops.POP_JUMP_IF_FALSE : -1, + + ops.BUILD_LIST_FROM_ARG: 1, } From noreply at buildbot.pypy.org Mon Apr 9 20:54:37 2012 From: noreply at buildbot.pypy.org (amauryfa) Date: Mon, 9 Apr 2012 20:54:37 +0200 (CEST) Subject: [pypy-commit] pypy default: Fix self translation Message-ID: <20120409185437.118DE46E0EF@wyvern.cs.uni-duesseldorf.de> Author: Amaury Forgeot d'Arc Branch: Changeset: r54270:41c799d11717 Date: 2012-04-09 20:53 +0200 http://bitbucket.org/pypy/pypy/changeset/41c799d11717/ Log: Fix self translation diff --git a/pypy/objspace/flow/flowcontext.py b/pypy/objspace/flow/flowcontext.py --- a/pypy/objspace/flow/flowcontext.py +++ b/pypy/objspace/flow/flowcontext.py @@ -434,6 +434,13 @@ self.lastblock = block self.pushvalue(w_result) + def BUILD_LIST_FROM_ARG(self, _, next_instr): + # This opcode was added with pypy-1.8. Here is a simpler + # version, enough for annotation. + last_val = self.popvalue() + self.pushvalue(self.space.newlist([])) + self.pushvalue(last_val) + # XXX Unimplemented 2.7 opcodes ---------------- # Set literals, set comprehensions From noreply at buildbot.pypy.org Tue Apr 10 00:02:15 2012 From: noreply at buildbot.pypy.org (alex_gaynor) Date: Tue, 10 Apr 2012 00:02:15 +0200 (CEST) Subject: [pypy-commit] pypy default: a failing test for using specialize.memo and specialize.arg together Message-ID: <20120409220215.07FA146E0EF@wyvern.cs.uni-duesseldorf.de> Author: Alex Gaynor Branch: Changeset: r54271:09e76fd5aa32 Date: 2012-04-09 18:01 -0400 http://bitbucket.org/pypy/pypy/changeset/09e76fd5aa32/ Log: a failing test for using specialize.memo and specialize.arg together diff --git a/pypy/annotation/test/test_annrpython.py b/pypy/annotation/test/test_annrpython.py --- a/pypy/annotation/test/test_annrpython.py +++ b/pypy/annotation/test/test_annrpython.py @@ -483,7 +483,7 @@ return a_str.strip(' ') elif n == 1: return a_str.rstrip(' ') - else: + else: return a_str.lstrip(' ') s = a.build_types(f, [int, annmodel.SomeString(no_nul=True)]) assert s.no_nul @@ -3737,6 +3737,25 @@ s = a.build_types(f, [int]) assert s.listdef.listitem.range_step == 0 + def test_specialize_arg_memo(self): + @objectmodel.specialize.memo() + def g(n): + return n + @objectmodel.specialize.arg(0) + def f(i): + return g(i) + def main(i): + if i == 2: + return f(i) + elif i == 3: + return f(i) + else: + raise NotImplementedError + + a = self.RPythonAnnotator() + s = a.build_types(f, [int]) + assert isinstance(s, SomeInteger) + def g(n): return [0,1,2,n] From noreply at buildbot.pypy.org Tue Apr 10 00:36:51 2012 From: noreply at buildbot.pypy.org (alex_gaynor) Date: Tue, 10 Apr 2012 00:36:51 +0200 (CEST) Subject: [pypy-commit] pypy default: correc the test, still fails (amaury) Message-ID: <20120409223651.A8F7346E0EF@wyvern.cs.uni-duesseldorf.de> Author: Alex Gaynor Branch: Changeset: r54272:533a81aa5113 Date: 2012-04-09 18:36 -0400 http://bitbucket.org/pypy/pypy/changeset/533a81aa5113/ Log: correc the test, still fails (amaury) diff --git a/pypy/annotation/test/test_annrpython.py b/pypy/annotation/test/test_annrpython.py --- a/pypy/annotation/test/test_annrpython.py +++ b/pypy/annotation/test/test_annrpython.py @@ -3753,7 +3753,7 @@ raise NotImplementedError a = self.RPythonAnnotator() - s = a.build_types(f, [int]) + s = a.build_types(main, [int]) assert isinstance(s, SomeInteger) From noreply at buildbot.pypy.org Tue Apr 10 01:22:21 2012 From: noreply at buildbot.pypy.org (alex_gaynor) Date: Tue, 10 Apr 2012 01:22:21 +0200 (CEST) Subject: [pypy-commit] pypy default: fix nameerror Message-ID: <20120409232221.16B6346E0EF@wyvern.cs.uni-duesseldorf.de> Author: Alex Gaynor Branch: Changeset: r54273:124c48df1aab Date: 2012-04-09 19:22 -0400 http://bitbucket.org/pypy/pypy/changeset/124c48df1aab/ Log: fix nameerror diff --git a/pypy/annotation/test/test_annrpython.py b/pypy/annotation/test/test_annrpython.py --- a/pypy/annotation/test/test_annrpython.py +++ b/pypy/annotation/test/test_annrpython.py @@ -3754,7 +3754,7 @@ a = self.RPythonAnnotator() s = a.build_types(main, [int]) - assert isinstance(s, SomeInteger) + assert isinstance(s, annmodel.SomeInteger) def g(n): From notifications-noreply at bitbucket.org Tue Apr 10 16:22:51 2012 From: notifications-noreply at bitbucket.org (Bitbucket) Date: Tue, 10 Apr 2012 14:22:51 -0000 Subject: [pypy-commit] Notification: benchmarks-pypy-phd Message-ID: <20120410142251.8915.48112@bitbucket12.managed.contegix.com> You have received a notification from Carl Friedrich Bolz. Hi, I forked benchmarks. My fork is at https://bitbucket.org/cfbolz/benchmarks-pypy-phd. -- Disable notifications at https://bitbucket.org/account/notifications/ From noreply at buildbot.pypy.org Tue Apr 10 17:57:15 2012 From: noreply at buildbot.pypy.org (antocuni) Date: Tue, 10 Apr 2012 17:57:15 +0200 (CEST) Subject: [pypy-commit] pypy default: re-add (again) these files, they are needed by buildbot (I think). See also e12baeb2c897 Message-ID: <20120410155715.6FF9582111@wyvern.cs.uni-duesseldorf.de> Author: Antonio Cuni Branch: Changeset: r54274:321cc342e9f6 Date: 2012-04-10 17:56 +0200 http://bitbucket.org/pypy/pypy/changeset/321cc342e9f6/ Log: re-add (again) these files, they are needed by buildbot (I think). See also e12baeb2c897 diff --git a/py/bin/_findpy.py b/py/bin/_findpy.py new file mode 100644 --- /dev/null +++ b/py/bin/_findpy.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +# +# find and import a version of 'py' +# +import sys +import os +from os.path import dirname as opd, exists, join, basename, abspath + +def searchpy(current): + while 1: + last = current + initpy = join(current, '__init__.py') + if not exists(initpy): + pydir = join(current, 'py') + # recognize py-package and ensure it is importable + if exists(pydir) and exists(join(pydir, '__init__.py')): + #for p in sys.path: + # if p == current: + # return True + if current != sys.path[0]: # if we are already first, then ok + sys.stderr.write("inserting into sys.path: %s\n" % current) + sys.path.insert(0, current) + return True + current = opd(current) + if last == current: + return False + +if not searchpy(abspath(os.curdir)): + if not searchpy(opd(abspath(sys.argv[0]))): + if not searchpy(opd(__file__)): + pass # let's hope it is just on sys.path + +import py +import pytest + +if __name__ == '__main__': + print ("py lib is at %s" % py.__file__) diff --git a/py/bin/py.test b/py/bin/py.test new file mode 100755 --- /dev/null +++ b/py/bin/py.test @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from _findpy import pytest +raise SystemExit(pytest.main()) From noreply at buildbot.pypy.org Tue Apr 10 18:16:38 2012 From: noreply at buildbot.pypy.org (antocuni) Date: Tue, 10 Apr 2012 18:16:38 +0200 (CEST) Subject: [pypy-commit] pypy default: add a point to how-to-release Message-ID: <20120410161638.5371082111@wyvern.cs.uni-duesseldorf.de> Author: Antonio Cuni Branch: Changeset: r54275:658930dda580 Date: 2012-04-10 18:16 +0200 http://bitbucket.org/pypy/pypy/changeset/658930dda580/ Log: add a point to how-to-release diff --git a/pypy/doc/how-to-release.rst b/pypy/doc/how-to-release.rst --- a/pypy/doc/how-to-release.rst +++ b/pypy/doc/how-to-release.rst @@ -37,6 +37,9 @@ no JIT: windows, linux, os/x sandbox: linux, os/x +* create source tarball/zip. Make sure that the directory inside them is + pypy-1.x and not pypy-pypy-revision + * write release announcement pypy/doc/release-x.y(.z).txt the release announcement should contain a direct link to the download page * update pypy.org (under extradoc/pypy.org), rebuild and commit From noreply at buildbot.pypy.org Tue Apr 10 20:22:57 2012 From: noreply at buildbot.pypy.org (arigo) Date: Tue, 10 Apr 2012 20:22:57 +0200 (CEST) Subject: [pypy-commit] pypy.org extradoc: regen Message-ID: <20120410182257.7146682F4F@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: extradoc Changeset: r353:64eeffac819c Date: 2012-04-10 20:22 +0200 http://bitbucket.org/pypy/pypy.org/changeset/64eeffac819c/ Log: regen diff --git a/download.html b/download.html --- a/download.html +++ b/download.html @@ -206,12 +206,14 @@ 1af8ee722721e9f5fd06b61af530ecb3 pypy-1.8-win32.zip 2c9f0054f3b93a6473f10be35277825a pypy-1.8-sandbox-linux64.tar.bz2 009c970b5fa75754ae4c32a5d108a8d4 pypy-1.8-sandbox-linux.tar.bz2 +661be06978fdc907d84f0ee1f1228c8b release-1.8.tar.bz2 (source) a6bb7b277d5186385fd09b71ec4e35c9e93b380d pypy-1.8-linux64.tar.bz2 089f4269a6079da2eabdeabd614f668f56c4121a pypy-1.8-linux.tar.bz2 15b99f780b9714e3ebd82b2e41577afab232d148 pypy-1.8-osx64.tar.bz2 77a565b1cfa4874a0079c17edd1b458b20e67bfd pypy-1.8-win32.zip 895aaf7bba5787dd30adda5cc0e0e7fc297c0ca7 pypy-1.8-sandbox-linux64.tar.bz2 be94460bed8b2682880495435c309b6611ae2c31 pypy-1.8-sandbox-linux.tar.bz2 +4ff684619671d6076879eff88343184d7656c699 release-1.8.tar.bz2 (source)
From noreply at buildbot.pypy.org Tue Apr 10 20:22:56 2012 From: noreply at buildbot.pypy.org (arigo) Date: Tue, 10 Apr 2012 20:22:56 +0200 (CEST) Subject: [pypy-commit] pypy.org extradoc: As usual, I don't get it, but it seems to make people more happy if I Message-ID: <20120410182256.4202B82F4E@wyvern.cs.uni-duesseldorf.de> Author: Armin Rigo Branch: extradoc Changeset: r352:5f5ac20464be Date: 2012-04-10 20:22 +0200 http://bitbucket.org/pypy/pypy.org/changeset/5f5ac20464be/ Log: As usual, I don't get it, but it seems to make people more happy if I download myself this file from the URL indicated, compute the checksums, and post it here. diff --git a/source/download.txt b/source/download.txt --- a/source/download.txt +++ b/source/download.txt @@ -214,6 +214,7 @@ 1af8ee722721e9f5fd06b61af530ecb3 pypy-1.8-win32.zip 2c9f0054f3b93a6473f10be35277825a pypy-1.8-sandbox-linux64.tar.bz2 009c970b5fa75754ae4c32a5d108a8d4 pypy-1.8-sandbox-linux.tar.bz2 + 661be06978fdc907d84f0ee1f1228c8b release-1.8.tar.bz2 (source) a6bb7b277d5186385fd09b71ec4e35c9e93b380d pypy-1.8-linux64.tar.bz2 089f4269a6079da2eabdeabd614f668f56c4121a pypy-1.8-linux.tar.bz2 @@ -221,3 +222,4 @@ 77a565b1cfa4874a0079c17edd1b458b20e67bfd pypy-1.8-win32.zip 895aaf7bba5787dd30adda5cc0e0e7fc297c0ca7 pypy-1.8-sandbox-linux64.tar.bz2 be94460bed8b2682880495435c309b6611ae2c31 pypy-1.8-sandbox-linux.tar.bz2 + 4ff684619671d6076879eff88343184d7656c699 release-1.8.tar.bz2 (source) From noreply at buildbot.pypy.org Tue Apr 10 21:59:20 2012 From: noreply at buildbot.pypy.org (amauryfa) Date: Tue, 10 Apr 2012 21:59:20 +0200 (CEST) Subject: [pypy-commit] pypy default: cpyext: Add PyUnicode_Count Message-ID: <20120410195920.2552382F4E@wyvern.cs.uni-duesseldorf.de> Author: Amaury Forgeot d'Arc Branch: Changeset: r54276:b1e988d5a3f5 Date: 2012-04-10 21:58 +0200 http://bitbucket.org/pypy/pypy/changeset/b1e988d5a3f5/ Log: cpyext: Add PyUnicode_Count diff --git a/pypy/module/cpyext/stubs.py b/pypy/module/cpyext/stubs.py --- a/pypy/module/cpyext/stubs.py +++ b/pypy/module/cpyext/stubs.py @@ -2300,16 +2300,6 @@ systems.""" raise NotImplementedError - at cpython_api([PyObject, PyObject, Py_ssize_t, Py_ssize_t], Py_ssize_t, error=-1) -def PyUnicode_Count(space, str, substr, start, end): - """Return the number of non-overlapping occurrences of substr in - str[start:end]. Return -1 if an error occurred. - - This function returned an int type and used an int - type for start and end. This might require changes in your code for - properly supporting 64-bit systems.""" - raise NotImplementedError - @cpython_api([PyObject, PyObject, rffi.INT_real], PyObject) def PyUnicode_RichCompare(space, left, right, op): """Rich compare two unicode strings and return one of the following: diff --git a/pypy/module/cpyext/test/test_unicodeobject.py b/pypy/module/cpyext/test/test_unicodeobject.py --- a/pypy/module/cpyext/test/test_unicodeobject.py +++ b/pypy/module/cpyext/test/test_unicodeobject.py @@ -457,3 +457,9 @@ assert api.PyUnicode_Tailmatch(w_str, space.wrap("cde"), 1, 5, -1) == 1 self.raises(space, api, TypeError, api.PyUnicode_Tailmatch, w_str, space.wrap(3), 2, 10, 1) + + def test_count(self, space, api): + w_str = space.wrap(u"abcabdab") + assert api.PyUnicode_Count(w_str, space.wrap("ab"), 0, -1) == 2 + assert api.PyUnicode_Count(w_str, space.wrap("ab"), 0, 2) == 1 + assert api.PyUnicode_Count(w_str, space.wrap("ab"), -5, 30) == 2 diff --git a/pypy/module/cpyext/unicodeobject.py b/pypy/module/cpyext/unicodeobject.py --- a/pypy/module/cpyext/unicodeobject.py +++ b/pypy/module/cpyext/unicodeobject.py @@ -598,3 +598,10 @@ else: return stringtype.stringendswith(str, substr, start, end) + at cpython_api([PyObject, PyObject, Py_ssize_t, Py_ssize_t], Py_ssize_t, error=-1) +def PyUnicode_Count(space, w_str, w_substr, start, end): + """Return the number of non-overlapping occurrences of substr in + str[start:end]. Return -1 if an error occurred.""" + w_count = space.call_method(w_str, "count", w_substr, + space.wrap(start), space.wrap(end)) + return space.int_w(w_count) From noreply at buildbot.pypy.org Tue Apr 10 23:24:53 2012 From: noreply at buildbot.pypy.org (fijal) Date: Tue, 10 Apr 2012 23:24:53 +0200 (CEST) Subject: [pypy-commit] pypy default: We don't do that. Those are created automatically from tags. I'm unsure how Message-ID: <20120410212453.C24F782F4E@wyvern.cs.uni-duesseldorf.de> Author: Maciej Fijalkowski Branch: Changeset: r54277:88c488265ad9 Date: 2012-04-10 23:24 +0200 http://bitbucket.org/pypy/pypy/changeset/88c488265ad9/ Log: We don't do that. Those are created automatically from tags. I'm unsure how to make sure they're in a well-named directories. diff --git a/pypy/doc/how-to-release.rst b/pypy/doc/how-to-release.rst --- a/pypy/doc/how-to-release.rst +++ b/pypy/doc/how-to-release.rst @@ -37,9 +37,6 @@ no JIT: windows, linux, os/x sandbox: linux, os/x -* create source tarball/zip. Make sure that the directory inside them is - pypy-1.x and not pypy-pypy-revision - * write release announcement pypy/doc/release-x.y(.z).txt the release announcement should contain a direct link to the download page * update pypy.org (under extradoc/pypy.org), rebuild and commit From noreply at buildbot.pypy.org Tue Apr 10 23:55:14 2012 From: noreply at buildbot.pypy.org (amauryfa) Date: Tue, 10 Apr 2012 23:55:14 +0200 (CEST) Subject: [pypy-commit] pypy default: cpyext: Add PyUnicode_Find Message-ID: <20120410215514.C247B82F4E@wyvern.cs.uni-duesseldorf.de> Author: Amaury Forgeot d'Arc Branch: Changeset: r54278:10ee763df5ba Date: 2012-04-10 22:42 +0200 http://bitbucket.org/pypy/pypy/changeset/10ee763df5ba/ Log: cpyext: Add PyUnicode_Find diff --git a/pypy/module/cpyext/stubs.py b/pypy/module/cpyext/stubs.py --- a/pypy/module/cpyext/stubs.py +++ b/pypy/module/cpyext/stubs.py @@ -2287,19 +2287,6 @@ use the default error handling.""" raise NotImplementedError - at cpython_api([PyObject, PyObject, Py_ssize_t, Py_ssize_t, rffi.INT_real], Py_ssize_t, error=-2) -def PyUnicode_Find(space, str, substr, start, end, direction): - """Return the first position of substr in str*[*start:end] using the given - direction (direction == 1 means to do a forward search, direction == -1 a - backward search). The return value is the index of the first match; a value of - -1 indicates that no match was found, and -2 indicates that an error - occurred and an exception has been set. - - This function used an int type for start and end. This - might require changes in your code for properly supporting 64-bit - systems.""" - raise NotImplementedError - @cpython_api([PyObject, PyObject, rffi.INT_real], PyObject) def PyUnicode_RichCompare(space, left, right, op): """Rich compare two unicode strings and return one of the following: diff --git a/pypy/module/cpyext/test/test_unicodeobject.py b/pypy/module/cpyext/test/test_unicodeobject.py --- a/pypy/module/cpyext/test/test_unicodeobject.py +++ b/pypy/module/cpyext/test/test_unicodeobject.py @@ -460,6 +460,15 @@ def test_count(self, space, api): w_str = space.wrap(u"abcabdab") - assert api.PyUnicode_Count(w_str, space.wrap("ab"), 0, -1) == 2 - assert api.PyUnicode_Count(w_str, space.wrap("ab"), 0, 2) == 1 - assert api.PyUnicode_Count(w_str, space.wrap("ab"), -5, 30) == 2 + assert api.PyUnicode_Count(w_str, space.wrap(u"ab"), 0, -1) == 2 + assert api.PyUnicode_Count(w_str, space.wrap(u"ab"), 0, 2) == 1 + assert api.PyUnicode_Count(w_str, space.wrap(u"ab"), -5, 30) == 2 + + def test_find(self, space, api): + w_str = space.wrap(u"abcabcd") + assert api.PyUnicode_Find(w_str, space.wrap(u"c"), 0, 7, 1) == 2 + assert api.PyUnicode_Find(w_str, space.wrap(u"c"), 3, 7, 1) == 5 + assert api.PyUnicode_Find(w_str, space.wrap(u"c"), 0, 7, -1) == 5 + assert api.PyUnicode_Find(w_str, space.wrap(u"c"), 3, 7, -1) == 5 + assert api.PyUnicode_Find(w_str, space.wrap(u"c"), 0, 4, -1) == 2 + assert api.PyUnicode_Find(w_str, space.wrap(u"z"), 0, 4, -1) == -1 diff --git a/pypy/module/cpyext/unicodeobject.py b/pypy/module/cpyext/unicodeobject.py --- a/pypy/module/cpyext/unicodeobject.py +++ b/pypy/module/cpyext/unicodeobject.py @@ -605,3 +605,20 @@ w_count = space.call_method(w_str, "count", w_substr, space.wrap(start), space.wrap(end)) return space.int_w(w_count) + + at cpython_api([PyObject, PyObject, Py_ssize_t, Py_ssize_t, rffi.INT_real], + Py_ssize_t, error=-2) +def PyUnicode_Find(space, w_str, w_substr, start, end, direction): + """Return the first position of substr in str*[*start:end] using + the given direction (direction == 1 means to do a forward search, + direction == -1 a backward search). The return value is the index + of the first match; a value of -1 indicates that no match was + found, and -2 indicates that an error occurred and an exception + has been set.""" + if rffi.cast(lltype.Signed, direction) > 0: + w_pos = space.call_method(w_str, "find", w_substr, + space.wrap(start), space.wrap(end)) + else: + w_pos = space.call_method(w_str, "rfind", w_substr, + space.wrap(start), space.wrap(end)) + return space.int_w(w_pos) From noreply at buildbot.pypy.org Tue Apr 10 23:55:16 2012 From: noreply at buildbot.pypy.org (amauryfa) Date: Tue, 10 Apr 2012 23:55:16 +0200 (CEST) Subject: [pypy-commit] pypy default: cpyext: implement PyUnicode_Split and PyUnicode_Splitlines Message-ID: <20120410215516.1213782F4E@wyvern.cs.uni-duesseldorf.de> Author: Amaury Forgeot d'Arc Branch: Changeset: r54279:859f1579f2bd Date: 2012-04-10 23:19 +0200 http://bitbucket.org/pypy/pypy/changeset/859f1579f2bd/ Log: cpyext: implement PyUnicode_Split and PyUnicode_Splitlines diff --git a/pypy/module/cpyext/stubs.py b/pypy/module/cpyext/stubs.py --- a/pypy/module/cpyext/stubs.py +++ b/pypy/module/cpyext/stubs.py @@ -2253,24 +2253,6 @@ """Concat two strings giving a new Unicode string.""" raise NotImplementedError - at cpython_api([PyObject, PyObject, Py_ssize_t], PyObject) -def PyUnicode_Split(space, s, sep, maxsplit): - """Split a string giving a list of Unicode strings. If sep is NULL, splitting - will be done at all whitespace substrings. Otherwise, splits occur at the given - separator. At most maxsplit splits will be done. If negative, no limit is - set. Separators are not included in the resulting list. - - This function used an int type for maxsplit. This might require - changes in your code for properly supporting 64-bit systems.""" - raise NotImplementedError - - at cpython_api([PyObject, rffi.INT_real], PyObject) -def PyUnicode_Splitlines(space, s, keepend): - """Split a Unicode string at line breaks, returning a list of Unicode strings. - CRLF is considered to be one line break. If keepend is 0, the Line break - characters are not included in the resulting strings.""" - raise NotImplementedError - @cpython_api([PyObject, PyObject, rffi.CCHARP], PyObject) def PyUnicode_Translate(space, str, table, errors): """Translate a string by applying a character mapping table to it and return the diff --git a/pypy/module/cpyext/test/test_unicodeobject.py b/pypy/module/cpyext/test/test_unicodeobject.py --- a/pypy/module/cpyext/test/test_unicodeobject.py +++ b/pypy/module/cpyext/test/test_unicodeobject.py @@ -472,3 +472,14 @@ assert api.PyUnicode_Find(w_str, space.wrap(u"c"), 3, 7, -1) == 5 assert api.PyUnicode_Find(w_str, space.wrap(u"c"), 0, 4, -1) == 2 assert api.PyUnicode_Find(w_str, space.wrap(u"z"), 0, 4, -1) == -1 + + def test_split(self, space, api): + w_str = space.wrap(u"a\nb\nc\nd") + assert "[u'a', u'b', u'c', u'd']" == space.unwrap(space.repr( + api.PyUnicode_Split(w_str, space.wrap('\n'), -1))) + assert r"[u'a', u'b', u'c\nd']" == space.unwrap(space.repr( + api.PyUnicode_Split(w_str, space.wrap('\n'), 2))) + assert "[u'a', u'b', u'c', u'd']" == space.unwrap(space.repr( + api.PyUnicode_Splitlines(w_str, 0))) + assert r"[u'a\n', u'b\n', u'c\n', u'd']" == space.unwrap(space.repr( + api.PyUnicode_Splitlines(w_str, 1))) diff --git a/pypy/module/cpyext/unicodeobject.py b/pypy/module/cpyext/unicodeobject.py --- a/pypy/module/cpyext/unicodeobject.py +++ b/pypy/module/cpyext/unicodeobject.py @@ -622,3 +622,20 @@ w_pos = space.call_method(w_str, "rfind", w_substr, space.wrap(start), space.wrap(end)) return space.int_w(w_pos) + + at cpython_api([PyObject, PyObject, Py_ssize_t], PyObject) +def PyUnicode_Split(space, w_str, w_sep, maxsplit): + """Split a string giving a list of Unicode strings. If sep is + NULL, splitting will be done at all whitespace substrings. + Otherwise, splits occur at the given separator. At most maxsplit + splits will be done. If negative, no limit is set. Separators + are not included in the resulting list.""" + return space.call_method(w_str, "split", w_sep, space.wrap(maxsplit)) + + at cpython_api([PyObject, rffi.INT_real], PyObject) +def PyUnicode_Splitlines(space, w_str, keepend): + """Split a Unicode string at line breaks, returning a list of + Unicode strings. CRLF is considered to be one line break. If + keepend is 0, the Line break characters are not included in the + resulting strings.""" + return space.call_method(w_str, "splitlines", space.wrap(keepend)) From noreply at buildbot.pypy.org Wed Apr 11 11:19:15 2012 From: noreply at buildbot.pypy.org (mattip) Date: Wed, 11 Apr 2012 11:19:15 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: fix include order Message-ID: <20120411091915.6637582F4E@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54280:2921217e4b74 Date: 2012-04-09 20:05 +0300 http://bitbucket.org/pypy/pypy/changeset/2921217e4b74/ Log: fix include order diff --git a/pypy/module/rctime/interp_time.py b/pypy/module/rctime/interp_time.py --- a/pypy/module/rctime/interp_time.py +++ b/pypy/module/rctime/interp_time.py @@ -24,10 +24,9 @@ from pypy.module.thread import ll_thread as thread eci = ExternalCompilationInfo( + includes = ["windows.h"], post_include_bits = ["BOOL pypy_timemodule_setCtrlHandler(HANDLE event);"], separate_module_sources=[''' - #include - static HANDLE interrupt_event; static BOOL WINAPI CtrlHandlerRoutine( From noreply at buildbot.pypy.org Wed Apr 11 11:19:16 2012 From: noreply at buildbot.pypy.org (mattip) Date: Wed, 11 Apr 2012 11:19:16 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: remove/skip tests that check the sign of float('nan') Message-ID: <20120411091916.B160F82F4E@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54281:1b052e5c79b5 Date: 2012-04-10 14:25 +0300 http://bitbucket.org/pypy/pypy/changeset/1b052e5c79b5/ Log: remove/skip tests that check the sign of float('nan') diff --git a/pypy/module/micronumpy/test/test_ufuncs.py b/pypy/module/micronumpy/test/test_ufuncs.py --- a/pypy/module/micronumpy/test/test_ufuncs.py +++ b/pypy/module/micronumpy/test/test_ufuncs.py @@ -233,12 +233,15 @@ assert a[1] == 0 def test_signbit(self): - from _numpypy import signbit, copysign + from _numpypy import signbit - assert (signbit([0, 0.0, 1, 1.0, float('inf'), float('nan')]) == - [False, False, False, False, False, False]).all() - assert (signbit([-0, -0.0, -1, -1.0, float('-inf'), -float('nan'), float('-nan')]) == - [False, True, True, True, True, True, True]).all() + assert (signbit([0, 0.0, 1, 1.0, float('inf')]) == + [False, False, False, False, False]).all() + assert (signbit([-0, -0.0, -1, -1.0, float('-inf')]) == + [False, True, True, True, True]).all() + skip('sign of nan is non-determinant') + assert (signbit([float('nan'), float('-nan'), -float('nan')]) == + [False, True, True]).all() def test_reciporocal(self): from _numpypy import array, reciprocal From noreply at buildbot.pypy.org Wed Apr 11 11:19:18 2012 From: noreply at buildbot.pypy.org (mattip) Date: Wed, 11 Apr 2012 11:19:18 +0200 (CEST) Subject: [pypy-commit] pypy win32-stdlib: close file opened in test Message-ID: <20120411091918.0DD7C82F4E@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-stdlib Changeset: r54282:ac977b768615 Date: 2012-04-11 12:17 +0300 http://bitbucket.org/pypy/pypy/changeset/ac977b768615/ Log: close file opened in test diff --git a/lib-python/2.7/test/test_os.py b/lib-python/modified-2.7/test/test_os.py rename from lib-python/2.7/test/test_os.py rename to lib-python/modified-2.7/test/test_os.py --- a/lib-python/2.7/test/test_os.py +++ b/lib-python/modified-2.7/test/test_os.py @@ -74,7 +74,8 @@ self.assertFalse(os.path.exists(name), "file already exists for temporary file") # make sure we can create the file - open(name, "w") + fid = open(name, "w") + fid.close() self.files.append(name) def test_tempnam(self): From noreply at buildbot.pypy.org Wed Apr 11 13:24:28 2012 From: noreply at buildbot.pypy.org (mattip) Date: Wed, 11 Apr 2012 13:24:28 +0200 (CEST) Subject: [pypy-commit] pypy win32-stdlib: fix close() and tests for file closing Message-ID: <20120411112428.31DCC82F4E@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-stdlib Changeset: r54283:2a8e4f56269f Date: 2012-04-11 13:26 +0300 http://bitbucket.org/pypy/pypy/changeset/2a8e4f56269f/ Log: fix close() and tests for file closing diff --git a/lib-python/2.7/mailbox.py b/lib-python/2.7/mailbox.py deleted file mode 100644 --- a/lib-python/2.7/mailbox.py +++ /dev/null @@ -1,2171 +0,0 @@ -#! /usr/bin/env python - -"""Read/write support for Maildir, mbox, MH, Babyl, and MMDF mailboxes.""" - -# Notes for authors of new mailbox subclasses: -# -# Remember to fsync() changes to disk before closing a modified file -# or returning from a flush() method. See functions _sync_flush() and -# _sync_close(). - -import sys -import os -import time -import calendar -import socket -import errno -import copy -import email -import email.message -import email.generator -import StringIO -try: - if sys.platform == 'os2emx': - # OS/2 EMX fcntl() not adequate - raise ImportError - import fcntl -except ImportError: - fcntl = None - -import warnings -with warnings.catch_warnings(): - if sys.py3kwarning: - warnings.filterwarnings("ignore", ".*rfc822 has been removed", - DeprecationWarning) - import rfc822 - -__all__ = [ 'Mailbox', 'Maildir', 'mbox', 'MH', 'Babyl', 'MMDF', - 'Message', 'MaildirMessage', 'mboxMessage', 'MHMessage', - 'BabylMessage', 'MMDFMessage', 'UnixMailbox', - 'PortableUnixMailbox', 'MmdfMailbox', 'MHMailbox', 'BabylMailbox' ] - -class Mailbox: - """A group of messages in a particular place.""" - - def __init__(self, path, factory=None, create=True): - """Initialize a Mailbox instance.""" - self._path = os.path.abspath(os.path.expanduser(path)) - self._factory = factory - - def add(self, message): - """Add message and return assigned key.""" - raise NotImplementedError('Method must be implemented by subclass') - - def remove(self, key): - """Remove the keyed message; raise KeyError if it doesn't exist.""" - raise NotImplementedError('Method must be implemented by subclass') - - def __delitem__(self, key): - self.remove(key) - - def discard(self, key): - """If the keyed message exists, remove it.""" - try: - self.remove(key) - except KeyError: - pass - - def __setitem__(self, key, message): - """Replace the keyed message; raise KeyError if it doesn't exist.""" - raise NotImplementedError('Method must be implemented by subclass') - - def get(self, key, default=None): - """Return the keyed message, or default if it doesn't exist.""" - try: - return self.__getitem__(key) - except KeyError: - return default - - def __getitem__(self, key): - """Return the keyed message; raise KeyError if it doesn't exist.""" - if not self._factory: - return self.get_message(key) - else: - return self._factory(self.get_file(key)) - - def get_message(self, key): - """Return a Message representation or raise a KeyError.""" - raise NotImplementedError('Method must be implemented by subclass') - - def get_string(self, key): - """Return a string representation or raise a KeyError.""" - raise NotImplementedError('Method must be implemented by subclass') - - def get_file(self, key): - """Return a file-like representation or raise a KeyError.""" - raise NotImplementedError('Method must be implemented by subclass') - - def iterkeys(self): - """Return an iterator over keys.""" - raise NotImplementedError('Method must be implemented by subclass') - - def keys(self): - """Return a list of keys.""" - return list(self.iterkeys()) - - def itervalues(self): - """Return an iterator over all messages.""" - for key in self.iterkeys(): - try: - value = self[key] - except KeyError: - continue - yield value - - def __iter__(self): - return self.itervalues() - - def values(self): - """Return a list of messages. Memory intensive.""" - return list(self.itervalues()) - - def iteritems(self): - """Return an iterator over (key, message) tuples.""" - for key in self.iterkeys(): - try: - value = self[key] - except KeyError: - continue - yield (key, value) - - def items(self): - """Return a list of (key, message) tuples. Memory intensive.""" - return list(self.iteritems()) - - def has_key(self, key): - """Return True if the keyed message exists, False otherwise.""" - raise NotImplementedError('Method must be implemented by subclass') - - def __contains__(self, key): - return self.has_key(key) - - def __len__(self): - """Return a count of messages in the mailbox.""" - raise NotImplementedError('Method must be implemented by subclass') - - def clear(self): - """Delete all messages.""" - for key in self.iterkeys(): - self.discard(key) - - def pop(self, key, default=None): - """Delete the keyed message and return it, or default.""" - try: - result = self[key] - except KeyError: - return default - self.discard(key) - return result - - def popitem(self): - """Delete an arbitrary (key, message) pair and return it.""" - for key in self.iterkeys(): - return (key, self.pop(key)) # This is only run once. - else: - raise KeyError('No messages in mailbox') - - def update(self, arg=None): - """Change the messages that correspond to certain keys.""" - if hasattr(arg, 'iteritems'): - source = arg.iteritems() - elif hasattr(arg, 'items'): - source = arg.items() - else: - source = arg - bad_key = False - for key, message in source: - try: - self[key] = message - except KeyError: - bad_key = True - if bad_key: - raise KeyError('No message with key(s)') - - def flush(self): - """Write any pending changes to the disk.""" - raise NotImplementedError('Method must be implemented by subclass') - - def lock(self): - """Lock the mailbox.""" - raise NotImplementedError('Method must be implemented by subclass') - - def unlock(self): - """Unlock the mailbox if it is locked.""" - raise NotImplementedError('Method must be implemented by subclass') - - def close(self): - """Flush and close the mailbox.""" - raise NotImplementedError('Method must be implemented by subclass') - - def _dump_message(self, message, target, mangle_from_=False): - # Most files are opened in binary mode to allow predictable seeking. - # To get native line endings on disk, the user-friendly \n line endings - # used in strings and by email.Message are translated here. - """Dump message contents to target file.""" - if isinstance(message, email.message.Message): - buffer = StringIO.StringIO() - gen = email.generator.Generator(buffer, mangle_from_, 0) - gen.flatten(message) - buffer.seek(0) - target.write(buffer.read().replace('\n', os.linesep)) - elif isinstance(message, str): - if mangle_from_: - message = message.replace('\nFrom ', '\n>From ') - message = message.replace('\n', os.linesep) - target.write(message) - elif hasattr(message, 'read'): - while True: - line = message.readline() - if line == '': - break - if mangle_from_ and line.startswith('From '): - line = '>From ' + line[5:] - line = line.replace('\n', os.linesep) - target.write(line) - else: - raise TypeError('Invalid message type: %s' % type(message)) - - -class Maildir(Mailbox): - """A qmail-style Maildir mailbox.""" - - colon = ':' - - def __init__(self, dirname, factory=rfc822.Message, create=True): - """Initialize a Maildir instance.""" - Mailbox.__init__(self, dirname, factory, create) - self._paths = { - 'tmp': os.path.join(self._path, 'tmp'), - 'new': os.path.join(self._path, 'new'), - 'cur': os.path.join(self._path, 'cur'), - } - if not os.path.exists(self._path): - if create: - os.mkdir(self._path, 0700) - for path in self._paths.values(): - os.mkdir(path, 0o700) - else: - raise NoSuchMailboxError(self._path) - self._toc = {} - self._toc_mtimes = {} - for subdir in ('cur', 'new'): - self._toc_mtimes[subdir] = os.path.getmtime(self._paths[subdir]) - self._last_read = time.time() # Records last time we read cur/new - self._skewfactor = 0.1 # Adjust if os/fs clocks are skewing - - def add(self, message): - """Add message and return assigned key.""" - tmp_file = self._create_tmp() - try: - self._dump_message(message, tmp_file) - except BaseException: - tmp_file.close() - os.remove(tmp_file.name) - raise - _sync_close(tmp_file) - if isinstance(message, MaildirMessage): - subdir = message.get_subdir() - suffix = self.colon + message.get_info() - if suffix == self.colon: - suffix = '' - else: - subdir = 'new' - suffix = '' - uniq = os.path.basename(tmp_file.name).split(self.colon)[0] - dest = os.path.join(self._path, subdir, uniq + suffix) - try: - if hasattr(os, 'link'): - os.link(tmp_file.name, dest) - os.remove(tmp_file.name) - else: - os.rename(tmp_file.name, dest) - except OSError, e: - os.remove(tmp_file.name) - if e.errno == errno.EEXIST: - raise ExternalClashError('Name clash with existing message: %s' - % dest) - else: - raise - if isinstance(message, MaildirMessage): - os.utime(dest, (os.path.getatime(dest), message.get_date())) - return uniq - - def remove(self, key): - """Remove the keyed message; raise KeyError if it doesn't exist.""" - os.remove(os.path.join(self._path, self._lookup(key))) - - def discard(self, key): - """If the keyed message exists, remove it.""" - # This overrides an inapplicable implementation in the superclass. - try: - self.remove(key) - except KeyError: - pass - except OSError, e: - if e.errno != errno.ENOENT: - raise - - def __setitem__(self, key, message): - """Replace the keyed message; raise KeyError if it doesn't exist.""" - old_subpath = self._lookup(key) - temp_key = self.add(message) - temp_subpath = self._lookup(temp_key) - if isinstance(message, MaildirMessage): - # temp's subdir and suffix were specified by message. - dominant_subpath = temp_subpath - else: - # temp's subdir and suffix were defaults from add(). - dominant_subpath = old_subpath - subdir = os.path.dirname(dominant_subpath) - if self.colon in dominant_subpath: - suffix = self.colon + dominant_subpath.split(self.colon)[-1] - else: - suffix = '' - self.discard(key) - new_path = os.path.join(self._path, subdir, key + suffix) - os.rename(os.path.join(self._path, temp_subpath), new_path) - if isinstance(message, MaildirMessage): - os.utime(new_path, (os.path.getatime(new_path), - message.get_date())) - - def get_message(self, key): - """Return a Message representation or raise a KeyError.""" - subpath = self._lookup(key) - f = open(os.path.join(self._path, subpath), 'r') - try: - if self._factory: - msg = self._factory(f) - else: - msg = MaildirMessage(f) - finally: - f.close() - subdir, name = os.path.split(subpath) - msg.set_subdir(subdir) - if self.colon in name: - msg.set_info(name.split(self.colon)[-1]) - msg.set_date(os.path.getmtime(os.path.join(self._path, subpath))) - return msg - - def get_string(self, key): - """Return a string representation or raise a KeyError.""" - f = open(os.path.join(self._path, self._lookup(key)), 'r') - try: - return f.read() - finally: - f.close() - - def get_file(self, key): - """Return a file-like representation or raise a KeyError.""" - f = open(os.path.join(self._path, self._lookup(key)), 'rb') - return _ProxyFile(f) - - def iterkeys(self): - """Return an iterator over keys.""" - self._refresh() - for key in self._toc: - try: - self._lookup(key) - except KeyError: - continue - yield key - - def has_key(self, key): - """Return True if the keyed message exists, False otherwise.""" - self._refresh() - return key in self._toc - - def __len__(self): - """Return a count of messages in the mailbox.""" - self._refresh() - return len(self._toc) - - def flush(self): - """Write any pending changes to disk.""" - # Maildir changes are always written immediately, so there's nothing - # to do. - pass - - def lock(self): - """Lock the mailbox.""" - return - - def unlock(self): - """Unlock the mailbox if it is locked.""" - return - - def close(self): - """Flush and close the mailbox.""" - return - - def list_folders(self): - """Return a list of folder names.""" - result = [] - for entry in os.listdir(self._path): - if len(entry) > 1 and entry[0] == '.' and \ - os.path.isdir(os.path.join(self._path, entry)): - result.append(entry[1:]) - return result - - def get_folder(self, folder): - """Return a Maildir instance for the named folder.""" - return Maildir(os.path.join(self._path, '.' + folder), - factory=self._factory, - create=False) - - def add_folder(self, folder): - """Create a folder and return a Maildir instance representing it.""" - path = os.path.join(self._path, '.' + folder) - result = Maildir(path, factory=self._factory) - maildirfolder_path = os.path.join(path, 'maildirfolder') - if not os.path.exists(maildirfolder_path): - os.close(os.open(maildirfolder_path, os.O_CREAT | os.O_WRONLY, - 0666)) - return result - - def remove_folder(self, folder): - """Delete the named folder, which must be empty.""" - path = os.path.join(self._path, '.' + folder) - for entry in os.listdir(os.path.join(path, 'new')) + \ - os.listdir(os.path.join(path, 'cur')): - if len(entry) < 1 or entry[0] != '.': - raise NotEmptyError('Folder contains message(s): %s' % folder) - for entry in os.listdir(path): - if entry != 'new' and entry != 'cur' and entry != 'tmp' and \ - os.path.isdir(os.path.join(path, entry)): - raise NotEmptyError("Folder contains subdirectory '%s': %s" % - (folder, entry)) - for root, dirs, files in os.walk(path, topdown=False): - for entry in files: - os.remove(os.path.join(root, entry)) - for entry in dirs: - os.rmdir(os.path.join(root, entry)) - os.rmdir(path) - - def clean(self): - """Delete old files in "tmp".""" - now = time.time() - for entry in os.listdir(os.path.join(self._path, 'tmp')): - path = os.path.join(self._path, 'tmp', entry) - if now - os.path.getatime(path) > 129600: # 60 * 60 * 36 - os.remove(path) - - _count = 1 # This is used to generate unique file names. - - def _create_tmp(self): - """Create a file in the tmp subdirectory and open and return it.""" - now = time.time() - hostname = socket.gethostname() - if '/' in hostname: - hostname = hostname.replace('/', r'\057') - if ':' in hostname: - hostname = hostname.replace(':', r'\072') - uniq = "%s.M%sP%sQ%s.%s" % (int(now), int(now % 1 * 1e6), os.getpid(), - Maildir._count, hostname) - path = os.path.join(self._path, 'tmp', uniq) - try: - os.stat(path) - except OSError, e: - if e.errno == errno.ENOENT: - Maildir._count += 1 - try: - return _create_carefully(path) - except OSError, e: - if e.errno != errno.EEXIST: - raise - else: - raise - - # Fall through to here if stat succeeded or open raised EEXIST. - raise ExternalClashError('Name clash prevented file creation: %s' % - path) - - def _refresh(self): - """Update table of contents mapping.""" - # If it has been less than two seconds since the last _refresh() call, - # we have to unconditionally re-read the mailbox just in case it has - # been modified, because os.path.mtime() has a 2 sec resolution in the - # most common worst case (FAT) and a 1 sec resolution typically. This - # results in a few unnecessary re-reads when _refresh() is called - # multiple times in that interval, but once the clock ticks over, we - # will only re-read as needed. Because the filesystem might be being - # served by an independent system with its own clock, we record and - # compare with the mtimes from the filesystem. Because the other - # system's clock might be skewing relative to our clock, we add an - # extra delta to our wait. The default is one tenth second, but is an - # instance variable and so can be adjusted if dealing with a - # particularly skewed or irregular system. - if time.time() - self._last_read > 2 + self._skewfactor: - refresh = False - for subdir in self._toc_mtimes: - mtime = os.path.getmtime(self._paths[subdir]) - if mtime > self._toc_mtimes[subdir]: - refresh = True - self._toc_mtimes[subdir] = mtime - if not refresh: - return - # Refresh toc - self._toc = {} - for subdir in self._toc_mtimes: - path = self._paths[subdir] - for entry in os.listdir(path): - p = os.path.join(path, entry) - if os.path.isdir(p): - continue - uniq = entry.split(self.colon)[0] - self._toc[uniq] = os.path.join(subdir, entry) - self._last_read = time.time() - - def _lookup(self, key): - """Use TOC to return subpath for given key, or raise a KeyError.""" - try: - if os.path.exists(os.path.join(self._path, self._toc[key])): - return self._toc[key] - except KeyError: - pass - self._refresh() - try: - return self._toc[key] - except KeyError: - raise KeyError('No message with key: %s' % key) - - # This method is for backward compatibility only. - def next(self): - """Return the next message in a one-time iteration.""" - if not hasattr(self, '_onetime_keys'): - self._onetime_keys = self.iterkeys() - while True: - try: - return self[self._onetime_keys.next()] - except StopIteration: - return None - except KeyError: - continue - - -class _singlefileMailbox(Mailbox): - """A single-file mailbox.""" - - def __init__(self, path, factory=None, create=True): - """Initialize a single-file mailbox.""" - Mailbox.__init__(self, path, factory, create) - try: - f = open(self._path, 'rb+') - except IOError, e: - if e.errno == errno.ENOENT: - if create: - f = open(self._path, 'wb+') - else: - raise NoSuchMailboxError(self._path) - elif e.errno in (errno.EACCES, errno.EROFS): - f = open(self._path, 'rb') - else: - raise - self._file = f - self._toc = None - self._next_key = 0 - self._pending = False # No changes require rewriting the file. - self._locked = False - self._file_length = None # Used to record mailbox size - - def add(self, message): - """Add message and return assigned key.""" - self._lookup() - self._toc[self._next_key] = self._append_message(message) - self._next_key += 1 - self._pending = True - return self._next_key - 1 - - def remove(self, key): - """Remove the keyed message; raise KeyError if it doesn't exist.""" - self._lookup(key) - del self._toc[key] - self._pending = True - - def __setitem__(self, key, message): - """Replace the keyed message; raise KeyError if it doesn't exist.""" - self._lookup(key) - self._toc[key] = self._append_message(message) - self._pending = True - - def iterkeys(self): - """Return an iterator over keys.""" - self._lookup() - for key in self._toc.keys(): - yield key - - def has_key(self, key): - """Return True if the keyed message exists, False otherwise.""" - self._lookup() - return key in self._toc - - def __len__(self): - """Return a count of messages in the mailbox.""" - self._lookup() - return len(self._toc) - - def lock(self): - """Lock the mailbox.""" - if not self._locked: - _lock_file(self._file) - self._locked = True - - def unlock(self): - """Unlock the mailbox if it is locked.""" - if self._locked: - _unlock_file(self._file) - self._locked = False - - def flush(self): - """Write any pending changes to disk.""" - if not self._pending: - return - - # In order to be writing anything out at all, self._toc must - # already have been generated (and presumably has been modified - # by adding or deleting an item). - assert self._toc is not None - - # Check length of self._file; if it's changed, some other process - # has modified the mailbox since we scanned it. - self._file.seek(0, 2) - cur_len = self._file.tell() - if cur_len != self._file_length: - raise ExternalClashError('Size of mailbox file changed ' - '(expected %i, found %i)' % - (self._file_length, cur_len)) - - new_file = _create_temporary(self._path) - try: - new_toc = {} - self._pre_mailbox_hook(new_file) - for key in sorted(self._toc.keys()): - start, stop = self._toc[key] - self._file.seek(start) - self._pre_message_hook(new_file) - new_start = new_file.tell() - while True: - buffer = self._file.read(min(4096, - stop - self._file.tell())) - if buffer == '': - break - new_file.write(buffer) - new_toc[key] = (new_start, new_file.tell()) - self._post_message_hook(new_file) - except: - new_file.close() - os.remove(new_file.name) - raise - _sync_close(new_file) - # self._file is about to get replaced, so no need to sync. - self._file.close() - try: - os.rename(new_file.name, self._path) - except OSError, e: - if e.errno == errno.EEXIST or \ - (os.name == 'os2' and e.errno == errno.EACCES): - os.remove(self._path) - os.rename(new_file.name, self._path) - else: - raise - self._file = open(self._path, 'rb+') - self._toc = new_toc - self._pending = False - if self._locked: - _lock_file(self._file, dotlock=False) - - def _pre_mailbox_hook(self, f): - """Called before writing the mailbox to file f.""" - return - - def _pre_message_hook(self, f): - """Called before writing each message to file f.""" - return - - def _post_message_hook(self, f): - """Called after writing each message to file f.""" - return - - def close(self): - """Flush and close the mailbox.""" - self.flush() - if self._locked: - self.unlock() - self._file.close() # Sync has been done by self.flush() above. - - def _lookup(self, key=None): - """Return (start, stop) or raise KeyError.""" - if self._toc is None: - self._generate_toc() - if key is not None: - try: - return self._toc[key] - except KeyError: - raise KeyError('No message with key: %s' % key) - - def _append_message(self, message): - """Append message to mailbox and return (start, stop) offsets.""" - self._file.seek(0, 2) - before = self._file.tell() - try: - self._pre_message_hook(self._file) - offsets = self._install_message(message) - self._post_message_hook(self._file) - except BaseException: - self._file.truncate(before) - raise - self._file.flush() - self._file_length = self._file.tell() # Record current length of mailbox - return offsets - - - -class _mboxMMDF(_singlefileMailbox): - """An mbox or MMDF mailbox.""" - - _mangle_from_ = True - - def get_message(self, key): - """Return a Message representation or raise a KeyError.""" - start, stop = self._lookup(key) - self._file.seek(start) - from_line = self._file.readline().replace(os.linesep, '') - string = self._file.read(stop - self._file.tell()) - msg = self._message_factory(string.replace(os.linesep, '\n')) - msg.set_from(from_line[5:]) - return msg - - def get_string(self, key, from_=False): - """Return a string representation or raise a KeyError.""" - start, stop = self._lookup(key) - self._file.seek(start) - if not from_: - self._file.readline() - string = self._file.read(stop - self._file.tell()) - return string.replace(os.linesep, '\n') - - def get_file(self, key, from_=False): - """Return a file-like representation or raise a KeyError.""" - start, stop = self._lookup(key) - self._file.seek(start) - if not from_: - self._file.readline() - return _PartialFile(self._file, self._file.tell(), stop) - - def _install_message(self, message): - """Format a message and blindly write to self._file.""" - from_line = None - if isinstance(message, str) and message.startswith('From '): - newline = message.find('\n') - if newline != -1: - from_line = message[:newline] - message = message[newline + 1:] - else: - from_line = message - message = '' - elif isinstance(message, _mboxMMDFMessage): - from_line = 'From ' + message.get_from() - elif isinstance(message, email.message.Message): - from_line = message.get_unixfrom() # May be None. - if from_line is None: - from_line = 'From MAILER-DAEMON %s' % time.asctime(time.gmtime()) - start = self._file.tell() - self._file.write(from_line + os.linesep) - self._dump_message(message, self._file, self._mangle_from_) - stop = self._file.tell() - return (start, stop) - - -class mbox(_mboxMMDF): - """A classic mbox mailbox.""" - - _mangle_from_ = True - - def __init__(self, path, factory=None, create=True): - """Initialize an mbox mailbox.""" - self._message_factory = mboxMessage - _mboxMMDF.__init__(self, path, factory, create) - - def _pre_message_hook(self, f): - """Called before writing each message to file f.""" - if f.tell() != 0: - f.write(os.linesep) - - def _generate_toc(self): - """Generate key-to-(start, stop) table of contents.""" - starts, stops = [], [] - self._file.seek(0) - while True: - line_pos = self._file.tell() - line = self._file.readline() - if line.startswith('From '): - if len(stops) < len(starts): - stops.append(line_pos - len(os.linesep)) - starts.append(line_pos) - elif line == '': - stops.append(line_pos) - break - self._toc = dict(enumerate(zip(starts, stops))) - self._next_key = len(self._toc) - self._file_length = self._file.tell() - - -class MMDF(_mboxMMDF): - """An MMDF mailbox.""" - - def __init__(self, path, factory=None, create=True): - """Initialize an MMDF mailbox.""" - self._message_factory = MMDFMessage - _mboxMMDF.__init__(self, path, factory, create) - - def _pre_message_hook(self, f): - """Called before writing each message to file f.""" - f.write('\001\001\001\001' + os.linesep) - - def _post_message_hook(self, f): - """Called after writing each message to file f.""" - f.write(os.linesep + '\001\001\001\001' + os.linesep) - - def _generate_toc(self): - """Generate key-to-(start, stop) table of contents.""" - starts, stops = [], [] - self._file.seek(0) - next_pos = 0 - while True: - line_pos = next_pos - line = self._file.readline() - next_pos = self._file.tell() - if line.startswith('\001\001\001\001' + os.linesep): - starts.append(next_pos) - while True: - line_pos = next_pos - line = self._file.readline() - next_pos = self._file.tell() - if line == '\001\001\001\001' + os.linesep: - stops.append(line_pos - len(os.linesep)) - break - elif line == '': - stops.append(line_pos) - break - elif line == '': - break - self._toc = dict(enumerate(zip(starts, stops))) - self._next_key = len(self._toc) - self._file.seek(0, 2) - self._file_length = self._file.tell() - - -class MH(Mailbox): - """An MH mailbox.""" - - def __init__(self, path, factory=None, create=True): - """Initialize an MH instance.""" - Mailbox.__init__(self, path, factory, create) - if not os.path.exists(self._path): - if create: - os.mkdir(self._path, 0700) - os.close(os.open(os.path.join(self._path, '.mh_sequences'), - os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0600)) - else: - raise NoSuchMailboxError(self._path) - self._locked = False - - def add(self, message): - """Add message and return assigned key.""" - keys = self.keys() - if len(keys) == 0: - new_key = 1 - else: - new_key = max(keys) + 1 - new_path = os.path.join(self._path, str(new_key)) - f = _create_carefully(new_path) - closed = False - try: - if self._locked: - _lock_file(f) - try: - try: - self._dump_message(message, f) - except BaseException: - # Unlock and close so it can be deleted on Windows - if self._locked: - _unlock_file(f) - _sync_close(f) - closed = True - os.remove(new_path) - raise - if isinstance(message, MHMessage): - self._dump_sequences(message, new_key) - finally: - if self._locked: - _unlock_file(f) - finally: - if not closed: - _sync_close(f) - return new_key - - def remove(self, key): - """Remove the keyed message; raise KeyError if it doesn't exist.""" - path = os.path.join(self._path, str(key)) - try: - f = open(path, 'rb+') - except IOError, e: - if e.errno == errno.ENOENT: - raise KeyError('No message with key: %s' % key) - else: - raise - else: - f.close() - os.remove(path) - - def __setitem__(self, key, message): - """Replace the keyed message; raise KeyError if it doesn't exist.""" - path = os.path.join(self._path, str(key)) - try: - f = open(path, 'rb+') - except IOError, e: - if e.errno == errno.ENOENT: - raise KeyError('No message with key: %s' % key) - else: - raise - try: - if self._locked: - _lock_file(f) - try: - os.close(os.open(path, os.O_WRONLY | os.O_TRUNC)) - self._dump_message(message, f) - if isinstance(message, MHMessage): - self._dump_sequences(message, key) - finally: - if self._locked: - _unlock_file(f) - finally: - _sync_close(f) - - def get_message(self, key): - """Return a Message representation or raise a KeyError.""" - try: - if self._locked: - f = open(os.path.join(self._path, str(key)), 'r+') - else: - f = open(os.path.join(self._path, str(key)), 'r') - except IOError, e: - if e.errno == errno.ENOENT: - raise KeyError('No message with key: %s' % key) - else: - raise - try: - if self._locked: - _lock_file(f) - try: - msg = MHMessage(f) - finally: - if self._locked: - _unlock_file(f) - finally: - f.close() - for name, key_list in self.get_sequences().iteritems(): - if key in key_list: - msg.add_sequence(name) - return msg - - def get_string(self, key): - """Return a string representation or raise a KeyError.""" - try: - if self._locked: - f = open(os.path.join(self._path, str(key)), 'r+') - else: - f = open(os.path.join(self._path, str(key)), 'r') - except IOError, e: - if e.errno == errno.ENOENT: - raise KeyError('No message with key: %s' % key) - else: - raise - try: - if self._locked: - _lock_file(f) - try: - return f.read() - finally: - if self._locked: - _unlock_file(f) - finally: - f.close() - - def get_file(self, key): - """Return a file-like representation or raise a KeyError.""" - try: - f = open(os.path.join(self._path, str(key)), 'rb') - except IOError, e: - if e.errno == errno.ENOENT: - raise KeyError('No message with key: %s' % key) - else: - raise - return _ProxyFile(f) - - def iterkeys(self): - """Return an iterator over keys.""" - return iter(sorted(int(entry) for entry in os.listdir(self._path) - if entry.isdigit())) - - def has_key(self, key): - """Return True if the keyed message exists, False otherwise.""" - return os.path.exists(os.path.join(self._path, str(key))) - - def __len__(self): - """Return a count of messages in the mailbox.""" - return len(list(self.iterkeys())) - - def lock(self): - """Lock the mailbox.""" - if not self._locked: - self._file = open(os.path.join(self._path, '.mh_sequences'), 'rb+') - _lock_file(self._file) - self._locked = True - - def unlock(self): - """Unlock the mailbox if it is locked.""" - if self._locked: - _unlock_file(self._file) - _sync_close(self._file) - del self._file - self._locked = False - - def flush(self): - """Write any pending changes to the disk.""" - return - - def close(self): - """Flush and close the mailbox.""" - if self._locked: - self.unlock() - - def list_folders(self): - """Return a list of folder names.""" - result = [] - for entry in os.listdir(self._path): - if os.path.isdir(os.path.join(self._path, entry)): - result.append(entry) - return result - - def get_folder(self, folder): - """Return an MH instance for the named folder.""" - return MH(os.path.join(self._path, folder), - factory=self._factory, create=False) - - def add_folder(self, folder): - """Create a folder and return an MH instance representing it.""" - return MH(os.path.join(self._path, folder), - factory=self._factory) - - def remove_folder(self, folder): - """Delete the named folder, which must be empty.""" - path = os.path.join(self._path, folder) - entries = os.listdir(path) - if entries == ['.mh_sequences']: - os.remove(os.path.join(path, '.mh_sequences')) - elif entries == []: - pass - else: - raise NotEmptyError('Folder not empty: %s' % self._path) - os.rmdir(path) - - def get_sequences(self): - """Return a name-to-key-list dictionary to define each sequence.""" - results = {} - f = open(os.path.join(self._path, '.mh_sequences'), 'r') - try: - all_keys = set(self.keys()) - for line in f: - try: - name, contents = line.split(':') - keys = set() - for spec in contents.split(): - if spec.isdigit(): - keys.add(int(spec)) - else: - start, stop = (int(x) for x in spec.split('-')) - keys.update(range(start, stop + 1)) - results[name] = [key for key in sorted(keys) \ - if key in all_keys] - if len(results[name]) == 0: - del results[name] - except ValueError: - raise FormatError('Invalid sequence specification: %s' % - line.rstrip()) - finally: - f.close() - return results - - def set_sequences(self, sequences): - """Set sequences using the given name-to-key-list dictionary.""" - f = open(os.path.join(self._path, '.mh_sequences'), 'r+') - try: - os.close(os.open(f.name, os.O_WRONLY | os.O_TRUNC)) - for name, keys in sequences.iteritems(): - if len(keys) == 0: - continue - f.write('%s:' % name) - prev = None - completing = False - for key in sorted(set(keys)): - if key - 1 == prev: - if not completing: - completing = True - f.write('-') - elif completing: - completing = False - f.write('%s %s' % (prev, key)) - else: - f.write(' %s' % key) - prev = key - if completing: - f.write(str(prev) + '\n') - else: - f.write('\n') - finally: - _sync_close(f) - - def pack(self): - """Re-name messages to eliminate numbering gaps. Invalidates keys.""" - sequences = self.get_sequences() - prev = 0 - changes = [] - for key in self.iterkeys(): - if key - 1 != prev: - changes.append((key, prev + 1)) - if hasattr(os, 'link'): - os.link(os.path.join(self._path, str(key)), - os.path.join(self._path, str(prev + 1))) - os.unlink(os.path.join(self._path, str(key))) - else: - os.rename(os.path.join(self._path, str(key)), - os.path.join(self._path, str(prev + 1))) - prev += 1 - self._next_key = prev + 1 - if len(changes) == 0: - return - for name, key_list in sequences.items(): - for old, new in changes: - if old in key_list: - key_list[key_list.index(old)] = new - self.set_sequences(sequences) - - def _dump_sequences(self, message, key): - """Inspect a new MHMessage and update sequences appropriately.""" - pending_sequences = message.get_sequences() - all_sequences = self.get_sequences() - for name, key_list in all_sequences.iteritems(): - if name in pending_sequences: - key_list.append(key) - elif key in key_list: - del key_list[key_list.index(key)] - for sequence in pending_sequences: - if sequence not in all_sequences: - all_sequences[sequence] = [key] - self.set_sequences(all_sequences) - - -class Babyl(_singlefileMailbox): - """An Rmail-style Babyl mailbox.""" - - _special_labels = frozenset(('unseen', 'deleted', 'filed', 'answered', - 'forwarded', 'edited', 'resent')) - - def __init__(self, path, factory=None, create=True): - """Initialize a Babyl mailbox.""" - _singlefileMailbox.__init__(self, path, factory, create) - self._labels = {} - - def add(self, message): - """Add message and return assigned key.""" - key = _singlefileMailbox.add(self, message) - if isinstance(message, BabylMessage): - self._labels[key] = message.get_labels() - return key - - def remove(self, key): - """Remove the keyed message; raise KeyError if it doesn't exist.""" - _singlefileMailbox.remove(self, key) - if key in self._labels: - del self._labels[key] - - def __setitem__(self, key, message): - """Replace the keyed message; raise KeyError if it doesn't exist.""" - _singlefileMailbox.__setitem__(self, key, message) - if isinstance(message, BabylMessage): - self._labels[key] = message.get_labels() - - def get_message(self, key): - """Return a Message representation or raise a KeyError.""" - start, stop = self._lookup(key) - self._file.seek(start) - self._file.readline() # Skip '1,' line specifying labels. - original_headers = StringIO.StringIO() - while True: - line = self._file.readline() - if line == '*** EOOH ***' + os.linesep or line == '': - break - original_headers.write(line.replace(os.linesep, '\n')) - visible_headers = StringIO.StringIO() - while True: - line = self._file.readline() - if line == os.linesep or line == '': - break - visible_headers.write(line.replace(os.linesep, '\n')) - body = self._file.read(stop - self._file.tell()).replace(os.linesep, - '\n') - msg = BabylMessage(original_headers.getvalue() + body) - msg.set_visible(visible_headers.getvalue()) - if key in self._labels: - msg.set_labels(self._labels[key]) - return msg - - def get_string(self, key): - """Return a string representation or raise a KeyError.""" - start, stop = self._lookup(key) - self._file.seek(start) - self._file.readline() # Skip '1,' line specifying labels. - original_headers = StringIO.StringIO() - while True: - line = self._file.readline() - if line == '*** EOOH ***' + os.linesep or line == '': - break - original_headers.write(line.replace(os.linesep, '\n')) - while True: - line = self._file.readline() - if line == os.linesep or line == '': - break - return original_headers.getvalue() + \ - self._file.read(stop - self._file.tell()).replace(os.linesep, - '\n') - - def get_file(self, key): - """Return a file-like representation or raise a KeyError.""" - return StringIO.StringIO(self.get_string(key).replace('\n', - os.linesep)) - - def get_labels(self): - """Return a list of user-defined labels in the mailbox.""" - self._lookup() - labels = set() - for label_list in self._labels.values(): - labels.update(label_list) - labels.difference_update(self._special_labels) - return list(labels) - - def _generate_toc(self): - """Generate key-to-(start, stop) table of contents.""" - starts, stops = [], [] - self._file.seek(0) - next_pos = 0 - label_lists = [] - while True: - line_pos = next_pos - line = self._file.readline() - next_pos = self._file.tell() - if line == '\037\014' + os.linesep: - if len(stops) < len(starts): - stops.append(line_pos - len(os.linesep)) - starts.append(next_pos) - labels = [label.strip() for label - in self._file.readline()[1:].split(',') - if label.strip() != ''] - label_lists.append(labels) - elif line == '\037' or line == '\037' + os.linesep: - if len(stops) < len(starts): - stops.append(line_pos - len(os.linesep)) - elif line == '': - stops.append(line_pos - len(os.linesep)) - break - self._toc = dict(enumerate(zip(starts, stops))) - self._labels = dict(enumerate(label_lists)) - self._next_key = len(self._toc) - self._file.seek(0, 2) - self._file_length = self._file.tell() - - def _pre_mailbox_hook(self, f): - """Called before writing the mailbox to file f.""" - f.write('BABYL OPTIONS:%sVersion: 5%sLabels:%s%s\037' % - (os.linesep, os.linesep, ','.join(self.get_labels()), - os.linesep)) - - def _pre_message_hook(self, f): - """Called before writing each message to file f.""" - f.write('\014' + os.linesep) - - def _post_message_hook(self, f): - """Called after writing each message to file f.""" - f.write(os.linesep + '\037') - - def _install_message(self, message): - """Write message contents and return (start, stop).""" - start = self._file.tell() - if isinstance(message, BabylMessage): - special_labels = [] - labels = [] - for label in message.get_labels(): - if label in self._special_labels: - special_labels.append(label) - else: - labels.append(label) - self._file.write('1') - for label in special_labels: - self._file.write(', ' + label) - self._file.write(',,') - for label in labels: - self._file.write(' ' + label + ',') - self._file.write(os.linesep) - else: - self._file.write('1,,' + os.linesep) - if isinstance(message, email.message.Message): - orig_buffer = StringIO.StringIO() - orig_generator = email.generator.Generator(orig_buffer, False, 0) - orig_generator.flatten(message) - orig_buffer.seek(0) - while True: - line = orig_buffer.readline() - self._file.write(line.replace('\n', os.linesep)) - if line == '\n' or line == '': - break - self._file.write('*** EOOH ***' + os.linesep) - if isinstance(message, BabylMessage): - vis_buffer = StringIO.StringIO() - vis_generator = email.generator.Generator(vis_buffer, False, 0) - vis_generator.flatten(message.get_visible()) - while True: - line = vis_buffer.readline() - self._file.write(line.replace('\n', os.linesep)) - if line == '\n' or line == '': - break - else: - orig_buffer.seek(0) - while True: - line = orig_buffer.readline() - self._file.write(line.replace('\n', os.linesep)) - if line == '\n' or line == '': - break - while True: - buffer = orig_buffer.read(4096) # Buffer size is arbitrary. - if buffer == '': - break - self._file.write(buffer.replace('\n', os.linesep)) - elif isinstance(message, str): - body_start = message.find('\n\n') + 2 - if body_start - 2 != -1: - self._file.write(message[:body_start].replace('\n', - os.linesep)) - self._file.write('*** EOOH ***' + os.linesep) - self._file.write(message[:body_start].replace('\n', - os.linesep)) - self._file.write(message[body_start:].replace('\n', - os.linesep)) - else: - self._file.write('*** EOOH ***' + os.linesep + os.linesep) - self._file.write(message.replace('\n', os.linesep)) - elif hasattr(message, 'readline'): - original_pos = message.tell() - first_pass = True - while True: - line = message.readline() - self._file.write(line.replace('\n', os.linesep)) - if line == '\n' or line == '': - self._file.write('*** EOOH ***' + os.linesep) - if first_pass: - first_pass = False - message.seek(original_pos) - else: - break - while True: - buffer = message.read(4096) # Buffer size is arbitrary. - if buffer == '': - break - self._file.write(buffer.replace('\n', os.linesep)) - else: - raise TypeError('Invalid message type: %s' % type(message)) - stop = self._file.tell() - return (start, stop) - - -class Message(email.message.Message): - """Message with mailbox-format-specific properties.""" - - def __init__(self, message=None): - """Initialize a Message instance.""" - if isinstance(message, email.message.Message): - self._become_message(copy.deepcopy(message)) - if isinstance(message, Message): - message._explain_to(self) - elif isinstance(message, str): - self._become_message(email.message_from_string(message)) - elif hasattr(message, "read"): - self._become_message(email.message_from_file(message)) - elif message is None: - email.message.Message.__init__(self) - else: - raise TypeError('Invalid message type: %s' % type(message)) - - def _become_message(self, message): - """Assume the non-format-specific state of message.""" - for name in ('_headers', '_unixfrom', '_payload', '_charset', - 'preamble', 'epilogue', 'defects', '_default_type'): - self.__dict__[name] = message.__dict__[name] - - def _explain_to(self, message): - """Copy format-specific state to message insofar as possible.""" - if isinstance(message, Message): - return # There's nothing format-specific to explain. - else: - raise TypeError('Cannot convert to specified type') - - -class MaildirMessage(Message): - """Message with Maildir-specific properties.""" - - def __init__(self, message=None): - """Initialize a MaildirMessage instance.""" - self._subdir = 'new' - self._info = '' - self._date = time.time() - Message.__init__(self, message) - - def get_subdir(self): - """Return 'new' or 'cur'.""" - return self._subdir - - def set_subdir(self, subdir): - """Set subdir to 'new' or 'cur'.""" - if subdir == 'new' or subdir == 'cur': - self._subdir = subdir - else: - raise ValueError("subdir must be 'new' or 'cur': %s" % subdir) - - def get_flags(self): - """Return as a string the flags that are set.""" - if self._info.startswith('2,'): - return self._info[2:] - else: - return '' - - def set_flags(self, flags): - """Set the given flags and unset all others.""" - self._info = '2,' + ''.join(sorted(flags)) - - def add_flag(self, flag): - """Set the given flag(s) without changing others.""" - self.set_flags(''.join(set(self.get_flags()) | set(flag))) - - def remove_flag(self, flag): - """Unset the given string flag(s) without changing others.""" - if self.get_flags() != '': - self.set_flags(''.join(set(self.get_flags()) - set(flag))) - - def get_date(self): - """Return delivery date of message, in seconds since the epoch.""" - return self._date - - def set_date(self, date): - """Set delivery date of message, in seconds since the epoch.""" - try: - self._date = float(date) - except ValueError: - raise TypeError("can't convert to float: %s" % date) - - def get_info(self): - """Get the message's "info" as a string.""" - return self._info - - def set_info(self, info): - """Set the message's "info" string.""" - if isinstance(info, str): - self._info = info - else: - raise TypeError('info must be a string: %s' % type(info)) - - def _explain_to(self, message): - """Copy Maildir-specific state to message insofar as possible.""" - if isinstance(message, MaildirMessage): - message.set_flags(self.get_flags()) - message.set_subdir(self.get_subdir()) - message.set_date(self.get_date()) - elif isinstance(message, _mboxMMDFMessage): - flags = set(self.get_flags()) - if 'S' in flags: - message.add_flag('R') - if self.get_subdir() == 'cur': - message.add_flag('O') - if 'T' in flags: - message.add_flag('D') - if 'F' in flags: - message.add_flag('F') - if 'R' in flags: - message.add_flag('A') - message.set_from('MAILER-DAEMON', time.gmtime(self.get_date())) - elif isinstance(message, MHMessage): - flags = set(self.get_flags()) - if 'S' not in flags: - message.add_sequence('unseen') - if 'R' in flags: - message.add_sequence('replied') - if 'F' in flags: - message.add_sequence('flagged') - elif isinstance(message, BabylMessage): - flags = set(self.get_flags()) - if 'S' not in flags: - message.add_label('unseen') - if 'T' in flags: - message.add_label('deleted') - if 'R' in flags: - message.add_label('answered') - if 'P' in flags: - message.add_label('forwarded') - elif isinstance(message, Message): - pass - else: - raise TypeError('Cannot convert to specified type: %s' % - type(message)) - - -class _mboxMMDFMessage(Message): - """Message with mbox- or MMDF-specific properties.""" - - def __init__(self, message=None): - """Initialize an mboxMMDFMessage instance.""" - self.set_from('MAILER-DAEMON', True) - if isinstance(message, email.message.Message): - unixfrom = message.get_unixfrom() - if unixfrom is not None and unixfrom.startswith('From '): - self.set_from(unixfrom[5:]) - Message.__init__(self, message) - - def get_from(self): - """Return contents of "From " line.""" - return self._from - - def set_from(self, from_, time_=None): - """Set "From " line, formatting and appending time_ if specified.""" - if time_ is not None: - if time_ is True: - time_ = time.gmtime() - from_ += ' ' + time.asctime(time_) - self._from = from_ - - def get_flags(self): - """Return as a string the flags that are set.""" - return self.get('Status', '') + self.get('X-Status', '') - - def set_flags(self, flags): - """Set the given flags and unset all others.""" - flags = set(flags) - status_flags, xstatus_flags = '', '' - for flag in ('R', 'O'): - if flag in flags: - status_flags += flag - flags.remove(flag) - for flag in ('D', 'F', 'A'): - if flag in flags: - xstatus_flags += flag - flags.remove(flag) - xstatus_flags += ''.join(sorted(flags)) - try: - self.replace_header('Status', status_flags) - except KeyError: - self.add_header('Status', status_flags) - try: - self.replace_header('X-Status', xstatus_flags) - except KeyError: - self.add_header('X-Status', xstatus_flags) - - def add_flag(self, flag): - """Set the given flag(s) without changing others.""" - self.set_flags(''.join(set(self.get_flags()) | set(flag))) - - def remove_flag(self, flag): - """Unset the given string flag(s) without changing others.""" - if 'Status' in self or 'X-Status' in self: - self.set_flags(''.join(set(self.get_flags()) - set(flag))) - - def _explain_to(self, message): - """Copy mbox- or MMDF-specific state to message insofar as possible.""" - if isinstance(message, MaildirMessage): - flags = set(self.get_flags()) - if 'O' in flags: - message.set_subdir('cur') - if 'F' in flags: - message.add_flag('F') - if 'A' in flags: - message.add_flag('R') - if 'R' in flags: - message.add_flag('S') - if 'D' in flags: - message.add_flag('T') - del message['status'] - del message['x-status'] - maybe_date = ' '.join(self.get_from().split()[-5:]) - try: - message.set_date(calendar.timegm(time.strptime(maybe_date, - '%a %b %d %H:%M:%S %Y'))) - except (ValueError, OverflowError): - pass - elif isinstance(message, _mboxMMDFMessage): - message.set_flags(self.get_flags()) - message.set_from(self.get_from()) - elif isinstance(message, MHMessage): - flags = set(self.get_flags()) - if 'R' not in flags: - message.add_sequence('unseen') - if 'A' in flags: - message.add_sequence('replied') - if 'F' in flags: - message.add_sequence('flagged') - del message['status'] - del message['x-status'] - elif isinstance(message, BabylMessage): - flags = set(self.get_flags()) - if 'R' not in flags: - message.add_label('unseen') - if 'D' in flags: - message.add_label('deleted') - if 'A' in flags: - message.add_label('answered') - del message['status'] - del message['x-status'] - elif isinstance(message, Message): - pass - else: - raise TypeError('Cannot convert to specified type: %s' % - type(message)) - - -class mboxMessage(_mboxMMDFMessage): - """Message with mbox-specific properties.""" - - -class MHMessage(Message): - """Message with MH-specific properties.""" - - def __init__(self, message=None): - """Initialize an MHMessage instance.""" - self._sequences = [] - Message.__init__(self, message) - - def get_sequences(self): - """Return a list of sequences that include the message.""" - return self._sequences[:] - - def set_sequences(self, sequences): - """Set the list of sequences that include the message.""" - self._sequences = list(sequences) - - def add_sequence(self, sequence): - """Add sequence to list of sequences including the message.""" - if isinstance(sequence, str): - if not sequence in self._sequences: - self._sequences.append(sequence) - else: - raise TypeError('sequence must be a string: %s' % type(sequence)) - - def remove_sequence(self, sequence): - """Remove sequence from the list of sequences including the message.""" - try: - self._sequences.remove(sequence) - except ValueError: - pass - - def _explain_to(self, message): - """Copy MH-specific state to message insofar as possible.""" - if isinstance(message, MaildirMessage): - sequences = set(self.get_sequences()) - if 'unseen' in sequences: - message.set_subdir('cur') - else: - message.set_subdir('cur') - message.add_flag('S') - if 'flagged' in sequences: - message.add_flag('F') - if 'replied' in sequences: - message.add_flag('R') - elif isinstance(message, _mboxMMDFMessage): - sequences = set(self.get_sequences()) - if 'unseen' not in sequences: - message.add_flag('RO') - else: - message.add_flag('O') - if 'flagged' in sequences: - message.add_flag('F') - if 'replied' in sequences: - message.add_flag('A') - elif isinstance(message, MHMessage): - for sequence in self.get_sequences(): - message.add_sequence(sequence) - elif isinstance(message, BabylMessage): - sequences = set(self.get_sequences()) - if 'unseen' in sequences: - message.add_label('unseen') - if 'replied' in sequences: - message.add_label('answered') - elif isinstance(message, Message): - pass - else: - raise TypeError('Cannot convert to specified type: %s' % - type(message)) - - -class BabylMessage(Message): - """Message with Babyl-specific properties.""" - - def __init__(self, message=None): - """Initialize an BabylMessage instance.""" - self._labels = [] - self._visible = Message() - Message.__init__(self, message) - - def get_labels(self): - """Return a list of labels on the message.""" - return self._labels[:] - - def set_labels(self, labels): - """Set the list of labels on the message.""" - self._labels = list(labels) - - def add_label(self, label): - """Add label to list of labels on the message.""" - if isinstance(label, str): - if label not in self._labels: - self._labels.append(label) - else: - raise TypeError('label must be a string: %s' % type(label)) - - def remove_label(self, label): - """Remove label from the list of labels on the message.""" - try: - self._labels.remove(label) - except ValueError: - pass - - def get_visible(self): - """Return a Message representation of visible headers.""" - return Message(self._visible) - - def set_visible(self, visible): - """Set the Message representation of visible headers.""" - self._visible = Message(visible) - - def update_visible(self): - """Update and/or sensibly generate a set of visible headers.""" - for header in self._visible.keys(): - if header in self: - self._visible.replace_header(header, self[header]) - else: - del self._visible[header] - for header in ('Date', 'From', 'Reply-To', 'To', 'CC', 'Subject'): - if header in self and header not in self._visible: - self._visible[header] = self[header] - - def _explain_to(self, message): - """Copy Babyl-specific state to message insofar as possible.""" - if isinstance(message, MaildirMessage): - labels = set(self.get_labels()) - if 'unseen' in labels: - message.set_subdir('cur') - else: - message.set_subdir('cur') - message.add_flag('S') - if 'forwarded' in labels or 'resent' in labels: - message.add_flag('P') - if 'answered' in labels: - message.add_flag('R') - if 'deleted' in labels: - message.add_flag('T') - elif isinstance(message, _mboxMMDFMessage): - labels = set(self.get_labels()) - if 'unseen' not in labels: - message.add_flag('RO') - else: - message.add_flag('O') - if 'deleted' in labels: - message.add_flag('D') - if 'answered' in labels: - message.add_flag('A') - elif isinstance(message, MHMessage): - labels = set(self.get_labels()) - if 'unseen' in labels: - message.add_sequence('unseen') - if 'answered' in labels: - message.add_sequence('replied') - elif isinstance(message, BabylMessage): - message.set_visible(self.get_visible()) - for label in self.get_labels(): - message.add_label(label) - elif isinstance(message, Message): - pass - else: - raise TypeError('Cannot convert to specified type: %s' % - type(message)) - - -class MMDFMessage(_mboxMMDFMessage): - """Message with MMDF-specific properties.""" - - -class _ProxyFile: - """A read-only wrapper of a file.""" - - def __init__(self, f, pos=None): - """Initialize a _ProxyFile.""" - self._file = f - if pos is None: - self._pos = f.tell() - else: - self._pos = pos - - def read(self, size=None): - """Read bytes.""" - return self._read(size, self._file.read) - - def readline(self, size=None): - """Read a line.""" - return self._read(size, self._file.readline) - - def readlines(self, sizehint=None): - """Read multiple lines.""" - result = [] - for line in self: - result.append(line) - if sizehint is not None: - sizehint -= len(line) - if sizehint <= 0: - break - return result - - def __iter__(self): - """Iterate over lines.""" - return iter(self.readline, "") - - def tell(self): - """Return the position.""" - return self._pos - - def seek(self, offset, whence=0): - """Change position.""" - if whence == 1: - self._file.seek(self._pos) - self._file.seek(offset, whence) - self._pos = self._file.tell() - - def close(self): - """Close the file.""" - del self._file - - def _read(self, size, read_method): - """Read size bytes using read_method.""" - if size is None: - size = -1 - self._file.seek(self._pos) - result = read_method(size) - self._pos = self._file.tell() - return result - - -class _PartialFile(_ProxyFile): - """A read-only wrapper of part of a file.""" - - def __init__(self, f, start=None, stop=None): - """Initialize a _PartialFile.""" - _ProxyFile.__init__(self, f, start) - self._start = start - self._stop = stop - - def tell(self): - """Return the position with respect to start.""" - return _ProxyFile.tell(self) - self._start - - def seek(self, offset, whence=0): - """Change position, possibly with respect to start or stop.""" - if whence == 0: - self._pos = self._start - whence = 1 - elif whence == 2: - self._pos = self._stop - whence = 1 - _ProxyFile.seek(self, offset, whence) - - def _read(self, size, read_method): - """Read size bytes using read_method, honoring start and stop.""" - remaining = self._stop - self._pos - if remaining <= 0: - return '' - if size is None or size < 0 or size > remaining: - size = remaining - return _ProxyFile._read(self, size, read_method) - - -def _lock_file(f, dotlock=True): - """Lock file f using lockf and dot locking.""" - dotlock_done = False - try: - if fcntl: - try: - fcntl.lockf(f, fcntl.LOCK_EX | fcntl.LOCK_NB) - except IOError, e: - if e.errno in (errno.EAGAIN, errno.EACCES, errno.EROFS): - raise ExternalClashError('lockf: lock unavailable: %s' % - f.name) - else: - raise - if dotlock: - try: - pre_lock = _create_temporary(f.name + '.lock') - pre_lock.close() - except IOError, e: - if e.errno in (errno.EACCES, errno.EROFS): - return # Without write access, just skip dotlocking. - else: - raise - try: - if hasattr(os, 'link'): - os.link(pre_lock.name, f.name + '.lock') - dotlock_done = True - os.unlink(pre_lock.name) - else: - os.rename(pre_lock.name, f.name + '.lock') - dotlock_done = True - except OSError, e: - if e.errno == errno.EEXIST or \ - (os.name == 'os2' and e.errno == errno.EACCES): - os.remove(pre_lock.name) - raise ExternalClashError('dot lock unavailable: %s' % - f.name) - else: - raise - except: - if fcntl: - fcntl.lockf(f, fcntl.LOCK_UN) - if dotlock_done: - os.remove(f.name + '.lock') - raise - -def _unlock_file(f): - """Unlock file f using lockf and dot locking.""" - if fcntl: - fcntl.lockf(f, fcntl.LOCK_UN) - if os.path.exists(f.name + '.lock'): - os.remove(f.name + '.lock') - -def _create_carefully(path): - """Create a file if it doesn't exist and open for reading and writing.""" - fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, 0666) - try: - return open(path, 'rb+') - finally: - os.close(fd) - -def _create_temporary(path): - """Create a temp file based on path and open for reading and writing.""" - return _create_carefully('%s.%s.%s.%s' % (path, int(time.time()), - socket.gethostname(), - os.getpid())) - -def _sync_flush(f): - """Ensure changes to file f are physically on disk.""" - f.flush() - if hasattr(os, 'fsync'): - os.fsync(f.fileno()) - -def _sync_close(f): - """Close file f, ensuring all changes are physically on disk.""" - _sync_flush(f) - f.close() - -## Start: classes from the original module (for backward compatibility). - -# Note that the Maildir class, whose name is unchanged, itself offers a next() -# method for backward compatibility. - -class _Mailbox: - - def __init__(self, fp, factory=rfc822.Message): - self.fp = fp - self.seekp = 0 - self.factory = factory - - def __iter__(self): - return iter(self.next, None) - - def next(self): - while 1: - self.fp.seek(self.seekp) - try: - self._search_start() - except EOFError: - self.seekp = self.fp.tell() - return None - start = self.fp.tell() - self._search_end() - self.seekp = stop = self.fp.tell() - if start != stop: - break - return self.factory(_PartialFile(self.fp, start, stop)) - -# Recommended to use PortableUnixMailbox instead! -class UnixMailbox(_Mailbox): - - def _search_start(self): - while 1: - pos = self.fp.tell() - line = self.fp.readline() - if not line: - raise EOFError - if line[:5] == 'From ' and self._isrealfromline(line): - self.fp.seek(pos) - return - - def _search_end(self): - self.fp.readline() # Throw away header line - while 1: - pos = self.fp.tell() - line = self.fp.readline() - if not line: - return - if line[:5] == 'From ' and self._isrealfromline(line): - self.fp.seek(pos) - return - - # An overridable mechanism to test for From-line-ness. You can either - # specify a different regular expression or define a whole new - # _isrealfromline() method. Note that this only gets called for lines - # starting with the 5 characters "From ". - # - # BAW: According to - #http://home.netscape.com/eng/mozilla/2.0/relnotes/demo/content-length.html - # the only portable, reliable way to find message delimiters in a BSD (i.e - # Unix mailbox) style folder is to search for "\n\nFrom .*\n", or at the - # beginning of the file, "^From .*\n". While _fromlinepattern below seems - # like a good idea, in practice, there are too many variations for more - # strict parsing of the line to be completely accurate. - # - # _strict_isrealfromline() is the old version which tries to do stricter - # parsing of the From_ line. _portable_isrealfromline() simply returns - # true, since it's never called if the line doesn't already start with - # "From ". - # - # This algorithm, and the way it interacts with _search_start() and - # _search_end() may not be completely correct, because it doesn't check - # that the two characters preceding "From " are \n\n or the beginning of - # the file. Fixing this would require a more extensive rewrite than is - # necessary. For convenience, we've added a PortableUnixMailbox class - # which does no checking of the format of the 'From' line. - - _fromlinepattern = (r"From \s*[^\s]+\s+\w\w\w\s+\w\w\w\s+\d?\d\s+" - r"\d?\d:\d\d(:\d\d)?(\s+[^\s]+)?\s+\d\d\d\d\s*" - r"[^\s]*\s*" - "$") - _regexp = None - - def _strict_isrealfromline(self, line): - if not self._regexp: - import re - self._regexp = re.compile(self._fromlinepattern) - return self._regexp.match(line) - - def _portable_isrealfromline(self, line): - return True - - _isrealfromline = _strict_isrealfromline - - -class PortableUnixMailbox(UnixMailbox): - _isrealfromline = UnixMailbox._portable_isrealfromline - - -class MmdfMailbox(_Mailbox): - - def _search_start(self): - while 1: - line = self.fp.readline() - if not line: - raise EOFError - if line[:5] == '\001\001\001\001\n': - return - - def _search_end(self): - while 1: - pos = self.fp.tell() - line = self.fp.readline() - if not line: - return - if line == '\001\001\001\001\n': - self.fp.seek(pos) - return - - -class MHMailbox: - - def __init__(self, dirname, factory=rfc822.Message): - import re - pat = re.compile('^[1-9][0-9]*$') - self.dirname = dirname - # the three following lines could be combined into: - # list = map(long, filter(pat.match, os.listdir(self.dirname))) - list = os.listdir(self.dirname) - list = filter(pat.match, list) - list = map(long, list) - list.sort() - # This only works in Python 1.6 or later; - # before that str() added 'L': - self.boxes = map(str, list) - self.boxes.reverse() - self.factory = factory - - def __iter__(self): - return iter(self.next, None) - - def next(self): - if not self.boxes: - return None - fn = self.boxes.pop() - fp = open(os.path.join(self.dirname, fn)) - msg = self.factory(fp) - try: - msg._mh_msgno = fn - except (AttributeError, TypeError): - pass - return msg - - -class BabylMailbox(_Mailbox): - - def _search_start(self): - while 1: - line = self.fp.readline() - if not line: - raise EOFError - if line == '*** EOOH ***\n': - return - - def _search_end(self): - while 1: - pos = self.fp.tell() - line = self.fp.readline() - if not line: - return - if line == '\037\014\n' or line == '\037': - self.fp.seek(pos) - return - -## End: classes from the original module (for backward compatibility). - - -class Error(Exception): - """Raised for module-specific errors.""" - -class NoSuchMailboxError(Error): - """The specified mailbox does not exist and won't be created.""" - -class NotEmptyError(Error): - """The specified mailbox is not empty and deletion was requested.""" - -class ExternalClashError(Error): - """Another process caused an action to fail.""" - -class FormatError(Error): - """A file appears to have an invalid format.""" diff --git a/lib-python/modified-2.7/mailbox.py b/lib-python/modified-2.7/mailbox.py new file mode 100644 --- /dev/null +++ b/lib-python/modified-2.7/mailbox.py @@ -0,0 +1,2172 @@ +#! /usr/bin/env python + +"""Read/write support for Maildir, mbox, MH, Babyl, and MMDF mailboxes.""" + +# Notes for authors of new mailbox subclasses: +# +# Remember to fsync() changes to disk before closing a modified file +# or returning from a flush() method. See functions _sync_flush() and +# _sync_close(). + +import sys +import os +import time +import calendar +import socket +import errno +import copy +import email +import email.message +import email.generator +import StringIO +try: + if sys.platform == 'os2emx': + # OS/2 EMX fcntl() not adequate + raise ImportError + import fcntl +except ImportError: + fcntl = None + +import warnings +with warnings.catch_warnings(): + if sys.py3kwarning: + warnings.filterwarnings("ignore", ".*rfc822 has been removed", + DeprecationWarning) + import rfc822 + +__all__ = [ 'Mailbox', 'Maildir', 'mbox', 'MH', 'Babyl', 'MMDF', + 'Message', 'MaildirMessage', 'mboxMessage', 'MHMessage', + 'BabylMessage', 'MMDFMessage', 'UnixMailbox', + 'PortableUnixMailbox', 'MmdfMailbox', 'MHMailbox', 'BabylMailbox' ] + +class Mailbox: + """A group of messages in a particular place.""" + + def __init__(self, path, factory=None, create=True): + """Initialize a Mailbox instance.""" + self._path = os.path.abspath(os.path.expanduser(path)) + self._factory = factory + + def add(self, message): + """Add message and return assigned key.""" + raise NotImplementedError('Method must be implemented by subclass') + + def remove(self, key): + """Remove the keyed message; raise KeyError if it doesn't exist.""" + raise NotImplementedError('Method must be implemented by subclass') + + def __delitem__(self, key): + self.remove(key) + + def discard(self, key): + """If the keyed message exists, remove it.""" + try: + self.remove(key) + except KeyError: + pass + + def __setitem__(self, key, message): + """Replace the keyed message; raise KeyError if it doesn't exist.""" + raise NotImplementedError('Method must be implemented by subclass') + + def get(self, key, default=None): + """Return the keyed message, or default if it doesn't exist.""" + try: + return self.__getitem__(key) + except KeyError: + return default + + def __getitem__(self, key): + """Return the keyed message; raise KeyError if it doesn't exist.""" + if not self._factory: + return self.get_message(key) + else: + return self._factory(self.get_file(key)) + + def get_message(self, key): + """Return a Message representation or raise a KeyError.""" + raise NotImplementedError('Method must be implemented by subclass') + + def get_string(self, key): + """Return a string representation or raise a KeyError.""" + raise NotImplementedError('Method must be implemented by subclass') + + def get_file(self, key): + """Return a file-like representation or raise a KeyError.""" + raise NotImplementedError('Method must be implemented by subclass') + + def iterkeys(self): + """Return an iterator over keys.""" + raise NotImplementedError('Method must be implemented by subclass') + + def keys(self): + """Return a list of keys.""" + return list(self.iterkeys()) + + def itervalues(self): + """Return an iterator over all messages.""" + for key in self.iterkeys(): + try: + value = self[key] + except KeyError: + continue + yield value + + def __iter__(self): + return self.itervalues() + + def values(self): + """Return a list of messages. Memory intensive.""" + return list(self.itervalues()) + + def iteritems(self): + """Return an iterator over (key, message) tuples.""" + for key in self.iterkeys(): + try: + value = self[key] + except KeyError: + continue + yield (key, value) + + def items(self): + """Return a list of (key, message) tuples. Memory intensive.""" + return list(self.iteritems()) + + def has_key(self, key): + """Return True if the keyed message exists, False otherwise.""" + raise NotImplementedError('Method must be implemented by subclass') + + def __contains__(self, key): + return self.has_key(key) + + def __len__(self): + """Return a count of messages in the mailbox.""" + raise NotImplementedError('Method must be implemented by subclass') + + def clear(self): + """Delete all messages.""" + for key in self.iterkeys(): + self.discard(key) + + def pop(self, key, default=None): + """Delete the keyed message and return it, or default.""" + try: + result = self[key] + except KeyError: + return default + self.discard(key) + return result + + def popitem(self): + """Delete an arbitrary (key, message) pair and return it.""" + for key in self.iterkeys(): + return (key, self.pop(key)) # This is only run once. + else: + raise KeyError('No messages in mailbox') + + def update(self, arg=None): + """Change the messages that correspond to certain keys.""" + if hasattr(arg, 'iteritems'): + source = arg.iteritems() + elif hasattr(arg, 'items'): + source = arg.items() + else: + source = arg + bad_key = False + for key, message in source: + try: + self[key] = message + except KeyError: + bad_key = True + if bad_key: + raise KeyError('No message with key(s)') + + def flush(self): + """Write any pending changes to the disk.""" + raise NotImplementedError('Method must be implemented by subclass') + + def lock(self): + """Lock the mailbox.""" + raise NotImplementedError('Method must be implemented by subclass') + + def unlock(self): + """Unlock the mailbox if it is locked.""" + raise NotImplementedError('Method must be implemented by subclass') + + def close(self): + """Flush and close the mailbox.""" + raise NotImplementedError('Method must be implemented by subclass') + + def _dump_message(self, message, target, mangle_from_=False): + # Most files are opened in binary mode to allow predictable seeking. + # To get native line endings on disk, the user-friendly \n line endings + # used in strings and by email.Message are translated here. + """Dump message contents to target file.""" + if isinstance(message, email.message.Message): + buffer = StringIO.StringIO() + gen = email.generator.Generator(buffer, mangle_from_, 0) + gen.flatten(message) + buffer.seek(0) + target.write(buffer.read().replace('\n', os.linesep)) + elif isinstance(message, str): + if mangle_from_: + message = message.replace('\nFrom ', '\n>From ') + message = message.replace('\n', os.linesep) + target.write(message) + elif hasattr(message, 'read'): + while True: + line = message.readline() + if line == '': + break + if mangle_from_ and line.startswith('From '): + line = '>From ' + line[5:] + line = line.replace('\n', os.linesep) + target.write(line) + else: + raise TypeError('Invalid message type: %s' % type(message)) + + +class Maildir(Mailbox): + """A qmail-style Maildir mailbox.""" + + colon = ':' + + def __init__(self, dirname, factory=rfc822.Message, create=True): + """Initialize a Maildir instance.""" + Mailbox.__init__(self, dirname, factory, create) + self._paths = { + 'tmp': os.path.join(self._path, 'tmp'), + 'new': os.path.join(self._path, 'new'), + 'cur': os.path.join(self._path, 'cur'), + } + if not os.path.exists(self._path): + if create: + os.mkdir(self._path, 0700) + for path in self._paths.values(): + os.mkdir(path, 0o700) + else: + raise NoSuchMailboxError(self._path) + self._toc = {} + self._toc_mtimes = {} + for subdir in ('cur', 'new'): + self._toc_mtimes[subdir] = os.path.getmtime(self._paths[subdir]) + self._last_read = time.time() # Records last time we read cur/new + self._skewfactor = 0.1 # Adjust if os/fs clocks are skewing + + def add(self, message): + """Add message and return assigned key.""" + tmp_file = self._create_tmp() + try: + self._dump_message(message, tmp_file) + except BaseException: + tmp_file.close() + os.remove(tmp_file.name) + raise + _sync_close(tmp_file) + if isinstance(message, MaildirMessage): + subdir = message.get_subdir() + suffix = self.colon + message.get_info() + if suffix == self.colon: + suffix = '' + else: + subdir = 'new' + suffix = '' + uniq = os.path.basename(tmp_file.name).split(self.colon)[0] + dest = os.path.join(self._path, subdir, uniq + suffix) + try: + if hasattr(os, 'link'): + os.link(tmp_file.name, dest) + os.remove(tmp_file.name) + else: + os.rename(tmp_file.name, dest) + except OSError, e: + os.remove(tmp_file.name) + if e.errno == errno.EEXIST: + raise ExternalClashError('Name clash with existing message: %s' + % dest) + else: + raise + if isinstance(message, MaildirMessage): + os.utime(dest, (os.path.getatime(dest), message.get_date())) + return uniq + + def remove(self, key): + """Remove the keyed message; raise KeyError if it doesn't exist.""" + os.remove(os.path.join(self._path, self._lookup(key))) + + def discard(self, key): + """If the keyed message exists, remove it.""" + # This overrides an inapplicable implementation in the superclass. + try: + self.remove(key) + except KeyError: + pass + except OSError, e: + if e.errno != errno.ENOENT: + raise + + def __setitem__(self, key, message): + """Replace the keyed message; raise KeyError if it doesn't exist.""" + old_subpath = self._lookup(key) + temp_key = self.add(message) + temp_subpath = self._lookup(temp_key) + if isinstance(message, MaildirMessage): + # temp's subdir and suffix were specified by message. + dominant_subpath = temp_subpath + else: + # temp's subdir and suffix were defaults from add(). + dominant_subpath = old_subpath + subdir = os.path.dirname(dominant_subpath) + if self.colon in dominant_subpath: + suffix = self.colon + dominant_subpath.split(self.colon)[-1] + else: + suffix = '' + self.discard(key) + new_path = os.path.join(self._path, subdir, key + suffix) + os.rename(os.path.join(self._path, temp_subpath), new_path) + if isinstance(message, MaildirMessage): + os.utime(new_path, (os.path.getatime(new_path), + message.get_date())) + + def get_message(self, key): + """Return a Message representation or raise a KeyError.""" + subpath = self._lookup(key) + f = open(os.path.join(self._path, subpath), 'r') + try: + if self._factory: + msg = self._factory(f) + else: + msg = MaildirMessage(f) + finally: + f.close() + subdir, name = os.path.split(subpath) + msg.set_subdir(subdir) + if self.colon in name: + msg.set_info(name.split(self.colon)[-1]) + msg.set_date(os.path.getmtime(os.path.join(self._path, subpath))) + return msg + + def get_string(self, key): + """Return a string representation or raise a KeyError.""" + f = open(os.path.join(self._path, self._lookup(key)), 'r') + try: + return f.read() + finally: + f.close() + + def get_file(self, key): + """Return a file-like representation or raise a KeyError.""" + f = open(os.path.join(self._path, self._lookup(key)), 'rb') + return _ProxyFile(f) + + def iterkeys(self): + """Return an iterator over keys.""" + self._refresh() + for key in self._toc: + try: + self._lookup(key) + except KeyError: + continue + yield key + + def has_key(self, key): + """Return True if the keyed message exists, False otherwise.""" + self._refresh() + return key in self._toc + + def __len__(self): + """Return a count of messages in the mailbox.""" + self._refresh() + return len(self._toc) + + def flush(self): + """Write any pending changes to disk.""" + # Maildir changes are always written immediately, so there's nothing + # to do. + pass + + def lock(self): + """Lock the mailbox.""" + return + + def unlock(self): + """Unlock the mailbox if it is locked.""" + return + + def close(self): + """Flush and close the mailbox.""" + return + + def list_folders(self): + """Return a list of folder names.""" + result = [] + for entry in os.listdir(self._path): + if len(entry) > 1 and entry[0] == '.' and \ + os.path.isdir(os.path.join(self._path, entry)): + result.append(entry[1:]) + return result + + def get_folder(self, folder): + """Return a Maildir instance for the named folder.""" + return Maildir(os.path.join(self._path, '.' + folder), + factory=self._factory, + create=False) + + def add_folder(self, folder): + """Create a folder and return a Maildir instance representing it.""" + path = os.path.join(self._path, '.' + folder) + result = Maildir(path, factory=self._factory) + maildirfolder_path = os.path.join(path, 'maildirfolder') + if not os.path.exists(maildirfolder_path): + os.close(os.open(maildirfolder_path, os.O_CREAT | os.O_WRONLY, + 0666)) + return result + + def remove_folder(self, folder): + """Delete the named folder, which must be empty.""" + path = os.path.join(self._path, '.' + folder) + for entry in os.listdir(os.path.join(path, 'new')) + \ + os.listdir(os.path.join(path, 'cur')): + if len(entry) < 1 or entry[0] != '.': + raise NotEmptyError('Folder contains message(s): %s' % folder) + for entry in os.listdir(path): + if entry != 'new' and entry != 'cur' and entry != 'tmp' and \ + os.path.isdir(os.path.join(path, entry)): + raise NotEmptyError("Folder contains subdirectory '%s': %s" % + (folder, entry)) + for root, dirs, files in os.walk(path, topdown=False): + for entry in files: + os.remove(os.path.join(root, entry)) + for entry in dirs: + os.rmdir(os.path.join(root, entry)) + os.rmdir(path) + + def clean(self): + """Delete old files in "tmp".""" + now = time.time() + for entry in os.listdir(os.path.join(self._path, 'tmp')): + path = os.path.join(self._path, 'tmp', entry) + if now - os.path.getatime(path) > 129600: # 60 * 60 * 36 + os.remove(path) + + _count = 1 # This is used to generate unique file names. + + def _create_tmp(self): + """Create a file in the tmp subdirectory and open and return it.""" + now = time.time() + hostname = socket.gethostname() + if '/' in hostname: + hostname = hostname.replace('/', r'\057') + if ':' in hostname: + hostname = hostname.replace(':', r'\072') + uniq = "%s.M%sP%sQ%s.%s" % (int(now), int(now % 1 * 1e6), os.getpid(), + Maildir._count, hostname) + path = os.path.join(self._path, 'tmp', uniq) + try: + os.stat(path) + except OSError, e: + if e.errno == errno.ENOENT: + Maildir._count += 1 + try: + return _create_carefully(path) + except OSError, e: + if e.errno != errno.EEXIST: + raise + else: + raise + + # Fall through to here if stat succeeded or open raised EEXIST. + raise ExternalClashError('Name clash prevented file creation: %s' % + path) + + def _refresh(self): + """Update table of contents mapping.""" + # If it has been less than two seconds since the last _refresh() call, + # we have to unconditionally re-read the mailbox just in case it has + # been modified, because os.path.mtime() has a 2 sec resolution in the + # most common worst case (FAT) and a 1 sec resolution typically. This + # results in a few unnecessary re-reads when _refresh() is called + # multiple times in that interval, but once the clock ticks over, we + # will only re-read as needed. Because the filesystem might be being + # served by an independent system with its own clock, we record and + # compare with the mtimes from the filesystem. Because the other + # system's clock might be skewing relative to our clock, we add an + # extra delta to our wait. The default is one tenth second, but is an + # instance variable and so can be adjusted if dealing with a + # particularly skewed or irregular system. + if time.time() - self._last_read > 2 + self._skewfactor: + refresh = False + for subdir in self._toc_mtimes: + mtime = os.path.getmtime(self._paths[subdir]) + if mtime > self._toc_mtimes[subdir]: + refresh = True + self._toc_mtimes[subdir] = mtime + if not refresh: + return + # Refresh toc + self._toc = {} + for subdir in self._toc_mtimes: + path = self._paths[subdir] + for entry in os.listdir(path): + p = os.path.join(path, entry) + if os.path.isdir(p): + continue + uniq = entry.split(self.colon)[0] + self._toc[uniq] = os.path.join(subdir, entry) + self._last_read = time.time() + + def _lookup(self, key): + """Use TOC to return subpath for given key, or raise a KeyError.""" + try: + if os.path.exists(os.path.join(self._path, self._toc[key])): + return self._toc[key] + except KeyError: + pass + self._refresh() + try: + return self._toc[key] + except KeyError: + raise KeyError('No message with key: %s' % key) + + # This method is for backward compatibility only. + def next(self): + """Return the next message in a one-time iteration.""" + if not hasattr(self, '_onetime_keys'): + self._onetime_keys = self.iterkeys() + while True: + try: + return self[self._onetime_keys.next()] + except StopIteration: + return None + except KeyError: + continue + + +class _singlefileMailbox(Mailbox): + """A single-file mailbox.""" + + def __init__(self, path, factory=None, create=True): + """Initialize a single-file mailbox.""" + Mailbox.__init__(self, path, factory, create) + try: + f = open(self._path, 'rb+') + except IOError, e: + if e.errno == errno.ENOENT: + if create: + f = open(self._path, 'wb+') + else: + raise NoSuchMailboxError(self._path) + elif e.errno in (errno.EACCES, errno.EROFS): + f = open(self._path, 'rb') + else: + raise + self._file = f + self._toc = None + self._next_key = 0 + self._pending = False # No changes require rewriting the file. + self._locked = False + self._file_length = None # Used to record mailbox size + + def add(self, message): + """Add message and return assigned key.""" + self._lookup() + self._toc[self._next_key] = self._append_message(message) + self._next_key += 1 + self._pending = True + return self._next_key - 1 + + def remove(self, key): + """Remove the keyed message; raise KeyError if it doesn't exist.""" + self._lookup(key) + del self._toc[key] + self._pending = True + + def __setitem__(self, key, message): + """Replace the keyed message; raise KeyError if it doesn't exist.""" + self._lookup(key) + self._toc[key] = self._append_message(message) + self._pending = True + + def iterkeys(self): + """Return an iterator over keys.""" + self._lookup() + for key in self._toc.keys(): + yield key + + def has_key(self, key): + """Return True if the keyed message exists, False otherwise.""" + self._lookup() + return key in self._toc + + def __len__(self): + """Return a count of messages in the mailbox.""" + self._lookup() + return len(self._toc) + + def lock(self): + """Lock the mailbox.""" + if not self._locked: + _lock_file(self._file) + self._locked = True + + def unlock(self): + """Unlock the mailbox if it is locked.""" + if self._locked: + _unlock_file(self._file) + self._locked = False + + def flush(self): + """Write any pending changes to disk.""" + if not self._pending: + return + + # In order to be writing anything out at all, self._toc must + # already have been generated (and presumably has been modified + # by adding or deleting an item). + assert self._toc is not None + + # Check length of self._file; if it's changed, some other process + # has modified the mailbox since we scanned it. + self._file.seek(0, 2) + cur_len = self._file.tell() + if cur_len != self._file_length: + raise ExternalClashError('Size of mailbox file changed ' + '(expected %i, found %i)' % + (self._file_length, cur_len)) + + new_file = _create_temporary(self._path) + try: + new_toc = {} + self._pre_mailbox_hook(new_file) + for key in sorted(self._toc.keys()): + start, stop = self._toc[key] + self._file.seek(start) + self._pre_message_hook(new_file) + new_start = new_file.tell() + while True: + buffer = self._file.read(min(4096, + stop - self._file.tell())) + if buffer == '': + break + new_file.write(buffer) + new_toc[key] = (new_start, new_file.tell()) + self._post_message_hook(new_file) + except: + new_file.close() + os.remove(new_file.name) + raise + _sync_close(new_file) + # self._file is about to get replaced, so no need to sync. + self._file.close() + try: + os.rename(new_file.name, self._path) + except OSError, e: + if e.errno == errno.EEXIST or \ + (os.name == 'os2' and e.errno == errno.EACCES): + os.remove(self._path) + os.rename(new_file.name, self._path) + else: + raise + self._file = open(self._path, 'rb+') + self._toc = new_toc + self._pending = False + if self._locked: + _lock_file(self._file, dotlock=False) + + def _pre_mailbox_hook(self, f): + """Called before writing the mailbox to file f.""" + return + + def _pre_message_hook(self, f): + """Called before writing each message to file f.""" + return + + def _post_message_hook(self, f): + """Called after writing each message to file f.""" + return + + def close(self): + """Flush and close the mailbox.""" + self.flush() + if self._locked: + self.unlock() + self._file.close() # Sync has been done by self.flush() above. + + def _lookup(self, key=None): + """Return (start, stop) or raise KeyError.""" + if self._toc is None: + self._generate_toc() + if key is not None: + try: + return self._toc[key] + except KeyError: + raise KeyError('No message with key: %s' % key) + + def _append_message(self, message): + """Append message to mailbox and return (start, stop) offsets.""" + self._file.seek(0, 2) + before = self._file.tell() + try: + self._pre_message_hook(self._file) + offsets = self._install_message(message) + self._post_message_hook(self._file) + except BaseException: + self._file.truncate(before) + raise + self._file.flush() + self._file_length = self._file.tell() # Record current length of mailbox + return offsets + + + +class _mboxMMDF(_singlefileMailbox): + """An mbox or MMDF mailbox.""" + + _mangle_from_ = True + + def get_message(self, key): + """Return a Message representation or raise a KeyError.""" + start, stop = self._lookup(key) + self._file.seek(start) + from_line = self._file.readline().replace(os.linesep, '') + string = self._file.read(stop - self._file.tell()) + msg = self._message_factory(string.replace(os.linesep, '\n')) + msg.set_from(from_line[5:]) + return msg + + def get_string(self, key, from_=False): + """Return a string representation or raise a KeyError.""" + start, stop = self._lookup(key) + self._file.seek(start) + if not from_: + self._file.readline() + string = self._file.read(stop - self._file.tell()) + return string.replace(os.linesep, '\n') + + def get_file(self, key, from_=False): + """Return a file-like representation or raise a KeyError.""" + start, stop = self._lookup(key) + self._file.seek(start) + if not from_: + self._file.readline() + return _PartialFile(self._file, self._file.tell(), stop) + + def _install_message(self, message): + """Format a message and blindly write to self._file.""" + from_line = None + if isinstance(message, str) and message.startswith('From '): + newline = message.find('\n') + if newline != -1: + from_line = message[:newline] + message = message[newline + 1:] + else: + from_line = message + message = '' + elif isinstance(message, _mboxMMDFMessage): + from_line = 'From ' + message.get_from() + elif isinstance(message, email.message.Message): + from_line = message.get_unixfrom() # May be None. + if from_line is None: + from_line = 'From MAILER-DAEMON %s' % time.asctime(time.gmtime()) + start = self._file.tell() + self._file.write(from_line + os.linesep) + self._dump_message(message, self._file, self._mangle_from_) + stop = self._file.tell() + return (start, stop) + + +class mbox(_mboxMMDF): + """A classic mbox mailbox.""" + + _mangle_from_ = True + + def __init__(self, path, factory=None, create=True): + """Initialize an mbox mailbox.""" + self._message_factory = mboxMessage + _mboxMMDF.__init__(self, path, factory, create) + + def _pre_message_hook(self, f): + """Called before writing each message to file f.""" + if f.tell() != 0: + f.write(os.linesep) + + def _generate_toc(self): + """Generate key-to-(start, stop) table of contents.""" + starts, stops = [], [] + self._file.seek(0) + while True: + line_pos = self._file.tell() + line = self._file.readline() + if line.startswith('From '): + if len(stops) < len(starts): + stops.append(line_pos - len(os.linesep)) + starts.append(line_pos) + elif line == '': + stops.append(line_pos) + break + self._toc = dict(enumerate(zip(starts, stops))) + self._next_key = len(self._toc) + self._file_length = self._file.tell() + + +class MMDF(_mboxMMDF): + """An MMDF mailbox.""" + + def __init__(self, path, factory=None, create=True): + """Initialize an MMDF mailbox.""" + self._message_factory = MMDFMessage + _mboxMMDF.__init__(self, path, factory, create) + + def _pre_message_hook(self, f): + """Called before writing each message to file f.""" + f.write('\001\001\001\001' + os.linesep) + + def _post_message_hook(self, f): + """Called after writing each message to file f.""" + f.write(os.linesep + '\001\001\001\001' + os.linesep) + + def _generate_toc(self): + """Generate key-to-(start, stop) table of contents.""" + starts, stops = [], [] + self._file.seek(0) + next_pos = 0 + while True: + line_pos = next_pos + line = self._file.readline() + next_pos = self._file.tell() + if line.startswith('\001\001\001\001' + os.linesep): + starts.append(next_pos) + while True: + line_pos = next_pos + line = self._file.readline() + next_pos = self._file.tell() + if line == '\001\001\001\001' + os.linesep: + stops.append(line_pos - len(os.linesep)) + break + elif line == '': + stops.append(line_pos) + break + elif line == '': + break + self._toc = dict(enumerate(zip(starts, stops))) + self._next_key = len(self._toc) + self._file.seek(0, 2) + self._file_length = self._file.tell() + + +class MH(Mailbox): + """An MH mailbox.""" + + def __init__(self, path, factory=None, create=True): + """Initialize an MH instance.""" + Mailbox.__init__(self, path, factory, create) + if not os.path.exists(self._path): + if create: + os.mkdir(self._path, 0700) + os.close(os.open(os.path.join(self._path, '.mh_sequences'), + os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0600)) + else: + raise NoSuchMailboxError(self._path) + self._locked = False + + def add(self, message): + """Add message and return assigned key.""" + keys = self.keys() + if len(keys) == 0: + new_key = 1 + else: + new_key = max(keys) + 1 + new_path = os.path.join(self._path, str(new_key)) + f = _create_carefully(new_path) + closed = False + try: + if self._locked: + _lock_file(f) + try: + try: + self._dump_message(message, f) + except BaseException: + # Unlock and close so it can be deleted on Windows + if self._locked: + _unlock_file(f) + _sync_close(f) + closed = True + os.remove(new_path) + raise + if isinstance(message, MHMessage): + self._dump_sequences(message, new_key) + finally: + if self._locked: + _unlock_file(f) + finally: + if not closed: + _sync_close(f) + return new_key + + def remove(self, key): + """Remove the keyed message; raise KeyError if it doesn't exist.""" + path = os.path.join(self._path, str(key)) + try: + f = open(path, 'rb+') + except IOError, e: + if e.errno == errno.ENOENT: + raise KeyError('No message with key: %s' % key) + else: + raise + else: + f.close() + os.remove(path) + + def __setitem__(self, key, message): + """Replace the keyed message; raise KeyError if it doesn't exist.""" + path = os.path.join(self._path, str(key)) + try: + f = open(path, 'rb+') + except IOError, e: + if e.errno == errno.ENOENT: + raise KeyError('No message with key: %s' % key) + else: + raise + try: + if self._locked: + _lock_file(f) + try: + os.close(os.open(path, os.O_WRONLY | os.O_TRUNC)) + self._dump_message(message, f) + if isinstance(message, MHMessage): + self._dump_sequences(message, key) + finally: + if self._locked: + _unlock_file(f) + finally: + _sync_close(f) + + def get_message(self, key): + """Return a Message representation or raise a KeyError.""" + try: + if self._locked: + f = open(os.path.join(self._path, str(key)), 'r+') + else: + f = open(os.path.join(self._path, str(key)), 'r') + except IOError, e: + if e.errno == errno.ENOENT: + raise KeyError('No message with key: %s' % key) + else: + raise + try: + if self._locked: + _lock_file(f) + try: + msg = MHMessage(f) + finally: + if self._locked: + _unlock_file(f) + finally: + f.close() + for name, key_list in self.get_sequences().iteritems(): + if key in key_list: + msg.add_sequence(name) + return msg + + def get_string(self, key): + """Return a string representation or raise a KeyError.""" + try: + if self._locked: + f = open(os.path.join(self._path, str(key)), 'r+') + else: + f = open(os.path.join(self._path, str(key)), 'r') + except IOError, e: + if e.errno == errno.ENOENT: + raise KeyError('No message with key: %s' % key) + else: + raise + try: + if self._locked: + _lock_file(f) + try: + return f.read() + finally: + if self._locked: + _unlock_file(f) + finally: + f.close() + + def get_file(self, key): + """Return a file-like representation or raise a KeyError.""" + try: + f = open(os.path.join(self._path, str(key)), 'rb') + except IOError, e: + if e.errno == errno.ENOENT: + raise KeyError('No message with key: %s' % key) + else: + raise + return _ProxyFile(f) + + def iterkeys(self): + """Return an iterator over keys.""" + return iter(sorted(int(entry) for entry in os.listdir(self._path) + if entry.isdigit())) + + def has_key(self, key): + """Return True if the keyed message exists, False otherwise.""" + return os.path.exists(os.path.join(self._path, str(key))) + + def __len__(self): + """Return a count of messages in the mailbox.""" + return len(list(self.iterkeys())) + + def lock(self): + """Lock the mailbox.""" + if not self._locked: + self._file = open(os.path.join(self._path, '.mh_sequences'), 'rb+') + _lock_file(self._file) + self._locked = True + + def unlock(self): + """Unlock the mailbox if it is locked.""" + if self._locked: + _unlock_file(self._file) + _sync_close(self._file) + del self._file + self._locked = False + + def flush(self): + """Write any pending changes to the disk.""" + return + + def close(self): + """Flush and close the mailbox.""" + if self._locked: + self.unlock() + + def list_folders(self): + """Return a list of folder names.""" + result = [] + for entry in os.listdir(self._path): + if os.path.isdir(os.path.join(self._path, entry)): + result.append(entry) + return result + + def get_folder(self, folder): + """Return an MH instance for the named folder.""" + return MH(os.path.join(self._path, folder), + factory=self._factory, create=False) + + def add_folder(self, folder): + """Create a folder and return an MH instance representing it.""" + return MH(os.path.join(self._path, folder), + factory=self._factory) + + def remove_folder(self, folder): + """Delete the named folder, which must be empty.""" + path = os.path.join(self._path, folder) + entries = os.listdir(path) + if entries == ['.mh_sequences']: + os.remove(os.path.join(path, '.mh_sequences')) + elif entries == []: + pass + else: + raise NotEmptyError('Folder not empty: %s' % self._path) + os.rmdir(path) + + def get_sequences(self): + """Return a name-to-key-list dictionary to define each sequence.""" + results = {} + f = open(os.path.join(self._path, '.mh_sequences'), 'r') + try: + all_keys = set(self.keys()) + for line in f: + try: + name, contents = line.split(':') + keys = set() + for spec in contents.split(): + if spec.isdigit(): + keys.add(int(spec)) + else: + start, stop = (int(x) for x in spec.split('-')) + keys.update(range(start, stop + 1)) + results[name] = [key for key in sorted(keys) \ + if key in all_keys] + if len(results[name]) == 0: + del results[name] + except ValueError: + raise FormatError('Invalid sequence specification: %s' % + line.rstrip()) + finally: + f.close() + return results + + def set_sequences(self, sequences): + """Set sequences using the given name-to-key-list dictionary.""" + f = open(os.path.join(self._path, '.mh_sequences'), 'r+') + try: + os.close(os.open(f.name, os.O_WRONLY | os.O_TRUNC)) + for name, keys in sequences.iteritems(): + if len(keys) == 0: + continue + f.write('%s:' % name) + prev = None + completing = False + for key in sorted(set(keys)): + if key - 1 == prev: + if not completing: + completing = True + f.write('-') + elif completing: + completing = False + f.write('%s %s' % (prev, key)) + else: + f.write(' %s' % key) + prev = key + if completing: + f.write(str(prev) + '\n') + else: + f.write('\n') + finally: + _sync_close(f) + + def pack(self): + """Re-name messages to eliminate numbering gaps. Invalidates keys.""" + sequences = self.get_sequences() + prev = 0 + changes = [] + for key in self.iterkeys(): + if key - 1 != prev: + changes.append((key, prev + 1)) + if hasattr(os, 'link'): + os.link(os.path.join(self._path, str(key)), + os.path.join(self._path, str(prev + 1))) + os.unlink(os.path.join(self._path, str(key))) + else: + os.rename(os.path.join(self._path, str(key)), + os.path.join(self._path, str(prev + 1))) + prev += 1 + self._next_key = prev + 1 + if len(changes) == 0: + return + for name, key_list in sequences.items(): + for old, new in changes: + if old in key_list: + key_list[key_list.index(old)] = new + self.set_sequences(sequences) + + def _dump_sequences(self, message, key): + """Inspect a new MHMessage and update sequences appropriately.""" + pending_sequences = message.get_sequences() + all_sequences = self.get_sequences() + for name, key_list in all_sequences.iteritems(): + if name in pending_sequences: + key_list.append(key) + elif key in key_list: + del key_list[key_list.index(key)] + for sequence in pending_sequences: + if sequence not in all_sequences: + all_sequences[sequence] = [key] + self.set_sequences(all_sequences) + + +class Babyl(_singlefileMailbox): + """An Rmail-style Babyl mailbox.""" + + _special_labels = frozenset(('unseen', 'deleted', 'filed', 'answered', + 'forwarded', 'edited', 'resent')) + + def __init__(self, path, factory=None, create=True): + """Initialize a Babyl mailbox.""" + _singlefileMailbox.__init__(self, path, factory, create) + self._labels = {} + + def add(self, message): + """Add message and return assigned key.""" + key = _singlefileMailbox.add(self, message) + if isinstance(message, BabylMessage): + self._labels[key] = message.get_labels() + return key + + def remove(self, key): + """Remove the keyed message; raise KeyError if it doesn't exist.""" + _singlefileMailbox.remove(self, key) + if key in self._labels: + del self._labels[key] + + def __setitem__(self, key, message): + """Replace the keyed message; raise KeyError if it doesn't exist.""" + _singlefileMailbox.__setitem__(self, key, message) + if isinstance(message, BabylMessage): + self._labels[key] = message.get_labels() + + def get_message(self, key): + """Return a Message representation or raise a KeyError.""" + start, stop = self._lookup(key) + self._file.seek(start) + self._file.readline() # Skip '1,' line specifying labels. + original_headers = StringIO.StringIO() + while True: + line = self._file.readline() + if line == '*** EOOH ***' + os.linesep or line == '': + break + original_headers.write(line.replace(os.linesep, '\n')) + visible_headers = StringIO.StringIO() + while True: + line = self._file.readline() + if line == os.linesep or line == '': + break + visible_headers.write(line.replace(os.linesep, '\n')) + body = self._file.read(stop - self._file.tell()).replace(os.linesep, + '\n') + msg = BabylMessage(original_headers.getvalue() + body) + msg.set_visible(visible_headers.getvalue()) + if key in self._labels: + msg.set_labels(self._labels[key]) + return msg + + def get_string(self, key): + """Return a string representation or raise a KeyError.""" + start, stop = self._lookup(key) + self._file.seek(start) + self._file.readline() # Skip '1,' line specifying labels. + original_headers = StringIO.StringIO() + while True: + line = self._file.readline() + if line == '*** EOOH ***' + os.linesep or line == '': + break + original_headers.write(line.replace(os.linesep, '\n')) + while True: + line = self._file.readline() + if line == os.linesep or line == '': + break + return original_headers.getvalue() + \ + self._file.read(stop - self._file.tell()).replace(os.linesep, + '\n') + + def get_file(self, key): + """Return a file-like representation or raise a KeyError.""" + return StringIO.StringIO(self.get_string(key).replace('\n', + os.linesep)) + + def get_labels(self): + """Return a list of user-defined labels in the mailbox.""" + self._lookup() + labels = set() + for label_list in self._labels.values(): + labels.update(label_list) + labels.difference_update(self._special_labels) + return list(labels) + + def _generate_toc(self): + """Generate key-to-(start, stop) table of contents.""" + starts, stops = [], [] + self._file.seek(0) + next_pos = 0 + label_lists = [] + while True: + line_pos = next_pos + line = self._file.readline() + next_pos = self._file.tell() + if line == '\037\014' + os.linesep: + if len(stops) < len(starts): + stops.append(line_pos - len(os.linesep)) + starts.append(next_pos) + labels = [label.strip() for label + in self._file.readline()[1:].split(',') + if label.strip() != ''] + label_lists.append(labels) + elif line == '\037' or line == '\037' + os.linesep: + if len(stops) < len(starts): + stops.append(line_pos - len(os.linesep)) + elif line == '': + stops.append(line_pos - len(os.linesep)) + break + self._toc = dict(enumerate(zip(starts, stops))) + self._labels = dict(enumerate(label_lists)) + self._next_key = len(self._toc) + self._file.seek(0, 2) + self._file_length = self._file.tell() + + def _pre_mailbox_hook(self, f): + """Called before writing the mailbox to file f.""" + f.write('BABYL OPTIONS:%sVersion: 5%sLabels:%s%s\037' % + (os.linesep, os.linesep, ','.join(self.get_labels()), + os.linesep)) + + def _pre_message_hook(self, f): + """Called before writing each message to file f.""" + f.write('\014' + os.linesep) + + def _post_message_hook(self, f): + """Called after writing each message to file f.""" + f.write(os.linesep + '\037') + + def _install_message(self, message): + """Write message contents and return (start, stop).""" + start = self._file.tell() + if isinstance(message, BabylMessage): + special_labels = [] + labels = [] + for label in message.get_labels(): + if label in self._special_labels: + special_labels.append(label) + else: + labels.append(label) + self._file.write('1') + for label in special_labels: + self._file.write(', ' + label) + self._file.write(',,') + for label in labels: + self._file.write(' ' + label + ',') + self._file.write(os.linesep) + else: + self._file.write('1,,' + os.linesep) + if isinstance(message, email.message.Message): + orig_buffer = StringIO.StringIO() + orig_generator = email.generator.Generator(orig_buffer, False, 0) + orig_generator.flatten(message) + orig_buffer.seek(0) + while True: + line = orig_buffer.readline() + self._file.write(line.replace('\n', os.linesep)) + if line == '\n' or line == '': + break + self._file.write('*** EOOH ***' + os.linesep) + if isinstance(message, BabylMessage): + vis_buffer = StringIO.StringIO() + vis_generator = email.generator.Generator(vis_buffer, False, 0) + vis_generator.flatten(message.get_visible()) + while True: + line = vis_buffer.readline() + self._file.write(line.replace('\n', os.linesep)) + if line == '\n' or line == '': + break + else: + orig_buffer.seek(0) + while True: + line = orig_buffer.readline() + self._file.write(line.replace('\n', os.linesep)) + if line == '\n' or line == '': + break + while True: + buffer = orig_buffer.read(4096) # Buffer size is arbitrary. + if buffer == '': + break + self._file.write(buffer.replace('\n', os.linesep)) + elif isinstance(message, str): + body_start = message.find('\n\n') + 2 + if body_start - 2 != -1: + self._file.write(message[:body_start].replace('\n', + os.linesep)) + self._file.write('*** EOOH ***' + os.linesep) + self._file.write(message[:body_start].replace('\n', + os.linesep)) + self._file.write(message[body_start:].replace('\n', + os.linesep)) + else: + self._file.write('*** EOOH ***' + os.linesep + os.linesep) + self._file.write(message.replace('\n', os.linesep)) + elif hasattr(message, 'readline'): + original_pos = message.tell() + first_pass = True + while True: + line = message.readline() + self._file.write(line.replace('\n', os.linesep)) + if line == '\n' or line == '': + self._file.write('*** EOOH ***' + os.linesep) + if first_pass: + first_pass = False + message.seek(original_pos) + else: + break + while True: + buffer = message.read(4096) # Buffer size is arbitrary. + if buffer == '': + break + self._file.write(buffer.replace('\n', os.linesep)) + else: + raise TypeError('Invalid message type: %s' % type(message)) + stop = self._file.tell() + return (start, stop) + + +class Message(email.message.Message): + """Message with mailbox-format-specific properties.""" + + def __init__(self, message=None): + """Initialize a Message instance.""" + if isinstance(message, email.message.Message): + self._become_message(copy.deepcopy(message)) + if isinstance(message, Message): + message._explain_to(self) + elif isinstance(message, str): + self._become_message(email.message_from_string(message)) + elif hasattr(message, "read"): + self._become_message(email.message_from_file(message)) + elif message is None: + email.message.Message.__init__(self) + else: + raise TypeError('Invalid message type: %s' % type(message)) + + def _become_message(self, message): + """Assume the non-format-specific state of message.""" + for name in ('_headers', '_unixfrom', '_payload', '_charset', + 'preamble', 'epilogue', 'defects', '_default_type'): + self.__dict__[name] = message.__dict__[name] + + def _explain_to(self, message): + """Copy format-specific state to message insofar as possible.""" + if isinstance(message, Message): + return # There's nothing format-specific to explain. + else: + raise TypeError('Cannot convert to specified type') + + +class MaildirMessage(Message): + """Message with Maildir-specific properties.""" + + def __init__(self, message=None): + """Initialize a MaildirMessage instance.""" + self._subdir = 'new' + self._info = '' + self._date = time.time() + Message.__init__(self, message) + + def get_subdir(self): + """Return 'new' or 'cur'.""" + return self._subdir + + def set_subdir(self, subdir): + """Set subdir to 'new' or 'cur'.""" + if subdir == 'new' or subdir == 'cur': + self._subdir = subdir + else: + raise ValueError("subdir must be 'new' or 'cur': %s" % subdir) + + def get_flags(self): + """Return as a string the flags that are set.""" + if self._info.startswith('2,'): + return self._info[2:] + else: + return '' + + def set_flags(self, flags): + """Set the given flags and unset all others.""" + self._info = '2,' + ''.join(sorted(flags)) + + def add_flag(self, flag): + """Set the given flag(s) without changing others.""" + self.set_flags(''.join(set(self.get_flags()) | set(flag))) + + def remove_flag(self, flag): + """Unset the given string flag(s) without changing others.""" + if self.get_flags() != '': + self.set_flags(''.join(set(self.get_flags()) - set(flag))) + + def get_date(self): + """Return delivery date of message, in seconds since the epoch.""" + return self._date + + def set_date(self, date): + """Set delivery date of message, in seconds since the epoch.""" + try: + self._date = float(date) + except ValueError: + raise TypeError("can't convert to float: %s" % date) + + def get_info(self): + """Get the message's "info" as a string.""" + return self._info + + def set_info(self, info): + """Set the message's "info" string.""" + if isinstance(info, str): + self._info = info + else: + raise TypeError('info must be a string: %s' % type(info)) + + def _explain_to(self, message): + """Copy Maildir-specific state to message insofar as possible.""" + if isinstance(message, MaildirMessage): + message.set_flags(self.get_flags()) + message.set_subdir(self.get_subdir()) + message.set_date(self.get_date()) + elif isinstance(message, _mboxMMDFMessage): + flags = set(self.get_flags()) + if 'S' in flags: + message.add_flag('R') + if self.get_subdir() == 'cur': + message.add_flag('O') + if 'T' in flags: + message.add_flag('D') + if 'F' in flags: + message.add_flag('F') + if 'R' in flags: + message.add_flag('A') + message.set_from('MAILER-DAEMON', time.gmtime(self.get_date())) + elif isinstance(message, MHMessage): + flags = set(self.get_flags()) + if 'S' not in flags: + message.add_sequence('unseen') + if 'R' in flags: + message.add_sequence('replied') + if 'F' in flags: + message.add_sequence('flagged') + elif isinstance(message, BabylMessage): + flags = set(self.get_flags()) + if 'S' not in flags: + message.add_label('unseen') + if 'T' in flags: + message.add_label('deleted') + if 'R' in flags: + message.add_label('answered') + if 'P' in flags: + message.add_label('forwarded') + elif isinstance(message, Message): + pass + else: + raise TypeError('Cannot convert to specified type: %s' % + type(message)) + + +class _mboxMMDFMessage(Message): + """Message with mbox- or MMDF-specific properties.""" + + def __init__(self, message=None): + """Initialize an mboxMMDFMessage instance.""" + self.set_from('MAILER-DAEMON', True) + if isinstance(message, email.message.Message): + unixfrom = message.get_unixfrom() + if unixfrom is not None and unixfrom.startswith('From '): + self.set_from(unixfrom[5:]) + Message.__init__(self, message) + + def get_from(self): + """Return contents of "From " line.""" + return self._from + + def set_from(self, from_, time_=None): + """Set "From " line, formatting and appending time_ if specified.""" + if time_ is not None: + if time_ is True: + time_ = time.gmtime() + from_ += ' ' + time.asctime(time_) + self._from = from_ + + def get_flags(self): + """Return as a string the flags that are set.""" + return self.get('Status', '') + self.get('X-Status', '') + + def set_flags(self, flags): + """Set the given flags and unset all others.""" + flags = set(flags) + status_flags, xstatus_flags = '', '' + for flag in ('R', 'O'): + if flag in flags: + status_flags += flag + flags.remove(flag) + for flag in ('D', 'F', 'A'): + if flag in flags: + xstatus_flags += flag + flags.remove(flag) + xstatus_flags += ''.join(sorted(flags)) + try: + self.replace_header('Status', status_flags) + except KeyError: + self.add_header('Status', status_flags) + try: + self.replace_header('X-Status', xstatus_flags) + except KeyError: + self.add_header('X-Status', xstatus_flags) + + def add_flag(self, flag): + """Set the given flag(s) without changing others.""" + self.set_flags(''.join(set(self.get_flags()) | set(flag))) + + def remove_flag(self, flag): + """Unset the given string flag(s) without changing others.""" + if 'Status' in self or 'X-Status' in self: + self.set_flags(''.join(set(self.get_flags()) - set(flag))) + + def _explain_to(self, message): + """Copy mbox- or MMDF-specific state to message insofar as possible.""" + if isinstance(message, MaildirMessage): + flags = set(self.get_flags()) + if 'O' in flags: + message.set_subdir('cur') + if 'F' in flags: + message.add_flag('F') + if 'A' in flags: + message.add_flag('R') + if 'R' in flags: + message.add_flag('S') + if 'D' in flags: + message.add_flag('T') + del message['status'] + del message['x-status'] + maybe_date = ' '.join(self.get_from().split()[-5:]) + try: + message.set_date(calendar.timegm(time.strptime(maybe_date, + '%a %b %d %H:%M:%S %Y'))) + except (ValueError, OverflowError): + pass + elif isinstance(message, _mboxMMDFMessage): + message.set_flags(self.get_flags()) + message.set_from(self.get_from()) + elif isinstance(message, MHMessage): + flags = set(self.get_flags()) + if 'R' not in flags: + message.add_sequence('unseen') + if 'A' in flags: + message.add_sequence('replied') + if 'F' in flags: + message.add_sequence('flagged') + del message['status'] + del message['x-status'] + elif isinstance(message, BabylMessage): + flags = set(self.get_flags()) + if 'R' not in flags: + message.add_label('unseen') + if 'D' in flags: + message.add_label('deleted') + if 'A' in flags: + message.add_label('answered') + del message['status'] + del message['x-status'] + elif isinstance(message, Message): + pass + else: + raise TypeError('Cannot convert to specified type: %s' % + type(message)) + + +class mboxMessage(_mboxMMDFMessage): + """Message with mbox-specific properties.""" + + +class MHMessage(Message): + """Message with MH-specific properties.""" + + def __init__(self, message=None): + """Initialize an MHMessage instance.""" + self._sequences = [] + Message.__init__(self, message) + + def get_sequences(self): + """Return a list of sequences that include the message.""" + return self._sequences[:] + + def set_sequences(self, sequences): + """Set the list of sequences that include the message.""" + self._sequences = list(sequences) + + def add_sequence(self, sequence): + """Add sequence to list of sequences including the message.""" + if isinstance(sequence, str): + if not sequence in self._sequences: + self._sequences.append(sequence) + else: + raise TypeError('sequence must be a string: %s' % type(sequence)) + + def remove_sequence(self, sequence): + """Remove sequence from the list of sequences including the message.""" + try: + self._sequences.remove(sequence) + except ValueError: + pass + + def _explain_to(self, message): + """Copy MH-specific state to message insofar as possible.""" + if isinstance(message, MaildirMessage): + sequences = set(self.get_sequences()) + if 'unseen' in sequences: + message.set_subdir('cur') + else: + message.set_subdir('cur') + message.add_flag('S') + if 'flagged' in sequences: + message.add_flag('F') + if 'replied' in sequences: + message.add_flag('R') + elif isinstance(message, _mboxMMDFMessage): + sequences = set(self.get_sequences()) + if 'unseen' not in sequences: + message.add_flag('RO') + else: + message.add_flag('O') + if 'flagged' in sequences: + message.add_flag('F') + if 'replied' in sequences: + message.add_flag('A') + elif isinstance(message, MHMessage): + for sequence in self.get_sequences(): + message.add_sequence(sequence) + elif isinstance(message, BabylMessage): + sequences = set(self.get_sequences()) + if 'unseen' in sequences: + message.add_label('unseen') + if 'replied' in sequences: + message.add_label('answered') + elif isinstance(message, Message): + pass + else: + raise TypeError('Cannot convert to specified type: %s' % + type(message)) + + +class BabylMessage(Message): + """Message with Babyl-specific properties.""" + + def __init__(self, message=None): + """Initialize an BabylMessage instance.""" + self._labels = [] + self._visible = Message() + Message.__init__(self, message) + + def get_labels(self): + """Return a list of labels on the message.""" + return self._labels[:] + + def set_labels(self, labels): + """Set the list of labels on the message.""" + self._labels = list(labels) + + def add_label(self, label): + """Add label to list of labels on the message.""" + if isinstance(label, str): + if label not in self._labels: + self._labels.append(label) + else: + raise TypeError('label must be a string: %s' % type(label)) + + def remove_label(self, label): + """Remove label from the list of labels on the message.""" + try: + self._labels.remove(label) + except ValueError: + pass + + def get_visible(self): + """Return a Message representation of visible headers.""" + return Message(self._visible) + + def set_visible(self, visible): + """Set the Message representation of visible headers.""" + self._visible = Message(visible) + + def update_visible(self): + """Update and/or sensibly generate a set of visible headers.""" + for header in self._visible.keys(): + if header in self: + self._visible.replace_header(header, self[header]) + else: + del self._visible[header] + for header in ('Date', 'From', 'Reply-To', 'To', 'CC', 'Subject'): + if header in self and header not in self._visible: + self._visible[header] = self[header] + + def _explain_to(self, message): + """Copy Babyl-specific state to message insofar as possible.""" + if isinstance(message, MaildirMessage): + labels = set(self.get_labels()) + if 'unseen' in labels: + message.set_subdir('cur') + else: + message.set_subdir('cur') + message.add_flag('S') + if 'forwarded' in labels or 'resent' in labels: + message.add_flag('P') + if 'answered' in labels: + message.add_flag('R') + if 'deleted' in labels: + message.add_flag('T') + elif isinstance(message, _mboxMMDFMessage): + labels = set(self.get_labels()) + if 'unseen' not in labels: + message.add_flag('RO') + else: + message.add_flag('O') + if 'deleted' in labels: + message.add_flag('D') + if 'answered' in labels: + message.add_flag('A') + elif isinstance(message, MHMessage): + labels = set(self.get_labels()) + if 'unseen' in labels: + message.add_sequence('unseen') + if 'answered' in labels: + message.add_sequence('replied') + elif isinstance(message, BabylMessage): + message.set_visible(self.get_visible()) + for label in self.get_labels(): + message.add_label(label) + elif isinstance(message, Message): + pass + else: + raise TypeError('Cannot convert to specified type: %s' % + type(message)) + + +class MMDFMessage(_mboxMMDFMessage): + """Message with MMDF-specific properties.""" + + +class _ProxyFile: + """A read-only wrapper of a file.""" + + def __init__(self, f, pos=None): + """Initialize a _ProxyFile.""" + self._file = f + if pos is None: + self._pos = f.tell() + else: + self._pos = pos + + def read(self, size=None): + """Read bytes.""" + return self._read(size, self._file.read) + + def readline(self, size=None): + """Read a line.""" + return self._read(size, self._file.readline) + + def readlines(self, sizehint=None): + """Read multiple lines.""" + result = [] + for line in self: + result.append(line) + if sizehint is not None: + sizehint -= len(line) + if sizehint <= 0: + break + return result + + def __iter__(self): + """Iterate over lines.""" + return iter(self.readline, "") + + def tell(self): + """Return the position.""" + return self._pos + + def seek(self, offset, whence=0): + """Change position.""" + if whence == 1: + self._file.seek(self._pos) + self._file.seek(offset, whence) + self._pos = self._file.tell() + + def close(self): + """Close the file.""" + self._file.close() + del self._file + + def _read(self, size, read_method): + """Read size bytes using read_method.""" + if size is None: + size = -1 + self._file.seek(self._pos) + result = read_method(size) + self._pos = self._file.tell() + return result + + +class _PartialFile(_ProxyFile): + """A read-only wrapper of part of a file.""" + + def __init__(self, f, start=None, stop=None): + """Initialize a _PartialFile.""" + _ProxyFile.__init__(self, f, start) + self._start = start + self._stop = stop + + def tell(self): + """Return the position with respect to start.""" + return _ProxyFile.tell(self) - self._start + + def seek(self, offset, whence=0): + """Change position, possibly with respect to start or stop.""" + if whence == 0: + self._pos = self._start + whence = 1 + elif whence == 2: + self._pos = self._stop + whence = 1 + _ProxyFile.seek(self, offset, whence) + + def _read(self, size, read_method): + """Read size bytes using read_method, honoring start and stop.""" + remaining = self._stop - self._pos + if remaining <= 0: + return '' + if size is None or size < 0 or size > remaining: + size = remaining + return _ProxyFile._read(self, size, read_method) + + +def _lock_file(f, dotlock=True): + """Lock file f using lockf and dot locking.""" + dotlock_done = False + try: + if fcntl: + try: + fcntl.lockf(f, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError, e: + if e.errno in (errno.EAGAIN, errno.EACCES, errno.EROFS): + raise ExternalClashError('lockf: lock unavailable: %s' % + f.name) + else: + raise + if dotlock: + try: + pre_lock = _create_temporary(f.name + '.lock') + pre_lock.close() + except IOError, e: + if e.errno in (errno.EACCES, errno.EROFS): + return # Without write access, just skip dotlocking. + else: + raise + try: + if hasattr(os, 'link'): + os.link(pre_lock.name, f.name + '.lock') + dotlock_done = True + os.unlink(pre_lock.name) + else: + os.rename(pre_lock.name, f.name + '.lock') + dotlock_done = True + except OSError, e: + if e.errno == errno.EEXIST or \ + (os.name == 'os2' and e.errno == errno.EACCES): + os.remove(pre_lock.name) + raise ExternalClashError('dot lock unavailable: %s' % + f.name) + else: + raise + except: + if fcntl: + fcntl.lockf(f, fcntl.LOCK_UN) + if dotlock_done: + os.remove(f.name + '.lock') + raise + +def _unlock_file(f): + """Unlock file f using lockf and dot locking.""" + if fcntl: + fcntl.lockf(f, fcntl.LOCK_UN) + if os.path.exists(f.name + '.lock'): + os.remove(f.name + '.lock') + +def _create_carefully(path): + """Create a file if it doesn't exist and open for reading and writing.""" + fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, 0666) + try: + return open(path, 'rb+') + finally: + os.close(fd) + +def _create_temporary(path): + """Create a temp file based on path and open for reading and writing.""" + return _create_carefully('%s.%s.%s.%s' % (path, int(time.time()), + socket.gethostname(), + os.getpid())) + +def _sync_flush(f): + """Ensure changes to file f are physically on disk.""" + f.flush() + if hasattr(os, 'fsync'): + os.fsync(f.fileno()) + +def _sync_close(f): + """Close file f, ensuring all changes are physically on disk.""" + _sync_flush(f) + f.close() + +## Start: classes from the original module (for backward compatibility). + +# Note that the Maildir class, whose name is unchanged, itself offers a next() +# method for backward compatibility. + +class _Mailbox: + + def __init__(self, fp, factory=rfc822.Message): + self.fp = fp + self.seekp = 0 + self.factory = factory + + def __iter__(self): + return iter(self.next, None) + + def next(self): + while 1: + self.fp.seek(self.seekp) + try: + self._search_start() + except EOFError: + self.seekp = self.fp.tell() + return None + start = self.fp.tell() + self._search_end() + self.seekp = stop = self.fp.tell() + if start != stop: + break + return self.factory(_PartialFile(self.fp, start, stop)) + +# Recommended to use PortableUnixMailbox instead! +class UnixMailbox(_Mailbox): + + def _search_start(self): + while 1: + pos = self.fp.tell() + line = self.fp.readline() + if not line: + raise EOFError + if line[:5] == 'From ' and self._isrealfromline(line): + self.fp.seek(pos) + return + + def _search_end(self): + self.fp.readline() # Throw away header line + while 1: + pos = self.fp.tell() + line = self.fp.readline() + if not line: + return + if line[:5] == 'From ' and self._isrealfromline(line): + self.fp.seek(pos) + return + + # An overridable mechanism to test for From-line-ness. You can either + # specify a different regular expression or define a whole new + # _isrealfromline() method. Note that this only gets called for lines + # starting with the 5 characters "From ". + # + # BAW: According to + #http://home.netscape.com/eng/mozilla/2.0/relnotes/demo/content-length.html + # the only portable, reliable way to find message delimiters in a BSD (i.e + # Unix mailbox) style folder is to search for "\n\nFrom .*\n", or at the + # beginning of the file, "^From .*\n". While _fromlinepattern below seems + # like a good idea, in practice, there are too many variations for more + # strict parsing of the line to be completely accurate. + # + # _strict_isrealfromline() is the old version which tries to do stricter + # parsing of the From_ line. _portable_isrealfromline() simply returns + # true, since it's never called if the line doesn't already start with + # "From ". + # + # This algorithm, and the way it interacts with _search_start() and + # _search_end() may not be completely correct, because it doesn't check + # that the two characters preceding "From " are \n\n or the beginning of + # the file. Fixing this would require a more extensive rewrite than is + # necessary. For convenience, we've added a PortableUnixMailbox class + # which does no checking of the format of the 'From' line. + + _fromlinepattern = (r"From \s*[^\s]+\s+\w\w\w\s+\w\w\w\s+\d?\d\s+" + r"\d?\d:\d\d(:\d\d)?(\s+[^\s]+)?\s+\d\d\d\d\s*" + r"[^\s]*\s*" + "$") + _regexp = None + + def _strict_isrealfromline(self, line): + if not self._regexp: + import re + self._regexp = re.compile(self._fromlinepattern) + return self._regexp.match(line) + + def _portable_isrealfromline(self, line): + return True + + _isrealfromline = _strict_isrealfromline + + +class PortableUnixMailbox(UnixMailbox): + _isrealfromline = UnixMailbox._portable_isrealfromline + + +class MmdfMailbox(_Mailbox): + + def _search_start(self): + while 1: + line = self.fp.readline() + if not line: + raise EOFError + if line[:5] == '\001\001\001\001\n': + return + + def _search_end(self): + while 1: + pos = self.fp.tell() + line = self.fp.readline() + if not line: + return + if line == '\001\001\001\001\n': + self.fp.seek(pos) + return + + +class MHMailbox: + + def __init__(self, dirname, factory=rfc822.Message): + import re + pat = re.compile('^[1-9][0-9]*$') + self.dirname = dirname + # the three following lines could be combined into: + # list = map(long, filter(pat.match, os.listdir(self.dirname))) + list = os.listdir(self.dirname) + list = filter(pat.match, list) + list = map(long, list) + list.sort() + # This only works in Python 1.6 or later; + # before that str() added 'L': + self.boxes = map(str, list) + self.boxes.reverse() + self.factory = factory + + def __iter__(self): + return iter(self.next, None) + + def next(self): + if not self.boxes: + return None + fn = self.boxes.pop() + fp = open(os.path.join(self.dirname, fn)) + msg = self.factory(fp) + try: + msg._mh_msgno = fn + except (AttributeError, TypeError): + pass + return msg + + +class BabylMailbox(_Mailbox): + + def _search_start(self): + while 1: + line = self.fp.readline() + if not line: + raise EOFError + if line == '*** EOOH ***\n': + return + + def _search_end(self): + while 1: + pos = self.fp.tell() + line = self.fp.readline() + if not line: + return + if line == '\037\014\n' or line == '\037': + self.fp.seek(pos) + return + +## End: classes from the original module (for backward compatibility). + + +class Error(Exception): + """Raised for module-specific errors.""" + +class NoSuchMailboxError(Error): + """The specified mailbox does not exist and won't be created.""" + +class NotEmptyError(Error): + """The specified mailbox is not empty and deletion was requested.""" + +class ExternalClashError(Error): + """Another process caused an action to fail.""" + +class FormatError(Error): + """A file appears to have an invalid format.""" diff --git a/lib-python/2.7/test/test_old_mailbox.py b/lib-python/modified-2.7/test/test_old_mailbox.py rename from lib-python/2.7/test/test_old_mailbox.py rename to lib-python/modified-2.7/test/test_old_mailbox.py --- a/lib-python/2.7/test/test_old_mailbox.py +++ b/lib-python/modified-2.7/test/test_old_mailbox.py @@ -7,6 +7,15 @@ import unittest from test import test_support +def myunlink(path): + try: + os._unlink(path) + except: + import pdb;pdb.set_trace() + +#os._unlink = os.unlink +#os.unlink = myunlink + # cleanup earlier tests try: os.unlink(test_support.TESTFN) @@ -35,11 +44,14 @@ self._msgfiles = [] def tearDown(self): - map(os.unlink, self._msgfiles) - os.rmdir(os.path.join(self._dir, "cur")) - os.rmdir(os.path.join(self._dir, "tmp")) - os.rmdir(os.path.join(self._dir, "new")) - os.rmdir(self._dir) + try: + map(os.unlink, self._msgfiles) + os.rmdir(os.path.join(self._dir, "cur")) + os.rmdir(os.path.join(self._dir, "tmp")) + os.rmdir(os.path.join(self._dir, "new")) + os.rmdir(self._dir) + except: + import pdb;pdb.set_trace() def createMessage(self, dir, mbox=False): t = int(time.time() % 1000000) @@ -73,7 +85,9 @@ self.createMessage("cur") self.mbox = mailbox.Maildir(test_support.TESTFN) self.assertTrue(len(self.mbox) == 1) - self.assertTrue(self.mbox.next() is not None) + msg = self.mbox.next() + self.assertTrue(msg is not None) + msg.fp.close() self.assertTrue(self.mbox.next() is None) self.assertTrue(self.mbox.next() is None) @@ -81,7 +95,9 @@ self.createMessage("new") self.mbox = mailbox.Maildir(test_support.TESTFN) self.assertTrue(len(self.mbox) == 1) - self.assertTrue(self.mbox.next() is not None) + msg = self.mbox.next() + self.assertTrue(msg is not None) + msg.fp.close() self.assertTrue(self.mbox.next() is None) self.assertTrue(self.mbox.next() is None) @@ -90,8 +106,12 @@ self.createMessage("new") self.mbox = mailbox.Maildir(test_support.TESTFN) self.assertTrue(len(self.mbox) == 2) - self.assertTrue(self.mbox.next() is not None) - self.assertTrue(self.mbox.next() is not None) + msg = self.mbox.next() + self.assertTrue(msg is not None) + msg.fp.close() + msg = self.mbox.next() + self.assertTrue(msg is not None) + msg.fp.close() self.assertTrue(self.mbox.next() is None) self.assertTrue(self.mbox.next() is None) From noreply at buildbot.pypy.org Wed Apr 11 13:24:29 2012 From: noreply at buildbot.pypy.org (mattip) Date: Wed, 11 Apr 2012 13:24:29 +0200 (CEST) Subject: [pypy-commit] pypy win32-stdlib: add __del__, is it ever called? Message-ID: <20120411112429.CB74A82F4E@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-stdlib Changeset: r54284:a1087214df4c Date: 2012-04-11 13:42 +0300 http://bitbucket.org/pypy/pypy/changeset/a1087214df4c/ Log: add __del__, is it ever called? diff --git a/lib-python/modified-2.7/mailbox.py b/lib-python/modified-2.7/mailbox.py --- a/lib-python/modified-2.7/mailbox.py +++ b/lib-python/modified-2.7/mailbox.py @@ -1818,6 +1818,10 @@ else: self._pos = pos + def __del__(self): + if hasattr(self,'_file'): + self.close() + def read(self, size=None): """Read bytes.""" return self._read(size, self._file.read) From noreply at buildbot.pypy.org Wed Apr 11 13:24:31 2012 From: noreply at buildbot.pypy.org (mattip) Date: Wed, 11 Apr 2012 13:24:31 +0200 (CEST) Subject: [pypy-commit] pypy win32-stdlib: clean up cruft, fix parts of test_mailbox Message-ID: <20120411112431.4A74982F4E@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-stdlib Changeset: r54285:870298de090f Date: 2012-04-11 14:23 +0300 http://bitbucket.org/pypy/pypy/changeset/870298de090f/ Log: clean up cruft, fix parts of test_mailbox diff --git a/lib-python/2.7/test/test_mailbox.py b/lib-python/modified-2.7/test/test_mailbox.py rename from lib-python/2.7/test/test_mailbox.py rename to lib-python/modified-2.7/test/test_mailbox.py --- a/lib-python/2.7/test/test_mailbox.py +++ b/lib-python/modified-2.7/test/test_mailbox.py @@ -44,7 +44,7 @@ for name in dirs: os.rmdir(os.path.join(path, name)) os.rmdir(target) - elif os.path.exists(target): + elif os.pathtexists(target): os.remove(target) @@ -137,6 +137,7 @@ msg = self._box.get(key1) self.assertEqual(msg['from'], 'foo') self.assertEqual(msg.fp.read(), '1') + msg.fp.close() def test_getitem(self): # Retrieve message using __getitem__() @@ -169,10 +170,12 @@ # Get file representations of messages key0 = self._box.add(self._template % 0) key1 = self._box.add(_sample_message) - self.assertEqual(self._box.get_file(key0).read().replace(os.linesep, '\n'), - self._template % 0) - self.assertEqual(self._box.get_file(key1).read().replace(os.linesep, '\n'), - _sample_message) + msg = self._box.get_file(key0) + self.assertEqual(msg.read().replace(os.linesep, '\n'), self._template % 0) + msg.close() + msg = self._box.get_file(key1) + self.assertEqual(msg.read().replace(os.linesep, '\n'), _sample_message) + msg.close() def test_iterkeys(self): # Get keys using iterkeys() diff --git a/lib-python/modified-2.7/test/test_old_mailbox.py b/lib-python/modified-2.7/test/test_old_mailbox.py --- a/lib-python/modified-2.7/test/test_old_mailbox.py +++ b/lib-python/modified-2.7/test/test_old_mailbox.py @@ -7,15 +7,6 @@ import unittest from test import test_support -def myunlink(path): - try: - os._unlink(path) - except: - import pdb;pdb.set_trace() - -#os._unlink = os.unlink -#os.unlink = myunlink - # cleanup earlier tests try: os.unlink(test_support.TESTFN) @@ -44,14 +35,11 @@ self._msgfiles = [] def tearDown(self): - try: - map(os.unlink, self._msgfiles) - os.rmdir(os.path.join(self._dir, "cur")) - os.rmdir(os.path.join(self._dir, "tmp")) - os.rmdir(os.path.join(self._dir, "new")) - os.rmdir(self._dir) - except: - import pdb;pdb.set_trace() + map(os.unlink, self._msgfiles) + os.rmdir(os.path.join(self._dir, "cur")) + os.rmdir(os.path.join(self._dir, "tmp")) + os.rmdir(os.path.join(self._dir, "new")) + os.rmdir(self._dir) def createMessage(self, dir, mbox=False): t = int(time.time() % 1000000) From noreply at buildbot.pypy.org Wed Apr 11 15:03:20 2012 From: noreply at buildbot.pypy.org (fijal) Date: Wed, 11 Apr 2012 15:03:20 +0200 (CEST) Subject: [pypy-commit] extradoc extradoc: work on the blog post Message-ID: <20120411130320.3D61382F4E@wyvern.cs.uni-duesseldorf.de> Author: Maciej Fijalkowski Branch: extradoc Changeset: r4181:29ebad2bd7b6 Date: 2012-04-11 15:01 +0200 http://bitbucket.org/pypy/extradoc/changeset/29ebad2bd7b6/ Log: work on the blog post diff --git a/blog/draft/pycon-wrapup.rst b/blog/draft/pycon-wrapup.rst --- a/blog/draft/pycon-wrapup.rst +++ b/blog/draft/pycon-wrapup.rst @@ -10,7 +10,8 @@ PyPy and at least partly failing. He however did not fail to explain bits and pieces about PyPy's architecture. `Video`_ is available. -* We gave tons of talks, including the `tutorial`_ and `why pypy by example`_. +* We gave tons of talks, including the `tutorial`_, `why pypy by example`_ + and `pypy's JIT architecturew`_ * We had a giant influx of new commiters, easily doubling the amount of pull requests ever created for PyPy. The main topics for newcomers were numpy and @@ -19,9 +20,16 @@ * Guido argued in his keynote that Python is not too slow. In the meantime, we're trying to `prove him correct`_ :-) -* XXX stuff stuff +We would like to thank everyone who talked to us, shared ideas and especially +those who participated in sprints - we're always happy to welcome newcomers! -.. _`Video`: xxx -.. _`tutorial`: xxx -.. _`why pypy by example`: xxx +I'm sure there is tons of things I forgot, but thank you all! + +Cheers, +fijal + +.. _`Video`: http://pyvideo.org/video/659/keynote-david-beazley +.. _`tutorial`: http://pyvideo.org/video/612/how-to-get-the-most-out-of-your-pypy +.. _`why pypy by example`: http://pyvideo.org/video/661/why-pypy-by-example +.. _`pypy's JIT architecturew`: http://pyvideo.org/video/662/how-the-pypy-jit-works .. _`prove him correct`: http://mrjoes.github.com/2011/12/15/sockjs-bench.html From noreply at buildbot.pypy.org Wed Apr 11 15:03:21 2012 From: noreply at buildbot.pypy.org (fijal) Date: Wed, 11 Apr 2012 15:03:21 +0200 (CEST) Subject: [pypy-commit] extradoc extradoc: merge Message-ID: <20120411130321.EE6AF82F4E@wyvern.cs.uni-duesseldorf.de> Author: Maciej Fijalkowski Branch: extradoc Changeset: r4182:c61a7069d337 Date: 2012-04-11 15:02 +0200 http://bitbucket.org/pypy/extradoc/changeset/c61a7069d337/ Log: merge diff --git a/blog/draft/py3k-activity-march.png b/blog/draft/py3k-activity-march.png new file mode 100644 index 0000000000000000000000000000000000000000..23b5b2a2aa5a7284a06c01328c6c1b4561d2a063 GIT binary patch [cut] diff --git a/blog/draft/py3k-status-update-3.rst b/blog/draft/py3k-status-update-3.rst new file mode 100644 --- /dev/null +++ b/blog/draft/py3k-status-update-3.rst @@ -0,0 +1,64 @@ +Py3k status update #3 +--------------------- + +This is the third status update about my work on the `py3k branch`_, which I +can work on thanks to all of the people who donated_ to the `py3k proposal`_. + +A lot of work has been done during the last month: as usual, the list of +changes is too big to be reported in a detalied way, so this is just a summary +of what happened. + +One of the most active areas was killing old and deprecated features. In +particular, we killed support for the ``__cmp__`` special method and its +counsins, the ``cmp`` builtin function and keyword argument for +``list.sort()`` and ``sorted()``. Killing is easy, but then you have to fix +all the places which breaks because of this, including all the types which +relied on ``__cmp__`` to be comparable,, fixing all the tests which tried to +order objects which are no longer ordeable now, or implementing new behavior +like forbidding calling ``hash()`` on objects which implement ``__eq__`` but +not ``__hash__``. + +Among the other features, we killed lots of now-gone functions in the +``operator`` module, the builtins ``apply()``, ``reduce()`` and ``buffer``, +and the ``os.*`` functions to deal with temporary files, which has been +deprecated in favour of the new ``tempfile`` module. + +The other topic which can't miss in a py3k status update is, as usual, +string-vs-unicode. At this round, we fixed bugs in string formatting (in +particular to teach ``format()`` to always use unicode strings) and various +corner cases about when calling the (possibly overridden) ``__str__`` method +on subclasses of ``str``. Believe me, you don't want to know the precise rules +:-). + +Other features which we worked on and fixed tests include, but are not limited +to, ``marshal``, ``hashlib``, ``zipimport``, ``_socket`` and ``itertools``, +plus the habitual endless lists of tests which fail for shallow reasons such +as the syntactic differences, ``int`` vs ``long``, ``range()`` vs +``list(range())`` etc. As a result, the number of failing tests dropped_ from +650 to 235: we are beginning to see the light at the end of the tunnel :-) + +Benjamin finished implementing Python 3 syntax. Most of it was small cleanups +and tweaks to be compatible with CPython such as making ``True`` and ``False`` +keywords and preventing ``. . .`` from being parsed as ``Ellipsis``. Larger +syntax additions included keyword only arguments and function annotations. + +Finally, we did some RPython fixes, so that it is possible again to translate +PyPy in the py3k branch. However, the resuling binary is a strange beast which +mixes python 2 and python 3 semantics, so it is unusable for anything but +`showing friends how cool it's that`_. + +.. image:: py3k-activity-march.png + +I would like to underline that I was not alone in doing all this work. In +particular, a lot of people joined the PyPy sprint at Pycon and worked on the +branch, as you can clearly see in this activity graph. I would like to thank +all who helped! + +cheers, +Antonio and Benjamin + +.. _donated: http://morepypy.blogspot.com/2012/01/py3k-and-numpy-first-stage-thanks-to.html +.. _`py3k proposal`: http://pypy.org/py3donate.html +.. _`py3k branch`: https://bitbucket.org/pypy/pypy/src/py3k +.. _`showing friends how cool it's that`: http://paste.pocoo.org/show/577006/ +.. _dropped: http://buildbot.pypy.org/summary?category=linux32&branch=py3k&recentrev=53956:3c8ac35c653a diff --git a/sprintinfo/leipzig2012/people.txt b/sprintinfo/leipzig2012/people.txt --- a/sprintinfo/leipzig2012/people.txt +++ b/sprintinfo/leipzig2012/people.txt @@ -9,5 +9,6 @@ Name Arrive/Depart Accomodation ==================== ============== ===================== Armin Rigo ? ? +Christian Tismer ? ? ==================== ============== ===================== From noreply at buildbot.pypy.org Wed Apr 11 15:05:14 2012 From: noreply at buildbot.pypy.org (gutworth) Date: Wed, 11 Apr 2012 15:05:14 +0200 (CEST) Subject: [pypy-commit] extradoc extradoc: edits Message-ID: <20120411130514.E4C3982F4E@wyvern.cs.uni-duesseldorf.de> Author: Benjamin Peterson Branch: extradoc Changeset: r4183:a9af41b489f2 Date: 2012-04-06 13:30 -0400 http://bitbucket.org/pypy/extradoc/changeset/a9af41b489f2/ Log: edits diff --git a/blog/draft/py3k-status-update-3.rst b/blog/draft/py3k-status-update-3.rst --- a/blog/draft/py3k-status-update-3.rst +++ b/blog/draft/py3k-status-update-3.rst @@ -1,22 +1,22 @@ Py3k status update #3 --------------------- -This is the third status update about my work on the `py3k branch`_, which I +This is the third status update about our work on the `py3k branch`_, which we can work on thanks to all of the people who donated_ to the `py3k proposal`_. A lot of work has been done during the last month: as usual, the list of -changes is too big to be reported in a detalied way, so this is just a summary +changes is too big to be reported in a detailed way, so this is just a summary of what happened. One of the most active areas was killing old and deprecated features. In particular, we killed support for the ``__cmp__`` special method and its -counsins, the ``cmp`` builtin function and keyword argument for -``list.sort()`` and ``sorted()``. Killing is easy, but then you have to fix -all the places which breaks because of this, including all the types which -relied on ``__cmp__`` to be comparable,, fixing all the tests which tried to -order objects which are no longer ordeable now, or implementing new behavior -like forbidding calling ``hash()`` on objects which implement ``__eq__`` but -not ``__hash__``. +counsins, the ``cmp`` builtin function and keyword argument for ``list.sort()`` +and ``sorted()``. Killing is easy, but then you have to fix all the places +which breaks because of this, including all the types which relied on +``__cmp__`` to be comparable and all the tests which tried to order objects +which are no longer ordeable. New behavior, like forbidding calling ``hash()`` +on objects which implement ``__eq__`` but not ``__hash__``, also has to be +implemented. Among the other features, we killed lots of now-gone functions in the ``operator`` module, the builtins ``apply()``, ``reduce()`` and ``buffer``, From noreply at buildbot.pypy.org Wed Apr 11 15:05:16 2012 From: noreply at buildbot.pypy.org (gutworth) Date: Wed, 11 Apr 2012 15:05:16 +0200 (CEST) Subject: [pypy-commit] extradoc extradoc: merge heads Message-ID: <20120411130516.1D2D382F4E@wyvern.cs.uni-duesseldorf.de> Author: Benjamin Peterson Branch: extradoc Changeset: r4184:bb1caf50c520 Date: 2012-04-11 09:05 -0400 http://bitbucket.org/pypy/extradoc/changeset/bb1caf50c520/ Log: merge heads diff --git a/blog/draft/pycon-wrapup.rst b/blog/draft/pycon-wrapup.rst --- a/blog/draft/pycon-wrapup.rst +++ b/blog/draft/pycon-wrapup.rst @@ -10,7 +10,8 @@ PyPy and at least partly failing. He however did not fail to explain bits and pieces about PyPy's architecture. `Video`_ is available. -* We gave tons of talks, including the `tutorial`_ and `why pypy by example`_. +* We gave tons of talks, including the `tutorial`_, `why pypy by example`_ + and `pypy's JIT architecturew`_ * We had a giant influx of new commiters, easily doubling the amount of pull requests ever created for PyPy. The main topics for newcomers were numpy and @@ -19,9 +20,16 @@ * Guido argued in his keynote that Python is not too slow. In the meantime, we're trying to `prove him correct`_ :-) -* XXX stuff stuff +We would like to thank everyone who talked to us, shared ideas and especially +those who participated in sprints - we're always happy to welcome newcomers! -.. _`Video`: xxx -.. _`tutorial`: xxx -.. _`why pypy by example`: xxx +I'm sure there is tons of things I forgot, but thank you all! + +Cheers, +fijal + +.. _`Video`: http://pyvideo.org/video/659/keynote-david-beazley +.. _`tutorial`: http://pyvideo.org/video/612/how-to-get-the-most-out-of-your-pypy +.. _`why pypy by example`: http://pyvideo.org/video/661/why-pypy-by-example +.. _`pypy's JIT architecturew`: http://pyvideo.org/video/662/how-the-pypy-jit-works .. _`prove him correct`: http://mrjoes.github.com/2011/12/15/sockjs-bench.html From noreply at buildbot.pypy.org Wed Apr 11 15:23:57 2012 From: noreply at buildbot.pypy.org (edelsohn) Date: Wed, 11 Apr 2012 15:23:57 +0200 (CEST) Subject: [pypy-commit] extradoc extradoc: Some cleanups. Message-ID: <20120411132357.4BB8782F4E@wyvern.cs.uni-duesseldorf.de> Author: edelsohn Branch: extradoc Changeset: r4185:958c29a7ede1 Date: 2012-04-11 09:23 -0400 http://bitbucket.org/pypy/extradoc/changeset/958c29a7ede1/ Log: Some cleanups. diff --git a/blog/draft/pycon-wrapup.rst b/blog/draft/pycon-wrapup.rst --- a/blog/draft/pycon-wrapup.rst +++ b/blog/draft/pycon-wrapup.rst @@ -6,12 +6,13 @@ From the PyPy perspective, a lot at PyCon was about PyPy. Listing things: -* David Beazley did an excellent keynote on trying to dive head-first into - PyPy and at least partly failing. He however did not fail to explain - bits and pieces about PyPy's architecture. `Video`_ is available. +* David Beazley presented an excellent keynote describing his experience + diving head-first into PyPy and at least partly failing. He, however, did + not fail to explain bits and pieces about PyPy's architecture. + `Video`_ is available. * We gave tons of talks, including the `tutorial`_, `why pypy by example`_ - and `pypy's JIT architecturew`_ + and `pypy's JIT architecture`_ * We had a giant influx of new commiters, easily doubling the amount of pull requests ever created for PyPy. The main topics for newcomers were numpy and @@ -31,5 +32,5 @@ .. _`Video`: http://pyvideo.org/video/659/keynote-david-beazley .. _`tutorial`: http://pyvideo.org/video/612/how-to-get-the-most-out-of-your-pypy .. _`why pypy by example`: http://pyvideo.org/video/661/why-pypy-by-example -.. _`pypy's JIT architecturew`: http://pyvideo.org/video/662/how-the-pypy-jit-works +.. _`pypy's JIT architecture`: http://pyvideo.org/video/662/how-the-pypy-jit-works .. _`prove him correct`: http://mrjoes.github.com/2011/12/15/sockjs-bench.html From noreply at buildbot.pypy.org Wed Apr 11 15:30:33 2012 From: noreply at buildbot.pypy.org (mattip) Date: Wed, 11 Apr 2012 15:30:33 +0200 (CEST) Subject: [pypy-commit] pypy win32-stdlib: finish test_mailbox Message-ID: <20120411133033.658BB82F4E@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-stdlib Changeset: r54286:5e1e95bc2659 Date: 2012-04-11 16:00 +0300 http://bitbucket.org/pypy/pypy/changeset/5e1e95bc2659/ Log: finish test_mailbox diff --git a/lib-python/modified-2.7/mailbox.py b/lib-python/modified-2.7/mailbox.py --- a/lib-python/modified-2.7/mailbox.py +++ b/lib-python/modified-2.7/mailbox.py @@ -619,7 +619,9 @@ """Write any pending changes to disk.""" if not self._pending: return - + if self._file.closed: + self._pending = False + return # In order to be writing anything out at all, self._toc must # already have been generated (and presumably has been modified # by adding or deleting an item). @@ -747,6 +749,7 @@ """Return a file-like representation or raise a KeyError.""" start, stop = self._lookup(key) self._file.seek(start) + if not from_: self._file.readline() return _PartialFile(self._file, self._file.tell(), stop) diff --git a/lib-python/modified-2.7/test/test_mailbox.py b/lib-python/modified-2.7/test/test_mailbox.py --- a/lib-python/modified-2.7/test/test_mailbox.py +++ b/lib-python/modified-2.7/test/test_mailbox.py @@ -44,7 +44,7 @@ for name in dirs: os.rmdir(os.path.join(path, name)) os.rmdir(target) - elif os.pathtexists(target): + elif os.path.exists(target): os.remove(target) @@ -60,6 +60,8 @@ def tearDown(self): self._box.close() + if os.name == 'nt': + time.sleep(0.1) #Allow all syncing to take place self._delete_recursively(self._path) def test_add(self): @@ -170,12 +172,12 @@ # Get file representations of messages key0 = self._box.add(self._template % 0) key1 = self._box.add(_sample_message) - msg = self._box.get_file(key0) - self.assertEqual(msg.read().replace(os.linesep, '\n'), self._template % 0) - msg.close() - msg = self._box.get_file(key1) - self.assertEqual(msg.read().replace(os.linesep, '\n'), _sample_message) - msg.close() + msg0 = self._box.get_file(key0) + self.assertEqual(msg0.read().replace(os.linesep, '\n'), self._template % 0) + msg1 = self._box.get_file(key1) + self.assertEqual(msg1.read().replace(os.linesep, '\n'), _sample_message) + msg0.close() + msg1.close() def test_iterkeys(self): # Get keys using iterkeys() @@ -789,6 +791,8 @@ class _TestMboxMMDF(TestMailbox): def tearDown(self): + if os.name == 'nt': + time.sleep(0.1) #Allow os to sync files self._box.close() self._delete_recursively(self._path) for lock_remnant in glob.glob(self._path + '.*'): @@ -1840,7 +1844,9 @@ self.createMessage("cur") self.mbox = mailbox.Maildir(test_support.TESTFN) #self.assertTrue(len(self.mbox.boxes) == 1) - self.assertIsNot(self.mbox.next(), None) + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() self.assertIs(self.mbox.next(), None) self.assertIs(self.mbox.next(), None) @@ -1848,7 +1854,9 @@ self.createMessage("new") self.mbox = mailbox.Maildir(test_support.TESTFN) #self.assertTrue(len(self.mbox.boxes) == 1) - self.assertIsNot(self.mbox.next(), None) + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() self.assertIs(self.mbox.next(), None) self.assertIs(self.mbox.next(), None) @@ -1857,8 +1865,12 @@ self.createMessage("new") self.mbox = mailbox.Maildir(test_support.TESTFN) #self.assertTrue(len(self.mbox.boxes) == 2) - self.assertIsNot(self.mbox.next(), None) - self.assertIsNot(self.mbox.next(), None) + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() self.assertIs(self.mbox.next(), None) self.assertIs(self.mbox.next(), None) @@ -1867,11 +1879,13 @@ import email.parser fname = self.createMessage("cur", True) n = 0 - for msg in mailbox.PortableUnixMailbox(open(fname), + fid = open(fname) + for msg in mailbox.PortableUnixMailbox(fid, email.parser.Parser().parse): n += 1 self.assertEqual(msg["subject"], "Simple Test") self.assertEqual(len(str(msg)), len(FROM_)+len(DUMMY_MESSAGE)) + fid.close() self.assertEqual(n, 1) ## End: classes from the original module (for backward compatibility). From noreply at buildbot.pypy.org Wed Apr 11 15:30:34 2012 From: noreply at buildbot.pypy.org (mattip) Date: Wed, 11 Apr 2012 15:30:34 +0200 (CEST) Subject: [pypy-commit] pypy win32-stdlib: allow capitests to find libpypy-c Message-ID: <20120411133034.BA58D82F4E@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-stdlib Changeset: r54287:e26139b9710c Date: 2012-04-11 16:29 +0300 http://bitbucket.org/pypy/pypy/changeset/e26139b9710c/ Log: allow capitests to find libpypy-c diff --git a/lib_pypy/_testcapi.py b/lib_pypy/_testcapi.py --- a/lib_pypy/_testcapi.py +++ b/lib_pypy/_testcapi.py @@ -29,6 +29,13 @@ if sys.platform == 'win32': # XXX libpypy-c.lib is currently not installed automatically library = os.path.join(thisdir, '..', 'include', 'libpypy-c') + if not os.path.exists(library + '.lib'): + #For a nightly build + library = os.path.join(thisdir, '..', 'include', 'python27') + if not os.path.exists(library + '.lib'): + # For a local translation + library = os.path.join(thisdir, '..', 'pypy', 'translator', + 'goal', 'libpypy-c') libraries = [library, 'oleaut32'] extra_ldargs = ['/MANIFEST', # needed for VC10 '/EXPORT:init_testcapi'] From noreply at buildbot.pypy.org Wed Apr 11 15:35:53 2012 From: noreply at buildbot.pypy.org (edelsohn) Date: Wed, 11 Apr 2012 15:35:53 +0200 (CEST) Subject: [pypy-commit] extradoc extradoc: (DanielHolth): is -> are. Message-ID: <20120411133553.C226682F4E@wyvern.cs.uni-duesseldorf.de> Author: edelsohn Branch: extradoc Changeset: r4186:3cd508c8a5c1 Date: 2012-04-11 09:35 -0400 http://bitbucket.org/pypy/extradoc/changeset/3cd508c8a5c1/ Log: (DanielHolth): is -> are. diff --git a/blog/draft/pycon-wrapup.rst b/blog/draft/pycon-wrapup.rst --- a/blog/draft/pycon-wrapup.rst +++ b/blog/draft/pycon-wrapup.rst @@ -24,7 +24,7 @@ We would like to thank everyone who talked to us, shared ideas and especially those who participated in sprints - we're always happy to welcome newcomers! -I'm sure there is tons of things I forgot, but thank you all! +I'm sure there are tons of things I forgot, but thank you all! Cheers, fijal From noreply at buildbot.pypy.org Wed Apr 11 15:44:24 2012 From: noreply at buildbot.pypy.org (mattip) Date: Wed, 11 Apr 2012 15:44:24 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: merge win32-stdlib changes Message-ID: <20120411134424.9C5B082F4E@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54288:11f147df436c Date: 2012-04-11 16:43 +0300 http://bitbucket.org/pypy/pypy/changeset/11f147df436c/ Log: merge win32-stdlib changes diff --git a/lib-python/2.7/mailbox.py b/lib-python/2.7/mailbox.py deleted file mode 100644 --- a/lib-python/2.7/mailbox.py +++ /dev/null @@ -1,2171 +0,0 @@ -#! /usr/bin/env python - -"""Read/write support for Maildir, mbox, MH, Babyl, and MMDF mailboxes.""" - -# Notes for authors of new mailbox subclasses: -# -# Remember to fsync() changes to disk before closing a modified file -# or returning from a flush() method. See functions _sync_flush() and -# _sync_close(). - -import sys -import os -import time -import calendar -import socket -import errno -import copy -import email -import email.message -import email.generator -import StringIO -try: - if sys.platform == 'os2emx': - # OS/2 EMX fcntl() not adequate - raise ImportError - import fcntl -except ImportError: - fcntl = None - -import warnings -with warnings.catch_warnings(): - if sys.py3kwarning: - warnings.filterwarnings("ignore", ".*rfc822 has been removed", - DeprecationWarning) - import rfc822 - -__all__ = [ 'Mailbox', 'Maildir', 'mbox', 'MH', 'Babyl', 'MMDF', - 'Message', 'MaildirMessage', 'mboxMessage', 'MHMessage', - 'BabylMessage', 'MMDFMessage', 'UnixMailbox', - 'PortableUnixMailbox', 'MmdfMailbox', 'MHMailbox', 'BabylMailbox' ] - -class Mailbox: - """A group of messages in a particular place.""" - - def __init__(self, path, factory=None, create=True): - """Initialize a Mailbox instance.""" - self._path = os.path.abspath(os.path.expanduser(path)) - self._factory = factory - - def add(self, message): - """Add message and return assigned key.""" - raise NotImplementedError('Method must be implemented by subclass') - - def remove(self, key): - """Remove the keyed message; raise KeyError if it doesn't exist.""" - raise NotImplementedError('Method must be implemented by subclass') - - def __delitem__(self, key): - self.remove(key) - - def discard(self, key): - """If the keyed message exists, remove it.""" - try: - self.remove(key) - except KeyError: - pass - - def __setitem__(self, key, message): - """Replace the keyed message; raise KeyError if it doesn't exist.""" - raise NotImplementedError('Method must be implemented by subclass') - - def get(self, key, default=None): - """Return the keyed message, or default if it doesn't exist.""" - try: - return self.__getitem__(key) - except KeyError: - return default - - def __getitem__(self, key): - """Return the keyed message; raise KeyError if it doesn't exist.""" - if not self._factory: - return self.get_message(key) - else: - return self._factory(self.get_file(key)) - - def get_message(self, key): - """Return a Message representation or raise a KeyError.""" - raise NotImplementedError('Method must be implemented by subclass') - - def get_string(self, key): - """Return a string representation or raise a KeyError.""" - raise NotImplementedError('Method must be implemented by subclass') - - def get_file(self, key): - """Return a file-like representation or raise a KeyError.""" - raise NotImplementedError('Method must be implemented by subclass') - - def iterkeys(self): - """Return an iterator over keys.""" - raise NotImplementedError('Method must be implemented by subclass') - - def keys(self): - """Return a list of keys.""" - return list(self.iterkeys()) - - def itervalues(self): - """Return an iterator over all messages.""" - for key in self.iterkeys(): - try: - value = self[key] - except KeyError: - continue - yield value - - def __iter__(self): - return self.itervalues() - - def values(self): - """Return a list of messages. Memory intensive.""" - return list(self.itervalues()) - - def iteritems(self): - """Return an iterator over (key, message) tuples.""" - for key in self.iterkeys(): - try: - value = self[key] - except KeyError: - continue - yield (key, value) - - def items(self): - """Return a list of (key, message) tuples. Memory intensive.""" - return list(self.iteritems()) - - def has_key(self, key): - """Return True if the keyed message exists, False otherwise.""" - raise NotImplementedError('Method must be implemented by subclass') - - def __contains__(self, key): - return self.has_key(key) - - def __len__(self): - """Return a count of messages in the mailbox.""" - raise NotImplementedError('Method must be implemented by subclass') - - def clear(self): - """Delete all messages.""" - for key in self.iterkeys(): - self.discard(key) - - def pop(self, key, default=None): - """Delete the keyed message and return it, or default.""" - try: - result = self[key] - except KeyError: - return default - self.discard(key) - return result - - def popitem(self): - """Delete an arbitrary (key, message) pair and return it.""" - for key in self.iterkeys(): - return (key, self.pop(key)) # This is only run once. - else: - raise KeyError('No messages in mailbox') - - def update(self, arg=None): - """Change the messages that correspond to certain keys.""" - if hasattr(arg, 'iteritems'): - source = arg.iteritems() - elif hasattr(arg, 'items'): - source = arg.items() - else: - source = arg - bad_key = False - for key, message in source: - try: - self[key] = message - except KeyError: - bad_key = True - if bad_key: - raise KeyError('No message with key(s)') - - def flush(self): - """Write any pending changes to the disk.""" - raise NotImplementedError('Method must be implemented by subclass') - - def lock(self): - """Lock the mailbox.""" - raise NotImplementedError('Method must be implemented by subclass') - - def unlock(self): - """Unlock the mailbox if it is locked.""" - raise NotImplementedError('Method must be implemented by subclass') - - def close(self): - """Flush and close the mailbox.""" - raise NotImplementedError('Method must be implemented by subclass') - - def _dump_message(self, message, target, mangle_from_=False): - # Most files are opened in binary mode to allow predictable seeking. - # To get native line endings on disk, the user-friendly \n line endings - # used in strings and by email.Message are translated here. - """Dump message contents to target file.""" - if isinstance(message, email.message.Message): - buffer = StringIO.StringIO() - gen = email.generator.Generator(buffer, mangle_from_, 0) - gen.flatten(message) - buffer.seek(0) - target.write(buffer.read().replace('\n', os.linesep)) - elif isinstance(message, str): - if mangle_from_: - message = message.replace('\nFrom ', '\n>From ') - message = message.replace('\n', os.linesep) - target.write(message) - elif hasattr(message, 'read'): - while True: - line = message.readline() - if line == '': - break - if mangle_from_ and line.startswith('From '): - line = '>From ' + line[5:] - line = line.replace('\n', os.linesep) - target.write(line) - else: - raise TypeError('Invalid message type: %s' % type(message)) - - -class Maildir(Mailbox): - """A qmail-style Maildir mailbox.""" - - colon = ':' - - def __init__(self, dirname, factory=rfc822.Message, create=True): - """Initialize a Maildir instance.""" - Mailbox.__init__(self, dirname, factory, create) - self._paths = { - 'tmp': os.path.join(self._path, 'tmp'), - 'new': os.path.join(self._path, 'new'), - 'cur': os.path.join(self._path, 'cur'), - } - if not os.path.exists(self._path): - if create: - os.mkdir(self._path, 0700) - for path in self._paths.values(): - os.mkdir(path, 0o700) - else: - raise NoSuchMailboxError(self._path) - self._toc = {} - self._toc_mtimes = {} - for subdir in ('cur', 'new'): - self._toc_mtimes[subdir] = os.path.getmtime(self._paths[subdir]) - self._last_read = time.time() # Records last time we read cur/new - self._skewfactor = 0.1 # Adjust if os/fs clocks are skewing - - def add(self, message): - """Add message and return assigned key.""" - tmp_file = self._create_tmp() - try: - self._dump_message(message, tmp_file) - except BaseException: - tmp_file.close() - os.remove(tmp_file.name) - raise - _sync_close(tmp_file) - if isinstance(message, MaildirMessage): - subdir = message.get_subdir() - suffix = self.colon + message.get_info() - if suffix == self.colon: - suffix = '' - else: - subdir = 'new' - suffix = '' - uniq = os.path.basename(tmp_file.name).split(self.colon)[0] - dest = os.path.join(self._path, subdir, uniq + suffix) - try: - if hasattr(os, 'link'): - os.link(tmp_file.name, dest) - os.remove(tmp_file.name) - else: - os.rename(tmp_file.name, dest) - except OSError, e: - os.remove(tmp_file.name) - if e.errno == errno.EEXIST: - raise ExternalClashError('Name clash with existing message: %s' - % dest) - else: - raise - if isinstance(message, MaildirMessage): - os.utime(dest, (os.path.getatime(dest), message.get_date())) - return uniq - - def remove(self, key): - """Remove the keyed message; raise KeyError if it doesn't exist.""" - os.remove(os.path.join(self._path, self._lookup(key))) - - def discard(self, key): - """If the keyed message exists, remove it.""" - # This overrides an inapplicable implementation in the superclass. - try: - self.remove(key) - except KeyError: - pass - except OSError, e: - if e.errno != errno.ENOENT: - raise - - def __setitem__(self, key, message): - """Replace the keyed message; raise KeyError if it doesn't exist.""" - old_subpath = self._lookup(key) - temp_key = self.add(message) - temp_subpath = self._lookup(temp_key) - if isinstance(message, MaildirMessage): - # temp's subdir and suffix were specified by message. - dominant_subpath = temp_subpath - else: - # temp's subdir and suffix were defaults from add(). - dominant_subpath = old_subpath - subdir = os.path.dirname(dominant_subpath) - if self.colon in dominant_subpath: - suffix = self.colon + dominant_subpath.split(self.colon)[-1] - else: - suffix = '' - self.discard(key) - new_path = os.path.join(self._path, subdir, key + suffix) - os.rename(os.path.join(self._path, temp_subpath), new_path) - if isinstance(message, MaildirMessage): - os.utime(new_path, (os.path.getatime(new_path), - message.get_date())) - - def get_message(self, key): - """Return a Message representation or raise a KeyError.""" - subpath = self._lookup(key) - f = open(os.path.join(self._path, subpath), 'r') - try: - if self._factory: - msg = self._factory(f) - else: - msg = MaildirMessage(f) - finally: - f.close() - subdir, name = os.path.split(subpath) - msg.set_subdir(subdir) - if self.colon in name: - msg.set_info(name.split(self.colon)[-1]) - msg.set_date(os.path.getmtime(os.path.join(self._path, subpath))) - return msg - - def get_string(self, key): - """Return a string representation or raise a KeyError.""" - f = open(os.path.join(self._path, self._lookup(key)), 'r') - try: - return f.read() - finally: - f.close() - - def get_file(self, key): - """Return a file-like representation or raise a KeyError.""" - f = open(os.path.join(self._path, self._lookup(key)), 'rb') - return _ProxyFile(f) - - def iterkeys(self): - """Return an iterator over keys.""" - self._refresh() - for key in self._toc: - try: - self._lookup(key) - except KeyError: - continue - yield key - - def has_key(self, key): - """Return True if the keyed message exists, False otherwise.""" - self._refresh() - return key in self._toc - - def __len__(self): - """Return a count of messages in the mailbox.""" - self._refresh() - return len(self._toc) - - def flush(self): - """Write any pending changes to disk.""" - # Maildir changes are always written immediately, so there's nothing - # to do. - pass - - def lock(self): - """Lock the mailbox.""" - return - - def unlock(self): - """Unlock the mailbox if it is locked.""" - return - - def close(self): - """Flush and close the mailbox.""" - return - - def list_folders(self): - """Return a list of folder names.""" - result = [] - for entry in os.listdir(self._path): - if len(entry) > 1 and entry[0] == '.' and \ - os.path.isdir(os.path.join(self._path, entry)): - result.append(entry[1:]) - return result - - def get_folder(self, folder): - """Return a Maildir instance for the named folder.""" - return Maildir(os.path.join(self._path, '.' + folder), - factory=self._factory, - create=False) - - def add_folder(self, folder): - """Create a folder and return a Maildir instance representing it.""" - path = os.path.join(self._path, '.' + folder) - result = Maildir(path, factory=self._factory) - maildirfolder_path = os.path.join(path, 'maildirfolder') - if not os.path.exists(maildirfolder_path): - os.close(os.open(maildirfolder_path, os.O_CREAT | os.O_WRONLY, - 0666)) - return result - - def remove_folder(self, folder): - """Delete the named folder, which must be empty.""" - path = os.path.join(self._path, '.' + folder) - for entry in os.listdir(os.path.join(path, 'new')) + \ - os.listdir(os.path.join(path, 'cur')): - if len(entry) < 1 or entry[0] != '.': - raise NotEmptyError('Folder contains message(s): %s' % folder) - for entry in os.listdir(path): - if entry != 'new' and entry != 'cur' and entry != 'tmp' and \ - os.path.isdir(os.path.join(path, entry)): - raise NotEmptyError("Folder contains subdirectory '%s': %s" % - (folder, entry)) - for root, dirs, files in os.walk(path, topdown=False): - for entry in files: - os.remove(os.path.join(root, entry)) - for entry in dirs: - os.rmdir(os.path.join(root, entry)) - os.rmdir(path) - - def clean(self): - """Delete old files in "tmp".""" - now = time.time() - for entry in os.listdir(os.path.join(self._path, 'tmp')): - path = os.path.join(self._path, 'tmp', entry) - if now - os.path.getatime(path) > 129600: # 60 * 60 * 36 - os.remove(path) - - _count = 1 # This is used to generate unique file names. - - def _create_tmp(self): - """Create a file in the tmp subdirectory and open and return it.""" - now = time.time() - hostname = socket.gethostname() - if '/' in hostname: - hostname = hostname.replace('/', r'\057') - if ':' in hostname: - hostname = hostname.replace(':', r'\072') - uniq = "%s.M%sP%sQ%s.%s" % (int(now), int(now % 1 * 1e6), os.getpid(), - Maildir._count, hostname) - path = os.path.join(self._path, 'tmp', uniq) - try: - os.stat(path) - except OSError, e: - if e.errno == errno.ENOENT: - Maildir._count += 1 - try: - return _create_carefully(path) - except OSError, e: - if e.errno != errno.EEXIST: - raise - else: - raise - - # Fall through to here if stat succeeded or open raised EEXIST. - raise ExternalClashError('Name clash prevented file creation: %s' % - path) - - def _refresh(self): - """Update table of contents mapping.""" - # If it has been less than two seconds since the last _refresh() call, - # we have to unconditionally re-read the mailbox just in case it has - # been modified, because os.path.mtime() has a 2 sec resolution in the - # most common worst case (FAT) and a 1 sec resolution typically. This - # results in a few unnecessary re-reads when _refresh() is called - # multiple times in that interval, but once the clock ticks over, we - # will only re-read as needed. Because the filesystem might be being - # served by an independent system with its own clock, we record and - # compare with the mtimes from the filesystem. Because the other - # system's clock might be skewing relative to our clock, we add an - # extra delta to our wait. The default is one tenth second, but is an - # instance variable and so can be adjusted if dealing with a - # particularly skewed or irregular system. - if time.time() - self._last_read > 2 + self._skewfactor: - refresh = False - for subdir in self._toc_mtimes: - mtime = os.path.getmtime(self._paths[subdir]) - if mtime > self._toc_mtimes[subdir]: - refresh = True - self._toc_mtimes[subdir] = mtime - if not refresh: - return - # Refresh toc - self._toc = {} - for subdir in self._toc_mtimes: - path = self._paths[subdir] - for entry in os.listdir(path): - p = os.path.join(path, entry) - if os.path.isdir(p): - continue - uniq = entry.split(self.colon)[0] - self._toc[uniq] = os.path.join(subdir, entry) - self._last_read = time.time() - - def _lookup(self, key): - """Use TOC to return subpath for given key, or raise a KeyError.""" - try: - if os.path.exists(os.path.join(self._path, self._toc[key])): - return self._toc[key] - except KeyError: - pass - self._refresh() - try: - return self._toc[key] - except KeyError: - raise KeyError('No message with key: %s' % key) - - # This method is for backward compatibility only. - def next(self): - """Return the next message in a one-time iteration.""" - if not hasattr(self, '_onetime_keys'): - self._onetime_keys = self.iterkeys() - while True: - try: - return self[self._onetime_keys.next()] - except StopIteration: - return None - except KeyError: - continue - - -class _singlefileMailbox(Mailbox): - """A single-file mailbox.""" - - def __init__(self, path, factory=None, create=True): - """Initialize a single-file mailbox.""" - Mailbox.__init__(self, path, factory, create) - try: - f = open(self._path, 'rb+') - except IOError, e: - if e.errno == errno.ENOENT: - if create: - f = open(self._path, 'wb+') - else: - raise NoSuchMailboxError(self._path) - elif e.errno in (errno.EACCES, errno.EROFS): - f = open(self._path, 'rb') - else: - raise - self._file = f - self._toc = None - self._next_key = 0 - self._pending = False # No changes require rewriting the file. - self._locked = False - self._file_length = None # Used to record mailbox size - - def add(self, message): - """Add message and return assigned key.""" - self._lookup() - self._toc[self._next_key] = self._append_message(message) - self._next_key += 1 - self._pending = True - return self._next_key - 1 - - def remove(self, key): - """Remove the keyed message; raise KeyError if it doesn't exist.""" - self._lookup(key) - del self._toc[key] - self._pending = True - - def __setitem__(self, key, message): - """Replace the keyed message; raise KeyError if it doesn't exist.""" - self._lookup(key) - self._toc[key] = self._append_message(message) - self._pending = True - - def iterkeys(self): - """Return an iterator over keys.""" - self._lookup() - for key in self._toc.keys(): - yield key - - def has_key(self, key): - """Return True if the keyed message exists, False otherwise.""" - self._lookup() - return key in self._toc - - def __len__(self): - """Return a count of messages in the mailbox.""" - self._lookup() - return len(self._toc) - - def lock(self): - """Lock the mailbox.""" - if not self._locked: - _lock_file(self._file) - self._locked = True - - def unlock(self): - """Unlock the mailbox if it is locked.""" - if self._locked: - _unlock_file(self._file) - self._locked = False - - def flush(self): - """Write any pending changes to disk.""" - if not self._pending: - return - - # In order to be writing anything out at all, self._toc must - # already have been generated (and presumably has been modified - # by adding or deleting an item). - assert self._toc is not None - - # Check length of self._file; if it's changed, some other process - # has modified the mailbox since we scanned it. - self._file.seek(0, 2) - cur_len = self._file.tell() - if cur_len != self._file_length: - raise ExternalClashError('Size of mailbox file changed ' - '(expected %i, found %i)' % - (self._file_length, cur_len)) - - new_file = _create_temporary(self._path) - try: - new_toc = {} - self._pre_mailbox_hook(new_file) - for key in sorted(self._toc.keys()): - start, stop = self._toc[key] - self._file.seek(start) - self._pre_message_hook(new_file) - new_start = new_file.tell() - while True: - buffer = self._file.read(min(4096, - stop - self._file.tell())) - if buffer == '': - break - new_file.write(buffer) - new_toc[key] = (new_start, new_file.tell()) - self._post_message_hook(new_file) - except: - new_file.close() - os.remove(new_file.name) - raise - _sync_close(new_file) - # self._file is about to get replaced, so no need to sync. - self._file.close() - try: - os.rename(new_file.name, self._path) - except OSError, e: - if e.errno == errno.EEXIST or \ - (os.name == 'os2' and e.errno == errno.EACCES): - os.remove(self._path) - os.rename(new_file.name, self._path) - else: - raise - self._file = open(self._path, 'rb+') - self._toc = new_toc - self._pending = False - if self._locked: - _lock_file(self._file, dotlock=False) - - def _pre_mailbox_hook(self, f): - """Called before writing the mailbox to file f.""" - return - - def _pre_message_hook(self, f): - """Called before writing each message to file f.""" - return - - def _post_message_hook(self, f): - """Called after writing each message to file f.""" - return - - def close(self): - """Flush and close the mailbox.""" - self.flush() - if self._locked: - self.unlock() - self._file.close() # Sync has been done by self.flush() above. - - def _lookup(self, key=None): - """Return (start, stop) or raise KeyError.""" - if self._toc is None: - self._generate_toc() - if key is not None: - try: - return self._toc[key] - except KeyError: - raise KeyError('No message with key: %s' % key) - - def _append_message(self, message): - """Append message to mailbox and return (start, stop) offsets.""" - self._file.seek(0, 2) - before = self._file.tell() - try: - self._pre_message_hook(self._file) - offsets = self._install_message(message) - self._post_message_hook(self._file) - except BaseException: - self._file.truncate(before) - raise - self._file.flush() - self._file_length = self._file.tell() # Record current length of mailbox - return offsets - - - -class _mboxMMDF(_singlefileMailbox): - """An mbox or MMDF mailbox.""" - - _mangle_from_ = True - - def get_message(self, key): - """Return a Message representation or raise a KeyError.""" - start, stop = self._lookup(key) - self._file.seek(start) - from_line = self._file.readline().replace(os.linesep, '') - string = self._file.read(stop - self._file.tell()) - msg = self._message_factory(string.replace(os.linesep, '\n')) - msg.set_from(from_line[5:]) - return msg - - def get_string(self, key, from_=False): - """Return a string representation or raise a KeyError.""" - start, stop = self._lookup(key) - self._file.seek(start) - if not from_: - self._file.readline() - string = self._file.read(stop - self._file.tell()) - return string.replace(os.linesep, '\n') - - def get_file(self, key, from_=False): - """Return a file-like representation or raise a KeyError.""" - start, stop = self._lookup(key) - self._file.seek(start) - if not from_: - self._file.readline() - return _PartialFile(self._file, self._file.tell(), stop) - - def _install_message(self, message): - """Format a message and blindly write to self._file.""" - from_line = None - if isinstance(message, str) and message.startswith('From '): - newline = message.find('\n') - if newline != -1: - from_line = message[:newline] - message = message[newline + 1:] - else: - from_line = message - message = '' - elif isinstance(message, _mboxMMDFMessage): - from_line = 'From ' + message.get_from() - elif isinstance(message, email.message.Message): - from_line = message.get_unixfrom() # May be None. - if from_line is None: - from_line = 'From MAILER-DAEMON %s' % time.asctime(time.gmtime()) - start = self._file.tell() - self._file.write(from_line + os.linesep) - self._dump_message(message, self._file, self._mangle_from_) - stop = self._file.tell() - return (start, stop) - - -class mbox(_mboxMMDF): - """A classic mbox mailbox.""" - - _mangle_from_ = True - - def __init__(self, path, factory=None, create=True): - """Initialize an mbox mailbox.""" - self._message_factory = mboxMessage - _mboxMMDF.__init__(self, path, factory, create) - - def _pre_message_hook(self, f): - """Called before writing each message to file f.""" - if f.tell() != 0: - f.write(os.linesep) - - def _generate_toc(self): - """Generate key-to-(start, stop) table of contents.""" - starts, stops = [], [] - self._file.seek(0) - while True: - line_pos = self._file.tell() - line = self._file.readline() - if line.startswith('From '): - if len(stops) < len(starts): - stops.append(line_pos - len(os.linesep)) - starts.append(line_pos) - elif line == '': - stops.append(line_pos) - break - self._toc = dict(enumerate(zip(starts, stops))) - self._next_key = len(self._toc) - self._file_length = self._file.tell() - - -class MMDF(_mboxMMDF): - """An MMDF mailbox.""" - - def __init__(self, path, factory=None, create=True): - """Initialize an MMDF mailbox.""" - self._message_factory = MMDFMessage - _mboxMMDF.__init__(self, path, factory, create) - - def _pre_message_hook(self, f): - """Called before writing each message to file f.""" - f.write('\001\001\001\001' + os.linesep) - - def _post_message_hook(self, f): - """Called after writing each message to file f.""" - f.write(os.linesep + '\001\001\001\001' + os.linesep) - - def _generate_toc(self): - """Generate key-to-(start, stop) table of contents.""" - starts, stops = [], [] - self._file.seek(0) - next_pos = 0 - while True: - line_pos = next_pos - line = self._file.readline() - next_pos = self._file.tell() - if line.startswith('\001\001\001\001' + os.linesep): - starts.append(next_pos) - while True: - line_pos = next_pos - line = self._file.readline() - next_pos = self._file.tell() - if line == '\001\001\001\001' + os.linesep: - stops.append(line_pos - len(os.linesep)) - break - elif line == '': - stops.append(line_pos) - break - elif line == '': - break - self._toc = dict(enumerate(zip(starts, stops))) - self._next_key = len(self._toc) - self._file.seek(0, 2) - self._file_length = self._file.tell() - - -class MH(Mailbox): - """An MH mailbox.""" - - def __init__(self, path, factory=None, create=True): - """Initialize an MH instance.""" - Mailbox.__init__(self, path, factory, create) - if not os.path.exists(self._path): - if create: - os.mkdir(self._path, 0700) - os.close(os.open(os.path.join(self._path, '.mh_sequences'), - os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0600)) - else: - raise NoSuchMailboxError(self._path) - self._locked = False - - def add(self, message): - """Add message and return assigned key.""" - keys = self.keys() - if len(keys) == 0: - new_key = 1 - else: - new_key = max(keys) + 1 - new_path = os.path.join(self._path, str(new_key)) - f = _create_carefully(new_path) - closed = False - try: - if self._locked: - _lock_file(f) - try: - try: - self._dump_message(message, f) - except BaseException: - # Unlock and close so it can be deleted on Windows - if self._locked: - _unlock_file(f) - _sync_close(f) - closed = True - os.remove(new_path) - raise - if isinstance(message, MHMessage): - self._dump_sequences(message, new_key) - finally: - if self._locked: - _unlock_file(f) - finally: - if not closed: - _sync_close(f) - return new_key - - def remove(self, key): - """Remove the keyed message; raise KeyError if it doesn't exist.""" - path = os.path.join(self._path, str(key)) - try: - f = open(path, 'rb+') - except IOError, e: - if e.errno == errno.ENOENT: - raise KeyError('No message with key: %s' % key) - else: - raise - else: - f.close() - os.remove(path) - - def __setitem__(self, key, message): - """Replace the keyed message; raise KeyError if it doesn't exist.""" - path = os.path.join(self._path, str(key)) - try: - f = open(path, 'rb+') - except IOError, e: - if e.errno == errno.ENOENT: - raise KeyError('No message with key: %s' % key) - else: - raise - try: - if self._locked: - _lock_file(f) - try: - os.close(os.open(path, os.O_WRONLY | os.O_TRUNC)) - self._dump_message(message, f) - if isinstance(message, MHMessage): - self._dump_sequences(message, key) - finally: - if self._locked: - _unlock_file(f) - finally: - _sync_close(f) - - def get_message(self, key): - """Return a Message representation or raise a KeyError.""" - try: - if self._locked: - f = open(os.path.join(self._path, str(key)), 'r+') - else: - f = open(os.path.join(self._path, str(key)), 'r') - except IOError, e: - if e.errno == errno.ENOENT: - raise KeyError('No message with key: %s' % key) - else: - raise - try: - if self._locked: - _lock_file(f) - try: - msg = MHMessage(f) - finally: - if self._locked: - _unlock_file(f) - finally: - f.close() - for name, key_list in self.get_sequences().iteritems(): - if key in key_list: - msg.add_sequence(name) - return msg - - def get_string(self, key): - """Return a string representation or raise a KeyError.""" - try: - if self._locked: - f = open(os.path.join(self._path, str(key)), 'r+') - else: - f = open(os.path.join(self._path, str(key)), 'r') - except IOError, e: - if e.errno == errno.ENOENT: - raise KeyError('No message with key: %s' % key) - else: - raise - try: - if self._locked: - _lock_file(f) - try: - return f.read() - finally: - if self._locked: - _unlock_file(f) - finally: - f.close() - - def get_file(self, key): - """Return a file-like representation or raise a KeyError.""" - try: - f = open(os.path.join(self._path, str(key)), 'rb') - except IOError, e: - if e.errno == errno.ENOENT: - raise KeyError('No message with key: %s' % key) - else: - raise - return _ProxyFile(f) - - def iterkeys(self): - """Return an iterator over keys.""" - return iter(sorted(int(entry) for entry in os.listdir(self._path) - if entry.isdigit())) - - def has_key(self, key): - """Return True if the keyed message exists, False otherwise.""" - return os.path.exists(os.path.join(self._path, str(key))) - - def __len__(self): - """Return a count of messages in the mailbox.""" - return len(list(self.iterkeys())) - - def lock(self): - """Lock the mailbox.""" - if not self._locked: - self._file = open(os.path.join(self._path, '.mh_sequences'), 'rb+') - _lock_file(self._file) - self._locked = True - - def unlock(self): - """Unlock the mailbox if it is locked.""" - if self._locked: - _unlock_file(self._file) - _sync_close(self._file) - del self._file - self._locked = False - - def flush(self): - """Write any pending changes to the disk.""" - return - - def close(self): - """Flush and close the mailbox.""" - if self._locked: - self.unlock() - - def list_folders(self): - """Return a list of folder names.""" - result = [] - for entry in os.listdir(self._path): - if os.path.isdir(os.path.join(self._path, entry)): - result.append(entry) - return result - - def get_folder(self, folder): - """Return an MH instance for the named folder.""" - return MH(os.path.join(self._path, folder), - factory=self._factory, create=False) - - def add_folder(self, folder): - """Create a folder and return an MH instance representing it.""" - return MH(os.path.join(self._path, folder), - factory=self._factory) - - def remove_folder(self, folder): - """Delete the named folder, which must be empty.""" - path = os.path.join(self._path, folder) - entries = os.listdir(path) - if entries == ['.mh_sequences']: - os.remove(os.path.join(path, '.mh_sequences')) - elif entries == []: - pass - else: - raise NotEmptyError('Folder not empty: %s' % self._path) - os.rmdir(path) - - def get_sequences(self): - """Return a name-to-key-list dictionary to define each sequence.""" - results = {} - f = open(os.path.join(self._path, '.mh_sequences'), 'r') - try: - all_keys = set(self.keys()) - for line in f: - try: - name, contents = line.split(':') - keys = set() - for spec in contents.split(): - if spec.isdigit(): - keys.add(int(spec)) - else: - start, stop = (int(x) for x in spec.split('-')) - keys.update(range(start, stop + 1)) - results[name] = [key for key in sorted(keys) \ - if key in all_keys] - if len(results[name]) == 0: - del results[name] - except ValueError: - raise FormatError('Invalid sequence specification: %s' % - line.rstrip()) - finally: - f.close() - return results - - def set_sequences(self, sequences): - """Set sequences using the given name-to-key-list dictionary.""" - f = open(os.path.join(self._path, '.mh_sequences'), 'r+') - try: - os.close(os.open(f.name, os.O_WRONLY | os.O_TRUNC)) - for name, keys in sequences.iteritems(): - if len(keys) == 0: - continue - f.write('%s:' % name) - prev = None - completing = False - for key in sorted(set(keys)): - if key - 1 == prev: - if not completing: - completing = True - f.write('-') - elif completing: - completing = False - f.write('%s %s' % (prev, key)) - else: - f.write(' %s' % key) - prev = key - if completing: - f.write(str(prev) + '\n') - else: - f.write('\n') - finally: - _sync_close(f) - - def pack(self): - """Re-name messages to eliminate numbering gaps. Invalidates keys.""" - sequences = self.get_sequences() - prev = 0 - changes = [] - for key in self.iterkeys(): - if key - 1 != prev: - changes.append((key, prev + 1)) - if hasattr(os, 'link'): - os.link(os.path.join(self._path, str(key)), - os.path.join(self._path, str(prev + 1))) - os.unlink(os.path.join(self._path, str(key))) - else: - os.rename(os.path.join(self._path, str(key)), - os.path.join(self._path, str(prev + 1))) - prev += 1 - self._next_key = prev + 1 - if len(changes) == 0: - return - for name, key_list in sequences.items(): - for old, new in changes: - if old in key_list: - key_list[key_list.index(old)] = new - self.set_sequences(sequences) - - def _dump_sequences(self, message, key): - """Inspect a new MHMessage and update sequences appropriately.""" - pending_sequences = message.get_sequences() - all_sequences = self.get_sequences() - for name, key_list in all_sequences.iteritems(): - if name in pending_sequences: - key_list.append(key) - elif key in key_list: - del key_list[key_list.index(key)] - for sequence in pending_sequences: - if sequence not in all_sequences: - all_sequences[sequence] = [key] - self.set_sequences(all_sequences) - - -class Babyl(_singlefileMailbox): - """An Rmail-style Babyl mailbox.""" - - _special_labels = frozenset(('unseen', 'deleted', 'filed', 'answered', - 'forwarded', 'edited', 'resent')) - - def __init__(self, path, factory=None, create=True): - """Initialize a Babyl mailbox.""" - _singlefileMailbox.__init__(self, path, factory, create) - self._labels = {} - - def add(self, message): - """Add message and return assigned key.""" - key = _singlefileMailbox.add(self, message) - if isinstance(message, BabylMessage): - self._labels[key] = message.get_labels() - return key - - def remove(self, key): - """Remove the keyed message; raise KeyError if it doesn't exist.""" - _singlefileMailbox.remove(self, key) - if key in self._labels: - del self._labels[key] - - def __setitem__(self, key, message): - """Replace the keyed message; raise KeyError if it doesn't exist.""" - _singlefileMailbox.__setitem__(self, key, message) - if isinstance(message, BabylMessage): - self._labels[key] = message.get_labels() - - def get_message(self, key): - """Return a Message representation or raise a KeyError.""" - start, stop = self._lookup(key) - self._file.seek(start) - self._file.readline() # Skip '1,' line specifying labels. - original_headers = StringIO.StringIO() - while True: - line = self._file.readline() - if line == '*** EOOH ***' + os.linesep or line == '': - break - original_headers.write(line.replace(os.linesep, '\n')) - visible_headers = StringIO.StringIO() - while True: - line = self._file.readline() - if line == os.linesep or line == '': - break - visible_headers.write(line.replace(os.linesep, '\n')) - body = self._file.read(stop - self._file.tell()).replace(os.linesep, - '\n') - msg = BabylMessage(original_headers.getvalue() + body) - msg.set_visible(visible_headers.getvalue()) - if key in self._labels: - msg.set_labels(self._labels[key]) - return msg - - def get_string(self, key): - """Return a string representation or raise a KeyError.""" - start, stop = self._lookup(key) - self._file.seek(start) - self._file.readline() # Skip '1,' line specifying labels. - original_headers = StringIO.StringIO() - while True: - line = self._file.readline() - if line == '*** EOOH ***' + os.linesep or line == '': - break - original_headers.write(line.replace(os.linesep, '\n')) - while True: - line = self._file.readline() - if line == os.linesep or line == '': - break - return original_headers.getvalue() + \ - self._file.read(stop - self._file.tell()).replace(os.linesep, - '\n') - - def get_file(self, key): - """Return a file-like representation or raise a KeyError.""" - return StringIO.StringIO(self.get_string(key).replace('\n', - os.linesep)) - - def get_labels(self): - """Return a list of user-defined labels in the mailbox.""" - self._lookup() - labels = set() - for label_list in self._labels.values(): - labels.update(label_list) - labels.difference_update(self._special_labels) - return list(labels) - - def _generate_toc(self): - """Generate key-to-(start, stop) table of contents.""" - starts, stops = [], [] - self._file.seek(0) - next_pos = 0 - label_lists = [] - while True: - line_pos = next_pos - line = self._file.readline() - next_pos = self._file.tell() - if line == '\037\014' + os.linesep: - if len(stops) < len(starts): - stops.append(line_pos - len(os.linesep)) - starts.append(next_pos) - labels = [label.strip() for label - in self._file.readline()[1:].split(',') - if label.strip() != ''] - label_lists.append(labels) - elif line == '\037' or line == '\037' + os.linesep: - if len(stops) < len(starts): - stops.append(line_pos - len(os.linesep)) - elif line == '': - stops.append(line_pos - len(os.linesep)) - break - self._toc = dict(enumerate(zip(starts, stops))) - self._labels = dict(enumerate(label_lists)) - self._next_key = len(self._toc) - self._file.seek(0, 2) - self._file_length = self._file.tell() - - def _pre_mailbox_hook(self, f): - """Called before writing the mailbox to file f.""" - f.write('BABYL OPTIONS:%sVersion: 5%sLabels:%s%s\037' % - (os.linesep, os.linesep, ','.join(self.get_labels()), - os.linesep)) - - def _pre_message_hook(self, f): - """Called before writing each message to file f.""" - f.write('\014' + os.linesep) - - def _post_message_hook(self, f): - """Called after writing each message to file f.""" - f.write(os.linesep + '\037') - - def _install_message(self, message): - """Write message contents and return (start, stop).""" - start = self._file.tell() - if isinstance(message, BabylMessage): - special_labels = [] - labels = [] - for label in message.get_labels(): - if label in self._special_labels: - special_labels.append(label) - else: - labels.append(label) - self._file.write('1') - for label in special_labels: - self._file.write(', ' + label) - self._file.write(',,') - for label in labels: - self._file.write(' ' + label + ',') - self._file.write(os.linesep) - else: - self._file.write('1,,' + os.linesep) - if isinstance(message, email.message.Message): - orig_buffer = StringIO.StringIO() - orig_generator = email.generator.Generator(orig_buffer, False, 0) - orig_generator.flatten(message) - orig_buffer.seek(0) - while True: - line = orig_buffer.readline() - self._file.write(line.replace('\n', os.linesep)) - if line == '\n' or line == '': - break - self._file.write('*** EOOH ***' + os.linesep) - if isinstance(message, BabylMessage): - vis_buffer = StringIO.StringIO() - vis_generator = email.generator.Generator(vis_buffer, False, 0) - vis_generator.flatten(message.get_visible()) - while True: - line = vis_buffer.readline() - self._file.write(line.replace('\n', os.linesep)) - if line == '\n' or line == '': - break - else: - orig_buffer.seek(0) - while True: - line = orig_buffer.readline() - self._file.write(line.replace('\n', os.linesep)) - if line == '\n' or line == '': - break - while True: - buffer = orig_buffer.read(4096) # Buffer size is arbitrary. - if buffer == '': - break - self._file.write(buffer.replace('\n', os.linesep)) - elif isinstance(message, str): - body_start = message.find('\n\n') + 2 - if body_start - 2 != -1: - self._file.write(message[:body_start].replace('\n', - os.linesep)) - self._file.write('*** EOOH ***' + os.linesep) - self._file.write(message[:body_start].replace('\n', - os.linesep)) - self._file.write(message[body_start:].replace('\n', - os.linesep)) - else: - self._file.write('*** EOOH ***' + os.linesep + os.linesep) - self._file.write(message.replace('\n', os.linesep)) - elif hasattr(message, 'readline'): - original_pos = message.tell() - first_pass = True - while True: - line = message.readline() - self._file.write(line.replace('\n', os.linesep)) - if line == '\n' or line == '': - self._file.write('*** EOOH ***' + os.linesep) - if first_pass: - first_pass = False - message.seek(original_pos) - else: - break - while True: - buffer = message.read(4096) # Buffer size is arbitrary. - if buffer == '': - break - self._file.write(buffer.replace('\n', os.linesep)) - else: - raise TypeError('Invalid message type: %s' % type(message)) - stop = self._file.tell() - return (start, stop) - - -class Message(email.message.Message): - """Message with mailbox-format-specific properties.""" - - def __init__(self, message=None): - """Initialize a Message instance.""" - if isinstance(message, email.message.Message): - self._become_message(copy.deepcopy(message)) - if isinstance(message, Message): - message._explain_to(self) - elif isinstance(message, str): - self._become_message(email.message_from_string(message)) - elif hasattr(message, "read"): - self._become_message(email.message_from_file(message)) - elif message is None: - email.message.Message.__init__(self) - else: - raise TypeError('Invalid message type: %s' % type(message)) - - def _become_message(self, message): - """Assume the non-format-specific state of message.""" - for name in ('_headers', '_unixfrom', '_payload', '_charset', - 'preamble', 'epilogue', 'defects', '_default_type'): - self.__dict__[name] = message.__dict__[name] - - def _explain_to(self, message): - """Copy format-specific state to message insofar as possible.""" - if isinstance(message, Message): - return # There's nothing format-specific to explain. - else: - raise TypeError('Cannot convert to specified type') - - -class MaildirMessage(Message): - """Message with Maildir-specific properties.""" - - def __init__(self, message=None): - """Initialize a MaildirMessage instance.""" - self._subdir = 'new' - self._info = '' - self._date = time.time() - Message.__init__(self, message) - - def get_subdir(self): - """Return 'new' or 'cur'.""" - return self._subdir - - def set_subdir(self, subdir): - """Set subdir to 'new' or 'cur'.""" - if subdir == 'new' or subdir == 'cur': - self._subdir = subdir - else: - raise ValueError("subdir must be 'new' or 'cur': %s" % subdir) - - def get_flags(self): - """Return as a string the flags that are set.""" - if self._info.startswith('2,'): - return self._info[2:] - else: - return '' - - def set_flags(self, flags): - """Set the given flags and unset all others.""" - self._info = '2,' + ''.join(sorted(flags)) - - def add_flag(self, flag): - """Set the given flag(s) without changing others.""" - self.set_flags(''.join(set(self.get_flags()) | set(flag))) - - def remove_flag(self, flag): - """Unset the given string flag(s) without changing others.""" - if self.get_flags() != '': - self.set_flags(''.join(set(self.get_flags()) - set(flag))) - - def get_date(self): - """Return delivery date of message, in seconds since the epoch.""" - return self._date - - def set_date(self, date): - """Set delivery date of message, in seconds since the epoch.""" - try: - self._date = float(date) - except ValueError: - raise TypeError("can't convert to float: %s" % date) - - def get_info(self): - """Get the message's "info" as a string.""" - return self._info - - def set_info(self, info): - """Set the message's "info" string.""" - if isinstance(info, str): - self._info = info - else: - raise TypeError('info must be a string: %s' % type(info)) - - def _explain_to(self, message): - """Copy Maildir-specific state to message insofar as possible.""" - if isinstance(message, MaildirMessage): - message.set_flags(self.get_flags()) - message.set_subdir(self.get_subdir()) - message.set_date(self.get_date()) - elif isinstance(message, _mboxMMDFMessage): - flags = set(self.get_flags()) - if 'S' in flags: - message.add_flag('R') - if self.get_subdir() == 'cur': - message.add_flag('O') - if 'T' in flags: - message.add_flag('D') - if 'F' in flags: - message.add_flag('F') - if 'R' in flags: - message.add_flag('A') - message.set_from('MAILER-DAEMON', time.gmtime(self.get_date())) - elif isinstance(message, MHMessage): - flags = set(self.get_flags()) - if 'S' not in flags: - message.add_sequence('unseen') - if 'R' in flags: - message.add_sequence('replied') - if 'F' in flags: - message.add_sequence('flagged') - elif isinstance(message, BabylMessage): - flags = set(self.get_flags()) - if 'S' not in flags: - message.add_label('unseen') - if 'T' in flags: - message.add_label('deleted') - if 'R' in flags: - message.add_label('answered') - if 'P' in flags: - message.add_label('forwarded') - elif isinstance(message, Message): - pass - else: - raise TypeError('Cannot convert to specified type: %s' % - type(message)) - - -class _mboxMMDFMessage(Message): - """Message with mbox- or MMDF-specific properties.""" - - def __init__(self, message=None): - """Initialize an mboxMMDFMessage instance.""" - self.set_from('MAILER-DAEMON', True) - if isinstance(message, email.message.Message): - unixfrom = message.get_unixfrom() - if unixfrom is not None and unixfrom.startswith('From '): - self.set_from(unixfrom[5:]) - Message.__init__(self, message) - - def get_from(self): - """Return contents of "From " line.""" - return self._from - - def set_from(self, from_, time_=None): - """Set "From " line, formatting and appending time_ if specified.""" - if time_ is not None: - if time_ is True: - time_ = time.gmtime() - from_ += ' ' + time.asctime(time_) - self._from = from_ - - def get_flags(self): - """Return as a string the flags that are set.""" - return self.get('Status', '') + self.get('X-Status', '') - - def set_flags(self, flags): - """Set the given flags and unset all others.""" - flags = set(flags) - status_flags, xstatus_flags = '', '' - for flag in ('R', 'O'): - if flag in flags: - status_flags += flag - flags.remove(flag) - for flag in ('D', 'F', 'A'): - if flag in flags: - xstatus_flags += flag - flags.remove(flag) - xstatus_flags += ''.join(sorted(flags)) - try: - self.replace_header('Status', status_flags) - except KeyError: - self.add_header('Status', status_flags) - try: - self.replace_header('X-Status', xstatus_flags) - except KeyError: - self.add_header('X-Status', xstatus_flags) - - def add_flag(self, flag): - """Set the given flag(s) without changing others.""" - self.set_flags(''.join(set(self.get_flags()) | set(flag))) - - def remove_flag(self, flag): - """Unset the given string flag(s) without changing others.""" - if 'Status' in self or 'X-Status' in self: - self.set_flags(''.join(set(self.get_flags()) - set(flag))) - - def _explain_to(self, message): - """Copy mbox- or MMDF-specific state to message insofar as possible.""" - if isinstance(message, MaildirMessage): - flags = set(self.get_flags()) - if 'O' in flags: - message.set_subdir('cur') - if 'F' in flags: - message.add_flag('F') - if 'A' in flags: - message.add_flag('R') - if 'R' in flags: - message.add_flag('S') - if 'D' in flags: - message.add_flag('T') - del message['status'] - del message['x-status'] - maybe_date = ' '.join(self.get_from().split()[-5:]) - try: - message.set_date(calendar.timegm(time.strptime(maybe_date, - '%a %b %d %H:%M:%S %Y'))) - except (ValueError, OverflowError): - pass - elif isinstance(message, _mboxMMDFMessage): - message.set_flags(self.get_flags()) - message.set_from(self.get_from()) - elif isinstance(message, MHMessage): - flags = set(self.get_flags()) - if 'R' not in flags: - message.add_sequence('unseen') - if 'A' in flags: - message.add_sequence('replied') - if 'F' in flags: - message.add_sequence('flagged') - del message['status'] - del message['x-status'] - elif isinstance(message, BabylMessage): - flags = set(self.get_flags()) - if 'R' not in flags: - message.add_label('unseen') - if 'D' in flags: - message.add_label('deleted') - if 'A' in flags: - message.add_label('answered') - del message['status'] - del message['x-status'] - elif isinstance(message, Message): - pass - else: - raise TypeError('Cannot convert to specified type: %s' % - type(message)) - - -class mboxMessage(_mboxMMDFMessage): - """Message with mbox-specific properties.""" - - -class MHMessage(Message): - """Message with MH-specific properties.""" - - def __init__(self, message=None): - """Initialize an MHMessage instance.""" - self._sequences = [] - Message.__init__(self, message) - - def get_sequences(self): - """Return a list of sequences that include the message.""" - return self._sequences[:] - - def set_sequences(self, sequences): - """Set the list of sequences that include the message.""" - self._sequences = list(sequences) - - def add_sequence(self, sequence): - """Add sequence to list of sequences including the message.""" - if isinstance(sequence, str): - if not sequence in self._sequences: - self._sequences.append(sequence) - else: - raise TypeError('sequence must be a string: %s' % type(sequence)) - - def remove_sequence(self, sequence): - """Remove sequence from the list of sequences including the message.""" - try: - self._sequences.remove(sequence) - except ValueError: - pass - - def _explain_to(self, message): - """Copy MH-specific state to message insofar as possible.""" - if isinstance(message, MaildirMessage): - sequences = set(self.get_sequences()) - if 'unseen' in sequences: - message.set_subdir('cur') - else: - message.set_subdir('cur') - message.add_flag('S') - if 'flagged' in sequences: - message.add_flag('F') - if 'replied' in sequences: - message.add_flag('R') - elif isinstance(message, _mboxMMDFMessage): - sequences = set(self.get_sequences()) - if 'unseen' not in sequences: - message.add_flag('RO') - else: - message.add_flag('O') - if 'flagged' in sequences: - message.add_flag('F') - if 'replied' in sequences: - message.add_flag('A') - elif isinstance(message, MHMessage): - for sequence in self.get_sequences(): - message.add_sequence(sequence) - elif isinstance(message, BabylMessage): - sequences = set(self.get_sequences()) - if 'unseen' in sequences: - message.add_label('unseen') - if 'replied' in sequences: - message.add_label('answered') - elif isinstance(message, Message): - pass - else: - raise TypeError('Cannot convert to specified type: %s' % - type(message)) - - -class BabylMessage(Message): - """Message with Babyl-specific properties.""" - - def __init__(self, message=None): - """Initialize an BabylMessage instance.""" - self._labels = [] - self._visible = Message() - Message.__init__(self, message) - - def get_labels(self): - """Return a list of labels on the message.""" - return self._labels[:] - - def set_labels(self, labels): - """Set the list of labels on the message.""" - self._labels = list(labels) - - def add_label(self, label): - """Add label to list of labels on the message.""" - if isinstance(label, str): - if label not in self._labels: - self._labels.append(label) - else: - raise TypeError('label must be a string: %s' % type(label)) - - def remove_label(self, label): - """Remove label from the list of labels on the message.""" - try: - self._labels.remove(label) - except ValueError: - pass - - def get_visible(self): - """Return a Message representation of visible headers.""" - return Message(self._visible) - - def set_visible(self, visible): - """Set the Message representation of visible headers.""" - self._visible = Message(visible) - - def update_visible(self): - """Update and/or sensibly generate a set of visible headers.""" - for header in self._visible.keys(): - if header in self: - self._visible.replace_header(header, self[header]) - else: - del self._visible[header] - for header in ('Date', 'From', 'Reply-To', 'To', 'CC', 'Subject'): - if header in self and header not in self._visible: - self._visible[header] = self[header] - - def _explain_to(self, message): - """Copy Babyl-specific state to message insofar as possible.""" - if isinstance(message, MaildirMessage): - labels = set(self.get_labels()) - if 'unseen' in labels: - message.set_subdir('cur') - else: - message.set_subdir('cur') - message.add_flag('S') - if 'forwarded' in labels or 'resent' in labels: - message.add_flag('P') - if 'answered' in labels: - message.add_flag('R') - if 'deleted' in labels: - message.add_flag('T') - elif isinstance(message, _mboxMMDFMessage): - labels = set(self.get_labels()) - if 'unseen' not in labels: - message.add_flag('RO') - else: - message.add_flag('O') - if 'deleted' in labels: - message.add_flag('D') - if 'answered' in labels: - message.add_flag('A') - elif isinstance(message, MHMessage): - labels = set(self.get_labels()) - if 'unseen' in labels: - message.add_sequence('unseen') - if 'answered' in labels: - message.add_sequence('replied') - elif isinstance(message, BabylMessage): - message.set_visible(self.get_visible()) - for label in self.get_labels(): - message.add_label(label) - elif isinstance(message, Message): - pass - else: - raise TypeError('Cannot convert to specified type: %s' % - type(message)) - - -class MMDFMessage(_mboxMMDFMessage): - """Message with MMDF-specific properties.""" - - -class _ProxyFile: - """A read-only wrapper of a file.""" - - def __init__(self, f, pos=None): - """Initialize a _ProxyFile.""" - self._file = f - if pos is None: - self._pos = f.tell() - else: - self._pos = pos - - def read(self, size=None): - """Read bytes.""" - return self._read(size, self._file.read) - - def readline(self, size=None): - """Read a line.""" - return self._read(size, self._file.readline) - - def readlines(self, sizehint=None): - """Read multiple lines.""" - result = [] - for line in self: - result.append(line) - if sizehint is not None: - sizehint -= len(line) - if sizehint <= 0: - break - return result - - def __iter__(self): - """Iterate over lines.""" - return iter(self.readline, "") - - def tell(self): - """Return the position.""" - return self._pos - - def seek(self, offset, whence=0): - """Change position.""" - if whence == 1: - self._file.seek(self._pos) - self._file.seek(offset, whence) - self._pos = self._file.tell() - - def close(self): - """Close the file.""" - del self._file - - def _read(self, size, read_method): - """Read size bytes using read_method.""" - if size is None: - size = -1 - self._file.seek(self._pos) - result = read_method(size) - self._pos = self._file.tell() - return result - - -class _PartialFile(_ProxyFile): - """A read-only wrapper of part of a file.""" - - def __init__(self, f, start=None, stop=None): - """Initialize a _PartialFile.""" - _ProxyFile.__init__(self, f, start) - self._start = start - self._stop = stop - - def tell(self): - """Return the position with respect to start.""" - return _ProxyFile.tell(self) - self._start - - def seek(self, offset, whence=0): - """Change position, possibly with respect to start or stop.""" - if whence == 0: - self._pos = self._start - whence = 1 - elif whence == 2: - self._pos = self._stop - whence = 1 - _ProxyFile.seek(self, offset, whence) - - def _read(self, size, read_method): - """Read size bytes using read_method, honoring start and stop.""" - remaining = self._stop - self._pos - if remaining <= 0: - return '' - if size is None or size < 0 or size > remaining: - size = remaining - return _ProxyFile._read(self, size, read_method) - - -def _lock_file(f, dotlock=True): - """Lock file f using lockf and dot locking.""" - dotlock_done = False - try: - if fcntl: - try: - fcntl.lockf(f, fcntl.LOCK_EX | fcntl.LOCK_NB) - except IOError, e: - if e.errno in (errno.EAGAIN, errno.EACCES, errno.EROFS): - raise ExternalClashError('lockf: lock unavailable: %s' % - f.name) - else: - raise - if dotlock: - try: - pre_lock = _create_temporary(f.name + '.lock') - pre_lock.close() - except IOError, e: - if e.errno in (errno.EACCES, errno.EROFS): - return # Without write access, just skip dotlocking. - else: - raise - try: - if hasattr(os, 'link'): - os.link(pre_lock.name, f.name + '.lock') - dotlock_done = True - os.unlink(pre_lock.name) - else: - os.rename(pre_lock.name, f.name + '.lock') - dotlock_done = True - except OSError, e: - if e.errno == errno.EEXIST or \ - (os.name == 'os2' and e.errno == errno.EACCES): - os.remove(pre_lock.name) - raise ExternalClashError('dot lock unavailable: %s' % - f.name) - else: - raise - except: - if fcntl: - fcntl.lockf(f, fcntl.LOCK_UN) - if dotlock_done: - os.remove(f.name + '.lock') - raise - -def _unlock_file(f): - """Unlock file f using lockf and dot locking.""" - if fcntl: - fcntl.lockf(f, fcntl.LOCK_UN) - if os.path.exists(f.name + '.lock'): - os.remove(f.name + '.lock') - -def _create_carefully(path): - """Create a file if it doesn't exist and open for reading and writing.""" - fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, 0666) - try: - return open(path, 'rb+') - finally: - os.close(fd) - -def _create_temporary(path): - """Create a temp file based on path and open for reading and writing.""" - return _create_carefully('%s.%s.%s.%s' % (path, int(time.time()), - socket.gethostname(), - os.getpid())) - -def _sync_flush(f): - """Ensure changes to file f are physically on disk.""" - f.flush() - if hasattr(os, 'fsync'): - os.fsync(f.fileno()) - -def _sync_close(f): - """Close file f, ensuring all changes are physically on disk.""" - _sync_flush(f) - f.close() - -## Start: classes from the original module (for backward compatibility). - -# Note that the Maildir class, whose name is unchanged, itself offers a next() -# method for backward compatibility. - -class _Mailbox: - - def __init__(self, fp, factory=rfc822.Message): - self.fp = fp - self.seekp = 0 - self.factory = factory - - def __iter__(self): - return iter(self.next, None) - - def next(self): - while 1: - self.fp.seek(self.seekp) - try: - self._search_start() - except EOFError: - self.seekp = self.fp.tell() - return None - start = self.fp.tell() - self._search_end() - self.seekp = stop = self.fp.tell() - if start != stop: - break - return self.factory(_PartialFile(self.fp, start, stop)) - -# Recommended to use PortableUnixMailbox instead! -class UnixMailbox(_Mailbox): - - def _search_start(self): - while 1: - pos = self.fp.tell() - line = self.fp.readline() - if not line: - raise EOFError - if line[:5] == 'From ' and self._isrealfromline(line): - self.fp.seek(pos) - return - - def _search_end(self): - self.fp.readline() # Throw away header line - while 1: - pos = self.fp.tell() - line = self.fp.readline() - if not line: - return - if line[:5] == 'From ' and self._isrealfromline(line): - self.fp.seek(pos) - return - - # An overridable mechanism to test for From-line-ness. You can either - # specify a different regular expression or define a whole new - # _isrealfromline() method. Note that this only gets called for lines - # starting with the 5 characters "From ". - # - # BAW: According to - #http://home.netscape.com/eng/mozilla/2.0/relnotes/demo/content-length.html - # the only portable, reliable way to find message delimiters in a BSD (i.e - # Unix mailbox) style folder is to search for "\n\nFrom .*\n", or at the - # beginning of the file, "^From .*\n". While _fromlinepattern below seems - # like a good idea, in practice, there are too many variations for more - # strict parsing of the line to be completely accurate. - # - # _strict_isrealfromline() is the old version which tries to do stricter - # parsing of the From_ line. _portable_isrealfromline() simply returns - # true, since it's never called if the line doesn't already start with - # "From ". - # - # This algorithm, and the way it interacts with _search_start() and - # _search_end() may not be completely correct, because it doesn't check - # that the two characters preceding "From " are \n\n or the beginning of - # the file. Fixing this would require a more extensive rewrite than is - # necessary. For convenience, we've added a PortableUnixMailbox class - # which does no checking of the format of the 'From' line. - - _fromlinepattern = (r"From \s*[^\s]+\s+\w\w\w\s+\w\w\w\s+\d?\d\s+" - r"\d?\d:\d\d(:\d\d)?(\s+[^\s]+)?\s+\d\d\d\d\s*" - r"[^\s]*\s*" - "$") - _regexp = None - - def _strict_isrealfromline(self, line): - if not self._regexp: - import re - self._regexp = re.compile(self._fromlinepattern) - return self._regexp.match(line) - - def _portable_isrealfromline(self, line): - return True - - _isrealfromline = _strict_isrealfromline - - -class PortableUnixMailbox(UnixMailbox): - _isrealfromline = UnixMailbox._portable_isrealfromline - - -class MmdfMailbox(_Mailbox): - - def _search_start(self): - while 1: - line = self.fp.readline() - if not line: - raise EOFError - if line[:5] == '\001\001\001\001\n': - return - - def _search_end(self): - while 1: - pos = self.fp.tell() - line = self.fp.readline() - if not line: - return - if line == '\001\001\001\001\n': - self.fp.seek(pos) - return - - -class MHMailbox: - - def __init__(self, dirname, factory=rfc822.Message): - import re - pat = re.compile('^[1-9][0-9]*$') - self.dirname = dirname - # the three following lines could be combined into: - # list = map(long, filter(pat.match, os.listdir(self.dirname))) - list = os.listdir(self.dirname) - list = filter(pat.match, list) - list = map(long, list) - list.sort() - # This only works in Python 1.6 or later; - # before that str() added 'L': - self.boxes = map(str, list) - self.boxes.reverse() - self.factory = factory - - def __iter__(self): - return iter(self.next, None) - - def next(self): - if not self.boxes: - return None - fn = self.boxes.pop() - fp = open(os.path.join(self.dirname, fn)) - msg = self.factory(fp) - try: - msg._mh_msgno = fn - except (AttributeError, TypeError): - pass - return msg - - -class BabylMailbox(_Mailbox): - - def _search_start(self): - while 1: - line = self.fp.readline() - if not line: - raise EOFError - if line == '*** EOOH ***\n': - return - - def _search_end(self): - while 1: - pos = self.fp.tell() - line = self.fp.readline() - if not line: - return - if line == '\037\014\n' or line == '\037': - self.fp.seek(pos) - return - -## End: classes from the original module (for backward compatibility). - - -class Error(Exception): - """Raised for module-specific errors.""" - -class NoSuchMailboxError(Error): - """The specified mailbox does not exist and won't be created.""" - -class NotEmptyError(Error): - """The specified mailbox is not empty and deletion was requested.""" - -class ExternalClashError(Error): - """Another process caused an action to fail.""" - -class FormatError(Error): - """A file appears to have an invalid format.""" diff --git a/lib-python/modified-2.7/mailbox.py b/lib-python/modified-2.7/mailbox.py new file mode 100644 --- /dev/null +++ b/lib-python/modified-2.7/mailbox.py @@ -0,0 +1,2179 @@ +#! /usr/bin/env python + +"""Read/write support for Maildir, mbox, MH, Babyl, and MMDF mailboxes.""" + +# Notes for authors of new mailbox subclasses: +# +# Remember to fsync() changes to disk before closing a modified file +# or returning from a flush() method. See functions _sync_flush() and +# _sync_close(). + +import sys +import os +import time +import calendar +import socket +import errno +import copy +import email +import email.message +import email.generator +import StringIO +try: + if sys.platform == 'os2emx': + # OS/2 EMX fcntl() not adequate + raise ImportError + import fcntl +except ImportError: + fcntl = None + +import warnings +with warnings.catch_warnings(): + if sys.py3kwarning: + warnings.filterwarnings("ignore", ".*rfc822 has been removed", + DeprecationWarning) + import rfc822 + +__all__ = [ 'Mailbox', 'Maildir', 'mbox', 'MH', 'Babyl', 'MMDF', + 'Message', 'MaildirMessage', 'mboxMessage', 'MHMessage', + 'BabylMessage', 'MMDFMessage', 'UnixMailbox', + 'PortableUnixMailbox', 'MmdfMailbox', 'MHMailbox', 'BabylMailbox' ] + +class Mailbox: + """A group of messages in a particular place.""" + + def __init__(self, path, factory=None, create=True): + """Initialize a Mailbox instance.""" + self._path = os.path.abspath(os.path.expanduser(path)) + self._factory = factory + + def add(self, message): + """Add message and return assigned key.""" + raise NotImplementedError('Method must be implemented by subclass') + + def remove(self, key): + """Remove the keyed message; raise KeyError if it doesn't exist.""" + raise NotImplementedError('Method must be implemented by subclass') + + def __delitem__(self, key): + self.remove(key) + + def discard(self, key): + """If the keyed message exists, remove it.""" + try: + self.remove(key) + except KeyError: + pass + + def __setitem__(self, key, message): + """Replace the keyed message; raise KeyError if it doesn't exist.""" + raise NotImplementedError('Method must be implemented by subclass') + + def get(self, key, default=None): + """Return the keyed message, or default if it doesn't exist.""" + try: + return self.__getitem__(key) + except KeyError: + return default + + def __getitem__(self, key): + """Return the keyed message; raise KeyError if it doesn't exist.""" + if not self._factory: + return self.get_message(key) + else: + return self._factory(self.get_file(key)) + + def get_message(self, key): + """Return a Message representation or raise a KeyError.""" + raise NotImplementedError('Method must be implemented by subclass') + + def get_string(self, key): + """Return a string representation or raise a KeyError.""" + raise NotImplementedError('Method must be implemented by subclass') + + def get_file(self, key): + """Return a file-like representation or raise a KeyError.""" + raise NotImplementedError('Method must be implemented by subclass') + + def iterkeys(self): + """Return an iterator over keys.""" + raise NotImplementedError('Method must be implemented by subclass') + + def keys(self): + """Return a list of keys.""" + return list(self.iterkeys()) + + def itervalues(self): + """Return an iterator over all messages.""" + for key in self.iterkeys(): + try: + value = self[key] + except KeyError: + continue + yield value + + def __iter__(self): + return self.itervalues() + + def values(self): + """Return a list of messages. Memory intensive.""" + return list(self.itervalues()) + + def iteritems(self): + """Return an iterator over (key, message) tuples.""" + for key in self.iterkeys(): + try: + value = self[key] + except KeyError: + continue + yield (key, value) + + def items(self): + """Return a list of (key, message) tuples. Memory intensive.""" + return list(self.iteritems()) + + def has_key(self, key): + """Return True if the keyed message exists, False otherwise.""" + raise NotImplementedError('Method must be implemented by subclass') + + def __contains__(self, key): + return self.has_key(key) + + def __len__(self): + """Return a count of messages in the mailbox.""" + raise NotImplementedError('Method must be implemented by subclass') + + def clear(self): + """Delete all messages.""" + for key in self.iterkeys(): + self.discard(key) + + def pop(self, key, default=None): + """Delete the keyed message and return it, or default.""" + try: + result = self[key] + except KeyError: + return default + self.discard(key) + return result + + def popitem(self): + """Delete an arbitrary (key, message) pair and return it.""" + for key in self.iterkeys(): + return (key, self.pop(key)) # This is only run once. + else: + raise KeyError('No messages in mailbox') + + def update(self, arg=None): + """Change the messages that correspond to certain keys.""" + if hasattr(arg, 'iteritems'): + source = arg.iteritems() + elif hasattr(arg, 'items'): + source = arg.items() + else: + source = arg + bad_key = False + for key, message in source: + try: + self[key] = message + except KeyError: + bad_key = True + if bad_key: + raise KeyError('No message with key(s)') + + def flush(self): + """Write any pending changes to the disk.""" + raise NotImplementedError('Method must be implemented by subclass') + + def lock(self): + """Lock the mailbox.""" + raise NotImplementedError('Method must be implemented by subclass') + + def unlock(self): + """Unlock the mailbox if it is locked.""" + raise NotImplementedError('Method must be implemented by subclass') + + def close(self): + """Flush and close the mailbox.""" + raise NotImplementedError('Method must be implemented by subclass') + + def _dump_message(self, message, target, mangle_from_=False): + # Most files are opened in binary mode to allow predictable seeking. + # To get native line endings on disk, the user-friendly \n line endings + # used in strings and by email.Message are translated here. + """Dump message contents to target file.""" + if isinstance(message, email.message.Message): + buffer = StringIO.StringIO() + gen = email.generator.Generator(buffer, mangle_from_, 0) + gen.flatten(message) + buffer.seek(0) + target.write(buffer.read().replace('\n', os.linesep)) + elif isinstance(message, str): + if mangle_from_: + message = message.replace('\nFrom ', '\n>From ') + message = message.replace('\n', os.linesep) + target.write(message) + elif hasattr(message, 'read'): + while True: + line = message.readline() + if line == '': + break + if mangle_from_ and line.startswith('From '): + line = '>From ' + line[5:] + line = line.replace('\n', os.linesep) + target.write(line) + else: + raise TypeError('Invalid message type: %s' % type(message)) + + +class Maildir(Mailbox): + """A qmail-style Maildir mailbox.""" + + colon = ':' + + def __init__(self, dirname, factory=rfc822.Message, create=True): + """Initialize a Maildir instance.""" + Mailbox.__init__(self, dirname, factory, create) + self._paths = { + 'tmp': os.path.join(self._path, 'tmp'), + 'new': os.path.join(self._path, 'new'), + 'cur': os.path.join(self._path, 'cur'), + } + if not os.path.exists(self._path): + if create: + os.mkdir(self._path, 0700) + for path in self._paths.values(): + os.mkdir(path, 0o700) + else: + raise NoSuchMailboxError(self._path) + self._toc = {} + self._toc_mtimes = {} + for subdir in ('cur', 'new'): + self._toc_mtimes[subdir] = os.path.getmtime(self._paths[subdir]) + self._last_read = time.time() # Records last time we read cur/new + self._skewfactor = 0.1 # Adjust if os/fs clocks are skewing + + def add(self, message): + """Add message and return assigned key.""" + tmp_file = self._create_tmp() + try: + self._dump_message(message, tmp_file) + except BaseException: + tmp_file.close() + os.remove(tmp_file.name) + raise + _sync_close(tmp_file) + if isinstance(message, MaildirMessage): + subdir = message.get_subdir() + suffix = self.colon + message.get_info() + if suffix == self.colon: + suffix = '' + else: + subdir = 'new' + suffix = '' + uniq = os.path.basename(tmp_file.name).split(self.colon)[0] + dest = os.path.join(self._path, subdir, uniq + suffix) + try: + if hasattr(os, 'link'): + os.link(tmp_file.name, dest) + os.remove(tmp_file.name) + else: + os.rename(tmp_file.name, dest) + except OSError, e: + os.remove(tmp_file.name) + if e.errno == errno.EEXIST: + raise ExternalClashError('Name clash with existing message: %s' + % dest) + else: + raise + if isinstance(message, MaildirMessage): + os.utime(dest, (os.path.getatime(dest), message.get_date())) + return uniq + + def remove(self, key): + """Remove the keyed message; raise KeyError if it doesn't exist.""" + os.remove(os.path.join(self._path, self._lookup(key))) + + def discard(self, key): + """If the keyed message exists, remove it.""" + # This overrides an inapplicable implementation in the superclass. + try: + self.remove(key) + except KeyError: + pass + except OSError, e: + if e.errno != errno.ENOENT: + raise + + def __setitem__(self, key, message): + """Replace the keyed message; raise KeyError if it doesn't exist.""" + old_subpath = self._lookup(key) + temp_key = self.add(message) + temp_subpath = self._lookup(temp_key) + if isinstance(message, MaildirMessage): + # temp's subdir and suffix were specified by message. + dominant_subpath = temp_subpath + else: + # temp's subdir and suffix were defaults from add(). + dominant_subpath = old_subpath + subdir = os.path.dirname(dominant_subpath) + if self.colon in dominant_subpath: + suffix = self.colon + dominant_subpath.split(self.colon)[-1] + else: + suffix = '' + self.discard(key) + new_path = os.path.join(self._path, subdir, key + suffix) + os.rename(os.path.join(self._path, temp_subpath), new_path) + if isinstance(message, MaildirMessage): + os.utime(new_path, (os.path.getatime(new_path), + message.get_date())) + + def get_message(self, key): + """Return a Message representation or raise a KeyError.""" + subpath = self._lookup(key) + f = open(os.path.join(self._path, subpath), 'r') + try: + if self._factory: + msg = self._factory(f) + else: + msg = MaildirMessage(f) + finally: + f.close() + subdir, name = os.path.split(subpath) + msg.set_subdir(subdir) + if self.colon in name: + msg.set_info(name.split(self.colon)[-1]) + msg.set_date(os.path.getmtime(os.path.join(self._path, subpath))) + return msg + + def get_string(self, key): + """Return a string representation or raise a KeyError.""" + f = open(os.path.join(self._path, self._lookup(key)), 'r') + try: + return f.read() + finally: + f.close() + + def get_file(self, key): + """Return a file-like representation or raise a KeyError.""" + f = open(os.path.join(self._path, self._lookup(key)), 'rb') + return _ProxyFile(f) + + def iterkeys(self): + """Return an iterator over keys.""" + self._refresh() + for key in self._toc: + try: + self._lookup(key) + except KeyError: + continue + yield key + + def has_key(self, key): + """Return True if the keyed message exists, False otherwise.""" + self._refresh() + return key in self._toc + + def __len__(self): + """Return a count of messages in the mailbox.""" + self._refresh() + return len(self._toc) + + def flush(self): + """Write any pending changes to disk.""" + # Maildir changes are always written immediately, so there's nothing + # to do. + pass + + def lock(self): + """Lock the mailbox.""" + return + + def unlock(self): + """Unlock the mailbox if it is locked.""" + return + + def close(self): + """Flush and close the mailbox.""" + return + + def list_folders(self): + """Return a list of folder names.""" + result = [] + for entry in os.listdir(self._path): + if len(entry) > 1 and entry[0] == '.' and \ + os.path.isdir(os.path.join(self._path, entry)): + result.append(entry[1:]) + return result + + def get_folder(self, folder): + """Return a Maildir instance for the named folder.""" + return Maildir(os.path.join(self._path, '.' + folder), + factory=self._factory, + create=False) + + def add_folder(self, folder): + """Create a folder and return a Maildir instance representing it.""" + path = os.path.join(self._path, '.' + folder) + result = Maildir(path, factory=self._factory) + maildirfolder_path = os.path.join(path, 'maildirfolder') + if not os.path.exists(maildirfolder_path): + os.close(os.open(maildirfolder_path, os.O_CREAT | os.O_WRONLY, + 0666)) + return result + + def remove_folder(self, folder): + """Delete the named folder, which must be empty.""" + path = os.path.join(self._path, '.' + folder) + for entry in os.listdir(os.path.join(path, 'new')) + \ + os.listdir(os.path.join(path, 'cur')): + if len(entry) < 1 or entry[0] != '.': + raise NotEmptyError('Folder contains message(s): %s' % folder) + for entry in os.listdir(path): + if entry != 'new' and entry != 'cur' and entry != 'tmp' and \ + os.path.isdir(os.path.join(path, entry)): + raise NotEmptyError("Folder contains subdirectory '%s': %s" % + (folder, entry)) + for root, dirs, files in os.walk(path, topdown=False): + for entry in files: + os.remove(os.path.join(root, entry)) + for entry in dirs: + os.rmdir(os.path.join(root, entry)) + os.rmdir(path) + + def clean(self): + """Delete old files in "tmp".""" + now = time.time() + for entry in os.listdir(os.path.join(self._path, 'tmp')): + path = os.path.join(self._path, 'tmp', entry) + if now - os.path.getatime(path) > 129600: # 60 * 60 * 36 + os.remove(path) + + _count = 1 # This is used to generate unique file names. + + def _create_tmp(self): + """Create a file in the tmp subdirectory and open and return it.""" + now = time.time() + hostname = socket.gethostname() + if '/' in hostname: + hostname = hostname.replace('/', r'\057') + if ':' in hostname: + hostname = hostname.replace(':', r'\072') + uniq = "%s.M%sP%sQ%s.%s" % (int(now), int(now % 1 * 1e6), os.getpid(), + Maildir._count, hostname) + path = os.path.join(self._path, 'tmp', uniq) + try: + os.stat(path) + except OSError, e: + if e.errno == errno.ENOENT: + Maildir._count += 1 + try: + return _create_carefully(path) + except OSError, e: + if e.errno != errno.EEXIST: + raise + else: + raise + + # Fall through to here if stat succeeded or open raised EEXIST. + raise ExternalClashError('Name clash prevented file creation: %s' % + path) + + def _refresh(self): + """Update table of contents mapping.""" + # If it has been less than two seconds since the last _refresh() call, + # we have to unconditionally re-read the mailbox just in case it has + # been modified, because os.path.mtime() has a 2 sec resolution in the + # most common worst case (FAT) and a 1 sec resolution typically. This + # results in a few unnecessary re-reads when _refresh() is called + # multiple times in that interval, but once the clock ticks over, we + # will only re-read as needed. Because the filesystem might be being + # served by an independent system with its own clock, we record and + # compare with the mtimes from the filesystem. Because the other + # system's clock might be skewing relative to our clock, we add an + # extra delta to our wait. The default is one tenth second, but is an + # instance variable and so can be adjusted if dealing with a + # particularly skewed or irregular system. + if time.time() - self._last_read > 2 + self._skewfactor: + refresh = False + for subdir in self._toc_mtimes: + mtime = os.path.getmtime(self._paths[subdir]) + if mtime > self._toc_mtimes[subdir]: + refresh = True + self._toc_mtimes[subdir] = mtime + if not refresh: + return + # Refresh toc + self._toc = {} + for subdir in self._toc_mtimes: + path = self._paths[subdir] + for entry in os.listdir(path): + p = os.path.join(path, entry) + if os.path.isdir(p): + continue + uniq = entry.split(self.colon)[0] + self._toc[uniq] = os.path.join(subdir, entry) + self._last_read = time.time() + + def _lookup(self, key): + """Use TOC to return subpath for given key, or raise a KeyError.""" + try: + if os.path.exists(os.path.join(self._path, self._toc[key])): + return self._toc[key] + except KeyError: + pass + self._refresh() + try: + return self._toc[key] + except KeyError: + raise KeyError('No message with key: %s' % key) + + # This method is for backward compatibility only. + def next(self): + """Return the next message in a one-time iteration.""" + if not hasattr(self, '_onetime_keys'): + self._onetime_keys = self.iterkeys() + while True: + try: + return self[self._onetime_keys.next()] + except StopIteration: + return None + except KeyError: + continue + + +class _singlefileMailbox(Mailbox): + """A single-file mailbox.""" + + def __init__(self, path, factory=None, create=True): + """Initialize a single-file mailbox.""" + Mailbox.__init__(self, path, factory, create) + try: + f = open(self._path, 'rb+') + except IOError, e: + if e.errno == errno.ENOENT: + if create: + f = open(self._path, 'wb+') + else: + raise NoSuchMailboxError(self._path) + elif e.errno in (errno.EACCES, errno.EROFS): + f = open(self._path, 'rb') + else: + raise + self._file = f + self._toc = None + self._next_key = 0 + self._pending = False # No changes require rewriting the file. + self._locked = False + self._file_length = None # Used to record mailbox size + + def add(self, message): + """Add message and return assigned key.""" + self._lookup() + self._toc[self._next_key] = self._append_message(message) + self._next_key += 1 + self._pending = True + return self._next_key - 1 + + def remove(self, key): + """Remove the keyed message; raise KeyError if it doesn't exist.""" + self._lookup(key) + del self._toc[key] + self._pending = True + + def __setitem__(self, key, message): + """Replace the keyed message; raise KeyError if it doesn't exist.""" + self._lookup(key) + self._toc[key] = self._append_message(message) + self._pending = True + + def iterkeys(self): + """Return an iterator over keys.""" + self._lookup() + for key in self._toc.keys(): + yield key + + def has_key(self, key): + """Return True if the keyed message exists, False otherwise.""" + self._lookup() + return key in self._toc + + def __len__(self): + """Return a count of messages in the mailbox.""" + self._lookup() + return len(self._toc) + + def lock(self): + """Lock the mailbox.""" + if not self._locked: + _lock_file(self._file) + self._locked = True + + def unlock(self): + """Unlock the mailbox if it is locked.""" + if self._locked: + _unlock_file(self._file) + self._locked = False + + def flush(self): + """Write any pending changes to disk.""" + if not self._pending: + return + if self._file.closed: + self._pending = False + return + # In order to be writing anything out at all, self._toc must + # already have been generated (and presumably has been modified + # by adding or deleting an item). + assert self._toc is not None + + # Check length of self._file; if it's changed, some other process + # has modified the mailbox since we scanned it. + self._file.seek(0, 2) + cur_len = self._file.tell() + if cur_len != self._file_length: + raise ExternalClashError('Size of mailbox file changed ' + '(expected %i, found %i)' % + (self._file_length, cur_len)) + + new_file = _create_temporary(self._path) + try: + new_toc = {} + self._pre_mailbox_hook(new_file) + for key in sorted(self._toc.keys()): + start, stop = self._toc[key] + self._file.seek(start) + self._pre_message_hook(new_file) + new_start = new_file.tell() + while True: + buffer = self._file.read(min(4096, + stop - self._file.tell())) + if buffer == '': + break + new_file.write(buffer) + new_toc[key] = (new_start, new_file.tell()) + self._post_message_hook(new_file) + except: + new_file.close() + os.remove(new_file.name) + raise + _sync_close(new_file) + # self._file is about to get replaced, so no need to sync. + self._file.close() + try: + os.rename(new_file.name, self._path) + except OSError, e: + if e.errno == errno.EEXIST or \ + (os.name == 'os2' and e.errno == errno.EACCES): + os.remove(self._path) + os.rename(new_file.name, self._path) + else: + raise + self._file = open(self._path, 'rb+') + self._toc = new_toc + self._pending = False + if self._locked: + _lock_file(self._file, dotlock=False) + + def _pre_mailbox_hook(self, f): + """Called before writing the mailbox to file f.""" + return + + def _pre_message_hook(self, f): + """Called before writing each message to file f.""" + return + + def _post_message_hook(self, f): + """Called after writing each message to file f.""" + return + + def close(self): + """Flush and close the mailbox.""" + self.flush() + if self._locked: + self.unlock() + self._file.close() # Sync has been done by self.flush() above. + + def _lookup(self, key=None): + """Return (start, stop) or raise KeyError.""" + if self._toc is None: + self._generate_toc() + if key is not None: + try: + return self._toc[key] + except KeyError: + raise KeyError('No message with key: %s' % key) + + def _append_message(self, message): + """Append message to mailbox and return (start, stop) offsets.""" + self._file.seek(0, 2) + before = self._file.tell() + try: + self._pre_message_hook(self._file) + offsets = self._install_message(message) + self._post_message_hook(self._file) + except BaseException: + self._file.truncate(before) + raise + self._file.flush() + self._file_length = self._file.tell() # Record current length of mailbox + return offsets + + + +class _mboxMMDF(_singlefileMailbox): + """An mbox or MMDF mailbox.""" + + _mangle_from_ = True + + def get_message(self, key): + """Return a Message representation or raise a KeyError.""" + start, stop = self._lookup(key) + self._file.seek(start) + from_line = self._file.readline().replace(os.linesep, '') + string = self._file.read(stop - self._file.tell()) + msg = self._message_factory(string.replace(os.linesep, '\n')) + msg.set_from(from_line[5:]) + return msg + + def get_string(self, key, from_=False): + """Return a string representation or raise a KeyError.""" + start, stop = self._lookup(key) + self._file.seek(start) + if not from_: + self._file.readline() + string = self._file.read(stop - self._file.tell()) + return string.replace(os.linesep, '\n') + + def get_file(self, key, from_=False): + """Return a file-like representation or raise a KeyError.""" + start, stop = self._lookup(key) + self._file.seek(start) + + if not from_: + self._file.readline() + return _PartialFile(self._file, self._file.tell(), stop) + + def _install_message(self, message): + """Format a message and blindly write to self._file.""" + from_line = None + if isinstance(message, str) and message.startswith('From '): + newline = message.find('\n') + if newline != -1: + from_line = message[:newline] + message = message[newline + 1:] + else: + from_line = message + message = '' + elif isinstance(message, _mboxMMDFMessage): + from_line = 'From ' + message.get_from() + elif isinstance(message, email.message.Message): + from_line = message.get_unixfrom() # May be None. + if from_line is None: + from_line = 'From MAILER-DAEMON %s' % time.asctime(time.gmtime()) + start = self._file.tell() + self._file.write(from_line + os.linesep) + self._dump_message(message, self._file, self._mangle_from_) + stop = self._file.tell() + return (start, stop) + + +class mbox(_mboxMMDF): + """A classic mbox mailbox.""" + + _mangle_from_ = True + + def __init__(self, path, factory=None, create=True): + """Initialize an mbox mailbox.""" + self._message_factory = mboxMessage + _mboxMMDF.__init__(self, path, factory, create) + + def _pre_message_hook(self, f): + """Called before writing each message to file f.""" + if f.tell() != 0: + f.write(os.linesep) + + def _generate_toc(self): + """Generate key-to-(start, stop) table of contents.""" + starts, stops = [], [] + self._file.seek(0) + while True: + line_pos = self._file.tell() + line = self._file.readline() + if line.startswith('From '): + if len(stops) < len(starts): + stops.append(line_pos - len(os.linesep)) + starts.append(line_pos) + elif line == '': + stops.append(line_pos) + break + self._toc = dict(enumerate(zip(starts, stops))) + self._next_key = len(self._toc) + self._file_length = self._file.tell() + + +class MMDF(_mboxMMDF): + """An MMDF mailbox.""" + + def __init__(self, path, factory=None, create=True): + """Initialize an MMDF mailbox.""" + self._message_factory = MMDFMessage + _mboxMMDF.__init__(self, path, factory, create) + + def _pre_message_hook(self, f): + """Called before writing each message to file f.""" + f.write('\001\001\001\001' + os.linesep) + + def _post_message_hook(self, f): + """Called after writing each message to file f.""" + f.write(os.linesep + '\001\001\001\001' + os.linesep) + + def _generate_toc(self): + """Generate key-to-(start, stop) table of contents.""" + starts, stops = [], [] + self._file.seek(0) + next_pos = 0 + while True: + line_pos = next_pos + line = self._file.readline() + next_pos = self._file.tell() + if line.startswith('\001\001\001\001' + os.linesep): + starts.append(next_pos) + while True: + line_pos = next_pos + line = self._file.readline() + next_pos = self._file.tell() + if line == '\001\001\001\001' + os.linesep: + stops.append(line_pos - len(os.linesep)) + break + elif line == '': + stops.append(line_pos) + break + elif line == '': + break + self._toc = dict(enumerate(zip(starts, stops))) + self._next_key = len(self._toc) + self._file.seek(0, 2) + self._file_length = self._file.tell() + + +class MH(Mailbox): + """An MH mailbox.""" + + def __init__(self, path, factory=None, create=True): + """Initialize an MH instance.""" + Mailbox.__init__(self, path, factory, create) + if not os.path.exists(self._path): + if create: + os.mkdir(self._path, 0700) + os.close(os.open(os.path.join(self._path, '.mh_sequences'), + os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0600)) + else: + raise NoSuchMailboxError(self._path) + self._locked = False + + def add(self, message): + """Add message and return assigned key.""" + keys = self.keys() + if len(keys) == 0: + new_key = 1 + else: + new_key = max(keys) + 1 + new_path = os.path.join(self._path, str(new_key)) + f = _create_carefully(new_path) + closed = False + try: + if self._locked: + _lock_file(f) + try: + try: + self._dump_message(message, f) + except BaseException: + # Unlock and close so it can be deleted on Windows + if self._locked: + _unlock_file(f) + _sync_close(f) + closed = True + os.remove(new_path) + raise + if isinstance(message, MHMessage): + self._dump_sequences(message, new_key) + finally: + if self._locked: + _unlock_file(f) + finally: + if not closed: + _sync_close(f) + return new_key + + def remove(self, key): + """Remove the keyed message; raise KeyError if it doesn't exist.""" + path = os.path.join(self._path, str(key)) + try: + f = open(path, 'rb+') + except IOError, e: + if e.errno == errno.ENOENT: + raise KeyError('No message with key: %s' % key) + else: + raise + else: + f.close() + os.remove(path) + + def __setitem__(self, key, message): + """Replace the keyed message; raise KeyError if it doesn't exist.""" + path = os.path.join(self._path, str(key)) + try: + f = open(path, 'rb+') + except IOError, e: + if e.errno == errno.ENOENT: + raise KeyError('No message with key: %s' % key) + else: + raise + try: + if self._locked: + _lock_file(f) + try: + os.close(os.open(path, os.O_WRONLY | os.O_TRUNC)) + self._dump_message(message, f) + if isinstance(message, MHMessage): + self._dump_sequences(message, key) + finally: + if self._locked: + _unlock_file(f) + finally: + _sync_close(f) + + def get_message(self, key): + """Return a Message representation or raise a KeyError.""" + try: + if self._locked: + f = open(os.path.join(self._path, str(key)), 'r+') + else: + f = open(os.path.join(self._path, str(key)), 'r') + except IOError, e: + if e.errno == errno.ENOENT: + raise KeyError('No message with key: %s' % key) + else: + raise + try: + if self._locked: + _lock_file(f) + try: + msg = MHMessage(f) + finally: + if self._locked: + _unlock_file(f) + finally: + f.close() + for name, key_list in self.get_sequences().iteritems(): + if key in key_list: + msg.add_sequence(name) + return msg + + def get_string(self, key): + """Return a string representation or raise a KeyError.""" + try: + if self._locked: + f = open(os.path.join(self._path, str(key)), 'r+') + else: + f = open(os.path.join(self._path, str(key)), 'r') + except IOError, e: + if e.errno == errno.ENOENT: + raise KeyError('No message with key: %s' % key) + else: + raise + try: + if self._locked: + _lock_file(f) + try: + return f.read() + finally: + if self._locked: + _unlock_file(f) + finally: + f.close() + + def get_file(self, key): + """Return a file-like representation or raise a KeyError.""" + try: + f = open(os.path.join(self._path, str(key)), 'rb') + except IOError, e: + if e.errno == errno.ENOENT: + raise KeyError('No message with key: %s' % key) + else: + raise + return _ProxyFile(f) + + def iterkeys(self): + """Return an iterator over keys.""" + return iter(sorted(int(entry) for entry in os.listdir(self._path) + if entry.isdigit())) + + def has_key(self, key): + """Return True if the keyed message exists, False otherwise.""" + return os.path.exists(os.path.join(self._path, str(key))) + + def __len__(self): + """Return a count of messages in the mailbox.""" + return len(list(self.iterkeys())) + + def lock(self): + """Lock the mailbox.""" + if not self._locked: + self._file = open(os.path.join(self._path, '.mh_sequences'), 'rb+') + _lock_file(self._file) + self._locked = True + + def unlock(self): + """Unlock the mailbox if it is locked.""" + if self._locked: + _unlock_file(self._file) + _sync_close(self._file) + del self._file + self._locked = False + + def flush(self): + """Write any pending changes to the disk.""" + return + + def close(self): + """Flush and close the mailbox.""" + if self._locked: + self.unlock() + + def list_folders(self): + """Return a list of folder names.""" + result = [] + for entry in os.listdir(self._path): + if os.path.isdir(os.path.join(self._path, entry)): + result.append(entry) + return result + + def get_folder(self, folder): + """Return an MH instance for the named folder.""" + return MH(os.path.join(self._path, folder), + factory=self._factory, create=False) + + def add_folder(self, folder): + """Create a folder and return an MH instance representing it.""" + return MH(os.path.join(self._path, folder), + factory=self._factory) + + def remove_folder(self, folder): + """Delete the named folder, which must be empty.""" + path = os.path.join(self._path, folder) + entries = os.listdir(path) + if entries == ['.mh_sequences']: + os.remove(os.path.join(path, '.mh_sequences')) + elif entries == []: + pass + else: + raise NotEmptyError('Folder not empty: %s' % self._path) + os.rmdir(path) + + def get_sequences(self): + """Return a name-to-key-list dictionary to define each sequence.""" + results = {} + f = open(os.path.join(self._path, '.mh_sequences'), 'r') + try: + all_keys = set(self.keys()) + for line in f: + try: + name, contents = line.split(':') + keys = set() + for spec in contents.split(): + if spec.isdigit(): + keys.add(int(spec)) + else: + start, stop = (int(x) for x in spec.split('-')) + keys.update(range(start, stop + 1)) + results[name] = [key for key in sorted(keys) \ + if key in all_keys] + if len(results[name]) == 0: + del results[name] + except ValueError: + raise FormatError('Invalid sequence specification: %s' % + line.rstrip()) + finally: + f.close() + return results + + def set_sequences(self, sequences): + """Set sequences using the given name-to-key-list dictionary.""" + f = open(os.path.join(self._path, '.mh_sequences'), 'r+') + try: + os.close(os.open(f.name, os.O_WRONLY | os.O_TRUNC)) + for name, keys in sequences.iteritems(): + if len(keys) == 0: + continue + f.write('%s:' % name) + prev = None + completing = False + for key in sorted(set(keys)): + if key - 1 == prev: + if not completing: + completing = True + f.write('-') + elif completing: + completing = False + f.write('%s %s' % (prev, key)) + else: + f.write(' %s' % key) + prev = key + if completing: + f.write(str(prev) + '\n') + else: + f.write('\n') + finally: + _sync_close(f) + + def pack(self): + """Re-name messages to eliminate numbering gaps. Invalidates keys.""" + sequences = self.get_sequences() + prev = 0 + changes = [] + for key in self.iterkeys(): + if key - 1 != prev: + changes.append((key, prev + 1)) + if hasattr(os, 'link'): + os.link(os.path.join(self._path, str(key)), + os.path.join(self._path, str(prev + 1))) + os.unlink(os.path.join(self._path, str(key))) + else: + os.rename(os.path.join(self._path, str(key)), + os.path.join(self._path, str(prev + 1))) + prev += 1 + self._next_key = prev + 1 + if len(changes) == 0: + return + for name, key_list in sequences.items(): + for old, new in changes: + if old in key_list: + key_list[key_list.index(old)] = new + self.set_sequences(sequences) + + def _dump_sequences(self, message, key): + """Inspect a new MHMessage and update sequences appropriately.""" + pending_sequences = message.get_sequences() + all_sequences = self.get_sequences() + for name, key_list in all_sequences.iteritems(): + if name in pending_sequences: + key_list.append(key) + elif key in key_list: + del key_list[key_list.index(key)] + for sequence in pending_sequences: + if sequence not in all_sequences: + all_sequences[sequence] = [key] + self.set_sequences(all_sequences) + + +class Babyl(_singlefileMailbox): + """An Rmail-style Babyl mailbox.""" + + _special_labels = frozenset(('unseen', 'deleted', 'filed', 'answered', + 'forwarded', 'edited', 'resent')) + + def __init__(self, path, factory=None, create=True): + """Initialize a Babyl mailbox.""" + _singlefileMailbox.__init__(self, path, factory, create) + self._labels = {} + + def add(self, message): + """Add message and return assigned key.""" + key = _singlefileMailbox.add(self, message) + if isinstance(message, BabylMessage): + self._labels[key] = message.get_labels() + return key + + def remove(self, key): + """Remove the keyed message; raise KeyError if it doesn't exist.""" + _singlefileMailbox.remove(self, key) + if key in self._labels: + del self._labels[key] + + def __setitem__(self, key, message): + """Replace the keyed message; raise KeyError if it doesn't exist.""" + _singlefileMailbox.__setitem__(self, key, message) + if isinstance(message, BabylMessage): + self._labels[key] = message.get_labels() + + def get_message(self, key): + """Return a Message representation or raise a KeyError.""" + start, stop = self._lookup(key) + self._file.seek(start) + self._file.readline() # Skip '1,' line specifying labels. + original_headers = StringIO.StringIO() + while True: + line = self._file.readline() + if line == '*** EOOH ***' + os.linesep or line == '': + break + original_headers.write(line.replace(os.linesep, '\n')) + visible_headers = StringIO.StringIO() + while True: + line = self._file.readline() + if line == os.linesep or line == '': + break + visible_headers.write(line.replace(os.linesep, '\n')) + body = self._file.read(stop - self._file.tell()).replace(os.linesep, + '\n') + msg = BabylMessage(original_headers.getvalue() + body) + msg.set_visible(visible_headers.getvalue()) + if key in self._labels: + msg.set_labels(self._labels[key]) + return msg + + def get_string(self, key): + """Return a string representation or raise a KeyError.""" + start, stop = self._lookup(key) + self._file.seek(start) + self._file.readline() # Skip '1,' line specifying labels. + original_headers = StringIO.StringIO() + while True: + line = self._file.readline() + if line == '*** EOOH ***' + os.linesep or line == '': + break + original_headers.write(line.replace(os.linesep, '\n')) + while True: + line = self._file.readline() + if line == os.linesep or line == '': + break + return original_headers.getvalue() + \ + self._file.read(stop - self._file.tell()).replace(os.linesep, + '\n') + + def get_file(self, key): + """Return a file-like representation or raise a KeyError.""" + return StringIO.StringIO(self.get_string(key).replace('\n', + os.linesep)) + + def get_labels(self): + """Return a list of user-defined labels in the mailbox.""" + self._lookup() + labels = set() + for label_list in self._labels.values(): + labels.update(label_list) + labels.difference_update(self._special_labels) + return list(labels) + + def _generate_toc(self): + """Generate key-to-(start, stop) table of contents.""" + starts, stops = [], [] + self._file.seek(0) + next_pos = 0 + label_lists = [] + while True: + line_pos = next_pos + line = self._file.readline() + next_pos = self._file.tell() + if line == '\037\014' + os.linesep: + if len(stops) < len(starts): + stops.append(line_pos - len(os.linesep)) + starts.append(next_pos) + labels = [label.strip() for label + in self._file.readline()[1:].split(',') + if label.strip() != ''] + label_lists.append(labels) + elif line == '\037' or line == '\037' + os.linesep: + if len(stops) < len(starts): + stops.append(line_pos - len(os.linesep)) + elif line == '': + stops.append(line_pos - len(os.linesep)) + break + self._toc = dict(enumerate(zip(starts, stops))) + self._labels = dict(enumerate(label_lists)) + self._next_key = len(self._toc) + self._file.seek(0, 2) + self._file_length = self._file.tell() + + def _pre_mailbox_hook(self, f): + """Called before writing the mailbox to file f.""" + f.write('BABYL OPTIONS:%sVersion: 5%sLabels:%s%s\037' % + (os.linesep, os.linesep, ','.join(self.get_labels()), + os.linesep)) + + def _pre_message_hook(self, f): + """Called before writing each message to file f.""" + f.write('\014' + os.linesep) + + def _post_message_hook(self, f): + """Called after writing each message to file f.""" + f.write(os.linesep + '\037') + + def _install_message(self, message): + """Write message contents and return (start, stop).""" + start = self._file.tell() + if isinstance(message, BabylMessage): + special_labels = [] + labels = [] + for label in message.get_labels(): + if label in self._special_labels: + special_labels.append(label) + else: + labels.append(label) + self._file.write('1') + for label in special_labels: + self._file.write(', ' + label) + self._file.write(',,') + for label in labels: + self._file.write(' ' + label + ',') + self._file.write(os.linesep) + else: + self._file.write('1,,' + os.linesep) + if isinstance(message, email.message.Message): + orig_buffer = StringIO.StringIO() + orig_generator = email.generator.Generator(orig_buffer, False, 0) + orig_generator.flatten(message) + orig_buffer.seek(0) + while True: + line = orig_buffer.readline() + self._file.write(line.replace('\n', os.linesep)) + if line == '\n' or line == '': + break + self._file.write('*** EOOH ***' + os.linesep) + if isinstance(message, BabylMessage): + vis_buffer = StringIO.StringIO() + vis_generator = email.generator.Generator(vis_buffer, False, 0) + vis_generator.flatten(message.get_visible()) + while True: + line = vis_buffer.readline() + self._file.write(line.replace('\n', os.linesep)) + if line == '\n' or line == '': + break + else: + orig_buffer.seek(0) + while True: + line = orig_buffer.readline() + self._file.write(line.replace('\n', os.linesep)) + if line == '\n' or line == '': + break + while True: + buffer = orig_buffer.read(4096) # Buffer size is arbitrary. + if buffer == '': + break + self._file.write(buffer.replace('\n', os.linesep)) + elif isinstance(message, str): + body_start = message.find('\n\n') + 2 + if body_start - 2 != -1: + self._file.write(message[:body_start].replace('\n', + os.linesep)) + self._file.write('*** EOOH ***' + os.linesep) + self._file.write(message[:body_start].replace('\n', + os.linesep)) + self._file.write(message[body_start:].replace('\n', + os.linesep)) + else: + self._file.write('*** EOOH ***' + os.linesep + os.linesep) + self._file.write(message.replace('\n', os.linesep)) + elif hasattr(message, 'readline'): + original_pos = message.tell() + first_pass = True + while True: + line = message.readline() + self._file.write(line.replace('\n', os.linesep)) + if line == '\n' or line == '': + self._file.write('*** EOOH ***' + os.linesep) + if first_pass: + first_pass = False + message.seek(original_pos) + else: + break + while True: + buffer = message.read(4096) # Buffer size is arbitrary. + if buffer == '': + break + self._file.write(buffer.replace('\n', os.linesep)) + else: + raise TypeError('Invalid message type: %s' % type(message)) + stop = self._file.tell() + return (start, stop) + + +class Message(email.message.Message): + """Message with mailbox-format-specific properties.""" + + def __init__(self, message=None): + """Initialize a Message instance.""" + if isinstance(message, email.message.Message): + self._become_message(copy.deepcopy(message)) + if isinstance(message, Message): + message._explain_to(self) + elif isinstance(message, str): + self._become_message(email.message_from_string(message)) + elif hasattr(message, "read"): + self._become_message(email.message_from_file(message)) + elif message is None: + email.message.Message.__init__(self) + else: + raise TypeError('Invalid message type: %s' % type(message)) + + def _become_message(self, message): + """Assume the non-format-specific state of message.""" + for name in ('_headers', '_unixfrom', '_payload', '_charset', + 'preamble', 'epilogue', 'defects', '_default_type'): + self.__dict__[name] = message.__dict__[name] + + def _explain_to(self, message): + """Copy format-specific state to message insofar as possible.""" + if isinstance(message, Message): + return # There's nothing format-specific to explain. + else: + raise TypeError('Cannot convert to specified type') + + +class MaildirMessage(Message): + """Message with Maildir-specific properties.""" + + def __init__(self, message=None): + """Initialize a MaildirMessage instance.""" + self._subdir = 'new' + self._info = '' + self._date = time.time() + Message.__init__(self, message) + + def get_subdir(self): + """Return 'new' or 'cur'.""" + return self._subdir + + def set_subdir(self, subdir): + """Set subdir to 'new' or 'cur'.""" + if subdir == 'new' or subdir == 'cur': + self._subdir = subdir + else: + raise ValueError("subdir must be 'new' or 'cur': %s" % subdir) + + def get_flags(self): + """Return as a string the flags that are set.""" + if self._info.startswith('2,'): + return self._info[2:] + else: + return '' + + def set_flags(self, flags): + """Set the given flags and unset all others.""" + self._info = '2,' + ''.join(sorted(flags)) + + def add_flag(self, flag): + """Set the given flag(s) without changing others.""" + self.set_flags(''.join(set(self.get_flags()) | set(flag))) + + def remove_flag(self, flag): + """Unset the given string flag(s) without changing others.""" + if self.get_flags() != '': + self.set_flags(''.join(set(self.get_flags()) - set(flag))) + + def get_date(self): + """Return delivery date of message, in seconds since the epoch.""" + return self._date + + def set_date(self, date): + """Set delivery date of message, in seconds since the epoch.""" + try: + self._date = float(date) + except ValueError: + raise TypeError("can't convert to float: %s" % date) + + def get_info(self): + """Get the message's "info" as a string.""" + return self._info + + def set_info(self, info): + """Set the message's "info" string.""" + if isinstance(info, str): + self._info = info + else: + raise TypeError('info must be a string: %s' % type(info)) + + def _explain_to(self, message): + """Copy Maildir-specific state to message insofar as possible.""" + if isinstance(message, MaildirMessage): + message.set_flags(self.get_flags()) + message.set_subdir(self.get_subdir()) + message.set_date(self.get_date()) + elif isinstance(message, _mboxMMDFMessage): + flags = set(self.get_flags()) + if 'S' in flags: + message.add_flag('R') + if self.get_subdir() == 'cur': + message.add_flag('O') + if 'T' in flags: + message.add_flag('D') + if 'F' in flags: + message.add_flag('F') + if 'R' in flags: + message.add_flag('A') + message.set_from('MAILER-DAEMON', time.gmtime(self.get_date())) + elif isinstance(message, MHMessage): + flags = set(self.get_flags()) + if 'S' not in flags: + message.add_sequence('unseen') + if 'R' in flags: + message.add_sequence('replied') + if 'F' in flags: + message.add_sequence('flagged') + elif isinstance(message, BabylMessage): + flags = set(self.get_flags()) + if 'S' not in flags: + message.add_label('unseen') + if 'T' in flags: + message.add_label('deleted') + if 'R' in flags: + message.add_label('answered') + if 'P' in flags: + message.add_label('forwarded') + elif isinstance(message, Message): + pass + else: + raise TypeError('Cannot convert to specified type: %s' % + type(message)) + + +class _mboxMMDFMessage(Message): + """Message with mbox- or MMDF-specific properties.""" + + def __init__(self, message=None): + """Initialize an mboxMMDFMessage instance.""" + self.set_from('MAILER-DAEMON', True) + if isinstance(message, email.message.Message): + unixfrom = message.get_unixfrom() + if unixfrom is not None and unixfrom.startswith('From '): + self.set_from(unixfrom[5:]) + Message.__init__(self, message) + + def get_from(self): + """Return contents of "From " line.""" + return self._from + + def set_from(self, from_, time_=None): + """Set "From " line, formatting and appending time_ if specified.""" + if time_ is not None: + if time_ is True: + time_ = time.gmtime() + from_ += ' ' + time.asctime(time_) + self._from = from_ + + def get_flags(self): + """Return as a string the flags that are set.""" + return self.get('Status', '') + self.get('X-Status', '') + + def set_flags(self, flags): + """Set the given flags and unset all others.""" + flags = set(flags) + status_flags, xstatus_flags = '', '' + for flag in ('R', 'O'): + if flag in flags: + status_flags += flag + flags.remove(flag) + for flag in ('D', 'F', 'A'): + if flag in flags: + xstatus_flags += flag + flags.remove(flag) + xstatus_flags += ''.join(sorted(flags)) + try: + self.replace_header('Status', status_flags) + except KeyError: + self.add_header('Status', status_flags) + try: + self.replace_header('X-Status', xstatus_flags) + except KeyError: + self.add_header('X-Status', xstatus_flags) + + def add_flag(self, flag): + """Set the given flag(s) without changing others.""" + self.set_flags(''.join(set(self.get_flags()) | set(flag))) + + def remove_flag(self, flag): + """Unset the given string flag(s) without changing others.""" + if 'Status' in self or 'X-Status' in self: + self.set_flags(''.join(set(self.get_flags()) - set(flag))) + + def _explain_to(self, message): + """Copy mbox- or MMDF-specific state to message insofar as possible.""" + if isinstance(message, MaildirMessage): + flags = set(self.get_flags()) + if 'O' in flags: + message.set_subdir('cur') + if 'F' in flags: + message.add_flag('F') + if 'A' in flags: + message.add_flag('R') + if 'R' in flags: + message.add_flag('S') + if 'D' in flags: + message.add_flag('T') + del message['status'] + del message['x-status'] + maybe_date = ' '.join(self.get_from().split()[-5:]) + try: + message.set_date(calendar.timegm(time.strptime(maybe_date, + '%a %b %d %H:%M:%S %Y'))) + except (ValueError, OverflowError): + pass + elif isinstance(message, _mboxMMDFMessage): + message.set_flags(self.get_flags()) + message.set_from(self.get_from()) + elif isinstance(message, MHMessage): + flags = set(self.get_flags()) + if 'R' not in flags: + message.add_sequence('unseen') + if 'A' in flags: + message.add_sequence('replied') + if 'F' in flags: + message.add_sequence('flagged') + del message['status'] + del message['x-status'] + elif isinstance(message, BabylMessage): + flags = set(self.get_flags()) + if 'R' not in flags: + message.add_label('unseen') + if 'D' in flags: + message.add_label('deleted') + if 'A' in flags: + message.add_label('answered') + del message['status'] + del message['x-status'] + elif isinstance(message, Message): + pass + else: + raise TypeError('Cannot convert to specified type: %s' % + type(message)) + + +class mboxMessage(_mboxMMDFMessage): + """Message with mbox-specific properties.""" + + +class MHMessage(Message): + """Message with MH-specific properties.""" + + def __init__(self, message=None): + """Initialize an MHMessage instance.""" + self._sequences = [] + Message.__init__(self, message) + + def get_sequences(self): + """Return a list of sequences that include the message.""" + return self._sequences[:] + + def set_sequences(self, sequences): + """Set the list of sequences that include the message.""" + self._sequences = list(sequences) + + def add_sequence(self, sequence): + """Add sequence to list of sequences including the message.""" + if isinstance(sequence, str): + if not sequence in self._sequences: + self._sequences.append(sequence) + else: + raise TypeError('sequence must be a string: %s' % type(sequence)) + + def remove_sequence(self, sequence): + """Remove sequence from the list of sequences including the message.""" + try: + self._sequences.remove(sequence) + except ValueError: + pass + + def _explain_to(self, message): + """Copy MH-specific state to message insofar as possible.""" + if isinstance(message, MaildirMessage): + sequences = set(self.get_sequences()) + if 'unseen' in sequences: + message.set_subdir('cur') + else: + message.set_subdir('cur') + message.add_flag('S') + if 'flagged' in sequences: + message.add_flag('F') + if 'replied' in sequences: + message.add_flag('R') + elif isinstance(message, _mboxMMDFMessage): + sequences = set(self.get_sequences()) + if 'unseen' not in sequences: + message.add_flag('RO') + else: + message.add_flag('O') + if 'flagged' in sequences: + message.add_flag('F') + if 'replied' in sequences: + message.add_flag('A') + elif isinstance(message, MHMessage): + for sequence in self.get_sequences(): + message.add_sequence(sequence) + elif isinstance(message, BabylMessage): + sequences = set(self.get_sequences()) + if 'unseen' in sequences: + message.add_label('unseen') + if 'replied' in sequences: + message.add_label('answered') + elif isinstance(message, Message): + pass + else: + raise TypeError('Cannot convert to specified type: %s' % + type(message)) + + +class BabylMessage(Message): + """Message with Babyl-specific properties.""" + + def __init__(self, message=None): + """Initialize an BabylMessage instance.""" + self._labels = [] + self._visible = Message() + Message.__init__(self, message) + + def get_labels(self): + """Return a list of labels on the message.""" + return self._labels[:] + + def set_labels(self, labels): + """Set the list of labels on the message.""" + self._labels = list(labels) + + def add_label(self, label): + """Add label to list of labels on the message.""" + if isinstance(label, str): + if label not in self._labels: + self._labels.append(label) + else: + raise TypeError('label must be a string: %s' % type(label)) + + def remove_label(self, label): + """Remove label from the list of labels on the message.""" + try: + self._labels.remove(label) + except ValueError: + pass + + def get_visible(self): + """Return a Message representation of visible headers.""" + return Message(self._visible) + + def set_visible(self, visible): + """Set the Message representation of visible headers.""" + self._visible = Message(visible) + + def update_visible(self): + """Update and/or sensibly generate a set of visible headers.""" + for header in self._visible.keys(): + if header in self: + self._visible.replace_header(header, self[header]) + else: + del self._visible[header] + for header in ('Date', 'From', 'Reply-To', 'To', 'CC', 'Subject'): + if header in self and header not in self._visible: + self._visible[header] = self[header] + + def _explain_to(self, message): + """Copy Babyl-specific state to message insofar as possible.""" + if isinstance(message, MaildirMessage): + labels = set(self.get_labels()) + if 'unseen' in labels: + message.set_subdir('cur') + else: + message.set_subdir('cur') + message.add_flag('S') + if 'forwarded' in labels or 'resent' in labels: + message.add_flag('P') + if 'answered' in labels: + message.add_flag('R') + if 'deleted' in labels: + message.add_flag('T') + elif isinstance(message, _mboxMMDFMessage): + labels = set(self.get_labels()) + if 'unseen' not in labels: + message.add_flag('RO') + else: + message.add_flag('O') + if 'deleted' in labels: + message.add_flag('D') + if 'answered' in labels: + message.add_flag('A') + elif isinstance(message, MHMessage): + labels = set(self.get_labels()) + if 'unseen' in labels: + message.add_sequence('unseen') + if 'answered' in labels: + message.add_sequence('replied') + elif isinstance(message, BabylMessage): + message.set_visible(self.get_visible()) + for label in self.get_labels(): + message.add_label(label) + elif isinstance(message, Message): + pass + else: + raise TypeError('Cannot convert to specified type: %s' % + type(message)) + + +class MMDFMessage(_mboxMMDFMessage): + """Message with MMDF-specific properties.""" + + +class _ProxyFile: + """A read-only wrapper of a file.""" + + def __init__(self, f, pos=None): + """Initialize a _ProxyFile.""" + self._file = f + if pos is None: + self._pos = f.tell() + else: + self._pos = pos + + def __del__(self): + if hasattr(self,'_file'): + self.close() + + def read(self, size=None): + """Read bytes.""" + return self._read(size, self._file.read) + + def readline(self, size=None): + """Read a line.""" + return self._read(size, self._file.readline) + + def readlines(self, sizehint=None): + """Read multiple lines.""" + result = [] + for line in self: + result.append(line) + if sizehint is not None: + sizehint -= len(line) + if sizehint <= 0: + break + return result + + def __iter__(self): + """Iterate over lines.""" + return iter(self.readline, "") + + def tell(self): + """Return the position.""" + return self._pos + + def seek(self, offset, whence=0): + """Change position.""" + if whence == 1: + self._file.seek(self._pos) + self._file.seek(offset, whence) + self._pos = self._file.tell() + + def close(self): + """Close the file.""" + self._file.close() + del self._file + + def _read(self, size, read_method): + """Read size bytes using read_method.""" + if size is None: + size = -1 + self._file.seek(self._pos) + result = read_method(size) + self._pos = self._file.tell() + return result + + +class _PartialFile(_ProxyFile): + """A read-only wrapper of part of a file.""" + + def __init__(self, f, start=None, stop=None): + """Initialize a _PartialFile.""" + _ProxyFile.__init__(self, f, start) + self._start = start + self._stop = stop + + def tell(self): + """Return the position with respect to start.""" + return _ProxyFile.tell(self) - self._start + + def seek(self, offset, whence=0): + """Change position, possibly with respect to start or stop.""" + if whence == 0: + self._pos = self._start + whence = 1 + elif whence == 2: + self._pos = self._stop + whence = 1 + _ProxyFile.seek(self, offset, whence) + + def _read(self, size, read_method): + """Read size bytes using read_method, honoring start and stop.""" + remaining = self._stop - self._pos + if remaining <= 0: + return '' + if size is None or size < 0 or size > remaining: + size = remaining + return _ProxyFile._read(self, size, read_method) + + +def _lock_file(f, dotlock=True): + """Lock file f using lockf and dot locking.""" + dotlock_done = False + try: + if fcntl: + try: + fcntl.lockf(f, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError, e: + if e.errno in (errno.EAGAIN, errno.EACCES, errno.EROFS): + raise ExternalClashError('lockf: lock unavailable: %s' % + f.name) + else: + raise + if dotlock: + try: + pre_lock = _create_temporary(f.name + '.lock') + pre_lock.close() + except IOError, e: + if e.errno in (errno.EACCES, errno.EROFS): + return # Without write access, just skip dotlocking. + else: + raise + try: + if hasattr(os, 'link'): + os.link(pre_lock.name, f.name + '.lock') + dotlock_done = True + os.unlink(pre_lock.name) + else: + os.rename(pre_lock.name, f.name + '.lock') + dotlock_done = True + except OSError, e: + if e.errno == errno.EEXIST or \ + (os.name == 'os2' and e.errno == errno.EACCES): + os.remove(pre_lock.name) + raise ExternalClashError('dot lock unavailable: %s' % + f.name) + else: + raise + except: + if fcntl: + fcntl.lockf(f, fcntl.LOCK_UN) + if dotlock_done: + os.remove(f.name + '.lock') + raise + +def _unlock_file(f): + """Unlock file f using lockf and dot locking.""" + if fcntl: + fcntl.lockf(f, fcntl.LOCK_UN) + if os.path.exists(f.name + '.lock'): + os.remove(f.name + '.lock') + +def _create_carefully(path): + """Create a file if it doesn't exist and open for reading and writing.""" + fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, 0666) + try: + return open(path, 'rb+') + finally: + os.close(fd) + +def _create_temporary(path): + """Create a temp file based on path and open for reading and writing.""" + return _create_carefully('%s.%s.%s.%s' % (path, int(time.time()), + socket.gethostname(), + os.getpid())) + +def _sync_flush(f): + """Ensure changes to file f are physically on disk.""" + f.flush() + if hasattr(os, 'fsync'): + os.fsync(f.fileno()) + +def _sync_close(f): + """Close file f, ensuring all changes are physically on disk.""" + _sync_flush(f) + f.close() + +## Start: classes from the original module (for backward compatibility). + +# Note that the Maildir class, whose name is unchanged, itself offers a next() +# method for backward compatibility. + +class _Mailbox: + + def __init__(self, fp, factory=rfc822.Message): + self.fp = fp + self.seekp = 0 + self.factory = factory + + def __iter__(self): + return iter(self.next, None) + + def next(self): + while 1: + self.fp.seek(self.seekp) + try: + self._search_start() + except EOFError: + self.seekp = self.fp.tell() + return None + start = self.fp.tell() + self._search_end() + self.seekp = stop = self.fp.tell() + if start != stop: + break + return self.factory(_PartialFile(self.fp, start, stop)) + +# Recommended to use PortableUnixMailbox instead! +class UnixMailbox(_Mailbox): + + def _search_start(self): + while 1: + pos = self.fp.tell() + line = self.fp.readline() + if not line: + raise EOFError + if line[:5] == 'From ' and self._isrealfromline(line): + self.fp.seek(pos) + return + + def _search_end(self): + self.fp.readline() # Throw away header line + while 1: + pos = self.fp.tell() + line = self.fp.readline() + if not line: + return + if line[:5] == 'From ' and self._isrealfromline(line): + self.fp.seek(pos) + return + + # An overridable mechanism to test for From-line-ness. You can either + # specify a different regular expression or define a whole new + # _isrealfromline() method. Note that this only gets called for lines + # starting with the 5 characters "From ". + # + # BAW: According to + #http://home.netscape.com/eng/mozilla/2.0/relnotes/demo/content-length.html + # the only portable, reliable way to find message delimiters in a BSD (i.e + # Unix mailbox) style folder is to search for "\n\nFrom .*\n", or at the + # beginning of the file, "^From .*\n". While _fromlinepattern below seems + # like a good idea, in practice, there are too many variations for more + # strict parsing of the line to be completely accurate. + # + # _strict_isrealfromline() is the old version which tries to do stricter + # parsing of the From_ line. _portable_isrealfromline() simply returns + # true, since it's never called if the line doesn't already start with + # "From ". + # + # This algorithm, and the way it interacts with _search_start() and + # _search_end() may not be completely correct, because it doesn't check + # that the two characters preceding "From " are \n\n or the beginning of + # the file. Fixing this would require a more extensive rewrite than is + # necessary. For convenience, we've added a PortableUnixMailbox class + # which does no checking of the format of the 'From' line. + + _fromlinepattern = (r"From \s*[^\s]+\s+\w\w\w\s+\w\w\w\s+\d?\d\s+" + r"\d?\d:\d\d(:\d\d)?(\s+[^\s]+)?\s+\d\d\d\d\s*" + r"[^\s]*\s*" + "$") + _regexp = None + + def _strict_isrealfromline(self, line): + if not self._regexp: + import re + self._regexp = re.compile(self._fromlinepattern) + return self._regexp.match(line) + + def _portable_isrealfromline(self, line): + return True + + _isrealfromline = _strict_isrealfromline + + +class PortableUnixMailbox(UnixMailbox): + _isrealfromline = UnixMailbox._portable_isrealfromline + + +class MmdfMailbox(_Mailbox): + + def _search_start(self): + while 1: + line = self.fp.readline() + if not line: + raise EOFError + if line[:5] == '\001\001\001\001\n': + return + + def _search_end(self): + while 1: + pos = self.fp.tell() + line = self.fp.readline() + if not line: + return + if line == '\001\001\001\001\n': + self.fp.seek(pos) + return + + +class MHMailbox: + + def __init__(self, dirname, factory=rfc822.Message): + import re + pat = re.compile('^[1-9][0-9]*$') + self.dirname = dirname + # the three following lines could be combined into: + # list = map(long, filter(pat.match, os.listdir(self.dirname))) + list = os.listdir(self.dirname) + list = filter(pat.match, list) + list = map(long, list) + list.sort() + # This only works in Python 1.6 or later; + # before that str() added 'L': + self.boxes = map(str, list) + self.boxes.reverse() + self.factory = factory + + def __iter__(self): + return iter(self.next, None) + + def next(self): + if not self.boxes: + return None + fn = self.boxes.pop() + fp = open(os.path.join(self.dirname, fn)) + msg = self.factory(fp) + try: + msg._mh_msgno = fn + except (AttributeError, TypeError): + pass + return msg + + +class BabylMailbox(_Mailbox): + + def _search_start(self): + while 1: + line = self.fp.readline() + if not line: + raise EOFError + if line == '*** EOOH ***\n': + return + + def _search_end(self): + while 1: + pos = self.fp.tell() + line = self.fp.readline() + if not line: + return + if line == '\037\014\n' or line == '\037': + self.fp.seek(pos) + return + +## End: classes from the original module (for backward compatibility). + + +class Error(Exception): + """Raised for module-specific errors.""" + +class NoSuchMailboxError(Error): + """The specified mailbox does not exist and won't be created.""" + +class NotEmptyError(Error): + """The specified mailbox is not empty and deletion was requested.""" + +class ExternalClashError(Error): + """Another process caused an action to fail.""" + +class FormatError(Error): + """A file appears to have an invalid format.""" diff --git a/lib-python/2.7/test/test_mailbox.py b/lib-python/modified-2.7/test/test_mailbox.py rename from lib-python/2.7/test/test_mailbox.py rename to lib-python/modified-2.7/test/test_mailbox.py --- a/lib-python/2.7/test/test_mailbox.py +++ b/lib-python/modified-2.7/test/test_mailbox.py @@ -60,6 +60,8 @@ def tearDown(self): self._box.close() + if os.name == 'nt': + time.sleep(0.1) #Allow all syncing to take place self._delete_recursively(self._path) def test_add(self): @@ -137,6 +139,7 @@ msg = self._box.get(key1) self.assertEqual(msg['from'], 'foo') self.assertEqual(msg.fp.read(), '1') + msg.fp.close() def test_getitem(self): # Retrieve message using __getitem__() @@ -169,10 +172,12 @@ # Get file representations of messages key0 = self._box.add(self._template % 0) key1 = self._box.add(_sample_message) - self.assertEqual(self._box.get_file(key0).read().replace(os.linesep, '\n'), - self._template % 0) - self.assertEqual(self._box.get_file(key1).read().replace(os.linesep, '\n'), - _sample_message) + msg0 = self._box.get_file(key0) + self.assertEqual(msg0.read().replace(os.linesep, '\n'), self._template % 0) + msg1 = self._box.get_file(key1) + self.assertEqual(msg1.read().replace(os.linesep, '\n'), _sample_message) + msg0.close() + msg1.close() def test_iterkeys(self): # Get keys using iterkeys() @@ -786,6 +791,8 @@ class _TestMboxMMDF(TestMailbox): def tearDown(self): + if os.name == 'nt': + time.sleep(0.1) #Allow os to sync files self._box.close() self._delete_recursively(self._path) for lock_remnant in glob.glob(self._path + '.*'): @@ -1837,7 +1844,9 @@ self.createMessage("cur") self.mbox = mailbox.Maildir(test_support.TESTFN) #self.assertTrue(len(self.mbox.boxes) == 1) - self.assertIsNot(self.mbox.next(), None) + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() self.assertIs(self.mbox.next(), None) self.assertIs(self.mbox.next(), None) @@ -1845,7 +1854,9 @@ self.createMessage("new") self.mbox = mailbox.Maildir(test_support.TESTFN) #self.assertTrue(len(self.mbox.boxes) == 1) - self.assertIsNot(self.mbox.next(), None) + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() self.assertIs(self.mbox.next(), None) self.assertIs(self.mbox.next(), None) @@ -1854,8 +1865,12 @@ self.createMessage("new") self.mbox = mailbox.Maildir(test_support.TESTFN) #self.assertTrue(len(self.mbox.boxes) == 2) - self.assertIsNot(self.mbox.next(), None) - self.assertIsNot(self.mbox.next(), None) + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() + msg = self.mbox.next() + self.assertIsNot(msg, None) + msg.fp.close() self.assertIs(self.mbox.next(), None) self.assertIs(self.mbox.next(), None) @@ -1864,11 +1879,13 @@ import email.parser fname = self.createMessage("cur", True) n = 0 - for msg in mailbox.PortableUnixMailbox(open(fname), + fid = open(fname) + for msg in mailbox.PortableUnixMailbox(fid, email.parser.Parser().parse): n += 1 self.assertEqual(msg["subject"], "Simple Test") self.assertEqual(len(str(msg)), len(FROM_)+len(DUMMY_MESSAGE)) + fid.close() self.assertEqual(n, 1) ## End: classes from the original module (for backward compatibility). diff --git a/lib-python/2.7/test/test_old_mailbox.py b/lib-python/modified-2.7/test/test_old_mailbox.py rename from lib-python/2.7/test/test_old_mailbox.py rename to lib-python/modified-2.7/test/test_old_mailbox.py --- a/lib-python/2.7/test/test_old_mailbox.py +++ b/lib-python/modified-2.7/test/test_old_mailbox.py @@ -73,7 +73,9 @@ self.createMessage("cur") self.mbox = mailbox.Maildir(test_support.TESTFN) self.assertTrue(len(self.mbox) == 1) - self.assertTrue(self.mbox.next() is not None) + msg = self.mbox.next() + self.assertTrue(msg is not None) + msg.fp.close() self.assertTrue(self.mbox.next() is None) self.assertTrue(self.mbox.next() is None) @@ -81,7 +83,9 @@ self.createMessage("new") self.mbox = mailbox.Maildir(test_support.TESTFN) self.assertTrue(len(self.mbox) == 1) - self.assertTrue(self.mbox.next() is not None) + msg = self.mbox.next() + self.assertTrue(msg is not None) + msg.fp.close() self.assertTrue(self.mbox.next() is None) self.assertTrue(self.mbox.next() is None) @@ -90,8 +94,12 @@ self.createMessage("new") self.mbox = mailbox.Maildir(test_support.TESTFN) self.assertTrue(len(self.mbox) == 2) - self.assertTrue(self.mbox.next() is not None) - self.assertTrue(self.mbox.next() is not None) + msg = self.mbox.next() + self.assertTrue(msg is not None) + msg.fp.close() + msg = self.mbox.next() + self.assertTrue(msg is not None) + msg.fp.close() self.assertTrue(self.mbox.next() is None) self.assertTrue(self.mbox.next() is None) diff --git a/lib-python/2.7/test/test_os.py b/lib-python/modified-2.7/test/test_os.py rename from lib-python/2.7/test/test_os.py rename to lib-python/modified-2.7/test/test_os.py --- a/lib-python/2.7/test/test_os.py +++ b/lib-python/modified-2.7/test/test_os.py @@ -74,7 +74,8 @@ self.assertFalse(os.path.exists(name), "file already exists for temporary file") # make sure we can create the file - open(name, "w") + fid = open(name, "w") + fid.close() self.files.append(name) def test_tempnam(self): diff --git a/lib_pypy/_testcapi.py b/lib_pypy/_testcapi.py --- a/lib_pypy/_testcapi.py +++ b/lib_pypy/_testcapi.py @@ -29,6 +29,13 @@ if sys.platform == 'win32': # XXX libpypy-c.lib is currently not installed automatically library = os.path.join(thisdir, '..', 'include', 'libpypy-c') + if not os.path.exists(library + '.lib'): + #For a nightly build + library = os.path.join(thisdir, '..', 'include', 'python27') + if not os.path.exists(library + '.lib'): + # For a local translation + library = os.path.join(thisdir, '..', 'pypy', 'translator', + 'goal', 'libpypy-c') libraries = [library, 'oleaut32'] extra_ldargs = ['/MANIFEST', # needed for VC10 '/EXPORT:init_testcapi'] diff --git a/py/bin/_findpy.py b/py/bin/_findpy.py new file mode 100644 --- /dev/null +++ b/py/bin/_findpy.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +# +# find and import a version of 'py' +# +import sys +import os +from os.path import dirname as opd, exists, join, basename, abspath + +def searchpy(current): + while 1: + last = current + initpy = join(current, '__init__.py') + if not exists(initpy): + pydir = join(current, 'py') + # recognize py-package and ensure it is importable + if exists(pydir) and exists(join(pydir, '__init__.py')): + #for p in sys.path: + # if p == current: + # return True + if current != sys.path[0]: # if we are already first, then ok + sys.stderr.write("inserting into sys.path: %s\n" % current) + sys.path.insert(0, current) + return True + current = opd(current) + if last == current: + return False + +if not searchpy(abspath(os.curdir)): + if not searchpy(opd(abspath(sys.argv[0]))): + if not searchpy(opd(__file__)): + pass # let's hope it is just on sys.path + +import py +import pytest + +if __name__ == '__main__': + print ("py lib is at %s" % py.__file__) diff --git a/py/bin/py.test b/py/bin/py.test new file mode 100755 --- /dev/null +++ b/py/bin/py.test @@ -0,0 +1,3 @@ +#!/usr/bin/env python +from _findpy import pytest +raise SystemExit(pytest.main()) diff --git a/pypy/annotation/test/test_annrpython.py b/pypy/annotation/test/test_annrpython.py --- a/pypy/annotation/test/test_annrpython.py +++ b/pypy/annotation/test/test_annrpython.py @@ -483,7 +483,7 @@ return a_str.strip(' ') elif n == 1: return a_str.rstrip(' ') - else: + else: return a_str.lstrip(' ') s = a.build_types(f, [int, annmodel.SomeString(no_nul=True)]) assert s.no_nul @@ -3737,6 +3737,25 @@ s = a.build_types(f, [int]) assert s.listdef.listitem.range_step == 0 + def test_specialize_arg_memo(self): + @objectmodel.specialize.memo() + def g(n): + return n + @objectmodel.specialize.arg(0) + def f(i): + return g(i) + def main(i): + if i == 2: + return f(i) + elif i == 3: + return f(i) + else: + raise NotImplementedError + + a = self.RPythonAnnotator() + s = a.build_types(main, [int]) + assert isinstance(s, annmodel.SomeInteger) + def g(n): return [0,1,2,n] diff --git a/pypy/module/cpyext/stubs.py b/pypy/module/cpyext/stubs.py --- a/pypy/module/cpyext/stubs.py +++ b/pypy/module/cpyext/stubs.py @@ -2253,24 +2253,6 @@ """Concat two strings giving a new Unicode string.""" raise NotImplementedError - at cpython_api([PyObject, PyObject, Py_ssize_t], PyObject) -def PyUnicode_Split(space, s, sep, maxsplit): - """Split a string giving a list of Unicode strings. If sep is NULL, splitting - will be done at all whitespace substrings. Otherwise, splits occur at the given - separator. At most maxsplit splits will be done. If negative, no limit is - set. Separators are not included in the resulting list. - - This function used an int type for maxsplit. This might require - changes in your code for properly supporting 64-bit systems.""" - raise NotImplementedError - - at cpython_api([PyObject, rffi.INT_real], PyObject) -def PyUnicode_Splitlines(space, s, keepend): - """Split a Unicode string at line breaks, returning a list of Unicode strings. - CRLF is considered to be one line break. If keepend is 0, the Line break - characters are not included in the resulting strings.""" - raise NotImplementedError - @cpython_api([PyObject, PyObject, rffi.CCHARP], PyObject) def PyUnicode_Translate(space, str, table, errors): """Translate a string by applying a character mapping table to it and return the @@ -2287,29 +2269,6 @@ use the default error handling.""" raise NotImplementedError - at cpython_api([PyObject, PyObject, Py_ssize_t, Py_ssize_t, rffi.INT_real], Py_ssize_t, error=-2) -def PyUnicode_Find(space, str, substr, start, end, direction): - """Return the first position of substr in str*[*start:end] using the given - direction (direction == 1 means to do a forward search, direction == -1 a - backward search). The return value is the index of the first match; a value of - -1 indicates that no match was found, and -2 indicates that an error - occurred and an exception has been set. - - This function used an int type for start and end. This - might require changes in your code for properly supporting 64-bit - systems.""" - raise NotImplementedError - - at cpython_api([PyObject, PyObject, Py_ssize_t, Py_ssize_t], Py_ssize_t, error=-1) -def PyUnicode_Count(space, str, substr, start, end): - """Return the number of non-overlapping occurrences of substr in - str[start:end]. Return -1 if an error occurred. - - This function returned an int type and used an int - type for start and end. This might require changes in your code for - properly supporting 64-bit systems.""" - raise NotImplementedError - @cpython_api([PyObject, PyObject, rffi.INT_real], PyObject) def PyUnicode_RichCompare(space, left, right, op): """Rich compare two unicode strings and return one of the following: diff --git a/pypy/module/cpyext/test/test_unicodeobject.py b/pypy/module/cpyext/test/test_unicodeobject.py --- a/pypy/module/cpyext/test/test_unicodeobject.py +++ b/pypy/module/cpyext/test/test_unicodeobject.py @@ -457,3 +457,29 @@ assert api.PyUnicode_Tailmatch(w_str, space.wrap("cde"), 1, 5, -1) == 1 self.raises(space, api, TypeError, api.PyUnicode_Tailmatch, w_str, space.wrap(3), 2, 10, 1) + + def test_count(self, space, api): + w_str = space.wrap(u"abcabdab") + assert api.PyUnicode_Count(w_str, space.wrap(u"ab"), 0, -1) == 2 + assert api.PyUnicode_Count(w_str, space.wrap(u"ab"), 0, 2) == 1 + assert api.PyUnicode_Count(w_str, space.wrap(u"ab"), -5, 30) == 2 + + def test_find(self, space, api): + w_str = space.wrap(u"abcabcd") + assert api.PyUnicode_Find(w_str, space.wrap(u"c"), 0, 7, 1) == 2 + assert api.PyUnicode_Find(w_str, space.wrap(u"c"), 3, 7, 1) == 5 + assert api.PyUnicode_Find(w_str, space.wrap(u"c"), 0, 7, -1) == 5 + assert api.PyUnicode_Find(w_str, space.wrap(u"c"), 3, 7, -1) == 5 + assert api.PyUnicode_Find(w_str, space.wrap(u"c"), 0, 4, -1) == 2 + assert api.PyUnicode_Find(w_str, space.wrap(u"z"), 0, 4, -1) == -1 + + def test_split(self, space, api): + w_str = space.wrap(u"a\nb\nc\nd") + assert "[u'a', u'b', u'c', u'd']" == space.unwrap(space.repr( + api.PyUnicode_Split(w_str, space.wrap('\n'), -1))) + assert r"[u'a', u'b', u'c\nd']" == space.unwrap(space.repr( + api.PyUnicode_Split(w_str, space.wrap('\n'), 2))) + assert "[u'a', u'b', u'c', u'd']" == space.unwrap(space.repr( + api.PyUnicode_Splitlines(w_str, 0))) + assert r"[u'a\n', u'b\n', u'c\n', u'd']" == space.unwrap(space.repr( + api.PyUnicode_Splitlines(w_str, 1))) diff --git a/pypy/module/cpyext/unicodeobject.py b/pypy/module/cpyext/unicodeobject.py --- a/pypy/module/cpyext/unicodeobject.py +++ b/pypy/module/cpyext/unicodeobject.py @@ -598,3 +598,44 @@ else: return stringtype.stringendswith(str, substr, start, end) + at cpython_api([PyObject, PyObject, Py_ssize_t, Py_ssize_t], Py_ssize_t, error=-1) +def PyUnicode_Count(space, w_str, w_substr, start, end): + """Return the number of non-overlapping occurrences of substr in + str[start:end]. Return -1 if an error occurred.""" + w_count = space.call_method(w_str, "count", w_substr, + space.wrap(start), space.wrap(end)) + return space.int_w(w_count) + + at cpython_api([PyObject, PyObject, Py_ssize_t, Py_ssize_t, rffi.INT_real], + Py_ssize_t, error=-2) +def PyUnicode_Find(space, w_str, w_substr, start, end, direction): + """Return the first position of substr in str*[*start:end] using + the given direction (direction == 1 means to do a forward search, + direction == -1 a backward search). The return value is the index + of the first match; a value of -1 indicates that no match was + found, and -2 indicates that an error occurred and an exception + has been set.""" + if rffi.cast(lltype.Signed, direction) > 0: + w_pos = space.call_method(w_str, "find", w_substr, + space.wrap(start), space.wrap(end)) + else: + w_pos = space.call_method(w_str, "rfind", w_substr, + space.wrap(start), space.wrap(end)) + return space.int_w(w_pos) + + at cpython_api([PyObject, PyObject, Py_ssize_t], PyObject) +def PyUnicode_Split(space, w_str, w_sep, maxsplit): + """Split a string giving a list of Unicode strings. If sep is + NULL, splitting will be done at all whitespace substrings. + Otherwise, splits occur at the given separator. At most maxsplit + splits will be done. If negative, no limit is set. Separators + are not included in the resulting list.""" + return space.call_method(w_str, "split", w_sep, space.wrap(maxsplit)) + + at cpython_api([PyObject, rffi.INT_real], PyObject) +def PyUnicode_Splitlines(space, w_str, keepend): + """Split a Unicode string at line breaks, returning a list of + Unicode strings. CRLF is considered to be one line break. If + keepend is 0, the Line break characters are not included in the + resulting strings.""" + return space.call_method(w_str, "splitlines", space.wrap(keepend)) diff --git a/pypy/objspace/flow/flowcontext.py b/pypy/objspace/flow/flowcontext.py --- a/pypy/objspace/flow/flowcontext.py +++ b/pypy/objspace/flow/flowcontext.py @@ -434,6 +434,13 @@ self.lastblock = block self.pushvalue(w_result) + def BUILD_LIST_FROM_ARG(self, _, next_instr): + # This opcode was added with pypy-1.8. Here is a simpler + # version, enough for annotation. + last_val = self.popvalue() + self.pushvalue(self.space.newlist([])) + self.pushvalue(last_val) + # XXX Unimplemented 2.7 opcodes ---------------- # Set literals, set comprehensions From noreply at buildbot.pypy.org Wed Apr 11 20:42:36 2012 From: noreply at buildbot.pypy.org (amauryfa) Date: Wed, 11 Apr 2012 20:42:36 +0200 (CEST) Subject: [pypy-commit] pypy default: PyUnicode_Split(): support NULL for the separator. Message-ID: <20120411184236.D21C582F4E@wyvern.cs.uni-duesseldorf.de> Author: Amaury Forgeot d'Arc Branch: Changeset: r54289:ad03b1c52876 Date: 2012-04-11 20:41 +0200 http://bitbucket.org/pypy/pypy/changeset/ad03b1c52876/ Log: PyUnicode_Split(): support NULL for the separator. diff --git a/pypy/module/cpyext/test/test_unicodeobject.py b/pypy/module/cpyext/test/test_unicodeobject.py --- a/pypy/module/cpyext/test/test_unicodeobject.py +++ b/pypy/module/cpyext/test/test_unicodeobject.py @@ -479,6 +479,8 @@ api.PyUnicode_Split(w_str, space.wrap('\n'), -1))) assert r"[u'a', u'b', u'c\nd']" == space.unwrap(space.repr( api.PyUnicode_Split(w_str, space.wrap('\n'), 2))) + assert r"[u'a', u'b', u'c d']" == space.unwrap(space.repr( + api.PyUnicode_Split(space.wrap(u'a\nb c d'), None, 2))) assert "[u'a', u'b', u'c', u'd']" == space.unwrap(space.repr( api.PyUnicode_Splitlines(w_str, 0))) assert r"[u'a\n', u'b\n', u'c\n', u'd']" == space.unwrap(space.repr( diff --git a/pypy/module/cpyext/unicodeobject.py b/pypy/module/cpyext/unicodeobject.py --- a/pypy/module/cpyext/unicodeobject.py +++ b/pypy/module/cpyext/unicodeobject.py @@ -630,6 +630,8 @@ Otherwise, splits occur at the given separator. At most maxsplit splits will be done. If negative, no limit is set. Separators are not included in the resulting list.""" + if w_sep is None: + w_sep = space.w_None return space.call_method(w_str, "split", w_sep, space.wrap(maxsplit)) @cpython_api([PyObject, rffi.INT_real], PyObject) From noreply at buildbot.pypy.org Wed Apr 11 21:15:21 2012 From: noreply at buildbot.pypy.org (mattip) Date: Wed, 11 Apr 2012 21:15:21 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: help find the library, wherever it may be Message-ID: <20120411191521.9FF3182F4E@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54290:d00b67396a4e Date: 2012-04-11 20:18 +0300 http://bitbucket.org/pypy/pypy/changeset/d00b67396a4e/ Log: help find the library, wherever it may be diff --git a/lib_pypy/_ctypes_test.py b/lib_pypy/_ctypes_test.py --- a/lib_pypy/_ctypes_test.py +++ b/lib_pypy/_ctypes_test.py @@ -34,6 +34,13 @@ if sys.platform == 'win32': # XXX libpypy-c.lib is currently not installed automatically library = os.path.join(thisdir, '..', 'include', 'libpypy-c') + if not os.path.exists(library + '.lib'): + #For a nightly build + library = os.path.join(thisdir, '..', 'include', 'python27') + if not os.path.exists(library + '.lib'): + # For a local translation + library = os.path.join(thisdir, '..', 'pypy', 'translator', + 'goal', 'libpypy-c') libraries = [library, 'oleaut32'] extra_ldargs = ['/MANIFEST'] # needed for VC10 else: From noreply at buildbot.pypy.org Wed Apr 11 21:15:22 2012 From: noreply at buildbot.pypy.org (mattip) Date: Wed, 11 Apr 2012 21:15:22 +0200 (CEST) Subject: [pypy-commit] pypy win32-cleanup2: silence warnings Message-ID: <20120411191522.DE0CA82F4E@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-cleanup2 Changeset: r54291:8117f3454026 Date: 2012-04-11 21:06 +0300 http://bitbucket.org/pypy/pypy/changeset/8117f3454026/ Log: silence warnings diff --git a/lib_pypy/_ctypes_test.py b/lib_pypy/_ctypes_test.py --- a/lib_pypy/_ctypes_test.py +++ b/lib_pypy/_ctypes_test.py @@ -21,7 +21,7 @@ # Compile .c file include_dir = os.path.join(thisdir, '..', 'include') if sys.platform == 'win32': - ccflags = [] + ccflags = ['-D_CRT_SECURE_NO_WARNINGS'] else: ccflags = ['-fPIC'] res = compiler.compile([os.path.join(thisdir, '_ctypes_test.c')], diff --git a/lib_pypy/_testcapi.py b/lib_pypy/_testcapi.py --- a/lib_pypy/_testcapi.py +++ b/lib_pypy/_testcapi.py @@ -16,7 +16,7 @@ # Compile .c file include_dir = os.path.join(thisdir, '..', 'include') if sys.platform == 'win32': - ccflags = [] + ccflags = ['-D_CRT_SECURE_NO_WARNINGS'] else: ccflags = ['-fPIC', '-Wimplicit-function-declaration'] res = compiler.compile([os.path.join(thisdir, '_testcapimodule.c')], From noreply at buildbot.pypy.org Wed Apr 11 21:15:24 2012 From: noreply at buildbot.pypy.org (mattip) Date: Wed, 11 Apr 2012 21:15:24 +0200 (CEST) Subject: [pypy-commit] pypy win32-stdlib: restore files removed by mistake Message-ID: <20120411191524.651B582F4E@wyvern.cs.uni-duesseldorf.de> Author: Matti Picus Branch: win32-stdlib Changeset: r54292:d1833eeffd0d Date: 2012-04-11 22:07 +0300 http://bitbucket.org/pypy/pypy/changeset/d1833eeffd0d/ Log: restore files removed by mistake diff --git a/lib-python/2.7/mailbox.py b/lib-python/2.7/mailbox.py new file mode 100644 --- /dev/null +++ b/lib-python/2.7/mailbox.py @@ -0,0 +1,2171 @@ +#! /usr/bin/env python + +"""Read/write support for Maildir, mbox, MH, Babyl, and MMDF mailboxes.""" + +# Notes for authors of new mailbox subclasses: +# +# Remember to fsync() changes to disk before closing a modified file +# or returning from a flush() method. See functions _sync_flush() and +# _sync_close(). + +import sys +import os +import time +import calendar +import socket +import errno +import copy +import email +import email.message +import email.generator +import StringIO +try: + if sys.platform == 'os2emx': + # OS/2 EMX fcntl() not adequate + raise ImportError + import fcntl +except ImportError: + fcntl = None + +import warnings +with warnings.catch_warnings(): + if sys.py3kwarning: + warnings.filterwarnings("ignore", ".*rfc822 has been removed", + DeprecationWarning) + import rfc822 + +__all__ = [ 'Mailbox', 'Maildir', 'mbox', 'MH', 'Babyl', 'MMDF', + 'Message', 'MaildirMessage', 'mboxMessage', 'MHMessage', + 'BabylMessage', 'MMDFMessage', 'UnixMailbox', + 'PortableUnixMailbox', 'MmdfMailbox', 'MHMailbox', 'BabylMailbox' ] + +class Mailbox: + """A group of messages in a particular place.""" + + def __init__(self, path, factory=None, create=True): + """Initialize a Mailbox instance.""" + self._path = os.path.abspath(os.path.expanduser(path)) + self._factory = factory + + def add(self, message): + """Add message and return assigned key.""" + raise NotImplementedError('Method must be implemented by subclass') + + def remove(self, key): + """Remove the keyed message; raise KeyError if it doesn't exist.""" + raise NotImplementedError('Method must be implemented by subclass') + + def __delitem__(self, key): + self.remove(key) + + def discard(self, key): + """If the keyed message exists, remove it.""" + try: + self.remove(key) + except KeyError: + pass + + def __setitem__(self, key, message): + """Replace the keyed message; raise KeyError if it doesn't exist.""" + raise NotImplementedError('Method must be implemented by subclass') + + def get(self, key, default=None): + """Return the keyed message, or default if it doesn't exist.""" + try: + return self.__getitem__(key) + except KeyError: + return default + + def __getitem__(self, key): + """Return the keyed message; raise KeyError if it doesn't exist.""" + if not self._factory: + return self.get_message(key) + else: + return self._factory(self.get_file(key)) + + def get_message(self, key): + """Return a Message representation or raise a KeyError.""" + raise NotImplementedError('Method must be implemented by subclass') + + def get_string(self, key): + """Return a string representation or raise a KeyError.""" + raise NotImplementedError('Method must be implemented by subclass') + + def get_file(self, key): + """Return a file-like representation or raise a KeyError.""" + raise NotImplementedError('Method must be implemented by subclass') + + def iterkeys(self): + """Return an iterator over keys.""" + raise NotImplementedError('Method must be implemented by subclass') + + def keys(self): + """Return a list of keys.""" + return list(self.iterkeys()) + + def itervalues(self): + """Return an iterator over all messages.""" + for key in self.iterkeys(): + try: + value = self[key] + except KeyError: + continue + yield value + + def __iter__(self): + return self.itervalues() + + def values(self): + """Return a list of messages. Memory intensive.""" + return list(self.itervalues()) + + def iteritems(self): + """Return an iterator over (key, message) tuples.""" + for key in self.iterkeys(): + try: + value = self[key] + except KeyError: + continue + yield (key, value) + + def items(self): + """Return a list of (key, message) tuples. Memory intensive.""" + return list(self.iteritems()) + + def has_key(self, key): + """Return True if the keyed message exists, False otherwise.""" + raise NotImplementedError('Method must be implemented by subclass') + + def __contains__(self, key): + return self.has_key(key) + + def __len__(self): + """Return a count of messages in the mailbox.""" + raise NotImplementedError('Method must be implemented by subclass') + + def clear(self): + """Delete all messages.""" + for key in self.iterkeys(): + self.discard(key) + + def pop(self, key, default=None): + """Delete the keyed message and return it, or default.""" + try: + result = self[key] + except KeyError: + return default + self.discard(key) + return result + + def popitem(self): + """Delete an arbitrary (key, message) pair and return it.""" + for key in self.iterkeys(): + return (key, self.pop(key)) # This is only run once. + else: + raise KeyError('No messages in mailbox') + + def update(self, arg=None): + """Change the messages that correspond to certain keys.""" + if hasattr(arg, 'iteritems'): + source = arg.iteritems() + elif hasattr(arg, 'items'): + source = arg.items() + else: + source = arg + bad_key = False + for key, message in source: + try: + self[key] = message + except KeyError: + bad_key = True + if bad_key: + raise KeyError('No message with key(s)') + + def flush(self): + """Write any pending changes to the disk.""" + raise NotImplementedError('Method must be implemented by subclass') + + def lock(self): + """Lock the mailbox.""" + raise NotImplementedError('Method must be implemented by subclass') + + def unlock(self): + """Unlock the mailbox if it is locked.""" + raise NotImplementedError('Method must be implemented by subclass') + + def close(self): + """Flush and close the mailbox.""" + raise NotImplementedError('Method must be implemented by subclass') + + def _dump_message(self, message, target, mangle_from_=False): + # Most files are opened in binary mode to allow predictable seeking. + # To get native line endings on disk, the user-friendly \n line endings + # used in strings and by email.Message are translated here. + """Dump message contents to target file.""" + if isinstance(message, email.message.Message): + buffer = StringIO.StringIO() + gen = email.generator.Generator(buffer, mangle_from_, 0) + gen.flatten(message) + buffer.seek(0) + target.write(buffer.read().replace('\n', os.linesep)) + elif isinstance(message, str): + if mangle_from_: + message = message.replace('\nFrom ', '\n>From ') + message = message.replace('\n', os.linesep) + target.write(message) + elif hasattr(message, 'read'): + while True: + line = message.readline() + if line == '': + break + if mangle_from_ and line.startswith('From '): + line = '>From ' + line[5:] + line = line.replace('\n', os.linesep) + target.write(line) + else: + raise TypeError('Invalid message type: %s' % type(message)) + + +class Maildir(Mailbox): + """A qmail-style Maildir mailbox.""" + + colon = ':' + + def __init__(self, dirname, factory=rfc822.Message, create=True): + """Initialize a Maildir instance.""" + Mailbox.__init__(self, dirname, factory, create) + self._paths = { + 'tmp': os.path.join(self._path, 'tmp'), + 'new': os.path.join(self._path, 'new'), + 'cur': os.path.join(self._path, 'cur'), + } + if not os.path.exists(self._path): + if create: + os.mkdir(self._path, 0700) + for path in self._paths.values(): + os.mkdir(path, 0o700) + else: + raise NoSuchMailboxError(self._path) + self._toc = {} + self._toc_mtimes = {} + for subdir in ('cur', 'new'): + self._toc_mtimes[subdir] = os.path.getmtime(self._paths[subdir]) + self._last_read = time.time() # Records last time we read cur/new + self._skewfactor = 0.1 # Adjust if os/fs clocks are skewing + + def add(self, message): + """Add message and return assigned key.""" + tmp_file = self._create_tmp() + try: + self._dump_message(message, tmp_file) + except BaseException: + tmp_file.close() + os.remove(tmp_file.name) + raise + _sync_close(tmp_file) + if isinstance(message, MaildirMessage): + subdir = message.get_subdir() + suffix = self.colon + message.get_info() + if suffix == self.colon: + suffix = '' + else: + subdir = 'new' + suffix = '' + uniq = os.path.basename(tmp_file.name).split(self.colon)[0] + dest = os.path.join(self._path, subdir, uniq + suffix) + try: + if hasattr(os, 'link'): + os.link(tmp_file.name, dest) + os.remove(tmp_file.name) + else: + os.rename(tmp_file.name, dest) + except OSError, e: + os.remove(tmp_file.name) + if e.errno == errno.EEXIST: + raise ExternalClashError('Name clash with existing message: %s' + % dest) + else: + raise + if isinstance(message, MaildirMessage): + os.utime(dest, (os.path.getatime(dest), message.get_date())) + return uniq + + def remove(self, key): + """Remove the keyed message; raise KeyError if it doesn't exist.""" + os.remove(os.path.join(self._path, self._lookup(key))) + + def discard(self, key): + """If the keyed message exists, remove it.""" + # This overrides an inapplicable implementation in the superclass. + try: + self.remove(key) + except KeyError: + pass + except OSError, e: + if e.errno != errno.ENOENT: + raise + + def __setitem__(self, key, message): + """Replace the keyed message; raise KeyError if it doesn't exist.""" + old_subpath = self._lookup(key) + temp_key = self.add(message) + temp_subpath = self._lookup(temp_key) + if isinstance(message, MaildirMessage): + # temp's subdir and suffix were specified by message. + dominant_subpath = temp_subpath + else: + # temp's subdir and suffix were defaults from add(). + dominant_subpath = old_subpath + subdir = os.path.dirname(dominant_subpath) + if self.colon in dominant_subpath: + suffix = self.colon + dominant_subpath.split(self.colon)[-1] + else: + suffix = '' + self.discard(key) + new_path = os.path.join(self._path, subdir, key + suffix) + os.rename(os.path.join(self._path, temp_subpath), new_path) + if isinstance(message, MaildirMessage): + os.utime(new_path, (os.path.getatime(new_path), + message.get_date())) + + def get_message(self, key): + """Return a Message representation or raise a KeyError.""" + subpath = self._lookup(key) + f = open(os.path.join(self._path, subpath), 'r') + try: + if self._factory: + msg = self._factory(f) + else: + msg = MaildirMessage(f) + finally: + f.close() + subdir, name = os.path.split(subpath) + msg.set_subdir(subdir) + if self.colon in name: + msg.set_info(name.split(self.colon)[-1]) + msg.set_date(os.path.getmtime(os.path.join(self._path, subpath))) + return msg + + def get_string(self, key): + """Return a string representation or raise a KeyError.""" + f = open(os.path.join(self._path, self._lookup(key)), 'r') + try: + return f.read() + finally: + f.close() + + def get_file(self, key): + """Return a file-like representation or raise a KeyError.""" + f = open(os.path.join(self._path, self._lookup(key)), 'rb') + return _ProxyFile(f) + + def iterkeys(self): + """Return an iterator over keys.""" + self._refresh() + for key in self._toc: + try: + self._lookup(key) + except KeyError: + continue + yield key + + def has_key(self, key): + """Return True if the keyed message exists, False otherwise.""" + self._refresh() + return key in self._toc + + def __len__(self): + """Return a count of messages in the mailbox.""" + self._refresh() + return len(self._toc) + + def flush(self): + """Write any pending changes to disk.""" + # Maildir changes are always written immediately, so there's nothing + # to do. + pass + + def lock(self): + """Lock the mailbox.""" + return + + def unlock(self): + """Unlock the mailbox if it is locked.""" + return + + def close(self): + """Flush and close the mailbox.""" + return + + def list_folders(self): + """Return a list of folder names.""" + result = [] + for entry in os.listdir(self._path): + if len(entry) > 1 and entry[0] == '.' and \ + os.path.isdir(os.path.join(self._path, entry)): + result.append(entry[1:]) + return result + + def get_folder(self, folder): + """Return a Maildir instance for the named folder.""" + return Maildir(os.path.join(self._path, '.' + folder), + factory=self._factory, + create=False) + + def add_folder(self, folder): + """Create a folder and return a Maildir instance representing it.""" + path = os.path.join(self._path, '.' + folder) + result = Maildir(path, factory=self._factory) + maildirfolder_path = os.path.join(path, 'maildirfolder') + if not os.path.exists(maildirfolder_path): + os.close(os.open(maildirfolder_path, os.O_CREAT | os.O_WRONLY, + 0666)) + return result + + def remove_folder(self, folder): + """Delete the named folder, which must be empty.""" + path = os.path.join(self._path, '.' + folder) + for entry in os.listdir(os.path.join(path, 'new')) + \ + os.listdir(os.path.join(path, 'cur')): + if len(entry) < 1 or entry[0] != '.': + raise NotEmptyError('Folder contains message(s): %s' % folder) + for entry in os.listdir(path): + if entry != 'new' and entry != 'cur' and entry != 'tmp' and \ + os.path.isdir(os.path.join(path, entry)): + raise NotEmptyError("Folder contains subdirectory '%s': %s" % + (folder, entry)) + for root, dirs, files in os.walk(path, topdown=False): + for entry in files: + os.remove(os.path.join(root, entry)) + for entry in dirs: + os.rmdir(os.path.join(root, entry)) + os.rmdir(path) + + def clean(self): + """Delete old files in "tmp".""" + now = time.time() + for entry in os.listdir(os.path.join(self._path, 'tmp')): + path = os.path.join(self._path, 'tmp', entry) + if now - os.path.getatime(path) > 129600: # 60 * 60 * 36 + os.remove(path) + + _count = 1 # This is used to generate unique file names. + + def _create_tmp(self): + """Create a file in the tmp subdirectory and open and return it.""" + now = time.time() + hostname = socket.gethostname() + if '/' in hostname: + hostname = hostname.replace('/', r'\057') + if ':' in hostname: + hostname = hostname.replace(':', r'\072') + uniq = "%s.M%sP%sQ%s.%s" % (int(now), int(now % 1 * 1e6), os.getpid(), + Maildir._count, hostname) + path = os.path.join(self._path, 'tmp', uniq) + try: + os.stat(path) + except OSError, e: + if e.errno == errno.ENOENT: + Maildir._count += 1 + try: + return _create_carefully(path) + except OSError, e: + if e.errno != errno.EEXIST: + raise + else: + raise + + # Fall through to here if stat succeeded or open raised EEXIST. + raise ExternalClashError('Name clash prevented file creation: %s' % + path) + + def _refresh(self): + """Update table of contents mapping.""" + # If it has been less than two seconds since the last _refresh() call, + # we have to unconditionally re-read the mailbox just in case it has + # been modified, because os.path.mtime() has a 2 sec resolution in the + # most common worst case (FAT) and a 1 sec resolution typically. This + # results in a few unnecessary re-reads when _refresh() is called + # multiple times in that interval, but once the clock ticks over, we + # will only re-read as needed. Because the filesystem might be being + # served by an independent system with its own clock, we record and + # compare with the mtimes from the filesystem. Because the other + # system's clock might be skewing relative to our clock, we add an + # extra delta to our wait. The default is one tenth second, but is an + # instance variable and so can be adjusted if dealing with a + # particularly skewed or irregular system. + if time.time() - self._last_read > 2 + self._skewfactor: + refresh = False + for subdir in self._toc_mtimes: + mtime = os.path.getmtime(self._paths[subdir]) + if mtime > self._toc_mtimes[subdir]: + refresh = True + self._toc_mtimes[subdir] = mtime + if not refresh: + return + # Refresh toc + self._toc = {} + for subdir in self._toc_mtimes: + path = self._paths[subdir] + for entry in os.listdir(path): + p = os.path.join(path, entry) + if os.path.isdir(p): + continue + uniq = entry.split(self.colon)[0] + self._toc[uniq] = os.path.join(subdir, entry) + self._last_read = time.time() + + def _lookup(self, key): + """Use TOC to return subpath for given key, or raise a KeyError.""" + try: + if os.path.exists(os.path.join(self._path, self._toc[key])): + return self._toc[key] + except KeyError: + pass + self._refresh() + try: + return self._toc[key] + except KeyError: + raise KeyError('No message with key: %s' % key) + + # This method is for backward compatibility only. + def next(self): + """Return the next message in a one-time iteration.""" + if not hasattr(self, '_onetime_keys'): + self._onetime_keys = self.iterkeys() + while True: + try: + return self[self._onetime_keys.next()] + except StopIteration: + return None + except KeyError: + continue + + +class _singlefileMailbox(Mailbox): + """A single-file mailbox.""" + + def __init__(self, path, factory=None, create=True): + """Initialize a single-file mailbox.""" + Mailbox.__init__(self, path, factory, create) + try: + f = open(self._path, 'rb+') + except IOError, e: + if e.errno == errno.ENOENT: + if create: + f = open(self._path, 'wb+') + else: + raise NoSuchMailboxError(self._path) + elif e.errno in (errno.EACCES, errno.EROFS): + f = open(self._path, 'rb') + else: + raise + self._file = f + self._toc = None + self._next_key = 0 + self._pending = False # No changes require rewriting the file. + self._locked = False + self._file_length = None # Used to record mailbox size + + def add(self, message): + """Add message and return assigned key.""" + self._lookup() + self._toc[self._next_key] = self._append_message(message) + self._next_key += 1 + self._pending = True + return self._next_key - 1 + + def remove(self, key): + """Remove the keyed message; raise KeyError if it doesn't exist.""" + self._lookup(key) + del self._toc[key] + self._pending = True + + def __setitem__(self, key, message): + """Replace the keyed message; raise KeyError if it doesn't exist.""" + self._lookup(key) + self._toc[key] = self._append_message(message) + self._pending = True + + def iterkeys(self): + """Return an iterator over keys.""" + self._lookup() + for key in self._toc.keys(): + yield key + + def has_key(self, key): + """Return True if the keyed message exists, False otherwise.""" + self._lookup() + return key in self._toc + + def __len__(self): + """Return a count of messages in the mailbox.""" + self._lookup() + return len(self._toc) + + def lock(self): + """Lock the mailbox.""" + if not self._locked: + _lock_file(self._file) + self._locked = True + + def unlock(self): + """Unlock the mailbox if it is locked.""" + if self._locked: + _unlock_file(self._file) + self._locked = False + + def flush(self): + """Write any pending changes to disk.""" + if not self._pending: + return + + # In order to be writing anything out at all, self._toc must + # already have been generated (and presumably has been modified + # by adding or deleting an item). + assert self._toc is not None + + # Check length of self._file; if it's changed, some other process + # has modified the mailbox since we scanned it. + self._file.seek(0, 2) + cur_len = self._file.tell() + if cur_len != self._file_length: + raise ExternalClashError('Size of mailbox file changed ' + '(expected %i, found %i)' % + (self._file_length, cur_len)) + + new_file = _create_temporary(self._path) + try: + new_toc = {} + self._pre_mailbox_hook(new_file) + for key in sorted(self._toc.keys()): + start, stop = self._toc[key] + self._file.seek(start) + self._pre_message_hook(new_file) + new_start = new_file.tell() + while True: + buffer = self._file.read(min(4096, + stop - self._file.tell())) + if buffer == '': + break + new_file.write(buffer) + new_toc[key] = (new_start, new_file.tell()) + self._post_message_hook(new_file) + except: + new_file.close() + os.remove(new_file.name) + raise + _sync_close(new_file) + # self._file is about to get replaced, so no need to sync. + self._file.close() + try: + os.rename(new_file.name, self._path) + except OSError, e: + if e.errno == errno.EEXIST or \ + (os.name == 'os2' and e.errno == errno.EACCES): + os.remove(self._path) + os.rename(new_file.name, self._path) + else: + raise + self._file = open(self._path, 'rb+') + self._toc = new_toc + self._pending = False + if self._locked: + _lock_file(self._file, dotlock=False) + + def _pre_mailbox_hook(self, f): + """Called before writing the mailbox to file f.""" + return + + def _pre_message_hook(self, f): + """Called before writing each message to file f.""" + return + + def _post_message_hook(self, f): + """Called after writing each message to file f.""" + return + + def close(self): + """Flush and close the mailbox.""" + self.flush() + if self._locked: + self.unlock() + self._file.close() # Sync has been done by self.flush() above. + + def _lookup(self, key=None): + """Return (start, stop) or raise KeyError.""" + if self._toc is None: + self._generate_toc() + if key is not None: + try: + return self._toc[key] + except KeyError: + raise KeyError('No message with key: %s' % key) + + def _append_message(self, message): + """Append message to mailbox and return (start, stop) offsets.""" + self._file.seek(0, 2) + before = self._file.tell() + try: + self._pre_message_hook(self._file) + offsets = self._install_message(message) + self._post_message_hook(self._file) + except BaseException: + self._file.truncate(before) + raise + self._file.flush() + self._file_length = self._file.tell() # Record current length of mailbox + return offsets + + + +class _mboxMMDF(_singlefileMailbox): + """An mbox or MMDF mailbox.""" + + _mangle_from_ = True + + def get_message(self, key): + """Return a Message representation or raise a KeyError.""" + start, stop = self._lookup(key) + self._file.seek(start) + from_line = self._file.readline().replace(os.linesep, '') + string = self._file.read(stop - self._file.tell()) + msg = self._message_factory(string.replace(os.linesep, '\n')) + msg.set_from(from_line[5:]) + return msg + + def get_string(self, key, from_=False): + """Return a string representation or raise a KeyError.""" + start, stop = self._lookup(key) + self._file.seek(start) + if not from_: + self._file.readline() + string = self._file.read(stop - self._file.tell()) + return string.replace(os.linesep, '\n') + + def get_file(self, key, from_=False): + """Return a file-like representation or raise a KeyError.""" + start, stop = self._lookup(key) + self._file.seek(start) + if not from_: + self._file.readline() + return _PartialFile(self._file, self._file.tell(), stop) + + def _install_message(self, message): + """Format a message and blindly write to self._file.""" + from_line = None + if isinstance(message, str) and message.startswith('From '): + newline = message.find('\n') + if newline != -1: + from_line = message[:newline] + message = message[newline + 1:] + else: + from_line = message + message = '' + elif isinstance(message, _mboxMMDFMessage): + from_line = 'From ' + message.get_from() + elif isinstance(message, email.message.Message): + from_line = message.get_unixfrom() # May be None. + if from_line is None: + from_line = 'From MAILER-DAEMON %s' % time.asctime(time.gmtime()) + start = self._file.tell() + self._file.write(from_line + os.linesep) + self._dump_message(message, self._file, self._mangle_from_) + stop = self._file.tell() + return (start, stop) + + +class mbox(_mboxMMDF): + """A classic mbox mailbox.""" + + _mangle_from_ = True + + def __init__(self, path, factory=None, create=True): + """Initialize an mbox mailbox.""" + self._message_factory = mboxMessage + _mboxMMDF.__init__(self, path, factory, create) + + def _pre_message_hook(self, f): + """Called before writing each message to file f.""" + if f.tell() != 0: + f.write(os.linesep) + + def _generate_toc(self): + """Generate key-to-(start, stop) table of contents.""" + starts, stops = [], [] + self._file.seek(0) + while True: + line_pos = self._file.tell() + line = self._file.readline() + if line.startswith('From '): + if len(stops) < len(starts): + stops.append(line_pos - len(os.linesep)) + starts.append(line_pos) + elif line == '': + stops.append(line_pos) + break + self._toc = dict(enumerate(zip(starts, stops))) + self._next_key = len(self._toc) + self._file_length = self._file.tell() + + +class MMDF(_mboxMMDF): + """An MMDF mailbox.""" + + def __init__(self, path, factory=None, create=True): + """Initialize an MMDF mailbox.""" + self._message_factory = MMDFMessage + _mboxMMDF.__init__(self, path, factory, create) + + def _pre_message_hook(self, f): + """Called before writing each message to file f.""" + f.write('\001\001\001\001' + os.linesep) + + def _post_message_hook(self, f): + """Called after writing each message to file f.""" + f.write(os.linesep + '\001\001\001\001' + os.linesep) + + def _generate_toc(self): + """Generate key-to-(start, stop) table of contents.""" + starts, stops = [], [] + self._file.seek(0) + next_pos = 0 + while True: + line_pos = next_pos + line = self._file.readline() + next_pos = self._file.tell() + if line.startswith('\001\001\001\001' + os.linesep): + starts.append(next_pos) + while True: + line_pos = next_pos + line = self._file.readline() + next_pos = self._file.tell() + if line == '\001\001\001\001' + os.linesep: + stops.append(line_pos - len(os.linesep)) + break + elif line == '': + stops.append(line_pos) + break + elif line == '': + break + self._toc = dict(enumerate(zip(starts, stops))) + self._next_key = len(self._toc) + self._file.seek(0, 2) + self._file_length = self._file.tell() + + +class MH(Mailbox): + """An MH mailbox.""" + + def __init__(self, path, factory=None, create=True): + """Initialize an MH instance.""" + Mailbox.__init__(self, path, factory, create) + if not os.path.exists(self._path): + if create: + os.mkdir(self._path, 0700) + os.close(os.open(os.path.join(self._path, '.mh_sequences'), + os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0600)) + else: + raise NoSuchMailboxError(self._path) + self._locked = False + + def add(self, message): + """Add message and return assigned key.""" + keys = self.keys() + if len(keys) == 0: + new_key = 1 + else: + new_key = max(keys) + 1 + new_path = os.path.join(self._path, str(new_key)) + f = _create_carefully(new_path) + closed = False + try: + if self._locked: + _lock_file(f) + try: + try: + self._dump_message(message, f) + except BaseException: + # Unlock and close so it can be deleted on Windows + if self._locked: + _unlock_file(f) + _sync_close(f) + closed = True + os.remove(new_path) + raise + if isinstance(message, MHMessage): + self._dump_sequences(message, new_key) + finally: + if self._locked: + _unlock_file(f) + finally: + if not closed: + _sync_close(f) + return new_key + + def remove(self, key): + """Remove the keyed message; raise KeyError if it doesn't exist.""" + path = os.path.join(self._path, str(key)) + try: + f = open(path, 'rb+') + except IOError, e: + if e.errno == errno.ENOENT: + raise KeyError('No message with key: %s' % key) + else: + raise + else: + f.close() + os.remove(path) + + def __setitem__(self, key, message): + """Replace the keyed message; raise KeyError if it doesn't exist.""" + path = os.path.join(self._path, str(key)) + try: + f = open(path, 'rb+') + except IOError, e: + if e.errno == errno.ENOENT: + raise KeyError('No message with key: %s' % key) + else: + raise + try: + if self._locked: + _lock_file(f) + try: + os.close(os.open(path, os.O_WRONLY | os.O_TRUNC)) + self._dump_message(message, f) + if isinstance(message, MHMessage): + self._dump_sequences(message, key) + finally: + if self._locked: + _unlock_file(f) + finally: + _sync_close(f) + + def get_message(self, key): + """Return a Message representation or raise a KeyError.""" + try: + if self._locked: + f = open(os.path.join(self._path, str(key)), 'r+') + else: + f = open(os.path.join(self._path, str(key)), 'r') + except IOError, e: + if e.errno == errno.ENOENT: + raise KeyError('No message with key: %s' % key) + else: + raise + try: + if self._locked: + _lock_file(f) + try: + msg = MHMessage(f) + finally: + if self._locked: + _unlock_file(f) + finally: + f.close() + for name, key_list in self.get_sequences().iteritems(): + if key in key_list: + msg.add_sequence(name) + return msg + + def get_string(self, key): + """Return a string representation or raise a KeyError.""" + try: + if self._locked: + f = open(os.path.join(self._path, str(key)), 'r+') + else: + f = open(os.path.join(self._path, str(key)), 'r') + except IOError, e: + if e.errno == errno.ENOENT: + raise KeyError('No message with key: %s' % key) + else: + raise + try: + if self._locked: + _lock_file(f) + try: + return f.read() + finally: + if self._locked: + _unlock_file(f) + finally: + f.close() + + def get_file(self, key): + """Return a file-like representation or raise a KeyError.""" + try: + f = open(os.path.join(self._path, str(key)), 'rb') + except IOError, e: + if e.errno == errno.ENOENT: + raise KeyError('No message with key: %s' % key) + else: + raise + return _ProxyFile(f) + + def iterkeys(self): + """Return an iterator over keys.""" + return iter(sorted(int(entry) for entry in os.listdir(self._path) + if entry.isdigit())) + + def has_key(self, key): + """Return True if the keyed message exists, False otherwise.""" + return os.path.exists(os.path.join(self._path, str(key))) + + def __len__(self): + """Return a count of messages in the mailbox.""" + return len(list(self.iterkeys())) + + def lock(self): + """Lock the mailbox.""" + if not self._locked: + self._file = open(os.path.join(self._path, '.mh_sequences'), 'rb+') + _lock_file(self._file) + self._locked = True + + def unlock(self): + """Unlock the mailbox if it is locked.""" + if self._locked: + _unlock_file(self._file) + _sync_close(self._file) + del self._file + self._locked = False + + def flush(self): + """Write any pending changes to the disk.""" + return + + def close(self): + """Flush and close the mailbox.""" + if self._locked: + self.unlock() + + def list_folders(self): + """Return a list of folder names.""" + result = [] + for entry in os.listdir(self._path): + if os.path.isdir(os.path.join(self._path, entry)): + result.append(entry) + return result + + def get_folder(self, folder): + """Return an MH instance for the named folder.""" + return MH(os.path.join(self._path, folder), + factory=self._factory, create=False) + + def add_folder(self, folder): + """Create a folder and return an MH instance representing it.""" + return MH(os.path.join(self._path, folder), + factory=self._factory) + + def remove_folder(self, folder): + """Delete the named folder, which must be empty.""" + path = os.path.join(self._path, folder) + entries = os.listdir(path) + if entries == ['.mh_sequences']: + os.remove(os.path.join(path, '.mh_sequences')) + elif entries == []: + pass + else: + raise NotEmptyError('Folder not empty: %s' % self._path) + os.rmdir(path) + + def get_sequences(self): + """Return a name-to-key-list dictionary to define each sequence.""" + results = {} + f = open(os.path.join(self._path, '.mh_sequences'), 'r') + try: + all_keys = set(self.keys()) + for line in f: + try: + name, contents = line.split(':') + keys = set() + for spec in contents.split(): + if spec.isdigit(): + keys.add(int(spec)) + else: + start, stop = (int(x) for x in spec.split('-')) + keys.update(range(start, stop + 1)) + results[name] = [key for key in sorted(keys) \ + if key in all_keys] + if len(results[name]) == 0: + del results[name] + except ValueError: + raise FormatError('Invalid sequence specification: %s' % + line.rstrip()) + finally: + f.close() + return results + + def set_sequences(self, sequences): + """Set sequences using the given name-to-key-list dictionary.""" + f = open(os.path.join(self._path, '.mh_sequences'), 'r+') + try: + os.close(os.open(f.name, os.O_WRONLY | os.O_TRUNC)) + for name, keys in sequences.iteritems(): + if len(keys) == 0: + continue + f.write('%s:' % name) + prev = None + completing = False + for key in sorted(set(keys)): + if key - 1 == prev: + if not completing: + completing = True + f.write('-') + elif completing: + completing = False + f.write('%s %s' % (prev, key)) + else: + f.write(' %s' % key) + prev = key + if completing: + f.write(str(prev) + '\n') + else: + f.write('\n') + finally: + _sync_close(f) + + def pack(self): + """Re-name messages to eliminate numbering gaps. Invalidates keys.""" + sequences = self.get_sequences() + prev = 0 + changes = [] + for key in self.iterkeys(): + if key - 1 != prev: + changes.append((key, prev + 1)) + if hasattr(os, 'link'): + os.link(os.path.join(self._path, str(key)), + os.path.join(self._path, str(prev + 1))) + os.unlink(os.path.join(self._path, str(key))) + else: + os.rename(os.path.join(self._path, str(key)), + os.path.join(self._path, str(prev + 1))) + prev += 1 + self._next_key = prev + 1 + if len(changes) == 0: + return + for name, key_list in sequences.items(): + for old, new in changes: + if old in key_list: + key_list[key_list.index(old)] = new + self.set_sequences(sequences) + + def _dump_sequences(self, message, key): + """Inspect a new MHMessage and update sequences appropriately.""" + pending_sequences = message.get_sequences() + all_sequences = self.get_sequences() + for name, key_list in all_sequences.iteritems(): + if name in pending_sequences: + key_list.append(key) + elif key in key_list: + del key_list[key_list.index(key)] + for sequence in pending_sequences: + if sequence not in all_sequences: + all_sequences[sequence] = [key] + self.set_sequences(all_sequences) + + +class Babyl(_singlefileMailbox): + """An Rmail-style Babyl mailbox.""" + + _special_labels = frozenset(('unseen', 'deleted', 'filed', 'answered', + 'forwarded', 'edited', 'resent')) + + def __init__(self, path, factory=None, create=True): + """Initialize a Babyl mailbox.""" + _singlefileMailbox.__init__(self, path, factory, create) + self._labels = {} + + def add(self, message): + """Add message and return assigned key.""" + key = _singlefileMailbox.add(self, message) + if isinstance(message, BabylMessage): + self._labels[key] = message.get_labels() + return key + + def remove(self, key): + """Remove the keyed message; raise KeyError if it doesn't exist.""" + _singlefileMailbox.remove(self, key) + if key in self._labels: + del self._labels[key] + + def __setitem__(self, key, message): + """Replace the keyed message; raise KeyError if it doesn't exist.""" + _singlefileMailbox.__setitem__(self, key, message) + if isinstance(message, BabylMessage): + self._labels[key] = message.get_labels() + + def get_message(self, key): + """Return a Message representation or raise a KeyError.""" + start, stop = self._lookup(key) + self._file.seek(start) + self._file.readline() # Skip '1,' line specifying labels. + original_headers = StringIO.StringIO() + while True: + line = self._file.readline() + if line == '*** EOOH ***' + os.linesep or line == '': + break + original_headers.write(line.replace(os.linesep, '\n')) + visible_headers = StringIO.StringIO() + while True: + line = self._file.readline() + if line == os.linesep or line == '': + break + visible_headers.write(line.replace(os.linesep, '\n')) + body = self._file.read(stop - self._file.tell()).replace(os.linesep, + '\n') + msg = BabylMessage(original_headers.getvalue() + body) + msg.set_visible(visible_headers.getvalue()) + if key in self._labels: + msg.set_labels(self._labels[key]) + return msg + + def get_string(self, key): + """Return a string representation or raise a KeyError.""" + start, stop = self._lookup(key) + self._file.seek(start) + self._file.readline() # Skip '1,' line specifying labels. + original_headers = StringIO.StringIO() + while True: + line = self._file.readline() + if line == '*** EOOH ***' + os.linesep or line == '': + break + original_headers.write(line.replace(os.linesep, '\n')) + while True: + line = self._file.readline() + if line == os.linesep or line == '': + break + return original_headers.getvalue() + \ + self._file.read(stop - self._file.tell()).replace(os.linesep, + '\n') + + def get_file(self, key): + """Return a file-like representation or raise a KeyError.""" + return StringIO.StringIO(self.get_string(key).replace('\n', + os.linesep)) + + def get_labels(self): + """Return a list of user-defined labels in the mailbox.""" + self._lookup() + labels = set() + for label_list in self._labels.values(): + labels.update(label_list) + labels.difference_update(self._special_labels) + return list(labels) + + def _generate_toc(self): + """Generate key-to-(start, stop) table of contents.""" + starts, stops = [], [] + self._file.seek(0) + next_pos = 0 + label_lists = [] + while True: + line_pos = next_pos + line = self._file.readline() + next_pos = self._file.tell() + if line == '\037\014' + os.linesep: + if len(stops) < len(starts): + stops.append(line_pos - len(os.linesep)) + starts.append(next_pos) + labels = [label.strip() for label + in self._file.readline()[1:].split(',') + if label.strip() != ''] + label_lists.append(labels) + elif line == '\037' or line == '\037' + os.linesep: + if len(stops) < len(starts): + stops.append(line_pos - len(os.linesep)) + elif line == '': + stops.append(line_pos - len(os.linesep)) + break + self._toc = dict(enumerate(zip(starts, stops))) + self._labels = dict(enumerate(label_lists)) + self._next_key = len(self._toc) + self._file.seek(0, 2) + self._file_length = self._file.tell() + + def _pre_mailbox_hook(self, f): + """Called before writing the mailbox to file f.""" + f.write('BABYL OPTIONS:%sVersion: 5%sLabels:%s%s\037' % + (os.linesep, os.linesep, ','.join(self.get_labels()), + os.linesep)) + + def _pre_message_hook(self, f): + """Called before writing each message to file f.""" + f.write('\014' + os.linesep) + + def _post_message_hook(self, f): + """Called after writing each message to file f.""" + f.write(os.linesep + '\037') + + def _install_message(self, message): + """Write message contents and return (start, stop).""" + start = self._file.tell() + if isinstance(message, BabylMessage): + special_labels = [] + labels = [] + for label in message.get_labels(): + if label in self._special_labels: + special_labels.append(label) + else: + labels.append(label) + self._file.write('1') + for label in special_labels: + self._file.write(', ' + label) + self._file.write(',,') + for label in labels: + self._file.write(' ' + label + ',') + self._file.write(os.linesep) + else: + self._file.write('1,,' + os.linesep) + if isinstance(message, email.message.Message): + orig_buffer = StringIO.StringIO() + orig_generator = email.generator.Generator(orig_buffer, False, 0) + orig_generator.flatten(message) + orig_buffer.seek(0) + while True: + line = orig_buffer.readline() + self._file.write(line.replace('\n', os.linesep)) + if line == '\n' or line == '': + break + self._file.write('*** EOOH ***' + os.linesep) + if isinstance(message, BabylMessage): + vis_buffer = StringIO.StringIO() + vis_generator = email.generator.Generator(vis_buffer, False, 0) + vis_generator.flatten(message.get_visible()) + while True: + line = vis_buffer.readline() + self._file.write(line.replace('\n', os.linesep)) + if line == '\n' or line == '': + break + else: + orig_buffer.seek(0) + while True: + line = orig_buffer.readline() + self._file.write(line.replace('\n', os.linesep)) + if line == '\n' or line == '': + break + while True: + buffer = orig_buffer.read(4096) # Buffer size is arbitrary. + if buffer == '': + break + self._file.write(buffer.replace('\n', os.linesep)) + elif isinstance(message, str): + body_start = message.find('\n\n') + 2 + if body_start - 2 != -1: + self._file.write(message[:body_start].replace('\n', + os.linesep)) + self._file.write('*** EOOH ***' + os.linesep) + self._file.write(message[:body_start].replace('\n', + os.linesep)) + self._file.write(message[body_start:].replace('\n', + os.linesep)) + else: + self._file.write('*** EOOH ***' + os.linesep + os.linesep) + self._file.write(message.replace('\n', os.linesep)) + elif hasattr(message, 'readline'): + original_pos = message.tell() + first_pass = True + while True: + line = message.readline() + self._file.write(line.replace('\n', os.linesep)) + if line == '\n' or line == '': + self._file.write('*** EOOH ***' + os.linesep) + if first_pass: + first_pass = False + message.seek(original_pos) + else: + break + while True: + buffer = message.read(4096) # Buffer size is arbitrary. + if buffer == '': + break + self._file.write(buffer.replace('\n', os.linesep)) + else: + raise TypeError('Invalid message type: %s' % type(message)) + stop = self._file.tell() + return (start, stop) + + +class Message(email.message.Message): + """Message with mailbox-format-specific properties.""" + + def __init__(self, message=None): + """Initialize a Message instance.""" + if isinstance(message, email.message.Message): + self._become_message(copy.deepcopy(message)) + if isinstance(message, Message): + message._explain_to(self) + elif isinstance(message, str): + self._become_message(email.message_from_string(message)) + elif hasattr(message, "read"): + self._become_message(email.message_from_file(message)) + elif message is None: + email.message.Message.__init__(self) + else: + raise TypeError('Invalid message type: %s' % type(message)) + + def _become_message(self, message): + """Assume the non-format-specific state of message.""" + for name in ('_headers', '_unixfrom', '_payload', '_charset', + 'preamble', 'epilogue', 'defects', '_default_type'): + self.__dict__[name] = message.__dict__[name] + + def _explain_to(self, message): + """Copy format-specific state to message insofar as possible.""" + if isinstance(message, Message): + return # There's nothing format-specific to explain. + else: + raise TypeError('Cannot convert to specified type') + + +class MaildirMessage(Message): + """Message with Maildir-specific properties.""" + + def __init__(self, message=None): + """Initialize a MaildirMessage instance.""" + self._subdir = 'new' + self._info = '' + self._date = time.time() + Message.__init__(self, message) + + def get_subdir(self): + """Return 'new' or 'cur'.""" + return self._subdir + + def set_subdir(self, subdir): + """Set subdir to 'new' or 'cur'.""" + if subdir == 'new' or subdir == 'cur': + self._subdir = subdir + else: + raise ValueError("subdir must be 'new' or 'cur': %s" % subdir) + + def get_flags(self): + """Return as a string the flags that are set.""" + if self._info.startswith('2,'): + return self._info[2:] + else: + return '' + + def set_flags(self, flags): + """Set the given flags and unset all others.""" + self._info = '2,' + ''.join(sorted(flags)) + + def add_flag(self, flag): + """Set the given flag(s) without changing others.""" + self.set_flags(''.join(set(self.get_flags()) | set(flag))) + + def remove_flag(self, flag): + """Unset the given string flag(s) without changing others.""" + if self.get_flags() != '': + self.set_flags(''.join(set(self.get_flags()) - set(flag))) + + def get_date(self): + """Return delivery date of message, in seconds since the epoch.""" + return self._date + + def set_date(self, date): + """Set delivery date of message, in seconds since the epoch.""" + try: + self._date = float(date) + except ValueError: + raise TypeError("can't convert to float: %s" % date) + + def get_info(self): + """Get the message's "info" as a string.""" + return self._info + + def set_info(self, info): + """Set the message's "info" string.""" + if isinstance(info, str): + self._info = info + else: + raise TypeError('info must be a string: %s' % type(info)) + + def _explain_to(self, message): + """Copy Maildir-specific state to message insofar as possible.""" + if isinstance(message, MaildirMessage): + message.set_flags(self.get_flags()) + message.set_subdir(self.get_subdir()) + message.set_date(self.get_date()) + elif isinstance(message, _mboxMMDFMessage): + flags = set(self.get_flags()) + if 'S' in flags: + message.add_flag('R') + if self.get_subdir() == 'cur': + message.add_flag('O') + if 'T' in flags: + message.add_flag('D') + if 'F' in flags: + message.add_flag('F') + if 'R' in flags: + message.add_flag('A') + message.set_from('MAILER-DAEMON', time.gmtime(self.get_date())) + elif isinstance(message, MHMessage): + flags = set(self.get_flags()) + if 'S' not in flags: + message.add_sequence('unseen') + if 'R' in flags: + message.add_sequence('replied') + if 'F' in flags: + message.add_sequence('flagged') + elif isinstance(message, BabylMessage): + flags = set(self.get_flags()) + if 'S' not in flags: + message.add_label('unseen') + if 'T' in flags: + message.add_label('deleted') + if 'R' in flags: + message.add_label('answered') + if 'P' in flags: + message.add_label('forwarded') + elif isinstance(message, Message): + pass + else: + raise TypeError('Cannot convert to specified type: %s' % + type(message)) + + +class _mboxMMDFMessage(Message): + """Message with mbox- or MMDF-specific properties.""" + + def __init__(self, message=None): + """Initialize an mboxMMDFMessage instance.""" + self.set_from('MAILER-DAEMON', True) + if isinstance(message, email.message.Message): + unixfrom = message.get_unixfrom() + if unixfrom is not None and unixfrom.startswith('From '): + self.set_from(unixfrom[5:]) + Message.__init__(self, message) + + def get_from(self): + """Return contents of "From " line.""" + return self._from + + def set_from(self, from_, time_=None): + """Set "From " line, formatting and appending time_ if specified.""" + if time_ is not None: + if time_ is True: + time_ = time.gmtime() + from_ += ' ' + time.asctime(time_) + self._from = from_ + + def get_flags(self): + """Return as a string the flags that are set.""" + return self.get('Status', '') + self.get('X-Status', '') + + def set_flags(self, flags): + """Set the given flags and unset all others.""" + flags = set(flags) + status_flags, xstatus_flags = '', '' + for flag in ('R', 'O'): + if flag in flags: + status_flags += flag + flags.remove(flag) + for flag in ('D', 'F', 'A'): + if flag in flags: + xstatus_flags += flag + flags.remove(flag) + xstatus_flags += ''.join(sorted(flags)) + try: + self.replace_header('Status', status_flags) + except KeyError: + self.add_header('Status', status_flags) + try: + self.replace_header('X-Status', xstatus_flags) + except KeyError: + self.add_header('X-Status', xstatus_flags) + + def add_flag(self, flag): + """Set the given flag(s) without changing others.""" + self.set_flags(''.join(set(self.get_flags()) | set(flag))) + + def remove_flag(self, flag): + """Unset the given string flag(s) without changing others.""" + if 'Status' in self or 'X-Status' in self: + self.set_flags(''.join(set(self.get_flags()) - set(flag))) + + def _explain_to(self, message): + """Copy mbox- or MMDF-specific state to message insofar as possible.""" + if isinstance(message, MaildirMessage): + flags = set(self.get_flags()) + if 'O' in flags: + message.set_subdir('cur') + if 'F' in flags: + message.add_flag('F') + if 'A' in flags: + message.add_flag('R') + if 'R' in flags: + message.add_flag('S') + if 'D' in flags: + message.add_flag('T') + del message['status'] + del message['x-status'] + maybe_date = ' '.join(self.get_from().split()[-5:]) + try: + message.set_date(calendar.timegm(time.strptime(maybe_date, + '%a %b %d %H:%M:%S %Y'))) + except (ValueError, OverflowError): + pass + elif isinstance(message, _mboxMMDFMessage): + message.set_flags(self.get_flags()) + message.set_from(self.get_from()) + elif isinstance(message, MHMessage): + flags = set(self.get_flags()) + if 'R' not in flags: + message.add_sequence('unseen') + if 'A' in flags: + message.add_sequence('replied') + if 'F' in flags: + message.add_sequence('flagged') + del message['status'] + del message['x-status'] + elif isinstance(message, BabylMessage): + flags = set(self.get_flags()) + if 'R' not in flags: + message.add_label('unseen') + if 'D' in flags: + message.add_label('deleted') + if 'A' in flags: + message.add_label('answered') + del message['status'] + del message['x-status'] + elif isinstance(message, Message): + pass + else: + raise TypeError('Cannot convert to specified type: %s' % + type(message)) + + +class mboxMessage(_mboxMMDFMessage): + """Message with mbox-specific properties.""" + + +class MHMessage(Message): + """Message with MH-specific properties.""" + + def __init__(self, message=None): + """Initialize an MHMessage instance.""" + self._sequences = [] + Message.__init__(self, message) + + def get_sequences(self): + """Return a list of sequences that include the message.""" + return self._sequences[:] + + def set_sequences(self, sequences): + """Set the list of sequences that include the message.""" + self._sequences = list(sequences) + + def add_sequence(self, sequence): + """Add sequence to list of sequences including the message.""" + if isinstance(sequence, str): + if not sequence in self._sequences: + self._sequences.append(sequence) + else: + raise TypeError('sequence must be a string: %s' % type(sequence)) + + def remove_sequence(self, sequence): + """Remove sequence from the list of sequences including the message.""" + try: + self._sequences.remove(sequence) + except ValueError: + pass + + def _explain_to(self, message): + """Copy MH-specific state to message insofar as possible.""" + if isinstance(message, MaildirMessage): + sequences = set(self.get_sequences()) + if 'unseen' in sequences: + message.set_subdir('cur') + else: + message.set_subdir('cur') + message.add_flag('S') + if 'flagged' in sequences: + message.add_flag('F') + if 'replied' in sequences: + message.add_flag('R') + elif isinstance(message, _mboxMMDFMessage): + sequences = set(self.get_sequences()) + if 'unseen' not in sequences: + message.add_flag('RO') + else: + message.add_flag('O') + if 'flagged' in sequences: + message.add_flag('F') + if 'replied' in sequences: + message.add_flag('A') + elif isinstance(message, MHMessage): + for sequence in self.get_sequences(): + message.add_sequence(sequence) + elif isinstance(message, BabylMessage): + sequences = set(self.get_sequences()) + if 'unseen' in sequences: + message.add_label('unseen') + if 'replied' in sequences: + message.add_label('answered') + elif isinstance(message, Message): + pass + else: + raise TypeError('Cannot convert to specified type: %s' % + type(message)) + + +class BabylMessage(Message): + """Message with Babyl-specific properties.""" + + def __init__(self, message=None): + """Initialize an BabylMessage instance.""" + self._labels = [] + self._visible = Message() + Message.__init__(self, message) + + def get_labels(self): + """Return a list of labels on the message.""" + return self._labels[:] + + def set_labels(self, labels): + """Set the list of labels on the message.""" + self._labels = list(labels) + + def add_label(self, label): + """Add label to list of labels on the message.""" + if isinstance(label, str): + if label not in self._labels: + self._labels.append(label) + else: + raise TypeError('label must be a string: %s' % type(label)) + + def remove_label(self, label): + """Remove label from the list of labels on the message.""" + try: + self._labels.remove(label) + except ValueError: + pass + + def get_visible(self): + """Return a Message representation of visible headers.""" + return Message(self._visible) + + def set_visible(self, visible): + """Set the Message representation of visible headers.""" + self._visible = Message(visible) + + def update_visible(self): + """Update and/or sensibly generate a set of visible headers.""" + for header in self._visible.keys(): + if header in self: + self._visible.replace_header(header, self[header]) + else: + del self._visible[header] + for header in ('Date', 'From', 'Reply-To', 'To', 'CC', 'Subject'): + if header in self and header not in self._visible: + self._visible[header] = self[header] + + def _explain_to(self, message): + """Copy Babyl-specific state to message insofar as possible.""" + if isinstance(message, MaildirMessage): + labels = set(self.get_labels()) + if 'unseen' in labels: + message.set_subdir('cur') + else: + message.set_subdir('cur') + message.add_flag('S') + if 'forwarded' in labels or 'resent' in labels: + message.add_flag('P') + if 'answered' in labels: + message.add_flag('R') + if 'deleted' in labels: + message.add_flag('T') + elif isinstance(message, _mboxMMDFMessage): + labels = set(self.get_labels()) + if 'unseen' not in labels: + message.add_flag('RO') + else: + message.add_flag('O') + if 'deleted' in labels: + message.add_flag('D') + if 'answered' in labels: + message.add_flag('A') + elif isinstance(message, MHMessage): + labels = set(self.get_labels()) + if 'unseen' in labels: + message.add_sequence('unseen') + if 'answered' in labels: + message.add_sequence('replied') + elif isinstance(message, BabylMessage): + message.set_visible(self.get_visible()) + for label in self.get_labels(): + message.add_label(label) + elif isinstance(message, Message): + pass + else: + raise TypeError('Cannot convert to specified type: %s' % + type(message)) + + +class MMDFMessage(_mboxMMDFMessage): + """Message with MMDF-specific properties.""" + + +class _ProxyFile: + """A read-only wrapper of a file.""" + + def __init__(self, f, pos=None): + """Initialize a _ProxyFile.""" + self._file = f + if pos is None: + self._pos = f.tell() + else: + self._pos = pos + + def read(self, size=None): + """Read bytes.""" + return self._read(size, self._file.read) + + def readline(self, size=None): + """Read a line.""" + return self._read(size, self._file.readline) + + def readlines(self, sizehint=None): + """Read multiple lines.""" + result = [] + for line in self: + result.append(line) + if sizehint is not None: + sizehint -= len(line) + if sizehint <= 0: + break + return result + + def __iter__(self): + """Iterate over lines.""" + return iter(self.readline, "") + + def tell(self): + """Return the position.""" + return self._pos + + def seek(self, offset, whence=0): + """Change position.""" + if whence == 1: + self._file.seek(self._pos) + self._file.seek(offset, whence) + self._pos = self._file.tell() + + def close(self): + """Close the file.""" + del self._file + + def _read(self, size, read_method): + """Read size bytes using read_method.""" + if size is None: + size = -1 + self._file.seek(self._pos) + result = read_method(size) + self._pos = self._file.tell() + return result + + +class _PartialFile(_ProxyFile): + """A read-only wrapper of part of a file.""" + + def __init__(self, f, start=None, stop=None): + """Initialize a _PartialFile.""" + _ProxyFile.__init__(self, f, start) + self._start = start + self._stop = stop + + def tell(self): + """Return the position with respect to start.""" + return _ProxyFile.tell(self) - self._start + + def seek(self, offset, whence=0): + """Change position, possibly with respect to start or stop.""" + if whence == 0: + self._pos = self._start + whence = 1 + elif whence == 2: + self._pos = self._stop + whence = 1 + _ProxyFile.seek(self, offset, whence) + + def _read(self, size, read_method): + """Read size bytes using read_method, honoring start and stop.""" + remaining = self._stop - self._pos + if remaining <= 0: + return '' + if size is None or size < 0 or size > remaining: + size = remaining + return _ProxyFile._read(self, size, read_method) + + +def _lock_file(f, dotlock=True): + """Lock file f using lockf and dot locking.""" + dotlock_done = False + try: + if fcntl: + try: + fcntl.lockf(f, fcntl.LOCK_EX | fcntl.LOCK_NB) + except IOError, e: + if e.errno in (errno.EAGAIN, errno.EACCES, errno.EROFS): + raise ExternalClashError('lockf: lock unavailable: %s' % + f.name) + else: + raise + if dotlock: + try: + pre_lock = _create_temporary(f.name + '.lock') + pre_lock.close() + except IOError, e: + if e.errno in (errno.EACCES, errno.EROFS): + return # Without write access, just skip dotlocking. + else: + raise + try: + if hasattr(os, 'link'): + os.link(pre_lock.name, f.name + '.lock') + dotlock_done = True + os.unlink(pre_lock.name) + else: + os.rename(pre_lock.name, f.name + '.lock') + dotlock_done = True + except OSError, e: + if e.errno == errno.EEXIST or \ + (os.name == 'os2' and e.errno == errno.EACCES): + os.remove(pre_lock.name) + raise ExternalClashError('dot lock unavailable: %s' % + f.name) + else: + raise + except: + if fcntl: + fcntl.lockf(f, fcntl.LOCK_UN) + if dotlock_done: + os.remove(f.name + '.lock') + raise + +def _unlock_file(f): + """Unlock file f using lockf and dot locking.""" + if fcntl: + fcntl.lockf(f, fcntl.LOCK_UN) + if os.path.exists(f.name + '.lock'): + os.remove(f.name + '.lock') + +def _create_carefully(path): + """Create a file if it doesn't exist and open for reading and writing.""" + fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, 0666) + try: + return open(path, 'rb+') + finally: + os.close(fd) + +def _create_temporary(path): + """Create a temp file based on path and open for reading and writing.""" + return _create_carefully('%s.%s.%s.%s' % (path, int(time.time()), + socket.gethostname(), + os.getpid())) + +def _sync_flush(f): + """Ensure changes to file f are physically on disk.""" + f.flush() + if hasattr(os, 'fsync'): + os.fsync(f.fileno()) + +def _sync_close(f): + """Close file f, ensuring all changes are physically on disk.""" + _sync_flush(f) + f.close() + +## Start: classes from the original module (for backward compatibility). + +# Note that the Maildir class, whose name is unchanged, itself offers a next() +# method for backward compatibility. + +class _Mailbox: + + def __init__(self, fp, factory=rfc822.Message): + self.fp = fp + self.seekp = 0 + self.factory = factory + + def __iter__(self): + return iter(self.next, None) + + def next(self): + while 1: + self.fp.seek(self.seekp) + try: + self._search_start() + except EOFError: + self.seekp = self.fp.tell() + return None + start = self.fp.tell() + self._search_end() + self.seekp = stop = self.fp.tell() + if start != stop: + break + return self.factory(_PartialFile(self.fp, start, stop)) + +# Recommended to use PortableUnixMailbox instead! +class UnixMailbox(_Mailbox): + + def _search_start(self): + while 1: + pos = self.fp.tell() + line = self.fp.readline() + if not line: + raise EOFError + if line[:5] == 'From ' and self._isrealfromline(line): + self.fp.seek(pos) + return + + def _search_end(self): + self.fp.readline() # Throw away header line + while 1: + pos = self.fp.tell() + line = self.fp.readline() + if not line: + return + if line[:5] == 'From ' and self._isrealfromline(line): + self.fp.seek(pos) + return + + # An overridable mechanism to test for From-line-ness. You can either + # specify a different regular expression or define a whole new + # _isrealfromline() method. Note that this only gets called for lines + # starting with the 5 characters "From ". + # + # BAW: According to + #http://home.netscape.com/eng/mozilla/2.0/relnotes/demo/content-length.html + # the only portable, reliable way to find message delimiters in a BSD (i.e + # Unix mailbox) style folder is to search for "\n\nFrom .*\n", or at the + # beginning of the file, "^From .*\n". While _fromlinepattern below seems + # like a good idea, in practice, there are too many variations for more + # strict parsing of the line to be completely accurate. + # + # _strict_isrealfromline() is the old version which tries to do stricter + # parsing of the From_ line. _portable_isrealfromline() simply returns + # true, since it's never called if the line doesn't already start with + # "From ". + # + # This algorithm, and the way it interacts with _search_start() and + # _search_end() may not be completely correct, because it doesn't check + # that the two characters preceding "From " are \n\n or the beginning of + # the file. Fixing this would require a more extensive rewrite than is + # necessary. For convenience, we've added a PortableUnixMailbox class + # which does no checking of the format of the 'From' line. + + _fromlinepattern = (r"From \s*[^\s]+\s+\w\w\w\s+\w\w\w\s+\d?\d\s+" + r"\d?\d:\d\d(:\d\d)?(\s+[^\s]+)?\s+\d\d\d\d\s*" + r"[^\s]*\s*" + "$") + _regexp = None + + def _strict_isrealfromline(self, line): + if not self._regexp: + import re + self._regexp = re.compile(self._fromlinepattern) + return self._regexp.match(line) + + def _portable_isrealfromline(self, line): + return True + + _isrealfromline = _strict_isrealfromline + + +class PortableUnixMailbox(UnixMailbox): + _isrealfromline = UnixMailbox._portable_isrealfromline + + +class MmdfMailbox(_Mailbox): + + def _search_start(self): + while 1: + line = self.fp.readline() + if not line: + raise EOFError + if line[:5] == '\001\001\001\001\n': + return + + def _search_end(self): + while 1: + pos = self.fp.tell() + line = self.fp.readline() + if not line: + return + if line == '\001\001\001\001\n': + self.fp.seek(pos) + return + + +class MHMailbox: + + def __init__(self, dirname, factory=rfc822.Message): + import re + pat = re.compile('^[1-9][0-9]*$') + self.dirname = dirname + # the three following lines could be combined into: + # list = map(long, filter(pat.match, os.listdir(self.dirname))) + list = os.listdir(self.dirname) + list = filter(pat.match, list) + list = map(long, list) + list.sort() + # This only works in Python 1.6 or later; + # before that str() added 'L': + self.boxes = map(str, list) + self.boxes.reverse() + self.factory = factory + + def __iter__(self): + return iter(self.next, None) + + def next(self): + if not self.boxes: + return None + fn = self.boxes.pop() + fp = open(os.path.join(self.dirname, fn)) + msg = self.factory(fp) + try: + msg._mh_msgno = fn + except (AttributeError, TypeError): + pass + return msg + + +class BabylMailbox(_Mailbox): + + def _search_start(self): + while 1: + line = self.fp.readline() + if not line: + raise EOFError + if line == '*** EOOH ***\n': + return + + def _search_end(self): + while 1: + pos = self.fp.tell() + line = self.fp.readline() + if not line: + return + if line == '\037\014\n' or line == '\037': + self.fp.seek(pos) + return + +## End: classes from the original module (for backward compatibility). + + +class Error(Exception): + """Raised for module-specific errors.""" + +class NoSuchMailboxError(Error): + """The specified mailbox does not exist and won't be created.""" + +class NotEmptyError(Error): + """The specified mailbox is not empty and deletion was requested.""" + +class ExternalClashError(Error): + """Another process caused an action to fail.""" + +class FormatError(Error): + """A file appears to have an invalid format.""" diff --git a/lib-python/2.7/test/test_mailbox.py b/lib-python/2.7/test/test_mailbox.py new file mode 100644 --- /dev/null +++ b/lib-python/2.7/test/test_mailbox.py @@ -0,0 +1,1970 @@ +import os +import sys +import time +import stat +import socket +import email +import email.message +import re +import StringIO +from test import test_support +import unittest +import mailbox +import glob +try: + import fcntl +except ImportError: + pass + +# Silence Py3k warning +rfc822 = test_support.import_module('rfc822', deprecated=True) + +class TestBase(unittest.TestCase): + + def _check_sample(self, msg): + # Inspect a mailbox.Message representation of the sample message + self.assertIsInstance(msg, email.message.Message) + self.assertIsInstance(msg, mailbox.Message) + for key, value in _sample_headers.iteritems(): + self.assertIn(value, msg.get_all(key)) + self.assertTrue(msg.is_multipart()) + self.assertEqual(len(msg.get_payload()), len(_sample_payloads)) + for i, payload in enumerate(_sample_payloads): + part = msg.get_payload(i) + self.assertIsInstance(part, email.message.Message) + self.assertNotIsInstance(part, mailbox.Message) + self.assertEqual(part.get_payload(), payload) + + def _delete_recursively(self, target): + # Delete a file or delete a directory recursively + if os.path.isdir(target): + for path, dirs, files in os.walk(target, topdown=False): + for name in files: + os.remove(os.path.join(path, name)) + for name in dirs: + os.rmdir(os.path.join(path, name)) + os.rmdir(target) + elif os.path.exists(target): + os.remove(target) + + +class TestMailbox(TestBase): + + _factory = None # Overridden by subclasses to reuse tests + _template = 'From: foo\n\n%s' + + def setUp(self): + self._path = test_support.TESTFN + self._delete_recursively(self._path) + self._box = self._factory(self._path) + + def tearDown(self): + self._box.close() + self._delete_recursively(self._path) + + def test_add(self): + # Add copies of a sample message + keys = [] + keys.append(self._box.add(self._template % 0)) + self.assertEqual(len(self._box), 1) + keys.append(self._box.add(mailbox.Message(_sample_message))) + self.assertEqual(len(self._box), 2) + keys.append(self._box.add(email.message_from_string(_sample_message))) + self.assertEqual(len(self._box), 3) + keys.append(self._box.add(StringIO.StringIO(_sample_message))) + self.assertEqual(len(self._box), 4) + keys.append(self._box.add(_sample_message)) + self.assertEqual(len(self._box), 5) + self.assertEqual(self._box.get_string(keys[0]), self._template % 0) + for i in (1, 2, 3, 4): + self._check_sample(self._box[keys[i]]) + + def test_remove(self): + # Remove messages using remove() + self._test_remove_or_delitem(self._box.remove) + + def test_delitem(self): + # Remove messages using __delitem__() + self._test_remove_or_delitem(self._box.__delitem__) + + def _test_remove_or_delitem(self, method): + # (Used by test_remove() and test_delitem().) + key0 = self._box.add(self._template % 0) + key1 = self._box.add(self._template % 1) + self.assertEqual(len(self._box), 2) + method(key0) + l = len(self._box) + self.assertEqual(l, 1) + self.assertRaises(KeyError, lambda: self._box[key0]) + self.assertRaises(KeyError, lambda: method(key0)) + self.assertEqual(self._box.get_string(key1), self._template % 1) + key2 = self._box.add(self._template % 2) + self.assertEqual(len(self._box), 2) + method(key2) + l = len(self._box) + self.assertEqual(l, 1) + self.assertRaises(KeyError, lambda: self._box[key2]) + self.assertRaises(KeyError, lambda: method(key2)) + self.assertEqual(self._box.get_string(key1), self._template % 1) + method(key1) + self.assertEqual(len(self._box), 0) + self.assertRaises(KeyError, lambda: self._box[key1]) + self.assertRaises(KeyError, lambda: method(key1)) + + def test_discard(self, repetitions=10): + # Discard messages + key0 = self._box.add(self._template % 0) + key1 = self._box.add(self._template % 1) + self.assertEqual(len(self._box), 2) + self._box.discard(key0) + self.assertEqual(len(self._box), 1) + self.assertRaises(KeyError, lambda: self._box[key0]) + self._box.discard(key0) + self.assertEqual(len(self._box), 1) + self.assertRaises(KeyError, lambda: self._box[key0]) + + def test_get(self): + # Retrieve messages using get() + key0 = self._box.add(self._template % 0) + msg = self._box.get(key0) + self.assertEqual(msg['from'], 'foo') + self.assertEqual(msg.get_payload(), '0') + self.assertIs(self._box.get('foo'), None) + self.assertFalse(self._box.get('foo', False)) + self._box.close() + self._box = self._factory(self._path, factory=rfc822.Message) + key1 = self._box.add(self._template % 1) + msg = self._box.get(key1) + self.assertEqual(msg['from'], 'foo') + self.assertEqual(msg.fp.read(), '1') + + def test_getitem(self): + # Retrieve message using __getitem__() + key0 = self._box.add(self._template % 0) + msg = self._box[key0] + self.assertEqual(msg['from'], 'foo') + self.assertEqual(msg.get_payload(), '0') + self.assertRaises(KeyError, lambda: self._box['foo']) + self._box.discard(key0) + self.assertRaises(KeyError, lambda: self._box[key0]) + + def test_get_message(self): + # Get Message representations of messages + key0 = self._box.add(self._template % 0) + key1 = self._box.add(_sample_message) + msg0 = self._box.get_message(key0) + self.assertIsInstance(msg0, mailbox.Message) + self.assertEqual(msg0['from'], 'foo') + self.assertEqual(msg0.get_payload(), '0') + self._check_sample(self._box.get_message(key1)) + + def test_get_string(self): + # Get string representations of messages + key0 = self._box.add(self._template % 0) + key1 = self._box.add(_sample_message) + self.assertEqual(self._box.get_string(key0), self._template % 0) + self.assertEqual(self._box.get_string(key1), _sample_message) + + def test_get_file(self): + # Get file representations of messages + key0 = self._box.add(self._template % 0) + key1 = self._box.add(_sample_message) + self.assertEqual(self._box.get_file(key0).read().replace(os.linesep, '\n'), + self._template % 0) + self.assertEqual(self._box.get_file(key1).read().replace(os.linesep, '\n'), + _sample_message) + + def test_iterkeys(self): + # Get keys using iterkeys() + self._check_iteration(self._box.iterkeys, do_keys=True, do_values=False) + + def test_keys(self): + # Get keys using keys() + self._check_iteration(self._box.keys, do_keys=True, do_values=False) + + def test_itervalues(self): + # Get values using itervalues() + self._check_iteration(self._box.itervalues, do_keys=False, + do_values=True) + + def test_iter(self): + # Get values using __iter__() + self._check_iteration(self._box.__iter__, do_keys=False, + do_values=True) + + def test_values(self): + # Get values using values() + self._check_iteration(self._box.values, do_keys=False, do_values=True) + + def test_iteritems(self): + # Get keys and values using iteritems() + self._check_iteration(self._box.iteritems, do_keys=True, + do_values=True) + + def test_items(self): + # Get keys and values using items() + self._check_iteration(self._box.items, do_keys=True, do_values=True) + + def _check_iteration(self, method, do_keys, do_values, repetitions=10): + for value in method(): + self.fail("Not empty") + keys, values = [], [] + for i in xrange(repetitions): + keys.append(self._box.add(self._template % i)) + values.append(self._template % i) + if do_keys and not do_values: + returned_keys = list(method()) + elif do_values and not do_keys: + returned_values = list(method()) + else: + returned_keys, returned_values = [], [] + for key, value in method(): + returned_keys.append(key) + returned_values.append(value) + if do_keys: + self.assertEqual(len(keys), len(returned_keys)) + self.assertEqual(set(keys), set(returned_keys)) + if do_values: + count = 0 + for value in returned_values: + self.assertEqual(value['from'], 'foo') + self.assertTrue(int(value.get_payload()) < repetitions, + (value.get_payload(), repetitions)) + count += 1 + self.assertEqual(len(values), count) + + def test_has_key(self): + # Check existence of keys using has_key() + self._test_has_key_or_contains(self._box.has_key) + + def test_contains(self): + # Check existence of keys using __contains__() + self._test_has_key_or_contains(self._box.__contains__) + + def _test_has_key_or_contains(self, method): + # (Used by test_has_key() and test_contains().) + self.assertFalse(method('foo')) + key0 = self._box.add(self._template % 0) + self.assertTrue(method(key0)) + self.assertFalse(method('foo')) + key1 = self._box.add(self._template % 1) + self.assertTrue(method(key1)) + self.assertTrue(method(key0)) + self.assertFalse(method('foo')) + self._box.remove(key0) + self.assertFalse(method(key0)) + self.assertTrue(method(key1)) + self.assertFalse(method('foo')) + self._box.remove(key1) + self.assertFalse(method(key1)) + self.assertFalse(method(key0)) + self.assertFalse(method('foo')) + + def test_len(self, repetitions=10): + # Get message count + keys = [] + for i in xrange(repetitions): + self.assertEqual(len(self._box), i) + keys.append(self._box.add(self._template % i)) + self.assertEqual(len(self._box), i + 1) + for i in xrange(repetitions): + self.assertEqual(len(self._box), repetitions - i) + self._box.remove(keys[i]) + self.assertEqual(len(self._box), repetitions - i - 1) + + def test_set_item(self): + # Modify messages using __setitem__() + key0 = self._box.add(self._template % 'original 0') + self.assertEqual(self._box.get_string(key0), + self._template % 'original 0') + key1 = self._box.add(self._template % 'original 1') + self.assertEqual(self._box.get_string(key1), + self._template % 'original 1') + self._box[key0] = self._template % 'changed 0' + self.assertEqual(self._box.get_string(key0), + self._template % 'changed 0') + self._box[key1] = self._template % 'changed 1' + self.assertEqual(self._box.get_string(key1), + self._template % 'changed 1') + self._box[key0] = _sample_message + self._check_sample(self._box[key0]) + self._box[key1] = self._box[key0] + self._check_sample(self._box[key1]) + self._box[key0] = self._template % 'original 0' + self.assertEqual(self._box.get_string(key0), + self._template % 'original 0') + self._check_sample(self._box[key1]) + self.assertRaises(KeyError, + lambda: self._box.__setitem__('foo', 'bar')) + self.assertRaises(KeyError, lambda: self._box['foo']) + self.assertEqual(len(self._box), 2) + + def test_clear(self, iterations=10): + # Remove all messages using clear() + keys = [] + for i in xrange(iterations): + self._box.add(self._template % i) + for i, key in enumerate(keys): + self.assertEqual(self._box.get_string(key), self._template % i) + self._box.clear() + self.assertEqual(len(self._box), 0) + for i, key in enumerate(keys): + self.assertRaises(KeyError, lambda: self._box.get_string(key)) + + def test_pop(self): + # Get and remove a message using pop() + key0 = self._box.add(self._template % 0) + self.assertIn(key0, self._box) + key1 = self._box.add(self._template % 1) + self.assertIn(key1, self._box) + self.assertEqual(self._box.pop(key0).get_payload(), '0') + self.assertNotIn(key0, self._box) + self.assertIn(key1, self._box) + key2 = self._box.add(self._template % 2) + self.assertIn(key2, self._box) + self.assertEqual(self._box.pop(key2).get_payload(), '2') + self.assertNotIn(key2, self._box) + self.assertIn(key1, self._box) + self.assertEqual(self._box.pop(key1).get_payload(), '1') + self.assertNotIn(key1, self._box) + self.assertEqual(len(self._box), 0) + + def test_popitem(self, iterations=10): + # Get and remove an arbitrary (key, message) using popitem() + keys = [] + for i in xrange(10): + keys.append(self._box.add(self._template % i)) + seen = [] + for i in xrange(10): + key, msg = self._box.popitem() + self.assertIn(key, keys) + self.assertNotIn(key, seen) + seen.append(key) + self.assertEqual(int(msg.get_payload()), keys.index(key)) + self.assertEqual(len(self._box), 0) + for key in keys: + self.assertRaises(KeyError, lambda: self._box[key]) + + def test_update(self): + # Modify multiple messages using update() + key0 = self._box.add(self._template % 'original 0') + key1 = self._box.add(self._template % 'original 1') + key2 = self._box.add(self._template % 'original 2') + self._box.update({key0: self._template % 'changed 0', + key2: _sample_message}) + self.assertEqual(len(self._box), 3) + self.assertEqual(self._box.get_string(key0), + self._template % 'changed 0') + self.assertEqual(self._box.get_string(key1), + self._template % 'original 1') + self._check_sample(self._box[key2]) + self._box.update([(key2, self._template % 'changed 2'), + (key1, self._template % 'changed 1'), + (key0, self._template % 'original 0')]) + self.assertEqual(len(self._box), 3) + self.assertEqual(self._box.get_string(key0), + self._template % 'original 0') + self.assertEqual(self._box.get_string(key1), + self._template % 'changed 1') + self.assertEqual(self._box.get_string(key2), + self._template % 'changed 2') + self.assertRaises(KeyError, + lambda: self._box.update({'foo': 'bar', + key0: self._template % "changed 0"})) + self.assertEqual(len(self._box), 3) + self.assertEqual(self._box.get_string(key0), + self._template % "changed 0") + self.assertEqual(self._box.get_string(key1), + self._template % "changed 1") + self.assertEqual(self._box.get_string(key2), + self._template % "changed 2") + + def test_flush(self): + # Write changes to disk + self._test_flush_or_close(self._box.flush, True) + + def test_lock_unlock(self): + # Lock and unlock the mailbox + self.assertFalse(os.path.exists(self._get_lock_path())) + self._box.lock() + self.assertTrue(os.path.exists(self._get_lock_path())) + self._box.unlock() + self.assertFalse(os.path.exists(self._get_lock_path())) + + def test_close(self): + # Close mailbox and flush changes to disk + self._test_flush_or_close(self._box.close, False) + + def _test_flush_or_close(self, method, should_call_close): + contents = [self._template % i for i in xrange(3)] + self._box.add(contents[0]) + self._box.add(contents[1]) + self._box.add(contents[2]) + method() + if should_call_close: + self._box.close() + self._box = self._factory(self._path) + keys = self._box.keys() + self.assertEqual(len(keys), 3) + for key in keys: + self.assertIn(self._box.get_string(key), contents) + + def test_dump_message(self): + # Write message representations to disk + for input in (email.message_from_string(_sample_message), + _sample_message, StringIO.StringIO(_sample_message)): + output = StringIO.StringIO() + self._box._dump_message(input, output) + self.assertEqual(output.getvalue(), + _sample_message.replace('\n', os.linesep)) + output = StringIO.StringIO() + self.assertRaises(TypeError, + lambda: self._box._dump_message(None, output)) + + def _get_lock_path(self): + # Return the path of the dot lock file. May be overridden. + return self._path + '.lock' + + +class TestMailboxSuperclass(TestBase): + + def test_notimplemented(self): + # Test that all Mailbox methods raise NotImplementedException. + box = mailbox.Mailbox('path') + self.assertRaises(NotImplementedError, lambda: box.add('')) + self.assertRaises(NotImplementedError, lambda: box.remove('')) + self.assertRaises(NotImplementedError, lambda: box.__delitem__('')) + self.assertRaises(NotImplementedError, lambda: box.discard('')) + self.assertRaises(NotImplementedError, lambda: box.__setitem__('', '')) + self.assertRaises(NotImplementedError, lambda: box.iterkeys()) + self.assertRaises(NotImplementedError, lambda: box.keys()) + self.assertRaises(NotImplementedError, lambda: box.itervalues().next()) + self.assertRaises(NotImplementedError, lambda: box.__iter__().next()) + self.assertRaises(NotImplementedError, lambda: box.values()) + self.assertRaises(NotImplementedError, lambda: box.iteritems().next()) + self.assertRaises(NotImplementedError, lambda: box.items()) + self.assertRaises(NotImplementedError, lambda: box.get('')) + self.assertRaises(NotImplementedError, lambda: box.__getitem__('')) + self.assertRaises(NotImplementedError, lambda: box.get_message('')) + self.assertRaises(NotImplementedError, lambda: box.get_string('')) + self.assertRaises(NotImplementedError, lambda: box.get_file('')) + self.assertRaises(NotImplementedError, lambda: box.has_key('')) + self.assertRaises(NotImplementedError, lambda: box.__contains__('')) + self.assertRaises(NotImplementedError, lambda: box.__len__()) + self.assertRaises(NotImplementedError, lambda: box.clear()) + self.assertRaises(NotImplementedError, lambda: box.pop('')) + self.assertRaises(NotImplementedError, lambda: box.popitem()) + self.assertRaises(NotImplementedError, lambda: box.update((('', ''),))) + self.assertRaises(NotImplementedError, lambda: box.flush()) + self.assertRaises(NotImplementedError, lambda: box.lock()) + self.assertRaises(NotImplementedError, lambda: box.unlock()) + self.assertRaises(NotImplementedError, lambda: box.close()) + + +class TestMaildir(TestMailbox): + + _factory = lambda self, path, factory=None: mailbox.Maildir(path, factory) + + def setUp(self): + TestMailbox.setUp(self) + if os.name in ('nt', 'os2') or sys.platform == 'cygwin': + self._box.colon = '!' + + def test_add_MM(self): + # Add a MaildirMessage instance + msg = mailbox.MaildirMessage(self._template % 0) + msg.set_subdir('cur') + msg.set_info('foo') + key = self._box.add(msg) + self.assertTrue(os.path.exists(os.path.join(self._path, 'cur', '%s%sfoo' % + (key, self._box.colon)))) + + def test_get_MM(self): + # Get a MaildirMessage instance + msg = mailbox.MaildirMessage(self._template % 0) + msg.set_subdir('cur') + msg.set_flags('RF') + key = self._box.add(msg) + msg_returned = self._box.get_message(key) + self.assertIsInstance(msg_returned, mailbox.MaildirMessage) + self.assertEqual(msg_returned.get_subdir(), 'cur') + self.assertEqual(msg_returned.get_flags(), 'FR') + + def test_set_MM(self): + # Set with a MaildirMessage instance + msg0 = mailbox.MaildirMessage(self._template % 0) + msg0.set_flags('TP') + key = self._box.add(msg0) + msg_returned = self._box.get_message(key) + self.assertEqual(msg_returned.get_subdir(), 'new') + self.assertEqual(msg_returned.get_flags(), 'PT') + msg1 = mailbox.MaildirMessage(self._template % 1) + self._box[key] = msg1 + msg_returned = self._box.get_message(key) + self.assertEqual(msg_returned.get_subdir(), 'new') + self.assertEqual(msg_returned.get_flags(), '') + self.assertEqual(msg_returned.get_payload(), '1') + msg2 = mailbox.MaildirMessage(self._template % 2) + msg2.set_info('2,S') + self._box[key] = msg2 + self._box[key] = self._template % 3 + msg_returned = self._box.get_message(key) + self.assertEqual(msg_returned.get_subdir(), 'new') + self.assertEqual(msg_returned.get_flags(), 'S') + self.assertEqual(msg_returned.get_payload(), '3') + + def test_consistent_factory(self): + # Add a message. + msg = mailbox.MaildirMessage(self._template % 0) + msg.set_subdir('cur') + msg.set_flags('RF') + key = self._box.add(msg) + + # Create new mailbox with + class FakeMessage(mailbox.MaildirMessage): + pass + box = mailbox.Maildir(self._path, factory=FakeMessage) + box.colon = self._box.colon + msg2 = box.get_message(key) + self.assertIsInstance(msg2, FakeMessage) + + def test_initialize_new(self): + # Initialize a non-existent mailbox + self.tearDown() + self._box = mailbox.Maildir(self._path) + self._check_basics(factory=rfc822.Message) + self._delete_recursively(self._path) + self._box = self._factory(self._path, factory=None) + self._check_basics() + + def test_initialize_existing(self): + # Initialize an existing mailbox + self.tearDown() + for subdir in '', 'tmp', 'new', 'cur': + os.mkdir(os.path.normpath(os.path.join(self._path, subdir))) + self._box = mailbox.Maildir(self._path) + self._check_basics(factory=rfc822.Message) + self._box = mailbox.Maildir(self._path, factory=None) + self._check_basics() + + def _check_basics(self, factory=None): + # (Used by test_open_new() and test_open_existing().) + self.assertEqual(self._box._path, os.path.abspath(self._path)) + self.assertEqual(self._box._factory, factory) + for subdir in '', 'tmp', 'new', 'cur': + path = os.path.join(self._path, subdir) + mode = os.stat(path)[stat.ST_MODE] + self.assertTrue(stat.S_ISDIR(mode), "Not a directory: '%s'" % path) + + def test_list_folders(self): + # List folders + self._box.add_folder('one') + self._box.add_folder('two') + self._box.add_folder('three') + self.assertEqual(len(self._box.list_folders()), 3) + self.assertEqual(set(self._box.list_folders()), + set(('one', 'two', 'three'))) + + def test_get_folder(self): + # Open folders + self._box.add_folder('foo.bar') + folder0 = self._box.get_folder('foo.bar') + folder0.add(self._template % 'bar') + self.assertTrue(os.path.isdir(os.path.join(self._path, '.foo.bar'))) + folder1 = self._box.get_folder('foo.bar') + self.assertEqual(folder1.get_string(folder1.keys()[0]), + self._template % 'bar') + + def test_add_and_remove_folders(self): + # Delete folders + self._box.add_folder('one') + self._box.add_folder('two') + self.assertEqual(len(self._box.list_folders()), 2) + self.assertEqual(set(self._box.list_folders()), set(('one', 'two'))) + self._box.remove_folder('one') + self.assertEqual(len(self._box.list_folders()), 1) + self.assertEqual(set(self._box.list_folders()), set(('two',))) + self._box.add_folder('three') + self.assertEqual(len(self._box.list_folders()), 2) + self.assertEqual(set(self._box.list_folders()), set(('two', 'three'))) + self._box.remove_folder('three') + self.assertEqual(len(self._box.list_folders()), 1) + self.assertEqual(set(self._box.list_folders()), set(('two',))) + self._box.remove_folder('two') + self.assertEqual(len(self._box.list_folders()), 0) + self.assertEqual(self._box.list_folders(), []) + + def test_clean(self): + # Remove old files from 'tmp' + foo_path = os.path.join(self._path, 'tmp', 'foo') + bar_path = os.path.join(self._path, 'tmp', 'bar') + with open(foo_path, 'w') as f: + f.write("@") + with open(bar_path, 'w') as f: + f.write("@") + self._box.clean() + self.assertTrue(os.path.exists(foo_path)) + self.assertTrue(os.path.exists(bar_path)) + foo_stat = os.stat(foo_path) + os.utime(foo_path, (time.time() - 129600 - 2, + foo_stat.st_mtime)) + self._box.clean() + self.assertFalse(os.path.exists(foo_path)) + self.assertTrue(os.path.exists(bar_path)) + + def test_create_tmp(self, repetitions=10): + # Create files in tmp directory + hostname = socket.gethostname() + if '/' in hostname: + hostname = hostname.replace('/', r'\057') + if ':' in hostname: + hostname = hostname.replace(':', r'\072') + pid = os.getpid() + pattern = re.compile(r"(?P