Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 31 additions & 18 deletions auto/generate_test_runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ def find_tests(source)

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)
Expand All @@ -143,25 +143,38 @@ def find_tests(source)

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, I split parts of single argument regex for readability & easily improving every regex part independently.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I noticed that in your PR, for some reason separating the elements makes it visualize how the regex will process the command.
I normally only do it if I need to reuse parts of the query, so I only have one place to maintain

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
Expand Down
4 changes: 3 additions & 1 deletion docs/UnityConfigurationGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -448,16 +448,18 @@ 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:
| User defines macro | Unity will _not_ define following macro |
|---|---|
| `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
Expand Down
87 changes: 87 additions & 0 deletions docs/UnityHelperScriptsGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `[<parm1>, <param2>, ..., <paramN-1>, <paramN>]`.

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. `<ENUM_NAME>`
- Elements of arrays e.g. `<data[0]>`

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.
Expand Down
2 changes: 1 addition & 1 deletion src/unity.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions src/unity_internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
90 changes: 90 additions & 0 deletions test/tests/test_unity_parameterized.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <setjmp.h>
#include <stdio.h>
#include "unity.h"
#include "types_for_test.h"

/* Include Passthroughs for Linking Tests */
void putcharSpy(int c) { (void)putchar(c);}
Expand Down Expand Up @@ -209,10 +210,99 @@ 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);
TEST_ASSERT_LESS_THAN(bigger, index);

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;
}
}
}
4 changes: 4 additions & 0 deletions test/tests/test_unity_parameterizedDemo.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 14 additions & 0 deletions test/tests/types_for_test.h
Original file line number Diff line number Diff line change
@@ -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,
};