1 //===-- TestCompletion.cpp ------------------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "lldb/Host/FileSystem.h"
10 #include "lldb/Interpreter/CommandCompletions.h"
11 #include "lldb/Utility/StringList.h"
12 #include "lldb/Utility/TildeExpressionResolver.h"
14 #include "gmock/gmock.h"
15 #include "gtest/gtest.h"
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"
25 namespace fs
= llvm::sys::fs
;
26 namespace path
= llvm::sys::path
;
28 using namespace lldb_private
;
32 class CompletionTest
: public testing::Test
{
33 SubsystemRAII
<FileSystem
> subsystems
;
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
;
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
;
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
;
54 SmallString
<128> FileAA
;
55 SmallString
<128> FileAB
;
56 SmallString
<128> FileAC
;
57 SmallString
<128> FileFoo
;
58 SmallString
<128> FileBar
;
59 SmallString
<128> FileBaz
;
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
));
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
));
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
);
82 path::append(Path
, std::get
<0>(Dir
));
83 ASSERT_NO_ERROR(fs::create_directories(Path
));
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
);
91 path::append(Path
, std::get
<0>(File
));
93 ASSERT_NO_ERROR(fs::createUniqueFile(Path
, FD
, Path
));
98 static void SetUpTestCase() {
99 ASSERT_NO_ERROR(fs::current_path(OriginalWorkingDir
));
102 void TearDown() override
{
103 ASSERT_NO_ERROR(fs::remove_directories(BaseDir
));
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
]))
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
));
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
));
134 SmallString
<128> CompletionTest::OriginalWorkingDir
;
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
]);
143 using testing::UnorderedElementsAre
;
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
151 std::string Prefixes
[] = {(Twine(BaseDir
) + "/").str(), ""};
153 StandardTildeExpressionResolver Resolver
;
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
,
161 ASSERT_EQ(1u, Results
.GetSize());
162 EXPECT_TRUE(HasEquivalentFile(DirFooA
, Results
));
164 CommandCompletions::DiskDirectories(Twine(BaseDir
) + "/.", Results
, Resolver
);
165 ASSERT_EQ(0u, Results
.GetSize());
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
));
178 DoDirCompletions(Twine(BaseDir
) + "/", Resolver
, Results
);
179 llvm::sys::fs::set_current_path(BaseDir
);
180 DoDirCompletions("", Resolver
, Results
);
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
188 StandardTildeExpressionResolver Resolver
;
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
));
196 // The previous check verified a directory match. But it should work for
198 CommandCompletions::DiskFiles(Twine(BaseDir
) + "/aa", Results
, Resolver
);
199 ASSERT_EQ(1u, Results
.GetSize());
200 EXPECT_TRUE(HasEquivalentFile(FileAA
, Results
));
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
));
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
));
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
));
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());
238 // Just resolving current user's home directory by itself should return the
241 CommandCompletions::DiskDirectories("~", Results
, Resolver
);
242 EXPECT_THAT(toVector(Results
), UnorderedElementsAre("~" + sep
));
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
));
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
));
255 CommandCompletions::DiskDirectories("~/foo/nes", Results
, Resolver
);
256 EXPECT_THAT(toVector(Results
), UnorderedElementsAre("~/foo/nested" + sep
));
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
));
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());
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
));