xref: /llvm-project/clang/unittests/Serialization/ModuleCacheTest.cpp (revision 476e7c49ecb762df1d68273696b06c36feb0fd96)
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(ModuleCachePath, "mcp");
33     ASSERT_FALSE(sys::fs::create_directories(ModuleCachePath));
34   }
35 
36   void TearDown() override { sys::fs::remove_directories(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(AbsPath, 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("test.m", R"cpp(
58         @import Top;
59     )cpp");
60 
61     addFile("frameworks/Top.framework/Headers/top.h", R"cpp(
62         @import M;
63     )cpp");
64     addFile("frameworks/Top.framework/Modules/module.modulemap", R"cpp(
65         framework module Top [system] {
66           header "top.h"
67           export *
68         }
69     )cpp");
70 
71     addFile("frameworks/M.framework/Headers/m.h", R"cpp(
72         void foo();
73     )cpp");
74     addFile("frameworks/M.framework/Modules/module.modulemap", R"cpp(
75         framework module M [system] {
76           header "m.h"
77           export *
78         }
79     )cpp");
80 
81     addFile("frameworks2/M.framework/Headers/m.h", R"cpp(
82         void foo();
83     )cpp");
84     addFile("frameworks2/M.framework/Modules/module.modulemap", R"cpp(
85         framework module M [system] {
86           header "m.h"
87           export *
88         }
89     )cpp");
90   }
91 };
92 
93 TEST_F(ModuleCacheTest, CachedModuleNewPath) {
94   addDuplicateFrameworks();
95 
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();
103 
104   // First run should pass with no errors
105   const char *Args[] = {"clang",        "-fmodules",          "-Fframeworks",
106                         MCPArg.c_str(), "-working-directory", TestDir.c_str(),
107                         "test.m"};
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());
117 
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.
121   //
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.
126   //
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());
141 }
142 
143 TEST_F(ModuleCacheTest, CachedModuleNewPathAllowErrors) {
144   addDuplicateFrameworks();
145 
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 
153   // First run should pass with no errors
154   const char *Args[] = {"clang",        "-fmodules",          "-Fframeworks",
155                         MCPArg.c_str(), "-working-directory", TestDir.c_str(),
156                         "test.m"};
157   std::shared_ptr<CompilerInvocation> Invocation =
158       createInvocation(Args, CIOpts);
159   ASSERT_TRUE(Invocation);
160   CompilerInstance Instance;
161   Instance.setDiagnostics(Diags.get());
162   Instance.setInvocation(Invocation);
163   SyntaxOnlyAction Action;
164   ASSERT_TRUE(Instance.ExecuteAction(Action));
165   ASSERT_FALSE(Diags->hasErrorOccurred());
166 
167   // Same as `CachedModuleNewPath` but while allowing errors. This is a hard
168   // failure where the module wasn't created, so it should still fail.
169   const char *Args2[] = {
170       "clang",         "-fmodules",    "-Fframeworks2",
171       "-Fframeworks",  MCPArg.c_str(), "-working-directory",
172       TestDir.c_str(), "-Xclang",      "-fallow-pcm-with-compiler-errors",
173       "test.m"};
174   std::shared_ptr<CompilerInvocation> Invocation2 =
175       createInvocation(Args2, CIOpts);
176   ASSERT_TRUE(Invocation2);
177   CompilerInstance Instance2(Instance.getPCHContainerOperations(),
178                              &Instance.getModuleCache());
179   Instance2.setDiagnostics(Diags.get());
180   Instance2.setInvocation(Invocation2);
181   SyntaxOnlyAction Action2;
182   ASSERT_FALSE(Instance2.ExecuteAction(Action2));
183   ASSERT_TRUE(Diags->hasErrorOccurred());
184 }
185 
186 } // anonymous namespace
187