blob: bbd27717dc16ecf49b0177da45810e4c633e7026 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "signal-coordinator.h"
#include <lib/sync/completion.h>
#include <lib/zx/time.h>
#include <atomic>
#include <gtest/gtest.h>
namespace fuzzing {
namespace {
// Test fixtures
// This class provides a SignalHandler that can be used as the |on_signal| parameter for
// |SignalCoordinator::Create| and |SignalCoordinator::Pair|. It allows tests to block on signal
// receipt. Tests can also control when the |SignalCoordinator::WaitLoop| exits by calling
// |set_result(false)| before sending a signal.
class TestSignalHandler final {
public:
void set_result(bool result) { result_ = result; }
bool OnSignal(zx_signals_t observed) {
observed_ |= observed;
sync_completion_signal(&sync_);
return result_;
}
zx_signals_t WaitOne() {
sync_completion_wait(&sync_, ZX_TIME_INFINITE);
sync_completion_reset(&sync_);
return observed_.exchange(0);
}
private:
std::atomic<zx_signals_t> observed_ = 0;
sync_completion_t sync_;
bool result_ = true;
};
class SignalCoordinatorTest : public ::testing::Test {
protected:
void SetUp() override {
zx::eventpair paired;
coordinator1_.Create(&paired, [&](zx_signals_t s) { return handler1_.OnSignal(s); });
coordinator2_.Pair(std::move(paired), [&](zx_signals_t s) { return handler2_.OnSignal(s); });
}
TestSignalHandler handler1_;
TestSignalHandler handler2_;
SignalCoordinator coordinator1_;
SignalCoordinator coordinator2_;
};
// Unit tests
TEST_F(SignalCoordinatorTest, JoinAndReset) {
// |Join| will block until the coordinator is stopped. |Reset| stops the object and its peer.
std::thread t1([&]() { coordinator1_.Join(); });
coordinator2_.Reset();
t1.join();
// |Join| does not block if already stopped. |Reset| is idempotent.
coordinator1_.Join();
coordinator2_.Reset();
coordinator2_.Join();
}
TEST_F(SignalCoordinatorTest, AutoReset) {
// Re-creating the coordinator will reset it, and stop its peer.
zx::eventpair paired1;
std::thread t1([&]() { coordinator1_.Join(); });
coordinator2_.Create(&paired1, [&](zx_signals_t s) { return handler2_.OnSignal(s); });
t1.join();
coordinator1_.Pair(std::move(paired1), [&](zx_signals_t s) { return handler1_.OnSignal(s); });
// Similarly, re-pairing also resets.
zx::eventpair paired2a, paired2b;
ASSERT_EQ(zx::eventpair::create(0, &paired2a, &paired2b), ZX_OK);
std::thread t2([&]() { coordinator2_.Join(); });
coordinator1_.Pair(std::move(paired2a), [&](zx_signals_t s) { return handler1_.OnSignal(s); });
t2.join();
}
TEST_F(SignalCoordinatorTest, WaitLoop) {
// Can send all signals, both ways.
std::vector<Signal> signals = {
kExecuteCallback, kCollectCoverage, kTryDetectingALeak, kLeakDetected, kDetectLeaksAtExit,
};
std::thread t;
for (auto signal : signals) {
t = std::thread([&]() { EXPECT_EQ(handler2_.WaitOne(), signal); });
EXPECT_TRUE(coordinator1_.SignalPeer(signal));
t.join();
t = std::thread([&]() { EXPECT_EQ(handler1_.WaitOne(), signal); });
EXPECT_TRUE(coordinator2_.SignalPeer(signal));
t.join();
}
}
TEST_F(SignalCoordinatorTest, PeerClosed) {
// This will cause |TestSignalHandler::OnSignal| to return false, which tells the
// |SignalCoordinator::WaitLoop| to exit.
handler2_.set_result(false);
std::thread t([&]() {
auto observed = handler2_.WaitOne();
// If the signal handler returns false, it's called again with ZX_EVENTPAIR_PEER_CLOSED.
// Either one or both calls happen before the call to |WaitOne| above returns.
if (observed == kExecuteCallback) {
EXPECT_EQ(handler2_.WaitOne(), ZX_EVENTPAIR_PEER_CLOSED);
} else {
EXPECT_EQ(observed, kExecuteCallback | ZX_EVENTPAIR_PEER_CLOSED);
}
});
EXPECT_TRUE(coordinator1_.SignalPeer(kExecuteCallback));
t.join();
// After ZX_EVENTPAIR_PEER_CLOSED, the coordinator stops...
coordinator2_.Join();
// ...which causes the other end to receive ZX_EVENTPAIR_PEER_CLOSED and stop.
EXPECT_EQ(handler1_.WaitOne(), ZX_EVENTPAIR_PEER_CLOSED);
coordinator1_.Join();
// Once stopped, can't send more signals.
EXPECT_FALSE(coordinator1_.SignalPeer(kExecuteCallback));
}
} // namespace
} // namespace fuzzing