352 lines
12 KiB
C++
352 lines
12 KiB
C++
// Copyright (c) 2012 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 "base/files/important_file_writer.h"
|
|
|
|
#include "base/bind.h"
|
|
#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/location.h"
|
|
#include "base/logging.h"
|
|
#include "base/macros.h"
|
|
#include "base/memory/ptr_util.h"
|
|
#include "base/message_loop/message_loop.h"
|
|
#include "base/run_loop.h"
|
|
#include "base/single_thread_task_runner.h"
|
|
#include "base/test/metrics/histogram_tester.h"
|
|
#include "base/threading/thread.h"
|
|
#include "base/threading/thread_task_runner_handle.h"
|
|
#include "base/time/time.h"
|
|
#include "base/timer/mock_timer.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) {
|
|
}
|
|
|
|
bool SerializeData(std::string* output) override {
|
|
output->assign(data_);
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
const std::string data_;
|
|
};
|
|
|
|
class FailingDataSerializer : public ImportantFileWriter::DataSerializer {
|
|
public:
|
|
bool SerializeData(std::string* output) override { return false; }
|
|
};
|
|
|
|
enum WriteCallbackObservationState {
|
|
NOT_CALLED,
|
|
CALLED_WITH_ERROR,
|
|
CALLED_WITH_SUCCESS,
|
|
};
|
|
|
|
class WriteCallbacksObserver {
|
|
public:
|
|
WriteCallbacksObserver() = default;
|
|
|
|
// Register OnBeforeWrite() and OnAfterWrite() to be called on the next write
|
|
// of |writer|.
|
|
void ObserveNextWriteCallbacks(ImportantFileWriter* writer);
|
|
|
|
// 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;
|
|
}
|
|
|
|
bool before_write_called_ = false;
|
|
WriteCallbackObservationState after_write_observation_state_ = NOT_CALLED;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(WriteCallbacksObserver);
|
|
};
|
|
|
|
void WriteCallbacksObserver::ObserveNextWriteCallbacks(
|
|
ImportantFileWriter* writer) {
|
|
writer->RegisterOnNextWriteCallbacks(
|
|
base::Bind(&WriteCallbacksObserver::OnBeforeWrite,
|
|
base::Unretained(this)),
|
|
base::Bind(&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_;
|
|
MessageLoop loop_;
|
|
|
|
private:
|
|
ScopedTempDir temp_dir_;
|
|
};
|
|
|
|
TEST_F(ImportantFileWriterTest, Basic) {
|
|
ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
|
|
EXPECT_FALSE(PathExists(writer.path()));
|
|
EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
|
|
writer.WriteNow(std::make_unique<std::string>("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_, ThreadTaskRunnerHandle::Get());
|
|
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(std::make_unique<std::string>("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(std::make_unique<std::string>("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(std::make_unique<std::string>("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"),
|
|
ThreadTaskRunnerHandle::Get());
|
|
EXPECT_FALSE(PathExists(writer.path()));
|
|
EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
|
|
write_callback_observer_.ObserveNextWriteCallbacks(&writer);
|
|
writer.WriteNow(std::make_unique<std::string>("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(std::make_unique<std::string>("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 = TimeDelta::FromSeconds(12345);
|
|
MockOneShotTimer timer;
|
|
ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get(),
|
|
kCommitInterval);
|
|
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()));
|
|
}
|
|
|
|
TEST_F(ImportantFileWriterTest, DoScheduledWrite) {
|
|
MockOneShotTimer timer;
|
|
ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
|
|
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_, ThreadTaskRunnerHandle::Get());
|
|
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_, ThreadTaskRunnerHandle::Get());
|
|
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_, ThreadTaskRunnerHandle::Get());
|
|
writer.SetTimerForTesting(&timer);
|
|
EXPECT_FALSE(writer.HasPendingWrite());
|
|
DataSerializer serializer("foo");
|
|
writer.ScheduleWrite(&serializer);
|
|
EXPECT_TRUE(writer.HasPendingWrite());
|
|
writer.WriteNow(std::make_unique<std::string>("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) {
|
|
MockOneShotTimer timer;
|
|
ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
|
|
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()));
|
|
}
|
|
|
|
TEST_F(ImportantFileWriterTest, WriteFileAtomicallyHistogramSuffixTest) {
|
|
base::HistogramTester histogram_tester;
|
|
EXPECT_FALSE(PathExists(file_));
|
|
EXPECT_TRUE(ImportantFileWriter::WriteFileAtomically(file_, "baz", "test"));
|
|
EXPECT_TRUE(PathExists(file_));
|
|
EXPECT_EQ("baz", GetFileContent(file_));
|
|
histogram_tester.ExpectTotalCount("ImportantFile.FileCreateError", 0);
|
|
histogram_tester.ExpectTotalCount("ImportantFile.FileCreateError.test", 0);
|
|
|
|
FilePath invalid_file_ = FilePath().AppendASCII("bad/../non_existent/path");
|
|
EXPECT_FALSE(PathExists(invalid_file_));
|
|
EXPECT_FALSE(
|
|
ImportantFileWriter::WriteFileAtomically(invalid_file_, nullptr));
|
|
histogram_tester.ExpectTotalCount("ImportantFile.FileCreateError", 1);
|
|
histogram_tester.ExpectTotalCount("ImportantFile.FileCreateError.test", 0);
|
|
EXPECT_FALSE(
|
|
ImportantFileWriter::WriteFileAtomically(invalid_file_, nullptr, "test"));
|
|
histogram_tester.ExpectTotalCount("ImportantFile.FileCreateError", 1);
|
|
histogram_tester.ExpectTotalCount("ImportantFile.FileCreateError.test", 1);
|
|
}
|
|
|
|
} // namespace base
|