| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include <AzCore/DOM/DomPrefixTree.h>
- #include <Tests/DOM/DomFixtures.h>
- namespace AZ::Dom::Tests
- {
- class DomPrefixTreeTests : public DomTestFixture
- {
- public:
- void SetUp() override
- {
- DomTestFixture::SetUp();
- m_runData = AZStd::make_unique<RunData>();
- m_runData->m_visitorFn = [this](const Path& path, int value)
- {
- auto pathString = path.ToString();
- AZStd::string visitedPathString = path.ToString();
- for (const auto& entry : m_runData->m_visitorResults)
- {
- AZStd::string entryPathString = entry.first.ToString();
- if (m_runData->m_useDepthFirstTraversal)
- {
- EXPECT_FALSE(visitedPathString.starts_with(entryPathString));
- }
- else
- {
- EXPECT_FALSE(entryPathString.starts_with(visitedPathString));
- }
- }
- m_runData->m_visitorResults.emplace_back(path, value);
- return true;
- };
- m_runData->m_visitorTree.SetValue(Path("/foo"), 99);
- m_runData->m_visitorTree.SetValue(Path("/foo/0"), 0);
- m_runData->m_visitorTree.SetValue(Path("/foo/1"), 42);
- m_runData->m_visitorTree.SetValue(Path("/bar/bat"), 1);
- m_runData->m_visitorTree.SetValue(Path("/bar/baz"), 2);
- }
- void TearDown() override
- {
- m_runData.reset();
- DomTestFixture::TearDown();
- }
- protected:
- struct RunData
- {
- DomPrefixTree<int> m_visitorTree;
- DomPrefixTree<int>::VisitorFunction m_visitorFn;
- AZStd::vector<AZStd::pair<Path, int>> m_visitorResults;
- bool m_useDepthFirstTraversal = false;
- };
- AZStd::unique_ptr<RunData> m_runData;
- size_t VisitorResultCount()
- {
- return m_runData->m_visitorResults.size();
- }
- bool ValidateResult(const Path& path, int n)
- {
- for (const auto& pair : m_runData->m_visitorResults)
- {
- if (pair.first == path)
- {
- return pair.second == n;
- }
- }
- return false;
- }
- void SetVisitDepthFirst(bool visitDepthFirst)
- {
- m_runData->m_useDepthFirstTraversal = visitDepthFirst;
- }
- void TestVisitPath(const Path& path, PrefixTreeTraversalFlags flags)
- {
- if (m_runData->m_useDepthFirstTraversal)
- {
- flags = flags | PrefixTreeTraversalFlags::TraverseMostToLeastSpecific;
- }
- else
- {
- flags = flags | PrefixTreeTraversalFlags::TraverseLeastToMostSpecific;
- }
- m_runData->m_visitorTree.VisitPath(path, m_runData->m_visitorFn, flags);
- }
- };
- static_assert(!RangeConvertibleToPrefixTree<AZStd::vector<int>, int>, "Non-pair range should not convert to tree");
- static_assert(
- !RangeConvertibleToPrefixTree<AZStd::vector<AZStd::pair<Path, AZStd::string>>, int>,
- "Mismatched value type should not convert to tree");
- static_assert(
- !RangeConvertibleToPrefixTree<AZStd::vector<AZStd::pair<AZStd::string, int>>, int>,
- "Mismatched value type should not convert to tree");
- static_assert(
- RangeConvertibleToPrefixTree<AZStd::vector<AZStd::pair<Path, int>>, int>,
- "Vector with path / key type pairs should convert to tree");
- TEST_F(DomPrefixTreeTests, InitializeFromInitializerList)
- {
- DomPrefixTree<int> tree({
- { Path(), 0 },
- { Path("/foo/bar"), 1 },
- });
- EXPECT_EQ(0, *tree.ValueAtPath(Path(), PrefixTreeMatch::ExactPath));
- EXPECT_EQ(1, *tree.ValueAtPath(Path("/foo/bar"), PrefixTreeMatch::ExactPath));
- }
- TEST_F(DomPrefixTreeTests, InitializeFromRange)
- {
- AZStd::vector<AZStd::pair<Path, int>> container({
- { Path(), 21 },
- { Path("/foo/bar"), 42 },
- });
- DomPrefixTree<int> tree(container);
- EXPECT_EQ(21, *tree.ValueAtPath(Path(), PrefixTreeMatch::ExactPath));
- EXPECT_EQ(42, *tree.ValueAtPath(Path("/foo/bar"), PrefixTreeMatch::ExactPath));
- }
- TEST_F(DomPrefixTreeTests, GetAndSetRoot)
- {
- DomPrefixTree<AZStd::string> tree;
- tree.SetValue(Path(), "root");
- EXPECT_EQ("root", *tree.ValueAtPath(Path(), PrefixTreeMatch::ExactPath));
- }
- TEST_F(DomPrefixTreeTests, GetExactPath)
- {
- DomPrefixTree<int> tree;
- tree.SetValue(Path("/foo/0"), 0);
- tree.SetValue(Path("/foo/1"), 42);
- tree.SetValue(Path("/foo/foo"), 1);
- tree.SetValue(Path("/foo/bar"), 2);
- EXPECT_EQ(0, *tree.ValueAtPath(Path("/foo/0"), PrefixTreeMatch::ExactPath));
- EXPECT_EQ(42, *tree.ValueAtPath(Path("/foo/1"), PrefixTreeMatch::ExactPath));
- EXPECT_EQ(1, *tree.ValueAtPath(Path("/foo/foo"), PrefixTreeMatch::ExactPath));
- EXPECT_EQ(2, *tree.ValueAtPath(Path("/foo/bar"), PrefixTreeMatch::ExactPath));
- EXPECT_EQ(nullptr, tree.ValueAtPath(Path(), PrefixTreeMatch::ExactPath));
- EXPECT_EQ(nullptr, tree.ValueAtPath(Path("/foo"), PrefixTreeMatch::ExactPath));
- EXPECT_EQ(nullptr, tree.ValueAtPath(Path("/foo/0/subpath"), PrefixTreeMatch::ExactPath));
- }
- TEST_F(DomPrefixTreeTests, GetParent)
- {
- DomPrefixTree<int> tree;
- tree.SetValue(Path("/foo/0"), 0);
- tree.SetValue(Path("/foo/1"), 42);
- EXPECT_EQ(0, *tree.ValueAtPath(Path("/foo/0/bar"), PrefixTreeMatch::ParentsOnly));
- EXPECT_EQ(0, *tree.ValueAtPath(Path("/foo/0/bar/baz"), PrefixTreeMatch::ParentsOnly));
- EXPECT_EQ(42, *tree.ValueAtPath(Path("/foo/1/0"), PrefixTreeMatch::ParentsOnly));
- EXPECT_EQ(nullptr, tree.ValueAtPath(Path("/foo/0"), PrefixTreeMatch::ParentsOnly));
- EXPECT_EQ(nullptr, tree.ValueAtPath(Path("/foo/1"), PrefixTreeMatch::ParentsOnly));
- }
- TEST_F(DomPrefixTreeTests, GetPathOrParent)
- {
- DomPrefixTree<int> tree;
- tree.SetValue(Path("/foo/0"), 0);
- tree.SetValue(Path("/foo/1"), 42);
- EXPECT_EQ(0, *tree.ValueAtPath(Path("/foo/0"), PrefixTreeMatch::PathAndParents));
- EXPECT_EQ(0, *tree.ValueAtPath(Path("/foo/0/bar"), PrefixTreeMatch::PathAndParents));
- EXPECT_EQ(0, *tree.ValueAtPath(Path("/foo/0/bar/baz"), PrefixTreeMatch::PathAndParents));
- EXPECT_EQ(42, *tree.ValueAtPath(Path("/foo/1"), PrefixTreeMatch::PathAndParents));
- EXPECT_EQ(42, *tree.ValueAtPath(Path("/foo/1/0"), PrefixTreeMatch::PathAndParents));
- EXPECT_EQ(nullptr, tree.ValueAtPath(Path(), PrefixTreeMatch::PathAndParents));
- EXPECT_EQ(nullptr, tree.ValueAtPath(Path("/foo"), PrefixTreeMatch::PathAndParents));
- EXPECT_EQ(nullptr, tree.ValueAtPath(Path("/path/0"), PrefixTreeMatch::PathAndParents));
- }
- TEST_F(DomPrefixTreeTests, RemovePath)
- {
- DomPrefixTree<int> tree;
- tree.SetValue(Path(), 20);
- tree.SetValue(Path("/foo"), 40);
- tree.SetValue(Path("/foo/0"), 80);
- tree.EraseValue(Path("/foo"));
- EXPECT_EQ(20, *tree.ValueAtPath(Path("/foo"), PrefixTreeMatch::PathAndParents));
- EXPECT_EQ(80, *tree.ValueAtPath(Path("/foo/0"), PrefixTreeMatch::PathAndParents));
- }
- TEST_F(DomPrefixTreeTests, RemovePathAndChildren)
- {
- DomPrefixTree<int> tree;
- tree.SetValue(Path(), 20);
- tree.SetValue(Path("/foo"), 40);
- tree.SetValue(Path("/foo/0"), 80);
- tree.EraseValue(Path("/foo"), true);
- EXPECT_EQ(20, *tree.ValueAtPath(Path("/foo"), PrefixTreeMatch::PathAndParents));
- EXPECT_EQ(20, *tree.ValueAtPath(Path("/foo/0"), PrefixTreeMatch::PathAndParents));
- }
- TEST_F(DomPrefixTreeTests, DetachSubTreeAtExistingPath)
- {
- DomPrefixTree<int> tree;
- tree.SetValue(Path("/foo"), 40);
- tree.SetValue(Path("/foo/0"), 80);
- DomPrefixTree<int> detachedTree = tree.DetachSubTree(AZ::Dom::Path("/foo"));
- // Validate that the values aren't present in the old tree after detaching.
- EXPECT_EQ(nullptr, tree.ValueAtPath(Path("/foo"), PrefixTreeMatch::ExactPath));
- EXPECT_EQ(nullptr, tree.ValueAtPath(Path("/foo/0"), PrefixTreeMatch::ExactPath));
- // Validate that the values are present in the detached tree.
- EXPECT_EQ(40, *detachedTree.ValueAtPath(Path(), PrefixTreeMatch::ExactPath));
- EXPECT_EQ(80, *detachedTree.ValueAtPath(Path("/0"), PrefixTreeMatch::ExactPath));
- }
- TEST_F(DomPrefixTreeTests, DetachSubTreeAtNonExistingPath)
- {
- DomPrefixTree<int> tree;
- tree.SetValue(Path("/foo"), 40);
- tree.SetValue(Path("/foo/0"), 80);
- DomPrefixTree<int> detachedTree = tree.DetachSubTree(AZ::Dom::Path("/bar"));
- // Validate that the returned tree is empty since a non-existing path is provided.
- EXPECT_TRUE(detachedTree.IsEmpty());
- // Validate that the values in the original tree are still present.
- EXPECT_EQ(40, *tree.ValueAtPath(Path("/foo"), PrefixTreeMatch::ExactPath));
- EXPECT_EQ(80, *tree.ValueAtPath(Path("/foo/0"), PrefixTreeMatch::ExactPath));
- }
- TEST_F(DomPrefixTreeTests, OverwritePathAddsNewNodeAtExistingPath)
- {
- DomPrefixTree<int> tree;
- tree.SetValue(Path("/foo"), 40);
- DomPrefixTree<int> subtree;
- subtree.SetValue(Path(), 80);
- subtree.SetValue(Path("/1"), 120);
- bool isOverwritten = tree.OverwritePath(Path("/foo"), AZStd::move(subtree));
- // Validate that the subtree is overwritten and new values are present at the paths in the original tree.
- EXPECT_TRUE(isOverwritten);
- EXPECT_EQ(80, *tree.ValueAtPath(Path("/foo"), PrefixTreeMatch::ExactPath));
- EXPECT_EQ(120, *tree.ValueAtPath(Path("/foo/1"), PrefixTreeMatch::ExactPath));
- }
- TEST_F(DomPrefixTreeTests, OverwritePathReplacesExistingNodeAndChildren)
- {
- DomPrefixTree<int> tree;
- tree.SetValue(Path("/foo"), 40);
- tree.SetValue(Path("/foo/1"), 60);
- tree.SetValue(Path("/foo/2"), 70);
- DomPrefixTree<int> subtree;
- subtree.SetValue(Path(), 80);
- subtree.SetValue(Path("/1"), 120);
- bool isOverwritten = tree.OverwritePath(Path("/foo"), AZStd::move(subtree));
- // Validate that the subtree is overwritten and existing nodes are replaced in the original tree.
- EXPECT_TRUE(isOverwritten);
- EXPECT_EQ(80, *tree.ValueAtPath(Path("/foo"), PrefixTreeMatch::ExactPath));
- EXPECT_EQ(120, *tree.ValueAtPath(Path("/foo/1"), PrefixTreeMatch::ExactPath));
- EXPECT_EQ(nullptr, tree.ValueAtPath(Path("/foo/2"), PrefixTreeMatch::ExactPath));
- }
- TEST_F(DomPrefixTreeTests, OverwritePathSucceedsAtNonExistingPath)
- {
- DomPrefixTree<int> tree;
- tree.SetValue(Path("/foo"), 40);
- DomPrefixTree<int> subtree;
- subtree.SetValue(Path(), 80);
- subtree.SetValue(Path("/1"), 120);
- tree.OverwritePath(Path("/foo/bar"), AZStd::move(subtree)); // create the node if needed
- // Validate that the subtree is overwritten and new values are present at the paths in the original tree.
- EXPECT_EQ(80, *tree.ValueAtPath(Path("/foo/bar"), PrefixTreeMatch::ExactPath));
- EXPECT_EQ(120, *tree.ValueAtPath(Path("/foo/bar/1"), PrefixTreeMatch::ExactPath));
- }
- TEST_F(DomPrefixTreeTests, OverwritePathSucceedsAtPathWithoutParents)
- {
- DomPrefixTree<int> tree;
- tree.SetValue(Path("/foo"), 40);
- DomPrefixTree<int> subtree;
- subtree.SetValue(Path(), 80);
- subtree.SetValue(Path("/1"), 120);
- tree.OverwritePath(Path("/foo/bar/baz"), AZStd::move(subtree)); // create all nodes if needed
- // Validate that the subtree is overwritten and new values are present at the paths in the original tree.
- EXPECT_EQ(80, *tree.ValueAtPath(Path("/foo/bar/baz"), PrefixTreeMatch::ExactPath));
- EXPECT_EQ(120, *tree.ValueAtPath(Path("/foo/bar/baz/1"), PrefixTreeMatch::ExactPath));
- }
- TEST_F(DomPrefixTreeTests, OverwritePathFailsAtNonExistingPathIfNotCreateNodes)
- {
- DomPrefixTree<int> tree;
- tree.SetValue(Path("/foo"), 40);
- DomPrefixTree<int> subtree;
- subtree.SetValue(Path(), 80);
- bool isOverwritten = tree.OverwritePath(Path("/foo/bar"), AZStd::move(subtree), false); // 'bar' node does not exist
- // Validate that the subtree could not be overwritten.
- EXPECT_FALSE(isOverwritten);
- }
- TEST_F(DomPrefixTreeTests, OverwritePathFailsAtPathWithMissingParentsIfNotCreateNodes)
- {
- DomPrefixTree<int> tree;
- tree.SetValue(Path("/foo"), 40);
- DomPrefixTree<int> subtree;
- subtree.SetValue(Path(), 80);
- bool isOverwritten = tree.OverwritePath(Path("/foo/bar/baz"), AZStd::move(subtree), false); // Parent entry 'bar' does not exist
- // Validate that the subtree could not be overwritten.
- EXPECT_FALSE(isOverwritten);
- }
- TEST_F(DomPrefixTreeTests, ClearTree)
- {
- DomPrefixTree<int> tree;
- tree.SetValue(Path(), 20);
- tree.SetValue(Path("/foo"), 40);
- tree.Clear();
- EXPECT_EQ(-10, tree.ValueAtPathOrDefault(Path("/foo"), -10, PrefixTreeMatch::PathAndParents));
- EXPECT_TRUE(tree.IsEmpty());
- }
- TEST_F(DomPrefixTreeTests, VisitMissingExactPath)
- {
- TestVisitPath(Path("/bar"), PrefixTreeTraversalFlags::ExcludeChildPaths | PrefixTreeTraversalFlags::ExcludeParentPaths);
- EXPECT_EQ(0, VisitorResultCount());
- }
- TEST_F(DomPrefixTreeTests, VisitExactPath)
- {
- TestVisitPath(Path("/foo/0"), PrefixTreeTraversalFlags::ExcludeChildPaths | PrefixTreeTraversalFlags::ExcludeParentPaths);
- EXPECT_EQ(1, VisitorResultCount());
- EXPECT_TRUE(ValidateResult(Path("/foo/0"), 0));
- }
- TEST_F(DomPrefixTreeTests, VisitChildren)
- {
- TestVisitPath(Path("/foo"), PrefixTreeTraversalFlags::ExcludeExactPath | PrefixTreeTraversalFlags::ExcludeParentPaths);
- EXPECT_EQ(2, VisitorResultCount());
- EXPECT_TRUE(ValidateResult(Path("/foo/0"), 0));
- EXPECT_TRUE(ValidateResult(Path("/foo/1"), 42));
- }
- TEST_F(DomPrefixTreeTests, VisitPathAndChildren)
- {
- TestVisitPath(Path("/foo"), PrefixTreeTraversalFlags::ExcludeParentPaths);
- EXPECT_EQ(3, VisitorResultCount());
- EXPECT_TRUE(ValidateResult(Path("/foo"), 99));
- EXPECT_TRUE(ValidateResult(Path("/foo/0"), 0));
- EXPECT_TRUE(ValidateResult(Path("/foo/1"), 42));
- }
- TEST_F(DomPrefixTreeTests, VisitEntireTree)
- {
- TestVisitPath(Path(), PrefixTreeTraversalFlags::ExcludeExactPath);
- EXPECT_EQ(5, VisitorResultCount());
- EXPECT_TRUE(ValidateResult(Path("/foo"), 99));
- EXPECT_TRUE(ValidateResult(Path("/foo/0"), 0));
- EXPECT_TRUE(ValidateResult(Path("/foo/1"), 42));
- EXPECT_TRUE(ValidateResult(Path("/bar/bat"), 1));
- EXPECT_TRUE(ValidateResult(Path("/bar/baz"), 2));
- }
- TEST_F(DomPrefixTreeTests, VisitEntireTree_DepthFirst)
- {
- SetVisitDepthFirst(true);
- TestVisitPath(Path(), PrefixTreeTraversalFlags::ExcludeExactPath);
- EXPECT_EQ(5, VisitorResultCount());
- EXPECT_TRUE(ValidateResult(Path("/foo"), 99));
- EXPECT_TRUE(ValidateResult(Path("/foo/0"), 0));
- EXPECT_TRUE(ValidateResult(Path("/foo/1"), 42));
- EXPECT_TRUE(ValidateResult(Path("/bar/bat"), 1));
- EXPECT_TRUE(ValidateResult(Path("/bar/baz"), 2));
- }
- TEST_F(DomPrefixTreeTests, VisitParentsAndSelf)
- {
- TestVisitPath(Path("/foo/0"), PrefixTreeTraversalFlags::ExcludeChildPaths);
- EXPECT_EQ(2, VisitorResultCount());
- EXPECT_TRUE(ValidateResult(Path("/foo"), 99));
- EXPECT_TRUE(ValidateResult(Path("/foo/0"), 0));
- }
- TEST_F(DomPrefixTreeTests, VisitParentsOnly)
- {
- TestVisitPath(Path("/foo/0"), PrefixTreeTraversalFlags::ExcludeExactPath | PrefixTreeTraversalFlags::ExcludeChildPaths);
- EXPECT_EQ(1, VisitorResultCount());
- EXPECT_TRUE(ValidateResult(Path("/foo"), 99));
- }
- TEST_F(DomPrefixTreeTests, EarlyTerminateVisit)
- {
- size_t visitCalls = 0;
- auto visitorFn = [&visitCalls](const Path&, int)
- {
- ++visitCalls;
- return false;
- };
- m_runData->m_visitorTree.VisitPath(Path(), visitorFn, PrefixTreeTraversalFlags::TraverseLeastToMostSpecific);
- EXPECT_EQ(visitCalls, 1);
- visitCalls = 0;
- m_runData->m_visitorTree.VisitPath(Path(), visitorFn, PrefixTreeTraversalFlags::TraverseMostToLeastSpecific);
- EXPECT_EQ(visitCalls, 1);
- }
- TEST_F(DomPrefixTreeTests, RemoveChildrenDuringVisit)
- {
- // Remove children during a depth-first traversal
- auto visitorFn = [&](const Path& path, int)
- {
- m_runData->m_visitorTree.EraseValue(path, true);
- return true;
- };
- m_runData->m_visitorTree.VisitPath(Path(), visitorFn, PrefixTreeTraversalFlags::TraverseMostToLeastSpecific);
- // Ensure all entries were successfully deleted
- TestVisitPath(Path(), PrefixTreeTraversalFlags::None);
- EXPECT_EQ(0, VisitorResultCount());
- }
- } // namespace AZ::Dom::Tests
|