ZX Test is a testing library which provides a similar interface as ‘Google Test’ for writing tests in Zircon. Unlike gTest, ZX Test has a limited set of dependencies used to test kernel constructs, which ‘Google Test’ expects to work. Additionally, Zx Test only depends on a subset libc++, which is currently restricted to header only libraries in Zircon.
ZX Test also supports writing tests in C, a feature which is not supported in ‘Google Test’.
This document may reference fatal and non-fatal failures. A failure is the result of an asserted condition being false. A fatal failure requires the test to abort or stop execution. On the other hand a non fatal failure allows the test execution to continue uninterrupted. A fatal failure is triggered by macros starting with ASSERT_*
and non fatal failures are triggered by macros starting with EXPECT_*
.
ASSERT/EXPECT_OK
custom macro.ASSERT_TRUE(false, "My msg %d", i);
namespace
is ::zxtest
instead of ::testing
. (zxtest::Test, zxtest::Environment, zxtest::Runner,...
)RUN_ALL_TESTS
takes argc and argv as arguments.By default the library provides its own main
symbol, which will run all registered tests. This means that users only need to write their tests. If there is a need for customized initialization, the program must provide its own main
. By doing so, the library‘s main
will be ignored and the main program’s will be picked instead.
In order to execute all registered tests, RUN_ALL_TESTS(int argc, char** argv)
should be called.
int return_val = RUN_ALL_TESTS(argc, argv);
In this example return_val is 0 on success and non-zero on error. RUN_ALL_TESTS
may be invoked at any time.
The following example will add a test named FooIsBar
to a test case named FooTestCase
. Both test case and test names are case sentitive, meaning that FooTestCase
and FOoTestCase
are two different test cases.
The test case name and the test name for a tuple which represents a unique identifier for each test. This identifier must be unique within an entire binary.
#include <zxtest/zxtest.h> TEST(FooTestCase, FooIsBar) { Foo foo(/*is_bar=*/true); ASSERT_TRUE(foo.IsBar()); }
A helper method must conform to void(*)(Args...args)
signature if any assertion will used within the helper function body. The following example demonstrates how to call a helper function and abort execution if within the call stack of the helper function a fatal failure occurs.
#include <zxtest/zxtest.h> void ValidateFoo(const Foo& foo); TEST(FooTestCase, FooIsBar) { Foo foo(true);\ // Call helper method, and aborts if any error ocurred printing the formatted message. ASSERT_NO_FATAL_FAILURES(ValidateFoo(foo), "Foo is invalid %s", foo.ToString().c_str()); ASSERT_TRUE(foo.IsBar()); }
Test writers should prefer helper methods over fixtures.
Deaths tests are mechanisms in which we expect that a statement may crash the executing thread. This useful for testing API preconditions violations that would enforce a crash. There are two types of death tests, those that we expect to crash and those that don't.
In C, |statement| must of type (void*)(void), while in Cpp |statement| must be convertible to |fit::function<void()>| or closure.
ASSERT_DEATH(statement, message-optional, message-arguments-options); ASSERT_NO_DEATH(statement, message-optional, message-arguments-options);
TEST(CheckPanicTest, ZxAssertFalse) { ASSERT_DEATH([] {ZX_ASSERT(false);}, "Assertion Failure did not crash"); }
In Cpp, we might have multiple lines, or commas within |statement|, this may cause the surprising behavior of an error on the macro expansion. To deal with this problem wrap |statement| with parenthesis().
TEST(CheckPanicTest, ZxAssertFalse) { ASSERT_DEATH(([] { int a,b; a = 1; b = 2; ZX_ASSERT(a == b);}), "Assertion Failure did not crash"); }
TEST(CheckPanicTest, CrashingFunction) { ASSERT_DEATH(&CrashingFunction, "CrashingFunction did not crash"); }
Tests frequently required SetUp/TearDown which is different from the test logic itself. Common patterns include setting up some resource the system under test or component under test needs. The following example illustrates how to use a fixture to set up state before any test is executed and clean afterwards.
#include <zxtest/zxtest.h> class RamdiskFixture : public zxtest::Test { public: const fbl::String& ramdisk_path() const { return ramdisk_path_;} // Called before this test case's first test runs. // Optional: Defaults to nothing. static void SetUpTestCase() {} // Called after all tests of this test case runs. // Optional: Defaults to nothing. static void TearDownTestCase() {} protected: // Called before every test of this test case. // Optional: Defaults to nothing. void SetUp() override { ramdisk_create(......, ramdisk_path.get()); } // Called after every test test of this test case. // Optional: Defaults to nothing. void TearDown() override { ramdisk_destroy(ramdisk_path.get()); } private: // Test body does not have direct access to this value. fbl::StringBuffer<PATH_MAX> ramdisk_path_; }; // In order to reuse a fixture across multiple test cases, aliasing the fixture or // using class inheritance allows renaming. In this case RamdiskFixture is used for // FooTestCase. using FooTestCase = RamdiskFixture; // In this case, RamdiskFixture is used for BarTestCase. These are two different test cases, // that use the same logic. using BarTestCase = RamdiskFixture; TEST_F(FooTestCase, FooUsesRamdisk) { Foo foo(/*is_bar=*/true); // Maybe move this to the fixture? fbl::unique_fd ramdisk_fd(open(ramdisk_path().c_str(), O_RDWR)); ASSERT_TRUE(ramdisk_fd.IsValid()); ASSERT_TRUE(foo.IsBar()); }
There are occasions when there is a global resource that needs lifecycle management. The library provides zxtest::Environment
with a SetUp and TearDown method for creating and dismantling any resource. Once created, the zxtest::Runner
instance will take ownership of the environment, and call the respective methods accordingly. When used with --gtest_repeat
the environment will be set up and torn down for each iteration.
#include <zxtest/zxtest.h> class MyGlobalResource : public zxtest::Environment { public: // Called before every test of this test case. // Optional: Defaults to nothing. void SetUp() override { ramdisk_create(......, ramdisk_path.get()); } // Called after every test test of this test case. // Optional: Defaults to nothing. void TearDown() override { ramdisk_destroy(ramdisk_path.get()); } private: // Test body does not have direct access to this value. fbl::StringBuffer<PATH_MAX> ramdisk_path_; }; // Use an alias to match the testcase name we want, which means that it will be treated // as a completly different testcase. int main(int argc, char** argv) { // Takes ownership. zxtest::Runner->GetInstance()->AddGlobalTestEnvironment(new MyGlobalResource()); return RUN_ALL_TESTS(argc, argv); }
We preserve the gtest flag names intentionally, so binaries that interact through that interface can treat the test binary as a black box.
--gtest_filter(-f) pattern
Follows gtest syntax for defining patterns that match a given test name.--gtest_break_on_failure(-b)
Will finish test execution upon encountering a fatal failure.--gtest_repeat(-i) iter
Will run all matching tests |iter| times. If |iter| is -1 will run until killed.--gtest_list_tests(-l)
List all matching tests.--gtest_shuffle(-s)
Shuffle test execution order.--gtest_random_seed(-r)
Provides a seed for random decisions, such as shuffling. The value is available to the user, through zxtest::Runner::GetInstance()->random_seed();
. If unset a random seed is provided.--help(-h)
Prints help message.