Author: Matti Picus <[email protected]>
Branch: py3.7
Changeset: r98594:55e4d78197be
Date: 2020-01-29 22:05 +0200
http://bitbucket.org/pypy/pypy/changeset/55e4d78197be/

Log:    merge py3.6 into branch

diff --git a/lib-python/3/test/test_curses.py b/lib-python/3/test/test_curses.py
--- a/lib-python/3/test/test_curses.py
+++ b/lib-python/3/test/test_curses.py
@@ -15,7 +15,8 @@
 import tempfile
 import unittest
 
-from test.support import requires, import_module, verbose, SaveSignals
+from test.support import (requires, import_module, verbose, SaveSignals,
+        cpython_only)
 
 # Optionally test curses module.  This currently requires that the
 # 'curses' resource be given on the regrtest command line using the -u
@@ -315,6 +316,7 @@
                                msg='userptr should fail since not set'):
             p.userptr()
 
+    @cpython_only
     @requires_curses_func('panel')
     def test_userptr_memory_leak(self):
         w = curses.newwin(10, 10)
@@ -328,6 +330,7 @@
         self.assertEqual(sys.getrefcount(obj), nrefs,
                          "set_userptr leaked references")
 
+    @cpython_only
     @requires_curses_func('panel')
     def test_userptr_segfault(self):
         w = curses.newwin(10, 10)
@@ -420,20 +423,20 @@
         # we will need to rewrite this test.
         try:
             signature = inspect.signature(stdscr.addch)
-            self.assertFalse(signature)
         except ValueError:
-            # not generating a signature is fine.
-            pass
 
-        # So.  No signature for addch.
-        # But Argument Clinic gave us a human-readable equivalent
-        # as the first line of the docstring.  So we parse that,
-        # and ensure that the parameters appear in the correct order.
-        # Since this is parsing output from Argument Clinic, we can
-        # be reasonably certain the generated parsing code will be
-        # correct too.
-        human_readable_signature = stdscr.addch.__doc__.split("\n")[0]
-        self.assertIn("[y, x,]", human_readable_signature)
+            # So.  No signature for addch.
+            # But Argument Clinic gave us a human-readable equivalent
+            # as the first line of the docstring.  So we parse that,
+            # and ensure that the parameters appear in the correct order.
+            # Since this is parsing output from Argument Clinic, we can
+            # be reasonably certain the generated parsing code will be
+            # correct too.
+            human_readable_signature = stdscr.addch.__doc__.split("\n")[0]
+            self.assertIn("[y, x,]", human_readable_signature)
+        else:
+            params = list(signature.parameters.keys())
+            self.assertTrue(params.index('y') < params.index('x'))
 
     def test_issue13051(self):
         stdscr = self.stdscr
diff --git a/lib-python/3/venv/__init__.py b/lib-python/3/venv/__init__.py
--- a/lib-python/3/venv/__init__.py
+++ b/lib-python/3/venv/__init__.py
@@ -187,6 +187,9 @@
                     logger.warning('Unable to symlink %r to %r', src, dst)
                     force_copy = True
             if force_copy:
+            if os.path.isdir(src):
+                shutil.copytree(src, dst)
+            else:
                 shutil.copyfile(src, dst)
     else:
         def symlink_or_copy(self, src, dst, relative_symlinks_ok=False):
diff --git a/lib_pypy/_curses.py b/lib_pypy/_curses.py
--- a/lib_pypy/_curses.py
+++ b/lib_pypy/_curses.py
@@ -4,6 +4,7 @@
 if sys.platform == 'win32':
     #This module does not exist in windows
     raise ImportError('No module named _curses')
+import locale
 from functools import wraps
 
 from _curses_cffi import ffi, lib
@@ -57,7 +58,7 @@
             if key_n == b"UNKNOWN KEY":
                 continue
             if not isinstance(key_n, str):   # python 3
-                key_n = key_n.decode()
+                key_n = key_n.decode('utf-8')
             key_n = key_n.replace('(', '').replace(')', '')
             globals()[key_n] = key
 
@@ -83,7 +84,9 @@
 
 
 def _ensure_initialised_color():
-    if not _initialised and _initialised_color:
+    if not _initialised:
+        raise error("must call initscr() first")
+    if not _initialised_color:
         raise error("must call start_color() first")
 
 
@@ -173,6 +176,40 @@
         raise TypeError("bytes or str expected, got a '%s' object"
                         % (type(text).__name__,))
 
+def _convert_to_chtype(win, obj):
+    if isinstance(obj, bytes) and len(obj) == 1:
+        value = ord(obj)
+    elif isinstance(obj, str):
+        if len(obj) != 1:
+            raise TypeError("expect bytes or str of length 1 or int, "
+                            "got a str of length %d", len(obj))
+        value = ord(obj)
+        if (128 < value):
+            if win:
+                encoding = win.encoding
+            else:
+                encoding = screen_encoding
+            b = obj.encode(encoding)
+            if len(bytes) == 1:
+                value = ord(b)
+            else:
+                OverflowError("byte doesn't fit in chtype")
+    elif isinstance(obj, int):
+        value = obj
+    else:
+        raise TypeError('expect bytes or str of length 1, or int, got %s' % 
type(obj))
+    return value
+
+def _convert_to_string(win, obj):
+    if isinstance(obj, str):
+        value = obj.encode(win.encoding)
+    elif isinstance(obj, bytes):
+        value = obj
+    else:
+        raise TypeError('expect bytes or str, got %s' % type(obj))
+    if b'\0' in value:
+        raise ValueError('embedded null character') 
+    return value
 
 def _extract_yx(args):
     if len(args) >= 2:
@@ -216,8 +253,17 @@
 
 
 class Window(object):
-    def __init__(self, window):
+    def __init__(self, window, encoding=None):
+        if encoding is None:
+            # CPython has a win32 branch here, but _curses is not supported
+            # on win32
+            codeset = locale.nl_langinfo(locale.CODESET)
+            if codeset:
+                encoding = codeset
+            else:
+                encoding = 'utf-8'
         self._win = window
+        self._encoding = encoding
 
     def __del__(self):
         if self._win != lib.stdscr:
@@ -286,7 +332,7 @@
 
     @_argspec(1, 1, 2)
     def addstr(self, y, x, text, attr=None):
-        text = _bytestype(text)
+        text = _convert_to_string(self, text)
         if attr is not None:
             attr_old = lib.getattrs(self._win)
             lib.wattrset(self._win, attr)
@@ -300,7 +346,7 @@
 
     @_argspec(2, 1, 2)
     def addnstr(self, y, x, text, n, attr=None):
-        text = _bytestype(text)
+        text = _convert_to_string(self, text)
         if attr is not None:
             attr_old = lib.getattrs(self._win)
             lib.wattrset(self._win, attr)
@@ -333,7 +379,15 @@
                     _chtype(tl), _chtype(tr), _chtype(bl), _chtype(br))
         return None
 
-    def box(self, vertint=0, horint=0):
+    def box(self, *args):
+        if len(args) == 0:
+            vertint = 0
+            horint = 0
+        elif len(args) == 2:
+            vertint = _convert_to_chtype(self, args[0])
+            horint = _convert_to_chtype(self, args[1])
+        else:
+            raise TypeError('verch,horch required')
         lib.box(self._win, vertint, horint)
         return None
 
@@ -434,11 +488,16 @@
             val = lib.keyname(val)
             if val == ffi.NULL:
                 return ""
-            return ffi.string(val)
+            key_n = ffi.string(val)
+            if not isinstance(key_n, str):
+                key_n = key_n.decode('utf-8')
+            return key_n
 
     @_argspec(0, 1, 2)
     def getstr(self, y, x, n=1023):
         n = min(n, 1023)
+        if n < 0:
+            raise ValueError("'n' must be nonnegative")
         buf = ffi.new("char[1024]")  # /* This should be big enough.. I hope */
 
         if y is None:
@@ -481,6 +540,8 @@
     @_argspec(0, 1, 2)
     def instr(self, y, x, n=1023):
         n = min(n, 1023)
+        if n < 0:
+            raise ValueError("'n' must be nonnegative")
         buf = ffi.new("char[1024]")  # /* This should be big enough.. I hope */
         if y is None:
             code = lib.winnstr(self._win, buf, n)
@@ -493,7 +554,7 @@
 
     @_argspec(1, 1, 2)
     def insstr(self, y, x, text, attr=None):
-        text = _bytestype(text)
+        text = _convert_to_string(self, text)
         if attr is not None:
             attr_old = lib.getattrs(self._win)
             lib.wattrset(self._win, attr)
@@ -507,7 +568,7 @@
 
     @_argspec(2, 1, 2)
     def insnstr(self, y, x, text, n, attr=None):
-        text = _bytestype(text)
+        text = _convert_to_string(self, text)
         if attr is not None:
             attr_old = lib.getattrs(self._win)
             lib.wattrset(self._win, attr)
@@ -601,7 +662,7 @@
             win = lib.subpad(self._win, nlines, ncols, begin_y, begin_x)
         else:
             win = lib.subwin(self._win, nlines, ncols, begin_y, begin_x)
-        return Window(_check_NULL(win))
+        return Window(_check_NULL(win), self.encoding)
 
     def scroll(self, nlines=None):
         if nlines is None:
@@ -625,6 +686,23 @@
             _check_ERR(lib.wmove(self._win, y, x), "wmove")
         return _check_ERR(lib.wvline(self._win, ch | attr, n), "vline")
 
+    @property
+    def encoding(self):
+        return self._encoding
+
+    @encoding.setter
+    def encoding(self, val):
+        if not val:
+            raise TypeError('encoding may not be deleted')
+        if not isinstance(val, str):
+            raise TypeError('setting encoding to a non-string')
+        encoding = val.encode('ascii')
+        self._encoding = val 
+
+    @encoding.deleter
+    def encoding(self):
+        raise TypeError('encoding may not be deleted')
+        
 
 beep = _mk_no_return("beep")
 def_prog_mode = _mk_no_return("def_prog_mode")
@@ -812,7 +890,9 @@
     globals()["LINES"] = lib.LINES
     globals()["COLS"] = lib.COLS
 
-    return Window(win)
+    window = Window(win)
+    globals()['screen_encoding'] = window.encoding
+    return window
 
 
 def setupterm(term=None, fd=-1):
@@ -1021,7 +1101,13 @@
 
 def unget_wch(ch):
     _ensure_initialised()
-    return _check_ERR(lib.unget_wch(_chtype(ch)), "unget_wch")
+    if isinstance(ch, str):
+        if len(ch) != 1:
+            raise TypeError("expect bytes or str of length1, or int, "
+                            "got a str of length %d" % len(ch))
+    elif isinstance(ch, int): 
+        ch = chr(ch)
+    return _check_ERR(lib.unget_wch(ch), "unget_wch")
 
 
 def use_env(flag):
diff --git a/lib_pypy/_sysconfigdata.py b/lib_pypy/_sysconfigdata.py
--- a/lib_pypy/_sysconfigdata.py
+++ b/lib_pypy/_sysconfigdata.py
@@ -47,4 +47,5 @@
     build_time_vars['CC'] += ' -arch %s' % (arch,)
     if "CXX" in build_time_vars:
         build_time_vars['CXX'] += ' -arch %s' % (arch,)
+    build_time_vars['MACOSX_DEPLOYMENT_TARGET'] = '10.7'
 
diff --git a/pypy/conftest.py b/pypy/conftest.py
--- a/pypy/conftest.py
+++ b/pypy/conftest.py
@@ -68,6 +68,11 @@
     if not mode_A and not mode_D:  # 'own' tests
         from rpython.conftest import LeakFinder
         config.pluginmanager.register(LeakFinder())
+    if mode_A:
+        from pypy.tool.pytest.apptest import PythonInterpreter
+        config.applevel = PythonInterpreter(config.option.python)
+    else:
+        config.applevel = None
 
 def pytest_addoption(parser):
     group = parser.getgroup("pypy options")
@@ -201,13 +206,17 @@
 @pytest.hookimpl(tryfirst=True)
 def pytest_runtest_setup(item):
     if isinstance(item, py.test.collect.Function):
+        config = item.config
+        if item.get_marker(name='pypy_only'):
+            if config.applevel is not None and not config.applevel.is_pypy:
+                pytest.skip('PyPy-specific test')
         appclass = item.getparent(py.test.Class)
         if appclass is not None:
             from pypy.tool.pytest.objspace import gettestobjspace
             # Make cls.space and cls.runappdirect available in tests.
             spaceconfig = getattr(appclass.obj, 'spaceconfig', {})
             appclass.obj.space = gettestobjspace(**spaceconfig)
-            appclass.obj.runappdirect = option.runappdirect
+            appclass.obj.runappdirect = config.option.runappdirect
 
 def pytest_ignore_collect(path, config):
     if (config.getoption('direct_apptest') and not path.isdir()
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
@@ -806,7 +806,7 @@
         exc = raises(TypeError, '(lambda *, kw: 0)(1, kw=3)')
         assert str(exc.value) == "<lambda>() takes 0 positional arguments but 
1 positional argument (and 1 keyword-only argument) were given"
 
-    @py.test.mark.skipif("config.option.runappdirect")
+    @pytest.mark.pypy_only
     def test_error_message_method(self):
         class A(object):
             def f0():
@@ -823,14 +823,14 @@
         # does not contain the warning about missing self
         assert exc.value.args[0] == "f0() takes 0 positional arguments but 1 
was given"
 
-    @py.test.mark.skipif("config.option.runappdirect")
     def test_error_message_module_function(self):
         import operator # use countOf because it's defined at applevel
         exc = raises(TypeError, lambda : operator.countOf(1, 2, 3))
-        # does not contain the warning about missing self
-        assert exc.value.args[0] == "countOf() takes 2 positional arguments 
but 3 were given"
+        # does not contain the warning
+        # 'Did you forget 'self' in the function definition?'
+        assert 'self' not in str(exc.value)
 
-    @py.test.mark.skipif("config.option.runappdirect")
+    @pytest.mark.pypy_only
     def test_error_message_bound_method(self):
         class A(object):
             def f0():
diff --git a/pypy/module/__builtin__/test/test_abstractinst.py 
b/pypy/module/__builtin__/test/test_abstractinst.py
--- a/pypy/module/__builtin__/test/test_abstractinst.py
+++ b/pypy/module/__builtin__/test/test_abstractinst.py
@@ -215,7 +215,7 @@
 
     def test_dont_call_instancecheck_fast_path(self):
         called = []
-        
+
         class M(type):
             def __instancecheck__(self, obj):
                 called.append("called")
@@ -272,9 +272,30 @@
         except Special:
             pass
 
+    def test_exception_bad_subclasscheck(self):
+        """
+        import sys
+        class Meta(type):
+            def __subclasscheck__(cls, subclass):
+                raise ValueError()
+
+        class MyException(Exception, metaclass=Meta):
+            pass
+
+        try:
+            raise KeyError()
+        except MyException as e:
+            assert False, "exception should not be a MyException"
+        except KeyError:
+            pass
+        except:
+            assert False, "Should have raised KeyError"
+        else:
+            assert False, "Should have raised KeyError"
+        """
+
     def test_exception_contains_type_name(self):
         with raises(TypeError) as e:
             issubclass(type, None)
         print(e.value)
         assert "NoneType" in str(e.value)
-
diff --git a/pypy/module/_multibytecodec/c_codecs.py 
b/pypy/module/_multibytecodec/c_codecs.py
--- a/pypy/module/_multibytecodec/c_codecs.py
+++ b/pypy/module/_multibytecodec/c_codecs.py
@@ -194,17 +194,23 @@
                                            rffi.SSIZE_T)
 pypy_cjk_enc_getcodec = llexternal('pypy_cjk_enc_getcodec',
                                    [ENCODEBUF_P], MULTIBYTECODEC_P)
+pypy_cjk_enc_copystate = llexternal('pypy_cjk_enc_copystate',
+                                    [ENCODEBUF_P, ENCODEBUF_P], lltype.Void)
 MBENC_FLUSH = 1
 MBENC_RESET = 2
 
 def encode(codec, unicodedata, length, errors="strict", errorcb=None,
-           namecb=None):
+           namecb=None, copystate=lltype.nullptr(ENCODEBUF_P.TO)):
     encodebuf = pypy_cjk_enc_new(codec)
     if not encodebuf:
         raise MemoryError
+    if copystate:
+        pypy_cjk_enc_copystate(encodebuf, copystate)
     try:
         return encodeex(encodebuf, unicodedata, length, errors, errorcb, 
namecb)
     finally:
+        if copystate:
+            pypy_cjk_enc_copystate(copystate, encodebuf)
         pypy_cjk_enc_free(encodebuf)
 
 def encodeex(encodebuf, utf8data, length, errors="strict", errorcb=None,
@@ -257,22 +263,21 @@
         raise EncodeDecodeError(start, end, reason)
     elif errors == "ignore":
         replace = ""
+        rettype = 'b'   # != 'u'
     elif errors == "replace":
-        codec = pypy_cjk_enc_getcodec(encodebuf)
-        try:
-            replace = encode(codec, "?", 1)
-        except EncodeDecodeError:
-            replace = "?"
+        replace = "?"    # utf-8 unicode
+        rettype = 'u'
     else:
         assert errorcb
         replace, end, rettype = errorcb(errors, namecb, reason,
                             unicodedata, start, end)
-        if rettype == 'u':
-            codec = pypy_cjk_enc_getcodec(encodebuf)
-            lgt = rutf8.check_utf8(replace, False)
-            replace = encode(codec, replace, lgt)
-    lgt = len(replace)
+    if rettype == 'u':
+        codec = pypy_cjk_enc_getcodec(encodebuf)
+        lgt = rutf8.check_utf8(replace, False)
+        replace = encode(codec, replace, lgt, copystate=encodebuf)
+    #else:
+    #   replace is meant to be a byte string already
     with rffi.scoped_nonmovingbuffer(replace) as inbuf:
-        r = pypy_cjk_enc_replace_on_error(encodebuf, inbuf, lgt, end)
+        r = pypy_cjk_enc_replace_on_error(encodebuf, inbuf, len(replace), end)
     if r == MBERR_NOMEMORY:
         raise MemoryError
diff --git a/pypy/module/_multibytecodec/src/cjkcodecs/multibytecodec.c 
b/pypy/module/_multibytecodec/src/cjkcodecs/multibytecodec.c
--- a/pypy/module/_multibytecodec/src/cjkcodecs/multibytecodec.c
+++ b/pypy/module/_multibytecodec/src/cjkcodecs/multibytecodec.c
@@ -135,6 +135,11 @@
   return d;
 }
 
+void pypy_cjk_enc_copystate(struct pypy_cjk_enc_s *dst, struct pypy_cjk_enc_s 
*src)
+{
+    dst->state = src->state;
+}
+
 Py_ssize_t pypy_cjk_enc_init(struct pypy_cjk_enc_s *d,
                              Py_UNICODE *inbuf, Py_ssize_t inlen)
 {
diff --git a/pypy/module/_multibytecodec/src/cjkcodecs/multibytecodec.h 
b/pypy/module/_multibytecodec/src/cjkcodecs/multibytecodec.h
--- a/pypy/module/_multibytecodec/src/cjkcodecs/multibytecodec.h
+++ b/pypy/module/_multibytecodec/src/cjkcodecs/multibytecodec.h
@@ -146,6 +146,8 @@
                                       char *, pypymbc_ssize_t, 
pypymbc_ssize_t);
 RPY_EXTERN
 const MultibyteCodec *pypy_cjk_enc_getcodec(struct pypy_cjk_enc_s *);
+RPY_EXTERN
+void pypy_cjk_enc_copystate(struct pypy_cjk_enc_s *dst, struct pypy_cjk_enc_s 
*src);
 
 /* list of codecs defined in the .c files */
 
diff --git a/pypy/module/_multibytecodec/test/test_app_codecs.py 
b/pypy/module/_multibytecodec/test/test_app_codecs.py
--- a/pypy/module/_multibytecodec/test/test_app_codecs.py
+++ b/pypy/module/_multibytecodec/test/test_app_codecs.py
@@ -126,3 +126,33 @@
                               lambda e: (b'\xc3', e.end))
         result = "\uDDA1".encode("gbk", 
"test.test_encode_custom_error_handler_type")
         assert b'\xc3' in result
+
+    def test_encode_replacement_with_state(self):
+        import codecs
+        s = u'\u4ee4\u477c\u4ee4'.encode("iso-2022-jp", errors="replace")
+        assert s == b'\x1b$BNa\x1b(B?\x1b$BNa\x1b(B'
+
+    def test_streaming_codec(self):
+        test_0 = u'\uc5fc\u76d0\u5869\u9e7d\u477c\u4e3d/\u3012'
+        test_1 = 
u'\u4ee4\u477c\u3080\u304b\u3057\u3080\u304b\u3057\u3042\u308b\u3068\u3053\u308d\u306b'
+        test_2 = u' foo = "Quoted string ****\u4ee4\u477c" '
+
+        ereplace = {'errors': 'replace'}
+        exml = {'errors': 'xmlcharrefreplace'}
+        for codec in ("iso-2022-jp", "iso-2022-jp-ext", "iso-2022-jp-1",
+                      "iso-2022-jp-2", "iso-2022-jp-3", "iso-2022-jp-2004",
+                      "iso-2022-kr",
+                     ):
+
+            out_1 = test_1.encode(codec, **ereplace).decode(codec, **ereplace)
+            assert 
out_1.endswith(u'\u3080\u304b\u3057\u3080\u304b\u3057\u3042\u308b\u3068\u3053\u308d\u306b')
+
+            out_0a = test_0.encode(codec, **ereplace).decode(codec, **ereplace)
+            for n, char in enumerate(out_0a):
+                assert char in (test_0[n], "?")
+
+            out_0b = test_0.encode(codec, **exml).decode(codec, **ereplace)
+            assert "&#18300;" in out_0b
+
+            out_2 = test_2.encode(codec, **ereplace).decode(codec, **ereplace)
+            assert out_2.count('"') == 2
diff --git a/pypy/module/cpyext/codecs.py b/pypy/module/cpyext/codecs.py
--- a/pypy/module/cpyext/codecs.py
+++ b/pypy/module/cpyext/codecs.py
@@ -20,3 +20,12 @@
     else:
         return space.call_method(w_codec, "incrementaldecoder")
 
+@cpython_api([CONST_STRING], PyObject)
+def PyCodec_Encoder(space, encoding):
+    w_codec = interp_codecs.lookup_codec(space, rffi.charp2str(encoding))
+    return space.getitem(w_codec, space.newint(0))
+
+@cpython_api([CONST_STRING], PyObject)
+def PyCodec_Decoder(space, encoding):
+    w_codec = interp_codecs.lookup_codec(space, rffi.charp2str(encoding))
+    return space.getitem(w_codec, space.newint(1))
diff --git a/pypy/module/cpyext/include/Python.h 
b/pypy/module/cpyext/include/Python.h
--- a/pypy/module/cpyext/include/Python.h
+++ b/pypy/module/cpyext/include/Python.h
@@ -123,6 +123,7 @@
 #include "sliceobject.h"
 #include "genobject.h"
 #include "datetime.h"
+#include "structseq.h"
 #include "pystate.h"
 #include "fileobject.h"
 #include "pysignals.h"
diff --git a/pypy/module/cpyext/test/test_codecs.py 
b/pypy/module/cpyext/test/test_codecs.py
--- a/pypy/module/cpyext/test/test_codecs.py
+++ b/pypy/module/cpyext/test/test_codecs.py
@@ -2,7 +2,8 @@
 from pypy.module.cpyext.test.test_api import BaseApiTest
 from rpython.rtyper.lltypesystem import rffi
 from pypy.module.cpyext.codecs import (
-    PyCodec_IncrementalEncoder, PyCodec_IncrementalDecoder)
+    PyCodec_IncrementalEncoder, PyCodec_IncrementalDecoder,
+    PyCodec_Encoder, PyCodec_Decoder)
 
 class TestCodecs(BaseApiTest):
     def test_incremental(self, space):
@@ -13,3 +14,13 @@
         w_decoded = space.call_method(w_decoder, 'decode', w_encoded)
         assert space.utf8_w(w_decoded) == u'sp&#228;m'.encode("utf-8")
         rffi.free_charp(utf8)
+
+    def test_encoder_decoder(self, space):
+        utf8 = rffi.str2charp('utf-8')
+        w_encoder = PyCodec_Encoder(space, utf8)
+        w_decoder = PyCodec_Decoder(space, utf8)
+        rffi.free_charp(utf8)
+        space.appexec([w_encoder, w_decoder], r"""(encoder, decoder):
+            assert encoder(u"\u1234") == (b"\xe1\x88\xb4", 1)
+            assert decoder(b"\xe1\x88\xb4") == (u"\u1234", 3)
+        """)
diff --git a/pypy/tool/pytest/apptest.py b/pypy/tool/pytest/apptest.py
--- a/pypy/tool/pytest/apptest.py
+++ b/pypy/tool/pytest/apptest.py
@@ -35,6 +35,24 @@
     def __init__(self, excinfo):
         self.excinfo = excinfo
 
+class PythonInterpreter(object):
+    def __init__(self, path):
+        self.path = path
+        self._is_pypy = None
+
+    @property
+    def is_pypy(self):
+        if self._is_pypy is not None:
+            return self._is_pypy
+        CODE = "import sys; print('__pypy__' in sys.builtin_module_names)"
+        res, stdout, stderr = run_subprocess( self.path, ["-c", CODE])
+        if res != 0:
+            raise ValueError("Invalid Python interpreter")
+        print stdout
+        is_pypy = stdout.strip() == 'True'
+        self._is_pypy = is_pypy
+        return is_pypy
+
 
 def py3k_repr(value):
     "return the repr() that py3k would give for an object."""
@@ -120,7 +138,7 @@
                 pytest.fail("DID NOT RAISE")
             self.value = tp[1]
             return issubclass(tp[0], self.expected_exception)
-    
+
     __builtins__.raises = raises
     class Test:
         pass
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to