Skip to content

Commit fbd6120

Browse files
committed
Add support for randomizing test execution order
This commit reintroduces the option to shuffle the test execution order into the test runner. This has been tested with the temp_sensor example project in Ceedling. Signed-off-by: James Raphael Tiovalen <[email protected]>
1 parent 64939db commit fbd6120

File tree

1 file changed

+89
-8
lines changed

1 file changed

+89
-8
lines changed

auto/generate_test_runner.rb

Lines changed: 89 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ def self.default_options
4747
use_param_tests: false,
4848
use_system_files: true,
4949
include_extensions: '(?:hpp|hh|H|h)',
50-
source_extensions: '(?:cpp|cc|ino|C|c)'
50+
source_extensions: '(?:cpp|cc|ino|C|c)',
51+
shuffle_tests: false,
52+
rng_seed: 0
5153
}
5254
end
5355

@@ -90,6 +92,7 @@ def run(input_file, output_file, options = nil)
9092
def generate(input_file, output_file, tests, used_mocks, testfile_includes)
9193
File.open(output_file, 'w') do |output|
9294
create_header(output, used_mocks, testfile_includes)
95+
create_run_test_params_struct(output)
9396
create_externs(output, tests, used_mocks)
9497
create_mock_management(output, used_mocks)
9598
create_setup(output)
@@ -99,6 +102,7 @@ def generate(input_file, output_file, tests, used_mocks, testfile_includes)
99102
create_reset(output)
100103
create_run_test(output) unless tests.empty?
101104
create_args_wrappers(output, tests)
105+
create_shuffle_tests(output) if @options[:shuffle_tests]
102106
create_main(output, input_file, tests, used_mocks)
103107
end
104108

@@ -231,16 +235,40 @@ def find_setup_and_teardown(source)
231235
@options[:has_suite_teardown] ||= (source =~ /int\s+suiteTearDown\s*\(int\s+([a-zA-Z0-9_])+\s*\)/)
232236
end
233237

238+
def count_tests(tests)
239+
if (@options[:use_param_tests])
240+
idx = 0
241+
tests.each do |test|
242+
if ((test[:args].nil?) or (test[:args].empty?))
243+
idx += 1
244+
else
245+
test[:args].each do |args|
246+
idx += 1
247+
end
248+
end
249+
end
250+
return idx
251+
else
252+
return tests.size
253+
end
254+
end
255+
234256
def create_header(output, mocks, testfile_includes = [])
235257
output.puts('/* AUTOGENERATED FILE. DO NOT EDIT. */')
236258
output.puts("\n/*=======Automagically Detected Files To Include=====*/")
237259
output.puts('extern "C" {') if @options[:externcincludes]
260+
if @options[:shuffle_tests]
261+
output.puts('#include <stdlib.h>')
262+
if @options[:rng_seed] == 0
263+
output.puts('#include <time.h>')
264+
end
265+
end
238266
output.puts("#include \"#{@options[:framework]}.h\"")
239267
output.puts('#include "cmock.h"') unless mocks.empty?
240268
output.puts('}') if @options[:externcincludes]
241269
if @options[:defines] && !@options[:defines].empty?
242270
output.puts("/* injected defines for unity settings, etc */")
243-
@options[:defines].each do |d|
271+
@options[:defines].each do |d|
244272
def_only = d.match(/(\w+).*/)[1]
245273
output.puts("#ifndef #{def_only}\n#define #{d}\n#endif /* #{def_only} */")
246274
end
@@ -270,6 +298,16 @@ def create_header(output, mocks, testfile_includes = [])
270298
output.puts('char* GlobalOrderError;')
271299
end
272300

301+
def create_run_test_params_struct(output)
302+
output.puts("\n/*=======Structure Used By Test Runner=====*/")
303+
output.puts('struct UnityRunTestParameters')
304+
output.puts('{')
305+
output.puts(' UnityTestFunction func;')
306+
output.puts(' const char* name;')
307+
output.puts(' UNITY_LINE_TYPE line_num;')
308+
output.puts('};')
309+
end
310+
273311
def create_externs(output, tests, _mocks)
274312
output.puts("\n/*=======External Functions This Runner Calls=====*/")
275313
output.puts("extern void #{@options[:setup_name]}(void);")
@@ -392,6 +430,22 @@ def create_args_wrappers(output, tests)
392430
end
393431
end
394432

433+
def create_shuffle_tests(output)
434+
output.puts("\n/*=======Shuffle Test Order=====*/")
435+
output.puts('static void shuffleTests(struct UnityRunTestParameters run_test_params_arr[], int num_of_tests)')
436+
output.puts('{')
437+
438+
# Use Fisher-Yates shuffle algorithm
439+
output.puts(' for (int i = 0; i < num_of_tests - 1; i++)')
440+
output.puts(' {')
441+
output.puts(' int j = (rand() % (num_of_tests - i)) + i;')
442+
output.puts(' struct UnityRunTestParameters temp = run_test_params_arr[i];')
443+
output.puts(' run_test_params_arr[i] = run_test_params_arr[j];')
444+
output.puts(' run_test_params_arr[j] = temp;')
445+
output.puts(' }')
446+
output.puts('}')
447+
end
448+
395449
def create_main(output, filename, tests, used_mocks)
396450
output.puts("\n/*=======MAIN=====*/")
397451
main_name = @options[:main_name].to_sym == :auto ? "main_#{filename.gsub('.c', '')}" : (@options[:main_name]).to_s
@@ -437,18 +491,43 @@ def create_main(output, filename, tests, used_mocks)
437491
else
438492
output.puts(" UnityBegin(\"#{filename.gsub(/\\/, '\\\\\\')}\");")
439493
end
440-
tests.each do |test|
494+
if @options[:shuffle_tests]
495+
output.puts
496+
if @options[:rng_seed] == 0
497+
output.puts(' srand(time(NULL));')
498+
else
499+
output.puts(" srand(#{@options[:rng_seed]});")
500+
end
501+
end
502+
output.puts
503+
output.puts(" int number_of_tests = #{count_tests(tests)};")
504+
output.puts(' struct UnityRunTestParameters run_test_params_arr[number_of_tests];')
505+
output.puts
506+
tests.each.with_index() do |test, idx|
441507
if (!@options[:use_param_tests]) || test[:args].nil? || test[:args].empty?
442-
output.puts(" run_test(#{test[:test]}, \"#{test[:test]}\", #{test[:line_number]});")
508+
output.puts(" run_test_params_arr[#{idx}].func = #{test[:test]};")
509+
output.puts(" run_test_params_arr[#{idx}].name = \"#{test[:test]}\";")
510+
output.puts(" run_test_params_arr[#{idx}].line_num = #{test[:line_number]};")
443511
else
444-
test[:args].each.with_index(1) do |args, idx|
445-
wrapper = "runner_args#{idx}_#{test[:test]}"
512+
test[:args].each.with_index(1) do |args, arg_idx|
513+
wrapper = "runner_args#{arg_idx}_#{test[:test]}"
446514
testname = "#{test[:test]}(#{args})".dump
447-
output.puts(" run_test(#{wrapper}, #{testname}, #{test[:line_number]});")
515+
output.puts(" run_test_params_arr[#{idx}].func = #{wrapper};")
516+
output.puts(" run_test_params_arr[#{idx}].name = #{testname};")
517+
output.puts(" run_test_params_arr[#{idx}].line_num = #{test[:line_number]};")
448518
end
449519
end
450520
end
451521
output.puts
522+
if @options[:shuffle_tests]
523+
output.puts(' shuffleTests(run_test_params_arr, number_of_tests);')
524+
output.puts
525+
end
526+
output.puts(' for (int i = 0; i < number_of_tests; i++)')
527+
output.puts(' {')
528+
output.puts(' run_test(run_test_params_arr[i].func, run_test_params_arr[i].name, run_test_params_arr[i].line_num);')
529+
output.puts(' }')
530+
output.puts
452531
output.puts(' CMock_Guts_MemFreeFinal();') unless used_mocks.empty?
453532
if @options[:has_suite_teardown]
454533
if @options[:omit_begin_end]
@@ -534,7 +613,9 @@ def create_h_file(output, filename, tests, testfile_includes, used_mocks)
534613
' --suite_teardown="" - code to execute for teardown of entire suite',
535614
' --use_param_tests=1 - enable parameterized tests (disabled by default)',
536615
' --omit_begin_end=1 - omit calls to UnityBegin and UnityEnd (disabled by default)',
537-
' --header_file="" - path/name of test header file to generate too'].join("\n")
616+
' --header_file="" - path/name of test header file to generate too',
617+
' --shuffle_tests=1 - enable shuffling of the test execution order (disabled by default)',
618+
' --rng_seed=1 - seed value for randomization of test execution order'].join("\n")
538619
exit 1
539620
end
540621

0 commit comments

Comments
 (0)