llvm/llvm/unittests/Support/Path.cpp

//===- llvm/unittest/Support/Path.cpp - Path tests ------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "llvm/Support/Path.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/BinaryFormat/Magic.h"
#include "llvm/Config/llvm-config.h"
#include "llvm/Config/llvm-config.h" // for LLVM_ON_UNIX
#include "llvm/Support/Compiler.h"
#include "llvm/Support/ConvertUTF.h"
#include "llvm/Support/Duration.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/FileUtilities.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/TargetParser/Host.h"
#include "llvm/TargetParser/Triple.h"
#include "llvm/Testing/Support/Error.h"
#include "llvm/Testing/Support/SupportHelpers.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"

#ifdef _WIN32
#include "llvm/ADT/ArrayRef.h"
#include "llvm/Support/Chrono.h"
#include "llvm/Support/Windows/WindowsSupport.h"
#include <windows.h>
#include <winerror.h>
#endif

#ifdef LLVM_ON_UNIX
#include <pwd.h>
#include <sys/stat.h>
#endif

usingnamespacellvm;
usingnamespacellvm::sys;

#define ASSERT_NO_ERROR(x)

#define ASSERT_ERROR(x)

namespace {

void checkSeparators(StringRef Path) {}

struct FileDescriptorCloser {};

TEST(is_style_Style, Works) {}

TEST(is_separator, Works) {}

TEST(get_separator, Works) {}

TEST(is_absolute_gnu, Works) {}

TEST(Support, Path) {}

TEST(Support, PathRoot) {}

TEST(Support, FilenameParent) {}

static std::vector<StringRef>
GetComponents(StringRef Path, path::Style S = path::Style::native) {}

TEST(Support, PathIterator) {}

TEST(Support, AbsolutePathIteratorEnd) {}

#ifdef _WIN32
std::string getEnvWin(const wchar_t *Var) {
  std::string expected;
  if (wchar_t const *path = ::_wgetenv(Var)) {
    auto pathLen = ::wcslen(path);
    ArrayRef<char> ref{reinterpret_cast<char const *>(path),
                       pathLen * sizeof(wchar_t)};
    convertUTF16ToUTF8String(ref, expected);
    SmallString<32> Buf(expected);
    path::make_preferred(Buf);
    expected.assign(Buf.begin(), Buf.end());
  }
  return expected;
}
#else
// RAII helper to set and restore an environment variable.
class WithEnv {};
#endif

TEST(Support, HomeDirectory) {}

// Apple has their own solution for this.
#if defined(LLVM_ON_UNIX) && !defined(__APPLE__)
TEST(Support, HomeDirectoryWithNoEnv) {}

TEST(Support, ConfigDirectoryWithEnv) {}

TEST(Support, ConfigDirectoryNoEnv) {}

TEST(Support, CacheDirectoryWithEnv) {}

TEST(Support, CacheDirectoryNoEnv) {}
#endif

#ifdef __APPLE__
TEST(Support, ConfigDirectory) {
  SmallString<128> Fallback;
  ASSERT_TRUE(path::home_directory(Fallback));
  path::append(Fallback, "Library/Preferences");

  SmallString<128> ConfigDir;
  EXPECT_TRUE(path::user_config_directory(ConfigDir));
  EXPECT_EQ(Fallback, ConfigDir);
}
#endif

#ifdef _WIN32
TEST(Support, ConfigDirectory) {
  std::string Expected = getEnvWin(L"LOCALAPPDATA");
  // Do not try to test it if we don't know what to expect.
  if (Expected.empty())
    GTEST_SKIP();
  SmallString<128> CacheDir;
  EXPECT_TRUE(path::user_config_directory(CacheDir));
  EXPECT_EQ(Expected, CacheDir);
}

TEST(Support, CacheDirectory) {
  std::string Expected = getEnvWin(L"LOCALAPPDATA");
  // Do not try to test it if we don't know what to expect.
  if (Expected.empty())
    GTEST_SKIP();
  SmallString<128> CacheDir;
  EXPECT_TRUE(path::cache_directory(CacheDir));
  EXPECT_EQ(Expected, CacheDir);
}
#endif

TEST(Support, TempDirectory) {}

#ifdef _WIN32
static std::string path2regex(std::string Path) {
  size_t Pos = 0;
  bool Forward = path::get_separator()[0] == '/';
  while ((Pos = Path.find('\\', Pos)) != std::string::npos) {
    if (Forward) {
      Path.replace(Pos, 1, "/");
      Pos += 1;
    } else {
      Path.replace(Pos, 1, "\\\\");
      Pos += 2;
    }
  }
  return Path;
}

/// Helper for running temp dir test in separated process. See below.
#define EXPECT_TEMP_DIR

TEST(SupportDeathTest, TempDirectoryOnWindows) {
  // In this test we want to check how system_temp_directory responds to
  // different values of specific env vars. To prevent corrupting env vars of
  // the current process all checks are done in separated processes.
  EXPECT_TEMP_DIR(_wputenv_s(L"TMP", L"C:\\OtherFolder"), "C:\\OtherFolder");
  EXPECT_TEMP_DIR(_wputenv_s(L"TMP", L"C:/Unix/Path/Separators"),
                  "C:\\Unix\\Path\\Separators");
  EXPECT_TEMP_DIR(_wputenv_s(L"TMP", L"Local Path"), ".+\\Local Path$");
  EXPECT_TEMP_DIR(_wputenv_s(L"TMP", L"F:\\TrailingSep\\"), "F:\\TrailingSep");
  EXPECT_TEMP_DIR(
      _wputenv_s(L"TMP", L"C:\\2\x03C0r-\x00B5\x00B3\\\x2135\x2080"),
      "C:\\2\xCF\x80r-\xC2\xB5\xC2\xB3\\\xE2\x84\xB5\xE2\x82\x80");

  // Test $TMP empty, $TEMP set.
  EXPECT_TEMP_DIR(
      {
        _wputenv_s(L"TMP", L"");
        _wputenv_s(L"TEMP", L"C:\\Valid\\Path");
      },
      "C:\\Valid\\Path");

  // All related env vars empty
  EXPECT_TEMP_DIR(
  {
    _wputenv_s(L"TMP", L"");
    _wputenv_s(L"TEMP", L"");
    _wputenv_s(L"USERPROFILE", L"");
  },
    "C:\\Temp");

  // Test evn var / path with 260 chars.
  SmallString<270> Expected{"C:\\Temp\\AB\\123456789"};
  while (Expected.size() < 260)
    Expected.append("\\DirNameWith19Charss");
  ASSERT_EQ(260U, Expected.size());
  EXPECT_TEMP_DIR(_putenv_s("TMP", Expected.c_str()), Expected.c_str());
}
#endif

class FileSystemTest : public testing::Test {};

TEST_F(FileSystemTest, Unique) {}

TEST_F(FileSystemTest, RealPath) {}

TEST_F(FileSystemTest, ExpandTilde) {}

#ifdef LLVM_ON_UNIX
TEST_F(FileSystemTest, RealPathNoReadPerm) {}
TEST_F(FileSystemTest, RemoveDirectoriesNoExePerm) {}
#endif


TEST_F(FileSystemTest, TempFileKeepDiscard) {}

TEST_F(FileSystemTest, TempFileDiscardDiscard) {}

TEST_F(FileSystemTest, TempFiles) {}

TEST_F(FileSystemTest, TempFileCollisions) {}

TEST_F(FileSystemTest, CreateDir) {}

TEST_F(FileSystemTest, DirectoryIteration) {}

TEST_F(FileSystemTest, DirectoryNotExecutable) {}

#ifdef LLVM_ON_UNIX
TEST_F(FileSystemTest, BrokenSymlinkDirectoryIteration) {}
#endif

#ifdef _WIN32
TEST_F(FileSystemTest, UTF8ToUTF16DirectoryIteration) {
  // The Windows filesystem support uses UTF-16 and converts paths from the
  // input UTF-8. The UTF-16 equivalent of the input path can be shorter in
  // length.

  // This test relies on TestDirectory not being so long such that MAX_PATH
  // would be exceeded (see widenPath). If that were the case, the UTF-16
  // path is likely to be longer than the input.
  const char *Pi = "\xcf\x80"; // UTF-8 lower case pi.
  std::string RootDir = (TestDirectory + "/" + Pi).str();

  // Create test directories.
  ASSERT_NO_ERROR(fs::create_directories(Twine(RootDir) + "/a"));
  ASSERT_NO_ERROR(fs::create_directories(Twine(RootDir) + "/b"));

  std::error_code EC;
  unsigned Count = 0;
  for (fs::directory_iterator I(Twine(RootDir), EC), E; I != E;
       I.increment(EC)) {
    ASSERT_NO_ERROR(EC);
    StringRef DirName = path::filename(I->path());
    EXPECT_TRUE(DirName == "a" || DirName == "b");
    ++Count;
  }
  EXPECT_EQ(Count, 2U);

  ASSERT_NO_ERROR(fs::remove(Twine(RootDir) + "/a"));
  ASSERT_NO_ERROR(fs::remove(Twine(RootDir) + "/b"));
  ASSERT_NO_ERROR(fs::remove(Twine(RootDir)));
}
#endif

TEST_F(FileSystemTest, Remove) {}

#ifdef _WIN32
TEST_F(FileSystemTest, CarriageReturn) {
  SmallString<128> FilePathname(TestDirectory);
  std::error_code EC;
  path::append(FilePathname, "test");

  {
    raw_fd_ostream File(FilePathname, EC, sys::fs::OF_TextWithCRLF);
    ASSERT_NO_ERROR(EC);
    File << '\n';
  }
  {
    auto Buf = MemoryBuffer::getFile(FilePathname.str());
    EXPECT_TRUE((bool)Buf);
    EXPECT_EQ(Buf.get()->getBuffer(), "\r\n");
  }

  {
    raw_fd_ostream File(FilePathname, EC, sys::fs::OF_None);
    ASSERT_NO_ERROR(EC);
    File << '\n';
  }
  {
    auto Buf = MemoryBuffer::getFile(FilePathname.str());
    EXPECT_TRUE((bool)Buf);
    EXPECT_EQ(Buf.get()->getBuffer(), "\n");
  }
  ASSERT_NO_ERROR(fs::remove(Twine(FilePathname)));
}
#endif

TEST_F(FileSystemTest, Resize) {}

TEST_F(FileSystemTest, ResizeBeforeMapping) {}

TEST_F(FileSystemTest, MD5) {}

TEST_F(FileSystemTest, FileMapping) {}

TEST(Support, NormalizePath) {}

TEST(Support, RemoveLeadingDotSlash) {}

static std::string remove_dots(StringRef path, bool remove_dot_dot,
                               path::Style style) {}

TEST(Support, RemoveDots) {}

TEST(Support, ReplacePathPrefix) {}

TEST_F(FileSystemTest, OpenFileForRead) {}

TEST_F(FileSystemTest, OpenDirectoryAsFileForRead) {}

TEST_F(FileSystemTest, OpenDirectoryAsFileForWrite) {}

static void createFileWithData(const Twine &Path, bool ShouldExistBefore,
                               fs::CreationDisposition Disp, StringRef Data) {}

static void verifyFileContents(const Twine &Path, StringRef Contents) {}

TEST_F(FileSystemTest, CreateNew) {}

TEST_F(FileSystemTest, CreateAlways) {}

TEST_F(FileSystemTest, OpenExisting) {}

TEST_F(FileSystemTest, OpenAlways) {}

TEST_F(FileSystemTest, AppendSetsCorrectFileOffset) {}

static void verifyRead(int FD, StringRef Data, bool ShouldSucceed) {}

static void verifyWrite(int FD, StringRef Data, bool ShouldSucceed) {}

TEST_F(FileSystemTest, ReadOnlyFileCantWrite) {}

TEST_F(FileSystemTest, WriteOnlyFileCantRead) {}

TEST_F(FileSystemTest, ReadWriteFileCanReadOrWrite) {}

TEST_F(FileSystemTest, readNativeFile) {}

TEST_F(FileSystemTest, readNativeFileToEOF) {}

TEST_F(FileSystemTest, readNativeFileSlice) {}

TEST_F(FileSystemTest, is_local) {}

TEST_F(FileSystemTest, getUmask) {}

TEST_F(FileSystemTest, RespectUmask) {}

TEST_F(FileSystemTest, set_current_path) {}

TEST_F(FileSystemTest, permissions) {}

#ifdef _WIN32
TEST_F(FileSystemTest, widenPath) {
  const std::wstring LongPathPrefix(L"\\\\?\\");

  // Test that the length limit is checked against the UTF-16 length and not the
  // UTF-8 length.
  std::string Input("C:\\foldername\\");
  const std::string Pi("\xcf\x80"); // UTF-8 lower case pi.
  // Add Pi up to the MAX_PATH limit.
  const size_t NumChars = MAX_PATH - Input.size() - 1;
  for (size_t i = 0; i < NumChars; ++i)
    Input += Pi;
  // Check that UTF-8 length already exceeds MAX_PATH.
  EXPECT_GT(Input.size(), (size_t)MAX_PATH);
  SmallVector<wchar_t, MAX_PATH + 16> Result;
  ASSERT_NO_ERROR(windows::widenPath(Input, Result));
  // Result should not start with the long path prefix.
  EXPECT_TRUE(std::wmemcmp(Result.data(), LongPathPrefix.c_str(),
                           LongPathPrefix.size()) != 0);
  EXPECT_EQ(Result.size(), (size_t)MAX_PATH - 1);

  // Add another Pi to exceed the MAX_PATH limit.
  Input += Pi;
  // Construct the expected result.
  SmallVector<wchar_t, MAX_PATH + 16> Expected;
  ASSERT_NO_ERROR(windows::UTF8ToUTF16(Input, Expected));
  Expected.insert(Expected.begin(), LongPathPrefix.begin(),
                  LongPathPrefix.end());

  ASSERT_NO_ERROR(windows::widenPath(Input, Result));
  EXPECT_EQ(Result, Expected);
  // Pass a path with forward slashes, check that it ends up with
  // backslashes when widened with the long path prefix.
  SmallString<MAX_PATH + 16> InputForward(Input);
  path::make_preferred(InputForward, path::Style::windows_slash);
  ASSERT_NO_ERROR(windows::widenPath(InputForward, Result));
  EXPECT_EQ(Result, Expected);

  // Pass a path which has the long path prefix prepended originally, but
  // which is short enough to not require the long path prefix. If such a
  // path is passed with forward slashes, make sure it gets normalized to
  // backslashes.
  SmallString<MAX_PATH + 16> PrefixedPath("\\\\?\\C:\\foldername");
  ASSERT_NO_ERROR(windows::UTF8ToUTF16(PrefixedPath, Expected));
  // Mangle the input to forward slashes.
  path::make_preferred(PrefixedPath, path::Style::windows_slash);
  ASSERT_NO_ERROR(windows::widenPath(PrefixedPath, Result));
  EXPECT_EQ(Result, Expected);

  // A short path with an inconsistent prefix is passed through as-is; this
  // is a degenerate case that we currently don't care about handling.
  PrefixedPath.assign("/\\?/C:/foldername");
  ASSERT_NO_ERROR(windows::UTF8ToUTF16(PrefixedPath, Expected));
  ASSERT_NO_ERROR(windows::widenPath(PrefixedPath, Result));
  EXPECT_EQ(Result, Expected);

  // Test that UNC paths are handled correctly.
  const std::string ShareName("\\\\sharename\\");
  const std::string FileName("\\filename");
  // Initialize directory name so that the input is within the MAX_PATH limit.
  const char DirChar = 'x';
  std::string DirName(MAX_PATH - ShareName.size() - FileName.size() - 1,
                      DirChar);

  Input = ShareName + DirName + FileName;
  ASSERT_NO_ERROR(windows::widenPath(Input, Result));
  // Result should not start with the long path prefix.
  EXPECT_TRUE(std::wmemcmp(Result.data(), LongPathPrefix.c_str(),
                           LongPathPrefix.size()) != 0);
  EXPECT_EQ(Result.size(), (size_t)MAX_PATH - 1);

  // Extend the directory name so the input exceeds the MAX_PATH limit.
  DirName += DirChar;
  Input = ShareName + DirName + FileName;
  // Construct the expected result.
  ASSERT_NO_ERROR(windows::UTF8ToUTF16(StringRef(Input).substr(2), Expected));
  const std::wstring UNCPrefix(LongPathPrefix + L"UNC\\");
  Expected.insert(Expected.begin(), UNCPrefix.begin(), UNCPrefix.end());

  ASSERT_NO_ERROR(windows::widenPath(Input, Result));
  EXPECT_EQ(Result, Expected);

  // Check that Unix separators are handled correctly.
  std::replace(Input.begin(), Input.end(), '\\', '/');
  ASSERT_NO_ERROR(windows::widenPath(Input, Result));
  EXPECT_EQ(Result, Expected);

  // Check the removal of "dots".
  Input = ShareName + DirName + "\\.\\foo\\.\\.." + FileName;
  ASSERT_NO_ERROR(windows::widenPath(Input, Result));
  EXPECT_EQ(Result, Expected);
}
#endif

#ifdef _WIN32
// Windows refuses lock request if file region is already locked by the same
// process. POSIX system in this case updates the existing lock.
TEST_F(FileSystemTest, FileLocker) {
  using namespace std::chrono;
  int FD;
  std::error_code EC;
  SmallString<64> TempPath;
  EC = fs::createTemporaryFile("test", "temp", FD, TempPath);
  ASSERT_NO_ERROR(EC);
  FileRemover Cleanup(TempPath);
  raw_fd_ostream Stream(TempPath, EC);

  EC = fs::tryLockFile(FD);
  ASSERT_NO_ERROR(EC);
  EC = fs::unlockFile(FD);
  ASSERT_NO_ERROR(EC);

  if (auto L = Stream.lock()) {
    ASSERT_ERROR(fs::tryLockFile(FD));
    ASSERT_NO_ERROR(L->unlock());
    ASSERT_NO_ERROR(fs::tryLockFile(FD));
    ASSERT_NO_ERROR(fs::unlockFile(FD));
  } else {
    ADD_FAILURE();
    handleAllErrors(L.takeError(), [&](ErrorInfoBase &EIB) {});
  }

  ASSERT_NO_ERROR(fs::tryLockFile(FD));
  ASSERT_NO_ERROR(fs::unlockFile(FD));

  {
    Expected<fs::FileLocker> L1 = Stream.lock();
    ASSERT_THAT_EXPECTED(L1, Succeeded());
    raw_fd_ostream Stream2(FD, false);
    Expected<fs::FileLocker> L2 = Stream2.tryLockFor(250ms);
    ASSERT_THAT_EXPECTED(L2, Failed());
    ASSERT_NO_ERROR(L1->unlock());
    Expected<fs::FileLocker> L3 = Stream.tryLockFor(0ms);
    ASSERT_THAT_EXPECTED(L3, Succeeded());
  }

  ASSERT_NO_ERROR(fs::tryLockFile(FD));
  ASSERT_NO_ERROR(fs::unlockFile(FD));
}
#endif

TEST_F(FileSystemTest, CopyFile) {}

} // anonymous namespace