/* * * 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 #include #include #include #include #include #include #include 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; } 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; using CtorDtorCounter::operator==; using CtorDtorCounter::operator!=; }; int CtorDtorCounter::created = 0; int CtorDtorCounter::destroyed = 0; } // namespace TEST(TestNullable, TestBasic) { // 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(100); EXPECT_TRUE(CtorDtorCounter::created == 1 && CtorDtorCounter::destroyed == 0); EXPECT_TRUE(!testNullable.IsNull() && testNullable.Value().m == 100); EXPECT_EQ(testNullable, c100); EXPECT_NE(testNullable, c101); EXPECT_NE(testNullable, c102); testNullable.SetNull(); EXPECT_TRUE(CtorDtorCounter::created == 1 && CtorDtorCounter::destroyed == 1); EXPECT_TRUE(testNullable.IsNull()); EXPECT_NE(testNullable, c100); EXPECT_NE(testNullable, c101); EXPECT_NE(testNullable, c102); testNullable.SetNonNull(CtorDtorCounter(101)); EXPECT_TRUE(CtorDtorCounter::created == 3 && CtorDtorCounter::destroyed == 2); EXPECT_TRUE(!testNullable.IsNull() && testNullable.Value().m == 101); EXPECT_NE(testNullable, c100); EXPECT_EQ(testNullable, c101); EXPECT_NE(testNullable, c102); testNullable.SetNonNull(102); EXPECT_TRUE(CtorDtorCounter::created == 4 && CtorDtorCounter::destroyed == 3); EXPECT_TRUE(!testNullable.IsNull() && testNullable.Value().m == 102); EXPECT_NE(testNullable, c100); EXPECT_NE(testNullable, c101); EXPECT_EQ(testNullable, c102); } // Our test CtorDtorCounter objects are still in scope here. EXPECT_TRUE(CtorDtorCounter::created == 4 && CtorDtorCounter::destroyed == 4); } TEST(TestNullable, TestMake) { CtorDtorCounter::ResetCounter(); { auto testNullable = MakeNullable(200); EXPECT_TRUE(CtorDtorCounter::created == 1 && CtorDtorCounter::destroyed == 0); EXPECT_TRUE(!testNullable.IsNull() && testNullable.Value().m == 200); } EXPECT_TRUE(CtorDtorCounter::created == 1 && CtorDtorCounter::destroyed == 1); } TEST(TestNullable, TestCopy) { CtorDtorCounter::ResetCounter(); { auto testSrc = MakeNullable(300); EXPECT_TRUE(CtorDtorCounter::created == 1 && CtorDtorCounter::destroyed == 0); EXPECT_TRUE(!testSrc.IsNull() && testSrc.Value().m == 300); { Nullable testDst(testSrc); EXPECT_TRUE(CtorDtorCounter::created == 2 && CtorDtorCounter::destroyed == 0); EXPECT_TRUE(!testDst.IsNull() && testDst.Value().m == 300); } EXPECT_TRUE(CtorDtorCounter::created == 2 && CtorDtorCounter::destroyed == 1); { Nullable testDst; EXPECT_TRUE(CtorDtorCounter::created == 2 && CtorDtorCounter::destroyed == 1); EXPECT_TRUE(testDst.IsNull()); testDst = testSrc; EXPECT_TRUE(CtorDtorCounter::created == 3 && CtorDtorCounter::destroyed == 1); EXPECT_TRUE(!testDst.IsNull() && testDst.Value().m == 300); } EXPECT_TRUE(CtorDtorCounter::created == 3 && CtorDtorCounter::destroyed == 2); } EXPECT_TRUE(CtorDtorCounter::created == 3 && CtorDtorCounter::destroyed == 3); } TEST(TestNullable, TestMove) { CtorDtorCounter::ResetCounter(); { auto testSrc = MakeNullable(400); // construct Nullable testDst(std::move(testSrc)); // move construct EXPECT_TRUE(CtorDtorCounter::created == 2 && CtorDtorCounter::destroyed == 0); EXPECT_TRUE(!testDst.IsNull() && testDst.Value().m == 400); // destroy both testsSrc and testDst } EXPECT_TRUE(CtorDtorCounter::created == 2 && CtorDtorCounter::destroyed == 2); CtorDtorCounter::ResetCounter(); { Nullable testDst; // no object construction EXPECT_TRUE(CtorDtorCounter::created == 0 && CtorDtorCounter::destroyed == 0); EXPECT_TRUE(testDst.IsNull()); auto testSrc = MakeNullable(401); // construct object testDst = std::move(testSrc); // construct a copy EXPECT_TRUE(CtorDtorCounter::created == 2 && CtorDtorCounter::destroyed == 0); EXPECT_TRUE(!testDst.IsNull() && testDst.Value().m == 401); } EXPECT_TRUE(CtorDtorCounter::created == 2 && CtorDtorCounter::destroyed == 2); } TEST(TestNullable, TestUpdate) { using SmallArray = std::array; // Arrays { auto nullable1 = MakeNullable({ 1, 2, 3 }); auto nullable2 = MakeNullable({ 1, 2, 3 }); EXPECT_FALSE(nullable1.IsNull()); EXPECT_FALSE(nullable2.IsNull()); EXPECT_EQ(nullable1, nullable2); // No-op on change to same. EXPECT_FALSE(nullable1.Update(nullable2)); EXPECT_EQ(nullable1, nullable2); nullable1.Value()[0] = 100; EXPECT_NE(nullable1, nullable2); EXPECT_TRUE(nullable2.Update(nullable1)); EXPECT_EQ(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({ 1, 2 }); auto nullable2 = MakeNullable({ 1, 2 }); EXPECT_FALSE(nullable1.IsNull()); EXPECT_FALSE(nullable2.IsNull()); EXPECT_EQ(nullable1, nullable2); // No-op on change to same. EXPECT_FALSE(nullable1.Update(nullable2)); EXPECT_EQ(nullable1, nullable2); nullable1.Value().a = 100; EXPECT_NE(nullable1, nullable2); EXPECT_TRUE(nullable2.Update(nullable1)); EXPECT_EQ(nullable1, nullable2); } // Scalar cases { auto nullable1 = MakeNullable(static_cast(1)); EXPECT_FALSE(nullable1.IsNull()); // Non-null to non-null same value EXPECT_FALSE(nullable1.Update(nullable1)); EXPECT_FALSE(nullable1.IsNull()); // Non-null to null EXPECT_TRUE(nullable1.Update(NullNullable)); EXPECT_TRUE(nullable1.IsNull()); // Null to null EXPECT_FALSE(nullable1.Update(NullNullable)); EXPECT_TRUE(nullable1.IsNull()); // Null to non-null EXPECT_TRUE(nullable1.Update(MakeNullable(static_cast(1)))); EXPECT_FALSE(nullable1.IsNull()); EXPECT_EQ(nullable1.Value(), 1); // Non-null to non-null different value EXPECT_TRUE(nullable1.Update(MakeNullable(static_cast(2)))); EXPECT_FALSE(nullable1.IsNull()); EXPECT_EQ(nullable1.Value(), 2); // Non-null to extent of range --> changes to "invalid" value in range. EXPECT_TRUE(nullable1.Update(MakeNullable(static_cast(255)))); EXPECT_FALSE(nullable1.IsNull()); EXPECT_EQ(nullable1.Value(), 255); } }