1 //===--- DirectiveTreeTest.cpp --------------------------------------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 #include "clang-pseudo/DirectiveTree.h"
11 #include "clang-pseudo/Token.h"
12 #include "clang/Basic/LangOptions.h"
13 #include "clang/Basic/TokenKinds.h"
14 #include "llvm/ADT/StringExtras.h"
15 #include "llvm/ADT/StringRef.h"
16 #include "gmock/gmock.h"
17 #include "gtest/gtest.h"
24 using testing::ElementsAre
;
25 using testing::Matcher
;
28 using Chunk
= DirectiveTree::Chunk
;
30 // Matches text of a list of tokens against a string (joined with spaces).
31 // e.g. EXPECT_THAT(Stream.tokens(), tokens("int main ( ) { }"));
32 MATCHER_P(tokens
, Tokens
, "") {
33 std::vector
<llvm::StringRef
> Texts
;
34 for (const Token
&Tok
: arg
)
35 Texts
.push_back(Tok
.text());
36 return Matcher
<std::string
>(StrEq(Tokens
))
37 .MatchAndExplain(llvm::join(Texts
, " "), result_listener
);
40 // Matches tokens covered a directive chunk (with a Tokens property) against a
41 // string, similar to tokens() above.
42 // e.g. EXPECT_THAT(SomeDirective, tokensAre(Stream, "# include < vector >"));
43 MATCHER_P2(tokensAre
, TS
, Tokens
, "tokens are " + std::string(Tokens
)) {
44 return testing::Matches(tokens(Tokens
))(TS
.tokens(arg
.Tokens
));
47 MATCHER(directiveChunk
, "") {
48 return std::holds_alternative
<DirectiveTree::Directive
>(arg
);
50 MATCHER(codeChunk
, "") {
51 return std::holds_alternative
<DirectiveTree::Code
>(arg
);
53 MATCHER(conditionalChunk
, "") {
54 return std::holds_alternative
<DirectiveTree::Conditional
>(arg
);
57 TEST(DirectiveTree
, Parse
) {
59 std::string Code
= R
"cpp(
75 TokenStream S
= cook(lex(Code
, Opts
), Opts
);
76 DirectiveTree PP
= DirectiveTree::parse(S
);
77 ASSERT_THAT(PP
.Chunks
, ElementsAre(directiveChunk(), codeChunk(),
78 conditionalChunk(), codeChunk()));
80 EXPECT_THAT(std::get
<DirectiveTree::Directive
>(PP
.Chunks
[0]),
81 tokensAre(S
, "# include < foo . h >"));
82 EXPECT_THAT(std::get
<DirectiveTree::Code
>(PP
.Chunks
[1]),
83 tokensAre(S
, "int main ( ) {"));
84 EXPECT_THAT(std::get
<DirectiveTree::Code
>(PP
.Chunks
[3]), tokensAre(S
, "}"));
86 const auto &Ifdef
= std::get
<DirectiveTree::Conditional
>(PP
.Chunks
[2]);
87 EXPECT_THAT(Ifdef
.Branches
,
88 ElementsAre(Pair(tokensAre(S
, "# ifdef HAS_FOO"), _
),
89 Pair(tokensAre(S
, "# elif NEEDS_FOO"), _
)));
90 EXPECT_THAT(Ifdef
.End
, tokensAre(S
, "# endif"));
92 const DirectiveTree
&HasFoo(Ifdef
.Branches
[0].second
);
93 const DirectiveTree
&NeedsFoo(Ifdef
.Branches
[1].second
);
95 EXPECT_THAT(HasFoo
.Chunks
, ElementsAre(conditionalChunk()));
96 const auto &If
= std::get
<DirectiveTree::Conditional
>(HasFoo
.Chunks
[0]);
97 EXPECT_THAT(If
.Branches
, ElementsAre(Pair(tokensAre(S
, "# if HAS_BAR"), _
),
98 Pair(tokensAre(S
, "# else"), _
)));
99 EXPECT_THAT(If
.Branches
[0].second
.Chunks
, ElementsAre(codeChunk()));
100 EXPECT_THAT(If
.Branches
[1].second
.Chunks
, ElementsAre(codeChunk()));
102 EXPECT_THAT(NeedsFoo
.Chunks
, ElementsAre(directiveChunk()));
103 const auto &Error
= std::get
<DirectiveTree::Directive
>(NeedsFoo
.Chunks
[0]);
104 EXPECT_THAT(Error
, tokensAre(S
, "# error missing_foo"));
105 EXPECT_EQ(Error
.Kind
, tok::pp_error
);
108 TEST(DirectiveTree
, ParseUgly
) {
110 std::string Code
= R
"cpp(
117 TokenStream S
= cook(lex(Code
, Opts
), Opts
);
118 DirectiveTree PP
= DirectiveTree::parse(S
);
120 ASSERT_THAT(PP
.Chunks
,
121 ElementsAre(codeChunk(), directiveChunk(), codeChunk()));
122 EXPECT_THAT(std::get
<DirectiveTree::Code
>(PP
.Chunks
[0]),
123 tokensAre(S
, "/*A*/"));
124 const auto &Define
= std::get
<DirectiveTree::Directive
>(PP
.Chunks
[1]);
125 EXPECT_EQ(Define
.Kind
, tok::pp_define
);
126 EXPECT_THAT(Define
, tokensAre(S
, "# /*B*/ /*C*/ define BAR /*D*/"));
127 EXPECT_THAT(std::get
<DirectiveTree::Code
>(PP
.Chunks
[2]),
128 tokensAre(S
, "/*E*/"));
131 TEST(DirectiveTree
, ParseBroken
) {
133 std::string Code
= R
"cpp(
139 TokenStream S
= cook(lex(Code
, Opts
), Opts
);
140 DirectiveTree PP
= DirectiveTree::parse(S
);
142 ASSERT_THAT(PP
.Chunks
,
143 ElementsAre(codeChunk(), directiveChunk(), conditionalChunk()));
144 EXPECT_THAT(std::get
<DirectiveTree::Code
>(PP
.Chunks
[0]), tokensAre(S
, "a"));
145 const auto &Endif
= std::get
<DirectiveTree::Directive
>(PP
.Chunks
[1]);
146 EXPECT_EQ(Endif
.Kind
, tok::pp_endif
);
147 EXPECT_THAT(Endif
, tokensAre(S
, "# endif // mismatched"));
149 const auto &X
= std::get
<DirectiveTree::Conditional
>(PP
.Chunks
[2]);
150 EXPECT_EQ(1u, X
.Branches
.size());
151 // The (only) branch of the broken conditional section runs until eof.
152 EXPECT_EQ(tok::pp_if
, X
.Branches
.front().first
.Kind
);
153 EXPECT_THAT(X
.Branches
.front().second
.Chunks
, ElementsAre(codeChunk()));
154 // The missing terminating directive is marked as pp_not_keyword.
155 EXPECT_EQ(tok::pp_not_keyword
, X
.End
.Kind
);
156 EXPECT_EQ(0u, X
.End
.Tokens
.size());
159 TEST(DirectiveTree
, ChooseBranches
) {
161 const std::string Cases
[] = {
163 // Branches with no alternatives are taken
170 // Empty branches are better than nothing
176 // Trivially false branches are not taken, even with no alternatives.
183 // Longer branches are preferred over shorter branches
198 // Trivially true branches are taken if previous branches are trivial.
222 // #else is a trivially true branch
233 // Directives break ties, but nondirective text is more important.
251 // Avoid #error directives.
254 #error This branch is no good
259 // All paths here lead to errors.
263 #error This branch is no good
265 #error This one is no good either
273 // Populate taken branches recursively.
291 for (const auto &Code
: Cases
) {
292 TokenStream S
= cook(lex(Code
, Opts
), Opts
);
294 std::function
<void(const DirectiveTree
&)> Verify
=
295 [&](const DirectiveTree
&M
) {
296 for (const auto &C
: M
.Chunks
) {
297 if (!std::holds_alternative
<DirectiveTree::Conditional
>(C
))
299 const DirectiveTree::Conditional
&Cond
=
300 std::get
<DirectiveTree::Conditional
>(C
);
301 for (unsigned I
= 0; I
< Cond
.Branches
.size(); ++I
) {
302 auto Directive
= S
.tokens(Cond
.Branches
[I
].first
.Tokens
);
303 EXPECT_EQ(I
== Cond
.Taken
, Directive
.back().text() == "// TAKEN")
304 << "At line " << Directive
.front().Line
<< " of: " << Code
;
305 Verify(Cond
.Branches
[I
].second
);
310 DirectiveTree Tree
= DirectiveTree::parse(S
);
311 chooseConditionalBranches(Tree
, S
);
316 TEST(DirectiveTree
, StripDirectives
) {
318 std::string Code
= R
"cpp(
342 TokenStream S
= lex(Code
, Opts
);
344 DirectiveTree Tree
= DirectiveTree::parse(S
);
345 chooseConditionalBranches(Tree
, S
);
346 EXPECT_THAT(Tree
.stripDirectives(S
).tokens(),
347 tokens("a a a b b b c c c e e e f f f h h h j j j"));
349 const DirectiveTree
&Part
=
350 std::get
<DirectiveTree::Conditional
>(Tree
.Chunks
[4]).Branches
[0].second
;
351 EXPECT_THAT(Part
.stripDirectives(S
).tokens(),
352 tokens("c c c e e e f f f h h h"));
356 } // namespace pseudo