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