blob: 111a8a73671802e9e765d05ab2c85b9410f576d0 [file] [log] [blame]
/*
*
* 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 <gtest/gtest.h>
#include <lib/core/CHIPCallback.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/CodeUtils.h>
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<CallFn>(increment), &n);
Callback<> cancelcb(reinterpret_cast<CallFn>(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<CallFn>(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<Callback<>>(reinterpret_cast<CallFn>(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<NotifyFn> * cb = Callback<NotifyFn>::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<NotifyFn> * cb) { Enqueue(cb->Cancel(), Cancel); }
};
static void increment_by(int * n, int by)
{
*n += by;
}
TEST_F(TestCHIPCallback, NotifierTest)
{
int n = 1;
Callback<Notifier::NotifyFn> cb(reinterpret_cast<Notifier::NotifyFn>(increment_by), &n);
Callback<Notifier::NotifyFn> cancelcb(reinterpret_cast<Notifier::NotifyFn>(canceler), 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();
}