|  | // Copyright 2017 The Fuchsia Authors | 
|  | // | 
|  | // Use of this source code is governed by a MIT-style | 
|  | // license that can be found in the LICENSE file or at | 
|  | // https://opensource.org/licenses/MIT | 
|  |  | 
|  | #include <lib/unittest/unittest.h> | 
|  |  | 
|  | #include <fbl/ref_ptr.h> | 
|  | #include <object/dispatcher.h> | 
|  | #include <object/signal_observer.h> | 
|  |  | 
|  | namespace { | 
|  | class TestDispatcher; | 
|  | } | 
|  |  | 
|  | template <> | 
|  | struct CanaryTag<TestDispatcher> { | 
|  | static constexpr uint32_t magic = 0; | 
|  | }; | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | class TestDispatcher final : public SoloDispatcher<TestDispatcher, ZX_RIGHTS_BASIC> { | 
|  | public: | 
|  | TestDispatcher() {} | 
|  | ~TestDispatcher() final = default; | 
|  | zx_obj_type_t get_type() const final { return ZX_OBJ_TYPE_NONE; } | 
|  |  | 
|  | void SetSignals(zx_signals_t signals) { | 
|  | this->UpdateState(/*clear_mask=*/0, /*set_mask=*/signals); | 
|  | } | 
|  | }; | 
|  |  | 
|  | class TestSignalObserver final : public SignalObserver { | 
|  | public: | 
|  | TestSignalObserver() = default; | 
|  | TestSignalObserver(void* port, uint64_t key) : port_(port), key_(key) {} | 
|  |  | 
|  | zx_signals_t signals() { return signals_; } | 
|  |  | 
|  | bool cancel_called() { return cancel_called_; } | 
|  | bool match_called() { return match_called_; } | 
|  | bool called() { return match_called_ || cancel_called_; } | 
|  |  | 
|  | private: | 
|  | void OnMatch(zx_signals_t signals) final { | 
|  | // Ensure we are not called twice. | 
|  | ZX_ASSERT(!cancel_called_); | 
|  | ZX_ASSERT(!match_called_); | 
|  |  | 
|  | signals_ = signals; | 
|  | match_called_ = true; | 
|  | } | 
|  |  | 
|  | void OnCancel(zx_signals_t signals) final { | 
|  | // Ensure we are not called twice. | 
|  | ZX_ASSERT(!cancel_called_); | 
|  | ZX_ASSERT(!match_called_); | 
|  |  | 
|  | signals_ = signals; | 
|  | cancel_called_ = true; | 
|  | } | 
|  |  | 
|  | bool MatchesKey(const void* port, uint64_t key) final { return port == port_ && key == key_; } | 
|  |  | 
|  | zx_signals_t signals_ = 0; | 
|  | bool cancel_called_ = false; | 
|  | bool match_called_ = false; | 
|  |  | 
|  | void* port_; | 
|  | uint64_t key_; | 
|  | }; | 
|  |  | 
|  | bool TestBasicMatch() { | 
|  | BEGIN_TEST; | 
|  |  | 
|  | TestSignalObserver observer; | 
|  | TestDispatcher dispatcher; | 
|  |  | 
|  | // Add the observer. | 
|  | ASSERT_EQ(ZX_OK, dispatcher.AddObserver(&observer, nullptr, ZX_USER_SIGNAL_0)); | 
|  | ASSERT_FALSE(observer.called()); | 
|  |  | 
|  | // Set an unrelated signal. | 
|  | dispatcher.SetSignals(ZX_USER_SIGNAL_1); | 
|  | ASSERT_FALSE(observer.called()); | 
|  |  | 
|  | // Set the triggered signal. | 
|  | dispatcher.SetSignals(ZX_USER_SIGNAL_0); | 
|  | ASSERT_TRUE(observer.called()); | 
|  |  | 
|  | END_TEST; | 
|  | } | 
|  |  | 
|  | bool TestAlreadyMatched() { | 
|  | BEGIN_TEST; | 
|  |  | 
|  | TestDispatcher dispatcher; | 
|  | dispatcher.SetSignals(ZX_USER_SIGNAL_0); | 
|  |  | 
|  | TestSignalObserver observer; | 
|  |  | 
|  | // Add the observer, when the signal has already matched. | 
|  | ASSERT_EQ(ZX_OK, dispatcher.AddObserver(&observer, nullptr, ZX_USER_SIGNAL_0)); | 
|  | ASSERT_TRUE(observer.match_called()); | 
|  |  | 
|  | END_TEST; | 
|  | } | 
|  |  | 
|  | bool TestCancelled() { | 
|  | BEGIN_TEST; | 
|  |  | 
|  | TestSignalObserver observer; | 
|  |  | 
|  | // Create a dispatcher and some handles. | 
|  | fbl::AllocChecker ac; | 
|  | auto dispatcher = fbl::MakeRefCountedChecked<TestDispatcher>(&ac); | 
|  | ASSERT_TRUE(ac.check()); | 
|  |  | 
|  | HandleOwner handle1 = Handle::Make(dispatcher, TestDispatcher::default_rights()); | 
|  | HandleOwner handle2 = Handle::Make(dispatcher, TestDispatcher::default_rights()); | 
|  |  | 
|  | // Add the observer. | 
|  | ASSERT_EQ(ZX_OK, dispatcher->AddObserver(&observer, handle1.get(), ZX_USER_SIGNAL_0)); | 
|  | ASSERT_FALSE(observer.called()); | 
|  |  | 
|  | // Cancel an unrelated handle. | 
|  | dispatcher->Cancel(handle2.get()); | 
|  | ASSERT_FALSE(observer.called()); | 
|  |  | 
|  | // Cancel the associated handle. | 
|  | dispatcher->Cancel(handle1.get()); | 
|  | ASSERT_TRUE(observer.cancel_called()); | 
|  |  | 
|  | END_TEST; | 
|  | } | 
|  |  | 
|  | bool TestRemoveObserver() { | 
|  | BEGIN_TEST; | 
|  |  | 
|  | TestSignalObserver observer; | 
|  | TestDispatcher dispatcher; | 
|  |  | 
|  | // Add the observer. | 
|  | ASSERT_EQ(ZX_OK, dispatcher.AddObserver(&observer, nullptr, ZX_USER_SIGNAL_0)); | 
|  |  | 
|  | // Remove it again. | 
|  | EXPECT_TRUE(dispatcher.RemoveObserver(&observer)); | 
|  |  | 
|  | // Remove it yet again, but expect "false" return code. | 
|  | EXPECT_FALSE(dispatcher.RemoveObserver(&observer)); | 
|  |  | 
|  | // Trigger the signal: it shouldn't fire. | 
|  | dispatcher.SetSignals(ZX_USER_SIGNAL_0); | 
|  | ASSERT_FALSE(observer.called()); | 
|  |  | 
|  | END_TEST; | 
|  | } | 
|  |  | 
|  | bool TestRemoveObserverAfterMatch() { | 
|  | BEGIN_TEST; | 
|  |  | 
|  | TestSignalObserver observer; | 
|  | TestDispatcher dispatcher; | 
|  |  | 
|  | // Add the observer. | 
|  | ASSERT_EQ(ZX_OK, dispatcher.AddObserver(&observer, nullptr, ZX_USER_SIGNAL_0)); | 
|  |  | 
|  | // Fire the signal. | 
|  | dispatcher.SetSignals(ZX_USER_SIGNAL_0); | 
|  | EXPECT_TRUE(observer.match_called()); | 
|  |  | 
|  | // Removing the observer after a match should return false. | 
|  | EXPECT_FALSE(dispatcher.RemoveObserver(&observer)); | 
|  |  | 
|  | END_TEST; | 
|  | } | 
|  |  | 
|  | bool TestRemoveByKey() { | 
|  | BEGIN_TEST; | 
|  |  | 
|  | // Create a dispatcher and some handles. | 
|  | fbl::AllocChecker ac; | 
|  | auto dispatcher = fbl::MakeRefCountedChecked<TestDispatcher>(&ac); | 
|  | ASSERT_TRUE(ac.check()); | 
|  |  | 
|  | HandleOwner handle1 = Handle::Make(dispatcher, TestDispatcher::default_rights()); | 
|  | HandleOwner handle2 = Handle::Make(dispatcher, TestDispatcher::default_rights()); | 
|  |  | 
|  | // Create an observer with the given port and key. | 
|  | int dummy_port; | 
|  | const uint64_t dummy_key = 0x123; | 
|  | TestSignalObserver observer{&dummy_port, dummy_key}; | 
|  |  | 
|  | // Add the observer. | 
|  | ASSERT_EQ(ZX_OK, dispatcher->AddObserver(&observer, handle1.get(), ZX_USER_SIGNAL_0)); | 
|  |  | 
|  | // Cancel the wrong handle / port / key. | 
|  | int different_port; | 
|  | ASSERT_FALSE(dispatcher->CancelByKey(handle2.get(), &dummy_port, dummy_key)); | 
|  | ASSERT_FALSE(dispatcher->CancelByKey(handle1.get(), &different_port, dummy_key)); | 
|  | ASSERT_FALSE(dispatcher->CancelByKey(handle1.get(), &dummy_port, 0x321)); | 
|  | ASSERT_FALSE(observer.called()); | 
|  |  | 
|  | // Cancel the correct handle / port / key combination. | 
|  | ASSERT_TRUE(dispatcher->CancelByKey(handle1.get(), &dummy_port, dummy_key)); | 
|  | ASSERT_TRUE(observer.cancel_called()); | 
|  |  | 
|  | END_TEST; | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | #define ST_UNITTEST(fname) UNITTEST(#fname, fname) | 
|  |  | 
|  | UNITTEST_START_TESTCASE(state_tracker_tests) | 
|  |  | 
|  | ST_UNITTEST(TestBasicMatch) | 
|  | ST_UNITTEST(TestAlreadyMatched) | 
|  | ST_UNITTEST(TestCancelled) | 
|  | ST_UNITTEST(TestRemoveObserver) | 
|  | ST_UNITTEST(TestRemoveObserverAfterMatch) | 
|  | ST_UNITTEST(TestRemoveByKey) | 
|  |  | 
|  | UNITTEST_END_TESTCASE(state_tracker_tests, "statetracker", "StateTracker test") |