blob: bc6b9790331ed30423d993ed0b206ce3e50d2034 [file] [log] [blame] [edit]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/files/important_file_writer.h"
#include <optional>
#include <utility>
#include "base/compiler_specific.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/sequence_checker.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "base/timer/mock_timer.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
namespace {
std::string GetFileContent(const FilePath& path) {
std::string content;
if (!ReadFileToString(path, &content)) {
NOTREACHED();
}
return content;
}
class DataSerializer : public ImportantFileWriter::DataSerializer {
public:
explicit DataSerializer(const std::string& data) : data_(data) {}
std::optional<std::string> SerializeData() override {
EXPECT_TRUE(sequence_checker_.CalledOnValidSequence());
return data_;
}
private:
const base::SequenceChecker sequence_checker_;
const std::string data_;
};
class FailingDataSerializer : public ImportantFileWriter::DataSerializer {
public:
std::optional<std::string> SerializeData() override { return std::nullopt; }
};
class BackgroundDataSerializer
: public ImportantFileWriter::BackgroundDataSerializer {
public:
explicit BackgroundDataSerializer(
ImportantFileWriter::BackgroundDataProducerCallback
data_producer_callback)
: data_producer_callback_(std::move(data_producer_callback)) {
DCHECK(data_producer_callback_);
}
ImportantFileWriter::BackgroundDataProducerCallback
GetSerializedDataProducerForBackgroundSequence() override {
EXPECT_TRUE(sequence_checker_.CalledOnValidSequence());
return std::move(data_producer_callback_);
}
bool producer_callback_obtained() const {
return data_producer_callback_.is_null();
}
private:
const base::SequenceChecker sequence_checker_;
ImportantFileWriter::BackgroundDataProducerCallback data_producer_callback_;
};
enum WriteCallbackObservationState {
NOT_CALLED,
CALLED_WITH_ERROR,
CALLED_WITH_SUCCESS,
};
class WriteCallbacksObserver {
public:
WriteCallbacksObserver() = default;
WriteCallbacksObserver(const WriteCallbacksObserver&) = delete;
WriteCallbacksObserver& operator=(const WriteCallbacksObserver&) = delete;
// Register OnBeforeWrite() and OnAfterWrite() to be called on the next write
// of |writer|. `after_write_closure` will also be invoked from
// OnAfterWrite().
void ObserveNextWriteCallbacks(
ImportantFileWriter* writer,
base::OnceClosure after_write_closure = base::DoNothing());
// Returns the |WriteCallbackObservationState| which was observed, then resets
// it to |NOT_CALLED|.
WriteCallbackObservationState GetAndResetObservationState();
private:
void OnBeforeWrite() {
EXPECT_FALSE(before_write_called_);
before_write_called_ = true;
}
void OnAfterWrite(bool success) {
EXPECT_EQ(NOT_CALLED, after_write_observation_state_);
after_write_observation_state_ =
success ? CALLED_WITH_SUCCESS : CALLED_WITH_ERROR;
std::move(after_write_closure_).Run();
}
bool before_write_called_ = false;
WriteCallbackObservationState after_write_observation_state_ = NOT_CALLED;
base::OnceClosure after_write_closure_ = base::DoNothing();
};
void WriteCallbacksObserver::ObserveNextWriteCallbacks(
ImportantFileWriter* writer,
base::OnceClosure after_write_closure) {
after_write_closure_ = std::move(after_write_closure);
writer->RegisterOnNextWriteCallbacks(
base::BindOnce(&WriteCallbacksObserver::OnBeforeWrite,
base::Unretained(this)),
base::BindOnce(&WriteCallbacksObserver::OnAfterWrite,
base::Unretained(this)));
}
WriteCallbackObservationState
WriteCallbacksObserver::GetAndResetObservationState() {
EXPECT_EQ(after_write_observation_state_ != NOT_CALLED, before_write_called_)
<< "The before-write callback should always be called before the "
"after-write callback";
WriteCallbackObservationState state = after_write_observation_state_;
before_write_called_ = false;
after_write_observation_state_ = NOT_CALLED;
return state;
}
} // namespace
class ImportantFileWriterTest : public testing::Test {
public:
ImportantFileWriterTest() = default;
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
file_ = temp_dir_.GetPath().AppendASCII("test-file");
}
protected:
WriteCallbacksObserver write_callback_observer_;
FilePath file_;
test::TaskEnvironment task_environment_;
private:
ScopedTempDir temp_dir_;
};
TEST_F(ImportantFileWriterTest, Basic) {
ImportantFileWriter writer(file_,
SingleThreadTaskRunner::GetCurrentDefault());
EXPECT_FALSE(PathExists(writer.path()));
EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
writer.WriteNow("foo");
RunLoop().RunUntilIdle();
EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
ASSERT_TRUE(PathExists(writer.path()));
EXPECT_EQ("foo", GetFileContent(writer.path()));
}
TEST_F(ImportantFileWriterTest, WriteWithObserver) {
ImportantFileWriter writer(file_,
SingleThreadTaskRunner::GetCurrentDefault());
EXPECT_FALSE(PathExists(writer.path()));
EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
// Confirm that the observer is invoked.
write_callback_observer_.ObserveNextWriteCallbacks(&writer);
writer.WriteNow("foo");
RunLoop().RunUntilIdle();
EXPECT_EQ(CALLED_WITH_SUCCESS,
write_callback_observer_.GetAndResetObservationState());
ASSERT_TRUE(PathExists(writer.path()));
EXPECT_EQ("foo", GetFileContent(writer.path()));
// Confirm that re-installing the observer works for another write.
EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
write_callback_observer_.ObserveNextWriteCallbacks(&writer);
writer.WriteNow("bar");
RunLoop().RunUntilIdle();
EXPECT_EQ(CALLED_WITH_SUCCESS,
write_callback_observer_.GetAndResetObservationState());
ASSERT_TRUE(PathExists(writer.path()));
EXPECT_EQ("bar", GetFileContent(writer.path()));
// Confirm that writing again without re-installing the observer doesn't
// result in a notification.
EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
writer.WriteNow("baz");
RunLoop().RunUntilIdle();
EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
ASSERT_TRUE(PathExists(writer.path()));
EXPECT_EQ("baz", GetFileContent(writer.path()));
}
TEST_F(ImportantFileWriterTest, FailedWriteWithObserver) {
// Use an invalid file path (relative paths are invalid) to get a
// FILE_ERROR_ACCESS_DENIED error when trying to write the file.
ImportantFileWriter writer(FilePath().AppendASCII("bad/../path"),
SingleThreadTaskRunner::GetCurrentDefault());
EXPECT_FALSE(PathExists(writer.path()));
EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
write_callback_observer_.ObserveNextWriteCallbacks(&writer);
writer.WriteNow("foo");
RunLoop().RunUntilIdle();
// Confirm that the write observer was invoked with its boolean parameter set
// to false.
EXPECT_EQ(CALLED_WITH_ERROR,
write_callback_observer_.GetAndResetObservationState());
EXPECT_FALSE(PathExists(writer.path()));
}
TEST_F(ImportantFileWriterTest, CallbackRunsOnWriterThread) {
base::Thread file_writer_thread("ImportantFileWriter test thread");
file_writer_thread.Start();
ImportantFileWriter writer(file_, file_writer_thread.task_runner());
EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
// Block execution on |file_writer_thread| to verify that callbacks are
// executed on it.
base::WaitableEvent wait_helper(
base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED);
file_writer_thread.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&base::WaitableEvent::Wait,
base::Unretained(&wait_helper)));
write_callback_observer_.ObserveNextWriteCallbacks(&writer);
writer.WriteNow("foo");
RunLoop().RunUntilIdle();
// Expect the callback to not have been executed before the
// |file_writer_thread| is unblocked.
EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
wait_helper.Signal();
file_writer_thread.FlushForTesting();
EXPECT_EQ(CALLED_WITH_SUCCESS,
write_callback_observer_.GetAndResetObservationState());
ASSERT_TRUE(PathExists(writer.path()));
EXPECT_EQ("foo", GetFileContent(writer.path()));
}
TEST_F(ImportantFileWriterTest, ScheduleWrite) {
constexpr TimeDelta kCommitInterval = Seconds(12345);
MockOneShotTimer timer;
ImportantFileWriter writer(file_, SingleThreadTaskRunner::GetCurrentDefault(),
kCommitInterval);
EXPECT_EQ(0u, writer.previous_data_size());
writer.SetTimerForTesting(&timer);
EXPECT_FALSE(writer.HasPendingWrite());
DataSerializer serializer("foo");
writer.ScheduleWrite(&serializer);
EXPECT_TRUE(writer.HasPendingWrite());
ASSERT_TRUE(timer.IsRunning());
EXPECT_EQ(kCommitInterval, timer.GetCurrentDelay());
timer.Fire();
EXPECT_FALSE(writer.HasPendingWrite());
EXPECT_FALSE(timer.IsRunning());
RunLoop().RunUntilIdle();
ASSERT_TRUE(PathExists(writer.path()));
EXPECT_EQ("foo", GetFileContent(writer.path()));
EXPECT_EQ(3u, writer.previous_data_size());
}
TEST_F(ImportantFileWriterTest, DoScheduledWrite) {
MockOneShotTimer timer;
ImportantFileWriter writer(file_,
SingleThreadTaskRunner::GetCurrentDefault());
writer.SetTimerForTesting(&timer);
EXPECT_FALSE(writer.HasPendingWrite());
DataSerializer serializer("foo");
writer.ScheduleWrite(&serializer);
EXPECT_TRUE(writer.HasPendingWrite());
writer.DoScheduledWrite();
EXPECT_FALSE(writer.HasPendingWrite());
RunLoop().RunUntilIdle();
ASSERT_TRUE(PathExists(writer.path()));
EXPECT_EQ("foo", GetFileContent(writer.path()));
}
TEST_F(ImportantFileWriterTest, BatchingWrites) {
MockOneShotTimer timer;
ImportantFileWriter writer(file_,
SingleThreadTaskRunner::GetCurrentDefault());
writer.SetTimerForTesting(&timer);
DataSerializer foo("foo"), bar("bar"), baz("baz");
writer.ScheduleWrite(&foo);
writer.ScheduleWrite(&bar);
writer.ScheduleWrite(&baz);
ASSERT_TRUE(timer.IsRunning());
timer.Fire();
RunLoop().RunUntilIdle();
ASSERT_TRUE(PathExists(writer.path()));
EXPECT_EQ("baz", GetFileContent(writer.path()));
}
TEST_F(ImportantFileWriterTest, ScheduleWrite_FailToSerialize) {
MockOneShotTimer timer;
ImportantFileWriter writer(file_,
SingleThreadTaskRunner::GetCurrentDefault());
writer.SetTimerForTesting(&timer);
EXPECT_FALSE(writer.HasPendingWrite());
FailingDataSerializer serializer;
writer.ScheduleWrite(&serializer);
EXPECT_TRUE(writer.HasPendingWrite());
ASSERT_TRUE(timer.IsRunning());
timer.Fire();
EXPECT_FALSE(writer.HasPendingWrite());
RunLoop().RunUntilIdle();
EXPECT_FALSE(PathExists(writer.path()));
}
TEST_F(ImportantFileWriterTest, ScheduleWrite_WriteNow) {
MockOneShotTimer timer;
ImportantFileWriter writer(file_,
SingleThreadTaskRunner::GetCurrentDefault());
writer.SetTimerForTesting(&timer);
EXPECT_FALSE(writer.HasPendingWrite());
DataSerializer serializer("foo");
writer.ScheduleWrite(&serializer);
EXPECT_TRUE(writer.HasPendingWrite());
writer.WriteNow("bar");
EXPECT_FALSE(writer.HasPendingWrite());
EXPECT_FALSE(timer.IsRunning());
RunLoop().RunUntilIdle();
ASSERT_TRUE(PathExists(writer.path()));
EXPECT_EQ("bar", GetFileContent(writer.path()));
}
TEST_F(ImportantFileWriterTest, DoScheduledWrite_FailToSerialize) {
base::HistogramTester histogram_tester;
MockOneShotTimer timer;
ImportantFileWriter writer(file_,
SingleThreadTaskRunner::GetCurrentDefault());
writer.SetTimerForTesting(&timer);
EXPECT_FALSE(writer.HasPendingWrite());
FailingDataSerializer serializer;
writer.ScheduleWrite(&serializer);
EXPECT_TRUE(writer.HasPendingWrite());
writer.DoScheduledWrite();
EXPECT_FALSE(timer.IsRunning());
EXPECT_FALSE(writer.HasPendingWrite());
RunLoop().RunUntilIdle();
EXPECT_FALSE(PathExists(writer.path()));
// We don't record metrics in case the serialization fails.
histogram_tester.ExpectTotalCount("ImportantFile.SerializationDuration", 0);
histogram_tester.ExpectTotalCount("ImportantFile.SerializationDuration.All",
0);
histogram_tester.ExpectTotalCount("ImportantFile.WriteDuration", 0);
histogram_tester.ExpectTotalCount("ImportantFile.WriteDuration.All", 0);
}
TEST_F(ImportantFileWriterTest, ScheduleWriteWithBackgroundDataSerializer) {
base::HistogramTester histogram_tester;
base::Thread file_writer_thread("ImportantFileWriter test thread");
file_writer_thread.Start();
constexpr TimeDelta kCommitInterval = Seconds(12345);
MockOneShotTimer timer;
ImportantFileWriter writer(file_, file_writer_thread.task_runner(),
kCommitInterval);
EXPECT_EQ(0u, writer.previous_data_size());
writer.SetTimerForTesting(&timer);
EXPECT_FALSE(writer.HasPendingWrite());
ASSERT_FALSE(file_writer_thread.task_runner()->RunsTasksInCurrentSequence());
BackgroundDataSerializer serializer(
base::BindLambdaForTesting([&]() -> std::optional<std::string> {
EXPECT_TRUE(
file_writer_thread.task_runner()->RunsTasksInCurrentSequence());
return "foo";
}));
writer.ScheduleWriteWithBackgroundDataSerializer(&serializer);
EXPECT_TRUE(writer.HasPendingWrite());
EXPECT_FALSE(serializer.producer_callback_obtained());
ASSERT_TRUE(timer.IsRunning());
EXPECT_EQ(kCommitInterval, timer.GetCurrentDelay());
timer.Fire();
EXPECT_FALSE(writer.HasPendingWrite());
EXPECT_TRUE(serializer.producer_callback_obtained());
EXPECT_FALSE(timer.IsRunning());
file_writer_thread.FlushForTesting();
ASSERT_TRUE(PathExists(writer.path()));
EXPECT_EQ("foo", GetFileContent(writer.path()));
histogram_tester.ExpectTotalCount("ImportantFile.SerializationDuration", 1);
histogram_tester.ExpectTotalCount("ImportantFile.SerializationDuration.All",
1);
histogram_tester.ExpectTotalCount("ImportantFile.WriteDuration", 1);
histogram_tester.ExpectTotalCount("ImportantFile.WriteDuration.All", 1);
}
TEST_F(ImportantFileWriterTest,
ScheduleWriteWithBackgroundDataSerializer_FailToSerialize) {
base::HistogramTester histogram_tester;
base::Thread file_writer_thread("ImportantFileWriter test thread");
file_writer_thread.Start();
constexpr TimeDelta kCommitInterval = Seconds(12345);
MockOneShotTimer timer;
ImportantFileWriter writer(file_, file_writer_thread.task_runner(),
kCommitInterval);
EXPECT_EQ(0u, writer.previous_data_size());
writer.SetTimerForTesting(&timer);
EXPECT_FALSE(writer.HasPendingWrite());
ASSERT_FALSE(file_writer_thread.task_runner()->RunsTasksInCurrentSequence());
BackgroundDataSerializer serializer(
base::BindLambdaForTesting([&]() -> std::optional<std::string> {
EXPECT_TRUE(
file_writer_thread.task_runner()->RunsTasksInCurrentSequence());
return std::nullopt;
}));
writer.ScheduleWriteWithBackgroundDataSerializer(&serializer);
EXPECT_TRUE(writer.HasPendingWrite());
EXPECT_FALSE(serializer.producer_callback_obtained());
EXPECT_TRUE(timer.IsRunning());
timer.Fire();
EXPECT_FALSE(timer.IsRunning());
EXPECT_TRUE(serializer.producer_callback_obtained());
EXPECT_FALSE(writer.HasPendingWrite());
file_writer_thread.FlushForTesting();
EXPECT_FALSE(PathExists(writer.path()));
// We record the foreground serialization metric despite later failure in
// background sequence.
histogram_tester.ExpectTotalCount("ImportantFile.SerializationDuration", 1);
histogram_tester.ExpectTotalCount("ImportantFile.SerializationDuration.All",
1);
histogram_tester.ExpectTotalCount("ImportantFile.WriteDuration", 0);
histogram_tester.ExpectTotalCount("ImportantFile.WriteDuration.All", 0);
}
// Test that the chunking to avoid very large writes works.
TEST_F(ImportantFileWriterTest, WriteLargeFile) {
// One byte larger than kMaxWriteAmount.
const std::string large_data(8 * 1024 * 1024 + 1, 'g');
EXPECT_FALSE(PathExists(file_));
EXPECT_TRUE(ImportantFileWriter::WriteFileAtomically(file_, large_data));
std::string actual;
EXPECT_TRUE(ReadFileToString(file_, &actual));
EXPECT_EQ(large_data, actual);
}
// Verify that a UMA metric for the serialization duration is recorded.
TEST_F(ImportantFileWriterTest, SerializationDuration) {
base::HistogramTester histogram_tester;
ImportantFileWriter writer(file_,
SingleThreadTaskRunner::GetCurrentDefault());
DataSerializer serializer("foo");
writer.ScheduleWrite(&serializer);
writer.DoScheduledWrite();
RunLoop().RunUntilIdle();
histogram_tester.ExpectTotalCount("ImportantFile.SerializationDuration", 1);
histogram_tester.ExpectTotalCount("ImportantFile.SerializationDuration.All",
1);
histogram_tester.ExpectTotalCount("ImportantFile.WriteDuration", 1);
histogram_tester.ExpectTotalCount("ImportantFile.WriteDuration.All", 1);
}
// Verify that a UMA metric for the serialization duration is recorded if the
// ImportantFileWriter has a custom histogram suffix.
TEST_F(ImportantFileWriterTest, SerializationDurationWithCustomSuffix) {
base::HistogramTester histogram_tester;
ImportantFileWriter writer(file_, SingleThreadTaskRunner::GetCurrentDefault(),
"Foo");
DataSerializer serializer("foo");
writer.ScheduleWrite(&serializer);
writer.DoScheduledWrite();
RunLoop().RunUntilIdle();
histogram_tester.ExpectTotalCount("ImportantFile.SerializationDuration.Foo",
1);
histogram_tester.ExpectTotalCount("ImportantFile.WriteDuration.Foo", 1);
// Should not be written to the unsuffixed ("unknown") histogram.
histogram_tester.ExpectTotalCount("ImportantFile.SerializationDuration", 0);
histogram_tester.ExpectTotalCount("ImportantFile.SerializationDuration.All",
1);
histogram_tester.ExpectTotalCount("ImportantFile.WriteDuration", 0);
histogram_tester.ExpectTotalCount("ImportantFile.WriteDuration.All", 1);
}
#if BUILDFLAG(IS_WIN)
// Tests that failures of ReplaceFile are handled. These don't call the OS
// ReplaceFile because they count the exact number of calls, which could be
// flaky if the test runs on a machine with file scanners.
TEST_F(ImportantFileWriterTest, ReplaceFileSuccess) {
base::HistogramTester histogram_tester;
ImportantFileWriter writer(file_,
SingleThreadTaskRunner::GetCurrentDefault());
// Unconditional success in ReplaceFile.
writer.SetReplaceFileCallbackForTesting(base::BindRepeating(
[](const FilePath&, const FilePath&, File::Error* error) {
*error = File::FILE_OK;
return true;
}));
DataSerializer serializer("foo");
EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
base::RunLoop run_loop;
write_callback_observer_.ObserveNextWriteCallbacks(&writer,
run_loop.QuitClosure());
writer.WriteNow("foo");
run_loop.Run();
EXPECT_EQ(CALLED_WITH_SUCCESS,
write_callback_observer_.GetAndResetObservationState());
// 0 means no retries were needed.
histogram_tester.ExpectUniqueSample("ImportantFile.FileReplaceRetryCount", 0,
1);
// FileReplaceRetryCount2 is only recorded if retries were needed.
histogram_tester.ExpectTotalCount("ImportantFile.FileReplaceRetryCount2", 0);
histogram_tester.ExpectTotalCount("ImportantFile.FileReplaceRetryCount2.All",
0);
// 0 means no retries were needed.
histogram_tester.ExpectUniqueSample("ImportantFile.FileReplaceResult", 0, 1);
histogram_tester.ExpectUniqueSample("ImportantFile.FileReplaceResult.All", 0,
1);
}
TEST_F(ImportantFileWriterTest, ReplaceFileRetry) {
base::HistogramTester histogram_tester;
ImportantFileWriter writer(file_,
SingleThreadTaskRunner::GetCurrentDefault());
// Fake a failure on the first two calls to ReplaceFile.
size_t retry_count = 0;
writer.SetReplaceFileCallbackForTesting(base::BindLambdaForTesting(
[&retry_count](const FilePath&, const FilePath&, File::Error* error) {
if (retry_count < 2) {
retry_count += 1;
*error = File::FILE_ERROR_IN_USE;
return false;
}
*error = File::FILE_OK;
return true;
}));
DataSerializer serializer("foo");
EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
base::RunLoop run_loop;
write_callback_observer_.ObserveNextWriteCallbacks(&writer,
run_loop.QuitClosure());
writer.WriteNow("foo");
run_loop.Run();
EXPECT_EQ(CALLED_WITH_SUCCESS,
write_callback_observer_.GetAndResetObservationState());
EXPECT_EQ(retry_count, 2u);
histogram_tester.ExpectUniqueSample("ImportantFile.FileReplaceRetryCount", 2,
1);
histogram_tester.ExpectUniqueSample("ImportantFile.FileReplaceRetryCount2", 2,
1);
histogram_tester.ExpectUniqueSample(
"ImportantFile.FileReplaceRetryCount2.All", 2, 1);
// 1 means succeeded with retries.
histogram_tester.ExpectUniqueSample("ImportantFile.FileReplaceResult", 1, 1);
histogram_tester.ExpectUniqueSample("ImportantFile.FileReplaceResult.All", 1,
1);
}
TEST_F(ImportantFileWriterTest, ReplaceFileFails) {
base::HistogramTester histogram_tester;
ImportantFileWriter writer(file_,
SingleThreadTaskRunner::GetCurrentDefault());
// Unconditional failure in ReplaceFile.
writer.SetReplaceFileCallbackForTesting(base::BindRepeating(
[](const FilePath&, const FilePath&, File::Error* error) {
*error = File::FILE_ERROR_IN_USE;
return false;
}));
DataSerializer serializer("foo");
EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
base::RunLoop run_loop;
write_callback_observer_.ObserveNextWriteCallbacks(&writer,
run_loop.QuitClosure());
writer.WriteNow("foo");
run_loop.Run();
EXPECT_EQ(CALLED_WITH_ERROR,
write_callback_observer_.GetAndResetObservationState());
// 10 means ReplaceFile never succeeded.
histogram_tester.ExpectUniqueSample("ImportantFile.FileReplaceRetryCount", 10,
1);
histogram_tester.ExpectUniqueSample("ImportantFile.FileReplaceRetryCount2", 5,
1);
histogram_tester.ExpectUniqueSample(
"ImportantFile.FileReplaceRetryCount2.All", 5, 1);
// 2 means ReplaceFile never succeeded.
histogram_tester.ExpectUniqueSample("ImportantFile.FileReplaceResult", 2, 1);
histogram_tester.ExpectUniqueSample("ImportantFile.FileReplaceResult.All", 2,
1);
}
TEST_F(ImportantFileWriterTest, ReplaceFileFailsWithSuffix) {
base::HistogramTester histogram_tester;
ImportantFileWriter writer(file_, SingleThreadTaskRunner::GetCurrentDefault(),
"Foo");
// Unconditional failure in ReplaceFile.
writer.SetReplaceFileCallbackForTesting(base::BindRepeating(
[](const FilePath&, const FilePath&, File::Error* error) {
*error = File::FILE_ERROR_IN_USE;
return false;
}));
DataSerializer serializer("foo");
EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
base::RunLoop run_loop;
write_callback_observer_.ObserveNextWriteCallbacks(&writer,
run_loop.QuitClosure());
writer.WriteNow("foo");
run_loop.Run();
EXPECT_EQ(CALLED_WITH_ERROR,
write_callback_observer_.GetAndResetObservationState());
// 10 means ReplaceFile never succeeded.
histogram_tester.ExpectUniqueSample("ImportantFile.FileReplaceRetryCount", 10,
1);
histogram_tester.ExpectUniqueSample(
"ImportantFile.FileReplaceRetryCount2.Foo", 5, 1);
histogram_tester.ExpectUniqueSample(
"ImportantFile.FileReplaceRetryCount2.All", 5, 1);
// 2 means ReplaceFile never succeeded.
histogram_tester.ExpectUniqueSample("ImportantFile.FileReplaceResult.Foo", 2,
1);
histogram_tester.ExpectUniqueSample("ImportantFile.FileReplaceResult.All", 2,
1);
}
#endif
} // namespace base