Assert macros (C++ SDK)#

Assert macros define test properties about your software or workload. They are part of the Antithesis C++ SDK.

Every macro takes a parameter message, which is a human-readable identifier for assertions; and a parameter details, which allows you to allows you to record additional contextual information.

Always assertions#

Always assertions assert that every time the assertion is encountered, some condition is true. If a single counterexample to the asserted property is found, the assertion will fail. See the always properties documentation for further details.

ALWAYS(bool condition, const char* message, const JSON& details)

Asserts that condition is true every time this macro is called, and that it is called at least once. The corresponding test property will be viewable in the Antithesis SDK: Always group of your triage report.

Parameters: See common parameters below.

Example

#include <antithesis_sdk.h>
// ...
int bar(int x, int y){
    ALWAYS(x > 0, "x is positive", {{"x", x}});
    // Include the value of x to help debug if this fails
}

ALWAYS_OR_UNREACHABLE(bool condition, const char* message, const JSON& details)

Asserts that condition is true every time this macro is called. The corresponding test property will pass if the assertion is never encountered (unlike Always assertion types). This test property will be viewable in the “Antithesis SDK: Always” group of your triage report.

This function is similar to assertions in most assertions libraries that you may be familiar with.

Parameters: See common parameters below.


UNREACHABLE(const char* message, const JSON& details)

Asserts that a line of code cannot be reached. The assertion will fail if it is reached, and will pass if it is never reached. Assertions of this form will be grouped in the “Antithesis SDK: Reachability” section of the triage report.

Parameters: See common parameters below.

#include <antithesis_sdk.h>
// The queue should never have more than 100 elements
// So the following code should never be exercised
if (queue_length > 100) {
    UNREACHABLE("Queue overflow handling should never be reached", {{"queue_length", queue_length}});
}

ALWAYS_GREATER_THAN(T left, T right, const char* message, const JSON& details)
ALWAYS_GREATER_THAN_OR_EQUAL_TO(T left, T right, const char* message, const JSON& details)
ALWAYS_LESS_THAN(T left, T right, const char* message, const JSON& details)
ALWAYS_LESS_THAN_OR_EQUAL_TO(T left, T right, const char* message, const JSON& details)

Equivalent to asserting ALWAYS(left > right, message, details) etc. Information about left and right will automatically be added to the details parameter, with keys left and right. If you use these macros for assertions that compare numeric quantities, you may help Antithesis find more bugs.

Parameters: T is numeric. See also common parameters below.

Example

#include <antithesis_sdk.h>
// ...
int bar(int x, int y){
    ALWAYS_GREATER_THAN(x, 0, "x is positive", {});
    // The value of x will automatically be included in 'details'
}

ALWAYS_SOME(std::vector<std::pair<string, bool>> named_bools, const char* message, const JSON& details)

Asserts that every time the macro is called, at least one bool in named_bools is true. Equivalent to ALWAYS(named_bools[0].second || named_bools[1].second || ..., message, details). If you use these macros for assertions about the behavior of booleans, you may help Antithesis find more bugs. Information about named_bools will automatically be added to the details parameter, and the keys will be the names of the bools.

Parameters: See common parameters below. Note that the NAMED_LIST utility macro is provided to pass the named_bools argument more conveniently.

Example

#include <antithesis_sdk.h>
// You have a variable my_data
// Always, my_data should be stored in at least one replica
ALWAYS_SOME(NAMED_LIST(replica1.contains(my_data), replica2.contains(my_data), replica3.contains(my_data)), "Data is always in some replica", {})

Sometimes assertions#

Sometimes assertions assert that at least one time the assertion is encountered, some condition is true. If a single example of the asserted property is found, the assertion will pass. See why you should use sometimes properties for further details.

SOMETIMES(bool condition, const char* message, const JSON& details)

Asserts that condition is true at least one time that this macro was called. (If the assertion is never encountered, the test property will therefore fail.) This test property will be viewable in the “Antithesis SDK: Sometimes” group.

Parameters: See common parameters below.

Example

#include <antithesis_sdk.h>
// A function to square a number
// Your tests should sometimes test negative inputs
float squareNumber(float x){
    SOMETIMES(x < 0, "Input to squareNumber is negative", {{"x", x}})
    return x*x
    }

REACHABLE(const char* message, const JSON& details)

Assert that a line of code is reached at least once. The assertion will pass if it is reached at least once. (If it is never reached, it will therefore fail.) The resulting property will be grouped in the “Antithesis SDK: Reachability” section of your triage report.

Parameters: See common parameters below.

Example

#include <antithesis_sdk.h>
// Some sort of retry logic
// Your tests should exercise this functionality at least once
void retryTransaction{
    REACHABLE("Retry logic is reachable")
    // More code
    }

SOMETIMES_GREATER_THAN(T left, T right, const char* message, const JSON& details)
SOMETIMES_GREATER_THAN_OR_EQUAL_TO(T left, T right, const char* message, const JSON& details)
SOMETIMES_LESS_THAN(T left, T right, const char* message, const JSON& details)
SOMETIMES_LESS_THAN_OR_EQUAL_TO(T left, T right, const char* message, const JSON& details)

Equivalent to asserting SOMETIMES(T left > T right, ...), etc. Information about left and right will automatically be added to the details parameter, with keys left and right. If you use these macros for assertions that compare numeric quantities, you may help Antithesis find more bugs.

Parameters: T is numeric. See also common parameters below.


SOMETIMES_ALL(std::vector<std::pair<string, bool>> named_bools, const char* message, const JSON& details)

Asserts that at least one time the macro is called, every bool in named_bools is true. Equivalent to SOMETIMES(named_bools[0].second && named_bools[1].second && ..., message, details). If you use these macros for assertions about the behavior of booleans, you may help Antithesis find more bugs. Information about named_bools will automatically be added to the details parameter, and the keys will be the names of the bools.

Parameters: See common parameters below. Note that the NAMED_LIST utility macro is provided to pass the named_bools argument more conveniently.

#include <antithesis_sdk.h>
// When testing, sometimes all of the replicas should be alive
SOMETIMES_ALL(NAMED_LIST(replica1.isAlive(), replica2.isAlive(), replica3.isAlive()), "Sometimes all replicas are alive", {})

Common parameters#

message

const char* that must be known at compile time. A human readable identifier used to aggregate assertions. Antithesis generates one test property per unique message and this test property will be named message in triage reports.

This test property either passes or fails, which depends upon the evaluation of every assertion that shares its message. Different assertions in different parts of the code should have different message, but the same assertion should always have the same message even if it is moved to a different file.

details

JSON. Optional additional information provided by the user to add context for assertion failures. The information that is logged will appear in a triage report, under the details section of the generated property.

Normally the values passed to details are evaluated at runtime. If you do not wish to provide details, then pass {} as the parameter.

The type of details is std::map<std::string, antithesis::ValueType> where antithesis::ValueType is a std::variant consisting of many common types, including a nested JSON type.

You can specify details using initialization syntax:

{
    {"name", "Bob"},
    {"number", 123.4}
}

You can also include nested JSON in details , which requires the identifier antithesis::JSON:

{
    {"nested", antithesis::JSON{
                                {"a", "b"},
                                {"c", 1234}
                                }
    }
}

Utility macros#

The macro NAMED_LIST is provided to conveniently pass arguments to boolean assertions such as SOMETIMES_ALL and ALWAYS_SOME. It can also be used to pass the details argument.

NAMED_LIST(foo, bar, baz) expands to { { "foo", foo }, { "bar", bar }, { "baz", baz } }.

NAMED_LIST(dog.say("woof"), cat.say("meow")) expands to { { "dog.say(\"woof\")", dog.say("woof") }, { "cat.say(\"meow\")", cat.say("meow") } }.

The macro takes between 1 and 10 parameters (inclusive). Contact us if you require the macro to support more parameters.

Additional Considerations#

Assertions can be added to template definitions. However, you should ensure that the assertion identifier message is distinct for each instantiation of the template.

We recommend using the __PRETTY_FUNCTION__ macro, which shows the actual name of the instantiated function or method. This should be used in message to ensure that each instantiation’s assertion has a unique identifier:

#include <antithesis_sdk.h>

template<typename T>
bool is_in_range(T val, T min_val, T max_val) {
  bool in_range = ((val >= min_val) && (x <= max_val));
  ALWAYS(in_range, "Is in range: " __PRETTY_FUNCTION__, {});
  return in_range;
}

Alternatively, __PRETTY_FUNCTION__ can be included in the details associated with the assertion:

#include <antithesis_sdk.h>

template<typename T>
bool is_in_range(T val, T min_val, T max_val) {
  bool in_range = ((val >= min_val) && (x <= max_val));
  ALWAYS(in_range, "Is in range", {{"where", __PRETTY_FUNCTION__}});
  return in_range;
}