diff options
Diffstat (limited to 'src/zenserver-test/process-tests.cpp')
| -rw-r--r-- | src/zenserver-test/process-tests.cpp | 298 |
1 files changed, 298 insertions, 0 deletions
diff --git a/src/zenserver-test/process-tests.cpp b/src/zenserver-test/process-tests.cpp new file mode 100644 index 000000000..649f24f54 --- /dev/null +++ b/src/zenserver-test/process-tests.cpp @@ -0,0 +1,298 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include <zencore/zencore.h> + +#if ZEN_WITH_TESTS + +# include "zenserver-test.h" + +# include <zencore/filesystem.h> +# include <zencore/process.h> +# include <zencore/testing.h> + +# if ZEN_PLATFORM_WINDOWS +# include <zencore/windows.h> +# else +# include <unistd.h> +# endif + +namespace zen::tests { + +using namespace std::literals; + +static std::filesystem::path +GetAppStubPath() +{ + return TestEnv.ProgramBaseDir() / ("zentest-appstub" ZEN_EXE_SUFFIX_LITERAL); +} + +// Read all available data from the read end of a StdoutPipeHandles. +// Must be called after CloseWriteEnd() so that the read will see EOF. +static std::string +ReadAllFromPipe(StdoutPipeHandles& Pipe) +{ + std::string Result; + char Buffer[4096]; + +# if ZEN_PLATFORM_WINDOWS + DWORD BytesRead = 0; + while (::ReadFile(Pipe.ReadHandle, Buffer, sizeof(Buffer), &BytesRead, nullptr) && BytesRead > 0) + { + Result.append(Buffer, BytesRead); + } +# else + ssize_t BytesRead = 0; + while ((BytesRead = read(Pipe.ReadFd, Buffer, sizeof(Buffer))) > 0) + { + Result.append(Buffer, static_cast<size_t>(BytesRead)); + } +# endif + + return Result; +} + +TEST_SUITE_BEGIN("server.process"); + +////////////////////////////////////////////////////////////////////////// + +TEST_CASE("pipe.capture_stdout") +{ + StdoutPipeHandles Pipe; + REQUIRE(CreateStdoutPipe(Pipe)); + + const std::string ExpectedOutput = "hello_from_pipe_test"; + std::filesystem::path AppStub = GetAppStubPath(); + std::string CommandLine = fmt::format("zentest-appstub -echo={}", ExpectedOutput); + + CreateProcOptions Options; + Options.StdoutPipe = &Pipe; + + CreateProcResult ProcResult = CreateProc(AppStub, CommandLine, Options); + + ProcessHandle Process; + Process.Initialize(ProcResult); + + // Close the write end, then drain before Wait() to avoid deadlock if output fills the pipe buffer. + Pipe.CloseWriteEnd(); + + std::string Output = ReadAllFromPipe(Pipe); + + Process.Wait(); + + // The appstub also prints "[zentest] exiting with exit code: 0\n" + CHECK(Output.find(ExpectedOutput) != std::string::npos); + CHECK_EQ(Process.GetExitCode(), 0); +} + +TEST_CASE("pipe.capture_multiline") +{ + StdoutPipeHandles Pipe; + REQUIRE(CreateStdoutPipe(Pipe)); + + std::filesystem::path AppStub = GetAppStubPath(); + std::string CommandLine = "zentest-appstub -echo=line1 -echo=line2 -echo=line3"; + + CreateProcOptions Options; + Options.StdoutPipe = &Pipe; + + CreateProcResult ProcResult = CreateProc(AppStub, CommandLine, Options); + + ProcessHandle Process; + Process.Initialize(ProcResult); + + Pipe.CloseWriteEnd(); + + std::string Output = ReadAllFromPipe(Pipe); + + Process.Wait(); + + CHECK(Output.find("line1") != std::string::npos); + CHECK(Output.find("line2") != std::string::npos); + CHECK(Output.find("line3") != std::string::npos); + CHECK_EQ(Process.GetExitCode(), 0); +} + +TEST_CASE("pipe.raii_cleanup") +{ + // Verify that StdoutPipeHandles cleans up handles when it goes out of scope + // (no leaked handles). We can't directly assert on handle counts, but we can + // verify that creating and destroying many pipes doesn't fail. + for (int i = 0; i < 100; ++i) + { + StdoutPipeHandles Pipe; + REQUIRE(CreateStdoutPipe(Pipe)); + // Pipe goes out of scope here — destructor should close both ends + } +} + +TEST_CASE("pipe.move_semantics") +{ + StdoutPipeHandles Original; + REQUIRE(CreateStdoutPipe(Original)); + + // Move-construct a new pipe from Original + StdoutPipeHandles Moved(std::move(Original)); + +# if ZEN_PLATFORM_WINDOWS + CHECK(Moved.ReadHandle != nullptr); + CHECK(Moved.WriteHandle != nullptr); + CHECK(Original.ReadHandle == nullptr); + CHECK(Original.WriteHandle == nullptr); +# else + CHECK(Moved.ReadFd >= 0); + CHECK(Moved.WriteFd >= 0); + CHECK(Original.ReadFd == -1); + CHECK(Original.WriteFd == -1); +# endif + + // Move-assign + StdoutPipeHandles Assigned; + Assigned = std::move(Moved); + +# if ZEN_PLATFORM_WINDOWS + CHECK(Assigned.ReadHandle != nullptr); + CHECK(Assigned.WriteHandle != nullptr); + CHECK(Moved.ReadHandle == nullptr); + CHECK(Moved.WriteHandle == nullptr); +# else + CHECK(Assigned.ReadFd >= 0); + CHECK(Assigned.WriteFd >= 0); + CHECK(Moved.ReadFd == -1); + CHECK(Moved.WriteFd == -1); +# endif + + // Assigned goes out of scope — destructor closes handles +} + +TEST_CASE("pipe.close_is_idempotent") +{ + StdoutPipeHandles Pipe; + REQUIRE(CreateStdoutPipe(Pipe)); + + Pipe.Close(); + // Calling Close again should be safe (no double-close) + Pipe.Close(); + +# if ZEN_PLATFORM_WINDOWS + CHECK(Pipe.ReadHandle == nullptr); + CHECK(Pipe.WriteHandle == nullptr); +# else + CHECK(Pipe.ReadFd == -1); + CHECK(Pipe.WriteFd == -1); +# endif +} + +TEST_CASE("pipe.close_write_end_only") +{ + StdoutPipeHandles Pipe; + REQUIRE(CreateStdoutPipe(Pipe)); + + Pipe.CloseWriteEnd(); + +# if ZEN_PLATFORM_WINDOWS + CHECK(Pipe.ReadHandle != nullptr); + CHECK(Pipe.WriteHandle == nullptr); +# else + CHECK(Pipe.ReadFd >= 0); + CHECK(Pipe.WriteFd == -1); +# endif + + // Remaining read handle cleaned up by destructor +} + +TEST_CASE("pipe.capture_with_nonzero_exit") +{ + StdoutPipeHandles Pipe; + REQUIRE(CreateStdoutPipe(Pipe)); + + std::filesystem::path AppStub = GetAppStubPath(); + std::string CommandLine = "zentest-appstub -echo=before_exit -f=42"; + + CreateProcOptions Options; + Options.StdoutPipe = &Pipe; + + CreateProcResult ProcResult = CreateProc(AppStub, CommandLine, Options); + + ProcessHandle Process; + Process.Initialize(ProcResult); + + Pipe.CloseWriteEnd(); + + std::string Output = ReadAllFromPipe(Pipe); + + Process.Wait(); + + CHECK(Output.find("before_exit") != std::string::npos); + CHECK_EQ(Process.GetExitCode(), 42); +} + +TEST_CASE("pipe.stderr_on_shared_pipe") +{ + StdoutPipeHandles Pipe; + REQUIRE(CreateStdoutPipe(Pipe)); + + std::filesystem::path AppStub = GetAppStubPath(); + std::string CommandLine = "zentest-appstub -echo=from_stdout -echoerr=from_stderr"; + + CreateProcOptions Options; + Options.StdoutPipe = &Pipe; + + CreateProcResult ProcResult = CreateProc(AppStub, CommandLine, Options); + + ProcessHandle Process; + Process.Initialize(ProcResult); + + Pipe.CloseWriteEnd(); + + std::string Output = ReadAllFromPipe(Pipe); + + Process.Wait(); + + // Both stdout and stderr content should appear on the shared pipe + CHECK(Output.find("from_stdout") != std::string::npos); + CHECK(Output.find("from_stderr") != std::string::npos); + CHECK_EQ(Process.GetExitCode(), 0); +} + +TEST_CASE("pipe.separate_stderr") +{ + StdoutPipeHandles StdoutPipe; + StdoutPipeHandles StderrPipe; + REQUIRE(CreateStdoutPipe(StdoutPipe)); + REQUIRE(CreateStdoutPipe(StderrPipe)); + + std::filesystem::path AppStub = GetAppStubPath(); + std::string CommandLine = "zentest-appstub -echo=on_stdout -echoerr=on_stderr"; + + CreateProcOptions Options; + Options.StdoutPipe = &StdoutPipe; + Options.StderrPipe = &StderrPipe; + + CreateProcResult ProcResult = CreateProc(AppStub, CommandLine, Options); + + ProcessHandle Process; + Process.Initialize(ProcResult); + + StdoutPipe.CloseWriteEnd(); + StderrPipe.CloseWriteEnd(); + + std::string StdoutOutput = ReadAllFromPipe(StdoutPipe); + std::string StderrOutput = ReadAllFromPipe(StderrPipe); + + Process.Wait(); + + CHECK(StdoutOutput.find("on_stdout") != std::string::npos); + CHECK(StderrOutput.find("on_stderr") != std::string::npos); + // Verify separation: stderr content should NOT appear in stdout pipe + CHECK(StdoutOutput.find("on_stderr") == std::string::npos); + CHECK(StderrOutput.find("on_stdout") == std::string::npos); + CHECK_EQ(Process.GetExitCode(), 0); +} + +////////////////////////////////////////////////////////////////////////// + +TEST_SUITE_END(); + +} // namespace zen::tests + +#endif |