blob: c1290c1a0cef66b9862de4c2084853150d94f999 [file] [log] [blame]
/*
*
* Copyright (c) 2024 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.
*/
#include <array>
#include <inttypes.h>
#include <stdint.h>
#include <string.h>
#include <app/data-model/Nullable.h>
#include <lib/support/Span.h>
#include <lib/support/UnitTestRegistration.h>
#include <nlunit-test.h>
using namespace chip;
using namespace chip::app::DataModel;
namespace {
// Counts calls to constructor and destructor, to determine if the right
// semantics applied in cases where destruction is expected.
struct CtorDtorCounter
{
CtorDtorCounter(int i) : m(i) { ++created; }
~CtorDtorCounter() { ++destroyed; }
CtorDtorCounter(const CtorDtorCounter & o) : m(o.m) { ++created; }
CtorDtorCounter & operator=(const CtorDtorCounter &) = default;
CtorDtorCounter(CtorDtorCounter && o) : m(o.m) { ++created; }
CtorDtorCounter & operator=(CtorDtorCounter &&) = default;
bool operator==(const CtorDtorCounter & o) const { return m == o.m; }
int m;
static void ResetCounter()
{
created = 0;
destroyed = 0;
}
static int created;
static int destroyed;
};
struct MovableCtorDtorCounter : public CtorDtorCounter
{
public:
MovableCtorDtorCounter(int i) : CtorDtorCounter(i) {}
MovableCtorDtorCounter(const MovableCtorDtorCounter & o) = delete;
MovableCtorDtorCounter & operator=(const MovableCtorDtorCounter &) = delete;
MovableCtorDtorCounter(MovableCtorDtorCounter && o) = default;
MovableCtorDtorCounter & operator=(MovableCtorDtorCounter &&) = default;
};
int CtorDtorCounter::created = 0;
int CtorDtorCounter::destroyed = 0;
} // namespace
static void TestBasic(nlTestSuite * inSuite, void * inContext)
{
// Set up our test CtorDtorCounter objects, which will mess with counts, before we reset the
// counts.
CtorDtorCounter c100(100), c101(101), c102(102);
CtorDtorCounter::ResetCounter();
{
auto testNullable = MakeNullable<CtorDtorCounter>(100);
NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 1 && CtorDtorCounter::destroyed == 0);
NL_TEST_ASSERT(inSuite, !testNullable.IsNull() && testNullable.Value().m == 100);
NL_TEST_ASSERT(inSuite, testNullable == c100);
NL_TEST_ASSERT(inSuite, testNullable != c101);
NL_TEST_ASSERT(inSuite, testNullable != c102);
testNullable.SetNull();
NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 1 && CtorDtorCounter::destroyed == 1);
NL_TEST_ASSERT(inSuite, !!testNullable.IsNull());
NL_TEST_ASSERT(inSuite, testNullable != c100);
NL_TEST_ASSERT(inSuite, testNullable != c101);
NL_TEST_ASSERT(inSuite, testNullable != c102);
testNullable.SetNonNull(CtorDtorCounter(101));
NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 3 && CtorDtorCounter::destroyed == 2);
NL_TEST_ASSERT(inSuite, !testNullable.IsNull() && testNullable.Value().m == 101);
NL_TEST_ASSERT(inSuite, testNullable != c100);
NL_TEST_ASSERT(inSuite, testNullable == c101);
NL_TEST_ASSERT(inSuite, testNullable != c102);
testNullable.SetNonNull(102);
NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 4 && CtorDtorCounter::destroyed == 3);
NL_TEST_ASSERT(inSuite, !testNullable.IsNull() && testNullable.Value().m == 102);
NL_TEST_ASSERT(inSuite, testNullable != c100);
NL_TEST_ASSERT(inSuite, testNullable != c101);
NL_TEST_ASSERT(inSuite, testNullable == c102);
}
// Our test CtorDtorCounter objects are still in scope here.
NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 4 && CtorDtorCounter::destroyed == 4);
}
static void TestMake(nlTestSuite * inSuite, void * inContext)
{
CtorDtorCounter::ResetCounter();
{
auto testNullable = MakeNullable<CtorDtorCounter>(200);
NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 1 && CtorDtorCounter::destroyed == 0);
NL_TEST_ASSERT(inSuite, !testNullable.IsNull() && testNullable.Value().m == 200);
}
NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 1 && CtorDtorCounter::destroyed == 1);
}
static void TestCopy(nlTestSuite * inSuite, void * inContext)
{
CtorDtorCounter::ResetCounter();
{
auto testSrc = MakeNullable<CtorDtorCounter>(300);
NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 1 && CtorDtorCounter::destroyed == 0);
NL_TEST_ASSERT(inSuite, !testSrc.IsNull() && testSrc.Value().m == 300);
{
Nullable<CtorDtorCounter> testDst(testSrc);
NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 2 && CtorDtorCounter::destroyed == 0);
NL_TEST_ASSERT(inSuite, !testDst.IsNull() && testDst.Value().m == 300);
}
NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 2 && CtorDtorCounter::destroyed == 1);
{
Nullable<CtorDtorCounter> testDst;
NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 2 && CtorDtorCounter::destroyed == 1);
NL_TEST_ASSERT(inSuite, !!testDst.IsNull());
testDst = testSrc;
NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 3 && CtorDtorCounter::destroyed == 1);
NL_TEST_ASSERT(inSuite, !testDst.IsNull() && testDst.Value().m == 300);
}
NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 3 && CtorDtorCounter::destroyed == 2);
}
NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 3 && CtorDtorCounter::destroyed == 3);
}
static void TestMove(nlTestSuite * inSuite, void * inContext)
{
CtorDtorCounter::ResetCounter();
{
auto testSrc = MakeNullable<MovableCtorDtorCounter>(400);
Nullable<MovableCtorDtorCounter> testDst(std::move(testSrc));
NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 2 && CtorDtorCounter::destroyed == 1);
NL_TEST_ASSERT(inSuite, !testDst.IsNull() && testDst.Value().m == 400);
}
NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 2 && CtorDtorCounter::destroyed == 2);
{
Nullable<MovableCtorDtorCounter> testDst;
NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 2 && CtorDtorCounter::destroyed == 2);
NL_TEST_ASSERT(inSuite, !!testDst.IsNull());
auto testSrc = MakeNullable<MovableCtorDtorCounter>(401);
testDst = std::move(testSrc);
NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 4 && CtorDtorCounter::destroyed == 3);
NL_TEST_ASSERT(inSuite, !testDst.IsNull() && testDst.Value().m == 401);
}
NL_TEST_ASSERT(inSuite, CtorDtorCounter::created == 4 && CtorDtorCounter::destroyed == 4);
}
static void TestUpdate(nlTestSuite * inSuite, void * inContext)
{
using SmallArray = std::array<uint8_t, 3>;
// Arrays
{
auto nullable1 = MakeNullable<SmallArray>({ 1, 2, 3 });
auto nullable2 = MakeNullable<SmallArray>({ 1, 2, 3 });
NL_TEST_ASSERT(inSuite, !nullable1.IsNull());
NL_TEST_ASSERT(inSuite, !nullable2.IsNull());
NL_TEST_ASSERT(inSuite, nullable1 == nullable2);
// No-op on change to same.
NL_TEST_ASSERT(inSuite, nullable1.Update(nullable2) == false);
NL_TEST_ASSERT(inSuite, nullable1 == nullable2);
nullable1.Value()[0] = 100;
NL_TEST_ASSERT(inSuite, nullable1 != nullable2);
NL_TEST_ASSERT(inSuite, nullable2.Update(nullable1) == true);
NL_TEST_ASSERT(inSuite, nullable1 == nullable2);
}
// Structs
{
struct SomeObject
{
uint8_t a;
uint8_t b;
bool operator==(const SomeObject & other) const { return (a == other.a) && (b == other.b); }
};
auto nullable1 = MakeNullable<SomeObject>({ 1, 2 });
auto nullable2 = MakeNullable<SomeObject>({ 1, 2 });
NL_TEST_ASSERT(inSuite, !nullable1.IsNull());
NL_TEST_ASSERT(inSuite, !nullable2.IsNull());
NL_TEST_ASSERT(inSuite, nullable1 == nullable2);
// No-op on change to same.
NL_TEST_ASSERT(inSuite, nullable1.Update(nullable2) == false);
NL_TEST_ASSERT(inSuite, nullable1 == nullable2);
nullable1.Value().a = 100;
NL_TEST_ASSERT(inSuite, nullable1 != nullable2);
NL_TEST_ASSERT(inSuite, nullable2.Update(nullable1) == true);
NL_TEST_ASSERT(inSuite, nullable1 == nullable2);
}
// Scalar cases
{
auto nullable1 = MakeNullable(static_cast<uint8_t>(1));
NL_TEST_ASSERT(inSuite, !nullable1.IsNull());
// Non-null to non-null same value
NL_TEST_ASSERT(inSuite, nullable1.Update(nullable1) == false);
NL_TEST_ASSERT(inSuite, !nullable1.IsNull());
// Non-null to null
NL_TEST_ASSERT(inSuite, nullable1.Update(NullNullable) == true);
NL_TEST_ASSERT(inSuite, nullable1.IsNull());
// Null to null
NL_TEST_ASSERT(inSuite, nullable1.Update(NullNullable) == false);
NL_TEST_ASSERT(inSuite, nullable1.IsNull());
// Null to non-null
NL_TEST_ASSERT(inSuite, nullable1.Update(MakeNullable(static_cast<uint8_t>(1))) == true);
NL_TEST_ASSERT(inSuite, !nullable1.IsNull());
NL_TEST_ASSERT(inSuite, nullable1.Value() == 1);
// Non-null to non-null different value
NL_TEST_ASSERT(inSuite, nullable1.Update(MakeNullable(static_cast<uint8_t>(2))) == true);
NL_TEST_ASSERT(inSuite, !nullable1.IsNull());
NL_TEST_ASSERT(inSuite, nullable1.Value() == 2);
// Non-null to extent of range --> changes to "invalid" value in range.
NL_TEST_ASSERT(inSuite, nullable1.Update(MakeNullable(static_cast<uint8_t>(255))) == true);
NL_TEST_ASSERT(inSuite, !nullable1.IsNull());
NL_TEST_ASSERT(inSuite, nullable1.Value() == 255);
}
}
// clang-format off
static const nlTest sTests[] =
{
NL_TEST_DEF("NullableBasic", TestBasic),
NL_TEST_DEF("NullableMake", TestMake),
NL_TEST_DEF("NullableCopy", TestCopy),
NL_TEST_DEF("NullableMove", TestMove),
NL_TEST_DEF("Nullable Update operation", TestUpdate),
NL_TEST_SENTINEL()
};
// clang-format on
int TestNullable()
{
// clang-format off
nlTestSuite theSuite =
{
"Test for Nullable abstraction",
&sTests[0],
nullptr,
nullptr
};
// clang-format on
nlTestRunner(&theSuite, nullptr);
return (nlTestRunnerStats(&theSuite));
}
CHIP_REGISTER_TEST_SUITE(TestNullable)