From a228325bd55abe68a2a7ed31ee0ddc9ea1422f3d Mon Sep 17 00:00:00 2001 From: SirLynix Date: Tue, 23 Jan 2024 15:11:02 +0100 Subject: [PATCH] Add process unit tests --- include/Nazara/Core.hpp | 1 + src/Nazara/Core/Format.cpp | 1 + tests/UnitTests/Engine/Core/ProcessTests.cpp | 119 +++++++++++++++++++ tests/UnitTests/subprocess1.cpp | 39 ++++++ tests/UnitTests/subprocess2.cpp | 78 ++++++++++++ tests/UnitTests/xmake.lua | 39 ++++-- xmake/actions/checkfiles.lua | 1 + 7 files changed, 265 insertions(+), 13 deletions(-) create mode 100644 tests/UnitTests/Engine/Core/ProcessTests.cpp create mode 100644 tests/UnitTests/subprocess1.cpp create mode 100644 tests/UnitTests/subprocess2.cpp diff --git a/include/Nazara/Core.hpp b/include/Nazara/Core.hpp index 799609447..9f0836a09 100644 --- a/include/Nazara/Core.hpp +++ b/include/Nazara/Core.hpp @@ -78,6 +78,7 @@ #include #include #include +#include #include #include #include diff --git a/src/Nazara/Core/Format.cpp b/src/Nazara/Core/Format.cpp index 696af607d..462dca7fd 100644 --- a/src/Nazara/Core/Format.cpp +++ b/src/Nazara/Core/Format.cpp @@ -33,6 +33,7 @@ namespace Nz NAZARA_FORMAT_IMPLEM(std::filesystem::path); NAZARA_FORMAT_IMPLEM(std::string); NAZARA_FORMAT_IMPLEM(std::string_view); + NAZARA_FORMAT_IMPLEM(char*); NAZARA_FORMAT_IMPLEM(const char*); NAZARA_FORMAT_IMPLEM(short); NAZARA_FORMAT_IMPLEM(int); diff --git a/tests/UnitTests/Engine/Core/ProcessTests.cpp b/tests/UnitTests/Engine/Core/ProcessTests.cpp new file mode 100644 index 000000000..ffb38252d --- /dev/null +++ b/tests/UnitTests/Engine/Core/ProcessTests.cpp @@ -0,0 +1,119 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +SCENARIO("Process", "[CORE][PROCESS]") +{ + WHEN("Checking if the current process exists") + { + if (Nz::Result result = Nz::Process::Exists(Nz::Process::GetCurrentPid())) + CHECK(result.GetValue()); + else + { + INFO(result.GetError()); + CHECK(false); + } + } + + WHEN("Creating a detached process") + { + std::array args = { "simple_arg", "space \\arg", "okay\n\\\\\"_wt\"f\\" }; + + std::filesystem::path path = Nz::Utf8Path("ProcessTests"); + if (std::filesystem::exists(path)) + REQUIRE(std::filesystem::remove_all(path)); + + REQUIRE(std::filesystem::create_directory(path)); + + Nz::Result spawnResult = Nz::Process::SpawnDetached(Nz::Utf8Path("UnitTests_sub1"), args, path); + if (!spawnResult) + { + INFO(spawnResult.GetError()); + REQUIRE(false); + } + + std::this_thread::sleep_for(std::chrono::seconds(1)); + + // Some files were written in the ProcessTests directory + + auto ReadFile = [&](std::string_view filename, std::string_view failureName = {}) + { + std::filesystem::path filePath = path / Nz::Utf8Path(filename); + + auto fileContentOpt = Nz::File::ReadWhole(filePath); + if (!fileContentOpt) + { + if (!failureName.empty()) + { + fileContentOpt = Nz::File::ReadWhole(path / Nz::Utf8Path(failureName)); + if (fileContentOpt) + { + std::string_view error(reinterpret_cast(fileContentOpt->data()), fileContentOpt->size()); + + INFO(failureName << ": " << error); + REQUIRE(false); + } + else + { + INFO(filename << " nor " << failureName << " exist"); + REQUIRE(false); + } + } + else + { + INFO(filename << " doesn't exist"); + REQUIRE(false); + } + } + + return std::string(reinterpret_cast(fileContentOpt->data()), fileContentOpt->size()); + }; + + auto CheckStep = [&](std::string_view filename, std::string_view expected, std::string_view failureName = {}) + { + std::string content = ReadFile(filename, failureName); + INFO("checking " << filename << " content"); + REQUIRE(content == expected); + }; + + CheckStep("step1_success.txt", "1", "step1_failure.txt"); + + // Parameter passing check + for (std::size_t i = 0; i < args.size(); ++i) + CheckStep(Nz::Format("step2_param{}.txt", i + 1), args[i]); + CHECK(!std::filesystem::exists(path / Nz::Utf8Path(Nz::Format("step2_param{}.txt", args.size() + 1)))); + + std::string pidStr = ReadFile("step3_pid.txt", "step3_failure.txt"); + Nz::Pid pid; + if (auto pidParse = std::from_chars(pidStr.data(), pidStr.data() + pidStr.size(), pid); pidParse.ec == std::errc()) + { + Nz::Result result = Nz::Process::Exists(pid); + if (result) + { + INFO("checking that grand-child process still exists"); + CHECK(result.GetValue()); + } + else + { + INFO("failed to retrieve grand-child status: " << result.GetError()); + CHECK(false); + } + } + else + { + INFO("failed to parse pid from step3_pid.txt: " << pidStr); + CHECK(false); + } + + CheckStep("step4_success.txt", "1", "step4_failure.txt"); + CheckStep("step5_success.txt", "1", "step5_failure.txt"); + CheckStep("step6_success.txt", "1", "step6_failure.txt"); + } +} diff --git a/tests/UnitTests/subprocess1.cpp b/tests/UnitTests/subprocess1.cpp new file mode 100644 index 000000000..1ed619b93 --- /dev/null +++ b/tests/UnitTests/subprocess1.cpp @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char* argv[]) +{ + { + Nz::File file(Nz::Utf8Path("step1_success.txt"), Nz::OpenMode::Write); + file.Write("1"); + } + + for (int i = 1; i < argc; ++i) + { + Nz::File file(Nz::Utf8Path(Nz::Format("step2_param{}.txt", i)), Nz::OpenMode::Write); + file.Write(argv[i]); + } + + std::vector parameters; + parameters.push_back(std::to_string(Nz::Process::GetCurrentPid())); + + Nz::Result result = Nz::Process::SpawnDetached("../UnitTests_sub2", parameters); + if (result) + { + Nz::File file(Nz::Utf8Path("step3_pid.txt"), Nz::OpenMode::Write); + file.Write(std::to_string(result.GetValue())); + } + else + { + Nz::File file(Nz::Utf8Path("step3_failure.txt"), Nz::OpenMode::Write); + file.Write(result.GetError()); + } + + // Wait for our child process to start and check for our existence + std::this_thread::sleep_for(std::chrono::milliseconds(200)); +} diff --git a/tests/UnitTests/subprocess2.cpp b/tests/UnitTests/subprocess2.cpp new file mode 100644 index 000000000..808580346 --- /dev/null +++ b/tests/UnitTests/subprocess2.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char* argv[]) +{ + if (argc != 2) + { + Nz::File errFile(Nz::Utf8Path("step4_failure.txt"), Nz::OpenMode::Write); + errFile.Write(Nz::Format("unexpected argc: {}", argc)); + + return EXIT_FAILURE; + } + + Nz::Pid pid; + if (auto pidParse = std::from_chars(argv[1], argv[1] + std::strlen(argv[1]), pid); pidParse.ec != std::errc()) + { + Nz::File errFile(Nz::Utf8Path("step4_failure.txt"), Nz::OpenMode::Write); + errFile.Write(Nz::Format("invalid pid: {}", argv[1])); + + return EXIT_FAILURE; + } + + Nz::File successFile4(Nz::Utf8Path("step4_success.txt"), Nz::OpenMode::Write); + successFile4.Write("1"); + + Nz::Result result = Nz::Process::Exists(pid); + if (!result) + { + Nz::File errFile(Nz::Utf8Path("step5_failure.txt"), Nz::OpenMode::Write); + errFile.Write(Nz::Format("failed to retrieve parent process status: {}", result.GetError())); + + return EXIT_FAILURE; + } + + if (!result.GetValue()) + { + Nz::File file(Nz::Utf8Path("step5_failure.txt"), Nz::OpenMode::Write); + file.Write("parent process is already dead"); + + return EXIT_FAILURE; + } + + Nz::File successFile5(Nz::Utf8Path("step5_success.txt"), Nz::OpenMode::Write); + successFile5.Write("1"); + + // Wait until parent dies + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + result = Nz::Process::Exists(pid); + if (!result) + { + Nz::File errFile(Nz::Utf8Path("step6_failure.txt"), Nz::OpenMode::Write); + errFile.Write(Nz::Format("failed to retrieve parent process status after waiting: {}", result.GetError())); + + return EXIT_FAILURE; + } + + if (result.GetValue()) + { + Nz::File errFile(Nz::Utf8Path("step6_failure.txt"), Nz::OpenMode::Write); + errFile.Write("parent process is still alive"); + + return EXIT_FAILURE; + } + + Nz::File successFile6(Nz::Utf8Path("step6_success.txt"), Nz::OpenMode::Write); + successFile6.Write("1"); + + // Wait for the unit tests process to check our existence + std::this_thread::sleep_for(std::chrono::milliseconds(3000)); +} diff --git a/tests/UnitTests/xmake.lua b/tests/UnitTests/xmake.lua index 8f66b93f2..ca0792ad4 100644 --- a/tests/UnitTests/xmake.lua +++ b/tests/UnitTests/xmake.lua @@ -1,22 +1,35 @@ add_requires("catch2 >=3.x") -if has_config("asan") then - add_defines("CATCH_CONFIG_NO_WINDOWS_SEH") - add_defines("CATCH_CONFIG_NO_POSIX_SIGNALS") -end +target("UnitTests_sub1", function () + add_deps("NazaraCore") -add_deps("NazaraAudio", "NazaraCore", "NazaraNetwork", "NazaraChipmunkPhysics2D") -add_packages("catch2", "entt", "frozen") -add_headerfiles("Engine/**.hpp", { prefixdir = "private", install = false }) -add_files("resources.cpp") -add_files("Engine/**.cpp") -add_includedirs(".") + add_files("subprocess1.cpp") +end) -if has_config("unitybuild") then - add_rules("c++.unity_build") -end +target("UnitTests_sub2", function () + add_deps("NazaraCore") + + add_files("subprocess2.cpp") +end) target("UnitTests", function () + if has_config("asan") then + add_defines("CATCH_CONFIG_NO_WINDOWS_SEH") + add_defines("CATCH_CONFIG_NO_POSIX_SIGNALS") + end + + add_deps("NazaraAudio", "NazaraCore", "NazaraNetwork", "NazaraChipmunkPhysics2D") + add_deps("UnitTests_sub1", "UnitTests_sub2", { links = {} }) + add_packages("catch2", "entt", "frozen") + add_headerfiles("Engine/**.hpp", { prefixdir = "private", install = false }) + add_files("resources.cpp") + add_files("Engine/**.cpp") + add_includedirs(".") + + if has_config("unitybuild") then + add_rules("c++.unity_build") + end + add_files("main.cpp", {unity_ignored = true}) if has_config("usepch") then diff --git a/xmake/actions/checkfiles.lua b/xmake/actions/checkfiles.lua index 1e23ac6cd..485fff66d 100644 --- a/xmake/actions/checkfiles.lua +++ b/xmake/actions/checkfiles.lua @@ -43,6 +43,7 @@ local systemHeaders = { ["poll.h"] = true, ["process.h"] = true, ["pthread.h"] = true, + ["spawn.h"] = true, ["unistd.h"] = true, ["windows.h"] = true, ["winsock2"] = true,