1 //===-- CppModuleConfigurationTest.cpp ------------------------------------===//
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 "Plugins/ExpressionParser/Clang/CppModuleConfiguration.h"
10 #include "Plugins/ExpressionParser/Clang/ClangHost.h"
11 #include "TestingSupport/SubsystemRAII.h"
12 #include "lldb/Host/FileSystem.h"
13 #include "lldb/Host/HostInfo.h"
14 #include "llvm/Support/SmallVectorMemoryBuffer.h"
15 
16 #include "gmock/gmock.h"
17 #include "gtest/gtest.h"
18 
19 using namespace lldb_private;
20 
21 namespace {
22 struct CppModuleConfigurationTest : public testing::Test {
23   llvm::MemoryBufferRef m_empty_buffer;
24   llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> m_fs;
25 
CppModuleConfigurationTest__anon6affa9db0111::CppModuleConfigurationTest26   CppModuleConfigurationTest()
27       : m_empty_buffer("", "<empty buffer>"),
28         m_fs(new llvm::vfs::InMemoryFileSystem()) {}
29 
SetUp__anon6affa9db0111::CppModuleConfigurationTest30   void SetUp() override {
31     FileSystem::Initialize(m_fs);
32     HostInfo::Initialize();
33   }
34 
TearDown__anon6affa9db0111::CppModuleConfigurationTest35   void TearDown() override {
36     HostInfo::Terminate();
37     FileSystem::Terminate();
38   }
39 
40   /// Utility function turning a list of paths into a FileSpecList.
makeFiles__anon6affa9db0111::CppModuleConfigurationTest41   FileSpecList makeFiles(llvm::ArrayRef<std::string> paths) {
42     FileSpecList result;
43     for (const std::string &path : paths) {
44       result.Append(FileSpec(path, FileSpec::Style::posix));
45       if (!m_fs->addFileNoOwn(path, static_cast<time_t>(0), m_empty_buffer))
46         llvm_unreachable("Invalid test configuration?");
47     }
48     return result;
49   }
50 };
51 } // namespace
52 
53 /// Returns the Clang resource include directory.
ResourceInc()54 static std::string ResourceInc() {
55   llvm::SmallString<256> resource_dir;
56   llvm::sys::path::append(resource_dir, GetClangResourceDir().GetPath(),
57                           "include");
58   return std::string(resource_dir);
59 }
60 
61 
TEST_F(CppModuleConfigurationTest,Linux)62 TEST_F(CppModuleConfigurationTest, Linux) {
63   // Test the average Linux configuration.
64 
65   std::string usr = "/usr/include";
66   std::string libcpp = "/usr/include/c++/v1";
67   std::vector<std::string> files = {// C library
68                                     usr + "/stdio.h",
69                                     // C++ library
70                                     libcpp + "/vector",
71                                     libcpp + "/module.modulemap"};
72   CppModuleConfiguration config(makeFiles(files));
73   EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre("std"));
74   EXPECT_THAT(config.GetIncludeDirs(),
75               testing::ElementsAre(libcpp, ResourceInc(), usr));
76 }
77 
TEST_F(CppModuleConfigurationTest,Sysroot)78 TEST_F(CppModuleConfigurationTest, Sysroot) {
79   // Test that having a sysroot for the whole system works fine.
80 
81   std::string libcpp = "/home/user/sysroot/usr/include/c++/v1";
82   std::string usr = "/home/user/sysroot/usr/include";
83   std::vector<std::string> files = {// C library
84                                     usr + "/stdio.h",
85                                     // C++ library
86                                     libcpp + "/vector",
87                                     libcpp + "/module.modulemap"};
88   CppModuleConfiguration config(makeFiles(files));
89   EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre("std"));
90   EXPECT_THAT(config.GetIncludeDirs(),
91               testing::ElementsAre(libcpp, ResourceInc(), usr));
92 }
93 
TEST_F(CppModuleConfigurationTest,LinuxLocalLibCpp)94 TEST_F(CppModuleConfigurationTest, LinuxLocalLibCpp) {
95   // Test that a locally build libc++ is detected.
96 
97   std::string usr = "/usr/include";
98   std::string libcpp = "/home/user/llvm-build/include/c++/v1";
99   std::vector<std::string> files = {// C library
100                                     usr + "/stdio.h",
101                                     // C++ library
102                                     libcpp + "/vector",
103                                     libcpp + "/module.modulemap"};
104   CppModuleConfiguration config(makeFiles(files));
105   EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre("std"));
106   EXPECT_THAT(config.GetIncludeDirs(),
107               testing::ElementsAre(libcpp, ResourceInc(), usr));
108 }
109 
TEST_F(CppModuleConfigurationTest,UnrelatedLibrary)110 TEST_F(CppModuleConfigurationTest, UnrelatedLibrary) {
111   // Test that having an unrelated library in /usr/include doesn't break.
112 
113   std::string usr = "/usr/include";
114   std::string libcpp = "/home/user/llvm-build/include/c++/v1";
115   std::vector<std::string> files = {// C library
116                                     usr + "/stdio.h",
117                                     // unrelated library
118                                     usr + "/boost/vector",
119                                     // C++ library
120                                     libcpp + "/vector",
121                                     libcpp + "/module.modulemap"};
122   CppModuleConfiguration config(makeFiles(files));
123   EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre("std"));
124   EXPECT_THAT(config.GetIncludeDirs(),
125               testing::ElementsAre(libcpp, ResourceInc(), usr));
126 }
127 
TEST_F(CppModuleConfigurationTest,Xcode)128 TEST_F(CppModuleConfigurationTest, Xcode) {
129   // Test detection of libc++ coming from Xcode with generic platform names.
130 
131   std::string p = "/Applications/Xcode.app/Contents/Developer/";
132   std::string libcpp = p + "Toolchains/B.xctoolchain/usr/include/c++/v1";
133   std::string usr =
134       p + "Platforms/A.platform/Developer/SDKs/OSVers.sdk/usr/include";
135   std::vector<std::string> files = {
136       // C library
137       usr + "/stdio.h",
138       // C++ library
139       libcpp + "/vector",
140       libcpp + "/module.modulemap",
141   };
142   CppModuleConfiguration config(makeFiles(files));
143   EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre("std"));
144   EXPECT_THAT(config.GetIncludeDirs(),
145               testing::ElementsAre(libcpp, ResourceInc(), usr));
146 }
147 
TEST_F(CppModuleConfigurationTest,LibCppV2)148 TEST_F(CppModuleConfigurationTest, LibCppV2) {
149   // Test that a "v2" of libc++ is still correctly detected.
150 
151   std::string libcpp = "/usr/include/c++/v2";
152   std::vector<std::string> files = {// C library
153                                     "/usr/include/stdio.h",
154                                     // C++ library
155                                     libcpp + "/vector",
156                                     libcpp + "/module.modulemap"};
157   CppModuleConfiguration config(makeFiles(files));
158   EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre("std"));
159   EXPECT_THAT(config.GetIncludeDirs(),
160               testing::ElementsAre("/usr/include/c++/v2", ResourceInc(),
161                                    "/usr/include"));
162 }
163 
TEST_F(CppModuleConfigurationTest,UnknownLibCppFile)164 TEST_F(CppModuleConfigurationTest, UnknownLibCppFile) {
165   // Test that having some unknown file in the libc++ path doesn't break
166   // anything.
167 
168   std::string libcpp = "/usr/include/c++/v1";
169   std::vector<std::string> files = {// C library
170                                     "/usr/include/stdio.h",
171                                     // C++ library
172                                     libcpp + "/non_existing_file",
173                                     libcpp + "/module.modulemap",
174                                     libcpp + "/vector"};
175   CppModuleConfiguration config(makeFiles(files));
176   EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre("std"));
177   EXPECT_THAT(config.GetIncludeDirs(),
178               testing::ElementsAre("/usr/include/c++/v1", ResourceInc(),
179                                    "/usr/include"));
180 }
181 
TEST_F(CppModuleConfigurationTest,MissingUsrInclude)182 TEST_F(CppModuleConfigurationTest, MissingUsrInclude) {
183   // Test that we don't load 'std' if we can't find the C standard library.
184 
185   std::string libcpp = "/usr/include/c++/v1";
186   std::vector<std::string> files = {// C++ library
187                                     libcpp + "/vector",
188                                     libcpp + "/module.modulemap"};
189   CppModuleConfiguration config(makeFiles(files));
190   EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre());
191   EXPECT_THAT(config.GetIncludeDirs(), testing::ElementsAre());
192 }
193 
TEST_F(CppModuleConfigurationTest,MissingLibCpp)194 TEST_F(CppModuleConfigurationTest, MissingLibCpp) {
195   // Test that we don't load 'std' if we don't have a libc++.
196 
197   std::string usr = "/usr/include";
198   std::vector<std::string> files = {
199       // C library
200       usr + "/stdio.h",
201   };
202   CppModuleConfiguration config(makeFiles(files));
203   EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre());
204   EXPECT_THAT(config.GetIncludeDirs(), testing::ElementsAre());
205 }
206 
TEST_F(CppModuleConfigurationTest,IgnoreLibStdCpp)207 TEST_F(CppModuleConfigurationTest, IgnoreLibStdCpp) {
208   // Test that we don't do anything bad when we encounter libstdc++ paths.
209 
210   std::string usr = "/usr/include";
211   std::vector<std::string> files = {
212       // C library
213       usr + "/stdio.h",
214       // C++ library
215       usr + "/c++/8.0.1/vector",
216   };
217   CppModuleConfiguration config(makeFiles(files));
218   EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre());
219   EXPECT_THAT(config.GetIncludeDirs(), testing::ElementsAre());
220 }
221 
TEST_F(CppModuleConfigurationTest,AmbiguousCLib)222 TEST_F(CppModuleConfigurationTest, AmbiguousCLib) {
223   // Test that we don't do anything when we are not sure where the
224   // right C standard library is.
225 
226   std::string usr1 = "/usr/include";
227   std::string usr2 = "/usr/include/other/path";
228   std::string libcpp = usr1 + "c++/v1";
229   std::vector<std::string> files = {
230       // First C library
231       usr1 + "/stdio.h",
232       // Second C library
233       usr2 + "/stdio.h",
234       // C++ library
235       libcpp + "/vector",
236       libcpp + "/module.modulemap",
237   };
238   CppModuleConfiguration config(makeFiles(files));
239   EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre());
240   EXPECT_THAT(config.GetIncludeDirs(), testing::ElementsAre());
241 }
242 
TEST_F(CppModuleConfigurationTest,AmbiguousLibCpp)243 TEST_F(CppModuleConfigurationTest, AmbiguousLibCpp) {
244   // Test that we don't do anything when we are not sure where the
245   // right libc++ is.
246 
247   std::string usr = "/usr/include";
248   std::string libcpp1 = usr + "c++/v1";
249   std::string libcpp2 = usr + "c++/v2";
250   std::vector<std::string> files = {
251       // C library
252       usr + "/stdio.h",
253       // First C++ library
254       libcpp1 + "/vector",
255       libcpp1 + "/module.modulemap",
256       // Second C++ library
257       libcpp2 + "/vector",
258       libcpp2 + "/module.modulemap",
259   };
260   CppModuleConfiguration config(makeFiles(files));
261   EXPECT_THAT(config.GetImportedModules(), testing::ElementsAre());
262   EXPECT_THAT(config.GetIncludeDirs(), testing::ElementsAre());
263 }
264