Add unittests for file_scanner. Fix minor bug.

Bug: 73625480
Change-Id: Ice90c62ee6dd9742add9627cf7d8c9b9f2e6d741
diff --git a/Android.bp b/Android.bp
index ad89c9d..46ce586 100644
--- a/Android.bp
+++ b/Android.bp
@@ -3374,6 +3374,7 @@
     "src/protozero/test/fake_scattered_buffer.cc",
     "src/protozero/test/protozero_conformance_unittest.cc",
     "src/traced/probes/filesystem/file_scanner.cc",
+    "src/traced/probes/filesystem/file_scanner_unittest.cc",
     "src/traced/probes/filesystem/fs_mount.cc",
     "src/traced/probes/filesystem/fs_mount_unittest.cc",
     "src/traced/probes/filesystem/inode_file_data_source.cc",
diff --git a/src/traced/probes/filesystem/BUILD.gn b/src/traced/probes/filesystem/BUILD.gn
index 6beb973..78d58be 100644
--- a/src/traced/probes/filesystem/BUILD.gn
+++ b/src/traced/probes/filesystem/BUILD.gn
@@ -43,8 +43,10 @@
     ":filesystem",
     "../../../../gn:default_deps",
     "../../../../gn:gtest_deps",
+    "../../../../src/base:test_support",
   ]
   sources = [
+    "file_scanner_unittest.cc",
     "fs_mount_unittest.cc",
     "lru_inode_cache_unittest.cc",
     "prefix_finder_unittest.cc",
diff --git a/src/traced/probes/filesystem/file_scanner.cc b/src/traced/probes/filesystem/file_scanner.cc
index 8369e50..83dc767 100644
--- a/src/traced/probes/filesystem/file_scanner.cc
+++ b/src/traced/probes/filesystem/file_scanner.cc
@@ -58,6 +58,7 @@
 void FileScanner::Scan() {
   while (!Done())
     Step();
+  delegate_->OnInodeScanDone();
 }
 void FileScanner::Scan(base::TaskRunner* task_runner) {
   PERFETTO_DCHECK(scan_interval_ms_ && scan_steps_);
diff --git a/src/traced/probes/filesystem/file_scanner_unittest.cc b/src/traced/probes/filesystem/file_scanner_unittest.cc
new file mode 100644
index 0000000..44e127d
--- /dev/null
+++ b/src/traced/probes/filesystem/file_scanner_unittest.cc
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "src/traced/probes/filesystem/file_scanner.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include <sys/stat.h>
+#include <memory>
+#include <string>
+
+#include "perfetto/base/logging.h"
+#include "src/base/test/test_task_runner.h"
+
+namespace perfetto {
+namespace {
+
+using ::testing::Eq;
+using ::testing::Contains;
+using ::testing::UnorderedElementsAre;
+
+class TestDelegate : public FileScanner::Delegate {
+ public:
+  TestDelegate(
+      std::function<bool(BlockDeviceID,
+                         Inode,
+                         const std::string&,
+                         protos::pbzero::InodeFileMap_Entry_Type)> callback,
+      std::function<void()> done_callback)
+      : callback_(std::move(callback)),
+        done_callback_(std::move(done_callback)) {}
+  bool OnInodeFound(BlockDeviceID block_device_id,
+                    Inode inode,
+                    const std::string& path,
+                    protos::pbzero::InodeFileMap_Entry_Type type) override {
+    return callback_(block_device_id, inode, path, type);
+  }
+
+  void OnInodeScanDone() { return done_callback_(); }
+
+ private:
+  std::function<bool(BlockDeviceID,
+                     Inode,
+                     const std::string&,
+                     protos::pbzero::InodeFileMap_Entry_Type)>
+      callback_;
+  std::function<void()> done_callback_;
+};
+
+struct FileEntry {
+  FileEntry(BlockDeviceID block_device_id,
+            Inode inode,
+            std::string path,
+            protos::pbzero::InodeFileMap_Entry_Type type)
+      : block_device_id_(block_device_id),
+        inode_(inode),
+        path_(std::move(path)),
+        type_(type) {}
+
+  bool operator==(const FileEntry& other) const {
+    return block_device_id_ == other.block_device_id_ &&
+           inode_ == other.inode_ && path_ == other.path_ &&
+           type_ == other.type_;
+  }
+
+  BlockDeviceID block_device_id_;
+  Inode inode_;
+  std::string path_;
+  protos::pbzero::InodeFileMap_Entry_Type type_;
+};
+
+struct stat CheckStat(const std::string& path) {
+  struct stat buf;
+  PERFETTO_CHECK(lstat(path.c_str(), &buf) != -1);
+  return buf;
+}
+
+FileEntry StatFileEntry(const std::string& path,
+                        protos::pbzero::InodeFileMap_Entry_Type type) {
+  struct stat buf = CheckStat(path);
+  return FileEntry(buf.st_dev, buf.st_ino, path, type);
+}
+
+TEST(FileScannerTest, TestSynchronousStop) {
+  uint64_t seen = 0;
+  bool done = false;
+  TestDelegate delegate(
+      [&seen](BlockDeviceID block_device_id, Inode inode,
+              const std::string& path,
+              protos::pbzero::InodeFileMap_Entry_Type type) {
+        ++seen;
+        return false;
+      },
+      [&done] { done = true; });
+
+  FileScanner fs({"src/traced/probes/filesystem/testdata"}, &delegate);
+  fs.Scan();
+
+  EXPECT_EQ(seen, 1u);
+  EXPECT_TRUE(done);
+}
+
+TEST(FileScannerTest, TestAsynchronousStop) {
+  uint64_t seen = 0;
+  base::TestTaskRunner task_runner;
+  TestDelegate delegate(
+      [&seen](BlockDeviceID block_device_id, Inode inode,
+              const std::string& path,
+              protos::pbzero::InodeFileMap_Entry_Type type) {
+        ++seen;
+        return false;
+      },
+      task_runner.CreateCheckpoint("done"));
+
+  FileScanner fs({"src/traced/probes/filesystem/testdata"}, &delegate, 1, 1);
+  fs.Scan(&task_runner);
+
+  task_runner.RunUntilCheckpoint("done");
+
+  EXPECT_EQ(seen, 1u);
+}
+
+TEST(FileScannerTest, TestSynchronousFindFiles) {
+  std::vector<FileEntry> file_entries;
+  TestDelegate delegate(
+      [&file_entries](BlockDeviceID block_device_id, Inode inode,
+                      const std::string& path,
+                      protos::pbzero::InodeFileMap_Entry_Type type) {
+        file_entries.emplace_back(block_device_id, inode, path, type);
+        return true;
+      },
+      [] {});
+
+  FileScanner fs({"src/traced/probes/filesystem/testdata"}, &delegate);
+  fs.Scan();
+
+  EXPECT_THAT(
+      file_entries,
+      UnorderedElementsAre(
+          Eq(StatFileEntry("src/traced/probes/filesystem/testdata/dir1/file1",
+                           protos::pbzero::InodeFileMap_Entry_Type_FILE)),
+          Eq(StatFileEntry("src/traced/probes/filesystem/testdata/file2",
+                           protos::pbzero::InodeFileMap_Entry_Type_FILE)),
+          Eq(StatFileEntry(
+              "src/traced/probes/filesystem/testdata/dir1",
+              protos::pbzero::InodeFileMap_Entry_Type_DIRECTORY))));
+}
+
+TEST(FileScannerTest, TestAsynchronousFindFiles) {
+  base::TestTaskRunner task_runner;
+  std::vector<FileEntry> file_entries;
+  TestDelegate delegate(
+      [&file_entries](BlockDeviceID block_device_id, Inode inode,
+                      const std::string& path,
+                      protos::pbzero::InodeFileMap_Entry_Type type) {
+        file_entries.emplace_back(block_device_id, inode, path, type);
+        return true;
+      },
+      task_runner.CreateCheckpoint("done"));
+
+  FileScanner fs({"src/traced/probes/filesystem/testdata"}, &delegate, 1, 1);
+  fs.Scan(&task_runner);
+
+  task_runner.RunUntilCheckpoint("done");
+
+  EXPECT_THAT(
+      file_entries,
+      UnorderedElementsAre(
+          Eq(StatFileEntry("src/traced/probes/filesystem/testdata/dir1/file1",
+                           protos::pbzero::InodeFileMap_Entry_Type_FILE)),
+          Eq(StatFileEntry("src/traced/probes/filesystem/testdata/file2",
+                           protos::pbzero::InodeFileMap_Entry_Type_FILE)),
+          Eq(StatFileEntry(
+              "src/traced/probes/filesystem/testdata/dir1",
+              protos::pbzero::InodeFileMap_Entry_Type_DIRECTORY))));
+}
+
+}  // namespace
+}  // namespace perfetto
diff --git a/src/traced/probes/filesystem/testdata/dir1/file1 b/src/traced/probes/filesystem/testdata/dir1/file1
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/traced/probes/filesystem/testdata/dir1/file1
diff --git a/src/traced/probes/filesystem/testdata/file2 b/src/traced/probes/filesystem/testdata/file2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/traced/probes/filesystem/testdata/file2
diff --git a/tools/test_data.txt b/tools/test_data.txt
index c0692b0..36c9cdb 100644
--- a/tools/test_data.txt
+++ b/tools/test_data.txt
@@ -1,3 +1,4 @@
 # List of test deps that should be pushed on the device. Paths are relative
 # to the root.
 src/ftrace_reader/test/data/
+src/traced/probes/filesystem/testdata/