1
0
mirror of https://github.com/ThrowTheSwitch/Unity.git synced 2026-01-23 00:15:58 +01:00

Merge pull request #685 from jonhenneberg/test_matix_feature

Thanks to @jonhenneberg (especially) and @AJIOB for your work on the TEST_MATRIX feature!
This commit is contained in:
Mark VanderVoord
2023-08-13 09:24:03 -04:00
committed by GitHub
8 changed files with 233 additions and 20 deletions

View File

@@ -132,8 +132,8 @@ class UnityTestRunnerGenerator
lines.each_with_index do |line, _index| lines.each_with_index do |line, _index|
# find tests # 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|RANGE|MATRIX))\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]})\w*)\s*\(\s*(.*)\s*\)/m
arguments = Regexp.last_match(1) arguments = Regexp.last_match(1)
name = Regexp.last_match(2) name = Regexp.last_match(2)
@@ -143,25 +143,38 @@ class UnityTestRunnerGenerator
if @options[:use_param_tests] && !arguments.empty? if @options[:use_param_tests] && !arguments.empty?
args = [] 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) 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') args << type_and_args[i + 1].sub(/^\s*\(\s*(.*?)\s*\)\s*$/m, '\1')
next
end
# RANGE 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| 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] == '>' exclude_end = arg_values_str[0] == '<' && arg_values_str[-1] == '>'
arg_values_str[1...-1].map do |arg_value_str| arg_values_str[1...-1].map do |arg_value_str|
arg_value_str.include?('.') ? arg_value_str.to_f : arg_value_str.to_i arg_value_str.include?('.') ? arg_value_str.to_f : arg_value_str.to_i
end.push(exclude_end) end.push(exclude_end)
end.map do |arg_values| end.map do |arg_values|
Range.new(arg_values[0], arg_values[1], arg_values[3]).step(arg_values[2]).to_a 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| end.reduce(nil) do |result, arg_range_expanded|
result.nil? ? arg_range_expanded.map { |a| [a] } : result.product(arg_range_expanded) result.nil? ? arg_range_expanded.map { |a| [a] } : result.product(arg_range_expanded)
end.map do |arg_combinations| end.map do |arg_combinations|
arg_combinations.flatten.join(', ') 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 end
end end

View File

@@ -448,7 +448,7 @@ To enable it, use the following example:
#define UNITY_SUPPORT_TEST_CASES #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. 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 If you provide one of the following macros, some of default definitions will not be
defined: defined:
@@ -456,8 +456,10 @@ defined:
|---|---| |---|---|
| `UNITY_EXCLUDE_TEST_CASE` | `TEST_CASE` | | `UNITY_EXCLUDE_TEST_CASE` | `TEST_CASE` |
| `UNITY_EXCLUDE_TEST_RANGE` | `TEST_RANGE` | | `UNITY_EXCLUDE_TEST_RANGE` | `TEST_RANGE` |
| `UNITY_EXCLUDE_TEST_MATRIX` | `TEST_MATRIX` |
| `TEST_CASE` | `TEST_CASE` | | `TEST_CASE` | `TEST_CASE` |
| `TEST_RANGE` | `TEST_RANGE` | | `TEST_RANGE` | `TEST_RANGE` |
| `TEST_MATRIX` | `TEST_MATRIX` |
`UNITY_EXCLUDE_TEST_*` defines is not processed by test runner generator script. `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 If you exclude one of them from definition, you should provide your own definition

View File

@@ -296,6 +296,93 @@ TEST_CASE(4, 8, 30)
TEST_CASE(4, 6, 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` ### `unity_test_summary.rb`
A Unity test file contains one or more test case functions. A Unity test file contains one or more test case functions.

View File

@@ -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 * - define UNITY_SUPPORT_TEST_CASES to include the TEST_CASE macro, though really it's mostly about the runner generator script
* Parameterized Tests * 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 * Tests with Arguments
* - you'll want to define UNITY_USE_COMMAND_LINE_ARGS if you have the test runner passing arguments to Unity * - you'll want to define UNITY_USE_COMMAND_LINE_ARGS if you have the test runner passing arguments to Unity

View File

@@ -793,6 +793,9 @@ extern const char UnityStrErrShorthand[];
#if !defined(TEST_RANGE) && !defined(UNITY_EXCLUDE_TEST_RANGE) #if !defined(TEST_RANGE) && !defined(UNITY_EXCLUDE_TEST_RANGE)
#define TEST_RANGE(...) #define TEST_RANGE(...)
#endif #endif
#if !defined(TEST_MATRIX) && !defined(UNITY_EXCLUDE_TEST_MATRIX)
#define TEST_MATRIX(...)
#endif
#endif #endif
#endif #endif

View File

@@ -7,6 +7,7 @@
#include <setjmp.h> #include <setjmp.h>
#include <stdio.h> #include <stdio.h>
#include "unity.h" #include "unity.h"
#include "types_for_test.h"
/* Include Passthroughs for Linking Tests */ /* Include Passthroughs for Linking Tests */
void putcharSpy(int c) { (void)putchar(c);} void putcharSpy(int c) { (void)putchar(c);}
@@ -209,6 +210,14 @@ TEST_RANGE([2,
TEST_CASE( TEST_CASE(
6 , 7) 6 , 7)
TEST_MATRIX([7,
8 ,
9, 10],
[
11]
)
void test_SpaceInTestCase(unsigned index, unsigned bigger) void test_SpaceInTestCase(unsigned index, unsigned bigger)
{ {
TEST_ASSERT_EQUAL_UINT32(NextExpectedSpaceIndex, index); TEST_ASSERT_EQUAL_UINT32(NextExpectedSpaceIndex, index);
@@ -216,3 +225,84 @@ void test_SpaceInTestCase(unsigned index, unsigned bigger)
NextExpectedSpaceIndex++; 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;
}
}
}

View File

@@ -7,10 +7,14 @@
#ifndef TEST_RANGE #ifndef TEST_RANGE
#define TEST_RANGE(...) #define TEST_RANGE(...)
#endif #endif
#ifndef TEST_MATRIX
#define TEST_MATRIX(...)
#endif
TEST_CASE(1, 2, 5) TEST_CASE(1, 2, 5)
TEST_CASE(10, 7, 20) TEST_CASE(10, 7, 20)
TEST_RANGE([3, 4, 1], [10, 5, -2], <30, 31, 1>) 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) void test_demoParamFunction(int a, int b, int c)
{ {
TEST_ASSERT_GREATER_THAN_INT(a + b, c); TEST_ASSERT_GREATER_THAN_INT(a + b, c);

View File

@@ -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,
};