aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/testing/common
diff options
context:
space:
mode:
authorDevtools Arcadia <arcadia-devtools@yandex-team.ru>2022-02-07 18:08:42 +0300
committerDevtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net>2022-02-07 18:08:42 +0300
commit1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch)
treee26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/cpp/testing/common
downloadydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/testing/common')
-rw-r--r--library/cpp/testing/common/env.cpp275
-rw-r--r--library/cpp/testing/common/env.h84
-rw-r--r--library/cpp/testing/common/network.cpp208
-rw-r--r--library/cpp/testing/common/network.h52
-rw-r--r--library/cpp/testing/common/probe.cpp1
-rw-r--r--library/cpp/testing/common/probe.h140
-rw-r--r--library/cpp/testing/common/scope.cpp1
-rw-r--r--library/cpp/testing/common/scope.h39
-rw-r--r--library/cpp/testing/common/ut/env_ut.cpp162
-rw-r--r--library/cpp/testing/common/ut/network_ut.cpp54
-rw-r--r--library/cpp/testing/common/ut/scope_ut.cpp28
-rw-r--r--library/cpp/testing/common/ut/ya.make19
-rw-r--r--library/cpp/testing/common/ya.make23
13 files changed, 1086 insertions, 0 deletions
diff --git a/library/cpp/testing/common/env.cpp b/library/cpp/testing/common/env.cpp
new file mode 100644
index 00000000000..fa3a47fe16b
--- /dev/null
+++ b/library/cpp/testing/common/env.cpp
@@ -0,0 +1,275 @@
+#include "env.h"
+
+#include <build/scripts/c_templates/svnversion.h>
+
+#include <util/folder/dirut.h>
+#include <util/folder/path.h>
+#include <util/generic/singleton.h>
+#include <util/stream/file.h>
+#include <util/stream/fwd.h>
+#include <util/system/env.h>
+#include <util/system/file.h>
+#include <util/system/file_lock.h>
+#include <util/system/guard.h>
+
+#include <library/cpp/json/json_reader.h>
+#include <library/cpp/json/json_value.h>
+#include <library/cpp/json/json_writer.h>
+
+TString ArcadiaSourceRoot() {
+ if (const auto& sourceRoot = NPrivate::GetTestEnv().SourceRoot) {
+ return sourceRoot;
+ } else {
+ return GetArcadiaSourcePath();
+ }
+}
+
+TString BuildRoot() {
+ if (const auto& buildRoot = NPrivate::GetTestEnv().BuildRoot) {
+ return buildRoot;
+ } else {
+ return GetArcadiaSourcePath();
+ }
+}
+
+TString ArcadiaFromCurrentLocation(TStringBuf where, TStringBuf path) {
+ return (TFsPath(ArcadiaSourceRoot()) / TFsPath(where).Parent() / path).Fix();
+}
+
+TString BinaryPath(TStringBuf path) {
+ return (TFsPath(BuildRoot()) / path).Fix();
+}
+
+TString GetArcadiaTestsData() {
+ TString atdRoot = NPrivate::GetTestEnv().ArcadiaTestsDataDir;
+ if (atdRoot) {
+ return atdRoot;
+ }
+
+ TString path = NPrivate::GetCwd();
+ const char pathsep = GetDirectorySeparator();
+ while (!path.empty()) {
+ TString dataDir = path + "/arcadia_tests_data";
+ if (IsDir(dataDir)) {
+ return dataDir;
+ }
+
+ size_t pos = path.find_last_of(pathsep);
+ if (pos == TString::npos) {
+ pos = 0;
+ }
+ path.erase(pos);
+ }
+
+ return {};
+}
+
+TString GetWorkPath() {
+ TString workPath = NPrivate::GetTestEnv().WorkPath;
+ if (workPath) {
+ return workPath;
+ }
+
+ return NPrivate::GetCwd();
+}
+
+TFsPath GetOutputPath() {
+ return GetWorkPath() + "/testing_out_stuff";
+}
+
+const TString& GetRamDrivePath() {
+ return NPrivate::GetTestEnv().RamDrivePath;
+}
+
+const TString& GetYtHddPath() {
+ return NPrivate::GetTestEnv().YtHddPath;
+}
+
+const TString& GetOutputRamDrivePath() {
+ return NPrivate::GetTestEnv().TestOutputRamDrivePath;
+}
+
+const TString& GdbPath() {
+ return NPrivate::GetTestEnv().GdbPath;
+}
+
+const TString& GetTestParam(TStringBuf name) {
+ const static TString def = "";
+ return GetTestParam(name, def);
+}
+
+const TString& GetTestParam(TStringBuf name, const TString& def) {
+ auto& testParameters = NPrivate::GetTestEnv().TestParameters;
+ auto it = testParameters.find(name.data());
+ if (it != testParameters.end()) {
+ return it->second;
+ }
+ return def;
+}
+
+void AddEntryToCoreSearchFile(const TString& filename, TStringBuf cmd, int pid, const TFsPath& binaryPath = TFsPath(), const TFsPath& cwd = TFsPath()) {
+ auto lock = TFileLock(filename);
+ TGuard<TFileLock> guard(lock);
+
+ TOFStream output(TFile(filename, WrOnly | ForAppend | OpenAlways));
+
+ NJson::TJsonWriter writer(&output, false);
+ writer.OpenMap();
+ writer.Write("cmd", cmd);
+ writer.Write("pid", pid);
+ if (binaryPath) {
+ writer.Write("binary_path", binaryPath);
+ }
+ if (cwd) {
+ writer.Write("cwd", cwd);
+ }
+ writer.CloseMap();
+ writer.Flush();
+
+ output.Write("\n");
+}
+
+void WatchProcessCore(int pid, const TFsPath& binaryPath, const TFsPath& cwd) {
+ auto& filename = NPrivate::GetTestEnv().CoreSearchFile;
+ if (filename) {
+ AddEntryToCoreSearchFile(filename, "add", pid, binaryPath, cwd);
+ }
+}
+
+void StopProcessCoreWatching(int pid) {
+ auto& filename = NPrivate::GetTestEnv().CoreSearchFile;
+ if (filename) {
+ AddEntryToCoreSearchFile(filename, "drop", pid);
+ }
+}
+
+bool FromYaTest() {
+ return NPrivate::GetTestEnv().IsRunningFromTest;
+}
+
+namespace NPrivate {
+ TTestEnv::TTestEnv() {
+ ReInitialize();
+ }
+
+ void TTestEnv::ReInitialize() {
+ IsRunningFromTest = false;
+ ArcadiaTestsDataDir = "";
+ SourceRoot = "";
+ BuildRoot = "";
+ WorkPath = "";
+ RamDrivePath = "";
+ YtHddPath = "";
+ TestOutputRamDrivePath = "";
+ GdbPath = "";
+ CoreSearchFile = "";
+ TestParameters.clear();
+
+ const TString contextFilename = GetEnv("YA_TEST_CONTEXT_FILE");
+ if (contextFilename) {
+ NJson::TJsonValue context;
+ NJson::ReadJsonTree(TFileInput(contextFilename).ReadAll(), &context);
+
+ NJson::TJsonValue* value;
+
+ value = context.GetValueByPath("runtime.source_root");
+ if (value) {
+ SourceRoot = value->GetStringSafe("");
+ }
+
+ value = context.GetValueByPath("runtime.build_root");
+ if (value) {
+ BuildRoot = value->GetStringSafe("");
+ }
+
+ value = context.GetValueByPath("runtime.atd_root");
+ if (value) {
+ ArcadiaTestsDataDir = value->GetStringSafe("");
+ }
+
+ value = context.GetValueByPath("runtime.work_path");
+ if (value) {
+ WorkPath = value->GetStringSafe("");
+ }
+
+ value = context.GetValueByPath("runtime.ram_drive_path");
+ if (value) {
+ RamDrivePath = value->GetStringSafe("");
+ }
+
+ value = context.GetValueByPath("runtime.yt_hdd_path");
+ if (value) {
+ YtHddPath = value->GetStringSafe("");
+ }
+
+ value = context.GetValueByPath("runtime.test_output_ram_drive_path");
+ if (value) {
+ TestOutputRamDrivePath = value->GetStringSafe("");
+ }
+
+ value = context.GetValueByPath("runtime.gdb_bin");
+ if (value) {
+ GdbPath = value->GetStringSafe("");
+ }
+
+ value = context.GetValueByPath("runtime.test_params");
+ if (value) {
+ for (const auto& entry : context.GetValueByPath("runtime.test_params")->GetMap()) {
+ TestParameters[entry.first] = entry.second.GetStringSafe("");
+ }
+ }
+
+ value = context.GetValueByPath("internal.core_search_file");
+ if (value) {
+ CoreSearchFile = value->GetStringSafe("");
+ }
+ }
+
+ if (!YtHddPath) {
+ YtHddPath = GetEnv("HDD_PATH");
+ }
+
+ if (!SourceRoot) {
+ SourceRoot = GetEnv("ARCADIA_SOURCE_ROOT");
+ }
+
+ if (!BuildRoot) {
+ BuildRoot = GetEnv("ARCADIA_BUILD_ROOT");
+ }
+
+ if (!ArcadiaTestsDataDir) {
+ ArcadiaTestsDataDir = GetEnv("ARCADIA_TESTS_DATA_DIR");
+ }
+
+ if (!WorkPath) {
+ WorkPath = GetEnv("TEST_WORK_PATH");
+ }
+
+ if (!RamDrivePath) {
+ RamDrivePath = GetEnv("YA_TEST_RAM_DRIVE_PATH");
+ }
+
+ if (!TestOutputRamDrivePath) {
+ TestOutputRamDrivePath = GetEnv("YA_TEST_OUTPUT_RAM_DRIVE_PATH");
+ }
+
+ const TString fromEnv = GetEnv("YA_TEST_RUNNER");
+ IsRunningFromTest = (fromEnv == "1");
+ }
+
+ void TTestEnv::AddTestParam(TStringBuf name, TStringBuf value) {
+ TestParameters[TString{name}] = value;
+ }
+
+ TString GetCwd() {
+ try {
+ return NFs::CurrentWorkingDirectory();
+ } catch (...) {
+ return {};
+ }
+ }
+
+ const TTestEnv& GetTestEnv() {
+ return *Singleton<TTestEnv>();
+ }
+}
diff --git a/library/cpp/testing/common/env.h b/library/cpp/testing/common/env.h
new file mode 100644
index 00000000000..7b89aa1bed8
--- /dev/null
+++ b/library/cpp/testing/common/env.h
@@ -0,0 +1,84 @@
+#pragma once
+
+#include <unordered_map>
+
+#include <util/folder/path.h>
+#include <util/generic/string.h>
+#include <util/generic/strbuf.h>
+#include <util/system/src_location.h>
+
+// @brief return full path to arcadia root
+TString ArcadiaSourceRoot();
+
+// @brief return full path for file or folder specified by known source location `where` and `path` which is relative to parent folder of `where`
+// for the instance: there is 2 files in folder test example_ut.cpp and example.data, so full path to test/example.data can be obtained
+// from example_ut.cpp as ArcadiaFromCurrentLocation(__SOURCE_FILE__, "example.data")
+TString ArcadiaFromCurrentLocation(TStringBuf where, TStringBuf path);
+
+// @brief return build folder path
+TString BuildRoot();
+
+// @brief return full path to built artefact, where path is relative from arcadia root
+TString BinaryPath(TStringBuf path);
+
+// @brief return true if environment is testenv otherwise false
+bool FromYaTest();
+
+// @brief returns TestsData dir (from env:ARCADIA_TESTS_DATA_DIR or path to existing folder `arcadia_tests_data` within parent folders)
+TString GetArcadiaTestsData();
+
+// @brief return current working dir (from env:TEST_WORK_PATH or cwd)
+TString GetWorkPath();
+
+// @brief return tests output path (workdir + testing_out_stuff)
+TFsPath GetOutputPath();
+
+// @brief return path from env:YA_TEST_RAM_DRIVE_PATH
+const TString& GetRamDrivePath();
+
+// @brief return path from env:YA_TEST_OUTPUT_RAM_DRIVE_PATH
+const TString& GetOutputRamDrivePath();
+
+// @brief return test parameter by name. If not exists, return an empty string
+const TString& GetTestParam(TStringBuf name);
+
+// @brief return test parameter by name. If not exists, return specified default value
+const TString& GetTestParam(TStringBuf name, const TString& def);
+
+// @brief return path to the gdb
+const TString& GdbPath();
+
+// @brief register the process. Test suite will be marked as failed if the process is terminated with a core dump file after testing
+void WatchProcessCore(int pid, const TFsPath& binaryPath, const TFsPath& cwd = TFsPath());
+
+// @brief mark the process as successfully completed - a test machinery won't try to recover core dump file for the process
+void StopProcessCoreWatching(int pid);
+
+#define SRC_(path) ArcadiaFromCurrentLocation(__SOURCE_FILE__, path)
+
+namespace NPrivate {
+ class TTestEnv {
+ public:
+ TTestEnv();
+
+ void ReInitialize();
+
+ void AddTestParam(TStringBuf name, TStringBuf value);
+
+ bool IsRunningFromTest;
+ TString ArcadiaTestsDataDir;
+ TString SourceRoot;
+ TString BuildRoot;
+ TString WorkPath;
+ TString RamDrivePath;
+ TString YtHddPath;
+ TString TestOutputRamDrivePath;
+ TString GdbPath;
+ TString CoreSearchFile;
+ std::unordered_map<TString, TString> TestParameters;
+ };
+
+ TString GetCwd();
+
+ const TTestEnv& GetTestEnv();
+}
diff --git a/library/cpp/testing/common/network.cpp b/library/cpp/testing/common/network.cpp
new file mode 100644
index 00000000000..230c50ee6de
--- /dev/null
+++ b/library/cpp/testing/common/network.cpp
@@ -0,0 +1,208 @@
+#include "network.h"
+
+#include <util/folder/dirut.h>
+#include <util/folder/path.h>
+#include <util/generic/singleton.h>
+#include <util/generic/utility.h>
+#include <util/generic/vector.h>
+#include <util/generic/ylimits.h>
+#include <util/network/address.h>
+#include <util/network/sock.h>
+#include <util/random/random.h>
+#include <util/stream/file.h>
+#include <util/string/split.h>
+#include <util/system/env.h>
+#include <util/system/error.h>
+#include <util/system/file_lock.h>
+#include <util/system/fs.h>
+
+#ifdef _darwin_
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#endif
+
+namespace {
+#define Y_VERIFY_SYSERROR(expr) \
+ do { \
+ if (!(expr)) { \
+ Y_FAIL(#expr ", errno=%d", LastSystemError()); \
+ } \
+ } while (false)
+
+ class TPortGuard : public NTesting::IPort {
+ public:
+ TPortGuard(ui16 port, THolder<TFileLock> lock)
+ : Lock_(std::move(lock))
+ , Port_(port)
+ {
+ }
+
+ ~TPortGuard() override {
+ Y_VERIFY_SYSERROR(NFs::Remove(Lock_->GetName()));
+ }
+
+ ui16 Get() override {
+ return Port_;
+ }
+
+ private:
+ THolder<TFileLock> Lock_;
+ ui16 Port_;
+ };
+
+ std::pair<ui16, ui16> GetEphemeralRange() {
+ // IANA suggestion
+ std::pair<ui16, ui16> pair{(1 << 15) + (1 << 14), (1 << 16) - 1};
+ #ifdef _linux_
+ if (NFs::Exists("/proc/sys/net/ipv4/ip_local_port_range")) {
+ TIFStream fileStream("/proc/sys/net/ipv4/ip_local_port_range");
+ fileStream >> pair.first >> pair.second;
+ }
+ #endif
+ #ifdef _darwin_
+ ui32 first, last;
+ size_t size;
+ sysctlbyname("net.inet.ip.portrange.first", &first, &size, NULL, 0);
+ sysctlbyname("net.inet.ip.portrange.last", &last, &size, NULL, 0);
+ pair.first = first;
+ pair.second = last;
+ #endif
+ return pair;
+ }
+
+ TVector<std::pair<ui16, ui16>> GetPortRanges() {
+ TString givenRange = GetEnv("VALID_PORT_RANGE");
+ TVector<std::pair<ui16, ui16>> ranges;
+ if (givenRange.Contains(':')) {
+ auto res = StringSplitter(givenRange).Split(':').Limit(2).ToList<TString>();
+ ranges.emplace_back(FromString<ui16>(res.front()), FromString<ui16>(res.back()));
+ } else {
+ const ui16 firstValid = 1025;
+ const ui16 lastValid = Max<ui16>();
+
+ auto [firstEphemeral, lastEphemeral] = GetEphemeralRange();
+ const ui16 firstInvalid = Max(firstEphemeral, firstValid);
+ const ui16 lastInvalid = Min(lastEphemeral, lastValid);
+
+ if (firstInvalid > firstValid)
+ ranges.emplace_back(firstValid, firstInvalid - 1);
+ if (lastInvalid < lastValid)
+ ranges.emplace_back(lastInvalid + 1, lastValid);
+ }
+ return ranges;
+ }
+
+ class TPortManager {
+ static constexpr size_t Retries = 20;
+ public:
+ TPortManager()
+ : SyncDir_(GetEnv("PORT_SYNC_PATH"))
+ , Ranges_(GetPortRanges())
+ , TotalCount_(0)
+ {
+ if (!SyncDir_.IsDefined()) {
+ SyncDir_ = TFsPath(GetSystemTempDir()) / "yandex_port_locks";
+ }
+ Y_VERIFY(SyncDir_.IsDefined());
+ NFs::MakeDirectoryRecursive(SyncDir_);
+
+ for (auto [left, right] : Ranges_) {
+ TotalCount_ += right - left;
+ }
+ Y_VERIFY(0 != TotalCount_);
+ }
+
+ NTesting::TPortHolder GetFreePort() const {
+ ui16 salt = RandomNumber<ui16>();
+ for (ui16 attempt = 0; attempt < TotalCount_; ++attempt) {
+ ui16 probe = (salt + attempt) % TotalCount_;
+
+ for (auto [left, right] : Ranges_) {
+ if (probe >= right - left)
+ probe -= right - left;
+ else {
+ probe += left;
+ break;
+ }
+ }
+
+ auto port = TryAcquirePort(probe);
+ if (port) {
+ return NTesting::TPortHolder{std::move(port)};
+ }
+ }
+
+ Y_FAIL("Cannot get free port!");
+ }
+
+ TVector<NTesting::TPortHolder> GetFreePortsRange(size_t count) const {
+ Y_VERIFY(count > 0);
+ TVector<NTesting::TPortHolder> ports(Reserve(count));
+ for (size_t i = 0; i < Retries; ++i) {
+ for (auto[left, right] : Ranges_) {
+ if (right - left < count) {
+ continue;
+ }
+ ui16 start = left + RandomNumber<ui16>((right - left) / 2);
+ if (right - start < count) {
+ continue;
+ }
+ for (ui16 probe = start; probe < right; ++probe) {
+ auto port = TryAcquirePort(probe);
+ if (port) {
+ ports.emplace_back(std::move(port));
+ } else {
+ ports.clear();
+ }
+ if (ports.size() == count) {
+ return ports;
+ }
+ }
+ // Can't find required number of ports without gap in the current range
+ ports.clear();
+ }
+ }
+ Y_FAIL("Cannot get range of %zu ports!", count);
+ }
+
+ private:
+ THolder<NTesting::IPort> TryAcquirePort(ui16 port) const {
+ auto lock = MakeHolder<TFileLock>(TString(SyncDir_ / ::ToString(port)));
+ if (!lock->TryAcquire()) {
+ return nullptr;
+ }
+
+ TInet6StreamSocket sock;
+ Y_VERIFY_SYSERROR(INVALID_SOCKET != static_cast<SOCKET>(sock));
+
+ TSockAddrInet6 addr("::", port);
+ if (sock.Bind(&addr) != 0) {
+ lock->Release();
+ Y_VERIFY(EADDRINUSE == LastSystemError(), "unexpected error: %d", LastSystemError());
+ return nullptr;
+ }
+ return MakeHolder<TPortGuard>(port, std::move(lock));
+ }
+
+ private:
+ TFsPath SyncDir_;
+ TVector<std::pair<ui16, ui16>> Ranges_;
+ size_t TotalCount_;
+ };
+}
+
+namespace NTesting {
+ TPortHolder GetFreePort() {
+ return Singleton<TPortManager>()->GetFreePort();
+ }
+
+ namespace NLegacy {
+ TVector<TPortHolder> GetFreePortsRange(size_t count) {
+ return Singleton<TPortManager>()->GetFreePortsRange(count);
+ }
+ }
+
+ IOutputStream& operator<<(IOutputStream& out, const TPortHolder& port) {
+ return out << static_cast<ui16>(port);
+ }
+}
diff --git a/library/cpp/testing/common/network.h b/library/cpp/testing/common/network.h
new file mode 100644
index 00000000000..eb4d32f3a1b
--- /dev/null
+++ b/library/cpp/testing/common/network.h
@@ -0,0 +1,52 @@
+#pragma once
+
+#include <util/generic/ptr.h>
+#include <util/generic/vector.h>
+
+namespace NTesting {
+
+ //@brief network port holder interface
+ class IPort {
+ public:
+ virtual ~IPort() {}
+
+ virtual ui16 Get() = 0;
+ };
+
+ class TPortHolder : private THolder<IPort> {
+ using TBase = THolder<IPort>;
+ public:
+ using TBase::TBase;
+ using TBase::Release;
+ using TBase::Reset;
+
+ operator ui16() const& {
+ return (*this)->Get();
+ }
+
+ operator ui16() const&& = delete;
+ };
+
+ IOutputStream& operator<<(IOutputStream& out, const TPortHolder& port);
+
+ //@brief Get first free port.
+ [[nodiscard]] TPortHolder GetFreePort();
+
+ namespace NLegacy {
+ // Do not use this method, it needs only for TPortManager from unittests.
+ // Returns continuous sequence of the specified number of ports.
+ [[nodiscard]] TVector<TPortHolder> GetFreePortsRange(size_t count);
+ }
+
+ //@brief helper class for inheritance
+ struct TFreePortOwner {
+ TFreePortOwner() : Port_(GetFreePort()) {}
+
+ ui16 GetPort() const {
+ return Port_;
+ }
+
+ private:
+ TPortHolder Port_;
+ };
+}
diff --git a/library/cpp/testing/common/probe.cpp b/library/cpp/testing/common/probe.cpp
new file mode 100644
index 00000000000..73f2fb63605
--- /dev/null
+++ b/library/cpp/testing/common/probe.cpp
@@ -0,0 +1 @@
+#include "probe.h"
diff --git a/library/cpp/testing/common/probe.h b/library/cpp/testing/common/probe.h
new file mode 100644
index 00000000000..19910979b56
--- /dev/null
+++ b/library/cpp/testing/common/probe.h
@@ -0,0 +1,140 @@
+#pragma once
+
+#include <util/system/yassert.h>
+
+namespace NTesting {
+ ////////////////////////////////////////////////////////////////////////////////
+
+ // Below there is a serie of probe classes for testing construction/destruction copying/moving of class.
+ // for examples see tests in probe_ut.cpp
+
+ struct TProbeState {
+ int Constructors = 0;
+ int Destructors = 0;
+ int ShadowDestructors = 0;
+ int CopyConstructors = 0;
+ int CopyAssignments = 0;
+ int MoveConstructors = 0;
+ int MoveAssignments = 0;
+ int Touches = 0;
+
+ TProbeState() = default;
+
+ void Reset() {
+ *this = TProbeState{};
+ }
+ };
+
+ // Used for probing the number of copies that occur if a type must be coerced.
+ class TCoercibleToProbe {
+ public:
+ TProbeState* State;
+ TProbeState* ShadowState;
+
+ public:
+ explicit TCoercibleToProbe(TProbeState* state)
+ : State(state)
+ , ShadowState(state)
+ {}
+
+ private:
+ TCoercibleToProbe(const TCoercibleToProbe&);
+ TCoercibleToProbe(TCoercibleToProbe&&);
+ TCoercibleToProbe& operator=(const TCoercibleToProbe&);
+ TCoercibleToProbe& operator=(TCoercibleToProbe&&);
+ };
+
+ // Used for probing the number of copies in an argument.
+ class TProbe {
+ public:
+ TProbeState* State;
+ TProbeState* ShadowState;
+
+ public:
+ static TProbe ExplicitlyCreateInvalidProbe() {
+ return TProbe();
+ }
+
+ explicit TProbe(TProbeState* state)
+ : State(state)
+ , ShadowState(state)
+ {
+ Y_ASSERT(State);
+ ++State->Constructors;
+ }
+
+ ~TProbe() {
+ if (State) {
+ ++State->Destructors;
+ }
+ if (ShadowState) {
+ ++ShadowState->ShadowDestructors;
+ }
+ }
+
+ TProbe(const TProbe& other)
+ : State(other.State)
+ , ShadowState(other.ShadowState)
+ {
+ Y_ASSERT(State);
+ ++State->CopyConstructors;
+ }
+
+ TProbe(TProbe&& other)
+ : State(other.State)
+ , ShadowState(other.ShadowState)
+ {
+ Y_ASSERT(State);
+ other.State = nullptr;
+ ++State->MoveConstructors;
+ }
+
+ TProbe(const TCoercibleToProbe& other)
+ : State(other.State)
+ , ShadowState(other.ShadowState)
+ {
+ Y_ASSERT(State);
+ ++State->CopyConstructors;
+ }
+
+ TProbe(TCoercibleToProbe&& other)
+ : State(other.State)
+ , ShadowState(other.ShadowState)
+ {
+ Y_ASSERT(State);
+ other.State = nullptr;
+ ++State->MoveConstructors;
+ }
+
+ TProbe& operator=(const TProbe& other) {
+ State = other.State;
+ ShadowState = other.ShadowState;
+ Y_ASSERT(State);
+ ++State->CopyAssignments;
+ return *this;
+ }
+
+ TProbe& operator=(TProbe&& other) {
+ State = other.State;
+ ShadowState = other.ShadowState;
+ Y_ASSERT(State);
+ other.State = nullptr;
+ ++State->MoveAssignments;
+ return *this;
+ }
+
+ void Touch() const {
+ Y_ASSERT(State);
+ ++State->Touches;
+ }
+
+ bool IsValid() const {
+ return nullptr != State;
+ }
+
+ private:
+ TProbe()
+ : State(nullptr)
+ {}
+ };
+} // namespace NTesting
diff --git a/library/cpp/testing/common/scope.cpp b/library/cpp/testing/common/scope.cpp
new file mode 100644
index 00000000000..c70d695c1b9
--- /dev/null
+++ b/library/cpp/testing/common/scope.cpp
@@ -0,0 +1 @@
+#include "scope.h"
diff --git a/library/cpp/testing/common/scope.h b/library/cpp/testing/common/scope.h
new file mode 100644
index 00000000000..a2ca0e77e4c
--- /dev/null
+++ b/library/cpp/testing/common/scope.h
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <util/generic/string.h>
+#include <util/generic/vector.h>
+#include <util/system/env.h>
+
+#include <utility>
+
+namespace NTesting {
+ // @brief Assigns new values to the given environment variables and restores old values upon destruction.
+ // @note if there was no env variable with given name, it will be set to empty string upon destruction IGNIETFERRO-1486
+ struct TScopedEnvironment {
+ TScopedEnvironment(const TString& name, const TString& value)
+ : PreviousState{1, {name, ::GetEnv(name)}}
+ {
+ ::SetEnv(name, value);
+ }
+
+ TScopedEnvironment(const TVector<std::pair<TString, TString>>& vars)
+ : PreviousState(Reserve(vars.size()))
+ {
+ for (const auto& [k, v] : vars) {
+ PreviousState.emplace_back(k, ::GetEnv(k));
+ ::SetEnv(k, v);
+ }
+ }
+
+ ~TScopedEnvironment() {
+ for (const auto& [k, v] : PreviousState) {
+ ::SetEnv(k, v);
+ }
+ }
+
+ TScopedEnvironment(const TScopedEnvironment&) = delete;
+ TScopedEnvironment& operator=(const TScopedEnvironment&) = delete;
+ private:
+ TVector<std::pair<TString, TString>> PreviousState;
+ };
+}
diff --git a/library/cpp/testing/common/ut/env_ut.cpp b/library/cpp/testing/common/ut/env_ut.cpp
new file mode 100644
index 00000000000..2aed1e4a255
--- /dev/null
+++ b/library/cpp/testing/common/ut/env_ut.cpp
@@ -0,0 +1,162 @@
+#include <library/cpp/testing/common/env.h>
+#include <library/cpp/testing/common/scope.h>
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <util/folder/dirut.h>
+#include <util/stream/file.h>
+#include <util/system/env.h>
+#include <util/system/execpath.h>
+#include <util/system/fs.h>
+
+
+TEST(Runtime, ArcadiaSourceRoot) {
+ NTesting::TScopedEnvironment contextGuard("YA_TEST_CONTEXT_FILE", ""); // remove context filename
+ {
+ auto tmpDir = ::GetSystemTempDir();
+ NTesting::TScopedEnvironment guard("ARCADIA_SOURCE_ROOT", tmpDir);
+ Singleton<NPrivate::TTestEnv>()->ReInitialize();
+ EXPECT_EQ(tmpDir, ArcadiaSourceRoot());
+ }
+ {
+ NTesting::TScopedEnvironment guard("ARCADIA_SOURCE_ROOT", "");
+ Singleton<NPrivate::TTestEnv>()->ReInitialize();
+ EXPECT_FALSE(ArcadiaSourceRoot().empty());
+ }
+}
+
+TEST(Runtime, BuildRoot) {
+ NTesting::TScopedEnvironment contextGuard("YA_TEST_CONTEXT_FILE", ""); // remove context filename
+ {
+ auto tmpDir = ::GetSystemTempDir();
+ NTesting::TScopedEnvironment guard("ARCADIA_BUILD_ROOT", tmpDir);
+ Singleton<NPrivate::TTestEnv>()->ReInitialize();
+ EXPECT_EQ(tmpDir, BuildRoot());
+ }
+ {
+ NTesting::TScopedEnvironment guard("ARCADIA_BUILD_ROOT", "");
+ Singleton<NPrivate::TTestEnv>()->ReInitialize();
+ EXPECT_FALSE(BuildRoot().empty());
+ }
+}
+
+TEST(Runtime, BinaryPath) {
+ NTesting::TScopedEnvironment contextGuard("YA_TEST_CONTEXT_FILE", ""); // remove context filename
+ Singleton<NPrivate::TTestEnv>()->ReInitialize();
+ EXPECT_TRUE(TFsPath(BinaryPath("library/cpp/testing/common/ut")).Exists());
+}
+
+TEST(Runtime, GetArcadiaTestsData) {
+ NTesting::TScopedEnvironment contextGuard("YA_TEST_CONTEXT_FILE", ""); // remove context filename
+ {
+ auto tmpDir = ::GetSystemTempDir();
+ NTesting::TScopedEnvironment guard("ARCADIA_TESTS_DATA_DIR", tmpDir);
+ Singleton<NPrivate::TTestEnv>()->ReInitialize();
+ EXPECT_EQ(tmpDir, GetArcadiaTestsData());
+ }
+ {
+ NTesting::TScopedEnvironment guard("ARCADIA_TESTS_DATA_DIR", "");
+ Singleton<NPrivate::TTestEnv>()->ReInitialize();
+ auto path = GetArcadiaTestsData();
+ // it is not error if path is empty
+ const bool ok = (path.empty() || GetBaseName(path) == "arcadia_tests_data");
+ EXPECT_TRUE(ok);
+ }
+}
+
+TEST(Runtime, GetWorkPath) {
+ NTesting::TScopedEnvironment contextGuard("YA_TEST_CONTEXT_FILE", ""); // remove context filename
+ {
+ auto tmpDir = ::GetSystemTempDir();
+ NTesting::TScopedEnvironment guard("TEST_WORK_PATH", tmpDir);
+ Singleton<NPrivate::TTestEnv>()->ReInitialize();
+ EXPECT_EQ(tmpDir, GetWorkPath());
+ }
+ {
+ NTesting::TScopedEnvironment guard("TEST_WORK_PATH", "");
+ Singleton<NPrivate::TTestEnv>()->ReInitialize();
+ EXPECT_TRUE(!GetWorkPath().empty());
+ }
+}
+
+TEST(Runtime, GetOutputPath) {
+ NTesting::TScopedEnvironment contextGuard("YA_TEST_CONTEXT_FILE", ""); // remove context filename
+ Singleton<NPrivate::TTestEnv>()->ReInitialize();
+ EXPECT_EQ(GetOutputPath().Basename(), "testing_out_stuff");
+}
+
+TEST(Runtime, GetRamDrivePath) {
+ NTesting::TScopedEnvironment contextGuard("YA_TEST_CONTEXT_FILE", ""); // remove context filename
+ auto tmpDir = ::GetSystemTempDir();
+ NTesting::TScopedEnvironment guard("YA_TEST_RAM_DRIVE_PATH", tmpDir);
+ Singleton<NPrivate::TTestEnv>()->ReInitialize();
+ EXPECT_EQ(tmpDir, GetRamDrivePath());
+}
+
+TEST(Runtime, GetOutputRamDrivePath) {
+ NTesting::TScopedEnvironment contextGuard("YA_TEST_CONTEXT_FILE", ""); // remove context filename
+ auto tmpDir = ::GetSystemTempDir();
+ NTesting::TScopedEnvironment guard("YA_TEST_OUTPUT_RAM_DRIVE_PATH", tmpDir);
+ Singleton<NPrivate::TTestEnv>()->ReInitialize();
+ EXPECT_EQ(tmpDir, GetOutputRamDrivePath());
+}
+
+#ifdef _linux_
+TEST(Runtime, GdbPath) {
+ Singleton<NPrivate::TTestEnv>()->ReInitialize();
+ EXPECT_TRUE(NFs::Exists(::GdbPath()));
+}
+#endif
+
+TString ReInitializeContext(TStringBuf data) {
+ auto tmpDir = ::GetSystemTempDir();
+ auto filename = tmpDir + "/context.json";
+ TOFStream stream(filename);
+ stream.Write(data.data(), data.size());
+ stream.Finish();
+
+ NTesting::TScopedEnvironment contextGuard("YA_TEST_CONTEXT_FILE", filename);
+ Singleton<NPrivate::TTestEnv>()->ReInitialize();
+
+ return filename;
+}
+
+TEST(Runtime, GetTestParam) {
+ TString context = R"json({
+ "runtime": {
+ "test_params": {
+ "a": "b",
+ "c": "d"
+ }
+ }
+ })json";
+ auto filename = ReInitializeContext(context);
+
+ EXPECT_EQ("b", GetTestParam("a"));
+ EXPECT_EQ("d", GetTestParam("c"));
+ EXPECT_EQ("", GetTestParam("e"));
+ EXPECT_EQ("w", GetTestParam("e", "w"));
+
+ Singleton<NPrivate::TTestEnv>()->AddTestParam("e", "e");
+ EXPECT_EQ("e", GetTestParam("e"));
+}
+
+TEST(Runtime, WatchProcessCore) {
+ TString context = R"json({
+ "internal": {
+ "core_search_file": "watch_core.txt"
+ }
+ })json";
+ auto filename = ReInitializeContext(context);
+
+ WatchProcessCore(1, "bin1", "pwd");
+ WatchProcessCore(2, "bin1");
+ StopProcessCoreWatching(2);
+
+ TIFStream file("watch_core.txt");
+ auto data = file.ReadAll();
+ TString expected = R"json({"cmd":"add","pid":1,"binary_path":"bin1","cwd":"pwd"}
+{"cmd":"add","pid":2,"binary_path":"bin1"}
+{"cmd":"drop","pid":2}
+)json";
+ EXPECT_EQ(expected, data);
+}
diff --git a/library/cpp/testing/common/ut/network_ut.cpp b/library/cpp/testing/common/ut/network_ut.cpp
new file mode 100644
index 00000000000..6a40775fd93
--- /dev/null
+++ b/library/cpp/testing/common/ut/network_ut.cpp
@@ -0,0 +1,54 @@
+#include <library/cpp/testing/common/network.h>
+#include <library/cpp/testing/common/scope.h>
+
+#include <util/generic/hash_set.h>
+
+#include <util/folder/dirut.h>
+#include <util/folder/path.h>
+#include <util/folder/tempdir.h>
+#include <util/network/sock.h>
+#include <util/system/fs.h>
+
+#include <library/cpp/testing/gtest/gtest.h>
+
+static TTempDir TmpDir;
+
+TEST(NetworkTest, FreePort) {
+ NTesting::TScopedEnvironment envGuard("PORT_SYNC_PATH", TmpDir.Name());
+
+ TVector<NTesting::TPortHolder> ports(Reserve(100));
+
+ for (size_t i = 0; i < 100; ++i) {
+ ports.push_back(NTesting::GetFreePort());
+ }
+
+ THashSet<ui16> uniqPorts;
+ for (auto& port : ports) {
+ const TString guardPath = TmpDir.Path() / ToString(static_cast<ui16>(port));
+ EXPECT_TRUE(NFs::Exists(guardPath));
+ EXPECT_TRUE(uniqPorts.emplace(port).second);
+
+ TInetStreamSocket sock;
+ TSockAddrInet addr(TIpHost{INADDR_ANY}, port);
+ ASSERT_EQ(0, SetSockOpt(sock, SOL_SOCKET, SO_REUSEADDR, 1));
+ EXPECT_EQ(0, sock.Bind(&addr));
+ }
+ ports.clear();
+ for (ui16 port : uniqPorts) {
+ const TString guardPath = TmpDir.Path() / ToString(port);
+ EXPECT_FALSE(NFs::Exists(guardPath));
+ }
+}
+
+
+TEST(FreePortTest, FreePortsRange) {
+ NTesting::TScopedEnvironment envGuard("PORT_SYNC_PATH", TmpDir.Name());
+
+ for (ui16 i = 2; i < 10; ++i) {
+ TVector<NTesting::TPortHolder> ports = NTesting::NLegacy::GetFreePortsRange(i);
+ ASSERT_EQ(i, ports.size());
+ for (ui16 j = 1; j < i; ++j) {
+ EXPECT_EQ(static_cast<ui16>(ports[j]), static_cast<ui16>(ports[0]) + j);
+ }
+ }
+}
diff --git a/library/cpp/testing/common/ut/scope_ut.cpp b/library/cpp/testing/common/ut/scope_ut.cpp
new file mode 100644
index 00000000000..4fb82c2466d
--- /dev/null
+++ b/library/cpp/testing/common/ut/scope_ut.cpp
@@ -0,0 +1,28 @@
+#include <library/cpp/testing/common/scope.h>
+
+#include <util/system/env.h>
+
+#include <library/cpp/testing/gtest/gtest.h>
+
+TEST(TScopedEnvironment, SingleValue) {
+ auto before = GetEnv("ARCADIA_SOURCE_ROOT");
+ {
+ NTesting::TScopedEnvironment guard("ARCADIA_SOURCE_ROOT", "source");
+ EXPECT_EQ("source", GetEnv("ARCADIA_SOURCE_ROOT"));
+ }
+ EXPECT_EQ(before, GetEnv("ARCADIA_SOURCE_ROOT"));
+}
+
+TEST(TScopedEnvironment, MultiValue) {
+ TVector<TString> before{GetEnv("ARCADIA_SOURCE_ROOT"), GetEnv("ARCADIA_BUILD_ROOT")};
+ {
+ NTesting::TScopedEnvironment guard{{
+ {"ARCADIA_SOURCE_ROOT", "source"},
+ {"ARCADIA_BUILD_ROOT", "build"},
+ }};
+ EXPECT_EQ("source", GetEnv("ARCADIA_SOURCE_ROOT"));
+ EXPECT_EQ("build", GetEnv("ARCADIA_BUILD_ROOT"));
+ }
+ TVector<TString> after{GetEnv("ARCADIA_SOURCE_ROOT"), GetEnv("ARCADIA_BUILD_ROOT")};
+ EXPECT_EQ(before, after);
+}
diff --git a/library/cpp/testing/common/ut/ya.make b/library/cpp/testing/common/ut/ya.make
new file mode 100644
index 00000000000..053aa380791
--- /dev/null
+++ b/library/cpp/testing/common/ut/ya.make
@@ -0,0 +1,19 @@
+GTEST()
+OWNER(
+ amatanhead
+ bulatman
+ thegeorg
+ g:cpp-contrib
+)
+
+SRCS(
+ env_ut.cpp
+ network_ut.cpp
+ scope_ut.cpp
+)
+
+PEERDIR(
+ library/cpp/testing/common
+)
+
+END()
diff --git a/library/cpp/testing/common/ya.make b/library/cpp/testing/common/ya.make
new file mode 100644
index 00000000000..2f4b0ce26ed
--- /dev/null
+++ b/library/cpp/testing/common/ya.make
@@ -0,0 +1,23 @@
+LIBRARY()
+
+OWNER(
+ amatanhead
+ bulatman
+ thegeorg
+ g:cpp-contrib
+)
+
+SRCS(
+ env.cpp
+ network.cpp
+ probe.cpp
+ scope.cpp
+)
+
+PEERDIR(
+ library/cpp/json
+)
+
+END()
+
+RECURSE_FOR_TESTS(ut)