/* * * Copyright (c) 2020 Project CHIP Authors * All rights reserved. * * 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 * This file implements a test for CHIP Callback * */ #include #include #include #include #include using namespace chip::Callback; /** * An example Callback registrar. Resumer::Resume() accepts Callbacks * to be run during the next call to Resumer::Dispatch(). In an environment * completely driven by callbacks, an application's main() would just call * something like Resumer::Dispatch() in a loop. */ class Resumer : private CallbackDeque { public: /** * @brief run this callback next Dispatch */ void Resume(Callback<> * cb) { // always first thing: cancel to take ownership of // cb members Enqueue(cb->Cancel()); } void Dispatch() { Cancelable ready; DequeueAll(ready); // runs the ready list while (ready.mNext != &ready) { Callback<> * cb = Callback<>::FromCancelable(ready.mNext); // one-shot semantics cb->Cancel(); cb->mCall(cb->mContext); } } }; static void increment(int * v) { (*v)++; } struct Resume { Callback<> * cb; Resumer * resumer; }; static void resume(struct Resume * me) { me->resumer->Resume(me->cb); } static void canceler(Cancelable * ca) { ca->Cancel(); } class TestCHIPCallback : public ::testing::Test { public: static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); } static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); } }; TEST_F(TestCHIPCallback, ResumerTest) { int n = 1; Callback<> cb(reinterpret_cast(increment), &n); Callback<> cancelcb(reinterpret_cast(canceler), cb.Cancel()); Resumer resumer; // Resume() works resumer.Resume(&cb); resumer.Dispatch(); resumer.Resume(&cb); resumer.Dispatch(); EXPECT_EQ(n, 3); n = 1; // test cb->Cancel() cancels resumer.Resume(&cb); cb.Cancel(); resumer.Dispatch(); EXPECT_EQ(n, 1); n = 1; // Cancel cb before Dispatch() gets around to us (tests FIFO *and* cancel() from readylist) resumer.Resume(&cancelcb); resumer.Resume(&cb); resumer.Dispatch(); EXPECT_EQ(n, 1); n = 1; // 2nd Resume() cancels first registration resumer.Resume(&cb); resumer.Resume(&cb); // cancels previous registration resumer.Dispatch(); // runs the list resumer.Dispatch(); // runs an empty list EXPECT_EQ(n, 2); n = 1; // Resume() during Dispatch() runs only once, but enqueues for next dispatch struct Resume res = { .cb = &cb, .resumer = &resumer }; Callback<> resumecb(reinterpret_cast(resume), &res); resumer.Resume(&cb); resumer.Resume(&resumecb); resumer.Dispatch(); EXPECT_EQ(n, 2); resumer.Dispatch(); EXPECT_EQ(n, 3); Callback<> * pcb = chip::Platform::New>(reinterpret_cast(increment), &n); n = 1; // cancel on destruct resumer.Resume(pcb); resumer.Dispatch(); EXPECT_EQ(n, 2); resumer.Resume(pcb); chip::Platform::Delete(pcb); resumer.Dispatch(); EXPECT_EQ(n, 2); } /** * An example Callback registrar. Notifier implements persistently-registered * semantics, and uses Callbacks with a non-default signature. */ class Notifier : private CallbackDeque { public: typedef void (*NotifyFn)(void *, int); /** * run all the callers */ void Notify(int v) { for (Cancelable * ca = mNext; ca != this; ca = ca->mNext) { // persistent registration semantics, with data Callback * cb = Callback::FromCancelable(ca); cb->mCall(cb->mContext, v); } } /** * @brief example */ static void Cancel(Cancelable * cb) { Dequeue(cb); // take off ready list } /** * @brief illustrate a case where this needs notification of cancellation */ void Register(Callback * cb) { Enqueue(cb->Cancel(), Cancel); } }; static void increment_by(int * n, int by) { *n += by; } TEST_F(TestCHIPCallback, NotifierTest) { int n = 1; Callback cb(reinterpret_cast(increment_by), &n); Callback cancelcb([](void * call, int) { canceler(reinterpret_cast(call)); }, cb.Cancel()); // safe to call anytime cb.Cancel(); Notifier notifier; // Simple stuff works, e.g. and there's persistent registration notifier.Register(&cb); notifier.Notify(1); notifier.Notify(8); EXPECT_EQ(n, 10); n = 1; // Cancel cb before Dispatch() gets around to us (tests FIFO *and* cancel() from readylist) notifier.Register(&cancelcb); notifier.Register(&cb); notifier.Notify(8); EXPECT_EQ(n, 1); cb.Cancel(); cancelcb.Cancel(); }