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