diff options
-rw-r--r-- | absl/base/CMakeLists.txt | 1 | ||||
-rw-r--r-- | absl/base/log_severity.cc | 1 | ||||
-rw-r--r-- | absl/base/log_severity.h | 12 | ||||
-rw-r--r-- | absl/log/CMakeLists.txt | 1 | ||||
-rw-r--r-- | absl/log/globals_test.cc | 29 | ||||
-rw-r--r-- | absl/log/internal/conditions.h | 11 | ||||
-rw-r--r-- | absl/log/log.h | 2 | ||||
-rw-r--r-- | absl/log/log_streamer.h | 10 | ||||
-rw-r--r-- | absl/log/log_streamer_test.cc | 51 | ||||
-rw-r--r-- | absl/log/stripping_test.cc | 43 |
10 files changed, 161 insertions, 0 deletions
diff --git a/absl/base/CMakeLists.txt b/absl/base/CMakeLists.txt index 76c4ff1d..c3271a10 100644 --- a/absl/base/CMakeLists.txt +++ b/absl/base/CMakeLists.txt @@ -49,6 +49,7 @@ absl_cc_library( SRCS "log_severity.cc" DEPS + absl::config absl::core_headers COPTS ${ABSL_DEFAULT_COPTS} diff --git a/absl/base/log_severity.cc b/absl/base/log_severity.cc index 60a8fc1f..8e7bbbc9 100644 --- a/absl/base/log_severity.cc +++ b/absl/base/log_severity.cc @@ -17,6 +17,7 @@ #include <ostream> #include "absl/base/attributes.h" +#include "absl/base/config.h" namespace absl { ABSL_NAMESPACE_BEGIN diff --git a/absl/base/log_severity.h b/absl/base/log_severity.h index 8bdca38b..c8bcd2fd 100644 --- a/absl/base/log_severity.h +++ b/absl/base/log_severity.h @@ -64,6 +64,8 @@ ABSL_NAMESPACE_BEGIN // --my_log_level=info // --my_log_level=0 // +// `DFATAL` and `kLogDebugFatal` are similarly accepted. +// // Unparsing a flag produces the same result as `absl::LogSeverityName()` for // the standard levels and a base-ten integer otherwise. enum class LogSeverity : int { @@ -82,6 +84,16 @@ constexpr std::array<absl::LogSeverity, 4> LogSeverities() { absl::LogSeverity::kError, absl::LogSeverity::kFatal}}; } +// `absl::kLogDebugFatal` equals `absl::LogSeverity::kFatal` in debug builds +// (i.e. when `NDEBUG` is not defined) and `absl::LogSeverity::kError` +// otherwise. Avoid ODR-using this variable as it has internal linkage and thus +// distinct storage in different TUs. +#ifdef NDEBUG +static constexpr absl::LogSeverity kLogDebugFatal = absl::LogSeverity::kError; +#else +static constexpr absl::LogSeverity kLogDebugFatal = absl::LogSeverity::kFatal; +#endif + // LogSeverityName() // // Returns the all-caps string representation (e.g. "INFO") of the specified diff --git a/absl/log/CMakeLists.txt b/absl/log/CMakeLists.txt index 9320ce59..2fc34d01 100644 --- a/absl/log/CMakeLists.txt +++ b/absl/log/CMakeLists.txt @@ -845,6 +845,7 @@ absl_cc_test( absl::log_internal_test_helpers absl::log_severity absl::scoped_mock_log + GTest::gmock GTest::gtest_main ) diff --git a/absl/log/globals_test.cc b/absl/log/globals_test.cc index f7af47cd..3d936cd7 100644 --- a/absl/log/globals_test.cc +++ b/absl/log/globals_test.cc @@ -101,4 +101,33 @@ TEST(TestGlobals, AndroidLogTag) { EXPECT_DEATH_IF_SUPPORTED(absl::SetAndroidNativeTag("test_tag_fail"), ".*"); } +TEST(TestExitOnDFatal, OffTest) { + // Turn off... + absl::log_internal::SetExitOnDFatal(false); + EXPECT_FALSE(absl::log_internal::ExitOnDFatal()); + + // We don't die. + { + absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected); + + // LOG(DFATAL) has severity FATAL if debugging, but is + // downgraded to ERROR if not debugging. + EXPECT_CALL(log, Log(absl::kLogDebugFatal, _, "This should not be fatal")); + + log.StartCapturingLogs(); + LOG(DFATAL) << "This should not be fatal"; + } +} + +#if GTEST_HAS_DEATH_TEST +TEST(TestDeathWhileExitOnDFatal, OnTest) { + absl::log_internal::SetExitOnDFatal(true); + EXPECT_TRUE(absl::log_internal::ExitOnDFatal()); + + // Death comes on little cats' feet. + EXPECT_DEBUG_DEATH({ LOG(DFATAL) << "This should be fatal in debug mode"; }, + "This should be fatal in debug mode"); +} +#endif + } // namespace diff --git a/absl/log/internal/conditions.h b/absl/log/internal/conditions.h index f576d650..41f67215 100644 --- a/absl/log/internal/conditions.h +++ b/absl/log/internal/conditions.h @@ -137,6 +137,15 @@ ? true \ : (::absl::log_internal::ExitQuietly(), false)) \ : false)) +#define ABSL_LOG_INTERNAL_CONDITION_DFATAL(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION( \ + (ABSL_ASSUME(absl::kLogDebugFatal == absl::LogSeverity::kError || \ + absl::kLogDebugFatal == absl::LogSeverity::kFatal), \ + (condition) && \ + (::absl::kLogDebugFatal >= \ + static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) || \ + (::absl::kLogDebugFatal == ::absl::LogSeverity::kFatal && \ + (::absl::log_internal::AbortQuietly(), false))))) #define ABSL_LOG_INTERNAL_CONDITION_LEVEL(severity) \ for (int log_internal_severity_loop = 1; log_internal_severity_loop; \ @@ -163,6 +172,8 @@ ABSL_LOG_INTERNAL_##type##_CONDITION(condition) #define ABSL_LOG_INTERNAL_CONDITION_QFATAL(type, condition) \ ABSL_LOG_INTERNAL_##type##_CONDITION(condition) +#define ABSL_LOG_INTERNAL_CONDITION_DFATAL(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION(condition) #define ABSL_LOG_INTERNAL_CONDITION_LEVEL(severity) \ for (int log_internal_severity_loop = 1; log_internal_severity_loop; \ log_internal_severity_loop = 0) \ diff --git a/absl/log/log.h b/absl/log/log.h index 602b5acf..99e78ea8 100644 --- a/absl/log/log.h +++ b/absl/log/log.h @@ -32,6 +32,8 @@ // * The `QFATAL` pseudo-severity level is equivalent to `FATAL` but triggers // quieter termination messages, e.g. without a full stack trace, and skips // running registered error handlers. +// * The `DFATAL` pseudo-severity level is defined as `FATAL` in debug mode and +// as `ERROR` otherwise. // Some preprocessor shenanigans are used to ensure that e.g. `LOG(INFO)` has // the same meaning even if a local symbol or preprocessor macro named `INFO` is // defined. To specify a severity level using an expression instead of a diff --git a/absl/log/log_streamer.h b/absl/log/log_streamer.h index 2d41a07f..4ed2435d 100644 --- a/absl/log/log_streamer.h +++ b/absl/log/log_streamer.h @@ -165,6 +165,16 @@ inline LogStreamer LogFatalStreamer(absl::string_view file, int line) { return absl::LogStreamer(absl::LogSeverity::kFatal, file, line); } +// LogDebugFatalStreamer() +// +// Returns a LogStreamer that writes at level LogSeverity::kLogDebugFatal. +// +// In debug mode, the program will be terminated when this `LogStreamer` is +// destroyed, regardless of whether any data were streamed in. +inline LogStreamer LogDebugFatalStreamer(absl::string_view file, int line) { + return absl::LogStreamer(absl::kLogDebugFatal, file, line); +} + ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/log/log_streamer_test.cc b/absl/log/log_streamer_test.cc index 328d70d0..40c7d488 100644 --- a/absl/log/log_streamer_test.cc +++ b/absl/log/log_streamer_test.cc @@ -151,6 +151,57 @@ TEST(LogStreamerDeathTest, LogFatalStreamer) { } #endif +#ifdef NDEBUG +TEST(LogStreamerTest, LogDebugFatalStreamer) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)), + Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kError)), + TimestampInMatchWindow(), + ThreadID(Eq(absl::base_internal::GetTID())), + TextMessage(Eq("WriteToStream: foo")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "WriteToStream: foo" + })pb")), + Stacktrace(IsEmpty())))); + + test_sink.StartCapturingLogs(); + WriteToStream("foo", + &absl::LogDebugFatalStreamer("path/file.cc", 1234).stream()); +} +#elif GTEST_HAS_DEATH_TEST +TEST(LogStreamerDeathTest, LogDebugFatalStreamer) { + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink; + + EXPECT_CALL(test_sink, Send) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + + EXPECT_CALL( + test_sink, + Send(AllOf( + SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)), + Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kFatal)), + TimestampInMatchWindow(), + ThreadID(Eq(absl::base_internal::GetTID())), + TextMessage(Eq("WriteToStream: foo")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "WriteToStream: foo" + })pb"))))) + .WillOnce(DeathTestExpectedLogging()); + + test_sink.StartCapturingLogs(); + WriteToStream( + "foo", &absl::LogDebugFatalStreamer("path/file.cc", 1234).stream()); + }, + DiedOfFatal, DeathTestValidateExpectations()); +} +#endif + TEST(LogStreamerTest, LogStreamer) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); diff --git a/absl/log/stripping_test.cc b/absl/log/stripping_test.cc index aff91495..35357039 100644 --- a/absl/log/stripping_test.cc +++ b/absl/log/stripping_test.cc @@ -322,6 +322,49 @@ TEST_F(StrippingTest, Fatal) { } } +TEST_F(StrippingTest, DFatal) { + // We need to load a copy of the needle string into memory (so we can search + // for it) without leaving it lying around in plaintext in the executable file + // as would happen if we used a literal. We might (or might not) leave it + // lying around later; that's what the tests are for! + const std::string needle = absl::Base64Escape("StrippingTest.DFatal"); + // We don't care if the LOG statement is actually executed, we're just + // checking that it's stripped. + if (kReallyDie) LOG(DFATAL) << "U3RyaXBwaW5nVGVzdC5ERmF0YWw="; + + std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable(); + ASSERT_THAT(exe, NotNull()); + // `DFATAL` can be `ERROR` or `FATAL`, and a compile-time optimizer doesn't + // know which, because `absl::kLogDebugFatal` is declared `extern` and defined + // in another TU. Link-time optimization might do better. We have six cases: + // | `AMLL` is-> | `<=ERROR` | `FATAL` | `>FATAL` | + // | ------------------- | --------- | ------- | -------- | + // | `DFATAL` is `ERROR` | present | ? | stripped | + // | `DFATAL` is `FATAL` | present | present | stripped | + + // These constexpr variables are used to suppress unreachable code warnings + // in the if-else statements below. + + // "present" in the table above: `DFATAL` exceeds `ABSL_MIN_LOG_LEVEL`, so + // `DFATAL` statements should not be stripped (and they should be logged + // when executed, but that's a different testsuite). + constexpr bool kExpectPresent = absl::kLogDebugFatal >= kAbslMinLogLevel; + + // "stripped" in the table above: even though the compiler may not know + // which value `DFATAL` has, it should be able to strip it since both + // possible values ought to be stripped. + constexpr bool kExpectStripped = kAbslMinLogLevel > absl::LogSeverity::kFatal; + + if (kExpectPresent) { + EXPECT_THAT(exe.get(), FileHasSubstr(needle)); + } else if (kExpectStripped) { + EXPECT_THAT(exe.get(), Not(FileHasSubstr(needle))); + } else { + // "?" in the table above; may or may not be stripped depending on whether + // any link-time optimization is done. Either outcome is ok. + } +} + TEST_F(StrippingTest, Level) { const std::string needle = absl::Base64Escape("StrippingTest.Level"); volatile auto severity = absl::LogSeverity::kWarning; |