// Copyright (c) 2013, Kenton Varda <temporal@gmail.com> // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, this // list of conditions and the following disclaimer. // 2. Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "schema-loader.h" #include <gtest/gtest.h> #include "test-util.h" #include <kj/debug.h> namespace capnp { namespace _ { // private namespace { TEST(SchemaLoader, Load) { SchemaLoader loader; Schema struct32Schema = loader.load(Schema::from<test::TestLists::Struct32>().getProto()); auto nativeSchema = Schema::from<test::TestLists>(); Schema testListsSchema = loader.load(nativeSchema.getProto()); Schema struct8Schema = loader.load(Schema::from<test::TestLists::Struct8>().getProto()); Schema structPSchema = loader.load(Schema::from<test::TestLists::StructP>().getProto()); EXPECT_EQ(kj::str(nativeSchema.getProto()), kj::str(testListsSchema.getProto())); EXPECT_FALSE(testListsSchema == nativeSchema); EXPECT_FALSE(struct32Schema == Schema::from<test::TestLists::Struct32>()); EXPECT_FALSE(struct8Schema == Schema::from<test::TestLists::Struct8>()); EXPECT_FALSE(structPSchema == Schema::from<test::TestLists::StructP>()); EXPECT_TRUE(testListsSchema.getDependency(typeId<test::TestLists::Struct32>()) == struct32Schema); EXPECT_TRUE(testListsSchema.getDependency(typeId<test::TestLists::Struct8>()) == struct8Schema); EXPECT_TRUE(testListsSchema.getDependency(typeId<test::TestLists::StructP>()) == structPSchema); auto struct16Schema = testListsSchema.getDependency(typeId<test::TestLists::Struct16>()); EXPECT_EQ(0u, struct16Schema.getProto().getStruct().getFields().size()); } TEST(SchemaLoader, LoadLateUnion) { SchemaLoader loader; StructSchema schema = loader.load(Schema::from<test::TestLateUnion>().getProto()).asStruct(); loader.load(Schema::from<test::TestLateUnion::TheUnion>().getProto()).asStruct(); loader.load(Schema::from<test::TestLateUnion::AnotherUnion>().getProto()).asStruct(); EXPECT_EQ(6, schema.getDependency(schema.getFieldByName("theUnion").getProto().getGroup()) .asStruct().getFieldByName("grault").getProto().getOrdinal().getExplicit()); EXPECT_EQ(9, schema.getDependency(schema.getFieldByName("anotherUnion").getProto().getGroup()) .asStruct().getFieldByName("corge").getProto().getOrdinal().getExplicit()); EXPECT_TRUE(schema.findFieldByName("corge") == nullptr); EXPECT_TRUE(schema.findFieldByName("grault") == nullptr); } TEST(SchemaLoader, LoadUnnamedUnion) { SchemaLoader loader; StructSchema schema = loader.load(Schema::from<test::TestUnnamedUnion>().getProto()).asStruct(); EXPECT_TRUE(schema.findFieldByName("") == nullptr); EXPECT_TRUE(schema.findFieldByName("foo") != nullptr); EXPECT_TRUE(schema.findFieldByName("bar") != nullptr); EXPECT_TRUE(schema.findFieldByName("before") != nullptr); EXPECT_TRUE(schema.findFieldByName("after") != nullptr); } #if KJ_NO_EXCEPTIONS #undef EXPECT_ANY_THROW #define EXPECT_ANY_THROW(code) EXPECT_DEATH(code, ".") #define EXPECT_NONFATAL_FAILURE(code) code #else #define EXPECT_NONFATAL_FAILURE EXPECT_ANY_THROW #endif TEST(SchemaLoader, Use) { SchemaLoader loader; StructSchema schema = loader.load(Schema::from<TestAllTypes>().getProto()).asStruct(); // Also have to load TestEnum. loader.load(Schema::from<TestEnum>().getProto()); { MallocMessageBuilder builder; auto root = builder.getRoot<DynamicStruct>(schema); initDynamicTestMessage(root); checkDynamicTestMessage(root.asReader()); // Can't convert to TestAllTypes because we didn't use loadCompiledTypeAndDependencies(). EXPECT_ANY_THROW(root.as<TestAllTypes>()); // But if we reinterpret the raw bytes, it works. checkTestMessage(builder.getRoot<TestAllTypes>()); } loader.loadCompiledTypeAndDependencies<TestAllTypes>(); { MallocMessageBuilder builder; auto root = builder.getRoot<DynamicStruct>(schema); initDynamicTestMessage(root); // Now we can actually cast. checkTestMessage(root.as<TestAllTypes>()); } // Let's also test TestListDefaults, but as we do so, let's load the compiled types first, to // make sure the opposite order works. loader.loadCompiledTypeAndDependencies<TestListDefaults>(); StructSchema testListsSchema = loader.get(typeId<TestListDefaults>()).asStruct(); EXPECT_TRUE(testListsSchema != Schema::from<TestListDefaults>()); { MallocMessageBuilder builder; auto root = builder.getRoot<DynamicStruct>(testListsSchema); initDynamicTestLists(root); checkDynamicTestLists(root.asReader()); checkTestMessage(root.as<TestListDefaults>()); } EXPECT_TRUE(loader.load(Schema::from<TestListDefaults>().getProto()) == testListsSchema); { MallocMessageBuilder builder; auto root = builder.getRoot<DynamicStruct>(testListsSchema); initDynamicTestLists(root); checkTestMessage(root.as<TestListDefaults>()); } // Finally, let's test some unions. StructSchema unionSchema = loader.load(Schema::from<TestUnion>().getProto()).asStruct(); loader.load(Schema::from<TestUnion::Union0>().getProto()); loader.load(Schema::from<TestUnion::Union1>().getProto()); { MallocMessageBuilder builder; auto root = builder.getRoot<DynamicStruct>(unionSchema); root.get("union0").as<DynamicStruct>().set("u0f1s16", 123); root.get("union1").as<DynamicStruct>().set("u1f0sp", "hello"); auto reader = builder.getRoot<TestUnion>().asReader(); EXPECT_EQ(123, reader.getUnion0().getU0f1s16()); EXPECT_EQ("hello", reader.getUnion1().getU1f0sp()); } } template <typename T> Schema loadUnderAlternateTypeId(SchemaLoader& loader, uint64_t id) { MallocMessageBuilder schemaBuilder; schemaBuilder.setRoot(Schema::from<T>().getProto()); auto root = schemaBuilder.getRoot<schema2::Node>(); root.setId(id); if (root.which() == schema2::Node::STRUCT) { // If the struct contains any self-referential members, change their type IDs as well. auto fields = root.getStruct().getFields(); for (auto field: fields) { if (field.which() == schema2::Field::REGULAR) { auto type = field.getRegular().getType(); if (type.which() == schema2::Type::STRUCT && type.getStruct() == typeId<T>()) { type.setStruct(id); } } } } return loader.load(root); } TEST(SchemaLoader, Upgrade) { SchemaLoader loader; loader.loadCompiledTypeAndDependencies<test::TestOldVersion>(); StructSchema schema = loader.get(typeId<test::TestOldVersion>()).asStruct(); EXPECT_EQ(kj::str(Schema::from<test::TestOldVersion>().getProto()), kj::str(schema.getProto())); loadUnderAlternateTypeId<test::TestNewVersion>(loader, typeId<test::TestOldVersion>()); // The new version replaced the old. EXPECT_EQ(Schema::from<test::TestNewVersion>().getProto().getDisplayName(), schema.getProto().getDisplayName()); // But it is still usable as the old version. schema.requireUsableAs<test::TestOldVersion>(); } TEST(SchemaLoader, Downgrade) { SchemaLoader loader; loader.loadCompiledTypeAndDependencies<test::TestNewVersion>(); StructSchema schema = loader.get(typeId<test::TestNewVersion>()).asStruct(); EXPECT_EQ(kj::str(Schema::from<test::TestNewVersion>().getProto()), kj::str(schema.getProto())); loadUnderAlternateTypeId<test::TestOldVersion>(loader, typeId<test::TestNewVersion>()); // We kept the new version, because the replacement was older. EXPECT_EQ(Schema::from<test::TestNewVersion>().getProto().getDisplayName(), schema.getProto().getDisplayName()); schema.requireUsableAs<test::TestNewVersion>(); } TEST(SchemaLoader, Incompatible) { SchemaLoader loader; loader.loadCompiledTypeAndDependencies<test::TestListDefaults>(); EXPECT_NONFATAL_FAILURE( loadUnderAlternateTypeId<test::TestAllTypes>(loader, typeId<test::TestListDefaults>())); } // TODO(test): More extensively test upgrade/downgrade checks. TEST(SchemaLoader, Enumerate) { SchemaLoader loader; loader.loadCompiledTypeAndDependencies<TestAllTypes>(); auto list = loader.getAllLoaded(); ASSERT_EQ(2u, list.size()); if (list[0] == loader.get(typeId<TestAllTypes>())) { EXPECT_TRUE(list[1] == loader.get(typeId<TestEnum>())); } else { EXPECT_TRUE(list[0] == loader.get(typeId<TestEnum>())); EXPECT_TRUE(list[1] == loader.get(typeId<TestAllTypes>())); } } TEST(SchemaLoader, EnumerateNoPlaceholders) { SchemaLoader loader; Schema schema = loader.load(Schema::from<TestDefaults>().getProto()); { auto list = loader.getAllLoaded(); ASSERT_EQ(1u, list.size()); EXPECT_TRUE(list[0] == schema); } Schema dep = schema.getDependency(typeId<TestAllTypes>()); { auto list = loader.getAllLoaded(); ASSERT_EQ(2u, list.size()); if (list[0] == schema) { EXPECT_TRUE(list[1] == dep); } else { EXPECT_TRUE(list[0] == dep); EXPECT_TRUE(list[1] == schema); } } } class FakeLoaderCallback: public SchemaLoader::LazyLoadCallback { public: FakeLoaderCallback(const schema2::Node::Reader node): node(node), loaded(false) {} bool isLoaded() { return loaded; } void load(const SchemaLoader& loader, uint64_t id) const override { if (id == 1234) { // Magic "not found" ID. return; } EXPECT_EQ(node.getId(), id); EXPECT_FALSE(loaded); loaded = true; loader.loadOnce(node); } private: const schema2::Node::Reader node; mutable bool loaded = false; }; TEST(SchemaLoader, LazyLoad) { FakeLoaderCallback callback(Schema::from<TestAllTypes>().getProto()); SchemaLoader loader(callback); EXPECT_TRUE(loader.tryGet(1234) == nullptr); EXPECT_FALSE(callback.isLoaded()); Schema schema = loader.get(typeId<TestAllTypes>()); EXPECT_TRUE(callback.isLoaded()); EXPECT_EQ(schema.getProto().getDisplayName(), Schema::from<TestAllTypes>().getProto().getDisplayName()); EXPECT_EQ(schema, schema.getDependency(typeId<TestAllTypes>())); EXPECT_EQ(schema, loader.get(typeId<TestAllTypes>())); } TEST(SchemaLoader, LazyLoadGetDependency) { FakeLoaderCallback callback(Schema::from<TestAllTypes>().getProto()); SchemaLoader loader(callback); Schema schema = loader.load(Schema::from<TestDefaults>().getProto()); EXPECT_FALSE(callback.isLoaded()); Schema dep = schema.getDependency(typeId<TestAllTypes>()); EXPECT_TRUE(callback.isLoaded()); EXPECT_EQ(dep.getProto().getDisplayName(), Schema::from<TestAllTypes>().getProto().getDisplayName()); EXPECT_EQ(dep, schema.getDependency(typeId<TestAllTypes>())); EXPECT_EQ(dep, loader.get(typeId<TestAllTypes>())); } } // namespace } // namespace _ (private) } // namespace capnp