1 | //===- unittests/Serialization/ModuleCacheTest.cpp - CI tests -------------===// |
2 | // |
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 |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
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" |
19 | |
20 | #include "gtest/gtest.h" |
21 | |
22 | using namespace llvm; |
23 | using namespace clang; |
24 | |
25 | namespace { |
26 | |
27 | class ModuleCacheTest : public ::testing::Test { |
28 | void SetUp() override { |
29 | ASSERT_FALSE(sys::fs::createUniqueDirectory("modulecache-test" , TestDir)); |
30 | |
31 | ModuleCachePath = SmallString<256>(TestDir); |
32 | sys::path::append(path&: ModuleCachePath, a: "mcp" ); |
33 | ASSERT_FALSE(sys::fs::create_directories(ModuleCachePath)); |
34 | } |
35 | |
36 | void TearDown() override { sys::fs::remove_directories(path: TestDir); } |
37 | |
38 | public: |
39 | SmallString<256> TestDir; |
40 | SmallString<256> ModuleCachePath; |
41 | |
42 | void addFile(StringRef Path, StringRef Contents) { |
43 | ASSERT_FALSE(sys::path::is_absolute(Path)); |
44 | |
45 | SmallString<256> AbsPath(TestDir); |
46 | sys::path::append(path&: AbsPath, a: Path); |
47 | |
48 | std::error_code EC; |
49 | ASSERT_FALSE( |
50 | sys::fs::create_directories(llvm::sys::path::parent_path(AbsPath))); |
51 | llvm::raw_fd_ostream OS(AbsPath, EC); |
52 | ASSERT_FALSE(EC); |
53 | OS << Contents; |
54 | } |
55 | |
56 | void addDuplicateFrameworks() { |
57 | addFile(Path: "test.m" , Contents: R"cpp( |
58 | @import Top; |
59 | )cpp" ); |
60 | |
61 | addFile(Path: "frameworks/Top.framework/Headers/top.h" , Contents: R"cpp( |
62 | @import M; |
63 | )cpp" ); |
64 | addFile(Path: "frameworks/Top.framework/Modules/module.modulemap" , Contents: R"cpp( |
65 | framework module Top [system] { |
66 | header "top.h" |
67 | export * |
68 | } |
69 | )cpp" ); |
70 | |
71 | addFile(Path: "frameworks/M.framework/Headers/m.h" , Contents: R"cpp( |
72 | void foo(); |
73 | )cpp" ); |
74 | addFile(Path: "frameworks/M.framework/Modules/module.modulemap" , Contents: R"cpp( |
75 | framework module M [system] { |
76 | header "m.h" |
77 | export * |
78 | } |
79 | )cpp" ); |
80 | |
81 | addFile(Path: "frameworks2/M.framework/Headers/m.h" , Contents: R"cpp( |
82 | void foo(); |
83 | )cpp" ); |
84 | addFile(Path: "frameworks2/M.framework/Modules/module.modulemap" , Contents: R"cpp( |
85 | framework module M [system] { |
86 | header "m.h" |
87 | export * |
88 | } |
89 | )cpp" ); |
90 | } |
91 | |
92 | std::unique_ptr<CompilerInvocation> |
93 | createInvocationAndEnableFree(ArrayRef<const char *> Args, |
94 | CreateInvocationOptions Opts) { |
95 | std::unique_ptr<CompilerInvocation> Invocation = |
96 | createInvocation(Args, Opts); |
97 | if (Invocation) |
98 | Invocation->getFrontendOpts().DisableFree = false; |
99 | |
100 | return Invocation; |
101 | } |
102 | }; |
103 | |
104 | TEST_F(ModuleCacheTest, CachedModuleNewPath) { |
105 | addDuplicateFrameworks(); |
106 | |
107 | SmallString<256> MCPArg("-fmodules-cache-path=" ); |
108 | MCPArg.append(RHS: ModuleCachePath); |
109 | IntrusiveRefCntPtr<DiagnosticsEngine> Diags = |
110 | CompilerInstance::createDiagnostics(Opts: new DiagnosticOptions()); |
111 | CreateInvocationOptions CIOpts; |
112 | CIOpts.Diags = Diags; |
113 | CIOpts.VFS = llvm::vfs::createPhysicalFileSystem(); |
114 | |
115 | // First run should pass with no errors |
116 | const char *Args[] = {"clang" , "-fmodules" , "-Fframeworks" , |
117 | MCPArg.c_str(), "-working-directory" , TestDir.c_str(), |
118 | "test.m" }; |
119 | std::shared_ptr<CompilerInvocation> Invocation = |
120 | createInvocationAndEnableFree(Args, Opts: CIOpts); |
121 | ASSERT_TRUE(Invocation); |
122 | CompilerInstance Instance; |
123 | Instance.setDiagnostics(Diags.get()); |
124 | Instance.setInvocation(Invocation); |
125 | SyntaxOnlyAction Action; |
126 | ASSERT_TRUE(Instance.ExecuteAction(Action)); |
127 | ASSERT_FALSE(Diags->hasErrorOccurred()); |
128 | |
129 | // Now add `frameworks2` to the search path. `Top.pcm` will have a reference |
130 | // to the `M` from `frameworks`, but a search will find the `M` from |
131 | // `frameworks2` - causing a mismatch and it to be considered out of date. |
132 | // |
133 | // Normally this would be fine - `M` and the modules it depends on would be |
134 | // rebuilt. However, since we have a shared module cache and thus an already |
135 | // finalized `Top`, recompiling `Top` will cause the existing module to be |
136 | // removed from the cache, causing possible crashed if it is ever used. |
137 | // |
138 | // Make sure that an error occurs instead. |
139 | const char *Args2[] = {"clang" , "-fmodules" , "-Fframeworks2" , |
140 | "-Fframeworks" , MCPArg.c_str(), "-working-directory" , |
141 | TestDir.c_str(), "test.m" }; |
142 | std::shared_ptr<CompilerInvocation> Invocation2 = |
143 | createInvocationAndEnableFree(Args: Args2, Opts: CIOpts); |
144 | ASSERT_TRUE(Invocation2); |
145 | CompilerInstance Instance2(Instance.getPCHContainerOperations(), |
146 | &Instance.getModuleCache()); |
147 | Instance2.setDiagnostics(Diags.get()); |
148 | Instance2.setInvocation(Invocation2); |
149 | SyntaxOnlyAction Action2; |
150 | ASSERT_FALSE(Instance2.ExecuteAction(Action2)); |
151 | ASSERT_TRUE(Diags->hasErrorOccurred()); |
152 | } |
153 | |
154 | TEST_F(ModuleCacheTest, CachedModuleNewPathAllowErrors) { |
155 | addDuplicateFrameworks(); |
156 | |
157 | SmallString<256> MCPArg("-fmodules-cache-path=" ); |
158 | MCPArg.append(RHS: ModuleCachePath); |
159 | IntrusiveRefCntPtr<DiagnosticsEngine> Diags = |
160 | CompilerInstance::createDiagnostics(Opts: new DiagnosticOptions()); |
161 | CreateInvocationOptions CIOpts; |
162 | CIOpts.Diags = Diags; |
163 | CIOpts.VFS = llvm::vfs::createPhysicalFileSystem(); |
164 | |
165 | // First run should pass with no errors |
166 | const char *Args[] = {"clang" , "-fmodules" , "-Fframeworks" , |
167 | MCPArg.c_str(), "-working-directory" , TestDir.c_str(), |
168 | "test.m" }; |
169 | std::shared_ptr<CompilerInvocation> Invocation = |
170 | createInvocationAndEnableFree(Args, Opts: CIOpts); |
171 | ASSERT_TRUE(Invocation); |
172 | CompilerInstance Instance; |
173 | Instance.setDiagnostics(Diags.get()); |
174 | Instance.setInvocation(Invocation); |
175 | SyntaxOnlyAction Action; |
176 | ASSERT_TRUE(Instance.ExecuteAction(Action)); |
177 | ASSERT_FALSE(Diags->hasErrorOccurred()); |
178 | |
179 | // Same as `CachedModuleNewPath` but while allowing errors. This is a hard |
180 | // failure where the module wasn't created, so it should still fail. |
181 | const char *Args2[] = { |
182 | "clang" , "-fmodules" , "-Fframeworks2" , |
183 | "-Fframeworks" , MCPArg.c_str(), "-working-directory" , |
184 | TestDir.c_str(), "-Xclang" , "-fallow-pcm-with-compiler-errors" , |
185 | "test.m" }; |
186 | std::shared_ptr<CompilerInvocation> Invocation2 = |
187 | createInvocationAndEnableFree(Args: Args2, Opts: CIOpts); |
188 | ASSERT_TRUE(Invocation2); |
189 | CompilerInstance Instance2(Instance.getPCHContainerOperations(), |
190 | &Instance.getModuleCache()); |
191 | Instance2.setDiagnostics(Diags.get()); |
192 | Instance2.setInvocation(Invocation2); |
193 | SyntaxOnlyAction Action2; |
194 | ASSERT_FALSE(Instance2.ExecuteAction(Action2)); |
195 | ASSERT_TRUE(Diags->hasErrorOccurred()); |
196 | } |
197 | |
198 | } // anonymous namespace |
199 | |