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