xref: /llvm-project/clang/unittests/Serialization/ModuleCacheTest.cpp (revision 499d0b96cb52c828e7fc4d58825b5e8b3f9931c5)
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/raw_ostream.h"
18 
19 #include "gtest/gtest.h"
20 
21 using namespace llvm;
22 using namespace clang;
23 
24 namespace {
25 
26 class ModuleCacheTest : public ::testing::Test {
27   void SetUp() override {
28     ASSERT_FALSE(sys::fs::createUniqueDirectory("modulecache-test", TestDir));
29 
30     ModuleCachePath = SmallString<256>(TestDir);
31     sys::path::append(ModuleCachePath, "mcp");
32     ASSERT_FALSE(sys::fs::create_directories(ModuleCachePath));
33   }
34 
35   void TearDown() override { sys::fs::remove_directories(TestDir); }
36 
37 public:
38   SmallString<256> TestDir;
39   SmallString<256> ModuleCachePath;
40 
41   void addFile(StringRef Path, StringRef Contents) {
42     ASSERT_FALSE(sys::path::is_absolute(Path));
43 
44     SmallString<256> AbsPath(TestDir);
45     sys::path::append(AbsPath, Path);
46 
47     std::error_code EC;
48     ASSERT_FALSE(
49         sys::fs::create_directories(llvm::sys::path::parent_path(AbsPath)));
50     llvm::raw_fd_ostream OS(AbsPath, EC);
51     ASSERT_FALSE(EC);
52     OS << Contents;
53   }
54 
55   void addDuplicateFrameworks() {
56     addFile("test.m", R"cpp(
57         @import Top;
58     )cpp");
59 
60     addFile("frameworks/Top.framework/Headers/top.h", R"cpp(
61         @import M;
62     )cpp");
63     addFile("frameworks/Top.framework/Modules/module.modulemap", R"cpp(
64         framework module Top [system] {
65           header "top.h"
66           export *
67         }
68     )cpp");
69 
70     addFile("frameworks/M.framework/Headers/m.h", R"cpp(
71         void foo();
72     )cpp");
73     addFile("frameworks/M.framework/Modules/module.modulemap", R"cpp(
74         framework module M [system] {
75           header "m.h"
76           export *
77         }
78     )cpp");
79 
80     addFile("frameworks2/M.framework/Headers/m.h", R"cpp(
81         void foo();
82     )cpp");
83     addFile("frameworks2/M.framework/Modules/module.modulemap", R"cpp(
84         framework module M [system] {
85           header "m.h"
86           export *
87         }
88     )cpp");
89   }
90 };
91 
92 TEST_F(ModuleCacheTest, CachedModuleNewPath) {
93   addDuplicateFrameworks();
94 
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;
101 
102   // First run should pass with no errors
103   const char *Args[] = {"clang",        "-fmodules",          "-Fframeworks",
104                         MCPArg.c_str(), "-working-directory", TestDir.c_str(),
105                         "test.m"};
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());
115 
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.
119   //
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.
124   //
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());
139 }
140 
141 TEST_F(ModuleCacheTest, CachedModuleNewPathAllowErrors) {
142   addDuplicateFrameworks();
143 
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;
150 
151   // First run should pass with no errors
152   const char *Args[] = {"clang",        "-fmodules",          "-Fframeworks",
153                         MCPArg.c_str(), "-working-directory", TestDir.c_str(),
154                         "test.m"};
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());
164 
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",
171       "test.m"};
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());
182 }
183 
184 } // anonymous namespace
185