xref: /llvm-project/lldb/unittests/Interpreter/TestCompletion.cpp (revision 678e3ee12351e525fa9d94e7ff68ba7c1a8ca657)
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 
SetUp()61   void SetUp() override {
62     // chdir back into the original working dir this test binary started with.
63     // A previous test may 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 
SetUpTestCase()98   static void SetUpTestCase() {
99     ASSERT_NO_ERROR(fs::current_path(OriginalWorkingDir));
100   }
101 
TearDown()102   void TearDown() override {
103     ASSERT_NO_ERROR(fs::remove_directories(BaseDir));
104   }
105 
HasEquivalentFile(const Twine & Path,const StringList & Paths)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 
DoDirCompletions(const Twine & Prefix,StandardTildeExpressionResolver & Resolver,StringList & Results)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 
toVector(const StringList & SL)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 
TEST_F(CompletionTest,DirCompletionAbsolute)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 
TEST_F(CompletionTest,FileCompletionAbsolute)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 
TEST_F(CompletionTest,DirCompletionUsername)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