Author: Amaury Forgeot d'Arc <amaur...@gmail.com> Branch: Changeset: r58749:443aecb1a7e2 Date: 2012-11-05 21:32 +0100 http://bitbucket.org/pypy/pypy/changeset/443aecb1a7e2/
Log: Merge branch move-apptest-support: Move many helper classes out of conftest.py, to make it compatible with python3 syntax, and help with the -A option in the py3k branch. diff --git a/pypy/conftest.py b/pypy/conftest.py --- a/pypy/conftest.py +++ b/pypy/conftest.py @@ -1,12 +1,5 @@ -import py, pytest, sys, os, textwrap, types -from pypy.interpreter.gateway import app2interp_temp -from pypy.interpreter.error import OperationError -from pypy.interpreter.function import Method -from pypy.tool.pytest import appsupport -from inspect import isclass, getmro -from pypy.tool.udir import udir -from pypy.tool.autopath import pypydir -from pypy.tool import leakfinder +import py, pytest, sys, os, textwrap +from inspect import isclass # pytest settings rsyncdirs = ['.', '../lib-python', '../lib_pypy', '../demo'] @@ -31,6 +24,12 @@ def pytest_report_header(): return "pytest-%s from %s" %(pytest.__version__, pytest.__file__) + +def pytest_addhooks(pluginmanager): + from pypy.tool.pytest.plugins import LeakFinder + pluginmanager.register(LeakFinder()) + + def pytest_configure(config): global option option = config.option @@ -72,23 +71,6 @@ spaceconfig = getattr(request.cls, 'spaceconfig', {}) return gettestobjspace(**spaceconfig) -def translation_test_so_skip_if_appdirect(): - if option.runappdirect: - py.test.skip("translation test, skipped for appdirect") - - -class OpErrKeyboardInterrupt(KeyboardInterrupt): - pass - -def check_keyboard_interrupt(e): - # we cannot easily convert w_KeyboardInterrupt to KeyboardInterrupt - # in general without a space -- here is an approximation - try: - if e.w_type.name == 'KeyboardInterrupt': - tb = sys.exc_info()[2] - raise OpErrKeyboardInterrupt, OpErrKeyboardInterrupt(), tb - except AttributeError: - pass # # Interfacing/Integrating with py.test's collection process @@ -150,10 +132,12 @@ def makeitem(self, name, obj): if isclass(obj) and self.classnamefilter(name): if name.startswith('AppTest'): + from pypy.tool.pytest.apptest import AppClassCollector return AppClassCollector(name, parent=self) elif name.startswith('ExpectTest'): if self.config.option.rundirect: return py.test.collect.Class(name, parent=self) + from pypy.tool.pytest.expecttest import ExpectClassCollector return ExpectClassCollector(name, parent=self) # XXX todo #elif name.startswith('AppExpectTest'): @@ -161,16 +145,19 @@ # return AppClassCollector(name, parent=self) # return AppExpectClassCollector(name, parent=self) else: + from pypy.tool.pytest.inttest import IntClassCollector return IntClassCollector(name, parent=self) elif hasattr(obj, 'func_code') and self.funcnamefilter(name): if name.startswith('app_test_'): assert not obj.func_code.co_flags & 32, \ "generator app level functions? you must be joking" + from pypy.tool.pytest.apptest import AppTestFunction return AppTestFunction(name, parent=self) elif obj.func_code.co_flags & 32: # generator function return pytest.Generator(name, parent=self) else: + from pypy.tool.pytest.inttest import IntTestFunction return IntTestFunction(name, parent=self) def skip_on_missing_buildoption(**ropts): @@ -198,11 +185,6 @@ return space -class AppError(Exception): - - def __init__(self, excinfo): - self.excinfo = excinfo - def pytest_runtest_setup(__multicall__, item): if isinstance(item, py.test.collect.Function): appclass = item.getparent(PyPyClassCollector) @@ -216,130 +198,16 @@ __multicall__.execute() - if isinstance(item, py.test.collect.Function): - if not getattr(item.obj, 'dont_track_allocations', False): - leakfinder.start_tracking_allocations() - -def pytest_runtest_call(__multicall__, item): - __multicall__.execute() - item._success = True - def pytest_runtest_teardown(__multicall__, item): __multicall__.execute() - if isinstance(item, py.test.collect.Function): - if (not getattr(item.obj, 'dont_track_allocations', False) - and leakfinder.TRACK_ALLOCATIONS): - item._pypytest_leaks = leakfinder.stop_tracking_allocations(False) - else: # stop_tracking_allocations() already called - item._pypytest_leaks = None - - # check for leaks, but only if the test passed so far - if getattr(item, '_success', False) and item._pypytest_leaks: - raise leakfinder.MallocMismatch(item._pypytest_leaks) - if 'pygame' in sys.modules: assert option.view, ("should not invoke Pygame " "if conftest.option.view is False") -_pygame_imported = False - -class IntTestFunction(py.test.collect.Function): - def __init__(self, *args, **kwargs): - super(IntTestFunction, self).__init__(*args, **kwargs) - self.keywords['interplevel'] = True - - def runtest(self): - try: - super(IntTestFunction, self).runtest() - except OperationError, e: - check_keyboard_interrupt(e) - raise - except Exception, e: - cls = e.__class__ - while cls is not Exception: - if cls.__name__ == 'DistutilsPlatformError': - from distutils.errors import DistutilsPlatformError - if isinstance(e, DistutilsPlatformError): - py.test.skip('%s: %s' % (e.__class__.__name__, e)) - cls = cls.__bases__[0] - raise - -class AppTestFunction(py.test.collect.Function): - def __init__(self, *args, **kwargs): - super(AppTestFunction, self).__init__(*args, **kwargs) - self.keywords['applevel'] = True - - def _prunetraceback(self, traceback): - return traceback - - def execute_appex(self, space, target, *args): - try: - target(*args) - except OperationError, e: - tb = sys.exc_info()[2] - if e.match(space, space.w_KeyboardInterrupt): - raise OpErrKeyboardInterrupt, OpErrKeyboardInterrupt(), tb - appexcinfo = appsupport.AppExceptionInfo(space, e) - if appexcinfo.traceback: - raise AppError, AppError(appexcinfo), tb - raise - - def runtest(self): - target = self.obj - if self.config.option.runappdirect: - return target() - from pypy.tool.pytest.objspace import gettestobjspace - space = gettestobjspace() - filename = self._getdynfilename(target) - func = app2interp_temp(target, filename=filename) - print "executing", func - self.execute_appex(space, func, space) - - def repr_failure(self, excinfo): - if excinfo.errisinstance(AppError): - excinfo = excinfo.value.excinfo - return super(AppTestFunction, self).repr_failure(excinfo) - - def _getdynfilename(self, func): - code = getattr(func, 'im_func', func).func_code - return "[%s:%s]" % (code.co_filename, code.co_firstlineno) - -class AppTestMethod(AppTestFunction): - def setup(self): - super(AppTestMethod, self).setup() - instance = self.parent.obj - w_instance = self.parent.w_instance - space = instance.space - for name in dir(instance): - if name.startswith('w_'): - if self.config.option.runappdirect: - setattr(instance, name[2:], getattr(instance, name)) - else: - obj = getattr(instance, name) - if isinstance(obj, types.MethodType): - source = py.std.inspect.getsource(obj).lstrip() - w_func = space.appexec([], textwrap.dedent(""" - (): - %s - return %s - """) % (source, name)) - w_obj = Method(space, w_func, w_instance, space.w_None) - else: - w_obj = obj - space.setattr(w_instance, space.wrap(name[2:]), w_obj) - - def runtest(self): - target = self.obj - if self.config.option.runappdirect: - return target() - space = target.im_self.space - filename = self._getdynfilename(target) - func = app2interp_temp(target.im_func, filename=filename) - w_instance = self.parent.w_instance - self.execute_appex(space, func, space, w_instance) class PyPyClassCollector(py.test.collect.Class): + # All pypy Test classes have a "space" member. def setup(self): cls = self.obj if not hasattr(cls, 'spaceconfig'): @@ -348,125 +216,6 @@ assert hasattr(cls, 'space') # set by pytest_runtest_setup super(PyPyClassCollector, self).setup() -class IntInstanceCollector(py.test.collect.Instance): - Function = IntTestFunction - -class IntClassCollector(PyPyClassCollector): - Instance = IntInstanceCollector - - def _haskeyword(self, keyword): - return keyword == 'interplevel' or \ - super(IntClassCollector, self)._haskeyword(keyword) - - def _keywords(self): - return super(IntClassCollector, self)._keywords() + ['interplevel'] - -class AppClassInstance(py.test.collect.Instance): - Function = AppTestMethod - - def setup(self): - super(AppClassInstance, self).setup() - instance = self.obj - space = instance.space - w_class = self.parent.w_class - if self.config.option.runappdirect: - self.w_instance = instance - else: - self.w_instance = space.call_function(w_class) - -class AppClassCollector(PyPyClassCollector): - Instance = AppClassInstance - - def _haskeyword(self, keyword): - return keyword == 'applevel' or \ - super(AppClassCollector, self)._haskeyword(keyword) - - def _keywords(self): - return super(AppClassCollector, self)._keywords() + ['applevel'] - - def setup(self): - super(AppClassCollector, self).setup() - cls = self.obj - # - # <hack> - for name in dir(cls): - if name.startswith('test_'): - func = getattr(cls, name, None) - code = getattr(func, 'func_code', None) - if code and code.co_flags & 32: - raise AssertionError("unsupported: %r is a generator " - "app-level test method" % (name,)) - # </hack> - # - space = cls.space - clsname = cls.__name__ - if self.config.option.runappdirect: - w_class = cls - else: - w_class = space.call_function(space.w_type, - space.wrap(clsname), - space.newtuple([]), - space.newdict()) - self.w_class = w_class - -class ExpectTestMethod(py.test.collect.Function): - def safe_name(target): - s = "_".join(target) - s = s.replace("()", "paren") - s = s.replace(".py", "") - s = s.replace(".", "_") - s = s.replace(os.sep, "_") - return s - - safe_name = staticmethod(safe_name) - - def safe_filename(self): - name = self.safe_name(self.listnames()) - num = 0 - while udir.join(name + '.py').check(): - num += 1 - name = self.safe_name(self.listnames()) + "_" + str(num) - return name + '.py' - - def _spawn(self, *args, **kwds): - import pexpect - kwds.setdefault('timeout', 600) - child = pexpect.spawn(*args, **kwds) - child.logfile = sys.stdout - return child - - def spawn(self, argv): - return self._spawn(sys.executable, argv) - - def runtest(self): - target = self.obj - import pexpect - source = py.code.Source(target)[1:].deindent() - filename = self.safe_filename() - source.lines = ['import sys', - 'sys.path.insert(0, %s)' % repr(os.path.dirname(pypydir)) - ] + source.lines - source.lines.append('print "%s ok!"' % filename) - f = udir.join(filename) - f.write(source) - # run target in the guarded environment - child = self.spawn([str(f)]) - import re - child.expect(re.escape(filename + " ok!")) - -class ExpectClassInstance(py.test.collect.Instance): - Function = ExpectTestMethod - -class ExpectClassCollector(py.test.collect.Class): - Instance = ExpectClassInstance - - def setup(self): - super(ExpectClassCollector, self).setup() - try: - import pexpect - except ImportError: - py.test.skip("pexpect not found") - def pytest_ignore_collect(path): return path.check(link=1) diff --git a/pypy/module/signal/test/test_interp_signal.py b/pypy/module/signal/test/test_interp_signal.py --- a/pypy/module/signal/test/test_interp_signal.py +++ b/pypy/module/signal/test/test_interp_signal.py @@ -1,5 +1,4 @@ import os, py -from pypy import conftest; conftest.translation_test_so_skip_if_appdirect() from pypy.translator.c.test.test_genc import compile from pypy.module.signal import interp_signal diff --git a/pypy/tool/pytest/apptest.py b/pypy/tool/pytest/apptest.py new file mode 100644 --- /dev/null +++ b/pypy/tool/pytest/apptest.py @@ -0,0 +1,148 @@ +# Collects and executes application-level tests. +# +# Classes which names start with "AppTest", or function which names +# start with "app_test*" are not executed by the host Python, but +# by an interpreted pypy object space. +# +# ...unless the -A option ('runappdirect') is passed. + +import py +import sys, textwrap, types +from pypy.interpreter.gateway import app2interp_temp +from pypy.interpreter.error import OperationError +from pypy.interpreter.function import Method +from pypy.tool.pytest import appsupport +from pypy.tool.pytest.objspace import gettestobjspace +from pypy.conftest import PyPyClassCollector +from inspect import getmro + + +class AppError(Exception): + def __init__(self, excinfo): + self.excinfo = excinfo + + +class AppTestFunction(py.test.collect.Function): + def __init__(self, *args, **kwargs): + super(AppTestFunction, self).__init__(*args, **kwargs) + self.keywords['applevel'] = True + + def _prunetraceback(self, traceback): + return traceback + + def execute_appex(self, space, target, *args): + try: + target(*args) + except OperationError, e: + tb = sys.exc_info()[2] + if e.match(space, space.w_KeyboardInterrupt): + raise KeyboardInterrupt, KeyboardInterrupt(), tb + appexcinfo = appsupport.AppExceptionInfo(space, e) + if appexcinfo.traceback: + raise AppError, AppError(appexcinfo), tb + raise + + def runtest(self): + target = self.obj + if self.config.option.runappdirect: + return target() + space = gettestobjspace() + filename = self._getdynfilename(target) + func = app2interp_temp(target, filename=filename) + print "executing", func + self.execute_appex(space, func, space) + + def repr_failure(self, excinfo): + if excinfo.errisinstance(AppError): + excinfo = excinfo.value.excinfo + return super(AppTestFunction, self).repr_failure(excinfo) + + def _getdynfilename(self, func): + code = getattr(func, 'im_func', func).func_code + return "[%s:%s]" % (code.co_filename, code.co_firstlineno) + + +class AppTestMethod(AppTestFunction): + def setup(self): + super(AppTestMethod, self).setup() + instance = self.parent.obj + w_instance = self.parent.w_instance + space = instance.space + for name in dir(instance): + if name.startswith('w_'): + if self.config.option.runappdirect: + setattr(instance, name[2:], getattr(instance, name)) + else: + obj = getattr(instance, name) + if isinstance(obj, types.MethodType): + source = py.std.inspect.getsource(obj).lstrip() + w_func = space.appexec([], textwrap.dedent(""" + (): + %s + return %s + """) % (source, name)) + w_obj = Method(space, w_func, w_instance, space.w_None) + else: + w_obj = obj + space.setattr(w_instance, space.wrap(name[2:]), w_obj) + + def runtest(self): + target = self.obj + if self.config.option.runappdirect: + return target() + space = target.im_self.space + filename = self._getdynfilename(target) + func = app2interp_temp(target.im_func, filename=filename) + w_instance = self.parent.w_instance + self.execute_appex(space, func, space, w_instance) + + +class AppClassInstance(py.test.collect.Instance): + Function = AppTestMethod + + def setup(self): + super(AppClassInstance, self).setup() + instance = self.obj + space = instance.space + w_class = self.parent.w_class + if self.config.option.runappdirect: + self.w_instance = instance + else: + self.w_instance = space.call_function(w_class) + + +class AppClassCollector(PyPyClassCollector): + Instance = AppClassInstance + + def _haskeyword(self, keyword): + return keyword == 'applevel' or \ + super(AppClassCollector, self)._haskeyword(keyword) + + def _keywords(self): + return super(AppClassCollector, self)._keywords() + ['applevel'] + + def setup(self): + super(AppClassCollector, self).setup() + cls = self.obj + # + # <hack> + for name in dir(cls): + if name.startswith('test_'): + func = getattr(cls, name, None) + code = getattr(func, 'func_code', None) + if code and code.co_flags & 32: + raise AssertionError("unsupported: %r is a generator " + "app-level test method" % (name,)) + # </hack> + # + space = cls.space + clsname = cls.__name__ + if self.config.option.runappdirect: + w_class = cls + else: + w_class = space.call_function(space.w_type, + space.wrap(clsname), + space.newtuple([]), + space.newdict()) + self.w_class = w_class + diff --git a/pypy/tool/pytest/expecttest.py b/pypy/tool/pytest/expecttest.py new file mode 100644 --- /dev/null +++ b/pypy/tool/pytest/expecttest.py @@ -0,0 +1,74 @@ +# Collects and executes "Expect" tests. +# +# Classes which names start with "ExpectTest", are started in a +# separate process, and monitored by the pexpect module. This allows +# execution of dangerous code, which messes with the terminal for +# example. + + +import py +import os, sys +from pypy.tool.udir import udir +from pypy.tool.autopath import pypydir + + +class ExpectTestMethod(py.test.collect.Function): + @staticmethod + def safe_name(target): + s = "_".join(target) + s = s.replace("()", "paren") + s = s.replace(".py", "") + s = s.replace(".", "_") + s = s.replace(os.sep, "_") + return s + + def safe_filename(self): + name = self.safe_name(self.listnames()) + num = 0 + while udir.join(name + '.py').check(): + num += 1 + name = self.safe_name(self.listnames()) + "_" + str(num) + return name + '.py' + + def _spawn(self, *args, **kwds): + import pexpect + kwds.setdefault('timeout', 600) + child = pexpect.spawn(*args, **kwds) + child.logfile = sys.stdout + return child + + def spawn(self, argv): + return self._spawn(sys.executable, argv) + + def runtest(self): + target = self.obj + import pexpect + source = py.code.Source(target)[1:].deindent() + filename = self.safe_filename() + source.lines = ['import sys', + 'sys.path.insert(0, %s)' % repr(os.path.dirname(pypydir)) + ] + source.lines + source.lines.append('print "%s ok!"' % filename) + f = udir.join(filename) + f.write(source) + # run target in the guarded environment + child = self.spawn([str(f)]) + import re + child.expect(re.escape(filename + " ok!")) + + +class ExpectClassInstance(py.test.collect.Instance): + Function = ExpectTestMethod + + +class ExpectClassCollector(py.test.collect.Class): + Instance = ExpectClassInstance + + def setup(self): + super(ExpectClassCollector, self).setup() + try: + import pexpect + except ImportError: + py.test.skip("pexpect not found") + + diff --git a/pypy/tool/pytest/inttest.py b/pypy/tool/pytest/inttest.py new file mode 100644 --- /dev/null +++ b/pypy/tool/pytest/inttest.py @@ -0,0 +1,56 @@ +# Collects and executes interpreter-level tests. +# +# Most pypy tests are of this kind. + +import py +from pypy.interpreter.error import OperationError +from pypy.conftest import PyPyClassCollector + + +def check_keyboard_interrupt(e): + # we cannot easily convert w_KeyboardInterrupt to KeyboardInterrupt + # in general without a space -- here is an approximation + try: + if e.w_type.name == 'KeyboardInterrupt': + tb = sys.exc_info()[2] + raise KeyboardInterrupt, KeyboardInterrupt(), tb + except AttributeError: + pass + + +class IntTestFunction(py.test.collect.Function): + def __init__(self, *args, **kwargs): + super(IntTestFunction, self).__init__(*args, **kwargs) + self.keywords['interplevel'] = True + + def runtest(self): + try: + super(IntTestFunction, self).runtest() + except OperationError, e: + check_keyboard_interrupt(e) + raise + except Exception, e: + cls = e.__class__ + while cls is not Exception: + if cls.__name__ == 'DistutilsPlatformError': + from distutils.errors import DistutilsPlatformError + if isinstance(e, DistutilsPlatformError): + py.test.skip('%s: %s' % (e.__class__.__name__, e)) + cls = cls.__bases__[0] + raise + + +class IntInstanceCollector(py.test.collect.Instance): + Function = IntTestFunction + + +class IntClassCollector(PyPyClassCollector): + Instance = IntInstanceCollector + + def _haskeyword(self, keyword): + return (keyword == 'interplevel' or + super(IntClassCollector, self)._haskeyword(keyword)) + + def _keywords(self): + return super(IntClassCollector, self)._keywords() + ['interplevel'] + diff --git a/pypy/tool/pytest/plugins.py b/pypy/tool/pytest/plugins.py new file mode 100644 --- /dev/null +++ b/pypy/tool/pytest/plugins.py @@ -0,0 +1,36 @@ +# pytest hooks, installed by pypy.conftest. + +import py +from pypy.tool import leakfinder + +class LeakFinder: + """Track memory allocations during test execution. + + So far, only used by the function lltype.malloc(flavor='raw'). + """ + def pytest_runtest_setup(self, __multicall__, item): + __multicall__.execute() + if not isinstance(item, py.test.collect.Function): + return + if not getattr(item.obj, 'dont_track_allocations', False): + leakfinder.start_tracking_allocations() + + def pytest_runtest_call(self, __multicall__, item): + __multicall__.execute() + if not isinstance(item, py.test.collect.Function): + return + item._success = True + + def pytest_runtest_teardown(self, __multicall__, item): + __multicall__.execute() + if not isinstance(item, py.test.collect.Function): + return + if (not getattr(item.obj, 'dont_track_allocations', False) + and leakfinder.TRACK_ALLOCATIONS): + item._pypytest_leaks = leakfinder.stop_tracking_allocations(False) + else: # stop_tracking_allocations() already called + item._pypytest_leaks = None + + # check for leaks, but only if the test passed so far + if getattr(item, '_success', False) and item._pypytest_leaks: + raise leakfinder.MallocMismatch(item._pypytest_leaks) 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 @@ -128,7 +128,7 @@ assert passed == 1 def test_safename(): - from pypy.conftest import ExpectTestMethod + from pypy.tool.pytest.expecttest import ExpectTestMethod safe_name = ExpectTestMethod.safe_name assert safe_name(['pypy', 'tool', 'test', 'test_pytestsupport.py', _______________________________________________ pypy-commit mailing list pypy-commit@python.org http://mail.python.org/mailman/listinfo/pypy-commit