11
11
import gc
12
12
import inspect
13
13
import json
14
+ import os
14
15
import random
15
16
import sys
16
17
import time as time_module
17
- from collections import defaultdict
18
18
from functools import wraps
19
19
from pathlib import Path
20
20
@@ -61,9 +61,6 @@ def pytest_configure(config):
61
61
# be enough: https://github.com/pytest-dev/pytest-xdist/issues/271.
62
62
# Need a lockfile or equivalent.
63
63
64
- assert not hasattr (
65
- config , "workerinput"
66
- ), "--hypothesis-benchmark-shrinks does not currently support xdist. Run without -n"
67
64
assert config .getoption (
68
65
"--hypothesis-benchmark-output"
69
66
), "must specify shrinking output file"
@@ -222,27 +219,35 @@ def pytest_runtest_call(item):
222
219
)
223
220
224
221
225
- shrink_calls = defaultdict (list )
226
- shrink_time = defaultdict (list )
227
222
timer = time_module .process_time
228
223
229
224
230
- def _benchmark_shrinks (item ):
225
+ def _worker_path (session : pytest .Session ) -> Path :
226
+ return (
227
+ Path (session .config .getoption ("--hypothesis-benchmark-output" )).parent
228
+ # https://pytest-xdist.readthedocs.io/en/stable/how-to.html#envvar-PYTEST_XDIST_WORKER
229
+ / f"shrinking_results_{ os .environ ['PYTEST_XDIST_WORKER' ]} .json"
230
+ )
231
+
232
+
233
+ def _benchmark_shrinks (item : pytest .Function ) -> None :
231
234
from hypothesis .internal .conjecture .shrinker import Shrinker
232
235
233
236
# this isn't perfect, but it is cheap!
234
237
if "minimal(" not in inspect .getsource (item .function ):
235
238
pytest .skip ("(probably) does not call minimal()" )
236
239
237
240
actual_shrink = Shrinker .shrink
241
+ shrink_calls = []
242
+ shrink_time = []
238
243
239
244
def shrink (self , * args , ** kwargs ):
245
+ nonlocal shrink_calls
246
+ nonlocal shrink_time
240
247
start_t = timer ()
241
248
result = actual_shrink (self , * args , ** kwargs )
242
- # remove leading hypothesis-python/tests/...
243
- nodeid = item .nodeid .rsplit ("/" , 1 )[1 ]
244
- shrink_calls [nodeid ].append (self .engine .call_count - self .initial_calls )
245
- shrink_time [nodeid ].append (timer () - start_t )
249
+ shrink_calls .append (self .engine .call_count - self .initial_calls )
250
+ shrink_time .append (timer () - start_t )
246
251
return result
247
252
248
253
monkeypatch = MonkeyPatch ()
@@ -256,15 +261,39 @@ def shrink(self, *args, **kwargs):
256
261
257
262
monkeypatch .undo ()
258
263
264
+ # remove leading hypothesis-python/tests/...
265
+ nodeid = item .nodeid .rsplit ("/" , 1 )[1 ]
266
+
267
+ results_p = _worker_path (item .session )
268
+ if not results_p .exists ():
269
+ results_p .write_text (json .dumps ({"calls" : {}, "time" : {}}))
270
+
271
+ data = json .loads (results_p .read_text ())
272
+ data ["calls" ][nodeid ] = shrink_calls
273
+ data ["time" ][nodeid ] = shrink_time
274
+ results_p .write_text (json .dumps (data ))
275
+
259
276
260
277
def pytest_sessionfinish (session , exitstatus ):
261
278
if not (mode := session .config .getoption ("--hypothesis-benchmark-shrinks" )):
262
279
return
263
- p = Path (session .config .getoption ("--hypothesis-benchmark-output" ))
264
- results = {mode : {"calls" : shrink_calls , "time" : shrink_time }}
265
- if not p .exists ():
266
- p .write_text (json .dumps (results ))
280
+ # only run on the controller process, not the workers
281
+ if hasattr (session .config , "workerinput" ):
282
+ return
283
+
284
+ results = {"calls" : {}, "time" : {}}
285
+ output_p = Path (session .config .getoption ("--hypothesis-benchmark-output" ))
286
+ for p in output_p .parent .iterdir ():
287
+ if p .name .startswith ("shrinking_results_" ):
288
+ worker_results = json .loads (p .read_text ())
289
+ results ["calls" ] |= worker_results ["calls" ]
290
+ results ["time" ] |= worker_results ["time" ]
291
+ p .unlink ()
292
+
293
+ results = {mode : results }
294
+ if not output_p .exists ():
295
+ output_p .write_text (json .dumps (results ))
267
296
else :
268
- data = json .loads (p .read_text ())
297
+ data = json .loads (output_p .read_text ())
269
298
data [mode ] = results [mode ]
270
- p .write_text (json .dumps (data ))
299
+ output_p .write_text (json .dumps (data ))
0 commit comments