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/VirtualFileSystem.h"
18 #include "llvm/Support/raw_ostream.h"
20 #include "gtest/gtest.h"
23 using namespace clang
;
27 class ModuleCacheTest
: public ::testing::Test
{
28 void SetUp() override
{
29 ASSERT_FALSE(sys::fs::createUniqueDirectory("modulecache-test", TestDir
));
31 ModuleCachePath
= SmallString
<256>(TestDir
);
32 sys::path::append(ModuleCachePath
, "mcp");
33 ASSERT_FALSE(sys::fs::create_directories(ModuleCachePath
));
36 void TearDown() override
{ sys::fs::remove_directories(TestDir
); }
39 SmallString
<256> TestDir
;
40 SmallString
<256> ModuleCachePath
;
42 void addFile(StringRef Path
, StringRef Contents
) {
43 ASSERT_FALSE(sys::path::is_absolute(Path
));
45 SmallString
<256> AbsPath(TestDir
);
46 sys::path::append(AbsPath
, Path
);
50 sys::fs::create_directories(llvm::sys::path::parent_path(AbsPath
)));
51 llvm::raw_fd_ostream
OS(AbsPath
, EC
);
56 void addDuplicateFrameworks() {
57 addFile("test.m", R
"cpp(
61 addFile("frameworks/Top.framework/Headers/top.h", R
"cpp(
64 addFile("frameworks/Top.framework/Modules/module.modulemap", R
"cpp(
65 framework module Top [system] {
71 addFile("frameworks/M.framework/Headers/m.h", R
"cpp(
74 addFile("frameworks/M.framework/Modules/module.modulemap", R
"cpp(
75 framework module M [system] {
81 addFile("frameworks2/M.framework/Headers/m.h", R
"cpp(
84 addFile("frameworks2/M.framework/Modules/module.modulemap", R
"cpp(
85 framework module M [system] {
93 TEST_F(ModuleCacheTest
, CachedModuleNewPath
) {
94 addDuplicateFrameworks();
96 SmallString
<256> MCPArg("-fmodules-cache-path=");
97 MCPArg
.append(ModuleCachePath
);
98 IntrusiveRefCntPtr
<DiagnosticsEngine
> Diags
=
99 CompilerInstance::createDiagnostics(new DiagnosticOptions());
100 CreateInvocationOptions CIOpts
;
101 CIOpts
.Diags
= Diags
;
102 CIOpts
.VFS
= llvm::vfs::createPhysicalFileSystem();
104 // First run should pass with no errors
105 const char *Args
[] = {"clang", "-fmodules", "-Fframeworks",
106 MCPArg
.c_str(), "-working-directory", TestDir
.c_str(),
108 std::shared_ptr
<CompilerInvocation
> Invocation
=
109 createInvocation(Args
, CIOpts
);
110 ASSERT_TRUE(Invocation
);
111 CompilerInstance Instance
;
112 Instance
.setDiagnostics(Diags
.get());
113 Instance
.setInvocation(Invocation
);
114 SyntaxOnlyAction Action
;
115 ASSERT_TRUE(Instance
.ExecuteAction(Action
));
116 ASSERT_FALSE(Diags
->hasErrorOccurred());
118 // Now add `frameworks2` to the search path. `Top.pcm` will have a reference
119 // to the `M` from `frameworks`, but a search will find the `M` from
120 // `frameworks2` - causing a mismatch and it to be considered out of date.
122 // Normally this would be fine - `M` and the modules it depends on would be
123 // rebuilt. However, since we have a shared module cache and thus an already
124 // finalized `Top`, recompiling `Top` will cause the existing module to be
125 // removed from the cache, causing possible crashed if it is ever used.
127 // Make sure that an error occurs instead.
128 const char *Args2
[] = {"clang", "-fmodules", "-Fframeworks2",
129 "-Fframeworks", MCPArg
.c_str(), "-working-directory",
130 TestDir
.c_str(), "test.m"};
131 std::shared_ptr
<CompilerInvocation
> Invocation2
=
132 createInvocation(Args2
, CIOpts
);
133 ASSERT_TRUE(Invocation2
);
134 CompilerInstance
Instance2(Instance
.getPCHContainerOperations(),
135 &Instance
.getModuleCache());
136 Instance2
.setDiagnostics(Diags
.get());
137 Instance2
.setInvocation(Invocation2
);
138 SyntaxOnlyAction Action2
;
139 ASSERT_FALSE(Instance2
.ExecuteAction(Action2
));
140 ASSERT_TRUE(Diags
->hasErrorOccurred());
143 TEST_F(ModuleCacheTest
, CachedModuleNewPathAllowErrors
) {
144 addDuplicateFrameworks();
146 SmallString
<256> MCPArg("-fmodules-cache-path=");
147 MCPArg
.append(ModuleCachePath
);
148 IntrusiveRefCntPtr
<DiagnosticsEngine
> Diags
=
149 CompilerInstance::createDiagnostics(new DiagnosticOptions());
150 CreateInvocationOptions CIOpts
;
151 CIOpts
.Diags
= Diags
;
152 CIOpts
.VFS
= llvm::vfs::createPhysicalFileSystem();
154 // First run should pass with no errors
155 const char *Args
[] = {"clang", "-fmodules", "-Fframeworks",
156 MCPArg
.c_str(), "-working-directory", TestDir
.c_str(),
158 std::shared_ptr
<CompilerInvocation
> Invocation
=
159 createInvocation(Args
, CIOpts
);
160 ASSERT_TRUE(Invocation
);
161 CompilerInstance Instance
;
162 Instance
.setDiagnostics(Diags
.get());
163 Instance
.setInvocation(Invocation
);
164 SyntaxOnlyAction Action
;
165 ASSERT_TRUE(Instance
.ExecuteAction(Action
));
166 ASSERT_FALSE(Diags
->hasErrorOccurred());
168 // Same as `CachedModuleNewPath` but while allowing errors. This is a hard
169 // failure where the module wasn't created, so it should still fail.
170 const char *Args2
[] = {
171 "clang", "-fmodules", "-Fframeworks2",
172 "-Fframeworks", MCPArg
.c_str(), "-working-directory",
173 TestDir
.c_str(), "-Xclang", "-fallow-pcm-with-compiler-errors",
175 std::shared_ptr
<CompilerInvocation
> Invocation2
=
176 createInvocation(Args2
, CIOpts
);
177 ASSERT_TRUE(Invocation2
);
178 CompilerInstance
Instance2(Instance
.getPCHContainerOperations(),
179 &Instance
.getModuleCache());
180 Instance2
.setDiagnostics(Diags
.get());
181 Instance2
.setInvocation(Invocation2
);
182 SyntaxOnlyAction Action2
;
183 ASSERT_FALSE(Instance2
.ExecuteAction(Action2
));
184 ASSERT_TRUE(Diags
->hasErrorOccurred());
187 } // anonymous namespace