1 // Copyright 2012 The Chromium Authors
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/compiler_specific.h"
8 #include "base/files/file_path.h"
9 #include "base/files/file_util.h"
10 #include "base/files/scoped_temp_dir.h"
11 #include "base/functional/bind.h"
12 #include "base/location.h"
13 #include "base/memory/ptr_util.h"
14 #include "base/notreached.h"
15 #include "base/run_loop.h"
16 #include "base/sequence_checker.h"
17 #include "base/task/single_thread_task_runner.h"
18 #include "base/test/bind.h"
19 #include "base/test/metrics/histogram_tester.h"
20 #include "base/test/task_environment.h"
21 #include "base/threading/thread.h"
22 #include "base/time/time.h"
23 #include "base/timer/mock_timer.h"
24 #include "testing/gtest/include/gtest/gtest.h"
25 #include "third_party/abseil-cpp/absl/types/optional.h"
26
27 namespace base {
28
29 namespace {
30
GetFileContent(const FilePath & path)31 std::string GetFileContent(const FilePath& path) {
32 std::string content;
33 if (!ReadFileToString(path, &content)) {
34 NOTREACHED();
35 }
36 return content;
37 }
38
39 class DataSerializer : public ImportantFileWriter::DataSerializer {
40 public:
DataSerializer(const std::string & data)41 explicit DataSerializer(const std::string& data) : data_(data) {
42 }
43
SerializeData()44 absl::optional<std::string> SerializeData() override {
45 EXPECT_TRUE(sequence_checker_.CalledOnValidSequence());
46 return data_;
47 }
48
49 private:
50 const base::SequenceChecker sequence_checker_;
51 const std::string data_;
52 };
53
54 class FailingDataSerializer : public ImportantFileWriter::DataSerializer {
55 public:
SerializeData()56 absl::optional<std::string> SerializeData() override { return absl::nullopt; }
57 };
58
59 class BackgroundDataSerializer
60 : public ImportantFileWriter::BackgroundDataSerializer {
61 public:
BackgroundDataSerializer(ImportantFileWriter::BackgroundDataProducerCallback data_producer_callback)62 explicit BackgroundDataSerializer(
63 ImportantFileWriter::BackgroundDataProducerCallback
64 data_producer_callback)
65 : data_producer_callback_(std::move(data_producer_callback)) {
66 DCHECK(data_producer_callback_);
67 }
68
69 ImportantFileWriter::BackgroundDataProducerCallback
GetSerializedDataProducerForBackgroundSequence()70 GetSerializedDataProducerForBackgroundSequence() override {
71 EXPECT_TRUE(sequence_checker_.CalledOnValidSequence());
72 return std::move(data_producer_callback_);
73 }
74
producer_callback_obtained() const75 bool producer_callback_obtained() const {
76 return data_producer_callback_.is_null();
77 }
78
79 private:
80 const base::SequenceChecker sequence_checker_;
81 ImportantFileWriter::BackgroundDataProducerCallback data_producer_callback_;
82 };
83
84 enum WriteCallbackObservationState {
85 NOT_CALLED,
86 CALLED_WITH_ERROR,
87 CALLED_WITH_SUCCESS,
88 };
89
90 class WriteCallbacksObserver {
91 public:
92 WriteCallbacksObserver() = default;
93 WriteCallbacksObserver(const WriteCallbacksObserver&) = delete;
94 WriteCallbacksObserver& operator=(const WriteCallbacksObserver&) = delete;
95
96 // Register OnBeforeWrite() and OnAfterWrite() to be called on the next write
97 // of |writer|.
98 void ObserveNextWriteCallbacks(ImportantFileWriter* writer);
99
100 // Returns the |WriteCallbackObservationState| which was observed, then resets
101 // it to |NOT_CALLED|.
102 WriteCallbackObservationState GetAndResetObservationState();
103
104 private:
OnBeforeWrite()105 void OnBeforeWrite() {
106 EXPECT_FALSE(before_write_called_);
107 before_write_called_ = true;
108 }
109
OnAfterWrite(bool success)110 void OnAfterWrite(bool success) {
111 EXPECT_EQ(NOT_CALLED, after_write_observation_state_);
112 after_write_observation_state_ =
113 success ? CALLED_WITH_SUCCESS : CALLED_WITH_ERROR;
114 }
115
116 bool before_write_called_ = false;
117 WriteCallbackObservationState after_write_observation_state_ = NOT_CALLED;
118 };
119
ObserveNextWriteCallbacks(ImportantFileWriter * writer)120 void WriteCallbacksObserver::ObserveNextWriteCallbacks(
121 ImportantFileWriter* writer) {
122 writer->RegisterOnNextWriteCallbacks(
123 base::BindOnce(&WriteCallbacksObserver::OnBeforeWrite,
124 base::Unretained(this)),
125 base::BindOnce(&WriteCallbacksObserver::OnAfterWrite,
126 base::Unretained(this)));
127 }
128
129 WriteCallbackObservationState
GetAndResetObservationState()130 WriteCallbacksObserver::GetAndResetObservationState() {
131 EXPECT_EQ(after_write_observation_state_ != NOT_CALLED, before_write_called_)
132 << "The before-write callback should always be called before the "
133 "after-write callback";
134
135 WriteCallbackObservationState state = after_write_observation_state_;
136 before_write_called_ = false;
137 after_write_observation_state_ = NOT_CALLED;
138 return state;
139 }
140
141 } // namespace
142
143 class ImportantFileWriterTest : public testing::Test {
144 public:
145 ImportantFileWriterTest() = default;
SetUp()146 void SetUp() override {
147 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
148 file_ = temp_dir_.GetPath().AppendASCII("test-file");
149 }
150
151 protected:
152 WriteCallbacksObserver write_callback_observer_;
153 FilePath file_;
154 test::TaskEnvironment task_environment_;
155
156 private:
157 ScopedTempDir temp_dir_;
158 };
159
TEST_F(ImportantFileWriterTest,Basic)160 TEST_F(ImportantFileWriterTest, Basic) {
161 ImportantFileWriter writer(file_,
162 SingleThreadTaskRunner::GetCurrentDefault());
163 EXPECT_FALSE(PathExists(writer.path()));
164 EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
165 writer.WriteNow("foo");
166 RunLoop().RunUntilIdle();
167
168 EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
169 ASSERT_TRUE(PathExists(writer.path()));
170 EXPECT_EQ("foo", GetFileContent(writer.path()));
171 }
172
TEST_F(ImportantFileWriterTest,WriteWithObserver)173 TEST_F(ImportantFileWriterTest, WriteWithObserver) {
174 ImportantFileWriter writer(file_,
175 SingleThreadTaskRunner::GetCurrentDefault());
176 EXPECT_FALSE(PathExists(writer.path()));
177 EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
178
179 // Confirm that the observer is invoked.
180 write_callback_observer_.ObserveNextWriteCallbacks(&writer);
181 writer.WriteNow("foo");
182 RunLoop().RunUntilIdle();
183
184 EXPECT_EQ(CALLED_WITH_SUCCESS,
185 write_callback_observer_.GetAndResetObservationState());
186 ASSERT_TRUE(PathExists(writer.path()));
187 EXPECT_EQ("foo", GetFileContent(writer.path()));
188
189 // Confirm that re-installing the observer works for another write.
190 EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
191 write_callback_observer_.ObserveNextWriteCallbacks(&writer);
192 writer.WriteNow("bar");
193 RunLoop().RunUntilIdle();
194
195 EXPECT_EQ(CALLED_WITH_SUCCESS,
196 write_callback_observer_.GetAndResetObservationState());
197 ASSERT_TRUE(PathExists(writer.path()));
198 EXPECT_EQ("bar", GetFileContent(writer.path()));
199
200 // Confirm that writing again without re-installing the observer doesn't
201 // result in a notification.
202 EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
203 writer.WriteNow("baz");
204 RunLoop().RunUntilIdle();
205
206 EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
207 ASSERT_TRUE(PathExists(writer.path()));
208 EXPECT_EQ("baz", GetFileContent(writer.path()));
209 }
210
TEST_F(ImportantFileWriterTest,FailedWriteWithObserver)211 TEST_F(ImportantFileWriterTest, FailedWriteWithObserver) {
212 // Use an invalid file path (relative paths are invalid) to get a
213 // FILE_ERROR_ACCESS_DENIED error when trying to write the file.
214 ImportantFileWriter writer(FilePath().AppendASCII("bad/../path"),
215 SingleThreadTaskRunner::GetCurrentDefault());
216 EXPECT_FALSE(PathExists(writer.path()));
217 EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
218 write_callback_observer_.ObserveNextWriteCallbacks(&writer);
219 writer.WriteNow("foo");
220 RunLoop().RunUntilIdle();
221
222 // Confirm that the write observer was invoked with its boolean parameter set
223 // to false.
224 EXPECT_EQ(CALLED_WITH_ERROR,
225 write_callback_observer_.GetAndResetObservationState());
226 EXPECT_FALSE(PathExists(writer.path()));
227 }
228
TEST_F(ImportantFileWriterTest,CallbackRunsOnWriterThread)229 TEST_F(ImportantFileWriterTest, CallbackRunsOnWriterThread) {
230 base::Thread file_writer_thread("ImportantFileWriter test thread");
231 file_writer_thread.Start();
232 ImportantFileWriter writer(file_, file_writer_thread.task_runner());
233 EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
234
235 // Block execution on |file_writer_thread| to verify that callbacks are
236 // executed on it.
237 base::WaitableEvent wait_helper(
238 base::WaitableEvent::ResetPolicy::MANUAL,
239 base::WaitableEvent::InitialState::NOT_SIGNALED);
240 file_writer_thread.task_runner()->PostTask(
241 FROM_HERE, base::BindOnce(&base::WaitableEvent::Wait,
242 base::Unretained(&wait_helper)));
243
244 write_callback_observer_.ObserveNextWriteCallbacks(&writer);
245 writer.WriteNow("foo");
246 RunLoop().RunUntilIdle();
247
248 // Expect the callback to not have been executed before the
249 // |file_writer_thread| is unblocked.
250 EXPECT_EQ(NOT_CALLED, write_callback_observer_.GetAndResetObservationState());
251
252 wait_helper.Signal();
253 file_writer_thread.FlushForTesting();
254
255 EXPECT_EQ(CALLED_WITH_SUCCESS,
256 write_callback_observer_.GetAndResetObservationState());
257 ASSERT_TRUE(PathExists(writer.path()));
258 EXPECT_EQ("foo", GetFileContent(writer.path()));
259 }
260
TEST_F(ImportantFileWriterTest,ScheduleWrite)261 TEST_F(ImportantFileWriterTest, ScheduleWrite) {
262 constexpr TimeDelta kCommitInterval = Seconds(12345);
263 MockOneShotTimer timer;
264 ImportantFileWriter writer(file_, SingleThreadTaskRunner::GetCurrentDefault(),
265 kCommitInterval);
266 EXPECT_EQ(0u, writer.previous_data_size());
267 writer.SetTimerForTesting(&timer);
268 EXPECT_FALSE(writer.HasPendingWrite());
269 DataSerializer serializer("foo");
270 writer.ScheduleWrite(&serializer);
271 EXPECT_TRUE(writer.HasPendingWrite());
272 ASSERT_TRUE(timer.IsRunning());
273 EXPECT_EQ(kCommitInterval, timer.GetCurrentDelay());
274 timer.Fire();
275 EXPECT_FALSE(writer.HasPendingWrite());
276 EXPECT_FALSE(timer.IsRunning());
277 RunLoop().RunUntilIdle();
278 ASSERT_TRUE(PathExists(writer.path()));
279 EXPECT_EQ("foo", GetFileContent(writer.path()));
280 EXPECT_EQ(3u, writer.previous_data_size());
281 }
282
TEST_F(ImportantFileWriterTest,DoScheduledWrite)283 TEST_F(ImportantFileWriterTest, DoScheduledWrite) {
284 MockOneShotTimer timer;
285 ImportantFileWriter writer(file_,
286 SingleThreadTaskRunner::GetCurrentDefault());
287 writer.SetTimerForTesting(&timer);
288 EXPECT_FALSE(writer.HasPendingWrite());
289 DataSerializer serializer("foo");
290 writer.ScheduleWrite(&serializer);
291 EXPECT_TRUE(writer.HasPendingWrite());
292 writer.DoScheduledWrite();
293 EXPECT_FALSE(writer.HasPendingWrite());
294 RunLoop().RunUntilIdle();
295 ASSERT_TRUE(PathExists(writer.path()));
296 EXPECT_EQ("foo", GetFileContent(writer.path()));
297 }
298
TEST_F(ImportantFileWriterTest,BatchingWrites)299 TEST_F(ImportantFileWriterTest, BatchingWrites) {
300 MockOneShotTimer timer;
301 ImportantFileWriter writer(file_,
302 SingleThreadTaskRunner::GetCurrentDefault());
303 writer.SetTimerForTesting(&timer);
304 DataSerializer foo("foo"), bar("bar"), baz("baz");
305 writer.ScheduleWrite(&foo);
306 writer.ScheduleWrite(&bar);
307 writer.ScheduleWrite(&baz);
308 ASSERT_TRUE(timer.IsRunning());
309 timer.Fire();
310 RunLoop().RunUntilIdle();
311 ASSERT_TRUE(PathExists(writer.path()));
312 EXPECT_EQ("baz", GetFileContent(writer.path()));
313 }
314
TEST_F(ImportantFileWriterTest,ScheduleWrite_FailToSerialize)315 TEST_F(ImportantFileWriterTest, ScheduleWrite_FailToSerialize) {
316 MockOneShotTimer timer;
317 ImportantFileWriter writer(file_,
318 SingleThreadTaskRunner::GetCurrentDefault());
319 writer.SetTimerForTesting(&timer);
320 EXPECT_FALSE(writer.HasPendingWrite());
321 FailingDataSerializer serializer;
322 writer.ScheduleWrite(&serializer);
323 EXPECT_TRUE(writer.HasPendingWrite());
324 ASSERT_TRUE(timer.IsRunning());
325 timer.Fire();
326 EXPECT_FALSE(writer.HasPendingWrite());
327 RunLoop().RunUntilIdle();
328 EXPECT_FALSE(PathExists(writer.path()));
329 }
330
TEST_F(ImportantFileWriterTest,ScheduleWrite_WriteNow)331 TEST_F(ImportantFileWriterTest, ScheduleWrite_WriteNow) {
332 MockOneShotTimer timer;
333 ImportantFileWriter writer(file_,
334 SingleThreadTaskRunner::GetCurrentDefault());
335 writer.SetTimerForTesting(&timer);
336 EXPECT_FALSE(writer.HasPendingWrite());
337 DataSerializer serializer("foo");
338 writer.ScheduleWrite(&serializer);
339 EXPECT_TRUE(writer.HasPendingWrite());
340 writer.WriteNow("bar");
341 EXPECT_FALSE(writer.HasPendingWrite());
342 EXPECT_FALSE(timer.IsRunning());
343
344 RunLoop().RunUntilIdle();
345 ASSERT_TRUE(PathExists(writer.path()));
346 EXPECT_EQ("bar", GetFileContent(writer.path()));
347 }
348
TEST_F(ImportantFileWriterTest,DoScheduledWrite_FailToSerialize)349 TEST_F(ImportantFileWriterTest, DoScheduledWrite_FailToSerialize) {
350 base::HistogramTester histogram_tester;
351 MockOneShotTimer timer;
352 ImportantFileWriter writer(file_,
353 SingleThreadTaskRunner::GetCurrentDefault());
354 writer.SetTimerForTesting(&timer);
355 EXPECT_FALSE(writer.HasPendingWrite());
356 FailingDataSerializer serializer;
357 writer.ScheduleWrite(&serializer);
358 EXPECT_TRUE(writer.HasPendingWrite());
359
360 writer.DoScheduledWrite();
361 EXPECT_FALSE(timer.IsRunning());
362 EXPECT_FALSE(writer.HasPendingWrite());
363 RunLoop().RunUntilIdle();
364 EXPECT_FALSE(PathExists(writer.path()));
365 // We don't record metrics in case the serialization fails.
366 histogram_tester.ExpectTotalCount("ImportantFile.SerializationDuration", 0);
367 histogram_tester.ExpectTotalCount("ImportantFile.WriteDuration", 0);
368 }
369
TEST_F(ImportantFileWriterTest,ScheduleWriteWithBackgroundDataSerializer)370 TEST_F(ImportantFileWriterTest, ScheduleWriteWithBackgroundDataSerializer) {
371 base::HistogramTester histogram_tester;
372 base::Thread file_writer_thread("ImportantFileWriter test thread");
373 file_writer_thread.Start();
374 constexpr TimeDelta kCommitInterval = Seconds(12345);
375 MockOneShotTimer timer;
376 ImportantFileWriter writer(file_, file_writer_thread.task_runner(),
377 kCommitInterval);
378 EXPECT_EQ(0u, writer.previous_data_size());
379 writer.SetTimerForTesting(&timer);
380 EXPECT_FALSE(writer.HasPendingWrite());
381 ASSERT_FALSE(file_writer_thread.task_runner()->RunsTasksInCurrentSequence());
382 BackgroundDataSerializer serializer(
383 base::BindLambdaForTesting([&]() -> absl::optional<std::string> {
384 EXPECT_TRUE(
385 file_writer_thread.task_runner()->RunsTasksInCurrentSequence());
386 return "foo";
387 }));
388 writer.ScheduleWriteWithBackgroundDataSerializer(&serializer);
389 EXPECT_TRUE(writer.HasPendingWrite());
390 EXPECT_FALSE(serializer.producer_callback_obtained());
391 ASSERT_TRUE(timer.IsRunning());
392 EXPECT_EQ(kCommitInterval, timer.GetCurrentDelay());
393
394 timer.Fire();
395 EXPECT_FALSE(writer.HasPendingWrite());
396 EXPECT_TRUE(serializer.producer_callback_obtained());
397 EXPECT_FALSE(timer.IsRunning());
398 file_writer_thread.FlushForTesting();
399 ASSERT_TRUE(PathExists(writer.path()));
400 EXPECT_EQ("foo", GetFileContent(writer.path()));
401 histogram_tester.ExpectTotalCount("ImportantFile.SerializationDuration", 1);
402 histogram_tester.ExpectTotalCount("ImportantFile.WriteDuration", 1);
403 }
404
TEST_F(ImportantFileWriterTest,ScheduleWriteWithBackgroundDataSerializer_FailToSerialize)405 TEST_F(ImportantFileWriterTest,
406 ScheduleWriteWithBackgroundDataSerializer_FailToSerialize) {
407 base::HistogramTester histogram_tester;
408 base::Thread file_writer_thread("ImportantFileWriter test thread");
409 file_writer_thread.Start();
410 constexpr TimeDelta kCommitInterval = Seconds(12345);
411 MockOneShotTimer timer;
412 ImportantFileWriter writer(file_, file_writer_thread.task_runner(),
413 kCommitInterval);
414 EXPECT_EQ(0u, writer.previous_data_size());
415 writer.SetTimerForTesting(&timer);
416 EXPECT_FALSE(writer.HasPendingWrite());
417 ASSERT_FALSE(file_writer_thread.task_runner()->RunsTasksInCurrentSequence());
418 BackgroundDataSerializer serializer(
419 base::BindLambdaForTesting([&]() -> absl::optional<std::string> {
420 EXPECT_TRUE(
421 file_writer_thread.task_runner()->RunsTasksInCurrentSequence());
422 return absl::nullopt;
423 }));
424 writer.ScheduleWriteWithBackgroundDataSerializer(&serializer);
425 EXPECT_TRUE(writer.HasPendingWrite());
426 EXPECT_FALSE(serializer.producer_callback_obtained());
427 EXPECT_TRUE(timer.IsRunning());
428
429 timer.Fire();
430 EXPECT_FALSE(timer.IsRunning());
431 EXPECT_TRUE(serializer.producer_callback_obtained());
432 EXPECT_FALSE(writer.HasPendingWrite());
433 file_writer_thread.FlushForTesting();
434 EXPECT_FALSE(PathExists(writer.path()));
435 // We record the foreground serialization metric despite later failure in
436 // background sequence.
437 histogram_tester.ExpectTotalCount("ImportantFile.SerializationDuration", 1);
438 histogram_tester.ExpectTotalCount("ImportantFile.WriteDuration", 0);
439 }
440
441 // Test that the chunking to avoid very large writes works.
TEST_F(ImportantFileWriterTest,WriteLargeFile)442 TEST_F(ImportantFileWriterTest, WriteLargeFile) {
443 // One byte larger than kMaxWriteAmount.
444 const std::string large_data(8 * 1024 * 1024 + 1, 'g');
445 EXPECT_FALSE(PathExists(file_));
446 EXPECT_TRUE(ImportantFileWriter::WriteFileAtomically(file_, large_data));
447 std::string actual;
448 EXPECT_TRUE(ReadFileToString(file_, &actual));
449 EXPECT_EQ(large_data, actual);
450 }
451
452 // Verify that a UMA metric for the serialization duration is recorded.
TEST_F(ImportantFileWriterTest,SerializationDuration)453 TEST_F(ImportantFileWriterTest, SerializationDuration) {
454 base::HistogramTester histogram_tester;
455 ImportantFileWriter writer(file_,
456 SingleThreadTaskRunner::GetCurrentDefault());
457 DataSerializer serializer("foo");
458 writer.ScheduleWrite(&serializer);
459 writer.DoScheduledWrite();
460 RunLoop().RunUntilIdle();
461 histogram_tester.ExpectTotalCount("ImportantFile.SerializationDuration", 1);
462 histogram_tester.ExpectTotalCount("ImportantFile.WriteDuration", 1);
463 }
464
465 // Verify that a UMA metric for the serialization duration is recorded if the
466 // ImportantFileWriter has a custom histogram suffix.
TEST_F(ImportantFileWriterTest,SerializationDurationWithCustomSuffix)467 TEST_F(ImportantFileWriterTest, SerializationDurationWithCustomSuffix) {
468 base::HistogramTester histogram_tester;
469 ImportantFileWriter writer(file_, SingleThreadTaskRunner::GetCurrentDefault(),
470 "Foo");
471 DataSerializer serializer("foo");
472 writer.ScheduleWrite(&serializer);
473 writer.DoScheduledWrite();
474 RunLoop().RunUntilIdle();
475 histogram_tester.ExpectTotalCount("ImportantFile.SerializationDuration.Foo",
476 1);
477 histogram_tester.ExpectTotalCount("ImportantFile.WriteDuration.Foo", 1);
478 }
479
480 } // namespace base
481