blob: 9c022ac299771920818037b0b55689d52d0a6477 [file] [log] [blame]
// Copyright 2025 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/browser/web_applications/web_app_logging.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/json/json_reader.h"
#include "base/numerics/safe_conversions.h"
#include "base/test/gmock_expected_support.h"
#include "base/test/simple_test_clock.h"
#include "base/test/task_environment.h"
#include "base/test/test_simple_task_runner.h"
#include "base/test/values_test_util.h"
#include "base/values.h"
#include "chrome/browser/web_applications/file_utils_wrapper.h"
#include "net/http/http_status_code.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
namespace web_app {
namespace {
class WebAppLoggingTest : public testing::Test {
public:
void SetUp() override {
ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
log_path_ = scoped_temp_dir_.GetPath().AppendASCII("Test.log");
log_writing_task_runner_ =
base::MakeRefCounted<base::TestSimpleTaskRunner>();
log_deletion_task_runner_ =
base::MakeRefCounted<base::TestSimpleTaskRunner>();
}
protected:
base::FilePath log_path() { return log_path_; }
void RunLoggingTasks() {
log_writing_task_runner()->RunUntilIdle();
log_deletion_task_runner()->RunUntilIdle();
// Run any tasks in this environment, as replies occur too.
base::RunLoop loop;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, loop.QuitClosure());
loop.Run();
}
base::TestSimpleTaskRunner* log_writing_task_runner() {
return log_writing_task_runner_.get();
}
base::TestSimpleTaskRunner* log_deletion_task_runner() {
return log_deletion_task_runner_.get();
}
base::SimpleTestClock clock_;
private:
base::test::TaskEnvironment task_environment_;
base::ScopedTempDir scoped_temp_dir_;
base::FilePath log_path_;
scoped_refptr<base::TestSimpleTaskRunner> log_writing_task_runner_;
scoped_refptr<base::TestSimpleTaskRunner> log_deletion_task_runner_;
};
TEST_F(WebAppLoggingTest, InMemoryModeDeletesLog) {
base::WriteFile(log_path(), "test data");
ASSERT_TRUE(base::PathExists(log_path()));
auto log = PersistableLog::CreateForTesting(
log_path(), PersistableLogMode::kInMemory, 10,
base::MakeRefCounted<FileUtilsWrapper>(), log_writing_task_runner(),
log_deletion_task_runner(), &clock_);
RunLoggingTasks();
ASSERT_FALSE(base::PathExists(log_path().DirName()));
}
TEST_F(WebAppLoggingTest, LogIsWrittenToDisk) {
auto log = PersistableLog::CreateForTesting(
log_path(), PersistableLogMode::kPersistToDisk, 10,
base::MakeRefCounted<FileUtilsWrapper>(), log_writing_task_runner(),
log_deletion_task_runner(), &clock_);
RunLoggingTasks();
clock_.SetNow(base::Time::FromMillisecondsSinceUnixEpoch(12345));
log->Append(base::Value::Dict().Set("key", "value"));
// The log should write on destruction.
log.reset();
RunLoggingTasks();
std::string file_contents;
ASSERT_TRUE(base::ReadFileToString(log_path(), &file_contents));
// Parse the JSON and check that the values are present.
ASSERT_OK_AND_ASSIGN(base::Value log_value,
base::JSONReader::ReadAndReturnValueWithError(
file_contents, base::JSON_ALLOW_TRAILING_COMMAS));
EXPECT_THAT(
log_value,
base::test::IsJson(R"([{"timestamp_ms": 2147483647, "key": "value"}])"));
}
TEST_F(WebAppLoggingTest, LogRotation) {
auto log = PersistableLog::CreateForTesting(
log_path(), PersistableLogMode::kPersistToDisk, 10,
base::MakeRefCounted<FileUtilsWrapper>(), log_writing_task_runner(),
log_deletion_task_runner(), &clock_);
RunLoggingTasks();
base::Time time;
ASSERT_TRUE(base::Time::FromString("2025-01-01 12:00:00 UTC", &time));
clock_.SetNow(time);
base::AutoReset<int> size_reset =
PersistableLog::SetMaxLogFileSizeBytesForTesting(1);
// Since the size is set to 1 byte, it should automatically schedule to write
// the log immediately for each append.
log->AppendValue(base::Value("value1"));
RunLoggingTasks();
// A second write is always needed, as we cannot rotate a log file that was
// never written.
log->AppendValue(base::Value("value2"));
RunLoggingTasks();
EXPECT_TRUE(base::PathExists(log_path()));
base::FileEnumerator enumerator(log_path().DirName(), /*recursive=*/false,
base::FileEnumerator::FILES);
std::vector<base::FilePath> files;
for (base::FilePath name = enumerator.Next(); !name.empty();
name = enumerator.Next()) {
files.push_back(name);
}
EXPECT_THAT(files, testing::UnorderedElementsAre(
log_path(),
log_path().InsertBeforeExtensionASCII("2025-01-01")));
}
TEST_F(WebAppLoggingTest, LogDownloadedIconsErrors) {
const GURL kIconUrl("https://example.com/icon.png");
IconsMap icons_map;
DownloadedIconsHttpResults icons_http_results;
icons_http_results.insert({{kIconUrl, gfx::Size(1, 1)}, net::HTTP_NOT_FOUND});
SkBitmap bitmap;
bitmap.allocN32Pixels(1, 1);
icons_map[kIconUrl].push_back(bitmap);
// No error if bitmap is present.
auto errors = LogDownloadedIconsErrors(IconsDownloadedResult::kCompleted,
icons_map, icons_http_results);
EXPECT_TRUE(errors.empty());
// Error if bitmap is missing.
icons_map.clear();
errors = LogDownloadedIconsErrors(IconsDownloadedResult::kCompleted,
icons_map, icons_http_results);
EXPECT_FALSE(errors.empty());
EXPECT_THAT(errors, base::test::IsJson(R"({
"icons_downloaded_result": "Completed",
"icons_http_results": [ {
"http_code_desc": "Not Found",
"http_status_code": 404,
"icon_size": "1x1",
"icon_url": "https://example.com/icon.png"
} ]
})"));
}
} // namespace
} // namespace web_app