[M1] add manifest::parse

This commit is contained in:
2026-05-08 00:23:46 +00:00
parent fba725e192
commit 95a8890623
7 changed files with 18463 additions and 0 deletions

View File

@@ -8,3 +8,4 @@ function(cargoxx_add_test name)
endfunction()
cargoxx_add_test(util_error)
cargoxx_add_test(manifest_parse)

245
tests/manifest_parse.cpp Normal file
View File

@@ -0,0 +1,245 @@
#include <catch2/catch_test_macros.hpp>
import cargoxx.manifest;
import cargoxx.util;
import std;
using cargoxx::manifest::Edition;
using cargoxx::manifest::parse;
using cargoxx::util::ErrorCode;
namespace {
auto write_manifest(std::string_view body) -> std::filesystem::path {
auto dir = std::filesystem::temp_directory_path() /
std::format("cargoxx-manifest-test-{}", std::random_device{}());
std::filesystem::create_directories(dir);
auto path = dir / "Cargoxx.toml";
std::ofstream{path} << body;
return path;
}
} // namespace
TEST_CASE("parse rejects missing file", "[manifest][parse]") {
auto r = parse("/nonexistent/Cargoxx.toml");
REQUIRE_FALSE(r.has_value());
REQUIRE(r.error().code == ErrorCode::ManifestNotFound);
}
TEST_CASE("parse rejects malformed toml", "[manifest][parse]") {
auto p = write_manifest("[package\nname = ");
auto r = parse(p);
REQUIRE_FALSE(r.has_value());
REQUIRE(r.error().code == ErrorCode::ManifestParseError);
}
TEST_CASE("parse accepts a minimal manifest", "[manifest][parse]") {
auto p = write_manifest(R"(
[package]
name = "foo"
version = "0.1.0"
)");
auto r = parse(p);
REQUIRE(r.has_value());
REQUIRE(r->package.name == "foo");
REQUIRE(r->package.version == "0.1.0");
REQUIRE(r->package.edition == Edition::Cpp23);
REQUIRE(r->dependencies.empty());
REQUIRE_FALSE(r->build.warnings_as_errors);
}
TEST_CASE("parse handles all edition values", "[manifest][parse]") {
for (auto [s, e] : std::vector<std::pair<std::string, Edition>>{
{"cpp20", Edition::Cpp20}, {"cpp23", Edition::Cpp23}, {"cpp26", Edition::Cpp26}}) {
auto p = write_manifest(std::format(R"(
[package]
name = "foo"
version = "0.1.0"
edition = "{}"
)",
s));
auto r = parse(p);
REQUIRE(r.has_value());
REQUIRE(r->package.edition == e);
}
}
TEST_CASE("parse rejects unknown edition", "[manifest][parse]") {
auto p = write_manifest(R"(
[package]
name = "foo"
version = "0.1.0"
edition = "cpp99"
)");
auto r = parse(p);
REQUIRE_FALSE(r.has_value());
REQUIRE(r.error().code == ErrorCode::ManifestInvalidField);
}
TEST_CASE("parse extracts authors and license", "[manifest][parse]") {
auto p = write_manifest(R"(
[package]
name = "foo"
version = "0.1.0"
authors = ["Ada", "Grace"]
license = "MIT"
)");
auto r = parse(p);
REQUIRE(r.has_value());
REQUIRE(r->package.authors == std::vector<std::string>{"Ada", "Grace"});
REQUIRE(r->package.license == "MIT");
}
TEST_CASE("parse accepts string-form dependency", "[manifest][parse]") {
auto p = write_manifest(R"(
[package]
name = "foo"
version = "0.1.0"
[dependencies]
fmt = "10.2"
)");
auto r = parse(p);
REQUIRE(r.has_value());
REQUIRE(r->dependencies.size() == 1);
REQUIRE(r->dependencies[0].name == "fmt");
REQUIRE(r->dependencies[0].version_spec == "10.2");
REQUIRE(r->dependencies[0].components.empty());
}
TEST_CASE("parse accepts table-form dependency with components", "[manifest][parse]") {
auto p = write_manifest(R"(
[package]
name = "foo"
version = "0.1.0"
[dependencies]
boost = { version = "1.84", components = ["filesystem", "system"] }
)");
auto r = parse(p);
REQUIRE(r.has_value());
REQUIRE(r->dependencies.size() == 1);
REQUIRE(r->dependencies[0].name == "boost");
REQUIRE(r->dependencies[0].version_spec == "1.84");
REQUIRE(r->dependencies[0].components == std::vector<std::string>{"filesystem", "system"});
}
TEST_CASE("parse rejects dependency value of wrong type", "[manifest][parse]") {
auto p = write_manifest(R"(
[package]
name = "foo"
version = "0.1.0"
[dependencies]
weird = 42
)");
auto r = parse(p);
REQUIRE_FALSE(r.has_value());
REQUIRE(r.error().code == ErrorCode::ManifestInvalidField);
}
TEST_CASE("parse extracts build settings", "[manifest][parse]") {
auto p = write_manifest(R"(
[package]
name = "foo"
version = "0.1.0"
[build]
warnings_as_errors = true
sanitizers = ["address", "undefined"]
)");
auto r = parse(p);
REQUIRE(r.has_value());
REQUIRE(r->build.warnings_as_errors);
REQUIRE(r->build.sanitizers == std::vector<std::string>{"address", "undefined"});
}
TEST_CASE("parse rejects unknown [package] key", "[manifest][parse]") {
auto p = write_manifest(R"(
[package]
name = "foo"
version = "0.1.0"
flavor = "spicy"
)");
auto r = parse(p);
REQUIRE_FALSE(r.has_value());
REQUIRE(r.error().code == ErrorCode::ManifestUnknownField);
}
TEST_CASE("parse rejects unknown [build] key", "[manifest][parse]") {
auto p = write_manifest(R"(
[package]
name = "foo"
version = "0.1.0"
[build]
optimize = "max"
)");
auto r = parse(p);
REQUIRE_FALSE(r.has_value());
REQUIRE(r.error().code == ErrorCode::ManifestUnknownField);
}
TEST_CASE("parse accepts reserved [package] fields", "[manifest][parse]") {
auto p = write_manifest(R"(
[package]
name = "foo"
version = "0.1.0"
description = "demo"
repository = "https://example.com/foo"
)");
auto r = parse(p);
REQUIRE(r.has_value());
}
TEST_CASE("parse accepts reserved top-level tables", "[manifest][parse]") {
auto p = write_manifest(R"(
[package]
name = "foo"
version = "0.1.0"
[dev-dependencies]
gtest = "1.14"
[features]
default = []
[workspace]
members = ["a"]
)");
auto r = parse(p);
REQUIRE(r.has_value());
}
TEST_CASE("parse rejects invalid name with spaces", "[manifest][parse]") {
auto p = write_manifest(R"(
[package]
name = "foo bar"
version = "0.1.0"
)");
auto r = parse(p);
REQUIRE_FALSE(r.has_value());
REQUIRE(r.error().code == ErrorCode::LayoutInvalidName);
}
TEST_CASE("parse rejects name starting with digit", "[manifest][parse]") {
auto p = write_manifest(R"(
[package]
name = "1foo"
version = "0.1.0"
)");
auto r = parse(p);
REQUIRE_FALSE(r.has_value());
REQUIRE(r.error().code == ErrorCode::LayoutInvalidName);
}
TEST_CASE("parse rejects missing [package].name", "[manifest][parse]") {
auto p = write_manifest(R"(
[package]
version = "0.1.0"
)");
auto r = parse(p);
REQUIRE_FALSE(r.has_value());
REQUIRE(r.error().code == ErrorCode::ManifestInvalidField);
}