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

307 lines
8.5 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;
MATCHER_P2(tokensAre, TS, Tokens, "tokens are " + std::string(Tokens)) {
std::vector<llvm::StringRef> Texts;
for (const Token &Tok : TS.tokens(arg.Tokens))
Texts.push_back(Tok.text());
return Matcher<std::string>(StrEq(Tokens))
.MatchAndExplain(llvm::join(Texts, " "), result_listener);
}
MATCHER_P(chunkKind, K, "") { return arg.kind() == K; }
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(chunkKind(Chunk::K_Directive),
chunkKind(Chunk::K_Code),
chunkKind(Chunk::K_Conditional),
chunkKind(Chunk::K_Code)));
EXPECT_THAT((const DirectiveTree::Directive &)PP.Chunks[0],
tokensAre(S, "# include < foo . h >"));
EXPECT_THAT((const DirectiveTree::Code &)PP.Chunks[1],
tokensAre(S, "int main ( ) {"));
EXPECT_THAT((const DirectiveTree::Code &)PP.Chunks[3], tokensAre(S, "}"));
const DirectiveTree::Conditional &Ifdef(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(chunkKind(Chunk::K_Conditional)));
const DirectiveTree::Conditional &If(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(chunkKind(Chunk::K_Code)));
EXPECT_THAT(If.Branches[1].second.Chunks,
ElementsAre(chunkKind(Chunk::K_Code)));
EXPECT_THAT(NeedsFoo.Chunks, ElementsAre(chunkKind(Chunk::K_Directive)));
const DirectiveTree::Directive &Error(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(chunkKind(Chunk::K_Code),
chunkKind(Chunk::K_Directive),
chunkKind(Chunk::K_Code)));
EXPECT_THAT((const DirectiveTree::Code &)PP.Chunks[0], tokensAre(S, "/*A*/"));
const DirectiveTree::Directive &Define(PP.Chunks[1]);
EXPECT_EQ(Define.Kind, tok::pp_define);
EXPECT_THAT(Define, tokensAre(S, "# /*B*/ /*C*/ define BAR /*D*/"));
EXPECT_THAT((const 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(chunkKind(Chunk::K_Code),
chunkKind(Chunk::K_Directive),
chunkKind(Chunk::K_Conditional)));
EXPECT_THAT((const DirectiveTree::Code &)PP.Chunks[0], tokensAre(S, "a"));
const DirectiveTree::Directive &Endif(PP.Chunks[1]);
EXPECT_EQ(Endif.Kind, tok::pp_endif);
EXPECT_THAT(Endif, tokensAre(S, "# endif // mismatched"));
const DirectiveTree::Conditional &X(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(chunkKind(Chunk::K_Code)));
// 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 (C.kind() != DirectiveTree::Chunk::K_Conditional)
continue;
const DirectiveTree::Conditional &Cond(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);
}
}
} // namespace
} // namespace pseudo
} // namespace clang