
Adds a def file to have a single location where tested language versions are specified. Removes the need to update multiple locations in the testing infrastructure to add a new language version to be tested. Test instatiation can now include all languages without needing to specify them. This patch also adds pretty printing for instantiated test names. That means, that a test instantiated with C++23 will have the name `...TestSuite/TestName/CXX23` instead ending with some number (index of the argument for instantiation of the test), which provides a better experience when encountering a test failure with a specific language version. The suffix will also contain an `_win` if the target contains `win`. --------- Co-authored-by: Sirraide <aeternalmail@gmail.com>
412 lines
15 KiB
C++
412 lines
15 KiB
C++
//===- TreeTest.cpp ---------------------------------------------*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "clang/Tooling/Syntax/Tree.h"
|
|
#include "TreeTestBase.h"
|
|
#include "clang/Basic/SourceManager.h"
|
|
#include "clang/Tooling/Syntax/BuildTree.h"
|
|
#include "clang/Tooling/Syntax/Nodes.h"
|
|
#include "llvm/ADT/STLExtras.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
using namespace clang;
|
|
using namespace clang::syntax;
|
|
|
|
namespace {
|
|
using testing::ElementsAre;
|
|
|
|
class TreeTest : public SyntaxTreeTest {
|
|
private:
|
|
Tree *createTree(ArrayRef<const Node *> Children) {
|
|
std::vector<std::pair<Node *, NodeRole>> ChildrenWithRoles;
|
|
ChildrenWithRoles.reserve(Children.size());
|
|
for (const auto *Child : Children) {
|
|
ChildrenWithRoles.push_back(std::make_pair(
|
|
deepCopyExpandingMacros(*Arena, *TM, Child), NodeRole::Unknown));
|
|
}
|
|
return clang::syntax::createTree(*Arena, ChildrenWithRoles,
|
|
NodeKind::UnknownExpression);
|
|
}
|
|
|
|
// Generate Forests by combining `Children` into `ParentCount` Trees.
|
|
//
|
|
// We do this recursively.
|
|
std::vector<std::vector<const Tree *>>
|
|
generateAllForests(ArrayRef<const Node *> Children, unsigned ParentCount) {
|
|
assert(ParentCount > 0);
|
|
// If there is only one Parent node, then combine `Children` under
|
|
// this Parent.
|
|
if (ParentCount == 1)
|
|
return {{createTree(Children)}};
|
|
|
|
// Otherwise, combine `ChildrenCount` children under the last parent and
|
|
// solve the smaller problem without these children and this parent. Do this
|
|
// for every `ChildrenCount` and combine the results.
|
|
std::vector<std::vector<const Tree *>> AllForests;
|
|
for (unsigned ChildrenCount = 0; ChildrenCount <= Children.size();
|
|
++ChildrenCount) {
|
|
auto *LastParent = createTree(Children.take_back(ChildrenCount));
|
|
for (auto &Forest : generateAllForests(Children.drop_back(ChildrenCount),
|
|
ParentCount - 1)) {
|
|
Forest.push_back(LastParent);
|
|
AllForests.push_back(Forest);
|
|
}
|
|
}
|
|
return AllForests;
|
|
}
|
|
|
|
protected:
|
|
// Generates all trees with a `Base` of `Node`s and `NodeCountPerLayer`
|
|
// `Node`s per layer. An example of Tree with `Base` = {`(`, `)`} and
|
|
// `NodeCountPerLayer` = {2, 2}:
|
|
// Tree
|
|
// |-Tree
|
|
// `-Tree
|
|
// |-Tree
|
|
// | `-'('
|
|
// `-Tree
|
|
// `-')'
|
|
std::vector<const Tree *>
|
|
generateAllTreesWithShape(ArrayRef<const Node *> Base,
|
|
ArrayRef<unsigned> NodeCountPerLayer) {
|
|
// We compute the solution per layer. A layer is a collection of bases,
|
|
// where each base has the same number of nodes, given by
|
|
// `NodeCountPerLayer`.
|
|
auto GenerateNextLayer = [this](ArrayRef<std::vector<const Node *>> Layer,
|
|
unsigned NextLayerNodeCount) {
|
|
std::vector<std::vector<const Node *>> NextLayer;
|
|
for (const auto &Base : Layer) {
|
|
for (const auto &NextBase :
|
|
generateAllForests(Base, NextLayerNodeCount)) {
|
|
NextLayer.push_back(
|
|
std::vector<const Node *>(NextBase.begin(), NextBase.end()));
|
|
}
|
|
}
|
|
return NextLayer;
|
|
};
|
|
|
|
std::vector<std::vector<const Node *>> Layer = {Base};
|
|
for (auto NodeCount : NodeCountPerLayer)
|
|
Layer = GenerateNextLayer(Layer, NodeCount);
|
|
|
|
std::vector<const Tree *> AllTrees;
|
|
AllTrees.reserve(Layer.size());
|
|
for (const auto &Base : Layer)
|
|
AllTrees.push_back(createTree(Base));
|
|
|
|
return AllTrees;
|
|
}
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
TreeTests, TreeTest, ::testing::ValuesIn(allTestClangConfigs()),
|
|
[](const testing::TestParamInfo<TestClangConfig> &Info) {
|
|
return Info.param.toShortString();
|
|
});
|
|
|
|
TEST_P(TreeTest, FirstLeaf) {
|
|
buildTree("", GetParam());
|
|
std::vector<const Node *> Leafs = {createLeaf(*Arena, *TM, tok::l_paren),
|
|
createLeaf(*Arena, *TM, tok::r_paren)};
|
|
for (const auto *Tree : generateAllTreesWithShape(Leafs, {3u})) {
|
|
ASSERT_TRUE(Tree->findFirstLeaf() != nullptr);
|
|
EXPECT_EQ(TM->getToken(Tree->findFirstLeaf()->getTokenKey())->kind(), tok::l_paren);
|
|
}
|
|
}
|
|
|
|
TEST_P(TreeTest, LastLeaf) {
|
|
buildTree("", GetParam());
|
|
std::vector<const Node *> Leafs = {createLeaf(*Arena, *TM, tok::l_paren),
|
|
createLeaf(*Arena, *TM, tok::r_paren)};
|
|
for (const auto *Tree : generateAllTreesWithShape(Leafs, {3u})) {
|
|
ASSERT_TRUE(Tree->findLastLeaf() != nullptr);
|
|
EXPECT_EQ(TM->getToken(Tree->findLastLeaf()->getTokenKey())->kind(), tok::r_paren);
|
|
}
|
|
}
|
|
|
|
TEST_F(TreeTest, Iterators) {
|
|
buildTree("", allTestClangConfigs().front());
|
|
std::vector<Node *> Children = {createLeaf(*Arena, *TM, tok::identifier, "a"),
|
|
createLeaf(*Arena, *TM, tok::identifier, "b"),
|
|
createLeaf(*Arena, *TM, tok::identifier, "c")};
|
|
auto *Tree = syntax::createTree(*Arena,
|
|
{{Children[0], NodeRole::LeftHandSide},
|
|
{Children[1], NodeRole::OperatorToken},
|
|
{Children[2], NodeRole::RightHandSide}},
|
|
NodeKind::TranslationUnit);
|
|
const auto *ConstTree = Tree;
|
|
|
|
auto Range = Tree->getChildren();
|
|
EXPECT_THAT(Range, ElementsAre(role(NodeRole::LeftHandSide),
|
|
role(NodeRole::OperatorToken),
|
|
role(NodeRole::RightHandSide)));
|
|
|
|
auto ConstRange = ConstTree->getChildren();
|
|
EXPECT_THAT(ConstRange, ElementsAre(role(NodeRole::LeftHandSide),
|
|
role(NodeRole::OperatorToken),
|
|
role(NodeRole::RightHandSide)));
|
|
|
|
// FIXME: mutate and observe no invalidation. Mutations are private for now...
|
|
auto It = Range.begin();
|
|
auto CIt = ConstRange.begin();
|
|
static_assert(std::is_same_v<decltype(*It), syntax::Node &>, "mutable range");
|
|
static_assert(std::is_same_v<decltype(*CIt), const syntax::Node &>,
|
|
"const range");
|
|
|
|
for (unsigned I = 0; I < 3; ++I) {
|
|
EXPECT_EQ(It, CIt);
|
|
EXPECT_TRUE(It);
|
|
EXPECT_TRUE(CIt);
|
|
EXPECT_EQ(It.asPointer(), Children[I]);
|
|
EXPECT_EQ(CIt.asPointer(), Children[I]);
|
|
EXPECT_EQ(&*It, Children[I]);
|
|
EXPECT_EQ(&*CIt, Children[I]);
|
|
++It;
|
|
++CIt;
|
|
}
|
|
EXPECT_EQ(It, CIt);
|
|
EXPECT_EQ(It, Tree::ChildIterator());
|
|
EXPECT_EQ(CIt, Tree::ConstChildIterator());
|
|
EXPECT_FALSE(It);
|
|
EXPECT_FALSE(CIt);
|
|
EXPECT_EQ(nullptr, It.asPointer());
|
|
EXPECT_EQ(nullptr, CIt.asPointer());
|
|
}
|
|
|
|
class ListTest : public SyntaxTreeTest {
|
|
private:
|
|
std::string dumpQuotedTokensOrNull(const Node *N) {
|
|
return N ? "'" +
|
|
StringRef(N->dumpTokens(*TM))
|
|
.trim()
|
|
.str() +
|
|
"'"
|
|
: "null";
|
|
}
|
|
|
|
protected:
|
|
std::string
|
|
dumpElementsAndDelimiters(ArrayRef<List::ElementAndDelimiter<Node>> EDs) {
|
|
std::string Storage;
|
|
llvm::raw_string_ostream OS(Storage);
|
|
|
|
OS << "[";
|
|
|
|
llvm::interleaveComma(
|
|
EDs, OS, [&OS, this](const List::ElementAndDelimiter<Node> &ED) {
|
|
OS << "(" << dumpQuotedTokensOrNull(ED.element) << ", "
|
|
<< dumpQuotedTokensOrNull(ED.delimiter) << ")";
|
|
});
|
|
|
|
OS << "]";
|
|
|
|
return Storage;
|
|
}
|
|
|
|
std::string dumpNodes(ArrayRef<Node *> Nodes) {
|
|
std::string Storage;
|
|
llvm::raw_string_ostream OS(Storage);
|
|
|
|
OS << "[";
|
|
|
|
llvm::interleaveComma(Nodes, OS, [&OS, this](const Node *N) {
|
|
OS << dumpQuotedTokensOrNull(N);
|
|
});
|
|
|
|
OS << "]";
|
|
|
|
return Storage;
|
|
}
|
|
};
|
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
TreeTests, ListTest, ::testing::ValuesIn(allTestClangConfigs()),
|
|
[](const testing::TestParamInfo<TestClangConfig> &Info) {
|
|
return Info.param.toShortString();
|
|
});
|
|
|
|
/// "a, b, c" <=> [("a", ","), ("b", ","), ("c", null)]
|
|
TEST_P(ListTest, List_Separated_WellFormed) {
|
|
buildTree("", GetParam());
|
|
|
|
// "a, b, c"
|
|
auto *List = dyn_cast<syntax::List>(syntax::createTree(
|
|
*Arena,
|
|
{
|
|
{createLeaf(*Arena, *TM, tok::identifier, "a"), NodeRole::ListElement},
|
|
{createLeaf(*Arena, *TM, tok::comma), NodeRole::ListDelimiter},
|
|
{createLeaf(*Arena, *TM, tok::identifier, "b"), NodeRole::ListElement},
|
|
{createLeaf(*Arena, *TM, tok::comma), NodeRole::ListDelimiter},
|
|
{createLeaf(*Arena, *TM, tok::identifier, "c"), NodeRole::ListElement},
|
|
},
|
|
NodeKind::CallArguments));
|
|
|
|
EXPECT_EQ(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()),
|
|
"[('a', ','), ('b', ','), ('c', null)]");
|
|
EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', 'b', 'c']");
|
|
}
|
|
|
|
/// "a, , c" <=> [("a", ","), (null, ","), ("c", null)]
|
|
TEST_P(ListTest, List_Separated_MissingElement) {
|
|
buildTree("", GetParam());
|
|
|
|
// "a, , c"
|
|
auto *List = dyn_cast<syntax::List>(syntax::createTree(
|
|
*Arena,
|
|
{
|
|
{createLeaf(*Arena, *TM, tok::identifier, "a"), NodeRole::ListElement},
|
|
{createLeaf(*Arena, *TM, tok::comma), NodeRole::ListDelimiter},
|
|
{createLeaf(*Arena, *TM, tok::comma), NodeRole::ListDelimiter},
|
|
{createLeaf(*Arena, *TM, tok::identifier, "c"), NodeRole::ListElement},
|
|
},
|
|
NodeKind::CallArguments));
|
|
|
|
EXPECT_EQ(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()),
|
|
"[('a', ','), (null, ','), ('c', null)]");
|
|
EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', null, 'c']");
|
|
}
|
|
|
|
/// "a, b c" <=> [("a", ","), ("b", null), ("c", null)]
|
|
TEST_P(ListTest, List_Separated_MissingDelimiter) {
|
|
buildTree("", GetParam());
|
|
|
|
// "a, b c"
|
|
auto *List = dyn_cast<syntax::List>(syntax::createTree(
|
|
*Arena,
|
|
{
|
|
{createLeaf(*Arena, *TM, tok::identifier, "a"), NodeRole::ListElement},
|
|
{createLeaf(*Arena, *TM, tok::comma), NodeRole::ListDelimiter},
|
|
{createLeaf(*Arena, *TM, tok::identifier, "b"), NodeRole::ListElement},
|
|
{createLeaf(*Arena, *TM, tok::identifier, "c"), NodeRole::ListElement},
|
|
},
|
|
NodeKind::CallArguments));
|
|
|
|
EXPECT_EQ(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()),
|
|
"[('a', ','), ('b', null), ('c', null)]");
|
|
EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', 'b', 'c']");
|
|
}
|
|
|
|
/// "a, b," <=> [("a", ","), ("b", ","), (null, null)]
|
|
TEST_P(ListTest, List_Separated_MissingLastElement) {
|
|
buildTree("", GetParam());
|
|
|
|
// "a, b, c"
|
|
auto *List = dyn_cast<syntax::List>(syntax::createTree(
|
|
*Arena,
|
|
{
|
|
{createLeaf(*Arena, *TM, tok::identifier, "a"), NodeRole::ListElement},
|
|
{createLeaf(*Arena, *TM, tok::comma), NodeRole::ListDelimiter},
|
|
{createLeaf(*Arena, *TM, tok::identifier, "b"), NodeRole::ListElement},
|
|
{createLeaf(*Arena, *TM, tok::comma), NodeRole::ListDelimiter},
|
|
},
|
|
NodeKind::CallArguments));
|
|
|
|
EXPECT_EQ(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()),
|
|
"[('a', ','), ('b', ','), (null, null)]");
|
|
EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', 'b', null]");
|
|
}
|
|
|
|
/// "a:: b:: c::" <=> [("a", "::"), ("b", "::"), ("c", "::")]
|
|
TEST_P(ListTest, List_Terminated_WellFormed) {
|
|
if (!GetParam().isCXX()) {
|
|
return;
|
|
}
|
|
buildTree("", GetParam());
|
|
|
|
// "a:: b:: c::"
|
|
auto *List = dyn_cast<syntax::List>(syntax::createTree(
|
|
*Arena,
|
|
{
|
|
{createLeaf(*Arena, *TM, tok::identifier, "a"), NodeRole::ListElement},
|
|
{createLeaf(*Arena, *TM, tok::coloncolon), NodeRole::ListDelimiter},
|
|
{createLeaf(*Arena, *TM, tok::identifier, "b"), NodeRole::ListElement},
|
|
{createLeaf(*Arena, *TM, tok::coloncolon), NodeRole::ListDelimiter},
|
|
{createLeaf(*Arena, *TM, tok::identifier, "c"), NodeRole::ListElement},
|
|
{createLeaf(*Arena, *TM, tok::coloncolon), NodeRole::ListDelimiter},
|
|
},
|
|
NodeKind::NestedNameSpecifier));
|
|
|
|
EXPECT_EQ(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()),
|
|
"[('a', '::'), ('b', '::'), ('c', '::')]");
|
|
EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', 'b', 'c']");
|
|
}
|
|
|
|
/// "a:: :: c::" <=> [("a", "::"), (null, "::"), ("c", "::")]
|
|
TEST_P(ListTest, List_Terminated_MissingElement) {
|
|
if (!GetParam().isCXX()) {
|
|
return;
|
|
}
|
|
buildTree("", GetParam());
|
|
|
|
// "a:: b:: c::"
|
|
auto *List = dyn_cast<syntax::List>(syntax::createTree(
|
|
*Arena,
|
|
{
|
|
{createLeaf(*Arena, *TM, tok::identifier, "a"), NodeRole::ListElement},
|
|
{createLeaf(*Arena, *TM, tok::coloncolon), NodeRole::ListDelimiter},
|
|
{createLeaf(*Arena, *TM, tok::coloncolon), NodeRole::ListDelimiter},
|
|
{createLeaf(*Arena, *TM, tok::identifier, "c"), NodeRole::ListElement},
|
|
{createLeaf(*Arena, *TM, tok::coloncolon), NodeRole::ListDelimiter},
|
|
},
|
|
NodeKind::NestedNameSpecifier));
|
|
|
|
EXPECT_EQ(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()),
|
|
"[('a', '::'), (null, '::'), ('c', '::')]");
|
|
EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', null, 'c']");
|
|
}
|
|
|
|
/// "a:: b c::" <=> [("a", "::"), ("b", null), ("c", "::")]
|
|
TEST_P(ListTest, List_Terminated_MissingDelimiter) {
|
|
if (!GetParam().isCXX()) {
|
|
return;
|
|
}
|
|
buildTree("", GetParam());
|
|
|
|
// "a:: b c::"
|
|
auto *List = dyn_cast<syntax::List>(syntax::createTree(
|
|
*Arena,
|
|
{
|
|
{createLeaf(*Arena, *TM, tok::identifier, "a"), NodeRole::ListElement},
|
|
{createLeaf(*Arena, *TM, tok::coloncolon), NodeRole::ListDelimiter},
|
|
{createLeaf(*Arena, *TM, tok::identifier, "b"), NodeRole::ListElement},
|
|
{createLeaf(*Arena, *TM, tok::identifier, "c"), NodeRole::ListElement},
|
|
{createLeaf(*Arena, *TM, tok::coloncolon), NodeRole::ListDelimiter},
|
|
},
|
|
NodeKind::NestedNameSpecifier));
|
|
|
|
EXPECT_EQ(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()),
|
|
"[('a', '::'), ('b', null), ('c', '::')]");
|
|
EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', 'b', 'c']");
|
|
}
|
|
|
|
/// "a:: b:: c" <=> [("a", "::"), ("b", "::"), ("c", null)]
|
|
TEST_P(ListTest, List_Terminated_MissingLastDelimiter) {
|
|
if (!GetParam().isCXX()) {
|
|
return;
|
|
}
|
|
buildTree("", GetParam());
|
|
|
|
// "a:: b:: c"
|
|
auto *List = dyn_cast<syntax::List>(syntax::createTree(
|
|
*Arena,
|
|
{
|
|
{createLeaf(*Arena, *TM, tok::identifier, "a"), NodeRole::ListElement},
|
|
{createLeaf(*Arena, *TM, tok::coloncolon), NodeRole::ListDelimiter},
|
|
{createLeaf(*Arena, *TM, tok::identifier, "b"), NodeRole::ListElement},
|
|
{createLeaf(*Arena, *TM, tok::coloncolon), NodeRole::ListDelimiter},
|
|
{createLeaf(*Arena, *TM, tok::identifier, "c"), NodeRole::ListElement},
|
|
},
|
|
NodeKind::NestedNameSpecifier));
|
|
|
|
EXPECT_EQ(dumpElementsAndDelimiters(List->getElementsAsNodesAndDelimiters()),
|
|
"[('a', '::'), ('b', '::'), ('c', null)]");
|
|
EXPECT_EQ(dumpNodes(List->getElementsAsNodes()), "['a', 'b', 'c']");
|
|
}
|
|
|
|
} // namespace
|