path_ut.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900
  1. #include "path.h"
  2. #include "pathsplit.h"
  3. #include "dirut.h"
  4. #include "tempdir.h"
  5. #include <library/cpp/testing/unittest/registar.h>
  6. #include <util/generic/scope.h>
  7. #include <util/system/platform.h>
  8. #include <util/system/yassert.h>
  9. #include <util/stream/output.h>
  10. #include <util/stream/file.h>
  11. #include <util/system/fs.h>
  12. #include <algorithm>
  13. #ifdef _win_
  14. #include <aclapi.h>
  15. #endif
  16. namespace {
  17. /// empty directory for test that needs filesystem
  18. /// recreates directory in constructor and removes directory in destructor
  19. class TTestDirectory {
  20. private:
  21. TFsPath Path_;
  22. public:
  23. TTestDirectory(const TString& name);
  24. ~TTestDirectory();
  25. TFsPath GetFsPath() const {
  26. return Path_;
  27. }
  28. TFsPath Child(const TString& name) const {
  29. return Path_.Child(name);
  30. }
  31. };
  32. TTestDirectory::TTestDirectory(const TString& name) {
  33. Y_ABORT_UNLESS(name.length() > 0, "have to specify name");
  34. Y_ABORT_UNLESS(name.find('.') == TString::npos, "must be simple name");
  35. Y_ABORT_UNLESS(name.find('/') == TString::npos, "must be simple name");
  36. Y_ABORT_UNLESS(name.find('\\') == TString::npos, "must be simple name");
  37. Path_ = TFsPath(name);
  38. Path_.ForceDelete();
  39. Path_.MkDir();
  40. }
  41. TTestDirectory::~TTestDirectory() {
  42. Path_.ForceDelete();
  43. }
  44. } // namespace
  45. Y_UNIT_TEST_SUITE(TFsPathTests) {
  46. Y_UNIT_TEST(TestMkDirs) {
  47. const TFsPath path = "a/b/c/d/e/f";
  48. path.ForceDelete();
  49. TFsPath current = path;
  50. ui32 checksCounter = 0;
  51. while (current != ".") {
  52. UNIT_ASSERT(!path.Exists());
  53. ++checksCounter;
  54. current = current.Parent();
  55. }
  56. UNIT_ASSERT_VALUES_EQUAL(checksCounter, 6);
  57. path.MkDirs();
  58. UNIT_ASSERT(path.Exists());
  59. current = path;
  60. while (current != ".") {
  61. UNIT_ASSERT(path.Exists());
  62. current = current.Parent();
  63. }
  64. }
  65. Y_UNIT_TEST(MkDirFreak) {
  66. TFsPath path;
  67. UNIT_ASSERT_EXCEPTION(path.MkDir(), TIoException);
  68. UNIT_ASSERT_EXCEPTION(path.MkDirs(), TIoException);
  69. path = ".";
  70. path.MkDir();
  71. path.MkDirs();
  72. }
  73. Y_UNIT_TEST(Parent) {
  74. #ifdef _win_
  75. UNIT_ASSERT_VALUES_EQUAL(TFsPath("\\etc/passwd").Parent(), TFsPath("\\etc"));
  76. UNIT_ASSERT_VALUES_EQUAL(TFsPath("\\etc").Parent(), TFsPath("\\"));
  77. UNIT_ASSERT_VALUES_EQUAL(TFsPath("\\").Parent(), TFsPath("\\"));
  78. UNIT_ASSERT_VALUES_EQUAL(TFsPath("etc\\passwd").Parent(), TFsPath("etc"));
  79. UNIT_ASSERT_VALUES_EQUAL(TFsPath("etc").Parent(), TFsPath("."));
  80. UNIT_ASSERT_VALUES_EQUAL(TFsPath(".\\etc").Parent(), TFsPath("."));
  81. UNIT_ASSERT_VALUES_EQUAL(TFsPath("C:\\etc/passwd").Parent(), TFsPath("C:\\etc"));
  82. UNIT_ASSERT_VALUES_EQUAL(TFsPath("C:\\etc").Parent(), TFsPath("C:\\"));
  83. UNIT_ASSERT_VALUES_EQUAL(TFsPath("C:\\").Parent(), TFsPath("C:\\"));
  84. #else
  85. UNIT_ASSERT_VALUES_EQUAL(TFsPath("/etc/passwd").Parent(), TFsPath("/etc"));
  86. UNIT_ASSERT_VALUES_EQUAL(TFsPath("/etc").Parent(), TFsPath("/"));
  87. UNIT_ASSERT_VALUES_EQUAL(TFsPath("/").Parent(), TFsPath("/"));
  88. UNIT_ASSERT_VALUES_EQUAL(TFsPath("etc/passwd").Parent(), TFsPath("etc"));
  89. UNIT_ASSERT_VALUES_EQUAL(TFsPath("etc").Parent(), TFsPath("."));
  90. UNIT_ASSERT_VALUES_EQUAL(TFsPath("./etc").Parent(), TFsPath("."));
  91. #endif
  92. #if 0
  93. UNIT_ASSERT_VALUES_EQUAL(TFsPath("./etc/passwd").Parent(), TFsPath("./etc"));
  94. UNIT_ASSERT_VALUES_EQUAL(TFsPath("./").Parent(), TFsPath(".."));
  95. UNIT_ASSERT_VALUES_EQUAL(TFsPath(".").Parent(), TFsPath(".."));
  96. UNIT_ASSERT_VALUES_EQUAL(TFsPath("..").Parent(), TFsPath("../.."));
  97. #endif
  98. }
  99. Y_UNIT_TEST(GetName) {
  100. TTestDirectory d("GetName");
  101. UNIT_ASSERT_VALUES_EQUAL(TString("dfgh"), d.Child("dfgh").GetName());
  102. // check does not fail
  103. TFsPath(".").GetName();
  104. #ifdef _unix_
  105. UNIT_ASSERT_VALUES_EQUAL(TString("/"), TFsPath("/").GetName());
  106. #endif
  107. }
  108. Y_UNIT_TEST(GetExtension) {
  109. TTestDirectory d("GetExtension");
  110. UNIT_ASSERT_VALUES_EQUAL("", d.Child("a").GetExtension());
  111. UNIT_ASSERT_VALUES_EQUAL("", d.Child(".a").GetExtension());
  112. UNIT_ASSERT_VALUES_EQUAL("", d.Child("zlib").GetExtension());
  113. UNIT_ASSERT_VALUES_EQUAL("zlib", d.Child("file.zlib").GetExtension());
  114. UNIT_ASSERT_VALUES_EQUAL("zlib", d.Child("file.ylib.zlib").GetExtension());
  115. }
  116. Y_UNIT_TEST(TestRename) {
  117. TTestDirectory xx("TestRename");
  118. TFsPath f1 = xx.Child("f1");
  119. TFsPath f2 = xx.Child("f2");
  120. f1.Touch();
  121. f1.RenameTo(f2);
  122. UNIT_ASSERT(!f1.Exists());
  123. UNIT_ASSERT(f2.Exists());
  124. }
  125. Y_UNIT_TEST(TestForceRename) {
  126. TTestDirectory xx("TestForceRename");
  127. TFsPath fMain = xx.Child("main");
  128. TFsPath f1 = fMain.Child("f1");
  129. f1.MkDirs();
  130. TFsPath f1Child = f1.Child("f1child");
  131. f1Child.Touch();
  132. TFsPath f2 = fMain.Child("f2");
  133. f2.MkDirs();
  134. fMain.ForceRenameTo("TestForceRename/main1");
  135. UNIT_ASSERT(!xx.Child("main").Exists());
  136. UNIT_ASSERT(xx.Child("main1").Child("f1").Exists());
  137. UNIT_ASSERT(xx.Child("main1").Child("f2").Exists());
  138. UNIT_ASSERT(xx.Child("main1").Child("f1").Child("f1child").Exists());
  139. }
  140. Y_UNIT_TEST(TestRenameFail) {
  141. UNIT_ASSERT_EXCEPTION(TFsPath("sfsfsfsdfsfsdfdf").RenameTo("sdfsdf"), TIoException);
  142. }
  143. #ifndef _win_
  144. Y_UNIT_TEST(TestRealPath) {
  145. UNIT_ASSERT(TFsPath(".").RealPath().IsDirectory());
  146. TTestDirectory td("TestRealPath");
  147. TFsPath link = td.Child("link");
  148. TFsPath target1 = td.Child("target1");
  149. target1.Touch();
  150. TFsPath target2 = td.Child("target2");
  151. target2.Touch();
  152. UNIT_ASSERT(NFs::SymLink(target1.RealPath(), link.GetPath()));
  153. UNIT_ASSERT_VALUES_EQUAL(link.RealPath(), target1.RealPath());
  154. UNIT_ASSERT(NFs::Remove(link.GetPath()));
  155. UNIT_ASSERT(NFs::SymLink(target2.RealPath(), link.GetPath()));
  156. UNIT_ASSERT_VALUES_EQUAL(link.RealPath(), target2.RealPath()); // must not cache old value
  157. }
  158. #endif
  159. Y_UNIT_TEST(TestSlashesAndBasename) {
  160. TFsPath p("/db/BASE/primus121-025-1380131338//");
  161. UNIT_ASSERT_VALUES_EQUAL(p.Basename(), TString("primus121-025-1380131338"));
  162. TFsPath testP = p / "test";
  163. #ifdef _win_
  164. UNIT_ASSERT_VALUES_EQUAL(testP.GetPath(), "\\db\\BASE\\primus121-025-1380131338\\test");
  165. #else
  166. UNIT_ASSERT_VALUES_EQUAL(testP.GetPath(), "/db/BASE/primus121-025-1380131338/test");
  167. #endif
  168. }
  169. Y_UNIT_TEST(TestSlashesAndBasenameWin) {
  170. TFsPath p("\\db\\BASE\\primus121-025-1380131338\\\\");
  171. TFsPath testP = p / "test";
  172. #ifdef _win_
  173. UNIT_ASSERT_VALUES_EQUAL(p.Basename(), TString("primus121-025-1380131338"));
  174. UNIT_ASSERT_VALUES_EQUAL(testP.GetPath(), "\\db\\BASE\\primus121-025-1380131338\\test");
  175. #else
  176. UNIT_ASSERT_VALUES_EQUAL(p.Basename(), TString("\\db\\BASE\\primus121-025-1380131338\\\\"));
  177. UNIT_ASSERT_VALUES_EQUAL(testP.GetPath(), "\\db\\BASE\\primus121-025-1380131338\\\\/test");
  178. #endif
  179. }
  180. Y_UNIT_TEST(TestSlashesAndBasenameWinDrive) {
  181. TFsPath p("C:\\db\\BASE\\primus121-025-1380131338\\\\");
  182. TFsPath testP = p / "test";
  183. #ifdef _win_
  184. UNIT_ASSERT_VALUES_EQUAL(p.Basename(), TString("primus121-025-1380131338"));
  185. UNIT_ASSERT_VALUES_EQUAL(testP.GetPath(), "C:\\db\\BASE\\primus121-025-1380131338\\test");
  186. #else
  187. UNIT_ASSERT_VALUES_EQUAL(p.Basename(), TString("C:\\db\\BASE\\primus121-025-1380131338\\\\"));
  188. UNIT_ASSERT_VALUES_EQUAL(testP.GetPath(), "C:\\db\\BASE\\primus121-025-1380131338\\\\/test");
  189. #endif
  190. }
  191. Y_UNIT_TEST(TestList) {
  192. TTestDirectory td("TestList-dir");
  193. TFsPath dir = td.GetFsPath();
  194. dir.Child("a").Touch();
  195. dir.Child("b").MkDir();
  196. dir.Child("b").Child("b-1").Touch();
  197. dir.Child("c").MkDir();
  198. dir.Child("d").Touch();
  199. TVector<TString> children;
  200. dir.ListNames(children);
  201. std::sort(children.begin(), children.end());
  202. TVector<TString> expected;
  203. expected.push_back("a");
  204. expected.push_back("b");
  205. expected.push_back("c");
  206. expected.push_back("d");
  207. UNIT_ASSERT_VALUES_EQUAL(expected, children);
  208. }
  209. #ifdef _unix_
  210. Y_UNIT_TEST(MkDirMode) {
  211. TTestDirectory td("MkDirMode");
  212. TFsPath subDir = td.Child("subdir");
  213. const int mode = MODE0775;
  214. subDir.MkDir(mode);
  215. TFileStat stat;
  216. UNIT_ASSERT(subDir.Stat(stat));
  217. // mkdir(2) places umask(2) on mode argument.
  218. const int mask = Umask(0);
  219. Umask(mask);
  220. UNIT_ASSERT_VALUES_EQUAL(stat.Mode & MODE0777, mode & ~mask);
  221. }
  222. #endif
  223. Y_UNIT_TEST(Cwd) {
  224. UNIT_ASSERT_VALUES_EQUAL(TFsPath::Cwd().RealPath(), TFsPath(".").RealPath());
  225. }
  226. Y_UNIT_TEST(TestSubpathOf) {
  227. UNIT_ASSERT(TFsPath("/a/b/c/d").IsSubpathOf("/a/b"));
  228. UNIT_ASSERT(TFsPath("/a").IsSubpathOf("/"));
  229. UNIT_ASSERT(!TFsPath("/").IsSubpathOf("/a"));
  230. UNIT_ASSERT(!TFsPath("/a").IsSubpathOf("/a"));
  231. UNIT_ASSERT(TFsPath("/a/b").IsSubpathOf("/a"));
  232. UNIT_ASSERT(TFsPath("a/b").IsSubpathOf("a"));
  233. UNIT_ASSERT(!TFsPath("/a/b").IsSubpathOf("/b"));
  234. UNIT_ASSERT(!TFsPath("a/b").IsSubpathOf("b"));
  235. // mixing absolute/relative
  236. UNIT_ASSERT(!TFsPath("a").IsSubpathOf("/"));
  237. UNIT_ASSERT(!TFsPath("a").IsSubpathOf("/a"));
  238. UNIT_ASSERT(!TFsPath("/a").IsSubpathOf("a"));
  239. UNIT_ASSERT(!TFsPath("a/b").IsSubpathOf("/a"));
  240. UNIT_ASSERT(!TFsPath("/a/b").IsSubpathOf("a"));
  241. #ifdef _win_
  242. UNIT_ASSERT(TFsPath("x:/a/b").IsSubpathOf("x:/a"));
  243. UNIT_ASSERT(!TFsPath("x:/a/b").IsSubpathOf("y:/a"));
  244. UNIT_ASSERT(!TFsPath("x:/a/b").IsSubpathOf("a"));
  245. #endif
  246. }
  247. Y_UNIT_TEST(TestNonStrictSubpathOf) {
  248. UNIT_ASSERT(TFsPath("/a/b/c/d").IsNonStrictSubpathOf("/a/b"));
  249. UNIT_ASSERT(TFsPath("/a").IsNonStrictSubpathOf("/"));
  250. UNIT_ASSERT(!TFsPath("/").IsNonStrictSubpathOf("/a"));
  251. UNIT_ASSERT(TFsPath("/a/b").IsNonStrictSubpathOf("/a"));
  252. UNIT_ASSERT(TFsPath("a/b").IsNonStrictSubpathOf("a"));
  253. UNIT_ASSERT(!TFsPath("/a/b").IsNonStrictSubpathOf("/b"));
  254. UNIT_ASSERT(!TFsPath("a/b").IsNonStrictSubpathOf("b"));
  255. // mixing absolute/relative
  256. UNIT_ASSERT(!TFsPath("a").IsNonStrictSubpathOf("/"));
  257. UNIT_ASSERT(!TFsPath("a").IsNonStrictSubpathOf("/a"));
  258. UNIT_ASSERT(!TFsPath("/a").IsNonStrictSubpathOf("a"));
  259. UNIT_ASSERT(!TFsPath("a/b").IsNonStrictSubpathOf("/a"));
  260. UNIT_ASSERT(!TFsPath("/a/b").IsNonStrictSubpathOf("a"));
  261. // equal paths
  262. UNIT_ASSERT(TFsPath("").IsNonStrictSubpathOf(""));
  263. UNIT_ASSERT(TFsPath("/").IsNonStrictSubpathOf("/"));
  264. UNIT_ASSERT(TFsPath("a").IsNonStrictSubpathOf("a"));
  265. UNIT_ASSERT(TFsPath("/a").IsNonStrictSubpathOf("/a"));
  266. UNIT_ASSERT(TFsPath("/a").IsNonStrictSubpathOf("/a/"));
  267. UNIT_ASSERT(TFsPath("/a/").IsNonStrictSubpathOf("/a"));
  268. UNIT_ASSERT(TFsPath("/a/").IsNonStrictSubpathOf("/a/"));
  269. #ifdef _win_
  270. UNIT_ASSERT(TFsPath("x:/a/b").IsNonStrictSubpathOf("x:/a"));
  271. UNIT_ASSERT(TFsPath("x:/a").IsNonStrictSubpathOf("x:/a"));
  272. UNIT_ASSERT(TFsPath("x:/a/").IsNonStrictSubpathOf("x:/a"));
  273. UNIT_ASSERT(TFsPath("x:/a").IsNonStrictSubpathOf("x:/a/"));
  274. UNIT_ASSERT(TFsPath("x:/a/").IsNonStrictSubpathOf("x:/a/"));
  275. UNIT_ASSERT(!TFsPath("x:/").IsNonStrictSubpathOf("y:/"));
  276. UNIT_ASSERT(!TFsPath("x:/a/b").IsNonStrictSubpathOf("y:/a"));
  277. UNIT_ASSERT(!TFsPath("x:/a/b").IsNonStrictSubpathOf("a"));
  278. #endif
  279. }
  280. Y_UNIT_TEST(TestRelativePath) {
  281. UNIT_ASSERT_VALUES_EQUAL(TFsPath("/a/b/c/d").RelativePath(TFsPath("/a/b")), TFsPath("c/d"));
  282. UNIT_ASSERT_VALUES_EQUAL(TFsPath("/a/b/c/d").RelativePath(TFsPath("/a/b/e/f")), TFsPath("../../c/d"));
  283. UNIT_ASSERT_VALUES_EQUAL(TFsPath("/").RelativePath(TFsPath("/")), TFsPath());
  284. UNIT_ASSERT_VALUES_EQUAL(TFsPath(".").RelativePath(TFsPath(".")), TFsPath());
  285. UNIT_ASSERT_VALUES_EQUAL(TFsPath("/a/c").RelativePath(TFsPath("/a/b/../c")), TFsPath());
  286. UNIT_ASSERT_VALUES_EQUAL(TFsPath("a/.././b").RelativePath(TFsPath("b/c")), TFsPath(".."));
  287. UNIT_ASSERT_EXCEPTION(TFsPath("a/b/c").RelativePath(TFsPath("d/e")), TIoException);
  288. }
  289. Y_UNIT_TEST(TestUndefined) {
  290. UNIT_ASSERT_VALUES_EQUAL(TFsPath(), TFsPath(""));
  291. UNIT_ASSERT_VALUES_EQUAL(TFsPath(), TFsPath().Fix());
  292. UNIT_ASSERT_VALUES_EQUAL(TFsPath() / TFsPath(), TFsPath());
  293. #ifdef _win_
  294. UNIT_ASSERT_VALUES_EQUAL(TFsPath("a\\b"), TFsPath() / TString("a\\b"));
  295. UNIT_ASSERT_VALUES_EQUAL(TFsPath("a\\b"), "a\\b" / TFsPath());
  296. UNIT_ASSERT_VALUES_EQUAL(TFsPath("\\a\\b"), TFsPath() / "\\a\\b");
  297. UNIT_ASSERT_VALUES_EQUAL(TFsPath("\\a\\b"), "\\a\\b" / TFsPath());
  298. #else
  299. UNIT_ASSERT_VALUES_EQUAL(TFsPath("a/b"), TFsPath() / TString("a/b"));
  300. UNIT_ASSERT_VALUES_EQUAL(TFsPath("a/b"), "a/b" / TFsPath());
  301. UNIT_ASSERT_VALUES_EQUAL(TFsPath("/a/b"), TFsPath() / "/a/b");
  302. UNIT_ASSERT_VALUES_EQUAL(TFsPath("/a/b"), "/a/b" / TFsPath());
  303. #endif
  304. UNIT_ASSERT_VALUES_EQUAL(TFsPath("."), TFsPath() / ".");
  305. UNIT_ASSERT_VALUES_EQUAL(TFsPath("."), "." / TFsPath());
  306. UNIT_ASSERT(TFsPath().PathSplit().empty());
  307. UNIT_ASSERT(!TFsPath().PathSplit().IsAbsolute);
  308. UNIT_ASSERT(TFsPath().IsRelative()); // undefined path is relative
  309. UNIT_ASSERT_VALUES_EQUAL(TFsPath().GetPath(), "");
  310. UNIT_ASSERT_VALUES_EQUAL(TFsPath().GetName(), "");
  311. UNIT_ASSERT_VALUES_EQUAL(TFsPath().GetExtension(), "");
  312. UNIT_ASSERT_VALUES_EQUAL(TFsPath().Parent(), TFsPath());
  313. UNIT_ASSERT_VALUES_EQUAL(TFsPath().Child("a"), TFsPath("a"));
  314. UNIT_ASSERT_VALUES_EQUAL(TFsPath().Basename(), "");
  315. UNIT_ASSERT_VALUES_EQUAL(TFsPath().Dirname(), "");
  316. UNIT_ASSERT(!TFsPath().IsSubpathOf("a/b"));
  317. UNIT_ASSERT(TFsPath().IsContainerOf("a/b"));
  318. UNIT_ASSERT(!TFsPath().IsContainerOf("/a/b"));
  319. #ifdef _win_
  320. UNIT_ASSERT_VALUES_EQUAL(TFsPath("a\\b").RelativeTo(TFsPath()), TFsPath("a\\b"));
  321. #else
  322. UNIT_ASSERT_VALUES_EQUAL(TFsPath("a/b").RelativeTo(TFsPath()), TFsPath("a/b"));
  323. #endif
  324. UNIT_ASSERT(!TFsPath().Exists());
  325. UNIT_ASSERT(!TFsPath().IsFile());
  326. UNIT_ASSERT(!TFsPath().IsDirectory());
  327. TFileStat stat;
  328. UNIT_ASSERT(!TFsPath().Stat(stat));
  329. }
  330. Y_UNIT_TEST(TestJoinFsPaths) {
  331. #ifdef _win_
  332. UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a\\b", "c\\d"), "a\\b\\c\\d");
  333. UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a\\b", "..\\c"), "a\\b\\..\\c");
  334. UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a\\b\\..\\c", "d"), "a\\c\\d");
  335. UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a", "b", "c", "d"), "a\\b\\c\\d");
  336. UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a\\b\\..\\c"), "a\\b\\..\\c");
  337. UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a\\b", ""), "a\\b");
  338. #else
  339. UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a/b", "c/d"), "a/b/c/d");
  340. UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a/b", "../c"), "a/b/../c");
  341. UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a/b/../c", "d"), "a/c/d");
  342. UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a", "b", "c", "d"), "a/b/c/d");
  343. UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a/b/../c"), "a/b/../c");
  344. UNIT_ASSERT_VALUES_EQUAL(JoinFsPaths("a/b", ""), "a/b");
  345. #endif
  346. }
  347. Y_UNIT_TEST(TestStringCast) {
  348. TFsPath pathOne;
  349. UNIT_ASSERT(TryFromString<TFsPath>("/a/b", pathOne));
  350. UNIT_ASSERT_VALUES_EQUAL(pathOne, TFsPath{"/a/b"});
  351. TFsPath pathTwo;
  352. UNIT_ASSERT_NO_EXCEPTION(TryFromString<TFsPath>("/a/b", pathTwo));
  353. UNIT_ASSERT_VALUES_EQUAL(FromString<TFsPath>("/a/b"), TFsPath{"/a/b"});
  354. TFsPath pathThree{"/a/b"};
  355. UNIT_ASSERT_VALUES_EQUAL(ToString(pathThree), "/a/b");
  356. }
  357. #ifdef _unix_
  358. Y_UNIT_TEST(TestRemoveSymlinkToDir) {
  359. TTempDir tempDir;
  360. TFsPath tempDirPath(tempDir());
  361. const TString originDir = tempDirPath.Child("origin");
  362. MakePathIfNotExist(originDir.c_str());
  363. const TString originFile = TFsPath(originDir).Child("data");
  364. {
  365. TFixedBufferFileOutput out(originFile);
  366. out << "data111!!!";
  367. }
  368. const TString link = tempDirPath.Child("origin_symlink");
  369. NFs::SymLink(originDir, link);
  370. TFsPath(link).ForceDelete();
  371. UNIT_ASSERT(!NFs::Exists(link));
  372. UNIT_ASSERT(NFs::Exists(originFile));
  373. UNIT_ASSERT(NFs::Exists(originDir));
  374. }
  375. Y_UNIT_TEST(TestRemoveSymlinkToFile) {
  376. TTempDir tempDir;
  377. TFsPath tempDirPath(tempDir());
  378. const TString originDir = tempDirPath.Child("origin");
  379. MakePathIfNotExist(originDir.c_str());
  380. const TString originFile = TFsPath(originDir).Child("data");
  381. {
  382. TFixedBufferFileOutput out(originFile);
  383. out << "data111!!!";
  384. }
  385. const TString link = tempDirPath.Child("origin_symlink");
  386. NFs::SymLink(originFile, link);
  387. TFsPath(link).ForceDelete();
  388. UNIT_ASSERT(!NFs::Exists(link));
  389. UNIT_ASSERT(NFs::Exists(originFile));
  390. UNIT_ASSERT(NFs::Exists(originDir));
  391. }
  392. Y_UNIT_TEST(TestRemoveDirWithSymlinkToDir) {
  393. TTempDir tempDir;
  394. TFsPath tempDirPath(tempDir());
  395. const TString symlinkedDir = tempDirPath.Child("to_remove");
  396. MakePathIfNotExist(symlinkedDir.c_str());
  397. const TString originDir = tempDirPath.Child("origin");
  398. MakePathIfNotExist(originDir.c_str());
  399. const TString originFile = TFsPath(originDir).Child("data");
  400. {
  401. TFixedBufferFileOutput out(originFile);
  402. out << "data111!!!";
  403. }
  404. const TString symlinkedFile = TFsPath(symlinkedDir).Child("origin_symlink");
  405. NFs::SymLink(originDir, symlinkedFile);
  406. TFsPath(symlinkedDir).ForceDelete();
  407. UNIT_ASSERT(!NFs::Exists(symlinkedFile));
  408. UNIT_ASSERT(!NFs::Exists(symlinkedDir));
  409. UNIT_ASSERT(NFs::Exists(originFile));
  410. UNIT_ASSERT(NFs::Exists(originDir));
  411. }
  412. Y_UNIT_TEST(TestRemoveDirWithSymlinkToFile) {
  413. TTempDir tempDir;
  414. TFsPath tempDirPath(tempDir());
  415. const TString symlinkedDir = tempDirPath.Child("to_remove");
  416. MakePathIfNotExist(symlinkedDir.c_str());
  417. const TString originDir = tempDirPath.Child("origin");
  418. MakePathIfNotExist(originDir.c_str());
  419. const TString originFile = TFsPath(originDir).Child("data");
  420. {
  421. TFixedBufferFileOutput out(originFile);
  422. out << "data111!!!";
  423. }
  424. const TString symlinkedFile = TFsPath(symlinkedDir).Child("origin_symlink");
  425. NFs::SymLink(originFile, symlinkedFile);
  426. TFsPath(symlinkedDir).ForceDelete();
  427. UNIT_ASSERT(!NFs::Exists(symlinkedFile));
  428. UNIT_ASSERT(!NFs::Exists(symlinkedDir));
  429. UNIT_ASSERT(NFs::Exists(originFile));
  430. UNIT_ASSERT(NFs::Exists(originDir));
  431. }
  432. #endif
  433. Y_UNIT_TEST(TestForceDeleteNonexisting) {
  434. TTempDir tempDir;
  435. TFsPath nonexisting = TFsPath(tempDir()).Child("nonexisting");
  436. nonexisting.ForceDelete();
  437. }
  438. // Here we want to test that all possible errors during TFsPath::ForceDelete
  439. // are properly handled. To do so we have to trigger fs operation errors in
  440. // three points:
  441. // 1. stat/GetFileInformationByHandle
  442. // 2. opendir
  443. // 3. unlink/rmdir
  444. //
  445. // On unix systems we can achieve this by simply setting access rights on
  446. // entry being deleted and its parent. But on windows it is more complicated.
  447. // Current Chmod implementation on windows is not enough as it sets only
  448. // FILE_ATTRIBUTE_READONLY throught SetFileAttributes call. But doing so does
  449. // not affect directory access rights on older versions of Windows and Wine
  450. // that we use to run autocheck tests.
  451. //
  452. // To get required access rights we use DACL in SetSecurityInfo. This is wrapped
  453. // in RAII class that drops requested permissions on file/dir and grantss them
  454. // back in destructor.
  455. //
  456. // Another obstacle is FILE_LIST_DIRECTORY permission when running on Wine.
  457. // Dropping this permission is necessary to provoke error
  458. // in GetFileInformationByHandle. Wine allows dropping this permission, but I
  459. // have not found a way to grant it back. So tests crash during cleanup sequence.
  460. // To make it possible to run this tests natively we detect Wine with special
  461. // registry key and skip these tests only there.
  462. #ifdef _win_
  463. struct TLocalFree {
  464. static void Destroy(void* ptr) {
  465. LocalFree((HLOCAL)ptr);
  466. }
  467. };
  468. bool IsWine() {
  469. HKEY subKey = nullptr;
  470. LONG result = RegOpenKeyEx(HKEY_CURRENT_USER, "Software\\Wine", 0, KEY_READ, &subKey);
  471. if (result == ERROR_SUCCESS) {
  472. return true;
  473. }
  474. result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Wine", 0, KEY_READ, &subKey);
  475. if (result == ERROR_SUCCESS) {
  476. return true;
  477. }
  478. HMODULE hntdll = GetModuleHandle("ntdll.dll");
  479. if (!hntdll) {
  480. return false;
  481. }
  482. auto func = GetProcAddress(hntdll, "wine_get_version");
  483. return func != nullptr;
  484. }
  485. class TWinFileDenyAccessScope {
  486. public:
  487. TWinFileDenyAccessScope(const TFsPath& name, DWORD permissions)
  488. : Name_(name)
  489. , Perms_(permissions)
  490. {
  491. DWORD res = 0;
  492. PACL oldAcl = nullptr;
  493. PSECURITY_DESCRIPTOR sd = nullptr;
  494. res = GetNamedSecurityInfoA((LPSTR)name.c_str(),
  495. SE_FILE_OBJECT,
  496. DACL_SECURITY_INFORMATION,
  497. nullptr,
  498. nullptr,
  499. &oldAcl,
  500. nullptr,
  501. &sd);
  502. SdHolder_.Reset(sd);
  503. if (res != ERROR_SUCCESS) {
  504. ythrow TSystemError(res) << "error in GetNamedSecurityInfoA";
  505. }
  506. Acl_ = SetAcl(oldAcl, DENY_ACCESS);
  507. }
  508. ~TWinFileDenyAccessScope() {
  509. try {
  510. const TFsPath parent = Name_.Parent();
  511. Chmod(parent.c_str(), MODE0777);
  512. Chmod(Name_.c_str(), MODE0777);
  513. SetAcl((PACL)Acl_.Get(), GRANT_ACCESS);
  514. } catch (const yexception& ex) {
  515. Cerr << "~TWinFileDenyAccessScope failed: " << ex.AsStrBuf() << Endl;
  516. }
  517. }
  518. THolder<void, TLocalFree> SetAcl(PACL oldAcl, ACCESS_MODE accessMode) {
  519. DWORD res = 0;
  520. EXPLICIT_ACCESS ea;
  521. PACL newAcl = nullptr;
  522. THolder<void, TLocalFree> newAclHolder;
  523. memset(&ea, 0, sizeof(EXPLICIT_ACCESS));
  524. ea.grfAccessPermissions = Perms_;
  525. ea.grfAccessMode = accessMode;
  526. ea.grfInheritance = NO_INHERITANCE;
  527. ea.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
  528. ea.Trustee.ptstrName = (LPSTR) "CURRENT_USER";
  529. res = SetEntriesInAcl(1, &ea, oldAcl, &newAcl);
  530. newAclHolder.Reset(newAcl);
  531. if (res != ERROR_SUCCESS) {
  532. ythrow TSystemError(res) << "error in SetEntriesInAcl";
  533. }
  534. res = SetNamedSecurityInfoA((LPSTR)Name_.c_str(),
  535. SE_FILE_OBJECT,
  536. DACL_SECURITY_INFORMATION,
  537. nullptr,
  538. nullptr,
  539. newAcl,
  540. nullptr);
  541. if (res != ERROR_SUCCESS) {
  542. ythrow TSystemError(res) << "error in SetNamedSecurityInfoA";
  543. }
  544. return std::move(newAclHolder);
  545. }
  546. private:
  547. const TFsPath Name_;
  548. const DWORD Perms_;
  549. THolder<void, TLocalFree> SdHolder_;
  550. THolder<void, TLocalFree> Acl_;
  551. };
  552. #endif
  553. Y_UNIT_TEST(TestForceDeleteErrorRemove) {
  554. TTempDir tempDir;
  555. const TFsPath testDir = TFsPath(tempDir()).Child("dir");
  556. MakePathIfNotExist(testDir.c_str());
  557. const TFsPath testFile = testDir.Child("file");
  558. {
  559. TFixedBufferFileOutput out(testFile);
  560. out << "data111!!!";
  561. }
  562. #ifdef _win_
  563. Chmod(testFile.c_str(), S_IRUSR);
  564. Y_DEFER {
  565. Chmod(testFile.c_str(), MODE0777);
  566. };
  567. // Checks that dir/file with readonly attribute will be deleted
  568. // on Windows
  569. UNIT_ASSERT_NO_EXCEPTION(testFile.ForceDelete());
  570. #else
  571. Chmod(testDir.c_str(), S_IRUSR | S_IXUSR);
  572. Y_DEFER {
  573. Chmod(testDir.c_str(), MODE0777);
  574. };
  575. UNIT_ASSERT_EXCEPTION_CONTAINS(testFile.ForceDelete(), TIoException,
  576. "failed to delete");
  577. #endif
  578. }
  579. Y_UNIT_TEST(TestForceDeleteErrorRmdir) {
  580. TTempDir tempDir;
  581. const TFsPath testDir = TFsPath(tempDir()).Child("dir");
  582. const TFsPath testSubdir = testDir.Child("file");
  583. MakePathIfNotExist(testSubdir.c_str());
  584. #ifdef _win_
  585. Chmod(testSubdir.c_str(), 0);
  586. Y_DEFER {
  587. Chmod(testSubdir.c_str(), MODE0777);
  588. };
  589. TWinFileDenyAccessScope dirAcl(testDir, FILE_WRITE_DATA);
  590. #else
  591. Chmod(testDir.c_str(), S_IRUSR | S_IXUSR);
  592. Y_DEFER {
  593. Chmod(testDir.c_str(), MODE0777);
  594. };
  595. #endif
  596. UNIT_ASSERT_EXCEPTION_CONTAINS(testSubdir.ForceDelete(), TIoException, "failed to delete");
  597. }
  598. Y_UNIT_TEST(TestForceDeleteErrorStatDir) {
  599. TTempDir tempDir;
  600. const TFsPath testDir = TFsPath(tempDir()).Child("dir");
  601. const TFsPath testSubdir = testDir.Child("file");
  602. MakePathIfNotExist(testSubdir.c_str());
  603. #ifdef _win_
  604. if (IsWine()) {
  605. // FILE_LIST_DIRECTORY seem to be irreversible on wine
  606. return;
  607. }
  608. TWinFileDenyAccessScope subdirAcl(testSubdir, FILE_READ_ATTRIBUTES);
  609. TWinFileDenyAccessScope dirAcl(testDir, FILE_LIST_DIRECTORY);
  610. #else
  611. Chmod(testDir.c_str(), 0);
  612. Y_DEFER {
  613. Chmod(testDir.c_str(), MODE0777);
  614. };
  615. #endif
  616. UNIT_ASSERT_EXCEPTION_CONTAINS(testSubdir.ForceDelete(), TIoException, "failed to stat");
  617. }
  618. Y_UNIT_TEST(TestForceDeleteErrorStatFile) {
  619. TTempDir tempDir;
  620. const TFsPath testDir = TFsPath(tempDir()).Child("dir");
  621. MakePathIfNotExist(testDir.c_str());
  622. const TFsPath testFile = testDir.Child("file");
  623. {
  624. TFixedBufferFileOutput out(testFile);
  625. out << "data111!!!";
  626. }
  627. #ifdef _win_
  628. if (IsWine()) {
  629. // FILE_LIST_DIRECTORY seem to be irreversible on wine
  630. return;
  631. }
  632. TWinFileDenyAccessScope fileAcl(testFile, FILE_READ_ATTRIBUTES);
  633. TWinFileDenyAccessScope dirAcl(testDir, FILE_LIST_DIRECTORY);
  634. #else
  635. Chmod(testDir.c_str(), 0);
  636. Y_DEFER {
  637. Chmod(testDir.c_str(), MODE0777);
  638. };
  639. #endif
  640. UNIT_ASSERT_EXCEPTION_CONTAINS(testFile.ForceDelete(), TIoException, "failed to stat");
  641. }
  642. Y_UNIT_TEST(TestForceDeleteErrorListDir) {
  643. TTempDir tempDir;
  644. const TFsPath testDir = TFsPath(tempDir()).Child("dir");
  645. const TFsPath testSubdir = testDir.Child("file");
  646. MakePathIfNotExist(testSubdir.c_str());
  647. #ifdef _win_
  648. if (IsWine()) {
  649. // FILE_LIST_DIRECTORY seem to be irreversible on wine
  650. return;
  651. }
  652. TWinFileDenyAccessScope subdirAcl(testSubdir, FILE_LIST_DIRECTORY);
  653. #else
  654. Chmod(testSubdir.c_str(), 0);
  655. Y_DEFER {
  656. Chmod(testSubdir.c_str(), MODE0777);
  657. };
  658. #endif
  659. UNIT_ASSERT_EXCEPTION_CONTAINS(testSubdir.ForceDelete(), TIoException, "failed to opendir");
  660. }
  661. #ifdef _unix_
  662. Y_UNIT_TEST(TestForceDeleteErrorSymlink) {
  663. TTempDir tempDir;
  664. const TFsPath testDir = TFsPath(tempDir()).Child("dir");
  665. MakePathIfNotExist(testDir.c_str());
  666. const TFsPath testSymlink = testDir.Child("symlink");
  667. NFs::SymLink("something", testSymlink);
  668. Chmod(testSymlink.c_str(), S_IRUSR);
  669. Chmod(testDir.c_str(), S_IRUSR | S_IXUSR);
  670. Y_DEFER {
  671. Chmod(testDir.c_str(), MODE0777);
  672. Chmod(testSymlink.c_str(), MODE0777);
  673. };
  674. UNIT_ASSERT_EXCEPTION_CONTAINS(testSymlink.ForceDelete(), TIoException, "failed to delete");
  675. }
  676. #endif
  677. Y_UNIT_TEST(TestCopyWithInitializedSplit) {
  678. const TFsPath path1 = TFsPath("some_folder_with_file") / TFsPath("file_in_folder");
  679. path1.PathSplit();
  680. const TFsPath path2 = path1;
  681. const TPathSplit& split2 = path2.PathSplit();
  682. for (const auto& it : split2) {
  683. UNIT_ASSERT(path2.GetPath().begin() <= it.begin());
  684. UNIT_ASSERT(it.end() <= path2.GetPath().end());
  685. }
  686. }
  687. Y_UNIT_TEST(TestAssignmentWithInitializedSplit) {
  688. TFsPath path1 = TFsPath("some_folder_with_file_1") / TFsPath("file_in_folder_1");
  689. TFsPath path2 = TFsPath("some_folder_with_file_2") / TFsPath("file_in_folder_2");
  690. path1.PathSplit();
  691. path1 = path2;
  692. UNIT_ASSERT_VALUES_EQUAL(path1.PathSplit().at(1), "file_in_folder_2");
  693. }
  694. #ifdef TSTRING_IS_STD_STRING
  695. Y_UNIT_TEST(TestCopySplitSSO) {
  696. // Summary length of path must be less minimal SSO length 19 bytes
  697. constexpr TStringBuf A("a");
  698. constexpr TStringBuf B("b");
  699. constexpr TStringBuf C("c");
  700. for (auto constructorType = 0; constructorType < 2; ++constructorType) {
  701. TFsPath path1 = TFsPath(A) / TFsPath(B);
  702. const auto& split1 = path1.PathSplit();
  703. // Check split of path1
  704. UNIT_ASSERT_VALUES_EQUAL(split1.size(), 2);
  705. UNIT_ASSERT_VALUES_EQUAL(split1.at(0), A);
  706. UNIT_ASSERT_VALUES_EQUAL(split1.at(1), B);
  707. TFsPath path2;
  708. if (constructorType == 0) { // copy
  709. path2 = TFsPath(path1); // copy constructor
  710. } else if (constructorType == 1) { // move
  711. path2 = TFsPath(std::move(path1)); // move constructor
  712. }
  713. const auto& split2 = path2.PathSplit();
  714. path1 = TFsPath(C); // invalidate previous Path_ in path1
  715. const auto& newsplit1 = path1.PathSplit();
  716. // Check that split of path1 was overwrited (invalidate previous TStringBuf)
  717. UNIT_ASSERT_VALUES_EQUAL(newsplit1.size(), 1);
  718. UNIT_ASSERT_VALUES_EQUAL(newsplit1.at(0), C);
  719. // Check split of path2 without segfault
  720. UNIT_ASSERT_VALUES_EQUAL(split2.size(), 2);
  721. UNIT_ASSERT_VALUES_EQUAL(split2.at(0), A);
  722. UNIT_ASSERT_VALUES_EQUAL(split2.at(1), B);
  723. }
  724. }
  725. #endif
  726. Y_UNIT_TEST(TestCopySplitNoneSSO) {
  727. // Lenght of directory name must overhead SSO length 19-23 bytes
  728. const TString DIR_A = TString("Dir") + TString(32, 'A');
  729. const TString DIR_B = TString("Dir") + TString(64, 'B');
  730. const TString DIR_C = TString("Dir") + TString(128, 'C');
  731. for (auto constructorType = 0; constructorType < 2; ++constructorType) {
  732. TFsPath path1 = TFsPath(DIR_A) / TFsPath(DIR_B);
  733. auto& split1 = path1.PathSplit();
  734. // Check split of path1
  735. UNIT_ASSERT_VALUES_EQUAL(split1.size(), 2);
  736. UNIT_ASSERT_VALUES_EQUAL(split1.at(0), DIR_A);
  737. UNIT_ASSERT_VALUES_EQUAL(split1.at(1), DIR_B);
  738. TFsPath path2;
  739. if (constructorType == 0) { // copy
  740. path2 = TFsPath(path1); // copy constructor
  741. } else if (constructorType == 1) { // move
  742. path2 = TFsPath(std::move(path1)); // move constructor
  743. }
  744. const auto& split2 = path2.PathSplit();
  745. path1 = TFsPath(DIR_C); // invalidate previous Path_ in path1
  746. const auto& newsplit1 = path1.PathSplit();
  747. // Check that split of path1 was overwrited (invalidate previous TStringBuf)
  748. UNIT_ASSERT_VALUES_EQUAL(newsplit1.size(), 1);
  749. UNIT_ASSERT_VALUES_EQUAL(newsplit1.at(0), DIR_C);
  750. // Check split of path2 without segfault
  751. UNIT_ASSERT_VALUES_EQUAL(split2.size(), 2);
  752. UNIT_ASSERT_VALUES_EQUAL(split2.at(0), DIR_A);
  753. UNIT_ASSERT_VALUES_EQUAL(split2.at(1), DIR_B);
  754. }
  755. }
  756. } // Y_UNIT_TEST_SUITE(TFsPathTests)