Usually library does not have control of the process lifespan. Without this function, it is impossible to init/shutdown reliably. It has been one of the major pain points for years when using glog in libraries. AFAIK 3 workarounds have been used previously: 1. Init without checking. This causes compatiblity issues with other libs using glog. 2. Also provide a init function in library's API. This makes API complicated and stateful, especially for libs that does not mean to stay for the entire life of process. 3. Steal the utility function in internal namespace. Does not work with msvc (due to missing dllexport) or `gcc -fvisibility=hidden`. None of them are perfect, except for the last hack that usually works well on Linux. 0.5.0 changes default visibility to hidden and it does not work anymore. Resolve https://github.com/google/glog/issues/125
1381 lines
41 KiB
C++
1381 lines
41 KiB
C++
// Copyright (c) 2002, Google Inc.
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are
|
|
// met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above
|
|
// copyright notice, this list of conditions and the following disclaimer
|
|
// in the documentation and/or other materials provided with the
|
|
// distribution.
|
|
// * Neither the name of Google Inc. nor the names of its
|
|
// contributors may be used to endorse or promote products derived from
|
|
// this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
//
|
|
// Author: Ray Sidney
|
|
|
|
#include "utilities.h"
|
|
|
|
#include <fcntl.h>
|
|
#ifdef HAVE_GLOB_H
|
|
# include <glob.h>
|
|
#endif
|
|
#include <sys/stat.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
# include <unistd.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_WAIT_H
|
|
# include <sys/wait.h>
|
|
#endif
|
|
|
|
#include <iomanip>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <memory>
|
|
#include <queue>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "base/commandlineflags.h"
|
|
#include "glog/logging.h"
|
|
#include "glog/raw_logging.h"
|
|
#include "googletest.h"
|
|
|
|
DECLARE_string(log_backtrace_at); // logging.cc
|
|
|
|
#ifdef HAVE_LIB_GFLAGS
|
|
#include <gflags/gflags.h>
|
|
using namespace GFLAGS_NAMESPACE;
|
|
#endif
|
|
|
|
#ifdef HAVE_LIB_GMOCK
|
|
#include <gmock/gmock.h>
|
|
#include "mock-log.h"
|
|
// Introduce several symbols from gmock.
|
|
using testing::_;
|
|
using testing::AnyNumber;
|
|
using testing::HasSubstr;
|
|
using testing::AllOf;
|
|
using testing::StrNe;
|
|
using testing::StrictMock;
|
|
using testing::InitGoogleMock;
|
|
using GOOGLE_NAMESPACE::glog_testing::ScopedMockLog;
|
|
#endif
|
|
|
|
using namespace std;
|
|
using namespace GOOGLE_NAMESPACE;
|
|
|
|
// Some non-advertised functions that we want to test or use.
|
|
_START_GOOGLE_NAMESPACE_
|
|
namespace base {
|
|
namespace internal {
|
|
bool GetExitOnDFatal();
|
|
void SetExitOnDFatal(bool value);
|
|
} // namespace internal
|
|
} // namespace base
|
|
_END_GOOGLE_NAMESPACE_
|
|
|
|
static void TestLogging(bool check_counts);
|
|
static void TestRawLogging();
|
|
static void LogWithLevels(int v, int severity, bool err, bool alsoerr);
|
|
static void TestLoggingLevels();
|
|
static void TestLogString();
|
|
static void TestLogSink();
|
|
static void TestLogToString();
|
|
static void TestLogSinkWaitTillSent();
|
|
static void TestCHECK();
|
|
static void TestDCHECK();
|
|
static void TestSTREQ();
|
|
static void TestBasename();
|
|
static void TestBasenameAppendWhenNoTimestamp();
|
|
static void TestTwoProcessesWrite();
|
|
static void TestSymlink();
|
|
static void TestExtension();
|
|
static void TestWrapper();
|
|
static void TestErrno();
|
|
static void TestTruncate();
|
|
static void TestCustomLoggerDeletionOnShutdown();
|
|
|
|
static int x = -1;
|
|
static void BM_Check1(int n) {
|
|
while (n-- > 0) {
|
|
CHECK_GE(n, x);
|
|
CHECK_GE(n, x);
|
|
CHECK_GE(n, x);
|
|
CHECK_GE(n, x);
|
|
CHECK_GE(n, x);
|
|
CHECK_GE(n, x);
|
|
CHECK_GE(n, x);
|
|
CHECK_GE(n, x);
|
|
}
|
|
}
|
|
BENCHMARK(BM_Check1);
|
|
|
|
static void CheckFailure(int a, int b, const char* file, int line, const char* msg);
|
|
static void BM_Check3(int n) {
|
|
while (n-- > 0) {
|
|
if (n < x) CheckFailure(n, x, __FILE__, __LINE__, "n < x");
|
|
if (n < x) CheckFailure(n, x, __FILE__, __LINE__, "n < x");
|
|
if (n < x) CheckFailure(n, x, __FILE__, __LINE__, "n < x");
|
|
if (n < x) CheckFailure(n, x, __FILE__, __LINE__, "n < x");
|
|
if (n < x) CheckFailure(n, x, __FILE__, __LINE__, "n < x");
|
|
if (n < x) CheckFailure(n, x, __FILE__, __LINE__, "n < x");
|
|
if (n < x) CheckFailure(n, x, __FILE__, __LINE__, "n < x");
|
|
if (n < x) CheckFailure(n, x, __FILE__, __LINE__, "n < x");
|
|
}
|
|
}
|
|
BENCHMARK(BM_Check3);
|
|
|
|
static void BM_Check2(int n) {
|
|
if (n == 17) {
|
|
x = 5;
|
|
}
|
|
while (n-- > 0) {
|
|
CHECK(n >= x);
|
|
CHECK(n >= x);
|
|
CHECK(n >= x);
|
|
CHECK(n >= x);
|
|
CHECK(n >= x);
|
|
CHECK(n >= x);
|
|
CHECK(n >= x);
|
|
CHECK(n >= x);
|
|
}
|
|
}
|
|
BENCHMARK(BM_Check2);
|
|
|
|
static void CheckFailure(int, int, const char* /* file */, int /* line */,
|
|
const char* /* msg */) {
|
|
}
|
|
|
|
static void BM_logspeed(int n) {
|
|
while (n-- > 0) {
|
|
LOG(INFO) << "test message";
|
|
}
|
|
}
|
|
BENCHMARK(BM_logspeed);
|
|
|
|
static void BM_vlog(int n) {
|
|
while (n-- > 0) {
|
|
VLOG(1) << "test message";
|
|
}
|
|
}
|
|
BENCHMARK(BM_vlog);
|
|
|
|
// Dynamically generate a prefix using the default format and write it to the stream.
|
|
void PrefixAttacher(std::ostream &s, const LogMessageInfo &l, void* data) {
|
|
// Assert that `data` contains the expected contents before producing the
|
|
// prefix (otherwise causing the tests to fail):
|
|
if (data == NULL || *static_cast<string*>(data) != "good data") {
|
|
return;
|
|
}
|
|
|
|
s << l.severity[0]
|
|
<< setw(4) << 1900 + l.time.year()
|
|
<< setw(2) << 1 + l.time.month()
|
|
<< setw(2) << l.time.day()
|
|
<< ' '
|
|
<< setw(2) << l.time.hour() << ':'
|
|
<< setw(2) << l.time.min() << ':'
|
|
<< setw(2) << l.time.sec() << "."
|
|
<< setw(6) << l.time.usec()
|
|
<< ' '
|
|
<< setfill(' ') << setw(5)
|
|
<< l.thread_id << setfill('0')
|
|
<< ' '
|
|
<< l.filename << ':' << l.line_number << "]";
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
FLAGS_colorlogtostderr = false;
|
|
FLAGS_timestamp_in_logfile_name = true;
|
|
#ifdef HAVE_LIB_GFLAGS
|
|
ParseCommandLineFlags(&argc, &argv, true);
|
|
#endif
|
|
// Make sure stderr is not buffered as stderr seems to be buffered
|
|
// on recent windows.
|
|
setbuf(stderr, NULL);
|
|
|
|
// Test some basics before InitGoogleLogging:
|
|
CaptureTestStderr();
|
|
LogWithLevels(FLAGS_v, FLAGS_stderrthreshold,
|
|
FLAGS_logtostderr, FLAGS_alsologtostderr);
|
|
LogWithLevels(0, 0, 0, 0); // simulate "before global c-tors"
|
|
const string early_stderr = GetCapturedTestStderr();
|
|
|
|
EXPECT_FALSE(IsGoogleLoggingInitialized());
|
|
|
|
// Setting a custom prefix generator (it will use the default format so that
|
|
// the golden outputs can be reused):
|
|
string prefix_attacher_data = "good data";
|
|
InitGoogleLogging(argv[0], &PrefixAttacher, static_cast<void*>(&prefix_attacher_data));
|
|
|
|
EXPECT_TRUE(IsGoogleLoggingInitialized());
|
|
|
|
RunSpecifiedBenchmarks();
|
|
|
|
FLAGS_logtostderr = true;
|
|
|
|
InitGoogleTest(&argc, argv);
|
|
#ifdef HAVE_LIB_GMOCK
|
|
InitGoogleMock(&argc, argv);
|
|
#endif
|
|
|
|
// so that death tests run before we use threads
|
|
CHECK_EQ(RUN_ALL_TESTS(), 0);
|
|
|
|
CaptureTestStderr();
|
|
|
|
// re-emit early_stderr
|
|
LogMessage("dummy", LogMessage::kNoLogPrefix, GLOG_INFO).stream() << early_stderr;
|
|
|
|
TestLogging(true);
|
|
TestRawLogging();
|
|
TestLoggingLevels();
|
|
TestLogString();
|
|
TestLogSink();
|
|
TestLogToString();
|
|
TestLogSinkWaitTillSent();
|
|
TestCHECK();
|
|
TestDCHECK();
|
|
TestSTREQ();
|
|
|
|
// TODO: The golden test portion of this test is very flakey.
|
|
EXPECT_TRUE(
|
|
MungeAndDiffTestStderr(FLAGS_test_srcdir + "/src/logging_custom_prefix_unittest.err"));
|
|
|
|
FLAGS_logtostderr = false;
|
|
|
|
TestBasename();
|
|
TestBasenameAppendWhenNoTimestamp();
|
|
TestTwoProcessesWrite();
|
|
TestSymlink();
|
|
TestExtension();
|
|
TestWrapper();
|
|
TestErrno();
|
|
TestTruncate();
|
|
TestCustomLoggerDeletionOnShutdown();
|
|
|
|
fprintf(stdout, "PASS\n");
|
|
return 0;
|
|
}
|
|
|
|
void TestLogging(bool check_counts) {
|
|
int64 base_num_infos = LogMessage::num_messages(GLOG_INFO);
|
|
int64 base_num_warning = LogMessage::num_messages(GLOG_WARNING);
|
|
int64 base_num_errors = LogMessage::num_messages(GLOG_ERROR);
|
|
|
|
LOG(INFO) << string("foo ") << "bar " << 10 << ' ' << 3.4;
|
|
for ( int i = 0; i < 10; ++i ) {
|
|
int old_errno = errno;
|
|
errno = i;
|
|
PLOG_EVERY_N(ERROR, 2) << "Plog every 2, iteration " << COUNTER;
|
|
errno = old_errno;
|
|
|
|
LOG_EVERY_N(ERROR, 3) << "Log every 3, iteration " << COUNTER << endl;
|
|
LOG_EVERY_N(ERROR, 4) << "Log every 4, iteration " << COUNTER << endl;
|
|
|
|
LOG_IF_EVERY_N(WARNING, true, 5) << "Log if every 5, iteration " << COUNTER;
|
|
LOG_IF_EVERY_N(WARNING, false, 3)
|
|
<< "Log if every 3, iteration " << COUNTER;
|
|
LOG_IF_EVERY_N(INFO, true, 1) << "Log if every 1, iteration " << COUNTER;
|
|
LOG_IF_EVERY_N(ERROR, (i < 3), 2)
|
|
<< "Log if less than 3 every 2, iteration " << COUNTER;
|
|
}
|
|
LOG_IF(WARNING, true) << "log_if this";
|
|
LOG_IF(WARNING, false) << "don't log_if this";
|
|
|
|
char s[] = "array";
|
|
LOG(INFO) << s;
|
|
const char const_s[] = "const array";
|
|
LOG(INFO) << const_s;
|
|
int j = 1000;
|
|
LOG(ERROR) << string("foo") << ' '<< j << ' ' << setw(10) << j << " "
|
|
<< setw(1) << hex << j;
|
|
|
|
{
|
|
google::LogMessage outer(__FILE__, __LINE__, GLOG_ERROR);
|
|
outer.stream() << "outer";
|
|
|
|
LOG(ERROR) << "inner";
|
|
}
|
|
|
|
LogMessage("foo", LogMessage::kNoLogPrefix, GLOG_INFO).stream() << "no prefix";
|
|
|
|
if (check_counts) {
|
|
CHECK_EQ(base_num_infos + 14, LogMessage::num_messages(GLOG_INFO));
|
|
CHECK_EQ(base_num_warning + 3, LogMessage::num_messages(GLOG_WARNING));
|
|
CHECK_EQ(base_num_errors + 17, LogMessage::num_messages(GLOG_ERROR));
|
|
}
|
|
}
|
|
|
|
static void NoAllocNewHook() {
|
|
LOG(FATAL) << "unexpected new";
|
|
}
|
|
|
|
struct NewHook {
|
|
NewHook() {
|
|
g_new_hook = &NoAllocNewHook;
|
|
}
|
|
~NewHook() {
|
|
g_new_hook = NULL;
|
|
}
|
|
};
|
|
|
|
TEST(DeathNoAllocNewHook, logging) {
|
|
// tests that NewHook used below works
|
|
NewHook new_hook;
|
|
ASSERT_DEATH({
|
|
new int;
|
|
}, "unexpected new");
|
|
}
|
|
|
|
void TestRawLogging() {
|
|
string* foo = new string("foo ");
|
|
string huge_str(50000, 'a');
|
|
|
|
FlagSaver saver;
|
|
|
|
// Check that RAW loggging does not use mallocs.
|
|
NewHook new_hook;
|
|
|
|
RAW_LOG(INFO, "%s%s%d%c%f", foo->c_str(), "bar ", 10, ' ', 3.4);
|
|
char s[] = "array";
|
|
RAW_LOG(WARNING, "%s", s);
|
|
const char const_s[] = "const array";
|
|
RAW_LOG(INFO, "%s", const_s);
|
|
void* p = reinterpret_cast<void*>(PTR_TEST_VALUE);
|
|
RAW_LOG(INFO, "ptr %p", p);
|
|
p = NULL;
|
|
RAW_LOG(INFO, "ptr %p", p);
|
|
int j = 1000;
|
|
RAW_LOG(ERROR, "%s%d%c%010d%s%1x", foo->c_str(), j, ' ', j, " ", j);
|
|
RAW_VLOG(0, "foo %d", j);
|
|
|
|
#ifdef NDEBUG
|
|
RAW_LOG(INFO, "foo %d", j); // so that have same stderr to compare
|
|
#else
|
|
RAW_DLOG(INFO, "foo %d", j); // test RAW_DLOG in debug mode
|
|
#endif
|
|
|
|
// test how long messages are chopped:
|
|
RAW_LOG(WARNING, "Huge string: %s", huge_str.c_str());
|
|
RAW_VLOG(0, "Huge string: %s", huge_str.c_str());
|
|
|
|
FLAGS_v = 0;
|
|
RAW_LOG(INFO, "log");
|
|
RAW_VLOG(0, "vlog 0 on");
|
|
RAW_VLOG(1, "vlog 1 off");
|
|
RAW_VLOG(2, "vlog 2 off");
|
|
RAW_VLOG(3, "vlog 3 off");
|
|
FLAGS_v = 2;
|
|
RAW_LOG(INFO, "log");
|
|
RAW_VLOG(1, "vlog 1 on");
|
|
RAW_VLOG(2, "vlog 2 on");
|
|
RAW_VLOG(3, "vlog 3 off");
|
|
|
|
#ifdef NDEBUG
|
|
RAW_DCHECK(1 == 2, " RAW_DCHECK's shouldn't be compiled in normal mode");
|
|
#endif
|
|
|
|
RAW_CHECK(1 == 1, "should be ok");
|
|
RAW_DCHECK(true, "should be ok");
|
|
|
|
delete foo;
|
|
}
|
|
|
|
void LogWithLevels(int v, int severity, bool err, bool alsoerr) {
|
|
RAW_LOG(INFO,
|
|
"Test: v=%d stderrthreshold=%d logtostderr=%d alsologtostderr=%d",
|
|
v, severity, err, alsoerr);
|
|
|
|
FlagSaver saver;
|
|
|
|
FLAGS_v = v;
|
|
FLAGS_stderrthreshold = severity;
|
|
FLAGS_logtostderr = err;
|
|
FLAGS_alsologtostderr = alsoerr;
|
|
|
|
RAW_VLOG(-1, "vlog -1");
|
|
RAW_VLOG(0, "vlog 0");
|
|
RAW_VLOG(1, "vlog 1");
|
|
RAW_LOG(INFO, "log info");
|
|
RAW_LOG(WARNING, "log warning");
|
|
RAW_LOG(ERROR, "log error");
|
|
|
|
VLOG(-1) << "vlog -1";
|
|
VLOG(0) << "vlog 0";
|
|
VLOG(1) << "vlog 1";
|
|
LOG(INFO) << "log info";
|
|
LOG(WARNING) << "log warning";
|
|
LOG(ERROR) << "log error";
|
|
|
|
VLOG_IF(-1, true) << "vlog_if -1";
|
|
VLOG_IF(-1, false) << "don't vlog_if -1";
|
|
VLOG_IF(0, true) << "vlog_if 0";
|
|
VLOG_IF(0, false) << "don't vlog_if 0";
|
|
VLOG_IF(1, true) << "vlog_if 1";
|
|
VLOG_IF(1, false) << "don't vlog_if 1";
|
|
LOG_IF(INFO, true) << "log_if info";
|
|
LOG_IF(INFO, false) << "don't log_if info";
|
|
LOG_IF(WARNING, true) << "log_if warning";
|
|
LOG_IF(WARNING, false) << "don't log_if warning";
|
|
LOG_IF(ERROR, true) << "log_if error";
|
|
LOG_IF(ERROR, false) << "don't log_if error";
|
|
|
|
int c;
|
|
c = 1; VLOG_IF(100, c -= 2) << "vlog_if 100 expr"; EXPECT_EQ(c, -1);
|
|
c = 1; VLOG_IF(0, c -= 2) << "vlog_if 0 expr"; EXPECT_EQ(c, -1);
|
|
c = 1; LOG_IF(INFO, c -= 2) << "log_if info expr"; EXPECT_EQ(c, -1);
|
|
c = 1; LOG_IF(ERROR, c -= 2) << "log_if error expr"; EXPECT_EQ(c, -1);
|
|
c = 2; VLOG_IF(0, c -= 2) << "don't vlog_if 0 expr"; EXPECT_EQ(c, 0);
|
|
c = 2; LOG_IF(ERROR, c -= 2) << "don't log_if error expr"; EXPECT_EQ(c, 0);
|
|
|
|
c = 3; LOG_IF_EVERY_N(INFO, c -= 4, 1) << "log_if info every 1 expr";
|
|
EXPECT_EQ(c, -1);
|
|
c = 3; LOG_IF_EVERY_N(ERROR, c -= 4, 1) << "log_if error every 1 expr";
|
|
EXPECT_EQ(c, -1);
|
|
c = 4; LOG_IF_EVERY_N(ERROR, c -= 4, 3) << "don't log_if info every 3 expr";
|
|
EXPECT_EQ(c, 0);
|
|
c = 4; LOG_IF_EVERY_N(ERROR, c -= 4, 3) << "don't log_if error every 3 expr";
|
|
EXPECT_EQ(c, 0);
|
|
c = 5; VLOG_IF_EVERY_N(0, c -= 4, 1) << "vlog_if 0 every 1 expr";
|
|
EXPECT_EQ(c, 1);
|
|
c = 5; VLOG_IF_EVERY_N(100, c -= 4, 3) << "vlog_if 100 every 3 expr";
|
|
EXPECT_EQ(c, 1);
|
|
c = 6; VLOG_IF_EVERY_N(0, c -= 6, 1) << "don't vlog_if 0 every 1 expr";
|
|
EXPECT_EQ(c, 0);
|
|
c = 6; VLOG_IF_EVERY_N(100, c -= 6, 3) << "don't vlog_if 100 every 1 expr";
|
|
EXPECT_EQ(c, 0);
|
|
}
|
|
|
|
void TestLoggingLevels() {
|
|
LogWithLevels(0, GLOG_INFO, false, false);
|
|
LogWithLevels(1, GLOG_INFO, false, false);
|
|
LogWithLevels(-1, GLOG_INFO, false, false);
|
|
LogWithLevels(0, GLOG_WARNING, false, false);
|
|
LogWithLevels(0, GLOG_ERROR, false, false);
|
|
LogWithLevels(0, GLOG_FATAL, false, false);
|
|
LogWithLevels(0, GLOG_FATAL, true, false);
|
|
LogWithLevels(0, GLOG_FATAL, false, true);
|
|
LogWithLevels(1, GLOG_WARNING, false, false);
|
|
LogWithLevels(1, GLOG_FATAL, false, true);
|
|
}
|
|
|
|
TEST(DeathRawCHECK, logging) {
|
|
ASSERT_DEATH(RAW_CHECK(false, "failure 1"),
|
|
"RAW: Check false failed: failure 1");
|
|
ASSERT_DEBUG_DEATH(RAW_DCHECK(1 == 2, "failure 2"),
|
|
"RAW: Check 1 == 2 failed: failure 2");
|
|
}
|
|
|
|
void TestLogString() {
|
|
vector<string> errors;
|
|
vector<string> *no_errors = NULL;
|
|
|
|
LOG_STRING(INFO, &errors) << "LOG_STRING: " << "collected info";
|
|
LOG_STRING(WARNING, &errors) << "LOG_STRING: " << "collected warning";
|
|
LOG_STRING(ERROR, &errors) << "LOG_STRING: " << "collected error";
|
|
|
|
LOG_STRING(INFO, no_errors) << "LOG_STRING: " << "reported info";
|
|
LOG_STRING(WARNING, no_errors) << "LOG_STRING: " << "reported warning";
|
|
LOG_STRING(ERROR, NULL) << "LOG_STRING: " << "reported error";
|
|
|
|
for (size_t i = 0; i < errors.size(); ++i) {
|
|
LOG(INFO) << "Captured by LOG_STRING: " << errors[i];
|
|
}
|
|
}
|
|
|
|
void TestLogToString() {
|
|
string error;
|
|
string* no_error = NULL;
|
|
|
|
LOG_TO_STRING(INFO, &error) << "LOG_TO_STRING: " << "collected info";
|
|
LOG(INFO) << "Captured by LOG_TO_STRING: " << error;
|
|
LOG_TO_STRING(WARNING, &error) << "LOG_TO_STRING: " << "collected warning";
|
|
LOG(INFO) << "Captured by LOG_TO_STRING: " << error;
|
|
LOG_TO_STRING(ERROR, &error) << "LOG_TO_STRING: " << "collected error";
|
|
LOG(INFO) << "Captured by LOG_TO_STRING: " << error;
|
|
|
|
LOG_TO_STRING(INFO, no_error) << "LOG_TO_STRING: " << "reported info";
|
|
LOG_TO_STRING(WARNING, no_error) << "LOG_TO_STRING: " << "reported warning";
|
|
LOG_TO_STRING(ERROR, NULL) << "LOG_TO_STRING: " << "reported error";
|
|
}
|
|
|
|
class TestLogSinkImpl : public LogSink {
|
|
public:
|
|
vector<string> errors;
|
|
virtual void send(LogSeverity severity, const char* /* full_filename */,
|
|
const char* base_filename, int line,
|
|
const struct tm* tm_time,
|
|
const char* message, size_t message_len, int usecs) {
|
|
errors.push_back(
|
|
ToString(severity, base_filename, line, tm_time, message, message_len, usecs));
|
|
}
|
|
virtual void send(LogSeverity severity, const char* full_filename,
|
|
const char* base_filename, int line,
|
|
const struct tm* tm_time,
|
|
const char* message, size_t message_len) {
|
|
send(severity, full_filename, base_filename, line,
|
|
tm_time, message, message_len, 0);
|
|
}
|
|
};
|
|
|
|
void TestLogSink() {
|
|
TestLogSinkImpl sink;
|
|
LogSink *no_sink = NULL;
|
|
|
|
LOG_TO_SINK(&sink, INFO) << "LOG_TO_SINK: " << "collected info";
|
|
LOG_TO_SINK(&sink, WARNING) << "LOG_TO_SINK: " << "collected warning";
|
|
LOG_TO_SINK(&sink, ERROR) << "LOG_TO_SINK: " << "collected error";
|
|
|
|
LOG_TO_SINK(no_sink, INFO) << "LOG_TO_SINK: " << "reported info";
|
|
LOG_TO_SINK(no_sink, WARNING) << "LOG_TO_SINK: " << "reported warning";
|
|
LOG_TO_SINK(NULL, ERROR) << "LOG_TO_SINK: " << "reported error";
|
|
|
|
LOG_TO_SINK_BUT_NOT_TO_LOGFILE(&sink, INFO)
|
|
<< "LOG_TO_SINK_BUT_NOT_TO_LOGFILE: " << "collected info";
|
|
LOG_TO_SINK_BUT_NOT_TO_LOGFILE(&sink, WARNING)
|
|
<< "LOG_TO_SINK_BUT_NOT_TO_LOGFILE: " << "collected warning";
|
|
LOG_TO_SINK_BUT_NOT_TO_LOGFILE(&sink, ERROR)
|
|
<< "LOG_TO_SINK_BUT_NOT_TO_LOGFILE: " << "collected error";
|
|
|
|
LOG_TO_SINK_BUT_NOT_TO_LOGFILE(no_sink, INFO)
|
|
<< "LOG_TO_SINK_BUT_NOT_TO_LOGFILE: " << "thrashed info";
|
|
LOG_TO_SINK_BUT_NOT_TO_LOGFILE(no_sink, WARNING)
|
|
<< "LOG_TO_SINK_BUT_NOT_TO_LOGFILE: " << "thrashed warning";
|
|
LOG_TO_SINK_BUT_NOT_TO_LOGFILE(NULL, ERROR)
|
|
<< "LOG_TO_SINK_BUT_NOT_TO_LOGFILE: " << "thrashed error";
|
|
|
|
LOG(INFO) << "Captured by LOG_TO_SINK:";
|
|
for (size_t i = 0; i < sink.errors.size(); ++i) {
|
|
LogMessage("foo", LogMessage::kNoLogPrefix, GLOG_INFO).stream()
|
|
<< sink.errors[i];
|
|
}
|
|
}
|
|
|
|
// For testing using CHECK*() on anonymous enums.
|
|
enum {
|
|
CASE_A,
|
|
CASE_B
|
|
};
|
|
|
|
void TestCHECK() {
|
|
// Tests using CHECK*() on int values.
|
|
CHECK(1 == 1);
|
|
CHECK_EQ(1, 1);
|
|
CHECK_NE(1, 2);
|
|
CHECK_GE(1, 1);
|
|
CHECK_GE(2, 1);
|
|
CHECK_LE(1, 1);
|
|
CHECK_LE(1, 2);
|
|
CHECK_GT(2, 1);
|
|
CHECK_LT(1, 2);
|
|
|
|
// Tests using CHECK*() on anonymous enums.
|
|
// Apple's GCC doesn't like this.
|
|
#if !defined(OS_MACOSX)
|
|
CHECK_EQ(CASE_A, CASE_A);
|
|
CHECK_NE(CASE_A, CASE_B);
|
|
CHECK_GE(CASE_A, CASE_A);
|
|
CHECK_GE(CASE_B, CASE_A);
|
|
CHECK_LE(CASE_A, CASE_A);
|
|
CHECK_LE(CASE_A, CASE_B);
|
|
CHECK_GT(CASE_B, CASE_A);
|
|
CHECK_LT(CASE_A, CASE_B);
|
|
#endif
|
|
}
|
|
|
|
void TestDCHECK() {
|
|
#ifdef NDEBUG
|
|
DCHECK( 1 == 2 ) << " DCHECK's shouldn't be compiled in normal mode";
|
|
#endif
|
|
DCHECK( 1 == 1 );
|
|
DCHECK_EQ(1, 1);
|
|
DCHECK_NE(1, 2);
|
|
DCHECK_GE(1, 1);
|
|
DCHECK_GE(2, 1);
|
|
DCHECK_LE(1, 1);
|
|
DCHECK_LE(1, 2);
|
|
DCHECK_GT(2, 1);
|
|
DCHECK_LT(1, 2);
|
|
|
|
int64* orig_ptr = new int64;
|
|
int64* ptr = DCHECK_NOTNULL(orig_ptr);
|
|
CHECK_EQ(ptr, orig_ptr);
|
|
delete orig_ptr;
|
|
}
|
|
|
|
void TestSTREQ() {
|
|
CHECK_STREQ("this", "this");
|
|
CHECK_STREQ(NULL, NULL);
|
|
CHECK_STRCASEEQ("this", "tHiS");
|
|
CHECK_STRCASEEQ(NULL, NULL);
|
|
CHECK_STRNE("this", "tHiS");
|
|
CHECK_STRNE("this", NULL);
|
|
CHECK_STRCASENE("this", "that");
|
|
CHECK_STRCASENE(NULL, "that");
|
|
CHECK_STREQ((string("a")+"b").c_str(), "ab");
|
|
CHECK_STREQ(string("test").c_str(),
|
|
(string("te") + string("st")).c_str());
|
|
}
|
|
|
|
TEST(DeathSTREQ, logging) {
|
|
ASSERT_DEATH(CHECK_STREQ(NULL, "this"), "");
|
|
ASSERT_DEATH(CHECK_STREQ("this", "siht"), "");
|
|
ASSERT_DEATH(CHECK_STRCASEEQ(NULL, "siht"), "");
|
|
ASSERT_DEATH(CHECK_STRCASEEQ("this", "siht"), "");
|
|
ASSERT_DEATH(CHECK_STRNE(NULL, NULL), "");
|
|
ASSERT_DEATH(CHECK_STRNE("this", "this"), "");
|
|
ASSERT_DEATH(CHECK_STREQ((string("a")+"b").c_str(), "abc"), "");
|
|
}
|
|
|
|
TEST(CheckNOTNULL, Simple) {
|
|
int64 t;
|
|
void *ptr = static_cast<void *>(&t);
|
|
void *ref = CHECK_NOTNULL(ptr);
|
|
EXPECT_EQ(ptr, ref);
|
|
CHECK_NOTNULL(reinterpret_cast<char *>(ptr));
|
|
CHECK_NOTNULL(reinterpret_cast<unsigned char *>(ptr));
|
|
CHECK_NOTNULL(reinterpret_cast<int *>(ptr));
|
|
CHECK_NOTNULL(reinterpret_cast<int64 *>(ptr));
|
|
}
|
|
|
|
TEST(DeathCheckNN, Simple) {
|
|
ASSERT_DEATH(CHECK_NOTNULL(static_cast<void *>(NULL)), "");
|
|
}
|
|
|
|
// Get list of file names that match pattern
|
|
static void GetFiles(const string& pattern, vector<string>* files) {
|
|
files->clear();
|
|
#if defined(HAVE_GLOB_H)
|
|
glob_t g;
|
|
const int r = glob(pattern.c_str(), 0, NULL, &g);
|
|
CHECK((r == 0) || (r == GLOB_NOMATCH)) << ": error matching " << pattern;
|
|
for (size_t i = 0; i < g.gl_pathc; i++) {
|
|
files->push_back(string(g.gl_pathv[i]));
|
|
}
|
|
globfree(&g);
|
|
#elif defined(OS_WINDOWS)
|
|
WIN32_FIND_DATAA data;
|
|
HANDLE handle = FindFirstFileA(pattern.c_str(), &data);
|
|
size_t index = pattern.rfind('\\');
|
|
if (index == string::npos) {
|
|
LOG(FATAL) << "No directory separator.";
|
|
}
|
|
const string dirname = pattern.substr(0, index + 1);
|
|
if (handle == INVALID_HANDLE_VALUE) {
|
|
// Finding no files is OK.
|
|
return;
|
|
}
|
|
do {
|
|
files->push_back(dirname + data.cFileName);
|
|
} while (FindNextFileA(handle, &data));
|
|
BOOL result = FindClose(handle);
|
|
LOG_SYSRESULT(result);
|
|
#else
|
|
# error There is no way to do glob.
|
|
#endif
|
|
}
|
|
|
|
// Delete files patching pattern
|
|
static void DeleteFiles(const string& pattern) {
|
|
vector<string> files;
|
|
GetFiles(pattern, &files);
|
|
for (size_t i = 0; i < files.size(); i++) {
|
|
CHECK(unlink(files[i].c_str()) == 0) << ": " << strerror(errno);
|
|
}
|
|
}
|
|
|
|
//check string is in file (or is *NOT*, depending on optional checkInFileOrNot)
|
|
static void CheckFile(const string& name, const string& expected_string, const bool checkInFileOrNot = true) {
|
|
vector<string> files;
|
|
GetFiles(name + "*", &files);
|
|
CHECK_EQ(files.size(), 1UL);
|
|
|
|
FILE* file = fopen(files[0].c_str(), "r");
|
|
CHECK(file != NULL) << ": could not open " << files[0];
|
|
char buf[1000];
|
|
while (fgets(buf, sizeof(buf), file) != NULL) {
|
|
char* first = strstr(buf, expected_string.c_str());
|
|
//if first == NULL, not found.
|
|
//Terser than if (checkInFileOrNot && first != NULL || !check...
|
|
if (checkInFileOrNot != (first == NULL)) {
|
|
fclose(file);
|
|
return;
|
|
}
|
|
}
|
|
fclose(file);
|
|
LOG(FATAL) << "Did " << (checkInFileOrNot? "not " : "") << "find " << expected_string << " in " << files[0];
|
|
}
|
|
|
|
static void TestBasename() {
|
|
fprintf(stderr, "==== Test setting log file basename\n");
|
|
const string dest = FLAGS_test_tmpdir + "/logging_test_basename";
|
|
DeleteFiles(dest + "*");
|
|
|
|
SetLogDestination(GLOG_INFO, dest.c_str());
|
|
LOG(INFO) << "message to new base";
|
|
FlushLogFiles(GLOG_INFO);
|
|
|
|
CheckFile(dest, "message to new base");
|
|
|
|
// Release file handle for the destination file to unlock the file in Windows.
|
|
LogToStderr();
|
|
DeleteFiles(dest + "*");
|
|
}
|
|
|
|
static void TestBasenameAppendWhenNoTimestamp() {
|
|
fprintf(stderr, "==== Test setting log file basename without timestamp and appending properly\n");
|
|
const string dest = FLAGS_test_tmpdir + "/logging_test_basename_append_when_no_timestamp";
|
|
DeleteFiles(dest + "*");
|
|
|
|
ofstream out(dest.c_str());
|
|
out << "test preexisting content" << endl;
|
|
out.close();
|
|
|
|
CheckFile(dest, "test preexisting content");
|
|
|
|
FLAGS_timestamp_in_logfile_name=false;
|
|
SetLogDestination(GLOG_INFO, dest.c_str());
|
|
LOG(INFO) << "message to new base, appending to preexisting file";
|
|
FlushLogFiles(GLOG_INFO);
|
|
FLAGS_timestamp_in_logfile_name=true;
|
|
|
|
//if the logging overwrites the file instead of appending it will fail.
|
|
CheckFile(dest, "test preexisting content");
|
|
CheckFile(dest, "message to new base, appending to preexisting file");
|
|
|
|
// Release file handle for the destination file to unlock the file in Windows.
|
|
LogToStderr();
|
|
DeleteFiles(dest + "*");
|
|
}
|
|
|
|
static void TestTwoProcessesWrite() {
|
|
// test only implemented for platforms with fork & wait; the actual implementation relies on flock
|
|
#if defined(HAVE_SYS_WAIT_H) && defined(HAVE_UNISTD_H) && defined(HAVE_FCNTL)
|
|
fprintf(stderr, "==== Test setting log file basename and two processes writing - second should fail\n");
|
|
const string dest = FLAGS_test_tmpdir + "/logging_test_basename_two_processes_writing";
|
|
DeleteFiles(dest + "*");
|
|
|
|
//make both processes write into the same file (easier test)
|
|
FLAGS_timestamp_in_logfile_name=false;
|
|
SetLogDestination(GLOG_INFO, dest.c_str());
|
|
LOG(INFO) << "message to new base, parent";
|
|
FlushLogFiles(GLOG_INFO);
|
|
|
|
pid_t pid = fork();
|
|
CHECK_ERR(pid);
|
|
if (pid == 0) {
|
|
LOG(INFO) << "message to new base, child - should only appear on STDERR not on the file";
|
|
ShutdownGoogleLogging(); //for children proc
|
|
exit(0);
|
|
} else if (pid > 0) {
|
|
wait(NULL);
|
|
}
|
|
FLAGS_timestamp_in_logfile_name=true;
|
|
|
|
CheckFile(dest, "message to new base, parent");
|
|
CheckFile(dest, "message to new base, child - should only appear on STDERR not on the file", false);
|
|
|
|
// Release
|
|
LogToStderr();
|
|
DeleteFiles(dest + "*");
|
|
#endif
|
|
}
|
|
|
|
static void TestSymlink() {
|
|
#ifndef OS_WINDOWS
|
|
fprintf(stderr, "==== Test setting log file symlink\n");
|
|
string dest = FLAGS_test_tmpdir + "/logging_test_symlink";
|
|
string sym = FLAGS_test_tmpdir + "/symlinkbase";
|
|
DeleteFiles(dest + "*");
|
|
DeleteFiles(sym + "*");
|
|
|
|
SetLogSymlink(GLOG_INFO, "symlinkbase");
|
|
SetLogDestination(GLOG_INFO, dest.c_str());
|
|
LOG(INFO) << "message to new symlink";
|
|
FlushLogFiles(GLOG_INFO);
|
|
CheckFile(sym, "message to new symlink");
|
|
|
|
DeleteFiles(dest + "*");
|
|
DeleteFiles(sym + "*");
|
|
#endif
|
|
}
|
|
|
|
static void TestExtension() {
|
|
fprintf(stderr, "==== Test setting log file extension\n");
|
|
string dest = FLAGS_test_tmpdir + "/logging_test_extension";
|
|
DeleteFiles(dest + "*");
|
|
|
|
SetLogDestination(GLOG_INFO, dest.c_str());
|
|
SetLogFilenameExtension("specialextension");
|
|
LOG(INFO) << "message to new extension";
|
|
FlushLogFiles(GLOG_INFO);
|
|
CheckFile(dest, "message to new extension");
|
|
|
|
// Check that file name ends with extension
|
|
vector<string> filenames;
|
|
GetFiles(dest + "*", &filenames);
|
|
CHECK_EQ(filenames.size(), 1UL);
|
|
CHECK(strstr(filenames[0].c_str(), "specialextension") != NULL);
|
|
|
|
// Release file handle for the destination file to unlock the file in Windows.
|
|
LogToStderr();
|
|
DeleteFiles(dest + "*");
|
|
}
|
|
|
|
struct MyLogger : public base::Logger {
|
|
string data;
|
|
|
|
virtual void Write(bool /* should_flush */,
|
|
time_t /* timestamp */,
|
|
const char* message,
|
|
int length) {
|
|
data.append(message, length);
|
|
}
|
|
|
|
virtual void Flush() { }
|
|
|
|
virtual uint32 LogSize() { return data.length(); }
|
|
};
|
|
|
|
static void TestWrapper() {
|
|
fprintf(stderr, "==== Test log wrapper\n");
|
|
|
|
MyLogger my_logger;
|
|
base::Logger* old_logger = base::GetLogger(GLOG_INFO);
|
|
base::SetLogger(GLOG_INFO, &my_logger);
|
|
LOG(INFO) << "Send to wrapped logger";
|
|
FlushLogFiles(GLOG_INFO);
|
|
base::SetLogger(GLOG_INFO, old_logger);
|
|
|
|
CHECK(strstr(my_logger.data.c_str(), "Send to wrapped logger") != NULL);
|
|
}
|
|
|
|
static void TestErrno() {
|
|
fprintf(stderr, "==== Test errno preservation\n");
|
|
|
|
errno = ENOENT;
|
|
TestLogging(false);
|
|
CHECK_EQ(errno, ENOENT);
|
|
}
|
|
|
|
static void TestOneTruncate(const char *path, int64 limit, int64 keep,
|
|
int64 dsize, int64 ksize, int64 expect) {
|
|
int fd;
|
|
CHECK_ERR(fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0600));
|
|
|
|
const char *discardstr = "DISCARDME!", *keepstr = "KEEPME!";
|
|
const size_t discard_size = strlen(discardstr), keep_size = strlen(keepstr);
|
|
|
|
// Fill the file with the requested data; first discard data, then kept data
|
|
int64 written = 0;
|
|
while (written < dsize) {
|
|
int bytes = min<int64>(dsize - written, discard_size);
|
|
CHECK_ERR(write(fd, discardstr, bytes));
|
|
written += bytes;
|
|
}
|
|
written = 0;
|
|
while (written < ksize) {
|
|
int bytes = min<int64>(ksize - written, keep_size);
|
|
CHECK_ERR(write(fd, keepstr, bytes));
|
|
written += bytes;
|
|
}
|
|
|
|
TruncateLogFile(path, limit, keep);
|
|
|
|
// File should now be shorter
|
|
struct stat statbuf;
|
|
CHECK_ERR(fstat(fd, &statbuf));
|
|
CHECK_EQ(statbuf.st_size, expect);
|
|
CHECK_ERR(lseek(fd, 0, SEEK_SET));
|
|
|
|
// File should contain the suffix of the original file
|
|
const size_t buf_size = statbuf.st_size + 1;
|
|
char* buf = new char[buf_size];
|
|
memset(buf, 0, buf_size);
|
|
CHECK_ERR(read(fd, buf, buf_size));
|
|
|
|
const char *p = buf;
|
|
int64 checked = 0;
|
|
while (checked < expect) {
|
|
int bytes = min<int64>(expect - checked, keep_size);
|
|
CHECK(!memcmp(p, keepstr, bytes));
|
|
checked += bytes;
|
|
}
|
|
close(fd);
|
|
delete[] buf;
|
|
}
|
|
|
|
static void TestTruncate() {
|
|
#ifdef HAVE_UNISTD_H
|
|
fprintf(stderr, "==== Test log truncation\n");
|
|
string path = FLAGS_test_tmpdir + "/truncatefile";
|
|
|
|
// Test on a small file
|
|
TestOneTruncate(path.c_str(), 10, 10, 10, 10, 10);
|
|
|
|
// And a big file (multiple blocks to copy)
|
|
TestOneTruncate(path.c_str(), 2<<20, 4<<10, 3<<20, 4<<10, 4<<10);
|
|
|
|
// Check edge-case limits
|
|
TestOneTruncate(path.c_str(), 10, 20, 0, 20, 20);
|
|
TestOneTruncate(path.c_str(), 10, 0, 0, 0, 0);
|
|
TestOneTruncate(path.c_str(), 10, 50, 0, 10, 10);
|
|
TestOneTruncate(path.c_str(), 50, 100, 0, 30, 30);
|
|
|
|
// MacOSX 10.4 doesn't fail in this case.
|
|
// Windows doesn't have symlink.
|
|
// Let's just ignore this test for these cases.
|
|
#if !defined(OS_MACOSX) && !defined(OS_WINDOWS)
|
|
// Through a symlink should fail to truncate
|
|
string linkname = path + ".link";
|
|
unlink(linkname.c_str());
|
|
CHECK_ERR(symlink(path.c_str(), linkname.c_str()));
|
|
TestOneTruncate(linkname.c_str(), 10, 10, 0, 30, 30);
|
|
#endif
|
|
|
|
// The /proc/self path makes sense only for linux.
|
|
#if defined(OS_LINUX)
|
|
// Through an open fd symlink should work
|
|
int fd;
|
|
CHECK_ERR(fd = open(path.c_str(), O_APPEND | O_WRONLY));
|
|
char fdpath[64];
|
|
snprintf(fdpath, sizeof(fdpath), "/proc/self/fd/%d", fd);
|
|
TestOneTruncate(fdpath, 10, 10, 10, 10, 10);
|
|
#endif
|
|
|
|
#endif
|
|
}
|
|
|
|
struct RecordDeletionLogger : public base::Logger {
|
|
RecordDeletionLogger(bool* set_on_destruction,
|
|
base::Logger* wrapped_logger) :
|
|
set_on_destruction_(set_on_destruction),
|
|
wrapped_logger_(wrapped_logger)
|
|
{
|
|
*set_on_destruction_ = false;
|
|
}
|
|
virtual ~RecordDeletionLogger() {
|
|
*set_on_destruction_ = true;
|
|
}
|
|
virtual void Write(bool force_flush,
|
|
time_t timestamp,
|
|
const char* message,
|
|
int length) {
|
|
wrapped_logger_->Write(force_flush, timestamp, message, length);
|
|
}
|
|
virtual void Flush() { wrapped_logger_->Flush(); }
|
|
virtual uint32 LogSize() { return wrapped_logger_->LogSize(); }
|
|
private:
|
|
bool* set_on_destruction_;
|
|
base::Logger* wrapped_logger_;
|
|
};
|
|
|
|
static void TestCustomLoggerDeletionOnShutdown() {
|
|
bool custom_logger_deleted = false;
|
|
base::SetLogger(GLOG_INFO,
|
|
new RecordDeletionLogger(&custom_logger_deleted,
|
|
base::GetLogger(GLOG_INFO)));
|
|
EXPECT_TRUE(IsGoogleLoggingInitialized());
|
|
ShutdownGoogleLogging();
|
|
EXPECT_TRUE(custom_logger_deleted);
|
|
EXPECT_FALSE(IsGoogleLoggingInitialized());
|
|
}
|
|
|
|
_START_GOOGLE_NAMESPACE_
|
|
namespace glog_internal_namespace_ {
|
|
extern // in logging.cc
|
|
bool SafeFNMatch_(const char* pattern, size_t patt_len,
|
|
const char* str, size_t str_len);
|
|
} // namespace glog_internal_namespace_
|
|
using glog_internal_namespace_::SafeFNMatch_;
|
|
_END_GOOGLE_NAMESPACE_
|
|
|
|
static bool WrapSafeFNMatch(string pattern, string str) {
|
|
pattern += "abc";
|
|
str += "defgh";
|
|
return SafeFNMatch_(pattern.data(), pattern.size() - 3,
|
|
str.data(), str.size() - 5);
|
|
}
|
|
|
|
TEST(SafeFNMatch, logging) {
|
|
CHECK(WrapSafeFNMatch("foo", "foo"));
|
|
CHECK(!WrapSafeFNMatch("foo", "bar"));
|
|
CHECK(!WrapSafeFNMatch("foo", "fo"));
|
|
CHECK(!WrapSafeFNMatch("foo", "foo2"));
|
|
CHECK(WrapSafeFNMatch("bar/foo.ext", "bar/foo.ext"));
|
|
CHECK(WrapSafeFNMatch("*ba*r/fo*o.ext*", "bar/foo.ext"));
|
|
CHECK(!WrapSafeFNMatch("bar/foo.ext", "bar/baz.ext"));
|
|
CHECK(!WrapSafeFNMatch("bar/foo.ext", "bar/foo"));
|
|
CHECK(!WrapSafeFNMatch("bar/foo.ext", "bar/foo.ext.zip"));
|
|
CHECK(WrapSafeFNMatch("ba?/*.ext", "bar/foo.ext"));
|
|
CHECK(WrapSafeFNMatch("ba?/*.ext", "baZ/FOO.ext"));
|
|
CHECK(!WrapSafeFNMatch("ba?/*.ext", "barr/foo.ext"));
|
|
CHECK(!WrapSafeFNMatch("ba?/*.ext", "bar/foo.ext2"));
|
|
CHECK(WrapSafeFNMatch("ba?/*", "bar/foo.ext2"));
|
|
CHECK(WrapSafeFNMatch("ba?/*", "bar/"));
|
|
CHECK(!WrapSafeFNMatch("ba?/?", "bar/"));
|
|
CHECK(!WrapSafeFNMatch("ba?/*", "bar"));
|
|
}
|
|
|
|
// TestWaitingLogSink will save messages here
|
|
// No lock: Accessed only by TestLogSinkWriter thread
|
|
// and after its demise by its creator.
|
|
static vector<string> global_messages;
|
|
|
|
// helper for TestWaitingLogSink below.
|
|
// Thread that does the logic of TestWaitingLogSink
|
|
// It's free to use LOG() itself.
|
|
class TestLogSinkWriter : public Thread {
|
|
public:
|
|
|
|
TestLogSinkWriter() : should_exit_(false) {
|
|
SetJoinable(true);
|
|
Start();
|
|
}
|
|
|
|
// Just buffer it (can't use LOG() here).
|
|
void Buffer(const string& message) {
|
|
mutex_.Lock();
|
|
RAW_LOG(INFO, "Buffering");
|
|
messages_.push(message);
|
|
mutex_.Unlock();
|
|
RAW_LOG(INFO, "Buffered");
|
|
}
|
|
|
|
// Wait for the buffer to clear (can't use LOG() here).
|
|
void Wait() {
|
|
RAW_LOG(INFO, "Waiting");
|
|
mutex_.Lock();
|
|
while (!NoWork()) {
|
|
mutex_.Unlock();
|
|
SleepForMilliseconds(1);
|
|
mutex_.Lock();
|
|
}
|
|
RAW_LOG(INFO, "Waited");
|
|
mutex_.Unlock();
|
|
}
|
|
|
|
// Trigger thread exit.
|
|
void Stop() {
|
|
MutexLock l(&mutex_);
|
|
should_exit_ = true;
|
|
}
|
|
|
|
private:
|
|
|
|
// helpers ---------------
|
|
|
|
// For creating a "Condition".
|
|
bool NoWork() { return messages_.empty(); }
|
|
bool HaveWork() { return !messages_.empty() || should_exit_; }
|
|
|
|
// Thread body; CAN use LOG() here!
|
|
virtual void Run() {
|
|
while (1) {
|
|
mutex_.Lock();
|
|
while (!HaveWork()) {
|
|
mutex_.Unlock();
|
|
SleepForMilliseconds(1);
|
|
mutex_.Lock();
|
|
}
|
|
if (should_exit_ && messages_.empty()) {
|
|
mutex_.Unlock();
|
|
break;
|
|
}
|
|
// Give the main thread time to log its message,
|
|
// so that we get a reliable log capture to compare to golden file.
|
|
// Same for the other sleep below.
|
|
SleepForMilliseconds(20);
|
|
RAW_LOG(INFO, "Sink got a messages"); // only RAW_LOG under mutex_ here
|
|
string message = messages_.front();
|
|
messages_.pop();
|
|
// Normally this would be some more real/involved logging logic
|
|
// where LOG() usage can't be eliminated,
|
|
// e.g. pushing the message over with an RPC:
|
|
int messages_left = messages_.size();
|
|
mutex_.Unlock();
|
|
SleepForMilliseconds(20);
|
|
// May not use LOG while holding mutex_, because Buffer()
|
|
// acquires mutex_, and Buffer is called from LOG(),
|
|
// which has its own internal mutex:
|
|
// LOG()->LogToSinks()->TestWaitingLogSink::send()->Buffer()
|
|
LOG(INFO) << "Sink is sending out a message: " << message;
|
|
LOG(INFO) << "Have " << messages_left << " left";
|
|
global_messages.push_back(message);
|
|
}
|
|
}
|
|
|
|
// data ---------------
|
|
|
|
Mutex mutex_;
|
|
bool should_exit_;
|
|
queue<string> messages_; // messages to be logged
|
|
};
|
|
|
|
// A log sink that exercises WaitTillSent:
|
|
// it pushes data to a buffer and wakes up another thread to do the logging
|
|
// (that other thread can than use LOG() itself),
|
|
class TestWaitingLogSink : public LogSink {
|
|
public:
|
|
|
|
TestWaitingLogSink() {
|
|
tid_ = pthread_self(); // for thread-specific behavior
|
|
AddLogSink(this);
|
|
}
|
|
~TestWaitingLogSink() {
|
|
RemoveLogSink(this);
|
|
writer_.Stop();
|
|
writer_.Join();
|
|
}
|
|
|
|
// (re)define LogSink interface
|
|
|
|
virtual void send(LogSeverity severity, const char* /* full_filename */,
|
|
const char* base_filename, int line,
|
|
const struct tm* tm_time,
|
|
const char* message, size_t message_len, int usecs) {
|
|
// Push it to Writer thread if we are the original logging thread.
|
|
// Note: Something like ThreadLocalLogSink is a better choice
|
|
// to do thread-specific LogSink logic for real.
|
|
if (pthread_equal(tid_, pthread_self())) {
|
|
writer_.Buffer(ToString(severity, base_filename, line,
|
|
tm_time, message, message_len, usecs));
|
|
}
|
|
}
|
|
|
|
virtual void send(LogSeverity severity, const char* full_filename,
|
|
const char* base_filename, int line,
|
|
const struct tm* tm_time,
|
|
const char* message, size_t message_len) {
|
|
send(severity, full_filename, base_filename, line, tm_time, message, message_len);
|
|
}
|
|
|
|
virtual void WaitTillSent() {
|
|
// Wait for Writer thread if we are the original logging thread.
|
|
if (pthread_equal(tid_, pthread_self())) writer_.Wait();
|
|
}
|
|
|
|
private:
|
|
|
|
pthread_t tid_;
|
|
TestLogSinkWriter writer_;
|
|
};
|
|
|
|
// Check that LogSink::WaitTillSent can be used in the advertised way.
|
|
// We also do golden-stderr comparison.
|
|
static void TestLogSinkWaitTillSent() {
|
|
{ TestWaitingLogSink sink;
|
|
// Sleeps give the sink threads time to do all their work,
|
|
// so that we get a reliable log capture to compare to the golden file.
|
|
LOG(INFO) << "Message 1";
|
|
SleepForMilliseconds(60);
|
|
LOG(ERROR) << "Message 2";
|
|
SleepForMilliseconds(60);
|
|
LOG(WARNING) << "Message 3";
|
|
SleepForMilliseconds(60);
|
|
}
|
|
for (size_t i = 0; i < global_messages.size(); ++i) {
|
|
LOG(INFO) << "Sink capture: " << global_messages[i];
|
|
}
|
|
CHECK_EQ(global_messages.size(), 3UL);
|
|
}
|
|
|
|
TEST(Strerror, logging) {
|
|
int errcode = EINTR;
|
|
char *msg = strdup(strerror(errcode));
|
|
const size_t buf_size = strlen(msg) + 1;
|
|
char *buf = new char[buf_size];
|
|
CHECK_EQ(posix_strerror_r(errcode, NULL, 0), -1);
|
|
buf[0] = 'A';
|
|
CHECK_EQ(posix_strerror_r(errcode, buf, 0), -1);
|
|
CHECK_EQ(buf[0], 'A');
|
|
CHECK_EQ(posix_strerror_r(errcode, NULL, buf_size), -1);
|
|
#if defined(OS_MACOSX) || defined(OS_FREEBSD) || defined(OS_OPENBSD)
|
|
// MacOSX or FreeBSD considers this case is an error since there is
|
|
// no enough space.
|
|
CHECK_EQ(posix_strerror_r(errcode, buf, 1), -1);
|
|
#else
|
|
CHECK_EQ(posix_strerror_r(errcode, buf, 1), 0);
|
|
#endif
|
|
CHECK_STREQ(buf, "");
|
|
CHECK_EQ(posix_strerror_r(errcode, buf, buf_size), 0);
|
|
CHECK_STREQ(buf, msg);
|
|
delete[] buf;
|
|
CHECK_EQ(msg, StrError(errcode));
|
|
free(msg);
|
|
}
|
|
|
|
// Simple routines to look at the sizes of generated code for LOG(FATAL) and
|
|
// CHECK(..) via objdump
|
|
/*
|
|
static void MyFatal() {
|
|
LOG(FATAL) << "Failed";
|
|
}
|
|
static void MyCheck(bool a, bool b) {
|
|
CHECK_EQ(a, b);
|
|
}
|
|
*/
|
|
#ifdef HAVE_LIB_GMOCK
|
|
|
|
TEST(DVLog, Basic) {
|
|
ScopedMockLog log;
|
|
|
|
#if NDEBUG
|
|
// We are expecting that nothing is logged.
|
|
EXPECT_CALL(log, Log(_, _, _)).Times(0);
|
|
#else
|
|
EXPECT_CALL(log, Log(INFO, __FILE__, "debug log"));
|
|
#endif
|
|
|
|
FLAGS_v = 1;
|
|
DVLOG(1) << "debug log";
|
|
}
|
|
|
|
TEST(DVLog, V0) {
|
|
ScopedMockLog log;
|
|
|
|
// We are expecting that nothing is logged.
|
|
EXPECT_CALL(log, Log(_, _, _)).Times(0);
|
|
|
|
FLAGS_v = 0;
|
|
DVLOG(1) << "debug log";
|
|
}
|
|
|
|
TEST(LogAtLevel, Basic) {
|
|
ScopedMockLog log;
|
|
|
|
// The function version outputs "logging.h" as a file name.
|
|
EXPECT_CALL(log, Log(WARNING, StrNe(__FILE__), "function version"));
|
|
EXPECT_CALL(log, Log(INFO, __FILE__, "macro version"));
|
|
|
|
int severity = WARNING;
|
|
LogAtLevel(severity, "function version");
|
|
|
|
severity = INFO;
|
|
// We can use the macro version as a C++ stream.
|
|
LOG_AT_LEVEL(severity) << "macro" << ' ' << "version";
|
|
}
|
|
|
|
TEST(TestExitOnDFatal, ToBeOrNotToBe) {
|
|
// Check the default setting...
|
|
EXPECT_TRUE(base::internal::GetExitOnDFatal());
|
|
|
|
// Turn off...
|
|
base::internal::SetExitOnDFatal(false);
|
|
EXPECT_FALSE(base::internal::GetExitOnDFatal());
|
|
|
|
// We don't die.
|
|
{
|
|
ScopedMockLog log;
|
|
//EXPECT_CALL(log, Log(_, _, _)).Times(AnyNumber());
|
|
// LOG(DFATAL) has severity FATAL if debugging, but is
|
|
// downgraded to ERROR if not debugging.
|
|
const LogSeverity severity =
|
|
#ifdef NDEBUG
|
|
ERROR;
|
|
#else
|
|
FATAL;
|
|
#endif
|
|
EXPECT_CALL(log, Log(severity, __FILE__, "This should not be fatal"));
|
|
LOG(DFATAL) << "This should not be fatal";
|
|
}
|
|
|
|
// Turn back on...
|
|
base::internal::SetExitOnDFatal(true);
|
|
EXPECT_TRUE(base::internal::GetExitOnDFatal());
|
|
|
|
#ifdef GTEST_HAS_DEATH_TEST
|
|
// 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
|
|
}
|
|
|
|
#ifdef HAVE_STACKTRACE
|
|
|
|
static void BacktraceAtHelper() {
|
|
LOG(INFO) << "Not me";
|
|
|
|
// The vertical spacing of the next 3 lines is significant.
|
|
LOG(INFO) << "Backtrace me";
|
|
}
|
|
static int kBacktraceAtLine = __LINE__ - 2; // The line of the LOG(INFO) above
|
|
|
|
TEST(LogBacktraceAt, DoesNotBacktraceWhenDisabled) {
|
|
StrictMock<ScopedMockLog> log;
|
|
|
|
FLAGS_log_backtrace_at = "";
|
|
|
|
EXPECT_CALL(log, Log(_, _, "Backtrace me"));
|
|
EXPECT_CALL(log, Log(_, _, "Not me"));
|
|
|
|
BacktraceAtHelper();
|
|
}
|
|
|
|
TEST(LogBacktraceAt, DoesBacktraceAtRightLineWhenEnabled) {
|
|
StrictMock<ScopedMockLog> log;
|
|
|
|
char where[100];
|
|
snprintf(where, 100, "%s:%d", const_basename(__FILE__), kBacktraceAtLine);
|
|
FLAGS_log_backtrace_at = where;
|
|
|
|
// The LOG at the specified line should include a stacktrace which includes
|
|
// the name of the containing function, followed by the log message.
|
|
// We use HasSubstr()s instead of ContainsRegex() for environments
|
|
// which don't have regexp.
|
|
EXPECT_CALL(log, Log(_, _, AllOf(HasSubstr("stacktrace:"),
|
|
HasSubstr("BacktraceAtHelper"),
|
|
HasSubstr("main"),
|
|
HasSubstr("Backtrace me"))));
|
|
// Other LOGs should not include a backtrace.
|
|
EXPECT_CALL(log, Log(_, _, "Not me"));
|
|
|
|
BacktraceAtHelper();
|
|
}
|
|
|
|
#endif // HAVE_STACKTRACE
|
|
|
|
#endif // HAVE_LIB_GMOCK
|
|
|
|
struct UserDefinedClass {
|
|
bool operator==(const UserDefinedClass&) const { return true; }
|
|
};
|
|
|
|
inline ostream& operator<<(ostream& out, const UserDefinedClass&) {
|
|
out << "OK";
|
|
return out;
|
|
}
|
|
|
|
TEST(UserDefinedClass, logging) {
|
|
UserDefinedClass u;
|
|
vector<string> buf;
|
|
LOG_STRING(INFO, &buf) << u;
|
|
CHECK_EQ(1UL, buf.size());
|
|
CHECK(buf[0].find("OK") != string::npos);
|
|
|
|
// We must be able to compile this.
|
|
CHECK_EQ(u, u);
|
|
}
|