Skip to content

Commit a014cdd

Browse files
committed
tests: add ClangTypeParserTest
Currently there is no testing for ClangTypeParser even though it's used in production. This is because adding integration tests is very hard: they require testing the build time behaviour at runtime, or else they'd be build failures intead of test failures. There's a PR available for integration tests but it's incomplete. In contrast ClangTypeParser can be sort of unit tested. This follows the structure of `test/test_drgn_parser.cpp` with some differences. There is a tonne of boilerplate for setting up the Clang tool, and this set of testing operates on type names instead of OID functions. The new tests are also incredibly slow as they compile the entire `integration_test_target.cpp` (which is huge) for every test case. I don't think this is avoidable without compromising the separation of the tests somewhat due to the way Clang tooling forces the code to be structured. Currently I can't run these tests locally on a Meta devserver due to some weirdness with the internal build and the `compile_commands.json` file. They run in the CI and on any other open source machine though so I'm happy to merge it - it's still useful. I'm going to close the PR to change the devserver build given I'll be unable to follow up if it ends up being bad. Test plan: - CI
1 parent a4723fb commit a014cdd

File tree

3 files changed

+221
-1
lines changed

3 files changed

+221
-1
lines changed

.circleci/config.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ workflows:
1212
name: test-gcc
1313
requires:
1414
- build-gcc
15-
exclude_regex: ".*inheritance_polymorphic.*|.*arrays_member_int0"
15+
exclude_regex: ".*inheritance_polymorphic.*|.*arrays_member_int0|ClangTypeParserTest.*"
1616
- coverage:
1717
name: coverage
1818
requires:
@@ -104,6 +104,7 @@ jobs:
104104
paths:
105105
- build/*
106106
- extern/*
107+
- include/*
107108
- types/*
108109

109110
test:
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
#include <clang/AST/Mangle.h>
2+
#include <clang/AST/QualTypeNames.h>
3+
#include <clang/Frontend/CompilerInstance.h>
4+
#include <clang/Frontend/CompilerInvocation.h>
5+
#include <clang/Frontend/FrontendAction.h>
6+
#include <clang/Sema/Lookup.h>
7+
#include <clang/Sema/Sema.h>
8+
#include <clang/Tooling/CompilationDatabase.h>
9+
#include <clang/Tooling/Tooling.h>
10+
#include <gtest/gtest.h>
11+
12+
#include <range/v3/core.hpp>
13+
#include <range/v3/view/drop.hpp>
14+
#include <range/v3/view/filter.hpp>
15+
#include <range/v3/view/for_each.hpp>
16+
#include <range/v3/view/take.hpp>
17+
#include <range/v3/view/transform.hpp>
18+
#include <string>
19+
#include <string_view>
20+
21+
#include "oi/type_graph/ClangTypeParser.h"
22+
#include "oi/type_graph/Printer.h"
23+
24+
using namespace oi::detail;
25+
using namespace oi::detail::type_graph;
26+
27+
class ClangTypeParserTest : public ::testing::Test {
28+
public:
29+
std::string run(std::string_view function, ClangTypeParserOptions opt);
30+
void test(std::string_view function,
31+
std::string_view expected,
32+
ClangTypeParserOptions opts = {
33+
.chaseRawPointers = true,
34+
.readEnumValues = true,
35+
});
36+
};
37+
38+
// This stuff is a total mess. Set up factories as ClangTooling expects them.
39+
class ConsumerContext;
40+
class CreateTypeGraphConsumer;
41+
42+
class CreateTypeGraphAction : public clang::ASTFrontendAction {
43+
public:
44+
CreateTypeGraphAction(ConsumerContext& ctx_) : ctx{ctx_} {
45+
}
46+
47+
void ExecuteAction() override;
48+
std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(
49+
clang::CompilerInstance& CI, clang::StringRef file) override;
50+
51+
private:
52+
ConsumerContext& ctx;
53+
};
54+
55+
class CreateTypeGraphActionFactory
56+
: public clang::tooling::FrontendActionFactory {
57+
public:
58+
CreateTypeGraphActionFactory(ConsumerContext& ctx_) : ctx{ctx_} {
59+
}
60+
61+
std::unique_ptr<clang::FrontendAction> create() override {
62+
return std::make_unique<CreateTypeGraphAction>(ctx);
63+
}
64+
65+
private:
66+
ConsumerContext& ctx;
67+
};
68+
69+
class ConsumerContext {
70+
public:
71+
ConsumerContext(
72+
const std::vector<std::unique_ptr<ContainerInfo>>& containerInfos_)
73+
: containerInfos{containerInfos_} {
74+
}
75+
std::string_view fullyQualifiedName;
76+
ClangTypeParserOptions opts;
77+
const std::vector<std::unique_ptr<ContainerInfo>>& containerInfos;
78+
TypeGraph typeGraph;
79+
Type* result = nullptr;
80+
81+
private:
82+
clang::Sema* sema = nullptr;
83+
friend CreateTypeGraphConsumer;
84+
friend CreateTypeGraphAction;
85+
};
86+
87+
class CreateTypeGraphConsumer : public clang::ASTConsumer {
88+
private:
89+
ConsumerContext& ctx;
90+
91+
public:
92+
CreateTypeGraphConsumer(ConsumerContext& ctx_) : ctx{ctx_} {
93+
}
94+
95+
void HandleTranslationUnit(clang::ASTContext& Context) override {
96+
const clang::Type* type = nullptr;
97+
for (const clang::Type* ty : Context.getTypes()) {
98+
std::string fqnWithoutTemplateParams;
99+
switch (ty->getTypeClass()) {
100+
case clang::Type::Record:
101+
fqnWithoutTemplateParams = llvm::cast<const clang::RecordType>(ty)
102+
->getDecl()
103+
->getQualifiedNameAsString();
104+
break;
105+
default:
106+
continue;
107+
}
108+
if (fqnWithoutTemplateParams != ctx.fullyQualifiedName)
109+
continue;
110+
111+
type = ty;
112+
break;
113+
}
114+
EXPECT_NE(type, nullptr);
115+
116+
ClangTypeParser parser{ctx.typeGraph, ctx.containerInfos, ctx.opts};
117+
ctx.result = &parser.parse(Context, *ctx.sema, *type);
118+
}
119+
};
120+
121+
void CreateTypeGraphAction::ExecuteAction() {
122+
clang::CompilerInstance& CI = getCompilerInstance();
123+
124+
if (!CI.hasSema())
125+
CI.createSema(clang::TU_Complete, nullptr);
126+
ctx.sema = &CI.getSema();
127+
128+
clang::ASTFrontendAction::ExecuteAction();
129+
}
130+
131+
std::unique_ptr<clang::ASTConsumer> CreateTypeGraphAction::CreateASTConsumer(
132+
[[maybe_unused]] clang::CompilerInstance& CI,
133+
[[maybe_unused]] clang::StringRef file) {
134+
return std::make_unique<CreateTypeGraphConsumer>(ctx);
135+
}
136+
137+
std::string ClangTypeParserTest::run(std::string_view type,
138+
ClangTypeParserOptions opts) {
139+
std::string err{"failed to load compilation database"};
140+
auto db =
141+
clang::tooling::CompilationDatabase::loadFromDirectory(BUILD_DIR, err);
142+
if (!db) {
143+
throw std::runtime_error("failed to load compilation database");
144+
}
145+
146+
std::vector<std::unique_ptr<ContainerInfo>> cis;
147+
ConsumerContext ctx{cis};
148+
ctx.fullyQualifiedName = type;
149+
ctx.opts = std::move(opts);
150+
CreateTypeGraphActionFactory factory{ctx};
151+
152+
std::vector<std::string> sourcePaths{TARGET_CPP_PATH};
153+
clang::tooling::ClangTool tool{*db, sourcePaths};
154+
if (auto retCode = tool.run(&factory); retCode != 0) {
155+
throw std::runtime_error("clang type parsing failed");
156+
}
157+
158+
std::stringstream out;
159+
NodeTracker tracker;
160+
Printer printer{out, tracker, ctx.typeGraph.size()};
161+
printer.print(*ctx.result);
162+
163+
return std::move(out).str();
164+
}
165+
166+
void ClangTypeParserTest::test(std::string_view type,
167+
std::string_view expected,
168+
ClangTypeParserOptions opts) {
169+
std::string actual = run(type, std::move(opts));
170+
expected.remove_prefix(1); // Remove initial '\n'
171+
EXPECT_EQ(expected, actual);
172+
}
173+
174+
TEST_F(ClangTypeParserTest, SimpleStruct) {
175+
test("ns_simple::SimpleStruct", R"(
176+
[0] Struct: SimpleStruct [ns_simple::SimpleStruct] (size: 16, align: 8)
177+
Member: a (offset: 0)
178+
Primitive: int32_t
179+
Member: b (offset: 4)
180+
Primitive: int8_t
181+
Member: c (offset: 8)
182+
Primitive: int64_t
183+
)");
184+
}
185+
186+
TEST_F(ClangTypeParserTest, MemberAlignment) {
187+
test("ns_alignment::MemberAlignment", R"(
188+
[0] Struct: MemberAlignment [ns_alignment::MemberAlignment] (size: 64, align: 32)
189+
Member: c (offset: 0)
190+
Primitive: int8_t
191+
Member: c32 (offset: 32, align: 32)
192+
Primitive: int8_t
193+
)");
194+
}

test/CMakeLists.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,31 @@ target_link_libraries(test_type_graph
6969
include(GoogleTest)
7070
gtest_discover_tests(test_type_graph)
7171

72+
add_executable(test_clang_type_parser
73+
main.cpp
74+
../oi/type_graph/ClangTypeParserTest.cpp
75+
)
76+
add_dependencies(test_clang_type_parser integration_test_target)
77+
target_compile_definitions(test_clang_type_parser PRIVATE
78+
TARGET_CPP_PATH="${CMAKE_CURRENT_BINARY_DIR}/integration/integration_test_target.cpp"
79+
BUILD_DIR="${CMAKE_BINARY_DIR}"
80+
)
81+
target_link_libraries(test_clang_type_parser
82+
codegen
83+
container_info
84+
type_graph
85+
86+
range-v3
87+
88+
GTest::gmock_main
89+
)
90+
if (FORCE_LLVM_STATIC)
91+
target_link_libraries(test_clang_type_parser clangTooling ${llvm_libs})
92+
else()
93+
target_link_libraries(test_clang_type_parser clang-cpp LLVM)
94+
endif()
95+
gtest_discover_tests(test_clang_type_parser)
96+
7297
cpp_unittest(
7398
NAME test_parser
7499
SRCS test_parser.cpp

0 commit comments

Comments
 (0)