diff --git a/src/__tests__/PhutilLibraryTestCase.php b/src/__tests__/PhutilLibraryTestCase.php --- a/src/__tests__/PhutilLibraryTestCase.php +++ b/src/__tests__/PhutilLibraryTestCase.php @@ -24,6 +24,8 @@ * that all the library map is up-to-date. */ public function testLibraryMap() { + $this->assertExecutable('xhpast'); + $root = $this->getLibraryRoot(); $library = phutil_get_library_name_for_root($root); diff --git a/src/channel/__tests__/PhutilPHPObjectProtocolChannelTestCase.php b/src/channel/__tests__/PhutilPHPObjectProtocolChannelTestCase.php --- a/src/channel/__tests__/PhutilPHPObjectProtocolChannelTestCase.php +++ b/src/channel/__tests__/PhutilPHPObjectProtocolChannelTestCase.php @@ -45,7 +45,9 @@ } public function testCloseExecWriteChannel() { - $future = new ExecFuture('cat'); + $bin = $this->getSupportExecutable('cat'); + + $future = new ExecFuture('php -f %R', $bin); // If this test breaks, we want to explode, not hang forever. $future->setTimeout(5); diff --git a/src/filesystem/Filesystem.php b/src/filesystem/Filesystem.php --- a/src/filesystem/Filesystem.php +++ b/src/filesystem/Filesystem.php @@ -898,8 +898,23 @@ // This won't work if the file doesn't exist or is on an unreadable mount // or something crazy like that. Try to resolve a parent so we at least // cover the nonexistent file case. - $parts = explode(DIRECTORY_SEPARATOR, trim($path, DIRECTORY_SEPARATOR)); - while (end($parts) !== false) { + + // We're also normalizing path separators to whatever is normal for the + // environment. + + if (phutil_is_windows()) { + $parts = trim($path, '/\\'); + $parts = preg_split('([/\\\\])', $parts); + + // Normalize the directory separators in the path. If we find a parent + // below, we'll overwrite this with a better resolved path. + $path = str_replace('/', '\\', $path); + } else { + $parts = trim($path, '/'); + $parts = explode('/', $parts); + } + + while ($parts) { array_pop($parts); if (phutil_is_windows()) { $attempt = implode(DIRECTORY_SEPARATOR, $parts); diff --git a/src/filesystem/__tests__/FileFinderTestCase.php b/src/filesystem/__tests__/FileFinderTestCase.php --- a/src/filesystem/__tests__/FileFinderTestCase.php +++ b/src/filesystem/__tests__/FileFinderTestCase.php @@ -125,6 +125,16 @@ } public function testFinderWithGlobMagic() { + if (phutil_is_windows()) { + // We can't write files with "\" since this is the path separator. + // We can't write files with "*" since Windows rejects them. + // This doesn't leave us too many interesting paths to test, so just + // skip this test case under Windows. + $this->assertSkipped( + pht( + 'Windows can not write files with sufficiently absurd names.')); + } + // Fill a temporary directory with all this magic garbage so we don't have // to check a bunch of files with backslashes in their names into version // control. @@ -209,8 +219,12 @@ private function assertFinder($label, FileFinder $finder, $expect) { $modes = array( 'php', - 'shell', ); + + if (!phutil_is_windows()) { + $modes[] = 'shell'; + } + foreach ($modes as $mode) { $actual = id(clone $finder) ->setForceMode($mode) diff --git a/src/filesystem/__tests__/FilesystemTestCase.php b/src/filesystem/__tests__/FilesystemTestCase.php --- a/src/filesystem/__tests__/FilesystemTestCase.php +++ b/src/filesystem/__tests__/FilesystemTestCase.php @@ -111,22 +111,17 @@ array(), ), - 'fictional paths work' => array( - '/x/y/z', - '/', - array( - '/x/y/z', - '/x/y', - '/x', - '/', - ), - ), - ); foreach ($test_cases as $test_case) { list($path, $root, $expected) = $test_case; + // On Windows, paths will have backslashes rather than forward slashes. + // Normalize our expectations to the path format for the environment. + foreach ($expected as $key => $epath) { + $expected[$key] = str_replace('/', DIRECTORY_SEPARATOR, $epath); + } + $this->assertEqual( $expected, Filesystem::walkToRoot($path, $root)); diff --git a/src/filesystem/__tests__/PhutilDeferredLogTestCase.php b/src/filesystem/__tests__/PhutilDeferredLogTestCase.php --- a/src/filesystem/__tests__/PhutilDeferredLogTestCase.php +++ b/src/filesystem/__tests__/PhutilDeferredLogTestCase.php @@ -95,8 +95,7 @@ } public function testManyWriters() { - $root = phutil_get_library_root('arcanist').'/../'; - $bin = $root.'support/unit/deferred_log.php'; + $bin = $this->getSupportExecutable('log'); $n_writers = 3; $n_lines = 8; @@ -105,7 +104,11 @@ $futures = array(); for ($ii = 0; $ii < $n_writers; $ii++) { - $futures[] = new ExecFuture('%s %d %s', $bin, $n_lines, (string)$tmp); + $futures[] = new ExecFuture( + 'php -f %R -- %d %s', + $bin, + $n_lines, + (string)$tmp); } id(new FutureIterator($futures)) diff --git a/src/filesystem/__tests__/PhutilFileLockTestCase.php b/src/filesystem/__tests__/PhutilFileLockTestCase.php --- a/src/filesystem/__tests__/PhutilFileLockTestCase.php +++ b/src/filesystem/__tests__/PhutilFileLockTestCase.php @@ -170,14 +170,20 @@ throw new Exception(pht('Unable to hold lock in external process!')); } - private function buildLockFuture($flags, $file) { - $root = dirname(phutil_get_library_root('arcanist')); - $bin = $root.'/support/unit/lock.php'; + private function buildLockFuture(/* ... */) { + $argv = func_get_args(); + $bin = $this->getSupportExecutable('lock'); + + if (phutil_is_windows()) { + $future = new ExecFuture('php -f %R -- %Ls', $bin, $argv); + } else { + // NOTE: Use `exec` so this passes on Ubuntu, where the default `dash` + // shell will eat any kills we send during the tests. + $future = new ExecFuture('exec php -f %R -- %Ls', $bin, $argv); + } - // NOTE: Use `exec` so this passes on Ubuntu, where the default `dash` shell - // will eat any kills we send during the tests. - $future = new ExecFuture('exec php %s %C %s', $bin, $flags, $file); $future->start(); + return $future; } diff --git a/src/filesystem/linesofalarge/__tests__/LinesOfALargeExecFutureTestCase.php b/src/filesystem/linesofalarge/__tests__/LinesOfALargeExecFutureTestCase.php --- a/src/filesystem/linesofalarge/__tests__/LinesOfALargeExecFutureTestCase.php +++ b/src/filesystem/linesofalarge/__tests__/LinesOfALargeExecFutureTestCase.php @@ -43,7 +43,9 @@ } private function writeAndRead($write, $read) { - $future = new ExecFuture('cat'); + $bin = $this->getSupportExecutable('cat'); + + $future = new ExecFuture('php -f %R', $bin); $future->write($write); $lines = array(); diff --git a/src/future/__tests__/FutureIteratorTestCase.php b/src/future/__tests__/FutureIteratorTestCase.php --- a/src/future/__tests__/FutureIteratorTestCase.php +++ b/src/future/__tests__/FutureIteratorTestCase.php @@ -3,8 +3,10 @@ final class FutureIteratorTestCase extends PhutilTestCase { public function testAddingFuture() { - $future1 = new ExecFuture('cat'); - $future2 = new ExecFuture('cat'); + $bin = $this->getSupportExecutable('cat'); + + $future1 = new ExecFuture('php -f %R', $bin); + $future2 = new ExecFuture('php -f %R', $bin); $iterator = new FutureIterator(array($future1)); $iterator->limit(2); diff --git a/src/future/exec/__tests__/ExecFutureTestCase.php b/src/future/exec/__tests__/ExecFutureTestCase.php --- a/src/future/exec/__tests__/ExecFutureTestCase.php +++ b/src/future/exec/__tests__/ExecFutureTestCase.php @@ -6,15 +6,27 @@ // NOTE: This is mostly testing that we don't hang while doing an empty // write. - list($stdout) = id(new ExecFuture('cat'))->write('')->resolvex(); + list($stdout) = $this->newCat() + ->write('') + ->resolvex(); $this->assertEqual('', $stdout); } + private function newCat() { + $bin = $this->getSupportExecutable('cat'); + return new ExecFuture('php -f %R', $bin); + } + + private function newSleep($duration) { + $bin = $this->getSupportExecutable('sleep'); + return new ExecFuture('php -f %R -- %s', $bin, $duration); + } + public function testKeepPipe() { // NOTE: This is mostly testing the semantics of $keep_pipe in write(). - list($stdout) = id(new ExecFuture('cat')) + list($stdout) = $this->newCat() ->write('', true) ->start() ->write('x', true) @@ -30,14 +42,14 @@ // flushing a buffer. $data = str_repeat('x', 1024 * 1024 * 4); - list($stdout) = id(new ExecFuture('cat'))->write($data)->resolvex(); + list($stdout) = $this->newCat()->write($data)->resolvex(); $this->assertEqual($data, $stdout); } public function testBufferLimit() { $data = str_repeat('x', 1024 * 1024); - list($stdout) = id(new ExecFuture('cat')) + list($stdout) = $this->newCat() ->setStdoutSizeLimit(1024) ->write($data) ->resolvex(); @@ -49,7 +61,7 @@ // NOTE: This tests interactions between the resolve() timeout and the // ExecFuture timeout, which are similar but not identical. - $future = id(new ExecFuture('sleep 32000'))->start(); + $future = $this->newSleep(32000)->start(); $future->setTimeout(32000); // We expect this to return in 0.01s. @@ -66,7 +78,7 @@ public function testTerminateWithoutStart() { // We never start this future, but it should be fine to kill a future from // any state. - $future = new ExecFuture('sleep 1'); + $future = $this->newSleep(1); $future->resolveKill(); $this->assertTrue(true); @@ -76,7 +88,7 @@ // NOTE: This is partly testing that we choose appropriate select wait // times; this test should run for significantly less than 1 second. - $future = new ExecFuture('sleep 32000'); + $future = $this->newSleep(32000); list($err) = $future->setTimeout(0.01)->resolve(); $this->assertTrue($err > 0); @@ -86,7 +98,7 @@ public function testMultipleTimeoutsTestShouldRunLessThan1Sec() { $futures = array(); for ($ii = 0; $ii < 4; $ii++) { - $futures[] = id(new ExecFuture('sleep 32000'))->setTimeout(0.01); + $futures[] = $this->newSleep(32000)->setTimeout(0.01); } foreach (new FutureIterator($futures) as $future) { @@ -100,8 +112,9 @@ public function testMultipleResolves() { // It should be safe to call resolve(), resolvex(), resolveKill(), etc., // as many times as you want on the same process. + $bin = $this->getSupportExecutable('echo'); - $future = new ExecFuture('echo quack'); + $future = new ExecFuture('php -f %R -- quack', $bin); $future->resolve(); $future->resolvex(); list($err) = $future->resolveKill(); @@ -114,7 +127,7 @@ $str_len_4 = 'abcd'; // This is a write/read with no read buffer. - $future = new ExecFuture('cat'); + $future = $this->newCat(); $future->write($str_len_8); do { @@ -131,7 +144,7 @@ // This is a write/read with a read buffer. - $future = new ExecFuture('cat'); + $future = $this->newCat(); $future->write($str_len_8); // Set the read buffer size. diff --git a/src/future/exec/__tests__/ExecPassthruTestCase.php b/src/future/exec/__tests__/ExecPassthruTestCase.php --- a/src/future/exec/__tests__/ExecPassthruTestCase.php +++ b/src/future/exec/__tests__/ExecPassthruTestCase.php @@ -8,7 +8,9 @@ // the terminal, which is undesirable). This makes crafting effective unit // tests a fairly involved process. - $exec = new PhutilExecPassthru('exit'); + $bin = $this->getSupportExecutable('exit'); + + $exec = new PhutilExecPassthru('php -f %R', $bin); $err = $exec->execute(); $this->assertEqual(0, $err); } diff --git a/src/future/oauth/__tests__/PhutilOAuth1FutureTestCase.php b/src/future/oauth/__tests__/PhutilOAuth1FutureTestCase.php --- a/src/future/oauth/__tests__/PhutilOAuth1FutureTestCase.php +++ b/src/future/oauth/__tests__/PhutilOAuth1FutureTestCase.php @@ -63,6 +63,10 @@ } public function testOAuth1SigningWithJIRAExamples() { + if (!function_exists('openssl_pkey_get_private')) { + $this->assertSkipped( + pht('Required "openssl" extension is not installed.')); + } // NOTE: This is an emprically example against JIRA v6.0.6, in that the // code seems to work when actually authing. It primarily serves as a check @@ -156,4 +160,5 @@ md5($future->getSignature())); } + } diff --git a/src/lint/linter/__tests__/ArcanistXHPASTLinterTestCase.php b/src/lint/linter/__tests__/ArcanistXHPASTLinterTestCase.php --- a/src/lint/linter/__tests__/ArcanistXHPASTLinterTestCase.php +++ b/src/lint/linter/__tests__/ArcanistXHPASTLinterTestCase.php @@ -3,6 +3,8 @@ final class ArcanistXHPASTLinterTestCase extends ArcanistLinterTestCase { public function testLinter() { + $this->assertExecutable('xhpast'); + $this->executeTestsInDirectory(dirname(__FILE__).'/xhpast/'); } diff --git a/src/lint/linter/xhpast/rules/__tests__/ArcanistXHPASTLinterRuleTestCase.php b/src/lint/linter/xhpast/rules/__tests__/ArcanistXHPASTLinterRuleTestCase.php --- a/src/lint/linter/xhpast/rules/__tests__/ArcanistXHPASTLinterRuleTestCase.php +++ b/src/lint/linter/xhpast/rules/__tests__/ArcanistXHPASTLinterRuleTestCase.php @@ -29,6 +29,8 @@ * @return ArcanistXHPASTLinterRule */ protected function getLinterRule() { + $this->assertExecutable('xhpast'); + $class = get_class($this); $matches = null; diff --git a/src/markup/engine/__tests__/PhutilRemarkupEngineTestCase.php b/src/markup/engine/__tests__/PhutilRemarkupEngineTestCase.php --- a/src/markup/engine/__tests__/PhutilRemarkupEngineTestCase.php +++ b/src/markup/engine/__tests__/PhutilRemarkupEngineTestCase.php @@ -45,6 +45,12 @@ $engine->setConfig('uri.base', 'http://www.example.com/'); $engine->setConfig('uri.here', 'http://www.example.com/page/'); break; + case 'quoted-code-block.txt': + // These tests depend on the syntax highlighting provided by "xhpast", + // so the output will differ if we're falling back to a different + // syntax highlighter. + $this->assertExecutable('xhpast'); + break; } $actual_output = (string)$engine->markupText($input_remarkup); diff --git a/src/markup/syntax/highlighter/__tests__/PhutilXHPASTSyntaxHighlighterTestCase.php b/src/markup/syntax/highlighter/__tests__/PhutilXHPASTSyntaxHighlighterTestCase.php --- a/src/markup/syntax/highlighter/__tests__/PhutilXHPASTSyntaxHighlighterTestCase.php +++ b/src/markup/syntax/highlighter/__tests__/PhutilXHPASTSyntaxHighlighterTestCase.php @@ -14,6 +14,8 @@ } public function testBuiltinClassnames() { + $this->assertExecutable('xhpast'); + $this->assertEqual( $this->read('builtin-classname.expect'), (string)$this->highlight($this->read('builtin-classname.source')), diff --git a/src/parser/PhutilEditorConfig.php b/src/parser/PhutilEditorConfig.php --- a/src/parser/PhutilEditorConfig.php +++ b/src/parser/PhutilEditorConfig.php @@ -107,9 +107,16 @@ $configs = $this->getEditorConfigs($path); $matches = array(); + // Normalize directory separators to "/". The ".editorconfig" standard + // uses only "/" as a directory separator, not "\". + $path = str_replace(DIRECTORY_SEPARATOR, '/', $path); + foreach ($configs as $config) { list($path_prefix, $editorconfig) = $config; + // Normalize path separators, as above. + $path_prefix = str_replace(DIRECTORY_SEPARATOR, '/', $path_prefix); + foreach ($editorconfig as $glob => $properties) { if (!$glob) { continue; @@ -163,12 +170,11 @@ * return list> */ private function getEditorConfigs($path) { - $configs = array(); - $found_root = false; - $root = $this->root; + $configs = array(); - do { - $path = dirname($path); + $found_root = false; + $paths = Filesystem::walkToRoot($path, $this->root); + foreach ($paths as $path) { $file = $path.'/.editorconfig'; if (!Filesystem::pathExists($file)) { @@ -187,7 +193,7 @@ if ($found_root) { break; } - } while ($path != $root && Filesystem::isDescendant($path, $root)); + } return $configs; } diff --git a/src/parser/__tests__/ArcanistBundleTestCase.php b/src/parser/__tests__/ArcanistBundleTestCase.php --- a/src/parser/__tests__/ArcanistBundleTestCase.php +++ b/src/parser/__tests__/ArcanistBundleTestCase.php @@ -11,6 +11,8 @@ } private function loadDiff($old, $new) { + $this->assertExecutable('diff'); + list($err, $stdout) = exec_manual( 'diff --unified=65535 --label %s --label %s -- %s %s', 'file 9999-99-99', diff --git a/src/parser/xhpast/api/__tests__/XHPASTNodeTestCase.php b/src/parser/xhpast/api/__tests__/XHPASTNodeTestCase.php --- a/src/parser/xhpast/api/__tests__/XHPASTNodeTestCase.php +++ b/src/parser/xhpast/api/__tests__/XHPASTNodeTestCase.php @@ -3,6 +3,8 @@ final class XHPASTNodeTestCase extends PhutilTestCase { public function testGetStringVariables() { + $this->assertExecutable('xhpast'); + $this->assertStringVariables(array(), '""'); $this->assertStringVariables(array(2 => 'abc'), '"$abc"'); $this->assertStringVariables(array(), '"\$abc"'); @@ -20,6 +22,8 @@ } private function assertStringVariables($expected, $string) { + $this->assertExecutable('xhpast'); + $statement = XHPASTTree::newStatementFromString($string); $this->assertEqual( $expected, @@ -28,6 +32,8 @@ } public function testGetNamespace() { + $this->assertExecutable('xhpast'); + $dir = dirname(__FILE__).'/namespace/'; $files = id(new FileFinder($dir)) ->withType('f') diff --git a/src/parser/xhpast/api/__tests__/XHPASTTreeTestCase.php b/src/parser/xhpast/api/__tests__/XHPASTTreeTestCase.php --- a/src/parser/xhpast/api/__tests__/XHPASTTreeTestCase.php +++ b/src/parser/xhpast/api/__tests__/XHPASTTreeTestCase.php @@ -6,6 +6,8 @@ final class XHPASTTreeTestCase extends PhutilTestCase { public function testEvalStaticString() { + $this->assertExecutable('xhpast'); + $this->assertEval(1, '1'); $this->assertEval('a', '"a"'); $this->assertEval(-1.1, '-1.1'); diff --git a/src/parser/xhpast/bin/PhutilXHPASTBinary.php b/src/parser/xhpast/bin/PhutilXHPASTBinary.php --- a/src/parser/xhpast/bin/PhutilXHPASTBinary.php +++ b/src/parser/xhpast/bin/PhutilXHPASTBinary.php @@ -32,7 +32,7 @@ $command = 'make'; } - $root = phutil_get_library_root('phutil'); + $root = phutil_get_library_root('arcanist'); $path = Filesystem::resolvePath($root.'/../support/xhpast'); // Run the build. diff --git a/src/phage/__tests__/PhageAgentTestCase.php b/src/phage/__tests__/PhageAgentTestCase.php --- a/src/phage/__tests__/PhageAgentTestCase.php +++ b/src/phage/__tests__/PhageAgentTestCase.php @@ -3,6 +3,10 @@ final class PhageAgentTestCase extends PhutilTestCase { public function testPhagePHPAgent() { + if (phutil_is_windows()) { + $this->assertSkipped(pht('Phage does not target Windows.')); + } + return $this->runBootloaderTests(new PhagePHPAgentBootloader()); } diff --git a/src/repository/api/__tests__/ArcanistRepositoryAPIStateTestCase.php b/src/repository/api/__tests__/ArcanistRepositoryAPIStateTestCase.php --- a/src/repository/api/__tests__/ArcanistRepositoryAPIStateTestCase.php +++ b/src/repository/api/__tests__/ArcanistRepositoryAPIStateTestCase.php @@ -3,33 +3,29 @@ final class ArcanistRepositoryAPIStateTestCase extends PhutilTestCase { public function testGitStateParsing() { - if (Filesystem::binaryExists('git')) { - $this->parseState('git_basic.git.tgz'); - $this->parseState('git_submodules_dirty.git.tgz'); - $this->parseState('git_submodules_staged.git.tgz'); - $this->parseState('git_spaces.git.tgz'); - } else { - $this->assertSkipped(pht('Git is not installed')); - } + $this->assertExecutable('git'); + + $this->parseState('git_basic.git.tgz'); + $this->parseState('git_submodules_dirty.git.tgz'); + $this->parseState('git_submodules_staged.git.tgz'); + $this->parseState('git_spaces.git.tgz'); } public function testHgStateParsing() { - if (Filesystem::binaryExists('hg')) { - $this->parseState('hg_basic.hg.tgz'); - } else { - $this->assertSkipped(pht('Mercurial is not installed')); - } + $this->assertExecutable('hg'); + + $this->parseState('hg_basic.hg.tgz'); } public function testSvnStateParsing() { - if (Filesystem::binaryExists('svn')) { - $this->parseState('svn_basic.svn.tgz'); - } else { - $this->assertSkipped(pht('Subversion is not installed')); - } + $this->assertExecutable('svn'); + + $this->parseState('svn_basic.svn.tgz'); } private function parseState($test) { + $this->assertExecutable('tar'); + $dir = dirname(__FILE__).'/state/'; $fixture = PhutilDirectoryFixture::newFromArchive($dir.'/'.$test); diff --git a/src/unit/engine/phutil/PhutilTestCase.php b/src/unit/engine/phutil/PhutilTestCase.php --- a/src/unit/engine/phutil/PhutilTestCase.php +++ b/src/unit/engine/phutil/PhutilTestCase.php @@ -20,6 +20,8 @@ private $paths; private $renderer; + private static $executables = array(); + /* -( Making Test Assertions )--------------------------------------------- */ @@ -110,15 +112,24 @@ $output .= "\n"; + static $have_diff; + if ($have_diff === null) { + $have_diff = Filesystem::binaryExists('diff'); + } + if (strpos($expect, "\n") === false && strpos($result, "\n") === false) { $output .= pht("Expected: %s\n Actual: %s", $expect, $result); - } else { + } else if ($have_diff) { $output .= pht( "Expected vs Actual Output Diff\n%s", ArcanistDiffUtils::renderDifferences( $expect, $result, $lines = 0xFFFF)); + } else { + // On systems without `diff`, including Windows, just show the raw + // values instead of using `diff` to compare them. + $output .= "EXPECTED\n{$expect}\n\nACTUAL\n{$result}\n"; } $this->failTest($output); @@ -751,4 +762,37 @@ throw new PhutilTestTerminatedException($output); } + final protected function assertExecutable($binary) { + if (!isset(self::$executables[$binary])) { + switch ($binary) { + case 'xhpast': + $ok = true; + if (!PhutilXHPASTBinary::isAvailable()) { + try { + PhutilXHPASTBinary::build(); + } catch (Exception $ex) { + $ok = false; + } + } + break; + default: + $ok = Filesystem::binaryExists($binary); + break; + } + + self::$executables[$binary] = $ok; + } + + if (!self::$executables[$binary]) { + $this->assertSkipped( + pht('Required executable "%s" is not available.', $binary)); + } + } + + final protected function getSupportExecutable($executable) { + $root = dirname(phutil_get_library_root('arcanist')); + return $root.'/support/unit/'.$executable.'.php'; + } + + } diff --git a/src/xsprintf/__tests__/PhutilCsprintfTestCase.php b/src/xsprintf/__tests__/PhutilCsprintfTestCase.php --- a/src/xsprintf/__tests__/PhutilCsprintfTestCase.php +++ b/src/xsprintf/__tests__/PhutilCsprintfTestCase.php @@ -39,25 +39,33 @@ } public function testNoPowershell() { - if (!phutil_is_windows()) { - $cmd = csprintf('%s', '#'); - $cmd->setEscapingMode(PhutilCommandString::MODE_DEFAULT); - - $this->assertEqual( - '\'#\'', - (string)$cmd); + if (phutil_is_windows()) { + // TOOLSETS: Restructure this. We must skip because tests fail if they + // do not make any assertions. + $this->assertSkipped( + pht( + 'This test can not currently run under Windows.')); } + + $cmd = csprintf('%s', '#'); + $cmd->setEscapingMode(PhutilCommandString::MODE_DEFAULT); + + $this->assertEqual( + '\'#\'', + (string)$cmd); } public function testPasswords() { + $bin = $this->getSupportExecutable('echo'); + // Normal "%s" doesn't do anything special. - $command = csprintf('echo %s', 'hunter2trustno1'); + $command = csprintf('php -f %R -- %s', $bin, 'hunter2trustno1'); $this->assertTrue(strpos($command, 'hunter2trustno1') !== false); // "%P" takes a PhutilOpaqueEnvelope. $caught = null; try { - csprintf('echo %P', 'hunter2trustno1'); + csprintf('php -f %R -- %P', $bin, 'hunter2trustno1'); } catch (Exception $ex) { $caught = $ex; } @@ -65,7 +73,10 @@ // "%P" masks the provided value. - $command = csprintf('echo %P', new PhutilOpaqueEnvelope('hunter2trustno1')); + $command = csprintf( + 'php -f %R -- %P', + $bin, + new PhutilOpaqueEnvelope('hunter2trustno1')); $this->assertFalse(strpos($command, 'hunter2trustno1')); diff --git a/support/unit/cat.php b/support/unit/cat.php new file mode 100755 --- /dev/null +++ b/support/unit/cat.php @@ -0,0 +1 @@ + $arg) { + $args[$key] = addcslashes($arg, "\\\n"); +} +$args = implode($args, "\n"); +echo $args; diff --git a/support/unit/exit.php b/support/unit/exit.php new file mode 100755 --- /dev/null +++ b/support/unit/exit.php @@ -0,0 +1 @@ +\n"; + exit(1); +} + +// NOTE: Sleep for the requested duration even if our actual sleep() call is +// interrupted by a signal. + +$then = microtime(true) + (double)$argv[1]; +while (true) { + $now = microtime(true); + if ($now >= $then) { + break; + } + + $sleep = max(1, ($then - $now)); + usleep((int)($sleep * 1000000)); +}