aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/test/unit_tests/process_worker.cpp
diff options
context:
space:
mode:
authorEgor Tensin <Egor.Tensin@gmail.com>2020-10-24 21:33:44 +0300
committerEgor Tensin <Egor.Tensin@gmail.com>2020-10-27 00:11:41 +0300
commita864099ba77157090c4cd12817245122c163ec24 (patch)
treef15b1f2801219fa9af6111fa28591858d87711ec /test/unit_tests/process_worker.cpp
parentProcess: add termination methods (diff)
downloadwinapi-common-a864099ba77157090c4cd12817245122c163ec24.tar.gz
winapi-common-a864099ba77157090c4cd12817245122c163ec24.zip
rework Process API & tests
* Add separate classes ProcessParameters & ShellParameters for Process::create() and Process::shell() methods. * Add a separate "worker" executable. It's used in tests via a fairly complicated scheme, receiving orders to execute via a shared memory region. * Add tests that utilize the Console API, reading console window's screen buffer directly, making for more reliable tests & broader coverage.
Diffstat (limited to 'test/unit_tests/process_worker.cpp')
-rw-r--r--test/unit_tests/process_worker.cpp278
1 files changed, 278 insertions, 0 deletions
diff --git a/test/unit_tests/process_worker.cpp b/test/unit_tests/process_worker.cpp
new file mode 100644
index 0000000..492ffff
--- /dev/null
+++ b/test/unit_tests/process_worker.cpp
@@ -0,0 +1,278 @@
+// Copyright (c) 2020 Egor Tensin <Egor.Tensin@gmail.com>
+// This file is part of the "winapi-common" project.
+// For details, see https://github.com/egor-tensin/winapi-common.
+// Distributed under the MIT License.
+
+#include "fixtures.hpp"
+#include "shared/command.hpp"
+#include "shared/console.hpp"
+#include "shared/test_data.hpp"
+#include "shared/worker.hpp"
+
+#include <winapi/buffer.hpp>
+#include <winapi/cmd_line.hpp>
+#include <winapi/handle.hpp>
+#include <winapi/pipe.hpp>
+#include <winapi/process.hpp>
+#include <winapi/process_io.hpp>
+
+#include <boost/test/unit_test.hpp>
+
+#include <windows.h>
+
+#include <cstddef>
+#include <iostream>
+#include <sstream>
+#include <string>
+#include <utility>
+#include <vector>
+
+using winapi::Buffer;
+using winapi::CommandLine;
+using winapi::Handle;
+using winapi::Pipe;
+using winapi::Process;
+using winapi::ProcessParameters;
+namespace process = winapi::process;
+
+using worker::StdHandles;
+using worker::Worker;
+
+namespace {
+
+void check_window_same(Worker& worker) {
+ BOOST_TEST(::GetConsoleWindow());
+ BOOST_TEST(worker.get_console_window() == ::GetConsoleWindow());
+ BOOST_TEST(worker.is_window_visible());
+}
+
+void check_window_none(Worker& worker) {
+ BOOST_TEST(::GetConsoleWindow());
+ BOOST_TEST(!worker.get_console_window());
+ BOOST_TEST(!worker.is_window_visible());
+}
+
+void check_window_new(Worker& worker) {
+ BOOST_TEST(::GetConsoleWindow());
+ BOOST_TEST(worker.get_console_window());
+ BOOST_TEST(worker.get_console_window() != ::GetConsoleWindow());
+ BOOST_TEST(worker.is_window_visible());
+}
+
+void check_std_handles(Worker& worker, const StdHandles& expected) {
+ const auto actual = worker.get_std_handles();
+ BOOST_TEST(actual.in == expected.in);
+ BOOST_TEST(actual.out == expected.out);
+ BOOST_TEST(actual.err == expected.err);
+}
+
+void check_std_handles_same(Worker& worker) {
+ check_std_handles(worker,
+ {::GetStdHandle(STD_INPUT_HANDLE),
+ ::GetStdHandle(STD_OUTPUT_HANDLE),
+ ::GetStdHandle(STD_ERROR_HANDLE)});
+}
+
+void check_std_handles_different(Worker& worker) {
+ const auto actual = worker.get_std_handles();
+ BOOST_TEST(actual.in);
+ BOOST_TEST(actual.out);
+ BOOST_TEST(actual.err);
+ BOOST_TEST(actual.in != ::GetStdHandle(STD_INPUT_HANDLE));
+ BOOST_TEST(actual.out != ::GetStdHandle(STD_OUTPUT_HANDLE));
+ BOOST_TEST(actual.err != ::GetStdHandle(STD_ERROR_HANDLE));
+}
+
+void check_write(Worker& worker) {
+ const auto handles = worker.test_write();
+ BOOST_TEST(!Handle::is_invalid(handles.in));
+ BOOST_TEST(!Handle::is_invalid(handles.out));
+ BOOST_TEST(!Handle::is_invalid(handles.err));
+}
+
+void check_redirected_output(const Buffer& buffer, const std::vector<std::string>& expected_lines) {
+ const auto actual = buffer.as_utf8();
+ std::ostringstream oss;
+ for (const auto& line : expected_lines) {
+ oss << line << "\r\n";
+ }
+ const std::string expected{oss.str()};
+ BOOST_TEST(actual == expected);
+}
+
+const std::string PLACEHOLDER{"This is a placeholder line."};
+
+void check_console_buffer_inherit(Worker& worker, const std::vector<std::string>& expected) {
+ // Check that
+ // 1) the worker process writes the expected lines to its console window,
+ // 2) the lines appear in this process's window, since they're the same.
+
+ const auto numof_lines = expected.size();
+ // Write some lines in this process's window, they should be overwritten
+ // by worker's writes.
+ for (std::size_t i = 0; i < numof_lines; ++i) {
+ std::cout << PLACEHOLDER << std::endl;
+ }
+ // Order worker to write the lines:
+ const auto handles = worker.test_write();
+ // Query worker process's window:
+ const auto worker_actual = worker.read_last_lines(numof_lines);
+ // Query this process's window:
+ const auto this_actual = console::Buffer{}.read_last_lines(numof_lines);
+
+ // They should look the same:
+ const auto& worker_expected = expected;
+ const auto& this_expected = worker_expected;
+
+ BOOST_TEST(worker_actual == worker_expected);
+ BOOST_TEST(this_actual == this_expected);
+}
+
+void check_console_buffer_new(Worker& worker, const std::vector<std::string>& expected) {
+ // Check that
+ // 1) the worker process writes the expected lines in its console window,
+ // 2) the lines _do not_ appear in this process's window, since they're
+ // different windows.
+
+ const auto numof_lines = expected.size();
+ // Write some lines in this process's window, they _should not_ be
+ // overwritten by worker's writes.
+ for (std::size_t i = 0; i < numof_lines; ++i) {
+ std::cout << PLACEHOLDER << std::endl;
+ }
+ // Order worker to write the lines:
+ const auto handles = worker.test_write();
+ // Query worker process's window:
+ const auto worker_actual = worker.read_last_lines(numof_lines);
+ // Query this process's window:
+ const auto this_actual = console::Buffer{}.read_last_lines(numof_lines);
+
+ // Placeholder lines are still last in this process's window:
+ const auto& worker_expected = expected;
+ const std::vector<std::string> this_expected(numof_lines, PLACEHOLDER);
+
+ BOOST_TEST(worker_actual == worker_expected);
+ BOOST_TEST(this_actual == this_expected);
+}
+
+// CREATE_NO_WINDOW actually does create a new buffer, it's just invisible.
+void check_console_buffer_none(Worker& worker, const std::vector<std::string>& expected) {
+ check_console_buffer_new(worker, expected);
+}
+
+} // namespace
+
+BOOST_AUTO_TEST_SUITE(process_console_tests)
+
+BOOST_FIXTURE_TEST_CASE(create_inherit, WithWorkerExe) {
+ const CommandLine cmd_line{get_worker_exe()};
+ ProcessParameters params{cmd_line};
+ params.console_mode = ProcessParameters::ConsoleInherit;
+
+ Worker worker{Process::create(std::move(params))};
+ check_window_same(worker);
+ check_std_handles_same(worker);
+ check_write(worker);
+ check_console_buffer_inherit(worker, {worker::test_data::out(), worker::test_data::err()});
+ BOOST_TEST(worker.exit() == 0);
+}
+
+BOOST_FIXTURE_TEST_CASE(create_inherit_override, WithWorkerExe) {
+ const CommandLine cmd_line{get_worker_exe()};
+ ProcessParameters params{cmd_line};
+ params.console_mode = ProcessParameters::ConsoleInherit;
+
+ Pipe stdin_pipe, stderr_pipe;
+ const StdHandles expected_handles{
+ stdin_pipe.read_end().get(), Handle::std_out().get(), stderr_pipe.write_end().get()};
+
+ process::IO io;
+ io.std_in = process::Stdin{stdin_pipe};
+ io.std_err = process::Stderr{stderr_pipe};
+ params.io = std::move(io);
+
+ Worker worker{Process::create(std::move(params))};
+ check_window_same(worker);
+ check_std_handles(worker, expected_handles);
+ check_write(worker);
+ check_console_buffer_inherit(worker, {worker::test_data::out()});
+ BOOST_TEST(worker.exit() == 0);
+ check_redirected_output(stderr_pipe.read_end().read(),
+ {worker::test_data::err(), worker::test_data::err()});
+}
+
+BOOST_FIXTURE_TEST_CASE(create_none, WithWorkerExe) {
+ const CommandLine cmd_line{get_worker_exe()};
+ ProcessParameters params{cmd_line};
+ params.console_mode = ProcessParameters::ConsoleNone;
+
+ Worker worker{Process::create(std::move(params))};
+ check_window_none(worker);
+ check_std_handles_different(worker);
+ check_write(worker);
+ check_console_buffer_none(worker, {worker::test_data::out(), worker::test_data::err()});
+ BOOST_TEST(worker.exit() == 0);
+}
+
+BOOST_FIXTURE_TEST_CASE(create_none_override, WithWorkerExe) {
+ const CommandLine cmd_line{get_worker_exe()};
+ ProcessParameters params{cmd_line};
+ params.console_mode = ProcessParameters::ConsoleNone;
+
+ Pipe stdin_pipe, stderr_pipe;
+ const StdHandles expected_handles{
+ stdin_pipe.read_end().get(), Handle::std_out().get(), stderr_pipe.write_end().get()};
+
+ process::IO io;
+ io.std_in = process::Stdin{stdin_pipe};
+ io.std_err = process::Stderr{stderr_pipe};
+ params.io = std::move(io);
+
+ Worker worker{Process::create(std::move(params))};
+ check_window_none(worker);
+ check_std_handles(worker, expected_handles);
+ check_write(worker);
+ check_console_buffer_none(worker, {worker::test_data::out()});
+ BOOST_TEST(worker.exit() == 0);
+ check_redirected_output(stderr_pipe.read_end().read(),
+ {worker::test_data::err(), worker::test_data::err()});
+}
+
+BOOST_FIXTURE_TEST_CASE(create_new, WithWorkerExe) {
+ const CommandLine cmd_line{get_worker_exe()};
+ ProcessParameters params{cmd_line};
+ params.console_mode = ProcessParameters::ConsoleNew;
+
+ Worker worker{Process::create(std::move(params))};
+ check_window_new(worker);
+ check_std_handles_different(worker);
+ check_write(worker);
+ check_console_buffer_new(worker, {worker::test_data::out(), worker::test_data::err()});
+ BOOST_TEST(worker.exit() == 0);
+}
+
+BOOST_FIXTURE_TEST_CASE(create_new_override, WithWorkerExe) {
+ const CommandLine cmd_line{get_worker_exe()};
+ ProcessParameters params{cmd_line};
+ params.console_mode = ProcessParameters::ConsoleNew;
+
+ Pipe stdin_pipe, stderr_pipe;
+ const StdHandles expected_handles{
+ stdin_pipe.read_end().get(), Handle::std_out().get(), stderr_pipe.write_end().get()};
+
+ process::IO io;
+ io.std_in = process::Stdin{stdin_pipe};
+ io.std_err = process::Stderr{stderr_pipe};
+ params.io = std::move(io);
+
+ Worker worker{Process::create(std::move(params))};
+ check_window_new(worker);
+ check_std_handles(worker, expected_handles);
+ check_write(worker);
+ check_console_buffer_new(worker, {worker::test_data::out()});
+ BOOST_TEST(worker.exit() == 0);
+ check_redirected_output(stderr_pipe.read_end().read(),
+ {worker::test_data::err(), worker::test_data::err()});
+}
+
+BOOST_AUTO_TEST_SUITE_END()