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|
# 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

View File

@@ -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

View File

@@ -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.

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
* 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

View File

@@ -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

View File

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

View File

@@ -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);

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