// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "tools/gn/filesystem_utils.h"

#include <thread>

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "tools/gn/target.h"
#include "util/build_config.h"
#include "util/test/test.h"

TEST(FilesystemUtils, FileExtensionOffset) {
  EXPECT_EQ(std::string::npos, FindExtensionOffset(""));
  EXPECT_EQ(std::string::npos, FindExtensionOffset("foo/bar/baz"));
  EXPECT_EQ(4u, FindExtensionOffset("foo."));
  EXPECT_EQ(4u, FindExtensionOffset("f.o.bar"));
  EXPECT_EQ(std::string::npos, FindExtensionOffset("foo.bar/"));
  EXPECT_EQ(std::string::npos, FindExtensionOffset("foo.bar/baz"));
}

TEST(FilesystemUtils, FindExtension) {
  std::string input;
  EXPECT_EQ("", FindExtension(&input).as_string());
  input = "foo/bar/baz";
  EXPECT_EQ("", FindExtension(&input).as_string());
  input = "foo.";
  EXPECT_EQ("", FindExtension(&input).as_string());
  input = "f.o.bar";
  EXPECT_EQ("bar", FindExtension(&input).as_string());
  input = "foo.bar/";
  EXPECT_EQ("", FindExtension(&input).as_string());
  input = "foo.bar/baz";
  EXPECT_EQ("", FindExtension(&input).as_string());
}

TEST(FilesystemUtils, FindFilenameOffset) {
  EXPECT_EQ(0u, FindFilenameOffset(""));
  EXPECT_EQ(0u, FindFilenameOffset("foo"));
  EXPECT_EQ(4u, FindFilenameOffset("foo/"));
  EXPECT_EQ(4u, FindFilenameOffset("foo/bar"));
}

TEST(FilesystemUtils, RemoveFilename) {
  std::string s;

  RemoveFilename(&s);
  EXPECT_STREQ("", s.c_str());

  s = "foo";
  RemoveFilename(&s);
  EXPECT_STREQ("", s.c_str());

  s = "/";
  RemoveFilename(&s);
  EXPECT_STREQ("/", s.c_str());

  s = "foo/bar";
  RemoveFilename(&s);
  EXPECT_STREQ("foo/", s.c_str());

  s = "foo/bar/baz.cc";
  RemoveFilename(&s);
  EXPECT_STREQ("foo/bar/", s.c_str());
}

TEST(FilesystemUtils, FindDir) {
  std::string input;
  EXPECT_EQ("", FindDir(&input));
  input = "/";
  EXPECT_EQ("/", FindDir(&input));
  input = "foo/";
  EXPECT_EQ("foo/", FindDir(&input));
  input = "foo/bar/baz";
  EXPECT_EQ("foo/bar/", FindDir(&input));
}

TEST(FilesystemUtils, FindLastDirComponent) {
  SourceDir empty;
  EXPECT_EQ("", FindLastDirComponent(empty));

  SourceDir root("/");
  EXPECT_EQ("", FindLastDirComponent(root));

  SourceDir srcroot("//");
  EXPECT_EQ("", FindLastDirComponent(srcroot));

  SourceDir regular1("//foo/");
  EXPECT_EQ("foo", FindLastDirComponent(regular1));

  SourceDir regular2("//foo/bar/");
  EXPECT_EQ("bar", FindLastDirComponent(regular2));
}

TEST(FilesystemUtils, EnsureStringIsInOutputDir) {
  SourceDir output_dir("//out/Debug/");

  // Some outside.
  Err err;
  EXPECT_FALSE(EnsureStringIsInOutputDir(output_dir, "//foo", nullptr, &err));
  EXPECT_TRUE(err.has_error());
  err = Err();
  EXPECT_FALSE(
      EnsureStringIsInOutputDir(output_dir, "//out/Debugit", nullptr, &err));
  EXPECT_TRUE(err.has_error());

  // Some inside.
  err = Err();
  EXPECT_TRUE(
      EnsureStringIsInOutputDir(output_dir, "//out/Debug/", nullptr, &err));
  EXPECT_FALSE(err.has_error());
  EXPECT_TRUE(
      EnsureStringIsInOutputDir(output_dir, "//out/Debug/foo", nullptr, &err));
  EXPECT_FALSE(err.has_error());

  // Pattern but no template expansions are allowed.
  EXPECT_FALSE(EnsureStringIsInOutputDir(output_dir, "{{source_gen_dir}}",
                                         nullptr, &err));
  EXPECT_TRUE(err.has_error());
}

TEST(FilesystemUtils, IsPathAbsolute) {
  EXPECT_TRUE(IsPathAbsolute("/foo/bar"));
  EXPECT_TRUE(IsPathAbsolute("/"));
  EXPECT_FALSE(IsPathAbsolute(""));
  EXPECT_FALSE(IsPathAbsolute("//"));
  EXPECT_FALSE(IsPathAbsolute("//foo/bar"));

#if defined(OS_WIN)
  EXPECT_TRUE(IsPathAbsolute("C:/foo"));
  EXPECT_TRUE(IsPathAbsolute("C:/"));
  EXPECT_TRUE(IsPathAbsolute("C:\\foo"));
  EXPECT_TRUE(IsPathAbsolute("C:\\"));
  EXPECT_TRUE(IsPathAbsolute("/C:/foo"));
  EXPECT_TRUE(IsPathAbsolute("/C:\\foo"));
#endif
}

TEST(FilesystemUtils, MakeAbsolutePathRelativeIfPossible) {
  std::string dest;

#if defined(OS_WIN)
  EXPECT_TRUE(
      MakeAbsolutePathRelativeIfPossible("C:\\base", "C:\\base\\foo", &dest));
  EXPECT_EQ("//foo", dest);
  EXPECT_TRUE(
      MakeAbsolutePathRelativeIfPossible("C:\\base", "/C:/base/foo", &dest));
  EXPECT_EQ("//foo", dest);
  EXPECT_TRUE(
      MakeAbsolutePathRelativeIfPossible("c:\\base", "C:\\base\\foo\\", &dest));
  EXPECT_EQ("//foo\\", dest);

  EXPECT_FALSE(MakeAbsolutePathRelativeIfPossible("C:\\base", "C:\\ba", &dest));
  EXPECT_FALSE(MakeAbsolutePathRelativeIfPossible("C:\\base",
                                                  "C:\\/notbase/foo", &dest));
#else

  EXPECT_TRUE(MakeAbsolutePathRelativeIfPossible("/base", "/base/foo/", &dest));
  EXPECT_EQ("//foo/", dest);
  EXPECT_TRUE(MakeAbsolutePathRelativeIfPossible("/base", "/base/foo", &dest));
  EXPECT_EQ("//foo", dest);
  EXPECT_TRUE(
      MakeAbsolutePathRelativeIfPossible("/base/", "/base/foo/", &dest));
  EXPECT_EQ("//foo/", dest);

  EXPECT_FALSE(MakeAbsolutePathRelativeIfPossible("/base", "/ba", &dest));
  EXPECT_FALSE(
      MakeAbsolutePathRelativeIfPossible("/base", "/notbase/foo", &dest));
#endif
}

TEST(FilesystemUtils, MakeAbsoluteFilePathRelativeIfPossible) {
#if defined(OS_WIN)
  EXPECT_EQ(
      base::FilePath(L"out\\Debug"),
      MakeAbsoluteFilePathRelativeIfPossible(
          base::FilePath(L"C:\\src"), base::FilePath(L"C:\\src\\out\\Debug")));
  EXPECT_EQ(base::FilePath(L".\\gn"),
            MakeAbsoluteFilePathRelativeIfPossible(
                base::FilePath(L"C:\\src\\out\\Debug"),
                base::FilePath(L"C:\\src\\out\\Debug\\gn")));
  EXPECT_EQ(
      base::FilePath(L"..\\.."),
      MakeAbsoluteFilePathRelativeIfPossible(
          base::FilePath(L"C:\\src\\out\\Debug"), base::FilePath(L"C:\\src")));
  EXPECT_EQ(base::FilePath(L"."),
            MakeAbsoluteFilePathRelativeIfPossible(base::FilePath(L"C:\\src"),
                                                   base::FilePath(L"C:\\src")));
  EXPECT_EQ(base::FilePath(L"..\\..\\..\\u\\v\\w"),
            MakeAbsoluteFilePathRelativeIfPossible(
                base::FilePath(L"C:\\a\\b\\c\\x\\y\\z"),
                base::FilePath(L"C:\\a\\b\\c\\u\\v\\w")));
  EXPECT_EQ(base::FilePath(L"D:\\bar"),
            MakeAbsoluteFilePathRelativeIfPossible(base::FilePath(L"C:\\foo"),
                                                   base::FilePath(L"D:\\bar")));
#else
  EXPECT_EQ(base::FilePath("out/Debug"),
            MakeAbsoluteFilePathRelativeIfPossible(
                base::FilePath("/src"), base::FilePath("/src/out/Debug")));
  EXPECT_EQ(base::FilePath("./gn"), MakeAbsoluteFilePathRelativeIfPossible(
                                        base::FilePath("/src/out/Debug"),
                                        base::FilePath("/src/out/Debug/gn")));
  EXPECT_EQ(base::FilePath("../.."),
            MakeAbsoluteFilePathRelativeIfPossible(
                base::FilePath("/src/out/Debug"), base::FilePath("/src")));
  EXPECT_EQ(base::FilePath("."),
            MakeAbsoluteFilePathRelativeIfPossible(base::FilePath("/src"),
                                                   base::FilePath("/src")));
  EXPECT_EQ(
      base::FilePath("../../../u/v/w"),
      MakeAbsoluteFilePathRelativeIfPossible(base::FilePath("/a/b/c/x/y/z"),
                                             base::FilePath("/a/b/c/u/v/w")));
#endif
}

TEST(FilesystemUtils, NormalizePath) {
  std::string input;

  NormalizePath(&input);
  EXPECT_EQ("", input);

  input = "foo/bar.txt";
  NormalizePath(&input);
  EXPECT_EQ("foo/bar.txt", input);

  input = ".";
  NormalizePath(&input);
  EXPECT_EQ("", input);

  input = "..";
  NormalizePath(&input);
  EXPECT_EQ("..", input);

  input = "foo//bar";
  NormalizePath(&input);
  EXPECT_EQ("foo/bar", input);

  input = "//foo";
  NormalizePath(&input);
  EXPECT_EQ("//foo", input);

  input = "foo/..//bar";
  NormalizePath(&input);
  EXPECT_EQ("bar", input);

  input = "foo/../../bar";
  NormalizePath(&input);
  EXPECT_EQ("../bar", input);

  input = "/../foo";  // Don't go above the root dir.
  NormalizePath(&input);
  EXPECT_EQ("/foo", input);

  input = "//../foo";  // Don't go above the root dir.
  NormalizePath(&input);
  EXPECT_EQ("//foo", input);

  input = "../foo";
  NormalizePath(&input);
  EXPECT_EQ("../foo", input);

  input = "..";
  NormalizePath(&input);
  EXPECT_EQ("..", input);

  input = "./././.";
  NormalizePath(&input);
  EXPECT_EQ("", input);

  input = "../../..";
  NormalizePath(&input);
  EXPECT_EQ("../../..", input);

  input = "../";
  NormalizePath(&input);
  EXPECT_EQ("../", input);

  // Backslash normalization.
  input = "foo\\..\\..\\bar";
  NormalizePath(&input);
  EXPECT_EQ("../bar", input);

  // Trailing slashes should get preserved.
  input = "//foo/bar/";
  NormalizePath(&input);
  EXPECT_EQ("//foo/bar/", input);

#if defined(OS_WIN)
  // Go above and outside of the source root.
  input = "//../foo";
  NormalizePath(&input, "/C:/source/root");
  EXPECT_EQ("/C:/source/foo", input);

  input = "//../foo";
  NormalizePath(&input, "C:\\source\\root");
  EXPECT_EQ("/C:/source/foo", input);

  input = "//../";
  NormalizePath(&input, "/C:/source/root");
  EXPECT_EQ("/C:/source/", input);

  input = "//../foo.txt";
  NormalizePath(&input, "/C:/source/root");
  EXPECT_EQ("/C:/source/foo.txt", input);

  input = "//../foo/bar/";
  NormalizePath(&input, "/C:/source/root");
  EXPECT_EQ("/C:/source/foo/bar/", input);

  // Go above and back into the source root. This should return a system-
  // absolute path. We could arguably return this as a source-absolute path,
  // but that would require additional handling to account for a rare edge
  // case.
  input = "//../root/foo";
  NormalizePath(&input, "/C:/source/root");
  EXPECT_EQ("/C:/source/root/foo", input);

  input = "//../root/foo/bar/";
  NormalizePath(&input, "/C:/source/root");
  EXPECT_EQ("/C:/source/root/foo/bar/", input);

  // Stay inside the source root
  input = "//foo/bar";
  NormalizePath(&input, "/C:/source/root");
  EXPECT_EQ("//foo/bar", input);

  input = "//foo/bar/";
  NormalizePath(&input, "/C:/source/root");
  EXPECT_EQ("//foo/bar/", input);

  // The path should not go above the system root. Note that on Windows, this
  // will consume the drive (C:).
  input = "//../../../../../foo/bar";
  NormalizePath(&input, "/C:/source/root");
  EXPECT_EQ("/foo/bar", input);

  // Test when the source root is the letter drive.
  input = "//../foo";
  NormalizePath(&input, "/C:");
  EXPECT_EQ("/foo", input);

  input = "//../foo";
  NormalizePath(&input, "C:");
  EXPECT_EQ("/foo", input);

  input = "//../foo";
  NormalizePath(&input, "/");
  EXPECT_EQ("/foo", input);

  input = "//../";
  NormalizePath(&input, "\\C:");
  EXPECT_EQ("/", input);

  input = "//../foo.txt";
  NormalizePath(&input, "/C:");
  EXPECT_EQ("/foo.txt", input);
#else
  // Go above and outside of the source root.
  input = "//../foo";
  NormalizePath(&input, "/source/root");
  EXPECT_EQ("/source/foo", input);

  input = "//../";
  NormalizePath(&input, "/source/root");
  EXPECT_EQ("/source/", input);

  input = "//../foo.txt";
  NormalizePath(&input, "/source/root");
  EXPECT_EQ("/source/foo.txt", input);

  input = "//../foo/bar/";
  NormalizePath(&input, "/source/root");
  EXPECT_EQ("/source/foo/bar/", input);

  // Go above and back into the source root. This should return a system-
  // absolute path. We could arguably return this as a source-absolute path,
  // but that would require additional handling to account for a rare edge
  // case.
  input = "//../root/foo";
  NormalizePath(&input, "/source/root");
  EXPECT_EQ("/source/root/foo", input);

  input = "//../root/foo/bar/";
  NormalizePath(&input, "/source/root");
  EXPECT_EQ("/source/root/foo/bar/", input);

  // Stay inside the source root
  input = "//foo/bar";
  NormalizePath(&input, "/source/root");
  EXPECT_EQ("//foo/bar", input);

  input = "//foo/bar/";
  NormalizePath(&input, "/source/root");
  EXPECT_EQ("//foo/bar/", input);

  // The path should not go above the system root.
  input = "//../../../../../foo/bar";
  NormalizePath(&input, "/source/root");
  EXPECT_EQ("/foo/bar", input);

  // Test when the source root is the system root.
  input = "//../foo/bar/";
  NormalizePath(&input, "/");
  EXPECT_EQ("/foo/bar/", input);

  input = "//../";
  NormalizePath(&input, "/");
  EXPECT_EQ("/", input);

  input = "//../foo.txt";
  NormalizePath(&input, "/");
  EXPECT_EQ("/foo.txt", input);

#endif
}

TEST(FilesystemUtils, RebasePath) {
  base::StringPiece source_root("/source/root");

  // Degenerate case.
  EXPECT_EQ(".", RebasePath("//", SourceDir("//"), source_root));
  EXPECT_EQ(".",
            RebasePath("//foo/bar/", SourceDir("//foo/bar/"), source_root));

  // Going up the tree.
  EXPECT_EQ("../foo", RebasePath("//foo", SourceDir("//bar/"), source_root));
  EXPECT_EQ("../foo/", RebasePath("//foo/", SourceDir("//bar/"), source_root));
  EXPECT_EQ("../../foo",
            RebasePath("//foo", SourceDir("//bar/moo"), source_root));
  EXPECT_EQ("../../foo/",
            RebasePath("//foo/", SourceDir("//bar/moo"), source_root));

  // Going down the tree.
  EXPECT_EQ("foo/bar", RebasePath("//foo/bar", SourceDir("//"), source_root));
  EXPECT_EQ("foo/bar/", RebasePath("//foo/bar/", SourceDir("//"), source_root));

  // Going up and down the tree.
  EXPECT_EQ("../../foo/bar",
            RebasePath("//foo/bar", SourceDir("//a/b/"), source_root));
  EXPECT_EQ("../../foo/bar/",
            RebasePath("//foo/bar/", SourceDir("//a/b/"), source_root));

  // Sharing prefix.
  EXPECT_EQ("foo", RebasePath("//a/foo", SourceDir("//a/"), source_root));
  EXPECT_EQ("foo/", RebasePath("//a/foo/", SourceDir("//a/"), source_root));
  EXPECT_EQ("foo", RebasePath("//a/b/foo", SourceDir("//a/b/"), source_root));
  EXPECT_EQ("foo/", RebasePath("//a/b/foo/", SourceDir("//a/b/"), source_root));
  EXPECT_EQ("foo/bar",
            RebasePath("//a/b/foo/bar", SourceDir("//a/b/"), source_root));
  EXPECT_EQ("foo/bar/",
            RebasePath("//a/b/foo/bar/", SourceDir("//a/b/"), source_root));

  // One could argue about this case. Since the input doesn't have a slash it
  // would normally not be treated like a directory and we'd go up, which is
  // simpler. However, since it matches the output directory's name, we could
  // potentially infer that it's the same and return "." for this.
  EXPECT_EQ("../bar",
            RebasePath("//foo/bar", SourceDir("//foo/bar/"), source_root));

  // Check when only |input| is system-absolute
  EXPECT_EQ("foo", RebasePath("/source/root/foo", SourceDir("//"),
                              base::StringPiece("/source/root")));
  EXPECT_EQ("foo/", RebasePath("/source/root/foo/", SourceDir("//"),
                               base::StringPiece("/source/root")));
  EXPECT_EQ("../../builddir/Out/Debug",
            RebasePath("/builddir/Out/Debug", SourceDir("//"),
                       base::StringPiece("/source/root")));
  EXPECT_EQ("../../../builddir/Out/Debug",
            RebasePath("/builddir/Out/Debug", SourceDir("//"),
                       base::StringPiece("/source/root/foo")));
  EXPECT_EQ("../../../builddir/Out/Debug/",
            RebasePath("/builddir/Out/Debug/", SourceDir("//"),
                       base::StringPiece("/source/root/foo")));
  EXPECT_EQ("../../path/to/foo", RebasePath("/path/to/foo", SourceDir("//"),
                                            base::StringPiece("/source/root")));
  EXPECT_EQ("../../../path/to/foo",
            RebasePath("/path/to/foo", SourceDir("//a"),
                       base::StringPiece("/source/root")));
  EXPECT_EQ("../../../../path/to/foo",
            RebasePath("/path/to/foo", SourceDir("//a/b"),
                       base::StringPiece("/source/root")));

  // Check when only |dest_dir| is system-absolute.
  EXPECT_EQ(".", RebasePath("//", SourceDir("/source/root"),
                            base::StringPiece("/source/root")));
  EXPECT_EQ("foo", RebasePath("//foo", SourceDir("/source/root"),
                              base::StringPiece("/source/root")));
  EXPECT_EQ("../foo", RebasePath("//foo", SourceDir("/source/root/bar"),
                                 base::StringPiece("/source/root")));
  EXPECT_EQ("../../../source/root/foo",
            RebasePath("//foo", SourceDir("/other/source/root"),
                       base::StringPiece("/source/root")));
  EXPECT_EQ("../../../../source/root/foo",
            RebasePath("//foo", SourceDir("/other/source/root/bar"),
                       base::StringPiece("/source/root")));

  // Check when |input| and |dest_dir| are both system-absolute. Also,
  // in this case |source_root| is never used so set it to a dummy
  // value.
  EXPECT_EQ("foo", RebasePath("/source/root/foo", SourceDir("/source/root"),
                              base::StringPiece("/x/y/z")));
  EXPECT_EQ("foo/", RebasePath("/source/root/foo/", SourceDir("/source/root"),
                               base::StringPiece("/x/y/z")));
  EXPECT_EQ("../../builddir/Out/Debug",
            RebasePath("/builddir/Out/Debug", SourceDir("/source/root"),
                       base::StringPiece("/x/y/z")));
  EXPECT_EQ("../../../builddir/Out/Debug",
            RebasePath("/builddir/Out/Debug", SourceDir("/source/root/foo"),
                       base::StringPiece("/source/root/foo")));
  EXPECT_EQ("../../../builddir/Out/Debug/",
            RebasePath("/builddir/Out/Debug/", SourceDir("/source/root/foo"),
                       base::StringPiece("/source/root/foo")));
  EXPECT_EQ("../../path/to/foo",
            RebasePath("/path/to/foo", SourceDir("/source/root"),
                       base::StringPiece("/x/y/z")));
  EXPECT_EQ("../../../path/to/foo",
            RebasePath("/path/to/foo", SourceDir("/source/root/a"),
                       base::StringPiece("/x/y/z")));
  EXPECT_EQ("../../../../path/to/foo",
            RebasePath("/path/to/foo", SourceDir("/source/root/a/b"),
                       base::StringPiece("/x/y/z")));

#if defined(OS_WIN)
  // Test corrections while rebasing Windows-style absolute paths.
  EXPECT_EQ("../../../../path/to/foo",
            RebasePath("C:/path/to/foo", SourceDir("//a/b"),
                       base::StringPiece("/C:/source/root")));
  EXPECT_EQ("../../../../path/to/foo",
            RebasePath("/C:/path/to/foo", SourceDir("//a/b"),
                       base::StringPiece("C:/source/root")));
  EXPECT_EQ("../../../../path/to/foo",
            RebasePath("/C:/path/to/foo", SourceDir("//a/b"),
                       base::StringPiece("/c:/source/root")));
  EXPECT_EQ("../../../../path/to/foo",
            RebasePath("/c:/path/to/foo", SourceDir("//a/b"),
                       base::StringPiece("c:/source/root")));
  EXPECT_EQ("../../../../path/to/foo",
            RebasePath("/c:/path/to/foo", SourceDir("//a/b"),
                       base::StringPiece("C:/source/root")));
#endif
}

TEST(FilesystemUtils, DirectoryWithNoLastSlash) {
  EXPECT_EQ("", DirectoryWithNoLastSlash(SourceDir()));
  EXPECT_EQ("/.", DirectoryWithNoLastSlash(SourceDir("/")));
  EXPECT_EQ("//.", DirectoryWithNoLastSlash(SourceDir("//")));
  EXPECT_EQ("//foo", DirectoryWithNoLastSlash(SourceDir("//foo/")));
  EXPECT_EQ("/bar", DirectoryWithNoLastSlash(SourceDir("/bar/")));
}

TEST(FilesystemUtils, SourceDirForPath) {
#if defined(OS_WIN)
  base::FilePath root(L"C:\\source\\foo\\");
  EXPECT_EQ("/C:/foo/bar/",
            SourceDirForPath(root, base::FilePath(L"C:\\foo\\bar")).value());
  EXPECT_EQ("/", SourceDirForPath(root, base::FilePath(L"/")).value());
  EXPECT_EQ("//",
            SourceDirForPath(root, base::FilePath(L"C:\\source\\foo")).value());
  EXPECT_EQ("//bar/",
            SourceDirForPath(root, base::FilePath(L"C:\\source\\foo\\bar\\"))
                .value());
  EXPECT_EQ("//bar/baz/",
            SourceDirForPath(root, base::FilePath(L"C:\\source\\foo\\bar\\baz"))
                .value());

  // Should be case-and-slash-insensitive.
  EXPECT_EQ(
      "//baR/",
      SourceDirForPath(root, base::FilePath(L"c:/SOURCE\\Foo/baR/")).value());

  // Some "weird" Windows paths.
  EXPECT_EQ("/foo/bar/",
            SourceDirForPath(root, base::FilePath(L"/foo/bar/")).value());
  EXPECT_EQ("/C:/foo/bar/",
            SourceDirForPath(root, base::FilePath(L"C:foo/bar/")).value());

  // Also allow absolute GN-style Windows paths.
  EXPECT_EQ("/C:/foo/bar/",
            SourceDirForPath(root, base::FilePath(L"/C:/foo/bar")).value());
  EXPECT_EQ(
      "//bar/",
      SourceDirForPath(root, base::FilePath(L"/C:/source/foo/bar")).value());

  // Empty source dir.
  base::FilePath empty;
  EXPECT_EQ(
      "/C:/source/foo/",
      SourceDirForPath(empty, base::FilePath(L"C:\\source\\foo")).value());
#else
  base::FilePath root("/source/foo/");
  EXPECT_EQ("/foo/bar/",
            SourceDirForPath(root, base::FilePath("/foo/bar/")).value());
  EXPECT_EQ("/", SourceDirForPath(root, base::FilePath("/")).value());
  EXPECT_EQ("//",
            SourceDirForPath(root, base::FilePath("/source/foo")).value());
  EXPECT_EQ("//bar/",
            SourceDirForPath(root, base::FilePath("/source/foo/bar/")).value());
  EXPECT_EQ(
      "//bar/baz/",
      SourceDirForPath(root, base::FilePath("/source/foo/bar/baz/")).value());

  // Should be case-sensitive.
  EXPECT_EQ("/SOURCE/foo/bar/",
            SourceDirForPath(root, base::FilePath("/SOURCE/foo/bar/")).value());

  // Empty source dir.
  base::FilePath empty;
  EXPECT_EQ("/source/foo/",
            SourceDirForPath(empty, base::FilePath("/source/foo")).value());
#endif
}

TEST(FilesystemUtils, ContentsEqual) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());

  std::string data = "foo";

  base::FilePath file_path = temp_dir.GetPath().AppendASCII("foo.txt");
  base::WriteFile(file_path, data.c_str(), static_cast<int>(data.size()));

  EXPECT_TRUE(ContentsEqual(file_path, data));

  // Different length and contents.
  data += "bar";
  EXPECT_FALSE(ContentsEqual(file_path, data));

  // The same length, different contents.
  EXPECT_FALSE(ContentsEqual(file_path, "bar"));
}

TEST(FilesystemUtils, WriteFileIfChanged) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());

  std::string data = "foo";

  // Write if file doesn't exist. Create also directory.
  base::FilePath file_path =
      temp_dir.GetPath().AppendASCII("bar").AppendASCII("foo.txt");
  EXPECT_TRUE(WriteFileIfChanged(file_path, data, nullptr));

  base::File::Info file_info;
  ASSERT_TRUE(base::GetFileInfo(file_path, &file_info));
  Ticks last_modified = file_info.last_modified;

  {
    using namespace std::chrono_literals;
#if defined(OS_MACOSX)
    // Modification times are in seconds in HFS on Mac.
    std::this_thread::sleep_for(1s);
#else
    std::this_thread::sleep_for(1ms);
#endif
  }

  // Don't write if contents is the same.
  EXPECT_TRUE(WriteFileIfChanged(file_path, data, nullptr));
  ASSERT_TRUE(base::GetFileInfo(file_path, &file_info));
  EXPECT_EQ(last_modified, file_info.last_modified);

  // Write if contents changed.
  EXPECT_TRUE(WriteFileIfChanged(file_path, "bar", nullptr));
  std::string file_data;
  ASSERT_TRUE(base::ReadFileToString(file_path, &file_data));
  EXPECT_EQ("bar", file_data);
}

TEST(FilesystemUtils, GetToolchainDirs) {
  BuildSettings build_settings;
  build_settings.SetBuildDir(SourceDir("//out/Debug/"));

  // The default toolchain.
  Settings default_settings(&build_settings, "");
  Label default_toolchain_label(SourceDir("//toolchain/"), "default");
  default_settings.set_toolchain_label(default_toolchain_label);
  default_settings.set_default_toolchain_label(default_toolchain_label);
  BuildDirContext default_context(&default_settings);

  // Default toolchain out dir as source dirs.
  EXPECT_EQ("//out/Debug/", GetBuildDirAsSourceDir(default_context,
                                                   BuildDirType::TOOLCHAIN_ROOT)
                                .value());
  EXPECT_EQ("//out/Debug/obj/",
            GetBuildDirAsSourceDir(default_context, BuildDirType::OBJ).value());
  EXPECT_EQ("//out/Debug/gen/",
            GetBuildDirAsSourceDir(default_context, BuildDirType::GEN).value());

  // Default toolchain our dir as output files.
  EXPECT_EQ(
      "", GetBuildDirAsOutputFile(default_context, BuildDirType::TOOLCHAIN_ROOT)
              .value());
  EXPECT_EQ(
      "obj/",
      GetBuildDirAsOutputFile(default_context, BuildDirType::OBJ).value());
  EXPECT_EQ(
      "gen/",
      GetBuildDirAsOutputFile(default_context, BuildDirType::GEN).value());

  // Check a secondary toolchain.
  Settings other_settings(&build_settings, "two/");
  Label other_toolchain_label(SourceDir("//toolchain/"), "two");
  other_settings.set_toolchain_label(other_toolchain_label);
  other_settings.set_default_toolchain_label(default_toolchain_label);
  BuildDirContext other_context(&other_settings);

  // Secondary toolchain out dir as source dirs.
  EXPECT_EQ("//out/Debug/two/",
            GetBuildDirAsSourceDir(other_context, BuildDirType::TOOLCHAIN_ROOT)
                .value());
  EXPECT_EQ("//out/Debug/two/obj/",
            GetBuildDirAsSourceDir(other_context, BuildDirType::OBJ).value());
  EXPECT_EQ("//out/Debug/two/gen/",
            GetBuildDirAsSourceDir(other_context, BuildDirType::GEN).value());

  // Secondary toolchain out dir as output files.
  EXPECT_EQ("two/",
            GetBuildDirAsOutputFile(other_context, BuildDirType::TOOLCHAIN_ROOT)
                .value());
  EXPECT_EQ("two/obj/",
            GetBuildDirAsOutputFile(other_context, BuildDirType::OBJ).value());
  EXPECT_EQ("two/gen/",
            GetBuildDirAsOutputFile(other_context, BuildDirType::GEN).value());
}

TEST(FilesystemUtils, GetSubBuildDir) {
  BuildSettings build_settings;
  build_settings.SetBuildDir(SourceDir("//out/Debug/"));

  // Test the default toolchain.
  Label default_toolchain_label(SourceDir("//toolchain/"), "default");
  Settings default_settings(&build_settings, "");
  default_settings.set_toolchain_label(default_toolchain_label);
  default_settings.set_default_toolchain_label(default_toolchain_label);
  BuildDirContext default_context(&default_settings);

  // Target in the root.
  EXPECT_EQ("//out/Debug/obj/",
            GetSubBuildDirAsSourceDir(default_context, SourceDir("//"),
                                      BuildDirType::OBJ)
                .value());
  EXPECT_EQ("gen/", GetSubBuildDirAsOutputFile(default_context, SourceDir("//"),
                                               BuildDirType::GEN)
                        .value());

  // Target in another directory.
  EXPECT_EQ("//out/Debug/obj/foo/bar/",
            GetSubBuildDirAsSourceDir(default_context, SourceDir("//foo/bar/"),
                                      BuildDirType::OBJ)
                .value());
  EXPECT_EQ("gen/foo/bar/",
            GetSubBuildDirAsOutputFile(default_context, SourceDir("//foo/bar/"),
                                       BuildDirType::GEN)
                .value());

  // Secondary toolchain.
  Settings other_settings(&build_settings, "two/");
  other_settings.set_toolchain_label(Label(SourceDir("//toolchain/"), "two"));
  other_settings.set_default_toolchain_label(default_toolchain_label);
  BuildDirContext other_context(&other_settings);

  // Target in the root.
  EXPECT_EQ("//out/Debug/two/obj/",
            GetSubBuildDirAsSourceDir(other_context, SourceDir("//"),
                                      BuildDirType::OBJ)
                .value());
  EXPECT_EQ("two/gen/", GetSubBuildDirAsOutputFile(
                            other_context, SourceDir("//"), BuildDirType::GEN)
                            .value());

  // Target in another directory.
  EXPECT_EQ("//out/Debug/two/obj/foo/bar/",
            GetSubBuildDirAsSourceDir(other_context, SourceDir("//foo/bar/"),
                                      BuildDirType::OBJ)
                .value());
  EXPECT_EQ("two/gen/foo/bar/",
            GetSubBuildDirAsOutputFile(other_context, SourceDir("//foo/bar/"),
                                       BuildDirType::GEN)
                .value());

  // Absolute source path
  EXPECT_EQ("//out/Debug/obj/ABS_PATH/abs/",
            GetSubBuildDirAsSourceDir(default_context, SourceDir("/abs"),
                                      BuildDirType::OBJ)
                .value());
  EXPECT_EQ("gen/ABS_PATH/abs/",
            GetSubBuildDirAsOutputFile(default_context, SourceDir("/abs"),
                                       BuildDirType::GEN)
                .value());
#if defined(OS_WIN)
  EXPECT_EQ("//out/Debug/obj/ABS_PATH/C/abs/",
            GetSubBuildDirAsSourceDir(default_context, SourceDir("/C:/abs"),
                                      BuildDirType::OBJ)
                .value());
  EXPECT_EQ("gen/ABS_PATH/C/abs/",
            GetSubBuildDirAsOutputFile(default_context, SourceDir("/C:/abs"),
                                       BuildDirType::GEN)
                .value());
#endif
}

TEST(FilesystemUtils, GetBuildDirForTarget) {
  BuildSettings build_settings;
  build_settings.SetBuildDir(SourceDir("//out/Debug/"));
  Settings settings(&build_settings, "");

  Target a(&settings, Label(SourceDir("//foo/bar/"), "baz"));
  EXPECT_EQ("//out/Debug/obj/foo/bar/",
            GetBuildDirForTargetAsSourceDir(&a, BuildDirType::OBJ).value());
  EXPECT_EQ("obj/foo/bar/",
            GetBuildDirForTargetAsOutputFile(&a, BuildDirType::OBJ).value());
  EXPECT_EQ("//out/Debug/gen/foo/bar/",
            GetBuildDirForTargetAsSourceDir(&a, BuildDirType::GEN).value());
  EXPECT_EQ("gen/foo/bar/",
            GetBuildDirForTargetAsOutputFile(&a, BuildDirType::GEN).value());
}

// Tests handling of output dirs when build dir is the same as the root.
TEST(FilesystemUtils, GetDirForEmptyBuildDir) {
  BuildSettings build_settings;
  build_settings.SetBuildDir(SourceDir("//"));
  Settings settings(&build_settings, "");

  BuildDirContext context(&settings);

  EXPECT_EQ(
      "//",
      GetBuildDirAsSourceDir(context, BuildDirType::TOOLCHAIN_ROOT).value());
  EXPECT_EQ("//gen/",
            GetBuildDirAsSourceDir(context, BuildDirType::GEN).value());
  EXPECT_EQ("//obj/",
            GetBuildDirAsSourceDir(context, BuildDirType::OBJ).value());

  EXPECT_EQ(
      "",
      GetBuildDirAsOutputFile(context, BuildDirType::TOOLCHAIN_ROOT).value());
  EXPECT_EQ("gen/",
            GetBuildDirAsOutputFile(context, BuildDirType::GEN).value());
  EXPECT_EQ("obj/",
            GetBuildDirAsOutputFile(context, BuildDirType::OBJ).value());
}
