xref: /llvm-project/lldb/unittests/Interpreter/TestCompletion.cpp (revision 678e3ee12351e525fa9d94e7ff68ba7c1a8ca657)
180814287SRaphael Isemann //===-- TestCompletion.cpp ------------------------------------------------===//
22cc5a18dSZachary Turner //
32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
62cc5a18dSZachary Turner //
72cc5a18dSZachary Turner //===----------------------------------------------------------------------===//
82cc5a18dSZachary Turner 
9edaf2bccSJonas Devlieghere #include "lldb/Host/FileSystem.h"
102cc5a18dSZachary Turner #include "lldb/Interpreter/CommandCompletions.h"
11573ab909SZachary Turner #include "lldb/Utility/StringList.h"
122cc5a18dSZachary Turner #include "lldb/Utility/TildeExpressionResolver.h"
13edaf2bccSJonas Devlieghere 
14e17e9579SPavel Labath #include "gmock/gmock.h"
15e17e9579SPavel Labath #include "gtest/gtest.h"
162cc5a18dSZachary Turner 
17a6db4167STim Hammerquist #include "TestingSupport/MockTildeExpressionResolver.h"
185dca0596SRaphael Isemann #include "TestingSupport/SubsystemRAII.h"
198a777920SGreg Clayton #include "TestingSupport/TestUtilities.h"
202cc5a18dSZachary Turner #include "llvm/ADT/SmallString.h"
212cc5a18dSZachary Turner #include "llvm/Support/FileSystem.h"
222cc5a18dSZachary Turner #include "llvm/Support/Path.h"
232cc5a18dSZachary Turner #include "llvm/Support/raw_ostream.h"
242cc5a18dSZachary Turner 
252cc5a18dSZachary Turner namespace fs = llvm::sys::fs;
262cc5a18dSZachary Turner namespace path = llvm::sys::path;
272cc5a18dSZachary Turner using namespace llvm;
282cc5a18dSZachary Turner using namespace lldb_private;
292cc5a18dSZachary Turner 
302cc5a18dSZachary Turner namespace {
312cc5a18dSZachary Turner 
322cc5a18dSZachary Turner class CompletionTest : public testing::Test {
335dca0596SRaphael Isemann   SubsystemRAII<FileSystem> subsystems;
345dca0596SRaphael Isemann 
352cc5a18dSZachary Turner protected:
362cc5a18dSZachary Turner   /// Unique temporary directory in which all created filesystem entities must
372cc5a18dSZachary Turner   /// be placed. It is removed at the end of the test suite.
38ffa7010bSRaphael Isemann   SmallString<128> BaseDir;
392cc5a18dSZachary Turner 
40ffa7010bSRaphael Isemann   /// The working directory that we got when starting the test. Every test
41ffa7010bSRaphael Isemann   /// should chdir into this directory first because some tests maybe chdir
42ffa7010bSRaphael Isemann   /// into another one during their run.
43426d1379SZachary Turner   static SmallString<128> OriginalWorkingDir;
44426d1379SZachary Turner 
45ffa7010bSRaphael Isemann   SmallString<128> DirFoo;
46ffa7010bSRaphael Isemann   SmallString<128> DirFooA;
47ffa7010bSRaphael Isemann   SmallString<128> DirFooB;
48ffa7010bSRaphael Isemann   SmallString<128> DirFooC;
49ffa7010bSRaphael Isemann   SmallString<128> DirBar;
50ffa7010bSRaphael Isemann   SmallString<128> DirBaz;
51ffa7010bSRaphael Isemann   SmallString<128> DirTestFolder;
52ffa7010bSRaphael Isemann   SmallString<128> DirNested;
532cc5a18dSZachary Turner 
54ffa7010bSRaphael Isemann   SmallString<128> FileAA;
55ffa7010bSRaphael Isemann   SmallString<128> FileAB;
56ffa7010bSRaphael Isemann   SmallString<128> FileAC;
57ffa7010bSRaphael Isemann   SmallString<128> FileFoo;
58ffa7010bSRaphael Isemann   SmallString<128> FileBar;
59ffa7010bSRaphael Isemann   SmallString<128> FileBaz;
602cc5a18dSZachary Turner 
SetUp()61ffa7010bSRaphael Isemann   void SetUp() override {
62ffa7010bSRaphael Isemann     // chdir back into the original working dir this test binary started with.
63*678e3ee1SFangrui Song     // A previous test may have changed the working dir.
64ffa7010bSRaphael Isemann     ASSERT_NO_ERROR(fs::set_current_path(OriginalWorkingDir));
65426d1379SZachary Turner 
66ffa7010bSRaphael Isemann     // Get the name of the current test. To prevent that by chance two tests
67ffa7010bSRaphael Isemann     // get the same temporary directory if createUniqueDirectory fails.
68ffa7010bSRaphael Isemann     auto test_info = ::testing::UnitTest::GetInstance()->current_test_info();
69ffa7010bSRaphael Isemann     ASSERT_TRUE(test_info != nullptr);
70ffa7010bSRaphael Isemann     std::string name = test_info->name();
71ffa7010bSRaphael Isemann     ASSERT_NO_ERROR(fs::createUniqueDirectory("FsCompletion-" + name, BaseDir));
72426d1379SZachary Turner 
732cc5a18dSZachary Turner     const char *DirNames[] = {"foo", "fooa", "foob",        "fooc",
742476374cSRaphael Isemann                               "bar", "baz",  "test_folder", "foo/nested"};
75bb114f84SPavel Labath     const char *FileNames[] = {"aa1234.tmp",  "ab1234.tmp",  "ac1234.tmp",
76bb114f84SPavel Labath                                "foo1234.tmp", "bar1234.tmp", "baz1234.tmp"};
772cc5a18dSZachary Turner     SmallString<128> *Dirs[] = {&DirFoo, &DirFooA, &DirFooB,       &DirFooC,
782476374cSRaphael Isemann                                 &DirBar, &DirBaz,  &DirTestFolder, &DirNested};
792cc5a18dSZachary Turner     for (auto Dir : llvm::zip(DirNames, Dirs)) {
802cc5a18dSZachary Turner       auto &Path = *std::get<1>(Dir);
812cc5a18dSZachary Turner       Path = BaseDir;
822cc5a18dSZachary Turner       path::append(Path, std::get<0>(Dir));
832476374cSRaphael Isemann       ASSERT_NO_ERROR(fs::create_directories(Path));
842cc5a18dSZachary Turner     }
852cc5a18dSZachary Turner 
862cc5a18dSZachary Turner     SmallString<128> *Files[] = {&FileAA,  &FileAB,  &FileAC,
872cc5a18dSZachary Turner                                  &FileFoo, &FileBar, &FileBaz};
882cc5a18dSZachary Turner     for (auto File : llvm::zip(FileNames, Files)) {
892cc5a18dSZachary Turner       auto &Path = *std::get<1>(File);
902cc5a18dSZachary Turner       Path = BaseDir;
912cc5a18dSZachary Turner       path::append(Path, std::get<0>(File));
922cc5a18dSZachary Turner       int FD;
932cc5a18dSZachary Turner       ASSERT_NO_ERROR(fs::createUniqueFile(Path, FD, Path));
942cc5a18dSZachary Turner       ::close(FD);
952cc5a18dSZachary Turner     }
962cc5a18dSZachary Turner   }
972cc5a18dSZachary Turner 
SetUpTestCase()98ffa7010bSRaphael Isemann   static void SetUpTestCase() {
99ffa7010bSRaphael Isemann     ASSERT_NO_ERROR(fs::current_path(OriginalWorkingDir));
1002cc5a18dSZachary Turner   }
1012cc5a18dSZachary Turner 
TearDown()102edaf2bccSJonas Devlieghere   void TearDown() override {
103edaf2bccSJonas Devlieghere     ASSERT_NO_ERROR(fs::remove_directories(BaseDir));
104edaf2bccSJonas Devlieghere   }
105ffa7010bSRaphael Isemann 
HasEquivalentFile(const Twine & Path,const StringList & Paths)1062cc5a18dSZachary Turner   static bool HasEquivalentFile(const Twine &Path, const StringList &Paths) {
1079bd69ad9SPavel Labath     for (size_t I = 0; I < Paths.GetSize(); ++I) {
1082cc5a18dSZachary Turner       if (fs::equivalent(Path, Paths[I]))
1092cc5a18dSZachary Turner         return true;
1102cc5a18dSZachary Turner     }
1112cc5a18dSZachary Turner     return false;
1122cc5a18dSZachary Turner   }
1132cc5a18dSZachary Turner 
DoDirCompletions(const Twine & Prefix,StandardTildeExpressionResolver & Resolver,StringList & Results)114426d1379SZachary Turner   void DoDirCompletions(const Twine &Prefix,
115426d1379SZachary Turner                         StandardTildeExpressionResolver &Resolver,
116426d1379SZachary Turner                         StringList &Results) {
117426d1379SZachary Turner     // When a partial name matches, it returns all matches.  If it matches both
118426d1379SZachary Turner     // a full name AND some partial names, it returns all of them.
119426d1379SZachary Turner     CommandCompletions::DiskDirectories(Prefix + "foo", Results, Resolver);
120ae34ed2cSRaphael Isemann     ASSERT_EQ(4u, Results.GetSize());
121426d1379SZachary Turner     EXPECT_TRUE(HasEquivalentFile(DirFoo, Results));
122426d1379SZachary Turner     EXPECT_TRUE(HasEquivalentFile(DirFooA, Results));
123426d1379SZachary Turner     EXPECT_TRUE(HasEquivalentFile(DirFooB, Results));
124426d1379SZachary Turner     EXPECT_TRUE(HasEquivalentFile(DirFooC, Results));
125426d1379SZachary Turner 
126426d1379SZachary Turner     // If it matches only partial names, it still works as expected.
127ae34ed2cSRaphael Isemann     CommandCompletions::DiskDirectories(Twine(Prefix) + "b", Results, Resolver);
128ae34ed2cSRaphael Isemann     ASSERT_EQ(2u, Results.GetSize());
129426d1379SZachary Turner     EXPECT_TRUE(HasEquivalentFile(DirBar, Results));
130426d1379SZachary Turner     EXPECT_TRUE(HasEquivalentFile(DirBaz, Results));
131426d1379SZachary Turner   }
1322cc5a18dSZachary Turner };
1332cc5a18dSZachary Turner 
134426d1379SZachary Turner SmallString<128> CompletionTest::OriginalWorkingDir;
135edaf2bccSJonas Devlieghere } // namespace
1362cc5a18dSZachary Turner 
toVector(const StringList & SL)137e17e9579SPavel Labath static std::vector<std::string> toVector(const StringList &SL) {
138e17e9579SPavel Labath   std::vector<std::string> Result;
139e17e9579SPavel Labath   for (size_t Idx = 0; Idx < SL.GetSize(); ++Idx)
140e17e9579SPavel Labath     Result.push_back(SL[Idx]);
141e17e9579SPavel Labath   return Result;
142e17e9579SPavel Labath }
143e17e9579SPavel Labath using testing::UnorderedElementsAre;
144e17e9579SPavel Labath 
TEST_F(CompletionTest,DirCompletionAbsolute)1452cc5a18dSZachary Turner TEST_F(CompletionTest, DirCompletionAbsolute) {
1462cc5a18dSZachary Turner   // All calls to DiskDirectories() return only directories, even when
1472cc5a18dSZachary Turner   // there are files which also match.  The tests below all check this
1482cc5a18dSZachary Turner   // by asserting an exact result count, and verifying against known
1492cc5a18dSZachary Turner   // folders.
1502cc5a18dSZachary Turner 
151426d1379SZachary Turner   std::string Prefixes[] = {(Twine(BaseDir) + "/").str(), ""};
152426d1379SZachary Turner 
15372787ac6SJonas Devlieghere   StandardTildeExpressionResolver Resolver;
1542cc5a18dSZachary Turner   StringList Results;
155426d1379SZachary Turner 
1562cc5a18dSZachary Turner   // When a directory is specified that doesn't end in a slash, it searches
1572cc5a18dSZachary Turner   // for that directory, not items under it.
1583e1c793eSRaphael Isemann   // Sanity check that the path we complete on exists and isn't too long.
159ae34ed2cSRaphael Isemann   CommandCompletions::DiskDirectories(Twine(BaseDir) + "/fooa", Results,
160ae34ed2cSRaphael Isemann                                       Resolver);
161ae34ed2cSRaphael Isemann   ASSERT_EQ(1u, Results.GetSize());
162412f24ddSFrederic Riss   EXPECT_TRUE(HasEquivalentFile(DirFooA, Results));
1632cc5a18dSZachary Turner 
164ae34ed2cSRaphael Isemann   CommandCompletions::DiskDirectories(Twine(BaseDir) + "/.", Results, Resolver);
165ae34ed2cSRaphael Isemann   ASSERT_EQ(0u, Results.GetSize());
16678a10a7aSFrederic Riss 
1672cc5a18dSZachary Turner   // When the same directory ends with a slash, it finds all children.
168ae34ed2cSRaphael Isemann   CommandCompletions::DiskDirectories(Prefixes[0], Results, Resolver);
169ae34ed2cSRaphael Isemann   ASSERT_EQ(7u, Results.GetSize());
1702cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(DirFoo, Results));
1712cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(DirFooA, Results));
1722cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(DirFooB, Results));
1732cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(DirFooC, Results));
1742cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(DirBar, Results));
1752cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(DirBaz, Results));
1762cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(DirTestFolder, Results));
1772cc5a18dSZachary Turner 
178426d1379SZachary Turner   DoDirCompletions(Twine(BaseDir) + "/", Resolver, Results);
179426d1379SZachary Turner   llvm::sys::fs::set_current_path(BaseDir);
180426d1379SZachary Turner   DoDirCompletions("", Resolver, Results);
1812cc5a18dSZachary Turner }
1822cc5a18dSZachary Turner 
TEST_F(CompletionTest,FileCompletionAbsolute)1832cc5a18dSZachary Turner TEST_F(CompletionTest, FileCompletionAbsolute) {
1842cc5a18dSZachary Turner   // All calls to DiskFiles() return both files and directories  The tests below
1852cc5a18dSZachary Turner   // all check this by asserting an exact result count, and verifying against
1862cc5a18dSZachary Turner   // known folders.
1872cc5a18dSZachary Turner 
18872787ac6SJonas Devlieghere   StandardTildeExpressionResolver Resolver;
1892cc5a18dSZachary Turner   StringList Results;
1902cc5a18dSZachary Turner   // When an item is specified that doesn't end in a slash but exactly matches
1912cc5a18dSZachary Turner   // one item, it returns that item.
192ae34ed2cSRaphael Isemann   CommandCompletions::DiskFiles(Twine(BaseDir) + "/fooa", Results, Resolver);
193ae34ed2cSRaphael Isemann   ASSERT_EQ(1u, Results.GetSize());
1942cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(DirFooA, Results));
1952cc5a18dSZachary Turner 
1962cc5a18dSZachary Turner   // The previous check verified a directory match.  But it should work for
1972cc5a18dSZachary Turner   // files too.
1989bd69ad9SPavel Labath   CommandCompletions::DiskFiles(Twine(BaseDir) + "/aa", Results, Resolver);
199ae34ed2cSRaphael Isemann   ASSERT_EQ(1u, Results.GetSize());
2002cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(FileAA, Results));
2012cc5a18dSZachary Turner 
2022cc5a18dSZachary Turner   // When it ends with a slash, it should find all files and directories.
2039bd69ad9SPavel Labath   CommandCompletions::DiskFiles(Twine(BaseDir) + "/", Results, Resolver);
204ae34ed2cSRaphael Isemann   ASSERT_EQ(13u, Results.GetSize());
2052cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(DirFoo, Results));
2062cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(DirFooA, Results));
2072cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(DirFooB, Results));
2082cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(DirFooC, Results));
2092cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(DirBar, Results));
2102cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(DirBaz, Results));
2112cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(DirTestFolder, Results));
2122cc5a18dSZachary Turner 
2132cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(FileAA, Results));
2142cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(FileAB, Results));
2152cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(FileAC, Results));
2162cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(FileFoo, Results));
2172cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(FileBar, Results));
2182cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(FileBaz, Results));
2192cc5a18dSZachary Turner 
2202cc5a18dSZachary Turner   // When a partial name matches, it returns all file & directory matches.
2219bd69ad9SPavel Labath   CommandCompletions::DiskFiles(Twine(BaseDir) + "/foo", Results, Resolver);
222ae34ed2cSRaphael Isemann   ASSERT_EQ(5u, Results.GetSize());
2232cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(DirFoo, Results));
2242cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(DirFooA, Results));
2252cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(DirFooB, Results));
2262cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(DirFooC, Results));
2272cc5a18dSZachary Turner   EXPECT_TRUE(HasEquivalentFile(FileFoo, Results));
2282cc5a18dSZachary Turner }
2292cc5a18dSZachary Turner 
TEST_F(CompletionTest,DirCompletionUsername)2302cc5a18dSZachary Turner TEST_F(CompletionTest, DirCompletionUsername) {
23172787ac6SJonas Devlieghere   MockTildeExpressionResolver Resolver("James", BaseDir);
2322cc5a18dSZachary Turner   Resolver.AddKnownUser("Kirk", DirFooB);
2332cc5a18dSZachary Turner   Resolver.AddKnownUser("Lars", DirFooC);
2342cc5a18dSZachary Turner   Resolver.AddKnownUser("Jason", DirFoo);
2352cc5a18dSZachary Turner   Resolver.AddKnownUser("Larry", DirFooA);
236adcd0268SBenjamin Kramer   std::string sep = std::string(path::get_separator());
2372cc5a18dSZachary Turner 
2382cc5a18dSZachary Turner   // Just resolving current user's home directory by itself should return the
2392cc5a18dSZachary Turner   // directory.
2402cc5a18dSZachary Turner   StringList Results;
241ae34ed2cSRaphael Isemann   CommandCompletions::DiskDirectories("~", Results, Resolver);
242e17e9579SPavel Labath   EXPECT_THAT(toVector(Results), UnorderedElementsAre("~" + sep));
2432cc5a18dSZachary Turner 
2442cc5a18dSZachary Turner   // With a slash appended, it should return all items in the directory.
245ae34ed2cSRaphael Isemann   CommandCompletions::DiskDirectories("~/", Results, Resolver);
246e17e9579SPavel Labath   EXPECT_THAT(toVector(Results),
247e17e9579SPavel Labath               UnorderedElementsAre(
248e17e9579SPavel Labath                   "~/foo" + sep, "~/fooa" + sep, "~/foob" + sep, "~/fooc" + sep,
249e17e9579SPavel Labath                   "~/bar" + sep, "~/baz" + sep, "~/test_folder" + sep));
2502cc5a18dSZachary Turner 
2512476374cSRaphael Isemann   // Check that we can complete directories in nested paths
252ae34ed2cSRaphael Isemann   CommandCompletions::DiskDirectories("~/foo/", Results, Resolver);
253e17e9579SPavel Labath   EXPECT_THAT(toVector(Results), UnorderedElementsAre("~/foo/nested" + sep));
254e17e9579SPavel Labath 
255ae34ed2cSRaphael Isemann   CommandCompletions::DiskDirectories("~/foo/nes", Results, Resolver);
256e17e9579SPavel Labath   EXPECT_THAT(toVector(Results), UnorderedElementsAre("~/foo/nested" + sep));
2572476374cSRaphael Isemann 
2582cc5a18dSZachary Turner   // With ~username syntax it should return one match if there is an exact
259e17e9579SPavel Labath   // match.  It shouldn't translate to the actual directory, it should keep the
260e17e9579SPavel Labath   // form the user typed.
261ae34ed2cSRaphael Isemann   CommandCompletions::DiskDirectories("~Lars", Results, Resolver);
262e17e9579SPavel Labath   EXPECT_THAT(toVector(Results), UnorderedElementsAre("~Lars" + sep));
2632cc5a18dSZachary Turner 
2642cc5a18dSZachary Turner   // But with a username that is not found, no results are returned.
265ae34ed2cSRaphael Isemann   CommandCompletions::DiskDirectories("~Dave", Results, Resolver);
266e17e9579SPavel Labath   EXPECT_THAT(toVector(Results), UnorderedElementsAre());
2672cc5a18dSZachary Turner 
2682cc5a18dSZachary Turner   // And if there are multiple matches, it should return all of them.
269ae34ed2cSRaphael Isemann   CommandCompletions::DiskDirectories("~La", Results, Resolver);
270e17e9579SPavel Labath   EXPECT_THAT(toVector(Results),
271e17e9579SPavel Labath               UnorderedElementsAre("~Lars" + sep, "~Larry" + sep));
2722cc5a18dSZachary Turner }
273