Thanks for the heads up, I'll try to look at this tomorrow. On Tue, Apr 11, 2017 at 5:55 PM Jason Molenda <jmole...@apple.com> wrote:
> I noticed that the llvm.org sources crash when you do filename completion > with a file in the current working directory: > > % build/Debug/lldb > (lldb) file aaa[TAB]Assertion failed: (!SearchDir.empty()), function > DiskFilesOrDirectories, file > /Volumes/newwork/svn/lldb/source/Commands/CommandCompletions.cpp, line 179. > Abort > > > Or through the SB API: > > % build/Debug/lldb > (lldb) scri > >>> print lldb.debugger.GetCommandInterpreter().HandleCompletion("file > /tmp/ll", 11, 0, -1, lldb.SBStringList()) > 0 > >>> print lldb.debugger.GetCommandInterpreter().HandleCompletion("file > ll", 7, 0, -1, lldb.SBStringList()) > Assertion failed: (!SearchDir.empty()), function DiskFilesOrDirectories, > file /Volumes/newwork/svn/lldb/source/Commands/CommandCompletions.cpp, line > 179. > Abort > > > maybe it's related from r297585? There have been a few overlapping > changes to CommandCompletions.cpp by you and Pavel recently, not sure > exactly which it might be. > > If this can't be tested through the unit test framework, it would be easy > to add an SB API test case, see above. > > Thanks > > > > > On Mar 12, 2017, at 11:18 AM, Zachary Turner via lldb-commits < > lldb-commits@lists.llvm.org> wrote: > > > > Author: zturner > > Date: Sun Mar 12 13:18:50 2017 > > New Revision: 297585 > > > > URL: http://llvm.org/viewvc/llvm-project?rev=297585&view=rev > > Log: > > Make file / directory completion work properly on Windows. > > > > There were a couple of problems with this function on Windows. Different > > separators and differences in how tilde expressions are resolved for > > starters, but in addition there was no clear indication of what the > > function's inputs or outputs were supposed to be, and there were no tests > > to demonstrate its use. > > > > To more easily paper over the differences between Windows paths, > > non-Windows paths, and tilde expressions, I've ported this function to > use > > LLVM-based directory iteration (in fact, I would like to eliminate all of > > LLDB's directory iteration code entirely since LLVM's is cleaner / more > > efficient (i.e. it invokes fewer stat calls)). and llvm's portable path > > manipulation library. > > > > Since file and directory completion assumes you are referring to files > and > > directories on your local machine, it's safe to assume the path syntax > > properties of the host in doing so, so LLVM's APIs are perfect for this. > > > > I've also added a fairly robust set of unit tests. Since you can't really > > predict what users will be on your machine, or what their home > directories > > will be, I added an interface called TildeExpressionResolver, and in the > > unit test I've mocked up a fake implementation that acts like a unix > > password database. This allows us to configure some fake users and home > > directories in the test, so we can exercise all of those hard-to-test > > codepaths that normally otherwise depend on the host. > > > > Differential Revision: https://reviews.llvm.org/D30789 > > > > Added: > > lldb/trunk/include/lldb/Utility/TildeExpressionResolver.h > > lldb/trunk/source/Utility/TildeExpressionResolver.cpp > > lldb/trunk/unittests/Interpreter/TestCompletion.cpp > > Modified: > > lldb/trunk/include/lldb/Interpreter/CommandCompletions.h > > lldb/trunk/source/Commands/CommandCompletions.cpp > > lldb/trunk/source/Utility/CMakeLists.txt > > lldb/trunk/unittests/Interpreter/CMakeLists.txt > > > > Modified: lldb/trunk/include/lldb/Interpreter/CommandCompletions.h > > URL: > http://llvm.org/viewvc/llvm-project/lldb/trunk/include/lldb/Interpreter/CommandCompletions.h?rev=297585&r1=297584&r2=297585&view=diff > > > ============================================================================== > > --- lldb/trunk/include/lldb/Interpreter/CommandCompletions.h (original) > > +++ lldb/trunk/include/lldb/Interpreter/CommandCompletions.h Sun Mar 12 > 13:18:50 2017 > > @@ -21,7 +21,10 @@ > > #include "lldb/Utility/RegularExpression.h" > > #include "lldb/lldb-private.h" > > > > +#include "llvm/ADT/Twine.h" > > + > > namespace lldb_private { > > +struct TildeExpressionResolver; > > class CommandCompletions { > > public: > > > //---------------------------------------------------------------------- > > @@ -76,12 +79,19 @@ public: > > int max_return_elements, SearchFilter *searcher, > > bool &word_complete, StringList &matches); > > > > + static int DiskFiles(const llvm::Twine &partial_file_name, > > + StringList &matches, TildeExpressionResolver > &Resolver); > > + > > static int DiskDirectories(CommandInterpreter &interpreter, > > llvm::StringRef partial_file_name, > > int match_start_point, int > max_return_elements, > > SearchFilter *searcher, bool &word_complete, > > StringList &matches); > > > > + static int DiskDirectories(const llvm::Twine &partial_file_name, > > + StringList &matches, > > + TildeExpressionResolver &Resolver); > > + > > static int SourceFiles(CommandInterpreter &interpreter, > > llvm::StringRef partial_file_name, > > int match_start_point, int max_return_elements, > > > > Added: lldb/trunk/include/lldb/Utility/TildeExpressionResolver.h > > URL: > http://llvm.org/viewvc/llvm-project/lldb/trunk/include/lldb/Utility/TildeExpressionResolver.h?rev=297585&view=auto > > > ============================================================================== > > --- lldb/trunk/include/lldb/Utility/TildeExpressionResolver.h (added) > > +++ lldb/trunk/include/lldb/Utility/TildeExpressionResolver.h Sun Mar 12 > 13:18:50 2017 > > @@ -0,0 +1,57 @@ > > +//===--------------------- TildeExpressionResolver.h ------------*- C++ > -*-===// > > +// > > +// The LLVM Compiler Infrastructure > > +// > > +// This file is distributed under the University of Illinois Open Source > > +// License. See LICENSE.TXT for details. > > +// > > > +//===----------------------------------------------------------------------===// > > + > > +#ifndef LLDB_UTILITY_TILDE_EXPRESSION_RESOLVER_H > > +#define LLDB_UTILITY_TILDE_EXPRESSION_RESOLVER_H > > + > > +#include "llvm/ADT/SmallVector.h" > > +#include "llvm/ADT/StringRef.h" > > +#include "llvm/ADT/StringSet.h" > > + > > +namespace lldb_private { > > +class TildeExpressionResolver { > > +public: > > + virtual ~TildeExpressionResolver(); > > + > > + /// \brief Resolve a Tilde Expression contained according to bash > rules. > > + /// > > + /// \param Expr Contains the tilde expression to resolve. A valid > tilde > > + /// expression must begin with a tilde and contain only > non > > + /// separator characters. > > + /// > > + /// \param Output Contains the resolved tilde expression, or the > original > > + /// input if the tilde expression could not be resolved. > > + /// > > + /// \returns true if \p Expr was successfully resolved, false > otherwise. > > + virtual bool ResolveExact(llvm::StringRef Expr, > > + llvm::SmallVectorImpl<char> &Output) = 0; > > + > > + /// \brief Auto-complete a tilde expression with all matching values. > > + /// > > + /// \param Expr Contains the tilde expression prefix to resolve. See > > + /// ResolveExact() for validity rules. > > + /// > > + /// \param Output Contains all matching home directories, each one > > + /// itself unresolved (i.e. you need to call > ResolveExact > > + /// on each item to turn it into a real path). > > + /// > > + /// \returns true if there were any matches, false otherwise. > > + virtual bool ResolvePartial(llvm::StringRef Expr, > > + llvm::StringSet<> &Output) = 0; > > +}; > > + > > +class StandardTildeExpressionResolver : public TildeExpressionResolver { > > +public: > > + bool ResolveExact(llvm::StringRef Expr, > > + llvm::SmallVectorImpl<char> &Output) override; > > + bool ResolvePartial(llvm::StringRef Expr, llvm::StringSet<> &Output) > override; > > +}; > > +} > > + > > +#endif // #ifndef LLDB_UTILITY_TILDE_EXPRESSION_RESOLVER_H > > > > Modified: lldb/trunk/source/Commands/CommandCompletions.cpp > > URL: > http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Commands/CommandCompletions.cpp?rev=297585&r1=297584&r2=297585&view=diff > > > ============================================================================== > > --- lldb/trunk/source/Commands/CommandCompletions.cpp (original) > > +++ lldb/trunk/source/Commands/CommandCompletions.cpp Sun Mar 12 > 13:18:50 2017 > > @@ -16,6 +16,7 @@ > > // C++ Includes > > // Other libraries and framework includes > > #include "llvm/ADT/SmallString.h" > > +#include "llvm/ADT/StringSet.h" > > > > // Project includes > > #include "lldb/Core/FileSpecList.h" > > @@ -31,9 +32,11 @@ > > #include "lldb/Symbol/Variable.h" > > #include "lldb/Target/Target.h" > > #include "lldb/Utility/CleanUp.h" > > +#include "lldb/Utility/TildeExpressionResolver.h" > > > > #include "llvm/ADT/SmallString.h" > > #include "llvm/Support/FileSystem.h" > > +#include "llvm/Support/Path.h" > > > > using namespace lldb_private; > > > > @@ -99,180 +102,127 @@ int CommandCompletions::SourceFiles(Comm > > return matches.GetSize(); > > } > > > > -typedef struct DiskFilesOrDirectoriesBaton { > > - const char *remainder; > > - char *partial_name_copy; > > - bool only_directories; > > - bool *saw_directory; > > - StringList *matches; > > - char *end_ptr; > > - size_t baselen; > > -} DiskFilesOrDirectoriesBaton; > > - > > -FileSpec::EnumerateDirectoryResult > > -DiskFilesOrDirectoriesCallback(void *baton, llvm::sys::fs::file_type > file_type, > > - const FileSpec &spec) { > > - const char *name = spec.GetFilename().AsCString(); > > - > > - const DiskFilesOrDirectoriesBaton *parameters = > > - (DiskFilesOrDirectoriesBaton *)baton; > > - char *end_ptr = parameters->end_ptr; > > - char *partial_name_copy = parameters->partial_name_copy; > > - const char *remainder = parameters->remainder; > > - > > - // Omit ".", ".." and any . files if the match string doesn't start > with . > > - if (name[0] == '.') { > > - if (name[1] == '\0') > > - return FileSpec::eEnumerateDirectoryResultNext; > > - else if (name[1] == '.' && name[2] == '\0') > > - return FileSpec::eEnumerateDirectoryResultNext; > > - else if (remainder[0] != '.') > > - return FileSpec::eEnumerateDirectoryResultNext; > > - } > > - > > - // If we found a directory, we put a "/" at the end of the name. > > - > > - if (remainder[0] == '\0' || strstr(name, remainder) == name) { > > - if (strlen(name) + parameters->baselen >= PATH_MAX) > > - return FileSpec::eEnumerateDirectoryResultNext; > > - > > - strcpy(end_ptr, name); > > - > > - namespace fs = llvm::sys::fs; > > - bool isa_directory = false; > > - if (file_type == fs::file_type::directory_file) > > - isa_directory = true; > > - else if (file_type == fs::file_type::symlink_file) > > - isa_directory = fs::is_directory(partial_name_copy); > > - > > - if (isa_directory) { > > - *parameters->saw_directory = true; > > - size_t len = strlen(parameters->partial_name_copy); > > - partial_name_copy[len] = '/'; > > - partial_name_copy[len + 1] = '\0'; > > - } > > - if (parameters->only_directories && !isa_directory) > > - return FileSpec::eEnumerateDirectoryResultNext; > > - parameters->matches->AppendString(partial_name_copy); > > - } > > - > > - return FileSpec::eEnumerateDirectoryResultNext; > > -} > > - > > -static int DiskFilesOrDirectories(llvm::StringRef partial_file_name, > > +static int DiskFilesOrDirectories(const llvm::Twine &partial_name, > > bool only_directories, bool > &saw_directory, > > - StringList &matches) { > > - // I'm going to use the "glob" function with GLOB_TILDE for user > directory > > - // expansion. > > - // If it is not defined on your host system, you'll need to implement > it > > - // yourself... > > - > > - size_t partial_name_len = partial_file_name.size(); > > - > > - if (partial_name_len >= PATH_MAX) > > - return matches.GetSize(); > > - > > - // This copy of the string will be cut up into the directory part, > and the > > - // remainder. end_ptr below will point to the place of the remainder > in this > > - // string. Then when we've resolved the containing directory, and > opened it, > > - // we'll read the directory contents and overwrite the > partial_name_copy > > - // starting from end_ptr with each of the matches. Thus we will > preserve the > > - // form the user originally typed. > > - > > - char partial_name_copy[PATH_MAX]; > > - memcpy(partial_name_copy, partial_file_name.data(), partial_name_len); > > - partial_name_copy[partial_name_len] = '\0'; > > - > > - // We'll need to save a copy of the remainder for comparison, which > we do > > - // here. > > - char remainder[PATH_MAX]; > > - > > - // end_ptr will point past the last / in partial_name_copy, or if > there is no > > - // slash to the beginning of the string. > > - char *end_ptr; > > - > > - end_ptr = strrchr(partial_name_copy, '/'); > > - > > - // This will store the resolved form of the containing directory > > - llvm::SmallString<64> containing_part; > > - > > - if (end_ptr == nullptr) { > > - // There's no directory. If the thing begins with a "~" then this > is a bare > > - // user name. > > - if (*partial_name_copy == '~') { > > - // Nothing here but the user name. We could just put a slash on > the end, > > - // but for completeness sake we'll resolve the user name and only > put a > > - // slash > > - // on the end if it exists. > > - llvm::SmallString<64> resolved_username(partial_name_copy); > > - FileSpec::ResolveUsername(resolved_username); > > - > > - // Not sure how this would happen, a username longer than > PATH_MAX? > > - // Still... > > - if (resolved_username.size() == 0) { > > - // The user name didn't resolve, let's look in the password > database for > > - // matches. > > - // The user name database contains duplicates, and is not in > > - // alphabetical order, so > > - // we'll use a set to manage that for us. > > - FileSpec::ResolvePartialUsername(partial_name_copy, matches); > > - if (matches.GetSize() > 0) > > - saw_directory = true; > > - return matches.GetSize(); > > - } else { > > - // The thing exists, put a '/' on the end, and return it... > > - // FIXME: complete user names here: > > - partial_name_copy[partial_name_len] = '/'; > > - partial_name_copy[partial_name_len + 1] = '\0'; > > - matches.AppendString(partial_name_copy); > > - saw_directory = true; > > - return matches.GetSize(); > > + StringList &matches, > > + TildeExpressionResolver &Resolver) { > > + matches.Clear(); > > + > > + llvm::SmallString<256> CompletionBuffer; > > + llvm::SmallString<256> Storage; > > + partial_name.toVector(CompletionBuffer); > > + > > + if (CompletionBuffer.size() >= PATH_MAX) > > + return 0; > > + > > + namespace fs = llvm::sys::fs; > > + namespace path = llvm::sys::path; > > + > > + llvm::StringRef SearchDir; > > + llvm::StringRef PartialItem; > > + > > + if (CompletionBuffer.startswith("~")) { > > + llvm::StringRef Buffer(CompletionBuffer); > > + size_t FirstSep = Buffer.find_if(path::is_separator); > > + > > + llvm::StringRef Username = Buffer.take_front(FirstSep); > > + llvm::StringRef Remainder; > > + if (FirstSep != llvm::StringRef::npos) > > + Remainder = Buffer.drop_front(FirstSep + 1); > > + > > + llvm::SmallString<PATH_MAX> Resolved; > > + if (!Resolver->ResolveExact(Username, Resolved)) { > > + // We couldn't resolve it as a full username. If there were no > slashes > > + // then this might be a partial username. We try to resolve it > as such > > + // but after that, we're done regardless of any matches. > > + if (FirstSep == llvm::StringRef::npos) { > > + llvm::StringSet<> MatchSet; > > + saw_directory = Resolver->ResolvePartial(Username, MatchSet); > > + for (const auto &S : MatchSet) { > > + Resolved = S.getKey(); > > + path::append(Resolved, path::get_separator()); > > + matches.AppendString(Resolved); > > + } > > + saw_directory = (matches.GetSize() > 0); > > } > > - } else { > > - // The containing part is the CWD, and the whole string is the > remainder. > > - containing_part = "."; > > - strcpy(remainder, partial_name_copy); > > - end_ptr = partial_name_copy; > > + return matches.GetSize(); > > } > > - } else { > > - if (end_ptr == partial_name_copy) { > > - // We're completing a file or directory in the root volume. > > - containing_part = "/"; > > - } else { > > - containing_part.append(partial_name_copy, end_ptr); > > + > > + // If there was no trailing slash, then we're done as soon as we > resolve the > > + // expression to the correct directory. Otherwise we need to > continue > > + // looking for matches within that directory. > > + if (FirstSep == llvm::StringRef::npos) { > > + // Make sure it ends with a separator. > > + path::append(CompletionBuffer, path::get_separator()); > > + saw_directory = true; > > + matches.AppendString(CompletionBuffer); > > + return 1; > > } > > - // Push end_ptr past the final "/" and set remainder. > > - end_ptr++; > > - strcpy(remainder, end_ptr); > > - } > > > > - // Look for a user name in the containing part, and if it's there, > resolve it > > - // and stick the > > - // result back into the containing_part: > > - > > - if (*partial_name_copy == '~') { > > - FileSpec::ResolveUsername(containing_part); > > - // User name doesn't exist, we're not getting any further... > > - if (containing_part.empty()) > > - return matches.GetSize(); > > + // We want to keep the form the user typed, so we special case this > to > > + // search in the fully resolved directory, but CompletionBuffer > keeps the > > + // unmodified form that the user typed. > > + Storage = Resolved; > > + SearchDir = Resolved; > > + } else { > > + SearchDir = path::parent_path(CompletionBuffer); > > } > > > > - // Okay, containing_part is now the directory we want to open and > look for > > - // files: > > + size_t FullPrefixLen = CompletionBuffer.size(); > > + > > + PartialItem = path::filename(CompletionBuffer); > > + if (PartialItem == ".") > > + PartialItem = llvm::StringRef(); > > > > - size_t baselen = end_ptr - partial_name_copy; > > + assert(!SearchDir.empty()); > > + assert(!PartialItem.contains(path::get_separator())); > > > > - DiskFilesOrDirectoriesBaton parameters; > > - parameters.remainder = remainder; > > - parameters.partial_name_copy = partial_name_copy; > > - parameters.only_directories = only_directories; > > - parameters.saw_directory = &saw_directory; > > - parameters.matches = &matches; > > - parameters.end_ptr = end_ptr; > > - parameters.baselen = baselen; > > + // SearchDir now contains the directory to search in, and Prefix > contains the > > + // text we want to match against items in that directory. > > > > - FileSpec::EnumerateDirectory(containing_part.c_str(), true, true, > true, > > - DiskFilesOrDirectoriesCallback, > ¶meters); > > + std::error_code EC; > > + fs::directory_iterator Iter(SearchDir, EC, false); > > + fs::directory_iterator End; > > + for (; Iter != End && !EC; Iter.increment(EC)) { > > + auto &Entry = *Iter; > > + > > + auto Name = path::filename(Entry.path()); > > + > > + // Omit ".", ".." > > + if (Name == "." || Name == ".." || !Name.startswith(PartialItem)) > > + continue; > > + > > + // We have a match. > > + > > + fs::file_status st; > > + if (EC = Entry.status(st)) > > + continue; > > + > > + // If it's a symlink, then we treat it as a directory as long as > the target > > + // is a directory. > > + bool is_dir = fs::is_directory(st); > > + if (fs::is_symlink_file(st)) { > > + fs::file_status target_st; > > + if (!fs::status(Entry.path(), target_st)) > > + is_dir = fs::is_directory(target_st); > > + } > > + if (only_directories && !is_dir) > > + continue; > > + > > + // Shrink it back down so that it just has the original prefix the > user > > + // typed and remove the part of the name which is common to the > located > > + // item and what the user typed. > > + CompletionBuffer.resize(FullPrefixLen); > > + Name = Name.drop_front(PartialItem.size()); > > + CompletionBuffer.append(Name); > > + > > + if (is_dir) { > > + saw_directory = true; > > + path::append(CompletionBuffer, path::get_separator()); > > + } > > + > > + matches.AppendString(CompletionBuffer); > > + } > > > > return matches.GetSize(); > > } > > @@ -283,9 +233,17 @@ int CommandCompletions::DiskFiles(Comman > > int max_return_elements, > > SearchFilter *searcher, bool > &word_complete, > > StringList &matches) { > > - int ret_val = > > - DiskFilesOrDirectories(partial_file_name, false, word_complete, > matches); > > - word_complete = !word_complete; > > + word_complete = false; > > + StandardTildeExpressionResolver Resolver; > > + return DiskFiles(partial_file_name, matches, Resolver); > > +} > > + > > +int CommandCompletions::DiskFiles(const llvm::Twine &partial_file_name, > > + StringList &matches, > > + TildeExpressionResolver &Resolver) { > > + bool word_complete; > > + int ret_val = DiskFilesOrDirectories(partial_file_name, false, > word_complete, > > + matches, Resolver); > > return ret_val; > > } > > > > @@ -293,9 +251,17 @@ int CommandCompletions::DiskDirectories( > > CommandInterpreter &interpreter, llvm::StringRef partial_file_name, > > int match_start_point, int max_return_elements, SearchFilter > *searcher, > > bool &word_complete, StringList &matches) { > > - int ret_val = > > - DiskFilesOrDirectories(partial_file_name, true, word_complete, > matches); > > word_complete = false; > > + StandardTildeExpressionResolver Resolver; > > + return DiskDirectories(partial_file_name, matches, Resolver); > > +} > > + > > +int CommandCompletions::DiskDirectories(const llvm::Twine > &partial_file_name, > > + StringList &matches, > > + TildeExpressionResolver > &Resolver) { > > + bool word_complete; > > + int ret_val = DiskFilesOrDirectories(partial_file_name, true, > word_complete, > > + matches, Resolver); > > return ret_val; > > } > > > > > > Modified: lldb/trunk/source/Utility/CMakeLists.txt > > URL: > http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Utility/CMakeLists.txt?rev=297585&r1=297584&r2=297585&view=diff > > > ============================================================================== > > --- lldb/trunk/source/Utility/CMakeLists.txt (original) > > +++ lldb/trunk/source/Utility/CMakeLists.txt Sun Mar 12 13:18:50 2017 > > @@ -25,6 +25,7 @@ add_lldb_library(lldbUtility > > StringExtractorGDBRemote.cpp > > StringLexer.cpp > > TaskPool.cpp > > + TildeExpressionResolver.cpp > > UserID.cpp > > UriParser.cpp > > UUID.cpp > > > > Added: lldb/trunk/source/Utility/TildeExpressionResolver.cpp > > URL: > http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Utility/TildeExpressionResolver.cpp?rev=297585&view=auto > > > ============================================================================== > > --- lldb/trunk/source/Utility/TildeExpressionResolver.cpp (added) > > +++ lldb/trunk/source/Utility/TildeExpressionResolver.cpp Sun Mar 12 > 13:18:50 2017 > > @@ -0,0 +1,66 @@ > > +//===--------------------- TildeExpressionResolver.cpp ----------*- C++ > -*-===// > > +// > > +// The LLVM Compiler Infrastructure > > +// > > +// This file is distributed under the University of Illinois Open Source > > +// License. See LICENSE.TXT for details. > > +// > > > +//===----------------------------------------------------------------------===// > > + > > +#include "lldb/Utility/TildeExpressionResolver.h" > > + > > +#include "llvm/ADT/SmallString.h" > > +#include "llvm/Support/FileSystem.h" > > +#include "llvm/Support/Path.h" > > + > > +using namespace lldb_private; > > +using namespace llvm; > > + > > +namespace fs = llvm::sys::fs; > > +namespace path = llvm::sys::path; > > + > > +TildeExpressionResolver::~TildeExpressionResolver() {} > > + > > +bool StandardTildeExpressionResolver::ResolveExact( > > + StringRef Expr, SmallVectorImpl<char> &Output) { > > + // We expect the tilde expression to be ONLY the expression itself, > and > > + // contain > > + // no separators. > > + assert(!llvm::any_of(Expr, path::is_separator)); > > + assert(Expr.empty() || Expr[0] == '~'); > > + > > + return !fs::real_path(Expr, Output, true); > > +} > > + > > +bool StandardTildeExpressionResolver::ResolvePartial(StringRef Expr, > > + StringSet<> > &Output) { > > + // We expect the tilde expression to be ONLY the expression itself, > and > > + // contain no separators. > > + assert(!llvm::any_of(Expr, path::is_separator)); > > + assert(Expr.empty() || Expr[0] == '~'); > > + > > + Output.clear(); > > +#if defined(LLVM_ON_WIN32) > > + return false; > > +#else > > + if (Expr.empty()) > > + return false; > > + > > + SmallString<32> Buffer = "~"; > > + setpwent(); > > + struct passwd *user_entry; > > + Expr = Expr.drop_front(); > > + > > + while ((user_entry = getpwent()) != NULL) { > > + StringRef ThisName(user_entry->pw_name); > > + if (!ThisName.startswith(Expr)) > > + continue; > > + > > + Buffer.resize(1); > > + Buffer.append(ThisName); > > + Buffer.append(path::get_separator()) Output.insert(Buffer); > > + } > > + > > + return true; > > +#endif > > +} > > > > Modified: lldb/trunk/unittests/Interpreter/CMakeLists.txt > > URL: > http://llvm.org/viewvc/llvm-project/lldb/trunk/unittests/Interpreter/CMakeLists.txt?rev=297585&r1=297584&r2=297585&view=diff > > > ============================================================================== > > --- lldb/trunk/unittests/Interpreter/CMakeLists.txt (original) > > +++ lldb/trunk/unittests/Interpreter/CMakeLists.txt Sun Mar 12 13:18:50 > 2017 > > @@ -1,5 +1,6 @@ > > add_lldb_unittest(InterpreterTests > > TestArgs.cpp > > + TestCompletion.cpp > > > > LINK_LIBS > > lldbInterpreter > > > > Added: lldb/trunk/unittests/Interpreter/TestCompletion.cpp > > URL: > http://llvm.org/viewvc/llvm-project/lldb/trunk/unittests/Interpreter/TestCompletion.cpp?rev=297585&view=auto > > > ============================================================================== > > --- lldb/trunk/unittests/Interpreter/TestCompletion.cpp (added) > > +++ lldb/trunk/unittests/Interpreter/TestCompletion.cpp Sun Mar 12 > 13:18:50 2017 > > @@ -0,0 +1,346 @@ > > +//===-- TestCompletion.cpp --------------------------------------*- C++ > -*-===// > > +// > > +// The LLVM Compiler Infrastructure > > +// > > +// This file is distributed under the University of Illinois Open Source > > +// License. See LICENSE.TXT for details. > > +// > > > +//===----------------------------------------------------------------------===// > > + > > +#include "gtest/gtest.h" > > + > > +#include "lldb/Core/StringList.h" > > +#include "lldb/Interpreter/CommandCompletions.h" > > +#include "lldb/Utility/TildeExpressionResolver.h" > > + > > +#include "llvm/ADT/SmallString.h" > > +#include "llvm/Support/FileSystem.h" > > +#include "llvm/Support/Path.h" > > +#include "llvm/Support/raw_ostream.h" > > + > > +namespace fs = llvm::sys::fs; > > +namespace path = llvm::sys::path; > > +using namespace llvm; > > +using namespace lldb_private; > > + > > +#define ASSERT_NO_ERROR(x) > \ > > + if (std::error_code ASSERT_NO_ERROR_ec = x) { > \ > > + SmallString<128> MessageStorage; > \ > > + raw_svector_ostream Message(MessageStorage); > \ > > + Message << #x ": did not return errc::success.\n" > \ > > + << "error number: " << ASSERT_NO_ERROR_ec.value() << "\n" > \ > > + << "error message: " << ASSERT_NO_ERROR_ec.message() << > "\n"; \ > > + GTEST_FATAL_FAILURE_(MessageStorage.c_str()); > \ > > + } else { > \ > > + } > > + > > +namespace { > > + > > +class MockTildeExpressionResolver : public TildeExpressionResolver { > > + StringRef CurrentUser; > > + StringMap<StringRef> UserDirectories; > > + > > +public: > > + explicit MockTildeExpressionResolver(StringRef CurrentUser, StringRef > HomeDir) > > + : CurrentUser(CurrentUser) { > > + UserDirectories.insert(std::make_pair(CurrentUser, HomeDir)); > > + } > > + > > + void AddKnownUser(StringRef User, StringRef HomeDir) { > > + assert(UserDirectories.find(User) == UserDirectories.end()); > > + UserDirectories.insert(std::make_pair(User, HomeDir)); > > + } > > + > > + void Clear() { > > + CurrentUser = StringRef(); > > + UserDirectories.clear(); > > + } > > + > > + void SetCurrentUser(StringRef User) { > > + assert(UserDirectories.find(User) != UserDirectories.end()); > > + CurrentUser = User; > > + } > > + > > + bool ResolveExact(StringRef Expr, SmallVectorImpl<char> &Output) > override { > > + Output.clear(); > > + > > + assert(!llvm::any_of(Expr, llvm::sys::path::is_separator)); > > + assert(Expr.empty() || Expr[0] == '~'); > > + Expr = Expr.drop_front(); > > + if (Expr.empty()) { > > + auto Dir = UserDirectories[CurrentUser]; > > + Output.append(Dir.begin(), Dir.end()); > > + return true; > > + } > > + > > + for (const auto &User : UserDirectories) { > > + if (User.getKey() != Expr) > > + continue; > > + Output.append(User.getValue().begin(), User.getValue().end()); > > + return true; > > + } > > + return false; > > + } > > + > > + bool ResolvePartial(StringRef Expr, StringSet<> &Output) override { > > + Output.clear(); > > + > > + assert(!llvm::any_of(Expr, llvm::sys::path::is_separator)); > > + assert(Expr.empty() || Expr[0] == '~'); > > + Expr = Expr.drop_front(); > > + > > + SmallString<16> QualifiedName = "~"; > > + for (const auto &User : UserDirectories) { > > + if (!User.getKey().startswith(Expr)) > > + continue; > > + QualifiedName.resize(1); > > + QualifiedName.append(User.getKey().begin(), User.getKey().end()); > > + Output.insert(QualifiedName); > > + } > > + > > + return !Output.empty(); > > + } > > +}; > > + > > +class CompletionTest : public testing::Test { > > +protected: > > + /// Unique temporary directory in which all created filesystem > entities must > > + /// be placed. It is removed at the end of the test suite. > > + static SmallString<128> BaseDir; > > + > > + static SmallString<128> DirFoo; > > + static SmallString<128> DirFooA; > > + static SmallString<128> DirFooB; > > + static SmallString<128> DirFooC; > > + static SmallString<128> DirBar; > > + static SmallString<128> DirBaz; > > + static SmallString<128> DirTestFolder; > > + > > + static SmallString<128> FileAA; > > + static SmallString<128> FileAB; > > + static SmallString<128> FileAC; > > + static SmallString<128> FileFoo; > > + static SmallString<128> FileBar; > > + static SmallString<128> FileBaz; > > + > > + static void SetUpTestCase() { > > + ASSERT_NO_ERROR(fs::createUniqueDirectory("FsCompletion", BaseDir)); > > + const char *DirNames[] = {"foo", "fooa", "foob", "fooc", > > + "bar", "baz", "test_folder"}; > > + const char *FileNames[] = {"aa%%%%.tmp", "ab%%%%.tmp", > "ac%%%%.tmp", > > + "foo%%%%.tmp", "bar%%%%.tmp", > "baz%%%%.tmp"}; > > + SmallString<128> *Dirs[] = {&DirFoo, &DirFooA, &DirFooB, > &DirFooC, > > + &DirBar, &DirBaz, &DirTestFolder}; > > + for (auto Dir : llvm::zip(DirNames, Dirs)) { > > + auto &Path = *std::get<1>(Dir); > > + Path = BaseDir; > > + path::append(Path, std::get<0>(Dir)); > > + ASSERT_NO_ERROR(fs::create_directory(Path)); > > + } > > + > > + SmallString<128> *Files[] = {&FileAA, &FileAB, &FileAC, > > + &FileFoo, &FileBar, &FileBaz}; > > + for (auto File : llvm::zip(FileNames, Files)) { > > + auto &Path = *std::get<1>(File); > > + Path = BaseDir; > > + path::append(Path, std::get<0>(File)); > > + int FD; > > + ASSERT_NO_ERROR(fs::createUniqueFile(Path, FD, Path)); > > + ::close(FD); > > + } > > + } > > + > > + static void TearDownTestCase() { > > + ASSERT_NO_ERROR(fs::remove_directories(BaseDir)); > > + } > > + > > + static bool HasEquivalentFile(const Twine &Path, const StringList > &Paths) { > > + for (int I = 0; I < Paths.GetSize(); ++I) { > > + if (fs::equivalent(Path, Paths[I])) > > + return true; > > + } > > + return false; > > + } > > + > > + static bool ContainsExactString(const Twine &Str, const StringList > &Paths) { > > + SmallString<16> Storage; > > + StringRef Rendered = Str.toStringRef(Storage); > > + for (int I = 0; I < Paths.GetSize(); ++I) { > > + if (Paths[I] == Rendered) > > + return true; > > + } > > + return false; > > + } > > +}; > > + > > +SmallString<128> CompletionTest::BaseDir; > > + > > +SmallString<128> CompletionTest::DirFoo; > > +SmallString<128> CompletionTest::DirFooA; > > +SmallString<128> CompletionTest::DirFooB; > > +SmallString<128> CompletionTest::DirFooC; > > +SmallString<128> CompletionTest::DirBar; > > +SmallString<128> CompletionTest::DirBaz; > > +SmallString<128> CompletionTest::DirTestFolder; > > + > > +SmallString<128> CompletionTest::FileAA; > > +SmallString<128> CompletionTest::FileAB; > > +SmallString<128> CompletionTest::FileAC; > > +SmallString<128> CompletionTest::FileFoo; > > +SmallString<128> CompletionTest::FileBar; > > +SmallString<128> CompletionTest::FileBaz; > > +} > > + > > +TEST_F(CompletionTest, DirCompletionAbsolute) { > > + // All calls to DiskDirectories() return only directories, even when > > + // there are files which also match. The tests below all check this > > + // by asserting an exact result count, and verifying against known > > + // folders. > > + > > + StringList Results; > > + // When a directory is specified that doesn't end in a slash, it > searches > > + // for that directory, not items under it. > > + int Count = CommandCompletions::DiskDirectories2(BaseDir, Results); > > + ASSERT_EQ(1, Count); > > + ASSERT_EQ(Count, Results.GetSize()); > > + EXPECT_TRUE(HasEquivalentFile(BaseDir, Results)); > > + > > + // When the same directory ends with a slash, it finds all children. > > + Count = CommandCompletions::DiskDirectories2(Twine(BaseDir) + "/", > Results); > > + ASSERT_EQ(7, Count); > > + ASSERT_EQ(Count, Results.GetSize()); > > + EXPECT_TRUE(HasEquivalentFile(DirFoo, Results)); > > + EXPECT_TRUE(HasEquivalentFile(DirFooA, Results)); > > + EXPECT_TRUE(HasEquivalentFile(DirFooB, Results)); > > + EXPECT_TRUE(HasEquivalentFile(DirFooC, Results)); > > + EXPECT_TRUE(HasEquivalentFile(DirBar, Results)); > > + EXPECT_TRUE(HasEquivalentFile(DirBaz, Results)); > > + EXPECT_TRUE(HasEquivalentFile(DirTestFolder, Results)); > > + > > + // When a partial name matches, it returns all matches. If it > matches both > > + // a full name AND some partial names, it returns all of them. > > + Count = > > + CommandCompletions::DiskDirectories2(Twine(BaseDir) + "/foo", > Results); > > + ASSERT_EQ(4, Count); > > + ASSERT_EQ(Count, Results.GetSize()); > > + EXPECT_TRUE(HasEquivalentFile(DirFoo, Results)); > > + EXPECT_TRUE(HasEquivalentFile(DirFooA, Results)); > > + EXPECT_TRUE(HasEquivalentFile(DirFooB, Results)); > > + EXPECT_TRUE(HasEquivalentFile(DirFooC, Results)); > > + > > + // If it matches only partial names, it still works as expected. > > + Count = CommandCompletions::DiskDirectories2(Twine(BaseDir) + "/b", > Results); > > + ASSERT_EQ(2, Count); > > + ASSERT_EQ(Count, Results.GetSize()); > > + EXPECT_TRUE(HasEquivalentFile(DirBar, Results)); > > + EXPECT_TRUE(HasEquivalentFile(DirBaz, Results)); > > +} > > + > > +TEST_F(CompletionTest, FileCompletionAbsolute) { > > + // All calls to DiskFiles() return both files and directories The > tests below > > + // all check this by asserting an exact result count, and verifying > against > > + // known folders. > > + > > + StringList Results; > > + // When an item is specified that doesn't end in a slash but exactly > matches > > + // one item, it returns that item. > > + int Count = CommandCompletions::DiskFiles2(Twine(BaseDir) + "/fooa", > Results); > > + ASSERT_EQ(1, Count); > > + ASSERT_EQ(Count, Results.GetSize()); > > + EXPECT_TRUE(HasEquivalentFile(DirFooA, Results)); > > + > > + // The previous check verified a directory match. But it should work > for > > + // files too. > > + Count = CommandCompletions::DiskFiles2(Twine(BaseDir) + "/aa", > Results); > > + ASSERT_EQ(1, Count); > > + ASSERT_EQ(Count, Results.GetSize()); > > + EXPECT_TRUE(HasEquivalentFile(FileAA, Results)); > > + > > + // When it ends with a slash, it should find all files and > directories. > > + Count = CommandCompletions::DiskFiles2(Twine(BaseDir) + "/", Results); > > + ASSERT_EQ(13, Count); > > + ASSERT_EQ(Count, Results.GetSize()); > > + EXPECT_TRUE(HasEquivalentFile(DirFoo, Results)); > > + EXPECT_TRUE(HasEquivalentFile(DirFooA, Results)); > > + EXPECT_TRUE(HasEquivalentFile(DirFooB, Results)); > > + EXPECT_TRUE(HasEquivalentFile(DirFooC, Results)); > > + EXPECT_TRUE(HasEquivalentFile(DirBar, Results)); > > + EXPECT_TRUE(HasEquivalentFile(DirBaz, Results)); > > + EXPECT_TRUE(HasEquivalentFile(DirTestFolder, Results)); > > + > > + EXPECT_TRUE(HasEquivalentFile(FileAA, Results)); > > + EXPECT_TRUE(HasEquivalentFile(FileAB, Results)); > > + EXPECT_TRUE(HasEquivalentFile(FileAC, Results)); > > + EXPECT_TRUE(HasEquivalentFile(FileFoo, Results)); > > + EXPECT_TRUE(HasEquivalentFile(FileBar, Results)); > > + EXPECT_TRUE(HasEquivalentFile(FileBaz, Results)); > > + > > + // When a partial name matches, it returns all file & directory > matches. > > + Count = CommandCompletions::DiskFiles2(Twine(BaseDir) + "/foo", > Results); > > + ASSERT_EQ(5, Count); > > + ASSERT_EQ(Count, Results.GetSize()); > > + EXPECT_TRUE(HasEquivalentFile(DirFoo, Results)); > > + EXPECT_TRUE(HasEquivalentFile(DirFooA, Results)); > > + EXPECT_TRUE(HasEquivalentFile(DirFooB, Results)); > > + EXPECT_TRUE(HasEquivalentFile(DirFooC, Results)); > > + EXPECT_TRUE(HasEquivalentFile(FileFoo, Results)); > > +} > > + > > +TEST_F(CompletionTest, DirCompletionUsername) { > > + MockTildeExpressionResolver Resolver("James", BaseDir); > > + Resolver.AddKnownUser("Kirk", DirFooB); > > + Resolver.AddKnownUser("Lars", DirFooC); > > + Resolver.AddKnownUser("Jason", DirFoo); > > + Resolver.AddKnownUser("Larry", DirFooA); > > + > > + // Just resolving current user's home directory by itself should > return the > > + // directory. > > + StringList Results; > > + int Count = CommandCompletions::DiskDirectories2("~", Results, > &Resolver); > > + ASSERT_EQ(1, Count); > > + ASSERT_EQ(Count, Results.GetSize()); > > + EXPECT_TRUE(ContainsExactString(Twine("~") + path::get_separator(), > Results)); > > + > > + // With a slash appended, it should return all items in the directory. > > + Count = CommandCompletions::DiskDirectories2("~/", Results, > &Resolver); > > + ASSERT_EQ(7, Count); > > + ASSERT_EQ(Count, Results.GetSize()); > > + EXPECT_TRUE( > > + ContainsExactString(Twine("~/foo") + path::get_separator(), > Results)); > > + EXPECT_TRUE( > > + ContainsExactString(Twine("~/fooa") + path::get_separator(), > Results)); > > + EXPECT_TRUE( > > + ContainsExactString(Twine("~/foob") + path::get_separator(), > Results)); > > + EXPECT_TRUE( > > + ContainsExactString(Twine("~/fooc") + path::get_separator(), > Results)); > > + EXPECT_TRUE( > > + ContainsExactString(Twine("~/bar") + path::get_separator(), > Results)); > > + EXPECT_TRUE( > > + ContainsExactString(Twine("~/baz") + path::get_separator(), > Results)); > > + EXPECT_TRUE(ContainsExactString( > > + Twine("~/test_folder") + path::get_separator(), Results)); > > + > > + // With ~username syntax it should return one match if there is an > exact > > + // match. > > + // It shouldn't translate to the actual directory, it should keep the > form the > > + // user typed. > > + Count = CommandCompletions::DiskDirectories2("~Lars", Results, > &Resolver); > > + ASSERT_EQ(1, Count); > > + ASSERT_EQ(Count, Results.GetSize()); > > + EXPECT_TRUE( > > + ContainsExactString(Twine("~Lars") + path::get_separator(), > Results)); > > + > > + // But with a username that is not found, no results are returned. > > + Count = CommandCompletions::DiskDirectories2("~Dave", Results, > &Resolver); > > + ASSERT_EQ(0, Count); > > + ASSERT_EQ(Count, Results.GetSize()); > > + > > + // And if there are multiple matches, it should return all of them. > > + Count = CommandCompletions::DiskDirectories2("~La", Results, > &Resolver); > > + ASSERT_EQ(2, Count); > > + ASSERT_EQ(Count, Results.GetSize()); > > + EXPECT_TRUE( > > + ContainsExactString(Twine("~Lars") + path::get_separator(), > Results)); > > + EXPECT_TRUE( > > + ContainsExactString(Twine("~Larry") + path::get_separator(), > Results)); > > +} > > > > > > _______________________________________________ > > lldb-commits mailing list > > lldb-commits@lists.llvm.org > > http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits > >
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits