2022-01-20 18:27:56 +00:00
|
|
|
// This file Copyright (C) 2017-2022 Mnemosyne LLC.
|
2022-08-08 18:05:39 +00:00
|
|
|
// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only),
|
2022-01-20 18:27:56 +00:00
|
|
|
// or any future license endorsed by Mnemosyne LLC.
|
|
|
|
// License text can be found in the licenses/ folder.
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2023-01-02 16:23:51 +00:00
|
|
|
#include <libtransmission/transmission.h>
|
|
|
|
#include <libtransmission/error.h>
|
|
|
|
#include <libtransmission/file.h>
|
|
|
|
#include <libtransmission/platform.h>
|
|
|
|
#include <libtransmission/subprocess.h>
|
2020-08-11 18:11:55 +00:00
|
|
|
|
|
|
|
#include "gtest/internal/gtest-port.h" // GetArgvs()
|
|
|
|
|
|
|
|
#include "test-fixtures.h"
|
|
|
|
|
|
|
|
#include <array>
|
2022-08-27 19:05:21 +00:00
|
|
|
#include <fstream>
|
2022-04-08 01:50:26 +00:00
|
|
|
#include <map>
|
2020-08-11 18:11:55 +00:00
|
|
|
#include <string>
|
2022-04-08 01:50:26 +00:00
|
|
|
#include <string_view>
|
2020-08-11 18:11:55 +00:00
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
#include <windows.h>
|
|
|
|
#define setenv(key, value, unused) SetEnvironmentVariableA(key, value)
|
|
|
|
#endif
|
|
|
|
|
2022-11-14 20:16:29 +00:00
|
|
|
namespace libtransmission::test
|
2020-08-11 18:11:55 +00:00
|
|
|
{
|
|
|
|
|
2021-08-16 01:45:50 +00:00
|
|
|
std::string getTestProgramPath(std::string const& filename)
|
2020-08-11 18:11:55 +00:00
|
|
|
{
|
2022-08-05 16:36:01 +00:00
|
|
|
auto const exe_path = tr_sys_path_resolve(testing::internal::GetArgvs().front().data());
|
2022-03-21 20:22:50 +00:00
|
|
|
auto const exe_dir = tr_sys_path_dirname(exe_path);
|
2022-05-21 16:10:58 +00:00
|
|
|
return std::string{ exe_dir } + TR_PATH_DELIMITER + filename;
|
2020-08-11 18:11:55 +00:00
|
|
|
}
|
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
class SubprocessTest
|
|
|
|
: public ::testing::Test
|
|
|
|
, public testing::WithParamInterface<std::string>
|
2020-08-11 18:11:55 +00:00
|
|
|
{
|
|
|
|
protected:
|
|
|
|
Sandbox sandbox_;
|
|
|
|
|
|
|
|
[[nodiscard]] std::string buildSandboxPath(std::string const& filename) const
|
|
|
|
{
|
|
|
|
auto path = sandbox_.path();
|
|
|
|
path += TR_PATH_DELIMITER;
|
|
|
|
path += filename;
|
|
|
|
tr_sys_path_native_separators(&path.front());
|
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
|
|
|
[[nodiscard]] static std::string nativeCwd()
|
|
|
|
{
|
2022-08-04 04:59:41 +00:00
|
|
|
auto path = tr_sys_dir_get_current();
|
|
|
|
tr_sys_path_native_separators(path.data());
|
2020-08-11 18:11:55 +00:00
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
std::string const arg_dump_args_{ "--dump-args" };
|
|
|
|
std::string const arg_dump_env_{ "--dump-env" };
|
|
|
|
std::string const arg_dump_cwd_{ "--dump-cwd" };
|
2020-08-11 18:11:55 +00:00
|
|
|
|
|
|
|
std::string self_path_;
|
|
|
|
|
2022-11-28 22:45:18 +00:00
|
|
|
static void waitForFileToBeReadable(std::string const& path)
|
2020-08-11 18:11:55 +00:00
|
|
|
{
|
2022-11-28 22:45:18 +00:00
|
|
|
auto const test = [&path]()
|
2021-08-15 09:41:48 +00:00
|
|
|
{
|
2022-11-28 22:45:18 +00:00
|
|
|
return std::ifstream{ path, std::ios_base::in }.is_open();
|
2021-08-15 09:41:48 +00:00
|
|
|
};
|
2020-10-14 22:27:52 +00:00
|
|
|
EXPECT_TRUE(waitFor(test, 30000));
|
2020-08-11 18:11:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SetUp() override
|
|
|
|
{
|
|
|
|
self_path_ = GetParam();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
TEST_P(SubprocessTest, SpawnAsyncMissingExec)
|
|
|
|
{
|
2021-08-15 09:41:48 +00:00
|
|
|
auto const missing_exe_path = std::string{ TR_IF_WIN32("C:\\", "/") "tr-missing-test-exe" TR_IF_WIN32(".exe", "") };
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2021-11-24 19:25:23 +00:00
|
|
|
auto args = std::array<char const*, 2>{ missing_exe_path.data(), nullptr };
|
2020-08-11 18:11:55 +00:00
|
|
|
|
|
|
|
tr_error* error = nullptr;
|
2022-08-05 19:16:25 +00:00
|
|
|
auto const ret = tr_spawn_async(std::data(args), {}, {}, &error);
|
2020-08-11 18:11:55 +00:00
|
|
|
EXPECT_FALSE(ret);
|
|
|
|
EXPECT_NE(nullptr, error);
|
|
|
|
EXPECT_NE(0, error->code);
|
|
|
|
EXPECT_NE(nullptr, error->message);
|
|
|
|
|
|
|
|
tr_error_clear(&error);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_P(SubprocessTest, SpawnAsyncArgs)
|
|
|
|
{
|
|
|
|
auto const result_path = buildSandboxPath("result.txt");
|
2023-06-30 14:49:58 +00:00
|
|
|
bool const allow_batch_metachars = TR_IF_WIN32(false, true) || !tr_strv_ends_with(tr_strlower(self_path_), ".cmd"sv);
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
auto const test_arg1 = std::string{ "arg1 " };
|
|
|
|
auto const test_arg2 = std::string{ " arg2" };
|
|
|
|
auto const test_arg3 = std::string{};
|
|
|
|
auto const test_arg4 = std::string{ "\"arg3'^! $PATH %PATH% \\" };
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2021-11-24 19:25:23 +00:00
|
|
|
auto const args = std::array<char const*, 8>{ self_path_.c_str(),
|
|
|
|
result_path.data(),
|
|
|
|
arg_dump_args_.data(),
|
|
|
|
test_arg1.data(),
|
|
|
|
test_arg2.data(),
|
|
|
|
test_arg3.data(),
|
|
|
|
allow_batch_metachars ? test_arg4.data() : nullptr,
|
|
|
|
nullptr };
|
2020-08-11 18:11:55 +00:00
|
|
|
|
|
|
|
tr_error* error = nullptr;
|
2022-08-05 19:16:25 +00:00
|
|
|
bool const ret = tr_spawn_async(std::data(args), {}, {}, &error);
|
2020-08-11 18:11:55 +00:00
|
|
|
EXPECT_TRUE(ret) << args[0] << ' ' << args[1];
|
2022-03-21 20:22:50 +00:00
|
|
|
EXPECT_EQ(nullptr, error) << *error;
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2022-11-28 22:45:18 +00:00
|
|
|
waitForFileToBeReadable(result_path);
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2022-08-27 19:05:21 +00:00
|
|
|
auto in = std::ifstream{ result_path, std::ios_base::in };
|
2022-11-28 22:45:18 +00:00
|
|
|
EXPECT_TRUE(in.is_open()) << strerror(errno);
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2022-08-27 19:05:21 +00:00
|
|
|
auto line = std::string{};
|
|
|
|
EXPECT_TRUE(std::getline(in, line));
|
|
|
|
EXPECT_EQ(test_arg1, line);
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2022-08-27 19:05:21 +00:00
|
|
|
EXPECT_TRUE(std::getline(in, line));
|
|
|
|
EXPECT_EQ(test_arg2, line);
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2022-08-27 19:05:21 +00:00
|
|
|
EXPECT_TRUE(std::getline(in, line));
|
|
|
|
EXPECT_EQ(test_arg3, line);
|
2020-08-11 18:11:55 +00:00
|
|
|
|
|
|
|
if (allow_batch_metachars)
|
|
|
|
{
|
2022-08-27 19:05:21 +00:00
|
|
|
EXPECT_TRUE(std::getline(in, line));
|
|
|
|
EXPECT_EQ(test_arg4, line);
|
2020-08-11 18:11:55 +00:00
|
|
|
}
|
|
|
|
|
2022-08-27 19:05:21 +00:00
|
|
|
EXPECT_FALSE(std::getline(in, line));
|
2020-08-11 18:11:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_P(SubprocessTest, SpawnAsyncEnv)
|
|
|
|
{
|
|
|
|
auto const result_path = buildSandboxPath("result.txt");
|
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
auto const test_env_key1 = std::string{ "VAR1" };
|
|
|
|
auto const test_env_key2 = std::string{ "_VAR_2_" };
|
|
|
|
auto const test_env_key3 = std::string{ "vAr#" };
|
|
|
|
auto const test_env_key4 = std::string{ "FOO" };
|
|
|
|
auto const test_env_key5 = std::string{ "ZOO" };
|
|
|
|
auto const test_env_key6 = std::string{ "TR_MISSING_TEST_ENV_KEY" };
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2021-08-15 09:41:48 +00:00
|
|
|
auto const test_env_value1 = std::string{ "value1 " };
|
|
|
|
auto const test_env_value2 = std::string{ " value2" };
|
|
|
|
auto const test_env_value3 = std::string{ " \"value3'^! $PATH %PATH% " };
|
|
|
|
auto const test_env_value4 = std::string{ "bar" };
|
|
|
|
auto const test_env_value5 = std::string{ "jar" };
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2021-11-24 19:25:23 +00:00
|
|
|
auto args = std::array<char const*, 10>{
|
|
|
|
self_path_.c_str(), //
|
|
|
|
result_path.data(), //
|
|
|
|
arg_dump_env_.data(), //
|
|
|
|
test_env_key1.data(), //
|
|
|
|
test_env_key2.data(), //
|
|
|
|
test_env_key3.data(), //
|
|
|
|
test_env_key4.data(), //
|
|
|
|
test_env_key5.data(), //
|
|
|
|
test_env_key6.data(), //
|
2021-08-15 09:41:48 +00:00
|
|
|
nullptr, //
|
2020-08-11 18:11:55 +00:00
|
|
|
};
|
|
|
|
|
2021-11-24 19:25:23 +00:00
|
|
|
auto const env = std::map<std::string_view, std::string_view>{
|
|
|
|
{ test_env_key1, test_env_value1 },
|
|
|
|
{ test_env_key2, test_env_value2 },
|
|
|
|
{ test_env_key3, test_env_value3 },
|
|
|
|
{ test_env_key5, test_env_value5 },
|
2020-08-11 18:11:55 +00:00
|
|
|
};
|
|
|
|
|
2022-01-24 19:07:55 +00:00
|
|
|
setenv("FOO", "bar", 1 /*true*/); // inherited
|
|
|
|
setenv("ZOO", "tar", 1 /*true*/); // overridden
|
2020-08-11 18:11:55 +00:00
|
|
|
|
|
|
|
tr_error* error = nullptr;
|
2022-08-05 19:16:25 +00:00
|
|
|
bool const ret = tr_spawn_async(std::data(args), env, {}, &error);
|
2020-08-11 18:11:55 +00:00
|
|
|
EXPECT_TRUE(ret);
|
2022-03-21 20:22:50 +00:00
|
|
|
EXPECT_EQ(nullptr, error) << *error;
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2022-11-28 22:45:18 +00:00
|
|
|
waitForFileToBeReadable(result_path);
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2022-08-27 19:05:21 +00:00
|
|
|
auto in = std::ifstream{ result_path, std::ios_base::in };
|
2022-11-28 22:45:18 +00:00
|
|
|
EXPECT_TRUE(in.is_open()) << strerror(errno);
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2022-08-27 19:05:21 +00:00
|
|
|
auto line = std::string{};
|
|
|
|
EXPECT_TRUE(std::getline(in, line));
|
|
|
|
EXPECT_EQ(test_env_value1, line);
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2022-08-27 19:05:21 +00:00
|
|
|
EXPECT_TRUE(std::getline(in, line));
|
|
|
|
EXPECT_EQ(test_env_value2, line);
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2022-08-27 19:05:21 +00:00
|
|
|
EXPECT_TRUE(std::getline(in, line));
|
|
|
|
EXPECT_EQ(test_env_value3, line);
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2022-08-27 19:05:21 +00:00
|
|
|
EXPECT_TRUE(std::getline(in, line));
|
|
|
|
EXPECT_EQ(test_env_value4, line);
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2022-08-27 19:05:21 +00:00
|
|
|
EXPECT_TRUE(std::getline(in, line));
|
|
|
|
EXPECT_EQ(test_env_value5, line);
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2022-08-27 19:05:21 +00:00
|
|
|
EXPECT_TRUE(std::getline(in, line));
|
|
|
|
EXPECT_EQ("<null>"sv, line);
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2022-08-27 19:05:21 +00:00
|
|
|
EXPECT_FALSE(std::getline(in, line));
|
2020-08-11 18:11:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_P(SubprocessTest, SpawnAsyncCwdExplicit)
|
|
|
|
{
|
|
|
|
auto const test_dir = sandbox_.path();
|
|
|
|
auto const result_path = buildSandboxPath("result.txt");
|
|
|
|
|
2022-11-28 22:45:18 +00:00
|
|
|
auto const args = std::array<char const*, 4>{ self_path_.c_str(), result_path.c_str(), arg_dump_cwd_.c_str(), nullptr };
|
2020-08-11 18:11:55 +00:00
|
|
|
|
|
|
|
tr_error* error = nullptr;
|
2022-08-05 19:16:25 +00:00
|
|
|
bool const ret = tr_spawn_async(std::data(args), {}, test_dir, &error);
|
2020-08-11 18:11:55 +00:00
|
|
|
EXPECT_TRUE(ret);
|
2022-03-21 20:22:50 +00:00
|
|
|
EXPECT_EQ(nullptr, error) << *error;
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2022-11-28 22:45:18 +00:00
|
|
|
waitForFileToBeReadable(result_path);
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2022-08-27 19:05:21 +00:00
|
|
|
auto in = std::ifstream{ result_path, std::ios_base::in };
|
2022-11-28 22:45:18 +00:00
|
|
|
EXPECT_TRUE(in.is_open()) << strerror(errno);
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2022-08-27 19:05:21 +00:00
|
|
|
auto line = std::string{};
|
|
|
|
EXPECT_TRUE(std::getline(in, line));
|
2022-08-17 00:28:57 +00:00
|
|
|
auto expected = std::string{ test_dir };
|
|
|
|
tr_sys_path_native_separators(std::data(expected));
|
2022-08-27 19:05:21 +00:00
|
|
|
auto actual = line;
|
2022-08-17 00:28:57 +00:00
|
|
|
tr_sys_path_native_separators(std::data(actual));
|
|
|
|
EXPECT_EQ(expected, actual);
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2022-08-27 19:05:21 +00:00
|
|
|
EXPECT_FALSE(std::getline(in, line));
|
2020-08-11 18:11:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_P(SubprocessTest, SpawnAsyncCwdInherit)
|
|
|
|
{
|
|
|
|
auto const result_path = buildSandboxPath("result.txt");
|
|
|
|
auto const expected_cwd = nativeCwd();
|
|
|
|
|
2021-11-24 19:25:23 +00:00
|
|
|
auto const args = std::array<char const*, 4>{ self_path_.c_str(), result_path.data(), arg_dump_cwd_.data(), nullptr };
|
2020-08-11 18:11:55 +00:00
|
|
|
|
|
|
|
tr_error* error = nullptr;
|
2022-08-05 19:16:25 +00:00
|
|
|
auto const ret = tr_spawn_async(std::data(args), {}, {}, &error);
|
2020-08-11 18:11:55 +00:00
|
|
|
EXPECT_TRUE(ret);
|
2022-03-21 20:22:50 +00:00
|
|
|
EXPECT_EQ(nullptr, error) << *error;
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2022-11-28 22:45:18 +00:00
|
|
|
waitForFileToBeReadable(result_path);
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2022-08-27 19:05:21 +00:00
|
|
|
auto in = std::ifstream{ result_path, std::ios_base::in };
|
2022-11-28 22:45:18 +00:00
|
|
|
EXPECT_TRUE(in.is_open()) << strerror(errno);
|
2022-08-27 19:05:21 +00:00
|
|
|
|
|
|
|
auto line = std::string{};
|
|
|
|
EXPECT_TRUE(std::getline(in, line));
|
|
|
|
auto actual = line;
|
|
|
|
tr_sys_path_native_separators(std::data(actual));
|
|
|
|
EXPECT_EQ(expected_cwd, actual);
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2022-08-27 19:05:21 +00:00
|
|
|
EXPECT_FALSE(std::getline(in, line));
|
2020-08-11 18:11:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST_P(SubprocessTest, SpawnAsyncCwdMissing)
|
|
|
|
{
|
|
|
|
auto const result_path = buildSandboxPath("result.txt");
|
|
|
|
|
2021-11-24 19:25:23 +00:00
|
|
|
auto const args = std::array<char const*, 4>{ self_path_.c_str(), result_path.data(), arg_dump_cwd_.data(), nullptr };
|
2020-08-11 18:11:55 +00:00
|
|
|
|
|
|
|
tr_error* error = nullptr;
|
2021-11-24 19:25:23 +00:00
|
|
|
auto const ret = tr_spawn_async(std::data(args), {}, TR_IF_WIN32("C:\\", "/") "tr-missing-test-work-dir", &error);
|
2020-08-11 18:11:55 +00:00
|
|
|
EXPECT_FALSE(ret);
|
|
|
|
EXPECT_NE(nullptr, error);
|
|
|
|
EXPECT_NE(0, error->code);
|
|
|
|
EXPECT_NE(nullptr, error->message);
|
|
|
|
tr_error_clear(&error);
|
|
|
|
}
|
|
|
|
|
2021-08-16 01:45:50 +00:00
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
2020-08-11 18:11:55 +00:00
|
|
|
Subprocess,
|
|
|
|
SubprocessTest,
|
2021-08-16 01:45:50 +00:00
|
|
|
TR_IF_WIN32(
|
|
|
|
::testing::Values( //
|
|
|
|
getTestProgramPath("subprocess-test.exe"),
|
|
|
|
getTestProgramPath("subprocess-test.cmd")),
|
|
|
|
::testing::Values( //
|
|
|
|
getTestProgramPath("subprocess-test"))));
|
2020-08-11 18:11:55 +00:00
|
|
|
|
2022-11-14 20:16:29 +00:00
|
|
|
} // namespace libtransmission::test
|