339 lines
8.7 KiB
C++
339 lines
8.7 KiB
C++
#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"
|
|
|
|
[features]
|
|
default = []
|
|
|
|
[workspace]
|
|
members = ["a"]
|
|
)");
|
|
auto r = parse(p);
|
|
REQUIRE(r.has_value());
|
|
}
|
|
|
|
TEST_CASE("parse extracts dev-dependencies", "[manifest][parse]") {
|
|
auto p = write_manifest(R"(
|
|
[package]
|
|
name = "foo"
|
|
version = "0.1.0"
|
|
|
|
[dev-dependencies]
|
|
catch2 = "3.5.0"
|
|
gtest = { version = "1.14", components = ["gmock"] }
|
|
)");
|
|
auto r = parse(p);
|
|
REQUIRE(r.has_value());
|
|
REQUIRE(r->dev_dependencies.size() == 2);
|
|
auto find = [&](std::string_view n) {
|
|
return std::ranges::find_if(r->dev_dependencies,
|
|
[&](const auto& d) { return d.name == n; });
|
|
};
|
|
auto catch2 = find("catch2");
|
|
REQUIRE(catch2 != r->dev_dependencies.end());
|
|
REQUIRE(catch2->version_spec == "3.5.0");
|
|
REQUIRE(catch2->components.empty());
|
|
auto gtest = find("gtest");
|
|
REQUIRE(gtest != r->dev_dependencies.end());
|
|
REQUIRE(gtest->version_spec == "1.14");
|
|
REQUIRE(gtest->components == std::vector<std::string>{"gmock"});
|
|
}
|
|
|
|
TEST_CASE("parse extracts [build].include_dirs", "[manifest][parse]") {
|
|
auto p = write_manifest(R"(
|
|
[package]
|
|
name = "foo"
|
|
version = "0.1.0"
|
|
|
|
[build]
|
|
include_dirs = ["third_party", "vendor/spdlog"]
|
|
)");
|
|
auto r = parse(p);
|
|
REQUIRE(r.has_value());
|
|
REQUIRE(r->build.include_dirs ==
|
|
std::vector<std::string>{"third_party", "vendor/spdlog"});
|
|
}
|
|
|
|
TEST_CASE("parse rejects [build].include_dirs that escape the project",
|
|
"[manifest][parse]") {
|
|
auto p = write_manifest(R"(
|
|
[package]
|
|
name = "foo"
|
|
version = "0.1.0"
|
|
|
|
[build]
|
|
include_dirs = ["../escape"]
|
|
)");
|
|
auto r = parse(p);
|
|
REQUIRE_FALSE(r.has_value());
|
|
REQUIRE(r.error().code == cargoxx::util::ErrorCode::ManifestInvalidField);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
TEST_CASE("parse recognizes { path = \"...\" } as a cargoxx path dep",
|
|
"[manifest][parse]") {
|
|
auto p = write_manifest(R"(
|
|
[package]
|
|
name = "consumer"
|
|
version = "0.1.0"
|
|
edition = "cpp23"
|
|
|
|
[dependencies]
|
|
mylib = { path = "../mylib" }
|
|
)");
|
|
auto r = parse(p);
|
|
REQUIRE(r.has_value());
|
|
REQUIRE(r->dependencies.size() == 1);
|
|
const auto& dep = r->dependencies[0];
|
|
REQUIRE(dep.name == "mylib");
|
|
REQUIRE(dep.source == cargoxx::manifest::DepSource::CargoxxPath);
|
|
REQUIRE(dep.path.has_value());
|
|
REQUIRE(*dep.path == "../mylib");
|
|
// Version defaults to "*" when only `path` is given.
|
|
REQUIRE(dep.version_spec == "*");
|
|
}
|
|
|
|
TEST_CASE("parse rejects dep table without version or path",
|
|
"[manifest][parse]") {
|
|
auto p = write_manifest(R"(
|
|
[package]
|
|
name = "consumer"
|
|
version = "0.1.0"
|
|
edition = "cpp23"
|
|
|
|
[dependencies]
|
|
mylib = { components = ["a"] }
|
|
)");
|
|
auto r = parse(p);
|
|
REQUIRE_FALSE(r.has_value());
|
|
REQUIRE(r.error().code == ErrorCode::ManifestInvalidField);
|
|
}
|