[M6] tests: levenshtein + pc_scan + brute_scan + findmodule_scan + last_failure_dir + cmd_linkdb_add + codegen PkgConfig/brute-force
This commit is contained in:
@@ -91,6 +91,15 @@ target_link_libraries(cargoxx_bin PRIVATE
|
|||||||
# ----- tests -----
|
# ----- tests -----
|
||||||
enable_testing()
|
enable_testing()
|
||||||
include(Catch)
|
include(Catch)
|
||||||
|
add_executable(test_brute_scan_parse ../tests/brute_scan_parse.cpp)
|
||||||
|
target_link_libraries(test_brute_scan_parse PRIVATE
|
||||||
|
cargoxx
|
||||||
|
reproc
|
||||||
|
SQLite::SQLite3
|
||||||
|
Catch2::Catch2
|
||||||
|
Catch2::Catch2WithMain
|
||||||
|
)
|
||||||
|
catch_discover_tests(test_brute_scan_parse)
|
||||||
add_executable(test_cmd_add ../tests/cmd_add.cpp)
|
add_executable(test_cmd_add ../tests/cmd_add.cpp)
|
||||||
target_link_libraries(test_cmd_add PRIVATE
|
target_link_libraries(test_cmd_add PRIVATE
|
||||||
cargoxx
|
cargoxx
|
||||||
@@ -118,6 +127,15 @@ target_link_libraries(test_cmd_clean PRIVATE
|
|||||||
Catch2::Catch2WithMain
|
Catch2::Catch2WithMain
|
||||||
)
|
)
|
||||||
catch_discover_tests(test_cmd_clean)
|
catch_discover_tests(test_cmd_clean)
|
||||||
|
add_executable(test_cmd_linkdb_add ../tests/cmd_linkdb_add.cpp)
|
||||||
|
target_link_libraries(test_cmd_linkdb_add PRIVATE
|
||||||
|
cargoxx
|
||||||
|
reproc
|
||||||
|
SQLite::SQLite3
|
||||||
|
Catch2::Catch2
|
||||||
|
Catch2::Catch2WithMain
|
||||||
|
)
|
||||||
|
catch_discover_tests(test_cmd_linkdb_add)
|
||||||
add_executable(test_cmd_new ../tests/cmd_new.cpp)
|
add_executable(test_cmd_new ../tests/cmd_new.cpp)
|
||||||
target_link_libraries(test_cmd_new PRIVATE
|
target_link_libraries(test_cmd_new PRIVATE
|
||||||
cargoxx
|
cargoxx
|
||||||
@@ -208,6 +226,24 @@ target_link_libraries(test_exec_run PRIVATE
|
|||||||
Catch2::Catch2WithMain
|
Catch2::Catch2WithMain
|
||||||
)
|
)
|
||||||
catch_discover_tests(test_exec_run)
|
catch_discover_tests(test_exec_run)
|
||||||
|
add_executable(test_findmodule_scan_live ../tests/findmodule_scan_live.cpp)
|
||||||
|
target_link_libraries(test_findmodule_scan_live PRIVATE
|
||||||
|
cargoxx
|
||||||
|
reproc
|
||||||
|
SQLite::SQLite3
|
||||||
|
Catch2::Catch2
|
||||||
|
Catch2::Catch2WithMain
|
||||||
|
)
|
||||||
|
catch_discover_tests(test_findmodule_scan_live)
|
||||||
|
add_executable(test_last_failure_dir ../tests/last_failure_dir.cpp)
|
||||||
|
target_link_libraries(test_last_failure_dir PRIVATE
|
||||||
|
cargoxx
|
||||||
|
reproc
|
||||||
|
SQLite::SQLite3
|
||||||
|
Catch2::Catch2
|
||||||
|
Catch2::Catch2WithMain
|
||||||
|
)
|
||||||
|
catch_discover_tests(test_last_failure_dir)
|
||||||
add_executable(test_layout_discovery ../tests/layout_discovery.cpp)
|
add_executable(test_layout_discovery ../tests/layout_discovery.cpp)
|
||||||
target_link_libraries(test_layout_discovery PRIVATE
|
target_link_libraries(test_layout_discovery PRIVATE
|
||||||
cargoxx
|
cargoxx
|
||||||
@@ -217,6 +253,15 @@ target_link_libraries(test_layout_discovery PRIVATE
|
|||||||
Catch2::Catch2WithMain
|
Catch2::Catch2WithMain
|
||||||
)
|
)
|
||||||
catch_discover_tests(test_layout_discovery)
|
catch_discover_tests(test_layout_discovery)
|
||||||
|
add_executable(test_levenshtein ../tests/levenshtein.cpp)
|
||||||
|
target_link_libraries(test_levenshtein PRIVATE
|
||||||
|
cargoxx
|
||||||
|
reproc
|
||||||
|
SQLite::SQLite3
|
||||||
|
Catch2::Catch2
|
||||||
|
Catch2::Catch2WithMain
|
||||||
|
)
|
||||||
|
catch_discover_tests(test_levenshtein)
|
||||||
add_executable(test_linkdb_lookup ../tests/linkdb_lookup.cpp)
|
add_executable(test_linkdb_lookup ../tests/linkdb_lookup.cpp)
|
||||||
target_link_libraries(test_linkdb_lookup PRIVATE
|
target_link_libraries(test_linkdb_lookup PRIVATE
|
||||||
cargoxx
|
cargoxx
|
||||||
@@ -307,6 +352,15 @@ target_link_libraries(test_nixpkgs_probe_parse PRIVATE
|
|||||||
Catch2::Catch2WithMain
|
Catch2::Catch2WithMain
|
||||||
)
|
)
|
||||||
catch_discover_tests(test_nixpkgs_probe_parse)
|
catch_discover_tests(test_nixpkgs_probe_parse)
|
||||||
|
add_executable(test_pc_scan_parse ../tests/pc_scan_parse.cpp)
|
||||||
|
target_link_libraries(test_pc_scan_parse PRIVATE
|
||||||
|
cargoxx
|
||||||
|
reproc
|
||||||
|
SQLite::SQLite3
|
||||||
|
Catch2::Catch2
|
||||||
|
Catch2::Catch2WithMain
|
||||||
|
)
|
||||||
|
catch_discover_tests(test_pc_scan_parse)
|
||||||
add_executable(test_semver_satisfies ../tests/semver_satisfies.cpp)
|
add_executable(test_semver_satisfies ../tests/semver_satisfies.cpp)
|
||||||
target_link_libraries(test_semver_satisfies PRIVATE
|
target_link_libraries(test_semver_satisfies PRIVATE
|
||||||
cargoxx
|
cargoxx
|
||||||
|
|||||||
89
tests/brute_scan_parse.cpp
Normal file
89
tests/brute_scan_parse.cpp
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
import cargoxx.resolver;
|
||||||
|
import cargoxx.util;
|
||||||
|
import std;
|
||||||
|
|
||||||
|
using cargoxx::resolver::brute_scan;
|
||||||
|
using cargoxx::util::ErrorCode;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
auto fresh_store() -> std::filesystem::path {
|
||||||
|
auto d = std::filesystem::temp_directory_path() /
|
||||||
|
std::format("cargoxx-brute-scan-{}", std::random_device{}());
|
||||||
|
std::filesystem::create_directories(d / "lib");
|
||||||
|
std::filesystem::create_directories(d / "include");
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
void touch_lib(const std::filesystem::path& store, std::string_view name) {
|
||||||
|
std::ofstream{store / "lib" / std::string{name}};
|
||||||
|
}
|
||||||
|
|
||||||
|
void touch_include(const std::filesystem::path& store, std::string_view rel) {
|
||||||
|
auto p = store / "include" / rel;
|
||||||
|
std::filesystem::create_directories(p.parent_path());
|
||||||
|
std::ofstream{p};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("brute_scan collects lib*.a and lib*.so files",
|
||||||
|
"[resolver][brute_scan]") {
|
||||||
|
auto store = fresh_store();
|
||||||
|
touch_lib(store, "libfoo.a");
|
||||||
|
touch_lib(store, "libbar.so");
|
||||||
|
touch_lib(store, "libbaz.so.1.2.3");
|
||||||
|
touch_lib(store, "not-a-lib.txt");
|
||||||
|
touch_include(store, "foo.h");
|
||||||
|
|
||||||
|
auto r = brute_scan(store, "foo");
|
||||||
|
REQUIRE(r.has_value());
|
||||||
|
REQUIRE(r->lib_files.size() == 3);
|
||||||
|
auto has = [&](std::string_view suffix) {
|
||||||
|
return std::ranges::any_of(r->lib_files, [&](const auto& p) {
|
||||||
|
return std::string_view{p}.ends_with(suffix);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
REQUIRE(has("libfoo.a"));
|
||||||
|
REQUIRE(has("libbar.so"));
|
||||||
|
REQUIRE(has("libbaz.so.1.2.3"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("brute_scan exposes include/ as a single search directory",
|
||||||
|
"[resolver][brute_scan]") {
|
||||||
|
auto store = fresh_store();
|
||||||
|
touch_lib(store, "libsdl.a");
|
||||||
|
touch_include(store, "SDL2/SDL.h");
|
||||||
|
touch_include(store, "SDL2/SDL_video.h");
|
||||||
|
|
||||||
|
auto r = brute_scan(store, "sdl");
|
||||||
|
REQUIRE(r.has_value());
|
||||||
|
REQUIRE(r->include_dirs.size() == 1);
|
||||||
|
REQUIRE(std::string_view{r->include_dirs[0]}.ends_with("/include"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("brute_scan errors when neither libs nor headers are present",
|
||||||
|
"[resolver][brute_scan]") {
|
||||||
|
auto d = std::filesystem::temp_directory_path() /
|
||||||
|
std::format("cargoxx-brute-empty-{}", std::random_device{}());
|
||||||
|
std::filesystem::create_directories(d);
|
||||||
|
|
||||||
|
auto r = brute_scan(d, "ghost");
|
||||||
|
REQUIRE_FALSE(r.has_value());
|
||||||
|
REQUIRE(r.error().code == ErrorCode::ResolutionUnknownPackage);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("brute_scan emits sorted lib paths for deterministic codegen",
|
||||||
|
"[resolver][brute_scan]") {
|
||||||
|
auto store = fresh_store();
|
||||||
|
touch_lib(store, "libzz.a");
|
||||||
|
touch_lib(store, "libaa.a");
|
||||||
|
touch_lib(store, "libmm.so");
|
||||||
|
|
||||||
|
auto r = brute_scan(store, "x");
|
||||||
|
REQUIRE(r.has_value());
|
||||||
|
REQUIRE(r->lib_files.size() == 3);
|
||||||
|
REQUIRE(std::ranges::is_sorted(r->lib_files));
|
||||||
|
}
|
||||||
65
tests/cmd_linkdb_add.cpp
Normal file
65
tests/cmd_linkdb_add.cpp
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
import cargoxx.cli;
|
||||||
|
import cargoxx.linkdb;
|
||||||
|
import cargoxx.util;
|
||||||
|
import std;
|
||||||
|
|
||||||
|
using cargoxx::cli::cmd_linkdb_add;
|
||||||
|
using cargoxx::linkdb::Database;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
auto fresh_overlay() -> std::filesystem::path {
|
||||||
|
auto d = std::filesystem::temp_directory_path() /
|
||||||
|
std::format("cargoxx-linkdb-add-test-{}", std::random_device{}());
|
||||||
|
std::filesystem::create_directories(d);
|
||||||
|
return d / "overlay.sqlite";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("cmd_linkdb_add inserts a recipe that resolve() can read back",
|
||||||
|
"[cli][linkdb_add]") {
|
||||||
|
auto overlay = fresh_overlay();
|
||||||
|
|
||||||
|
auto r = cmd_linkdb_add(
|
||||||
|
"sqlite3", "*", "SQLite3 REQUIRED", {"SQLite::SQLite3"}, "sqlite",
|
||||||
|
overlay);
|
||||||
|
REQUIRE(r.has_value());
|
||||||
|
|
||||||
|
auto db = Database::open(overlay);
|
||||||
|
REQUIRE(db.has_value());
|
||||||
|
auto rec = db->resolve("sqlite3", "*");
|
||||||
|
REQUIRE(rec.has_value());
|
||||||
|
REQUIRE(rec->source == "manual");
|
||||||
|
REQUIRE(rec->find_package == "SQLite3 REQUIRED");
|
||||||
|
REQUIRE(rec->targets == std::vector<std::string>{"SQLite::SQLite3"});
|
||||||
|
REQUIRE(rec->nixpkgs_attr == "sqlite");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("cmd_linkdb_add evicts auto-discovered rows for the same package",
|
||||||
|
"[cli][linkdb_add]") {
|
||||||
|
auto overlay = fresh_overlay();
|
||||||
|
auto db = Database::open(overlay);
|
||||||
|
REQUIRE(db.has_value());
|
||||||
|
|
||||||
|
// Seed an auto-source row first.
|
||||||
|
cargoxx::linkdb::Recipe auto_recipe{
|
||||||
|
.nixpkgs_attr = "sqlite",
|
||||||
|
.find_package = "auto CONFIG REQUIRED",
|
||||||
|
.targets = {"auto::auto"},
|
||||||
|
.source = "nix-probe",
|
||||||
|
};
|
||||||
|
REQUIRE(db->insert_provisional("sqlite3", "*", auto_recipe, "nix-probe")
|
||||||
|
.has_value());
|
||||||
|
REQUIRE(db->confirm_provisional("sqlite3", "*", "nix-probe").has_value());
|
||||||
|
|
||||||
|
REQUIRE(cmd_linkdb_add("sqlite3", "*", "SQLite3 REQUIRED",
|
||||||
|
{"SQLite::SQLite3"}, "sqlite", overlay)
|
||||||
|
.has_value());
|
||||||
|
|
||||||
|
auto rec = db->resolve("sqlite3", "*");
|
||||||
|
REQUIRE(rec.has_value());
|
||||||
|
REQUIRE(rec->source == "manual");
|
||||||
|
}
|
||||||
@@ -342,3 +342,70 @@ TEST_CASE("cmake_lists threads dev_recipes through find_package and tests",
|
|||||||
auto block = out.substr(link, end - link);
|
auto block = out.substr(link, end - link);
|
||||||
REQUIRE(block.find("Catch2::Catch2WithMain") != std::string::npos);
|
REQUIRE(block.find("Catch2::Catch2WithMain") != std::string::npos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("cmake_lists emits pkg_check_modules for pkg_config recipes",
|
||||||
|
"[codegen][cmake]") {
|
||||||
|
Manifest m{
|
||||||
|
.package = pkg("app"),
|
||||||
|
.dependencies = {{.name = "sqlite", .version_spec = "*"}},
|
||||||
|
};
|
||||||
|
DiscoveredLayout layout{
|
||||||
|
.library = std::nullopt,
|
||||||
|
.binaries = {src_target(TargetKind::Binary, "app", "src/main.cpp")},
|
||||||
|
.tests = {},
|
||||||
|
.examples = {},
|
||||||
|
};
|
||||||
|
Lockfile lock = lock_minimal();
|
||||||
|
Recipe sqlite{
|
||||||
|
.nixpkgs_attr = "sqlite",
|
||||||
|
.find_package = "PkgConfig REQUIRED",
|
||||||
|
.targets = {"PkgConfig::SQLITE3"},
|
||||||
|
.source = "pkg-config",
|
||||||
|
.pkg_config_module = "sqlite3",
|
||||||
|
};
|
||||||
|
GenerateInputs in{m, layout, lock, {sqlite}, {}, ROOT};
|
||||||
|
|
||||||
|
auto out = cmake_lists(in);
|
||||||
|
REQUIRE(out.find("find_package(PkgConfig REQUIRED)") != std::string::npos);
|
||||||
|
REQUIRE(out.find("pkg_check_modules(SQLITE3 REQUIRED IMPORTED_TARGET sqlite3)") !=
|
||||||
|
std::string::npos);
|
||||||
|
auto link = out.find("target_link_libraries(app_bin PRIVATE");
|
||||||
|
REQUIRE(link != std::string::npos);
|
||||||
|
auto block = out.substr(link, out.find(')', link) - link);
|
||||||
|
REQUIRE(block.find("PkgConfig::SQLITE3") != std::string::npos);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("cmake_lists synthesizes INTERFACE IMPORTED target for brute-force "
|
||||||
|
"recipes",
|
||||||
|
"[codegen][cmake]") {
|
||||||
|
Manifest m{
|
||||||
|
.package = pkg("app"),
|
||||||
|
.dependencies = {{.name = "obscure", .version_spec = "*"}},
|
||||||
|
};
|
||||||
|
DiscoveredLayout layout{
|
||||||
|
.library = std::nullopt,
|
||||||
|
.binaries = {src_target(TargetKind::Binary, "app", "src/main.cpp")},
|
||||||
|
.tests = {},
|
||||||
|
.examples = {},
|
||||||
|
};
|
||||||
|
Lockfile lock = lock_minimal();
|
||||||
|
Recipe brute{
|
||||||
|
.nixpkgs_attr = "obscure",
|
||||||
|
.find_package = "",
|
||||||
|
.targets = {"obscure::obscure"},
|
||||||
|
.source = "brute-force",
|
||||||
|
.brute_force_libs = {"/nix/store/abc-obscure/lib/libobscure.a"},
|
||||||
|
.brute_force_includes = {"/nix/store/abc-obscure/include"},
|
||||||
|
};
|
||||||
|
GenerateInputs in{m, layout, lock, {brute}, {}, ROOT};
|
||||||
|
|
||||||
|
auto out = cmake_lists(in);
|
||||||
|
REQUIRE(out.find("add_library(obscure::obscure INTERFACE IMPORTED)") !=
|
||||||
|
std::string::npos);
|
||||||
|
REQUIRE(out.find("INTERFACE_LINK_LIBRARIES") != std::string::npos);
|
||||||
|
REQUIRE(out.find("/nix/store/abc-obscure/lib/libobscure.a") !=
|
||||||
|
std::string::npos);
|
||||||
|
REQUIRE(out.find("INTERFACE_INCLUDE_DIRECTORIES") != std::string::npos);
|
||||||
|
REQUIRE(out.find("/nix/store/abc-obscure/include") != std::string::npos);
|
||||||
|
REQUIRE(out.find("find_package()") == std::string::npos);
|
||||||
|
}
|
||||||
|
|||||||
@@ -73,6 +73,37 @@ auto dep_pkg(std::string name, std::string version,
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("flake_nix adds pkgs.pkg-config to nativeBuildInputs only when needed",
|
||||||
|
"[codegen][flake]") {
|
||||||
|
Manifest m{pkg("app"), {dep("sqlite", "*")}, {}};
|
||||||
|
DiscoveredLayout layout{};
|
||||||
|
Lockfile lock{1, {root_pkg("app", "0.1.0")}};
|
||||||
|
std::vector<Recipe> recipes = {Recipe{
|
||||||
|
.nixpkgs_attr = "sqlite",
|
||||||
|
.find_package = "PkgConfig REQUIRED",
|
||||||
|
.targets = {"PkgConfig::SQLITE3"},
|
||||||
|
.source = "pkg-config",
|
||||||
|
.pkg_config_module = "sqlite3",
|
||||||
|
}};
|
||||||
|
GenerateInputs in{m, layout, lock, recipes, {}, "/tmp/app"};
|
||||||
|
|
||||||
|
auto out = flake_nix(in);
|
||||||
|
REQUIRE(out.find("pkgs.pkg-config") != std::string::npos);
|
||||||
|
REQUIRE(out.find("pkgs.sqlite") != std::string::npos);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("flake_nix omits pkgs.pkg-config when no recipe needs it",
|
||||||
|
"[codegen][flake]") {
|
||||||
|
Manifest m{pkg("hello"), {dep("fmt", "*")}, {}};
|
||||||
|
DiscoveredLayout layout{};
|
||||||
|
Lockfile lock{1, {root_pkg("hello", "0.1.0")}};
|
||||||
|
std::vector<Recipe> recipes = {recipe("fmt_10")};
|
||||||
|
GenerateInputs in{m, layout, lock, recipes, {}, "/tmp/hello"};
|
||||||
|
|
||||||
|
auto out = flake_nix(in);
|
||||||
|
REQUIRE(out.find("pkgs.pkg-config") == std::string::npos);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("flake_nix always emits the shared nixos-unstable nixpkgs input",
|
TEST_CASE("flake_nix always emits the shared nixos-unstable nixpkgs input",
|
||||||
"[codegen][flake]") {
|
"[codegen][flake]") {
|
||||||
Manifest m{pkg("hello"), {}, {}};
|
Manifest m{pkg("hello"), {}, {}};
|
||||||
|
|||||||
67
tests/findmodule_scan_live.cpp
Normal file
67
tests/findmodule_scan_live.cpp
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
import cargoxx.resolver;
|
||||||
|
import cargoxx.util;
|
||||||
|
import std;
|
||||||
|
|
||||||
|
using cargoxx::resolver::findmodule_scan;
|
||||||
|
using cargoxx::util::ErrorCode;
|
||||||
|
|
||||||
|
// `findmodule_scan` shells out to `cmake -P` to discover CMAKE_ROOT.
|
||||||
|
// Gate the test on the same env var we use for other live probes so
|
||||||
|
// CI without cmake can still pass.
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
auto live_tests_enabled() -> bool {
|
||||||
|
auto* e = std::getenv("CARGOXX_NETWORK_TESTS");
|
||||||
|
return e != nullptr && *e != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("findmodule_scan picks FindSQLite3 for 'sqlite'",
|
||||||
|
"[resolver][findmodule_scan][live]") {
|
||||||
|
if (!live_tests_enabled()) {
|
||||||
|
SKIP("CARGOXX_NETWORK_TESTS not set");
|
||||||
|
}
|
||||||
|
auto r = findmodule_scan("sqlite");
|
||||||
|
REQUIRE(r.has_value());
|
||||||
|
REQUIRE(r->find_package == "SQLite3 REQUIRED");
|
||||||
|
REQUIRE_FALSE(r->targets.empty());
|
||||||
|
auto has = [&](std::string_view t) {
|
||||||
|
return std::ranges::find(r->targets, std::string{t}) != r->targets.end();
|
||||||
|
};
|
||||||
|
REQUIRE((has("SQLite3::SQLite3") || has("SQLite::SQLite3")));
|
||||||
|
REQUIRE(r->module_file.filename() == "FindSQLite3.cmake");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("findmodule_scan picks FindZLIB for 'zlib'",
|
||||||
|
"[resolver][findmodule_scan][live]") {
|
||||||
|
if (!live_tests_enabled()) {
|
||||||
|
SKIP("CARGOXX_NETWORK_TESTS not set");
|
||||||
|
}
|
||||||
|
auto r = findmodule_scan("zlib");
|
||||||
|
REQUIRE(r.has_value());
|
||||||
|
REQUIRE(r->find_package == "ZLIB REQUIRED");
|
||||||
|
REQUIRE(r->module_file.filename() == "FindZLIB.cmake");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("findmodule_scan picks FindThreads for 'threads'",
|
||||||
|
"[resolver][findmodule_scan][live]") {
|
||||||
|
if (!live_tests_enabled()) {
|
||||||
|
SKIP("CARGOXX_NETWORK_TESTS not set");
|
||||||
|
}
|
||||||
|
auto r = findmodule_scan("threads");
|
||||||
|
REQUIRE(r.has_value());
|
||||||
|
REQUIRE(r->find_package == "Threads REQUIRED");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("findmodule_scan errors for a totally unknown name",
|
||||||
|
"[resolver][findmodule_scan][live]") {
|
||||||
|
if (!live_tests_enabled()) {
|
||||||
|
SKIP("CARGOXX_NETWORK_TESTS not set");
|
||||||
|
}
|
||||||
|
auto r = findmodule_scan("definitelynotacmaketmodule12345");
|
||||||
|
REQUIRE_FALSE(r.has_value());
|
||||||
|
REQUIRE(r.error().code == ErrorCode::ResolutionUnknownPackage);
|
||||||
|
}
|
||||||
59
tests/last_failure_dir.cpp
Normal file
59
tests/last_failure_dir.cpp
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
import cargoxx.resolver;
|
||||||
|
import std;
|
||||||
|
|
||||||
|
using cargoxx::resolver::last_failure_dir;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct EnvScope {
|
||||||
|
EnvScope(const char* k, std::optional<std::string> v) : key(k) {
|
||||||
|
if (auto* prior = std::getenv(key)) {
|
||||||
|
previous = std::string{prior};
|
||||||
|
}
|
||||||
|
if (v) {
|
||||||
|
setenv(key, v->c_str(), 1);
|
||||||
|
} else {
|
||||||
|
unsetenv(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
~EnvScope() {
|
||||||
|
if (previous) {
|
||||||
|
setenv(key, previous->c_str(), 1);
|
||||||
|
} else {
|
||||||
|
unsetenv(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const char* key;
|
||||||
|
std::optional<std::string> previous;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("last_failure_dir honors XDG_CACHE_HOME when set",
|
||||||
|
"[resolver][last_failure_dir]") {
|
||||||
|
EnvScope xdg{"XDG_CACHE_HOME", "/tmp/xdg-test"};
|
||||||
|
|
||||||
|
auto p = last_failure_dir("sqlite");
|
||||||
|
REQUIRE(p.string() == "/tmp/xdg-test/cargoxx/last-failure/sqlite");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("last_failure_dir falls back to $HOME/.cache when XDG is unset",
|
||||||
|
"[resolver][last_failure_dir]") {
|
||||||
|
EnvScope xdg{"XDG_CACHE_HOME", std::nullopt};
|
||||||
|
EnvScope home{"HOME", "/tmp/home-test"};
|
||||||
|
|
||||||
|
auto p = last_failure_dir("fmt");
|
||||||
|
REQUIRE(p.string() == "/tmp/home-test/.cache/cargoxx/last-failure/fmt");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("last_failure_dir uses cwd-based fallback when neither var is set",
|
||||||
|
"[resolver][last_failure_dir]") {
|
||||||
|
EnvScope xdg{"XDG_CACHE_HOME", std::nullopt};
|
||||||
|
EnvScope home{"HOME", std::nullopt};
|
||||||
|
|
||||||
|
auto p = last_failure_dir("obscure");
|
||||||
|
REQUIRE(p.filename() == "obscure");
|
||||||
|
REQUIRE(p.parent_path().filename() == ".cargoxx-last-failure");
|
||||||
|
}
|
||||||
36
tests/levenshtein.cpp
Normal file
36
tests/levenshtein.cpp
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
import cargoxx.util;
|
||||||
|
import std;
|
||||||
|
|
||||||
|
using cargoxx::util::levenshtein;
|
||||||
|
|
||||||
|
TEST_CASE("levenshtein of equal strings is zero", "[util][levenshtein]") {
|
||||||
|
REQUIRE(levenshtein("", "") == 0);
|
||||||
|
REQUIRE(levenshtein("sqlite", "sqlite") == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("levenshtein counts a single suffix character", "[util][levenshtein]") {
|
||||||
|
REQUIRE(levenshtein("sqlite", "sqlite3") == 1);
|
||||||
|
REQUIRE(levenshtein("fmt", "fmtlib") == 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("levenshtein is symmetric", "[util][levenshtein]") {
|
||||||
|
REQUIRE(levenshtein("sqlite", "sqlite3") == levenshtein("sqlite3", "sqlite"));
|
||||||
|
REQUIRE(levenshtein("abseil-cpp", "absl") == levenshtein("absl", "abseil-cpp"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("levenshtein counts a single substitution", "[util][levenshtein]") {
|
||||||
|
REQUIRE(levenshtein("kitten", "sitten") == 1);
|
||||||
|
REQUIRE(levenshtein("kitten", "kittes") == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("levenshtein matches the classic kitten/sitting example",
|
||||||
|
"[util][levenshtein]") {
|
||||||
|
REQUIRE(levenshtein("kitten", "sitting") == 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("levenshtein handles empty inputs", "[util][levenshtein]") {
|
||||||
|
REQUIRE(levenshtein("", "abc") == 3);
|
||||||
|
REQUIRE(levenshtein("abc", "") == 3);
|
||||||
|
}
|
||||||
89
tests/pc_scan_parse.cpp
Normal file
89
tests/pc_scan_parse.cpp
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
import cargoxx.resolver;
|
||||||
|
import cargoxx.util;
|
||||||
|
import std;
|
||||||
|
|
||||||
|
using cargoxx::resolver::pc_scan;
|
||||||
|
using cargoxx::util::ErrorCode;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
auto fresh_store() -> std::filesystem::path {
|
||||||
|
auto d = std::filesystem::temp_directory_path() /
|
||||||
|
std::format("cargoxx-pc-scan-{}", std::random_device{}());
|
||||||
|
std::filesystem::create_directories(d / "lib" / "pkgconfig");
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
void touch_pc(const std::filesystem::path& store, std::string_view name,
|
||||||
|
std::string_view content =
|
||||||
|
"Name: x\nDescription: x\nLibs: -lx\n") {
|
||||||
|
std::ofstream{store / "lib" / "pkgconfig" / std::string{name}} << content;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("pc_scan picks the exact-name .pc when present",
|
||||||
|
"[resolver][pc_scan]") {
|
||||||
|
auto store = fresh_store();
|
||||||
|
touch_pc(store, "sqlite3.pc");
|
||||||
|
|
||||||
|
auto r = pc_scan(store, "sqlite3");
|
||||||
|
REQUIRE(r.has_value());
|
||||||
|
REQUIRE(r->pc_module == "sqlite3");
|
||||||
|
REQUIRE(r->pc_file.filename() == "sqlite3.pc");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("pc_scan picks sqlite3.pc for nixpkgs name 'sqlite'",
|
||||||
|
"[resolver][pc_scan]") {
|
||||||
|
auto store = fresh_store();
|
||||||
|
touch_pc(store, "sqlite3.pc");
|
||||||
|
|
||||||
|
auto r = pc_scan(store, "sqlite");
|
||||||
|
REQUIRE(r.has_value());
|
||||||
|
REQUIRE(r->pc_module == "sqlite3");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("pc_scan picks the best match among multiple .pc files",
|
||||||
|
"[resolver][pc_scan]") {
|
||||||
|
auto store = fresh_store();
|
||||||
|
touch_pc(store, "zlib.pc");
|
||||||
|
touch_pc(store, "sqlite3.pc");
|
||||||
|
touch_pc(store, "unrelated.pc");
|
||||||
|
|
||||||
|
auto r = pc_scan(store, "zlib");
|
||||||
|
REQUIRE(r.has_value());
|
||||||
|
REQUIRE(r->pc_module == "zlib");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("pc_scan returns ResolutionUnknownPackage when nothing matches",
|
||||||
|
"[resolver][pc_scan]") {
|
||||||
|
auto store = fresh_store();
|
||||||
|
touch_pc(store, "totally-unrelated.pc");
|
||||||
|
|
||||||
|
auto r = pc_scan(store, "sqlite");
|
||||||
|
REQUIRE_FALSE(r.has_value());
|
||||||
|
REQUIRE(r.error().code == ErrorCode::ResolutionUnknownPackage);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("pc_scan errors when lib/pkgconfig is missing",
|
||||||
|
"[resolver][pc_scan]") {
|
||||||
|
auto d = std::filesystem::temp_directory_path() /
|
||||||
|
std::format("cargoxx-pc-empty-{}", std::random_device{}());
|
||||||
|
std::filesystem::create_directories(d);
|
||||||
|
|
||||||
|
auto r = pc_scan(d, "sqlite");
|
||||||
|
REQUIRE_FALSE(r.has_value());
|
||||||
|
REQUIRE(r.error().code == ErrorCode::ResolutionUnknownPackage);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("pc_scan skips .pc files that look like junk",
|
||||||
|
"[resolver][pc_scan]") {
|
||||||
|
auto store = fresh_store();
|
||||||
|
touch_pc(store, "sqlite3.pc", "this is not a real pc file\n");
|
||||||
|
|
||||||
|
auto r = pc_scan(store, "sqlite3");
|
||||||
|
REQUIRE_FALSE(r.has_value());
|
||||||
|
REQUIRE(r.error().code == ErrorCode::ResolutionUnknownPackage);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user