Files
cargoxx/tests/nix_cmake_scan_parse.cpp

160 lines
5.9 KiB
C++

#include <catch2/catch_test_macros.hpp>
import cargoxx.resolver;
import cargoxx.util;
import std;
using cargoxx::resolver::config_stem_to_package;
using cargoxx::resolver::nix_cmake_scan;
using cargoxx::resolver::scan_imported_targets;
using cargoxx::util::ErrorCode;
TEST_CASE("config_stem_to_package strips Config / -config / .cmake",
"[resolver][nix_cmake_scan]") {
REQUIRE(config_stem_to_package("fmtConfig.cmake") == "fmt");
REQUIRE(config_stem_to_package("fmt-config.cmake") == "fmt");
REQUIRE(config_stem_to_package("Catch2Config.cmake") == "Catch2");
REQUIRE(config_stem_to_package("range-v3-config.cmake") == "range-v3");
REQUIRE(config_stem_to_package("/abs/path/sub/spdlogConfig.cmake") == "spdlog");
}
TEST_CASE("scan_imported_targets picks up IMPORTED libraries",
"[resolver][nix_cmake_scan]") {
constexpr std::string_view text = R"(
# noise here
add_library(fmt::fmt SHARED IMPORTED)
set_target_properties(fmt::fmt PROPERTIES IMPORTED_LOCATION "/x")
)";
auto out = scan_imported_targets(text);
REQUIRE(out == std::vector<std::string>{"fmt::fmt"});
}
TEST_CASE("scan_imported_targets picks up ALIAS targets",
"[resolver][nix_cmake_scan]") {
constexpr std::string_view text = R"(
add_library(absl_strings_internal STATIC IMPORTED)
add_library(absl::strings ALIAS absl_strings_internal)
)";
auto out = scan_imported_targets(text);
// Both the imported impl and the alias should be reported, deduped,
// in declaration order.
REQUIRE(out.size() == 2);
REQUIRE(out[0] == "absl_strings_internal");
REQUIRE(out[1] == "absl::strings");
}
TEST_CASE("scan_imported_targets ignores plain (non-IMPORTED) add_library",
"[resolver][nix_cmake_scan]") {
constexpr std::string_view text = R"(
add_library(local STATIC src.cpp)
add_library(other_lib SHARED other.cpp)
)";
auto out = scan_imported_targets(text);
REQUIRE(out.empty());
}
TEST_CASE("scan_imported_targets does not match _add_library or similar",
"[resolver][nix_cmake_scan]") {
constexpr std::string_view text = R"(
# function definition uses the marker as a substring
function(_add_library_helper)
endfunction()
)";
auto out = scan_imported_targets(text);
REQUIRE(out.empty());
}
TEST_CASE("scan_imported_targets dedupes duplicate target names",
"[resolver][nix_cmake_scan]") {
constexpr std::string_view text = R"(
add_library(fmt::fmt SHARED IMPORTED)
# benign duplicate (e.g. config-version reuse)
add_library(fmt::fmt SHARED IMPORTED)
)";
auto out = scan_imported_targets(text);
REQUIRE(out == std::vector<std::string>{"fmt::fmt"});
}
namespace {
auto fresh_store() -> std::filesystem::path {
auto d = std::filesystem::temp_directory_path() /
std::format("cargoxx-cmake-scan-{}", std::random_device{}());
std::filesystem::create_directories(d / "lib" / "cmake");
return d;
}
void touch_config(const std::filesystem::path& store, std::string_view rel,
std::string_view content) {
auto p = store / "lib" / "cmake" / rel;
std::filesystem::create_directories(p.parent_path());
std::ofstream{p} << content;
}
} // namespace
TEST_CASE("nix_cmake_scan finds the canonical config", "[resolver][nix_cmake_scan]") {
auto store = fresh_store();
touch_config(store, "fmt/fmtConfig.cmake",
R"(add_library(fmt::fmt SHARED IMPORTED))");
auto r = nix_cmake_scan(store, "fmt");
REQUIRE(r.has_value());
REQUIRE(r->find_package == "fmt CONFIG REQUIRED");
REQUIRE(r->targets == std::vector<std::string>{"fmt::fmt"});
REQUIRE(r->config_file.filename() == "fmtConfig.cmake");
}
TEST_CASE("nix_cmake_scan prefers exact case-insensitive match",
"[resolver][nix_cmake_scan]") {
auto store = fresh_store();
touch_config(store, "absl/abslConfig.cmake",
R"(add_library(absl::strings SHARED IMPORTED))");
touch_config(store, "absl-extras/abslExtrasConfig.cmake",
R"(add_library(absl::extras SHARED IMPORTED))");
auto r = nix_cmake_scan(store, "absl");
REQUIRE(r.has_value());
REQUIRE(r->find_package == "absl CONFIG REQUIRED");
}
TEST_CASE("nix_cmake_scan returns ResolutionUnknownPackage when nothing found",
"[resolver][nix_cmake_scan]") {
auto store = fresh_store();
auto r = nix_cmake_scan(store, "nothing");
REQUIRE_FALSE(r.has_value());
REQUIRE(r.error().code == ErrorCode::ResolutionUnknownPackage);
}
TEST_CASE("nix_cmake_scan ignores Config files with no IMPORTED targets",
"[resolver][nix_cmake_scan]") {
auto store = fresh_store();
touch_config(store, "junk/junkConfig.cmake",
R"(message(STATUS "no targets here"))");
auto r = nix_cmake_scan(store, "junk");
REQUIRE_FALSE(r.has_value());
REQUIRE(r.error().code == ErrorCode::ResolutionUnknownPackage);
}
TEST_CASE("nix_cmake_scan scans sibling *-targets.cmake files",
"[resolver][nix_cmake_scan]") {
// Mirrors fmt's real on-disk layout: the *-config.cmake is empty of
// IMPORTED targets but include()s a sibling *-targets.cmake that
// carries them.
auto store = fresh_store();
touch_config(store, "fmt/fmt-config.cmake",
R"(include("${CMAKE_CURRENT_LIST_DIR}/fmt-targets.cmake"))");
touch_config(store, "fmt/fmt-targets.cmake",
R"(add_library(fmt::fmt SHARED IMPORTED)
add_library(fmt::fmt-header-only INTERFACE IMPORTED))");
auto r = nix_cmake_scan(store, "fmt");
REQUIRE(r.has_value());
REQUIRE(r->find_package == "fmt CONFIG REQUIRED");
REQUIRE(r->targets.size() == 2);
REQUIRE(std::ranges::find(r->targets, std::string{"fmt::fmt"}) !=
r->targets.end());
REQUIRE(std::ranges::find(r->targets, std::string{"fmt::fmt-header-only"}) !=
r->targets.end());
}