| // Copyright 2019 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/lib/inspect_deprecated/query/source.h" |
| |
| #include <lib/fdio/directory.h> |
| #include <lib/vfs/cpp/vmo_file.h> |
| |
| #include <src/lib/files/file.h> |
| |
| #include "fixture.h" |
| #include "fuchsia/io/cpp/fidl.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "lib/fidl/cpp/binding.h" |
| #include "lib/fidl/cpp/binding_set.h" |
| #include "lib/fidl/cpp/interface_request.h" |
| #include "src/lib/fxl/strings/concatenate.h" |
| #include "src/lib/fxl/strings/join_strings.h" |
| #include "src/lib/fxl/strings/substitute.h" |
| #include "src/lib/inspect_deprecated/hierarchy.h" |
| #include "src/lib/inspect_deprecated/inspect.h" |
| #include "src/lib/inspect_deprecated/query/location.h" |
| #include "src/lib/inspect_deprecated/reader.h" |
| #include "src/lib/inspect_deprecated/testing/inspect.h" |
| |
| using namespace inspect_deprecated::testing; |
| |
| namespace { |
| |
| class TestDataWrapper { |
| public: |
| explicit TestDataWrapper(inspect_deprecated::Node object) : object_(std::move(object)) { |
| version_ = object_.CreateStringProperty("version", "1.0"); |
| child_test_ = object_.CreateChild("test"); |
| count_ = child_test_.CreateIntMetric("count", 2); |
| nested_child_ = child_test_.CreateChild("nested"); |
| } |
| |
| private: |
| inspect_deprecated::Node object_; |
| inspect_deprecated::Node child_test_; |
| inspect_deprecated::Node nested_child_; |
| inspect_deprecated::StringProperty version_; |
| inspect_deprecated::IntMetric count_; |
| }; |
| |
| void CheckHierarchyMatches(const inspect_deprecated::ObjectHierarchy& hierarchy) { |
| EXPECT_THAT(hierarchy, |
| ::testing::AllOf( |
| NodeMatches(::testing::AllOf( |
| NameMatches("root"), |
| PropertyList(::testing::ElementsAre(StringPropertyIs("version", "1.0"))))), |
| ChildrenMatch(::testing::ElementsAre(::testing::AllOf( |
| ChildrenMatch(::testing::ElementsAre(NodeMatches(NameMatches("nested")))), |
| NodeMatches(::testing::AllOf( |
| NameMatches("test"), |
| MetricList(::testing::ElementsAre(IntMetricIs("count", 2)))))))))); |
| } |
| |
| class SourceTestFidl : public TestFixture { |
| public: |
| SourceTestFidl() |
| : fidl_dir_(component::ObjectDir::Make("root")), |
| test_data_(inspect_deprecated::Node(fidl_dir_)), |
| binding_(fidl_dir_.object().get()) { |
| binding_.Bind(ptr_.NewRequest().TakeChannel()); |
| } |
| |
| fit::result<inspect_deprecated::Source, std::string> MakeFromPath(std::string path, |
| int depth = -1) { |
| fit::result<inspect_deprecated::Source, std::string> result; |
| SchedulePromise(inspect_deprecated::Source::MakeFromFidl( |
| inspect_deprecated::Location::Parse(path).take_value(), |
| inspect_deprecated::ObjectReader(std::move(ptr_)), depth) |
| .then([&result](fit::result<inspect_deprecated::Source, std::string>& res) { |
| result = std::move(res); |
| })); |
| |
| RunLoopUntil([&result] { return !!result; }); |
| |
| return result; |
| } |
| |
| const std::string RootPath = "/test"; |
| |
| protected: |
| component::ObjectDir fidl_dir_; |
| TestDataWrapper test_data_; |
| fidl::Binding<fuchsia::inspect::deprecated::Inspect> binding_; |
| fuchsia::inspect::deprecated::InspectPtr ptr_; |
| }; |
| |
| class SourceTestVmo : public TestFixture { |
| public: |
| SourceTestVmo() |
| : inspector_(), |
| tree_(inspector_.CreateTree()), |
| vmo_file_(tree_.DuplicateVmo(), 0, 4096), |
| test_data_(std::move(tree_.GetRoot())) { |
| ZX_ASSERT(vmo_file_.Serve(fuchsia::io::OPEN_RIGHT_READABLE, |
| file_ptr_.NewRequest().TakeChannel()) == ZX_OK); |
| } |
| |
| fit::result<inspect_deprecated::Source, std::string> MakeFromPath(std::string path, |
| int depth = -1) { |
| fit::result<inspect_deprecated::Source, std::string> result; |
| SchedulePromise( |
| inspect_deprecated::Source::MakeFromVmo( |
| inspect_deprecated::Location::Parse(path).take_value(), std::move(file_ptr_), depth) |
| .then([&result](fit::result<inspect_deprecated::Source, std::string>& res) { |
| result = std::move(res); |
| })); |
| |
| RunLoopUntil([&result] { return !!result; }); |
| |
| return result; |
| } |
| |
| const std::string RootPath = "/test/root.inspect"; |
| |
| protected: |
| inspect_deprecated::Inspector inspector_; |
| inspect_deprecated::Tree tree_; |
| vfs::VmoFile vmo_file_; |
| TestDataWrapper test_data_; |
| fuchsia::io::FilePtr file_ptr_; |
| }; |
| |
| class SourceTestFile : public SourceTestVmo { |
| public: |
| fit::result<inspect_deprecated::Source, std::string> MakeFromPath(std::string path, |
| int depth = -1) { |
| fit::result<std::string, std::string> path_or = WriteFromVmo(path); |
| if (path_or.is_error()) { |
| return fit::error(path_or.error()); |
| } |
| |
| fuchsia::io::FilePtr file_backed_ptr; |
| zx_status_t status = fdio_open(path_or.value().c_str(), fuchsia::io::OPEN_RIGHT_READABLE, |
| file_backed_ptr.NewRequest().TakeChannel().release()); |
| ZX_ASSERT(status == ZX_OK && file_backed_ptr.is_bound()); |
| |
| fit::result<inspect_deprecated::Source, std::string> result; |
| SchedulePromise(inspect_deprecated::Source::MakeFromVmo( |
| inspect_deprecated::Location::Parse(path).take_value(), |
| std::move(file_backed_ptr), depth) |
| .then([&result](fit::result<inspect_deprecated::Source, std::string>& res) { |
| result = std::move(res); |
| })); |
| |
| RunLoopUntil([&result] { return !!result; }); |
| |
| return result; |
| } |
| |
| protected: |
| // Writes the contents of the VMO backed by test data into a file at |path|. |
| // Returns the resulting file name, or a string error. |
| fit::result<std::string, std::string> WriteFromVmo(const std::string& path) { |
| zx::vmo vmo = tree_.DuplicateVmo(); |
| uint64_t vmo_size; |
| zx_status_t status = vmo.get_size(&vmo_size); |
| if (status != ZX_OK) { |
| return fit::error("could not get VMO size"); |
| } |
| std::vector<uint8_t> buf(vmo_size); |
| status = vmo.read(buf.data(), 0, vmo_size); |
| if (status != ZX_OK) { |
| return fit::error("could not read from VMO"); |
| } |
| const char* f = reinterpret_cast<const char*>(buf.data()); // Known safe. |
| if (!files::WriteFile(path, f, buf.size())) { |
| return fit::error(fxl::Substitute("Could not write: $0", path)); |
| } |
| return fit::ok(path); |
| } |
| |
| const std::string RootPath = "/tmp/file.inspect"; |
| }; |
| |
| template <typename T> |
| class SourceTest : public T {}; |
| |
| using SourceTestTypes = ::testing::Types<SourceTestFidl, SourceTestVmo, SourceTestFile>; |
| TYPED_TEST_SUITE(SourceTest, SourceTestTypes); |
| |
| TYPED_TEST(SourceTest, MakeDefault) { |
| // TODO(FLK-186): Reenable this test. |
| GTEST_SKIP(); |
| auto result = this->MakeFromPath(this->RootPath); |
| ASSERT_TRUE(result.is_ok()); |
| auto source = result.take_value(); |
| CheckHierarchyMatches(source.GetHierarchy()); |
| } |
| |
| TYPED_TEST(SourceTest, MakeDepth0) { |
| // TODO(FLK-186): Reenable this test. |
| GTEST_SKIP(); |
| auto result = this->MakeFromPath(this->RootPath, 0); |
| ASSERT_TRUE(result.is_ok()); |
| |
| EXPECT_THAT(result.take_value().GetHierarchy(), |
| ::testing::AllOf(NodeMatches(PropertyList(::testing::SizeIs(1))), |
| ChildrenMatch(::testing::SizeIs(0)))); |
| } |
| |
| TYPED_TEST(SourceTest, MakeDepth1) { |
| // TODO(FLK-186): Reenable this test. |
| GTEST_SKIP(); |
| auto result = this->MakeFromPath(this->RootPath, 1); |
| ASSERT_TRUE(result.is_ok()); |
| |
| EXPECT_THAT(result.take_value().GetHierarchy(), |
| ::testing::AllOf(ChildrenMatch(::testing::ElementsAre(::testing::AllOf( |
| NodeMatches(NameMatches("test")), ::ChildrenMatch(::testing::SizeIs(0))))))); |
| } |
| |
| TYPED_TEST(SourceTest, MakeWithPath) { |
| // TODO(FLK-186): Reenable this test. |
| GTEST_SKIP(); |
| auto result = this->MakeFromPath(fxl::Concatenate({this->RootPath, "#test"})); |
| ASSERT_TRUE(result.is_ok()); |
| |
| EXPECT_THAT( |
| result.take_value().GetHierarchy(), |
| ::testing::AllOf(NodeMatches(MetricList(::testing::ElementsAre(IntMetricIs("count", 2)))), |
| ChildrenMatch(::testing::SizeIs(1)))); |
| } |
| |
| // These tests only apply to the test prototypes in SourceTestErrorTypes. |
| template <typename T> |
| class SourceTestError : public T {}; |
| |
| using SourceTestErrorTypes = ::testing::Types<SourceTestFidl, SourceTestVmo>; |
| TYPED_TEST_SUITE(SourceTestError, SourceTestErrorTypes); |
| |
| TYPED_TEST(SourceTestError, MakeError) { |
| // TODO(FLK-186): Reenable this test. |
| GTEST_SKIP(); |
| auto result = this->MakeFromPath(this->RootPath); |
| ASSERT_TRUE(result.is_ok()); |
| result = this->MakeFromPath(this->RootPath); // reusing the connection should fail. |
| ASSERT_TRUE(result.is_error()); |
| } |
| |
| inspect_deprecated::ObjectHierarchy MakeNode(std::string name) { |
| return inspect_deprecated::ObjectHierarchy(inspect_deprecated::hierarchy::Node(std::move(name)), |
| {}); |
| } |
| |
| TEST(Source, VisitObjectsInHierarchy) { |
| inspect_deprecated::ObjectHierarchy root = MakeNode("root"); |
| { |
| auto child = MakeNode("child"); |
| child.children().emplace_back(MakeNode("nested")); |
| root.children().emplace_back(std::move(child)); |
| } |
| root.children().emplace_back(MakeNode("a_child")); |
| |
| auto source = inspect_deprecated::Source({}, std::move(root)); |
| |
| std::vector<std::string> paths_visited; |
| source.VisitObjectsInHierarchy([&](const std::vector<std::string>& path, |
| const inspect_deprecated::ObjectHierarchy& hierarchy) { |
| paths_visited.push_back(fxl::JoinStrings(path, "/")); |
| }); |
| |
| EXPECT_THAT(paths_visited, ::testing::ElementsAre("", "child", "child/nested", "a_child")); |
| |
| paths_visited.clear(); |
| source.SortHierarchy(); |
| source.VisitObjectsInHierarchy([&](const std::vector<std::string>& path, |
| const inspect_deprecated::ObjectHierarchy& hierarchy) { |
| paths_visited.push_back(fxl::JoinStrings(path, "/")); |
| }); |
| |
| EXPECT_THAT(paths_visited, ::testing::ElementsAre("", "a_child", "child", "child/nested")); |
| } |
| |
| } // namespace |