| // 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 "src/developer/debug/zxdb/expr/index_walker.h" | |
| #include <gtest/gtest.h> | |
| #include "src/developer/debug/zxdb/expr/expr_parser.h" | |
| #include "src/developer/debug/zxdb/symbols/index.h" | |
| namespace zxdb { | |
| namespace { | |
| bool EqualsElements(const IndexWalker::Stage& a, const IndexWalker::Stage& b) { | |
| if (a.size() != b.size()) | |
| return false; | |
| for (const auto& cur : a) { | |
| if (std::find(b.begin(), b.end(), cur) == b.end()) | |
| return false; | |
| } | |
| return true; | |
| } | |
| } // namespace | |
| TEST(IndexWalker, ComponentMatchesNameOnly) { | |
| ParsedIdentifierComponent foo_comp("Foo"); | |
| ParsedIdentifierComponent foo_template_comp("Foo", {"A", "b"}); | |
| // Simple name-only comparisons. | |
| EXPECT_TRUE(IndexWalker::ComponentMatchesNameOnly("Foo", foo_comp)); | |
| EXPECT_FALSE(IndexWalker::ComponentMatchesNameOnly("FooBar", foo_comp)); | |
| EXPECT_FALSE(IndexWalker::ComponentMatchesNameOnly("Fo2", foo_comp)); | |
| // Component has a template, the index string doesn't. | |
| EXPECT_TRUE(IndexWalker::ComponentMatchesNameOnly("Foo", foo_template_comp)); | |
| // Component has no template, the index does (this input is non-canonical). | |
| EXPECT_TRUE(IndexWalker::ComponentMatchesNameOnly("Foo < C >", foo_template_comp)); | |
| } | |
| TEST(IndexWalker, ComponentMatchesTemplateOnly) { | |
| ParsedIdentifierComponent foo_comp("Foo"); | |
| ParsedIdentifierComponent foo_template_comp("Foo", {"A", "b"}); | |
| ParsedIdentifierComponent foo_empty_template_comp("Foo", {}); | |
| // Neither inputs have templates (should be a match). | |
| EXPECT_TRUE(IndexWalker::ComponentMatchesTemplateOnly("Foo", foo_comp)); | |
| // Template match but with different whitespace. | |
| EXPECT_TRUE(IndexWalker::ComponentMatchesTemplateOnly("Foo < A, b > ", foo_template_comp)); | |
| // One has a template but the other doesn't. | |
| EXPECT_FALSE(IndexWalker::ComponentMatchesTemplateOnly("Foo", foo_template_comp)); | |
| EXPECT_FALSE(IndexWalker::ComponentMatchesTemplateOnly("Foo<C>", foo_comp)); | |
| // Empty template doesn't match no template. | |
| EXPECT_FALSE(IndexWalker::ComponentMatchesTemplateOnly("Foo<>", foo_comp)); | |
| EXPECT_FALSE(IndexWalker::ComponentMatchesTemplateOnly("Foo", foo_empty_template_comp)); | |
| } | |
| // Most cases are tested by ComponentMatchesNameOnly and ...TemplateOnly above. | |
| TEST(IndexWalker, ComponentMatches) { | |
| ParsedIdentifierComponent foo_comp("Foo"); | |
| ParsedIdentifierComponent foo_template_comp("Foo", {"A", "b"}); | |
| EXPECT_TRUE(IndexWalker::ComponentMatches("Foo", foo_comp)); | |
| EXPECT_FALSE(IndexWalker::ComponentMatches("Foo<>", foo_comp)); | |
| EXPECT_FALSE(IndexWalker::ComponentMatches("Foo<>", foo_template_comp)); | |
| EXPECT_TRUE(IndexWalker::ComponentMatches("Foo <A,b >", foo_template_comp)); | |
| } | |
| TEST(IndexWalker, IsIndexStringBeyondName) { | |
| // Identity comparison. | |
| EXPECT_FALSE(IndexWalker::IsIndexStringBeyondName("Foo", "Foo")); | |
| // Index nodes clearly before. | |
| EXPECT_FALSE(IndexWalker::IsIndexStringBeyondName("Fo", "Foo")); | |
| EXPECT_FALSE(IndexWalker::IsIndexStringBeyondName("Foa", "Foo")); | |
| // Index nodes clearly after. | |
| EXPECT_TRUE(IndexWalker::IsIndexStringBeyondName("FooBar", "Foo")); | |
| EXPECT_TRUE(IndexWalker::IsIndexStringBeyondName("Foz", "Foo")); | |
| EXPECT_TRUE(IndexWalker::IsIndexStringBeyondName("Fz", "Foo")); | |
| // Templates in the index could has "not beyond". | |
| EXPECT_FALSE(IndexWalker::IsIndexStringBeyondName("Foo<a>", "Foo")); | |
| } | |
| TEST(IndexWalker, WalkInto) { | |
| Index index; | |
| auto& root = index.root(); | |
| auto foo_node = root.AddChild(IndexNode::Kind::kType, "Foo"); | |
| root.AddChild(IndexNode::Kind::kType, "Foo<Bar>"); | |
| // These template names are non-canonical so we can verify the correct comparisons happen. | |
| foo_node->AddChild(IndexNode::Kind::kType, "Bar< int >"); | |
| auto bar_int_char_node = foo_node->AddChild(IndexNode::Kind::kType, "Bar< int,char >"); | |
| // There could also be a non-template somewhere with the same name. | |
| auto bar_node = foo_node->AddChild(IndexNode::Kind::kType, "Bar"); | |
| // These nodes start with the prefix "Bar" for when we're searching. We test things that will | |
| // compare before and after "Bar<" ('9' before, 'f' after). | |
| auto barf_node = foo_node->AddChild(IndexNode::Kind::kType, "Barf<int>"); | |
| auto bar9_node = foo_node->AddChild(IndexNode::Kind::kType, "Bar9<int>"); | |
| IndexWalker walker(&index); | |
| EXPECT_TRUE(EqualsElements(walker.current(), {&root})); | |
| // Walking up at this point should be a no-op. | |
| EXPECT_FALSE(walker.WalkUp()); | |
| EXPECT_TRUE(EqualsElements(walker.current(), {&root})); | |
| // Walk to the "Foo" component. | |
| EXPECT_TRUE(walker.WalkInto(ParsedIdentifierComponent("Foo"))); | |
| EXPECT_TRUE(EqualsElements(walker.current(), {foo_node})); | |
| // Walk to the "NotPresent" component. The current location should be unchanged. | |
| EXPECT_FALSE(walker.WalkInto(ParsedIdentifierComponent("NotFound"))); | |
| EXPECT_TRUE(EqualsElements(walker.current(), {foo_node})); | |
| // Walk to the "Bar<int,char>" identifier. | |
| ParsedIdentifier bar_int_char; | |
| Err err = ExprParser::ParseIdentifier("Bar < int , char >", &bar_int_char); | |
| EXPECT_FALSE(err.has_error()) << err.msg(); | |
| EXPECT_TRUE(walker.WalkInto(bar_int_char)); | |
| EXPECT_TRUE(EqualsElements(walker.current(), {bar_int_char_node})); | |
| // Walk back up to "Foo". | |
| EXPECT_TRUE(walker.WalkUp()); | |
| EXPECT_TRUE(EqualsElements(walker.current(), {foo_node})); | |
| // Walk to the "Bar" node. | |
| EXPECT_TRUE(walker.WalkInto(ParsedIdentifierComponent("Bar"))); | |
| EXPECT_TRUE(EqualsElements(walker.current(), {bar_node})); | |
| // Parse the Barf identifier for the following two tests. This one has a toplevel scope. | |
| ParsedIdentifier barf; | |
| err = ExprParser::ParseIdentifier("::Foo::Barf<int>", &barf); | |
| EXPECT_FALSE(err.has_error()) << err.msg(); | |
| // Walk to the "Foo::Bar9<int>" with copying the walker. | |
| { | |
| IndexWalker nested_walker(walker); | |
| ParsedIdentifier bar9; | |
| err = ExprParser::ParseIdentifier(":: Foo :: Bar9 < int >", &bar9); | |
| EXPECT_FALSE(err.has_error()) << err.msg(); | |
| EXPECT_TRUE(nested_walker.WalkInto(bar9)); | |
| EXPECT_TRUE(EqualsElements(nested_walker.current(), {bar9_node})); | |
| } | |
| // Walking from the root into the barf template should work. | |
| EXPECT_TRUE(walker.WalkInto(barf)); | |
| EXPECT_TRUE(EqualsElements(walker.current(), {barf_node})); | |
| } | |
| // Tests that we can walk into multiple nodes of different types (namespaces, functions, etc.) at | |
| // the same time when they have the same name. | |
| TEST(IndexWalker, WalkIntoMultiple) { | |
| Index index; | |
| auto& root = index.root(); | |
| const char kFoo[] = "Foo"; | |
| auto foo_type_node = root.AddChild(IndexNode::Kind::kType, kFoo); | |
| auto foo_ns_node = root.AddChild(IndexNode::Kind::kNamespace, kFoo); | |
| auto foo_func_node = root.AddChild(IndexNode::Kind::kFunction, kFoo); | |
| auto foo_var_node = root.AddChild(IndexNode::Kind::kVar, kFoo); | |
| const char kBar[] = "Bar"; | |
| auto foo_bar_type_func = foo_type_node->AddChild(IndexNode::Kind::kFunction, kBar); | |
| auto foo_bar_ns_func = foo_ns_node->AddChild(IndexNode::Kind::kFunction, kBar); | |
| IndexWalker walker(&index); | |
| IndexWalker::Stage expected_root{&root}; | |
| EXPECT_TRUE(EqualsElements(walker.current(), expected_root)); | |
| // Walking into "Foo" should identify all 4 categories of thing. | |
| EXPECT_TRUE(walker.WalkInto(ParsedIdentifierComponent(kFoo))); | |
| IndexWalker::Stage expected_foo{foo_type_node, foo_ns_node, foo_func_node, foo_var_node}; | |
| EXPECT_TRUE(EqualsElements(walker.current(), expected_foo)); | |
| // Walking into "Bar" from there should narrow down to two. | |
| EXPECT_TRUE(walker.WalkInto(ParsedIdentifierComponent(kBar))); | |
| IndexWalker::Stage expected_foo_bar{foo_bar_type_func, foo_bar_ns_func}; | |
| EXPECT_TRUE(EqualsElements(walker.current(), expected_foo_bar)); | |
| // Walking into something that doesn't exist reports failure and stays in the same place. | |
| EXPECT_FALSE(walker.WalkInto(ParsedIdentifierComponent("Nonexistant"))); | |
| EXPECT_TRUE(EqualsElements(walker.current(), expected_foo_bar)); | |
| // Walk up should give the same results. | |
| EXPECT_TRUE(walker.WalkUp()); | |
| EXPECT_TRUE(EqualsElements(walker.current(), expected_foo)); | |
| EXPECT_TRUE(walker.WalkUp()); | |
| EXPECT_TRUE(EqualsElements(walker.current(), expected_root)); | |
| // Going up above the root fails and does nothing. | |
| EXPECT_FALSE(walker.WalkUp()); | |
| EXPECT_TRUE(EqualsElements(walker.current(), expected_root)); | |
| } | |
| TEST(IndexWalker, WalkIntoAnonNS) { | |
| Index index; | |
| auto& root = index.root(); | |
| // Anonymous and "ns" namespace at the top level. | |
| auto anon_ns = root.AddChild(IndexNode::Kind::kNamespace, ""); | |
| const char kNsName[] = "ns"; | |
| auto ns_ns = root.AddChild(IndexNode::Kind::kNamespace, kNsName); | |
| // Add a "Struct" at the toplevel and in the anon namespace. | |
| const char kStructName[] = "Struct"; | |
| auto toplevel_struct = root.AddChild(IndexNode::Kind::kType, kStructName); | |
| auto anon_struct = anon_ns->AddChild(IndexNode::Kind::kType, kStructName); | |
| // Add a ns::<anon>::<anon>::OtherStruct type. | |
| const char kOtherStructName[] = "OtherStruct"; | |
| auto ns_anon_ns = ns_ns->AddChild(IndexNode::Kind::kNamespace, ""); | |
| auto ns_anon_anon_ns = ns_anon_ns->AddChild(IndexNode::Kind::kNamespace, ""); | |
| auto otherstruct = ns_anon_anon_ns->AddChild(IndexNode::Kind::kType, kOtherStructName); | |
| IndexWalker walker(&index); | |
| // The initial search should be in the root and in the toplevel anonymous namespace. | |
| IndexWalker::Stage expected_root{&root, anon_ns}; | |
| EXPECT_TRUE(EqualsElements(walker.current(), expected_root)); | |
| // Walking into "Struct" should give both since we implicitly went into the anon namespace. | |
| EXPECT_TRUE(walker.WalkInto(ParsedIdentifier(kStructName))); | |
| IndexWalker::Stage expected_struct{toplevel_struct, anon_struct}; | |
| EXPECT_TRUE(EqualsElements(walker.current(), expected_struct)); | |
| // Go back to the root. | |
| EXPECT_TRUE(walker.WalkUp()); | |
| // Walk into "ns::OtherStruct" which should find the one with two anon namespaces in it. | |
| ParsedIdentifier ns_otherstruct; | |
| ASSERT_TRUE(ExprParser::ParseIdentifier("ns::OtherStruct", &ns_otherstruct).ok()); | |
| EXPECT_TRUE(walker.WalkInto(ns_otherstruct)); | |
| IndexWalker::Stage expected_otherstruct{otherstruct}; | |
| EXPECT_TRUE(EqualsElements(walker.current(), expected_otherstruct)); | |
| } | |
| } // namespace zxdb |