diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -48,8 +48,9 @@ 'FileFinder' => 'filesystem/FileFinder.php', 'FileFinderTestCase' => 'filesystem/__tests__/FileFinderTestCase.php', 'FileList' => 'filesystem/FileList.php', + 'FileListTestCase' => 'filesystem/__tests__/FileListTestCase.php', 'Filesystem' => 'filesystem/Filesystem.php', - 'FilesystemException' => 'filesystem/FilesystemException.php', + 'FilesystemException' => 'filesystem/exception/FilesystemException.php', 'FilesystemTestCase' => 'filesystem/__tests__/FilesystemTestCase.php', 'Future' => 'future/Future.php', 'FutureIterator' => 'future/FutureIterator.php', @@ -146,6 +147,7 @@ 'PhutilDeferredLogTestCase' => 'filesystem/__tests__/PhutilDeferredLogTestCase.php', 'PhutilDirectedScalarGraph' => 'utils/PhutilDirectedScalarGraph.php', 'PhutilDirectoryFixture' => 'filesystem/PhutilDirectoryFixture.php', + 'PhutilDirectoryFixtureTestCase' => 'filesystem/__tests__/PhutilDirectoryFixtureTestCase.php', 'PhutilDirectoryKeyValueCache' => 'cache/PhutilDirectoryKeyValueCache.php', 'PhutilDisqusAuthAdapter' => 'auth/PhutilDisqusAuthAdapter.php', 'PhutilDivinerSyntaxHighlighter' => 'markup/syntax/highlighter/PhutilDivinerSyntaxHighlighter.php', @@ -177,6 +179,7 @@ 'PhutilFileLock' => 'filesystem/PhutilFileLock.php', 'PhutilFileLockTestCase' => 'filesystem/__tests__/PhutilFileLockTestCase.php', 'PhutilFileTree' => 'filesystem/PhutilFileTree.php', + 'PhutilFileTreeTestCase' => 'filesystem/__tests__/PhutilFileTreeTestCase.php', 'PhutilGitHubAuthAdapter' => 'auth/PhutilGitHubAuthAdapter.php', 'PhutilGitURI' => 'parser/PhutilGitURI.php', 'PhutilGitURITestCase' => 'parser/__tests__/PhutilGitURITestCase.php', @@ -221,7 +224,7 @@ 'PhutilLipsumContextFreeGrammar' => 'grammar/PhutilLipsumContextFreeGrammar.php', 'PhutilLocale' => 'internationalization/PhutilLocale.php', 'PhutilLock' => 'filesystem/PhutilLock.php', - 'PhutilLockException' => 'filesystem/PhutilLockException.php', + 'PhutilLockException' => 'filesystem/exception/PhutilLockException.php', 'PhutilLogFileChannel' => 'channel/PhutilLogFileChannel.php', 'PhutilLunarPhase' => 'utils/PhutilLunarPhase.php', 'PhutilLunarPhaseTestCase' => 'utils/__tests__/PhutilLunarPhaseTestCase.php', @@ -357,6 +360,7 @@ 'PhutilXHPASTSyntaxHighlighterTestCase' => 'markup/syntax/highlighter/__tests__/PhutilXHPASTSyntaxHighlighterTestCase.php', 'QueryFuture' => 'future/query/QueryFuture.php', 'TempFile' => 'filesystem/TempFile.php', + 'TempFileTestCase' => 'filesystem/__tests__/TempFileTestCase.php', 'TestAbstractDirectedGraph' => 'utils/__tests__/TestAbstractDirectedGraph.php', 'XHPASTNode' => 'parser/xhpast/api/XHPASTNode.php', 'XHPASTNodeTestCase' => 'parser/xhpast/api/__tests__/XHPASTNodeTestCase.php', @@ -517,6 +521,7 @@ 'ExecFutureTestCase' => 'PhutilTestCase', 'ExecPassthruTestCase' => 'PhutilTestCase', 'FileFinderTestCase' => 'PhutilTestCase', + 'FileListTestCase' => 'PhutilTestCase', 'FilesystemException' => 'Exception', 'FilesystemTestCase' => 'PhutilTestCase', 'FutureIterator' => 'Iterator', @@ -595,6 +600,7 @@ 'PhutilDefaultSyntaxHighlighterEngineTestCase' => 'PhutilTestCase', 'PhutilDeferredLogTestCase' => 'PhutilTestCase', 'PhutilDirectedScalarGraph' => 'AbstractDirectedGraph', + 'PhutilDirectoryFixtureTestCase' => 'PhutilTestCase', 'PhutilDirectoryKeyValueCache' => 'PhutilKeyValueCache', 'PhutilDisqusAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilDocblockParserTestCase' => 'PhutilTestCase', @@ -614,6 +620,7 @@ 'PhutilFatalDaemon' => 'PhutilTortureTestDaemon', 'PhutilFileLock' => 'PhutilLock', 'PhutilFileLockTestCase' => 'PhutilTestCase', + 'PhutilFileTreeTestCase' => 'PhutilTestCase', 'PhutilGitHubAuthAdapter' => 'PhutilOAuthAuthAdapter', 'PhutilGitURITestCase' => 'PhutilTestCase', 'PhutilGoogleAuthAdapter' => 'PhutilOAuthAuthAdapter', @@ -753,6 +760,7 @@ 'PhutilXHPASTSyntaxHighlighterFuture' => 'FutureProxy', 'PhutilXHPASTSyntaxHighlighterTestCase' => 'PhutilTestCase', 'QueryFuture' => 'Future', + 'TempFileTestCase' => 'PhutilTestCase', 'TestAbstractDirectedGraph' => 'AbstractDirectedGraph', 'XHPASTNode' => 'AASTNode', 'XHPASTNodeTestCase' => 'PhutilTestCase', diff --git a/src/filesystem/Filesystem.php b/src/filesystem/Filesystem.php --- a/src/filesystem/Filesystem.php +++ b/src/filesystem/Filesystem.php @@ -731,38 +731,42 @@ /** - * Return all directories between a path and "/". Iterating over them walks - * from the path to the root. + * Return all directories between a path and the specified root directory + * (defaulting to "/"). Iterating over them walks from the path to the root. * - * @param string Path, absolute or relative to PWD. - * @return list List of parent paths, including the provided path. + * @param string Path, absolute or relative to PWD. + * @param string The root directory. + * @return list List of parent paths, including the provided path. * @task directory */ - public static function walkToRoot($path) { + public static function walkToRoot($path, $root = '/') { $path = self::resolvePath($path); + $root = self::resolvePath($root); if (is_link($path)) { $path = realpath($path); } - $walk = array(); - $parts = explode(DIRECTORY_SEPARATOR, $path); - foreach ($parts as $k => $part) { - if (!strlen($part)) { - unset($parts[$k]); - } + if (is_link($root)) { + $root = realpath($root); } - do { + + $walk = array(); + $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path)); + + while ($parts && implode(DIRECTORY_SEPARATOR, $parts) != $root) { if (phutil_is_windows()) { $walk[] = implode(DIRECTORY_SEPARATOR, $parts); } else { $walk[] = DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $parts); } - if (empty($parts)) { - break; - } + array_pop($parts); - } while (true); + } + + if ($root == '/') { + $walk[] = $root; + } return $walk; } @@ -851,7 +855,6 @@ * @task path */ public static function isDescendant($path, $root) { - try { self::assertExists($path); self::assertExists($root); diff --git a/src/filesystem/__tests__/FileListTestCase.php b/src/filesystem/__tests__/FileListTestCase.php new file mode 100644 --- /dev/null +++ b/src/filesystem/__tests__/FileListTestCase.php @@ -0,0 +1,3 @@ +assertEqual( + 'Test file.', + Filesystem::readFile(dirname(__FILE__).'/data/test')); + + $caught = null; + try { + Filesystem::readFile(dirname(__FILE__).'/data/nofile'); + } catch (Exception $ex) { + $caught = $ex; + } + $this->assertTrue($caught instanceof FilesystemException); + } + + public function testWriteFile() {} + + public function testWriteFileIfChanged() {} + + public function testWriteUniqueFile() { + $tmp = new TempFile(); + $dir = dirname($tmp); + + // Writing an empty file should work. + $f = Filesystem::writeUniqueFile($dir, ''); + $this->assertEqual('', Filesystem::readFile($f)); + + // File name should be unique. + $g = Filesystem::writeUniqueFile($dir, 'quack'); + $this->assertTrue($f != $g); + } + + public function testAppendFile() {} + + public function testRemove() {} + + public function testRename() {} + + public function testChangePermissions() {} + + public function testGetModifiedTime() {} + + public function testReadRandomBytes() { + $number_of_bytes = 1024; + $data = Filesystem::readRandomBytes($number_of_bytes); + $this->assertTrue(strlen($data) == $number_of_bytes); + + $data1 = Filesystem::readRandomBytes(128); + $data2 = Filesystem::readRandomBytes(128); + $this->assertFalse($data1 == $data2); + + $caught = null; + try { + Filesystem::readRandomBytes(0); + } catch (Exception $ex) { + $caught = $ex; + } + $this->assertTrue($caught instanceof Exception); + } + + public function readRandomCharacters() {} + + public function testGetMimeType() {} + + public function testCreateDirectory() {} + + public function testCreateTemporaryDirectory() {} + + public function testListDirectory() {} + + public function testWalkToRoot() { + $test_cases = array( + array( + '/foo/bar/baz', + '/', + array( + '/foo/bar/baz', + '/foo/bar', + '/foo', + '/', + ), + ), + array( + '/foo/bar/baz/', + '/', + array( + '/foo/bar/baz', + '/foo/bar', + '/foo', + '/', + ), + ), + + array( + '/foo/bar/baz', + '/foo', + array( + '/foo/bar/baz', + '/foo/bar', + '/foo', + ), + ), + ); + + foreach ($test_cases as $test_case) { + list($path, $root, $expected) = $test_case; + + $this->assertEqual( + $expected, + Filesystem::walkToRoot($path, $root)); + } + } + + public function testIsAbsolutePath() {} + + public function testResolvePath() {} + + public function testisDescendant() { + $test_cases = array( + array( + __FILE__, + dirname(__FILE__), + true, + ), + array( + dirname(__FILE__), + dirname(dirname(__FILE__)), + true, + ), + array( + dirname(__FILE__), + phutil_get_library_root_for_path(__FILE__), + true, + ), + array( + dirname(dirname(__FILE__)), + dirname(__FILE__), + false, + ), + array( + dirname(__FILE__).'/quack', + dirname(__FILE__), + false, + ), + ); + + foreach ($test_cases as $test_case) { + list($path, $root, $expected) = $test_case; + + $this->assertEqual( + $expected, + Filesystem::isDescendant($path, $root), + sprintf( + 'Filesystem::isDescendant(%s, %s)', + phutil_var_export($path), + phutil_var_export($root))); + } + } + + public function testReadablePath() {} + + public function testPathExists() {} + public function testBinaryExists() { // Test for the `which` binary on Linux, and the `where` binary on Windows, // because `which which` is cute. @@ -42,35 +204,18 @@ Filesystem::resolveBinary('halting-problem-decider')); } - public function testWriteUniqueFile() { - $tmp = new TempFile(); - $dir = dirname($tmp); + public function testPathsAreEquivalent() {} - // Writing an empty file should work. - $f = Filesystem::writeUniqueFile($dir, ''); - $this->assertEqual('', Filesystem::readFile($f)); + public function testAssertExists() {} - // File name should be unique. - $g = Filesystem::writeUniqueFile($dir, 'quack'); - $this->assertTrue($f != $g); - } + public function testAssertNotExists() {} - public function testReadRandomBytes() { - $number_of_bytes = 1024; - $data = Filesystem::readRandomBytes($number_of_bytes); - $this->assertTrue(strlen($data) == $number_of_bytes); + public function testAssertIsFile() {} - $data1 = Filesystem::readRandomBytes(128); - $data2 = Filesystem::readRandomBytes(128); - $this->assertFalse($data1 == $data2); + public function testAssertIsDirectory() {} - $caught = null; - try { - Filesystem::readRandomBytes(0); - } catch (Exception $ex) { - $caught = $ex; - } - $this->assertTrue($caught instanceof Exception); - } + public function testAssertWritable() {} + + public function testAssertReadable() {} } diff --git a/src/filesystem/__tests__/PhutilDirectoryFixtureTestCase.php b/src/filesystem/__tests__/PhutilDirectoryFixtureTestCase.php new file mode 100644 --- /dev/null +++ b/src/filesystem/__tests__/PhutilDirectoryFixtureTestCase.php @@ -0,0 +1,3 @@ +