#include "path.h"
#include "pathsplit.h"
#include "dirut.h"
#include "tempdir.h"
#include <library/cpp/testing/unittest/registar.h>
#include <util/generic/scope.h>
#include <util/system/platform.h>
#include <util/system/yassert.h>
#include <util/stream/output.h>
#include <util/stream/file.h>
#include <util/system/fs.h>
#include <algorithm>
#ifdef _win_
#include <aclapi.h>
#endif
namespace {
/// empty directory for test that needs filesystem
/// recreates directory in constructor and removes directory in destructor
class TTestDirectory {
private:
TFsPath Path_;
public:
TTestDirectory(const TString& name);
~TTestDirectory();
TFsPath GetFsPath() const {
return Path_;
}
TFsPath Child(const TString& name) const {
return Path_.Child(name);
}
};
TTestDirectory::TTestDirectory(const TString& name) {
Y_VERIFY(name.length() > 0, "have to specify name");
Y_VERIFY(name.find('.') == TString::npos, "must be simple name");
Y_VERIFY(name.find('/') == TString::npos, "must be simple name");
Y_VERIFY(name.find('\\') == TString::npos, "must be simple name");
Path_ = TFsPath(name);
Path_.ForceDelete();
Path_.MkDir();
}
TTestDirectory::~TTestDirectory() {
Path_.ForceDelete();
}
}
Y_UNIT_TEST_SUITE(TFsPathTests) {
Y_UNIT_TEST(TestMkDirs) {
const TFsPath path = "a/b/c/d/e/f";
path.ForceDelete();
TFsPath current = path;
ui32 checksCounter = 0;
while (current != ".") {
UNIT_ASSERT(!path.Exists());
++checksCounter;
current = current.Parent();
}
UNIT_ASSERT_VALUES_EQUAL(checksCounter, 6);
path.MkDirs();
UNIT_ASSERT(path.Exists());
current = path;
while (current != ".") {
UNIT_ASSERT(path.Exists());
current = current.Parent();
}
}
Y_UNIT_TEST(MkDirFreak) {
TFsPath path;
UNIT_ASSERT_EXCEPTION(path.MkDir(), TIoException);
UNIT_ASSERT_EXCEPTION(path.MkDirs(), TIoException);
path = ".";
path.MkDir();
path.MkDirs();
}
Y_UNIT_TEST(Parent) {
#ifdef _win_
UNIT_ASSERT_VALUES_EQUAL(TFsPath("\\etc/passwd").Parent(), TFsPath("\\etc"));
UNIT_ASSERT_VALUES_EQUAL(TFsPath("\\etc").Parent(), TFsPath("\\"));
UNIT_ASSERT_VALUES_EQUAL(TFsPath("\\").Parent(), TFsPath("\\"));
UNIT_ASSERT_VALUES_EQUAL(TFsPath("etc\\passwd").Parent(), TFsPath("etc"));
UNIT_ASSERT_VALUES_EQUAL(TFsPath("etc").Parent(), TFsPath("."));
UNIT_ASSERT_VALUES_EQUAL(TFsPath(".\\etc").Parent(), TFsPath("."));
UNIT_ASSERT_VALUES_EQUAL(TFsPath("C:\\etc/passwd").Parent(), TFsPath("C:\\etc"));
UNIT_ASSERT_VALUES_EQUAL(TFsPath("C:\\etc").Parent(), TFsPath("C:\\"));
UNIT_ASSERT_VALUES_EQUAL(TFsPath("C:\\").Parent(), TFsPath("C:\\"));
#else
UNIT_ASSERT_VALUES_EQUAL(TFsPath("/etc/passwd").Parent(), TFsPath("/etc"));
UNIT_ASSERT_VALUES_EQUAL(TFsPath("/etc").Parent(), TFsPath("/"));
UNIT_ASSERT_VALUES_EQUAL(TFsPath("/").Parent(), TFsPath("/"));
UNIT_ASSERT_VALUES_EQUAL(TFsPath("etc/passwd").Parent(), TFsPath("etc"));
UNIT_ASSERT_VALUES_EQUAL(TFsPath("etc").Parent(), TFsPath("."));
UNIT_ASSERT_VALUES_EQUAL(TFsPath("./etc").Parent(), TFsPath("."));
#endif
#if 0
UNIT_ASSERT_VALUES_EQUAL(TFsPath("./etc/passwd").Parent(), TFsPath("./etc"));
UNIT_ASSERT_VALUES_EQUAL(TFsPath("./").Parent(), TFsPath(".."));
UNIT_ASSERT_VALUES_EQUAL(TFsPath(".").Parent(), TFsPath(".."));
UNIT_ASSERT_VALUES_EQUAL(TFsPath("..").Parent(), TFsPath("../.."));
#endif
}
Y_UNIT_TEST(GetName) {
TTestDirectory d("GetName");
UNIT_ASSERT_VALUES_EQUAL(TString("dfgh"), d.Child("dfgh").GetName());
// check does not fail
TFsPath(".").GetName();
#ifdef _unix_
UNIT_ASSERT_VALUES_EQUAL(TString("/"), TFsPath("/").GetName());
#endif
}
Y_UNIT_TEST(GetExtension) {
TTestDirectory d("GetExtension");
UNIT_ASSERT_VALUES_EQUAL("", d.Child("a").GetExtension());
UNIT_ASSERT_VALUES_EQUAL("", d.Child(".a").GetExtension());
UNIT_ASSERT_VALUES_EQUAL("", d.Child("zlib").GetExtension());
UNIT_ASSERT_VALUES_EQUAL("zlib", d.Child("file.zlib").GetExtension());
UNIT_ASSERT_VALUES_EQUAL("zlib", d.Child("file.ylib.zlib").GetExtension());
}
Y_UNIT_TEST(TestRename) {
TTestDirectory xx("TestRename");
TFsPath f1 = xx.Child("f1");
TFsPath f2 = xx.Child("f2");
f1.Touch();
f1.RenameTo(f2);
UNIT_ASSERT(!f1.Exists());
UNIT_ASSERT(f2.Exists());
}
Y_UNIT_TEST(TestForceRename) {
TTestDirectory xx("TestForceRename");
TFsPath fMain = xx.Child("main");
TFsPath f1 = fMain.Child("f1");
f1.MkDirs();
TFsPath f1Child = f1.Child("f1child");
f1Child.Touch();
TFsPath f2 = fMain.Child("f2");
f2.MkDirs();
fMain.ForceRenameTo("TestForceRename/main1");
UNIT_ASSERT(!xx.Child("main").Exists());
UNIT_ASSERT(xx.Child("main1").Child("f1").Exists());
UNIT_ASSERT(xx.Child("main1").Child("f2").Exists());
UNIT_ASSERT(xx.Child("main1").Child("f1").Child("f1child").Exists());
}
Y_UNIT_TEST(TestRenameFail) {
UNIT_ASSERT_EXCEPTION(TFsPath("sfsfsfsdfsfsdfdf").RenameTo("sdfsdf"), TIoException);
}
#ifndef _win_
Y_UNIT_TEST(TestRealPath) {
UNIT_ASSERT(TFsPath(".").RealPath().IsDirectory());
TTestDirectory td("TestRealPath");
TFsPath link = td.Child("link");
TFsPath target1 = td.Child("target1");
target1.Touch();
TFsPath target2 = td.Child("target2");
target2.Touch();
UNIT_ASSERT(NFs::SymLink(target1.RealPath(), link.GetPath()));
UNIT_ASSERT_VALUES_EQUAL(link.RealPath(), target1.RealPath());
UNIT_ASSERT(NFs::Remove(link.GetPath()));
UNIT_ASSERT(NFs::SymLink(target2.RealPath(), link.GetPath()));
UNIT_ASSERT_VALUES_EQUAL(link.RealPath(), target2.RealPath()); // must not cache old value
}
#endif
Y_UNIT_TEST(TestSlashesAndBasename) {
TFsPath p("/db/BASE/primus121-025-1380131338//");
UNIT_ASSERT_VALUES_EQUAL(p.Basename(), TString("primus121-025-1380131338"));
TFsPath testP = p / "test";
#ifdef _win_
UNIT_ASSERT_VALUES_EQUAL(testP.GetPath(), "\\db\\BASE\\primus121-025-1380131338\\test");
#else
UNIT_ASSERT_VALUES_EQUAL(testP.GetPath(), "/db/BASE/primus121-025-1380131338/test");
#endif
}
Y_UNIT_TEST(TestSlashesAndBasenameWin) {
TFsPath p("\\db\\BASE\\primus121-025-1380131338\\\\");
TFsPath testP = p / "test";
#ifdef _win_
UNIT_ASSERT_VALUES_EQUAL(p.Basename(), TString("primus121-025-1380131338"));
UNIT_ASSERT_VALUES_EQUAL(testP.GetPath(), "\\db\\BASE\\primus121-025-1380131338\\test");
#else
UNIT_ASSERT_VALUES_EQUAL(p.Basename(), TString("\\db\\BASE\\primus121-025-1380131338\\\\"));
UNIT_ASSERT_VALUES_EQUAL(testP.GetPath(), "\\db\\BASE\\primus121-025-1380131338\\\\/test");
#endif
}
Y_UNIT_TEST(TestSlashesAndBasenameWinDrive) {
TFsPath p("C:\\db\\BASE\\primus121-025-1380131338\\\\");
TFsPath testP = p / "test";
#ifdef _win_
UNIT_ASSERT_VALUES_EQUAL(p.Basename(), TString("primus121-025-1380131338"));
UNIT_ASSERT_VALUES_EQUAL(testP.GetPath(), "C:\\db\\BASE\\primus121-025-1380131338\\test");
#else
UNIT_ASSERT_VALUES_EQUAL(p.Basename(), TString("C:\\db\\BASE\\primus121-025-1380131338\\\\"));
UNIT_ASSERT_VALUES_EQUAL(testP.GetPath(), "C:\\db\\BASE\\primus121-025-1380131338\\\\/test");
#endif
}
Y_UNIT_TEST(TestList) {
TTestDirectory td("TestList-dir");
TFsPath dir = td.GetFsPath();
dir.Child("a").Touch();
dir.Child("b").MkDir();
dir.Child("b").Child("b-1").Touch();
dir.Child("c").MkDir();
dir.Child("d").Touch();
TVector<TString> children;
dir.ListNames(children);
std::sort(children.begin(), children.end());
TVector<TString> expected;
expected.push_back("a");
expected.push_back("b");
expected.push_back("c");
expected.push_back("d");
UNIT_ASSERT_VALUES_EQUAL(expected, children);
}
#ifdef _unix_
Y_UNIT_TEST(MkDirMode) {
TTestDirectory td("MkDirMode");
TFsPath subDir = td.Child("subdir");
const int mode = MODE0775;
subDir.MkDir(mode);
TFileStat stat;
UNIT_ASSERT(subDir.Stat(stat));
// mkdir(2) places umask(2) on mode argument.
const int mask = Umask(0);
Umask(mask);
UNIT_ASSERT_VALUES_EQUAL(stat.Mode& MODE0777, mode & ~mask);
}
#endif
Y_UNIT_TEST(Cwd) {
UNIT_ASSERT_VALUES_EQUAL(TFsPath::Cwd().RealPath(), TFsPath(".").RealPath());
}
Y_UNIT_TEST(TestSubpathOf) {
UNIT_ASSERT(TFsPath("/a/b/c/d").IsSubpathOf("/a/b"));
UNIT_ASSERT(TFsPath("/a").IsSubpathOf("/"));
UNIT_ASSERT(!TFsPath("/").IsSubpathOf("/a"));
UNIT_ASSERT(!TFsPath("/a").IsSubpathOf("/a"));
UNIT_ASSERT(TFsPath("/a/b").IsSubpathOf("/a"));
UNIT_ASSERT(TFsPath("a/b").IsSubpathOf("a"));
UNIT_ASSERT(!TFsPath("/a/b").IsSubpathOf("/b"));
UNIT_ASSERT(!TFsPath("a/b").IsSubpathOf("b"));
// mixing absolute/relative
UNIT_ASSERT(!TFsPath("a").IsSubpathOf("/"));
UNIT_ASSERT(!TFsPath("a").IsSubpathOf("/a"));
UNIT_ASSERT(!TFsPath("/a").IsSubpathOf("a"));
UNIT_ASSERT(!TFsPath("a/b").IsSubpathOf("/a"));
UNIT_ASSERT(!TFsPath("/a/b").IsSubpathOf("a"));
#ifdef _win_
UNIT_ASSERT(TFsPath("x:/a/b").IsSubpathOf("x:/a"));
UNIT_ASSERT(!TFsPath("x:/a/b").IsSubpathOf("y:/a"));
UNIT_ASSERT(!TFsPath("x:/a/b").IsSubpathOf("a"));
#endif
}
Y_UNIT_TEST(TestNonStrictSubpathOf) {
UNIT_ASSERT(TFsPath("/a/b/c/d").IsNonStrictSubpathOf("/a/b"));
UNIT_ASSERT(TFsPath("/a").IsNonStrictSubpathOf("/"));
UNIT_ASSERT(!TFsPath("/").IsNonStrictSubpathOf("/a"));
UNIT_ASSERT(TFsPath("/a/b").IsNonStrictSubpathOf("/a"));
UNIT_ASSERT(TFsPath("a/b").IsNonStrictSubpathOf("a"));
UNIT_ASSERT(!TFsPath("/a/b").IsNonStrictSubpathOf("/b"));
UNIT_ASSERT(!TFsPath("a/b").IsNonStrictSubpathOf("b"));
// mixing absolute/relative
UNIT_ASSERT(!TFsPath("a").IsNonStrictSubpathOf("/"));
UNIT_ASSERT(!TFsPath("a").IsNonStrictSubpathOf("/a"));
UNIT_ASSERT(!TFsPath("/a").IsNonStrictSubpathOf("a"));
UNIT_ASSERT(!TFsPath("a/b").IsNonStrictSubpathOf("/a"));
UNIT_ASSERT(!TFsPath("/a/b").IsNonStrictSubpathOf("a"));
// equal paths
UNIT_ASSERT(TFsPath("").IsNonStrictSubpathOf(""));
UNIT_ASSERT(TFsPath("/").IsNonStrictSubpathOf("/"));
UNIT_ASSERT(TFsPath("a").IsNonStrictSubpathOf("a"));
UNIT_ASSERT(TFsPath("/a").IsNonStrictSubpathOf("/a"));
UNIT_ASSERT(TFsPath("/a").IsNonStrictSubpathOf("/a/"));
UNIT_ASSERT(TFsPath("/a/").IsNonStrictSubpathOf("/a"));
UNIT_ASSERT(TFsPath("/a/").IsNonStrictSubpathOf("/a/"));
#ifdef _win_
UNIT_ASSERT(TFsPath("x:/a/b").IsNonStrictSubpathOf("x:/a"));
UNIT_ASSERT(TFsPath("x:/a").IsNonStrictSubpathOf("x:/a"));
UNIT_ASSERT(TFsPath("x:/a/").IsNonStrictSubpathOf("x:/a"));
UNIT_ASSERT(TFsPath("x:/a").IsNonStrictSubpathOf("x:/a/"));
UNIT_ASSERT(TFsPath("x:/a/").IsNonStrictSubpathOf("x:/a/"));
UNIT_ASSERT(!TFsPath("x:/").IsNonStrictSubpathOf("y:/"));
UNIT_ASSERT(!TFsPath("x:/a/b").IsNonStrictSubpathOf("y:/a"));
UNIT_ASSERT(!TFsPath("x:/a/b").IsNonStrictSubpathOf("a"));
#endif
}
Y_UNIT_TEST(TestRelativePath) {
UNIT_ASSERT_VALUES_EQUAL(TFsPath("/a/b/c/d").RelativePath(TFsPath("/a/b")), TFsPath("c/d"));
UNIT_ASSERT_VALUES_EQUAL(TFsPath("/a/b/c/d").RelativePath(TFsPath("/a/b/e/f")), TFsPath("../../c/d"));
UNIT_ASSERT_VALUES_EQUAL(TFsPath("/").RelativePath(TFsPath("/")), TFsPath());
UNIT_ASSERT_VALUES_EQUAL(TFsPath(".").RelativePath(TFsPath(".")), TFsPath());
UNIT_ASSERT_VALUES_EQUAL(TFsPath("/a/c").RelativePath(TFsPath("/a/b/../c")), TFsPath());
UNIT_ASSERT_VALUES_EQUAL(TFsPath("a/.././b").RelativePath(TFsPath("b/c")), TFsPath(".."));
UNIT_ASSERT_EXCEPTION(TFsPath("a/b/c").RelativePath(TFsPath("d/e")), TIoException);
}
Y_UNIT_TEST(TestUndefined) {
UNIT_ASSERT_VALUES_EQUAL(TFsPath(), TFsPath(""));
UNIT_ASSERT_VALUES_EQUAL(TFsPath(), TFsPath().Fix());
UNIT_ASSERT_VALUES_EQUAL(TFsPath() / TFsPath(), TFsPath());
#ifdef _win_
UNIT_ASSERT_VALUES_EQUAL(TFsPath("a\\b"), TFsPath() / TString("a\\b"));
UNIT_ASSERT_VALUES_EQUAL(TFsPath("a\\b"), "a\\b" / TFsPath());
UNIT_ASSERT_VALUES_EQUAL(TFsPath("\\a\\b"), TFsPath() / "\\a\\b");
UNIT_ASSERT_VALUES_EQUAL(TFsPath("\\a\\b"), "\\a\\b" / TFsPath());
#else
UNIT_ASSERT_VALUES_EQUAL(TFsPath("a/b"), TFsPath() / TString("a/b"));
UNIT_ASSERT_VALUES_EQUAL(TFsPath("a/b"), "a/b" / TFsPath());
UNIT_ASSERT_VALUES_EQUAL(TFsPath("/a/b"), TFsPath() / "/a/b");
UNIT_ASSERT_VALUES_EQUAL(TFsPath("/a/b"), "/a/b" / TFsPath());
#endif
UNIT_ASSERT_VALUES_EQUAL(TFsPath("."), TFsPath() / ".");
UNIT_ASSERT_VALUES_EQUAL(TFsPath("."), "." / TFsPath());
UNIT_ASSERT(TFsPath().PathSplit().empty());
UNIT_ASSERT(!TFsPath().PathSplit().IsAbsolute);
UNIT_ASSERT(TFsPath().IsRelative()); // undefined path is relative
UNIT_ASSERT_VALUES_EQUAL(TFsPath().GetPath(), "");
UNIT_ASSERT_VALUES_EQUAL(TFsPath().GetName(), "");
UNIT_ASSERT_VALUES_EQUAL(TFsPath().GetExtension(), "");
UNIT_ASSERT_VALUES_EQUAL(TFsPath().Parent(), TFsPath());
UNIT_ASSERT_VALUES_EQUAL(TFsPath().Child("a"), TFsPath("a"));
UNIT_ASSERT_VALUES_EQUAL(TFsPath().Basename(), "");
UNIT_ASSERT_VALUES_EQUAL(TFsPath().Dirname(), "");
UNIT_ASSERT(!TFsPath().IsSubpathOf("a/b"));
UNIT_ASSERT(TFsPath().IsContainerOf("a/b"));
UNIT_ASSERT(!TFsPath().IsContainerOf("/a/b"));
#ifdef _win_
UNIT_ASSERT_VALUES_EQUAL(TFsPath("a\\b").RelativeTo(TFsPath()), TFsPath("a\\b"));
#else
UNIT_ASSERT_VALUES_EQUAL(TFsPath("a/b").RelativeTo(TFsPath()), TFsPath("a/b"));
#endif
UNIT_ASSERT(!TFsPath().Exists());
UNIT_ASSERT(!TFsPath().IsFile());
UNIT_ASSERT(!TFsPath().IsDirectory());
TFileStat stat;
UNIT_ASSERT(!TFsPath().Stat(stat));
}
Y_UNIT_TEST(TestJoinFsPaths) {
#ifdef _win_
UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a\\b", "c\\d"), "a\\b\\c\\d");
UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a\\b", "..\\c"), "a\\b\\..\\c");
UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a\\b\\..\\c", "d"), "a\\c\\d");
UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a", "b", "c", "d"), "a\\b\\c\\d");
UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a\\b\\..\\c"), "a\\b\\..\\c");
UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a\\b", ""), "a\\b");
#else
UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a/b", "c/d"), "a/b/c/d");
UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a/b", "../c"), "a/b/../c");
UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a/b/../c", "d"), "a/c/d");
UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a", "b", "c", "d"), "a/b/c/d");
UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a/b/../c"), "a/b/../c");
UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a/b", ""), "a/b");
#endif
}
Y_UNIT_TEST(TestStringCast) {
TFsPath pathOne;
UNIT_ASSERT(TryFromString<TFsPath>("/a/b", pathOne));
UNIT_ASSERT_VALUES_EQUAL(pathOne, TFsPath{"/a/b"});
TFsPath pathTwo;
UNIT_ASSERT_NO_EXCEPTION(TryFromString<TFsPath>("/a/b", pathTwo));
UNIT_ASSERT_VALUES_EQUAL(FromString<TFsPath>("/a/b"), TFsPath{"/a/b"});
TFsPath pathThree{"/a/b"};
UNIT_ASSERT_VALUES_EQUAL(ToString(pathThree), "/a/b");
}
#ifdef _unix_
Y_UNIT_TEST(TestRemoveSymlinkToDir) {
TTempDir tempDir;
TFsPath tempDirPath(tempDir());
const TString originDir = tempDirPath.Child("origin");
MakePathIfNotExist(originDir.c_str());
const TString originFile = TFsPath(originDir).Child("data");
{
TFixedBufferFileOutput out(originFile);
out << "data111!!!";
}
const TString link = tempDirPath.Child("origin_symlink");
NFs::SymLink(originDir, link);
TFsPath(link).ForceDelete();
UNIT_ASSERT(!NFs::Exists(link));
UNIT_ASSERT(NFs::Exists(originFile));
UNIT_ASSERT(NFs::Exists(originDir));
}
Y_UNIT_TEST(TestRemoveSymlinkToFile) {
TTempDir tempDir;
TFsPath tempDirPath(tempDir());
const TString originDir = tempDirPath.Child("origin");
MakePathIfNotExist(originDir.c_str());
const TString originFile = TFsPath(originDir).Child("data");
{
TFixedBufferFileOutput out(originFile);
out << "data111!!!";
}
const TString link = tempDirPath.Child("origin_symlink");
NFs::SymLink(originFile, link);
TFsPath(link).ForceDelete();
UNIT_ASSERT(!NFs::Exists(link));
UNIT_ASSERT(NFs::Exists(originFile));
UNIT_ASSERT(NFs::Exists(originDir));
}
Y_UNIT_TEST(TestRemoveDirWithSymlinkToDir) {
TTempDir tempDir;
TFsPath tempDirPath(tempDir());
const TString symlinkedDir = tempDirPath.Child("to_remove");
MakePathIfNotExist(symlinkedDir.c_str());
const TString originDir = tempDirPath.Child("origin");
MakePathIfNotExist(originDir.c_str());
const TString originFile = TFsPath(originDir).Child("data");
{
TFixedBufferFileOutput out(originFile);
out << "data111!!!";
}
const TString symlinkedFile = TFsPath(symlinkedDir).Child("origin_symlink");
NFs::SymLink(originDir, symlinkedFile);
TFsPath(symlinkedDir).ForceDelete();
UNIT_ASSERT(!NFs::Exists(symlinkedFile));
UNIT_ASSERT(!NFs::Exists(symlinkedDir));
UNIT_ASSERT(NFs::Exists(originFile));
UNIT_ASSERT(NFs::Exists(originDir));
}
Y_UNIT_TEST(TestRemoveDirWithSymlinkToFile) {
TTempDir tempDir;
TFsPath tempDirPath(tempDir());
const TString symlinkedDir = tempDirPath.Child("to_remove");
MakePathIfNotExist(symlinkedDir.c_str());
const TString originDir = tempDirPath.Child("origin");
MakePathIfNotExist(originDir.c_str());
const TString originFile = TFsPath(originDir).Child("data");
{
TFixedBufferFileOutput out(originFile);
out << "data111!!!";
}
const TString symlinkedFile = TFsPath(symlinkedDir).Child("origin_symlink");
NFs::SymLink(originFile, symlinkedFile);
TFsPath(symlinkedDir).ForceDelete();
UNIT_ASSERT(!NFs::Exists(symlinkedFile));
UNIT_ASSERT(!NFs::Exists(symlinkedDir));
UNIT_ASSERT(NFs::Exists(originFile));
UNIT_ASSERT(NFs::Exists(originDir));
}
#endif
Y_UNIT_TEST(TestForceDeleteNonexisting) {
TTempDir tempDir;
TFsPath nonexisting = TFsPath(tempDir()).Child("nonexisting");
nonexisting.ForceDelete();
}
// Here we want to test that all possible errors during TFsPath::ForceDelete
// are properly handled. To do so we have to trigger fs operation errors in
// three points:
// 1. stat/GetFileInformationByHandle
// 2. opendir
// 3. unlink/rmdir
//
// On unix systems we can achieve this by simply setting access rights on
// entry being deleted and its parent. But on windows it is more complicated.
// Current Chmod implementation on windows is not enough as it sets only
// FILE_ATTRIBUTE_READONLY throught SetFileAttributes call. But doing so does
// not affect directory access rights on older versions of Windows and Wine
// that we use to run autocheck tests.
//
// To get required access rights we use DACL in SetSecurityInfo. This is wrapped
// in RAII class that drops requested permissions on file/dir and grantss them
// back in destructor.
//
// Another obstacle is FILE_LIST_DIRECTORY permission when running on Wine.
// Dropping this permission is necessary to provoke error
// in GetFileInformationByHandle. Wine allows dropping this permission, but I
// have not found a way to grant it back. So tests crash during cleanup sequence.
// To make it possible to run this tests natively we detect Wine with special
// registry key and skip these tests only there.
#ifdef _win_
struct TLocalFree {
static void Destroy(void* ptr) {
LocalFree((HLOCAL)ptr);
}
};
bool IsWine() {
HKEY subKey = nullptr;
LONG result = RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Wine", 0, KEY_READ, &subKey);
if (result == ERROR_SUCCESS) {
return true;
}
result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Wine", 0, KEY_READ, &subKey);
if (result == ERROR_SUCCESS) {
return true;
}
HMODULE hntdll = GetModuleHandle("ntdll.dll");
if (!hntdll) {
return false;
}
auto func = GetProcAddress(hntdll, "wine_get_version");
return func != nullptr;
}
class TWinFileDenyAccessScope {
public:
TWinFileDenyAccessScope(const TFsPath& name, DWORD permissions)
: Name_(name)
, Perms_(permissions)
{
DWORD res = 0;
PACL oldAcl = nullptr;
PSECURITY_DESCRIPTOR sd = nullptr;
res = GetNamedSecurityInfoA((LPSTR)name.c_str(),
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION,
nullptr,
nullptr,
&oldAcl,
nullptr,
&sd);
SdHolder_.Reset(sd);
if (res != ERROR_SUCCESS) {
ythrow TSystemError(res) << "error in GetNamedSecurityInfoA";
}
Acl_ = SetAcl(oldAcl, DENY_ACCESS);
}
~TWinFileDenyAccessScope() {
try {
const TFsPath parent = Name_.Parent();
Chmod(parent.c_str(), MODE0777);
Chmod(Name_.c_str(), MODE0777);
SetAcl((PACL)Acl_.Get(), GRANT_ACCESS);
} catch (const yexception& ex) {
Cerr << "~TWinFileDenyAccessScope failed: " << ex.AsStrBuf() << Endl;
}
}
THolder<void, TLocalFree> SetAcl(PACL oldAcl, ACCESS_MODE accessMode) {
DWORD res = 0;
EXPLICIT_ACCESS ea;
PACL newAcl = nullptr;
THolder<void, TLocalFree> newAclHolder;
memset(&ea, 0, sizeof(EXPLICIT_ACCESS));
ea.grfAccessPermissions = Perms_;
ea.grfAccessMode = accessMode;
ea.grfInheritance = NO_INHERITANCE;
ea.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
ea.Trustee.ptstrName = (LPSTR) "CURRENT_USER";
res = SetEntriesInAcl(1, &ea, oldAcl, &newAcl);
newAclHolder.Reset(newAcl);
if (res != ERROR_SUCCESS) {
ythrow TSystemError(res) << "error in SetEntriesInAcl";
}
res = SetNamedSecurityInfoA((LPSTR)Name_.c_str(),
SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION,
nullptr,
nullptr,
newAcl,
nullptr);
if (res != ERROR_SUCCESS) {
ythrow TSystemError(res) << "error in SetNamedSecurityInfoA";
}
return std::move(newAclHolder);
}
private:
const TFsPath Name_;
const DWORD Perms_;
THolder<void, TLocalFree> SdHolder_;
THolder<void, TLocalFree> Acl_;
};
#endif
Y_UNIT_TEST(TestForceDeleteErrorUnlink) {
TTempDir tempDir;
const TFsPath testDir = TFsPath(tempDir()).Child("dir");
MakePathIfNotExist(testDir.c_str());
const TFsPath testFile = testDir.Child("file");
{
TFixedBufferFileOutput out(testFile);
out << "data111!!!";
}
#ifdef _win_
Chmod(testFile.c_str(), S_IRUSR);
Y_DEFER {
Chmod(testFile.c_str(), MODE0777);
};
#else
Chmod(testDir.c_str(), S_IRUSR | S_IXUSR);
Y_DEFER {
Chmod(testDir.c_str(), MODE0777);
};
#endif
UNIT_ASSERT_EXCEPTION_CONTAINS(testFile.ForceDelete(), TIoException, "failed to delete");
}
Y_UNIT_TEST(TestForceDeleteErrorRmdir) {
TTempDir tempDir;
const TFsPath testDir = TFsPath(tempDir()).Child("dir");
const TFsPath testSubdir = testDir.Child("file");
MakePathIfNotExist(testSubdir.c_str());
#ifdef _win_
Chmod(testSubdir.c_str(), 0);
Y_DEFER {
Chmod(testSubdir.c_str(), MODE0777);
};
TWinFileDenyAccessScope dirAcl(testDir, FILE_WRITE_DATA);
#else
Chmod(testDir.c_str(), S_IRUSR | S_IXUSR);
Y_DEFER {
Chmod(testDir.c_str(), MODE0777);
};
#endif
UNIT_ASSERT_EXCEPTION_CONTAINS(testSubdir.ForceDelete(), TIoException, "failed to delete");
}
Y_UNIT_TEST(TestForceDeleteErrorStatDir) {
TTempDir tempDir;
const TFsPath testDir = TFsPath(tempDir()).Child("dir");
const TFsPath testSubdir = testDir.Child("file");
MakePathIfNotExist(testSubdir.c_str());
#ifdef _win_
if (IsWine()) {
// FILE_LIST_DIRECTORY seem to be irreversible on wine
return;
}
TWinFileDenyAccessScope subdirAcl(testSubdir, FILE_READ_ATTRIBUTES);
TWinFileDenyAccessScope dirAcl(testDir, FILE_LIST_DIRECTORY);
#else
Chmod(testDir.c_str(), 0);
Y_DEFER {
Chmod(testDir.c_str(), MODE0777);
};
#endif
UNIT_ASSERT_EXCEPTION_CONTAINS(testSubdir.ForceDelete(), TIoException, "failed to stat");
}
Y_UNIT_TEST(TestForceDeleteErrorStatFile) {
TTempDir tempDir;
const TFsPath testDir = TFsPath(tempDir()).Child("dir");
MakePathIfNotExist(testDir.c_str());
const TFsPath testFile = testDir.Child("file");
{
TFixedBufferFileOutput out(testFile);
out << "data111!!!";
}
#ifdef _win_
if (IsWine()) {
// FILE_LIST_DIRECTORY seem to be irreversible on wine
return;
}
TWinFileDenyAccessScope fileAcl(testFile, FILE_READ_ATTRIBUTES);
TWinFileDenyAccessScope dirAcl(testDir, FILE_LIST_DIRECTORY);
#else
Chmod(testDir.c_str(), 0);
Y_DEFER {
Chmod(testDir.c_str(), MODE0777);
};
#endif
UNIT_ASSERT_EXCEPTION_CONTAINS(testFile.ForceDelete(), TIoException, "failed to stat");
}
Y_UNIT_TEST(TestForceDeleteErrorListDir) {
TTempDir tempDir;
const TFsPath testDir = TFsPath(tempDir()).Child("dir");
const TFsPath testSubdir = testDir.Child("file");
MakePathIfNotExist(testSubdir.c_str());
#ifdef _win_
if (IsWine()) {
// FILE_LIST_DIRECTORY seem to be irreversible on wine
return;
}
TWinFileDenyAccessScope subdirAcl(testSubdir, FILE_LIST_DIRECTORY);
#else
Chmod(testSubdir.c_str(), 0);
Y_DEFER {
Chmod(testSubdir.c_str(), MODE0777);
};
#endif
UNIT_ASSERT_EXCEPTION_CONTAINS(testSubdir.ForceDelete(), TIoException, "failed to opendir");
}
#ifdef _unix_
Y_UNIT_TEST(TestForceDeleteErrorSymlink) {
TTempDir tempDir;
const TFsPath testDir = TFsPath(tempDir()).Child("dir");
MakePathIfNotExist(testDir.c_str());
const TFsPath testSymlink = testDir.Child("symlink");
NFs::SymLink("something", testSymlink);
Chmod(testSymlink.c_str(), S_IRUSR);
Chmod(testDir.c_str(), S_IRUSR | S_IXUSR);
Y_DEFER {
Chmod(testDir.c_str(), MODE0777);
Chmod(testSymlink.c_str(), MODE0777);
};
UNIT_ASSERT_EXCEPTION_CONTAINS(testSymlink.ForceDelete(), TIoException, "failed to delete");
}
#endif
Y_UNIT_TEST(TestCopyWithInitializedSplit) {
const TFsPath path1 = TFsPath("some_folder_with_file") / TFsPath("file_in_folder");
path1.PathSplit();
const TFsPath path2 = path1;
const TPathSplit& split2 = path2.PathSplit();
for (const auto& it : split2) {
UNIT_ASSERT(path2.GetPath().begin() <= it.begin());
UNIT_ASSERT(it.end() <= path2.GetPath().end());
}
}
Y_UNIT_TEST(TestAssignmentWithInitializedSplit) {
TFsPath path1 = TFsPath("some_folder_with_file_1") / TFsPath("file_in_folder_1");
TFsPath path2 = TFsPath("some_folder_with_file_2") / TFsPath("file_in_folder_2");
path1.PathSplit();
path1 = path2;
UNIT_ASSERT_VALUES_EQUAL(path1.PathSplit().at(1), "file_in_folder_2");
}
}