[Lldb-commits] [lldb] r297585 - Make file / directory completion work properly on Windows.

Zachary Turner via lldb-commits lldb-commits at lists.llvm.org
Fri Apr 14 19:57:49 PDT 2017


Should be fixed in r300386, let me know if you're still experiencing
problems.

On Tue, Apr 11, 2017 at 5:59 PM Zachary Turner <zturner at google.com> wrote:

> Thanks for the heads up, I'll try to look at this tomorrow.
>
> On Tue, Apr 11, 2017 at 5:55 PM Jason Molenda <jmolenda at 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 at 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,
>> &parameters);
>> > +  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 at lists.llvm.org
>> > http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits
>>
>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/lldb-commits/attachments/20170415/cef60840/attachment-0001.html>


More information about the lldb-commits mailing list