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 <list>
6 #include <utility>
7 #include <vector>
8
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/functional/bind.h"
13 #include "base/i18n/file_util_icu.h"
14 #include "base/memory/raw_ptr.h"
15 #include "base/run_loop.h"
16 #include "base/strings/stringprintf.h"
17 #include "net/base/directory_lister.h"
18 #include "net/base/net_errors.h"
19 #include "net/test/gtest_util.h"
20 #include "net/test/test_with_task_environment.h"
21 #include "testing/gmock/include/gmock/gmock.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23 #include "testing/platform_test.h"
24
25 using net::test::IsError;
26 using net::test::IsOk;
27
28 namespace net {
29
30 namespace {
31
32 const int kMaxDepth = 3;
33 const int kBranchingFactor = 4;
34 const int kFilesPerDirectory = 5;
35
36 class ListerDelegate : public DirectoryLister::DirectoryListerDelegate {
37 public:
ListerDelegate(DirectoryLister::ListingType type)38 explicit ListerDelegate(DirectoryLister::ListingType type) : type_(type) {}
39
40 // When set to true, this signals that the directory list operation should be
41 // cancelled (And the run loop quit) in the first call to OnListFile.
set_cancel_lister_on_list_file(bool cancel_lister_on_list_file)42 void set_cancel_lister_on_list_file(bool cancel_lister_on_list_file) {
43 cancel_lister_on_list_file_ = cancel_lister_on_list_file;
44 }
45
46 // When set to true, this signals that the directory list operation should be
47 // cancelled (And the run loop quit) when OnDone is called.
set_cancel_lister_on_list_done(bool cancel_lister_on_list_done)48 void set_cancel_lister_on_list_done(bool cancel_lister_on_list_done) {
49 cancel_lister_on_list_done_ = cancel_lister_on_list_done;
50 }
51
OnListFile(const DirectoryLister::DirectoryListerData & data)52 void OnListFile(const DirectoryLister::DirectoryListerData& data) override {
53 ASSERT_FALSE(done_);
54
55 file_list_.push_back(data.info);
56 paths_.push_back(data.path);
57 if (cancel_lister_on_list_file_) {
58 lister_->Cancel();
59 run_loop.Quit();
60 }
61 }
62
OnListDone(int error)63 void OnListDone(int error) override {
64 ASSERT_FALSE(done_);
65
66 done_ = true;
67 error_ = error;
68 if (type_ == DirectoryLister::ALPHA_DIRS_FIRST)
69 CheckSort();
70
71 if (cancel_lister_on_list_done_)
72 lister_->Cancel();
73 run_loop.Quit();
74 }
75
CheckSort()76 void CheckSort() {
77 // Check that we got files in the right order.
78 if (!file_list_.empty()) {
79 for (size_t previous = 0, current = 1;
80 current < file_list_.size();
81 previous++, current++) {
82 // Directories should come before files.
83 if (file_list_[previous].IsDirectory() &&
84 !file_list_[current].IsDirectory()) {
85 continue;
86 }
87 EXPECT_NE(FILE_PATH_LITERAL(".."),
88 file_list_[current].GetName().BaseName().value());
89 EXPECT_EQ(file_list_[previous].IsDirectory(),
90 file_list_[current].IsDirectory());
91 EXPECT_TRUE(base::i18n::LocaleAwareCompareFilenames(
92 file_list_[previous].GetName(),
93 file_list_[current].GetName()));
94 }
95 }
96 }
97
Run(DirectoryLister * lister)98 void Run(DirectoryLister* lister) {
99 lister_ = lister;
100 lister_->Start();
101 run_loop.Run();
102 }
103
error() const104 int error() const { return error_; }
105
num_files() const106 int num_files() const { return file_list_.size(); }
107
done() const108 bool done() const { return done_; }
109
110 private:
111 bool cancel_lister_on_list_file_ = false;
112 bool cancel_lister_on_list_done_ = false;
113
114 // This is owned by the individual tests, rather than the ListerDelegate.
115 raw_ptr<DirectoryLister> lister_ = nullptr;
116
117 base::RunLoop run_loop;
118
119 bool done_ = false;
120 int error_ = -1;
121 DirectoryLister::ListingType type_;
122
123 std::vector<base::FileEnumerator::FileInfo> file_list_;
124 std::vector<base::FilePath> paths_;
125 };
126
127 } // namespace
128
129 class DirectoryListerTest : public PlatformTest, public WithTaskEnvironment {
130 public:
131 DirectoryListerTest() = default;
132
SetUp()133 void SetUp() override {
134 // Randomly create a directory structure of depth 3 in a temporary root
135 // directory.
136 std::list<std::pair<base::FilePath, int> > directories;
137 ASSERT_TRUE(temp_root_dir_.CreateUniqueTempDir());
138 directories.emplace_back(temp_root_dir_.GetPath(), 0);
139 while (!directories.empty()) {
140 std::pair<base::FilePath, int> dir_data = directories.front();
141 directories.pop_front();
142 for (int i = 0; i < kFilesPerDirectory; i++) {
143 std::string file_name = base::StringPrintf("file_id_%d", i);
144 base::FilePath file_path = dir_data.first.AppendASCII(file_name);
145 base::File file(file_path,
146 base::File::FLAG_CREATE | base::File::FLAG_WRITE);
147 ASSERT_TRUE(file.IsValid());
148 ++total_created_file_system_objects_in_temp_root_dir_;
149 if (dir_data.first == temp_root_dir_.GetPath())
150 ++created_file_system_objects_in_temp_root_dir_;
151 }
152 if (dir_data.second < kMaxDepth - 1) {
153 for (int i = 0; i < kBranchingFactor; i++) {
154 std::string dir_name = base::StringPrintf("child_dir_%d", i);
155 base::FilePath dir_path = dir_data.first.AppendASCII(dir_name);
156 ASSERT_TRUE(base::CreateDirectory(dir_path));
157 ++total_created_file_system_objects_in_temp_root_dir_;
158 if (dir_data.first == temp_root_dir_.GetPath())
159 ++created_file_system_objects_in_temp_root_dir_;
160 directories.emplace_back(dir_path, dir_data.second + 1);
161 }
162 }
163 }
164 PlatformTest::SetUp();
165 }
166
root_path() const167 const base::FilePath& root_path() const { return temp_root_dir_.GetPath(); }
168
expected_list_length_recursive() const169 int expected_list_length_recursive() const {
170 // List should include everything but the top level directory, and does not
171 // include "..".
172 return total_created_file_system_objects_in_temp_root_dir_;
173 }
174
expected_list_length_non_recursive() const175 int expected_list_length_non_recursive() const {
176 // List should include everything in the top level directory, and "..".
177 return created_file_system_objects_in_temp_root_dir_ + 1;
178 }
179
180 private:
181 // Number of files and directories created in SetUp, excluding
182 // |temp_root_dir_| itself. Includes all nested directories and their files.
183 int total_created_file_system_objects_in_temp_root_dir_ = 0;
184 // Number of files and directories created directly in |temp_root_dir_|.
185 int created_file_system_objects_in_temp_root_dir_ = 0;
186
187 base::ScopedTempDir temp_root_dir_;
188 };
189
TEST_F(DirectoryListerTest,BigDirTest)190 TEST_F(DirectoryListerTest, BigDirTest) {
191 ListerDelegate delegate(DirectoryLister::ALPHA_DIRS_FIRST);
192 DirectoryLister lister(root_path(), &delegate);
193 delegate.Run(&lister);
194
195 EXPECT_TRUE(delegate.done());
196 EXPECT_THAT(delegate.error(), IsOk());
197 EXPECT_EQ(expected_list_length_non_recursive(), delegate.num_files());
198 }
199
TEST_F(DirectoryListerTest,BigDirRecursiveTest)200 TEST_F(DirectoryListerTest, BigDirRecursiveTest) {
201 ListerDelegate delegate(DirectoryLister::NO_SORT_RECURSIVE);
202 DirectoryLister lister(root_path(), DirectoryLister::NO_SORT_RECURSIVE,
203 &delegate);
204 delegate.Run(&lister);
205
206 EXPECT_TRUE(delegate.done());
207 EXPECT_THAT(delegate.error(), IsOk());
208 EXPECT_EQ(expected_list_length_recursive(), delegate.num_files());
209 }
210
TEST_F(DirectoryListerTest,EmptyDirTest)211 TEST_F(DirectoryListerTest, EmptyDirTest) {
212 base::ScopedTempDir tempDir;
213 EXPECT_TRUE(tempDir.CreateUniqueTempDir());
214
215 ListerDelegate delegate(DirectoryLister::ALPHA_DIRS_FIRST);
216 DirectoryLister lister(tempDir.GetPath(), &delegate);
217 delegate.Run(&lister);
218
219 EXPECT_TRUE(delegate.done());
220 EXPECT_THAT(delegate.error(), IsOk());
221 // Contains only the parent directory ("..").
222 EXPECT_EQ(1, delegate.num_files());
223 }
224
225 // This doesn't really test much, except make sure calling cancel before any
226 // callbacks are invoked doesn't crash. Can't wait for all tasks running on a
227 // worker pool to complete, unfortunately.
228 // TODO(mmenke): See if there's a way to make this fail more reliably on
229 // regression.
TEST_F(DirectoryListerTest,BasicCancelTest)230 TEST_F(DirectoryListerTest, BasicCancelTest) {
231 ListerDelegate delegate(DirectoryLister::ALPHA_DIRS_FIRST);
232 auto lister = std::make_unique<DirectoryLister>(root_path(), &delegate);
233 lister->Start();
234 lister->Cancel();
235 base::RunLoop().RunUntilIdle();
236
237 EXPECT_FALSE(delegate.done());
238 EXPECT_EQ(0, delegate.num_files());
239 }
240
TEST_F(DirectoryListerTest,CancelOnListFileTest)241 TEST_F(DirectoryListerTest, CancelOnListFileTest) {
242 ListerDelegate delegate(DirectoryLister::ALPHA_DIRS_FIRST);
243 DirectoryLister lister(root_path(), &delegate);
244 delegate.set_cancel_lister_on_list_file(true);
245 delegate.Run(&lister);
246
247 EXPECT_FALSE(delegate.done());
248 EXPECT_EQ(1, delegate.num_files());
249 }
250
TEST_F(DirectoryListerTest,CancelOnListDoneTest)251 TEST_F(DirectoryListerTest, CancelOnListDoneTest) {
252 ListerDelegate delegate(DirectoryLister::ALPHA_DIRS_FIRST);
253 DirectoryLister lister(root_path(), &delegate);
254 delegate.set_cancel_lister_on_list_done(true);
255 delegate.Run(&lister);
256
257 EXPECT_TRUE(delegate.done());
258 EXPECT_THAT(delegate.error(), IsOk());
259 EXPECT_EQ(expected_list_length_non_recursive(), delegate.num_files());
260 }
261
TEST_F(DirectoryListerTest,CancelOnLastElementTest)262 TEST_F(DirectoryListerTest, CancelOnLastElementTest) {
263 base::ScopedTempDir tempDir;
264 EXPECT_TRUE(tempDir.CreateUniqueTempDir());
265
266 ListerDelegate delegate(DirectoryLister::ALPHA_DIRS_FIRST);
267 DirectoryLister lister(tempDir.GetPath(), &delegate);
268 delegate.set_cancel_lister_on_list_file(true);
269 delegate.Run(&lister);
270
271 EXPECT_FALSE(delegate.done());
272 // Contains only the parent directory ("..").
273 EXPECT_EQ(1, delegate.num_files());
274 }
275
TEST_F(DirectoryListerTest,NoSuchDirTest)276 TEST_F(DirectoryListerTest, NoSuchDirTest) {
277 base::ScopedTempDir tempDir;
278 EXPECT_TRUE(tempDir.CreateUniqueTempDir());
279
280 ListerDelegate delegate(DirectoryLister::ALPHA_DIRS_FIRST);
281 DirectoryLister lister(
282 tempDir.GetPath().AppendASCII("this_path_does_not_exist"), &delegate);
283 delegate.Run(&lister);
284
285 EXPECT_THAT(delegate.error(), IsError(ERR_FILE_NOT_FOUND));
286 EXPECT_EQ(0, delegate.num_files());
287 }
288
289 } // namespace net
290