// SPDX-License-Identifier: MPL-2.0 #include "sequa/platform/layout.hpp" #include #include #include #include #include #include namespace sequa::platform::testing { // Dossier temporaire propre à un test, supprimé à la fin. class TempDir { public: TempDir() : path_(std::filesystem::temp_directory_path() / ("sequa-layout-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + "-" + std::to_string(++counter_))) { std::filesystem::create_directories(path_); } ~TempDir() { std::error_code ignored; std::filesystem::remove_all(path_, ignored); } TempDir(const TempDir&) = delete; TempDir& operator=(const TempDir&) = delete; [[nodiscard]] const std::filesystem::path& path() const noexcept { return path_; } private: inline static int counter_ = 0; std::filesystem::path path_; }; // Écrit un fichier repère vide. inline void touch(const std::filesystem::path& file) { std::ofstream out(file); REQUIRE(out.good()); } // Fonction dont l'adresse sert à module_path. inline void function_in_test_executable() {} } // namespace sequa::platform::testing TEST_SUITE("sequa::platform::layout") { TEST_CASE("find_root finds marker in start directory itself") { using namespace sequa::platform; using namespace sequa::platform::testing; TempDir tmp; const auto root = tmp.path(); std::error_code ec; std::filesystem::create_directories(root / "core", ec); REQUIRE(!ec); std::filesystem::create_directories(root / "render", ec); REQUIRE(!ec); // Créer le fichier repère const auto marker = root / root_marker; std::filesystem::create_directories(marker.parent_path(), ec); REQUIRE(!ec); touch(marker); const auto found = find_root(root); REQUIRE(found.has_value()); CHECK(std::filesystem::equivalent(found.value(), root)); } TEST_CASE("find_root finds marker two levels up from nested directory") { using namespace sequa::platform; using namespace sequa::platform::testing; TempDir tmp; const auto root = tmp.path(); const auto nested = root / "a" / "b" / "c"; std::error_code ec; std::filesystem::create_directories(nested, ec); REQUIRE(!ec); // Créer le fichier repère à la racine const auto marker = root / root_marker; touch(marker); const auto found = find_root(nested); REQUIRE(found.has_value()); CHECK(std::filesystem::equivalent(found.value(), root)); } TEST_CASE("find_root returns nullopt when marker absent") { using namespace sequa::platform; using namespace sequa::platform::testing; TempDir tmp; const auto root = tmp.path(); const auto nested = root / "a" / "b"; std::error_code ec; std::filesystem::create_directories(nested, ec); REQUIRE(!ec); const auto found = find_root(nested); CHECK_FALSE(found.has_value()); } TEST_CASE("Layout paths match expected structure") { using namespace sequa::platform; using namespace sequa::platform::testing; TempDir tmp; const auto root = tmp.path(); Layout layout(root); CHECK(layout.root() == root); CHECK(layout.core_executable() == root / "core" / executable_name("sequa-core")); CHECK(layout.render_executable() == root / "render" / executable_name("sequa-render")); CHECK(layout.studio_executable() == root / "studio" / executable_name("sequa-studio")); CHECK(layout.ui_dir() == root / "studio" / "ui"); CHECK(layout.engine_dir("godot") == root / "engines" / "godot"); } TEST_CASE("adapter_library returns correct filename and rejects invalid names") { using namespace sequa::platform; using namespace sequa::platform::testing; TempDir tmp; const auto root = tmp.path(); Layout layout(root); // Nom valide const auto godot_adapter = layout.adapter_library("godot"); REQUIRE(godot_adapter.has_value()); CHECK(godot_adapter.value() == root / "engines" / "godot" / module_name("sequa_godot_adapter")); // Nom invalide : contient des caractères non autorisés CHECK_FALSE(layout.adapter_library("../x").has_value()); CHECK_FALSE(layout.adapter_library("").has_value()); CHECK_FALSE(layout.adapter_library("Godot").has_value()); // majuscule CHECK_FALSE(layout.adapter_library("godot-1").has_value()); // tiret CHECK_FALSE(layout.adapter_library("godot ").has_value()); // espace } TEST_CASE("executable_name and module_name per platform") { using namespace sequa::platform; #if defined(_WIN32) CHECK(executable_name("sequa-core") == "sequa-core.exe"); CHECK(module_name("sequa_godot_adapter") == "sequa_godot_adapter.dll"); #else CHECK(executable_name("sequa-core") == "sequa-core"); CHECK(module_name("sequa_godot_adapter") == "sequa_godot_adapter.so"); #endif } TEST_CASE("current_executable returns non-empty existing path") { using namespace sequa::platform; const auto exe = current_executable(); CHECK_FALSE(exe.empty()); std::error_code ec; CHECK(std::filesystem::exists(exe, ec)); CHECK(!ec); } TEST_CASE("module_path of a function in this test equals current_executable") { using namespace sequa::platform; const auto exe = current_executable(); REQUIRE_FALSE(exe.empty()); // Adresse d'une fonction locale pour tester module_path const auto mod = module_path(reinterpret_cast(&testing::function_in_test_executable)); REQUIRE_FALSE(mod.empty()); std::error_code ec; CHECK(std::filesystem::equivalent(exe, mod, ec)); CHECK(!ec); } TEST_CASE("is_valid_engine_name accepts valid names and rejects invalid ones") { using namespace sequa::platform; CHECK(is_valid_engine_name("godot")); CHECK(is_valid_engine_name("debug")); CHECK(is_valid_engine_name("engine_123")); CHECK(is_valid_engine_name("a")); CHECK_FALSE(is_valid_engine_name("")); CHECK_FALSE(is_valid_engine_name("Godot")); CHECK_FALSE(is_valid_engine_name("godot-1")); CHECK_FALSE(is_valid_engine_name("godot ")); CHECK_FALSE(is_valid_engine_name("godot.engine")); CHECK_FALSE(is_valid_engine_name("../godot")); CHECK_FALSE(is_valid_engine_name("godot/evil")); } TEST_CASE("path_from_utf8 and path_to_utf8 keep non-ASCII characters") { using namespace sequa::platform; const std::string utf8 = "C:/Users/Am\xC3\xA9lie/SEQUA"; // « é » en UTF-8 const auto path = path_from_utf8(utf8); CHECK(path.filename() == path_from_utf8("SEQUA")); CHECK(path_to_utf8(path) == utf8); CHECK(path.u8string() == u8"C:/Users/Amélie/SEQUA"); } TEST_CASE("utf8_arguments drops the program name") { using namespace sequa::platform; char program[] = "sequa-layout-tests"; char* argv[] = {program, nullptr}; const auto args = utf8_arguments(1, argv); #if !defined(_WIN32) CHECK(args.empty()); #else // Sous Windows, la vraie ligne de commande du test (arguments de doctest éventuels). CHECK(args.size() < 64); #endif } } // TEST_SUITE