diff options
Diffstat (limited to 'src/zen-test/zen-test.cpp')
| -rw-r--r-- | src/zen-test/zen-test.cpp | 238 |
1 files changed, 238 insertions, 0 deletions
diff --git a/src/zen-test/zen-test.cpp b/src/zen-test/zen-test.cpp new file mode 100644 index 000000000..3ee401045 --- /dev/null +++ b/src/zen-test/zen-test.cpp @@ -0,0 +1,238 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#define _SILENCE_CXX17_C_HEADER_DEPRECATION_WARNING + +#if ZEN_WITH_TESTS + +# include "zen-test.h" + +# include <zencore/except.h> +# include <zencore/filesystem.h> +# include <zencore/fmtutils.h> +# include <zencore/logging.h> +# include <zencore/logging/registry.h> +# include <zencore/process.h> +# include <zencore/string.h> +# include <zencore/testutils.h> +# include <zencore/trace.h> +# include <zenhttp/httpclient.h> +# include <zenutil/config/commandlineoptions.h> +# include <zenutil/logging/fullformatter.h> +# include <zenutil/zenserverprocess.h> + +# include <filesystem> + +# if ZEN_PLATFORM_WINDOWS +# include <zencore/windows.h> +# else +# include <cerrno> +# include <unistd.h> +# endif + +# include <zencore/memory/newdelete.h> + +////////////////////////////////////////////////////////////////////////// + +using namespace std::literals; + +////////////////////////////////////////////////////////////////////////// + +namespace zen::tests { + +zen::ZenServerEnvironment TestEnv; + +static std::filesystem::path s_ProgramBaseDir; + +std::filesystem::path +GetZenExecutablePath() +{ + return s_ProgramBaseDir / "zen" ZEN_EXE_SUFFIX_LITERAL; +} + +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; +} + +ZenCommandResult +RunZen(std::string_view Args) +{ + StdoutPipeHandles Pipe; + if (!CreateStdoutPipe(Pipe)) + { + return {.ExitCode = -1, .Output = "failed to create stdout pipe"}; + } + + const std::filesystem::path ZenExe = GetZenExecutablePath(); + const std::string CommandLine = fmt::format("zen {}", Args); + + CreateProcOptions Options; + Options.StdoutPipe = &Pipe; // stderr shares the stdout pipe when StderrPipe is null + + ProcessHandle Process(CreateProc(ZenExe, CommandLine, Options)); + + // Drain output before Wait() so the child doesn't block on a full pipe buffer. + Pipe.CloseWriteEnd(); + std::string Output = ReadAllFromPipe(Pipe); + + Process.Wait(); + + return {.ExitCode = Process.GetExitCode(), .Output = std::move(Output)}; +} + +} // namespace zen::tests + +int +main(int argc, char** argv) +{ +# if ZEN_PLATFORM_WINDOWS + setlocale(LC_ALL, "en_us.UTF8"); +# endif // ZEN_PLATFORM_WINDOWS + + using namespace std::literals; + using namespace zen; + + zen::CommandLineConverter ArgConverter(argc, argv); + +# if ZEN_WITH_TRACE + zen::TraceInit("zen-test"); + TraceOptions TraceCommandlineOptions; + if (GetTraceOptionsFromCommandline(TraceCommandlineOptions)) + { + TraceConfigure(TraceCommandlineOptions); + } +# endif // ZEN_WITH_TRACE + + zen::logging::InitializeLogging(); + + zen::logging::SetLogLevel(zen::logging::Debug); + zen::logging::Registry::Instance().SetFormatter( + std::make_unique<zen::logging::FullFormatter>("test", std::chrono::system_clock::now())); + + std::filesystem::path ProgramBaseDir = GetRunningExecutablePath().parent_path(); + std::filesystem::path TestBaseDir = std::filesystem::current_path() / ".test"; + + zen::tests::s_ProgramBaseDir = ProgramBaseDir; + + // This is pretty janky because we're passing most of the options through to the test + // framework, so we can't just use cxxopts (I think). This should ideally be cleaned up + // somehow in the future + + std::string ServerClass; + bool Verbose = false; + bool KillStale = false; + + for (int i = 1; i < argc; ++i) + { + if (argv[i] == "--http"sv) + { + if ((i + 1) < argc) + { + ServerClass = argv[++i]; + } + } + else if (std::string_view Arg(argv[i]); Arg.starts_with("--httpclient="sv)) + { + std::string_view Value = Arg.substr(13); + zen::SetDefaultHttpClientBackend(Value); + } + else if (argv[i] == "--verbose"sv) + { + Verbose = true; + } + else if (argv[i] == "--kill-stale-processes"sv) + { + KillStale = true; + } + } + + // Since GHA runners can leave processes behind from a previous test run, we need + // to be able to clean up any stale server processes before starting the tests to + // avoid interference + + if (KillStale) + { + ZEN_INFO("Killing any stale processes from previous test runs..."); + + auto KillStaleProcesses = [](const std::filesystem::path& Executable) { + ZEN_INFO(" Looking for stale '{}' processes...", Executable.filename()); + + for (;;) + { + ProcessHandle StaleProcess; + std::error_code Ec = FindProcess(Executable, StaleProcess, /*IncludeSelf*/ false); + + if (Ec || !StaleProcess.IsValid()) + { + break; + } + + ZEN_WARN("====> Found stale '{}' process (pid {}) from a previous test run - terminating it", + Executable.filename(), + StaleProcess.Pid()); + + StaleProcess.Terminate(0); + } + }; + + KillStaleProcesses(ProgramBaseDir / "zen" ZEN_EXE_SUFFIX_LITERAL); + KillStaleProcesses(ProgramBaseDir / "zenserver" ZEN_EXE_SUFFIX_LITERAL); + KillStaleProcesses(ProgramBaseDir / "zen-test" ZEN_EXE_SUFFIX_LITERAL); + KillStaleProcesses(ProgramBaseDir / "zentest-appstub" ZEN_EXE_SUFFIX_LITERAL); + } + + zen::tests::TestEnv.InitializeForTest(ProgramBaseDir, TestBaseDir, ServerClass); + + if (Verbose) + { + zen::tests::TestEnv.SetPassthroughOutput(true); + } + + ZEN_INFO("Running tests...(base dir: '{}')", TestBaseDir); + + zen::testing::TestRunner Runner; + Runner.ApplyCommandLine(argc, argv, "zen.*"); + + return Runner.Run(); +} + +namespace zen::tests { + +TEST_SUITE_BEGIN("zen.zen-test"); + +TEST_CASE("scaffolding.executable_present") +{ + const std::filesystem::path ZenExe = GetZenExecutablePath(); + CHECK_MESSAGE(std::filesystem::exists(ZenExe), fmt::format("zen executable not found at '{}'", ZenExe.string())); +} + +TEST_SUITE_END(); + +} // namespace zen::tests +#else +# include <stdio.h> + +int +main() +{ + printf("tests are disabled (ZEN_WITH_TESTS=0). Tests are only available in debug builds\n"); +} +#endif |