En-têtes repris d'un subagent interrompu, implémentation et tests écrits ensuite : CoreProcess (CreateProcessW avec héritage limité au journal, citation compatible CommandLineToArgvW, journal en ajout ; posix_spawn sous POSIX, non testé), is_process_alive, fichier d'exécution atomique, copies fantômes et nettoyage, read_last_lines, FileWatcher. 15 cas (Debug, ASan) avec un processus enfant de test.
88 lines
3.2 KiB
C++
88 lines
3.2 KiB
C++
// SPDX-License-Identifier: MPL-2.0
|
|
#include "sequa/studio/host/paths.hpp"
|
|
#include "sequa/studio/host/run_file.hpp"
|
|
|
|
#include "temp_dir.hpp"
|
|
|
|
#include <doctest/doctest.h>
|
|
|
|
#include <fstream>
|
|
|
|
using namespace sequa::studio::host;
|
|
|
|
namespace {
|
|
|
|
RunFile sample()
|
|
{
|
|
RunFile run;
|
|
run.pid = 4321;
|
|
run.port = 9100;
|
|
run.token = "0123456789abcdef0123456789abcdef";
|
|
run.instanceId = "fedcba9876543210fedcba9876543210";
|
|
run.exePath = std::filesystem::path(u8"C:/SEQUA/run/bin/sequa-core-20261106T210000Z-1.exe");
|
|
run.sourceExePath = std::filesystem::path(u8"E:/projets/déjà vu/build/sequa-core.exe");
|
|
run.startedAt = "2026-11-06T21:00:00Z";
|
|
return run;
|
|
}
|
|
|
|
void write_raw(const std::filesystem::path& path, const std::string& content)
|
|
{
|
|
std::ofstream file(path, std::ios::binary | std::ios::trunc);
|
|
file << content;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("Fichier d'execution : chemins par port")
|
|
{
|
|
const std::filesystem::path root = "racine";
|
|
CHECK(run_file_path(root, 9100) == root / "core-9100.json");
|
|
CHECK(log_file_path(root, 9101) == root / "core-9101.log");
|
|
CHECK(bin_dir(root) == root / "bin");
|
|
}
|
|
|
|
TEST_CASE("Fichier d'execution : aller-retour, chemin non ASCII, remplacement atomique")
|
|
{
|
|
testing::TempDir dir;
|
|
REQUIRE(ensure_run_dirs(dir.path()));
|
|
CHECK(std::filesystem::is_directory(bin_dir(dir.path())));
|
|
const auto path = run_file_path(dir.path(), 9100);
|
|
|
|
RunFile run = sample();
|
|
REQUIRE(write_run_file(path, run));
|
|
CHECK(read_run_file(path) == run);
|
|
|
|
run.pid = 99;
|
|
run.instanceId = "00000000000000000000000000000000";
|
|
REQUIRE(write_run_file(path, run)); // remplace l'existant
|
|
CHECK(read_run_file(path) == run);
|
|
|
|
// Aucun fichier temporaire ne traîne.
|
|
std::size_t files = 0;
|
|
for (const auto& entry : std::filesystem::directory_iterator(dir.path())) {
|
|
files += entry.is_regular_file() ? 1 : 0;
|
|
}
|
|
CHECK(files == 1);
|
|
|
|
CHECK(remove_run_file(path));
|
|
CHECK(remove_run_file(path)); // déjà absent : succès
|
|
CHECK_FALSE(read_run_file(path).has_value());
|
|
}
|
|
|
|
TEST_CASE("Fichier d'execution : contenu corrompu ou incomplet -> nullopt")
|
|
{
|
|
testing::TempDir dir;
|
|
const auto path = dir.path() / "core-9100.json";
|
|
for (const char* content : {"", "pas du json", "[1,2]", R"({"pid":1})",
|
|
R"({"pid":0,"host":"h","port":9100,"token":"t","instanceId":"i","exePath":"e","sourceExePath":"s","startedAt":"d"})",
|
|
R"({"pid":1,"host":"h","port":70000,"token":"t","instanceId":"i","exePath":"e","sourceExePath":"s","startedAt":"d"})",
|
|
R"({"pid":1,"host":"h","port":9100,"token":"","instanceId":"i","exePath":"e","sourceExePath":"s","startedAt":"d"})",
|
|
R"({"pid":-5,"host":"h","port":9100,"token":"t","instanceId":"i","exePath":"e","sourceExePath":"s","startedAt":"d"})"}) {
|
|
CAPTURE(content);
|
|
write_raw(path, content);
|
|
CHECK_FALSE(read_run_file(path).has_value());
|
|
}
|
|
write_raw(path, R"({"pid":1,"host":"h","port":9100,"token":"t","instanceId":"i","exePath":"e","sourceExePath":"s","startedAt":"d","inconnu":true})");
|
|
CHECK(read_run_file(path).has_value()); // champs inconnus ignorés
|
|
}
|