//===--- iwyu_include_picker_test.cc - test the iwyu include-picker file --===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

// Tests the internals of iwyu_include_picker.{h,cc}, and a few related
// functions from iwyu_path_util.h

#include "iwyu_include_picker.h"

#include <stddef.h>
#include <algorithm>
#include <string>
#include <vector>

#include "iwyu_globals.h"
#include "iwyu_path_util.h"
#include "testing/base/public/gunit.h"

namespace clang {
class ASTConsumer;
class ASTFrontendAction;
class CompilerInstance;
}  // namespace clang

using clang::ASTConsumer;
using clang::ASTFrontendAction;
using clang::CompilerInstance;

namespace include_what_you_use {

namespace {

// Returns a string representing the first element where actual (a vector),
// and expected (an array) differ, or "" if they're identical.
template <size_t kCount> string VectorDiff(const string (&expected)[kCount],
                                           const vector<string>& actual) {
  for (int i = 0; i < std::min(kCount, actual.size()); ++i) {
    if (expected[i] != actual[i]) {
      return ("Differ at #" + std::to_string(i) + ": expected=" + expected[i] +
              ", actual=" + actual[i]);
    }
  }
  if (kCount < actual.size()) {
    return ("Differ at #" + std::to_string(kCount) +
            ": expected at EOF, actual=" + actual[kCount]);
  } else if (actual.size() < kCount) {
    return ("Differ at #" + std::to_string(kCount) + ": expected=" +
            expected[actual.size()] + ", actual at EOF");
  } else {
    return "";
  }
}

// Different format than usual due to VA_ARGS.  First comes the actual
// value (as a string vector), then the expected values (as strings).
#define EXPECT_VECTOR_STREQ(actual, ...)  do {  \
  const string expected[] = { __VA_ARGS__ };    \
  EXPECT_EQ("", VectorDiff(expected, actual));  \
} while (0)


TEST(GetCanonicalName, StripsKnownSuffixes) {
  EXPECT_EQ("my/path/foo", GetCanonicalName("my/path/foo.cxx"));
  EXPECT_EQ("my/path/foo", GetCanonicalName("my/path/foo.cpp"));
  EXPECT_EQ("my/path/foo", GetCanonicalName("my/path/foo.cc"));
  EXPECT_EQ("my/path/foo", GetCanonicalName("my/path/foo.h"));
  EXPECT_EQ("my/path/foo", GetCanonicalName("my/path/foo-inl.h"));
  EXPECT_EQ("my/path/foo", GetCanonicalName("my/path/foo_unittest.cc"));
  EXPECT_EQ("my/path/foo", GetCanonicalName("my/path/foo_regtest.cc"));
  EXPECT_EQ("my/path/foo", GetCanonicalName("my/path/foo_test.cc"));
  EXPECT_EQ("my/path/foo", GetCanonicalName("my/path/foo.c"));
  EXPECT_EQ("my/path/foo", GetCanonicalName("my/path/foo-inl_unittest.cc"));
  EXPECT_EQ("my/path/foo_mytest", GetCanonicalName("my/path/foo_mytest.cc"));
}

TEST(GetCanonicalName, StripsQuotes) {
  EXPECT_EQ("set", GetCanonicalName("<set>"));
  EXPECT_EQ("bits/stl_set", GetCanonicalName("<bits/stl_set.h>"));
  EXPECT_EQ("my/path/foo", GetCanonicalName("\"my/path/foo-inl.h\""));
}

TEST(GetCanonicalName, MapsInternalToPublic) {
  EXPECT_EQ("my/public/foo", GetCanonicalName("my/internal/foo.cc"));
  EXPECT_EQ("my/public/foo", GetCanonicalName("my/public/foo.cc"));
  EXPECT_EQ("my/public/foo", GetCanonicalName("my/internal/foo.h"));
  EXPECT_EQ("my/public/foo", GetCanonicalName("my/public/foo.h"));
  EXPECT_EQ("internal/foo", GetCanonicalName("internal/foo"));
  EXPECT_EQ("path/internal_impl", GetCanonicalName("path/internal_impl.cc"));
}

TEST(IsSystemIncludeFile, Basic) {
  EXPECT_FALSE(IsSystemIncludeFile("foo.h"));
  EXPECT_TRUE(IsSystemIncludeFile("/usr/include/string.h"));
  EXPECT_TRUE(IsSystemIncludeFile("/usr/include/c++/4.3/bits/stl_vector.h"));
}

TEST(ConvertToQuotedInclude, Basic) {
  EXPECT_EQ("\"foo.h\"", ConvertToQuotedInclude("foo.h"));
  EXPECT_EQ("<string.h>", ConvertToQuotedInclude("/usr/include/string.h"));
  EXPECT_EQ("<bits/stl_vector.h>",
            ConvertToQuotedInclude("/usr/include/c++/4.3/bits/stl_vector.h"));
}


TEST(DynamicMapping, DoesMapping) {
  IncludePicker p;
  p.AddDirectInclude("project/public/foo.h", "project/internal/private.h", "");
  p.FinalizeAddedIncludes();
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepath("project/internal/private.h"),
      "\"project/public/foo.h\"");
}

TEST(DynamicMapping, MultiplePublicFiles) {
  IncludePicker p;
  p.AddDirectInclude("project/public/foo.h", "project/internal/private.h", "");
  p.AddDirectInclude("project/public/bar.h", "project/internal/private.h", "");
  p.AddDirectInclude("project/public/bar.h", "project/internal/other.h", "");
  p.FinalizeAddedIncludes();
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepath("project/internal/private.h"),
      "\"project/public/foo.h\"", "\"project/public/bar.h\"");
}

TEST(DynamicMapping, TransitiveMapping) {
  IncludePicker p;
  p.AddDirectInclude("project/public/foo.h", "project/internal/private.h", "");
  p.AddDirectInclude("project/internal/private.h", "project/internal/other.h",
                     "");
  p.FinalizeAddedIncludes();
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepath("project/internal/other.h"),
      "\"project/public/foo.h\"");
}

TEST(DynamicMapping, MultipleTransitiveMapping) {
  IncludePicker p;
  p.AddDirectInclude("project/public/foo.h", "project/internal/private.h", "");
  p.AddDirectInclude("project/public/bar.h", "project/internal/private.h", "");
  p.AddDirectInclude("project/public/baz.h", "project/internal/private2.h", "");
  p.AddDirectInclude("project/internal/private.h", "project/internal/other.h",
                     "");
  p.AddDirectInclude("project/internal/private2.h", "project/internal/other.h",
                     "");
  p.FinalizeAddedIncludes();
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepath("project/internal/other.h"),
      "\"project/public/foo.h\"", "\"project/public/bar.h\"",
      "\"project/public/baz.h\"");
}

TEST(DynamicMapping, NormalizesAsm) {
  IncludePicker p;
  p.AddDirectInclude("/usr/include/types.h",
                     "/usr/include/asm-cris/posix_types.h", "");
  p.FinalizeAddedIncludes();
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepath("/usr/src/linux-headers-2.6.24-gg23/"
                                       "include/asm-cris/posix_types.h"),
      "<asm/posix_types.h>");
}

TEST(DynamicMapping, PrivateToPublicMapping) {
  IncludePicker p;
  // These names are not the public/internal names that AddInclude looks at.
  p.AddMapping("\"project/private/foo.h\"", "\"project/not_private/bar.h\"");
  p.MarkIncludeAsPrivate("\"project/private/foo.h\"");
  p.FinalizeAddedIncludes();
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepath("project/private/foo.h"),
      "\"project/not_private/bar.h\"");
}

TEST(GetCandidateHeadersForSymbol, Basic) {
  IncludePicker p;
  p.FinalizeAddedIncludes();
  EXPECT_VECTOR_STREQ(p.GetCandidateHeadersForSymbol("dev_t"),
                      "<sys/types.h>", "<sys/stat.h>");
  EXPECT_VECTOR_STREQ(p.GetCandidateHeadersForSymbol("NULL"),
                      "<stddef.h>", "<cstddef>", "<clocale>", "<cstdio>",
                      "<cstdlib>", "<cstring>", "<ctime>", "<cwchar>",
                      "<locale.h>", "<stdio.h>", "<stdlib.h>", "<string.h>",
                      "<time.h>", "<wchar.h>"
                      );
  EXPECT_VECTOR_STREQ(p.GetCandidateHeadersForSymbol("std::allocator"),
                      "<memory>", "<string>", "<vector>", "<map>", "<set>");
  EXPECT_EQ(0, p.GetCandidateHeadersForSymbol("foo").size());
}

TEST(GetCandidateHeadersForFilepath, C) {
  IncludePicker p;
  p.FinalizeAddedIncludes();
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepath("/usr/include/bits/dlfcn.h"),
      "<dlfcn.h>");
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepath("/usr/grte/v1/include/assert.h"),
      "<assert.h>", "<cassert>");
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepath("/usr/grte/v1/include/stdarg.h"),
      "<stdarg.h>", "<cstdarg>");
}

TEST(GetCandidateHeadersForFilepath, CXX) {
  IncludePicker p;
  p.FinalizeAddedIncludes();
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepath("/usr/include/c++/4.2/bits/allocator.h"),
      "<memory>");
}

TEST(GetCandidateHeadersForFilepath, NoIdentityRegex) {
  IncludePicker p;
  // Make sure we don't complain when the key of a mapping is a regex
  // that includes the value (which would, naively, lead to an identity
  // mapping).
  p.AddMapping("@\"mydir/.*\\.h\"", "\"mydir/include.h\"");
  p.MarkIncludeAsPrivate("@\"mydir/.*\\.h\"");   // will *not* apply to include.h!
  // Add a direct include that should be mapped, and that already is.
  p.AddDirectInclude("a.h", "mydir/internal.h", "");
  p.AddDirectInclude("b.h", "mydir/include.h", "");
  p.FinalizeAddedIncludes();
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepath("mydir/internal.h"),
      "\"mydir/include.h\"");
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepath("mydir/include.h"),
      "\"mydir/include.h\"");
}

TEST(GetCandidateHeadersForFilepath, NotInAnyMap) {
  IncludePicker p;
  p.FinalizeAddedIncludes();
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepath("/usr/grte/v1/include/poll.h"),
      "<poll.h>");
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepath("././././my/dot.h"),
      "\"my/dot.h\"");
}

TEST(GetCandidateHeadersForFilepath, IncludeRecursion) {
  IncludePicker p;
  p.FinalizeAddedIncludes();
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepath("/usr/include/c++/4.2/bits/istream.tcc"),
      "<istream>", "<fstream>", "<iostream>", "<sstream>");
}

TEST(GetCandidateHeadersForFilepath, PrivateValueInRecursion) {
  IncludePicker p;
  p.FinalizeAddedIncludes();
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepath("/usr/include/linux/errno.h"),
      "<errno.h>", "<cerrno>");
}

TEST(GetCandidateHeadersForFilepath, NoBuiltin) {
  // Make sure we never specify "<built-in>" as an #include mapping.
  IncludePicker p;
  p.AddDirectInclude("<built-in>", "foo/bar/internal/code.cc", "");
  p.AddDirectInclude("foo/bar/internal/code.cc", "foo/qux/internal/lib.h", "");

  p.FinalizeAddedIncludes();
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepath("foo/qux/internal/lib.h"),
      "\"foo/qux/internal/lib.h\"");
}

TEST(GetCandidateHeadersForFilepathIncludedFrom, NoInternal) {
  IncludePicker p;
  p.FinalizeAddedIncludes();
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepathIncludedFrom("/usr/include/bits/dlfcn.h",
                                                   "mydir/myapp.h"),
      "<dlfcn.h>");
}

TEST(GetCandidateHeadersForFilepathIncludedFrom, Internal) {
  IncludePicker p;
  // clang always has <built-in> #including the file specified on the cmdline.
  p.AddDirectInclude("<built-in>", "foo/bar/internal/code.cc", "");
  p.AddDirectInclude("foo/bar/internal/code.cc", "foo/bar/public/code.h", "");
  p.AddDirectInclude("foo/bar/public/code.h", "foo/bar/internal/hdr.h", "");
  p.FinalizeAddedIncludes();
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepathIncludedFrom("foo/bar/internal/hdr.h",
                                                   "foo/bar/internal/code.cc"),
      "\"foo/bar/internal/hdr.h\"");
}

TEST(GetCandidateHeadersForFilepathIncludedFrom, OtherInternal) {
  IncludePicker p;
  p.AddDirectInclude("foo/bar/public/code.h", "foo/bar/internal/hdr.h", "");
  p.FinalizeAddedIncludes();
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepathIncludedFrom("foo/bar/internal/hdr.h",
                                                   "baz/internal/code.cc"),
      "\"foo/bar/public/code.h\"");
}

TEST(GetCandidateHeadersForFilepathIncludedFrom, PublicToInternal) {
  IncludePicker p;
  p.AddDirectInclude("foo/bar/public/code.cc", "foo/bar/public/code.h", "");
  p.AddDirectInclude("foo/bar/public/code.cc", "foo/bar/public/code2.h", "");
  p.AddDirectInclude("foo/bar/public/code.h", "foo/bar/internal/hdr.h", "");
  p.FinalizeAddedIncludes();
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepathIncludedFrom("foo/bar/internal/hdr.h",
                                                   "foo/bar/public/code.h"),
      "\"foo/bar/internal/hdr.h\"");
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepathIncludedFrom("foo/bar/internal/hdr.h",
                                                   "foo/bar/public/code2.h"),
      "\"foo/bar/internal/hdr.h\"");
}

TEST(GetCandidateHeadersForFilepathIncludedFrom, FriendRegex) {
  IncludePicker p;
  p.AddDirectInclude("baz.cc", "baz.h", "");
  p.AddDirectInclude("baz.cc", "abcde.h", "");
  p.AddDirectInclude("baz.cc", "random.h", "");
  p.AddDirectInclude("baz.h", "project/private/bar.h", "");
  p.AddDirectInclude("abcde.h", "project/private/bar.h", "");
  p.AddDirectInclude("random.h", "project/private/bar.h", "");
  p.AddMapping("\"project/private/bar.h\"", "\"foo.h\"");
  p.MarkIncludeAsPrivate("\"project/private/bar.h\"");
  p.AddFriendRegex("project/private/bar.h", "\"baz.*\"");
  p.AddFriendRegex("project/private/bar.h", "\"a.c.+\\.h\"");
  p.FinalizeAddedIncludes();
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepathIncludedFrom("project/private/bar.h",
                                                   "random.h"),
      "\"foo.h\"");
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepathIncludedFrom("project/private/bar.h",
                                                   "baz.h"),
      "\"project/private/bar.h\"");
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepathIncludedFrom("project/private/bar.h",
                                                   "abcde.h"),
      "\"project/private/bar.h\"");
}

TEST(GetCandidateHeadersForFilepathIncludedFrom, PreservesWrittenForm) {
  IncludePicker p;
  p.AddDirectInclude("baz.cc", "baz.h", "\"./././baz.h\"");
  p.FinalizeAddedIncludes();
  EXPECT_VECTOR_STREQ(
      p.GetCandidateHeadersForFilepathIncludedFrom("baz.h", "baz.cc"),
      "\"./././baz.h\"");
}

TEST(HasMapping, IncludeMatch) {
  IncludePicker p;
  p.FinalizeAddedIncludes();
  EXPECT_TRUE(p.HasMapping("/usr/include/stdio.h",
                           "/usr/include/c++/4.2/cstdio"));
  EXPECT_TRUE(p.HasMapping("/usr/include/c++/4.2/bits/stl_deque.h",
                           "/usr/include/c++/4.2/deque"));
  EXPECT_TRUE(p.HasMapping("/usr/include/bits/stat.h",
                           "/usr/include/sys/stat.h"));
  EXPECT_FALSE(p.HasMapping("/usr/include/bits/syscall.h",
                            "/usr/include/sys/stat.h"));
}

TEST(HasMapping, IncludeMatchIndirectly) {
  IncludePicker p;
  p.FinalizeAddedIncludes();
  EXPECT_TRUE(p.HasMapping("/usr/include/c++/4.2/ios",
                           "/usr/include/c++/4.2/iostream"));
  EXPECT_TRUE(p.HasMapping("/usr/include/linux/errno.h",
                           "/usr/include/errno.h"));
}

TEST(HasMapping, IncludeMatchDifferentMaps) {
  IncludePicker p;
  p.FinalizeAddedIncludes();
  // Testing when a google path re-exports a c++ system #include.
  EXPECT_TRUE(p.HasMapping("/usr/include/c++/4.2/ostream", "base/logging.h"));
   // Do some indirect checking too.
  EXPECT_TRUE(p.HasMapping("/usr/include/c++/4.2/ios", "base/logging.h"));
}

}  // namespace
}  // namespace include_what_you_use


int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  include_what_you_use::InitGlobalsAndFlagsForTesting();
  return RUN_ALL_TESTS();
}
