diff --git a/auto/generate_test_runner.rb b/auto/generate_test_runner.rb index ace5930..4fc83f6 100755 --- a/auto/generate_test_runner.rb +++ b/auto/generate_test_runner.rb @@ -132,8 +132,8 @@ class UnityTestRunnerGenerator lines.each_with_index do |line, _index| # find tests - next unless line =~ /^((?:\s*(?:TEST_CASE|TEST_RANGE)\s*\(.*?\)\s*)*)\s*void\s+((?:#{@options[:test_prefix]}).*)\s*\(\s*(.*)\s*\)/m - next unless line =~ /^((?:\s*(?:TEST_CASE|TEST_RANGE)\s*\(.*?\)\s*)*)\s*void\s+((?:#{@options[:test_prefix]})\w*)\s*\(\s*(.*)\s*\)/m + next unless line =~ /^((?:\s*(?:TEST_(?:CASE|RANGE|MATRIX))\s*\(.*?\)\s*)*)\s*void\s+((?:#{@options[:test_prefix]}).*)\s*\(\s*(.*)\s*\)/m + next unless line =~ /^((?:\s*(?:TEST_(?:CASE|RANGE|MATRIX))\s*\(.*?\)\s*)*)\s*void\s+((?:#{@options[:test_prefix]})\w*)\s*\(\s*(.*)\s*\)/m arguments = Regexp.last_match(1) name = Regexp.last_match(2) @@ -143,25 +143,38 @@ class UnityTestRunnerGenerator if @options[:use_param_tests] && !arguments.empty? args = [] - type_and_args = arguments.split(/TEST_(CASE|RANGE)/) + type_and_args = arguments.split(/TEST_(CASE|RANGE|MATRIX)/) for i in (1...type_and_args.length).step(2) - if type_and_args[i] == "CASE" + case type_and_args[i] + when "CASE" args << type_and_args[i + 1].sub(/^\s*\(\s*(.*?)\s*\)\s*$/m, '\1') - next - end - # RANGE - args += type_and_args[i + 1].scan(/(\[|<)\s*(-?\d+.?\d*)\s*,\s*(-?\d+.?\d*)\s*,\s*(-?\d+.?\d*)\s*(\]|>)/m).map do |arg_values_str| - exclude_end = arg_values_str[0] == '<' && arg_values_str[-1] == '>' - arg_values_str[1...-1].map do |arg_value_str| - arg_value_str.include?('.') ? arg_value_str.to_f : arg_value_str.to_i - end.push(exclude_end) - end.map do |arg_values| - Range.new(arg_values[0], arg_values[1], arg_values[3]).step(arg_values[2]).to_a - end.reduce(nil) do |result, arg_range_expanded| - result.nil? ? arg_range_expanded.map { |a| [a] } : result.product(arg_range_expanded) - end.map do |arg_combinations| - arg_combinations.flatten.join(', ') + when "RANGE" + args += type_and_args[i + 1].scan(/(\[|<)\s*(-?\d+.?\d*)\s*,\s*(-?\d+.?\d*)\s*,\s*(-?\d+.?\d*)\s*(\]|>)/m).map do |arg_values_str| + exclude_end = arg_values_str[0] == '<' && arg_values_str[-1] == '>' + arg_values_str[1...-1].map do |arg_value_str| + arg_value_str.include?('.') ? arg_value_str.to_f : arg_value_str.to_i + end.push(exclude_end) + end.map do |arg_values| + Range.new(arg_values[0], arg_values[1], arg_values[3]).step(arg_values[2]).to_a + end.reduce(nil) do |result, arg_range_expanded| + result.nil? ? arg_range_expanded.map { |a| [a] } : result.product(arg_range_expanded) + end.map do |arg_combinations| + arg_combinations.flatten.join(', ') + end + + when "MATRIX" + single_arg_regex_string = /(?:(?:"(?:\\"|[^\\])*?")+|(?:'\\?.')+|(?:[^\s\]\["'\,]|\[[\d\S_-]+\])+)/.source + args_regex = /\[((?:\s*#{single_arg_regex_string}\s*,?)*(?:\s*#{single_arg_regex_string})?\s*)\]/m + arg_elements_regex = /\s*(#{single_arg_regex_string})\s*,\s*/m + + args += type_and_args[i + 1].scan(args_regex).flatten.map do |arg_values_str| + (arg_values_str + ',').scan(arg_elements_regex) + end.reduce do |result, arg_range_expanded| + result.product(arg_range_expanded) + end.map do |arg_combinations| + arg_combinations.flatten.join(', ') + end end end end diff --git a/docs/UnityConfigurationGuide.md b/docs/UnityConfigurationGuide.md index d5e4098..88603fc 100644 --- a/docs/UnityConfigurationGuide.md +++ b/docs/UnityConfigurationGuide.md @@ -448,7 +448,7 @@ To enable it, use the following example: #define UNITY_SUPPORT_TEST_CASES ``` -You can manually provide required `TEST_CASE` or `TEST_RANGE` macro definitions +You can manually provide required `TEST_CASE`, `TEST_RANGE` or `TEST_MATRIX` macro definitions before including `unity.h`, and they won't be redefined. If you provide one of the following macros, some of default definitions will not be defined: @@ -456,8 +456,10 @@ defined: |---|---| | `UNITY_EXCLUDE_TEST_CASE` | `TEST_CASE` | | `UNITY_EXCLUDE_TEST_RANGE` | `TEST_RANGE` | +| `UNITY_EXCLUDE_TEST_MATRIX` | `TEST_MATRIX` | | `TEST_CASE` | `TEST_CASE` | | `TEST_RANGE` | `TEST_RANGE` | +| `TEST_MATRIX` | `TEST_MATRIX` | `UNITY_EXCLUDE_TEST_*` defines is not processed by test runner generator script. If you exclude one of them from definition, you should provide your own definition diff --git a/docs/UnityHelperScriptsGuide.md b/docs/UnityHelperScriptsGuide.md index 8b1e637..3c32133 100644 --- a/docs/UnityHelperScriptsGuide.md +++ b/docs/UnityHelperScriptsGuide.md @@ -296,6 +296,93 @@ TEST_CASE(4, 8, 30) TEST_CASE(4, 6, 30) ``` +##### `TEST_MATRIX` + +Test matix is an advanced generator. It single call can be converted to zero, +one or few `TEST_CASE` equivalent commands. + +That generator will create tests for all cobinations of the provided list. Each argument has to be given as a list of one or more elements in the format `[, , ..., , ]`. + +All parameters supported by the `TEST_CASE` is supported as arguments: +- Numbers incl type specifiers e.g. `<1>`, `<1u>`, `<1l>`, `<2.3>`, or `<2.3f>` +- Strings incl string concatianion e.g. `<"string">`, or `<"partial" "string">` +- Chars e.g. `<'c'>` +- Enums e.g. `` +- Elements of arrays e.g. `` + +Let's use our `test_demoParamFunction` test for checking, what ranges +will be generated for our single `TEST_RANGE` row: + +```C +TEST_MATRIX([3, 4, 7], [10, 8, 2, 1],[30u, 20.0f]) +``` + +Tests execution output will be similar to that text: + +```Log +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(3, 10, 30u):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(3, 10, 20.0f):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(3, 8, 30u):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(3, 8, 20.0f):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(3, 2, 30u):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(3, 2, 20.0f):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(3, 1, 30u):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(3, 1, 20.0f):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(4, 10, 30u):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(4, 10, 20.0f):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(4, 8, 30u):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(4, 8, 20.0f):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(4, 2, 30u):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(4, 2, 20.0f):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(4, 1, 30u):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(4, 1, 20.0f):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(7, 10, 30u):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(7, 10, 20.0f):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(7, 8, 30u):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(7, 8, 20.0f):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(7, 2, 30u):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(7, 2, 20.0f):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(7, 1, 30u):PASS +tests/test_unity_parameterizedDemo.c:18:test_demoParamFunction(7, 1, 20.0f):PASS +``` + +As we can see: + +| Parameter | Format | Count of values | +|---|---|---| +| `a` | `[3, 4, 7]` | 2 | +| `b` | `[10, 8, 2, 1]` | 4 | +| `c` | `[30u, 20.0f]` | 2 | + +We totally have 2 * 4 * 2 = 16 equal test cases, that can be written as following: + +```C +TEST_CASE(3, 10, 30u) +TEST_CASE(3, 10, 20.0f) +TEST_CASE(3, 8, 30u) +TEST_CASE(3, 8, 20.0f) +TEST_CASE(3, 2, 30u) +TEST_CASE(3, 2, 20.0f) +TEST_CASE(3, 1, 30u) +TEST_CASE(3, 1, 20.0f) +TEST_CASE(4, 10, 30u) +TEST_CASE(4, 10, 20.0f) +TEST_CASE(4, 8, 30u) +TEST_CASE(4, 8, 20.0f) +TEST_CASE(4, 2, 30u) +TEST_CASE(4, 2, 20.0f) +TEST_CASE(4, 1, 30u) +TEST_CASE(4, 1, 20.0f) +TEST_CASE(7, 10, 30u) +TEST_CASE(7, 10, 20.0f) +TEST_CASE(7, 8, 30u) +TEST_CASE(7, 8, 20.0f) +TEST_CASE(7, 2, 30u) +TEST_CASE(7, 2, 20.0f) +TEST_CASE(7, 1, 30u) +TEST_CASE(7, 1, 20.0f) +``` + ### `unity_test_summary.rb` A Unity test file contains one or more test case functions. diff --git a/src/unity.h b/src/unity.h index e321a1d..e4db314 100644 --- a/src/unity.h +++ b/src/unity.h @@ -89,7 +89,7 @@ void verifyTest(void); * - define UNITY_SUPPORT_TEST_CASES to include the TEST_CASE macro, though really it's mostly about the runner generator script * Parameterized Tests - * - you'll want to create a define of TEST_CASE(...) and/or TEST_RANGE(...) which basically evaluates to nothing + * - you'll want to create a define of TEST_CASE(...), TEST_RANGE(...) and/or TEST_MATRIX(...) which basically evaluates to nothing * Tests with Arguments * - you'll want to define UNITY_USE_COMMAND_LINE_ARGS if you have the test runner passing arguments to Unity diff --git a/src/unity_internals.h b/src/unity_internals.h index 98e298f..9f89eda 100644 --- a/src/unity_internals.h +++ b/src/unity_internals.h @@ -793,6 +793,9 @@ extern const char UnityStrErrShorthand[]; #if !defined(TEST_RANGE) && !defined(UNITY_EXCLUDE_TEST_RANGE) #define TEST_RANGE(...) #endif + #if !defined(TEST_MATRIX) && !defined(UNITY_EXCLUDE_TEST_MATRIX) + #define TEST_MATRIX(...) + #endif #endif #endif diff --git a/test/tests/test_unity_parameterized.c b/test/tests/test_unity_parameterized.c index 6b8aeb7..aa9f9c1 100644 --- a/test/tests/test_unity_parameterized.c +++ b/test/tests/test_unity_parameterized.c @@ -7,6 +7,7 @@ #include #include #include "unity.h" +#include "types_for_test.h" /* Include Passthroughs for Linking Tests */ void putcharSpy(int c) { (void)putchar(c);} @@ -209,6 +210,14 @@ TEST_RANGE([2, TEST_CASE( 6 , 7) +TEST_MATRIX([7, + 8 , + + 9, 10], + [ + 11] + + ) void test_SpaceInTestCase(unsigned index, unsigned bigger) { TEST_ASSERT_EQUAL_UINT32(NextExpectedSpaceIndex, index); @@ -216,3 +225,84 @@ void test_SpaceInTestCase(unsigned index, unsigned bigger) NextExpectedSpaceIndex++; } + +TEST_MATRIX([1, 5, (2*2)+1, 4]) +void test_SingleMatix(unsigned value) +{ + TEST_ASSERT_LESS_OR_EQUAL(10, value); +} + +TEST_MATRIX([2, 5l, 4u+3, 4ul], [-2, 3]) +void test_TwoMatrices(unsigned first, signed second) +{ + static unsigned idx = 0; + static const unsigned expected[] = + { + // -2 3 + -4, 6, // 2 + -10, 15, // 5 + -14, 21, // 7 + -8, 12, // 4 + }; + TEST_ASSERT_EQUAL_INT(expected[idx++], first * second); +} + +TEST_MATRIX(["String1", "String,2", "Stri" "ng3", "String[4]", "String\"5\""], [-5, 12.5f]) +void test_StringsAndNumbersMatrices(const char* str, float number) +{ + static unsigned idx = 0; + static const char* expected[] = + { + "String1_-05.00", + "String1_+12.50", + "String,2_-05.00", + "String,2_+12.50", + "String3_-05.00", + "String3_+12.50", + "String[4]_-05.00", + "String[4]_+12.50", + "String\"5\"_-05.00", + "String\"5\"_+12.50", + }; + char buf[200] = {0}; + snprintf(buf, sizeof(buf), "%s_%+06.2f", str, number); + TEST_ASSERT_EQUAL_STRING(expected[idx++], buf); +} + +TEST_MATRIX( + [ENUM_A, ENUM_4, ENUM_C], + [test_arr[0], 7.8f, test_arr[2]], + ['a', 'f', '[', ']', '\'', '"'], +) +void test_EnumCharAndArrayMatrices(test_enum_t e, float n, char c) +{ + static unsigned enum_idx = 0; + static const test_enum_t exp_enum[3] = { + ENUM_A, ENUM_4, ENUM_C, + }; + + static unsigned float_idx = 0; + float exp_float[3] = {0}; + exp_float[0] = test_arr[0]; + exp_float[1] = 7.8f; + exp_float[2] = test_arr[2]; + + static unsigned char_idx = 0; + static const test_enum_t exp_char[] = { + 'a', 'f', '[', ']', '\'', '"' + }; + + TEST_ASSERT_EQUAL_INT(exp_enum[enum_idx], e); + TEST_ASSERT_EQUAL_FLOAT(exp_float[float_idx], n); + TEST_ASSERT_EQUAL_CHAR(exp_char[char_idx], c); + + char_idx = (char_idx + 1) % 6; + if (char_idx == 0.0f) + { + float_idx = (float_idx + 1) % 3; + if (float_idx == 0.0f) + { + enum_idx = (enum_idx + 1) % 3; + } + } +} diff --git a/test/tests/test_unity_parameterizedDemo.c b/test/tests/test_unity_parameterizedDemo.c index 2e2efc8..83dfad6 100644 --- a/test/tests/test_unity_parameterizedDemo.c +++ b/test/tests/test_unity_parameterizedDemo.c @@ -7,10 +7,14 @@ #ifndef TEST_RANGE #define TEST_RANGE(...) #endif +#ifndef TEST_MATRIX +#define TEST_MATRIX(...) +#endif TEST_CASE(1, 2, 5) TEST_CASE(10, 7, 20) TEST_RANGE([3, 4, 1], [10, 5, -2], <30, 31, 1>) +TEST_MATRIX([3, 4, 7], [10, 8, 2, 1],[30u, 20.0f]) void test_demoParamFunction(int a, int b, int c) { TEST_ASSERT_GREATER_THAN_INT(a + b, c); diff --git a/test/tests/types_for_test.h b/test/tests/types_for_test.h new file mode 100644 index 0000000..6da4e51 --- /dev/null +++ b/test/tests/types_for_test.h @@ -0,0 +1,14 @@ +#pragma once + +typedef enum { + ENUM_A, + ENUM_2, + ENUM_C, + ENUM_4, +} test_enum_t; + +static const float test_arr[] = { + 1.2f, + 2.3f, + 3.4f, +};