parent
							
								
									b552f6eb46
								
							
						
					
					
						commit
						37976be529
					
				|  | @ -18,8 +18,8 @@ Changes between 1.0.0 and 1.0.1 | |||
| 
 | ||||
| * terser reporting of collection error tracebacks | ||||
| 
 | ||||
| * streamlined internal plugin arch code, renamed of internal methods  | ||||
|   and argnames (related to py/_com.py multicall/plugin) | ||||
| * simplified multicall mechanism and plugin architecture,  | ||||
|   renamed some internal methods and argnames  | ||||
| 
 | ||||
| Changes between 1.0.0b9 and 1.0.0 | ||||
| ===================================== | ||||
|  |  | |||
|  | @ -90,13 +90,13 @@ Available py.test hooks | |||
| ==================================== | ||||
| 
 | ||||
| py.test calls hooks functions to implement its `test collection`_, running and  | ||||
| reporting process.  Upon loading of a plugin py.test performs | ||||
| strict checking on contained hook functions.  Function and argument names  | ||||
| need to match exactly one of `hook definition specification`_. It thus  | ||||
| provides useful error reporting on mistyped hook or argument names | ||||
| and minimizes version incompatibilites.  Below you find some introductory  | ||||
| information on particular hooks.  It's sensible to look at existing  | ||||
| plugins so see example usages and start off with your own plugin.  | ||||
| reporting process.  When py.test loads a plugin it validates that all hook functions | ||||
| conform to the `hook definition specification`_.  The hook function name and its  | ||||
| argument names need to match exactly but it is allowed for an implementation | ||||
| to accept *less* parameters.  You'll get useful errors on mistyped hook or  | ||||
| argument names.  Read on for some introductory information on particular  | ||||
| hooks.  It's sensible to look at existing plugins so see example usages  | ||||
| and start off with your own plugin.  | ||||
| 
 | ||||
| .. _`hook definition specification`: plugin/hookspec.html | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										36
									
								
								py/_com.py
								
								
								
								
							
							
						
						
									
										36
									
								
								py/_com.py
								
								
								
								
							|  | @ -9,7 +9,8 @@ class MultiCall: | |||
| 
 | ||||
|     def __init__(self, methods, kwargs, firstresult=False): | ||||
|         self.methods = methods[:] | ||||
|         self.kwargs = kwargs  | ||||
|         self.kwargs = kwargs.copy() | ||||
|         self.kwargs['__multicall__'] = self | ||||
|         self.results = [] | ||||
|         self.firstresult = firstresult | ||||
| 
 | ||||
|  | @ -20,28 +21,30 @@ class MultiCall: | |||
|     def execute(self): | ||||
|         while self.methods: | ||||
|             method = self.methods.pop() | ||||
|             res = self._call1(method) | ||||
|             kwargs = self.getkwargs(method) | ||||
|             res = method(**kwargs) | ||||
|             if res is not None: | ||||
|                 self.results.append(res)  | ||||
|                 if self.firstresult: | ||||
|                     break | ||||
|                     return res | ||||
|         if not self.firstresult: | ||||
|             return self.results  | ||||
|         if self.results: | ||||
|             return self.results[-1] | ||||
| 
 | ||||
|     def _call1(self, method): | ||||
|         kwargs = self.kwargs | ||||
|         if '__call__' in varnames(method): | ||||
|             kwargs = kwargs.copy() | ||||
|             kwargs['__call__'] = self | ||||
|         return method(**kwargs) | ||||
|     def getkwargs(self, method): | ||||
|         kwargs = {} | ||||
|         for argname in varnames(method): | ||||
|             try: | ||||
|                 kwargs[argname] = self.kwargs[argname] | ||||
|             except KeyError: | ||||
|                 pass # might be optional param | ||||
|         return kwargs  | ||||
| 
 | ||||
| def varnames(rawcode): | ||||
|     ismethod = hasattr(rawcode, 'im_self') | ||||
|     rawcode = getattr(rawcode, 'im_func', rawcode) | ||||
|     rawcode = getattr(rawcode, 'func_code', rawcode) | ||||
|     try: | ||||
|         return rawcode.co_varnames  | ||||
|         return rawcode.co_varnames[ismethod:] | ||||
|     except AttributeError: | ||||
|         return () | ||||
| 
 | ||||
|  | @ -101,9 +104,6 @@ class HookRelay: | |||
|     def _performcall(self, name, multicall): | ||||
|         return multicall.execute() | ||||
|          | ||||
|     def __repr__(self): | ||||
|         return "<HookRelay %r %r>" %(self._hookspecs, self._registry) | ||||
| 
 | ||||
| class HookCaller: | ||||
|     def __init__(self, hookrelay, name, firstresult, extralookup=()): | ||||
|         self.hookrelay = hookrelay  | ||||
|  | @ -112,12 +112,10 @@ class HookCaller: | |||
|         self.extralookup = extralookup and [extralookup] or () | ||||
| 
 | ||||
|     def __repr__(self): | ||||
|         return "<HookCaller %r firstresult=%s %s>" %( | ||||
|             self.name, self.firstresult, self.hookrelay) | ||||
|         return "<HookCaller %r>" %(self.name,) | ||||
| 
 | ||||
|     def __call__(self, **kwargs): | ||||
|         methods = self.hookrelay._getmethods(self.name,  | ||||
|             extralookup=self.extralookup) | ||||
|         methods = self.hookrelay._getmethods(self.name, self.extralookup) | ||||
|         mc = MultiCall(methods, kwargs, firstresult=self.firstresult) | ||||
|         return self.hookrelay._performcall(self.name, mc) | ||||
|     | ||||
|  |  | |||
|  | @ -1,7 +1,17 @@ | |||
| 
 | ||||
| import py | ||||
| import os | ||||
| from py._com import Registry, MultiCall, HookRelay | ||||
| from py.__._com import Registry, MultiCall, HookRelay, varnames | ||||
| 
 | ||||
| def test_varnames(): | ||||
|     def f(x): | ||||
|         pass | ||||
|     class A: | ||||
|         def f(self, y): | ||||
|             pass | ||||
|     assert varnames(f) == ("x",) | ||||
|     assert varnames(A.f) == ('y',) | ||||
|     assert varnames(A().f) == ('y',) | ||||
|      | ||||
| class TestMultiCall: | ||||
|     def test_uses_copy_of_methods(self): | ||||
|  | @ -14,15 +24,15 @@ class TestMultiCall: | |||
| 
 | ||||
|     def test_call_passing(self): | ||||
|         class P1: | ||||
|             def m(self, __call__, x): | ||||
|                 assert len(__call__.results) == 1 | ||||
|                 assert not __call__.methods | ||||
|             def m(self, __multicall__, x): | ||||
|                 assert len(__multicall__.results) == 1 | ||||
|                 assert not __multicall__.methods | ||||
|                 return 17 | ||||
| 
 | ||||
|         class P2: | ||||
|             def m(self, __call__, x): | ||||
|                 assert __call__.results == [] | ||||
|                 assert __call__.methods | ||||
|             def m(self, __multicall__, x): | ||||
|                 assert __multicall__.results == [] | ||||
|                 assert __multicall__.methods | ||||
|                 return 23  | ||||
|                 | ||||
|         p1 = P1()  | ||||
|  | @ -37,24 +47,23 @@ class TestMultiCall: | |||
|     def test_keyword_args(self): | ||||
|         def f(x):  | ||||
|             return x + 1 | ||||
|         multicall = MultiCall([f], dict(x=23)) | ||||
|         class A: | ||||
|             def f(self, x, y): | ||||
|                 return x + y | ||||
|         multicall = MultiCall([f, A().f], dict(x=23, y=24)) | ||||
|         assert "'x': 23" in repr(multicall) | ||||
|         assert "'y': 24" in repr(multicall) | ||||
|         reslist = multicall.execute() | ||||
|         assert reslist == [24] | ||||
|         assert "1 results" in repr(multicall) | ||||
|         assert reslist == [24+23, 24] | ||||
|         assert "2 results" in repr(multicall) | ||||
| 
 | ||||
|     def test_optionalcallarg(self): | ||||
|         class P1: | ||||
|             def m(self, x): | ||||
|                 return x | ||||
|         call = MultiCall([P1().m], dict(x=23)) | ||||
|         assert "23" in repr(call) | ||||
|         assert call.execute() == [23] | ||||
|         call = MultiCall([P1().m], dict(x=23), firstresult=True) | ||||
|     def test_keywords_call_error(self): | ||||
|         multicall = MultiCall([lambda x: x], {}) | ||||
|         py.test.raises(TypeError, "multicall.execute()") | ||||
| 
 | ||||
|     def test_call_subexecute(self): | ||||
|         def m(__call__): | ||||
|             subresult = __call__.execute() | ||||
|         def m(__multicall__): | ||||
|             subresult = __multicall__.execute() | ||||
|             return subresult + 1 | ||||
| 
 | ||||
|         def n(): | ||||
|  |  | |||
|  | @ -77,8 +77,8 @@ class DSession(Session): | |||
|         self.item2nodes = {} | ||||
|         super(DSession, self).__init__(config=config) | ||||
| 
 | ||||
|     #def pytest_configure(self, __call__, config): | ||||
|     #    __call__.execute() | ||||
|     #def pytest_configure(self, __multicall__, config): | ||||
|     #    __multicall__.execute() | ||||
|     #    try: | ||||
|     #        config.getxspecs() | ||||
|     #    except config.Error: | ||||
|  |  | |||
|  | @ -181,11 +181,11 @@ class CaptureManager: | |||
|                 capfuncarg._finalize() | ||||
|             del self._capturing_funcargs | ||||
| 
 | ||||
|     def pytest_make_collect_report(self, __call__, collector): | ||||
|     def pytest_make_collect_report(self, __multicall__, collector): | ||||
|         method = self._getmethod(collector.config, collector.fspath) | ||||
|         self.resumecapture(method) | ||||
|         try: | ||||
|             rep = __call__.execute() | ||||
|             rep = __multicall__.execute() | ||||
|         finally: | ||||
|             outerr = self.suspendcapture() | ||||
|         addouterr(rep, outerr) | ||||
|  | @ -204,11 +204,11 @@ class CaptureManager: | |||
|     def pytest_runtest_teardown(self, item): | ||||
|         self.resumecapture_item(item) | ||||
| 
 | ||||
|     def pytest__teardown_final(self, __call__, session): | ||||
|     def pytest__teardown_final(self, __multicall__, session): | ||||
|         method = self._getmethod(session.config, None) | ||||
|         self.resumecapture(method) | ||||
|         try: | ||||
|             rep = __call__.execute() | ||||
|             rep = __multicall__.execute() | ||||
|         finally: | ||||
|             outerr = self.suspendcapture() | ||||
|         if rep: | ||||
|  | @ -219,9 +219,9 @@ class CaptureManager: | |||
|         if hasattr(self, '_capturing'): | ||||
|             self.suspendcapture() | ||||
| 
 | ||||
|     def pytest_runtest_makereport(self, __call__, item, call): | ||||
|     def pytest_runtest_makereport(self, __multicall__, item, call): | ||||
|         self.deactivate_funcargs() | ||||
|         rep = __call__.execute() | ||||
|         rep = __multicall__.execute() | ||||
|         outerr = self.suspendcapture() | ||||
|         outerr = (item.outerr[0] + outerr[0], item.outerr[1] + outerr[1]) | ||||
|         if not rep.passed: | ||||
|  |  | |||
|  | @ -2,8 +2,8 @@ | |||
| 
 | ||||
| import py | ||||
| 
 | ||||
| def pytest_pyfunc_call(__call__, pyfuncitem): | ||||
|     if not __call__.execute(): | ||||
| def pytest_pyfunc_call(__multicall__, pyfuncitem): | ||||
|     if not __multicall__.execute(): | ||||
|         testfunction = pyfuncitem.obj  | ||||
|         if pyfuncitem._isyieldedfunction(): | ||||
|             testfunction(*pyfuncitem._args) | ||||
|  |  | |||
|  | @ -32,10 +32,10 @@ class Execnetcleanup: | |||
|         #for gw in l: | ||||
|         #    gw.join() | ||||
|          | ||||
|     def pytest_pyfunc_call(self, __call__, pyfuncitem): | ||||
|     def pytest_pyfunc_call(self, __multicall__, pyfuncitem): | ||||
|         if self._gateways is not None: | ||||
|             gateways = self._gateways[:] | ||||
|             res = __call__.execute() | ||||
|             res = __multicall__.execute() | ||||
|             while len(self._gateways) > len(gateways): | ||||
|                 self._gateways[-1].exit() | ||||
|             return res | ||||
|  |  | |||
|  | @ -47,7 +47,7 @@ import py | |||
| import inspect | ||||
| import sys | ||||
| 
 | ||||
| def pytest_runtest_makereport(__call__, item, call): | ||||
| def pytest_runtest_makereport(__multicall__, item, call): | ||||
|     SkipTest = getattr(sys.modules.get('nose', None), 'SkipTest', None) | ||||
|     if SkipTest: | ||||
|         if call.excinfo and call.excinfo.errisinstance(SkipTest): | ||||
|  |  | |||
|  | @ -33,9 +33,9 @@ def pytest_addoption(parser): | |||
|         type="choice", choices=['failed', 'all'],  | ||||
|         help="send failed|all info to Pocoo pastebin service.") | ||||
| 
 | ||||
| def pytest_configure(__call__, config): | ||||
| def pytest_configure(__multicall__, config): | ||||
|     import tempfile | ||||
|     __call__.execute() | ||||
|     __multicall__.execute() | ||||
|     if config.option.pastebin == "all": | ||||
|         config._pastebinfile = tempfile.TemporaryFile() | ||||
|         tr = config.pluginmanager.impname2plugin['terminalreporter'] | ||||
|  |  | |||
|  | @ -250,8 +250,8 @@ class TerminalReporter: | |||
|         for i, testarg in py.builtin.enumerate(self.config.args): | ||||
|             self.write_line("test object %d: %s" %(i+1, testarg)) | ||||
| 
 | ||||
|     def pytest_sessionfinish(self, __call__, session, exitstatus): | ||||
|         __call__.execute()  | ||||
|     def pytest_sessionfinish(self, exitstatus, __multicall__): | ||||
|         __multicall__.execute()  | ||||
|         self._tw.line("") | ||||
|         if exitstatus in (0, 1, 2): | ||||
|             self.summary_errors() | ||||
|  |  | |||
|  | @ -19,12 +19,12 @@ when it fails. Instead terminal reporting will list it in the | |||
| 
 | ||||
| import py | ||||
| 
 | ||||
| def pytest_runtest_makereport(__call__, item, call): | ||||
| def pytest_runtest_makereport(__multicall__, item, call): | ||||
|     if call.when != "call": | ||||
|         return | ||||
|     if hasattr(item, 'obj') and hasattr(item.obj, 'func_dict'): | ||||
|         if 'xfail' in item.obj.func_dict: | ||||
|             res = __call__.execute() | ||||
|             res = __multicall__.execute() | ||||
|             if call.excinfo: | ||||
|                 res.skipped = True | ||||
|                 res.failed = res.passed = False | ||||
|  |  | |||
|  | @ -134,15 +134,16 @@ class PluginManager(object): | |||
|                 fail = True | ||||
|             else: | ||||
|                 method_args = getargs(method) | ||||
|                 if '__call__' in method_args: | ||||
|                     method_args.remove('__call__') | ||||
|                 if '__multicall__' in method_args: | ||||
|                     method_args.remove('__multicall__') | ||||
|                 hook = hooks[name] | ||||
|                 hookargs = getargs(hook) | ||||
|                 for arg, hookarg in zip(method_args, hookargs): | ||||
|                     if arg != hookarg:  | ||||
|                         Print("argument mismatch: %r != %r"  %(arg, hookarg)) | ||||
|                         Print("actual  : %s" %(formatdef(method))) | ||||
|                         Print("required:", formatdef(hook)) | ||||
|                 for arg in method_args: | ||||
|                     if arg not in hookargs: | ||||
|                         Print("argument %r not available"  %(arg, )) | ||||
|                         Print("actual definition: %s" %(formatdef(method))) | ||||
|                         Print("available hook arguments: %s" %  | ||||
|                                 ", ".join(hookargs)) | ||||
|                         fail = True | ||||
|                         break  | ||||
|                 #if not fail: | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue