aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/testing/gtest_extensions
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/gtest_extensions
downloadydb-1110808a9d39d4b808aef724c861a2e1a38d2a69.tar.gz
intermediate changes
ref:cde9a383711a11544ce7e107a78147fb96cc4029
Diffstat (limited to 'library/cpp/testing/gtest_extensions')
-rw-r--r--library/cpp/testing/gtest_extensions/README.md5
-rw-r--r--library/cpp/testing/gtest_extensions/assertions.cpp90
-rw-r--r--library/cpp/testing/gtest_extensions/assertions.h111
-rw-r--r--library/cpp/testing/gtest_extensions/gtest_extensions.cpp1
-rw-r--r--library/cpp/testing/gtest_extensions/gtest_extensions.h6
-rw-r--r--library/cpp/testing/gtest_extensions/matchers.cpp1
-rw-r--r--library/cpp/testing/gtest_extensions/matchers.h132
-rw-r--r--library/cpp/testing/gtest_extensions/pretty_printers.cpp1
-rw-r--r--library/cpp/testing/gtest_extensions/pretty_printers.h84
-rw-r--r--library/cpp/testing/gtest_extensions/probe.cpp13
-rw-r--r--library/cpp/testing/gtest_extensions/probe.h81
-rw-r--r--library/cpp/testing/gtest_extensions/ut/README.md1
-rw-r--r--library/cpp/testing/gtest_extensions/ut/gtest_extensions_ut.cpp346
-rw-r--r--library/cpp/testing/gtest_extensions/ut/probe_ut.cpp54
-rw-r--r--library/cpp/testing/gtest_extensions/ut/ya.make20
-rw-r--r--library/cpp/testing/gtest_extensions/ya.make26
16 files changed, 972 insertions, 0 deletions
diff --git a/library/cpp/testing/gtest_extensions/README.md b/library/cpp/testing/gtest_extensions/README.md
new file mode 100644
index 0000000000..5445c7a464
--- /dev/null
+++ b/library/cpp/testing/gtest_extensions/README.md
@@ -0,0 +1,5 @@
+# Extensions for Gtest and Gmock
+
+Extensions that enable better support of util types in gtest and gmock: pretty printers, matchers, some convenience macros.
+
+If you're using `GTEST`, include `library/cpp/testing/gtest/gtest.h` and it will automatically enable these extensions. This is the preferred way to include gtest and gmock as opposed to including gtest, gmock and extensions directly. It eliminates chances of forgetting to include extensions.
diff --git a/library/cpp/testing/gtest_extensions/assertions.cpp b/library/cpp/testing/gtest_extensions/assertions.cpp
new file mode 100644
index 0000000000..f390409d1b
--- /dev/null
+++ b/library/cpp/testing/gtest_extensions/assertions.cpp
@@ -0,0 +1,90 @@
+#include "assertions.h"
+
+#include <util/string/builder.h>
+#include <util/string/split.h>
+#include <util/system/type_name.h>
+
+namespace NGTest::NInternal {
+ namespace {
+ void FormatActual(const std::exception& err, const TBackTrace* bt, TStringBuilder& out) {
+ out << "an exception of type " << TypeName(err) << " "
+ << "with message " << TString(err.what()).Quote() << ".";
+ if (bt) {
+ out << "\n Trace: ";
+ for (auto& line: StringSplitter(bt->PrintToString()).Split('\n')) {
+ out << " " << line.Token() << "\n";
+ }
+ }
+ }
+
+ void FormatActual(TStringBuilder& out) {
+ out << " Actual: it throws ";
+ auto exceptionPtr = std::current_exception();
+ if (exceptionPtr) {
+ try {
+ std::rethrow_exception(exceptionPtr);
+ } catch (const yexception& err) {
+ FormatActual(err, err.BackTrace(), out);
+ return;
+ } catch (const std::exception& err) {
+ FormatActual(err, nullptr, out);
+ return;
+ } catch (...) {
+ out << "an unknown exception.";
+ return;
+ }
+ }
+ out << "nothing.";
+ }
+
+ void FormatExpected(const char* statement, const char* type, const TString& contains, TStringBuilder& out) {
+ out << "Expected: ";
+ if (TStringBuf(statement).size() > 80) {
+ out << "statement";
+ } else {
+ out << statement;
+ }
+ out << " throws an exception of type " << type;
+
+ if (!contains.empty()) {
+ out << " with message containing " << contains.Quote();
+ }
+
+ out << ".";
+ }
+ }
+
+ TString FormatErrorWrongException(const char* statement, const char* type) {
+ return FormatErrorWrongException(statement, type, "");
+ }
+
+ TString FormatErrorWrongException(const char* statement, const char* type, TString contains) {
+ TStringBuilder out;
+
+ FormatExpected(statement, type, contains, out);
+ out << "\n";
+ FormatActual(out);
+
+ return out;
+ }
+
+ TString FormatErrorUnexpectedException(const char* statement) {
+ TStringBuilder out;
+
+ out << "Expected: ";
+ if (TStringBuf(statement).size() > 80) {
+ out << "statement";
+ } else {
+ out << statement;
+ }
+ out << " doesn't throw an exception.\n ";
+
+ FormatActual(out);
+
+ return out;
+ }
+
+ bool ExceptionMessageContains(const std::exception& err, TString contains) {
+ return TStringBuf(err.what()).Contains(contains);
+ }
+}
diff --git a/library/cpp/testing/gtest_extensions/assertions.h b/library/cpp/testing/gtest_extensions/assertions.h
new file mode 100644
index 0000000000..e8ea07b5df
--- /dev/null
+++ b/library/cpp/testing/gtest_extensions/assertions.h
@@ -0,0 +1,111 @@
+#pragma once
+
+#include <util/generic/string.h>
+
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+/**
+ * Check that the given statement throws an exception of the given type,
+ * and that the thrown exception message contains the given substring.
+ */
+#define EXPECT_THROW_MESSAGE_HAS_SUBSTR(statement, expectedException, substring) \
+ _Y_GTEST_EXPECT_THROW_MESSAGE_HAS_SUBSTR_IMPL_(statement, expectedException, substring, GTEST_NONFATAL_FAILURE_)
+
+/**
+ * Check that the given statement throws an exception of the given type,
+ * and that the thrown exception message contains the given substring.
+ */
+#define ASSERT_THROW_MESSAGE_HAS_SUBSTR(statement, expectedException, substring) \
+ _Y_GTEST_EXPECT_THROW_MESSAGE_HAS_SUBSTR_IMPL_(statement, expectedException, substring, GTEST_FATAL_FAILURE_)
+
+
+// Improve default macros. New implementation shows better exception messages.
+// See https://github.com/google/googletest/issues/2878
+
+#undef EXPECT_THROW
+#define EXPECT_THROW(statement, expectedException) \
+ _Y_GTEST_EXPECT_THROW_IMPL_(statement, expectedException, GTEST_NONFATAL_FAILURE_)
+
+#undef ASSERT_THROW
+#define ASSERT_THROW(statement, expectedException) \
+ _Y_GTEST_EXPECT_THROW_IMPL_(statement, expectedException, GTEST_FATAL_FAILURE_)
+
+#undef EXPECT_NO_THROW
+#define EXPECT_NO_THROW(statement) \
+ _Y_GTEST_EXPECT_NO_THROW_IMPL_(statement, GTEST_NONFATAL_FAILURE_)
+
+#undef ASSERT_NO_THROW
+#define ASSERT_NO_THROW(statement) \
+ _Y_GTEST_EXPECT_NO_THROW_IMPL_(statement, GTEST_FATAL_FAILURE_)
+
+
+// Implementation details
+
+namespace NGTest::NInternal {
+ TString FormatErrorWrongException(const char* statement, const char* type);
+ TString FormatErrorWrongException(const char* statement, const char* type, TString contains);
+ TString FormatErrorUnexpectedException(const char* statement);
+ bool ExceptionMessageContains(const std::exception& err, TString contains);
+}
+
+#define _Y_GTEST_EXPECT_THROW_IMPL_(statement, expectedException, fail) \
+ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+ if (::TString gtestMsg = ""; ::testing::internal::AlwaysTrue()) { \
+ bool gtestCaughtExpected = false; \
+ try { \
+ GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
+ } catch (expectedException const&) { \
+ gtestCaughtExpected = true; \
+ } catch (...) { \
+ gtestMsg = ::NGTest::NInternal::FormatErrorWrongException( \
+ #statement, #expectedException); \
+ goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \
+ } if (!gtestCaughtExpected) { \
+ gtestMsg = ::NGTest::NInternal::FormatErrorWrongException( \
+ #statement, #expectedException); \
+ goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \
+ } \
+ } else \
+ GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__): \
+ fail(gtestMsg.c_str())
+
+#define _Y_GTEST_EXPECT_THROW_MESSAGE_HAS_SUBSTR_IMPL_(statement, expectedException, substring, fail) \
+ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+ if (::TString gtestMsg = ""; ::testing::internal::AlwaysTrue()) { \
+ bool gtestCaughtExpected = false; \
+ ::TString gtestSubstring{substring}; \
+ try { \
+ GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
+ } catch (expectedException const& gtestError) { \
+ if (!::NGTest::NInternal::ExceptionMessageContains(gtestError, gtestSubstring)) { \
+ gtestMsg = ::NGTest::NInternal::FormatErrorWrongException( \
+ #statement, #expectedException, gtestSubstring); \
+ goto GTEST_CONCAT_TOKEN_(gtest_label_testthrowsubstr_, __LINE__); \
+ } \
+ gtestCaughtExpected = true; \
+ } catch (...) { \
+ gtestMsg = ::NGTest::NInternal::FormatErrorWrongException( \
+ #statement, #expectedException, gtestSubstring); \
+ goto GTEST_CONCAT_TOKEN_(gtest_label_testthrowsubstr_, __LINE__); \
+ } if (!gtestCaughtExpected) { \
+ gtestMsg = ::NGTest::NInternal::FormatErrorWrongException( \
+ #statement, #expectedException, gtestSubstring); \
+ goto GTEST_CONCAT_TOKEN_(gtest_label_testthrowsubstr_, __LINE__); \
+ } \
+ } else \
+ GTEST_CONCAT_TOKEN_(gtest_label_testthrowsubstr_, __LINE__): \
+ fail(gtestMsg.c_str())
+
+#define _Y_GTEST_EXPECT_NO_THROW_IMPL_(statement, fail) \
+ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
+ if (::TString gtestMsg = ""; ::testing::internal::AlwaysTrue()) { \
+ try { \
+ GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \
+ } catch (...) { \
+ gtestMsg = ::NGTest::NInternal::FormatErrorUnexpectedException(#statement); \
+ goto GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__); \
+ } \
+ } else \
+ GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__): \
+ fail(gtestMsg.c_str())
diff --git a/library/cpp/testing/gtest_extensions/gtest_extensions.cpp b/library/cpp/testing/gtest_extensions/gtest_extensions.cpp
new file mode 100644
index 0000000000..1277a804bc
--- /dev/null
+++ b/library/cpp/testing/gtest_extensions/gtest_extensions.cpp
@@ -0,0 +1 @@
+#include "gtest_extensions.h"
diff --git a/library/cpp/testing/gtest_extensions/gtest_extensions.h b/library/cpp/testing/gtest_extensions/gtest_extensions.h
new file mode 100644
index 0000000000..e20532241e
--- /dev/null
+++ b/library/cpp/testing/gtest_extensions/gtest_extensions.h
@@ -0,0 +1,6 @@
+#pragma once
+
+#include "assertions.h"
+#include "matchers.h"
+#include "pretty_printers.h"
+#include "probe.h"
diff --git a/library/cpp/testing/gtest_extensions/matchers.cpp b/library/cpp/testing/gtest_extensions/matchers.cpp
new file mode 100644
index 0000000000..7da7be8b3c
--- /dev/null
+++ b/library/cpp/testing/gtest_extensions/matchers.cpp
@@ -0,0 +1 @@
+#include "matchers.h"
diff --git a/library/cpp/testing/gtest_extensions/matchers.h b/library/cpp/testing/gtest_extensions/matchers.h
new file mode 100644
index 0000000000..044c1c3ee4
--- /dev/null
+++ b/library/cpp/testing/gtest_extensions/matchers.h
@@ -0,0 +1,132 @@
+#pragma once
+
+#include <util/generic/string.h>
+
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+namespace testing {
+ /**
+ * When matching `const TStringBuf&`, implicitly convert other strings and string views to `Eq` matchers.
+ */
+ template <typename T, typename TT>
+ class Matcher<const TBasicStringBuf<T, TT>&>: public internal::MatcherBase<const TBasicStringBuf<T, TT>&> {
+ public:
+ Matcher() {
+ }
+
+ explicit Matcher(const MatcherInterface<const TBasicStringBuf<T, TT>&>* impl)
+ : internal::MatcherBase<const TBasicStringBuf<T, TT>&>(impl) {
+ }
+
+ template <typename M, typename = typename std::remove_reference<M>::type::is_gtest_matcher>
+ Matcher(M&& m)
+ : internal::MatcherBase<const TBasicStringBuf<T, TT>&>(std::forward<M>(m)) {
+ }
+
+ Matcher(const TBasicString<T, TT>& s) {
+ *this = Eq(TBasicStringBuf<T, TT>(s));
+ }
+
+ Matcher(const T* s) {
+ *this = Eq(TBasicStringBuf<T, TT>(s));
+ }
+
+ Matcher(TBasicStringBuf<T, TT> s) {
+ *this = Eq(s);
+ }
+ };
+
+ /**
+ * When matching `TBasicBuf`, implicitly convert other strings and string views to `Eq` matchers.
+ */
+ template <typename T, typename TT>
+ class Matcher<TBasicStringBuf<T, TT>>: public internal::MatcherBase<TBasicStringBuf<T, TT>> {
+ public:
+ Matcher() {
+ }
+
+ explicit Matcher(const MatcherInterface <TBasicStringBuf<T, TT>>* impl)
+ : internal::MatcherBase<TBasicStringBuf<T, TT>>(impl) {
+ }
+
+ explicit Matcher(const MatcherInterface<const TBasicStringBuf<T, TT>&>* impl)
+ : internal::MatcherBase<TBasicStringBuf<T, TT>>(impl) {
+ }
+
+ template <typename M, typename = typename std::remove_reference<M>::type::is_gtest_matcher>
+ Matcher(M&& m)
+ : internal::MatcherBase<TBasicStringBuf<T, TT>>(std::forward<M>(m)) {
+ }
+
+ Matcher(const TBasicString<T, TT>& s) {
+ *this = Eq(TBasicString<T, TT>(s));
+ }
+
+ Matcher(const T* s) {
+ *this = Eq(TBasicString<T, TT>(s));
+ }
+
+ Matcher(TBasicStringBuf<T, TT> s) {
+ *this = Eq(s);
+ }
+ };
+
+ /**
+ * When matching `const TString&`, implicitly convert other strings and string views to `Eq` matchers.
+ */
+ template <typename T, typename TT>
+ class Matcher<const TBasicString<T, TT>&>: public internal::MatcherBase<const TBasicString<T, TT>&> {
+ public:
+ Matcher() {
+ }
+
+ explicit Matcher(const MatcherInterface<const TBasicString<T, TT>&>* impl)
+ : internal::MatcherBase<const TBasicString<T, TT>&>(impl) {
+ }
+
+ Matcher(const TBasicString<T, TT>& s) {
+ *this = Eq(s);
+ }
+
+ template <typename M, typename = typename std::remove_reference<M>::type::is_gtest_matcher>
+ Matcher(M&& m)
+ : internal::MatcherBase<const TBasicString<T, TT>&>(std::forward<M>(m)) {
+ }
+
+ Matcher(const T* s) {
+ *this = Eq(TBasicString<T, TT>(s));
+ }
+ };
+
+ /**
+ * When matching `TString`, implicitly convert other strings and string views to `Eq` matchers.
+ */
+ template <typename T, typename TT>
+ class Matcher<TBasicString<T, TT>>: public internal::MatcherBase<TBasicString<T, TT>> {
+ public:
+ Matcher() {
+ }
+
+ explicit Matcher(const MatcherInterface <TBasicString<T, TT>>* impl)
+ : internal::MatcherBase<TBasicString<T, TT>>(impl) {
+ }
+
+ explicit Matcher(const MatcherInterface<const TBasicString<T, TT>&>* impl)
+ : internal::MatcherBase<TBasicString<T, TT>>(impl) {
+ }
+
+ template <typename M, typename = typename std::remove_reference<M>::type::is_gtest_matcher>
+ Matcher(M&& m)
+ : internal::MatcherBase<TBasicString<T, TT>>(std::forward<M>(m)) {
+ }
+
+ Matcher(const TBasicString<T, TT>& s) {
+ *this = Eq(s);
+ }
+
+ Matcher(const T* s) {
+ *this = Eq(TBasicString<T, TT>(s));
+ }
+ };
+}
diff --git a/library/cpp/testing/gtest_extensions/pretty_printers.cpp b/library/cpp/testing/gtest_extensions/pretty_printers.cpp
new file mode 100644
index 0000000000..401745cbcb
--- /dev/null
+++ b/library/cpp/testing/gtest_extensions/pretty_printers.cpp
@@ -0,0 +1 @@
+#include "pretty_printers.h"
diff --git a/library/cpp/testing/gtest_extensions/pretty_printers.h b/library/cpp/testing/gtest_extensions/pretty_printers.h
new file mode 100644
index 0000000000..14d8284446
--- /dev/null
+++ b/library/cpp/testing/gtest_extensions/pretty_printers.h
@@ -0,0 +1,84 @@
+#pragma once
+
+#include <util/generic/string.h>
+#include <util/generic/strbuf.h>
+#include <util/generic/maybe.h>
+#include <util/generic/variant.h>
+#include <util/stream/output.h>
+#include <util/stream/str.h>
+#include <util/datetime/base.h>
+
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+/**
+ * Automatically define GTest pretty printer for type that can print itself to util's `IOutputStream`.
+ *
+ * Note that this macro should be instantiated in the same namespace as the type you're printing, otherwise
+ * ADL will not find it.
+ *
+ * Example:
+ *
+ * We define a struct `TMyContainer` and an output operator that works with `IOutputStream`. We then use this macro
+ * to automatically define GTest pretty printer:
+ *
+ * ```
+ * namespace NMy {
+ * struct TMyContainer {
+ * int x, y;
+ * };
+ * }
+ *
+ * template <>
+ * inline void Out<NMy::TMyContainer>(IOutputStream& stream, TTypeTraits<NMy::TMyContainer>::TFuncParam value) {
+ * stream << "{ x=" << value.x << ", y=" << value.y << " }";
+ * }
+ *
+ * namespace NMy {
+ * Y_GTEST_ARCADIA_PRINTER(TMyContainer)
+ * }
+ * ```
+ */
+#define Y_GTEST_ARCADIA_PRINTER(T) \
+ void PrintTo(const T& value, std::ostream* stream) { \
+ ::TString ss; \
+ ::TStringOutput s{ss}; \
+ s << value; \
+ *stream << ss; \
+ }
+
+
+template <typename TCharType, typename TCharTraits>
+void PrintTo(const TBasicString<TCharType, TCharTraits>& value, std::ostream* stream) {
+ *stream << value.Quote().c_str();
+}
+
+template <typename TCharType, typename TCharTraits>
+void PrintTo(TBasicStringBuf<TCharType, TCharTraits> value, std::ostream* stream) {
+ *stream << TBasicString<TCharType, TCharTraits>{value}.Quote().c_str();
+}
+
+template <typename T, typename P>
+void PrintTo(const TMaybe<T, P>& value, std::ostream* stream) {
+ if (value.Defined()) {
+ ::testing::internal::UniversalPrint(value.GetRef(), stream);
+ } else {
+ *stream << "nothing";
+ }
+}
+
+inline void PrintTo(TNothing /* value */, std::ostream* stream) {
+ *stream << "nothing";
+}
+
+inline void PrintTo(std::monostate /* value */, std::ostream* stream) {
+ *stream << "monostate";
+}
+
+inline void PrintTo(TInstant value, std::ostream* stream) {
+ *stream << value.ToString();
+}
+
+inline void PrintTo(TDuration value, std::ostream* stream) {
+ *stream << value.ToString();
+}
diff --git a/library/cpp/testing/gtest_extensions/probe.cpp b/library/cpp/testing/gtest_extensions/probe.cpp
new file mode 100644
index 0000000000..c3a49b9323
--- /dev/null
+++ b/library/cpp/testing/gtest_extensions/probe.cpp
@@ -0,0 +1,13 @@
+#include "probe.h"
+
+#include <ostream>
+
+namespace testing {
+ void PrintTo(const TProbeState& state, ::std::ostream* os) {
+ int copies = state.CopyConstructors + state.CopyAssignments;
+ int moves = state.MoveConstructors + state.MoveAssignments;
+ *os << state.Constructors << " ctors, " << state.Destructors << " dtors; "
+ << "copies: " << copies << " = " << state.CopyConstructors << " + " << state.CopyAssignments << "; "
+ << "moves: " << moves << " = " << state.MoveConstructors << " + " << state.MoveAssignments;
+ }
+} // namespace testing
diff --git a/library/cpp/testing/gtest_extensions/probe.h b/library/cpp/testing/gtest_extensions/probe.h
new file mode 100644
index 0000000000..7d1fee83d3
--- /dev/null
+++ b/library/cpp/testing/gtest_extensions/probe.h
@@ -0,0 +1,81 @@
+#pragma once
+
+#include <util/system/yassert.h>
+
+#include <library/cpp/testing/common/probe.h>
+
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+
+namespace testing {
+ using NTesting::TProbe;
+ using NTesting::TProbeState;
+ using NTesting::TCoercibleToProbe;
+
+ // A helper functor which extracts from probe-like objectss their state.
+ struct TProbableTraits {
+ static const TProbeState& ExtractState(const TProbeState& probe) {
+ return probe;
+ }
+
+ static const TProbeState& ExtractState(const TProbeState* probe) {
+ return *probe;
+ }
+
+ static const TProbeState& ExtractState(const TProbe& probe) {
+ return *probe.State;
+ }
+
+ static const TProbeState& ExtractState(const TCoercibleToProbe& probe) {
+ return *probe.State;
+ }
+ };
+
+ void PrintTo(const TProbeState& state, ::std::ostream* os);
+
+ inline void PrintTo(const TProbe& probe, ::std::ostream* os) {
+ PrintTo(TProbableTraits::ExtractState(probe), os);
+ }
+
+ inline void PrintTo(const TCoercibleToProbe& probe, ::std::ostream* os) {
+ PrintTo(TProbableTraits::ExtractState(probe), os);
+ }
+
+ MATCHER(IsAlive, "is alive") {
+ Y_UNUSED(result_listener);
+ const auto& state = TProbableTraits::ExtractState(arg);
+ return state.Destructors < state.Constructors + state.CopyConstructors + state.CopyAssignments;
+ }
+
+ MATCHER(IsDead, "is dead") {
+ Y_UNUSED(result_listener);
+ const auto& state = TProbableTraits::ExtractState(arg);
+ return state.Destructors == state.Constructors + state.CopyConstructors + state.CopyAssignments;
+ }
+
+ MATCHER_P2(HasCopyMoveCounts, copyCount, moveCount, "" + \
+ PrintToString(copyCount) + " copy constructors and " + \
+ PrintToString(moveCount) + " move constructors were called") {
+ Y_UNUSED(result_listener);
+ const auto& state = TProbableTraits::ExtractState(arg);
+ return state.CopyConstructors == copyCount && state.MoveConstructors == moveCount;
+ }
+
+ MATCHER(NoCopies, "no copies were made") {
+ Y_UNUSED(result_listener);
+ const auto& state = TProbableTraits::ExtractState(arg);
+ return 0 == state.CopyConstructors && 0 == state.CopyAssignments;
+ }
+
+ MATCHER(NoMoves, "no moves were made") {
+ Y_UNUSED(result_listener);
+ const auto& state = TProbableTraits::ExtractState(arg);
+ return 0 == state.MoveConstructors && 0 == state.MoveAssignments;
+ }
+
+ MATCHER(NoAssignments, "no assignments were made") {
+ Y_UNUSED(result_listener);
+ const auto& state = TProbableTraits::ExtractState(arg);
+ return 0 == state.CopyAssignments && 0 == state.MoveAssignments;
+ }
+}
diff --git a/library/cpp/testing/gtest_extensions/ut/README.md b/library/cpp/testing/gtest_extensions/ut/README.md
new file mode 100644
index 0000000000..ee8d212c18
--- /dev/null
+++ b/library/cpp/testing/gtest_extensions/ut/README.md
@@ -0,0 +1 @@
+Note: integration tests are located in */devtools/ya/test/tests/gtest_beta*. Launch them as well after changing this library.
diff --git a/library/cpp/testing/gtest_extensions/ut/gtest_extensions_ut.cpp b/library/cpp/testing/gtest_extensions/ut/gtest_extensions_ut.cpp
new file mode 100644
index 0000000000..81cdfd0427
--- /dev/null
+++ b/library/cpp/testing/gtest_extensions/ut/gtest_extensions_ut.cpp
@@ -0,0 +1,346 @@
+#include <library/cpp/testing/gtest/gtest.h>
+
+#include <util/generic/string.h>
+#include <util/generic/maybe.h>
+#include <util/stream/output.h>
+#include <util/stream/str.h>
+
+namespace {
+ class IMock {
+ public:
+ virtual void M1(const TStringBuf&) = 0;
+ virtual void M2(TStringBuf) = 0;
+ virtual void M3(const TString&) = 0;
+ virtual void M4(TString) = 0;
+ };
+
+ class TSampleMock : IMock {
+ public:
+ MOCK_METHOD(void, M1, (const TStringBuf&));
+ MOCK_METHOD(void, M2, (TStringBuf));
+ MOCK_METHOD(void, M3, (const TString&));
+ MOCK_METHOD(void, M4, (TString));
+ };
+}
+
+
+TEST(MatchersSpecializations, String) {
+ TSampleMock mock;
+
+ TStringBuf simpleStringBuf = "SimpleStringBuf";
+ const TStringBuf constSimpleStringBuf = "ConstSimpleStringBuf";
+
+ TString simpleString = "SimpleString";
+ const TString constSimpleString = "ConstSimpleString";
+
+ EXPECT_CALL(mock, M1("ConstSimpleStringBuf")).Times(1);
+ EXPECT_CALL(mock, M2("SimpleStringBuf")).Times(1);
+ EXPECT_CALL(mock, M3("ConstSimpleString")).Times(1);
+ EXPECT_CALL(mock, M4("SimpleString")).Times(1);
+
+ mock.M1(constSimpleStringBuf);
+ mock.M2(simpleStringBuf);
+ mock.M3(constSimpleString);
+ mock.M4(simpleString);
+}
+
+template <typename T, typename M>
+std::pair<bool, std::string> Match(T&& t, M&& m) {
+ testing::StringMatchResultListener listener;
+ auto matches = testing::SafeMatcherCast<T>(std::forward<M>(m)).MatchAndExplain(std::forward<T>(t), &listener);
+ return {matches, listener.str()};
+}
+
+TEST(Matchers, Throws) {
+ auto matcher = testing::Throws<std::runtime_error>();
+
+ {
+ std::stringstream ss;
+ testing::SafeMatcherCast<void(*)()>(matcher).DescribeTo(&ss);
+ auto explanation = ss.str();
+
+ EXPECT_THAT(explanation, testing::HasSubstr("std::runtime_error"));
+ }
+
+ {
+ auto [matched, explanation] = Match([]() { throw std::runtime_error("error message"); }, matcher);
+ EXPECT_TRUE(matched);
+ EXPECT_THAT(explanation, testing::HasSubstr("std::runtime_error"));
+ }
+
+ {
+ auto [matched, explanation] = Match([]() { throw std::logic_error("error message"); }, matcher);
+ EXPECT_FALSE(matched);
+ EXPECT_THAT(explanation, testing::HasSubstr("std::logic_error"));
+ EXPECT_THAT(explanation, testing::HasSubstr("\"error message\""));
+ }
+
+ {
+ auto [matched, explanation] = Match([]() { throw 10; }, matcher);
+ EXPECT_FALSE(matched);
+ EXPECT_THAT(explanation, testing::HasSubstr("throws an exception of an unknown type"));
+ }
+
+ {
+ auto [matched, explanation] = Match([]() { (void)0; }, matcher);
+ EXPECT_FALSE(matched);
+ EXPECT_THAT(explanation, testing::HasSubstr("does not throw any exception"));
+ }
+}
+
+TEST(Matchers, ThrowsMessage) {
+ auto matcher = testing::ThrowsMessage<std::runtime_error>(testing::HasSubstr("error message"));
+
+ {
+ std::stringstream ss;
+ testing::SafeMatcherCast<void(*)()>(matcher).DescribeTo(&ss);
+ auto explanation = ss.str();
+
+ EXPECT_THAT(explanation, testing::HasSubstr("std::runtime_error"));
+ EXPECT_THAT(explanation, testing::HasSubstr("\"error message\""));
+ }
+
+ {
+ auto [matched, explanation] = Match([]() { throw std::runtime_error("error message"); }, matcher);
+ EXPECT_TRUE(matched);
+ EXPECT_THAT(explanation, testing::HasSubstr("std::runtime_error"));
+ }
+
+ {
+ auto [matched, explanation] = Match([]() { throw std::runtime_error("message error"); }, matcher);
+ EXPECT_FALSE(matched);
+ EXPECT_THAT(explanation, testing::HasSubstr("std::runtime_error"));
+ }
+
+ {
+ auto [matched, explanation] = Match([]() { throw std::logic_error("error message"); }, matcher);
+ EXPECT_FALSE(matched);
+ EXPECT_THAT(explanation, testing::HasSubstr("std::logic_error"));
+ EXPECT_THAT(explanation, testing::HasSubstr("\"error message\""));
+ }
+
+ {
+ auto [matched, explanation] = Match([]() { throw 10; }, matcher);
+ EXPECT_FALSE(matched);
+ EXPECT_THAT(explanation, testing::HasSubstr("throws an exception of an unknown type"));
+ }
+
+ {
+ auto [matched, explanation] = Match([]() { (void)0; }, matcher);
+ EXPECT_FALSE(matched);
+ EXPECT_THAT(explanation, testing::HasSubstr("does not throw any exception"));
+ }
+}
+
+TEST(Matchers, ThrowsMessageHasSubstr) {
+ auto matcher = testing::ThrowsMessage<std::runtime_error>(testing::HasSubstr("error message"));
+
+ {
+ std::stringstream ss;
+ testing::SafeMatcherCast<void(*)()>(matcher).DescribeTo(&ss);
+ auto explanation = ss.str();
+
+ EXPECT_THAT(explanation, testing::HasSubstr("std::runtime_error"));
+ EXPECT_THAT(explanation, testing::HasSubstr("\"error message\""));
+ }
+
+ {
+ auto [matched, explanation] = Match([]() { throw std::runtime_error("error message"); }, matcher);
+ EXPECT_TRUE(matched);
+ EXPECT_THAT(explanation, testing::HasSubstr("std::runtime_error"));
+ }
+
+ {
+ auto [matched, explanation] = Match([]() { throw std::runtime_error("message error"); }, matcher);
+ EXPECT_FALSE(matched);
+ EXPECT_THAT(explanation, testing::HasSubstr("std::runtime_error"));
+ }
+
+ {
+ auto [matched, explanation] = Match([]() { throw std::logic_error("error message"); }, matcher);
+ EXPECT_FALSE(matched);
+ EXPECT_THAT(explanation, testing::HasSubstr("std::logic_error"));
+ EXPECT_THAT(explanation, testing::HasSubstr("\"error message\""));
+ }
+
+ {
+ auto [matched, explanation] = Match([]() { throw 10; }, matcher);
+ EXPECT_FALSE(matched);
+ EXPECT_THAT(explanation, testing::HasSubstr("throws an exception of an unknown type"));
+ }
+
+ {
+ auto [matched, explanation] = Match([]() { (void)0; }, matcher);
+ EXPECT_FALSE(matched);
+ EXPECT_THAT(explanation, testing::HasSubstr("does not throw any exception"));
+ }
+}
+
+TEST(Matchers, ThrowsCondition) {
+ auto matcher = testing::Throws<std::runtime_error>(
+ testing::Property(&std::exception::what, testing::HasSubstr("error message")));
+
+ {
+ std::stringstream ss;
+ testing::SafeMatcherCast<void(*)()>(matcher).DescribeTo(&ss);
+ auto explanation = ss.str();
+
+ EXPECT_THAT(explanation, testing::HasSubstr("std::runtime_error"));
+ EXPECT_THAT(explanation, testing::HasSubstr("\"error message\""));
+ }
+
+ {
+ auto [matched, explanation] = Match([]() { throw std::runtime_error("error message"); }, matcher);
+ EXPECT_TRUE(matched);
+ EXPECT_THAT(explanation, testing::HasSubstr("std::runtime_error"));
+ }
+
+ {
+ auto [matched, explanation] = Match([]() { throw std::runtime_error("message error"); }, matcher);
+ EXPECT_FALSE(matched);
+ EXPECT_THAT(explanation, testing::HasSubstr("std::runtime_error"));
+ EXPECT_THAT(explanation, testing::HasSubstr("\"message error\""));
+ }
+
+ {
+ auto [matched, explanation] = Match([]() { throw std::logic_error("error message"); }, matcher);
+ EXPECT_FALSE(matched);
+ EXPECT_THAT(explanation, testing::HasSubstr("std::logic_error"));
+ EXPECT_THAT(explanation, testing::HasSubstr("\"error message\""));
+ }
+
+ {
+ auto [matched, explanation] = Match([]() { throw 10; }, matcher);
+ EXPECT_FALSE(matched);
+ EXPECT_THAT(explanation, testing::HasSubstr("throws an exception of an unknown type"));
+ }
+
+ {
+ auto [matched, explanation] = Match([]() { (void)0; }, matcher);
+ EXPECT_FALSE(matched);
+ EXPECT_THAT(explanation, testing::HasSubstr("does not throw any exception"));
+ }
+}
+
+template <typename T>
+std::string GtestPrint(T&& v) {
+ std::stringstream ss;
+ testing::internal::UniversalPrint(std::forward<T>(v), &ss);
+ return ss.str();
+}
+
+struct TThrowsOnMove {
+ TThrowsOnMove() = default;
+ TThrowsOnMove(TThrowsOnMove&&) {
+ ythrow yexception() << "move failed";
+ }
+};
+
+TEST(PrettyPrinters, String) {
+ EXPECT_EQ(GtestPrint(TString("hello world")), "\"hello world\"");
+ EXPECT_EQ(GtestPrint(TStringBuf("hello world")), "\"hello world\"");
+}
+
+TEST(PrettyPrinters, Maybe) {
+ EXPECT_EQ(GtestPrint(TMaybe<TString>("hello world")), "\"hello world\"");
+ EXPECT_EQ(GtestPrint(TMaybe<TString>()), "nothing");
+ EXPECT_EQ(GtestPrint(Nothing()), "nothing");
+}
+
+struct T1 {
+ int x;
+};
+
+void PrintTo(T1 value, std::ostream* stream) {
+ *stream << "T1{" << value.x << "}";
+}
+
+struct T2 {
+ int x;
+};
+
+Y_DECLARE_OUT_SPEC(inline, T2, stream, value) {
+ stream << "T2{" << value.x << "}";
+}
+
+Y_GTEST_ARCADIA_PRINTER(T2)
+
+TEST(PrettyPrinters, Custom) {
+ EXPECT_EQ(GtestPrint(T1{10}), "T1{10}");
+}
+
+TEST(PrettyPrinters, CustomArcadia) {
+ EXPECT_EQ(GtestPrint(T2{10}), "T2{10}");
+}
+
+TEST(Exceptions, ExpectThrow) {
+ EXPECT_THROW(ythrow yexception() << "msg", yexception);
+}
+
+TEST(Exceptions, ExpectThrowStructuredBindings) {
+ auto [a, b] = std::make_pair("a", "b");
+ EXPECT_THROW(throw yexception() << a << "-" << b, yexception);
+}
+
+TEST(Exceptions, ExpectThrowSkipInThrowTest) {
+ // this test should be skipped, not failed
+ EXPECT_THROW(GTEST_SKIP(), yexception);
+}
+
+TEST(Exceptions, AssertThrow) {
+ ASSERT_THROW(ythrow yexception() << "msg", yexception);
+}
+
+TEST(Exceptions, AssertThrowStructuredBindings) {
+ auto [a, b] = std::make_pair("a", "b");
+ ASSERT_THROW(throw yexception() << a << "-" << b, yexception);
+}
+
+TEST(Exceptions, AssertThrowSkipInThrowTest) {
+ // this test should be skipped, not failed
+ ASSERT_THROW(GTEST_SKIP(), yexception);
+}
+
+TEST(Exceptions, ExpectThrowMessageHasSubstr) {
+ EXPECT_THROW_MESSAGE_HAS_SUBSTR(ythrow yexception() << "msg", yexception, "msg");
+}
+
+TEST(Exceptions, ExpectThrowMessageHasSubstrStructuredBindings) {
+ auto [a, b] = std::make_pair("a", "b");
+ EXPECT_THROW_MESSAGE_HAS_SUBSTR(throw yexception() << a << "-" << b, yexception, "-");
+}
+
+TEST(Exceptions, ExpectThrowMessageHasSubstrSkipInThrowTest) {
+ // this test should be skipped, not failed
+ EXPECT_THROW_MESSAGE_HAS_SUBSTR(GTEST_SKIP(), yexception, "-");
+}
+
+TEST(Exceptions, AssertThrowMessageHasSubstr) {
+ ASSERT_THROW_MESSAGE_HAS_SUBSTR(ythrow yexception() << "msg", yexception, "msg");
+}
+
+TEST(Exceptions, AssertThrowMessageHasSubstrStructuredBindings) {
+ auto [a, b] = std::make_pair("a", "b");
+ ASSERT_THROW_MESSAGE_HAS_SUBSTR(throw yexception() << a << "-" << b, yexception, "-");
+}
+
+TEST(Exceptions, AssertThrowMessageHasSubstrSkipInThrowTest) {
+ // this test should be skipped, not failed
+ ASSERT_THROW_MESSAGE_HAS_SUBSTR(GTEST_SKIP(), yexception, "-");
+}
+
+TEST(Exceptions, ExpectNoThrow) {
+ EXPECT_NO_THROW((void)0);
+}
+
+TEST(Exceptions, AssertNoThrow) {
+ ASSERT_NO_THROW((void)0);
+}
+
+TEST(Exceptions, ExpectAnyThrow) {
+ EXPECT_ANY_THROW(ythrow yexception() << "msg");
+}
+
+TEST(Exceptions, AssertAnyThrow) {
+ ASSERT_ANY_THROW(ythrow yexception() << "msg");
+}
diff --git a/library/cpp/testing/gtest_extensions/ut/probe_ut.cpp b/library/cpp/testing/gtest_extensions/ut/probe_ut.cpp
new file mode 100644
index 0000000000..a9d53f896a
--- /dev/null
+++ b/library/cpp/testing/gtest_extensions/ut/probe_ut.cpp
@@ -0,0 +1,54 @@
+#include <library/cpp/testing/gtest/gtest.h>
+
+using namespace testing;
+
+TEST(ProbeStateTest, Example) {
+ // check that our test function does not make a copy of passed argument
+ auto copyless = [](auto&& x) {
+ TProbe p(std::move(x));
+ p.Touch();
+ return p;
+ };
+
+ TProbeState state;
+ auto probe = copyless(TProbe(&state));
+ EXPECT_EQ(1, state.Touches);
+ EXPECT_THAT(state, HasCopyMoveCounts(0, 2));
+}
+
+TEST(ProbeTest, Construct) {
+ TProbeState state;
+ {
+ TProbe probe(&state);
+ EXPECT_THAT(state, IsAlive());
+ }
+ EXPECT_THAT(state, IsDead());
+}
+
+TEST(ProbeTest, Copy) {
+ TProbeState state;
+
+ TProbe probe(&state);
+ TProbe copy(probe);
+ EXPECT_THAT(state, HasCopyMoveCounts(1, 0));
+ EXPECT_THAT(state, NoAssignments());
+ EXPECT_THAT(state, NoMoves());
+
+ TProbe copy2 = TProbe::ExplicitlyCreateInvalidProbe();
+ copy2 = probe;
+ EXPECT_EQ(1, state.CopyAssignments);
+}
+
+TEST(ProbeTest, Move) {
+ TProbeState state;
+ TProbe probe(&state);
+ TProbe probe2(std::move(probe));
+ EXPECT_FALSE(probe.IsValid());
+ EXPECT_THAT(state, NoCopies());
+
+ EXPECT_THAT(state, HasCopyMoveCounts(0, 1));
+
+ TProbe probe3 = TProbe::ExplicitlyCreateInvalidProbe();
+ probe3 = std::move(probe2);
+ EXPECT_EQ(1, state.MoveAssignments);
+}
diff --git a/library/cpp/testing/gtest_extensions/ut/ya.make b/library/cpp/testing/gtest_extensions/ut/ya.make
new file mode 100644
index 0000000000..39b41cecfd
--- /dev/null
+++ b/library/cpp/testing/gtest_extensions/ut/ya.make
@@ -0,0 +1,20 @@
+GTEST()
+OWNER(
+ amatanhead
+ bulatman
+ dancingqueue
+ prettyboy
+ thegeorg
+ g:cpp-contrib
+)
+
+SRCS(
+ gtest_extensions_ut.cpp
+ probe_ut.cpp
+)
+
+PEERDIR(
+ library/cpp/testing/gtest_extensions
+)
+
+END()
diff --git a/library/cpp/testing/gtest_extensions/ya.make b/library/cpp/testing/gtest_extensions/ya.make
new file mode 100644
index 0000000000..e24e81e8bd
--- /dev/null
+++ b/library/cpp/testing/gtest_extensions/ya.make
@@ -0,0 +1,26 @@
+LIBRARY()
+OWNER(
+ amatanhead
+ bulatman
+ dancingqueue
+ prettyboy
+ thegeorg
+ g:cpp-contrib
+)
+
+PEERDIR(
+ contrib/restricted/googletest/googlemock
+ contrib/restricted/googletest/googletest
+)
+
+SRCS(
+ assertions.cpp
+ gtest_extensions.cpp
+ matchers.cpp
+ pretty_printers.cpp
+ probe.cpp
+)
+
+END()
+
+RECURSE_FOR_TESTS(ut)