diff options
author | Devtools Arcadia <arcadia-devtools@yandex-team.ru> | 2022-02-07 18:08:42 +0300 |
---|---|---|
committer | Devtools Arcadia <arcadia-devtools@mous.vla.yp-c.yandex.net> | 2022-02-07 18:08:42 +0300 |
commit | 1110808a9d39d4b808aef724c861a2e1a38d2a69 (patch) | |
tree | e26c9fed0de5d9873cce7e00bc214573dc2195b7 /library/cpp/testing/common | |
download | ydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz |
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/testing/common')
-rw-r--r-- | library/cpp/testing/common/env.cpp | 275 | ||||
-rw-r--r-- | library/cpp/testing/common/env.h | 84 | ||||
-rw-r--r-- | library/cpp/testing/common/network.cpp | 208 | ||||
-rw-r--r-- | library/cpp/testing/common/network.h | 52 | ||||
-rw-r--r-- | library/cpp/testing/common/probe.cpp | 1 | ||||
-rw-r--r-- | library/cpp/testing/common/probe.h | 140 | ||||
-rw-r--r-- | library/cpp/testing/common/scope.cpp | 1 | ||||
-rw-r--r-- | library/cpp/testing/common/scope.h | 39 | ||||
-rw-r--r-- | library/cpp/testing/common/ut/env_ut.cpp | 162 | ||||
-rw-r--r-- | library/cpp/testing/common/ut/network_ut.cpp | 54 | ||||
-rw-r--r-- | library/cpp/testing/common/ut/scope_ut.cpp | 28 | ||||
-rw-r--r-- | library/cpp/testing/common/ut/ya.make | 19 | ||||
-rw-r--r-- | library/cpp/testing/common/ya.make | 23 |
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) |