1 //===-- TestCompletion.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 "lldb/Host/FileSystem.h" 10 #include "lldb/Interpreter/CommandCompletions.h" 11 #include "lldb/Utility/StringList.h" 12 #include "lldb/Utility/TildeExpressionResolver.h" 13 14 #include "gmock/gmock.h" 15 #include "gtest/gtest.h" 16 17 #include "TestingSupport/MockTildeExpressionResolver.h" 18 #include "TestingSupport/SubsystemRAII.h" 19 #include "TestingSupport/TestUtilities.h" 20 #include "llvm/ADT/SmallString.h" 21 #include "llvm/Support/FileSystem.h" 22 #include "llvm/Support/Path.h" 23 #include "llvm/Support/raw_ostream.h" 24 25 namespace fs = llvm::sys::fs; 26 namespace path = llvm::sys::path; 27 using namespace llvm; 28 using namespace lldb_private; 29 30 namespace { 31 32 class CompletionTest : public testing::Test { 33 SubsystemRAII<FileSystem> subsystems; 34 35 protected: 36 /// Unique temporary directory in which all created filesystem entities must 37 /// be placed. It is removed at the end of the test suite. 38 SmallString<128> BaseDir; 39 40 /// The working directory that we got when starting the test. Every test 41 /// should chdir into this directory first because some tests maybe chdir 42 /// into another one during their run. 43 static SmallString<128> OriginalWorkingDir; 44 45 SmallString<128> DirFoo; 46 SmallString<128> DirFooA; 47 SmallString<128> DirFooB; 48 SmallString<128> DirFooC; 49 SmallString<128> DirBar; 50 SmallString<128> DirBaz; 51 SmallString<128> DirTestFolder; 52 SmallString<128> DirNested; 53 54 SmallString<128> FileAA; 55 SmallString<128> FileAB; 56 SmallString<128> FileAC; 57 SmallString<128> FileFoo; 58 SmallString<128> FileBar; 59 SmallString<128> FileBaz; 60 61 void SetUp() override { 62 // chdir back into the original working dir this test binary started with. 63 // A previous test may have have changed the working dir. 64 ASSERT_NO_ERROR(fs::set_current_path(OriginalWorkingDir)); 65 66 // Get the name of the current test. To prevent that by chance two tests 67 // get the same temporary directory if createUniqueDirectory fails. 68 auto test_info = ::testing::UnitTest::GetInstance()->current_test_info(); 69 ASSERT_TRUE(test_info != nullptr); 70 std::string name = test_info->name(); 71 ASSERT_NO_ERROR(fs::createUniqueDirectory("FsCompletion-" + name, BaseDir)); 72 73 const char *DirNames[] = {"foo", "fooa", "foob", "fooc", 74 "bar", "baz", "test_folder", "foo/nested"}; 75 const char *FileNames[] = {"aa1234.tmp", "ab1234.tmp", "ac1234.tmp", 76 "foo1234.tmp", "bar1234.tmp", "baz1234.tmp"}; 77 SmallString<128> *Dirs[] = {&DirFoo, &DirFooA, &DirFooB, &DirFooC, 78 &DirBar, &DirBaz, &DirTestFolder, &DirNested}; 79 for (auto Dir : llvm::zip(DirNames, Dirs)) { 80 auto &Path = *std::get<1>(Dir); 81 Path = BaseDir; 82 path::append(Path, std::get<0>(Dir)); 83 ASSERT_NO_ERROR(fs::create_directories(Path)); 84 } 85 86 SmallString<128> *Files[] = {&FileAA, &FileAB, &FileAC, 87 &FileFoo, &FileBar, &FileBaz}; 88 for (auto File : llvm::zip(FileNames, Files)) { 89 auto &Path = *std::get<1>(File); 90 Path = BaseDir; 91 path::append(Path, std::get<0>(File)); 92 int FD; 93 ASSERT_NO_ERROR(fs::createUniqueFile(Path, FD, Path)); 94 ::close(FD); 95 } 96 } 97 98 static void SetUpTestCase() { 99 ASSERT_NO_ERROR(fs::current_path(OriginalWorkingDir)); 100 } 101 102 void TearDown() override { 103 ASSERT_NO_ERROR(fs::remove_directories(BaseDir)); 104 } 105 106 static bool HasEquivalentFile(const Twine &Path, const StringList &Paths) { 107 for (size_t I = 0; I < Paths.GetSize(); ++I) { 108 if (fs::equivalent(Path, Paths[I])) 109 return true; 110 } 111 return false; 112 } 113 114 void DoDirCompletions(const Twine &Prefix, 115 StandardTildeExpressionResolver &Resolver, 116 StringList &Results) { 117 // When a partial name matches, it returns all matches. If it matches both 118 // a full name AND some partial names, it returns all of them. 119 CommandCompletions::DiskDirectories(Prefix + "foo", Results, Resolver); 120 ASSERT_EQ(4u, Results.GetSize()); 121 EXPECT_TRUE(HasEquivalentFile(DirFoo, Results)); 122 EXPECT_TRUE(HasEquivalentFile(DirFooA, Results)); 123 EXPECT_TRUE(HasEquivalentFile(DirFooB, Results)); 124 EXPECT_TRUE(HasEquivalentFile(DirFooC, Results)); 125 126 // If it matches only partial names, it still works as expected. 127 CommandCompletions::DiskDirectories(Twine(Prefix) + "b", Results, Resolver); 128 ASSERT_EQ(2u, Results.GetSize()); 129 EXPECT_TRUE(HasEquivalentFile(DirBar, Results)); 130 EXPECT_TRUE(HasEquivalentFile(DirBaz, Results)); 131 } 132 }; 133 134 SmallString<128> CompletionTest::OriginalWorkingDir; 135 } // namespace 136 137 static std::vector<std::string> toVector(const StringList &SL) { 138 std::vector<std::string> Result; 139 for (size_t Idx = 0; Idx < SL.GetSize(); ++Idx) 140 Result.push_back(SL[Idx]); 141 return Result; 142 } 143 using testing::UnorderedElementsAre; 144 145 TEST_F(CompletionTest, DirCompletionAbsolute) { 146 // All calls to DiskDirectories() return only directories, even when 147 // there are files which also match. The tests below all check this 148 // by asserting an exact result count, and verifying against known 149 // folders. 150 151 std::string Prefixes[] = {(Twine(BaseDir) + "/").str(), ""}; 152 153 StandardTildeExpressionResolver Resolver; 154 StringList Results; 155 156 // When a directory is specified that doesn't end in a slash, it searches 157 // for that directory, not items under it. 158 // Sanity check that the path we complete on exists and isn't too long. 159 CommandCompletions::DiskDirectories(Twine(BaseDir) + "/fooa", Results, 160 Resolver); 161 ASSERT_EQ(1u, Results.GetSize()); 162 EXPECT_TRUE(HasEquivalentFile(DirFooA, Results)); 163 164 CommandCompletions::DiskDirectories(Twine(BaseDir) + "/.", Results, Resolver); 165 ASSERT_EQ(0u, Results.GetSize()); 166 167 // When the same directory ends with a slash, it finds all children. 168 CommandCompletions::DiskDirectories(Prefixes[0], Results, Resolver); 169 ASSERT_EQ(7u, Results.GetSize()); 170 EXPECT_TRUE(HasEquivalentFile(DirFoo, Results)); 171 EXPECT_TRUE(HasEquivalentFile(DirFooA, Results)); 172 EXPECT_TRUE(HasEquivalentFile(DirFooB, Results)); 173 EXPECT_TRUE(HasEquivalentFile(DirFooC, Results)); 174 EXPECT_TRUE(HasEquivalentFile(DirBar, Results)); 175 EXPECT_TRUE(HasEquivalentFile(DirBaz, Results)); 176 EXPECT_TRUE(HasEquivalentFile(DirTestFolder, Results)); 177 178 DoDirCompletions(Twine(BaseDir) + "/", Resolver, Results); 179 llvm::sys::fs::set_current_path(BaseDir); 180 DoDirCompletions("", Resolver, Results); 181 } 182 183 TEST_F(CompletionTest, FileCompletionAbsolute) { 184 // All calls to DiskFiles() return both files and directories The tests below 185 // all check this by asserting an exact result count, and verifying against 186 // known folders. 187 188 StandardTildeExpressionResolver Resolver; 189 StringList Results; 190 // When an item is specified that doesn't end in a slash but exactly matches 191 // one item, it returns that item. 192 CommandCompletions::DiskFiles(Twine(BaseDir) + "/fooa", Results, Resolver); 193 ASSERT_EQ(1u, Results.GetSize()); 194 EXPECT_TRUE(HasEquivalentFile(DirFooA, Results)); 195 196 // The previous check verified a directory match. But it should work for 197 // files too. 198 CommandCompletions::DiskFiles(Twine(BaseDir) + "/aa", Results, Resolver); 199 ASSERT_EQ(1u, Results.GetSize()); 200 EXPECT_TRUE(HasEquivalentFile(FileAA, Results)); 201 202 // When it ends with a slash, it should find all files and directories. 203 CommandCompletions::DiskFiles(Twine(BaseDir) + "/", Results, Resolver); 204 ASSERT_EQ(13u, Results.GetSize()); 205 EXPECT_TRUE(HasEquivalentFile(DirFoo, Results)); 206 EXPECT_TRUE(HasEquivalentFile(DirFooA, Results)); 207 EXPECT_TRUE(HasEquivalentFile(DirFooB, Results)); 208 EXPECT_TRUE(HasEquivalentFile(DirFooC, Results)); 209 EXPECT_TRUE(HasEquivalentFile(DirBar, Results)); 210 EXPECT_TRUE(HasEquivalentFile(DirBaz, Results)); 211 EXPECT_TRUE(HasEquivalentFile(DirTestFolder, Results)); 212 213 EXPECT_TRUE(HasEquivalentFile(FileAA, Results)); 214 EXPECT_TRUE(HasEquivalentFile(FileAB, Results)); 215 EXPECT_TRUE(HasEquivalentFile(FileAC, Results)); 216 EXPECT_TRUE(HasEquivalentFile(FileFoo, Results)); 217 EXPECT_TRUE(HasEquivalentFile(FileBar, Results)); 218 EXPECT_TRUE(HasEquivalentFile(FileBaz, Results)); 219 220 // When a partial name matches, it returns all file & directory matches. 221 CommandCompletions::DiskFiles(Twine(BaseDir) + "/foo", Results, Resolver); 222 ASSERT_EQ(5u, Results.GetSize()); 223 EXPECT_TRUE(HasEquivalentFile(DirFoo, Results)); 224 EXPECT_TRUE(HasEquivalentFile(DirFooA, Results)); 225 EXPECT_TRUE(HasEquivalentFile(DirFooB, Results)); 226 EXPECT_TRUE(HasEquivalentFile(DirFooC, Results)); 227 EXPECT_TRUE(HasEquivalentFile(FileFoo, Results)); 228 } 229 230 TEST_F(CompletionTest, DirCompletionUsername) { 231 MockTildeExpressionResolver Resolver("James", BaseDir); 232 Resolver.AddKnownUser("Kirk", DirFooB); 233 Resolver.AddKnownUser("Lars", DirFooC); 234 Resolver.AddKnownUser("Jason", DirFoo); 235 Resolver.AddKnownUser("Larry", DirFooA); 236 std::string sep = std::string(path::get_separator()); 237 238 // Just resolving current user's home directory by itself should return the 239 // directory. 240 StringList Results; 241 CommandCompletions::DiskDirectories("~", Results, Resolver); 242 EXPECT_THAT(toVector(Results), UnorderedElementsAre("~" + sep)); 243 244 // With a slash appended, it should return all items in the directory. 245 CommandCompletions::DiskDirectories("~/", Results, Resolver); 246 EXPECT_THAT(toVector(Results), 247 UnorderedElementsAre( 248 "~/foo" + sep, "~/fooa" + sep, "~/foob" + sep, "~/fooc" + sep, 249 "~/bar" + sep, "~/baz" + sep, "~/test_folder" + sep)); 250 251 // Check that we can complete directories in nested paths 252 CommandCompletions::DiskDirectories("~/foo/", Results, Resolver); 253 EXPECT_THAT(toVector(Results), UnorderedElementsAre("~/foo/nested" + sep)); 254 255 CommandCompletions::DiskDirectories("~/foo/nes", Results, Resolver); 256 EXPECT_THAT(toVector(Results), UnorderedElementsAre("~/foo/nested" + sep)); 257 258 // With ~username syntax it should return one match if there is an exact 259 // match. It shouldn't translate to the actual directory, it should keep the 260 // form the user typed. 261 CommandCompletions::DiskDirectories("~Lars", Results, Resolver); 262 EXPECT_THAT(toVector(Results), UnorderedElementsAre("~Lars" + sep)); 263 264 // But with a username that is not found, no results are returned. 265 CommandCompletions::DiskDirectories("~Dave", Results, Resolver); 266 EXPECT_THAT(toVector(Results), UnorderedElementsAre()); 267 268 // And if there are multiple matches, it should return all of them. 269 CommandCompletions::DiskDirectories("~La", Results, Resolver); 270 EXPECT_THAT(toVector(Results), 271 UnorderedElementsAre("~Lars" + sep, "~Larry" + sep)); 272 } 273