1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "src/traced/probes/filesystem/file_scanner.h"
18 
19 #include "gmock/gmock.h"
20 #include "gtest/gtest.h"
21 
22 #include <sys/stat.h>
23 #include <memory>
24 #include <string>
25 
26 #include "perfetto/base/logging.h"
27 #include "src/base/test/test_task_runner.h"
28 
29 namespace perfetto {
30 namespace {
31 
32 using ::testing::Eq;
33 using ::testing::Contains;
34 using ::testing::UnorderedElementsAre;
35 
36 class TestDelegate : public FileScanner::Delegate {
37  public:
TestDelegate(std::function<bool (BlockDeviceID,Inode,const std::string &,protos::pbzero::InodeFileMap_Entry_Type)> callback,std::function<void ()> done_callback)38   TestDelegate(
39       std::function<bool(BlockDeviceID,
40                          Inode,
41                          const std::string&,
42                          protos::pbzero::InodeFileMap_Entry_Type)> callback,
43       std::function<void()> done_callback)
44       : callback_(std::move(callback)),
45         done_callback_(std::move(done_callback)) {}
OnInodeFound(BlockDeviceID block_device_id,Inode inode,const std::string & path,protos::pbzero::InodeFileMap_Entry_Type type)46   bool OnInodeFound(BlockDeviceID block_device_id,
47                     Inode inode,
48                     const std::string& path,
49                     protos::pbzero::InodeFileMap_Entry_Type type) override {
50     return callback_(block_device_id, inode, path, type);
51   }
52 
OnInodeScanDone()53   void OnInodeScanDone() { return done_callback_(); }
54 
55  private:
56   std::function<bool(BlockDeviceID,
57                      Inode,
58                      const std::string&,
59                      protos::pbzero::InodeFileMap_Entry_Type)>
60       callback_;
61   std::function<void()> done_callback_;
62 };
63 
64 struct FileEntry {
FileEntryperfetto::__anona0bb470c0111::FileEntry65   FileEntry(BlockDeviceID block_device_id,
66             Inode inode,
67             std::string path,
68             protos::pbzero::InodeFileMap_Entry_Type type)
69       : block_device_id_(block_device_id),
70         inode_(inode),
71         path_(std::move(path)),
72         type_(type) {}
73 
operator ==perfetto::__anona0bb470c0111::FileEntry74   bool operator==(const FileEntry& other) const {
75     return block_device_id_ == other.block_device_id_ &&
76            inode_ == other.inode_ && path_ == other.path_ &&
77            type_ == other.type_;
78   }
79 
80   BlockDeviceID block_device_id_;
81   Inode inode_;
82   std::string path_;
83   protos::pbzero::InodeFileMap_Entry_Type type_;
84 };
85 
CheckStat(const std::string & path)86 struct stat CheckStat(const std::string& path) {
87   struct stat buf;
88   PERFETTO_CHECK(lstat(path.c_str(), &buf) != -1);
89   return buf;
90 }
91 
StatFileEntry(const std::string & path,protos::pbzero::InodeFileMap_Entry_Type type)92 FileEntry StatFileEntry(const std::string& path,
93                         protos::pbzero::InodeFileMap_Entry_Type type) {
94   struct stat buf = CheckStat(path);
95   return FileEntry(buf.st_dev, buf.st_ino, path, type);
96 }
97 
TEST(FileScannerTest,TestSynchronousStop)98 TEST(FileScannerTest, TestSynchronousStop) {
99   uint64_t seen = 0;
100   bool done = false;
101   TestDelegate delegate(
102       [&seen](BlockDeviceID, Inode, const std::string&,
103               protos::pbzero::InodeFileMap_Entry_Type) {
104         ++seen;
105         return false;
106       },
107       [&done] { done = true; });
108 
109   FileScanner fs({"src/traced/probes/filesystem/testdata"}, &delegate);
110   fs.Scan();
111 
112   EXPECT_EQ(seen, 1u);
113   EXPECT_TRUE(done);
114 }
115 
TEST(FileScannerTest,TestAsynchronousStop)116 TEST(FileScannerTest, TestAsynchronousStop) {
117   uint64_t seen = 0;
118   base::TestTaskRunner task_runner;
119   TestDelegate delegate(
120       [&seen](BlockDeviceID, Inode, const std::string&,
121               protos::pbzero::InodeFileMap_Entry_Type) {
122         ++seen;
123         return false;
124       },
125       task_runner.CreateCheckpoint("done"));
126 
127   FileScanner fs({"src/traced/probes/filesystem/testdata"}, &delegate, 1, 1);
128   fs.Scan(&task_runner);
129 
130   task_runner.RunUntilCheckpoint("done");
131 
132   EXPECT_EQ(seen, 1u);
133 }
134 
TEST(FileScannerTest,TestSynchronousFindFiles)135 TEST(FileScannerTest, TestSynchronousFindFiles) {
136   std::vector<FileEntry> file_entries;
137   TestDelegate delegate(
138       [&file_entries](BlockDeviceID block_device_id, Inode inode,
139                       const std::string& path,
140                       protos::pbzero::InodeFileMap_Entry_Type type) {
141         file_entries.emplace_back(block_device_id, inode, path, type);
142         return true;
143       },
144       [] {});
145 
146   FileScanner fs({"src/traced/probes/filesystem/testdata"}, &delegate);
147   fs.Scan();
148 
149   EXPECT_THAT(
150       file_entries,
151       UnorderedElementsAre(
152           Eq(StatFileEntry("src/traced/probes/filesystem/testdata/dir1/file1",
153                            protos::pbzero::InodeFileMap_Entry_Type_FILE)),
154           Eq(StatFileEntry("src/traced/probes/filesystem/testdata/file2",
155                            protos::pbzero::InodeFileMap_Entry_Type_FILE)),
156           Eq(StatFileEntry(
157               "src/traced/probes/filesystem/testdata/dir1",
158               protos::pbzero::InodeFileMap_Entry_Type_DIRECTORY))));
159 }
160 
TEST(FileScannerTest,TestAsynchronousFindFiles)161 TEST(FileScannerTest, TestAsynchronousFindFiles) {
162   base::TestTaskRunner task_runner;
163   std::vector<FileEntry> file_entries;
164   TestDelegate delegate(
165       [&file_entries](BlockDeviceID block_device_id, Inode inode,
166                       const std::string& path,
167                       protos::pbzero::InodeFileMap_Entry_Type type) {
168         file_entries.emplace_back(block_device_id, inode, path, type);
169         return true;
170       },
171       task_runner.CreateCheckpoint("done"));
172 
173   FileScanner fs({"src/traced/probes/filesystem/testdata"}, &delegate, 1, 1);
174   fs.Scan(&task_runner);
175 
176   task_runner.RunUntilCheckpoint("done");
177 
178   EXPECT_THAT(
179       file_entries,
180       UnorderedElementsAre(
181           Eq(StatFileEntry("src/traced/probes/filesystem/testdata/dir1/file1",
182                            protos::pbzero::InodeFileMap_Entry_Type_FILE)),
183           Eq(StatFileEntry("src/traced/probes/filesystem/testdata/file2",
184                            protos::pbzero::InodeFileMap_Entry_Type_FILE)),
185           Eq(StatFileEntry(
186               "src/traced/probes/filesystem/testdata/dir1",
187               protos::pbzero::InodeFileMap_Entry_Type_DIRECTORY))));
188 }
189 
190 }  // namespace
191 }  // namespace perfetto
192