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