// SPDX-License-Identifier: MPL-2.0 #include "sequa/platform/process.hpp" #include "temp_dir.hpp" #include #include #include #include #include #include using namespace sequa::platform; using namespace std::chrono_literals; namespace { const std::filesystem::path child = SEQUA_TEST_CHILD; // Les journaux de test restent petits : lecture entière, lignes séparées // (équivalent local à read_last_lines du supervisor, qui reste du côté Studio). std::vector read_lines(const std::filesystem::path& path) { std::ifstream file(path, std::ios::binary); std::vector lines; for (std::string line; std::getline(file, line);) { if (!line.empty() && line.back() == '\r') { line.pop_back(); } lines.push_back(std::move(line)); } return lines; } bool contains(const std::vector& lines, const std::string& expected) { return std::find(lines.begin(), lines.end(), expected) != lines.end(); } } // namespace TEST_CASE("Processus : code de sortie et journal en ajout (stdout et stderr)") { testing::TempDir dir; const auto log = dir.path() / "journal-9100.log"; LaunchOptions options; options.log_path = log; auto first = ChildProcess::launch(child, {"--out", "premier", "--err", "erreur", "--exit", "7"}, options); REQUIRE(first.has_value()); CHECK(first->pid() != 0); CHECK(first->wait_for(5s) == 7); CHECK_FALSE(first->running()); auto second = ChildProcess::launch(child, {"--out", "second"}, options); REQUIRE(second.has_value()); CHECK(second->wait_for(5s) == 0); const auto lines = read_lines(log); CHECK(contains(lines, "premier")); CHECK(contains(lines, "erreur")); CHECK(contains(lines, "second")); // le premier lancement n'a pas été écrasé const auto headers = std::count_if(lines.begin(), lines.end(), [](const std::string& line) { return line.starts_with("===== "); }); CHECK(headers == 2); } TEST_CASE("Processus : arguments avec espaces, guillemets et antislashs") { testing::TempDir dir; LaunchOptions options; options.log_path = dir.path() / "echo.log"; const std::vector tricky = {"simple", "avec espace", "guillemet \" milieu", "fin antislash\\", "C:\\Program Files\\", "\\\\serveur\\\"partage", "", "accentué é"}; std::vector args = {"--echo-rest"}; args.insert(args.end(), tricky.begin(), tricky.end()); auto process = ChildProcess::launch(child, args, options); REQUIRE(process.has_value()); REQUIRE(process->wait_for(5s) == 0); const auto lines = read_lines(options.log_path); for (const std::string& argument : tricky) { CAPTURE(argument); CHECK(contains(lines, "[" + argument + "]")); } } TEST_CASE("Processus : wait_for avec delai, terminate, is_process_alive") { auto process = ChildProcess::launch(child, {"--sleep", "10000"}); REQUIRE(process.has_value()); CHECK(process->running()); CHECK(is_process_alive(process->pid())); CHECK_FALSE(process->wait_for(100ms).has_value()); process->terminate(); CHECK_FALSE(process->running()); CHECK(process->wait_for(0ms).has_value()); CHECK_FALSE(is_process_alive(process->pid())); CHECK_FALSE(is_process_alive(0)); } TEST_CASE("Processus : executable absent -> nullopt ; deplacement") { CHECK_FALSE(ChildProcess::launch("introuvable-sequa.exe", {}).has_value()); auto process = ChildProcess::launch(child, {"--exit", "3"}); REQUIRE(process.has_value()); ChildProcess moved = std::move(*process); CHECK(moved.wait_for(5s) == 3); } TEST_CASE("Processus : le destructeur ne tue pas le processus") { std::uint32_t pid = 0; { auto process = ChildProcess::launch(child, {"--sleep", "1500"}); REQUIRE(process.has_value()); pid = process->pid(); } CHECK(is_process_alive(pid)); } TEST_CASE("Processus : kill_with_parent (job object sous Windows, fork + PR_SET_PDEATHSIG sous Linux)") { // Dans le même processus de test, on ne peut pas observer la mort du parent // (c'est ce processus qui porte le test). On vérifie donc ici que le // lancement avec l'option réussit, que l'enfant tourne et reste supervisable // normalement, et que terminate() rend la sortie comme sans l'option ; la // mort du parent (job object refermé par la disparition de ses handles, // SIGKILL de PR_SET_PDEATHSIG) est le mécanisme système lui-même, à // confirmer en dehors du test unitaire (ADR 0009). LaunchOptions options; options.kill_with_parent = true; auto process = ChildProcess::launch(child, {"--sleep", "10000"}, options); REQUIRE(process.has_value()); CHECK(process->running()); process->terminate(); CHECK(process->wait_for(2s).has_value()); CHECK_FALSE(process->running()); }