1 // Copyright 2020 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_cleaner.h"
6
7 #include <optional>
8
9 #include "base/check.h"
10 #include "base/files/file.h"
11 #include "base/files/file_path.h"
12 #include "base/files/file_util.h"
13 #include "base/files/scoped_temp_dir.h"
14 #include "base/task/thread_pool.h"
15 #include "base/test/bind.h"
16 #include "base/test/task_environment.h"
17 #include "base/test/test_waitable_event.h"
18 #include "base/time/time.h"
19 #include "testing/gmock/include/gmock/gmock.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21
22 using ::testing::ElementsAre;
23
24 namespace base {
25
26 class ImportantFileWriterCleanerTest : public ::testing::Test {
27 public:
ImportantFileWriterCleanerTest()28 ImportantFileWriterCleanerTest()
29 : old_file_time_(ImportantFileWriterCleaner::GetInstance()
30 .GetUpperBoundTimeForTest() -
31 Milliseconds(1)) {}
32
33 protected:
34 // Initializes and Starts the global cleaner at construction and Stops it
35 // at destruction. ("Lifetime" refers to its activity rather than existence.)
36 class ScopedCleanerLifetime {
37 public:
ScopedCleanerLifetime()38 ScopedCleanerLifetime() {
39 auto& instance = ImportantFileWriterCleaner::GetInstance();
40 instance.Initialize();
41 instance.Start();
42 }
43 ScopedCleanerLifetime(const ScopedCleanerLifetime&) = delete;
44 ScopedCleanerLifetime& operator=(const ScopedCleanerLifetime&) = delete;
~ScopedCleanerLifetime()45 ~ScopedCleanerLifetime() {
46 ImportantFileWriterCleaner::GetInstance().Stop();
47 }
48 };
49
50 void SetUp() override;
51 void TearDown() override;
52
dir_1() const53 const FilePath& dir_1() const { return dir_1_; }
dir_1_file_new() const54 const FilePath& dir_1_file_new() const { return dir_1_file_new_; }
dir_1_file_old() const55 const FilePath& dir_1_file_old() const { return dir_1_file_old_; }
dir_1_file_other() const56 const FilePath& dir_1_file_other() const { return dir_1_file_other_; }
dir_2() const57 const FilePath& dir_2() const { return dir_2_; }
dir_2_file_new() const58 const FilePath& dir_2_file_new() const { return dir_2_file_new_; }
dir_2_file_old() const59 const FilePath& dir_2_file_old() const { return dir_2_file_old_; }
dir_2_file_other() const60 const FilePath& dir_2_file_other() const { return dir_2_file_other_; }
61
StartCleaner()62 void StartCleaner() {
63 DCHECK(!cleaner_lifetime_.has_value());
64 cleaner_lifetime_.emplace();
65 }
66
StopCleaner()67 void StopCleaner() {
68 DCHECK(cleaner_lifetime_.has_value());
69 cleaner_lifetime_.reset();
70 }
71
CreateNewFileInDir(const FilePath & dir,FilePath & path)72 void CreateNewFileInDir(const FilePath& dir, FilePath& path) {
73 File file = CreateAndOpenTemporaryFileInDir(dir, &path);
74 ASSERT_TRUE(file.IsValid());
75 }
76
CreateOldFileInDir(const FilePath & dir,FilePath & path)77 void CreateOldFileInDir(const FilePath& dir, FilePath& path) {
78 File file = CreateAndOpenTemporaryFileInDir(dir, &path);
79 ASSERT_TRUE(file.IsValid());
80 ASSERT_TRUE(file.SetTimes(Time::Now(), old_file_time_));
81 }
82
CreateOldFile(const FilePath & path)83 void CreateOldFile(const FilePath& path) {
84 File file(path, File::FLAG_CREATE | File::FLAG_WRITE);
85 ASSERT_TRUE(file.IsValid());
86 ASSERT_TRUE(file.SetTimes(Time::Now(), old_file_time_));
87 }
88
89 ScopedTempDir temp_dir_;
90 test::TaskEnvironment task_environment_;
91
92 private:
93 const Time old_file_time_;
94 FilePath dir_1_;
95 FilePath dir_2_;
96 FilePath dir_1_file_new_;
97 FilePath dir_1_file_old_;
98 FilePath dir_1_file_other_;
99 FilePath dir_2_file_new_;
100 FilePath dir_2_file_old_;
101 FilePath dir_2_file_other_;
102 std::optional<ScopedCleanerLifetime> cleaner_lifetime_;
103 };
104
SetUp()105 void ImportantFileWriterCleanerTest::SetUp() {
106 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
107
108 // Create two directories that will hold files to be cleaned.
109 dir_1_ = temp_dir_.GetPath().Append(FILE_PATH_LITERAL("dir_1"));
110 ASSERT_TRUE(CreateDirectory(dir_1_));
111 dir_2_ = temp_dir_.GetPath().Append(FILE_PATH_LITERAL("dir_2"));
112 ASSERT_TRUE(CreateDirectory(dir_2_));
113
114 // Create some old and new files in each dir.
115 ASSERT_NO_FATAL_FAILURE(CreateNewFileInDir(dir_1_, dir_1_file_new_));
116
117 ASSERT_NO_FATAL_FAILURE(CreateOldFileInDir(dir_1_, dir_1_file_old_));
118
119 dir_1_file_other_ = dir_1_.Append(FILE_PATH_LITERAL("other.nottmp"));
120 ASSERT_NO_FATAL_FAILURE(CreateOldFile(dir_1_file_other_));
121
122 ASSERT_NO_FATAL_FAILURE(CreateNewFileInDir(dir_2_, dir_2_file_new_));
123
124 ASSERT_NO_FATAL_FAILURE(CreateOldFileInDir(dir_2_, dir_2_file_old_));
125
126 dir_2_file_other_ = dir_2_.Append(FILE_PATH_LITERAL("other.nottmp"));
127 ASSERT_NO_FATAL_FAILURE(CreateOldFile(dir_2_file_other_));
128 }
129
TearDown()130 void ImportantFileWriterCleanerTest::TearDown() {
131 cleaner_lifetime_.reset();
132 task_environment_.RunUntilIdle();
133 ImportantFileWriterCleaner::GetInstance().UninitializeForTesting();
134 EXPECT_TRUE(temp_dir_.Delete());
135 }
136
137 // Tests that adding a directory without initializing the cleaner does nothing.
TEST_F(ImportantFileWriterCleanerTest,NotInitializedNoOpAdd)138 TEST_F(ImportantFileWriterCleanerTest, NotInitializedNoOpAdd) {
139 ImportantFileWriterCleaner::AddDirectory(dir_1());
140 task_environment_.RunUntilIdle();
141 EXPECT_TRUE(PathExists(dir_1_file_new()));
142 EXPECT_TRUE(PathExists(dir_1_file_old()));
143 EXPECT_TRUE(PathExists(dir_1_file_other()));
144 EXPECT_TRUE(PathExists(dir_2_file_new()));
145 EXPECT_TRUE(PathExists(dir_2_file_old()));
146 EXPECT_TRUE(PathExists(dir_2_file_other()));
147 }
148
149 // Tests that adding a directory without starting the cleaner does nothing.
TEST_F(ImportantFileWriterCleanerTest,NotStartedNoOpAdd)150 TEST_F(ImportantFileWriterCleanerTest, NotStartedNoOpAdd) {
151 ImportantFileWriterCleaner::GetInstance().Initialize();
152 ImportantFileWriterCleaner::AddDirectory(dir_1());
153 task_environment_.RunUntilIdle();
154 EXPECT_TRUE(PathExists(dir_1_file_new()));
155 EXPECT_TRUE(PathExists(dir_1_file_old()));
156 EXPECT_TRUE(PathExists(dir_1_file_other()));
157 EXPECT_TRUE(PathExists(dir_2_file_new()));
158 EXPECT_TRUE(PathExists(dir_2_file_old()));
159 EXPECT_TRUE(PathExists(dir_2_file_other()));
160 }
161
162 // Tests that starting and stopping does no harm.
TEST_F(ImportantFileWriterCleanerTest,StartStop)163 TEST_F(ImportantFileWriterCleanerTest, StartStop) {
164 StartCleaner();
165 StopCleaner();
166 }
167
168 // Tests that adding a directory then starting the cleaner works.
TEST_F(ImportantFileWriterCleanerTest,AddStart)169 TEST_F(ImportantFileWriterCleanerTest, AddStart) {
170 ImportantFileWriterCleaner::GetInstance().Initialize();
171 ImportantFileWriterCleaner::AddDirectory(dir_1());
172 StartCleaner();
173 task_environment_.RunUntilIdle();
174
175 // The old file should have been cleaned from the added dir.
176 EXPECT_TRUE(PathExists(dir_1_file_new()));
177 EXPECT_FALSE(PathExists(dir_1_file_old()));
178 EXPECT_TRUE(PathExists(dir_1_file_other()));
179 EXPECT_TRUE(PathExists(dir_2_file_new()));
180 EXPECT_TRUE(PathExists(dir_2_file_old()));
181 EXPECT_TRUE(PathExists(dir_2_file_other()));
182 }
183
184 // Tests that adding multiple directories before starting cleans both.
TEST_F(ImportantFileWriterCleanerTest,AddAddStart)185 TEST_F(ImportantFileWriterCleanerTest, AddAddStart) {
186 ImportantFileWriterCleaner::GetInstance().Initialize();
187 ImportantFileWriterCleaner::AddDirectory(dir_1());
188 ImportantFileWriterCleaner::AddDirectory(dir_2());
189 StartCleaner();
190 task_environment_.RunUntilIdle();
191
192 // The old file should have been cleaned from both added dirs.
193 EXPECT_TRUE(PathExists(dir_1_file_new()));
194 EXPECT_FALSE(PathExists(dir_1_file_old()));
195 EXPECT_TRUE(PathExists(dir_1_file_other()));
196 EXPECT_TRUE(PathExists(dir_2_file_new()));
197 EXPECT_FALSE(PathExists(dir_2_file_old()));
198 EXPECT_TRUE(PathExists(dir_2_file_other()));
199 }
200
201 // Tests that starting the cleaner then adding a directory works.
TEST_F(ImportantFileWriterCleanerTest,StartAdd)202 TEST_F(ImportantFileWriterCleanerTest, StartAdd) {
203 StartCleaner();
204 ImportantFileWriterCleaner::AddDirectory(dir_1());
205 task_environment_.RunUntilIdle();
206
207 // The old file should have been cleaned from the added dir.
208 EXPECT_TRUE(PathExists(dir_1_file_new()));
209 EXPECT_FALSE(PathExists(dir_1_file_old()));
210 EXPECT_TRUE(PathExists(dir_1_file_other()));
211 EXPECT_TRUE(PathExists(dir_2_file_new()));
212 EXPECT_TRUE(PathExists(dir_2_file_old()));
213 EXPECT_TRUE(PathExists(dir_2_file_other()));
214 }
215
216 // Tests that starting the cleaner twice doesn't cause it to clean twice.
TEST_F(ImportantFileWriterCleanerTest,StartTwice)217 TEST_F(ImportantFileWriterCleanerTest, StartTwice) {
218 StartCleaner();
219 ImportantFileWriterCleaner::AddDirectory(dir_1());
220 task_environment_.RunUntilIdle();
221
222 // Recreate the old file that was just cleaned.
223 ASSERT_NO_FATAL_FAILURE(CreateOldFile(dir_1_file_old()));
224
225 // Start again and make sure it wasn't cleaned again.
226 ImportantFileWriterCleaner::GetInstance().Start();
227 task_environment_.RunUntilIdle();
228
229 EXPECT_TRUE(PathExists(dir_1_file_old()));
230 }
231
232 // Tests that adding a dir twice doesn't cause it to clean twice.
TEST_F(ImportantFileWriterCleanerTest,AddTwice)233 TEST_F(ImportantFileWriterCleanerTest, AddTwice) {
234 StartCleaner();
235 ImportantFileWriterCleaner::AddDirectory(dir_1());
236 task_environment_.RunUntilIdle();
237
238 // Recreate the old file that was just cleaned.
239 ASSERT_NO_FATAL_FAILURE(CreateOldFile(dir_1_file_old()));
240
241 // Add the directory again and make sure nothing else is cleaned.
242 ImportantFileWriterCleaner::AddDirectory(dir_1());
243 task_environment_.RunUntilIdle();
244
245 EXPECT_TRUE(PathExists(dir_1_file_old()));
246 }
247
248 // Tests that AddDirectory called from another thread properly bounces back to
249 // the main thread for processing.
TEST_F(ImportantFileWriterCleanerTest,StartAddFromOtherThread)250 TEST_F(ImportantFileWriterCleanerTest, StartAddFromOtherThread) {
251 StartCleaner();
252
253 // Add from the ThreadPool and wait for it to finish.
254 TestWaitableEvent waitable_event;
255 ThreadPool::PostTask(FROM_HERE, BindLambdaForTesting([&]() {
256 ImportantFileWriterCleaner::AddDirectory(dir_1());
257 waitable_event.Signal();
258 }));
259 waitable_event.Wait();
260
261 // Allow the cleaner to run.
262 task_environment_.RunUntilIdle();
263
264 // The old file should have been cleaned from the added dir.
265 EXPECT_TRUE(PathExists(dir_1_file_new()));
266 EXPECT_FALSE(PathExists(dir_1_file_old()));
267 EXPECT_TRUE(PathExists(dir_1_file_other()));
268 EXPECT_TRUE(PathExists(dir_2_file_new()));
269 EXPECT_TRUE(PathExists(dir_2_file_old()));
270 EXPECT_TRUE(PathExists(dir_2_file_other()));
271 }
272
273 // Tests that adding a directory while a session is processing a previous
274 // directory works.
TEST_F(ImportantFileWriterCleanerTest,AddStartAdd)275 TEST_F(ImportantFileWriterCleanerTest, AddStartAdd) {
276 ImportantFileWriterCleaner::GetInstance().Initialize();
277 ImportantFileWriterCleaner::AddDirectory(dir_1());
278 StartCleaner();
279 ImportantFileWriterCleaner::AddDirectory(dir_2());
280 task_environment_.RunUntilIdle();
281
282 // The old file should have been cleaned from both added dirs.
283 EXPECT_TRUE(PathExists(dir_1_file_new()));
284 EXPECT_FALSE(PathExists(dir_1_file_old()));
285 EXPECT_TRUE(PathExists(dir_1_file_other()));
286 EXPECT_TRUE(PathExists(dir_2_file_new()));
287 EXPECT_FALSE(PathExists(dir_2_file_old()));
288 EXPECT_TRUE(PathExists(dir_2_file_other()));
289 }
290
291 // Tests stopping while the background task is running.
TEST_F(ImportantFileWriterCleanerTest,StopWhileRunning)292 TEST_F(ImportantFileWriterCleanerTest, StopWhileRunning) {
293 ImportantFileWriterCleaner::GetInstance().Initialize();
294
295 // Create a great many old files in dir1.
296 for (int i = 0; i < 100; ++i) {
297 FilePath path;
298 CreateOldFileInDir(dir_1(), path);
299 }
300
301 ImportantFileWriterCleaner::AddDirectory(dir_1());
302 StartCleaner();
303
304 // It's possible that the background task will quickly delete all 100 files.
305 // In all likelihood, though, the stop flag will be read and processed before
306 // then. Either case is a success.
307 StopCleaner();
308 task_environment_.RunUntilIdle();
309 }
310
311 } // namespace base
312