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