blob: 60c1a448c264dbb5ea12291b392f4d84aa376f60 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/system_logs/single_log_file_log_source.h"
#include <memory>
#include <string>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/string_split.h"
#include "base/time/time.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace system_logs {
class SingleLogFileLogSourceTest : public ::testing::Test {
public:
SingleLogFileLogSourceTest() : num_callback_calls_(0) {
InitializeTestLogDir();
}
~SingleLogFileLogSourceTest() override {
SingleLogFileLogSource::SetChromeStartTimeForTesting(nullptr);
}
protected:
// Sets up a dummy system log directory.
void InitializeTestLogDir() {
ASSERT_TRUE(log_dir_.CreateUniqueTempDir());
// Create file "messages".
const base::FilePath messages_path = log_dir_.GetPath().Append("messages");
base::WriteFile(messages_path, "", 0);
EXPECT_TRUE(base::PathExists(messages_path)) << messages_path.value();
// Create file "ui/ui.LATEST".
const base::FilePath ui_dir_path = log_dir_.GetPath().Append("ui");
ASSERT_TRUE(base::CreateDirectory(ui_dir_path)) << ui_dir_path.value();
const base::FilePath ui_latest_path = ui_dir_path.Append("ui.LATEST");
base::WriteFile(ui_latest_path, "", 0);
ASSERT_TRUE(base::PathExists(ui_latest_path)) << ui_latest_path.value();
}
// Initializes the unit under test, |source_| to read a file from the dummy
// system log directory.
void InitializeSource(SingleLogFileLogSource::SupportedSource source_type) {
source_ = std::make_unique<SingleLogFileLogSource>(source_type);
source_->log_file_dir_path_ = log_dir_.GetPath();
log_file_path_ = source_->log_file_dir_path_.Append(source_->source_name());
ASSERT_TRUE(base::PathExists(log_file_path_)) << log_file_path_.value();
}
// Writes/appends (respectively) a string |input| to file indicated by
// |relative_path| under |log_dir_|.
bool WriteFile(const base::FilePath& relative_path,
const std::string& input) {
return base::WriteFile(log_dir_.GetPath().Append(relative_path),
input.data(),
input.size()) == static_cast<int>(input.size());
}
bool AppendToFile(const base::FilePath& relative_path,
const std::string& input) {
return base::AppendToFile(log_dir_.GetPath().Append(relative_path),
input.data(), input.size());
}
// Moves source file to destination path, then creates an empty file at the
// path of the original source file.
//
// |src_relative_path|: Source file path relative to |log_dir_|.
// |dest_relative_path|: Destination path relative to |log_dir_|.
bool RotateFile(const base::FilePath& src_relative_path,
const base::FilePath& dest_relative_path) {
return base::Move(log_dir_.GetPath().Append(src_relative_path),
log_dir_.GetPath().Append(dest_relative_path)) &&
WriteFile(src_relative_path, "");
}
// Calls source_.Fetch() to start a logs fetch operation. Passes in
// OnFileRead() as a callback. Runs until Fetch() has completed.
void FetchFromSource() {
source_->Fetch(base::Bind(&SingleLogFileLogSourceTest::OnFileRead,
base::Unretained(this)));
browser_thread_bundle_.RunUntilIdle();
}
// Callback for fetching logs from |source_|. Overwrites the previous stored
// value of |latest_response_|.
void OnFileRead(std::unique_ptr<SystemLogsResponse> response) {
++num_callback_calls_;
if (response->empty())
return;
// Since |source_| represents a single log source, it should only return a
// single string result.
EXPECT_EQ(1U, response->size());
latest_response_ = std::move(response->begin()->second);
}
int num_callback_calls() const { return num_callback_calls_; }
const std::string& latest_response() const { return latest_response_; }
const base::FilePath& log_file_path() const { return log_file_path_; }
private:
// Creates the necessary browser threads.
content::TestBrowserThreadBundle browser_thread_bundle_;
// Unit under test.
std::unique_ptr<SingleLogFileLogSource> source_;
// Counts the number of times that |source_| has invoked the callback.
int num_callback_calls_;
// Stores the string response returned from |source_| the last time it invoked
// OnFileRead.
std::string latest_response_;
// Temporary dir for creating a dummy log file.
base::ScopedTempDir log_dir_;
// Path to the dummy log file in |log_dir_|.
base::FilePath log_file_path_;
DISALLOW_COPY_AND_ASSIGN(SingleLogFileLogSourceTest);
};
TEST_F(SingleLogFileLogSourceTest, EmptyFile) {
InitializeSource(SingleLogFileLogSource::SupportedSource::kMessages);
FetchFromSource();
EXPECT_EQ(1, num_callback_calls());
EXPECT_EQ("", latest_response());
}
TEST_F(SingleLogFileLogSourceTest, SingleRead) {
InitializeSource(SingleLogFileLogSource::SupportedSource::kUiLatest);
EXPECT_TRUE(AppendToFile(base::FilePath("ui/ui.LATEST"), "Hello world!\n"));
FetchFromSource();
EXPECT_EQ(1, num_callback_calls());
EXPECT_EQ("Hello world!\n", latest_response());
}
TEST_F(SingleLogFileLogSourceTest, IncrementalReads) {
InitializeSource(SingleLogFileLogSource::SupportedSource::kMessages);
EXPECT_TRUE(AppendToFile(base::FilePath("messages"), "Hello world!\n"));
FetchFromSource();
EXPECT_EQ(1, num_callback_calls());
EXPECT_EQ("Hello world!\n", latest_response());
EXPECT_TRUE(AppendToFile(base::FilePath("messages"),
"The quick brown fox jumps over the lazy dog\n"));
FetchFromSource();
EXPECT_EQ(2, num_callback_calls());
EXPECT_EQ("The quick brown fox jumps over the lazy dog\n", latest_response());
EXPECT_TRUE(AppendToFile(base::FilePath("messages"),
"Some like it hot.\nSome like it cold\n"));
FetchFromSource();
EXPECT_EQ(3, num_callback_calls());
EXPECT_EQ("Some like it hot.\nSome like it cold\n", latest_response());
// As a sanity check, read entire contents of file separately to make sure it
// was written incrementally, and hence read incrementally.
std::string file_contents;
EXPECT_TRUE(base::ReadFileToString(log_file_path(), &file_contents));
EXPECT_EQ(
"Hello world!\nThe quick brown fox jumps over the lazy dog\n"
"Some like it hot.\nSome like it cold\n",
file_contents);
}
// The log files read by SingleLogFileLogSource are not expected to be
// overwritten. This test is just to ensure that the SingleLogFileLogSource
// class is robust enough not to break in the event of an overwrite.
TEST_F(SingleLogFileLogSourceTest, FileOverwrite) {
InitializeSource(SingleLogFileLogSource::SupportedSource::kUiLatest);
EXPECT_TRUE(AppendToFile(base::FilePath("ui/ui.LATEST"), "0123456789\n"));
FetchFromSource();
EXPECT_EQ(1, num_callback_calls());
EXPECT_EQ("0123456789\n", latest_response());
// Overwrite the file.
EXPECT_TRUE(WriteFile(base::FilePath("ui/ui.LATEST"), "abcdefg\n"));
FetchFromSource();
// Should re-read from the beginning.
EXPECT_EQ(2, num_callback_calls());
EXPECT_EQ("abcdefg\n", latest_response());
// Append to the file to make sure incremental read still works.
EXPECT_TRUE(AppendToFile(base::FilePath("ui/ui.LATEST"), "hijk\n"));
FetchFromSource();
EXPECT_EQ(3, num_callback_calls());
EXPECT_EQ("hijk\n", latest_response());
// Overwrite again, this time with a longer length than the existing file.
// Previous contents:
// abcdefg~hijk~ <-- "~" is a single-char representation of newline.
// New contents:
// lmnopqrstuvwxyz~ <-- excess text beyond end of prev contents: "yz~"
EXPECT_TRUE(WriteFile(base::FilePath("ui/ui.LATEST"), "lmnopqrstuvwxyz\n"));
FetchFromSource();
EXPECT_EQ(4, num_callback_calls());
EXPECT_EQ("yz\n", latest_response());
}
TEST_F(SingleLogFileLogSourceTest, IncompleteLines) {
InitializeSource(SingleLogFileLogSource::SupportedSource::kMessages);
EXPECT_TRUE(AppendToFile(base::FilePath("messages"), "0123456789"));
FetchFromSource();
EXPECT_EQ(1, num_callback_calls());
EXPECT_EQ("", latest_response());
EXPECT_TRUE(AppendToFile(base::FilePath("messages"), "abcdefg"));
FetchFromSource();
EXPECT_EQ(2, num_callback_calls());
EXPECT_EQ("", latest_response());
EXPECT_TRUE(AppendToFile(base::FilePath("messages"), "hijk\n"));
FetchFromSource();
EXPECT_EQ(3, num_callback_calls());
// All the previously written text should be read this time.
EXPECT_EQ("0123456789abcdefghijk\n", latest_response());
// Check ability to read whole lines while leaving the remainder for later.
EXPECT_TRUE(AppendToFile(base::FilePath("messages"), "Hello world\n"));
EXPECT_TRUE(AppendToFile(base::FilePath("messages"), "Goodbye world"));
FetchFromSource();
EXPECT_EQ(4, num_callback_calls());
EXPECT_EQ("Hello world\n", latest_response());
EXPECT_TRUE(AppendToFile(base::FilePath("messages"), "\n"));
FetchFromSource();
EXPECT_EQ(5, num_callback_calls());
EXPECT_EQ("Goodbye world\n", latest_response());
}
TEST_F(SingleLogFileLogSourceTest, HandleLogFileRotation) {
InitializeSource(SingleLogFileLogSource::SupportedSource::kMessages);
EXPECT_TRUE(AppendToFile(base::FilePath("messages"), "1st log file\n"));
FetchFromSource();
EXPECT_EQ(1, num_callback_calls());
EXPECT_EQ("1st log file\n", latest_response());
// Rotate file. Make sure the rest of the old file and the contents of the new
// file are both read.
EXPECT_TRUE(AppendToFile(base::FilePath("messages"), "More 1st log file\n"));
EXPECT_TRUE(
RotateFile(base::FilePath("messages"), base::FilePath("messages.1")));
EXPECT_TRUE(AppendToFile(base::FilePath("messages"), "2nd log file\n"));
FetchFromSource();
EXPECT_EQ(2, num_callback_calls());
EXPECT_EQ("More 1st log file\n2nd log file\n", latest_response());
// Rotate again, but this time omit the newline before rotating.
EXPECT_TRUE(AppendToFile(base::FilePath("messages"), "No newline here..."));
EXPECT_TRUE(
RotateFile(base::FilePath("messages"), base::FilePath("messages.1")));
EXPECT_TRUE(AppendToFile(base::FilePath("messages"), "3rd log file\n"));
EXPECT_TRUE(AppendToFile(base::FilePath("messages"), "Also no newline here"));
FetchFromSource();
EXPECT_EQ(3, num_callback_calls());
// Make sure the rotation didn't break anything: the last part of the new file
// does not end with a newline; thus the new file should not be read.
EXPECT_EQ("No newline here...3rd log file\n", latest_response());
// Finish the previous read attempt by adding the missing newline.
EXPECT_TRUE(AppendToFile(base::FilePath("messages"), "...yet\n"));
FetchFromSource();
EXPECT_EQ(4, num_callback_calls());
EXPECT_EQ("Also no newline here...yet\n", latest_response());
}
} // namespace system_logs