File tree Expand file tree Collapse file tree 4 files changed +92
-13
lines changed Expand file tree Collapse file tree 4 files changed +92
-13
lines changed Original file line number Diff line number Diff line change @@ -30,5 +30,6 @@ exclude_lines =
30
30
^\s*\.\.\.\s*$
31
31
# ignore coverage on ruff line continued
32
32
^\s*def.*:\ \.\.\.\s*$
33
+ .*: ...$
33
34
# ignore coverage on pass lines
34
35
^\s*passs*$
Original file line number Diff line number Diff line change @@ -93,25 +93,20 @@ def _multicall(
93
93
for hook_impl in reversed (hook_impls ):
94
94
try :
95
95
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
98
99
if argname not in caller_kwargs :
99
100
raise HookCallError (
100
101
f"hook call must provide argument { argname !r} "
101
- )
102
+ ) from e
102
103
103
104
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 )
110
109
111
- next (function_gen ) # first yield
112
- teardowns .append (function_gen )
113
- except StopIteration :
114
- _raise_wrapfail (function_gen , "did not yield" )
115
110
elif hook_impl .wrapper :
116
111
try :
117
112
# If this cast is not valid, a type error is raised below,
Original file line number Diff line number Diff line change 2
2
3
3
import pytest
4
4
5
+ import pluggy
5
6
from pluggy import HookimplMarker
6
7
from pluggy import HookspecMarker
7
8
from pluggy import PluginManager
@@ -202,3 +203,44 @@ def test_dist_facade_list_attributes() -> None:
202
203
res = dir (fc )
203
204
assert res == sorted (res )
204
205
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 1
1
from collections .abc import Iterator
2
+ from typing import Any
2
3
3
4
import pytest
4
5
@@ -326,3 +327,43 @@ def hello(self):
326
327
pm .register (Plugin3 ())
327
328
res = pm .hook .hello ()
328
329
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