mirror of
https://github.com/catchorg/Catch2.git
synced 2024-12-22 19:33:29 +01:00
New and hopefully improved documentation for matchers
This commit is contained in:
parent
121f04ffcf
commit
64d7f9b98a
421
docs/matchers.md
421
docs/matchers.md
@ -1,77 +1,123 @@
|
||||
<a id="top"></a>
|
||||
# Matchers
|
||||
|
||||
Matchers are an alternative way to do assertions which are easily extensible and composable.
|
||||
This makes them well suited to use with more complex types (such as collections) or your own custom types.
|
||||
Matchers were first popularised by the [Hamcrest](https://en.wikipedia.org/wiki/Hamcrest) family of frameworks.
|
||||
**Contents**<br>
|
||||
[Using Matchers](#using-matchers)<br>
|
||||
[Built-in matchers](#built-in-matchers)<br>
|
||||
[Writing custom matchers (old style)](#writing-custom-matchers-old-style)<br>
|
||||
[Writing custom matchers (new style)](#writing-custom-matchers-new-style)<br>
|
||||
|
||||
## In use
|
||||
Matchers, as popularized by the [Hamcrest](https://en.wikipedia.org/wiki/Hamcrest)
|
||||
framework are an alternative way to write assertions, useful for tests
|
||||
where you work with complex types or need to assert more complex
|
||||
properties. Matchers are easily composable and users can write their
|
||||
own and combine them with the Catch2-provided matchers seamlessly.
|
||||
|
||||
Matchers are introduced with the `REQUIRE_THAT` or `CHECK_THAT` macros, which take two arguments.
|
||||
The first argument is the thing (object or value) under test. The second part is a match _expression_,
|
||||
which consists of either a single matcher or one or more matchers combined using `&&`, `||` or `!` operators.
|
||||
|
||||
For example, to assert that a string ends with a certain substring:
|
||||
## Using Matchers
|
||||
|
||||
```c++
|
||||
using Catch::Matchers::EndsWith; // or Catch::EndsWith
|
||||
std::string str = getStringFromSomewhere();
|
||||
REQUIRE_THAT( str, EndsWith( "as a service" ) );
|
||||
```
|
||||
Matchers are most commonly used in tandem with the `REQUIRE_THAT` or
|
||||
`CHECK_THAT` macros. The `REQUIRE_THAT` macro takes two arguments,
|
||||
the first one is the input (object/value) to test, the second argument
|
||||
is the matcher itself.
|
||||
|
||||
The matcher objects can take multiple arguments, allowing more fine tuning.
|
||||
The built-in string matchers, for example, take a second argument specifying whether the comparison is
|
||||
case sensitive or not:
|
||||
|
||||
```c++
|
||||
REQUIRE_THAT( str, EndsWith( "as a service", Catch::CaseSensitive::No ) );
|
||||
```
|
||||
|
||||
And matchers can be combined:
|
||||
|
||||
```c++
|
||||
REQUIRE_THAT( str,
|
||||
EndsWith( "as a service" ) ||
|
||||
(StartsWith( "Big data" ) && !Contains( "web scale" ) ) );
|
||||
```
|
||||
|
||||
_The combining operators do not take ownership of the matcher objects.
|
||||
This means that if you store the combined object, you have to ensure that
|
||||
the matcher objects outlive its last use. What this means is that code
|
||||
like this leads to a use-after-free and (hopefully) a crash:_
|
||||
For example, to assert that a string ends with the "as a service"
|
||||
substring, you can write the following assertion
|
||||
|
||||
```cpp
|
||||
using Catch::Matchers::EndsWith;
|
||||
|
||||
REQUIRE_THAT( getSomeString(), EndsWith("as a service") );
|
||||
```
|
||||
|
||||
Individual matchers can also be combined using the C++ logical
|
||||
operators, that is `&&`, `||`, and `!`, like so:
|
||||
|
||||
```cpp
|
||||
using Catch::Matchers::EndsWith;
|
||||
using Catch::Matchers::Contains;
|
||||
|
||||
REQUIRE_THAT( getSomeString(),
|
||||
EndsWith("as a service") && Contains("web scale"));
|
||||
```
|
||||
|
||||
The example above asserts that the string returned from `getSomeString`
|
||||
_both_ ends with the suffix "as a service" _and_ contains the string
|
||||
"web scale" somewhere.
|
||||
|
||||
|
||||
Both of the string matchers used in the examples above live in the
|
||||
`catch_matchers_string.h` header, so to compile the code above also
|
||||
requires `#include <catch2/matchers/catch_matchers_string.h>`.
|
||||
|
||||
**IMPORTANT**: The combining operators do not take ownership of the
|
||||
matcher objects being combined. This means that if you store combined
|
||||
matcher object, you have to ensure that the matchers being combined
|
||||
outlive its last use. What this means is that the following code leads
|
||||
to a use-after-free (UAF):
|
||||
|
||||
```cpp
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_string.h>
|
||||
|
||||
TEST_CASE("Bugs, bugs, bugs", "[Bug]"){
|
||||
std::string str = "Bugs as a service";
|
||||
|
||||
auto match_expression = Catch::EndsWith( "as a service" ) ||
|
||||
(Catch::StartsWith( "Big data" ) && !Catch::Contains( "web scale" ) );
|
||||
auto match_expression = Catch::Matchers::EndsWith( "as a service" ) ||
|
||||
(Catch::Matchers::StartsWith( "Big data" ) && !Catch::Matchers::Contains( "web scale" ) );
|
||||
REQUIRE_THAT(str, match_expression);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Built in matchers
|
||||
Catch2 provides some matchers by default. They can be found in the
|
||||
`Catch::Matchers::foo` namespace and are imported into the `Catch`
|
||||
namespace as well.
|
||||
## Built-in matchers
|
||||
|
||||
There are two parts to each of the built-in matchers, the matcher
|
||||
type itself and a helper function that provides template argument
|
||||
deduction when creating templated matchers. As an example, the matcher
|
||||
for checking that two instances of `std::vector` are identical is
|
||||
`EqualsMatcher<T>`, but the user is expected to use the `Equals`
|
||||
helper function instead.
|
||||
Every matcher provided by Catch2 is split into 2 parts, a factory
|
||||
function that lives in the `Catch::Matchers` namespace, and the actual
|
||||
matcher type that is in some deeper namespace and should not be used by
|
||||
the user. In the examples above, we used `Catch::Matchers::Contains`.
|
||||
This is the factory function for the
|
||||
`Catch::Matchers::StdString::ContainsMatcher` type that does the actual
|
||||
matching.
|
||||
|
||||
Out of the box, Catch2 provides the following matchers:
|
||||
|
||||
|
||||
### String matchers
|
||||
The string matchers are `StartsWith`, `EndsWith`, `Contains`, `Equals` and `Matches`. The first four match a literal (sub)string against a result, while `Matches` takes and matches an ECMAScript regex. Do note that `Matches` matches the string as a whole, meaning that "abc" will not match against "abcd", but "abc.*" will.
|
||||
### `std::string` matchers
|
||||
|
||||
Each of the provided `std::string` matchers also takes an optional second argument, that decides case sensitivity (by-default, they are case sensitive).
|
||||
Catch2 provides 5 different matchers that work with `std::string`,
|
||||
* `StartsWith(std::string str, CaseSensitive)`,
|
||||
* `EndsWith(std::string str, CaseSensitive)`,
|
||||
* `Contains(std::string str, CaseSensitive)`,
|
||||
* `Equals(std::string str, CaseSensitive)`, and
|
||||
* `Matches(std::string str, CaseSensitive)`.
|
||||
|
||||
The first three should be fairly self-explanatory, they succeed if
|
||||
the argument starts with `str`, ends with `str`, or contains `str`
|
||||
somewhere inside it.
|
||||
|
||||
The `Equals` matcher matches a string if (and only if) the argument
|
||||
string is equal to `str`.
|
||||
|
||||
Finally, the `Matches` matcher performs an ECMASCript regex match using
|
||||
`str` against the argument string. It is important to know that
|
||||
the match is performed agains the string as a whole, meaning that
|
||||
the regex `"abc"` will not match input string `"abcd"`. To match
|
||||
`"abcd"`, you need to use e.g. `"abc.*"` as your regex.
|
||||
|
||||
The second argument sets whether the matching should be case-sensitive
|
||||
or not. By default, it is case-sensitive.
|
||||
|
||||
> `std::string` matchers live in `catch2/matchers/catch_matchers_string.h`
|
||||
|
||||
|
||||
### Vector matchers
|
||||
Catch2 currently provides 5 built-in matchers that work on `std::vector`.
|
||||
|
||||
_Vector matchers have been deprecated in favour of the generic
|
||||
range matchers with the same functionality._
|
||||
|
||||
Catch2 provides 5 built-in matchers that work on `std::vector`.
|
||||
|
||||
These are
|
||||
|
||||
* `Contains` which checks whether a specified vector is present in the result
|
||||
@ -81,40 +127,82 @@ These are
|
||||
* `Approx` which checks whether the result is "approx-equal" (order matters, but comparison is done via `Approx`) to a specific vector
|
||||
> Approx matcher was [introduced](https://github.com/catchorg/Catch2/issues/1499) in Catch 2.7.2.
|
||||
|
||||
An example usage:
|
||||
```cpp
|
||||
std::vector<int> some_vec{ 1, 2, 3 };
|
||||
REQUIRE_THAT(some_vec, Catch::Matchers::UnorderedEquals(std::vector<int>{ 3, 2, 1 }));
|
||||
```
|
||||
|
||||
This assertions will pass, because the elements given to the matchers
|
||||
are a permutation of the ones in `some_vec`.
|
||||
|
||||
> vector matchers live in `catch2/matchers/catch_matchers_vector.h`
|
||||
|
||||
|
||||
### Floating point matchers
|
||||
Catch2 provides 3 matchers for working with floating point numbers. These
|
||||
are `WithinAbsMatcher`, `WithinUlpsMatcher` and `WithinRelMatcher`.
|
||||
|
||||
The `WithinAbsMatcher` matcher accepts floating point numbers that are
|
||||
within a certain distance of target. It should be constructed with the
|
||||
`WithinAbs(double target, double margin)` helper.
|
||||
Catch2 provides 3 matchers that target floating point numbers. These
|
||||
are:
|
||||
|
||||
The `WithinUlpsMatcher` matcher accepts floating point numbers that are
|
||||
within a certain number of [ULPs](https://en.wikipedia.org/wiki/Unit_in_the_last_place)
|
||||
of the target. Because ULP comparisons need to be done differently for
|
||||
`float`s and for `double`s, there are two overloads of the helpers for
|
||||
this matcher, `WithinULP(float target, int64_t ULPs)`, and
|
||||
`WithinULP(double target, int64_t ULPs)`.
|
||||
|
||||
The `WithinRelMatcher` matcher accepts floating point numbers that are
|
||||
_approximately equal_ with the target number with some specific tolerance.
|
||||
In other words, it checks that `|lhs - rhs| <= epsilon * max(|lhs|, |rhs|)`,
|
||||
with special casing for `INFINITY` and `NaN`. There are _4_ overloads of
|
||||
the helpers for this matcher, `WithinRel(double target, double margin)`,
|
||||
`WithinRel(float target, float margin)`, `WithinRel(double target)`, and
|
||||
`WithinRel(float target)`. The latter two provide a default epsilon of
|
||||
machine epsilon * 100.
|
||||
* `WithinAbs(double target, double margin)`,
|
||||
* `WithinUlps(FloatingPoint target, uint64_t maxUlpDiff)`, and
|
||||
* `WithinRel(FloatingPoint target, FloatingPoint eps)`.
|
||||
|
||||
> `WithinRel` matcher was introduced in Catch 2.10.0
|
||||
|
||||
### Generic matchers
|
||||
Catch also aims to provide a set of generic matchers. Currently this set
|
||||
contains only a matcher that takes arbitrary callable predicate and applies
|
||||
it onto the provided object.
|
||||
|
||||
Because of type inference limitations, the argument type of the predicate
|
||||
has to be provided explicitly. Example:
|
||||
`WithinAbs` creates a matcher that accepts floating point numbers whose
|
||||
difference with `target` is less than the `margin`.
|
||||
|
||||
`WithinULP` creates a matcher that accepts floating point numbers that
|
||||
are no more than `maxUlpDiff`
|
||||
[ULPs](https://en.wikipedia.org/wiki/Unit_in_the_last_place)
|
||||
away from the `target` value. The short version of what this means
|
||||
is that there is no more than `maxUlpDiff - 1` representeable floating
|
||||
point numbers between the argument for matching and the `target` value.
|
||||
|
||||
`WithinRel` creates a matcher that accepts floating point numbers that
|
||||
are _approximately equal_ with the `target` with tolerance of `eps.`
|
||||
Specifically, it matches if
|
||||
`|arg - target| <= eps * max(|arg|, |target|)` holds. If you do not
|
||||
specify `eps`, `std::numeric_limits<FloatingPoint>::epsilon * 100`
|
||||
is used as the default.
|
||||
|
||||
|
||||
In practice, you will often want to combine multiple of these matchers,
|
||||
together for an assertion, because all 3 options have edge cases where
|
||||
they behave differently than you would expect. As an example, under
|
||||
the `WithinRel` matcher, a `0.` only ever matches a `0.` (or `-0.`),
|
||||
regardless of the relative tolerance specified. Thus, if you want to
|
||||
handle numbers that are "close enough to 0 to be 0", you have to combine
|
||||
it with the `WithinAbs` matcher.
|
||||
|
||||
For example, to check that our computation matches known good value
|
||||
within 0.1%, or is close enough (no different to 5 decimal places)
|
||||
to zero, we would write this assertion:
|
||||
```cpp
|
||||
REQUIRE_THAT( computation(input),
|
||||
Catch::Matchers::WithinRel(expected, 0.001)
|
||||
|| Catch::Matchers::WithinAbs(0, 0.000001) );
|
||||
```
|
||||
|
||||
|
||||
> floating point matchers live in `catch2/matchers/catch_matchers_floating.h`
|
||||
|
||||
|
||||
### Miscellaneous matchers
|
||||
|
||||
Catch2 also provides some matchers and matcher utilities that do not
|
||||
quite fit into other categories.
|
||||
|
||||
The first one of them is the `Predicate(Callable pred, std::string description)`
|
||||
matcher. It creates a matcher object that calls `pred` for the provided
|
||||
argument. The `description` argument allows users to set what the
|
||||
resulting matcher should self-describe as if required.
|
||||
|
||||
Do note that you will need to explicitly specify the type of the
|
||||
argument, like in this example:
|
||||
|
||||
```cpp
|
||||
REQUIRE_THAT("Hello olleH",
|
||||
Predicate<std::string>(
|
||||
@ -123,84 +211,175 @@ REQUIRE_THAT("Hello olleH",
|
||||
);
|
||||
```
|
||||
|
||||
The second argument is an optional description of the predicate, and is
|
||||
used only during reporting of the result.
|
||||
> the predicate matcher lives in `catch2/matchers/catch_matchers_generic.hpp`
|
||||
|
||||
|
||||
### Exception matchers
|
||||
Catch2 also provides an exception matcher that can be used to verify
|
||||
that an exception's message exactly matches desired string. The matcher
|
||||
is `ExceptionMessageMatcher`, and we also provide a helper function
|
||||
`Message`.
|
||||
The other miscellaneous matcher utility is exception matching.
|
||||
|
||||
The matched exception must publicly derive from `std::exception` and
|
||||
the message matching is done _exactly_, including case.
|
||||
|
||||
> `ExceptionMessageMatcher` was introduced in Catch 2.10.0
|
||||
#### Matching exceptions
|
||||
|
||||
Catch2 provides an utility macro for asserting that an expression
|
||||
throws exception of specific type, and that the exception has desired
|
||||
properties. The macro is `REQUIRE_THROWS_MATCHES(expr, ExceptionType, Matcher)`.
|
||||
|
||||
> `REQUIRE_THROWS_MATCHES` macro lives in `catch2/matchers/catch_matchers.h`
|
||||
|
||||
|
||||
Catch2 currently provides only one matcher for exceptions,
|
||||
`Message(std::string message)`. `Message` checks that the exception's
|
||||
message, as returned from `what` is exactly equal to `message`.
|
||||
|
||||
Example use:
|
||||
```cpp
|
||||
REQUIRE_THROWS_MATCHES(throwsDerivedException(), DerivedException, Message("DerivedException::what"));
|
||||
```
|
||||
|
||||
## Custom matchers
|
||||
It's easy to provide your own matchers to extend Catch or just to work with your own types.
|
||||
Note that `DerivedException` in the example above has to derive from
|
||||
`std::exception` for the example to work.
|
||||
|
||||
You need to provide two things:
|
||||
1. A matcher class, derived from `Catch::MatcherBase<T>` - where `T` is the type being tested.
|
||||
The constructor takes and stores any arguments needed (e.g. something to compare against) and you must
|
||||
override two methods: `match()` and `describe()`.
|
||||
2. A simple builder function. This is what is actually called from the test code and allows overloading.
|
||||
> the exception message matcher lives in `catch2/matchers/catch_matchers_exception.hpp`
|
||||
|
||||
Here's an example for asserting that an integer falls within a given range
|
||||
(note that it is all inline for the sake of keeping the example short):
|
||||
|
||||
|
||||
## Writing custom matchers (old style)
|
||||
|
||||
The old style of writing matchers has been introduced back in Catch
|
||||
Classic. To create an old-style matcher, you have to create your own
|
||||
type that derives from `Catch::Matchers::MatcherBase<ArgT>`, where
|
||||
`ArgT` is the type your matcher works for. Your type has to override
|
||||
two methods, `bool match(ArgT const&) const`,
|
||||
and `std::string describe() const`.
|
||||
|
||||
As the name suggests, `match` decides whether the provided argument
|
||||
is matched (accepted) by the matcher. `describe` then provides a
|
||||
human-oriented description of what the matcher does.
|
||||
|
||||
We also recommend that you create factory function, just like Catch2
|
||||
does, but that is mostly useful for template argument deduction for
|
||||
templated matchers (assuming you do not have CTAD available).
|
||||
|
||||
To combine these into an example, let's say that you want to write
|
||||
a matcher that decides whether the provided argument is a number
|
||||
within certain range. We will call it `IsBetweenMatcher<T>`:
|
||||
|
||||
```c++
|
||||
// The matcher class
|
||||
class IntRange : public Catch::MatcherBase<int> {
|
||||
int m_begin, m_end;
|
||||
public:
|
||||
IntRange( int begin, int end ) : m_begin( begin ), m_end( end ) {}
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers.h>
|
||||
// ...
|
||||
|
||||
// Performs the test for this matcher
|
||||
bool match( int const& i ) const override {
|
||||
return i >= m_begin && i <= m_end;
|
||||
|
||||
template <typename T>
|
||||
class IsBetweenMatcher : public Catch::Matchers::MatcherBase<T> {
|
||||
T m_begin, m_end;
|
||||
public:
|
||||
IsBetweenMatcher(T begin, T end) : m_begin(begin), m_end(end) {}
|
||||
|
||||
bool match(T const& in) const override {
|
||||
return in >= m_begin && in <= m_end;
|
||||
}
|
||||
|
||||
// Produces a string describing what this matcher does. It should
|
||||
// include any provided data (the begin/ end in this case) and
|
||||
// be written as if it were stating a fact (in the output it will be
|
||||
// preceded by the value under test).
|
||||
virtual std::string describe() const override {
|
||||
std::string describe() const override {
|
||||
std::ostringstream ss;
|
||||
ss << "is between " << m_begin << " and " << m_end;
|
||||
return ss.str();
|
||||
}
|
||||
};
|
||||
|
||||
// The builder function
|
||||
inline IntRange IsBetween( int begin, int end ) {
|
||||
return IntRange( begin, end );
|
||||
template <typename T>
|
||||
IsBetweenMatcher<T> IsBetween(T begin, T end) {
|
||||
return { begin, end };
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
// Usage
|
||||
TEST_CASE("Integers are within a range")
|
||||
{
|
||||
CHECK_THAT( 3, IsBetween( 1, 10 ) );
|
||||
CHECK_THAT( 100, IsBetween( 1, 10 ) );
|
||||
TEST_CASE("Numbers are within range") {
|
||||
// infers `double` for the argument type of the matcher
|
||||
CHECK_THAT(3., IsBetween(1., 10.));
|
||||
// infers `int` for the argument type of the matcher
|
||||
CHECK_THAT(100, IsBetween(1, 10));
|
||||
}
|
||||
```
|
||||
|
||||
Running this test gives the following in the console:
|
||||
Obviously, the code above can be improved somewhat, for example you
|
||||
might want to `static_assert` over the fact that `T` is an arithmetic
|
||||
type... or generalize the matcher to cover any type for which the user
|
||||
can provide a comparison function object.
|
||||
|
||||
Note that while any matcher written using the old style can also be
|
||||
written using the new style, combining old style matchers should
|
||||
generally compile faster. Also note that you can combine old and new
|
||||
style matchers arbitrarily.
|
||||
|
||||
> `MatcherBase` lives in `catch2/matchers/catch_matchers.h`
|
||||
|
||||
|
||||
## Writing custom matchers (new style)
|
||||
|
||||
The new style of writing matchers has been introduced in Catch2 v3.
|
||||
To create a new-style matcher, you have to create your own type that
|
||||
derives from `Catch::Matchers::MatcherGenericBase`. Your type has to
|
||||
also provide two methods, `bool match( ... ) const` and overriden
|
||||
`std::string describe() const`.
|
||||
|
||||
Unlike with old-style matchers, there are no requirements on how
|
||||
the `match` member function takes its argument. This means that the
|
||||
argument can be taken by value or by mutating reference, but also that
|
||||
the matcher's `match` member function can be templated.
|
||||
|
||||
This allows you to write more complex matcher, such as a matcher that
|
||||
can compare one range-like (something that responds to `begin` and
|
||||
`end`) object to another, like in the following example:
|
||||
|
||||
```cpp
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_templates.hpp>
|
||||
// ...
|
||||
|
||||
template<typename Range>
|
||||
struct EqualsRangeMatcher : Catch::Matchers::MatcherGenericBase {
|
||||
EqualsRangeMatcher(Range const& range):
|
||||
range{ range }
|
||||
{}
|
||||
|
||||
template<typename OtherRange>
|
||||
bool match(OtherRange const& other) const {
|
||||
using std::begin; using std::end;
|
||||
|
||||
return std::equal(begin(range), end(range), begin(other), end(other));
|
||||
}
|
||||
|
||||
std::string describe() const override {
|
||||
return "Equals: " + Catch::rangeToString(range);
|
||||
}
|
||||
|
||||
private:
|
||||
Range const& range;
|
||||
};
|
||||
|
||||
template<typename Range>
|
||||
auto EqualsRange(const Range& range) -> EqualsRangeMatcher<Range> {
|
||||
return EqualsRangeMatcher<Range>{range};
|
||||
}
|
||||
|
||||
TEST_CASE("Combining templated matchers", "[matchers][templated]") {
|
||||
std::array<int, 3> container{{ 1,2,3 }};
|
||||
|
||||
std::array<int, 3> a{{ 1,2,3 }};
|
||||
std::vector<int> b{ 0,1,2 };
|
||||
std::list<int> c{ 4,5,6 };
|
||||
|
||||
REQUIRE_THAT(container, EqualsRange(a) || EqualsRange(b) || EqualsRange(c));
|
||||
}
|
||||
```
|
||||
/**/TestFile.cpp:123: FAILED:
|
||||
CHECK_THAT( 100, IsBetween( 1, 10 ) )
|
||||
with expansion:
|
||||
100 is between 1 and 10
|
||||
```
|
||||
|
||||
Do note that while you can rewrite any matcher from the old style to
|
||||
a new style matcher, combining new style matchers is more expensive
|
||||
in terms of compilation time. Also note that you can combine old style
|
||||
and new style matchers arbitrarily.
|
||||
|
||||
> `MatcherGenericBase` lives in `catch2/matchers/catch_matchers_templates.hpp`
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
@ -61,6 +61,13 @@
|
||||
* `ListeningReporter` is now final
|
||||
|
||||
|
||||
|
||||
### Improvements
|
||||
* Matchers have been extended with the ability to use different signatures of `match` (#1307, #1553, #1554, #1843)
|
||||
* This includes having templated `match` member function
|
||||
* See the [rewritten Matchers documentation](matchers.md#top) for details
|
||||
|
||||
|
||||
### Fixes
|
||||
* The `INFO` macro no longer contains superfluous semicolon (#1456)
|
||||
* The `--list*` family of command line flags now return 0 on success (#1410, #1146)
|
||||
|
Loading…
Reference in New Issue
Block a user