blob: a9bffef5169a43315b34137ed2119ec4dc3b1b71 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <lib/devicetree/devicetree.h>
#include <lib/devicetree/matcher.h>
#include <lib/devicetree/testing/loaded-dtb.h>
#include <lib/fit/function.h>
#include <optional>
#include <string>
#include <string_view>
#include <zxtest/zxtest.h>
namespace {
using devicetree::testing::LoadedDtb;
// Invalid matcher detection by traits.
// Doesnt implement any of the Matcher contract, all traits should be false.
struct InvalidMatcher {};
using devicetree::internal::HasMaxScansMember;
using devicetree::internal::HasOnDone_v;
using devicetree::internal::HasOnError_v;
using devicetree::internal::HasOnNode_v;
using devicetree::internal::HasOnScan_v;
using devicetree::internal::OnDoneSignature_v;
using devicetree::internal::OnErrorSignature_v;
using devicetree::internal::OnNodeSignature_v;
using devicetree::internal::OnScanSignature_v;
static_assert(!HasOnError_v<InvalidMatcher>);
static_assert(!OnErrorSignature_v<InvalidMatcher>);
static_assert(!HasOnScan_v<InvalidMatcher>);
static_assert(!OnScanSignature_v<InvalidMatcher>);
static_assert(!HasOnNode_v<InvalidMatcher>);
static_assert(!OnNodeSignature_v<InvalidMatcher>);
static_assert(!HasOnDone_v<InvalidMatcher>);
static_assert(!OnDoneSignature_v<InvalidMatcher>);
static_assert(!HasMaxScansMember<InvalidMatcher>::value);
struct ValidMatcher {
static constexpr size_t kMaxScans = 1;
devicetree::ScanState OnNode(const devicetree::NodePath&,
const devicetree::PropertyDecoder& decoder);
void OnError(std::string_view v);
devicetree::ScanState OnScan();
void OnDone();
};
static_assert(HasOnError_v<ValidMatcher>);
static_assert(OnErrorSignature_v<ValidMatcher>);
static_assert(HasOnScan_v<ValidMatcher>);
static_assert(OnScanSignature_v<ValidMatcher>);
static_assert(HasOnNode_v<ValidMatcher>);
static_assert(OnNodeSignature_v<ValidMatcher>);
static_assert(HasOnDone_v<ValidMatcher>);
static_assert(OnDoneSignature_v<ValidMatcher>);
static_assert(HasMaxScansMember<ValidMatcher>::value);
// Helper matcher for tests.
template <size_t ScanBeforeCompletion>
struct SingleNodeMatcher {
static constexpr size_t kMaxScans = ScanBeforeCompletion;
template <typename T, typename U, typename V>
SingleNodeMatcher(std::string_view path_to_match, T&& node_cb, U&& walk_cb, V&& on_subtree_cb)
: path_to_match(path_to_match), node(node_cb), walk(walk_cb), on_subtree(on_subtree_cb) {}
template <typename T, typename U>
SingleNodeMatcher(std::string_view path_to_match, T&& node_cb, U&& walk_cb)
: SingleNodeMatcher(path_to_match, node_cb, walk_cb, [](const devicetree::NodePath&) {
return devicetree::ScanState::kActive;
}) {}
template <typename T>
SingleNodeMatcher(std::string_view path_to_match, T&& node_cb)
: SingleNodeMatcher(path_to_match, node_cb, []() {}) {}
devicetree::ScanState OnNode(const devicetree::NodePath& path,
const devicetree::PropertyDecoder& decoder) {
visit_count++;
auto resolved_path = decoder.ResolvePath(path_to_match);
if (resolved_path.is_error()) {
return resolved_path.error_value() ==
devicetree::PropertyDecoder::PathResolveError::kNoAliases
? devicetree::ScanState::kNeedsPathResolution
: devicetree::ScanState::kDoneWithSubtree;
}
switch (path.CompareWith(*resolved_path)) {
case devicetree::NodePath::Comparison::kEqual:
found = true;
node(path.back(), decoder);
return node_match_result;
case devicetree::NodePath::Comparison::kParent:
case devicetree::NodePath::Comparison::kIndirectAncestor:
return devicetree::ScanState::kActive;
case devicetree::NodePath::Comparison::kMismatch:
case devicetree::NodePath::Comparison::kChild:
case devicetree::NodePath::Comparison::kIndirectDescendent:
return devicetree::ScanState::kDoneWithSubtree;
};
}
devicetree::ScanState OnSubtree(const devicetree::NodePath& path) {
on_subtree_count++;
return on_subtree(path);
}
devicetree::ScanState OnScan() {
walk_count++;
walk();
return walk_result;
}
void OnDone() { on_done = true; }
void OnError(std::string_view error) { this->error = error; }
std::string error;
std::string_view path_to_match;
devicetree::ScanState node_match_result = devicetree::ScanState::kDone;
devicetree::ScanState walk_result = devicetree::ScanState::kDone;
bool found = false;
bool on_done = false;
int visit_count = 0;
size_t walk_count = 0;
size_t on_subtree_count = 0;
fit::function<void(std::string_view, const devicetree::PropertyDecoder&)> node;
fit::function<void()> walk;
fit::function<devicetree::ScanState(const devicetree::NodePath&)> on_subtree;
};
class MatchTest : public zxtest::Test {
public:
static void SetUpTestSuite() {
auto loaded_dtb = devicetree::testing::LoadDtb("complex_no_properties.dtb");
ASSERT_TRUE(loaded_dtb.is_ok(), "%s", loaded_dtb.error_value().c_str());
fdt_no_props_ = *loaded_dtb;
loaded_dtb = devicetree::testing::LoadDtb("complex_with_alias.dtb");
ASSERT_TRUE(loaded_dtb.is_ok(), "%s", loaded_dtb.error_value().c_str());
fdt_no_props_with_alias_ = *loaded_dtb;
}
static void TearDownTestuite() { fdt_no_props_ = std::nullopt; }
/*
*
/ \
A E
/ \ \
B C F
/ / \
D G I
/
H
*/
devicetree::Devicetree no_prop_tree() { return fdt_no_props_->fdt(); }
/*
Same as |no_prop_tree| but with the following aliases:
* foo = "/A/C"
* bar = "/E/F"
In this tree, the aliases node is the last one of the root's offspring.
*/
devicetree::Devicetree no_prop_tree_with_alias() { return fdt_no_props_with_alias_->fdt(); }
private:
static std::optional<devicetree::testing::LoadedDtb> fdt_no_props_;
static std::optional<devicetree::testing::LoadedDtb> fdt_no_props_with_alias_;
};
std::optional<devicetree::testing::LoadedDtb> MatchTest::fdt_no_props_ = {};
std::optional<devicetree::testing::LoadedDtb> MatchTest::fdt_no_props_with_alias_ = {};
TEST_F(MatchTest, EarlyCompletion) {
size_t seen = 0;
SingleNodeMatcher<2> matcher("/A/C/D", [&](auto name, const auto& decoder) {
seen++;
EXPECT_EQ(name, "D");
});
auto tree = no_prop_tree();
EXPECT_TRUE(devicetree::Match(tree, matcher));
// This matcher completes on the first iteration, so the walk count will be 0.
EXPECT_TRUE(matcher.found);
EXPECT_EQ(matcher.visit_count, 5);
EXPECT_EQ(matcher.walk_count, 0);
EXPECT_TRUE(matcher.error.empty());
EXPECT_EQ(seen, 1);
EXPECT_TRUE(matcher.on_done);
}
TEST_F(MatchTest, NoShortCircuitingAliasesNode) {
// Verify that when no matchers can make progress due to PathResolution being needed,
// the aliases eventually get resolved, and further progress can be made.
size_t seen = 0;
SingleNodeMatcher<1> matcher("foo/D", [&](auto name, const auto& decoder) {
seen++;
EXPECT_EQ(name, "D");
matcher.node_match_result = devicetree::ScanState::kDone;
});
auto tree = no_prop_tree_with_alias();
EXPECT_TRUE(devicetree::Match(tree, matcher));
// This matcher completes on the second iteration, so the walk count will be 1.
EXPECT_TRUE(matcher.found);
// Walk 0: * -> Meeds Path resolution at root. 1 visit.
// Walk 1: * -> A -> B -> C -> D (Done)
EXPECT_EQ(matcher.visit_count, 6);
// Its never called because of DFS, the matcher gets Done.
EXPECT_EQ(matcher.on_subtree_count, 0);
EXPECT_EQ(matcher.walk_count, 0);
EXPECT_TRUE(matcher.error.empty());
EXPECT_EQ(seen, 1);
EXPECT_TRUE(matcher.on_done);
}
TEST_F(MatchTest, MultipleWalksForCompletion) {
size_t seen = 0;
SingleNodeMatcher<2> matcher("/A/C/D", [&](auto name, const auto& decoder) {
seen++;
EXPECT_EQ(name, "D");
matcher.node_match_result =
(seen > 1) ? devicetree::ScanState::kDone : devicetree::ScanState::kActive;
});
matcher.walk_result = devicetree::ScanState::kActive;
auto tree = no_prop_tree();
EXPECT_TRUE(devicetree::Match(tree, matcher));
// This matcher completes on the second iteration, so the walk count will be 1.
EXPECT_TRUE(matcher.found);
// Walk 0: * -> A -> B -> C -> D -> E
// Walk 1: * -> A -> B -> C -> D (Done)
EXPECT_EQ(matcher.visit_count, 11);
// D -> C -> A -> Root subtrees completed before the matcher reaches completion (1st walk)
EXPECT_EQ(matcher.on_subtree_count, 4);
EXPECT_EQ(matcher.walk_count, 1);
EXPECT_TRUE(matcher.error.empty());
EXPECT_EQ(seen, 2);
EXPECT_TRUE(matcher.on_done);
}
TEST_F(MatchTest, OnScanCompetion) {
size_t seen = 0;
SingleNodeMatcher<2> matcher("/A/C/D", [&](auto name, const auto& decoder) {
seen++;
EXPECT_EQ(name, "D");
});
matcher.node_match_result = devicetree::ScanState::kActive;
matcher.walk_result = devicetree::ScanState::kDone;
auto tree = no_prop_tree();
EXPECT_TRUE(devicetree::Match(tree, matcher));
// This matcher completes after a full walk.
EXPECT_TRUE(matcher.found);
// D -> C -> A -> Root subtrees completed before the matcher reaches completion on walk (1st walk)
EXPECT_EQ(matcher.on_subtree_count, 4);
// Walk 0: * -> A -> B -> C -> D -> E
EXPECT_EQ(matcher.visit_count, 6);
EXPECT_EQ(matcher.walk_count, 1);
EXPECT_TRUE(matcher.error.empty());
EXPECT_EQ(seen, 1);
EXPECT_TRUE(matcher.on_done);
}
TEST_F(MatchTest, OnErrorReturnsFalse) {
size_t seen = 0;
SingleNodeMatcher<2> matcher("/A/C/D", [&](auto name, const auto& decoder) {
seen++;
EXPECT_EQ(name, "D");
});
// Matcher will never be 'Done'.
matcher.node_match_result = devicetree::ScanState::kActive;
matcher.walk_result = devicetree::ScanState::kActive;
auto tree = no_prop_tree();
EXPECT_FALSE(devicetree::Match(tree, matcher));
// This matcher completes after a full walk.
EXPECT_TRUE(matcher.found);
// Walk 0: * -> A -> B -> C -> D -> E
// Walk 1: * -> A -> B -> C -> D -> E
EXPECT_EQ(matcher.visit_count, 12);
// D -> C -> A -> Root subtrees completed before the matcher on each walk.
EXPECT_EQ(matcher.on_subtree_count, 8);
EXPECT_EQ(matcher.walk_count, 2);
EXPECT_FALSE(matcher.error.empty());
EXPECT_EQ(seen, 2);
EXPECT_FALSE(matcher.on_done);
}
TEST_F(MatchTest, MultipleMatchersEarlyCompletion) {
size_t seen_1 = 0;
SingleNodeMatcher<2> matcher_1("/A/C/D", [&](auto name, const auto& decoder) {
seen_1++;
EXPECT_EQ(name, "D");
});
size_t seen_2 = 0;
SingleNodeMatcher<2> matcher_2("/E/F/G/H", [&](auto name, const auto& decoder) {
seen_2++;
EXPECT_EQ(name, "H");
});
auto tree = no_prop_tree();
EXPECT_TRUE(devicetree::Match(tree, matcher_1, matcher_2));
EXPECT_TRUE(matcher_1.found);
EXPECT_EQ(matcher_1.visit_count, 5);
EXPECT_EQ(matcher_1.walk_count, 0);
EXPECT_EQ(matcher_1.on_subtree_count, 0);
EXPECT_TRUE(matcher_1.error.empty());
EXPECT_EQ(seen_1, 1);
EXPECT_TRUE(matcher_1.on_done);
EXPECT_TRUE(matcher_2.found);
EXPECT_EQ(matcher_2.visit_count, 6);
EXPECT_EQ(matcher_2.walk_count, 0);
EXPECT_EQ(matcher_2.on_subtree_count, 0);
EXPECT_TRUE(matcher_2.error.empty());
EXPECT_EQ(seen_2, 1);
EXPECT_TRUE(matcher_2.on_done);
}
TEST_F(MatchTest, MultipleMatchersOnScanCompletion) {
size_t seen_1 = 0;
SingleNodeMatcher<2> matcher_1("/A/C/D", [&](auto name, const auto& decoder) {
seen_1++;
EXPECT_EQ(name, "D");
});
matcher_1.node_match_result = devicetree::ScanState::kActive;
matcher_1.walk_result = devicetree::ScanState::kDone;
size_t seen_2 = 0;
SingleNodeMatcher<2> matcher_2("/E/F/G/H", [&](auto name, const auto& decoder) {
seen_2++;
EXPECT_EQ(name, "H");
});
matcher_2.node_match_result = devicetree::ScanState::kActive;
matcher_2.walk_result = devicetree::ScanState::kDone;
auto tree = no_prop_tree();
EXPECT_TRUE(devicetree::Match(tree, matcher_1, matcher_2));
EXPECT_TRUE(matcher_1.found);
EXPECT_EQ(matcher_1.visit_count, 6);
EXPECT_EQ(matcher_1.walk_count, 1);
// Only non zero because the matcher is completed on Walk, not on visit.
EXPECT_EQ(matcher_1.on_subtree_count, 4);
EXPECT_TRUE(matcher_1.error.empty());
EXPECT_EQ(seen_1, 1);
EXPECT_TRUE(matcher_1.on_done);
EXPECT_TRUE(matcher_2.found);
EXPECT_EQ(matcher_2.visit_count, 7);
EXPECT_EQ(matcher_2.walk_count, 1);
// Only non zero because the matcher is completed on Walk, not on visit.
EXPECT_EQ(matcher_2.on_subtree_count, 5);
EXPECT_TRUE(matcher_2.error.empty());
EXPECT_EQ(seen_2, 1);
EXPECT_TRUE(matcher_2.on_done);
}
TEST_F(MatchTest, MultipleMatchersOnErrorIsFalse) {
size_t seen_1 = 0;
SingleNodeMatcher<2> matcher_1("/A/C/D", [&](auto name, const auto& decoder) {
seen_1++;
EXPECT_EQ(name, "D");
});
size_t seen_2 = 0;
SingleNodeMatcher<2> matcher_2("/E/F/G/H", [&](auto name, const auto& decoder) {
seen_2++;
EXPECT_EQ(name, "H");
});
matcher_2.node_match_result = devicetree::ScanState::kActive;
matcher_2.walk_result = devicetree::ScanState::kActive;
auto tree = no_prop_tree();
EXPECT_FALSE(devicetree::Match(tree, matcher_1, matcher_2));
EXPECT_TRUE(matcher_1.found);
EXPECT_EQ(matcher_1.visit_count, 5);
EXPECT_EQ(matcher_1.walk_count, 0);
EXPECT_EQ(matcher_1.on_subtree_count, 0);
EXPECT_TRUE(matcher_1.error.empty());
EXPECT_EQ(seen_1, 1);
EXPECT_TRUE(matcher_1.on_done);
EXPECT_TRUE(matcher_2.found);
EXPECT_EQ(matcher_2.visit_count, 14);
EXPECT_EQ(matcher_2.walk_count, 2);
EXPECT_EQ(matcher_2.on_subtree_count, 10);
EXPECT_FALSE(matcher_2.error.empty());
EXPECT_EQ(seen_2, 2);
EXPECT_FALSE(matcher_2.on_done);
}
TEST_F(MatchTest, OnSubtreeCalledWhenActive) {
size_t seen_1 = 0;
size_t root_after = 0;
SingleNodeMatcher<2> matcher_1(
"/A/C/D",
[&](auto name, const auto& decoder) {
seen_1++;
EXPECT_EQ(name, "D");
},
[]() {},
[&](const devicetree::NodePath& path) {
if (path == "/A") {
root_after++;
return devicetree::ScanState::kDone;
}
return devicetree::ScanState::kActive;
});
matcher_1.node_match_result = devicetree::ScanState::kActive;
auto tree = no_prop_tree();
EXPECT_TRUE(devicetree::Match(tree, matcher_1));
EXPECT_EQ(root_after, 1);
EXPECT_EQ(matcher_1.visit_count, 5);
EXPECT_EQ(matcher_1.walk_count, 0);
EXPECT_EQ(matcher_1.on_subtree_count, 3);
EXPECT_TRUE(matcher_1.error.empty());
EXPECT_EQ(seen_1, 1);
EXPECT_TRUE(matcher_1.on_done);
}
TEST_F(MatchTest, OnSubtreeDoneWithSubtreeIsNoOp) {
size_t seen_1 = 0;
size_t root_after = 0;
SingleNodeMatcher<2> matcher_1(
"/A/C/D",
[&](auto name, const auto& decoder) {
seen_1++;
EXPECT_EQ(name, "D");
},
[]() {},
[&](const devicetree::NodePath& path) {
if (path == "/A") {
root_after++;
return devicetree::ScanState::kDone;
}
// Done with subtree means its not done yet, but nothing else to do with the subtree,
// in this case should be equivalent to |kDone|.
return devicetree::ScanState::kDoneWithSubtree;
});
matcher_1.node_match_result = devicetree::ScanState::kActive;
auto tree = no_prop_tree();
EXPECT_TRUE(devicetree::Match(tree, matcher_1));
EXPECT_EQ(root_after, 1);
EXPECT_EQ(matcher_1.visit_count, 5);
EXPECT_EQ(matcher_1.walk_count, 0);
EXPECT_EQ(matcher_1.on_subtree_count, 3);
EXPECT_TRUE(matcher_1.error.empty());
EXPECT_EQ(seen_1, 1);
EXPECT_TRUE(matcher_1.on_done);
}
} // namespace