blob: 37d4be8e7f2f3e0f0d45e4f31011f2088daf7a4a [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/common/logging_chrome.h"
#include <memory>
#include <optional>
#include "base/command_line.h"
#include "base/environment.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "build/build_config.h"
#include "chrome/common/env_vars.h"
#include "content/public/common/content_switches.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
class ChromeLoggingTest : public testing::Test {
public:
// Stores the current value of the log file name environment
// variable and sets the variable to new_value.
void SaveEnvironmentVariable(const std::string& new_value) {
std::unique_ptr<base::Environment> env(base::Environment::Create());
environment_filename_ = env->GetVar(env_vars::kLogFileName).value_or("");
env->SetVar(env_vars::kLogFileName, new_value);
}
// Restores the value of the log file nave environment variable
// previously saved by SaveEnvironmentVariable().
void RestoreEnvironmentVariable() {
std::unique_ptr<base::Environment> env(base::Environment::Create());
env->SetVar(env_vars::kLogFileName, environment_filename_);
}
void SetLogFileFlag(const std::string& value) {
cmd_line_.AppendSwitchASCII(switches::kLogFile, value);
}
const base::CommandLine& cmd_line() { return cmd_line_; }
private:
std::string environment_filename_; // Saves real environment value.
base::CommandLine cmd_line_ =
base::CommandLine(base::CommandLine::NO_PROGRAM);
};
// Tests the log file name getter without an environment variable.
TEST_F(ChromeLoggingTest, LogFileName) {
SaveEnvironmentVariable(std::string());
base::FilePath filename = logging::GetLogFileName(cmd_line());
ASSERT_NE(base::FilePath::StringType::npos,
filename.value().find(FILE_PATH_LITERAL("chrome_debug.log")));
#if BUILDFLAG(IS_WIN)
ASSERT_TRUE(filename.IsAbsolute());
#endif // BUILDFLAG(IS_WIN)
RestoreEnvironmentVariable();
}
// Tests the log file name getter with an environment variable.
#if BUILDFLAG(IS_WIN)
TEST_F(ChromeLoggingTest, EnvironmentLogFileName) {
SaveEnvironmentVariable("c:\\path\\test env value");
base::FilePath filename = logging::GetLogFileName(cmd_line());
ASSERT_NE(base::FilePath::StringType::npos,
filename.value().find(FILE_PATH_LITERAL("test env value")));
ASSERT_TRUE(filename.IsAbsolute());
RestoreEnvironmentVariable();
}
#else
TEST_F(ChromeLoggingTest, EnvironmentLogFileName) {
SaveEnvironmentVariable("test env value");
base::FilePath filename = logging::GetLogFileName(cmd_line());
ASSERT_NE(base::FilePath::StringType::npos,
filename.value().find(FILE_PATH_LITERAL("test env value")));
RestoreEnvironmentVariable();
}
#endif // BUILDFLAG(IS_WIN)
// Tests the log file name getter with a command-line flag.
#if BUILDFLAG(IS_WIN)
TEST_F(ChromeLoggingTest, FlagLogFileName) {
SetLogFileFlag("c:\\path\\test flag value");
base::FilePath filename = logging::GetLogFileName(cmd_line());
ASSERT_NE(base::FilePath::StringType::npos,
filename.value().find(FILE_PATH_LITERAL("test flag value")));
ASSERT_TRUE(filename.IsAbsolute());
}
// Non-absolute path falls back to default.
TEST_F(ChromeLoggingTest, FlagLogFileNameNonAbsolute) {
SetLogFileFlag("test file value");
base::FilePath filename = logging::GetLogFileName(cmd_line());
ASSERT_NE(base::FilePath::StringType::npos,
filename.value().find(FILE_PATH_LITERAL("chrome_debug.log")));
ASSERT_TRUE(filename.IsAbsolute());
}
#else
TEST_F(ChromeLoggingTest, FlagLogFileName) {
SetLogFileFlag("test flag value");
base::FilePath filename = logging::GetLogFileName(cmd_line());
ASSERT_NE(base::FilePath::StringType::npos,
filename.value().find(FILE_PATH_LITERAL("test flag value")));
}
#endif // BUILDFLAG(IS_WIN)
// Tests the log file name getter with with an environment variable and a
// command-line flag. The flag takes precedence.
#if BUILDFLAG(IS_WIN)
TEST_F(ChromeLoggingTest, EnvironmentAndFlagLogFileName) {
SaveEnvironmentVariable("c:\\path\\test env value");
SetLogFileFlag("d:\\path\\test flag value");
base::FilePath filename = logging::GetLogFileName(cmd_line());
ASSERT_NE(base::FilePath::StringType::npos,
filename.value().find(FILE_PATH_LITERAL("test flag value")));
ASSERT_TRUE(filename.IsAbsolute());
RestoreEnvironmentVariable();
}
#else
TEST_F(ChromeLoggingTest, EnvironmentAndFlagLogFileName) {
SaveEnvironmentVariable("test env value");
SetLogFileFlag("test flag value");
base::FilePath filename = logging::GetLogFileName(cmd_line());
ASSERT_NE(base::FilePath::StringType::npos,
filename.value().find(FILE_PATH_LITERAL("test flag value")));
RestoreEnvironmentVariable();
}
#endif // BUILDFLAG(IS_WIN)
#if BUILDFLAG(IS_CHROMEOS)
TEST_F(ChromeLoggingTest, TimestampedName) {
base::FilePath path = base::FilePath(FILE_PATH_LITERAL("xy.zzy"));
base::FilePath timestamped_path =
logging::GenerateTimestampedName(path, base::Time::Now());
EXPECT_THAT(timestamped_path.value(),
::testing::MatchesRegex("^xy_\\d+-\\d+\\.zzy$"));
}
TEST_F(ChromeLoggingTest, SetUpSymlink) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
const base::FilePath temp_dir_path = temp_dir.GetPath();
base::FilePath bare_symlink_path =
temp_dir_path.AppendASCII("chrome-test-log");
base::FilePath latest_symlink_path =
temp_dir_path.AppendASCII("chrome-test-log.LATEST");
base::FilePath previous_symlink_path =
temp_dir_path.AppendASCII("chrome-test-log.PREVIOUS");
// Start from a legacy situation, where "chrome-test-log" is a symlink
// pointing to the latest log, which has a time-stamped name from a while
// ago.
base::FilePath old_target_path = logging::GenerateTimestampedName(
bare_symlink_path, base::Time::UnixEpoch());
ASSERT_TRUE(base::CreateSymbolicLink(old_target_path, bare_symlink_path));
// Call the testee with the new symlink path, as if starting a new session.
logging::SetUpSymlinkIfNeeded(latest_symlink_path,
/*start_new_log=*/true);
// We now expect:
//
// chrome-test-log --> chrome-test-log.LATEST
// chrome-test-log.LATEST --> <new time-stamped path>
// no chrome-test-log.PREVIOUS on the legacy transition.
base::FilePath target_path;
ASSERT_TRUE(base::ReadSymbolicLink(bare_symlink_path, &target_path));
EXPECT_EQ(target_path.value(), latest_symlink_path.value());
base::FilePath latest_target_path;
ASSERT_TRUE(base::ReadSymbolicLink(latest_symlink_path, &latest_target_path));
EXPECT_NE(latest_target_path.value(), old_target_path.value());
EXPECT_THAT(latest_target_path.value(),
::testing::MatchesRegex("^.*chrome-test-log_\\d+-\\d+$"));
// Simulate one more session cycle.
logging::SetUpSymlinkIfNeeded(latest_symlink_path, /*start_new_log=*/true);
// We now expect:
//
// chrome-test-log.PREVIOUS --> <previous target of chrome-test-log.LATEST>
//
// We also expect that the .LATEST file is now pointing to a file with a
// newer time stamp. Unfortunately it's probably not newer enough to tell
// the difference since the time stamp granularity is 1 second.
ASSERT_TRUE(base::ReadSymbolicLink(previous_symlink_path, &target_path));
EXPECT_EQ(target_path.value(), latest_target_path.value());
ASSERT_TRUE(base::ReadSymbolicLink(latest_symlink_path, &latest_target_path));
EXPECT_THAT(latest_target_path.value(),
::testing::MatchesRegex("^.*chrome-test-log_\\d+-\\d+$"));
}
// Test the case of the normal rotation.
TEST_F(ChromeLoggingTest, RotateLogFiles) {
constexpr char kLog1Content[] = "log#1\n";
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
const base::FilePath temp_dir_path = temp_dir.GetPath();
base::FilePath log_path_latest = temp_dir_path.AppendASCII("chrome-test-log");
// Prepare the latest log file.
ASSERT_TRUE(base::WriteFile(log_path_latest, kLog1Content));
base::File::Info file_info;
base::File(log_path_latest, base::File::FLAG_OPEN | base::File::FLAG_READ)
.GetInfo(&file_info);
base::Time creation_time = file_info.creation_time;
// Generate the log file path which is rotated to.
base::FilePath expected_rotated_path =
logging::GenerateTimestampedName(log_path_latest, creation_time);
// Check the condition before rotation.
{
EXPECT_TRUE(base::PathExists(log_path_latest));
EXPECT_FALSE(base::PathExists(expected_rotated_path));
}
// Simulate the rotation.
ASSERT_TRUE(logging::RotateLogFile(log_path_latest));
// Check the conditions after rotation: the log file and the rotated log file.
{
EXPECT_FALSE(base::PathExists(log_path_latest));
EXPECT_TRUE(base::PathExists(expected_rotated_path));
std::string buffer;
base::ReadFileToString(expected_rotated_path, &buffer);
EXPECT_EQ(buffer, kLog1Content);
}
}
// Test the case that chrome tries the rotation but there is no files.
TEST_F(ChromeLoggingTest, RotateLogFilesNoFile) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
const base::FilePath temp_dir_path = temp_dir.GetPath();
base::FilePath log_path_latest = temp_dir_path.AppendASCII("chrome-test-log");
// Check the condition before rotation.
{
EXPECT_FALSE(base::PathExists(log_path_latest));
// Ensure no file in the directory.
base::FileEnumerator enumerator(temp_dir_path, true,
base::FileEnumerator::FILES);
EXPECT_TRUE(enumerator.Next().empty());
}
// Simulate the rotation.
ASSERT_TRUE(logging::RotateLogFile(log_path_latest));
// Check the condition after rotation: nothing happens.
{
EXPECT_FALSE(base::PathExists(log_path_latest));
// Ensure still no file in the directory.
base::FileEnumerator enumerator(temp_dir_path, true,
base::FileEnumerator::FILES);
EXPECT_TRUE(enumerator.Next().empty());
}
}
// Test the case that chrome tries the rotation but the target path already
// exists. The logic should use the altanate target path.
TEST_F(ChromeLoggingTest, RotateLogFilesExisting) {
constexpr char kLatestLogContent[] = "log#1\n";
constexpr char kOldLogContent[] = "log#2\n";
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
const base::FilePath temp_dir_path = temp_dir.GetPath();
base::FilePath log_path_latest = temp_dir_path.AppendASCII("chrome-test-log");
// Prepare the latest log file.
ASSERT_TRUE(base::WriteFile(log_path_latest, kLatestLogContent));
base::File::Info file_info;
{
base::File(log_path_latest, base::File::FLAG_OPEN | base::File::FLAG_READ)
.GetInfo(&file_info);
}
base::Time creation_time = file_info.creation_time;
base::FilePath exist_log_path =
logging::GenerateTimestampedName(log_path_latest, creation_time);
ASSERT_TRUE(base::WriteFile(exist_log_path, kOldLogContent));
base::FilePath rotated_log_path = logging::GenerateTimestampedName(
log_path_latest, creation_time + base::Seconds(1));
// Check the condition before rotation.
{
// The latest log file exists.
EXPECT_TRUE(base::PathExists(log_path_latest));
// First candidate does already exist.
EXPECT_TRUE(base::PathExists(exist_log_path));
// Second candidate does already exist.
EXPECT_FALSE(base::PathExists(rotated_log_path));
}
// Simulate one more session cycle.
ASSERT_TRUE(logging::RotateLogFile(log_path_latest));
// Check the condition after rotation: the log file is renamed to the second
// candidate.
{
EXPECT_FALSE(base::PathExists(log_path_latest));
EXPECT_TRUE(base::PathExists(exist_log_path));
EXPECT_TRUE(base::PathExists(rotated_log_path));
std::string buffer;
// The first candidate is kept.
base::ReadFileToString(exist_log_path, &buffer);
EXPECT_EQ(buffer, kOldLogContent);
// The second candidate is the previous latest log.
base::ReadFileToString(rotated_log_path, &buffer);
EXPECT_EQ(buffer, kLatestLogContent);
}
}
#endif // BUILDFLAG(IS_CHROMEOS)