Extending the framework
The applications can make their own types readable in the reports, and can write their own comparators. These are advanced features, which are not needed for the usual checks described in Writing checks.
Utility functions
namespace utility {
bool is_match (std::string_view input, std::string_view pattern);
}
namespace utility {
template <class T, class Delim_T> // string-like T only
auto split (T input, Delim_T delim) -> std::vector<T>;
// Selected for string literals and `const char*`.
std::vector<std::string_view>
split (std::string_view input, std::string_view delimiters);
}
is_match() checks whether the whole input matches a pattern, in which *
matches any sequence of characters, including an empty one, and ? matches any
single character; the comparison is case-sensitive. The --filter option uses
it, for each of the alternatives that it separates with |.
split() splits a string at any of the delimiter characters, and returns the
sub-strings in a std::vector, omitting the empty ones. For a
std::string_view, a string literal, or a const char*, the elements are
views into the input, which must outlive them; for a std::string they are
copies. The vector allocates memory from the heap, which may matter on
embedded targets.
Refer to the Utility Functions reference page.
Custom types
The function comparators also accept user-defined types, provided
that the type has the operator used by the comparator: operator== for
eq() and ne() (in C++20, operator== also provides !=), and
operator<=> (or the individual relational operators) for lt(),
le(), gt(), and ge().
Since the comparators report the values of their operands when a check
fails, they also need to know how to print the type. For this, the application
must define an operator<< that writes the value to an
mt::expression_formatter, in the namespace of the type, so that it is found
by argument-dependent lookup. Without it, the comparators do not compile
for that type: the call is rejected with a message that says that an operand
cannot be written in the report, and what to define; only the scoped
enumerations are shown without it, by their underlying value.
#include "micro-os-plus/micro-test-plus.h"
namespace mt = micro_os_plus::micro_test_plus;
namespace app
{
struct point
{
int x;
int y;
};
constexpr bool
operator== (const point& lhs, const point& rhs)
{
return lhs.x == rhs.x && lhs.y == rhs.y;
}
// Used to report the values of failed checks.
inline mt::expression_formatter&
operator<< (mt::expression_formatter& formatter, const point& p)
{
return formatter << "{ " << p.x << ", " << p.y << " }";
}
} // namespace app
mt::runner tr;
int
main (int argc, char* argv[])
{
tr.initialise (argc, argv);
auto& ts = tr.add_top_suite ();
ts.test ("Points", [] (auto& t) {
app::point p{ 1, 2 };
t.expect (mt::eq (p, app::point{ 1, 3 })) << "point is { 1, 3 }";
});
return tr.run_suites ();
}
The failed check reports both values:
not ok 1 - point is { 1, 3 }
---
condition: "{ 1, 2 } == { 1, 3 }"
at:
fileName: main.cpp
lineNumber: 37
...
The formatter accepts the standard arithmetic types, characters, strings,
pointers, containers, scoped enumerations, pairs, tuples, and optionals, so the
members can usually be written directly, as above. The char pointers and
arrays, also when not const, and those of the wchar_t, char8_t,
char16_t, and char32_t types, converted to UTF-8, are written as strings (a
null pointer as nullptr); the other pointers, including the signed char and
unsigned char (uint8_t) pointers, are written as addresses. A plain boolean
check, such as t.expect (p == q), needs no formatter, but on failure it
reports only the source location. The same operator<< also writes the
values appended to the messages (t.expect (p == q) << "p is " << p).
To test such an operator<< on its own, construct a formatter, write
the value, and compare str() with the expected text:
mt::expression_formatter formatter;
formatter << app::point{ 1, 2 };
// formatter.str () is "{ 1, 2 }".
A formatter constructed in this way writes the values as the reports of
the failed comparisons of the built-in types do, with the type suffixes
of the numbers (1l, 2u) and the strings quoted. In the reports of the
comparisons of the application types, the values written by the
operator<< of the application are shown as it writes them, without the
suffixes and the quotes, as in the messages.
Custom comparators
When the relation to check is not one of the built-in comparisons, a function
that returns bool can be used, but on failure it reports only the source
location. To report the operands, as the built-in comparators do, the function
must return a framework expression, built with the public templates of the
mt::extension namespace:
extension::compare_op{ lhs, rhs, tag }, for a comparison of two operands;extension::predicate_op{ operand, tag }, for a property of one operand.
The tag is an empty structure that defines the operation, with a
static apply() member function template, which receives the operands
and returns the result, and a static symbol, written in the failure
reports between the two operands, or before the single one. A tag that has no
symbol that can be written, or no apply() that can be called with the
operands and returns a value convertible to bool, is rejected with a
message that states what the tag needs, not with the trace of the concepts:
#include "micro-os-plus/micro-test-plus.h"
namespace mt = micro_os_plus::micro_test_plus;
namespace app
{
struct divides_tag
{
static constexpr const char* symbol = " divides ";
template <class Lhs_T, class Rhs_T>
static constexpr bool
apply (const Lhs_T& lhs, const Rhs_T& rhs)
{
return rhs % lhs == 0;
}
};
struct even_tag
{
static constexpr const char* symbol = "even ";
template <class T>
static constexpr bool
apply (const T& t)
{
return t % 2 == 0;
}
};
// A custom comparator.
template <class Lhs_T, class Rhs_T>
constexpr auto
divides (const Lhs_T& lhs, const Rhs_T& rhs)
{
return mt::extension::compare_op{ lhs, rhs, divides_tag{} };
}
// A custom predicate.
template <class T>
constexpr auto
even (const T& t)
{
return mt::extension::predicate_op{ t, even_tag{} };
}
} // namespace app
mt::runner tr;
int
main (int argc, char* argv[])
{
tr.initialise (argc, argv);
auto& ts = tr.add_top_suite ();
ts.test ("Custom", [] (auto& t) {
t.expect (app::divides (5, 12));
t.expect (app::even (7));
t.expect (mt::all_of (app::divides (3, 12), app::even (7)));
});
return tr.run_suites ();
}
The failed checks report the operands, with the symbols of the tags:
✗ FAILED (main.cpp:59, 5 divides 12)
✗ FAILED (main.cpp:60, even 7)
✗ FAILED (main.cpp:61, (3 divides 12 and even 7))
The tag is stateless, so it cannot carry a parameter, such as a tolerance,
to be reported with the operands. A parameter is passed as an operand, usually
the right one, as the divisor of divides above, and is then written in the
report like any other operand.
The expressions can be combined with the logical functions, and,
being constexpr, also checked at compile time
(static_assert (app::divides (3, 12))). The operands are written with
the operator<< of the formatter, so the types of the application need
one, as for the custom types. The C strings
and the arrays among the operands are reported as the built-in
comparators report them (quoted, and as ranges), while the tag receives
the operands as they were passed.
For brevity, the example above takes its operands as const&.
A temporary operand is moved into the expression, and a named operand is
copied, so an expression can be returned from the function that builds it,
or kept in a variable, after the operands are destroyed; the C strings and the
other arrays are the exception, since the expression refers to their characters
or elements. An operand whose class cannot be copied (for example an
application type with a deleted copy constructor) is referred to instead, and
must outlive the check. To move the temporaries, and not copy them, take
the operands as forwarding references (Lhs_T&& lhs) and pass them on with
std::forward<Lhs_T> (lhs), as eq() does.
A helper that passes its argument on to expect() or require() can be
constrained with extension::checkable, as these functions are, and one
that passes it on to the message operator with extension::printable.