|
| 1 | +#include <gtest/gtest.h> |
| 2 | + |
| 3 | +import std; |
| 4 | +import mcpp.manifest; |
| 5 | +import mcpp.manifest.xpkg; |
| 6 | +import mcpp.platform; |
| 7 | +import mcpp.platform.axis; |
| 8 | + |
| 9 | +// #355 needed two things from the Form B (xpkg .lua) descriptor parser that it |
| 10 | +// simply did not read, and neither failed loudly: |
| 11 | +// |
| 12 | +// * `targets.<x>.required_features` was dropped, so a descriptor could not |
| 13 | +// express the cost gate that makes an optional host tool affordable — |
| 14 | +// compat.protobuf's `protoc` pulls in libprotoc's ~157 extra TUs, which the |
| 15 | +// consumers who only want the runtime must never compile. Without the gate |
| 16 | +// the target is either always built or never available. |
| 17 | +// * `deps` values could only be a version STRING, so a Form B descriptor had |
| 18 | +// no syntax at all for requesting a tool from one of its own dependencies. |
| 19 | +// |
| 20 | +// Both were silent: an unread key is just an absent feature. These tests are |
| 21 | +// what makes them loud. |
| 22 | + |
| 23 | +namespace { |
| 24 | + |
| 25 | +mcpp::manifest::Manifest parse_or_fail(std::string_view lua) { |
| 26 | + auto m = mcpp::manifest::synthesize_from_xpkg_lua( |
| 27 | + lua, "compat.demo", "1.0.0", mcpp::platform::HostPlatform::current()); |
| 28 | + EXPECT_TRUE(m.has_value()) << (m ? "" : m.error().message); |
| 29 | + return m.value_or(mcpp::manifest::Manifest{}); |
| 30 | +} |
| 31 | + |
| 32 | +const mcpp::manifest::Target* find_target(const mcpp::manifest::Manifest& m, |
| 33 | + std::string_view name) { |
| 34 | + for (auto const& t : m.targets) |
| 35 | + if (t.name == name) return &t; |
| 36 | + return nullptr; |
| 37 | +} |
| 38 | + |
| 39 | +} // namespace |
| 40 | + |
| 41 | +TEST(XpkgHostTools, TargetsCarryRequiredFeatures) { |
| 42 | + auto m = parse_or_fail(R"LUA( |
| 43 | +package = { |
| 44 | + spec = "1", name = "demo", namespace = "compat", type = "package", |
| 45 | + mcpp = { |
| 46 | + sources = { "*/src/**.cc" }, |
| 47 | + targets = { |
| 48 | + ["demo"] = { kind = "lib" }, |
| 49 | + ["protoc"] = { kind = "bin", main = "src/compiler/main.cc", |
| 50 | + required_features = { "protoc", "upb" } }, |
| 51 | + }, |
| 52 | + }, |
| 53 | +} |
| 54 | +)LUA"); |
| 55 | + |
| 56 | + auto const* lib = find_target(m, "demo"); |
| 57 | + ASSERT_NE(lib, nullptr); |
| 58 | + EXPECT_EQ(lib->kind, mcpp::manifest::Target::Library); |
| 59 | + EXPECT_TRUE(lib->requiredFeatures.empty()); |
| 60 | + |
| 61 | + auto const* tool = find_target(m, "protoc"); |
| 62 | + ASSERT_NE(tool, nullptr); |
| 63 | + EXPECT_EQ(tool->kind, mcpp::manifest::Target::Binary); |
| 64 | + EXPECT_EQ(tool->main, "src/compiler/main.cc"); |
| 65 | + EXPECT_EQ(tool->requiredFeatures, |
| 66 | + (std::vector<std::string>{"protoc", "upb"})); |
| 67 | +} |
| 68 | + |
| 69 | +TEST(XpkgHostTools, DepsAcceptBothAStringAndATable) { |
| 70 | + // The string form is the long-standing one and must keep working |
| 71 | + // unchanged; the table form is what lets a descriptor request a tool. |
| 72 | + auto m = parse_or_fail(R"LUA( |
| 73 | +package = { |
| 74 | + spec = "1", name = "demo", namespace = "compat", type = "package", |
| 75 | + mcpp = { |
| 76 | + sources = { "*/src/**.cc" }, |
| 77 | + deps = { |
| 78 | + ["compat.zlib"] = "1.3.2", |
| 79 | + ["compat.protobuf"] = { version = "35.1", tools = { "protoc" } }, |
| 80 | + }, |
| 81 | + }, |
| 82 | +} |
| 83 | +)LUA"); |
| 84 | + |
| 85 | + const mcpp::manifest::DependencySpec* zlib = nullptr; |
| 86 | + const mcpp::manifest::DependencySpec* pb = nullptr; |
| 87 | + for (auto const& [k, spec] : m.dependencies) { |
| 88 | + if (k.find("zlib") != std::string::npos) zlib = &spec; |
| 89 | + if (k.find("protobuf") != std::string::npos) pb = &spec; |
| 90 | + } |
| 91 | + |
| 92 | + ASSERT_NE(zlib, nullptr); |
| 93 | + EXPECT_EQ(zlib->version, "1.3.2"); |
| 94 | + EXPECT_TRUE(zlib->tools.empty()); |
| 95 | + |
| 96 | + ASSERT_NE(pb, nullptr); |
| 97 | + EXPECT_EQ(pb->version, "35.1"); |
| 98 | + EXPECT_EQ(pb->tools, (std::vector<std::string>{"protoc"})); |
| 99 | +} |
| 100 | + |
| 101 | +TEST(XpkgHostTools, UnknownDepKeyIsRecordedRatherThanSwallowed) { |
| 102 | + // A descriptor author writing an unsupported key must be told. Silently |
| 103 | + // ignoring it leaves the dependency half-configured with no signal — the |
| 104 | + // exact failure the per-feature key recording already exists to prevent. |
| 105 | + auto m = parse_or_fail(R"LUA( |
| 106 | +package = { |
| 107 | + spec = "1", name = "demo", namespace = "compat", type = "package", |
| 108 | + mcpp = { |
| 109 | + sources = { "*/src/**.cc" }, |
| 110 | + deps = { |
| 111 | + ["compat.protobuf"] = { version = "35.1", no_such_key = "x" }, |
| 112 | + }, |
| 113 | + }, |
| 114 | +} |
| 115 | +)LUA"); |
| 116 | + |
| 117 | + bool recorded = false; |
| 118 | + for (auto const& k : m.xpkgUnknownKeys) |
| 119 | + if (k.find("no_such_key") != std::string::npos) recorded = true; |
| 120 | + EXPECT_TRUE(recorded) << "unknown dep key was swallowed"; |
| 121 | + |
| 122 | + // ...and the keys it DOES understand still take effect. |
| 123 | + for (auto const& [k, spec] : m.dependencies) |
| 124 | + if (k.find("protobuf") != std::string::npos) |
| 125 | + EXPECT_EQ(spec.version, "35.1"); |
| 126 | +} |
0 commit comments