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