@@ -87,13 +87,13 @@ def processCLIArgs():
87
87
default = - 1 ,
88
88
help = "number of files to split expressions tests in" ,
89
89
)
90
- tests_help_msg = "The path(s) to the test case(s) to run.\n "
91
- tests_help_msg += "Example: 'test/unit', 'test/prob', and/or\n "
92
- tests_help_msg += " 'test/unit/math/prim/fun/abs_test.cpp'"
93
- parser .add_argument ("tests" , nargs = "+" , type = str , help = tests_help_msg )
94
90
f_help_msg = "Only tests with file names matching these will be executed.\n "
95
91
f_help_msg += "Example: '-f chol', '-f opencl', '-f prim'"
96
92
parser .add_argument ("-f" , type = str , default = [], action = "append" , help = f_help_msg )
93
+ changed_help = (
94
+ "Use git to determine which tests may have changed, and run only those"
95
+ )
96
+ parser .add_argument ("--changed" , action = "store_true" , help = changed_help )
97
97
parser .add_argument (
98
98
"-d" ,
99
99
"--debug" ,
@@ -127,6 +127,11 @@ def processCLIArgs():
127
127
action = "store_true" ,
128
128
help = "Build/run jumbo tests." ,
129
129
)
130
+
131
+ tests_help_msg = "The path(s) to the test case(s) to run.\n "
132
+ tests_help_msg += "Example: 'test/unit', 'test/prob', and/or\n "
133
+ tests_help_msg += " 'test/unit/math/prim/fun/abs_test.cpp'"
134
+ parser .add_argument ("tests" , nargs = "*" , type = str , help = tests_help_msg )
130
135
# And parse the command line against those rules
131
136
return parser .parse_args ()
132
137
@@ -146,6 +151,8 @@ def isWin():
146
151
147
152
batchSize = 20 if isWin () else 200
148
153
jumboSize = 5 if isWin () else 12
154
+ maxChangedTests = 20
155
+
149
156
150
157
def mungeName (name ):
151
158
"""Set up the makefile target name"""
@@ -306,6 +313,38 @@ def findTests(base_path, filter_names, do_jumbo=False):
306
313
return tests
307
314
308
315
316
+ def findChangedTests (debug ):
317
+ import subprocess
318
+
319
+ changed_files = subprocess .run (
320
+ ["git" , "diff" , "--name-only" , "origin/develop...HEAD" ], text = True , capture_output = True
321
+ ).stdout .splitlines ()
322
+ if debug :
323
+ print ("Changed files:" , changed_files )
324
+
325
+ # changes in prim should also test other non-prim signatures
326
+ test_deps = {"prim/" : ["prim/" , "rev/" , "mix/" , "fwd/" ], "rev/" : ["rev/" , "mix/" ], "fwd/" : ["fwd/" , "mix/" ]}
327
+
328
+ changed_tests = set ()
329
+ for f in changed_files :
330
+ if 'stan/' in f and f .endswith ('.hpp' ) and 'opencl' not in f : # changed fn
331
+ maybe_test = f .replace ('stan/' , 'test/unit/' ).replace ('.hpp' , testsfx )
332
+ for (path , replacements ) in test_deps .items ():
333
+ if path in maybe_test :
334
+ for rep in replacements :
335
+ maybe_test = maybe_test .replace (path , rep )
336
+ if os .path .exists (maybe_test ):
337
+ changed_tests .add (maybe_test )
338
+
339
+ if f .endswith (testsfx ) and 'opencl' not in f :
340
+ changed_tests .add (f )
341
+
342
+ if len (changed_tests ) > maxChangedTests :
343
+ stopErr ("Number of changed tests excluded maximum, not running priority tests" , 0 )
344
+
345
+ return list (map (mungeName , changed_tests ))
346
+
347
+
309
348
def batched (tests ):
310
349
return [tests [i : i + batchSize ] for i in range (0 , len (tests ), batchSize )]
311
350
@@ -357,30 +396,37 @@ def main():
357
396
stan_mpi = "STAN_MPI" in f .read ()
358
397
except IOError :
359
398
stan_mpi = False
360
- # pass 0: generate all auto-generated tests
361
- if any ("test/prob" in arg for arg in inputs .tests ):
362
- generateTests (inputs .j )
363
- tests = inputs .tests
364
399
365
400
jumboFiles = []
366
- if inputs .do_jumbo :
367
- jumboFiles = generateJumboTests (tests , debug = inputs .debug )
368
- try :
401
+
402
+ if inputs .changed :
403
+ tests = findChangedTests (inputs .debug )
404
+ else :
405
+ # pass 0: generate all auto-generated tests
406
+ if any ("test/prob" in arg for arg in inputs .tests ):
407
+ generateTests (inputs .j )
408
+
409
+ if inputs .do_jumbo :
410
+ jumboFiles = generateJumboTests (inputs .tests )
369
411
if inputs .e == - 1 :
370
412
if inputs .j == 1 :
371
413
num_expr_test_files = 1
372
414
else :
373
415
num_expr_test_files = inputs .j * 4
374
416
else :
375
417
num_expr_test_files = inputs .e
376
- handleExpressionTests (tests , inputs .only_functions , num_expr_test_files )
418
+ handleExpressionTests (inputs . tests , inputs .only_functions , num_expr_test_files )
377
419
378
420
tests = findTests (inputs .tests , inputs .f , inputs .do_jumbo )
379
421
380
- if not tests :
381
- stopErr ("No matching tests found." , - 1 )
382
- if inputs .debug :
383
- print ("Collected the following tests:\n " , tests )
422
+ if not tests :
423
+ if inputs .changed :
424
+ stopErr ("No changed tests were found!" , 0 )
425
+ stopErr (
426
+ "No matching tests found. Check the path passed on the command line" , - 1
427
+ )
428
+
429
+ try :
384
430
# pass 1: make test executables
385
431
for batch in batched (tests ):
386
432
if inputs .debug :
@@ -399,5 +445,6 @@ def main():
399
445
cleanupJumboTests (jumboFiles )
400
446
pass
401
447
448
+
402
449
if __name__ == "__main__" :
403
450
main ()
0 commit comments