Add InodeFileDataSource unittests.

Bug: 73625480
Change-Id: I2b2603b9866148ab50705e402fb0d4e934b5186a
diff --git a/Android.bp b/Android.bp
index e8c900f..5fa4b37 100644
--- a/Android.bp
+++ b/Android.bp
@@ -3380,6 +3380,7 @@
     "src/traced/probes/filesystem/fs_mount.cc",
     "src/traced/probes/filesystem/fs_mount_unittest.cc",
     "src/traced/probes/filesystem/inode_file_data_source.cc",
+    "src/traced/probes/filesystem/inode_file_data_source_unittest.cc",
     "src/traced/probes/filesystem/lru_inode_cache.cc",
     "src/traced/probes/filesystem/lru_inode_cache_unittest.cc",
     "src/traced/probes/filesystem/prefix_finder.cc",
diff --git a/include/perfetto/traced/data_source_types.h b/include/perfetto/traced/data_source_types.h
index d79db97..6a76e50 100644
--- a/include/perfetto/traced/data_source_types.h
+++ b/include/perfetto/traced/data_source_types.h
@@ -49,6 +49,10 @@
   void SetPaths(std::set<std::string> paths) { paths_ = std::move(paths); }
   void AddPath(std::string path) { paths_.emplace(std::move(path)); }
 
+  bool operator==(const perfetto::InodeMapValue& rhs) const {
+    return type() == rhs.type() && paths() == rhs.paths();
+  }
+
  private:
   protos::pbzero::InodeFileMap_Entry_Type entry_type_;
   std::set<std::string> paths_;
diff --git a/src/traced/probes/filesystem/BUILD.gn b/src/traced/probes/filesystem/BUILD.gn
index 78d58be..db17b8c 100644
--- a/src/traced/probes/filesystem/BUILD.gn
+++ b/src/traced/probes/filesystem/BUILD.gn
@@ -48,6 +48,7 @@
   sources = [
     "file_scanner_unittest.cc",
     "fs_mount_unittest.cc",
+    "inode_file_data_source_unittest.cc",
     "lru_inode_cache_unittest.cc",
     "prefix_finder_unittest.cc",
     "range_tree_unittest.cc",
diff --git a/src/traced/probes/filesystem/inode_file_data_source.cc b/src/traced/probes/filesystem/inode_file_data_source.cc
index cc4c34c..fdc8785 100644
--- a/src/traced/probes/filesystem/inode_file_data_source.cc
+++ b/src/traced/probes/filesystem/inode_file_data_source.cc
@@ -99,9 +99,9 @@
   scanner.Scan();
 }
 
-void FillInodeEntry(InodeFileMap* destination,
-                    Inode inode_number,
-                    const InodeMapValue& inode_map_value) {
+void InodeFileDataSource::FillInodeEntry(InodeFileMap* destination,
+                                         Inode inode_number,
+                                         const InodeMapValue& inode_map_value) {
   auto* entry = destination->add_entries();
   entry->set_inode_number(inode_number);
   entry->set_type(inode_map_value.type());
@@ -128,20 +128,22 @@
       cache_(cache),
       writer_(std::move(writer)),
       weak_factory_(this) {
-  auto weak_this = GetWeakPtr();
-  // Flush TracePacket of current scan shortly before we expect the trace
-  // to end, to retain information from any scan that might be in
-  // progress.
-  task_runner_->PostDelayedTask(
-      [weak_this] {
-        if (!weak_this) {
-          PERFETTO_DLOG("Giving up flush.");
-          return;
-        }
-        PERFETTO_DLOG("Flushing.");
-        weak_this->ResetTracePacket();
-      },
-      source_config_.trace_duration_ms() - kFlushBeforeEndMs);
+  if (kFlushBeforeEndMs < source_config_.trace_duration_ms()) {
+    auto weak_this = GetWeakPtr();
+    // Flush TracePacket of current scan shortly before we expect the trace
+    // to end, to retain information from any scan that might be in
+    // progress.
+    task_runner_->PostDelayedTask(
+        [weak_this] {
+          if (!weak_this) {
+            PERFETTO_DLOG("Giving up flush.");
+            return;
+          }
+          PERFETTO_DLOG("Flushing.");
+          weak_this->ResetTracePacket();
+        },
+        source_config_.trace_duration_ms() - kFlushBeforeEndMs);
+  }
 }
 
 void InodeFileDataSource::AddInodesFromStaticMap(
@@ -374,17 +376,17 @@
   file_scanner_->Scan(task_runner_);
 }
 
-uint64_t InodeFileDataSource::GetScanIntervalMs() {
+uint64_t InodeFileDataSource::GetScanIntervalMs() const {
   return OrDefault(source_config_.inode_file_config().scan_interval_ms(),
                    kScanIntervalMs);
 }
 
-uint64_t InodeFileDataSource::GetScanDelayMs() {
+uint64_t InodeFileDataSource::GetScanDelayMs() const {
   return OrDefault(source_config_.inode_file_config().scan_delay_ms(),
                    kScanDelayMs);
 }
 
-uint64_t InodeFileDataSource::GetScanBatchSize() {
+uint64_t InodeFileDataSource::GetScanBatchSize() const {
   return OrDefault(source_config_.inode_file_config().scan_batch_size(),
                    kScanBatchSize);
 }
diff --git a/src/traced/probes/filesystem/inode_file_data_source.h b/src/traced/probes/filesystem/inode_file_data_source.h
index 1282c53..a2be1f8 100644
--- a/src/traced/probes/filesystem/inode_file_data_source.h
+++ b/src/traced/probes/filesystem/inode_file_data_source.h
@@ -42,22 +42,12 @@
 using InodeFileMap = protos::pbzero::InodeFileMap;
 class TraceWriter;
 
-void ScanFilesDFS(
-    const std::string& root_directory,
-    const std::function<bool(BlockDeviceID block_device_id,
-                             Inode inode_number,
-                             const std::string& path,
-                             protos::pbzero::InodeFileMap_Entry_Type type)>&);
-
 // Creates block_device_map for /system partition
 void CreateStaticDeviceToInodeMap(
     const std::string& root_directory,
     std::map<BlockDeviceID, std::unordered_map<Inode, InodeMapValue>>*
         static_file_map);
 
-void FillInodeEntry(InodeFileMap* destination,
-                    Inode inode_number,
-                    const InodeMapValue& inode_map_value);
 
 class InodeFileDataSource : public FileScanner::Delegate {
  public:
@@ -74,6 +64,7 @@
   base::WeakPtr<InodeFileDataSource> GetWeakPtr() const;
 
   // Called when Inodes are seen in the FtraceEventBundle
+  // TODO(fmayer): Change  to std::pair<BlockDeviceID, Inode>.
   void OnInodes(const std::vector<std::pair<Inode, BlockDeviceID>>& inodes);
 
   // Search in /system partition and add inodes to InodeFileMap proto if found
@@ -86,21 +77,31 @@
 
   virtual ~InodeFileDataSource() {}
 
+  virtual void FillInodeEntry(InodeFileMap* destination,
+                              Inode inode_number,
+                              const InodeMapValue& inode_map_value);
+
+ protected:
+  std::multimap<BlockDeviceID, std::string> mount_points_;
+
  private:
   InodeFileMap* AddToCurrentTracePacket(BlockDeviceID block_device_id);
   void ResetTracePacket();
   void FindMissingInodes();
+
+  // Callbacks for dynamic filesystem scan.
   bool OnInodeFound(BlockDeviceID block_device_id,
                     Inode inode_number,
                     const std::string& path,
                     protos::pbzero::InodeFileMap_Entry_Type type);
   void OnInodeScanDone();
+
   void AddRootsForBlockDevice(BlockDeviceID block_device_id,
                               std::vector<std::string>* roots);
 
-  uint64_t GetScanIntervalMs();
-  uint64_t GetScanDelayMs();
-  uint64_t GetScanBatchSize();
+  uint64_t GetScanIntervalMs() const;
+  uint64_t GetScanDelayMs() const;
+  uint64_t GetScanBatchSize() const;
 
   const DataSourceConfig source_config_;
   std::set<std::string> scan_mount_points_;
@@ -111,7 +112,6 @@
   std::map<BlockDeviceID, std::unordered_map<Inode, InodeMapValue>>*
       static_file_map_;
   LRUInodeCache* cache_;
-  std::multimap<BlockDeviceID, std::string> mount_points_;
   std::unique_ptr<TraceWriter> writer_;
   std::map<BlockDeviceID, std::set<Inode>> missing_inodes_;
   std::map<BlockDeviceID, std::set<Inode>> next_missing_inodes_;
diff --git a/src/traced/probes/filesystem/inode_file_data_source_unittest.cc b/src/traced/probes/filesystem/inode_file_data_source_unittest.cc
index 63d9fe8..deb943d 100644
--- a/src/traced/probes/filesystem/inode_file_data_source_unittest.cc
+++ b/src/traced/probes/filesystem/inode_file_data_source_unittest.cc
@@ -16,13 +16,128 @@
 
 #include "src/traced/probes/filesystem/inode_file_data_source.h"
 
+#include "perfetto/trace/filesystem/inode_file_map.pbzero.h"
+#include "src/base/test/test_task_runner.h"
+#include "src/traced/probes/filesystem/lru_inode_cache.h"
+#include "src/tracing/core/null_trace_writer.h"
+
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
 namespace perfetto {
 namespace {
 
-// TODO(azappone): Add tests
+using ::testing::Eq;
+using ::testing::InvokeWithoutArgs;
+using ::testing::IsNull;
+using ::testing::Pointee;
+using ::testing::_;
+
+class TestInodeFileDataSource : public InodeFileDataSource {
+ public:
+  TestInodeFileDataSource(
+      DataSourceConfig cfg,
+      base::TaskRunner* task_runner,
+      TracingSessionID tsid,
+      std::map<BlockDeviceID, std::unordered_map<Inode, InodeMapValue>>*
+          static_file_map,
+      LRUInodeCache* cache,
+      std::unique_ptr<TraceWriter> writer)
+      : InodeFileDataSource(std::move(cfg),
+                            task_runner,
+                            tsid,
+                            static_file_map,
+                            cache,
+                            std::move(writer)) {
+    struct stat buf;
+    PERFETTO_CHECK(lstat("src/traced/probes/filesystem/testdata", &buf) != -1);
+    mount_points_.emplace(buf.st_dev, "src/traced/probes/filesystem/testdata");
+  }
+
+  MOCK_METHOD3(FillInodeEntry,
+               void(InodeFileMap* destination,
+                    Inode inode_number,
+                    const InodeMapValue& inode_map_value));
+};
+
+class InodeFileDataSourceTest : public ::testing::Test {
+ protected:
+  InodeFileDataSourceTest() {}
+
+  std::unique_ptr<TestInodeFileDataSource> GetInodeFileDataSource(
+      DataSourceConfig cfg) {
+    return std::unique_ptr<TestInodeFileDataSource>(new TestInodeFileDataSource(
+        cfg, &task_runner_, 0, &static_file_map_, &cache_,
+        std::unique_ptr<NullTraceWriter>(new NullTraceWriter)));
+  }
+
+  LRUInodeCache cache_{100};
+  std::map<BlockDeviceID, std::unordered_map<Inode, InodeMapValue>>
+      static_file_map_;
+  base::TestTaskRunner task_runner_;
+};
+
+TEST_F(InodeFileDataSourceTest, TestFileSystemScan) {
+  DataSourceConfig config;
+  config.mutable_inode_file_config()->set_scan_interval_ms(1);
+  config.mutable_inode_file_config()->set_scan_delay_ms(1);
+  auto data_source = GetInodeFileDataSource(config);
+
+  struct stat buf;
+  PERFETTO_CHECK(lstat("src/traced/probes/filesystem/testdata/file2", &buf) !=
+                 -1);
+
+  auto done = task_runner_.CreateCheckpoint("done");
+  InodeMapValue value(protos::pbzero::InodeFileMap_Entry_Type_FILE,
+                      {"src/traced/probes/filesystem/testdata/file2"});
+  EXPECT_CALL(*data_source, FillInodeEntry(_, buf.st_ino, Eq(value)))
+      .WillOnce(InvokeWithoutArgs(done));
+
+  data_source->OnInodes({{buf.st_ino, buf.st_dev}});
+  task_runner_.RunUntilCheckpoint("done");
+
+  // Expect that the found inode is added the the LRU cache.
+  EXPECT_THAT(cache_.Get(std::make_pair(buf.st_dev, buf.st_ino)),
+              Pointee(Eq(value)));
+}
+
+TEST_F(InodeFileDataSourceTest, TestStaticMap) {
+  DataSourceConfig config;
+  auto data_source = GetInodeFileDataSource(config);
+  CreateStaticDeviceToInodeMap("src/traced/probes/filesystem/testdata",
+                               &static_file_map_);
+
+  struct stat buf;
+  PERFETTO_CHECK(lstat("src/traced/probes/filesystem/testdata/file2", &buf) !=
+                 -1);
+
+  InodeMapValue value(protos::pbzero::InodeFileMap_Entry_Type_FILE,
+                      {"src/traced/probes/filesystem/testdata/file2"});
+  EXPECT_CALL(*data_source, FillInodeEntry(_, buf.st_ino, Eq(value)));
+
+  data_source->OnInodes({{buf.st_ino, buf.st_dev}});
+  // Expect that the found inode is not added the the LRU cache.
+  EXPECT_THAT(cache_.Get(std::make_pair(buf.st_dev, buf.st_ino)), IsNull());
+}
+
+TEST_F(InodeFileDataSourceTest, TestCache) {
+  DataSourceConfig config;
+  auto data_source = GetInodeFileDataSource(config);
+  CreateStaticDeviceToInodeMap("src/traced/probes/filesystem/testdata",
+                               &static_file_map_);
+
+  struct stat buf;
+  PERFETTO_CHECK(lstat("src/traced/probes/filesystem/testdata/file2", &buf) !=
+                 -1);
+
+  InodeMapValue value(protos::pbzero::InodeFileMap_Entry_Type_FILE,
+                      {"src/traced/probes/filesystem/testdata/file2"});
+  cache_.Insert(std::make_pair(buf.st_dev, buf.st_ino), value);
+
+  EXPECT_CALL(*data_source, FillInodeEntry(_, buf.st_ino, Eq(value)));
+
+  data_source->OnInodes({{buf.st_ino, buf.st_dev}});
+}
 
 }  // namespace
 }  // namespace perfetto
diff --git a/src/traced/probes/filesystem/lru_inode_cache_unittest.cc b/src/traced/probes/filesystem/lru_inode_cache_unittest.cc
index 14417ea..aafdc97 100644
--- a/src/traced/probes/filesystem/lru_inode_cache_unittest.cc
+++ b/src/traced/probes/filesystem/lru_inode_cache_unittest.cc
@@ -24,13 +24,6 @@
 
 namespace perfetto {
 
-bool operator==(const perfetto::InodeMapValue& lhs,
-                const perfetto::InodeMapValue& rhs);
-bool operator==(const perfetto::InodeMapValue& lhs,
-                const perfetto::InodeMapValue& rhs) {
-  return lhs.type() == rhs.type() && lhs.paths() == rhs.paths();
-}
-
 namespace {
 
 using ::testing::Eq;