diff options
author | Dan Albert <danalbert@google.com> | 2015-03-20 21:06:52 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2015-03-20 21:06:52 +0000 |
commit | db7a994e1687574a675ccf5d56aa76df4cd98a88 (patch) | |
tree | 13ff7df288fe17714e55b2da8094b441229e6d97 | |
parent | 48a5288ed3bf527e68dbefc9884f3a713ef632a2 (diff) | |
parent | 58310b49fc8a7a713b922319a849a419858db79e (diff) | |
download | system_core-db7a994e1687574a675ccf5d56aa76df4cd98a88.zip system_core-db7a994e1687574a675ccf5d56aa76df4cd98a88.tar.gz system_core-db7a994e1687574a675ccf5d56aa76df4cd98a88.tar.bz2 |
Merge "Add google3 style logging to libbase."
-rw-r--r-- | base/Android.mk | 6 | ||||
-rw-r--r-- | base/CPPLINT.cfg | 2 | ||||
-rw-r--r-- | base/file_test.cpp | 24 | ||||
-rw-r--r-- | base/include/base/logging.h | 267 | ||||
-rw-r--r-- | base/logging.cpp | 323 | ||||
-rw-r--r-- | base/logging_test.cpp | 171 | ||||
-rw-r--r-- | base/test_main.cpp | 25 | ||||
-rw-r--r-- | base/test_utils.cpp | 38 | ||||
-rw-r--r-- | base/test_utils.h | 32 |
9 files changed, 864 insertions, 24 deletions
diff --git a/base/Android.mk b/base/Android.mk index 17d6ece..162c6cb 100644 --- a/base/Android.mk +++ b/base/Android.mk @@ -18,13 +18,17 @@ LOCAL_PATH := $(call my-dir) libbase_src_files := \ file.cpp \ + logging.cpp \ stringprintf.cpp \ strings.cpp \ libbase_test_src_files := \ file_test.cpp \ + logging_test.cpp \ stringprintf_test.cpp \ strings_test.cpp \ + test_main.cpp \ + test_utils.cpp \ libbase_cppflags := \ -Wall \ @@ -77,6 +81,7 @@ include $(CLEAR_VARS) LOCAL_MODULE := libbase_test LOCAL_CLANG := true LOCAL_SRC_FILES := $(libbase_test_src_files) +LOCAL_C_INCLUDES := $(LOCAL_PATH) LOCAL_CPPFLAGS := $(libbase_cppflags) LOCAL_SHARED_LIBRARIES := libbase LOCAL_MULTILIB := both @@ -87,6 +92,7 @@ include $(BUILD_NATIVE_TEST) include $(CLEAR_VARS) LOCAL_MODULE := libbase_test LOCAL_SRC_FILES := $(libbase_test_src_files) +LOCAL_C_INCLUDES := $(LOCAL_PATH) LOCAL_CPPFLAGS := $(libbase_cppflags) LOCAL_SHARED_LIBRARIES := libbase LOCAL_MULTILIB := both diff --git a/base/CPPLINT.cfg b/base/CPPLINT.cfg index 5ee068e..a61c08d 100644 --- a/base/CPPLINT.cfg +++ b/base/CPPLINT.cfg @@ -1,2 +1,2 @@ set noparent -filter=-build/header_guard +filter=-build/header_guard,-build/include,-build/c++11 diff --git a/base/file_test.cpp b/base/file_test.cpp index 34b8755..fc48b32 100644 --- a/base/file_test.cpp +++ b/base/file_test.cpp @@ -24,29 +24,7 @@ #include <string> -class TemporaryFile { - public: - TemporaryFile() { - init("/data/local/tmp"); - if (fd == -1) { - init("/tmp"); - } - } - - ~TemporaryFile() { - close(fd); - unlink(filename); - } - - int fd; - char filename[1024]; - - private: - void init(const char* tmp_dir) { - snprintf(filename, sizeof(filename), "%s/TemporaryFile-XXXXXX", tmp_dir); - fd = mkstemp(filename); - } -}; +#include "test_utils.h" TEST(file, ReadFileToString_ENOENT) { std::string s("hello"); diff --git a/base/include/base/logging.h b/base/include/base/logging.h new file mode 100644 index 0000000..5e115fe --- /dev/null +++ b/base/include/base/logging.h @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef BASE_LOGGING_H +#define BASE_LOGGING_H + +#include <memory> +#include <ostream> + +#include "base/macros.h" + +namespace android { +namespace base { + +enum LogSeverity { + VERBOSE, + DEBUG, + INFO, + WARNING, + ERROR, + FATAL, +}; + +// Configure logging based on ANDROID_LOG_TAGS environment variable. +// We need to parse a string that looks like +// +// *:v jdwp:d dalvikvm:d dalvikvm-gc:i dalvikvmi:i +// +// The tag (or '*' for the global level) comes first, followed by a colon and a +// letter indicating the minimum priority level we're expected to log. This can +// be used to reveal or conceal logs with specific tags. +extern void InitLogging(char* argv[]); + +// Returns the command line used to invoke the current tool or nullptr if +// InitLogging hasn't been performed. +extern const char* GetCmdLine(); + +// The command used to start the program, such as "/system/bin/dalvikvm". If +// InitLogging hasn't been performed then just returns "unknown" +extern const char* ProgramInvocationName(); + +// A short version of the command used to start the program, such as "dalvikvm". +// If InitLogging hasn't been performed then just returns "unknown" +extern const char* ProgramInvocationShortName(); + +// Logs a message to logcat on Android otherwise to stderr. If the severity is +// FATAL it also causes an abort. For example: +// +// LOG(FATAL) << "We didn't expect to reach here"; +#define LOG(severity) \ + ::android::base::LogMessage(__FILE__, __LINE__, ::android::base::severity, \ + -1).stream() + +// A variant of LOG that also logs the current errno value. To be used when +// library calls fail. +#define PLOG(severity) \ + ::android::base::LogMessage(__FILE__, __LINE__, ::android::base::severity, \ + errno).stream() + +// Marker that code is yet to be implemented. +#define UNIMPLEMENTED(level) \ + LOG(level) << __PRETTY_FUNCTION__ << " unimplemented " + +// Check whether condition x holds and LOG(FATAL) if not. The value of the +// expression x is only evaluated once. Extra logging can be appended using << +// after. For example: +// +// CHECK(false == true) results in a log message of +// "Check failed: false == true". +#define CHECK(x) \ + if (UNLIKELY(!(x))) \ + ::android::base::LogMessage(__FILE__, __LINE__, ::android::base::FATAL, \ + -1).stream() \ + << "Check failed: " #x << " " + +// Helper for CHECK_xx(x,y) macros. +#define CHECK_OP(LHS, RHS, OP) \ + for (auto _values = ::android::base::MakeEagerEvaluator(LHS, RHS); \ + UNLIKELY(!(_values.lhs OP _values.rhs)); \ + /* empty */) \ + ::android::base::LogMessage(__FILE__, __LINE__, ::android::base::FATAL, -1) \ + .stream() \ + << "Check failed: " << #LHS << " " << #OP << " " << #RHS \ + << " (" #LHS "=" << _values.lhs << ", " #RHS "=" << _values.rhs << ") " + +// Check whether a condition holds between x and y, LOG(FATAL) if not. The value +// of the expressions x and y is evaluated once. Extra logging can be appended +// using << after. For example: +// +// CHECK_NE(0 == 1, false) results in +// "Check failed: false != false (0==1=false, false=false) ". +#define CHECK_EQ(x, y) CHECK_OP(x, y, == ) +#define CHECK_NE(x, y) CHECK_OP(x, y, != ) +#define CHECK_LE(x, y) CHECK_OP(x, y, <= ) +#define CHECK_LT(x, y) CHECK_OP(x, y, < ) +#define CHECK_GE(x, y) CHECK_OP(x, y, >= ) +#define CHECK_GT(x, y) CHECK_OP(x, y, > ) + +// Helper for CHECK_STRxx(s1,s2) macros. +#define CHECK_STROP(s1, s2, sense) \ + if (UNLIKELY((strcmp(s1, s2) == 0) != sense)) \ + LOG(FATAL) << "Check failed: " \ + << "\"" << s1 << "\"" \ + << (sense ? " == " : " != ") << "\"" << s2 << "\"" + +// Check for string (const char*) equality between s1 and s2, LOG(FATAL) if not. +#define CHECK_STREQ(s1, s2) CHECK_STROP(s1, s2, true) +#define CHECK_STRNE(s1, s2) CHECK_STROP(s1, s2, false) + +// Perform the pthread function call(args), LOG(FATAL) on error. +#define CHECK_PTHREAD_CALL(call, args, what) \ + do { \ + int rc = call args; \ + if (rc != 0) { \ + errno = rc; \ + PLOG(FATAL) << #call << " failed for " << what; \ + } \ + } while (false) + +// CHECK that can be used in a constexpr function. For example: +// +// constexpr int half(int n) { +// return +// DCHECK_CONSTEXPR(n >= 0, , 0) +// CHECK_CONSTEXPR((n & 1) == 0), +// << "Extra debugging output: n = " << n, 0) +// n / 2; +// } +#define CHECK_CONSTEXPR(x, out, dummy) \ + (UNLIKELY(!(x))) \ + ? (LOG(FATAL) << "Check failed: " << #x out, dummy) \ + : + +// DCHECKs are debug variants of CHECKs only enabled in debug builds. Generally +// CHECK should be used unless profiling identifies a CHECK as being in +// performance critical code. +#if defined(NDEBUG) +static constexpr bool kEnableDChecks = false; +#else +static constexpr bool kEnableDChecks = true; +#endif + +#define DCHECK(x) \ + if (::android::base::kEnableDChecks) CHECK(x) +#define DCHECK_EQ(x, y) \ + if (::android::base::kEnableDChecks) CHECK_EQ(x, y) +#define DCHECK_NE(x, y) \ + if (::android::base::kEnableDChecks) CHECK_NE(x, y) +#define DCHECK_LE(x, y) \ + if (::android::base::kEnableDChecks) CHECK_LE(x, y) +#define DCHECK_LT(x, y) \ + if (::android::base::kEnableDChecks) CHECK_LT(x, y) +#define DCHECK_GE(x, y) \ + if (::android::base::kEnableDChecks) CHECK_GE(x, y) +#define DCHECK_GT(x, y) \ + if (::android::base::kEnableDChecks) CHECK_GT(x, y) +#define DCHECK_STREQ(s1, s2) \ + if (::android::base::kEnableDChecks) CHECK_STREQ(s1, s2) +#define DCHECK_STRNE(s1, s2) \ + if (::android::base::kEnableDChecks) CHECK_STRNE(s1, s2) +#if defined(NDEBUG) +#define DCHECK_CONSTEXPR(x, out, dummy) +#else +#define DCHECK_CONSTEXPR(x, out, dummy) CHECK_CONSTEXPR(x, out, dummy) +#endif + +// Temporary class created to evaluate the LHS and RHS, used with +// MakeEagerEvaluator to infer the types of LHS and RHS. +template <typename LHS, typename RHS> +struct EagerEvaluator { + EagerEvaluator(LHS l, RHS r) : lhs(l), rhs(r) { + } + LHS lhs; + RHS rhs; +}; + +// Helper function for CHECK_xx. +template <typename LHS, typename RHS> +static inline EagerEvaluator<LHS, RHS> MakeEagerEvaluator(LHS lhs, RHS rhs) { + return EagerEvaluator<LHS, RHS>(lhs, rhs); +} + +// Explicitly instantiate EagerEvalue for pointers so that char*s aren't treated +// as strings. To compare strings use CHECK_STREQ and CHECK_STRNE. We rely on +// signed/unsigned warnings to protect you against combinations not explicitly +// listed below. +#define EAGER_PTR_EVALUATOR(T1, T2) \ + template <> \ + struct EagerEvaluator<T1, T2> { \ + EagerEvaluator(T1 l, T2 r) \ + : lhs(reinterpret_cast<const void*>(l)), \ + rhs(reinterpret_cast<const void*>(r)) { \ + } \ + const void* lhs; \ + const void* rhs; \ + } +EAGER_PTR_EVALUATOR(const char*, const char*); +EAGER_PTR_EVALUATOR(const char*, char*); +EAGER_PTR_EVALUATOR(char*, const char*); +EAGER_PTR_EVALUATOR(char*, char*); +EAGER_PTR_EVALUATOR(const unsigned char*, const unsigned char*); +EAGER_PTR_EVALUATOR(const unsigned char*, unsigned char*); +EAGER_PTR_EVALUATOR(unsigned char*, const unsigned char*); +EAGER_PTR_EVALUATOR(unsigned char*, unsigned char*); +EAGER_PTR_EVALUATOR(const signed char*, const signed char*); +EAGER_PTR_EVALUATOR(const signed char*, signed char*); +EAGER_PTR_EVALUATOR(signed char*, const signed char*); +EAGER_PTR_EVALUATOR(signed char*, signed char*); + +// Data for the log message, not stored in LogMessage to avoid increasing the +// stack size. +class LogMessageData; + +// A LogMessage is a temporarily scoped object used by LOG and the unlikely part +// of a CHECK. The destructor will abort if the severity is FATAL. +class LogMessage { + public: + LogMessage(const char* file, unsigned int line, LogSeverity severity, + int error); + + ~LogMessage(); + + // Returns the stream associated with the message, the LogMessage performs + // output when it goes out of scope. + std::ostream& stream(); + + // The routine that performs the actual logging. + static void LogLine(const char* file, unsigned int line, LogSeverity severity, + const char* msg); + + // A variant of the above for use with little stack. + static void LogLineLowStack(const char* file, unsigned int line, + LogSeverity severity, const char* msg); + + private: + const std::unique_ptr<LogMessageData> data_; + + DISALLOW_COPY_AND_ASSIGN(LogMessage); +}; + +// Allows to temporarily change the minimum severity level for logging. +class ScopedLogSeverity { + public: + explicit ScopedLogSeverity(LogSeverity level); + ~ScopedLogSeverity(); + + private: + LogSeverity old_; +}; + +} // namespace base +} // namespace android + +#endif // BASE_LOGGING_H diff --git a/base/logging.cpp b/base/logging.cpp new file mode 100644 index 0000000..3d6c0c2 --- /dev/null +++ b/base/logging.cpp @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/logging.h" + +#include <iostream> +#include <limits> +#include <sstream> +#include <string> +#include <vector> + +#include "base/strings.h" + +// Headers for LogMessage::LogLine. +#ifdef __ANDROID__ +#include <android/set_abort_message.h> +#include "cutils/log.h" +#else +#include <sys/types.h> +#include <unistd.h> +#endif + +// For GetTid. +#if defined(__APPLE__) +#include "AvailabilityMacros.h" // For MAC_OS_X_VERSION_MAX_ALLOWED +#include <sys/syscall.h> +#include <sys/time.h> +#elif !defined(__BIONIC__) +#include <syscall.h> +#endif + +namespace android { +namespace base { + +static std::mutex logging_lock; + +static LogSeverity gMinimumLogSeverity = INFO; +static std::unique_ptr<std::string> gCmdLine; +static std::unique_ptr<std::string> gProgramInvocationName; +static std::unique_ptr<std::string> gProgramInvocationShortName; + +#ifndef __ANDROID__ +static pid_t GetTid() { +#if defined(__APPLE__) + uint64_t owner; + // Requires Mac OS 10.6 + CHECK_PTHREAD_CALL(pthread_threadid_np, (NULL, &owner), __FUNCTION__); + return owner; +#else + return syscall(__NR_gettid); +#endif +} +#endif // __ANDROID__ + +const char* GetCmdLine() { + return (gCmdLine.get() != nullptr) ? gCmdLine->c_str() : nullptr; +} + +const char* ProgramInvocationName() { + return (gProgramInvocationName.get() != nullptr) + ? gProgramInvocationName->c_str() + : "unknown"; +} + +const char* ProgramInvocationShortName() { + return (gProgramInvocationShortName.get() != nullptr) + ? gProgramInvocationShortName->c_str() + : "unknown"; +} + +void InitLogging(char* argv[]) { + if (gCmdLine.get() != nullptr) { + return; + } + + // Stash the command line for later use. We can use /proc/self/cmdline on + // Linux to recover this, but we don't have that luxury on the Mac, and there + // are a couple of argv[0] variants that are commonly used. + if (argv != nullptr) { + gCmdLine.reset(new std::string(argv[0])); + for (size_t i = 1; argv[i] != nullptr; ++i) { + gCmdLine->append(" "); + gCmdLine->append(argv[i]); + } + gProgramInvocationName.reset(new std::string(argv[0])); + const char* last_slash = strrchr(argv[0], '/'); + gProgramInvocationShortName.reset( + new std::string((last_slash != nullptr) ? last_slash + 1 : argv[0])); + } else { + // TODO: fall back to /proc/self/cmdline when argv is NULL on Linux. + gCmdLine.reset(new std::string("<unset>")); + } + const char* tags = getenv("ANDROID_LOG_TAGS"); + if (tags == nullptr) { + return; + } + + std::vector<std::string> specs; + Split(tags, ' ', &specs); + for (size_t i = 0; i < specs.size(); ++i) { + // "tag-pattern:[vdiwefs]" + std::string spec(specs[i]); + if (spec.size() == 3 && StartsWith(spec, "*:")) { + switch (spec[2]) { + case 'v': + gMinimumLogSeverity = VERBOSE; + continue; + case 'd': + gMinimumLogSeverity = DEBUG; + continue; + case 'i': + gMinimumLogSeverity = INFO; + continue; + case 'w': + gMinimumLogSeverity = WARNING; + continue; + case 'e': + gMinimumLogSeverity = ERROR; + continue; + case 'f': + gMinimumLogSeverity = FATAL; + continue; + // liblog will even suppress FATAL if you say 's' for silent, but that's + // crazy! + case 's': + gMinimumLogSeverity = FATAL; + continue; + } + } + LOG(FATAL) << "unsupported '" << spec << "' in ANDROID_LOG_TAGS (" << tags + << ")"; + } +} + +// This indirection greatly reduces the stack impact of having lots of +// checks/logging in a function. +class LogMessageData { + public: + LogMessageData(const char* file, unsigned int line, LogSeverity severity, + int error) + : file_(file), line_number_(line), severity_(severity), error_(error) { + const char* last_slash = strrchr(file, '/'); + file = (last_slash == nullptr) ? file : last_slash + 1; + } + + const char* GetFile() const { + return file_; + } + + unsigned int GetLineNumber() const { + return line_number_; + } + + LogSeverity GetSeverity() const { + return severity_; + } + + int GetError() const { + return error_; + } + + std::ostream& GetBuffer() { + return buffer_; + } + + std::string ToString() const { + return buffer_.str(); + } + + private: + std::ostringstream buffer_; + const char* const file_; + const unsigned int line_number_; + const LogSeverity severity_; + const int error_; + + DISALLOW_COPY_AND_ASSIGN(LogMessageData); +}; + +LogMessage::LogMessage(const char* file, unsigned int line, + LogSeverity severity, int error) + : data_(new LogMessageData(file, line, severity, error)) { +} + +LogMessage::~LogMessage() { + if (data_->GetSeverity() < gMinimumLogSeverity) { + return; // No need to format something we're not going to output. + } + + // Finish constructing the message. + if (data_->GetError() != -1) { + data_->GetBuffer() << ": " << strerror(data_->GetError()); + } + std::string msg(data_->ToString()); + + // Do the actual logging with the lock held. + { + std::lock_guard<std::mutex> lock(logging_lock); + if (msg.find('\n') == std::string::npos) { + LogLine(data_->GetFile(), data_->GetLineNumber(), data_->GetSeverity(), + msg.c_str()); + } else { + msg += '\n'; + size_t i = 0; + while (i < msg.size()) { + size_t nl = msg.find('\n', i); + msg[nl] = '\0'; + LogLine(data_->GetFile(), data_->GetLineNumber(), data_->GetSeverity(), + &msg[i]); + i = nl + 1; + } + } + } + + // Abort if necessary. + if (data_->GetSeverity() == FATAL) { +#ifdef __ANDROID__ + android_set_abort_message(msg.c_str()); +#endif + abort(); + } +} + +std::ostream& LogMessage::stream() { + return data_->GetBuffer(); +} + +#ifdef __ANDROID__ +static const android_LogPriority kLogSeverityToAndroidLogPriority[] = { + ANDROID_LOG_VERBOSE, ANDROID_LOG_DEBUG, ANDROID_LOG_INFO, + ANDROID_LOG_WARN, ANDROID_LOG_ERROR, ANDROID_LOG_FATAL}; +static_assert(arraysize(kLogSeverityToAndroidLogPriority) == FATAL + 1, + "Mismatch in size of kLogSeverityToAndroidLogPriority and values " + "in LogSeverity"); +#endif + +void LogMessage::LogLine(const char* file, unsigned int line, + LogSeverity log_severity, const char* message) { +#ifdef __ANDROID__ + const char* tag = ProgramInvocationShortName(); + int priority = kLogSeverityToAndroidLogPriority[log_severity]; + if (priority == ANDROID_LOG_FATAL) { + LOG_PRI(priority, tag, "%s:%u] %s", file, line, message); + } else { + LOG_PRI(priority, tag, "%s", message); + } +#else + static const char* log_characters = "VDIWEF"; + CHECK_EQ(strlen(log_characters), FATAL + 1U); + char severity = log_characters[log_severity]; + fprintf(stderr, "%s %c %5d %5d %s:%u] %s\n", ProgramInvocationShortName(), + severity, getpid(), GetTid(), file, line, message); +#endif +} + +void LogMessage::LogLineLowStack(const char* file, unsigned int line, + LogSeverity log_severity, const char* message) { +#ifdef __ANDROID__ + // Use android_writeLog() to avoid stack-based buffers used by + // android_printLog(). + const char* tag = ProgramInvocationShortName(); + int priority = kLogSeverityToAndroidLogPriority[log_severity]; + char* buf = nullptr; + size_t buf_size = 0u; + if (priority == ANDROID_LOG_FATAL) { + // Allocate buffer for snprintf(buf, buf_size, "%s:%u] %s", file, line, + // message) below. If allocation fails, fall back to printing only the + // message. + buf_size = strlen(file) + 1 /* ':' */ + + std::numeric_limits<typeof(line)>::max_digits10 + 2 /* "] " */ + + strlen(message) + 1 /* terminating 0 */; + buf = reinterpret_cast<char*>(malloc(buf_size)); + } + if (buf != nullptr) { + snprintf(buf, buf_size, "%s:%u] %s", file, line, message); + android_writeLog(priority, tag, buf); + free(buf); + } else { + android_writeLog(priority, tag, message); + } +#else + static const char* log_characters = "VDIWEF"; + CHECK_EQ(strlen(log_characters), FATAL + 1U); + + const char* program_name = ProgramInvocationShortName(); + write(STDERR_FILENO, program_name, strlen(program_name)); + write(STDERR_FILENO, " ", 1); + write(STDERR_FILENO, &log_characters[log_severity], 1); + write(STDERR_FILENO, " ", 1); + // TODO: pid and tid. + write(STDERR_FILENO, file, strlen(file)); + // TODO: line. + UNUSED(line); + write(STDERR_FILENO, "] ", 2); + write(STDERR_FILENO, message, strlen(message)); + write(STDERR_FILENO, "\n", 1); +#endif +} + +ScopedLogSeverity::ScopedLogSeverity(LogSeverity level) { + old_ = gMinimumLogSeverity; + gMinimumLogSeverity = level; +} + +ScopedLogSeverity::~ScopedLogSeverity() { + gMinimumLogSeverity = old_; +} + +} // namespace base +} // namespace android diff --git a/base/logging_test.cpp b/base/logging_test.cpp new file mode 100644 index 0000000..0a03e38 --- /dev/null +++ b/base/logging_test.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "base/logging.h" + +#include <regex> +#include <string> + +#include "base/file.h" +#include "base/stringprintf.h" +#include "test_utils.h" + +#include <gtest/gtest.h> + +#ifdef __ANDROID__ +#define HOST_TEST(suite, name) TEST(suite, DISABLED_ ## name) +#else +#define HOST_TEST(suite, name) TEST(suite, name) +#endif + +class CapturedStderr { + public: + CapturedStderr() : old_stderr_(-1) { + init(); + } + + ~CapturedStderr() { + reset(); + } + + int fd() const { + return temp_file_.fd; + } + + private: + void init() { + old_stderr_ = dup(STDERR_FILENO); + ASSERT_NE(-1, old_stderr_); + ASSERT_NE(-1, dup2(fd(), STDERR_FILENO)); + } + + void reset() { + ASSERT_NE(-1, dup2(old_stderr_, STDERR_FILENO)); + ASSERT_EQ(0, close(old_stderr_)); + } + + TemporaryFile temp_file_; + int old_stderr_; +}; + +HOST_TEST(logging, CHECK) { + ASSERT_DEATH(CHECK(false), "Check failed: false "); + CHECK(true); + + ASSERT_DEATH(CHECK_EQ(0, 1), "Check failed: 0 == 1 "); + CHECK_EQ(0, 0); + + ASSERT_DEATH(CHECK_STREQ("foo", "bar"), R"(Check failed: "foo" == "bar")"); + CHECK_STREQ("foo", "foo"); +} + +std::string make_log_pattern(android::base::LogSeverity severity, + const char* message) { + static const char* log_characters = "VDIWEF"; + char log_char = log_characters[severity]; + return android::base::StringPrintf( + "%c[[:space:]]+[[:digit:]]+[[:space:]]+[[:digit:]]+ " __FILE__ + ":[[:digit:]]+] %s", + log_char, message); +} + +HOST_TEST(logging, LOG) { + ASSERT_DEATH(LOG(FATAL) << "foobar", "foobar"); + + { + CapturedStderr cap; + LOG(WARNING) << "foobar"; + ASSERT_EQ(0, lseek(cap.fd(), SEEK_SET, 0)); + + std::string output; + android::base::ReadFdToString(cap.fd(), &output); + + std::regex message_regex( + make_log_pattern(android::base::WARNING, "foobar")); + ASSERT_TRUE(std::regex_search(output, message_regex)); + } + + { + CapturedStderr cap; + LOG(INFO) << "foobar"; + ASSERT_EQ(0, lseek(cap.fd(), SEEK_SET, 0)); + + std::string output; + android::base::ReadFdToString(cap.fd(), &output); + + std::regex message_regex( + make_log_pattern(android::base::INFO, "foobar")); + ASSERT_TRUE(std::regex_search(output, message_regex)); + } + + { + CapturedStderr cap; + LOG(DEBUG) << "foobar"; + ASSERT_EQ(0, lseek(cap.fd(), SEEK_SET, 0)); + + std::string output; + android::base::ReadFdToString(cap.fd(), &output); + ASSERT_TRUE(output.empty()); + } + + { + android::base::ScopedLogSeverity severity(android::base::DEBUG); + CapturedStderr cap; + LOG(DEBUG) << "foobar"; + ASSERT_EQ(0, lseek(cap.fd(), SEEK_SET, 0)); + + std::string output; + android::base::ReadFdToString(cap.fd(), &output); + + std::regex message_regex( + make_log_pattern(android::base::DEBUG, "foobar")); + ASSERT_TRUE(std::regex_search(output, message_regex)); + } +} + +HOST_TEST(logging, PLOG) { + { + CapturedStderr cap; + errno = ENOENT; + PLOG(INFO) << "foobar"; + ASSERT_EQ(0, lseek(cap.fd(), SEEK_SET, 0)); + + std::string output; + android::base::ReadFdToString(cap.fd(), &output); + + std::regex message_regex(make_log_pattern( + android::base::INFO, "foobar: No such file or directory")); + ASSERT_TRUE(std::regex_search(output, message_regex)); + } +} + +HOST_TEST(logging, UNIMPLEMENTED) { + { + CapturedStderr cap; + errno = ENOENT; + UNIMPLEMENTED(ERROR); + ASSERT_EQ(0, lseek(cap.fd(), SEEK_SET, 0)); + + std::string output; + android::base::ReadFdToString(cap.fd(), &output); + + std::string expected_message = + android::base::StringPrintf("%s unimplemented ", __PRETTY_FUNCTION__); + std::regex message_regex( + make_log_pattern(android::base::ERROR, expected_message.c_str())); + ASSERT_TRUE(std::regex_search(output, message_regex)); + } +} diff --git a/base/test_main.cpp b/base/test_main.cpp new file mode 100644 index 0000000..c49ca4b --- /dev/null +++ b/base/test_main.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <gtest/gtest.h> + +#include "base/logging.h" + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + android::base::InitLogging(argv); + return RUN_ALL_TESTS(); +} diff --git a/base/test_utils.cpp b/base/test_utils.cpp new file mode 100644 index 0000000..1f6d3cf --- /dev/null +++ b/base/test_utils.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "test_utils.h" + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +TemporaryFile::TemporaryFile() { + init("/data/local/tmp"); + if (fd == -1) { + init("/tmp"); + } +} + +TemporaryFile::~TemporaryFile() { + close(fd); + unlink(filename); +} + +void TemporaryFile::init(const char* tmp_dir) { + snprintf(filename, sizeof(filename), "%s/TemporaryFile-XXXXXX", tmp_dir); + fd = mkstemp(filename); +} diff --git a/base/test_utils.h b/base/test_utils.h new file mode 100644 index 0000000..132d3a7 --- /dev/null +++ b/base/test_utils.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TEST_UTILS_H +#define TEST_UTILS_H + +class TemporaryFile { + public: + TemporaryFile(); + ~TemporaryFile(); + + int fd; + char filename[1024]; + + private: + void init(const char* tmp_dir); +}; + +#endif // TEST_UTILS_H |