schema-parser-test.c++ (10730B)
1 // Copyright (c) 2013-2014 Sandstorm Development Group, Inc. and contributors 2 // Licensed under the MIT License: 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a copy 5 // of this software and associated documentation files (the "Software"), to deal 6 // in the Software without restriction, including without limitation the rights 7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 // copies of the Software, and to permit persons to whom the Software is 9 // furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 // THE SOFTWARE. 21 22 #define CAPNP_TESTING_CAPNP 1 23 24 #include "schema-parser.h" 25 #include <kj/compat/gtest.h> 26 #include "test-util.h" 27 #include <kj/debug.h> 28 #include <map> 29 30 namespace capnp { 31 namespace { 32 33 #if _WIN32 34 #define ABS(x) "C:\\" x 35 #else 36 #define ABS(x) "/" x 37 #endif 38 39 class FakeFileReader final: public kj::Filesystem { 40 public: 41 void add(kj::StringPtr name, kj::StringPtr content) { 42 root->openFile(cwd.evalNative(name), kj::WriteMode::CREATE | kj::WriteMode::CREATE_PARENT) 43 ->writeAll(content); 44 } 45 46 const kj::Directory& getRoot() const override { return *root; } 47 const kj::Directory& getCurrent() const override { return *current; } 48 kj::PathPtr getCurrentPath() const override { return cwd; } 49 50 private: 51 kj::Own<const kj::Directory> root = kj::newInMemoryDirectory(kj::nullClock()); 52 kj::Path cwd = kj::Path({}).evalNative(ABS("path/to/current/dir")); 53 kj::Own<const kj::Directory> current = root->openSubdir(cwd, 54 kj::WriteMode::CREATE | kj::WriteMode::CREATE_PARENT); 55 }; 56 57 static uint64_t getFieldTypeFileId(StructSchema::Field field) { 58 return field.getContainingStruct() 59 .getDependency(field.getProto().getSlot().getType().getStruct().getTypeId()) 60 .getProto().getScopeId(); 61 } 62 63 TEST(SchemaParser, Basic) { 64 FakeFileReader reader; 65 SchemaParser parser; 66 parser.setDiskFilesystem(reader); 67 68 reader.add("src/foo/bar.capnp", 69 "@0x8123456789abcdef;\n" 70 "struct Bar {\n" 71 " baz @0: import \"baz.capnp\".Baz;\n" 72 " corge @1: import \"../qux/corge.capnp\".Corge;\n" 73 " grault @2: import \"/grault.capnp\".Grault;\n" 74 " garply @3: import \"/garply.capnp\".Garply;\n" 75 "}\n"); 76 reader.add("src/foo/baz.capnp", 77 "@0x823456789abcdef1;\n" 78 "struct Baz {}\n"); 79 reader.add("src/qux/corge.capnp", 80 "@0x83456789abcdef12;\n" 81 "struct Corge {}\n"); 82 reader.add(ABS("usr/include/grault.capnp"), 83 "@0x8456789abcdef123;\n" 84 "struct Grault {}\n"); 85 reader.add(ABS("opt/include/grault.capnp"), 86 "@0x8000000000000001;\n" 87 "struct WrongGrault {}\n"); 88 reader.add(ABS("usr/local/include/garply.capnp"), 89 "@0x856789abcdef1234;\n" 90 "struct Garply {}\n"); 91 92 kj::StringPtr importPath[] = { 93 ABS("usr/include"), ABS("usr/local/include"), ABS("opt/include") 94 }; 95 96 ParsedSchema barSchema = parser.parseDiskFile( 97 "foo2/bar2.capnp", "src/foo/bar.capnp", importPath); 98 99 auto barProto = barSchema.getProto(); 100 EXPECT_EQ(0x8123456789abcdefull, barProto.getId()); 101 EXPECT_EQ("foo2/bar2.capnp", barProto.getDisplayName()); 102 103 auto barStruct = barSchema.getNested("Bar"); 104 auto barFields = barStruct.asStruct().getFields(); 105 ASSERT_EQ(4u, barFields.size()); 106 EXPECT_EQ("baz", barFields[0].getProto().getName()); 107 EXPECT_EQ(0x823456789abcdef1ull, getFieldTypeFileId(barFields[0])); 108 EXPECT_EQ("corge", barFields[1].getProto().getName()); 109 EXPECT_EQ(0x83456789abcdef12ull, getFieldTypeFileId(barFields[1])); 110 EXPECT_EQ("grault", barFields[2].getProto().getName()); 111 EXPECT_EQ(0x8456789abcdef123ull, getFieldTypeFileId(barFields[2])); 112 EXPECT_EQ("garply", barFields[3].getProto().getName()); 113 EXPECT_EQ(0x856789abcdef1234ull, getFieldTypeFileId(barFields[3])); 114 115 auto barStructs = barSchema.getAllNested(); 116 ASSERT_EQ(1, barStructs.size()); 117 EXPECT_EQ("Bar", barStructs[0].getUnqualifiedName()); 118 barFields = barStructs[0].asStruct().getFields(); 119 ASSERT_EQ(4u, barFields.size()); 120 EXPECT_EQ("baz", barFields[0].getProto().getName()); 121 EXPECT_EQ(0x823456789abcdef1ull, getFieldTypeFileId(barFields[0])); 122 123 auto bazSchema = parser.parseDiskFile( 124 "not/used/because/already/loaded", 125 "src/foo/baz.capnp", importPath); 126 EXPECT_EQ(0x823456789abcdef1ull, bazSchema.getProto().getId()); 127 EXPECT_EQ("foo2/baz.capnp", bazSchema.getProto().getDisplayName()); 128 auto bazStruct = bazSchema.getNested("Baz").asStruct(); 129 EXPECT_EQ(bazStruct, barStruct.getDependency(bazStruct.getProto().getId())); 130 131 auto corgeSchema = parser.parseDiskFile( 132 "not/used/because/already/loaded", 133 "src/qux/corge.capnp", importPath); 134 EXPECT_EQ(0x83456789abcdef12ull, corgeSchema.getProto().getId()); 135 EXPECT_EQ("qux/corge.capnp", corgeSchema.getProto().getDisplayName()); 136 auto corgeStruct = corgeSchema.getNested("Corge").asStruct(); 137 EXPECT_EQ(corgeStruct, barStruct.getDependency(corgeStruct.getProto().getId())); 138 139 auto graultSchema = parser.parseDiskFile( 140 "not/used/because/already/loaded", 141 ABS("usr/include/grault.capnp"), importPath); 142 EXPECT_EQ(0x8456789abcdef123ull, graultSchema.getProto().getId()); 143 EXPECT_EQ("grault.capnp", graultSchema.getProto().getDisplayName()); 144 auto graultStruct = graultSchema.getNested("Grault").asStruct(); 145 EXPECT_EQ(graultStruct, barStruct.getDependency(graultStruct.getProto().getId())); 146 147 // Try importing the other grault.capnp directly. It'll get the display name we specify since 148 // it wasn't imported before. 149 auto wrongGraultSchema = parser.parseDiskFile( 150 "weird/display/name.capnp", 151 ABS("opt/include/grault.capnp"), importPath); 152 EXPECT_EQ(0x8000000000000001ull, wrongGraultSchema.getProto().getId()); 153 EXPECT_EQ("weird/display/name.capnp", wrongGraultSchema.getProto().getDisplayName()); 154 } 155 156 TEST(SchemaParser, Constants) { 157 // This is actually a test of the full dynamic API stack for constants, because the schemas for 158 // constants are not actually accessible from the generated code API, so the only way to ever 159 // get a ConstSchema is by parsing it. 160 161 FakeFileReader reader; 162 SchemaParser parser; 163 parser.setDiskFilesystem(reader); 164 165 reader.add("const.capnp", 166 "@0x8123456789abcdef;\n" 167 "const uint32Const :UInt32 = 1234;\n" 168 "const listConst :List(Float32) = [1.25, 2.5, 3e4];\n" 169 "const structConst :Foo = (bar = 123, baz = \"qux\");\n" 170 "struct Foo {\n" 171 " bar @0 :Int16;\n" 172 " baz @1 :Text;\n" 173 "}\n" 174 "const genericConst :TestGeneric(Text) = (value = \"text\");\n" 175 "struct TestGeneric(T) {\n" 176 " value @0 :T;\n" 177 "}\n"); 178 179 ParsedSchema fileSchema = parser.parseDiskFile( 180 "const.capnp", "const.capnp", nullptr); 181 182 EXPECT_EQ(1234, fileSchema.getNested("uint32Const").asConst().as<uint32_t>()); 183 184 auto list = fileSchema.getNested("listConst").asConst().as<DynamicList>(); 185 ASSERT_EQ(3u, list.size()); 186 EXPECT_EQ(1.25, list[0].as<float>()); 187 EXPECT_EQ(2.5, list[1].as<float>()); 188 EXPECT_EQ(3e4f, list[2].as<float>()); 189 190 auto structConst = fileSchema.getNested("structConst").asConst().as<DynamicStruct>(); 191 EXPECT_EQ(123, structConst.get("bar").as<int16_t>()); 192 EXPECT_EQ("qux", structConst.get("baz").as<Text>()); 193 194 auto genericConst = fileSchema.getNested("genericConst").asConst().as<DynamicStruct>(); 195 EXPECT_EQ("text", genericConst.get("value").as<Text>()); 196 } 197 198 void expectSourceInfo(schema::Node::SourceInfo::Reader sourceInfo, 199 uint64_t expectedId, kj::StringPtr expectedComment, 200 std::initializer_list<const kj::StringPtr> expectedMembers) { 201 KJ_EXPECT(sourceInfo.getId() == expectedId, sourceInfo, expectedId); 202 KJ_EXPECT(sourceInfo.getDocComment() == expectedComment, sourceInfo, expectedComment); 203 204 auto members = sourceInfo.getMembers(); 205 KJ_ASSERT(members.size() == expectedMembers.size()); 206 for (auto i: kj::indices(expectedMembers)) { 207 KJ_EXPECT(members[i].getDocComment() == expectedMembers.begin()[i], 208 members[i], expectedMembers.begin()[i]); 209 } 210 } 211 212 TEST(SchemaParser, SourceInfo) { 213 FakeFileReader reader; 214 SchemaParser parser; 215 parser.setDiskFilesystem(reader); 216 217 reader.add("foo.capnp", 218 "@0x84a2c6051e1061ed;\n" 219 "# file doc comment\n" 220 "\n" 221 "struct Foo @0xc6527d0a670dc4c3 {\n" 222 " # struct doc comment\n" 223 " # second line\n" 224 "\n" 225 " bar @0 :UInt32;\n" 226 " # field doc comment\n" 227 " baz :group {\n" 228 " # group doc comment\n" 229 " qux @1 :Text;\n" 230 " # group field doc comment\n" 231 " }\n" 232 "}\n" 233 "\n" 234 "enum Corge @0xae08878f1a016f14 {\n" 235 " # enum doc comment\n" 236 " grault @0;\n" 237 " # enumerant doc comment\n" 238 " garply @1;\n" 239 "}\n" 240 "\n" 241 "interface Waldo @0xc0f1b0aff62b761e {\n" 242 " # interface doc comment\n" 243 " fred @0 (plugh :Int32) -> (xyzzy :Text);\n" 244 " # method doc comment\n" 245 "}\n" 246 "\n" 247 "struct Thud @0xcca9972702b730b4 {}\n" 248 "# post-comment\n"); 249 250 ParsedSchema file = parser.parseDiskFile( 251 "foo.capnp", "foo.capnp", nullptr); 252 ParsedSchema foo = file.getNested("Foo"); 253 254 expectSourceInfo(file.getSourceInfo(), 0x84a2c6051e1061edull, "file doc comment\n", {}); 255 256 expectSourceInfo(foo.getSourceInfo(), 0xc6527d0a670dc4c3ull, "struct doc comment\nsecond line\n", 257 { "field doc comment\n", "group doc comment\n" }); 258 259 auto group = foo.asStruct().getFieldByName("baz").getType().asStruct(); 260 expectSourceInfo(KJ_ASSERT_NONNULL(parser.getSourceInfo(group)), 261 group.getProto().getId(), "group doc comment\n", { "group field doc comment\n" }); 262 263 ParsedSchema corge = file.getNested("Corge"); 264 expectSourceInfo(corge.getSourceInfo(), 0xae08878f1a016f14, "enum doc comment\n", 265 { "enumerant doc comment\n", "" }); 266 267 ParsedSchema waldo = file.getNested("Waldo"); 268 expectSourceInfo(waldo.getSourceInfo(), 0xc0f1b0aff62b761e, "interface doc comment\n", 269 { "method doc comment\n" }); 270 271 ParsedSchema thud = file.getNested("Thud"); 272 expectSourceInfo(thud.getSourceInfo(), 0xcca9972702b730b4, "post-comment\n", {}); 273 } 274 275 } // namespace 276 } // namespace capnp