// Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors // Licensed under the MIT License: // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #include "schema-loader.h" #include <kj/compat/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().getTypeId()) .asStruct().getFieldByName("grault").getProto().getOrdinal().getExplicit()); EXPECT_EQ(9, schema.getDependency(schema.getFieldByName("anotherUnion").getProto().getGroup().getTypeId()) .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); } 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<schema::Node>(); root.setId(id); if (root.isStruct()) { // 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.isSlot()) { auto type = field.getSlot().getType(); if (type.isStruct() && type.getStruct().getTypeId() == typeId<T>()) { type.getStruct().setTypeId(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>())); } 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 schema::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 schema::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>())); } TEST(SchemaLoader, Generics) { SchemaLoader loader; StructSchema allTypes = loader.load(Schema::from<TestAllTypes>().getProto()).asStruct(); StructSchema tap = loader.load(Schema::from<test::TestAnyPointer>().getProto()).asStruct(); loader.load(Schema::from<test::TestGenerics<>::Inner>().getProto()); loader.load(Schema::from<test::TestGenerics<>::Inner2<>>().getProto()); loader.load(Schema::from<test::TestGenerics<>::Interface<>>().getProto()); loader.load(Schema::from<test::TestGenerics<>::Interface<>::CallResults>().getProto()); loader.load(Schema::from<test::TestGenerics<>>().getProto()); StructSchema schema = loader.load(Schema::from<test::TestUseGenerics>().getProto()).asStruct(); StructSchema branded; { StructSchema::Field basic = schema.getFieldByName("basic"); branded = basic.getType().asStruct(); StructSchema::Field foo = branded.getFieldByName("foo"); EXPECT_TRUE(foo.getType().asStruct() == allTypes); EXPECT_TRUE(foo.getType().asStruct() != tap); StructSchema instance2 = branded.getFieldByName("rev").getType().asStruct(); StructSchema::Field foo2 = instance2.getFieldByName("foo"); EXPECT_TRUE(foo2.getType().asStruct() == tap); EXPECT_TRUE(foo2.getType().asStruct() != allTypes); } { StructSchema inner2 = schema.getFieldByName("inner2").getType().asStruct(); StructSchema bound = inner2.getFieldByName("innerBound").getType().asStruct(); Type boundFoo = bound.getFieldByName("foo").getType(); EXPECT_FALSE(boundFoo.isAnyPointer()); EXPECT_TRUE(boundFoo.asStruct() == allTypes); StructSchema unbound = inner2.getFieldByName("innerUnbound").getType().asStruct(); Type unboundFoo = unbound.getFieldByName("foo").getType(); EXPECT_TRUE(unboundFoo.isAnyPointer()); } { InterfaceSchema cap = schema.getFieldByName("genericCap").getType().asInterface(); InterfaceSchema::Method method = cap.getMethodByName("call"); StructSchema inner2 = method.getParamType(); StructSchema bound = inner2.getFieldByName("innerBound").getType().asStruct(); Type boundFoo = bound.getFieldByName("foo").getType(); EXPECT_FALSE(boundFoo.isAnyPointer()); EXPECT_TRUE(boundFoo.asStruct() == allTypes); EXPECT_TRUE(inner2.getFieldByName("baz").getType().isText()); StructSchema results = method.getResultType(); EXPECT_TRUE(results.getFieldByName("qux").getType().isData()); EXPECT_TRUE(results.getFieldByName("gen").getType().asStruct() == branded); } } } // namespace } // namespace _ (private) } // namespace capnp