Index: clang/test/Unit/lit.cfg.py =================================================================== --- clang/test/Unit/lit.cfg.py +++ clang/test/Unit/lit.cfg.py @@ -23,6 +23,9 @@ # testFormat: The test format to use to interpret tests. config.test_format = lit.formats.GoogleTest(config.llvm_build_mode, 'Tests') +# Tell gtest not to report un-executed tests. +config.environment['GTEST_REPORT_ROTTEN'] = '0' + # Propagate the temp directory. Windows requires this because it uses \Windows\ # if none of these are present. if 'TMP' in os.environ: Index: clang/unittests/ASTMatchers/ASTMatchersInternalTest.cpp =================================================================== --- clang/unittests/ASTMatchers/ASTMatchersInternalTest.cpp +++ clang/unittests/ASTMatchers/ASTMatchersInternalTest.cpp @@ -21,6 +21,10 @@ using internal::DynTypedMatcher; #if GTEST_HAS_DEATH_TEST +// RGT-FIXME: The RGT infraststructure does not play nicely with ASSERT_DEATH +// when the statement lexically contains an EXPECT. +#undef GTEST_RGT_DECLARE +#define GTEST_RGT_DECLARE TEST(HasNameDeathTest, DiesOnEmptyName) { ASSERT_DEBUG_DEATH({ DeclarationMatcher HasEmptyName = recordDecl(hasName("")); @@ -34,6 +38,8 @@ EXPECT_TRUE(notMatches("class X {};", HasEmptyName)); }, ""); } +#undef GTEST_RGT_DECLARE +#define GTEST_RGT_DECLARE GTEST_RGT_DECLARE_ #endif TEST(ConstructVariadic, MismatchedTypes_Regression) { Index: clang/unittests/Tooling/Syntax/MutationsTest.cpp =================================================================== --- clang/unittests/Tooling/Syntax/MutationsTest.cpp +++ clang/unittests/Tooling/Syntax/MutationsTest.cpp @@ -41,6 +41,9 @@ EXPECT_EQ(Expected, *Output) << "input is:\n" << Input; }; +// GCC seems to have issues here. +#undef GTEST_RGT_DECLARE +#define GTEST_RGT_DECLARE // Removes the selected statement. Input should have exactly one selected // range and it should correspond to a single statement. Transformation RemoveStatement = [this](const llvm::Annotations &Input, @@ -52,6 +55,8 @@ EXPECT_FALSE(S->isOriginal()) << "node removed from tree cannot be marked as original"; }; +#undef GTEST_RGT_DECLARE +#define GTEST_RGT_DECLARE GTEST_RGT_DECLARE_ }; INSTANTIATE_TEST_CASE_P(SyntaxTreeTests, MutationTest, Index: llvm/test/Unit/lit.cfg.py =================================================================== --- llvm/test/Unit/lit.cfg.py +++ llvm/test/Unit/lit.cfg.py @@ -24,6 +24,9 @@ # testFormat: The test format to use to interpret tests. config.test_format = lit.formats.GoogleTest(config.llvm_build_mode, 'Tests') +# Tell gtest not to report un-executed tests. +config.environment['GTEST_REPORT_ROTTEN'] = '0' + # Propagate the temp directory. Windows requires this because it uses \Windows\ # if none of these are present. if 'TMP' in os.environ: Index: llvm/unittests/Support/ProgramTest.cpp =================================================================== --- llvm/unittests/Support/ProgramTest.cpp +++ llvm/unittests/Support/ProgramTest.cpp @@ -123,7 +123,8 @@ MyExe.append(MyAbsExe); StringRef ArgV[] = {MyExe, - "--gtest_filter=ProgramEnvTest.CreateProcessLongPath"}; + "--gtest_filter=ProgramEnvTest.CreateProcessLongPath", + "--gtest_report_rotten=0"}; // Add LLVM_PROGRAM_TEST_LONG_PATH to the environment of the child. addEnvVar("LLVM_PROGRAM_TEST_LONG_PATH=1"); @@ -166,6 +167,7 @@ StringRef argv[] = { my_exe, "--gtest_filter=ProgramEnvTest.CreateProcessTrailingSlash", + "--gtest_report_rotten=0", "-program-test-string-arg1", "has\\\\ trailing\\", "-program-test-string-arg2", @@ -201,7 +203,8 @@ std::string Executable = sys::fs::getMainExecutable(TestMainArgv0, &ProgramTestStringArg1); StringRef argv[] = {Executable, - "--gtest_filter=ProgramEnvTest.TestExecuteNoWait"}; + "--gtest_filter=ProgramEnvTest.TestExecuteNoWait", + "--gtest_report_rotten=0"}; // Add LLVM_PROGRAM_TEST_EXECUTE_NO_WAIT to the environment of the child. addEnvVar("LLVM_PROGRAM_TEST_EXECUTE_NO_WAIT=1"); @@ -255,8 +258,9 @@ std::string Executable = sys::fs::getMainExecutable(TestMainArgv0, &ProgramTestStringArg1); - StringRef argv[] = { - Executable, "--gtest_filter=ProgramEnvTest.TestExecuteAndWaitTimeout"}; + StringRef argv[] = {Executable, + "--gtest_filter=ProgramEnvTest.TestExecuteAndWaitTimeout", + "--gtest_report_rotten=0"}; // Add LLVM_PROGRAM_TEST_TIMEOUT to the environment of the child. addEnvVar("LLVM_PROGRAM_TEST_TIMEOUT=1"); @@ -346,7 +350,9 @@ std::string Executable = sys::fs::getMainExecutable(TestMainArgv0, &ProgramTestStringArg1); StringRef argv[] = { - Executable, "--gtest_filter=ProgramEnvTest.TestExecuteAndWaitStatistics"}; + Executable, + "--gtest_filter=ProgramEnvTest.TestExecuteAndWaitStatistics", + "--gtest_report_rotten=0"}; // Add LLVM_PROGRAM_TEST_STATISTICS to the environment of the child. addEnvVar("LLVM_PROGRAM_TEST_STATISTICS=1"); Index: llvm/utils/unittest/googletest/include/gtest/gtest.h =================================================================== --- llvm/utils/unittest/googletest/include/gtest/gtest.h +++ llvm/utils/unittest/googletest/include/gtest/gtest.h @@ -143,6 +143,14 @@ // the specified host machine. GTEST_DECLARE_string_(stream_result_to); +// This flag controls whether we print location info for each +// un-executed test. +GTEST_DECLARE_bool_(report_rotten); + +// When this flag is set with a filename, report all test assertion +// locations to that file. For debugging RGT. +GTEST_DECLARE_string_(report_all_assertions_to); + // The upper limit for valid stack trace depths. const int kMaxStackTraceDepth = 100; Index: llvm/utils/unittest/googletest/include/gtest/gtest_pred_impl.h =================================================================== --- llvm/utils/unittest/googletest/include/gtest/gtest_pred_impl.h +++ llvm/utils/unittest/googletest/include/gtest/gtest_pred_impl.h @@ -75,7 +75,7 @@ #define GTEST_ASSERT_(expression, on_failure) \ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ if (const ::testing::AssertionResult gtest_ar = (expression)) \ - ; \ + { GTEST_RGT_DECLARE } \ else \ on_failure(gtest_ar.failure_message()) Index: llvm/utils/unittest/googletest/include/gtest/internal/gtest-internal.h =================================================================== --- llvm/utils/unittest/googletest/include/gtest/internal/gtest-internal.h +++ llvm/utils/unittest/googletest/include/gtest/internal/gtest-internal.h @@ -38,6 +38,7 @@ #define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ #include "gtest/internal/gtest-port.h" +#include "gtest/internal/rgt.h" #if GTEST_OS_LINUX # include @@ -1183,6 +1184,7 @@ // Implements Boolean test assertions such as EXPECT_TRUE. expression can be // either a boolean expression or an AssertionResult. text is a textual // represenation of expression as it was passed into the EXPECT_TRUE. +#if 0 #define GTEST_TEST_BOOLEAN_(expression, text, actual, expected, fail) \ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ if (const ::testing::AssertionResult gtest_ar_ = \ @@ -1190,7 +1192,17 @@ ; \ else \ fail(::testing::internal::GetBoolAssertionFailureMessage(\ - gtest_ar_, text, #actual, #expected).c_str()) + gtest_ar_, text, #actual, #expected).c_str()) +#else +#define GTEST_TEST_BOOLEAN_(expression, text, actual, expected, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (const ::testing::AssertionResult gtest_ar_ = \ + ::testing::AssertionResult(expression)) \ + { GTEST_RGT_DECLARE } \ + else \ + fail(::testing::internal::GetBoolAssertionFailureMessage(\ + gtest_ar_, text, #actual, #expected).c_str()) +#endif #define GTEST_TEST_NO_FATAL_FAILURE_(statement, fail) \ GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ Index: llvm/utils/unittest/googletest/include/gtest/internal/rgt.h =================================================================== --- /dev/null +++ llvm/utils/unittest/googletest/include/gtest/internal/rgt.h @@ -0,0 +1,186 @@ +//===- gtest/include/internal/rgt.h - Rotten Green Test helper --*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines macros and classes for detecting Rotten Green Tests +// in the LLVM unittests. +// +//===----------------------------------------------------------------------===// +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_RGT_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_RGT_H_ + +// A Rotten Green Test is one that looks like it is testing some useful +// assertion about code behavior, but in fact the assertion is never +// executed. This file supports instrumenting LLVM unittests to detect +// EXPECT_* and ASSERT_* calls that are not executed, indicating that +// they are Rotten Green Tests. +// +// Inspired by "Rotten Green Tests", Delplanque et al., ICSE 2019 +// DOI 10.1109/ICSE.2019.00062 +// +// The implementation depends on having static data allocated and initialized +// at compile time, in order to identify each test assertion. Execution of +// the assertion will record that test as having been executed. On program +// exit, we scan the array of static data and report those that have not +// been executed. +// +// We allocate the static data into a custom section that has a name that is +// a legal C identifier. This will cause the linker to define symbols for +// the start and end of the section, if those symbols are referenced. This +// allows us to allocate static data piecemeal in the source, and still have +// effectively a single array of all such data at runtime. +// +// This tactic is known to work in GNU linkers and LLD. + +// Due to a quirk of data allocation for items in inline functions, gcc +// won't let us put local static data in a custom section. We use a hack +// to imitate runtime initialization of globals; we can't rely on normal +// initialization of static locals because that requires control to pass +// over the definition of the static local, which defeats the purpose. + +// For MSVC, allocatic static data to a custom section apparently doesn't +// keep it from being optimized away, and there is no equivalent of the +// "used" attribute. So we use a gcc-like hack, but instead of .init_array +// being run automatically on startup, we have to run it manually. + +#if defined(__GNUC__) && !defined(__clang__) +#define GTEST_RGT_RUNTIME_INIT_ARRAY_ 1 +#elif defined(_WIN32) +#define GTEST_RGT_RUNTIME_INIT_MANUAL_ 1 +#endif +#define GTEST_RGT_RUNTIME_INIT_ \ + (GTEST_RGT_RUNTIME_INIT_ARRAY_ || GTEST_RGT_RUNTIME_INIT_MANUAL_) + + +// In cases where a test is known and expected to fail, the client may +// disable checking for the failing test by doing +// #undef GTEST_RGT_DECLARE +// #define GTEST_RGT_DECLARE +// before the test, and re-enabling it afterward with +// #undef GTEST_RGT_DECLARE +// #define GTEST_RGT_DECLARE GTEST_RGT_DECLARE_ +// afterward. +#define GTEST_RGT_DECLARE GTEST_RGT_DECLARE_ + +namespace testing { +namespace internal { + +// The data to record per test-assertion site. +struct RGT_item { + const char *file; + unsigned line; + bool executed; +}; + +#if GTEST_RGT_RUNTIME_INIT_ +// Record the existence of a test point, when we can't arrange for that +// purely with static initialization. +void RGT_record(RGT_item *item); + +#if GTEST_RGT_RUNTIME_INIT_MANUAL_ +// Run all the initializations. +void RGT_init_manual(); + +// Provide a global pointer so we can fake-use all the items. +void *RGT_fake_use = nullptr; +#endif +#endif // GTEST_RGT_RUNTIME_INIT_ + +// Pass nullptr to get the first rotten item; pass the previous return value +// to get the next item. Returns nullptr when there are no more. +// TODO: Is there a reasonable way to achieve this with a range while still +// hiding the inconvenient platform differences? +RGT_item *RGT_get_next_rotten_item(RGT_item *item); + +// Get the total number of test assertions. +int RGT_get_item_count(); + +// For debugging: Report the locations of all test assertions to the +// specified file. +void RGT_report_all_assertions_to(std::string &filename); +} // end namespace internal +} // end namespace testing + +#if GTEST_RGT_RUNTIME_INIT_ + +// Conjure up a function to do startup-time initialization, given that +// we can't arrange for static initialization. + +#if GTEST_RGT_RUNTIME_INIT_ARRAY_ + +#define GTEST_RGT_RECORD_ITEM_(ITEM) \ + struct __rgt_recorder { \ + static void record() { testing::internal::RGT_record(&ITEM); } \ + }; \ + __asm__( \ + ".pushsection .init_array" "\n" \ + ".quad %c0" "\n" \ + ".popsection" "\n" \ + : : "i"(__rgt_recorder::record)); + +#else + +// Windows doesn't automatically provide start/end symbol names, so we roll +// our own with start/end entries. The sorting is determined by suffixes on +// the section name, so we need to paste the base name with another string to +// get correct sorting. Extra fun macro indirection required. + +#define GTEST_RGT_SECTION_NAME_WITH_SUFFIX_(SUFFIX) GTEST_RGT_SECTION_NAME_2(RGT, SUFFIX) +#define GTEST_RGT_SECTION_NAME_2(NAME, SUFFIX) GTEST_RGT_SECTION_NAME_3(NAME, SUFFIX) +#define GTEST_RGT_SECTION_NAME_3(NAME, SUFFIX) GTEST_RGT_SECTION_NAME_4(NAME ## SUFFIX) +#define GTEST_RGT_SECTION_NAME_4(NAME) #NAME + +#define GTEST_RGT_SECTION_NAME_ GTEST_RGT_SECTION_NAME_WITH_SUFFIX_($d) + +// MSVC requires the section to be declared before being used, but simply +// using a global #pragma seems not to work (despite the documentation). +// And because MSVC has no equivalent of attribute(used) we need to fake +// up a pointer escaping so it will look used. + +typedef void(*RGT_recorder)(void); + +#define GTEST_RGT_RECORD_ITEM_(ITEM) \ + struct __rgt_recorder { \ + static void record() { testing::internal::RGT_record(&ITEM); } \ + }; \ + __pragma(section(GTEST_RGT_SECTION_NAME_,read,write)) \ + __declspec(allocate(GTEST_RGT_SECTION_NAME_)) \ + static RGT_recorder __rgt_record_item = __rgt_recorder::record; \ + testing::internal::RGT_fake_use = (void*)&__rgt_record_item; + +#endif // GTEST_RGT_RUNTIME_INIT_ARRAY_ + +#define GTEST_RGT_DECLARE_ \ + static testing::internal::RGT_item \ + GTEST_RGT_item_ { __FILE__, __LINE__, false }; \ + GTEST_RGT_item_.executed = true; \ + GTEST_RGT_RECORD_ITEM_(GTEST_RGT_item_) + +#else + +// The "normal" case, allocate local static data to a custom section +// which we can then iterate over at program end. +// Note, the non-Windows case requires this be a legal C identifier. + +#define GTEST_RGT_SECTION_NAME_ RGT + +// Now define how to decorate the data declarations. +#define GTEST_RGT_SECTION_ATTR_3(NAME) __attribute__((section(#NAME),used)) +#define GTEST_RGT_SECTION_ATTR_2(NAME) GTEST_RGT_SECTION_ATTR_3(NAME) +#define GTEST_RGT_SECTION_ATTR GTEST_RGT_SECTION_ATTR_2(GTEST_RGT_SECTION_NAME_) + + +// Macro to declare one test assertion's data item and initialize it. + +#define GTEST_RGT_DECLARE_ \ + GTEST_RGT_SECTION_ATTR static testing::internal::RGT_item \ + GTEST_RGT_item_ { __FILE__, __LINE__, false }; \ + GTEST_RGT_item_.executed = true; + +#endif // GTEST_RGT_RUNTIME_INIT_ + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_RGT_H_ Index: llvm/utils/unittest/googletest/src/gtest-all.cc =================================================================== --- llvm/utils/unittest/googletest/src/gtest-all.cc +++ llvm/utils/unittest/googletest/src/gtest-all.cc @@ -46,3 +46,4 @@ #include "src/gtest-printers.cc" #include "src/gtest-test-part.cc" #include "src/gtest-typed-test.cc" +#include "src/rgt.cc" Index: llvm/utils/unittest/googletest/src/gtest-internal-inl.h =================================================================== --- llvm/utils/unittest/googletest/src/gtest-internal-inl.h +++ llvm/utils/unittest/googletest/src/gtest-internal-inl.h @@ -96,6 +96,8 @@ const char kPrintTimeFlag[] = "print_time"; const char kRandomSeedFlag[] = "random_seed"; const char kRepeatFlag[] = "repeat"; +const char kRGTReportLocation[] = "report_rotten"; +const char kRGTReportAllTo[] = "report_all_assertions_to"; const char kShuffleFlag[] = "shuffle"; const char kStackTraceDepthFlag[] = "stack_trace_depth"; const char kStreamResultToFlag[] = "stream_result_to"; Index: llvm/utils/unittest/googletest/src/gtest.cc =================================================================== --- llvm/utils/unittest/googletest/src/gtest.cc +++ llvm/utils/unittest/googletest/src/gtest.cc @@ -303,6 +303,18 @@ "This flag specifies the flagfile to read command-line flags from."); #endif // GTEST_USE_OWN_FLAGFILE_FLAG_ +GTEST_DEFINE_bool_( + report_rotten, + internal::BoolFromGTestEnv("report_rotten", true), + "When this flag is specified, the source locations of un-executed " + "test assertions will be reported."); + +GTEST_DEFINE_string_( + report_all_assertions_to, + internal::StringFromGTestEnv("report_all_assertions_to", ""), + "This flag specifies the file on which to report the source locations " + "of all test assertions."); + namespace internal { // Generates a random number from [0, range), using a Linear @@ -3230,6 +3242,63 @@ // End PrettyUnitTestResultPrinter +// This class implements the TestEventListener interface. +// +// Class RGTListener is not copyable. +class RGTListener : public EmptyTestEventListener { + public: + RGTListener() {} + virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test); + virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test); +}; + +void RGTListener::OnEnvironmentsSetUpStart(const UnitTest& unit_test) { +#if GTEST_RGT_RUNTIME_INIT_MANUAL_ + internal::RGT_init_manual(); +#endif +} +void RGTListener::OnEnvironmentsTearDownStart(const UnitTest& unit_test) { + // Possibly report all assertion locations. + internal::RGT_report_all_assertions_to(GTEST_FLAG(report_all_assertions_to)); + + // Report any un-executed test assertions. + int rotten_count = 0; + // FIXME: Is there a way to do this with a range? + internal::RGT_item *item = nullptr; + while ((item = internal::RGT_get_next_rotten_item(item))) { + // Exclude gtest.cc and gtest-port.cc, which contain assertions in + // methods that are rarely used and so show up as rotten. + if (String::EndsWithCaseInsensitive(item->file, "gtest.cc") || + String::EndsWithCaseInsensitive(item->file, "gtest-port.cc")) + continue; + if (GTEST_FLAG(report_rotten)) { + ColoredPrintf(COLOR_RED, "Rotten Test Detected"); + printf(" at %s:%u\n", item->file, item->line); + } + ++rotten_count; + } + + // FIXME: If basically all tests have good reason for un-executed tests, + // this should also be under the flag. + std::string message = "You have " + + FormatCountableNoun(rotten_count, "rotten test assertion", + "rotten test assertions") + " out of " + + FormatCountableNoun(internal::RGT_get_item_count(), + "total assertion", "total assertions") + "\n"; + // Optionally force the test to fail for rotten assertions. + if (rotten_count) { + if (GTEST_FLAG(report_rotten)) + internal::ReportFailureInUnknownLocation(TestPartResult::kNonFatalFailure, + message); + else + ColoredPrintf(COLOR_YELLOW, message.c_str()); + } else { + ColoredPrintf(COLOR_GREEN, message.c_str()); + } +} + +// End RGTListener + // class TestEventRepeater // // This class forwards events to other event listeners. @@ -4443,6 +4512,9 @@ listeners()->Append(new GTEST_CUSTOM_TEST_EVENT_LISTENER_()); #endif // defined(GTEST_CUSTOM_TEST_EVENT_LISTENER_) + // Register the RGT listener. + listeners()->Append(new RGTListener); + #if GTEST_HAS_DEATH_TEST InitDeathTestSubprocessControlInfo(); SuppressTestEventsIfInSubprocess(); @@ -5194,6 +5266,8 @@ " @G--" GTEST_FLAG_PREFIX_ "stream_result_to=@YHOST@G:@YPORT@D\n" " Stream test results to the given server.\n" #endif // GTEST_CAN_STREAM_RESULTS_ +" @G--" GTEST_FLAG_PREFIX_ "report_rotten@D\n" +" Report source location of un-executed test assertions.\n" "\n" "Assertion Behavior:\n" #if GTEST_HAS_DEATH_TEST && !GTEST_OS_WINDOWS @@ -5240,6 +5314,10 @@ ParseBoolFlag(arg, kPrintTimeFlag, >EST_FLAG(print_time)) || ParseInt32Flag(arg, kRandomSeedFlag, >EST_FLAG(random_seed)) || ParseInt32Flag(arg, kRepeatFlag, >EST_FLAG(repeat)) || + ParseBoolFlag(arg, kRGTReportLocation, + >EST_FLAG(report_rotten)) || + ParseStringFlag(arg, kRGTReportAllTo, + >EST_FLAG(report_all_assertions_to)) || ParseBoolFlag(arg, kShuffleFlag, >EST_FLAG(shuffle)) || ParseInt32Flag(arg, kStackTraceDepthFlag, >EST_FLAG(stack_trace_depth)) || Index: llvm/utils/unittest/googletest/src/rgt.cc =================================================================== --- /dev/null +++ llvm/utils/unittest/googletest/src/rgt.cc @@ -0,0 +1,236 @@ +//===- rgt.cc - Rotten Green Test support ---------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Support for the Rotten Green Test extension to LLVM's copy of the +// googletest framework. + +#include "gtest/internal/rgt.h" +#include +#include +#include +#include + +// When we can't get pure static initialization of the array of items +// describing test points, they get registered on startup and we keep +// pointers to the data in this vector. + +#if GTEST_RGT_RUNTIME_INIT_ + +namespace { + +using item_vector = std::vector; +class RegisteredItems { + static item_vector *items; +public: + RegisteredItems() = default; + item_vector *getItems() { + if (!items) + items = new item_vector; + return items; + } + int size() { return getItems()->size(); } + bool empty() { return getItems()->empty(); } + auto begin() { return getItems()->begin(); } + auto end() { return getItems()->end(); } + void push_back(testing::internal::RGT_item *item) { + getItems()->push_back(item); + } +}; + +RegisteredItems registered_items; +item_vector *RegisteredItems::items = nullptr; + +} // end anonymous namespace + +void testing::internal::RGT_record(testing::internal::RGT_item *item) { + registered_items.push_back(item); +} + +#if GTEST_RGT_RUNTIME_INIT_MANUAL_ +// On Windows we have to allocate our own placeholder start/stop data items. +// The linker will sort these into the right order relative to real data. +// Because the section concatenation might be padded, we'll have to skip over +// any items with a null file pointer, so make the first item look like it's +// padding. + +#define START_SECTION_NAME GTEST_RGT_SECTION_NAME_WITH_SUFFIX_($a) +#define STOP_SECTION_NAME GTEST_RGT_SECTION_NAME_WITH_SUFFIX_($z) + +#pragma section (START_SECTION_NAME,read,write) +__declspec(allocate(START_SECTION_NAME)) +RGT_recorder RGT_manual_init_start = (RGT_recorder)1; + +#pragma section (STOP_SECTION_NAME,read,write) +__declspec(allocate(STOP_SECTION_NAME)) +RGT_recorder RGT_manual_init_stop = (RGT_recorder)1; + +void testing::internal::RGT_init_manual() { + const RGT_recorder *F = &RGT_manual_init_start; + int call_count = 0; + int null_count = 0; + for (++F; F < &RGT_manual_init_stop; ++F) { + if (*F) { + ++call_count; + (*F)(); + } else { + ++null_count; + } + } + printf("RGT_init_manual: %d called, %d null\n", call_count, null_count); +} + +#endif // GTEST_RGT_RUNTIME_INIT_MANUAL_ +#endif // GTEST_RGT_RUNTIME_INIT_ + +// In order to deduplicate information due to template instantiations, +// we combine all raw reports for the same source location. Then for +// nicer reporting, we generate a vector such that all reports for a +// given file are sorted by line number. +// +// The runtime initialization tactic can result in the same item being +// registered multiple times; the deduplication automatically handles +// that as well. + +namespace testing { +namespace internal { + +bool operator<(const RGT_item &a, const RGT_item &b) { + assert(a.file == b.file); + return a.line < b.line; +} + +} // end namespace internal +} // end namespace testing + +#if !GTEST_RGT_RUNTIME_INIT_ +// Non-Windows linkers provide __start_
and __stop_
symbols. +// Declare these outside of all namespaces. +#define GEN_NAME2(prefix, section) prefix ## section +#define GEN_NAME(prefix, section) GEN_NAME2(prefix, section) +#define START_NAME GEN_NAME(__start_, GTEST_RGT_SECTION_NAME_) +#define STOP_NAME GEN_NAME(__stop_, GTEST_RGT_SECTION_NAME_) + +// extern "C" vars can't have qualified type names; we only care about +// the addresses, we'll do appropriate casts later. +extern "C" int START_NAME; +extern "C" int STOP_NAME; +#endif // !GTEST_RGT_RUNTIME_INIT_ + +namespace { + +std::vector RGT_collated_data; + +void collate_data() { + if (!RGT_collated_data.empty()) + return; + + // Collect raw data into a map with filename as the key; the value is + // another map with line number as the key and "executed" as the value. + // This collection deduplicates entries, and markes a line as executed + // if any of the (possibly duplicate) entries was marked executed. + // We sort the data by filename, because we run in various environments + // and sorting the output makes the data easier to compare. + struct test_info_compare { + bool operator()(const char *lhs, const char *rhs) const { + return strcmp(lhs, rhs) < 0; + } + }; + using file_info = std::map; + using test_info = std::map; + + test_info test_map; + testing::internal::RGT_item *item; +#if GTEST_RGT_RUNTIME_INIT_ + item_vector::iterator I = registered_items.begin(); + item_vector::iterator E = registered_items.end(); +#define GET_ITEM item = *I +#else + testing::internal::RGT_item *I = (testing::internal::RGT_item *)&START_NAME; + testing::internal::RGT_item *E = (testing::internal::RGT_item *)&STOP_NAME; +#define GET_ITEM item = I +#endif + + // This assumes all pointers to the same filename string are equal. + for (; I != E; ++I) { + GET_ITEM; + file_info &fileinfo = test_map[item->file]; + bool &was_executed = fileinfo[item->line]; + if (item->executed) + was_executed = true; + } + + + // Serialize the collated data into a vector where the data for a given + // file is all in adjacent entries, and sorted by line. + for (auto &M : test_map) { + const char *file = M.first; + // As we're adding elements to the vector, we can't preserve iterators. + std::size_t file_start = RGT_collated_data.size(); + for (auto &I : M.second) + RGT_collated_data.push_back({file, I.first, I.second}); + std::sort(RGT_collated_data.begin() + file_start, RGT_collated_data.end()); + } +} + +} // end anonymous namespace + +int testing::internal::RGT_get_item_count() { + collate_data(); + return RGT_collated_data.size(); +} + +// Iterator-like function to find un-executed test points. + +testing::internal::RGT_item * +testing::internal::RGT_get_next_rotten_item(testing::internal::RGT_item *item) { + collate_data(); + + if (item) + ++item; + else + item = &*RGT_collated_data.begin(); + for (; item != &*RGT_collated_data.end(); ++item) { + if (!item->executed) + return item; + } + + return nullptr; +} + +// Report source location of all identified assertions. For debugging. +// We emit one per line to simplify diffing. + +void testing::internal::RGT_report_all_assertions_to(std::string &filename) { + if (filename.empty()) + return; + + // Following is based on UnitTestOptions::GetAbsolutePathToOutputFile() + // which has a note regarding certain Windows paths not working. + internal::FilePath log_path(filename); + if (!log_path.IsAbsolutePath()) { + log_path = internal::FilePath::ConcatPaths( + internal::FilePath(UnitTest::GetInstance()->original_working_dir()), + internal::FilePath(filename)); + } + + if (log_path.IsDirectory()) { + fprintf(stderr, "Specified log file is a directory \"%s\"\n", + filename.c_str()); + return; + } + FILE* logfile = posix::FOpen(log_path.c_str(), "w"); + if (!logfile) { + fprintf(stderr, "Unable to open log file \"%s\"\n", log_path.c_str()); + return; + } + + collate_data(); + for (auto &item : RGT_collated_data) + fprintf(logfile, "%s::%u\n", item.file, item.line); + posix::FClose(logfile); +}