1 //===- unittests/Serialization/ModuleCacheTest.cpp - CI tests -------------===//
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/Basic/FileManager.h"
10 #include "clang/Frontend/CompilerInstance.h"
11 #include "clang/Frontend/CompilerInvocation.h"
12 #include "clang/Frontend/FrontendActions.h"
13 #include "clang/Frontend/Utils.h"
14 #include "clang/Lex/HeaderSearch.h"
15 #include "llvm/ADT/SmallString.h"
16 #include "llvm/Support/FileSystem.h"
17 #include "llvm/Support/raw_ostream.h"
19 #include "gtest/gtest.h"
22 using namespace clang
;
26 class ModuleCacheTest
: public ::testing::Test
{
27 void SetUp() override
{
28 ASSERT_FALSE(sys::fs::createUniqueDirectory("modulecache-test", TestDir
));
30 ModuleCachePath
= SmallString
<256>(TestDir
);
31 sys::path::append(ModuleCachePath
, "mcp");
32 ASSERT_FALSE(sys::fs::create_directories(ModuleCachePath
));
35 void TearDown() override
{ sys::fs::remove_directories(TestDir
); }
38 SmallString
<256> TestDir
;
39 SmallString
<256> ModuleCachePath
;
41 void addFile(StringRef Path
, StringRef Contents
) {
42 ASSERT_FALSE(sys::path::is_absolute(Path
));
44 SmallString
<256> AbsPath(TestDir
);
45 sys::path::append(AbsPath
, Path
);
49 sys::fs::create_directories(llvm::sys::path::parent_path(AbsPath
)));
50 llvm::raw_fd_ostream
OS(AbsPath
, EC
);
55 void addDuplicateFrameworks() {
56 addFile("test.m", R
"cpp(
60 addFile("frameworks/Top.framework/Headers/top.h", R
"cpp(
63 addFile("frameworks/Top.framework/Modules/module.modulemap", R
"cpp(
64 framework module Top [system] {
70 addFile("frameworks/M.framework/Headers/m.h", R
"cpp(
73 addFile("frameworks/M.framework/Modules/module.modulemap", R
"cpp(
74 framework module M [system] {
80 addFile("frameworks2/M.framework/Headers/m.h", R
"cpp(
83 addFile("frameworks2/M.framework/Modules/module.modulemap", R
"cpp(
84 framework module M [system] {
92 TEST_F(ModuleCacheTest
, CachedModuleNewPath
) {
93 addDuplicateFrameworks();
95 SmallString
<256> MCPArg("-fmodules-cache-path=");
96 MCPArg
.append(ModuleCachePath
);
97 IntrusiveRefCntPtr
<DiagnosticsEngine
> Diags
=
98 CompilerInstance::createDiagnostics(new DiagnosticOptions());
99 CreateInvocationOptions CIOpts
;
100 CIOpts
.Diags
= Diags
;
102 // First run should pass with no errors
103 const char *Args
[] = {"clang", "-fmodules", "-Fframeworks",
104 MCPArg
.c_str(), "-working-directory", TestDir
.c_str(),
106 std::shared_ptr
<CompilerInvocation
> Invocation
=
107 createInvocation(Args
, CIOpts
);
108 ASSERT_TRUE(Invocation
);
109 CompilerInstance Instance
;
110 Instance
.setDiagnostics(Diags
.get());
111 Instance
.setInvocation(Invocation
);
112 SyntaxOnlyAction Action
;
113 ASSERT_TRUE(Instance
.ExecuteAction(Action
));
114 ASSERT_FALSE(Diags
->hasErrorOccurred());
116 // Now add `frameworks2` to the search path. `Top.pcm` will have a reference
117 // to the `M` from `frameworks`, but a search will find the `M` from
118 // `frameworks2` - causing a mismatch and it to be considered out of date.
120 // Normally this would be fine - `M` and the modules it depends on would be
121 // rebuilt. However, since we have a shared module cache and thus an already
122 // finalized `Top`, recompiling `Top` will cause the existing module to be
123 // removed from the cache, causing possible crashed if it is ever used.
125 // Make sure that an error occurs instead.
126 const char *Args2
[] = {"clang", "-fmodules", "-Fframeworks2",
127 "-Fframeworks", MCPArg
.c_str(), "-working-directory",
128 TestDir
.c_str(), "test.m"};
129 std::shared_ptr
<CompilerInvocation
> Invocation2
=
130 createInvocation(Args2
, CIOpts
);
131 ASSERT_TRUE(Invocation2
);
132 CompilerInstance
Instance2(Instance
.getPCHContainerOperations(),
133 &Instance
.getModuleCache());
134 Instance2
.setDiagnostics(Diags
.get());
135 Instance2
.setInvocation(Invocation2
);
136 SyntaxOnlyAction Action2
;
137 ASSERT_FALSE(Instance2
.ExecuteAction(Action2
));
138 ASSERT_TRUE(Diags
->hasErrorOccurred());
141 TEST_F(ModuleCacheTest
, CachedModuleNewPathAllowErrors
) {
142 addDuplicateFrameworks();
144 SmallString
<256> MCPArg("-fmodules-cache-path=");
145 MCPArg
.append(ModuleCachePath
);
146 IntrusiveRefCntPtr
<DiagnosticsEngine
> Diags
=
147 CompilerInstance::createDiagnostics(new DiagnosticOptions());
148 CreateInvocationOptions CIOpts
;
149 CIOpts
.Diags
= Diags
;
151 // First run should pass with no errors
152 const char *Args
[] = {"clang", "-fmodules", "-Fframeworks",
153 MCPArg
.c_str(), "-working-directory", TestDir
.c_str(),
155 std::shared_ptr
<CompilerInvocation
> Invocation
=
156 createInvocation(Args
, CIOpts
);
157 ASSERT_TRUE(Invocation
);
158 CompilerInstance Instance
;
159 Instance
.setDiagnostics(Diags
.get());
160 Instance
.setInvocation(Invocation
);
161 SyntaxOnlyAction Action
;
162 ASSERT_TRUE(Instance
.ExecuteAction(Action
));
163 ASSERT_FALSE(Diags
->hasErrorOccurred());
165 // Same as `CachedModuleNewPath` but while allowing errors. This is a hard
166 // failure where the module wasn't created, so it should still fail.
167 const char *Args2
[] = {
168 "clang", "-fmodules", "-Fframeworks2",
169 "-Fframeworks", MCPArg
.c_str(), "-working-directory",
170 TestDir
.c_str(), "-Xclang", "-fallow-pcm-with-compiler-errors",
172 std::shared_ptr
<CompilerInvocation
> Invocation2
=
173 createInvocation(Args2
, CIOpts
);
174 ASSERT_TRUE(Invocation2
);
175 CompilerInstance
Instance2(Instance
.getPCHContainerOperations(),
176 &Instance
.getModuleCache());
177 Instance2
.setDiagnostics(Diags
.get());
178 Instance2
.setInvocation(Invocation2
);
179 SyntaxOnlyAction Action2
;
180 ASSERT_FALSE(Instance2
.ExecuteAction(Action2
));
181 ASSERT_TRUE(Diags
->hasErrorOccurred());
184 } // anonymous namespace