llvm-project/clang-tools-extra/pseudo/unittests/DirectiveTreeTest.cpp

358 lines
9.7 KiB
C++

//===--- DirectiveTreeTest.cpp --------------------------------------------===//
//
// 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-pseudo/DirectiveTree.h"
#include "clang-pseudo/Token.h"
#include "clang/Basic/LangOptions.h"
#include "clang/Basic/TokenKinds.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
namespace clang {
namespace pseudo {
namespace {
using testing::_;
using testing::ElementsAre;
using testing::Matcher;
using testing::Pair;
using testing::StrEq;
using Chunk = DirectiveTree::Chunk;
// Matches text of a list of tokens against a string (joined with spaces).
// e.g. EXPECT_THAT(Stream.tokens(), tokens("int main ( ) { }"));
MATCHER_P(tokens, Tokens, "") {
std::vector<llvm::StringRef> Texts;
for (const Token &Tok : arg)
Texts.push_back(Tok.text());
return Matcher<std::string>(StrEq(Tokens))
.MatchAndExplain(llvm::join(Texts, " "), result_listener);
}
// Matches tokens covered a directive chunk (with a Tokens property) against a
// string, similar to tokens() above.
// e.g. EXPECT_THAT(SomeDirective, tokensAre(Stream, "# include < vector >"));
MATCHER_P2(tokensAre, TS, Tokens, "tokens are " + std::string(Tokens)) {
return testing::Matches(tokens(Tokens))(TS.tokens(arg.Tokens));
}
MATCHER(directiveChunk, "") {
return std::holds_alternative<DirectiveTree::Directive>(arg);
}
MATCHER(codeChunk, "") {
return std::holds_alternative<DirectiveTree::Code>(arg);
}
MATCHER(conditionalChunk, "") {
return std::holds_alternative<DirectiveTree::Conditional>(arg);
}
TEST(DirectiveTree, Parse) {
LangOptions Opts;
std::string Code = R"cpp(
#include <foo.h>
int main() {
#ifdef HAS_FOO
#if HAS_BAR
foo(bar);
#else
foo(0)
#endif
#elif NEEDS_FOO
#error missing_foo
#endif
}
)cpp";
TokenStream S = cook(lex(Code, Opts), Opts);
DirectiveTree PP = DirectiveTree::parse(S);
ASSERT_THAT(PP.Chunks, ElementsAre(directiveChunk(), codeChunk(),
conditionalChunk(), codeChunk()));
EXPECT_THAT(std::get<DirectiveTree::Directive>(PP.Chunks[0]),
tokensAre(S, "# include < foo . h >"));
EXPECT_THAT(std::get<DirectiveTree::Code>(PP.Chunks[1]),
tokensAre(S, "int main ( ) {"));
EXPECT_THAT(std::get<DirectiveTree::Code>(PP.Chunks[3]), tokensAre(S, "}"));
const auto &Ifdef = std::get<DirectiveTree::Conditional>(PP.Chunks[2]);
EXPECT_THAT(Ifdef.Branches,
ElementsAre(Pair(tokensAre(S, "# ifdef HAS_FOO"), _),
Pair(tokensAre(S, "# elif NEEDS_FOO"), _)));
EXPECT_THAT(Ifdef.End, tokensAre(S, "# endif"));
const DirectiveTree &HasFoo(Ifdef.Branches[0].second);
const DirectiveTree &NeedsFoo(Ifdef.Branches[1].second);
EXPECT_THAT(HasFoo.Chunks, ElementsAre(conditionalChunk()));
const auto &If = std::get<DirectiveTree::Conditional>(HasFoo.Chunks[0]);
EXPECT_THAT(If.Branches, ElementsAre(Pair(tokensAre(S, "# if HAS_BAR"), _),
Pair(tokensAre(S, "# else"), _)));
EXPECT_THAT(If.Branches[0].second.Chunks, ElementsAre(codeChunk()));
EXPECT_THAT(If.Branches[1].second.Chunks, ElementsAre(codeChunk()));
EXPECT_THAT(NeedsFoo.Chunks, ElementsAre(directiveChunk()));
const auto &Error = std::get<DirectiveTree::Directive>(NeedsFoo.Chunks[0]);
EXPECT_THAT(Error, tokensAre(S, "# error missing_foo"));
EXPECT_EQ(Error.Kind, tok::pp_error);
}
TEST(DirectiveTree, ParseUgly) {
LangOptions Opts;
std::string Code = R"cpp(
/*A*/ # /*B*/ \
/*C*/ \
define \
BAR /*D*/
/*E*/
)cpp";
TokenStream S = cook(lex(Code, Opts), Opts);
DirectiveTree PP = DirectiveTree::parse(S);
ASSERT_THAT(PP.Chunks,
ElementsAre(codeChunk(), directiveChunk(), codeChunk()));
EXPECT_THAT(std::get<DirectiveTree::Code>(PP.Chunks[0]),
tokensAre(S, "/*A*/"));
const auto &Define = std::get<DirectiveTree::Directive>(PP.Chunks[1]);
EXPECT_EQ(Define.Kind, tok::pp_define);
EXPECT_THAT(Define, tokensAre(S, "# /*B*/ /*C*/ define BAR /*D*/"));
EXPECT_THAT(std::get<DirectiveTree::Code>(PP.Chunks[2]),
tokensAre(S, "/*E*/"));
}
TEST(DirectiveTree, ParseBroken) {
LangOptions Opts;
std::string Code = R"cpp(
a
#endif // mismatched
#if X
b
)cpp";
TokenStream S = cook(lex(Code, Opts), Opts);
DirectiveTree PP = DirectiveTree::parse(S);
ASSERT_THAT(PP.Chunks,
ElementsAre(codeChunk(), directiveChunk(), conditionalChunk()));
EXPECT_THAT(std::get<DirectiveTree::Code>(PP.Chunks[0]), tokensAre(S, "a"));
const auto &Endif = std::get<DirectiveTree::Directive>(PP.Chunks[1]);
EXPECT_EQ(Endif.Kind, tok::pp_endif);
EXPECT_THAT(Endif, tokensAre(S, "# endif // mismatched"));
const auto &X = std::get<DirectiveTree::Conditional>(PP.Chunks[2]);
EXPECT_EQ(1u, X.Branches.size());
// The (only) branch of the broken conditional section runs until eof.
EXPECT_EQ(tok::pp_if, X.Branches.front().first.Kind);
EXPECT_THAT(X.Branches.front().second.Chunks, ElementsAre(codeChunk()));
// The missing terminating directive is marked as pp_not_keyword.
EXPECT_EQ(tok::pp_not_keyword, X.End.Kind);
EXPECT_EQ(0u, X.End.Tokens.size());
}
TEST(DirectiveTree, ChooseBranches) {
LangOptions Opts;
const std::string Cases[] = {
R"cpp(
// Branches with no alternatives are taken
#if COND // TAKEN
int x;
#endif
)cpp",
R"cpp(
// Empty branches are better than nothing
#if COND // TAKEN
#endif
)cpp",
R"cpp(
// Trivially false branches are not taken, even with no alternatives.
#if 0
int x;
#endif
)cpp",
R"cpp(
// Longer branches are preferred over shorter branches
#if COND // TAKEN
int x = 1;
#else
int x;
#endif
#if COND
int x;
#else // TAKEN
int x = 1;
#endif
)cpp",
R"cpp(
// Trivially true branches are taken if previous branches are trivial.
#if 1 // TAKEN
#else
int x = 1;
#endif
#if 0
int x = 1;
#elif 0
int x = 2;
#elif 1 // TAKEN
int x;
#endif
#if 0
int x = 1;
#elif FOO // TAKEN
int x = 2;
#elif 1
int x;
#endif
)cpp",
R"cpp(
// #else is a trivially true branch
#if 0
int x = 1;
#elif 0
int x = 2;
#else // TAKEN
int x;
#endif
)cpp",
R"cpp(
// Directives break ties, but nondirective text is more important.
#if FOO
#define A 1 2 3
#else // TAKEN
#define B 4 5 6
#define C 7 8 9
#endif
#if FOO // TAKEN
;
#define A 1 2 3
#else
#define B 4 5 6
#define C 7 8 9
#endif
)cpp",
R"cpp(
// Avoid #error directives.
#if FOO
int x = 42;
#error This branch is no good
#else // TAKEN
#endif
#if FOO
// All paths here lead to errors.
int x = 42;
#if 1 // TAKEN
#if COND // TAKEN
#error This branch is no good
#else
#error This one is no good either
#endif
#endif
#else // TAKEN
#endif
)cpp",
R"cpp(
// Populate taken branches recursively.
#if FOO // TAKEN
int x = 42;
#if BAR
;
#else // TAKEN
int y = 43;
#endif
#else
int x;
#if BAR // TAKEN
int y;
#else
;
#endif
#endif
)cpp",
};
for (const auto &Code : Cases) {
TokenStream S = cook(lex(Code, Opts), Opts);
std::function<void(const DirectiveTree &)> Verify =
[&](const DirectiveTree &M) {
for (const auto &C : M.Chunks) {
if (!std::holds_alternative<DirectiveTree::Conditional>(C))
continue;
const DirectiveTree::Conditional &Cond =
std::get<DirectiveTree::Conditional>(C);
for (unsigned I = 0; I < Cond.Branches.size(); ++I) {
auto Directive = S.tokens(Cond.Branches[I].first.Tokens);
EXPECT_EQ(I == Cond.Taken, Directive.back().text() == "// TAKEN")
<< "At line " << Directive.front().Line << " of: " << Code;
Verify(Cond.Branches[I].second);
}
}
};
DirectiveTree Tree = DirectiveTree::parse(S);
chooseConditionalBranches(Tree, S);
Verify(Tree);
}
}
TEST(DirectiveTree, StripDirectives) {
LangOptions Opts;
std::string Code = R"cpp(
#include <stddef.h>
a a a
#warning AAA
b b b
#if 1
c c c
#warning BBB
#if 0
d d d
#warning CC
#else
e e e
#endif
f f f
#if 0
g g g
#endif
h h h
#else
i i i
#endif
j j j
)cpp";
TokenStream S = lex(Code, Opts);
DirectiveTree Tree = DirectiveTree::parse(S);
chooseConditionalBranches(Tree, S);
EXPECT_THAT(Tree.stripDirectives(S).tokens(),
tokens("a a a b b b c c c e e e f f f h h h j j j"));
const DirectiveTree &Part =
std::get<DirectiveTree::Conditional>(Tree.Chunks[4]).Branches[0].second;
EXPECT_THAT(Part.stripDirectives(S).tokens(),
tokens("c c c e e e f f f h h h"));
}
} // namespace
} // namespace pseudo
} // namespace clang