Switch a few more unit tests to async message delivery. (#12482)

* Create a LoopbackMessagingContext that supports async operation.

Trying to push the setup that AppContext has to a wider range of tests

* Convert TestExchangeMgr to LoopbackMessagingContext.

* Switch TestExchangeMgr to async message delivery.

* Convert TestPASESession to LoopbackMessagingContext.

* Convert TestPASESession to async message delivery.
diff --git a/src/app/tests/AppTestContext.cpp b/src/app/tests/AppTestContext.cpp
index ee4eee0..970e44d 100644
--- a/src/app/tests/AppTestContext.cpp
+++ b/src/app/tests/AppTestContext.cpp
@@ -25,10 +25,7 @@
 
 CHIP_ERROR AppContext::Init()
 {
-    ReturnErrorOnFailure(chip::Platform::MemoryInit());
-    ReturnErrorOnFailure(mIOContext.Init());
-    ReturnErrorOnFailure(mTransportManager.Init("LOOPBACK"));
-    ReturnErrorOnFailure(MessagingContext::Init(&mTransportManager, &mIOContext));
+    ReturnErrorOnFailure(Super::Init());
     ReturnErrorOnFailure(chip::app::InteractionModelEngine::GetInstance()->Init(&GetExchangeManager(), nullptr));
 
     return CHIP_NO_ERROR;
@@ -37,9 +34,7 @@
 CHIP_ERROR AppContext::Shutdown()
 {
     chip::app::InteractionModelEngine::GetInstance()->Shutdown();
-    ReturnErrorOnFailure(MessagingContext::Shutdown());
-    ReturnErrorOnFailure(mIOContext.Shutdown());
-    chip::Platform::MemoryShutdown();
+    ReturnErrorOnFailure(Super::Shutdown());
 
     return CHIP_NO_ERROR;
 }
diff --git a/src/app/tests/AppTestContext.h b/src/app/tests/AppTestContext.h
index d12e0c9..96ce876 100644
--- a/src/app/tests/AppTestContext.h
+++ b/src/app/tests/AppTestContext.h
@@ -15,12 +15,9 @@
  */
 #pragma once
 
-#include "system/SystemClock.h"
 #include <messaging/tests/MessagingContext.h>
 #include <transport/raw/tests/NetworkTestHelpers.h>
 
-#include <nlunit-test.h>
-
 namespace chip {
 namespace Test {
 
@@ -28,83 +25,16 @@
  * @brief The context of test cases for messaging layer. It wil initialize network layer and system layer, and create
  *        two secure sessions, connected with each other. Exchanges can be created for each secure session.
  */
-class AppContext : public MessagingContext
+class AppContext : public LoopbackMessagingContext<LoopbackTransport>
 {
+    typedef LoopbackMessagingContext<LoopbackTransport> Super;
+
 public:
-    /// Initialize the underlying layers and test suite pointer
-    CHIP_ERROR Init();
+    /// Initialize the underlying layers.
+    CHIP_ERROR Init() override;
 
     // Shutdown all layers, finalize operations
-    CHIP_ERROR Shutdown();
-
-    /*
-     * For unit-tests that simulate end-to-end transmission and reception of messages in loopback mode,
-     * this mode better replicates a real-functioning stack that correctly handles the processing
-     * of a transmitted message as an asynchronous, bottom half handler dispatched after the current execution context has
-     completed.
-     * This is achieved using SystemLayer::ScheduleWork.
-
-     * This should be used in conjunction with the DrainAndServiceIO function below to correctly service and drain the event queue.
-     *
-     */
-    void EnableAsyncDispatch()
-    {
-        auto & impl = mTransportManager.GetTransport().GetImplAtIndex<0>();
-        impl.EnableAsyncDispatch(&mIOContext.GetSystemLayer());
-    }
-
-    /*
-     * This drives the servicing of events using the embedded IOContext while there are pending
-     * messages in the loopback transport's pending message queue. This should run to completion
-     * in well-behaved logic (i.e there isn't an indefinite ping-pong of messages transmitted back
-     * and forth).
-     *
-     * Consequently, this is guarded with a user-provided timeout to ensure we don't have unit-tests that stall
-     * in CI due to bugs in the code that is being tested.
-     *
-     * This DOES NOT ensure that all pending events are serviced to completion (i.e timers, any ScheduleWork calls).
-     *
-     */
-    void DrainAndServiceIO(System::Clock::Timeout maxWait = chip::System::Clock::Seconds16(5))
-    {
-        auto & impl                        = mTransportManager.GetTransport().GetImplAtIndex<0>();
-        System::Clock::Timestamp startTime = System::SystemClock().GetMonotonicTimestamp();
-
-        while (impl.HasPendingMessages())
-        {
-            mIOContext.DriveIO();
-            if ((System::SystemClock().GetMonotonicTimestamp() - startTime) >= maxWait)
-            {
-                break;
-            }
-        }
-    }
-
-    static int Initialize(void * context)
-    {
-        auto * ctx = static_cast<AppContext *>(context);
-        return ctx->Init() == CHIP_NO_ERROR ? SUCCESS : FAILURE;
-    }
-
-    static int InitializeAsync(void * context)
-    {
-        auto * ctx = static_cast<AppContext *>(context);
-
-        VerifyOrReturnError(ctx->Init() == CHIP_NO_ERROR, FAILURE);
-        ctx->EnableAsyncDispatch();
-
-        return SUCCESS;
-    }
-
-    static int Finalize(void * context)
-    {
-        auto * ctx = static_cast<AppContext *>(context);
-        return ctx->Shutdown() == CHIP_NO_ERROR ? SUCCESS : FAILURE;
-    }
-
-private:
-    chip::TransportMgr<chip::Test::LoopbackTransport> mTransportManager;
-    chip::Test::IOContext mIOContext;
+    CHIP_ERROR Shutdown() override;
 };
 
 } // namespace Test
diff --git a/src/messaging/tests/MessagingContext.h b/src/messaging/tests/MessagingContext.h
index 0e91596..0435097 100644
--- a/src/messaging/tests/MessagingContext.h
+++ b/src/messaging/tests/MessagingContext.h
@@ -20,10 +20,13 @@
 #include <messaging/ExchangeMgr.h>
 #include <protocols/secure_channel/MessageCounterManager.h>
 #include <protocols/secure_channel/PASESession.h>
+#include <system/SystemClock.h>
 #include <transport/SessionManager.h>
 #include <transport/TransportMgr.h>
 #include <transport/raw/tests/NetworkTestHelpers.h>
 
+#include <nlunit-test.h>
+
 namespace chip {
 namespace Test {
 
@@ -109,5 +112,104 @@
     FabricIndex mDestFabricIndex = 0;
 };
 
+template <typename Transport>
+class LoopbackMessagingContext : public MessagingContext
+{
+public:
+    virtual ~LoopbackMessagingContext() {}
+
+    /// Initialize the underlying layers.
+    virtual CHIP_ERROR Init()
+    {
+        ReturnErrorOnFailure(chip::Platform::MemoryInit());
+        ReturnErrorOnFailure(mIOContext.Init());
+        ReturnErrorOnFailure(mTransportManager.Init("LOOPBACK"));
+        ReturnErrorOnFailure(MessagingContext::Init(&mTransportManager, &mIOContext));
+        return CHIP_NO_ERROR;
+    }
+
+    // Shutdown all layers, finalize operations
+    virtual CHIP_ERROR Shutdown()
+    {
+        ReturnErrorOnFailure(MessagingContext::Shutdown());
+        ReturnErrorOnFailure(mIOContext.Shutdown());
+        chip::Platform::MemoryShutdown();
+        return CHIP_NO_ERROR;
+    }
+
+    // Init/Shutdown Helpers that can be used directly as the nlTestSuite
+    // initialize/finalize function.
+    static int Initialize(void * context)
+    {
+        auto * ctx = static_cast<LoopbackMessagingContext *>(context);
+        return ctx->Init() == CHIP_NO_ERROR ? SUCCESS : FAILURE;
+    }
+
+    static int InitializeAsync(void * context)
+    {
+        auto * ctx = static_cast<LoopbackMessagingContext *>(context);
+
+        VerifyOrReturnError(ctx->Init() == CHIP_NO_ERROR, FAILURE);
+        ctx->EnableAsyncDispatch();
+
+        return SUCCESS;
+    }
+
+    static int Finalize(void * context)
+    {
+        auto * ctx = static_cast<LoopbackMessagingContext *>(context);
+        return ctx->Shutdown() == CHIP_NO_ERROR ? SUCCESS : FAILURE;
+    }
+
+    Transport & GetLoopback() { return mTransportManager.GetTransport().template GetImplAtIndex<0>(); }
+
+    /*
+     * For unit-tests that simulate end-to-end transmission and reception of messages in loopback mode,
+     * this mode better replicates a real-functioning stack that correctly handles the processing
+     * of a transmitted message as an asynchronous, bottom half handler dispatched after the current execution context has
+     completed.
+     * This is achieved using SystemLayer::ScheduleWork.
+
+     * This should be used in conjunction with the DrainAndServiceIO function below to correctly service and drain the event queue.
+     *
+     */
+    void EnableAsyncDispatch()
+    {
+        auto & impl = GetLoopback();
+        impl.EnableAsyncDispatch(&mIOContext.GetSystemLayer());
+    }
+
+    /*
+     * This drives the servicing of events using the embedded IOContext while there are pending
+     * messages in the loopback transport's pending message queue. This should run to completion
+     * in well-behaved logic (i.e there isn't an indefinite ping-pong of messages transmitted back
+     * and forth).
+     *
+     * Consequently, this is guarded with a user-provided timeout to ensure we don't have unit-tests that stall
+     * in CI due to bugs in the code that is being tested.
+     *
+     * This DOES NOT ensure that all pending events are serviced to completion (i.e timers, any ScheduleWork calls).
+     *
+     */
+    void DrainAndServiceIO(System::Clock::Timeout maxWait = chip::System::Clock::Seconds16(5))
+    {
+        auto & impl                        = GetLoopback();
+        System::Clock::Timestamp startTime = System::SystemClock().GetMonotonicTimestamp();
+
+        while (impl.HasPendingMessages())
+        {
+            mIOContext.DriveIO();
+            if ((System::SystemClock().GetMonotonicTimestamp() - startTime) >= maxWait)
+            {
+                break;
+            }
+        }
+    }
+
+private:
+    TransportMgr<Transport> mTransportManager;
+    Test::IOContext mIOContext;
+};
+
 } // namespace Test
 } // namespace chip
diff --git a/src/messaging/tests/TestExchangeMgr.cpp b/src/messaging/tests/TestExchangeMgr.cpp
index 52ee299..1ac634b 100644
--- a/src/messaging/tests/TestExchangeMgr.cpp
+++ b/src/messaging/tests/TestExchangeMgr.cpp
@@ -49,7 +49,7 @@
 using namespace chip::Transport;
 using namespace chip::Messaging;
 
-using TestContext = chip::Test::MessagingContext;
+using TestContext = chip::Test::LoopbackMessagingContext<Test::LoopbackTransport>;
 
 enum : uint8_t
 {
@@ -59,9 +59,6 @@
 
 TestContext sContext;
 
-TransportMgr<Test::LoopbackTransport> gTransportMgr;
-Test::IOContext gIOContext;
-
 class MockAppDelegate : public ExchangeDelegate
 {
 public:
@@ -134,6 +131,8 @@
     err = ec1->SendMessage(Protocols::BDX::Id, kMsgType_TEST1, System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize),
                            SendFlags(Messaging::SendMessageFlags::kNoAutoRequestAck));
     NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR);
+    ctx.DrainAndServiceIO();
+
     NL_TEST_ASSERT(inSuite, !receiveDelegate.IsOnMessageReceivedCalled);
     ec1->Close();
 
@@ -150,6 +149,8 @@
 
     ec1->SendMessage(Protocols::BDX::Id, kMsgType_TEST1, System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize),
                      SendFlags(Messaging::SendMessageFlags::kExpectResponse).Set(Messaging::SendMessageFlags::kNoAutoRequestAck));
+
+    ctx.DrainAndServiceIO();
     NL_TEST_ASSERT(inSuite, !sendDelegate.IsOnResponseTimeoutCalled);
 
     // Expire the session this exchange is supposedly on.  This should close the
@@ -203,6 +204,8 @@
     // send a malicious packet
     ec1->SendMessage(Protocols::BDX::Id, kMsgType_TEST2, System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize),
                      SendFlags(Messaging::SendMessageFlags::kNoAutoRequestAck));
+
+    ctx.DrainAndServiceIO();
     NL_TEST_ASSERT(inSuite, !mockUnsolicitedAppDelegate.IsOnMessageReceivedCalled);
 
     ec1 = ctx.NewExchangeToAlice(&mockSolicitedAppDelegate);
@@ -210,6 +213,8 @@
     // send a good packet
     ec1->SendMessage(Protocols::BDX::Id, kMsgType_TEST1, System::PacketBufferHandle::New(System::PacketBuffer::kMaxSize),
                      SendFlags(Messaging::SendMessageFlags::kNoAutoRequestAck));
+
+    ctx.DrainAndServiceIO();
     NL_TEST_ASSERT(inSuite, mockUnsolicitedAppDelegate.IsOnMessageReceivedCalled);
 
     err = ctx.GetExchangeManager().UnregisterUnsolicitedMessageHandlerForType(Protocols::BDX::Id, kMsgType_TEST1);
@@ -234,46 +239,16 @@
 };
 // clang-format on
 
-int Initialize(void * aContext);
-int Finalize(void * aContext);
-
 // clang-format off
 nlTestSuite sSuite =
 {
     "Test-CHIP-ExchangeManager",
     &sTests[0],
-    Initialize,
-    Finalize
+    TestContext::InitializeAsync,
+    TestContext::Finalize
 };
 // clang-format on
 
-/**
- *  Initialize the test suite.
- */
-int Initialize(void * aContext)
-{
-    // Initialize System memory and resources
-    VerifyOrReturnError(chip::Platform::MemoryInit() == CHIP_NO_ERROR, FAILURE);
-    VerifyOrReturnError(gIOContext.Init() == CHIP_NO_ERROR, FAILURE);
-    VerifyOrReturnError(gTransportMgr.Init("LOOPBACK") == CHIP_NO_ERROR, FAILURE);
-
-    auto * ctx = static_cast<TestContext *>(aContext);
-    VerifyOrReturnError(ctx->Init(&gTransportMgr, &gIOContext) == CHIP_NO_ERROR, FAILURE);
-
-    return SUCCESS;
-}
-
-/**
- *  Finalize the test suite.
- */
-int Finalize(void * aContext)
-{
-    CHIP_ERROR err = reinterpret_cast<TestContext *>(aContext)->Shutdown();
-    gIOContext.Shutdown();
-    chip::Platform::MemoryShutdown();
-    return (err == CHIP_NO_ERROR) ? SUCCESS : FAILURE;
-}
-
 } // namespace
 
 /**
diff --git a/src/protocols/secure_channel/tests/TestPASESession.cpp b/src/protocols/secure_channel/tests/TestPASESession.cpp
index a3d2316..befcd1f 100644
--- a/src/protocols/secure_channel/tests/TestPASESession.cpp
+++ b/src/protocols/secure_channel/tests/TestPASESession.cpp
@@ -41,7 +41,7 @@
 using namespace chip::Messaging;
 using namespace chip::Protocols;
 
-using TestContext = chip::Test::MessagingContext;
+namespace {
 
 class PASETestLoopbackTransport : public Test::LoopbackTransport
 {
@@ -50,19 +50,13 @@
 public:
     bool CanSendToPeer(const PeerAddress & address) override { return true; }
 
-    void Reset()
-    {
-        Test::LoopbackTransport::Reset();
-        mContext = nullptr;
-    }
-
-    TestContext * mContext = nullptr;
-    bool mMessageDropped   = false;
+    bool mMessageDropped = false;
 };
 
-TransportMgrBase gTransportMgr;
-PASETestLoopbackTransport gLoopback;
-chip::Test::IOContext gIOContext;
+using TestContext = chip::Test::LoopbackMessagingContext<PASETestLoopbackTransport>;
+
+TestContext sContext;
+auto & gLoopback = sContext.GetLoopback();
 
 class TestSecurePairingDelegate : public SessionEstablishmentDelegate
 {
@@ -91,6 +85,8 @@
 
 void SecurePairingWaitTest(nlTestSuite * inSuite, void * inContext)
 {
+    TestContext & ctx = *reinterpret_cast<TestContext *>(inContext);
+
     // Test all combinations of invalid parameters
     TestSecurePairingDelegate delegate;
     PASESession pairing;
@@ -103,11 +99,16 @@
     gLoopback.Reset();
 
     NL_TEST_ASSERT(inSuite, pairing.WaitForPairing(1234, 500, ByteSpan(nullptr, 0), 0, &delegate) == CHIP_ERROR_INVALID_ARGUMENT);
+    ctx.DrainAndServiceIO();
+
     NL_TEST_ASSERT(inSuite,
                    pairing.WaitForPairing(1234, 500, ByteSpan((const uint8_t *) "saltSalt", 8), 0, nullptr) ==
                        CHIP_ERROR_INVALID_ARGUMENT);
+    ctx.DrainAndServiceIO();
+
     NL_TEST_ASSERT(inSuite,
                    pairing.WaitForPairing(1234, 500, ByteSpan((const uint8_t *) "saltSalt", 8), 0, &delegate) == CHIP_NO_ERROR);
+    ctx.DrainAndServiceIO();
 }
 
 void SecurePairingStartTest(nlTestSuite * inSuite, void * inContext)
@@ -130,6 +131,7 @@
     gLoopback.Reset();
     NL_TEST_ASSERT(inSuite,
                    pairing.Pair(Transport::PeerAddress(Transport::Type::kBle), 1234, 0, context, &delegate) == CHIP_NO_ERROR);
+    ctx.DrainAndServiceIO();
 
     NL_TEST_ASSERT(inSuite, gLoopback.mSentMessageCount == 1);
 
@@ -148,6 +150,8 @@
     NL_TEST_ASSERT(inSuite,
                    pairing1.Pair(Transport::PeerAddress(Transport::Type::kBle), 1234, 0, context1, &delegate) ==
                        CHIP_ERROR_BAD_REQUEST);
+    ctx.DrainAndServiceIO();
+
     gLoopback.mMessageSendError = CHIP_NO_ERROR;
 }
 
@@ -178,7 +182,6 @@
                                                                  64_ms32, // CHIP_CONFIG_MRP_DEFAULT_IDLE_RETRY_INTERVAL
                                                                  64_ms32, // CHIP_CONFIG_MRP_DEFAULT_ACTIVE_RETRY_INTERVAL
                                                              });
-        gLoopback.mContext = &ctx;
     }
 
     NL_TEST_ASSERT(inSuite,
@@ -188,15 +191,19 @@
     NL_TEST_ASSERT(inSuite,
                    pairingAccessory.WaitForPairing(1234, 500, ByteSpan((const uint8_t *) "saltSALTsaltSALT", 16), 0,
                                                    &delegateAccessory) == CHIP_NO_ERROR);
+    ctx.DrainAndServiceIO();
+
     NL_TEST_ASSERT(inSuite,
                    pairingCommissioner.Pair(Transport::PeerAddress(Transport::Type::kBle), 1234, 0, contextCommissioner,
                                             &delegateCommissioner) == CHIP_NO_ERROR);
+    ctx.DrainAndServiceIO();
 
     while (gLoopback.mMessageDropped)
     {
         chip::test_utils::SleepMillis(65);
         gLoopback.mMessageDropped = false;
         ReliableMessageMgr::Timeout(&ctx.GetSystemLayer(), ctx.GetExchangeManager().GetReliableMessageMgr());
+        ctx.DrainAndServiceIO();
     };
 
     // Standalone acks also increment the mSentMessageCount. But some messages could be acked
@@ -206,7 +213,6 @@
     NL_TEST_ASSERT(inSuite, gLoopback.mSentMessageCount >= 5);
     NL_TEST_ASSERT(inSuite, delegateAccessory.mNumPairingComplete == 1);
     NL_TEST_ASSERT(inSuite, delegateCommissioner.mNumPairingComplete == 1);
-    gLoopback.mContext = nullptr;
 }
 
 void SecurePairingHandshakeTest(nlTestSuite * inSuite, void * inContext)
@@ -256,7 +262,6 @@
                                                              64_ms32, // CHIP_CONFIG_MRP_DEFAULT_IDLE_RETRY_INTERVAL
                                                              64_ms32, // CHIP_CONFIG_MRP_DEFAULT_ACTIVE_RETRY_INTERVAL
                                                          });
-    gLoopback.mContext = &ctx;
 
     NL_TEST_ASSERT(inSuite,
                    ctx.GetExchangeManager().RegisterUnsolicitedMessageHandlerForType(
@@ -265,11 +270,13 @@
     NL_TEST_ASSERT(inSuite,
                    pairingAccessory.WaitForPairing(1234, 500, ByteSpan((const uint8_t *) "saltSALTsaltSALT", 16), 0,
                                                    &delegateAccessory) == CHIP_NO_ERROR);
+    ctx.DrainAndServiceIO();
+
     NL_TEST_ASSERT(inSuite,
                    pairingCommissioner.Pair(Transport::PeerAddress(Transport::Type::kBle), 4321, 0, contextCommissioner,
                                             &delegateCommissioner) == CHIP_NO_ERROR);
+    ctx.DrainAndServiceIO();
 
-    gLoopback.mContext = nullptr;
     NL_TEST_ASSERT(inSuite, delegateAccessory.mNumPairingComplete == 0);
     NL_TEST_ASSERT(inSuite, delegateAccessory.mNumPairingErrors == 1);
     NL_TEST_ASSERT(inSuite, delegateCommissioner.mNumPairingComplete == 0);
@@ -371,8 +378,6 @@
 };
 // clang-format on
 
-static TestContext sContext;
-
 // clang-format on
 //
 /**
@@ -381,21 +386,15 @@
 int TestSecurePairing_Setup(void * inContext)
 {
     // Initialize System memory and resources
-    VerifyOrReturnError(chip::Platform::MemoryInit() == CHIP_NO_ERROR, FAILURE);
-    VerifyOrReturnError(gIOContext.Init() == CHIP_NO_ERROR, FAILURE);
-    VerifyOrReturnError(gTransportMgr.Init(&gLoopback) == CHIP_NO_ERROR, FAILURE);
+    VerifyOrReturnError(TestContext::InitializeAsync(inContext) == SUCCESS, FAILURE);
 
     auto & ctx = *static_cast<TestContext *>(inContext);
-    VerifyOrReturnError(ctx.Init(&gTransportMgr, &gIOContext) == CHIP_NO_ERROR, FAILURE);
-
     ctx.SetBobNodeId(kPlaceholderNodeId);
     ctx.SetAliceNodeId(kPlaceholderNodeId);
     ctx.SetBobKeyId(0);
     ctx.SetAliceKeyId(0);
     ctx.SetFabricIndex(kUndefinedFabricIndex);
 
-    gTransportMgr.SetSessionManager(&ctx.GetSecureSessionManager());
-
     return SUCCESS;
 }
 
@@ -404,13 +403,10 @@
  */
 int TestSecurePairing_Teardown(void * inContext)
 {
-    CHIP_ERROR err = reinterpret_cast<TestContext *>(inContext)->Shutdown();
-    gIOContext.Shutdown();
-    chip::Platform::MemoryShutdown();
-    return (err == CHIP_NO_ERROR) ? SUCCESS : FAILURE;
+    return TestContext::Finalize(inContext);
 }
 
-// TODO: TestPASESession is currently disabled due to lacking convenient way of creating exchange context
+} // anonymous namespace
 
 /**
  *  Main