1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2025-01-30 16:38:20 +01:00

[Wilds] Pass or skip all remaining Windows unit test failures

Summary:
Ref T13209. This gives us a clean suite under Windows. The actual changes are a lot of miscellaneous stuff which I'll walk through inline in more detail.

The biggest change here is just rewriting some stuff like `cat`, `echo`, `sleep`, etc., in PHP. These commands either don't exist, don't work the same way, or are shell builtins (and we're now bypassing the shell) under Windows. So replace `cat ...` with `php -f cat.php -- ...` to make the tests portable.

Test Plan: No remaining test failures on Windows.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13209

Differential Revision: https://secure.phabricator.com/D19729
This commit is contained in:
epriestley 2018-10-02 10:47:29 -07:00
parent fa27ad761a
commit 79d3692f66
30 changed files with 249 additions and 75 deletions

View file

@ -24,6 +24,8 @@ class PhutilLibraryTestCase extends PhutilTestCase {
* that all the library map is up-to-date. * that all the library map is up-to-date.
*/ */
public function testLibraryMap() { public function testLibraryMap() {
$this->assertExecutable('xhpast');
$root = $this->getLibraryRoot(); $root = $this->getLibraryRoot();
$library = phutil_get_library_name_for_root($root); $library = phutil_get_library_name_for_root($root);

View file

@ -45,7 +45,9 @@ final class PhutilPHPObjectProtocolChannelTestCase extends PhutilTestCase {
} }
public function testCloseExecWriteChannel() { 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. // If this test breaks, we want to explode, not hang forever.
$future->setTimeout(5); $future->setTimeout(5);

View file

@ -898,8 +898,23 @@ final class Filesystem extends Phobject {
// This won't work if the file doesn't exist or is on an unreadable mount // 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 // or something crazy like that. Try to resolve a parent so we at least
// cover the nonexistent file case. // 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); array_pop($parts);
if (phutil_is_windows()) { if (phutil_is_windows()) {
$attempt = implode(DIRECTORY_SEPARATOR, $parts); $attempt = implode(DIRECTORY_SEPARATOR, $parts);

View file

@ -125,6 +125,16 @@ final class FileFinderTestCase extends PhutilTestCase {
} }
public function testFinderWithGlobMagic() { 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 // 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 // to check a bunch of files with backslashes in their names into version
// control. // control.
@ -209,8 +219,12 @@ final class FileFinderTestCase extends PhutilTestCase {
private function assertFinder($label, FileFinder $finder, $expect) { private function assertFinder($label, FileFinder $finder, $expect) {
$modes = array( $modes = array(
'php', 'php',
'shell',
); );
if (!phutil_is_windows()) {
$modes[] = 'shell';
}
foreach ($modes as $mode) { foreach ($modes as $mode) {
$actual = id(clone $finder) $actual = id(clone $finder)
->setForceMode($mode) ->setForceMode($mode)

View file

@ -111,22 +111,17 @@ final class FilesystemTestCase extends PhutilTestCase {
array(), array(),
), ),
'fictional paths work' => array(
'/x/y/z',
'/',
array(
'/x/y/z',
'/x/y',
'/x',
'/',
),
),
); );
foreach ($test_cases as $test_case) { foreach ($test_cases as $test_case) {
list($path, $root, $expected) = $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( $this->assertEqual(
$expected, $expected,
Filesystem::walkToRoot($path, $root)); Filesystem::walkToRoot($path, $root));

View file

@ -95,8 +95,7 @@ final class PhutilDeferredLogTestCase extends PhutilTestCase {
} }
public function testManyWriters() { public function testManyWriters() {
$root = phutil_get_library_root('arcanist').'/../'; $bin = $this->getSupportExecutable('log');
$bin = $root.'support/unit/deferred_log.php';
$n_writers = 3; $n_writers = 3;
$n_lines = 8; $n_lines = 8;
@ -105,7 +104,11 @@ final class PhutilDeferredLogTestCase extends PhutilTestCase {
$futures = array(); $futures = array();
for ($ii = 0; $ii < $n_writers; $ii++) { 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)) id(new FutureIterator($futures))

View file

@ -170,14 +170,20 @@ final class PhutilFileLockTestCase extends PhutilTestCase {
throw new Exception(pht('Unable to hold lock in external process!')); throw new Exception(pht('Unable to hold lock in external process!'));
} }
private function buildLockFuture($flags, $file) { private function buildLockFuture(/* ... */) {
$root = dirname(phutil_get_library_root('arcanist')); $argv = func_get_args();
$bin = $root.'/support/unit/lock.php'; $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(); $future->start();
return $future; return $future;
} }

View file

@ -43,7 +43,9 @@ final class LinesOfALargeExecFutureTestCase extends PhutilTestCase {
} }
private function writeAndRead($write, $read) { private function writeAndRead($write, $read) {
$future = new ExecFuture('cat'); $bin = $this->getSupportExecutable('cat');
$future = new ExecFuture('php -f %R', $bin);
$future->write($write); $future->write($write);
$lines = array(); $lines = array();

View file

@ -3,8 +3,10 @@
final class FutureIteratorTestCase extends PhutilTestCase { final class FutureIteratorTestCase extends PhutilTestCase {
public function testAddingFuture() { public function testAddingFuture() {
$future1 = new ExecFuture('cat'); $bin = $this->getSupportExecutable('cat');
$future2 = new ExecFuture('cat');
$future1 = new ExecFuture('php -f %R', $bin);
$future2 = new ExecFuture('php -f %R', $bin);
$iterator = new FutureIterator(array($future1)); $iterator = new FutureIterator(array($future1));
$iterator->limit(2); $iterator->limit(2);

View file

@ -6,15 +6,27 @@ final class ExecFutureTestCase extends PhutilTestCase {
// NOTE: This is mostly testing that we don't hang while doing an empty // NOTE: This is mostly testing that we don't hang while doing an empty
// write. // write.
list($stdout) = id(new ExecFuture('cat'))->write('')->resolvex(); list($stdout) = $this->newCat()
->write('')
->resolvex();
$this->assertEqual('', $stdout); $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() { public function testKeepPipe() {
// NOTE: This is mostly testing the semantics of $keep_pipe in write(). // NOTE: This is mostly testing the semantics of $keep_pipe in write().
list($stdout) = id(new ExecFuture('cat')) list($stdout) = $this->newCat()
->write('', true) ->write('', true)
->start() ->start()
->write('x', true) ->write('x', true)
@ -30,14 +42,14 @@ final class ExecFutureTestCase extends PhutilTestCase {
// flushing a buffer. // flushing a buffer.
$data = str_repeat('x', 1024 * 1024 * 4); $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); $this->assertEqual($data, $stdout);
} }
public function testBufferLimit() { public function testBufferLimit() {
$data = str_repeat('x', 1024 * 1024); $data = str_repeat('x', 1024 * 1024);
list($stdout) = id(new ExecFuture('cat')) list($stdout) = $this->newCat()
->setStdoutSizeLimit(1024) ->setStdoutSizeLimit(1024)
->write($data) ->write($data)
->resolvex(); ->resolvex();
@ -49,7 +61,7 @@ final class ExecFutureTestCase extends PhutilTestCase {
// NOTE: This tests interactions between the resolve() timeout and the // NOTE: This tests interactions between the resolve() timeout and the
// ExecFuture timeout, which are similar but not identical. // ExecFuture timeout, which are similar but not identical.
$future = id(new ExecFuture('sleep 32000'))->start(); $future = $this->newSleep(32000)->start();
$future->setTimeout(32000); $future->setTimeout(32000);
// We expect this to return in 0.01s. // We expect this to return in 0.01s.
@ -66,7 +78,7 @@ final class ExecFutureTestCase extends PhutilTestCase {
public function testTerminateWithoutStart() { public function testTerminateWithoutStart() {
// We never start this future, but it should be fine to kill a future from // We never start this future, but it should be fine to kill a future from
// any state. // any state.
$future = new ExecFuture('sleep 1'); $future = $this->newSleep(1);
$future->resolveKill(); $future->resolveKill();
$this->assertTrue(true); $this->assertTrue(true);
@ -76,7 +88,7 @@ final class ExecFutureTestCase extends PhutilTestCase {
// NOTE: This is partly testing that we choose appropriate select wait // NOTE: This is partly testing that we choose appropriate select wait
// times; this test should run for significantly less than 1 second. // 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(); list($err) = $future->setTimeout(0.01)->resolve();
$this->assertTrue($err > 0); $this->assertTrue($err > 0);
@ -86,7 +98,7 @@ final class ExecFutureTestCase extends PhutilTestCase {
public function testMultipleTimeoutsTestShouldRunLessThan1Sec() { public function testMultipleTimeoutsTestShouldRunLessThan1Sec() {
$futures = array(); $futures = array();
for ($ii = 0; $ii < 4; $ii++) { 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) { foreach (new FutureIterator($futures) as $future) {
@ -100,8 +112,9 @@ final class ExecFutureTestCase extends PhutilTestCase {
public function testMultipleResolves() { public function testMultipleResolves() {
// It should be safe to call resolve(), resolvex(), resolveKill(), etc., // It should be safe to call resolve(), resolvex(), resolveKill(), etc.,
// as many times as you want on the same process. // 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->resolve();
$future->resolvex(); $future->resolvex();
list($err) = $future->resolveKill(); list($err) = $future->resolveKill();
@ -114,7 +127,7 @@ final class ExecFutureTestCase extends PhutilTestCase {
$str_len_4 = 'abcd'; $str_len_4 = 'abcd';
// This is a write/read with no read buffer. // This is a write/read with no read buffer.
$future = new ExecFuture('cat'); $future = $this->newCat();
$future->write($str_len_8); $future->write($str_len_8);
do { do {
@ -131,7 +144,7 @@ final class ExecFutureTestCase extends PhutilTestCase {
// This is a write/read with a read buffer. // This is a write/read with a read buffer.
$future = new ExecFuture('cat'); $future = $this->newCat();
$future->write($str_len_8); $future->write($str_len_8);
// Set the read buffer size. // Set the read buffer size.

View file

@ -8,7 +8,9 @@ final class ExecPassthruTestCase extends PhutilTestCase {
// the terminal, which is undesirable). This makes crafting effective unit // the terminal, which is undesirable). This makes crafting effective unit
// tests a fairly involved process. // tests a fairly involved process.
$exec = new PhutilExecPassthru('exit'); $bin = $this->getSupportExecutable('exit');
$exec = new PhutilExecPassthru('php -f %R', $bin);
$err = $exec->execute(); $err = $exec->execute();
$this->assertEqual(0, $err); $this->assertEqual(0, $err);
} }

View file

@ -63,6 +63,10 @@ final class PhutilOAuth1FutureTestCase extends PhutilTestCase {
} }
public function testOAuth1SigningWithJIRAExamples() { 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 // 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 // code seems to work when actually authing. It primarily serves as a check
@ -156,4 +160,5 @@ EOKEY;
md5($future->getSignature())); md5($future->getSignature()));
} }
} }

View file

@ -3,6 +3,8 @@
final class ArcanistXHPASTLinterTestCase extends ArcanistLinterTestCase { final class ArcanistXHPASTLinterTestCase extends ArcanistLinterTestCase {
public function testLinter() { public function testLinter() {
$this->assertExecutable('xhpast');
$this->executeTestsInDirectory(dirname(__FILE__).'/xhpast/'); $this->executeTestsInDirectory(dirname(__FILE__).'/xhpast/');
} }

View file

@ -29,6 +29,8 @@ abstract class ArcanistXHPASTLinterRuleTestCase
* @return ArcanistXHPASTLinterRule * @return ArcanistXHPASTLinterRule
*/ */
protected function getLinterRule() { protected function getLinterRule() {
$this->assertExecutable('xhpast');
$class = get_class($this); $class = get_class($this);
$matches = null; $matches = null;

View file

@ -45,6 +45,12 @@ final class PhutilRemarkupEngineTestCase extends PhutilTestCase {
$engine->setConfig('uri.base', 'http://www.example.com/'); $engine->setConfig('uri.base', 'http://www.example.com/');
$engine->setConfig('uri.here', 'http://www.example.com/page/'); $engine->setConfig('uri.here', 'http://www.example.com/page/');
break; 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); $actual_output = (string)$engine->markupText($input_remarkup);

View file

@ -14,6 +14,8 @@ final class PhutilXHPASTSyntaxHighlighterTestCase extends PhutilTestCase {
} }
public function testBuiltinClassnames() { public function testBuiltinClassnames() {
$this->assertExecutable('xhpast');
$this->assertEqual( $this->assertEqual(
$this->read('builtin-classname.expect'), $this->read('builtin-classname.expect'),
(string)$this->highlight($this->read('builtin-classname.source')), (string)$this->highlight($this->read('builtin-classname.source')),

View file

@ -107,9 +107,16 @@ final class PhutilEditorConfig extends Phobject {
$configs = $this->getEditorConfigs($path); $configs = $this->getEditorConfigs($path);
$matches = array(); $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) { foreach ($configs as $config) {
list($path_prefix, $editorconfig) = $config; list($path_prefix, $editorconfig) = $config;
// Normalize path separators, as above.
$path_prefix = str_replace(DIRECTORY_SEPARATOR, '/', $path_prefix);
foreach ($editorconfig as $glob => $properties) { foreach ($editorconfig as $glob => $properties) {
if (!$glob) { if (!$glob) {
continue; continue;
@ -164,11 +171,10 @@ final class PhutilEditorConfig extends Phobject {
*/ */
private function getEditorConfigs($path) { private function getEditorConfigs($path) {
$configs = array(); $configs = array();
$found_root = false;
$root = $this->root;
do { $found_root = false;
$path = dirname($path); $paths = Filesystem::walkToRoot($path, $this->root);
foreach ($paths as $path) {
$file = $path.'/.editorconfig'; $file = $path.'/.editorconfig';
if (!Filesystem::pathExists($file)) { if (!Filesystem::pathExists($file)) {
@ -187,7 +193,7 @@ final class PhutilEditorConfig extends Phobject {
if ($found_root) { if ($found_root) {
break; break;
} }
} while ($path != $root && Filesystem::isDescendant($path, $root)); }
return $configs; return $configs;
} }

View file

@ -11,6 +11,8 @@ final class ArcanistBundleTestCase extends PhutilTestCase {
} }
private function loadDiff($old, $new) { private function loadDiff($old, $new) {
$this->assertExecutable('diff');
list($err, $stdout) = exec_manual( list($err, $stdout) = exec_manual(
'diff --unified=65535 --label %s --label %s -- %s %s', 'diff --unified=65535 --label %s --label %s -- %s %s',
'file 9999-99-99', 'file 9999-99-99',

View file

@ -3,6 +3,8 @@
final class XHPASTNodeTestCase extends PhutilTestCase { final class XHPASTNodeTestCase extends PhutilTestCase {
public function testGetStringVariables() { public function testGetStringVariables() {
$this->assertExecutable('xhpast');
$this->assertStringVariables(array(), '""'); $this->assertStringVariables(array(), '""');
$this->assertStringVariables(array(2 => 'abc'), '"$abc"'); $this->assertStringVariables(array(2 => 'abc'), '"$abc"');
$this->assertStringVariables(array(), '"\$abc"'); $this->assertStringVariables(array(), '"\$abc"');
@ -20,6 +22,8 @@ final class XHPASTNodeTestCase extends PhutilTestCase {
} }
private function assertStringVariables($expected, $string) { private function assertStringVariables($expected, $string) {
$this->assertExecutable('xhpast');
$statement = XHPASTTree::newStatementFromString($string); $statement = XHPASTTree::newStatementFromString($string);
$this->assertEqual( $this->assertEqual(
$expected, $expected,
@ -28,6 +32,8 @@ final class XHPASTNodeTestCase extends PhutilTestCase {
} }
public function testGetNamespace() { public function testGetNamespace() {
$this->assertExecutable('xhpast');
$dir = dirname(__FILE__).'/namespace/'; $dir = dirname(__FILE__).'/namespace/';
$files = id(new FileFinder($dir)) $files = id(new FileFinder($dir))
->withType('f') ->withType('f')

View file

@ -6,6 +6,8 @@
final class XHPASTTreeTestCase extends PhutilTestCase { final class XHPASTTreeTestCase extends PhutilTestCase {
public function testEvalStaticString() { public function testEvalStaticString() {
$this->assertExecutable('xhpast');
$this->assertEval(1, '1'); $this->assertEval(1, '1');
$this->assertEval('a', '"a"'); $this->assertEval('a', '"a"');
$this->assertEval(-1.1, '-1.1'); $this->assertEval(-1.1, '-1.1');

View file

@ -32,7 +32,7 @@ final class PhutilXHPASTBinary extends Phobject {
$command = 'make'; $command = 'make';
} }
$root = phutil_get_library_root('phutil'); $root = phutil_get_library_root('arcanist');
$path = Filesystem::resolvePath($root.'/../support/xhpast'); $path = Filesystem::resolvePath($root.'/../support/xhpast');
// Run the build. // Run the build.

View file

@ -3,6 +3,10 @@
final class PhageAgentTestCase extends PhutilTestCase { final class PhageAgentTestCase extends PhutilTestCase {
public function testPhagePHPAgent() { public function testPhagePHPAgent() {
if (phutil_is_windows()) {
$this->assertSkipped(pht('Phage does not target Windows.'));
}
return $this->runBootloaderTests(new PhagePHPAgentBootloader()); return $this->runBootloaderTests(new PhagePHPAgentBootloader());
} }

View file

@ -3,33 +3,29 @@
final class ArcanistRepositoryAPIStateTestCase extends PhutilTestCase { final class ArcanistRepositoryAPIStateTestCase extends PhutilTestCase {
public function testGitStateParsing() { public function testGitStateParsing() {
if (Filesystem::binaryExists('git')) { $this->assertExecutable('git');
$this->parseState('git_basic.git.tgz'); $this->parseState('git_basic.git.tgz');
$this->parseState('git_submodules_dirty.git.tgz'); $this->parseState('git_submodules_dirty.git.tgz');
$this->parseState('git_submodules_staged.git.tgz'); $this->parseState('git_submodules_staged.git.tgz');
$this->parseState('git_spaces.git.tgz'); $this->parseState('git_spaces.git.tgz');
} else {
$this->assertSkipped(pht('Git is not installed'));
}
} }
public function testHgStateParsing() { public function testHgStateParsing() {
if (Filesystem::binaryExists('hg')) { $this->assertExecutable('hg');
$this->parseState('hg_basic.hg.tgz'); $this->parseState('hg_basic.hg.tgz');
} else {
$this->assertSkipped(pht('Mercurial is not installed'));
}
} }
public function testSvnStateParsing() { public function testSvnStateParsing() {
if (Filesystem::binaryExists('svn')) { $this->assertExecutable('svn');
$this->parseState('svn_basic.svn.tgz'); $this->parseState('svn_basic.svn.tgz');
} else {
$this->assertSkipped(pht('Subversion is not installed'));
}
} }
private function parseState($test) { private function parseState($test) {
$this->assertExecutable('tar');
$dir = dirname(__FILE__).'/state/'; $dir = dirname(__FILE__).'/state/';
$fixture = PhutilDirectoryFixture::newFromArchive($dir.'/'.$test); $fixture = PhutilDirectoryFixture::newFromArchive($dir.'/'.$test);

View file

@ -20,6 +20,8 @@ abstract class PhutilTestCase extends Phobject {
private $paths; private $paths;
private $renderer; private $renderer;
private static $executables = array();
/* -( Making Test Assertions )--------------------------------------------- */ /* -( Making Test Assertions )--------------------------------------------- */
@ -110,15 +112,24 @@ abstract class PhutilTestCase extends Phobject {
$output .= "\n"; $output .= "\n";
static $have_diff;
if ($have_diff === null) {
$have_diff = Filesystem::binaryExists('diff');
}
if (strpos($expect, "\n") === false && strpos($result, "\n") === false) { if (strpos($expect, "\n") === false && strpos($result, "\n") === false) {
$output .= pht("Expected: %s\n Actual: %s", $expect, $result); $output .= pht("Expected: %s\n Actual: %s", $expect, $result);
} else { } else if ($have_diff) {
$output .= pht( $output .= pht(
"Expected vs Actual Output Diff\n%s", "Expected vs Actual Output Diff\n%s",
ArcanistDiffUtils::renderDifferences( ArcanistDiffUtils::renderDifferences(
$expect, $expect,
$result, $result,
$lines = 0xFFFF)); $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); $this->failTest($output);
@ -751,4 +762,37 @@ abstract class PhutilTestCase extends Phobject {
throw new PhutilTestTerminatedException($output); 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';
}
} }

View file

@ -39,7 +39,14 @@ final class PhutilCsprintfTestCase extends PhutilTestCase {
} }
public function testNoPowershell() { public function testNoPowershell() {
if (!phutil_is_windows()) { 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 = csprintf('%s', '#');
$cmd->setEscapingMode(PhutilCommandString::MODE_DEFAULT); $cmd->setEscapingMode(PhutilCommandString::MODE_DEFAULT);
@ -47,17 +54,18 @@ final class PhutilCsprintfTestCase extends PhutilTestCase {
'\'#\'', '\'#\'',
(string)$cmd); (string)$cmd);
} }
}
public function testPasswords() { public function testPasswords() {
$bin = $this->getSupportExecutable('echo');
// Normal "%s" doesn't do anything special. // 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); $this->assertTrue(strpos($command, 'hunter2trustno1') !== false);
// "%P" takes a PhutilOpaqueEnvelope. // "%P" takes a PhutilOpaqueEnvelope.
$caught = null; $caught = null;
try { try {
csprintf('echo %P', 'hunter2trustno1'); csprintf('php -f %R -- %P', $bin, 'hunter2trustno1');
} catch (Exception $ex) { } catch (Exception $ex) {
$caught = $ex; $caught = $ex;
} }
@ -65,7 +73,10 @@ final class PhutilCsprintfTestCase extends PhutilTestCase {
// "%P" masks the provided value. // "%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')); $this->assertFalse(strpos($command, 'hunter2trustno1'));

1
support/unit/cat.php Executable file
View file

@ -0,0 +1 @@
<?php echo file_get_contents('php://stdin');

8
support/unit/echo.php Executable file
View file

@ -0,0 +1,8 @@
<?php
$args = array_slice($argv, 1);
foreach ($args as $key => $arg) {
$args[$key] = addcslashes($arg, "\\\n");
}
$args = implode($args, "\n");
echo $args;

1
support/unit/exit.php Executable file
View file

@ -0,0 +1 @@
<?php exit(0);

20
support/unit/sleep.php Executable file
View file

@ -0,0 +1,20 @@
<?php
if ($argc != 2) {
echo "usage: sleep <duration>\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));
}