Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix TestDnssd exit after last HandleResolved call #26138

Merged
merged 11 commits into from
Apr 28, 2023
2 changes: 1 addition & 1 deletion src/platform/tests/TestConfigurationMgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ int TestConfigurationMgr()
{
nlTestSuite theSuite = { "ConfigurationMgr tests", &sTests[0], TestConfigurationMgr_Setup, TestConfigurationMgr_Teardown };

// Run test suit againt one context.
// Run test suit against one context.
arkq marked this conversation as resolved.
Show resolved Hide resolved
nlTestRunner(&theSuite, nullptr);
return nlTestRunnerStats(&theSuite);
}
Expand Down
2 changes: 1 addition & 1 deletion src/platform/tests/TestConnectivityMgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ int TestConnectivityMgr()
{
nlTestSuite theSuite = { "ConfigurationMgr tests", &sTests[0], TestConnectivityMgr_Setup, TestConnectivityMgr_Teardown };

// Run test suit againt one context.
// Run test suit against one context.
nlTestRunner(&theSuite, nullptr);
return nlTestRunnerStats(&theSuite);
}
Expand Down
221 changes: 117 additions & 104 deletions src/platform/tests/TestDnssd.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
#include <condition_variable>
#include <mutex>
#include <thread>
/*
*
* Copyright (c) 2021 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/**
* @file
arkq marked this conversation as resolved.
Show resolved Hide resolved
* This file implements a unit test suite for the mDNS code functionality.
*
*/

#include <atomic>

#include <nlunit-test.h>

Expand All @@ -14,47 +35,64 @@ using chip::Dnssd::DnssdService;
using chip::Dnssd::DnssdServiceProtocol;
using chip::Dnssd::TextEntry;

static unsigned int gBrowsedServicesCount = 0;
static unsigned int gResolvedServicesCount = 0;
static bool gEndOfInput = false;
namespace {

struct DnssdContext
{
nlTestSuite * mTestSuite;

std::atomic<bool> mTimeoutExpired{ false };

intptr_t mBrowseIdentifier = 0;

unsigned int mBrowsedServicesCount = 0;
unsigned int mResolvedServicesCount = 0;
bool mEndOfInput = false;
};

} // namespace

static void HandleResolve(void * context, DnssdService * result, const chip::Span<chip::Inet::IPAddress> & addresses,
CHIP_ERROR error)
{
auto * ctx = static_cast<DnssdContext *>(context);
auto * suite = ctx->mTestSuite;
char addrBuf[100];
nlTestSuite * suite = static_cast<nlTestSuite *>(context);

NL_TEST_ASSERT(suite, result != nullptr);
NL_TEST_ASSERT(suite, error == CHIP_NO_ERROR);

if (!addresses.empty())
{
addresses.data()[0].ToString(addrBuf, sizeof(addrBuf));
printf("Service[%u] at [%s]:%u\n", gResolvedServicesCount, addrBuf, result->mPort);
printf("Service[%u] at [%s]:%u\n", ctx->mResolvedServicesCount, addrBuf, result->mPort);
}

NL_TEST_ASSERT(suite, result->mTextEntrySize == 1);
NL_TEST_ASSERT(suite, strcmp(result->mTextEntries[0].mKey, "key") == 0);
NL_TEST_ASSERT(suite, strcmp(reinterpret_cast<const char *>(result->mTextEntries[0].mData), "val") == 0);

if (gBrowsedServicesCount == ++gResolvedServicesCount)
if (ctx->mBrowsedServicesCount == ++ctx->mResolvedServicesCount)
{
// After last service is resolved, stop the event loop,
// so the test case can gracefully exit.
chip::DeviceLayer::PlatformMgr().StopEventLoopTask();
arkq marked this conversation as resolved.
Show resolved Hide resolved
arkq marked this conversation as resolved.
Show resolved Hide resolved
chip::DeviceLayer::PlatformMgr().Shutdown();
exit(0);
}
}

static void HandleBrowse(void * context, DnssdService * services, size_t servicesSize, bool finalBrowse, CHIP_ERROR error)
{
nlTestSuite * suite = static_cast<nlTestSuite *>(context);
auto * ctx = static_cast<DnssdContext *>(context);
auto * suite = ctx->mTestSuite;

// Make sure that we will not be called again after end-of-input is set
NL_TEST_ASSERT(suite, gEndOfInput == false);
NL_TEST_ASSERT(suite, error == CHIP_NO_ERROR);
NL_TEST_ASSERT(suite, ctx->mEndOfInput == false);
// Cancelled error is expected when the browse is stopped with
// ChipDnssdStopBrowse(), so we will not assert on it.
NL_TEST_ASSERT(suite, error == CHIP_NO_ERROR || error == CHIP_ERROR_CANCELLED);

gBrowsedServicesCount += servicesSize;
gEndOfInput = finalBrowse;
ctx->mBrowsedServicesCount += servicesSize;
ctx->mEndOfInput = finalBrowse;

if (servicesSize > 0)
{
Expand All @@ -63,22 +101,39 @@ static void HandleBrowse(void * context, DnssdService * services, size_t service
{
printf("Service[%u] name %s\n", i, services[i].mName);
printf("Service[%u] type %s\n", i, services[i].mType);
NL_TEST_ASSERT(suite, ChipDnssdResolve(&services[i], services[i].mInterface, HandleResolve, suite) == CHIP_NO_ERROR);
NL_TEST_ASSERT(suite, ChipDnssdResolve(&services[i], services[i].mInterface, HandleResolve, context) == CHIP_NO_ERROR);
}
}
}

static void HandlePublish(void * context, const char * type, const char * instanceName, CHIP_ERROR error) {}
static void Timeout(chip::System::Layer * systemLayer, void * context)
{
auto * ctx = static_cast<DnssdContext *>(context);
ChipLogError(DeviceLayer, "mDNS test timeout, is avahi daemon running?");
ctx->mTimeoutExpired = true;
chip::DeviceLayer::PlatformMgr().StopEventLoopTask();
}

static void InitCallback(void * context, CHIP_ERROR error)
static void DnssdErrorCallback(void * context, CHIP_ERROR error)
{
DnssdService service;
TextEntry entry;
char key[] = "key";
char val[] = "val";
nlTestSuite * suite = static_cast<nlTestSuite *>(context);
auto * ctx = static_cast<DnssdContext *>(context);
NL_TEST_ASSERT(ctx->mTestSuite, error == CHIP_NO_ERROR);
}

NL_TEST_ASSERT(suite, error == CHIP_NO_ERROR);
static void HandlePublish(void * context, const char * type, const char * instanceName, CHIP_ERROR error)
{
auto * ctx = static_cast<DnssdContext *>(context);
NL_TEST_ASSERT(ctx->mTestSuite, error == CHIP_NO_ERROR);
}

static void TestDnssdPubSub_DnssdInitCallback(void * context, CHIP_ERROR error)
{
auto * ctx = static_cast<DnssdContext *>(context);
NL_TEST_ASSERT(ctx->mTestSuite, error == CHIP_NO_ERROR);
auto * suite = ctx->mTestSuite;

DnssdService service{};
TextEntry entry{ "key", reinterpret_cast<const uint8_t *>("val"), 3 };

service.mInterface = chip::Inet::InterfaceId::Null();
service.mPort = 80;
Expand All @@ -87,107 +142,65 @@ static void InitCallback(void * context, CHIP_ERROR error)
strcpy(service.mType, "_mock");
service.mAddressType = chip::Inet::IPAddressType::kAny;
service.mProtocol = DnssdServiceProtocol::kDnssdProtocolTcp;
entry.mKey = key;
entry.mData = reinterpret_cast<const uint8_t *>(val);
entry.mDataSize = strlen(reinterpret_cast<const char *>(entry.mData));
service.mTextEntries = &entry;
service.mTextEntrySize = 1;
service.mSubTypes = nullptr;
service.mSubTypeSize = 0;

NL_TEST_ASSERT(suite, ChipDnssdPublishService(&service, HandlePublish) == CHIP_NO_ERROR);
intptr_t browseIdentifier;
ChipDnssdBrowse("_mock", DnssdServiceProtocol::kDnssdProtocolTcp, chip::Inet::IPAddressType::kAny,
chip::Inet::InterfaceId::Null(), HandleBrowse, suite, &browseIdentifier);
}
NL_TEST_ASSERT(suite, ChipDnssdPublishService(&service, HandlePublish, context) == CHIP_NO_ERROR);

static void ErrorCallback(void * context, CHIP_ERROR error)
{
VerifyOrDieWithMsg(error == CHIP_NO_ERROR, DeviceLayer, "Mdns error: %" CHIP_ERROR_FORMAT "\n", error.Format());
NL_TEST_ASSERT(suite,
ChipDnssdBrowse("_mock", DnssdServiceProtocol::kDnssdProtocolTcp, chip::Inet::IPAddressType::kAny,
chip::Inet::InterfaceId::Null(), HandleBrowse, context,
&ctx->mBrowseIdentifier) == CHIP_NO_ERROR);
}

void TestDnssdPubSub(nlTestSuite * inSuite, void * inContext)
{
chip::Platform::MemoryInit();
chip::DeviceLayer::PlatformMgr().InitChipStack();
NL_TEST_ASSERT(inSuite, chip::Dnssd::ChipDnssdInit(InitCallback, ErrorCallback, inSuite) == CHIP_NO_ERROR);
DnssdContext context;
context.mTestSuite = inSuite;

NL_TEST_ASSERT(inSuite,
chip::Dnssd::ChipDnssdInit(TestDnssdPubSub_DnssdInitCallback, DnssdErrorCallback, &context) == CHIP_NO_ERROR);
NL_TEST_ASSERT(inSuite,
chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds32(5), Timeout, &context) ==
CHIP_NO_ERROR);

ChipLogProgress(DeviceLayer, "Start EventLoop");
chip::DeviceLayer::PlatformMgr().RunEventLoop();
ChipLogProgress(DeviceLayer, "End EventLoop");
}

static const nlTest sTests[] = { NL_TEST_DEF("Test Dnssd::PubSub", TestDnssdPubSub), NL_TEST_SENTINEL() };

int TestDnssd()
{
std::mutex mtx;

std::condition_variable readyCondition;
bool ready = false;
NL_TEST_ASSERT(inSuite, !context.mTimeoutExpired);

std::condition_variable doneCondition;
bool done = false;
bool shutdown = false;
// Stop browsing so we can safely shutdown DNS-SD
chip::Dnssd::ChipDnssdStopBrowse(context.mBrowseIdentifier);

int retVal = EXIT_FAILURE;

std::thread t([&]() {
{
std::lock_guard<std::mutex> lock(mtx);
ready = true;
readyCondition.notify_one();
}

nlTestSuite theSuite = { "CHIP DeviceLayer mdns tests", &sTests[0], nullptr, nullptr };

nlTestRunner(&theSuite, nullptr);
retVal = nlTestRunnerStats(&theSuite);

{
std::lock_guard<std::mutex> lock(mtx);
done = true;
doneCondition.notify_all();
}
});
chip::Dnssd::ChipDnssdShutdown();
}

{
std::unique_lock<std::mutex> lock(mtx);
readyCondition.wait(lock, [&] { return ready; });
static const nlTest sTests[] = { NL_TEST_DEF("Test Dnssd::PubSub", TestDnssdPubSub), NL_TEST_SENTINEL() };

doneCondition.wait_for(lock, std::chrono::seconds(5));
if (!done)
{
fprintf(stderr, "mDNS test timeout, is avahi daemon running?\n");

//
// This will stop the event loop above, and wait till it has actually stopped
// (i.e exited RunEventLoop()).
//
chip::DeviceLayer::PlatformMgr().StopEventLoopTask();
chip::Dnssd::ChipDnssdShutdown();
chip::DeviceLayer::PlatformMgr().Shutdown();
shutdown = true;

doneCondition.wait_for(lock, std::chrono::seconds(1));
if (!done)
{
fprintf(stderr, "Orderly shutdown of the platform main loop failed as well.\n");
}
retVal = EXIT_FAILURE;
}
}
t.join();
int TestDnssd_Setup(void * inContext)
{
VerifyOrReturnError(chip::Platform::MemoryInit() == CHIP_NO_ERROR, FAILURE);
VerifyOrReturnError(chip::DeviceLayer::PlatformMgr().InitChipStack() == CHIP_NO_ERROR, FAILURE);
return SUCCESS;
}

if (!shutdown)
{
chip::DeviceLayer::PlatformMgr().StopEventLoopTask();
chip::Dnssd::ChipDnssdShutdown();
chip::DeviceLayer::PlatformMgr().Shutdown();
}
int TestDnssd_Teardown(void * inContext)
{
chip::DeviceLayer::PlatformMgr().Shutdown();
chip::Platform::MemoryShutdown();
return SUCCESS;
}

int TestDnssd()
{
nlTestSuite theSuite = { "CHIP DeviceLayer mDNS tests", &sTests[0], TestDnssd_Setup, TestDnssd_Teardown };

return retVal;
// Run test suit against one context.
nlTestRunner(&theSuite, nullptr);
return nlTestRunnerStats(&theSuite);
}

CHIP_REGISTER_TEST_SUITE(TestDnssd);
2 changes: 1 addition & 1 deletion src/platform/tests/TestKeyValueStoreMgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ int TestKeyValueStoreMgr()
{
nlTestSuite theSuite = { "KeyValueStoreMgr tests", &sTests[0], TestKeyValueStoreMgr_Setup, TestKeyValueStoreMgr_Teardown };

// Run test suit againt one context.
// Run test suit against one context.
nlTestRunner(&theSuite, nullptr);
return nlTestRunnerStats(&theSuite);
}
Expand Down
2 changes: 1 addition & 1 deletion src/platform/tests/TestPlatformMgr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ int TestPlatformMgr()
{
nlTestSuite theSuite = { "PlatformMgr tests", &sTests[0], TestPlatformMgr_Setup, TestPlatformMgr_Teardown };

// Run test suit againt one context.
// Run test suit against one context.
nlTestRunner(&theSuite, nullptr);
return nlTestRunnerStats(&theSuite);
}
Expand Down
2 changes: 1 addition & 1 deletion src/platform/tests/TestPlatformTime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ int TestPlatformTime()
{
nlTestSuite theSuite = { "PlatformTime tests", &sTests[0], nullptr, nullptr };

// Run test suit againt one context.
// Run test suit against one context.
nlTestRunner(&theSuite, nullptr);
return nlTestRunnerStats(&theSuite);
}
Expand Down