aboutsummaryrefslogtreecommitdiff
path: root/src/zenserver-test/process-tests.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/zenserver-test/process-tests.cpp')
-rw-r--r--src/zenserver-test/process-tests.cpp298
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