1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/files/important_file_writer.h"
6
7 #include "base/bind.h"
8 #include "base/compiler_specific.h"
9 #include "base/files/file_path.h"
10 #include "base/files/file_util.h"
11 #include "base/files/scoped_temp_dir.h"
12 #include "base/location.h"
13 #include "base/logging.h"
14 #include "base/macros.h"
15 #include "base/memory/ptr_util.h"
16 #include "base/message_loop/message_loop.h"
17 #include "base/run_loop.h"
18 #include "base/single_thread_task_runner.h"
19 #include "base/test/metrics/histogram_tester.h"
20 #include "base/threading/thread.h"
21 #include "base/threading/thread_task_runner_handle.h"
22 #include "base/time/time.h"
23 #include "base/timer/mock_timer.h"
24 #include "testing/gtest/include/gtest/gtest.h"
25
26 namespace base {
27
28 namespace {
29
GetFileContent(const FilePath & path)30 std::string GetFileContent(const FilePath& path) {
31 std::string content;
32 if (!ReadFileToString(path, &content)) {
33 NOTREACHED();
34 }
35 return content;
36 }
37
38 class DataSerializer : public ImportantFileWriter::DataSerializer {
39 public:
DataSerializer(const std::string & data)40 explicit DataSerializer(const std::string& data) : data_(data) {
41 }
42
SerializeData(std::string * output)43 bool SerializeData(std::string* output) override {
44 output->assign(data_);
45 return true;
46 }
47
48 private:
49 const std::string data_;
50 };
51
52 class FailingDataSerializer : public ImportantFileWriter::DataSerializer {
53 public:
SerializeData(std::string * output)54 bool SerializeData(std::string* output) override { return false; }
55 };
56
57 enum WriteCallbackObservationState {
58 NOT_CALLED,
59 CALLED_WITH_ERROR,
60 CALLED_WITH_SUCCESS,
61 };
62
63 class WriteCallbacksObserver {
64 public:
65 WriteCallbacksObserver() = default;
66
67 // Register OnBeforeWrite() and OnAfterWrite() to be called on the next write
68 // of |writer|.
69 void ObserveNextWriteCallbacks(ImportantFileWriter* writer);
70
71 // Returns the |WriteCallbackObservationState| which was observed, then resets
72 // it to |NOT_CALLED|.
73 WriteCallbackObservationState GetAndResetObservationState();
74
75 private:
OnBeforeWrite()76 void OnBeforeWrite() {
77 EXPECT_FALSE(before_write_called_);
78 before_write_called_ = true;
79 }
80
OnAfterWrite(bool success)81 void OnAfterWrite(bool success) {
82 EXPECT_EQ(NOT_CALLED, after_write_observation_state_);
83 after_write_observation_state_ =
84 success ? CALLED_WITH_SUCCESS : CALLED_WITH_ERROR;
85 }
86
87 bool before_write_called_ = false;
88 WriteCallbackObservationState after_write_observation_state_ = NOT_CALLED;
89
90 DISALLOW_COPY_AND_ASSIGN(WriteCallbacksObserver);
91 };
92
ObserveNextWriteCallbacks(ImportantFileWriter * writer)93 void WriteCallbacksObserver::ObserveNextWriteCallbacks(
94 ImportantFileWriter* writer) {
95 writer->RegisterOnNextWriteCallbacks(
96 base::Bind(&WriteCallbacksObserver::OnBeforeWrite,
97 base::Unretained(this)),
98 base::Bind(&WriteCallbacksObserver::OnAfterWrite,
99 base::Unretained(this)));
100 }
101
102 WriteCallbackObservationState
GetAndResetObservationState()103 WriteCallbacksObserver::GetAndResetObservationState() {
104 EXPECT_EQ(after_write_observation_state_ != NOT_CALLED, before_write_called_)
105 << "The before-write callback should always be called before the "
106 "after-write callback";
107
108 WriteCallbackObservationState state = after_write_observation_state_;
109 before_write_called_ = false;
110 after_write_observation_state_ = NOT_CALLED;
111 return state;
112 }
113
114 } // namespace
115
116 class ImportantFileWriterTest : public testing::Test {
117 public:
118 ImportantFileWriterTest() = default;
SetUp()119 void SetUp() override {
120 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
121 file_ = temp_dir_.GetPath().AppendASCII("test-file");
122 }
123
124 protected:
125 WriteCallbacksObserver write_callback_observer_;
126 FilePath file_;
127 MessageLoop loop_;
128
129 private:
130 ScopedTempDir temp_dir_;
131 };
132
TEST_F(ImportantFileWriterTest,Basic)133 TEST_F(ImportantFileWriterTest, Basic) {
134 ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
135 EXPECT_FALSE(PathExists(writer.path()));
136 EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
137 writer.WriteNow(std::make_unique<std::string>("foo"));
138 RunLoop().RunUntilIdle();
139
140 EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
141 ASSERT_TRUE(PathExists(writer.path()));
142 EXPECT_EQ("foo", GetFileContent(writer.path()));
143 }
144
TEST_F(ImportantFileWriterTest,WriteWithObserver)145 TEST_F(ImportantFileWriterTest, WriteWithObserver) {
146 ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
147 EXPECT_FALSE(PathExists(writer.path()));
148 EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
149
150 // Confirm that the observer is invoked.
151 write_callback_observer_.ObserveNextWriteCallbacks(&writer);
152 writer.WriteNow(std::make_unique<std::string>("foo"));
153 RunLoop().RunUntilIdle();
154
155 EXPECT_EQ(CALLED_WITH_SUCCESS,
156 write_callback_observer_.GetAndResetObservationState());
157 ASSERT_TRUE(PathExists(writer.path()));
158 EXPECT_EQ("foo", GetFileContent(writer.path()));
159
160 // Confirm that re-installing the observer works for another write.
161 EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
162 write_callback_observer_.ObserveNextWriteCallbacks(&writer);
163 writer.WriteNow(std::make_unique<std::string>("bar"));
164 RunLoop().RunUntilIdle();
165
166 EXPECT_EQ(CALLED_WITH_SUCCESS,
167 write_callback_observer_.GetAndResetObservationState());
168 ASSERT_TRUE(PathExists(writer.path()));
169 EXPECT_EQ("bar", GetFileContent(writer.path()));
170
171 // Confirm that writing again without re-installing the observer doesn't
172 // result in a notification.
173 EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
174 writer.WriteNow(std::make_unique<std::string>("baz"));
175 RunLoop().RunUntilIdle();
176
177 EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
178 ASSERT_TRUE(PathExists(writer.path()));
179 EXPECT_EQ("baz", GetFileContent(writer.path()));
180 }
181
TEST_F(ImportantFileWriterTest,FailedWriteWithObserver)182 TEST_F(ImportantFileWriterTest, FailedWriteWithObserver) {
183 // Use an invalid file path (relative paths are invalid) to get a
184 // FILE_ERROR_ACCESS_DENIED error when trying to write the file.
185 ImportantFileWriter writer(FilePath().AppendASCII("bad/../path"),
186 ThreadTaskRunnerHandle::Get());
187 EXPECT_FALSE(PathExists(writer.path()));
188 EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
189 write_callback_observer_.ObserveNextWriteCallbacks(&writer);
190 writer.WriteNow(std::make_unique<std::string>("foo"));
191 RunLoop().RunUntilIdle();
192
193 // Confirm that the write observer was invoked with its boolean parameter set
194 // to false.
195 EXPECT_EQ(CALLED_WITH_ERROR,
196 write_callback_observer_.GetAndResetObservationState());
197 EXPECT_FALSE(PathExists(writer.path()));
198 }
199
TEST_F(ImportantFileWriterTest,CallbackRunsOnWriterThread)200 TEST_F(ImportantFileWriterTest, CallbackRunsOnWriterThread) {
201 base::Thread file_writer_thread("ImportantFileWriter test thread");
202 file_writer_thread.Start();
203 ImportantFileWriter writer(file_, file_writer_thread.task_runner());
204 EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
205
206 // Block execution on |file_writer_thread| to verify that callbacks are
207 // executed on it.
208 base::WaitableEvent wait_helper(
209 base::WaitableEvent::ResetPolicy::MANUAL,
210 base::WaitableEvent::InitialState::NOT_SIGNALED);
211 file_writer_thread.task_runner()->PostTask(
212 FROM_HERE, base::BindOnce(&base::WaitableEvent::Wait,
213 base::Unretained(&wait_helper)));
214
215 write_callback_observer_.ObserveNextWriteCallbacks(&writer);
216 writer.WriteNow(std::make_unique<std::string>("foo"));
217 RunLoop().RunUntilIdle();
218
219 // Expect the callback to not have been executed before the
220 // |file_writer_thread| is unblocked.
221 EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
222
223 wait_helper.Signal();
224 file_writer_thread.FlushForTesting();
225
226 EXPECT_EQ(CALLED_WITH_SUCCESS,
227 write_callback_observer_.GetAndResetObservationState());
228 ASSERT_TRUE(PathExists(writer.path()));
229 EXPECT_EQ("foo", GetFileContent(writer.path()));
230 }
231
TEST_F(ImportantFileWriterTest,ScheduleWrite)232 TEST_F(ImportantFileWriterTest, ScheduleWrite) {
233 constexpr TimeDelta kCommitInterval = TimeDelta::FromSeconds(12345);
234 MockOneShotTimer timer;
235 ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get(),
236 kCommitInterval);
237 writer.SetTimerForTesting(&timer);
238 EXPECT_FALSE(writer.HasPendingWrite());
239 DataSerializer serializer("foo");
240 writer.ScheduleWrite(&serializer);
241 EXPECT_TRUE(writer.HasPendingWrite());
242 ASSERT_TRUE(timer.IsRunning());
243 EXPECT_EQ(kCommitInterval, timer.GetCurrentDelay());
244 timer.Fire();
245 EXPECT_FALSE(writer.HasPendingWrite());
246 EXPECT_FALSE(timer.IsRunning());
247 RunLoop().RunUntilIdle();
248 ASSERT_TRUE(PathExists(writer.path()));
249 EXPECT_EQ("foo", GetFileContent(writer.path()));
250 }
251
TEST_F(ImportantFileWriterTest,DoScheduledWrite)252 TEST_F(ImportantFileWriterTest, DoScheduledWrite) {
253 MockOneShotTimer timer;
254 ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
255 writer.SetTimerForTesting(&timer);
256 EXPECT_FALSE(writer.HasPendingWrite());
257 DataSerializer serializer("foo");
258 writer.ScheduleWrite(&serializer);
259 EXPECT_TRUE(writer.HasPendingWrite());
260 writer.DoScheduledWrite();
261 EXPECT_FALSE(writer.HasPendingWrite());
262 RunLoop().RunUntilIdle();
263 ASSERT_TRUE(PathExists(writer.path()));
264 EXPECT_EQ("foo", GetFileContent(writer.path()));
265 }
266
TEST_F(ImportantFileWriterTest,BatchingWrites)267 TEST_F(ImportantFileWriterTest, BatchingWrites) {
268 MockOneShotTimer timer;
269 ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
270 writer.SetTimerForTesting(&timer);
271 DataSerializer foo("foo"), bar("bar"), baz("baz");
272 writer.ScheduleWrite(&foo);
273 writer.ScheduleWrite(&bar);
274 writer.ScheduleWrite(&baz);
275 ASSERT_TRUE(timer.IsRunning());
276 timer.Fire();
277 RunLoop().RunUntilIdle();
278 ASSERT_TRUE(PathExists(writer.path()));
279 EXPECT_EQ("baz", GetFileContent(writer.path()));
280 }
281
TEST_F(ImportantFileWriterTest,ScheduleWrite_FailToSerialize)282 TEST_F(ImportantFileWriterTest, ScheduleWrite_FailToSerialize) {
283 MockOneShotTimer timer;
284 ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
285 writer.SetTimerForTesting(&timer);
286 EXPECT_FALSE(writer.HasPendingWrite());
287 FailingDataSerializer serializer;
288 writer.ScheduleWrite(&serializer);
289 EXPECT_TRUE(writer.HasPendingWrite());
290 ASSERT_TRUE(timer.IsRunning());
291 timer.Fire();
292 EXPECT_FALSE(writer.HasPendingWrite());
293 RunLoop().RunUntilIdle();
294 EXPECT_FALSE(PathExists(writer.path()));
295 }
296
TEST_F(ImportantFileWriterTest,ScheduleWrite_WriteNow)297 TEST_F(ImportantFileWriterTest, ScheduleWrite_WriteNow) {
298 MockOneShotTimer timer;
299 ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
300 writer.SetTimerForTesting(&timer);
301 EXPECT_FALSE(writer.HasPendingWrite());
302 DataSerializer serializer("foo");
303 writer.ScheduleWrite(&serializer);
304 EXPECT_TRUE(writer.HasPendingWrite());
305 writer.WriteNow(std::make_unique<std::string>("bar"));
306 EXPECT_FALSE(writer.HasPendingWrite());
307 EXPECT_FALSE(timer.IsRunning());
308
309 RunLoop().RunUntilIdle();
310 ASSERT_TRUE(PathExists(writer.path()));
311 EXPECT_EQ("bar", GetFileContent(writer.path()));
312 }
313
TEST_F(ImportantFileWriterTest,DoScheduledWrite_FailToSerialize)314 TEST_F(ImportantFileWriterTest, DoScheduledWrite_FailToSerialize) {
315 MockOneShotTimer timer;
316 ImportantFileWriter writer(file_, ThreadTaskRunnerHandle::Get());
317 writer.SetTimerForTesting(&timer);
318 EXPECT_FALSE(writer.HasPendingWrite());
319 FailingDataSerializer serializer;
320 writer.ScheduleWrite(&serializer);
321 EXPECT_TRUE(writer.HasPendingWrite());
322
323 writer.DoScheduledWrite();
324 EXPECT_FALSE(timer.IsRunning());
325 EXPECT_FALSE(writer.HasPendingWrite());
326 RunLoop().RunUntilIdle();
327 EXPECT_FALSE(PathExists(writer.path()));
328 }
329
TEST_F(ImportantFileWriterTest,WriteFileAtomicallyHistogramSuffixTest)330 TEST_F(ImportantFileWriterTest, WriteFileAtomicallyHistogramSuffixTest) {
331 base::HistogramTester histogram_tester;
332 EXPECT_FALSE(PathExists(file_));
333 EXPECT_TRUE(ImportantFileWriter::WriteFileAtomically(file_, "baz", "test"));
334 EXPECT_TRUE(PathExists(file_));
335 EXPECT_EQ("baz", GetFileContent(file_));
336 histogram_tester.ExpectTotalCount("ImportantFile.FileCreateError", 0);
337 histogram_tester.ExpectTotalCount("ImportantFile.FileCreateError.test", 0);
338
339 FilePath invalid_file_ = FilePath().AppendASCII("bad/../non_existent/path");
340 EXPECT_FALSE(PathExists(invalid_file_));
341 EXPECT_FALSE(
342 ImportantFileWriter::WriteFileAtomically(invalid_file_, nullptr));
343 histogram_tester.ExpectTotalCount("ImportantFile.FileCreateError", 1);
344 histogram_tester.ExpectTotalCount("ImportantFile.FileCreateError.test", 0);
345 EXPECT_FALSE(
346 ImportantFileWriter::WriteFileAtomically(invalid_file_, nullptr, "test"));
347 histogram_tester.ExpectTotalCount("ImportantFile.FileCreateError", 1);
348 histogram_tester.ExpectTotalCount("ImportantFile.FileCreateError.test", 1);
349 }
350
351 } // namespace base
352