File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -30,5 +30,6 @@ exclude_lines =
3030 ^\s*\.\.\.\s*$
3131 # ignore coverage on ruff line continued
3232 ^\s*def.*:\ \.\.\.\s*$
33+ .*: ...$
3334 # ignore coverage on pass lines
3435 ^\s*passs*$
Original file line number Diff line number Diff line change @@ -93,25 +93,20 @@ def _multicall(
9393 for hook_impl in reversed (hook_impls ):
9494 try :
9595 args = [caller_kwargs [argname ] for argname in hook_impl .argnames ]
96- except KeyError :
97- for argname in hook_impl .argnames :
96+ except KeyError as e :
97+ # coverage bug - this is tested
98+ for argname in hook_impl .argnames : # pragma: no cover
9899 if argname not in caller_kwargs :
99100 raise HookCallError (
100101 f"hook call must provide argument { argname !r} "
101- )
102+ ) from e
102103
103104 if hook_impl .hookwrapper :
104- try :
105- # If this cast is not valid, a type error is raised below,
106- # which is the desired response.
107- function_gen = run_old_style_hookwrapper (
108- hook_impl , hook_name , args
109- )
105+ function_gen = run_old_style_hookwrapper (hook_impl , hook_name , args )
106+
107+ next (function_gen ) # first yield
108+ teardowns .append (function_gen )
110109
111- next (function_gen ) # first yield
112- teardowns .append (function_gen )
113- except StopIteration :
114- _raise_wrapfail (function_gen , "did not yield" )
115110 elif hook_impl .wrapper :
116111 try :
117112 # If this cast is not valid, a type error is raised below,
Original file line number Diff line number Diff line change 22
33import pytest
44
5+ import pluggy
56from pluggy import HookimplMarker
67from pluggy import HookspecMarker
78from pluggy import PluginManager
@@ -202,3 +203,44 @@ def test_dist_facade_list_attributes() -> None:
202203 res = dir (fc )
203204 assert res == sorted (res )
204205 assert set (res ) - set (dir (fc ._dist )) == {"_dist" , "project_name" }
206+
207+
208+ def test_hookimpl_disallow_invalid_combination () -> None :
209+ decorator = hookspec (historic = True , firstresult = True )
210+ with pytest .raises (ValueError , match = "cannot have a historic firstresult hook" ):
211+ decorator (any )
212+
213+
214+ def test_hook_nonspec_call (pm : PluginManager ) -> None :
215+ class Plugin :
216+ @hookimpl
217+ def a_hook (self , passed : str , missing : int ) -> None :
218+ pass
219+
220+ pm .register (Plugin ())
221+ with pytest .raises (
222+ pluggy .HookCallError , match = "hook call must provide argument 'missing'"
223+ ):
224+ pm .hook .a_hook (passed = "a" )
225+ pm .hook .a_hook (passed = "a" , missing = "ok" )
226+
227+
228+ def test_wrapper_runtimeerror_passtrough (pm : PluginManager ) -> None :
229+ """
230+ ensure runtime-error passes trough a wrapper in case of exceptions
231+ """
232+
233+ class Fail :
234+ @hookimpl
235+ def fail_late (self ):
236+ raise RuntimeError ("this is personal" )
237+
238+ class Plugin :
239+ @hookimpl (wrapper = True )
240+ def fail_late (self ):
241+ yield
242+
243+ pm .register (Plugin ())
244+ pm .register (Fail ())
245+ with pytest .raises (RuntimeError , match = "this is personal" ):
246+ pm .hook .fail_late ()
Original file line number Diff line number Diff line change 11from collections .abc import Iterator
2+ from typing import Any
23
34import pytest
45
@@ -326,3 +327,43 @@ def hello(self):
326327 pm .register (Plugin3 ())
327328 res = pm .hook .hello ()
328329 assert [y for x in res for y in x ] == [2 , 3 , 1 ]
330+
331+
332+ @pytest .mark .parametrize (
333+ "kind" ,
334+ [
335+ pytest .param (hookimpl (wrapper = True ), id = "wrapper" ),
336+ pytest .param (hookimpl (hookwrapper = True ), id = "legacy-wrapper" ),
337+ ],
338+ )
339+ def test_wrappers_yield_twice_fails (pm : PluginManager , kind : Any ) -> None :
340+ class Plugin :
341+ @kind
342+ def wrap (self ):
343+ yield
344+ yield
345+
346+ pm .register (Plugin ())
347+ with pytest .raises (
348+ RuntimeError , match = "wrap_controller at 'wrap'.* has second yield"
349+ ):
350+ pm .hook .wrap ()
351+
352+
353+ @pytest .mark .parametrize (
354+ "kind" ,
355+ [
356+ pytest .param (hookimpl (wrapper = True ), id = "wrapper" ),
357+ pytest .param (hookimpl (hookwrapper = True ), id = "legacy-wrapper" ),
358+ ],
359+ )
360+ def test_wrappers_yield_never_fails (pm : PluginManager , kind : Any ) -> None :
361+ class Plugin :
362+ @kind
363+ def wrap (self ):
364+ if False :
365+ yield # type: ignore[unreachable]
366+
367+ pm .register (Plugin ())
368+ with pytest .raises (RuntimeError , match = "wrap_controller at 'wrap'.* did not yield" ):
369+ pm .hook .wrap ()
You can’t perform that action at this time.
0 commit comments