#include "clang/Frontend/FrontendAction.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Lex/PreprocessorOptions.h"
+#include "clang/Testing/CommandLineArgs.h"
#include "clang/Tooling/Core/Replacement.h"
#include "clang/Tooling/Syntax/BuildTree.h"
#include "clang/Tooling/Syntax/Mutations.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Error.h"
T->lastLeaf()->token() + 1);
}
-class SyntaxTreeTest : public ::testing::Test {
+struct TestClangConfig {
+ TestLanguage Language;
+ std::string Target;
+
+ bool isCXX() const {
+ return Language == Lang_CXX || Language == Lang_CXX11 ||
+ Language == Lang_CXX14 || Language == Lang_CXX17 ||
+ Language == Lang_CXX2a;
+ }
+
+ bool isCXX11OrLater() const {
+ return Language == Lang_CXX11 || Language == Lang_CXX14 ||
+ Language == Lang_CXX17 || Language == Lang_CXX2a;
+ }
+
+ bool hasDelayedTemplateParsing() const {
+ return Target == "x86_64-pc-win32-msvc";
+ }
+
+ std::vector<std::string> getCommandLineArgs() const {
+ std::vector<std::string> Result = getCommandLineArgsForTesting(Language);
+ Result.push_back("-target");
+ Result.push_back(Target);
+ return Result;
+ }
+
+ std::string toString() const {
+ std::string Result;
+ llvm::raw_string_ostream OS(Result);
+ OS << "{ Language=" << Language << ", Target=" << Target << " }";
+ return OS.str();
+ }
+
+ friend std::ostream &operator<<(std::ostream &OS,
+ const TestClangConfig &ClangConfig) {
+ return OS << ClangConfig.toString();
+ }
+
+ static std::vector<TestClangConfig> &allConfigs() {
+ static std::vector<TestClangConfig> all_configs = []() {
+ std::vector<TestClangConfig> all_configs;
+ for (TestLanguage lang : {Lang_C, Lang_C89, Lang_CXX, Lang_CXX11,
+ Lang_CXX14, Lang_CXX17, Lang_CXX2a}) {
+ TestClangConfig config;
+ config.Language = lang;
+ config.Target = "x86_64-pc-linux-gnu";
+ all_configs.push_back(config);
+
+ // Windows target is interesting to test because it enables
+ // `-fdelayed-template-parsing`.
+ config.Target = "x86_64-pc-win32-msvc";
+ all_configs.push_back(config);
+ }
+ return all_configs;
+ }();
+ return all_configs;
+ }
+};
+
+class SyntaxTreeTest : public ::testing::Test,
+ public ::testing::WithParamInterface<TestClangConfig> {
protected:
// Build a syntax tree for the code.
- syntax::TranslationUnit *
- buildTree(llvm::StringRef Code,
- const std::string &Target = "x86_64-pc-linux-gnu") {
+ syntax::TranslationUnit *buildTree(llvm::StringRef Code,
+ const TestClangConfig &ClangConfig) {
// FIXME: this code is almost the identical to the one in TokensTest. Share
// it.
class BuildSyntaxTree : public ASTConsumer {
diag::Severity::Ignored, SourceLocation());
// Prepare to run a compiler.
- std::vector<const char *> Args = {
- "syntax-test", "-target", Target.c_str(),
- FileName, "-fsyntax-only", "-std=c++17",
+ std::vector<std::string> Args = {
+ "syntax-test",
+ "-fsyntax-only",
};
- Invocation = createInvocationFromCommandLine(Args, Diags, FS);
+ llvm::copy(ClangConfig.getCommandLineArgs(), std::back_inserter(Args));
+ Args.push_back(FileName);
+
+ std::vector<const char *> ArgsCStr;
+ for (const std::string &arg : Args) {
+ ArgsCStr.push_back(arg.c_str());
+ }
+
+ Invocation = createInvocationFromCommandLine(ArgsCStr, Diags, FS);
assert(Invocation);
Invocation->getFrontendOpts().DisableFree = false;
Invocation->getPreprocessorOpts().addRemappedFile(
return Root;
}
- void expectTreeDumpEqual(StringRef Code, StringRef Tree,
- bool RunWithDelayedTemplateParsing = true) {
+ void expectTreeDumpEqual(StringRef Code, StringRef Tree) {
+ SCOPED_TRACE(llvm::join(GetParam().getCommandLineArgs(), " "));
SCOPED_TRACE(Code);
- std::string Expected = Tree.trim().str();
-
- // We want to run the test with -fdelayed-template-parsing enabled and
- // disabled, therefore we use these representative targets that differ in
- // the default value.
- // We are not passing -fdelayed-template-parsing directly but we are using
- // the `-target` to improve coverage and discover differences in behavior
- // early.
- for (const std::string Target :
- {"x86_64-pc-linux-gnu", "x86_64-pc-win32-msvc"}) {
- if (!RunWithDelayedTemplateParsing &&
- Target == "x86_64-pc-win32-msvc") {
- continue;
- }
- auto *Root = buildTree(Code, Target);
- EXPECT_EQ(Diags->getClient()->getNumErrors(), 0u)
- << "Source file has syntax errors, they were printed to the test log";
- std::string Actual = std::string(StringRef(Root->dump(*Arena)).trim());
- EXPECT_EQ(Expected, Actual)
- << "for target " << Target << " the resulting dump is:\n"
- << Actual;
- }
+ auto *Root = buildTree(Code, GetParam());
+ EXPECT_EQ(Diags->getClient()->getNumErrors(), 0u)
+ << "Source file has syntax errors, they were printed to the test log";
+ std::string Actual = std::string(StringRef(Root->dump(*Arena)).trim());
+ EXPECT_EQ(Tree.trim().str(), Actual);
}
// Adds a file to the test VFS.
std::unique_ptr<syntax::Arena> Arena;
};
-TEST_F(SyntaxTreeTest, Simple) {
+TEST_P(SyntaxTreeTest, Simple) {
expectTreeDumpEqual(
R"cpp(
int main() {}
)txt");
}
-TEST_F(SyntaxTreeTest, SimpleVariable) {
+TEST_P(SyntaxTreeTest, SimpleVariable) {
expectTreeDumpEqual(
R"cpp(
int a;
)txt");
}
-TEST_F(SyntaxTreeTest, SimpleFunction) {
+TEST_P(SyntaxTreeTest, SimpleFunction) {
expectTreeDumpEqual(
R"cpp(
void foo(int a, int b) {}
)txt");
}
-TEST_F(SyntaxTreeTest, If) {
+TEST_P(SyntaxTreeTest, If) {
expectTreeDumpEqual(
R"cpp(
int main() {
- if (true) {}
- if (true) {} else if (false) {}
+ if (1) {}
+ if (1) {} else if (0) {}
}
)cpp",
R"txt(
| |-if
| |-(
| |-UnknownExpression
- | | `-true
+ | | `-1
| |-)
| `-CompoundStatement
| |-{
| |-if
| |-(
| |-UnknownExpression
- | | `-true
+ | | `-1
| |-)
| |-CompoundStatement
| | |-{
| |-if
| |-(
| |-UnknownExpression
- | | `-false
+ | | `-0
| |-)
| `-CompoundStatement
| |-{
)txt");
}
-TEST_F(SyntaxTreeTest, For) {
+TEST_P(SyntaxTreeTest, For) {
expectTreeDumpEqual(
R"cpp(
void test() {
)txt");
}
-TEST_F(SyntaxTreeTest, RangeBasedFor) {
+TEST_P(SyntaxTreeTest, RangeBasedFor) {
+ if (!GetParam().isCXX11OrLater()) {
+ return;
+ }
expectTreeDumpEqual(
R"cpp(
void test() {
int a[3];
- for (int x : a) ;
+ for (int x : a)
+ ;
}
)cpp",
R"txt(
)txt");
}
-TEST_F(SyntaxTreeTest, DeclarationStatement) {
+TEST_P(SyntaxTreeTest, DeclarationStatement) {
expectTreeDumpEqual("void test() { int a = 10; }",
R"txt(
*: TranslationUnit
)txt");
}
-TEST_F(SyntaxTreeTest, Switch) {
+TEST_P(SyntaxTreeTest, Switch) {
expectTreeDumpEqual(
R"cpp(
void test() {
- switch (true) {
+ switch (1) {
case 0:
default:;
}
| |-switch
| |-(
| |-UnknownExpression
- | | `-true
+ | | `-1
| |-)
| `-CompoundStatement
| |-{
)txt");
}
-TEST_F(SyntaxTreeTest, While) {
+TEST_P(SyntaxTreeTest, While) {
expectTreeDumpEqual(
R"cpp(
void test() {
- while (true) { continue; break; }
+ while (1) { continue; break; }
}
)cpp",
R"txt(
| |-while
| |-(
| |-UnknownExpression
- | | `-true
+ | | `-1
| |-)
| `-CompoundStatement
| |-{
)txt");
}
-TEST_F(SyntaxTreeTest, UnhandledStatement) {
+TEST_P(SyntaxTreeTest, UnhandledStatement) {
// Unhandled statements should end up as 'unknown statement'.
// This example uses a 'label statement', which does not yet have a syntax
// counterpart.
)txt");
}
-TEST_F(SyntaxTreeTest, Expressions) {
+TEST_P(SyntaxTreeTest, Expressions) {
// expressions should be wrapped in 'ExpressionStatement' when they appear
// in a statement position.
expectTreeDumpEqual(
R"cpp(
void test() {
test();
- if (true) test(); else test();
+ if (1) test(); else test();
}
)cpp",
R"txt(
| |-if
| |-(
| |-UnknownExpression
- | | `-true
+ | | `-1
| |-)
| |-ExpressionStatement
| | |-UnknownExpression
)txt");
}
-TEST_F(SyntaxTreeTest, PostfixUnaryOperator) {
+TEST_P(SyntaxTreeTest, PostfixUnaryOperator) {
expectTreeDumpEqual(
R"cpp(
void test(int a) {
)txt");
}
-TEST_F(SyntaxTreeTest, PrefixUnaryOperator) {
+TEST_P(SyntaxTreeTest, PrefixUnaryOperator) {
+ if (!GetParam().isCXX()) {
+ // TODO: Split parts that depend on C++ into a separate test.
+ return;
+ }
expectTreeDumpEqual(
R"cpp(
void test(int a, int *ap, bool b) {
)txt");
}
-TEST_F(SyntaxTreeTest, BinaryOperator) {
+TEST_P(SyntaxTreeTest, BinaryOperator) {
+ if (!GetParam().isCXX()) {
+ // TODO: Split parts that depend on C++ into a separate test.
+ return;
+ }
expectTreeDumpEqual(
R"cpp(
void test(int a) {
)txt");
}
-TEST_F(SyntaxTreeTest, NestedBinaryOperator) {
+TEST_P(SyntaxTreeTest, NestedBinaryOperator) {
expectTreeDumpEqual(
R"cpp(
void test(int a, int b) {
)txt");
}
-TEST_F(SyntaxTreeTest, UserDefinedBinaryOperator) {
+TEST_P(SyntaxTreeTest, UserDefinedBinaryOperator) {
+ if (!GetParam().isCXX()) {
+ return;
+ }
expectTreeDumpEqual(
R"cpp(
struct X {
)txt");
}
-TEST_F(SyntaxTreeTest, MultipleDeclaratorsGrouping) {
+TEST_P(SyntaxTreeTest, MultipleDeclaratorsGrouping) {
expectTreeDumpEqual(
R"cpp(
int *a, b; int *c, d;
)txt");
}
-TEST_F(SyntaxTreeTest, MultipleDeclaratorsGroupingTypedef) {
+TEST_P(SyntaxTreeTest, MultipleDeclaratorsGroupingTypedef) {
expectTreeDumpEqual(
R"cpp(
typedef int *a, b;
)txt");
}
-TEST_F(SyntaxTreeTest, MultipleDeclaratorsInsideStatement) {
+TEST_P(SyntaxTreeTest, MultipleDeclaratorsInsideStatement) {
expectTreeDumpEqual(
R"cpp(
void foo() {
)txt");
}
-TEST_F(SyntaxTreeTest, Namespaces) {
+TEST_P(SyntaxTreeTest, Namespaces) {
+ if (!GetParam().isCXX()) {
+ return;
+ }
expectTreeDumpEqual(
R"cpp(
namespace a { namespace b {} }
)txt");
}
-TEST_F(SyntaxTreeTest, UsingDirective) {
+TEST_P(SyntaxTreeTest, UsingDirective) {
+ if (!GetParam().isCXX()) {
+ return;
+ }
expectTreeDumpEqual(
R"cpp(
namespace ns {}
)txt");
}
-TEST_F(SyntaxTreeTest, UsingDeclaration) {
+TEST_P(SyntaxTreeTest, UsingDeclaration) {
+ if (!GetParam().isCXX()) {
+ return;
+ }
expectTreeDumpEqual(
R"cpp(
namespace ns { int a; }
)txt");
}
-TEST_F(SyntaxTreeTest, FreeStandingClasses) {
+TEST_P(SyntaxTreeTest, FreeStandingClasses) {
// Free-standing classes, must live inside a SimpleDeclaration.
expectTreeDumpEqual(
R"cpp(
)txt");
}
-TEST_F(SyntaxTreeTest, Templates) {
+TEST_P(SyntaxTreeTest, Templates) {
+ if (!GetParam().isCXX()) {
+ return;
+ }
+ if (GetParam().hasDelayedTemplateParsing()) {
+ // FIXME: Make this test work on Windows by generating the expected syntax
+ // tree when `-fdelayed-template-parsing` is active.
+ return;
+ }
expectTreeDumpEqual(
R"cpp(
template <class T> struct cls {};
`-CompoundStatement
|-{
`-}
-)txt",
- // FIXME: Make this test work on windows by generating the expected Syntax
- // tree when -fdelayed-template-parsing is active.
- /*RunWithDelayedTemplateParsing=*/false);
+)txt");
}
-TEST_F(SyntaxTreeTest, NestedTemplates) {
+TEST_P(SyntaxTreeTest, NestedTemplates) {
+ if (!GetParam().isCXX()) {
+ return;
+ }
expectTreeDumpEqual(
R"cpp(
template <class T>
)txt");
}
-TEST_F(SyntaxTreeTest, Templates2) {
+TEST_P(SyntaxTreeTest, Templates2) {
+ if (!GetParam().isCXX()) {
+ return;
+ }
expectTreeDumpEqual(
R"cpp(
template <class T> struct X { struct Y; };
)txt");
}
-TEST_F(SyntaxTreeTest, TemplatesUsingUsing) {
+TEST_P(SyntaxTreeTest, TemplatesUsingUsing) {
+ if (!GetParam().isCXX()) {
+ return;
+ }
expectTreeDumpEqual(
R"cpp(
template <class T> struct X {
)txt");
}
-TEST_F(SyntaxTreeTest, ExplicitTemplateInstantations) {
+TEST_P(SyntaxTreeTest, ExplicitTemplateInstantations) {
+ if (!GetParam().isCXX()) {
+ return;
+ }
expectTreeDumpEqual(
R"cpp(
template <class T> struct X {};
)txt");
}
-TEST_F(SyntaxTreeTest, UsingType) {
+TEST_P(SyntaxTreeTest, UsingType) {
+ if (!GetParam().isCXX()) {
+ return;
+ }
expectTreeDumpEqual(
R"cpp(
using type = int;
)txt");
}
-TEST_F(SyntaxTreeTest, EmptyDeclaration) {
+TEST_P(SyntaxTreeTest, EmptyDeclaration) {
expectTreeDumpEqual(
R"cpp(
;
)txt");
}
-TEST_F(SyntaxTreeTest, StaticAssert) {
+TEST_P(SyntaxTreeTest, StaticAssert) {
+ if (!GetParam().isCXX11OrLater()) {
+ return;
+ }
expectTreeDumpEqual(
R"cpp(
static_assert(true, "message");
)txt");
}
-TEST_F(SyntaxTreeTest, ExternC) {
+TEST_P(SyntaxTreeTest, ExternC) {
+ if (!GetParam().isCXX()) {
+ return;
+ }
expectTreeDumpEqual(
R"cpp(
extern "C" int a;
)txt");
}
-TEST_F(SyntaxTreeTest, NonModifiableNodes) {
+TEST_P(SyntaxTreeTest, NonModifiableNodes) {
// Some nodes are non-modifiable, they are marked with 'I:'.
expectTreeDumpEqual(
R"cpp(
)txt");
}
-TEST_F(SyntaxTreeTest, ModifiableNodes) {
+TEST_P(SyntaxTreeTest, ModifiableNodes) {
// All nodes can be mutated.
expectTreeDumpEqual(
R"cpp(
)txt");
}
-TEST_F(SyntaxTreeTest, ArraySubscriptsInDeclarators) {
+TEST_P(SyntaxTreeTest, ArraySubscriptsInDeclarators) {
expectTreeDumpEqual(
R"cpp(
int a[10];
`-; )txt");
}
-TEST_F(SyntaxTreeTest, ParameterListsInDeclarators) {
+TEST_P(SyntaxTreeTest, ParameterListsInDeclarators) {
+ if (!GetParam().isCXX()) {
+ // TODO: Split parts that depend on C++ into a separate test.
+ return;
+ }
expectTreeDumpEqual(
R"cpp(
struct Test {
)txt");
}
-TEST_F(SyntaxTreeTest, TrailingConst) {
+TEST_P(SyntaxTreeTest, TrailingConst) {
+ if (!GetParam().isCXX()) {
+ // TODO: Split parts that depend on C++ into a separate test.
+ return;
+ }
expectTreeDumpEqual(
R"cpp(
struct X {
)txt");
}
-TEST_F(SyntaxTreeTest, TrailingReturn) {
+TEST_P(SyntaxTreeTest, TrailingReturn) {
+ if (!GetParam().isCXX11OrLater()) {
+ return;
+ }
expectTreeDumpEqual(
R"cpp(
auto foo() -> int;
)txt");
}
-TEST_F(SyntaxTreeTest, ExceptionSpecification) {
+TEST_P(SyntaxTreeTest, ExceptionSpecification) {
+ if (!GetParam().isCXX11OrLater()) {
+ // TODO: Split parts that depend on C++11 into a separate test.
+ return;
+ }
expectTreeDumpEqual(
R"cpp(
int a() noexcept;
)txt");
}
-TEST_F(SyntaxTreeTest, DeclaratorsInParentheses) {
+TEST_P(SyntaxTreeTest, DeclaratorsInParentheses) {
expectTreeDumpEqual(
R"cpp(
int (a);
)txt");
}
-TEST_F(SyntaxTreeTest, ConstVolatileQualifiers) {
+TEST_P(SyntaxTreeTest, ConstVolatileQualifiers) {
expectTreeDumpEqual(
R"cpp(
const int west = -1;
)txt");
}
-TEST_F(SyntaxTreeTest, RangesOfDeclaratorsWithTrailingReturnTypes) {
+TEST_P(SyntaxTreeTest, RangesOfDeclaratorsWithTrailingReturnTypes) {
+ if (!GetParam().isCXX11OrLater()) {
+ return;
+ }
expectTreeDumpEqual(
R"cpp(
auto foo() -> auto(*)(int) -> double*;
)txt");
}
-TEST_F(SyntaxTreeTest, MemberPointers) {
+TEST_P(SyntaxTreeTest, MemberPointers) {
+ if (!GetParam().isCXX()) {
+ return;
+ }
expectTreeDumpEqual(
R"cpp(
struct X {};
)txt");
}
-TEST_F(SyntaxTreeTest, ComplexDeclarator) {
+TEST_P(SyntaxTreeTest, ComplexDeclarator) {
expectTreeDumpEqual(
R"cpp(
void x(char a, short (*b)(int));
)txt");
}
-TEST_F(SyntaxTreeTest, ComplexDeclarator2) {
+TEST_P(SyntaxTreeTest, ComplexDeclarator2) {
expectTreeDumpEqual(
R"cpp(
void x(char a, short (*b)(int), long (**c)(long long));
)txt");
}
-TEST_F(SyntaxTreeTest, Mutations) {
+TEST_P(SyntaxTreeTest, Mutations) {
+ if (!GetParam().isCXX11OrLater()) {
+ return;
+ }
+
using Transformation = std::function<void(
const llvm::Annotations & /*Input*/, syntax::TranslationUnit * /*Root*/)>;
auto CheckTransformation = [this](std::string Input, std::string Expected,
Transformation Transform) -> void {
llvm::Annotations Source(Input);
- auto *Root = buildTree(Source.code());
+ auto *Root = buildTree(Source.code(), GetParam());
Transform(Source, Root);
CheckTransformation(C.first, C.second, RemoveStatement);
}
-TEST_F(SyntaxTreeTest, SynthesizedNodes) {
- buildTree("");
+TEST_P(SyntaxTreeTest, SynthesizedNodes) {
+ buildTree("", GetParam());
auto *C = syntax::createPunctuation(*Arena, tok::comma);
ASSERT_NE(C, nullptr);
EXPECT_TRUE(S->isDetached());
}
+INSTANTIATE_TEST_CASE_P(SyntaxTreeTests, SyntaxTreeTest,
+ testing::ValuesIn(TestClangConfig::allConfigs()));
+
} // namespace