/* * Copyright (c) 2021, Ali Mohammad Pur * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include namespace { struct Object : public RefCounted { }; } TEST_CASE(basic) { Variant the_value { 42 }; EXPECT(the_value.has()); EXPECT_EQ(the_value.get(), 42); the_value = ByteString("42"); EXPECT(the_value.has()); EXPECT_EQ(the_value.get(), "42"); } TEST_CASE(visit) { bool correct = false; Variant the_value { 42.0f }; the_value.visit( [&](int const&) { correct = false; }, [&](ByteString const&) { correct = false; }, [&](float const&) { correct = true; }); EXPECT(correct); } TEST_CASE(visit_const) { bool correct = false; Variant const the_value { "42"sv }; the_value.visit( [&](ByteString const&) { correct = true; }, [&](auto&) {}, [&](auto const&) {}); EXPECT(correct); correct = false; auto the_value_but_not_const = the_value; the_value_but_not_const.visit( [&](ByteString const&) { correct = true; }, [&](auto&) {}); EXPECT(correct); correct = false; the_value_but_not_const.visit( [&](T&) { correct = !IsConst; }); EXPECT(correct); } TEST_CASE(destructor) { struct DestructionChecker { explicit DestructionChecker(bool& was_destroyed) : m_was_destroyed(was_destroyed) { } ~DestructionChecker() { m_was_destroyed = true; } bool& m_was_destroyed; }; bool was_destroyed = false; { Variant test_variant { DestructionChecker { was_destroyed } }; } EXPECT(was_destroyed); bool was_destroyed_when_assigned_to = false; Variant original { DestructionChecker { was_destroyed_when_assigned_to } }; Variant other { 42 }; original = other; EXPECT(was_destroyed_when_assigned_to); } TEST_CASE(move_moves) { struct NoCopy { AK_MAKE_NONCOPYABLE(NoCopy); AK_MAKE_DEFAULT_MOVABLE(NoCopy); public: NoCopy() = default; }; Variant first_variant { 42 }; // Should not fail to compile first_variant = NoCopy {}; Variant second_variant = move(first_variant); EXPECT(second_variant.has()); } TEST_CASE(verify_cast) { Variant one_integer_to_rule_them_all { static_cast(42) }; auto fake_integer = one_integer_to_rule_them_all.downcast(); EXPECT(fake_integer.has()); EXPECT(one_integer_to_rule_them_all.has()); EXPECT_EQ(fake_integer.get(), 42); EXPECT_EQ(one_integer_to_rule_them_all.get(), 42); fake_integer = static_cast(60); one_integer_to_rule_them_all = fake_integer.downcast().downcast().downcast(); EXPECT(fake_integer.has()); EXPECT(one_integer_to_rule_them_all.has()); EXPECT_EQ(fake_integer.get(), 60); EXPECT_EQ(one_integer_to_rule_them_all.get(), 60); using SomeFancyType = Variant; one_integer_to_rule_them_all = fake_integer.downcast(); EXPECT(fake_integer.has()); EXPECT(one_integer_to_rule_them_all.has()); EXPECT_EQ(fake_integer.get(), 60); EXPECT_EQ(one_integer_to_rule_them_all.get(), 60); } TEST_CASE(moved_from_state) { // Note: This test requires that Vector's moved-from state be consistent // it need not be in a specific state (though as it is currently implemented, // a moved-from vector is the same as a newly-created vector) // This test does not make assumptions about the state itself, but rather that // it remains consistent when done on different instances. // Should this assumption be broken, we should probably switch to defining a local // class that has fixed semantics, but I doubt the moved-from state of Vector will // change any time soon :P Vector bunch_of_values { 1, 2, 3, 4, 5, 6, 7, 8 }; Variant, Empty> optionally_a_bunch_of_values { Vector { 1, 2, 3, 4, 5, 6, 7, 8 } }; { [[maybe_unused]] auto devnull_0 = move(bunch_of_values); [[maybe_unused]] auto devnull_1 = move(optionally_a_bunch_of_values); } // The moved-from state should be the same in both cases, and the variant should still contain a moved-from vector. // Note: Use after move is intentional. EXPECT(optionally_a_bunch_of_values.has>()); auto same_contents = __builtin_memcmp(&bunch_of_values, &optionally_a_bunch_of_values.get>(), sizeof(bunch_of_values)) == 0; EXPECT(same_contents); } TEST_CASE(duplicated_types) { Variant its_just_an_int { 42 }; EXPECT(its_just_an_int.has()); EXPECT_EQ(its_just_an_int.get(), 42); } TEST_CASE(return_values) { using MyVariant = Variant; { MyVariant the_value { 42.0f }; float value = the_value.visit( [&](int const&) { return 1.0f; }, [&](ByteString const&) { return 2.0f; }, [&](float const& f) { return f; }); EXPECT_EQ(value, 42.0f); } { MyVariant the_value { 42 }; int value = the_value.visit( [&](int& i) { return i; }, [&](ByteString&) { return 2; }, [&](float&) { return 3; }); EXPECT_EQ(value, 42); } { const MyVariant the_value { "str" }; ByteString value = the_value.visit( [&](int const&) { return ByteString { "wrong" }; }, [&](ByteString const& s) { return s; }, [&](float const&) { return ByteString { "wrong" }; }); EXPECT_EQ(value, "str"); } } TEST_CASE(return_values_by_reference) { auto ref = adopt_ref_if_nonnull(new (nothrow) Object()); Variant the_value { 42.0f }; auto& value = the_value.visit( [&](int const&) -> RefPtr& { return ref; }, [&](ByteString const&) -> RefPtr& { return ref; }, [&](float const&) -> RefPtr& { return ref; }); EXPECT_EQ(ref, value); EXPECT_EQ(ref->ref_count(), 1u); EXPECT_EQ(value->ref_count(), 1u); } struct HoldsInt { int i; }; struct HoldsFloat { float f; }; TEST_CASE(copy_assign) { { Variant the_value { 42.0f }; VERIFY(the_value.has()); EXPECT_EQ(the_value.get(), 42.0f); int twelve = 12; the_value = twelve; VERIFY(the_value.has()); EXPECT_EQ(the_value.get(), 12); the_value = ByteString("Hello, world!"); VERIFY(the_value.has()); EXPECT_EQ(the_value.get(), "Hello, world!"); } { Variant the_value { HoldsFloat { 42.0f } }; VERIFY(the_value.has()); EXPECT_EQ(the_value.get().f, 42.0f); HoldsInt twelve { 12 }; the_value = twelve; VERIFY(the_value.has()); EXPECT_EQ(the_value.get().i, 12); the_value = ByteString("Hello, world!"); VERIFY(the_value.has()); EXPECT_EQ(the_value.get(), "Hello, world!"); } } TEST_CASE(default_empty) { Variant my_variant; EXPECT(my_variant.has()); EXPECT(!my_variant.has()); } TEST_CASE(type_list_specialization) { EXPECT_EQ((TypeList>::size), 1u); EXPECT_EQ((TypeList>::size), 2u); EXPECT_EQ((TypeList>::size), 3u); using MyVariant = Variant; using MyList = TypeList; EXPECT((IsSame, Empty>)); EXPECT((IsSame, int>)); EXPECT((IsSame, String>)); } TEST_CASE(variant_equality) { using MyVariant = Variant; { MyVariant variant1 = 1; MyVariant variant2 = 1; EXPECT_EQ(variant1, variant2); } { MyVariant variant1 = 1; MyVariant variant2 = 1.5f; EXPECT_NE(variant1, variant2); } { MyVariant variant1 = 1; MyVariant variant2; EXPECT_NE(variant1, variant2); } { MyVariant variant1; MyVariant variant2; EXPECT_EQ(variant1, variant2); } }