| // Copyright 2018 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 <lib/fit/single_threaded_executor.h> |
| #include <lib/inspect/cpp/hierarchy.h> |
| #include <lib/inspect/cpp/inspect.h> |
| #include <lib/inspect/cpp/reader.h> |
| |
| #include <condition_variable> |
| #include <mutex> |
| #include <thread> |
| #include <type_traits> |
| |
| #include <zxtest/zxtest.h> |
| |
| using inspect::Hierarchy; |
| using inspect::Inspector; |
| using inspect::MissingValueReason; |
| using inspect::Node; |
| using inspect::Snapshot; |
| using inspect::internal::Block; |
| using inspect::internal::BlockType; |
| using inspect::internal::ExtentBlockFields; |
| using inspect::internal::GetState; |
| using inspect::internal::HeaderBlockFields; |
| using inspect::internal::kMagicNumber; |
| using inspect::internal::kMinOrderSize; |
| using inspect::internal::LinkBlockDisposition; |
| using inspect::internal::NameBlockFields; |
| using inspect::internal::PropertyBlockPayload; |
| using inspect::internal::State; |
| using inspect::internal::ValueBlockFields; |
| |
| namespace { |
| |
| TEST(Reader, GetByPath) { |
| Inspector inspector; |
| ASSERT_TRUE(bool(inspector)); |
| auto child = inspector.GetRoot().CreateChild("test"); |
| auto child2 = child.CreateChild("test2"); |
| |
| auto result = inspect::ReadFromVmo(inspector.DuplicateVmo()); |
| ASSERT_TRUE(result.is_ok()); |
| auto hierarchy = result.take_value(); |
| |
| EXPECT_NOT_NULL(hierarchy.GetByPath({"test"})); |
| EXPECT_NOT_NULL(hierarchy.GetByPath({"test", "test2"})); |
| EXPECT_NULL(hierarchy.GetByPath({"test", "test2", "test3"})); |
| } |
| |
| TEST(Reader, VisitHierarchy) { |
| Inspector inspector; |
| ASSERT_TRUE(bool(inspector)); |
| |
| // root: |
| // test: |
| // test2 |
| // test3 |
| auto child = inspector.GetRoot().CreateChild("test"); |
| auto child2 = child.CreateChild("test2"); |
| auto child3 = inspector.GetRoot().CreateChild("test3"); |
| |
| auto result = inspect::ReadFromVmo(inspector.DuplicateVmo()); |
| ASSERT_TRUE(result.is_ok()); |
| auto hierarchy = result.take_value(); |
| hierarchy.Sort(); |
| |
| std::vector<std::vector<std::string>> paths; |
| hierarchy.Visit([&](const std::vector<std::string>& path, Hierarchy* current) { |
| paths.push_back(path); |
| EXPECT_NE(nullptr, current); |
| return true; |
| }); |
| |
| std::vector<std::vector<std::string>> expected; |
| expected.emplace_back(std::vector<std::string>{"root"}); |
| expected.emplace_back(std::vector<std::string>{"root", "test"}); |
| expected.emplace_back(std::vector<std::string>{"root", "test", "test2"}); |
| expected.emplace_back(std::vector<std::string>{"root", "test3"}); |
| EXPECT_EQ(expected, paths); |
| |
| paths.clear(); |
| hierarchy.Visit([&](const std::vector<std::string>& path, Hierarchy* current) { |
| paths.push_back(path); |
| EXPECT_NE(nullptr, current); |
| return false; |
| }); |
| EXPECT_EQ(1u, paths.size()); |
| } |
| |
| TEST(Reader, VisitHierarchyWithTombstones) { |
| Inspector inspector; |
| ASSERT_TRUE(bool(inspector)); |
| |
| // root: |
| // test: |
| // test2 |
| auto child = inspector.GetRoot().CreateChild("test"); |
| auto child2 = child.CreateChild("test2"); |
| auto child3 = child2.CreateChild("test3"); |
| auto _prop = child2.CreateString("val", "test"); |
| // Delete node |
| child2 = inspect::Node(); |
| |
| auto result = inspect::ReadFromVmo(inspector.DuplicateVmo()); |
| ASSERT_TRUE(result.is_ok()); |
| auto hierarchy = result.take_value(); |
| hierarchy.Sort(); |
| |
| std::vector<std::vector<std::string>> paths; |
| hierarchy.Visit([&](const std::vector<std::string>& path, Hierarchy* current) { |
| paths.push_back(path); |
| EXPECT_NE(nullptr, current); |
| return true; |
| }); |
| |
| std::vector<std::vector<std::string>> expected; |
| expected.emplace_back(std::vector<std::string>{"root"}); |
| expected.emplace_back(std::vector<std::string>{"root", "test"}); |
| EXPECT_EQ(expected, paths); |
| } |
| |
| TEST(Reader, BucketComparison) { |
| inspect::UintArrayValue::HistogramBucket a(0, 2, 6); |
| inspect::UintArrayValue::HistogramBucket b(0, 2, 6); |
| inspect::UintArrayValue::HistogramBucket c(1, 2, 6); |
| inspect::UintArrayValue::HistogramBucket d(0, 3, 6); |
| inspect::UintArrayValue::HistogramBucket e(0, 2, 7); |
| |
| EXPECT_TRUE(a == b); |
| EXPECT_TRUE(a != c); |
| EXPECT_TRUE(b != c); |
| EXPECT_TRUE(a != d); |
| EXPECT_TRUE(a != e); |
| } |
| |
| TEST(Reader, InvalidNameParsing) { |
| std::vector<uint8_t> buf; |
| buf.resize(4096); |
| |
| Block* header = reinterpret_cast<Block*>(buf.data()); |
| header->header = HeaderBlockFields::Order::Make(0) | |
| HeaderBlockFields::Type::Make(BlockType::kHeader) | |
| HeaderBlockFields::Version::Make(0); |
| memcpy(&header->header_data[4], kMagicNumber, 4); |
| header->payload.u64 = 0; |
| |
| // Manually create a value with an invalid name field. |
| Block* value = reinterpret_cast<Block*>(buf.data() + kMinOrderSize); |
| value->header = ValueBlockFields::Order::Make(0) | |
| ValueBlockFields::Type::Make(BlockType::kNodeValue) | |
| ValueBlockFields::NameIndex::Make(2000); |
| |
| auto result = inspect::ReadFromBuffer(std::move(buf)); |
| EXPECT_TRUE(result.is_ok()); |
| } |
| |
| TEST(Reader, LargeExtentsWithCycle) { |
| std::vector<uint8_t> buf; |
| buf.resize(4096); |
| |
| Block* header = reinterpret_cast<Block*>(buf.data()); |
| header->header = HeaderBlockFields::Order::Make(0) | |
| HeaderBlockFields::Type::Make(BlockType::kHeader) | |
| HeaderBlockFields::Version::Make(0); |
| memcpy(&header->header_data[4], kMagicNumber, 4); |
| header->payload.u64 = 0; |
| |
| // Manually create a property. |
| Block* value = reinterpret_cast<Block*>(buf.data() + kMinOrderSize); |
| value->header = ValueBlockFields::Order::Make(0) | |
| ValueBlockFields::Type::Make(BlockType::kBufferValue) | |
| ValueBlockFields::NameIndex::Make(2); |
| value->payload.u64 = PropertyBlockPayload::TotalLength::Make(0xFFFFFFFF) | |
| PropertyBlockPayload::ExtentIndex::Make(3); |
| |
| Block* name = reinterpret_cast<Block*>(buf.data() + kMinOrderSize * 2); |
| name->header = NameBlockFields::Order::Make(0) | NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(1); |
| memcpy(name->payload.data, "a", 2); |
| |
| Block* extent = reinterpret_cast<Block*>(buf.data() + kMinOrderSize * 3); |
| extent->header = ExtentBlockFields::Order::Make(0) | |
| ExtentBlockFields::Type::Make(BlockType::kExtent) | |
| ExtentBlockFields::NextExtentIndex::Make(3); |
| |
| auto result = inspect::ReadFromBuffer(std::move(buf)); |
| EXPECT_TRUE(result.is_ok()); |
| EXPECT_EQ(1u, result.value().node().properties().size()); |
| } |
| |
| TEST(Reader, NameDoesNotFit) { |
| std::vector<uint8_t> buf; |
| buf.resize(4096); |
| |
| Block* header = reinterpret_cast<Block*>(buf.data()); |
| header->header = HeaderBlockFields::Order::Make(0) | |
| HeaderBlockFields::Type::Make(BlockType::kHeader) | |
| HeaderBlockFields::Version::Make(0); |
| memcpy(&header->header_data[4], kMagicNumber, 4); |
| header->payload.u64 = 0; |
| |
| // Manually create a node. |
| Block* value = reinterpret_cast<Block*>(buf.data() + kMinOrderSize); |
| value->header = ValueBlockFields::Order::Make(0) | |
| ValueBlockFields::Type::Make(BlockType::kNodeValue) | |
| ValueBlockFields::NameIndex::Make(2); |
| |
| Block* name = reinterpret_cast<Block*>(buf.data() + kMinOrderSize * 2); |
| name->header = NameBlockFields::Order::Make(0) | NameBlockFields::Type::Make(BlockType::kName) | |
| NameBlockFields::Length::Make(10); |
| memcpy(name->payload.data, "a", 2); |
| |
| auto result = inspect::ReadFromBuffer(std::move(buf)); |
| EXPECT_TRUE(result.is_ok()); |
| EXPECT_EQ(0u, result.value().children().size()); |
| } |
| |
| fit::result<Hierarchy> ReadHierarchyFromInspector(const Inspector& inspector) { |
| fit::result<Hierarchy> result; |
| fit::single_threaded_executor exec; |
| exec.schedule_task(inspect::ReadFromInspector(inspector).then( |
| [&](fit::result<Hierarchy>& res) { result = std::move(res); })); |
| exec.run(); |
| |
| return result; |
| } |
| |
| TEST(Reader, MissingNamedChild) { |
| Inspector inspector; |
| auto state = GetState(&inspector); |
| |
| auto link = |
| state->CreateLink("link", 0, "link-0", inspect::internal::LinkBlockDisposition::kChild); |
| |
| auto result = ReadHierarchyFromInspector(inspector); |
| |
| ASSERT_TRUE(result.is_ok()); |
| auto hierarchy = result.take_value(); |
| ASSERT_EQ(1, hierarchy.missing_values().size()); |
| EXPECT_EQ(MissingValueReason::kLinkNotFound, hierarchy.missing_values()[0].reason); |
| EXPECT_EQ("link", hierarchy.missing_values()[0].name); |
| } |
| |
| TEST(Reader, LinkedChildren) { |
| Inspector inspector; |
| auto state = GetState(&inspector); |
| |
| auto link0 = state->CreateLazyNode("link", 0, []() { |
| inspect::Inspector inspect; |
| inspect.GetRoot().CreateInt("val", 1, &inspect); |
| return fit::make_ok_promise(inspect); |
| }); |
| |
| auto link1 = state->CreateLazyNode("other", 0, []() { |
| inspect::Inspector inspect; |
| inspect.GetRoot().CreateInt("val", 2, &inspect); |
| return fit::make_ok_promise(inspect); |
| }); |
| |
| auto result = ReadHierarchyFromInspector(inspector); |
| |
| ASSERT_TRUE(result.is_ok()); |
| auto hierarchy = result.take_value(); |
| ASSERT_EQ(2, hierarchy.children().size()); |
| bool found_link = false, found_other = false; |
| for (const auto& c : hierarchy.children()) { |
| if (c.node().name() == "link") { |
| ASSERT_EQ(1, c.node().properties().size()); |
| found_link = true; |
| EXPECT_EQ("val", c.node().properties()[0].name()); |
| EXPECT_EQ(1, c.node().properties()[0].Get<inspect::IntPropertyValue>().value()); |
| } else if (c.node().name() == "other") { |
| ASSERT_EQ(1, c.node().properties().size()); |
| found_other = true; |
| EXPECT_EQ("val", c.node().properties()[0].name()); |
| EXPECT_EQ(2, c.node().properties()[0].Get<inspect::IntPropertyValue>().value()); |
| } |
| } |
| |
| EXPECT_TRUE(found_link); |
| EXPECT_TRUE(found_other); |
| } |
| |
| TEST(Reader, LinkedInline) { |
| Inspector inspector; |
| auto state = GetState(&inspector); |
| |
| auto link = state->CreateLazyValues("link", 0, []() { |
| inspect::Inspector inspector; |
| inspector.GetRoot().CreateChild("child", &inspector); |
| inspector.GetRoot().CreateInt("a", 10, &inspector); |
| return fit::make_ok_promise(inspector); |
| }); |
| |
| auto result = ReadHierarchyFromInspector(inspector); |
| ASSERT_TRUE(result.is_ok()); |
| auto hierarchy = result.take_value(); |
| |
| ASSERT_EQ(1, hierarchy.children().size()); |
| EXPECT_EQ("child", hierarchy.children()[0].node().name()); |
| ASSERT_EQ(1, hierarchy.node().properties().size()); |
| EXPECT_EQ("a", hierarchy.node().properties()[0].name()); |
| EXPECT_EQ(10, hierarchy.node().properties()[0].Get<inspect::IntPropertyValue>().value()); |
| } |
| |
| TEST(Reader, LinkedInlineChain) { |
| Inspector inspector; |
| auto state = GetState(&inspector); |
| |
| auto link = state->CreateLazyValues("link", 0, []() { |
| inspect::Inspector inspector; |
| inspector.GetRoot().CreateInt("a", 10, &inspector); |
| inspector.GetRoot().CreateLazyValues( |
| "link", |
| []() { |
| inspect::Inspector inspector; |
| inspector.GetRoot().CreateInt("b", 11, &inspector); |
| inspector.GetRoot().CreateLazyValues( |
| "link", |
| []() { |
| inspect::Inspector inspector; |
| inspector.GetRoot().CreateInt("c", 12, &inspector); |
| return fit::make_ok_promise(inspector); |
| }, |
| &inspector); |
| return fit::make_ok_promise(inspector); |
| }, |
| &inspector); |
| return fit::make_ok_promise(inspector); |
| }); |
| |
| auto result = ReadHierarchyFromInspector(inspector); |
| ASSERT_TRUE(result.is_ok()); |
| auto hierarchy = result.take_value(); |
| hierarchy.Sort(); |
| |
| ASSERT_EQ(0, hierarchy.children().size()); |
| ASSERT_EQ(3, hierarchy.node().properties().size()); |
| EXPECT_EQ("a", hierarchy.node().properties()[0].name()); |
| EXPECT_EQ("b", hierarchy.node().properties()[1].name()); |
| EXPECT_EQ("c", hierarchy.node().properties()[2].name()); |
| EXPECT_EQ(10, hierarchy.node().properties()[0].Get<inspect::IntPropertyValue>().value()); |
| EXPECT_EQ(11, hierarchy.node().properties()[1].Get<inspect::IntPropertyValue>().value()); |
| EXPECT_EQ(12, hierarchy.node().properties()[2].Get<inspect::IntPropertyValue>().value()); |
| } |
| |
| } // namespace |