Skip to content

Commit 23e8edb

Browse files
authored
Merge pull request #714 from jamestiotio/shuffle_tests
Add support for randomizing test execution order
2 parents 64e68d9 + b0bcdb5 commit 23e8edb

File tree

2 files changed

+106
-6
lines changed

2 files changed

+106
-6
lines changed

auto/generate_test_runner.rb

+90-6
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,10 +235,34 @@ 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]
@@ -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
@@ -439,18 +493,46 @@ def create_main(output, filename, tests, used_mocks)
439493
else
440494
output.puts(" UnityBegin(\"#{filename.gsub(/\\/, '\\\\\\')}\");")
441495
end
496+
if @options[:shuffle_tests]
497+
output.puts
498+
if @options[:rng_seed] == 0
499+
output.puts(' srand(time(NULL));')
500+
else
501+
output.puts(" srand(#{@options[:rng_seed]});")
502+
end
503+
end
504+
output.puts
505+
output.puts(" int number_of_tests = #{count_tests(tests)};")
506+
output.puts(' struct UnityRunTestParameters run_test_params_arr[number_of_tests];')
507+
output.puts
508+
idx = 0
442509
tests.each do |test|
443510
if (!@options[:use_param_tests]) || test[:args].nil? || test[:args].empty?
444-
output.puts(" run_test(#{test[:test]}, \"#{test[:test]}\", #{test[:line_number]});")
511+
output.puts(" run_test_params_arr[#{idx}].func = #{test[:test]};")
512+
output.puts(" run_test_params_arr[#{idx}].name = \"#{test[:test]}\";")
513+
output.puts(" run_test_params_arr[#{idx}].line_num = #{test[:line_number]};")
514+
idx += 1
445515
else
446-
test[:args].each.with_index(1) do |args, idx|
447-
wrapper = "runner_args#{idx}_#{test[:test]}"
516+
test[:args].each.with_index(1) do |args, arg_idx|
517+
wrapper = "runner_args#{arg_idx}_#{test[:test]}"
448518
testname = "#{test[:test]}(#{args})".dump
449-
output.puts(" run_test(#{wrapper}, #{testname}, #{test[:line_number]});")
519+
output.puts(" run_test_params_arr[#{idx}].func = #{wrapper};")
520+
output.puts(" run_test_params_arr[#{idx}].name = #{testname};")
521+
output.puts(" run_test_params_arr[#{idx}].line_num = #{test[:line_number]};")
522+
idx += 1
450523
end
451524
end
452525
end
453526
output.puts
527+
if @options[:shuffle_tests]
528+
output.puts(' shuffleTests(run_test_params_arr, number_of_tests);')
529+
output.puts
530+
end
531+
output.puts(' for (int i = 0; i < number_of_tests; i++)')
532+
output.puts(' {')
533+
output.puts(' run_test(run_test_params_arr[i].func, run_test_params_arr[i].name, run_test_params_arr[i].line_num);')
534+
output.puts(' }')
535+
output.puts
454536
output.puts(' CMock_Guts_MemFreeFinal();') unless used_mocks.empty?
455537
if @options[:has_suite_teardown]
456538
if @options[:omit_begin_end]
@@ -536,7 +618,9 @@ def create_h_file(output, filename, tests, testfile_includes, used_mocks)
536618
' --suite_teardown="" - code to execute for teardown of entire suite',
537619
' --use_param_tests=1 - enable parameterized tests (disabled by default)',
538620
' --omit_begin_end=1 - omit calls to UnityBegin and UNITY_END (disabled by default)',
539-
' --header_file="" - path/name of test header file to generate too'].join("\n")
621+
' --header_file="" - path/name of test header file to generate too',
622+
' --shuffle_tests=1 - enable shuffling of the test execution order (disabled by default)',
623+
' --rng_seed=1 - seed value for randomization of test execution order'].join("\n")
540624
exit 1
541625
end
542626

docs/UnityHelperScriptsGuide.md

+16
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,22 @@ Unity test state setup and cleanup.
277277

278278
This option can also be specified at the command prompt as `--omit_begin_end`
279279

280+
##### `:shuffle_tests`
281+
282+
If `true`, the test execution order will be shuffled. Is `false` by default.
283+
284+
This option can also be specified at the command prompt as `--shuffle_tests`
285+
286+
##### `:rng_seed`
287+
288+
If set to some positive integer value, said value will be used as the seed value passed
289+
to the `srand` function. Otherwise, if not set to any value, `time(NULL)` will be used
290+
as the seed value.
291+
292+
Only applicable if `:shuffle_tests` is set to `true`.
293+
294+
This option can also be specified at the command prompt as `--rng_seed`
295+
280296
#### Parameterized tests provided macros
281297

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

0 commit comments

Comments
 (0)