From 02d86f9b586167814a6f377ab6520fa2f0401dc1 Mon Sep 17 00:00:00 2001 From: Jeff Feasel Date: Thu, 16 May 2024 14:05:15 -0400 Subject: [PATCH 01/18] Revised unit testing documentation to refer to PW tests. --- docs/testing/unit_testing.md | 281 +++++++++++++++++++++++++++++------ 1 file changed, 232 insertions(+), 49 deletions(-) diff --git a/docs/testing/unit_testing.md b/docs/testing/unit_testing.md index c539420f397e92..de8387e1127550 100644 --- a/docs/testing/unit_testing.md +++ b/docs/testing/unit_testing.md @@ -11,85 +11,268 @@ example applications. - e.g. feature combinations not in example apps. -## Unit testing in the SDK - nlUnitTest +## Unit testing in the SDK - pw_unit_test -The following example gives a small demonstration of how to use nlUnitTest to -write a unit test +The following example demonstrates how to use pw_unit_test to write a simple unit test. +Note that this test does not use any fixtures or setup/teardown. ``` -#include -#include -#include +#include -class YourTestContext : public Test::AppContext { - ... +TEST(YourTestFunction1) +{ + //// Do some test things here, then check the results using EXPECT_* + SomeTypeX foo; + foo.DoSomething(); + EXPECT_EQ(foo.GetResultCount(), 7); + foo.DoSomethingElse(); + EXPECT_EQ(foo.GetResultCount(), 5); + + //// If you want to abort the rest of the test upon failure, use ASSERT_* + SomeTypeY * ptr = foo.GetSomePointer(); + ASSERT_NE(ptr, nullptr); + ptr->DoTheThing(); // Won't reach here if the ASSERT failed. +} + +TEST(YourTestFunction2) +{ + //// Do some test things here, then check the results using EXPECT_* + SomeTypeZ foo; + foo.DoSomething(); + EXPECT_EQ(foo.GetResultCount(), 3); +} +``` + +See +[TestSpan.cpp](https://github.com/project-chip/connectedhomeip/blob/master/src/lib/support/tests/TestSpan.cpp) +for a good example of a unit test without fixtures or setup/teardown. + +If your tests need fixtures or some kind of setup/teardown you will need to define a test context +that derives from `::testing::Test`. Your test functions will be defined with `TEST_F()` instead +of `TEST()`. The following example demonstrates how to use pw_unit_test to write a unit test that +uses fixtures and setup/teardown. + +``` +#include + +class YourTestContext : public ::testing::Test +{ +public: + // Performs shared setup for all tests in the test suite. Run once for the whole suite. + static void SetUpTestSuite() + { + //// Your per-suite setup goes here: + // sPerSuiteFixture.Init(); + // ASSERT_TRUE(sPerSuiteFixture.WorkingGreat()); + } + + // Performs shared teardown for all tests in the test suite. Run once for the whole suite. + static void TearDownTestSuite() + { + //// Your per-suite teardown goes here: + // sPerSuiteFixture.Shutdown(); + } + +protected: + // Performs setup for each test in the suite. Run once for each test function. + void SetUp() + { + //// Your per-test setup goes here: + // mPerTestFixture.Init(); + // ASSERT_TRUE(mPerTestFixture.WorkingGreat()); + } + + // Performs teardown for each test in the suite. Run once for each test function. + void TearDown() + { + //// Your per-test teardown goes here: + // mPerTestFixture.Shutdown(); + } + +private: + //// Your per-suite and per-test fixtures are declared here: + // static SomeTypeA sPerSuiteFixture; + // SomeTypeB mPerTestFixture; }; +//// Your per-suite fixtures are defined here: +// SomeTypeA YourTestContext::sPerSuiteFixture; +TEST_F(YourTestContext, YourTestFunction1) +{ + //// Do some test things here, then check the results using EXPECT_* + mPerTestFixture.DoSomething(); + EXPECT_EQ(mPerTestFixture.GetResultCount(), 7); + sPerSuiteFixture.DoSomething(); + EXPECT_EQ(sPerSuiteFixture.GetResultCount(), 5); -static void TestName(nlTestSuite * apSuite, void * apContext) { - // If you register the test suite with a context, cast - // apContext as appropriate - YourTestContext * ctx = static_cast(aContext); + //// If you want to abort the rest of the test upon failure, use ASSERT_* + SomeTypeC * ptr = mPerTestFixture.GetSomePointer(); + ASSERT_NE(ptr, nullptr); + ptr->DoTheThing(); // Won't reach here if the ASSERT failed. +} - // Do some test things here, then check the results using NL_TEST_ASSERT - NL_TEST_ASSERT(apSuite, ) +TEST_F(YourTestContext, YourTestFunction2) +{ + //// Do some test things here, then check the results using EXPECT_* + mPerTestFixture.DoSomethingElse(); + EXPECT_EQ(mPerTestFixture.GetResultCount(), 9); } +``` -static const nlTest sTests[] = +If you need messaging, there is a convenience class `Test::AppContext` that you +can derive your test context from. It provides a network layer and a system layer and +two secure sessions connected with each other. The following example demonstrates this. + +``` +#include + +class YourTestContext : public Test::AppContext { - NL_TEST_DEF("TestName", TestName), // Can have multiple of these - NL_TEST_SENTINEL() // If you forget this, you’re going to have a bad time +public: + // Performs shared setup for all tests in the test suite. Run once for the whole suite. + static void SetUpTestSuite() + { + AppContext::SetUpTestSuite(); // Call parent. + VerifyOrReturn(!HasFailure()); // Stop if parent had a failure. + + //// Your per-suite setup goes here: + // sPerSuiteFixture.Init(); + // ASSERT_TRUE(sPerSuiteFixture.WorkingGreat()); + } + + // Performs shared teardown for all tests in the test suite. Run once for the whole suite. + static void TearDownTestSuite() + { + //// Your per-suite teardown goes here: + // sPerSuiteFixture.Shutdown(); + + AppContext::TearDownTestSuite(); // Call parent. + } + +protected: + // Performs setup for each test in the suite. Run once for each test function. + void SetUp() + { + AppContext::SetUp(); // Call parent. + VerifyOrReturn(!HasFailure()); // Stop if parent had a failure. + + //// Your per-test setup goes here: + // mPerTestFixture.Init(); + // ASSERT_TRUE(mPerTestFixture.WorkingGreat()); + } + + // Performs teardown for each test in the suite. Run once for each test function. + void TearDown() + { + //// Your per-test teardown goes here: + // mPerTestFixture.Shutdown(); + + chip::app::EventManagement::DestroyEventManagement(); + AppContext::TearDown(); // Call parent. + } + +private: + //// Your per-suite and per-test fixtures are declared here: + // static SomeTypeA sPerSuiteFixture; + // SomeTypeB mPerTestFixture; }; +//// Your per-suite fixtures are defined here: +// SomeTypeA YourTestContext::sPerSuiteFixture; -nlTestSuite sSuite = +TEST_F(YourTestContext, YourTestFunction1) { - "TheNameOfYourTestSuite", // Test name - &sTests[0], // The list of tests to run - TestContext::Initialize, // Runs before all the tests (can be nullptr) - TestContext::Finalize // Runs after all the tests (can be nullptr) -}; + //// Do some test things here, then check the results using EXPECT_* +} -int YourTestSuiteName() +TEST_F(YourTestContext, YourTestFunction2) { - return chip::ExecuteTestsWithContext(&sSuite); // or “without” + //// Do some test things here, then check the results using EXPECT_* } +``` -CHIP_REGISTER_TEST_SUITE(YourTestSuiteName) +Note that our text context derives from +[Test::AppContext](https://github.com/project-chip/connectedhomeip/blob/master/src/app/tests/AppTestContext.h) + +## Notes and tips + +- Try to use as specific a test as possible. For example use these +``` + EXPECT_EQ(result, 3); + EXPECT_GT(result, 1); + EXPECT_STREQ(myString, "hello"); +``` +instead of these +``` + EXPECT_TRUE(result == 3); + EXPECT_TRUE(result > 1); + EXPECT_EQ(strcmp(myString, "hello"), 0); ``` -Each test gets an nlTestSuite object (apSuite) that is passed into the test -assertions, and a void\* context (apContext) that is yours to do with as you -require. +- If you want a test to abort use `ASSERT_*` instead of `EXPECT_*`. This will cause the current function to return. Note that this can not be usd in -The apContext should be derived from -[Test::AppContext](https://github.com/project-chip/connectedhomeip/blob/master/src/app/tests/AppTestContext.h) +- If a test calls a subroutine which exits due to an ASSERT failing, execution of the test will continue after the subroutine call. This is because ASSERT causes the subroutine to return (as opposed to throwing an execption). If you want to prevent the test from continuing, check the value of HasFailure() and stop execution if true. Example: +``` +void Subroutine() +{ + ASSERT_EQ(1, 2); // Fatal failure. +} -See -[TestSpan.cpp](https://github.com/project-chip/connectedhomeip/blob/master/src/lib/support/tests/TestSpan.cpp) -for a great example of a good unit test. +TEST(YourTestContext, YourTestFunction1) +{ + Subroutine(); // A fatal failure happens in this subroutine... + // ... however execution still continues. + print("This gets executed"); + VerifyOrReturn(!HasFailure()); + print("This does not get executed"); +} +``` + +- If you want to force a fatal failure use `FAIL()`, which will record a fatal failure and exit the current fuction. This is similar to using `ASSERT_TRUE(false)`. If you want to force a non-fatal failure use `ADD_FAILURE()`, which will record a non-fatal failure and continue executing the current function. This is similar to using `EXPECT_TRUE(false)`. -## nlUnitTest - Compiling and running +- If your test requires access to private/protected members of the underlying class you're testing, you'll need to create an accessor class that performs these operations and is friended to the underlying class. Please name the class `chip::Test::SomethingTestAccess` where `Something` is the name of the underlying class whose private/protected members you're trying to access. Then add `friend class chip::Test::SomethingTestAccess;` to the underlying class. Make sure your test's BUILD.gn file contains `sources = [ "SomethingTestAccess.h" ]`. Before creating a new TestAccess class, check if one already exists. If it does exist but doesn't expose the member you need, you can add a function to that class to do so. Note that you should make these functions as minimal as possible, with no logic besides just exposing the private/protected member. + - For an example see `ICDConfigurationDataTestAccess` which is defined in [ICDConfigurationDataTestAccess.h](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/tests/ICDConfigurationDataTestAccess.h), friends the underlying class in [ICDConfigurationData.h](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/ICDConfigurationData.h), is included in [BUILD.gn](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/tests/BUILD.gn), and is used by a test in [TestICDManager.cpp](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/tests/TestICDManager.cpp). + - For another example see `TCPBaseTestAccess` which is defined in [TCPBaseTestAccess.h](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/TCP.h), friends the underlying class in [ICDConfigurationData.h](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/ICDConfigurationData.h), is included in [BUILD.gn](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/tests/BUILD.gn), and is used by a test in [TestTCP.cpp](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/tests/TestTCP.cpp). -- Add to src/some_directory/tests/BUILD.gn - - chip_test_suite_using_nltest("tests") - - See for example - [src/lib/support/tests/BUILD.gn](https://github.com/project-chip/connectedhomeip/blob/master/src/lib/support/tests/BUILD.gn) -- [./gn_build.sh](https://github.com/project-chip/connectedhomeip/blob/master/gn_build.sh) - will build and run all tests +## pw_unit_test - Compiling and running + +- Add to `src/some_directory/tests/BUILD.gn` + - Example + - ```chip_test_suite("tests") { + output_name = "libSomethingTests" + + test_sources = [ + "TestSuite1.cpp", + "TestSuite2.cpp", + // Other test source files go here. + ] + + sources = [ + // Non-test source files go here. + ] + + cflags = [ "-Wconversion" ] + + public_deps = [ + // Dependencies go here. + ] +} +``` + - Another example: [src/lib/support/tests/BUILD.gn](https://github.com/project-chip/connectedhomeip/blob/master/src/lib/support/tests/BUILD.gn) +- Build and run all tests with [./gn_build.sh](https://github.com/project-chip/connectedhomeip/blob/master/gn_build.sh) - CI runs this, so any unit tests that get added will automatically be - added to the CI + added to the CI. - Test binaries are compiled into: - - out/debug//tests - - e.g. out/debug/linux_x64_clang/tests -- Tests are run when ./gn_build.sh runs, but you can run them individually in + - `out/debug//tests` + - e.g. `out/debug/linux_x64_clang/tests` +- Tests are run when `./gn_build.sh` runs, but you can run them individually in a debugger from their location. ## Debugging unit tests -- After running ./gn_build.sh, test binaries are compiled into - - out/debug//tests - - e.g. out/debug/linux_x64_clang/tests -- Individual binaries, can be run through regular tools: +- After running `./gn_build.sh`, test binaries are compiled into + - `out/debug//tests` + - e.g. `out/debug/linux_x64_clang/tests` +- Individual binaries can be run through regular tools: - gdb - valgrind - Your favorite tool that you tell everyone about. From 6dd60e05c24b7a0adc1425ec1c46ddbad51c0b57 Mon Sep 17 00:00:00 2001 From: Jeff Feasel Date: Thu, 16 May 2024 14:31:29 -0400 Subject: [PATCH 02/18] Documentation update --- docs/testing/unit_testing.md | 67 +++++++++++++++++++----------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/docs/testing/unit_testing.md b/docs/testing/unit_testing.md index de8387e1127550..22eeeedc8ecbc3 100644 --- a/docs/testing/unit_testing.md +++ b/docs/testing/unit_testing.md @@ -14,7 +14,8 @@ ## Unit testing in the SDK - pw_unit_test The following example demonstrates how to use pw_unit_test to write a simple unit test. -Note that this test does not use any fixtures or setup/teardown. +Each test function is defined using `TEST(NameOfFunction)`. +The set of test functions in a given source file is called a "suite". ``` #include @@ -45,12 +46,12 @@ TEST(YourTestFunction2) See [TestSpan.cpp](https://github.com/project-chip/connectedhomeip/blob/master/src/lib/support/tests/TestSpan.cpp) -for a good example of a unit test without fixtures or setup/teardown. +for an example of a simple unit test. -If your tests need fixtures or some kind of setup/teardown you will need to define a test context -that derives from `::testing::Test`. Your test functions will be defined with `TEST_F()` instead -of `TEST()`. The following example demonstrates how to use pw_unit_test to write a unit test that -uses fixtures and setup/teardown. +In the above example there are no fixtures or setup/teardown behavior. +If your tests need fixtures or some kind of setup/teardown you will need to define a test context that derives from `::testing::Test`. +Each of your test functions will be defined with `TEST_F(NameOfTestContext, NameOfFunction)`. +The following example demonstrates how to use pw_unit_test to write a unit test that uses fixtures and setup/teardown. ``` #include @@ -119,9 +120,9 @@ TEST_F(YourTestContext, YourTestFunction2) } ``` -If you need messaging, there is a convenience class `Test::AppContext` that you -can derive your test context from. It provides a network layer and a system layer and -two secure sessions connected with each other. The following example demonstrates this. +If you need messaging, there is a convenience class [Test::AppContext](https://github.com/project-chip/connectedhomeip/blob/master/src/app/tests/AppTestContext.h) that you can derive your test context from. +It provides a network layer and a system layer and two secure sessions connected with each other. +The following example demonstrates this. ``` #include @@ -190,45 +191,49 @@ TEST_F(YourTestContext, YourTestFunction2) } ``` -Note that our text context derives from -[Test::AppContext](https://github.com/project-chip/connectedhomeip/blob/master/src/app/tests/AppTestContext.h) +Note that you don't have to define all 4 functions `SetUpTestsuite`, `TearDownTestSuite`, `SetUp`, `TearDown`. +If you don't have any custom behavior for one of those functions just omit it. + ## Notes and tips -- Try to use as specific a test as possible. For example use these -``` +- Try to use as specific an assertion as possible. For example use these + ``` EXPECT_EQ(result, 3); EXPECT_GT(result, 1); EXPECT_STREQ(myString, "hello"); -``` + ``` instead of these -``` + ``` EXPECT_TRUE(result == 3); EXPECT_TRUE(result > 1); EXPECT_EQ(strcmp(myString, "hello"), 0); -``` + ``` -- If you want a test to abort use `ASSERT_*` instead of `EXPECT_*`. This will cause the current function to return. Note that this can not be usd in +- If you want a test to abort when an assertion fails, use `ASSERT_*` instead of `EXPECT_*`. This will cause the current function to return. - If a test calls a subroutine which exits due to an ASSERT failing, execution of the test will continue after the subroutine call. This is because ASSERT causes the subroutine to return (as opposed to throwing an execption). If you want to prevent the test from continuing, check the value of HasFailure() and stop execution if true. Example: -``` -void Subroutine() -{ - ASSERT_EQ(1, 2); // Fatal failure. -} + ``` + void Subroutine() + { + ASSERT_EQ(1, 2); // Fatal failure. + } -TEST(YourTestContext, YourTestFunction1) -{ - Subroutine(); // A fatal failure happens in this subroutine... - // ... however execution still continues. - print("This gets executed"); - VerifyOrReturn(!HasFailure()); - print("This does not get executed"); -} -``` + TEST(YourTestContext, YourTestFunction1) + { + Subroutine(); // A fatal failure happens in this subroutine... + // ... however execution still continues. + print("This gets executed"); + VerifyOrReturn(!HasFailure()); + print("This does not get executed"); + } + ``` - If you want to force a fatal failure use `FAIL()`, which will record a fatal failure and exit the current fuction. This is similar to using `ASSERT_TRUE(false)`. If you want to force a non-fatal failure use `ADD_FAILURE()`, which will record a non-fatal failure and continue executing the current function. This is similar to using `EXPECT_TRUE(false)`. +- `ASSERT_*` and `FAIL` will only work in functions with void return type, since they generate a `return;` statement. + If you must use these in a non-void function, instead use `EXPECT_*` or `ADD_FAILURE` and then check `HasFailure()` afterward and return if needed. + - If your test requires access to private/protected members of the underlying class you're testing, you'll need to create an accessor class that performs these operations and is friended to the underlying class. Please name the class `chip::Test::SomethingTestAccess` where `Something` is the name of the underlying class whose private/protected members you're trying to access. Then add `friend class chip::Test::SomethingTestAccess;` to the underlying class. Make sure your test's BUILD.gn file contains `sources = [ "SomethingTestAccess.h" ]`. Before creating a new TestAccess class, check if one already exists. If it does exist but doesn't expose the member you need, you can add a function to that class to do so. Note that you should make these functions as minimal as possible, with no logic besides just exposing the private/protected member. - For an example see `ICDConfigurationDataTestAccess` which is defined in [ICDConfigurationDataTestAccess.h](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/tests/ICDConfigurationDataTestAccess.h), friends the underlying class in [ICDConfigurationData.h](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/ICDConfigurationData.h), is included in [BUILD.gn](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/tests/BUILD.gn), and is used by a test in [TestICDManager.cpp](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/tests/TestICDManager.cpp). - For another example see `TCPBaseTestAccess` which is defined in [TCPBaseTestAccess.h](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/TCP.h), friends the underlying class in [ICDConfigurationData.h](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/ICDConfigurationData.h), is included in [BUILD.gn](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/tests/BUILD.gn), and is used by a test in [TestTCP.cpp](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/tests/TestTCP.cpp). From 201c6646509643b75a14a8500134d47dd4eef7b3 Mon Sep 17 00:00:00 2001 From: Jeff Feasel Date: Thu, 16 May 2024 14:36:31 -0400 Subject: [PATCH 03/18] More doc updates --- docs/testing/unit_testing.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/testing/unit_testing.md b/docs/testing/unit_testing.md index 22eeeedc8ecbc3..139e4dc21e518b 100644 --- a/docs/testing/unit_testing.md +++ b/docs/testing/unit_testing.md @@ -13,6 +13,8 @@ ## Unit testing in the SDK - pw_unit_test +### Simple unit tests + The following example demonstrates how to use pw_unit_test to write a simple unit test. Each test function is defined using `TEST(NameOfFunction)`. The set of test functions in a given source file is called a "suite". @@ -49,6 +51,9 @@ See for an example of a simple unit test. In the above example there are no fixtures or setup/teardown behavior. + +### Test fixtures and setup/teardown behavior + If your tests need fixtures or some kind of setup/teardown you will need to define a test context that derives from `::testing::Test`. Each of your test functions will be defined with `TEST_F(NameOfTestContext, NameOfFunction)`. The following example demonstrates how to use pw_unit_test to write a unit test that uses fixtures and setup/teardown. @@ -120,6 +125,8 @@ TEST_F(YourTestContext, YourTestFunction2) } ``` +### A loopback messaging context for convenience + If you need messaging, there is a convenience class [Test::AppContext](https://github.com/project-chip/connectedhomeip/blob/master/src/app/tests/AppTestContext.h) that you can derive your test context from. It provides a network layer and a system layer and two secure sessions connected with each other. The following example demonstrates this. @@ -203,7 +210,7 @@ If you don't have any custom behavior for one of those functions just omit it. EXPECT_GT(result, 1); EXPECT_STREQ(myString, "hello"); ``` -instead of these + instead of these ``` EXPECT_TRUE(result == 3); EXPECT_TRUE(result > 1); From 17e5bf5818e4d79e60233df7167089fa996cffa8 Mon Sep 17 00:00:00 2001 From: Jeff Feasel Date: Thu, 16 May 2024 14:46:30 -0400 Subject: [PATCH 04/18] More doc updates --- docs/testing/unit_testing.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/testing/unit_testing.md b/docs/testing/unit_testing.md index 139e4dc21e518b..83b7e22244270c 100644 --- a/docs/testing/unit_testing.md +++ b/docs/testing/unit_testing.md @@ -198,11 +198,13 @@ TEST_F(YourTestContext, YourTestFunction2) } ``` -Note that you don't have to define all 4 functions `SetUpTestsuite`, `TearDownTestSuite`, `SetUp`, `TearDown`. -If you don't have any custom behavior for one of those functions just omit it. +You don't have to override all 4 functions `SetUpTestsuite`, `TearDownTestSuite`, `SetUp`, `TearDown`. +If you don't need any custom behavior in one of those functions just omit it. +If you override one of the setup/teardown functions make sure to invoke the parent's version of the function as well. `AppContext::SetUpTestSuite` and `AppContext::SetUp` may generate fatal failures, so after you call these from your overriding function make sure to check `HasFailure()` and return if the parent function failed. -## Notes and tips + +## Best practices - Try to use as specific an assertion as possible. For example use these ``` From 489c365233ecd59eba45f2875e89507b14acf23a Mon Sep 17 00:00:00 2001 From: Jeff Feasel Date: Thu, 16 May 2024 14:53:26 -0400 Subject: [PATCH 05/18] More doc updates --- docs/testing/unit_testing.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/testing/unit_testing.md b/docs/testing/unit_testing.md index 83b7e22244270c..88d80e282af98e 100644 --- a/docs/testing/unit_testing.md +++ b/docs/testing/unit_testing.md @@ -221,7 +221,7 @@ If you override one of the setup/teardown functions make sure to invoke the pare - If you want a test to abort when an assertion fails, use `ASSERT_*` instead of `EXPECT_*`. This will cause the current function to return. -- If a test calls a subroutine which exits due to an ASSERT failing, execution of the test will continue after the subroutine call. This is because ASSERT causes the subroutine to return (as opposed to throwing an execption). If you want to prevent the test from continuing, check the value of HasFailure() and stop execution if true. Example: +- If a test calls a subroutine which exits due to an ASSERT failing, execution of the test will continue after the subroutine call. This is because ASSERT causes the subroutine to return (as opposed to throwing an execption). If you want to prevent the test from continuing, check the value of `HasFailure()` and stop execution if true. Example: ``` void Subroutine() { @@ -244,8 +244,8 @@ If you override one of the setup/teardown functions make sure to invoke the pare If you must use these in a non-void function, instead use `EXPECT_*` or `ADD_FAILURE` and then check `HasFailure()` afterward and return if needed. - If your test requires access to private/protected members of the underlying class you're testing, you'll need to create an accessor class that performs these operations and is friended to the underlying class. Please name the class `chip::Test::SomethingTestAccess` where `Something` is the name of the underlying class whose private/protected members you're trying to access. Then add `friend class chip::Test::SomethingTestAccess;` to the underlying class. Make sure your test's BUILD.gn file contains `sources = [ "SomethingTestAccess.h" ]`. Before creating a new TestAccess class, check if one already exists. If it does exist but doesn't expose the member you need, you can add a function to that class to do so. Note that you should make these functions as minimal as possible, with no logic besides just exposing the private/protected member. - - For an example see `ICDConfigurationDataTestAccess` which is defined in [ICDConfigurationDataTestAccess.h](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/tests/ICDConfigurationDataTestAccess.h), friends the underlying class in [ICDConfigurationData.h](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/ICDConfigurationData.h), is included in [BUILD.gn](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/tests/BUILD.gn), and is used by a test in [TestICDManager.cpp](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/tests/TestICDManager.cpp). - - For another example see `TCPBaseTestAccess` which is defined in [TCPBaseTestAccess.h](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/TCP.h), friends the underlying class in [ICDConfigurationData.h](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/ICDConfigurationData.h), is included in [BUILD.gn](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/tests/BUILD.gn), and is used by a test in [TestTCP.cpp](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/tests/TestTCP.cpp). + - For an example see `ICDConfigurationDataTestAccess` which is defined in [ICDConfigurationDataTestAccess.h](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/tests/ICDConfigurationDataTestAccess.h), friends the underlying class in [ICDConfigurationData.h](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/ICDConfigurationData.h), is included as a source in [BUILD.gn](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/tests/BUILD.gn), and is used by a test in [TestICDManager.cpp](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/tests/TestICDManager.cpp). + - For another example see `TCPBaseTestAccess` which is defined in [TCPBaseTestAccess.h](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/tests/TCPBaseTestAccess.h), friends the underlying class in [TCP.h](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/TCP.h), is included as a source in [BUILD.gn](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/tests/BUILD.gn), and is used by a test in [TestTCP.cpp](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/tests/TestTCP.cpp). ## pw_unit_test - Compiling and running From 5d3d05d3f2beb3a7935ae5d6437de6ecc2c976fc Mon Sep 17 00:00:00 2001 From: Jeff Feasel Date: Thu, 16 May 2024 14:55:23 -0400 Subject: [PATCH 06/18] More doc updates --- docs/testing/unit_testing.md | 41 ++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/docs/testing/unit_testing.md b/docs/testing/unit_testing.md index 88d80e282af98e..8f66f008ba0361 100644 --- a/docs/testing/unit_testing.md +++ b/docs/testing/unit_testing.md @@ -251,26 +251,27 @@ If you override one of the setup/teardown functions make sure to invoke the pare - Add to `src/some_directory/tests/BUILD.gn` - Example - - ```chip_test_suite("tests") { - output_name = "libSomethingTests" - - test_sources = [ - "TestSuite1.cpp", - "TestSuite2.cpp", - // Other test source files go here. - ] - - sources = [ - // Non-test source files go here. - ] - - cflags = [ "-Wconversion" ] - - public_deps = [ - // Dependencies go here. - ] -} -``` + ``` + chip_test_suite("tests") { + output_name = "libSomethingTests" + + test_sources = [ + "TestSuite1.cpp", + "TestSuite2.cpp", + // Other test source files go here. + ] + + sources = [ + // Non-test source files go here. + ] + + cflags = [ "-Wconversion" ] + + public_deps = [ + // Dependencies go here. + ] + } + ``` - Another example: [src/lib/support/tests/BUILD.gn](https://github.com/project-chip/connectedhomeip/blob/master/src/lib/support/tests/BUILD.gn) - Build and run all tests with [./gn_build.sh](https://github.com/project-chip/connectedhomeip/blob/master/gn_build.sh) - CI runs this, so any unit tests that get added will automatically be From 156005455bcff7d5f162a72fe78e8b7431505a17 Mon Sep 17 00:00:00 2001 From: Jeff Feasel Date: Thu, 16 May 2024 15:09:52 -0400 Subject: [PATCH 07/18] More doc updates --- docs/testing/unit_testing.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/testing/unit_testing.md b/docs/testing/unit_testing.md index 8f66f008ba0361..9cdc46017a1546 100644 --- a/docs/testing/unit_testing.md +++ b/docs/testing/unit_testing.md @@ -11,7 +11,9 @@ example applications. - e.g. feature combinations not in example apps. -## Unit testing in the SDK - pw_unit_test +## Unit testing in the SDK using pw_unit_test + +This SDK uses Pigweed unit tests (pw_unit_test), which are an implementation of GoogleTest. For more information see the [pw_unit_test documentation](https://pigweed.dev/pw_unit_test/) or the [GoogleTest documentation](https://google.github.io/googletest/). ### Simple unit tests @@ -56,7 +58,7 @@ In the above example there are no fixtures or setup/teardown behavior. If your tests need fixtures or some kind of setup/teardown you will need to define a test context that derives from `::testing::Test`. Each of your test functions will be defined with `TEST_F(NameOfTestContext, NameOfFunction)`. -The following example demonstrates how to use pw_unit_test to write a unit test that uses fixtures and setup/teardown. +The following example demonstrates how to use pw_unit_test to write a unit test that uses fixtures and setup/teardown behavior. ``` #include @@ -247,7 +249,7 @@ If you override one of the setup/teardown functions make sure to invoke the pare - For an example see `ICDConfigurationDataTestAccess` which is defined in [ICDConfigurationDataTestAccess.h](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/tests/ICDConfigurationDataTestAccess.h), friends the underlying class in [ICDConfigurationData.h](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/ICDConfigurationData.h), is included as a source in [BUILD.gn](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/tests/BUILD.gn), and is used by a test in [TestICDManager.cpp](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/tests/TestICDManager.cpp). - For another example see `TCPBaseTestAccess` which is defined in [TCPBaseTestAccess.h](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/tests/TCPBaseTestAccess.h), friends the underlying class in [TCP.h](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/TCP.h), is included as a source in [BUILD.gn](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/tests/BUILD.gn), and is used by a test in [TestTCP.cpp](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/tests/TestTCP.cpp). -## pw_unit_test - Compiling and running +## Compiling and running - Add to `src/some_directory/tests/BUILD.gn` - Example From 0e9754708f3125b0fa7cdf525041e549ae667c1f Mon Sep 17 00:00:00 2001 From: Jeff Feasel Date: Thu, 16 May 2024 15:11:23 -0400 Subject: [PATCH 08/18] More doc updates --- docs/testing/unit_testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/testing/unit_testing.md b/docs/testing/unit_testing.md index 9cdc46017a1546..acce8b1ea28201 100644 --- a/docs/testing/unit_testing.md +++ b/docs/testing/unit_testing.md @@ -13,7 +13,7 @@ ## Unit testing in the SDK using pw_unit_test -This SDK uses Pigweed unit tests (pw_unit_test), which are an implementation of GoogleTest. For more information see the [pw_unit_test documentation](https://pigweed.dev/pw_unit_test/) or the [GoogleTest documentation](https://google.github.io/googletest/). +This SDK uses Pigweed unit test (pw_unit_test), which is an implementation of GoogleTest. For more information see the [pw_unit_test documentation](https://pigweed.dev/pw_unit_test/) or the [GoogleTest documentation](https://google.github.io/googletest/). ### Simple unit tests From f327e58be009ba0a3cc016c6a4d6bab60cbc8efe Mon Sep 17 00:00:00 2001 From: Jeff Feasel Date: Thu, 16 May 2024 15:18:58 -0400 Subject: [PATCH 09/18] Updated documentation to reflect PW --- docs/guides/openiotsdk_unit_tests.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/openiotsdk_unit_tests.md b/docs/guides/openiotsdk_unit_tests.md index 7b9bba56707870..a24bab2c77c876 100644 --- a/docs/guides/openiotsdk_unit_tests.md +++ b/docs/guides/openiotsdk_unit_tests.md @@ -6,7 +6,7 @@ tests that are located in the `test` directory, e.g. `src/inet/tests`. Those sources are built as a static library that can be linked to the unit test application separately or as a monolithic test library. The common Matter test library collects all test cases and provides the engine based on -[Nest Labs Unit Test](https://github.com/nestlabs/nlunit-test) to run them in +[Pigweed Unit Test](https://pigweed.dev/pw_unit_test) to run them in the application. The Open IoT SDK unit tests implementation are located in the @@ -27,6 +27,7 @@ ChipCryptoTests CoreTests CredentialsTest DataModelTests +ICDServerTests InetLayerTests MdnsTests MessagingLayerTests @@ -41,7 +42,6 @@ SetupPayloadTests SupportTests SystemLayerTests TestShell -TransportLayerTests UserDirectedCommissioningTests ``` From 368986328e5d9ca2b22cdff63ef57b7cd1b90e44 Mon Sep 17 00:00:00 2001 From: Jeff Feasel Date: Thu, 16 May 2024 15:55:00 -0400 Subject: [PATCH 10/18] Fixed spelling error --- docs/testing/unit_testing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/testing/unit_testing.md b/docs/testing/unit_testing.md index acce8b1ea28201..c7fa05e6b5dbf6 100644 --- a/docs/testing/unit_testing.md +++ b/docs/testing/unit_testing.md @@ -223,7 +223,7 @@ If you override one of the setup/teardown functions make sure to invoke the pare - If you want a test to abort when an assertion fails, use `ASSERT_*` instead of `EXPECT_*`. This will cause the current function to return. -- If a test calls a subroutine which exits due to an ASSERT failing, execution of the test will continue after the subroutine call. This is because ASSERT causes the subroutine to return (as opposed to throwing an execption). If you want to prevent the test from continuing, check the value of `HasFailure()` and stop execution if true. Example: +- If a test calls a subroutine which exits due to an ASSERT failing, execution of the test will continue after the subroutine call. This is because ASSERT causes the subroutine to return (as opposed to throwing an exception). If you want to prevent the test from continuing, check the value of `HasFailure()` and stop execution if true. Example: ``` void Subroutine() { @@ -240,7 +240,7 @@ If you override one of the setup/teardown functions make sure to invoke the pare } ``` -- If you want to force a fatal failure use `FAIL()`, which will record a fatal failure and exit the current fuction. This is similar to using `ASSERT_TRUE(false)`. If you want to force a non-fatal failure use `ADD_FAILURE()`, which will record a non-fatal failure and continue executing the current function. This is similar to using `EXPECT_TRUE(false)`. +- If you want to force a fatal failure use `FAIL()`, which will record a fatal failure and exit the current function. This is similar to using `ASSERT_TRUE(false)`. If you want to force a non-fatal failure use `ADD_FAILURE()`, which will record a non-fatal failure and continue executing the current function. This is similar to using `EXPECT_TRUE(false)`. - `ASSERT_*` and `FAIL` will only work in functions with void return type, since they generate a `return;` statement. If you must use these in a non-void function, instead use `EXPECT_*` or `ADD_FAILURE` and then check `HasFailure()` afterward and return if needed. From 1c166a00cb0c147431836ecd8d8e99834eacd0e2 Mon Sep 17 00:00:00 2001 From: Jeff Feasel Date: Thu, 16 May 2024 22:51:16 -0400 Subject: [PATCH 11/18] Replaced gtest.h include. --- docs/testing/unit_testing.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/testing/unit_testing.md b/docs/testing/unit_testing.md index c7fa05e6b5dbf6..1bd7a85b4e598b 100644 --- a/docs/testing/unit_testing.md +++ b/docs/testing/unit_testing.md @@ -22,7 +22,7 @@ Each test function is defined using `TEST(NameOfFunction)`. The set of test functions in a given source file is called a "suite". ``` -#include +#include TEST(YourTestFunction1) { @@ -205,6 +205,8 @@ If you don't need any custom behavior in one of those functions just omit it. If you override one of the setup/teardown functions make sure to invoke the parent's version of the function as well. `AppContext::SetUpTestSuite` and `AppContext::SetUp` may generate fatal failures, so after you call these from your overriding function make sure to check `HasFailure()` and return if the parent function failed. +If you don't override any of the setup/teardown functions, you can simply make a type alias: `using YourTestContext = Test::AppContextPW;` instead of defining your own text context class. + ## Best practices From 048e5bf7561da6d8eac7dc72a75a2434f645a915 Mon Sep 17 00:00:00 2001 From: Jeff Feasel Date: Thu, 16 May 2024 23:27:24 -0400 Subject: [PATCH 12/18] Modified MessagingContext and its subclasses as well as AppContext to reflect PW migration. --- src/app/tests/AppTestContext.cpp | 26 +- src/app/tests/AppTestContext.h | 4 +- src/controller/tests/TestEventCaching.cpp | 77 ++---- src/controller/tests/TestWriteChunking.cpp | 266 +++++++++------------ src/messaging/tests/MessagingContext.cpp | 4 +- src/messaging/tests/MessagingContext.h | 90 +++---- 6 files changed, 197 insertions(+), 270 deletions(-) diff --git a/src/app/tests/AppTestContext.cpp b/src/app/tests/AppTestContext.cpp index eca7a2db76c83b..28ce87d473935c 100644 --- a/src/app/tests/AppTestContext.cpp +++ b/src/app/tests/AppTestContext.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include +#include "AppTestContext.h" #include #include @@ -40,11 +40,10 @@ namespace Test { void AppContext::SetUpTestSuite() { - CHIP_ERROR err = CHIP_NO_ERROR; LoopbackMessagingContext::SetUpTestSuite(); - // TODO: use ASSERT_EQ, once transition to pw_unit_test is complete - VerifyOrDieWithMsg((err = chip::DeviceLayer::PlatformMgr().InitChipStack()) == CHIP_NO_ERROR, AppServer, - "Init CHIP stack failed: %" CHIP_ERROR_FORMAT, err.Format()); + VerifyOrReturn(!HasFailure()); // Stop if parent had a failure. + + ASSERT_EQ(chip::DeviceLayer::PlatformMgr().InitChipStack(), CHIP_NO_ERROR); } void AppContext::TearDownTestSuite() @@ -55,17 +54,15 @@ void AppContext::TearDownTestSuite() void AppContext::SetUp() { - CHIP_ERROR err = CHIP_NO_ERROR; LoopbackMessagingContext::SetUp(); - // TODO: use ASSERT_EQ, once transition to pw_unit_test is complete - VerifyOrDieWithMsg((err = app::InteractionModelEngine::GetInstance()->Init(&GetExchangeManager(), &GetFabricTable(), - app::reporting::GetDefaultReportScheduler())) == - CHIP_NO_ERROR, - AppServer, "Init InteractionModelEngine failed: %" CHIP_ERROR_FORMAT, err.Format()); + VerifyOrReturn(!HasFailure()); // Stop if parent had a failure. + + ASSERT_EQ(app::InteractionModelEngine::GetInstance()->Init(&GetExchangeManager(), &GetFabricTable(), + app::reporting::GetDefaultReportScheduler()), + CHIP_NO_ERROR); Access::SetAccessControl(gPermissiveAccessControl); - VerifyOrDieWithMsg((err = Access::GetAccessControl().Init(chip::Access::Examples::GetPermissiveAccessControlDelegate(), - gDeviceTypeResolver)) == CHIP_NO_ERROR, - AppServer, "Init AccessControl failed: %" CHIP_ERROR_FORMAT, err.Format()); + ASSERT_EQ(Access::GetAccessControl().Init(chip::Access::Examples::GetPermissiveAccessControlDelegate(), gDeviceTypeResolver), + CHIP_NO_ERROR); } void AppContext::TearDown() @@ -73,6 +70,7 @@ void AppContext::TearDown() Access::GetAccessControl().Finish(); Access::ResetAccessControlToDefault(); chip::app::InteractionModelEngine::GetInstance()->Shutdown(); + LoopbackMessagingContext::TearDown(); } diff --git a/src/app/tests/AppTestContext.h b/src/app/tests/AppTestContext.h index a2fe3387d2d7aa..c05e490fb9e6aa 100644 --- a/src/app/tests/AppTestContext.h +++ b/src/app/tests/AppTestContext.h @@ -32,9 +32,9 @@ class AppContext : public LoopbackMessagingContext // Performs shared teardown for all tests in the test suite static void TearDownTestSuite(); // Performs setup for each individual test in the test suite - void SetUp(); + virtual void SetUp(); // Performs teardown for each individual test in the test suite - void TearDown(); + virtual void TearDown(); }; } // namespace Test diff --git a/src/controller/tests/TestEventCaching.cpp b/src/controller/tests/TestEventCaching.cpp index b4c262725f74d3..e31e3ed358b6f0 100644 --- a/src/controller/tests/TestEventCaching.cpp +++ b/src/controller/tests/TestEventCaching.cpp @@ -16,8 +16,6 @@ * limitations under the License. */ -#include - #include "app-common/zap-generated/ids/Attributes.h" #include "app-common/zap-generated/ids/Clusters.h" #include "app/ClusterStateCache.h" @@ -37,7 +35,6 @@ #include #include #include -#include using namespace chip; using namespace chip::app; @@ -50,8 +47,6 @@ static uint8_t gInfoEventBuffer[4096]; static uint8_t gCritEventBuffer[4096]; static chip::app::CircularEventBuffer gCircularEventBuffer[3]; -using TestContext = chip::Test::AppContext; - // // The generated endpoint_config for the controller app has Endpoint 1 // already used in the fixed endpoint set of size 1. Consequently, let's use the next @@ -59,33 +54,10 @@ using TestContext = chip::Test::AppContext; // constexpr EndpointId kTestEndpointId = 2; -class TestEventCaching : public ::testing::Test +class TestEventCaching : public Test::AppContext { -public: - // Performs shared setup for all tests in the test suite - static void SetUpTestSuite() - { - if (mpContext == nullptr) - { - mpContext = new TestContext(); - ASSERT_NE(mpContext, nullptr); - } - mpContext->SetUpTestSuite(); - } - - // Performs shared teardown for all tests in the test suite - static void TearDownTestSuite() - { - mpContext->TearDownTestSuite(); - if (mpContext != nullptr) - { - delete mpContext; - mpContext = nullptr; - } - } - protected: - // Performs setup for each test in the suite + // Performs setup for each test in the suite. Run once for each test function. void SetUp() { const chip::app::LogStorageResources logStorageResources[] = { @@ -94,29 +66,24 @@ class TestEventCaching : public ::testing::Test { &gCritEventBuffer[0], sizeof(gCritEventBuffer), chip::app::PriorityLevel::Critical }, }; - mpContext->SetUp(); + AppContext::SetUp(); // Call parent. + VerifyOrReturn(!HasFailure()); // Stop if parent had a failure. - CHIP_ERROR err = CHIP_NO_ERROR; - // TODO: use ASSERT_EQ, once transition to pw_unit_test is complete - VerifyOrDieWithMsg((err = mEventCounter.Init(0)) == CHIP_NO_ERROR, AppServer, - "Init EventCounter failed: %" CHIP_ERROR_FORMAT, err.Format()); - chip::app::EventManagement::CreateEventManagement(&mpContext->GetExchangeManager(), ArraySize(logStorageResources), + ASSERT_EQ(mEventCounter.Init(0), CHIP_NO_ERROR); + chip::app::EventManagement::CreateEventManagement(&GetExchangeManager(), ArraySize(logStorageResources), gCircularEventBuffer, logStorageResources, &mEventCounter); } - // Performs teardown for each test in the suite + // Performs teardown for each test in the suite. Run once for each test function. void TearDown() { chip::app::EventManagement::DestroyEventManagement(); - mpContext->TearDown(); + AppContext::TearDown(); // Call parent. } - static TestContext * mpContext; - private: MonotonicallyIncreasingCounter mEventCounter; }; -TestContext * TestEventCaching::mpContext = nullptr; //clang-format off DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(testClusterAttrs) @@ -179,7 +146,7 @@ void GenerateEvents(chip::EventNumber & firstEventNumber, chip::EventNumber & la */ TEST_F(TestEventCaching, TestBasicCaching) { - auto sessionHandle = mpContext->GetSessionBobToAlice(); + auto sessionHandle = GetSessionBobToAlice(); app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance(); // Initialize the ember side server logic @@ -207,12 +174,12 @@ TEST_F(TestEventCaching, TestBasicCaching) TestReadCallback readCallback; { - app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), - readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read); + app::ReadClient readClient(engine, &GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(), + app::ReadClient::InteractionType::Read); EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); - mpContext->DrainAndServiceIO(); + DrainAndServiceIO(); uint8_t generationCount = 0; readCallback.mClusterCacheAdapter.ForEachEventData( @@ -340,12 +307,12 @@ TEST_F(TestEventCaching, TestBasicCaching) GenerateEvents(firstEventNumber, lastEventNumber); { - app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), - readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read); + app::ReadClient readClient(engine, &GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(), + app::ReadClient::InteractionType::Read); EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); - mpContext->DrainAndServiceIO(); + DrainAndServiceIO(); // // Validate that we still have all 5 of the old events we received, as well as the new ones that just got generated. @@ -392,8 +359,8 @@ TEST_F(TestEventCaching, TestBasicCaching) // we don't receive events lower than that value. // { - app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), - readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read); + app::ReadClient readClient(engine, &GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(), + app::ReadClient::InteractionType::Read); readCallback.mClusterCacheAdapter.ClearEventCache(); constexpr EventNumber kLastSeenEventNumber = 3; @@ -405,7 +372,7 @@ TEST_F(TestEventCaching, TestBasicCaching) EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); - mpContext->DrainAndServiceIO(); + DrainAndServiceIO(); // We should only get events with event numbers larger than kHighestEventNumberSeen. EXPECT_EQ(readCallback.mEventsSeen, lastEventNumber - kLastSeenEventNumber); @@ -441,12 +408,12 @@ TEST_F(TestEventCaching, TestBasicCaching) { readParams.mEventNumber.SetValue(5); - app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), - readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read); + app::ReadClient readClient(engine, &GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(), + app::ReadClient::InteractionType::Read); readCallback.mClusterCacheAdapter.ClearEventCache(true); EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); - mpContext->DrainAndServiceIO(); + DrainAndServiceIO(); // // Validate that we would receive 5 events @@ -474,7 +441,7 @@ TEST_F(TestEventCaching, TestBasicCaching) EXPECT_TRUE(highestEventNumber.HasValue() && highestEventNumber.Value() == 9); } - EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); + EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); emberAfClearDynamicEndpoint(0); } diff --git a/src/controller/tests/TestWriteChunking.cpp b/src/controller/tests/TestWriteChunking.cpp index 60be2212aa00e8..5059b8eb40f2ac 100644 --- a/src/controller/tests/TestWriteChunking.cpp +++ b/src/controller/tests/TestWriteChunking.cpp @@ -19,8 +19,6 @@ #include #include -#include - #include "app-common/zap-generated/ids/Attributes.h" #include "app-common/zap-generated/ids/Clusters.h" #include "app/ConcreteAttributePath.h" @@ -40,7 +38,6 @@ #include #include -using TestContext = chip::Test::AppContext; using namespace chip; using namespace chip::app; using namespace chip::app::Clusters; @@ -62,41 +59,39 @@ constexpr uint32_t kTestListLength = 5; // We don't really care about the content, we just need a buffer. uint8_t sByteSpanData[app::kMaxSecureSduLengthBytes]; -class TestWriteChunking : public ::testing::Test +class TestWriteChunking : public Test::AppContext { -public: - // Performs shared setup for all tests in the test suite - static void SetUpTestSuite() - { - if (mpContext == nullptr) - { - mpContext = new TestContext(); - ASSERT_NE(mpContext, nullptr); - } - mpContext->SetUpTestSuite(); - } +private: + using PathStatus = std::pair; - // Performs shared teardown for all tests in the test suite - static void TearDownTestSuite() +protected: + enum class Operations : uint8_t { - mpContext->TearDownTestSuite(); - if (mpContext != nullptr) - { - delete mpContext; - mpContext = nullptr; - } - } + kNoop, + kShutdownWriteClient, + }; -protected: - // Performs setup for each test in the suite - void SetUp() { mpContext->SetUp(); } + enum class ListData : uint8_t + { + kNull, + kList, + kBadValue, + }; - // Performs teardown for each test in the suite - void TearDown() { mpContext->TearDown(); } + struct Instructions + { + // The paths used in write request + std::vector paths; + // The type of content of the list, it should be an empty vector or its size should equals to the list of paths. + std::vector data; + // operations on OnListWriteBegin and OnListWriteEnd on the server side. + std::function onListWriteBeginActions; + // The expected status when OnListWriteEnd is called. In the same order as paths + std::vector expectedStatus; + }; - static TestContext * mpContext; + void RunTest(Instructions instructions); }; -TestContext * TestWriteChunking::mpContext = nullptr; //clang-format off DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(testClusterAttrsOnEndpoint) @@ -211,7 +206,7 @@ CHIP_ERROR TestAttrAccess::Write(const app::ConcreteDataAttributePath & aPath, a */ TEST_F(TestWriteChunking, TestListChunking) { - auto sessionHandle = mpContext->GetSessionBobToAlice(); + auto sessionHandle = GetSessionBobToAlice(); // Initialize the ember side server logic InitDataModelHandler(); @@ -238,7 +233,7 @@ TEST_F(TestWriteChunking, TestListChunking) gIterationCount = i; - app::WriteClient writeClient(&mpContext->GetExchangeManager(), &writeCallback, Optional::Missing(), + app::WriteClient writeClient(&GetExchangeManager(), &writeCallback, Optional::Missing(), static_cast(minReservationSize + i) /* reserved buffer size */); ByteSpan list[kTestListLength]; @@ -256,14 +251,14 @@ TEST_F(TestWriteChunking, TestListChunking) // for (int j = 0; j < 10 && writeCallback.mOnDoneCount == 0; j++) { - mpContext->DrainAndServiceIO(); + DrainAndServiceIO(); } EXPECT_EQ(writeCallback.mSuccessCount, kTestListLength + 1 /* an extra item for the empty list at the beginning */); EXPECT_EQ(writeCallback.mErrorCount, 0u); EXPECT_EQ(writeCallback.mOnDoneCount, 1u); - EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); + EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); // // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs. @@ -281,7 +276,7 @@ TEST_F(TestWriteChunking, TestListChunking) // As the actual overhead may change, we will test over a few possible payload lengths, from 850 to MTU used in write clients. TEST_F(TestWriteChunking, TestBadChunking) { - auto sessionHandle = mpContext->GetSessionBobToAlice(); + auto sessionHandle = GetSessionBobToAlice(); bool atLeastOneRequestSent = false; bool atLeastOneRequestFailed = false; @@ -306,7 +301,7 @@ TEST_F(TestWriteChunking, TestBadChunking) gIterationCount = (uint32_t) i; - app::WriteClient writeClient(&mpContext->GetExchangeManager(), &writeCallback, Optional::Missing()); + app::WriteClient writeClient(&GetExchangeManager(), &writeCallback, Optional::Missing()); ByteSpan list[kTestListLength]; for (auto & item : list) @@ -335,14 +330,14 @@ TEST_F(TestWriteChunking, TestBadChunking) // for (int j = 0; j < 10 && writeCallback.mOnDoneCount == 0; j++) { - mpContext->DrainAndServiceIO(); + DrainAndServiceIO(); } EXPECT_EQ(writeCallback.mSuccessCount, kTestListLength + 1 /* an extra item for the empty list at the beginning */); EXPECT_EQ(writeCallback.mErrorCount, 0u); EXPECT_EQ(writeCallback.mOnDoneCount, 1u); - EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); + EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); // // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs. @@ -352,7 +347,7 @@ TEST_F(TestWriteChunking, TestBadChunking) break; } } - EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); + EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); EXPECT_TRUE(atLeastOneRequestSent && atLeastOneRequestFailed); emberAfClearDynamicEndpoint(0); } @@ -363,7 +358,7 @@ TEST_F(TestWriteChunking, TestBadChunking) */ TEST_F(TestWriteChunking, TestConflictWrite) { - auto sessionHandle = mpContext->GetSessionBobToAlice(); + auto sessionHandle = GetSessionBobToAlice(); // Initialize the ember side server logic InitDataModelHandler(); @@ -380,11 +375,11 @@ TEST_F(TestWriteChunking, TestConflictWrite) constexpr size_t kReserveSize = kMaxSecureSduLengthBytes - 128; TestWriteCallback writeCallback1; - app::WriteClient writeClient1(&mpContext->GetExchangeManager(), &writeCallback1, Optional::Missing(), + app::WriteClient writeClient1(&GetExchangeManager(), &writeCallback1, Optional::Missing(), static_cast(kReserveSize)); TestWriteCallback writeCallback2; - app::WriteClient writeClient2(&mpContext->GetExchangeManager(), &writeCallback2, Optional::Missing(), + app::WriteClient writeClient2(&GetExchangeManager(), &writeCallback2, Optional::Missing(), static_cast(kReserveSize)); ByteSpan list[kTestListLength]; @@ -402,7 +397,7 @@ TEST_F(TestWriteChunking, TestConflictWrite) err = writeClient2.SendWriteRequest(sessionHandle); EXPECT_EQ(err, CHIP_NO_ERROR); - mpContext->DrainAndServiceIO(); + DrainAndServiceIO(); { const TestWriteCallback * writeCallbackRef1 = &writeCallback1; @@ -426,7 +421,7 @@ TEST_F(TestWriteChunking, TestConflictWrite) EXPECT_EQ(writeCallbackRef2->mOnDoneCount, 1u); } - EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); + EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); emberAfClearDynamicEndpoint(0); } @@ -437,7 +432,7 @@ TEST_F(TestWriteChunking, TestConflictWrite) */ TEST_F(TestWriteChunking, TestNonConflictWrite) { - auto sessionHandle = mpContext->GetSessionBobToAlice(); + auto sessionHandle = GetSessionBobToAlice(); // Initialize the ember side server logic InitDataModelHandler(); @@ -455,11 +450,11 @@ TEST_F(TestWriteChunking, TestNonConflictWrite) constexpr size_t kReserveSize = kMaxSecureSduLengthBytes - 128; TestWriteCallback writeCallback1; - app::WriteClient writeClient1(&mpContext->GetExchangeManager(), &writeCallback1, Optional::Missing(), + app::WriteClient writeClient1(&GetExchangeManager(), &writeCallback1, Optional::Missing(), static_cast(kReserveSize)); TestWriteCallback writeCallback2; - app::WriteClient writeClient2(&mpContext->GetExchangeManager(), &writeCallback2, Optional::Missing(), + app::WriteClient writeClient2(&GetExchangeManager(), &writeCallback2, Optional::Missing(), static_cast(kReserveSize)); ByteSpan list[kTestListLength]; @@ -477,7 +472,7 @@ TEST_F(TestWriteChunking, TestNonConflictWrite) err = writeClient2.SendWriteRequest(sessionHandle); EXPECT_EQ(err, CHIP_NO_ERROR); - mpContext->DrainAndServiceIO(); + DrainAndServiceIO(); { EXPECT_EQ(writeCallback1.mErrorCount, 0u); @@ -489,48 +484,19 @@ TEST_F(TestWriteChunking, TestNonConflictWrite) EXPECT_EQ(writeCallback2.mOnDoneCount, 1u); } - EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); + EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); emberAfClearDynamicEndpoint(0); } -namespace TestTransactionalListInstructions { - -using PathStatus = std::pair; - -enum class Operations : uint8_t -{ - kNoop, - kShutdownWriteClient, -}; - -enum class ListData : uint8_t -{ - kNull, - kList, - kBadValue, -}; - -struct Instructions -{ - // The paths used in write request - std::vector paths; - // The type of content of the list, it should be an empty vector or its size should equals to the list of paths. - std::vector data; - // operations on OnListWriteBegin and OnListWriteEnd on the server side. - std::function onListWriteBeginActions; - // The expected status when OnListWriteEnd is called. In the same order as paths - std::vector expectedStatus; -}; - -void RunTest(TestContext * pContext, Instructions instructions) +void TestWriteChunking::RunTest(Instructions instructions) { CHIP_ERROR err = CHIP_NO_ERROR; - auto sessionHandle = pContext->GetSessionBobToAlice(); + auto sessionHandle = GetSessionBobToAlice(); TestWriteCallback writeCallback; std::unique_ptr writeClient = std::make_unique( - &pContext->GetExchangeManager(), &writeCallback, Optional::Missing(), + &GetExchangeManager(), &writeCallback, Optional::Missing(), static_cast(kMaxSecureSduLengthBytes - 128) /* use a smaller chunk so we only need a few attributes in the write request. */); @@ -598,9 +564,9 @@ void RunTest(TestContext * pContext, Instructions instructions) err = writeClient->SendWriteRequest(sessionHandle); EXPECT_EQ(err, CHIP_NO_ERROR); - pContext->GetIOContext().DriveIOUntil(sessionHandle->ComputeRoundTripTimeout(app::kExpectedIMProcessingTime) + - System::Clock::Seconds16(1), - [&]() { return pContext->GetExchangeManager().GetNumActiveExchanges() == 0; }); + GetIOContext().DriveIOUntil(sessionHandle->ComputeRoundTripTimeout(app::kExpectedIMProcessingTime) + + System::Clock::Seconds16(1), + [&]() { return GetExchangeManager().GetNumActiveExchanges() == 0; }); EXPECT_EQ(onGoingPath, app::ConcreteAttributePath()); EXPECT_EQ(status.size(), instructions.expectedStatus.size()); @@ -614,12 +580,8 @@ void RunTest(TestContext * pContext, Instructions instructions) testServer.mOnListWriteEnd = nullptr; } -} // namespace TestTransactionalListInstructions - TEST_F(TestWriteChunking, TestTransactionalList) { - using namespace TestTransactionalListInstructions; - // Initialize the ember side server logic InitDataModelHandler(); @@ -631,98 +593,88 @@ TEST_F(TestWriteChunking, TestTransactionalList) // Test 1: we should receive transaction notifications ChipLogProgress(Zcl, "Test 1: we should receive transaction notifications"); - RunTest(mpContext, - Instructions{ - .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, - .expectedStatus = { true }, - }); + RunTest(Instructions{ + .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, + .expectedStatus = { true }, + }); ChipLogProgress(Zcl, "Test 2: we should receive transaction notifications for incomplete list operations"); - RunTest( - mpContext, - Instructions{ - .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, - .onListWriteBeginActions = [&](const app::ConcreteAttributePath & aPath) { return Operations::kShutdownWriteClient; }, - .expectedStatus = { false }, - }); + RunTest(Instructions{ + .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, + .onListWriteBeginActions = [&](const app::ConcreteAttributePath & aPath) { return Operations::kShutdownWriteClient; }, + .expectedStatus = { false }, + }); ChipLogProgress(Zcl, "Test 3: we should receive transaction notifications for every list in the transaction"); - RunTest(mpContext, - Instructions{ - .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), - ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute2) }, - .expectedStatus = { true, true }, - }); + RunTest(Instructions{ + .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), + ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute2) }, + .expectedStatus = { true, true }, + }); ChipLogProgress(Zcl, "Test 4: we should receive transaction notifications with the status of each list"); - RunTest(mpContext, - Instructions{ - .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), - ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute2) }, - .onListWriteBeginActions = - [&](const app::ConcreteAttributePath & aPath) { - if (aPath.mAttributeId == kTestListAttribute2) - { - return Operations::kShutdownWriteClient; - } - return Operations::kNoop; - }, - .expectedStatus = { true, false }, - }); + RunTest(Instructions{ + .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), + ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute2) }, + .onListWriteBeginActions = + [&](const app::ConcreteAttributePath & aPath) { + if (aPath.mAttributeId == kTestListAttribute2) + { + return Operations::kShutdownWriteClient; + } + return Operations::kNoop; + }, + .expectedStatus = { true, false }, + }); ChipLogProgress(Zcl, "Test 5: transactional list callbacks will be called for nullable lists, test if it is handled correctly for " "null value before non null values"); - RunTest(mpContext, - Instructions{ - .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), - ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, - .data = { ListData::kNull, ListData::kList }, - .expectedStatus = { true }, - }); + RunTest(Instructions{ + .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), + ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, + .data = { ListData::kNull, ListData::kList }, + .expectedStatus = { true }, + }); ChipLogProgress(Zcl, "Test 6: transactional list callbacks will be called for nullable lists, test if it is handled correctly for " "null value after non null values"); - RunTest(mpContext, - Instructions{ - .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), - ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, - .data = { ListData::kList, ListData::kNull }, - .expectedStatus = { true }, - }); + RunTest(Instructions{ + .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), + ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, + .data = { ListData::kList, ListData::kNull }, + .expectedStatus = { true }, + }); ChipLogProgress(Zcl, "Test 7: transactional list callbacks will be called for nullable lists, test if it is handled correctly for " "null value between non null values"); - RunTest(mpContext, - Instructions{ - .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), - ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), - ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, - .data = { ListData::kList, ListData::kNull, ListData::kList }, - .expectedStatus = { true }, - }); + RunTest(Instructions{ + .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), + ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), + ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, + .data = { ListData::kList, ListData::kNull, ListData::kList }, + .expectedStatus = { true }, + }); ChipLogProgress(Zcl, "Test 8: transactional list callbacks will be called for nullable lists"); - RunTest(mpContext, - Instructions{ - .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, - .data = { ListData::kNull }, - .expectedStatus = { true }, - }); + RunTest(Instructions{ + .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, + .data = { ListData::kNull }, + .expectedStatus = { true }, + }); ChipLogProgress(Zcl, "Test 9: for nullable lists, we should receive notifications for unsuccessful writes when non-fatal occurred " "during processing the requests"); - RunTest(mpContext, - Instructions{ - .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, - .data = { ListData::kBadValue }, - .expectedStatus = { false }, - }); - - EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); + RunTest(Instructions{ + .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, + .data = { ListData::kBadValue }, + .expectedStatus = { false }, + }); + + EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); emberAfClearDynamicEndpoint(0); } diff --git a/src/messaging/tests/MessagingContext.cpp b/src/messaging/tests/MessagingContext.cpp index 61565da1cd4ac0..c5bd53a2a7da86 100644 --- a/src/messaging/tests/MessagingContext.cpp +++ b/src/messaging/tests/MessagingContext.cpp @@ -304,9 +304,9 @@ Messaging::ExchangeContext * MessagingContext::NewExchangeToBob(Messaging::Excha return mExchangeManager.NewContext(GetSessionAliceToBob(), delegate, isInitiator); } -LoopbackTransportManager LoopbackMessagingContext::sLoopbackTransportManager; +LoopbackTransportManager * LoopbackMessagingContext::spLoopbackTransportManager = nullptr; -UDPTransportManager UDPMessagingContext::sUDPTransportManager; +UDPTransportManager * UDPMessagingContext::spUDPTransportManager = nullptr; void MessageCapturer::OnMessageReceived(const PacketHeader & packetHeader, const PayloadHeader & payloadHeader, const SessionHandle & session, DuplicateMessage isDuplicate, diff --git a/src/messaging/tests/MessagingContext.h b/src/messaging/tests/MessagingContext.h index ab9912924e713c..2af05379706f2e 100644 --- a/src/messaging/tests/MessagingContext.h +++ b/src/messaging/tests/MessagingContext.h @@ -26,14 +26,13 @@ #include #include #include +#include #include #include #include #include #include -#include - #include namespace chip { @@ -207,94 +206,105 @@ class MessagingContext : public PlatformMemoryUser }; // LoopbackMessagingContext enriches MessagingContext with an async loopback transport -class LoopbackMessagingContext : public MessagingContext +class LoopbackMessagingContext : public ::testing::Test, public MessagingContext { public: virtual ~LoopbackMessagingContext() {} - // These functions wrap sLoopbackTransportManager methods - static auto & GetSystemLayer() { return sLoopbackTransportManager.GetSystemLayer(); } - static auto & GetLoopback() { return sLoopbackTransportManager.GetLoopback(); } - static auto & GetTransportMgr() { return sLoopbackTransportManager.GetTransportMgr(); } - static auto & GetIOContext() { return sLoopbackTransportManager.GetIOContext(); } + // These functions wrap spLoopbackTransportManager methods + static auto & GetSystemLayer() { return spLoopbackTransportManager->GetSystemLayer(); } + static auto & GetLoopback() { return spLoopbackTransportManager->GetLoopback(); } + static auto & GetTransportMgr() { return spLoopbackTransportManager->GetTransportMgr(); } + static auto & GetIOContext() { return spLoopbackTransportManager->GetIOContext(); } template static void DrainAndServiceIO(Ts... args) { - return sLoopbackTransportManager.DrainAndServiceIO(args...); + return spLoopbackTransportManager->DrainAndServiceIO(args...); } // Performs shared setup for all tests in the test suite static void SetUpTestSuite() { - CHIP_ERROR err = CHIP_NO_ERROR; - // TODO: use ASSERT_EQ, once transition to pw_unit_test is complete - VerifyOrDieWithMsg((err = chip::Platform::MemoryInit()) == CHIP_NO_ERROR, AppServer, - "Init CHIP memory failed: %" CHIP_ERROR_FORMAT, err.Format()); - VerifyOrDieWithMsg((err = sLoopbackTransportManager.Init()) == CHIP_NO_ERROR, AppServer, - "Init LoopbackTransportManager failed: %" CHIP_ERROR_FORMAT, err.Format()); + // Initialize memory. + ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); + // Instantiate the LoopbackTransportManager. + ASSERT_EQ(spLoopbackTransportManager, nullptr); + spLoopbackTransportManager = new LoopbackTransportManager(); + ASSERT_NE(spLoopbackTransportManager, nullptr); + // Initialize the LoopbackTransportManager. + ASSERT_EQ(spLoopbackTransportManager->Init(), CHIP_NO_ERROR); } // Performs shared teardown for all tests in the test suite static void TearDownTestSuite() { - sLoopbackTransportManager.Shutdown(); + // Shutdown the LoopbackTransportManager. + spLoopbackTransportManager->Shutdown(); + // Destroy the LoopbackTransportManager. + if (spLoopbackTransportManager != nullptr) + { + delete spLoopbackTransportManager; + spLoopbackTransportManager = nullptr; + } + // Shutdown memory. chip::Platform::MemoryShutdown(); } // Performs setup for each individual test in the test suite - virtual void SetUp() - { - CHIP_ERROR err = CHIP_NO_ERROR; - VerifyOrDieWithMsg((err = MessagingContext::Init(&GetTransportMgr(), &GetIOContext())) == CHIP_NO_ERROR, AppServer, - "Init MessagingContext failed: %" CHIP_ERROR_FORMAT, err.Format()); - } + virtual void SetUp() { ASSERT_EQ(MessagingContext::Init(&GetTransportMgr(), &GetIOContext()), CHIP_NO_ERROR); } // Performs teardown for each individual test in the test suite virtual void TearDown() { MessagingContext::Shutdown(); } - static LoopbackTransportManager sLoopbackTransportManager; + static LoopbackTransportManager * spLoopbackTransportManager; }; // UDPMessagingContext enriches MessagingContext with an UDP transport -class UDPMessagingContext : public MessagingContext +class UDPMessagingContext : public ::testing::Test, public MessagingContext { public: virtual ~UDPMessagingContext() {} - static auto & GetSystemLayer() { return sUDPTransportManager.GetSystemLayer(); } - static auto & GetTransportMgr() { return sUDPTransportManager.GetTransportMgr(); } - static auto & GetIOContext() { return sUDPTransportManager.GetIOContext(); } + static auto & GetSystemLayer() { return spUDPTransportManager->GetSystemLayer(); } + static auto & GetTransportMgr() { return spUDPTransportManager->GetTransportMgr(); } + static auto & GetIOContext() { return spUDPTransportManager->GetIOContext(); } // Performs shared setup for all tests in the test suite static void SetUpTestSuite() { - CHIP_ERROR err = CHIP_NO_ERROR; - VerifyOrDieWithMsg((err = chip::Platform::MemoryInit()) == CHIP_NO_ERROR, AppServer, - "Init CHIP memory failed: %" CHIP_ERROR_FORMAT, err.Format()); - VerifyOrDieWithMsg((err = sUDPTransportManager.Init()) == CHIP_NO_ERROR, AppServer, - "Init UDPTransportManager failed: %" CHIP_ERROR_FORMAT, err.Format()); + // Initialize memory. + ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); + // Instantiate the UDPTransportManager. + ASSERT_EQ(spUDPTransportManager, nullptr); + spUDPTransportManager = new UDPTransportManager(); + ASSERT_NE(spUDPTransportManager, nullptr); + // Initialize the UDPTransportManager. + ASSERT_EQ(spUDPTransportManager->Init(), CHIP_NO_ERROR); } // Performs shared teardown for all tests in the test suite static void TearDownTestSuite() { - sUDPTransportManager.Shutdown(); + // Shutdown the UDPTransportManager. + spUDPTransportManager->Shutdown(); + // Destroy the UDPTransportManager. + if (spUDPTransportManager != nullptr) + { + delete spUDPTransportManager; + spUDPTransportManager = nullptr; + } + // Shutdown memory. chip::Platform::MemoryShutdown(); } // Performs setup for each individual test in the test suite - virtual void SetUp() - { - CHIP_ERROR err = CHIP_NO_ERROR; - VerifyOrDieWithMsg((err = MessagingContext::Init(&GetTransportMgr(), &GetIOContext())) == CHIP_NO_ERROR, AppServer, - "Init MessagingContext failed: %" CHIP_ERROR_FORMAT, err.Format()); - } + virtual void SetUp() { ASSERT_EQ(MessagingContext::Init(&GetTransportMgr(), &GetIOContext()), CHIP_NO_ERROR); } // Performs teardown for each individual test in the test suite virtual void TearDown() { MessagingContext::Shutdown(); } - static UDPTransportManager sUDPTransportManager; + static UDPTransportManager * spUDPTransportManager; }; // Class that can be used to capture decrypted message traffic in tests using From f8b8d7b2305bbacbc5a60fe3ab023c0649bb1ec7 Mon Sep 17 00:00:00 2001 From: Jeff Feasel Date: Thu, 16 May 2024 23:32:08 -0400 Subject: [PATCH 13/18] Undid last commit. --- src/app/tests/AppTestContext.cpp | 26 ++- src/app/tests/AppTestContext.h | 4 +- src/controller/tests/TestEventCaching.cpp | 29 ++- src/controller/tests/TestWriteChunking.cpp | 243 +++++++++++---------- src/messaging/tests/MessagingContext.cpp | 4 +- src/messaging/tests/MessagingContext.h | 90 ++++---- 6 files changed, 200 insertions(+), 196 deletions(-) diff --git a/src/app/tests/AppTestContext.cpp b/src/app/tests/AppTestContext.cpp index 28ce87d473935c..eca7a2db76c83b 100644 --- a/src/app/tests/AppTestContext.cpp +++ b/src/app/tests/AppTestContext.cpp @@ -14,7 +14,7 @@ * limitations under the License. */ -#include "AppTestContext.h" +#include #include #include @@ -40,10 +40,11 @@ namespace Test { void AppContext::SetUpTestSuite() { + CHIP_ERROR err = CHIP_NO_ERROR; LoopbackMessagingContext::SetUpTestSuite(); - VerifyOrReturn(!HasFailure()); // Stop if parent had a failure. - - ASSERT_EQ(chip::DeviceLayer::PlatformMgr().InitChipStack(), CHIP_NO_ERROR); + // TODO: use ASSERT_EQ, once transition to pw_unit_test is complete + VerifyOrDieWithMsg((err = chip::DeviceLayer::PlatformMgr().InitChipStack()) == CHIP_NO_ERROR, AppServer, + "Init CHIP stack failed: %" CHIP_ERROR_FORMAT, err.Format()); } void AppContext::TearDownTestSuite() @@ -54,15 +55,17 @@ void AppContext::TearDownTestSuite() void AppContext::SetUp() { + CHIP_ERROR err = CHIP_NO_ERROR; LoopbackMessagingContext::SetUp(); - VerifyOrReturn(!HasFailure()); // Stop if parent had a failure. - - ASSERT_EQ(app::InteractionModelEngine::GetInstance()->Init(&GetExchangeManager(), &GetFabricTable(), - app::reporting::GetDefaultReportScheduler()), - CHIP_NO_ERROR); + // TODO: use ASSERT_EQ, once transition to pw_unit_test is complete + VerifyOrDieWithMsg((err = app::InteractionModelEngine::GetInstance()->Init(&GetExchangeManager(), &GetFabricTable(), + app::reporting::GetDefaultReportScheduler())) == + CHIP_NO_ERROR, + AppServer, "Init InteractionModelEngine failed: %" CHIP_ERROR_FORMAT, err.Format()); Access::SetAccessControl(gPermissiveAccessControl); - ASSERT_EQ(Access::GetAccessControl().Init(chip::Access::Examples::GetPermissiveAccessControlDelegate(), gDeviceTypeResolver), - CHIP_NO_ERROR); + VerifyOrDieWithMsg((err = Access::GetAccessControl().Init(chip::Access::Examples::GetPermissiveAccessControlDelegate(), + gDeviceTypeResolver)) == CHIP_NO_ERROR, + AppServer, "Init AccessControl failed: %" CHIP_ERROR_FORMAT, err.Format()); } void AppContext::TearDown() @@ -70,7 +73,6 @@ void AppContext::TearDown() Access::GetAccessControl().Finish(); Access::ResetAccessControlToDefault(); chip::app::InteractionModelEngine::GetInstance()->Shutdown(); - LoopbackMessagingContext::TearDown(); } diff --git a/src/app/tests/AppTestContext.h b/src/app/tests/AppTestContext.h index c05e490fb9e6aa..a2fe3387d2d7aa 100644 --- a/src/app/tests/AppTestContext.h +++ b/src/app/tests/AppTestContext.h @@ -32,9 +32,9 @@ class AppContext : public LoopbackMessagingContext // Performs shared teardown for all tests in the test suite static void TearDownTestSuite(); // Performs setup for each individual test in the test suite - virtual void SetUp(); + void SetUp(); // Performs teardown for each individual test in the test suite - virtual void TearDown(); + void TearDown(); }; } // namespace Test diff --git a/src/controller/tests/TestEventCaching.cpp b/src/controller/tests/TestEventCaching.cpp index e31e3ed358b6f0..a847af4c7793ba 100644 --- a/src/controller/tests/TestEventCaching.cpp +++ b/src/controller/tests/TestEventCaching.cpp @@ -27,7 +27,7 @@ #include #include #include -#include +#include "AppTestContextPW.h"//+++ should be version #include #include #include @@ -54,7 +54,7 @@ static chip::app::CircularEventBuffer gCircularEventBuffer[3]; // constexpr EndpointId kTestEndpointId = 2; -class TestEventCaching : public Test::AppContext +class TestEventCaching : public Test::AppContextPW { protected: // Performs setup for each test in the suite. Run once for each test function. @@ -66,19 +66,18 @@ class TestEventCaching : public Test::AppContext { &gCritEventBuffer[0], sizeof(gCritEventBuffer), chip::app::PriorityLevel::Critical }, }; - AppContext::SetUp(); // Call parent. - VerifyOrReturn(!HasFailure()); // Stop if parent had a failure. + AppContextPW::SetUp(); // Call parent. + VerifyOrReturn(!HasFailure()); // Stop if parent had a failure. ASSERT_EQ(mEventCounter.Init(0), CHIP_NO_ERROR); - chip::app::EventManagement::CreateEventManagement(&GetExchangeManager(), ArraySize(logStorageResources), - gCircularEventBuffer, logStorageResources, &mEventCounter); + chip::app::EventManagement::CreateEventManagement(&this->GetExchangeManager(), ArraySize(logStorageResources), gCircularEventBuffer, logStorageResources, &mEventCounter); } // Performs teardown for each test in the suite. Run once for each test function. void TearDown() { chip::app::EventManagement::DestroyEventManagement(); - AppContext::TearDown(); // Call parent. + AppContextPW::TearDown(); // Call parent. } private: @@ -174,8 +173,8 @@ TEST_F(TestEventCaching, TestBasicCaching) TestReadCallback readCallback; { - app::ReadClient readClient(engine, &GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(), - app::ReadClient::InteractionType::Read); + app::ReadClient readClient(engine, &GetExchangeManager(), + readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read); EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); @@ -307,8 +306,8 @@ TEST_F(TestEventCaching, TestBasicCaching) GenerateEvents(firstEventNumber, lastEventNumber); { - app::ReadClient readClient(engine, &GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(), - app::ReadClient::InteractionType::Read); + app::ReadClient readClient(engine, &GetExchangeManager(), + readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read); EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); @@ -359,8 +358,8 @@ TEST_F(TestEventCaching, TestBasicCaching) // we don't receive events lower than that value. // { - app::ReadClient readClient(engine, &GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(), - app::ReadClient::InteractionType::Read); + app::ReadClient readClient(engine, &GetExchangeManager(), + readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read); readCallback.mClusterCacheAdapter.ClearEventCache(); constexpr EventNumber kLastSeenEventNumber = 3; @@ -408,8 +407,8 @@ TEST_F(TestEventCaching, TestBasicCaching) { readParams.mEventNumber.SetValue(5); - app::ReadClient readClient(engine, &GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(), - app::ReadClient::InteractionType::Read); + app::ReadClient readClient(engine, &GetExchangeManager(), + readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read); readCallback.mClusterCacheAdapter.ClearEventCache(true); EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); diff --git a/src/controller/tests/TestWriteChunking.cpp b/src/controller/tests/TestWriteChunking.cpp index 5059b8eb40f2ac..d495f7d09b4ea7 100644 --- a/src/controller/tests/TestWriteChunking.cpp +++ b/src/controller/tests/TestWriteChunking.cpp @@ -30,7 +30,7 @@ #include #include #include -#include +#include "AppTestContextPW.h"//+++ should be version #include #include #include @@ -59,38 +59,8 @@ constexpr uint32_t kTestListLength = 5; // We don't really care about the content, we just need a buffer. uint8_t sByteSpanData[app::kMaxSecureSduLengthBytes]; -class TestWriteChunking : public Test::AppContext +class TestWriteChunking : public Test::AppContextPW//++++ can this just be: using TestContext = chip::Test::AppContext; { -private: - using PathStatus = std::pair; - -protected: - enum class Operations : uint8_t - { - kNoop, - kShutdownWriteClient, - }; - - enum class ListData : uint8_t - { - kNull, - kList, - kBadValue, - }; - - struct Instructions - { - // The paths used in write request - std::vector paths; - // The type of content of the list, it should be an empty vector or its size should equals to the list of paths. - std::vector data; - // operations on OnListWriteBegin and OnListWriteEnd on the server side. - std::function onListWriteBeginActions; - // The expected status when OnListWriteEnd is called. In the same order as paths - std::vector expectedStatus; - }; - - void RunTest(Instructions instructions); }; //clang-format off @@ -206,7 +176,7 @@ CHIP_ERROR TestAttrAccess::Write(const app::ConcreteDataAttributePath & aPath, a */ TEST_F(TestWriteChunking, TestListChunking) { - auto sessionHandle = GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); // Initialize the ember side server logic InitDataModelHandler(); @@ -233,7 +203,7 @@ TEST_F(TestWriteChunking, TestListChunking) gIterationCount = i; - app::WriteClient writeClient(&GetExchangeManager(), &writeCallback, Optional::Missing(), + app::WriteClient writeClient(&mpContext->GetExchangeManager(), &writeCallback, Optional::Missing(), static_cast(minReservationSize + i) /* reserved buffer size */); ByteSpan list[kTestListLength]; @@ -251,14 +221,14 @@ TEST_F(TestWriteChunking, TestListChunking) // for (int j = 0; j < 10 && writeCallback.mOnDoneCount == 0; j++) { - DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); } EXPECT_EQ(writeCallback.mSuccessCount, kTestListLength + 1 /* an extra item for the empty list at the beginning */); EXPECT_EQ(writeCallback.mErrorCount, 0u); EXPECT_EQ(writeCallback.mOnDoneCount, 1u); - EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); // // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs. @@ -276,7 +246,7 @@ TEST_F(TestWriteChunking, TestListChunking) // As the actual overhead may change, we will test over a few possible payload lengths, from 850 to MTU used in write clients. TEST_F(TestWriteChunking, TestBadChunking) { - auto sessionHandle = GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); bool atLeastOneRequestSent = false; bool atLeastOneRequestFailed = false; @@ -301,7 +271,7 @@ TEST_F(TestWriteChunking, TestBadChunking) gIterationCount = (uint32_t) i; - app::WriteClient writeClient(&GetExchangeManager(), &writeCallback, Optional::Missing()); + app::WriteClient writeClient(&mpContext->GetExchangeManager(), &writeCallback, Optional::Missing()); ByteSpan list[kTestListLength]; for (auto & item : list) @@ -330,14 +300,14 @@ TEST_F(TestWriteChunking, TestBadChunking) // for (int j = 0; j < 10 && writeCallback.mOnDoneCount == 0; j++) { - DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); } EXPECT_EQ(writeCallback.mSuccessCount, kTestListLength + 1 /* an extra item for the empty list at the beginning */); EXPECT_EQ(writeCallback.mErrorCount, 0u); EXPECT_EQ(writeCallback.mOnDoneCount, 1u); - EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); // // Stop the test if we detected an error. Otherwise, it'll be difficult to read the logs. @@ -347,7 +317,7 @@ TEST_F(TestWriteChunking, TestBadChunking) break; } } - EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); EXPECT_TRUE(atLeastOneRequestSent && atLeastOneRequestFailed); emberAfClearDynamicEndpoint(0); } @@ -358,7 +328,7 @@ TEST_F(TestWriteChunking, TestBadChunking) */ TEST_F(TestWriteChunking, TestConflictWrite) { - auto sessionHandle = GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); // Initialize the ember side server logic InitDataModelHandler(); @@ -375,11 +345,11 @@ TEST_F(TestWriteChunking, TestConflictWrite) constexpr size_t kReserveSize = kMaxSecureSduLengthBytes - 128; TestWriteCallback writeCallback1; - app::WriteClient writeClient1(&GetExchangeManager(), &writeCallback1, Optional::Missing(), + app::WriteClient writeClient1(&mpContext->GetExchangeManager(), &writeCallback1, Optional::Missing(), static_cast(kReserveSize)); TestWriteCallback writeCallback2; - app::WriteClient writeClient2(&GetExchangeManager(), &writeCallback2, Optional::Missing(), + app::WriteClient writeClient2(&mpContext->GetExchangeManager(), &writeCallback2, Optional::Missing(), static_cast(kReserveSize)); ByteSpan list[kTestListLength]; @@ -397,7 +367,7 @@ TEST_F(TestWriteChunking, TestConflictWrite) err = writeClient2.SendWriteRequest(sessionHandle); EXPECT_EQ(err, CHIP_NO_ERROR); - DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); { const TestWriteCallback * writeCallbackRef1 = &writeCallback1; @@ -421,7 +391,7 @@ TEST_F(TestWriteChunking, TestConflictWrite) EXPECT_EQ(writeCallbackRef2->mOnDoneCount, 1u); } - EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); emberAfClearDynamicEndpoint(0); } @@ -432,7 +402,7 @@ TEST_F(TestWriteChunking, TestConflictWrite) */ TEST_F(TestWriteChunking, TestNonConflictWrite) { - auto sessionHandle = GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); // Initialize the ember side server logic InitDataModelHandler(); @@ -450,11 +420,11 @@ TEST_F(TestWriteChunking, TestNonConflictWrite) constexpr size_t kReserveSize = kMaxSecureSduLengthBytes - 128; TestWriteCallback writeCallback1; - app::WriteClient writeClient1(&GetExchangeManager(), &writeCallback1, Optional::Missing(), + app::WriteClient writeClient1(&mpContext->GetExchangeManager(), &writeCallback1, Optional::Missing(), static_cast(kReserveSize)); TestWriteCallback writeCallback2; - app::WriteClient writeClient2(&GetExchangeManager(), &writeCallback2, Optional::Missing(), + app::WriteClient writeClient2(&mpContext->GetExchangeManager(), &writeCallback2, Optional::Missing(), static_cast(kReserveSize)); ByteSpan list[kTestListLength]; @@ -472,7 +442,7 @@ TEST_F(TestWriteChunking, TestNonConflictWrite) err = writeClient2.SendWriteRequest(sessionHandle); EXPECT_EQ(err, CHIP_NO_ERROR); - DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); { EXPECT_EQ(writeCallback1.mErrorCount, 0u); @@ -484,19 +454,48 @@ TEST_F(TestWriteChunking, TestNonConflictWrite) EXPECT_EQ(writeCallback2.mOnDoneCount, 1u); } - EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); emberAfClearDynamicEndpoint(0); } -void TestWriteChunking::RunTest(Instructions instructions) +namespace TestTransactionalListInstructions { + +using PathStatus = std::pair; + +enum class Operations : uint8_t +{ + kNoop, + kShutdownWriteClient, +}; + +enum class ListData : uint8_t +{ + kNull, + kList, + kBadValue, +}; + +struct Instructions +{ + // The paths used in write request + std::vector paths; + // The type of content of the list, it should be an empty vector or its size should equals to the list of paths. + std::vector data; + // operations on OnListWriteBegin and OnListWriteEnd on the server side. + std::function onListWriteBeginActions; + // The expected status when OnListWriteEnd is called. In the same order as paths + std::vector expectedStatus; +}; + +void RunTest(TestContext * pContext, Instructions instructions) { CHIP_ERROR err = CHIP_NO_ERROR; - auto sessionHandle = GetSessionBobToAlice(); + auto sessionHandle = pContext->GetSessionBobToAlice(); TestWriteCallback writeCallback; std::unique_ptr writeClient = std::make_unique( - &GetExchangeManager(), &writeCallback, Optional::Missing(), + &pContext->GetExchangeManager(), &writeCallback, Optional::Missing(), static_cast(kMaxSecureSduLengthBytes - 128) /* use a smaller chunk so we only need a few attributes in the write request. */); @@ -564,9 +563,9 @@ void TestWriteChunking::RunTest(Instructions instructions) err = writeClient->SendWriteRequest(sessionHandle); EXPECT_EQ(err, CHIP_NO_ERROR); - GetIOContext().DriveIOUntil(sessionHandle->ComputeRoundTripTimeout(app::kExpectedIMProcessingTime) + - System::Clock::Seconds16(1), - [&]() { return GetExchangeManager().GetNumActiveExchanges() == 0; }); + pContext->GetIOContext().DriveIOUntil(sessionHandle->ComputeRoundTripTimeout(app::kExpectedIMProcessingTime) + + System::Clock::Seconds16(1), + [&]() { return pContext->GetExchangeManager().GetNumActiveExchanges() == 0; }); EXPECT_EQ(onGoingPath, app::ConcreteAttributePath()); EXPECT_EQ(status.size(), instructions.expectedStatus.size()); @@ -580,8 +579,12 @@ void TestWriteChunking::RunTest(Instructions instructions) testServer.mOnListWriteEnd = nullptr; } +} // namespace TestTransactionalListInstructions + TEST_F(TestWriteChunking, TestTransactionalList) { + using namespace TestTransactionalListInstructions; + // Initialize the ember side server logic InitDataModelHandler(); @@ -593,88 +596,98 @@ TEST_F(TestWriteChunking, TestTransactionalList) // Test 1: we should receive transaction notifications ChipLogProgress(Zcl, "Test 1: we should receive transaction notifications"); - RunTest(Instructions{ - .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, - .expectedStatus = { true }, - }); + RunTest(mpContext, + Instructions{ + .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, + .expectedStatus = { true }, + }); ChipLogProgress(Zcl, "Test 2: we should receive transaction notifications for incomplete list operations"); - RunTest(Instructions{ - .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, - .onListWriteBeginActions = [&](const app::ConcreteAttributePath & aPath) { return Operations::kShutdownWriteClient; }, - .expectedStatus = { false }, - }); + RunTest( + mpContext, + Instructions{ + .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, + .onListWriteBeginActions = [&](const app::ConcreteAttributePath & aPath) { return Operations::kShutdownWriteClient; }, + .expectedStatus = { false }, + }); ChipLogProgress(Zcl, "Test 3: we should receive transaction notifications for every list in the transaction"); - RunTest(Instructions{ - .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), - ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute2) }, - .expectedStatus = { true, true }, - }); + RunTest(mpContext, + Instructions{ + .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), + ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute2) }, + .expectedStatus = { true, true }, + }); ChipLogProgress(Zcl, "Test 4: we should receive transaction notifications with the status of each list"); - RunTest(Instructions{ - .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), - ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute2) }, - .onListWriteBeginActions = - [&](const app::ConcreteAttributePath & aPath) { - if (aPath.mAttributeId == kTestListAttribute2) - { - return Operations::kShutdownWriteClient; - } - return Operations::kNoop; - }, - .expectedStatus = { true, false }, - }); + RunTest(mpContext, + Instructions{ + .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), + ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute2) }, + .onListWriteBeginActions = + [&](const app::ConcreteAttributePath & aPath) { + if (aPath.mAttributeId == kTestListAttribute2) + { + return Operations::kShutdownWriteClient; + } + return Operations::kNoop; + }, + .expectedStatus = { true, false }, + }); ChipLogProgress(Zcl, "Test 5: transactional list callbacks will be called for nullable lists, test if it is handled correctly for " "null value before non null values"); - RunTest(Instructions{ - .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), - ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, - .data = { ListData::kNull, ListData::kList }, - .expectedStatus = { true }, - }); + RunTest(mpContext, + Instructions{ + .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), + ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, + .data = { ListData::kNull, ListData::kList }, + .expectedStatus = { true }, + }); ChipLogProgress(Zcl, "Test 6: transactional list callbacks will be called for nullable lists, test if it is handled correctly for " "null value after non null values"); - RunTest(Instructions{ - .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), - ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, - .data = { ListData::kList, ListData::kNull }, - .expectedStatus = { true }, - }); + RunTest(mpContext, + Instructions{ + .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), + ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, + .data = { ListData::kList, ListData::kNull }, + .expectedStatus = { true }, + }); ChipLogProgress(Zcl, "Test 7: transactional list callbacks will be called for nullable lists, test if it is handled correctly for " "null value between non null values"); - RunTest(Instructions{ - .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), - ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), - ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, - .data = { ListData::kList, ListData::kNull, ListData::kList }, - .expectedStatus = { true }, - }); + RunTest(mpContext, + Instructions{ + .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), + ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute), + ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, + .data = { ListData::kList, ListData::kNull, ListData::kList }, + .expectedStatus = { true }, + }); ChipLogProgress(Zcl, "Test 8: transactional list callbacks will be called for nullable lists"); - RunTest(Instructions{ - .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, - .data = { ListData::kNull }, - .expectedStatus = { true }, - }); + RunTest(mpContext, + Instructions{ + .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, + .data = { ListData::kNull }, + .expectedStatus = { true }, + }); ChipLogProgress(Zcl, "Test 9: for nullable lists, we should receive notifications for unsuccessful writes when non-fatal occurred " "during processing the requests"); - RunTest(Instructions{ - .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, - .data = { ListData::kBadValue }, - .expectedStatus = { false }, - }); - - EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); + RunTest(mpContext, + Instructions{ + .paths = { ConcreteAttributePath(kTestEndpointId, Clusters::UnitTesting::Id, kTestListAttribute) }, + .data = { ListData::kBadValue }, + .expectedStatus = { false }, + }); + + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); emberAfClearDynamicEndpoint(0); } diff --git a/src/messaging/tests/MessagingContext.cpp b/src/messaging/tests/MessagingContext.cpp index c5bd53a2a7da86..61565da1cd4ac0 100644 --- a/src/messaging/tests/MessagingContext.cpp +++ b/src/messaging/tests/MessagingContext.cpp @@ -304,9 +304,9 @@ Messaging::ExchangeContext * MessagingContext::NewExchangeToBob(Messaging::Excha return mExchangeManager.NewContext(GetSessionAliceToBob(), delegate, isInitiator); } -LoopbackTransportManager * LoopbackMessagingContext::spLoopbackTransportManager = nullptr; +LoopbackTransportManager LoopbackMessagingContext::sLoopbackTransportManager; -UDPTransportManager * UDPMessagingContext::spUDPTransportManager = nullptr; +UDPTransportManager UDPMessagingContext::sUDPTransportManager; void MessageCapturer::OnMessageReceived(const PacketHeader & packetHeader, const PayloadHeader & payloadHeader, const SessionHandle & session, DuplicateMessage isDuplicate, diff --git a/src/messaging/tests/MessagingContext.h b/src/messaging/tests/MessagingContext.h index 2af05379706f2e..ab9912924e713c 100644 --- a/src/messaging/tests/MessagingContext.h +++ b/src/messaging/tests/MessagingContext.h @@ -26,13 +26,14 @@ #include #include #include -#include #include #include #include #include #include +#include + #include namespace chip { @@ -206,105 +207,94 @@ class MessagingContext : public PlatformMemoryUser }; // LoopbackMessagingContext enriches MessagingContext with an async loopback transport -class LoopbackMessagingContext : public ::testing::Test, public MessagingContext +class LoopbackMessagingContext : public MessagingContext { public: virtual ~LoopbackMessagingContext() {} - // These functions wrap spLoopbackTransportManager methods - static auto & GetSystemLayer() { return spLoopbackTransportManager->GetSystemLayer(); } - static auto & GetLoopback() { return spLoopbackTransportManager->GetLoopback(); } - static auto & GetTransportMgr() { return spLoopbackTransportManager->GetTransportMgr(); } - static auto & GetIOContext() { return spLoopbackTransportManager->GetIOContext(); } + // These functions wrap sLoopbackTransportManager methods + static auto & GetSystemLayer() { return sLoopbackTransportManager.GetSystemLayer(); } + static auto & GetLoopback() { return sLoopbackTransportManager.GetLoopback(); } + static auto & GetTransportMgr() { return sLoopbackTransportManager.GetTransportMgr(); } + static auto & GetIOContext() { return sLoopbackTransportManager.GetIOContext(); } template static void DrainAndServiceIO(Ts... args) { - return spLoopbackTransportManager->DrainAndServiceIO(args...); + return sLoopbackTransportManager.DrainAndServiceIO(args...); } // Performs shared setup for all tests in the test suite static void SetUpTestSuite() { - // Initialize memory. - ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); - // Instantiate the LoopbackTransportManager. - ASSERT_EQ(spLoopbackTransportManager, nullptr); - spLoopbackTransportManager = new LoopbackTransportManager(); - ASSERT_NE(spLoopbackTransportManager, nullptr); - // Initialize the LoopbackTransportManager. - ASSERT_EQ(spLoopbackTransportManager->Init(), CHIP_NO_ERROR); + CHIP_ERROR err = CHIP_NO_ERROR; + // TODO: use ASSERT_EQ, once transition to pw_unit_test is complete + VerifyOrDieWithMsg((err = chip::Platform::MemoryInit()) == CHIP_NO_ERROR, AppServer, + "Init CHIP memory failed: %" CHIP_ERROR_FORMAT, err.Format()); + VerifyOrDieWithMsg((err = sLoopbackTransportManager.Init()) == CHIP_NO_ERROR, AppServer, + "Init LoopbackTransportManager failed: %" CHIP_ERROR_FORMAT, err.Format()); } // Performs shared teardown for all tests in the test suite static void TearDownTestSuite() { - // Shutdown the LoopbackTransportManager. - spLoopbackTransportManager->Shutdown(); - // Destroy the LoopbackTransportManager. - if (spLoopbackTransportManager != nullptr) - { - delete spLoopbackTransportManager; - spLoopbackTransportManager = nullptr; - } - // Shutdown memory. + sLoopbackTransportManager.Shutdown(); chip::Platform::MemoryShutdown(); } // Performs setup for each individual test in the test suite - virtual void SetUp() { ASSERT_EQ(MessagingContext::Init(&GetTransportMgr(), &GetIOContext()), CHIP_NO_ERROR); } + virtual void SetUp() + { + CHIP_ERROR err = CHIP_NO_ERROR; + VerifyOrDieWithMsg((err = MessagingContext::Init(&GetTransportMgr(), &GetIOContext())) == CHIP_NO_ERROR, AppServer, + "Init MessagingContext failed: %" CHIP_ERROR_FORMAT, err.Format()); + } // Performs teardown for each individual test in the test suite virtual void TearDown() { MessagingContext::Shutdown(); } - static LoopbackTransportManager * spLoopbackTransportManager; + static LoopbackTransportManager sLoopbackTransportManager; }; // UDPMessagingContext enriches MessagingContext with an UDP transport -class UDPMessagingContext : public ::testing::Test, public MessagingContext +class UDPMessagingContext : public MessagingContext { public: virtual ~UDPMessagingContext() {} - static auto & GetSystemLayer() { return spUDPTransportManager->GetSystemLayer(); } - static auto & GetTransportMgr() { return spUDPTransportManager->GetTransportMgr(); } - static auto & GetIOContext() { return spUDPTransportManager->GetIOContext(); } + static auto & GetSystemLayer() { return sUDPTransportManager.GetSystemLayer(); } + static auto & GetTransportMgr() { return sUDPTransportManager.GetTransportMgr(); } + static auto & GetIOContext() { return sUDPTransportManager.GetIOContext(); } // Performs shared setup for all tests in the test suite static void SetUpTestSuite() { - // Initialize memory. - ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); - // Instantiate the UDPTransportManager. - ASSERT_EQ(spUDPTransportManager, nullptr); - spUDPTransportManager = new UDPTransportManager(); - ASSERT_NE(spUDPTransportManager, nullptr); - // Initialize the UDPTransportManager. - ASSERT_EQ(spUDPTransportManager->Init(), CHIP_NO_ERROR); + CHIP_ERROR err = CHIP_NO_ERROR; + VerifyOrDieWithMsg((err = chip::Platform::MemoryInit()) == CHIP_NO_ERROR, AppServer, + "Init CHIP memory failed: %" CHIP_ERROR_FORMAT, err.Format()); + VerifyOrDieWithMsg((err = sUDPTransportManager.Init()) == CHIP_NO_ERROR, AppServer, + "Init UDPTransportManager failed: %" CHIP_ERROR_FORMAT, err.Format()); } // Performs shared teardown for all tests in the test suite static void TearDownTestSuite() { - // Shutdown the UDPTransportManager. - spUDPTransportManager->Shutdown(); - // Destroy the UDPTransportManager. - if (spUDPTransportManager != nullptr) - { - delete spUDPTransportManager; - spUDPTransportManager = nullptr; - } - // Shutdown memory. + sUDPTransportManager.Shutdown(); chip::Platform::MemoryShutdown(); } // Performs setup for each individual test in the test suite - virtual void SetUp() { ASSERT_EQ(MessagingContext::Init(&GetTransportMgr(), &GetIOContext()), CHIP_NO_ERROR); } + virtual void SetUp() + { + CHIP_ERROR err = CHIP_NO_ERROR; + VerifyOrDieWithMsg((err = MessagingContext::Init(&GetTransportMgr(), &GetIOContext())) == CHIP_NO_ERROR, AppServer, + "Init MessagingContext failed: %" CHIP_ERROR_FORMAT, err.Format()); + } // Performs teardown for each individual test in the test suite virtual void TearDown() { MessagingContext::Shutdown(); } - static UDPTransportManager * spUDPTransportManager; + static UDPTransportManager sUDPTransportManager; }; // Class that can be used to capture decrypted message traffic in tests using From 8b99c5a159c14fa44961b01bc20f0e66004554c7 Mon Sep 17 00:00:00 2001 From: Jeff Feasel Date: Thu, 16 May 2024 23:37:23 -0400 Subject: [PATCH 14/18] Undid last commit. --- src/controller/tests/TestEventCaching.cpp | 72 ++++++++++++++++------ src/controller/tests/TestWriteChunking.cpp | 41 +++++++++++- 2 files changed, 91 insertions(+), 22 deletions(-) diff --git a/src/controller/tests/TestEventCaching.cpp b/src/controller/tests/TestEventCaching.cpp index a847af4c7793ba..b4c262725f74d3 100644 --- a/src/controller/tests/TestEventCaching.cpp +++ b/src/controller/tests/TestEventCaching.cpp @@ -16,6 +16,8 @@ * limitations under the License. */ +#include + #include "app-common/zap-generated/ids/Attributes.h" #include "app-common/zap-generated/ids/Clusters.h" #include "app/ClusterStateCache.h" @@ -27,7 +29,7 @@ #include #include #include -#include "AppTestContextPW.h"//+++ should be version +#include #include #include #include @@ -35,6 +37,7 @@ #include #include #include +#include using namespace chip; using namespace chip::app; @@ -47,6 +50,8 @@ static uint8_t gInfoEventBuffer[4096]; static uint8_t gCritEventBuffer[4096]; static chip::app::CircularEventBuffer gCircularEventBuffer[3]; +using TestContext = chip::Test::AppContext; + // // The generated endpoint_config for the controller app has Endpoint 1 // already used in the fixed endpoint set of size 1. Consequently, let's use the next @@ -54,10 +59,33 @@ static chip::app::CircularEventBuffer gCircularEventBuffer[3]; // constexpr EndpointId kTestEndpointId = 2; -class TestEventCaching : public Test::AppContextPW +class TestEventCaching : public ::testing::Test { +public: + // Performs shared setup for all tests in the test suite + static void SetUpTestSuite() + { + if (mpContext == nullptr) + { + mpContext = new TestContext(); + ASSERT_NE(mpContext, nullptr); + } + mpContext->SetUpTestSuite(); + } + + // Performs shared teardown for all tests in the test suite + static void TearDownTestSuite() + { + mpContext->TearDownTestSuite(); + if (mpContext != nullptr) + { + delete mpContext; + mpContext = nullptr; + } + } + protected: - // Performs setup for each test in the suite. Run once for each test function. + // Performs setup for each test in the suite void SetUp() { const chip::app::LogStorageResources logStorageResources[] = { @@ -66,23 +94,29 @@ class TestEventCaching : public Test::AppContextPW { &gCritEventBuffer[0], sizeof(gCritEventBuffer), chip::app::PriorityLevel::Critical }, }; - AppContextPW::SetUp(); // Call parent. - VerifyOrReturn(!HasFailure()); // Stop if parent had a failure. + mpContext->SetUp(); - ASSERT_EQ(mEventCounter.Init(0), CHIP_NO_ERROR); - chip::app::EventManagement::CreateEventManagement(&this->GetExchangeManager(), ArraySize(logStorageResources), gCircularEventBuffer, logStorageResources, &mEventCounter); + CHIP_ERROR err = CHIP_NO_ERROR; + // TODO: use ASSERT_EQ, once transition to pw_unit_test is complete + VerifyOrDieWithMsg((err = mEventCounter.Init(0)) == CHIP_NO_ERROR, AppServer, + "Init EventCounter failed: %" CHIP_ERROR_FORMAT, err.Format()); + chip::app::EventManagement::CreateEventManagement(&mpContext->GetExchangeManager(), ArraySize(logStorageResources), + gCircularEventBuffer, logStorageResources, &mEventCounter); } - // Performs teardown for each test in the suite. Run once for each test function. + // Performs teardown for each test in the suite void TearDown() { chip::app::EventManagement::DestroyEventManagement(); - AppContextPW::TearDown(); // Call parent. + mpContext->TearDown(); } + static TestContext * mpContext; + private: MonotonicallyIncreasingCounter mEventCounter; }; +TestContext * TestEventCaching::mpContext = nullptr; //clang-format off DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(testClusterAttrs) @@ -145,7 +179,7 @@ void GenerateEvents(chip::EventNumber & firstEventNumber, chip::EventNumber & la */ TEST_F(TestEventCaching, TestBasicCaching) { - auto sessionHandle = GetSessionBobToAlice(); + auto sessionHandle = mpContext->GetSessionBobToAlice(); app::InteractionModelEngine * engine = app::InteractionModelEngine::GetInstance(); // Initialize the ember side server logic @@ -173,12 +207,12 @@ TEST_F(TestEventCaching, TestBasicCaching) TestReadCallback readCallback; { - app::ReadClient readClient(engine, &GetExchangeManager(), + app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read); EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); - DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); uint8_t generationCount = 0; readCallback.mClusterCacheAdapter.ForEachEventData( @@ -306,12 +340,12 @@ TEST_F(TestEventCaching, TestBasicCaching) GenerateEvents(firstEventNumber, lastEventNumber); { - app::ReadClient readClient(engine, &GetExchangeManager(), + app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read); EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); - DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // // Validate that we still have all 5 of the old events we received, as well as the new ones that just got generated. @@ -358,7 +392,7 @@ TEST_F(TestEventCaching, TestBasicCaching) // we don't receive events lower than that value. // { - app::ReadClient readClient(engine, &GetExchangeManager(), + app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read); readCallback.mClusterCacheAdapter.ClearEventCache(); @@ -371,7 +405,7 @@ TEST_F(TestEventCaching, TestBasicCaching) EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); - DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // We should only get events with event numbers larger than kHighestEventNumberSeen. EXPECT_EQ(readCallback.mEventsSeen, lastEventNumber - kLastSeenEventNumber); @@ -407,12 +441,12 @@ TEST_F(TestEventCaching, TestBasicCaching) { readParams.mEventNumber.SetValue(5); - app::ReadClient readClient(engine, &GetExchangeManager(), + app::ReadClient readClient(engine, &mpContext->GetExchangeManager(), readCallback.mClusterCacheAdapter.GetBufferedCallback(), app::ReadClient::InteractionType::Read); readCallback.mClusterCacheAdapter.ClearEventCache(true); EXPECT_EQ(readClient.SendRequest(readParams), CHIP_NO_ERROR); - DrainAndServiceIO(); + mpContext->DrainAndServiceIO(); // // Validate that we would receive 5 events @@ -440,7 +474,7 @@ TEST_F(TestEventCaching, TestBasicCaching) EXPECT_TRUE(highestEventNumber.HasValue() && highestEventNumber.Value() == 9); } - EXPECT_EQ(GetExchangeManager().GetNumActiveExchanges(), 0u); + EXPECT_EQ(mpContext->GetExchangeManager().GetNumActiveExchanges(), 0u); emberAfClearDynamicEndpoint(0); } diff --git a/src/controller/tests/TestWriteChunking.cpp b/src/controller/tests/TestWriteChunking.cpp index d495f7d09b4ea7..63ca9bf3402a41 100644 --- a/src/controller/tests/TestWriteChunking.cpp +++ b/src/controller/tests/TestWriteChunking.cpp @@ -19,6 +19,8 @@ #include #include +#include + #include "app-common/zap-generated/ids/Attributes.h" #include "app-common/zap-generated/ids/Clusters.h" #include "app/ConcreteAttributePath.h" @@ -30,7 +32,7 @@ #include #include #include -#include "AppTestContextPW.h"//+++ should be version +#include #include #include #include @@ -38,6 +40,7 @@ #include #include +using TestContext = chip::Test::AppContext; using namespace chip; using namespace chip::app; using namespace chip::app::Clusters; @@ -59,9 +62,41 @@ constexpr uint32_t kTestListLength = 5; // We don't really care about the content, we just need a buffer. uint8_t sByteSpanData[app::kMaxSecureSduLengthBytes]; -class TestWriteChunking : public Test::AppContextPW//++++ can this just be: using TestContext = chip::Test::AppContext; +class TestWriteChunking : public ::testing::Test { +public: + // Performs shared setup for all tests in the test suite + static void SetUpTestSuite() + { + if (mpContext == nullptr) + { + mpContext = new TestContext(); + ASSERT_NE(mpContext, nullptr); + } + mpContext->SetUpTestSuite(); + } + + // Performs shared teardown for all tests in the test suite + static void TearDownTestSuite() + { + mpContext->TearDownTestSuite(); + if (mpContext != nullptr) + { + delete mpContext; + mpContext = nullptr; + } + } + +protected: + // Performs setup for each test in the suite + void SetUp() { mpContext->SetUp(); } + + // Performs teardown for each test in the suite + void TearDown() { mpContext->TearDown(); } + + static TestContext * mpContext; }; +TestContext * TestWriteChunking::mpContext = nullptr; //clang-format off DECLARE_DYNAMIC_ATTRIBUTE_LIST_BEGIN(testClusterAttrsOnEndpoint) @@ -692,4 +727,4 @@ TEST_F(TestWriteChunking, TestTransactionalList) emberAfClearDynamicEndpoint(0); } -} // namespace +} // namespace \ No newline at end of file From 44b9f55e37b229422b0ab5d9a2a279f63cd3c318 Mon Sep 17 00:00:00 2001 From: Jeff Feasel Date: Thu, 16 May 2024 23:43:10 -0400 Subject: [PATCH 15/18] Undid last commit. --- src/controller/tests/TestWriteChunking.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controller/tests/TestWriteChunking.cpp b/src/controller/tests/TestWriteChunking.cpp index 63ca9bf3402a41..60be2212aa00e8 100644 --- a/src/controller/tests/TestWriteChunking.cpp +++ b/src/controller/tests/TestWriteChunking.cpp @@ -727,4 +727,4 @@ TEST_F(TestWriteChunking, TestTransactionalList) emberAfClearDynamicEndpoint(0); } -} // namespace \ No newline at end of file +} // namespace From 32b7e618f44647d0b5a514a47fe107c69c4053f4 Mon Sep 17 00:00:00 2001 From: "Restyled.io" Date: Fri, 17 May 2024 03:44:02 +0000 Subject: [PATCH 16/18] Restyled by prettier-markdown --- docs/guides/openiotsdk_unit_tests.md | 4 +- docs/testing/unit_testing.md | 124 ++++++++++++++++++++------- 2 files changed, 94 insertions(+), 34 deletions(-) diff --git a/docs/guides/openiotsdk_unit_tests.md b/docs/guides/openiotsdk_unit_tests.md index a24bab2c77c876..7560d4dbd959a2 100644 --- a/docs/guides/openiotsdk_unit_tests.md +++ b/docs/guides/openiotsdk_unit_tests.md @@ -6,8 +6,8 @@ tests that are located in the `test` directory, e.g. `src/inet/tests`. Those sources are built as a static library that can be linked to the unit test application separately or as a monolithic test library. The common Matter test library collects all test cases and provides the engine based on -[Pigweed Unit Test](https://pigweed.dev/pw_unit_test) to run them in -the application. +[Pigweed Unit Test](https://pigweed.dev/pw_unit_test) to run them in the +application. The Open IoT SDK unit tests implementation are located in the `src/test_driver/openiotsdk/unit-tests` directory. This project builds a diff --git a/docs/testing/unit_testing.md b/docs/testing/unit_testing.md index 1bd7a85b4e598b..72e8828dc252d7 100644 --- a/docs/testing/unit_testing.md +++ b/docs/testing/unit_testing.md @@ -13,13 +13,16 @@ ## Unit testing in the SDK using pw_unit_test -This SDK uses Pigweed unit test (pw_unit_test), which is an implementation of GoogleTest. For more information see the [pw_unit_test documentation](https://pigweed.dev/pw_unit_test/) or the [GoogleTest documentation](https://google.github.io/googletest/). +This SDK uses Pigweed unit test (pw_unit_test), which is an implementation of +GoogleTest. For more information see the +[pw_unit_test documentation](https://pigweed.dev/pw_unit_test/) or the +[GoogleTest documentation](https://google.github.io/googletest/). ### Simple unit tests -The following example demonstrates how to use pw_unit_test to write a simple unit test. -Each test function is defined using `TEST(NameOfFunction)`. -The set of test functions in a given source file is called a "suite". +The following example demonstrates how to use pw_unit_test to write a simple +unit test. Each test function is defined using `TEST(NameOfFunction)`. The set +of test functions in a given source file is called a "suite". ``` #include @@ -48,7 +51,7 @@ TEST(YourTestFunction2) } ``` -See +See [TestSpan.cpp](https://github.com/project-chip/connectedhomeip/blob/master/src/lib/support/tests/TestSpan.cpp) for an example of a simple unit test. @@ -56,9 +59,11 @@ In the above example there are no fixtures or setup/teardown behavior. ### Test fixtures and setup/teardown behavior -If your tests need fixtures or some kind of setup/teardown you will need to define a test context that derives from `::testing::Test`. -Each of your test functions will be defined with `TEST_F(NameOfTestContext, NameOfFunction)`. -The following example demonstrates how to use pw_unit_test to write a unit test that uses fixtures and setup/teardown behavior. +If your tests need fixtures or some kind of setup/teardown you will need to +define a test context that derives from `::testing::Test`. Each of your test +functions will be defined with `TEST_F(NameOfTestContext, NameOfFunction)`. The +following example demonstrates how to use pw_unit_test to write a unit test that +uses fixtures and setup/teardown behavior. ``` #include @@ -129,9 +134,11 @@ TEST_F(YourTestContext, YourTestFunction2) ### A loopback messaging context for convenience -If you need messaging, there is a convenience class [Test::AppContext](https://github.com/project-chip/connectedhomeip/blob/master/src/app/tests/AppTestContext.h) that you can derive your test context from. -It provides a network layer and a system layer and two secure sessions connected with each other. -The following example demonstrates this. +If you need messaging, there is a convenience class +[Test::AppContext](https://github.com/project-chip/connectedhomeip/blob/master/src/app/tests/AppTestContext.h) +that you can derive your test context from. It provides a network layer and a +system layer and two secure sessions connected with each other. The following +example demonstrates this. ``` #include @@ -200,32 +207,47 @@ TEST_F(YourTestContext, YourTestFunction2) } ``` -You don't have to override all 4 functions `SetUpTestsuite`, `TearDownTestSuite`, `SetUp`, `TearDown`. -If you don't need any custom behavior in one of those functions just omit it. +You don't have to override all 4 functions `SetUpTestsuite`, +`TearDownTestSuite`, `SetUp`, `TearDown`. If you don't need any custom behavior +in one of those functions just omit it. -If you override one of the setup/teardown functions make sure to invoke the parent's version of the function as well. `AppContext::SetUpTestSuite` and `AppContext::SetUp` may generate fatal failures, so after you call these from your overriding function make sure to check `HasFailure()` and return if the parent function failed. - -If you don't override any of the setup/teardown functions, you can simply make a type alias: `using YourTestContext = Test::AppContextPW;` instead of defining your own text context class. +If you override one of the setup/teardown functions make sure to invoke the +parent's version of the function as well. `AppContext::SetUpTestSuite` and +`AppContext::SetUp` may generate fatal failures, so after you call these from +your overriding function make sure to check `HasFailure()` and return if the +parent function failed. +If you don't override any of the setup/teardown functions, you can simply make a +type alias: `using YourTestContext = Test::AppContextPW;` instead of defining +your own text context class. ## Best practices -- Try to use as specific an assertion as possible. For example use these +- Try to use as specific an assertion as possible. For example use these + ``` EXPECT_EQ(result, 3); EXPECT_GT(result, 1); EXPECT_STREQ(myString, "hello"); ``` + instead of these + ``` EXPECT_TRUE(result == 3); EXPECT_TRUE(result > 1); EXPECT_EQ(strcmp(myString, "hello"), 0); ``` -- If you want a test to abort when an assertion fails, use `ASSERT_*` instead of `EXPECT_*`. This will cause the current function to return. +- If you want a test to abort when an assertion fails, use `ASSERT_*` instead + of `EXPECT_*`. This will cause the current function to return. + +- If a test calls a subroutine which exits due to an ASSERT failing, execution + of the test will continue after the subroutine call. This is because ASSERT + causes the subroutine to return (as opposed to throwing an exception). If + you want to prevent the test from continuing, check the value of + `HasFailure()` and stop execution if true. Example: -- If a test calls a subroutine which exits due to an ASSERT failing, execution of the test will continue after the subroutine call. This is because ASSERT causes the subroutine to return (as opposed to throwing an exception). If you want to prevent the test from continuing, check the value of `HasFailure()` and stop execution if true. Example: ``` void Subroutine() { @@ -242,19 +264,53 @@ If you don't override any of the setup/teardown functions, you can simply make a } ``` -- If you want to force a fatal failure use `FAIL()`, which will record a fatal failure and exit the current function. This is similar to using `ASSERT_TRUE(false)`. If you want to force a non-fatal failure use `ADD_FAILURE()`, which will record a non-fatal failure and continue executing the current function. This is similar to using `EXPECT_TRUE(false)`. - -- `ASSERT_*` and `FAIL` will only work in functions with void return type, since they generate a `return;` statement. - If you must use these in a non-void function, instead use `EXPECT_*` or `ADD_FAILURE` and then check `HasFailure()` afterward and return if needed. - -- If your test requires access to private/protected members of the underlying class you're testing, you'll need to create an accessor class that performs these operations and is friended to the underlying class. Please name the class `chip::Test::SomethingTestAccess` where `Something` is the name of the underlying class whose private/protected members you're trying to access. Then add `friend class chip::Test::SomethingTestAccess;` to the underlying class. Make sure your test's BUILD.gn file contains `sources = [ "SomethingTestAccess.h" ]`. Before creating a new TestAccess class, check if one already exists. If it does exist but doesn't expose the member you need, you can add a function to that class to do so. Note that you should make these functions as minimal as possible, with no logic besides just exposing the private/protected member. - - For an example see `ICDConfigurationDataTestAccess` which is defined in [ICDConfigurationDataTestAccess.h](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/tests/ICDConfigurationDataTestAccess.h), friends the underlying class in [ICDConfigurationData.h](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/ICDConfigurationData.h), is included as a source in [BUILD.gn](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/tests/BUILD.gn), and is used by a test in [TestICDManager.cpp](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/tests/TestICDManager.cpp). - - For another example see `TCPBaseTestAccess` which is defined in [TCPBaseTestAccess.h](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/tests/TCPBaseTestAccess.h), friends the underlying class in [TCP.h](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/TCP.h), is included as a source in [BUILD.gn](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/tests/BUILD.gn), and is used by a test in [TestTCP.cpp](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/tests/TestTCP.cpp). +- If you want to force a fatal failure use `FAIL()`, which will record a fatal + failure and exit the current function. This is similar to using + `ASSERT_TRUE(false)`. If you want to force a non-fatal failure use + `ADD_FAILURE()`, which will record a non-fatal failure and continue + executing the current function. This is similar to using + `EXPECT_TRUE(false)`. + +- `ASSERT_*` and `FAIL` will only work in functions with void return type, + since they generate a `return;` statement. If you must use these in a + non-void function, instead use `EXPECT_*` or `ADD_FAILURE` and then check + `HasFailure()` afterward and return if needed. + +- If your test requires access to private/protected members of the underlying + class you're testing, you'll need to create an accessor class that performs + these operations and is friended to the underlying class. Please name the + class `chip::Test::SomethingTestAccess` where `Something` is the name of the + underlying class whose private/protected members you're trying to access. + Then add `friend class chip::Test::SomethingTestAccess;` to the underlying + class. Make sure your test's BUILD.gn file contains + `sources = [ "SomethingTestAccess.h" ]`. Before creating a new TestAccess + class, check if one already exists. If it does exist but doesn't expose the + member you need, you can add a function to that class to do so. Note that + you should make these functions as minimal as possible, with no logic + besides just exposing the private/protected member. + - For an example see `ICDConfigurationDataTestAccess` which is defined in + [ICDConfigurationDataTestAccess.h](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/tests/ICDConfigurationDataTestAccess.h), + friends the underlying class in + [ICDConfigurationData.h](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/ICDConfigurationData.h), + is included as a source in + [BUILD.gn](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/tests/BUILD.gn), + and is used by a test in + [TestICDManager.cpp](https://github.com/project-chip/connectedhomeip/blob/master/src/app/icd/server/tests/TestICDManager.cpp). + - For another example see `TCPBaseTestAccess` which is defined in + [TCPBaseTestAccess.h](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/tests/TCPBaseTestAccess.h), + friends the underlying class in + [TCP.h](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/TCP.h), + is included as a source in + [BUILD.gn](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/tests/BUILD.gn), + and is used by a test in + [TestTCP.cpp](https://github.com/project-chip/connectedhomeip/blob/master/src/transport/raw/tests/TestTCP.cpp). ## Compiling and running - Add to `src/some_directory/tests/BUILD.gn` - - Example + + - Example + ``` chip_test_suite("tests") { output_name = "libSomethingTests" @@ -276,15 +332,19 @@ If you don't override any of the setup/teardown functions, you can simply make a ] } ``` - - Another example: [src/lib/support/tests/BUILD.gn](https://github.com/project-chip/connectedhomeip/blob/master/src/lib/support/tests/BUILD.gn) -- Build and run all tests with [./gn_build.sh](https://github.com/project-chip/connectedhomeip/blob/master/gn_build.sh) + + - Another example: + [src/lib/support/tests/BUILD.gn](https://github.com/project-chip/connectedhomeip/blob/master/src/lib/support/tests/BUILD.gn) + +- Build and run all tests with + [./gn_build.sh](https://github.com/project-chip/connectedhomeip/blob/master/gn_build.sh) - CI runs this, so any unit tests that get added will automatically be added to the CI. - Test binaries are compiled into: - `out/debug//tests` - e.g. `out/debug/linux_x64_clang/tests` -- Tests are run when `./gn_build.sh` runs, but you can run them individually in - a debugger from their location. +- Tests are run when `./gn_build.sh` runs, but you can run them individually + in a debugger from their location. ## Debugging unit tests From ab8439fa31cdcfdd2ff09bf08e180f21643b872e Mon Sep 17 00:00:00 2001 From: Jeff Feasel Date: Fri, 17 May 2024 09:48:05 -0400 Subject: [PATCH 17/18] Changed the includes to be consistent. --- docs/testing/unit_testing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/testing/unit_testing.md b/docs/testing/unit_testing.md index 72e8828dc252d7..31899899300b9d 100644 --- a/docs/testing/unit_testing.md +++ b/docs/testing/unit_testing.md @@ -66,7 +66,7 @@ following example demonstrates how to use pw_unit_test to write a unit test that uses fixtures and setup/teardown behavior. ``` -#include +#include class YourTestContext : public ::testing::Test { From 1a06ad529be6765c66667d823fa6c1659c49ae5a Mon Sep 17 00:00:00 2001 From: Jeff Feasel Date: Fri, 17 May 2024 14:47:49 +0000 Subject: [PATCH 18/18] Cleaned up example code comments --- docs/testing/unit_testing.md | 76 ++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/docs/testing/unit_testing.md b/docs/testing/unit_testing.md index 31899899300b9d..64046a4c21a9d6 100644 --- a/docs/testing/unit_testing.md +++ b/docs/testing/unit_testing.md @@ -29,14 +29,14 @@ of test functions in a given source file is called a "suite". TEST(YourTestFunction1) { - //// Do some test things here, then check the results using EXPECT_* + // Do some test things here, then check the results using EXPECT_* SomeTypeX foo; foo.DoSomething(); EXPECT_EQ(foo.GetResultCount(), 7); foo.DoSomethingElse(); EXPECT_EQ(foo.GetResultCount(), 5); - //// If you want to abort the rest of the test upon failure, use ASSERT_* + // If you want to abort the rest of the test upon failure, use ASSERT_* SomeTypeY * ptr = foo.GetSomePointer(); ASSERT_NE(ptr, nullptr); ptr->DoTheThing(); // Won't reach here if the ASSERT failed. @@ -44,7 +44,7 @@ TEST(YourTestFunction1) TEST(YourTestFunction2) { - //// Do some test things here, then check the results using EXPECT_* + // Do some test things here, then check the results using EXPECT_* SomeTypeZ foo; foo.DoSomething(); EXPECT_EQ(foo.GetResultCount(), 3); @@ -74,51 +74,51 @@ public: // Performs shared setup for all tests in the test suite. Run once for the whole suite. static void SetUpTestSuite() { - //// Your per-suite setup goes here: - // sPerSuiteFixture.Init(); - // ASSERT_TRUE(sPerSuiteFixture.WorkingGreat()); + // Your per-suite setup goes here: + sPerSuiteFixture.Init(); + ASSERT_TRUE(sPerSuiteFixture.WorkingGreat()); } // Performs shared teardown for all tests in the test suite. Run once for the whole suite. static void TearDownTestSuite() { - //// Your per-suite teardown goes here: - // sPerSuiteFixture.Shutdown(); + // Your per-suite teardown goes here: + sPerSuiteFixture.Shutdown(); } protected: // Performs setup for each test in the suite. Run once for each test function. void SetUp() { - //// Your per-test setup goes here: - // mPerTestFixture.Init(); - // ASSERT_TRUE(mPerTestFixture.WorkingGreat()); + // Your per-test setup goes here: + mPerTestFixture.Init(); + ASSERT_TRUE(mPerTestFixture.WorkingGreat()); } // Performs teardown for each test in the suite. Run once for each test function. void TearDown() { - //// Your per-test teardown goes here: - // mPerTestFixture.Shutdown(); + // Your per-test teardown goes here: + mPerTestFixture.Shutdown(); } private: - //// Your per-suite and per-test fixtures are declared here: - // static SomeTypeA sPerSuiteFixture; - // SomeTypeB mPerTestFixture; + // Your per-suite and per-test fixtures are declared here: + static SomeTypeA sPerSuiteFixture; + SomeTypeB mPerTestFixture; }; -//// Your per-suite fixtures are defined here: -// SomeTypeA YourTestContext::sPerSuiteFixture; +// Your per-suite fixtures are defined here: +SomeTypeA YourTestContext::sPerSuiteFixture; TEST_F(YourTestContext, YourTestFunction1) { - //// Do some test things here, then check the results using EXPECT_* + // Do some test things here, then check the results using EXPECT_* mPerTestFixture.DoSomething(); EXPECT_EQ(mPerTestFixture.GetResultCount(), 7); sPerSuiteFixture.DoSomething(); EXPECT_EQ(sPerSuiteFixture.GetResultCount(), 5); - //// If you want to abort the rest of the test upon failure, use ASSERT_* + // If you want to abort the rest of the test upon failure, use ASSERT_* SomeTypeC * ptr = mPerTestFixture.GetSomePointer(); ASSERT_NE(ptr, nullptr); ptr->DoTheThing(); // Won't reach here if the ASSERT failed. @@ -126,7 +126,7 @@ TEST_F(YourTestContext, YourTestFunction1) TEST_F(YourTestContext, YourTestFunction2) { - //// Do some test things here, then check the results using EXPECT_* + // Do some test things here, then check the results using EXPECT_* mPerTestFixture.DoSomethingElse(); EXPECT_EQ(mPerTestFixture.GetResultCount(), 9); } @@ -152,16 +152,16 @@ public: AppContext::SetUpTestSuite(); // Call parent. VerifyOrReturn(!HasFailure()); // Stop if parent had a failure. - //// Your per-suite setup goes here: - // sPerSuiteFixture.Init(); - // ASSERT_TRUE(sPerSuiteFixture.WorkingGreat()); + // Your per-suite setup goes here: + sPerSuiteFixture.Init(); + ASSERT_TRUE(sPerSuiteFixture.WorkingGreat()); } // Performs shared teardown for all tests in the test suite. Run once for the whole suite. static void TearDownTestSuite() { - //// Your per-suite teardown goes here: - // sPerSuiteFixture.Shutdown(); + // Your per-suite teardown goes here: + sPerSuiteFixture.Shutdown(); AppContext::TearDownTestSuite(); // Call parent. } @@ -173,37 +173,37 @@ protected: AppContext::SetUp(); // Call parent. VerifyOrReturn(!HasFailure()); // Stop if parent had a failure. - //// Your per-test setup goes here: - // mPerTestFixture.Init(); - // ASSERT_TRUE(mPerTestFixture.WorkingGreat()); + // Your per-test setup goes here: + mPerTestFixture.Init(); + ASSERT_TRUE(mPerTestFixture.WorkingGreat()); } // Performs teardown for each test in the suite. Run once for each test function. void TearDown() { - //// Your per-test teardown goes here: - // mPerTestFixture.Shutdown(); + // Your per-test teardown goes here: + mPerTestFixture.Shutdown(); chip::app::EventManagement::DestroyEventManagement(); AppContext::TearDown(); // Call parent. } private: - //// Your per-suite and per-test fixtures are declared here: - // static SomeTypeA sPerSuiteFixture; - // SomeTypeB mPerTestFixture; + // Your per-suite and per-test fixtures are declared here: + static SomeTypeA sPerSuiteFixture; + SomeTypeB mPerTestFixture; }; -//// Your per-suite fixtures are defined here: -// SomeTypeA YourTestContext::sPerSuiteFixture; +// Your per-suite fixtures are defined here: +SomeTypeA YourTestContext::sPerSuiteFixture; TEST_F(YourTestContext, YourTestFunction1) { - //// Do some test things here, then check the results using EXPECT_* + // Do some test things here, then check the results using EXPECT_* } TEST_F(YourTestContext, YourTestFunction2) { - //// Do some test things here, then check the results using EXPECT_* + // Do some test things here, then check the results using EXPECT_* } ```