Skip to content

Commit 22ed956

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. Unit tests have also been successfully executed. Signed-off-by: James Raphael Tiovalen <[email protected]>
1 parent 64939db commit 22ed956

File tree

2 files changed

+103
-7
lines changed

2 files changed

+103
-7
lines changed

auto/generate_test_runner.rb

Lines changed: 91 additions & 7 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? || 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 = num_of_tests - 1; i > 0; i--)')
440+
output.puts(' {')
441+
output.puts(' int j = rand() % (i + 1);')
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,46 @@ def create_main(output, filename, tests, used_mocks)
437491
else
438492
output.puts(" UnityBegin(\"#{filename.gsub(/\\/, '\\\\\\')}\");")
439493
end
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+
idx = 0
440507
tests.each do |test|
441508
if (!@options[:use_param_tests]) || test[:args].nil? || test[:args].empty?
442-
output.puts(" run_test(#{test[:test]}, \"#{test[:test]}\", #{test[:line_number]});")
509+
output.puts(" run_test_params_arr[#{idx}].func = #{test[:test]};")
510+
output.puts(" run_test_params_arr[#{idx}].name = \"#{test[:test]}\";")
511+
output.puts(" run_test_params_arr[#{idx}].line_num = #{test[:line_number]};")
512+
idx += 1
443513
else
444-
test[:args].each.with_index(1) do |args, idx|
445-
wrapper = "runner_args#{idx}_#{test[:test]}"
514+
test[:args].each.with_index(1) do |args, arg_idx|
515+
wrapper = "runner_args#{arg_idx}_#{test[:test]}"
446516
testname = "#{test[:test]}(#{args})".dump
447-
output.puts(" run_test(#{wrapper}, #{testname}, #{test[:line_number]});")
517+
output.puts(" run_test_params_arr[#{idx}].func = #{wrapper};")
518+
output.puts(" run_test_params_arr[#{idx}].name = #{testname};")
519+
output.puts(" run_test_params_arr[#{idx}].line_num = #{test[:line_number]};")
520+
idx += 1
448521
end
449522
end
450523
end
451524
output.puts
525+
if @options[:shuffle_tests]
526+
output.puts(' shuffleTests(run_test_params_arr, number_of_tests);')
527+
output.puts
528+
end
529+
output.puts(' for (int i = 0; i < number_of_tests; i++)')
530+
output.puts(' {')
531+
output.puts(' run_test(run_test_params_arr[i].func, run_test_params_arr[i].name, run_test_params_arr[i].line_num);')
532+
output.puts(' }')
533+
output.puts
452534
output.puts(' CMock_Guts_MemFreeFinal();') unless used_mocks.empty?
453535
if @options[:has_suite_teardown]
454536
if @options[:omit_begin_end]
@@ -534,7 +616,9 @@ def create_h_file(output, filename, tests, testfile_includes, used_mocks)
534616
' --suite_teardown="" - code to execute for teardown of entire suite',
535617
' --use_param_tests=1 - enable parameterized tests (disabled by default)',
536618
' --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")
619+
' --header_file="" - path/name of test header file to generate too',
620+
' --shuffle_tests=1 - enable shuffling of the test execution order (disabled by default)',
621+
' --rng_seed=1 - seed value for randomization of test execution order'].join("\n")
538622
exit 1
539623
end
540624

docs/UnityHelperScriptsGuide.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,18 @@ Provide any `cdecl` for the `main()` test function. Is empty by default.
254254
If `true`, the `UnityBegin` and `UnityEnd` function will not be called for
255255
Unity test state setup and cleanup.
256256

257+
##### `:shuffle_tests`
258+
259+
If `true`, the test execution order will be shuffled. Is `false` by default.
260+
261+
##### `:rng_seed`
262+
263+
If set to some positive integer value, said value will be used as the seed value passed
264+
to the `srand` function. Otherwise, if not set to any value, `time(NULL)` will be used
265+
as the seed value.
266+
267+
Only applicable if `:shuffle_tests` is set to `true`.
268+
257269
#### Parameterized tests provided macros
258270

259271
Unity provides support for few param tests generators, that can be combined

0 commit comments

Comments
 (0)