1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-09-19 16:38:51 +02: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.
*/
public function testLibraryMap() {
$this->assertExecutable('xhpast');
$root = $this->getLibraryRoot();
$library = phutil_get_library_name_for_root($root);

View file

@ -45,7 +45,9 @@ final class PhutilPHPObjectProtocolChannelTestCase extends PhutilTestCase {
}
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);

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
// 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);

View file

@ -125,6 +125,16 @@ final class FileFinderTestCase extends PhutilTestCase {
}
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 @@ final class FileFinderTestCase extends PhutilTestCase {
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)

View file

@ -111,22 +111,17 @@ final class FilesystemTestCase extends PhutilTestCase {
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));

View file

@ -95,8 +95,7 @@ final class PhutilDeferredLogTestCase extends PhutilTestCase {
}
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 @@ final class PhutilDeferredLogTestCase extends PhutilTestCase {
$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))

View file

@ -170,14 +170,20 @@ final class PhutilFileLockTestCase extends PhutilTestCase {
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;
}

View file

@ -43,7 +43,9 @@ final class LinesOfALargeExecFutureTestCase extends PhutilTestCase {
}
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();

View file

@ -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);

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
// 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 @@ final class ExecFutureTestCase extends PhutilTestCase {
// 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 @@ final class ExecFutureTestCase extends PhutilTestCase {
// 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 @@ final class ExecFutureTestCase extends PhutilTestCase {
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 @@ final class ExecFutureTestCase extends PhutilTestCase {
// 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 @@ final class ExecFutureTestCase extends PhutilTestCase {
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 @@ final class ExecFutureTestCase extends PhutilTestCase {
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 @@ final class ExecFutureTestCase extends PhutilTestCase {
$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 @@ final class ExecFutureTestCase extends PhutilTestCase {
// 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.

View file

@ -8,7 +8,9 @@ final class ExecPassthruTestCase extends PhutilTestCase {
// 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);
}

View file

@ -63,6 +63,10 @@ final class PhutilOAuth1FutureTestCase extends PhutilTestCase {
}
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 @@ EOKEY;
md5($future->getSignature()));
}
}

View file

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

View file

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

View file

@ -45,6 +45,12 @@ final class PhutilRemarkupEngineTestCase extends PhutilTestCase {
$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);

View file

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

View file

@ -107,9 +107,16 @@ final class PhutilEditorConfig extends Phobject {
$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 @@ final class PhutilEditorConfig extends Phobject {
* return list<pair<string, map>>
*/
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 @@ final class PhutilEditorConfig extends Phobject {
if ($found_root) {
break;
}
} while ($path != $root && Filesystem::isDescendant($path, $root));
}
return $configs;
}

View file

@ -11,6 +11,8 @@ final class ArcanistBundleTestCase extends PhutilTestCase {
}
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',

View file

@ -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 @@ final class XHPASTNodeTestCase extends PhutilTestCase {
}
private function assertStringVariables($expected, $string) {
$this->assertExecutable('xhpast');
$statement = XHPASTTree::newStatementFromString($string);
$this->assertEqual(
$expected,
@ -28,6 +32,8 @@ final class XHPASTNodeTestCase extends PhutilTestCase {
}
public function testGetNamespace() {
$this->assertExecutable('xhpast');
$dir = dirname(__FILE__).'/namespace/';
$files = id(new FileFinder($dir))
->withType('f')

View file

@ -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');

View file

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

View file

@ -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());
}

View file

@ -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);

View file

@ -20,6 +20,8 @@ abstract class PhutilTestCase extends Phobject {
private $paths;
private $renderer;
private static $executables = array();
/* -( Making Test Assertions )--------------------------------------------- */
@ -110,15 +112,24 @@ abstract class PhutilTestCase extends Phobject {
$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 @@ abstract class PhutilTestCase extends Phobject {
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,25 +39,33 @@ final class PhutilCsprintfTestCase extends PhutilTestCase {
}
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 @@ final class PhutilCsprintfTestCase extends PhutilTestCase {
// "%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'));

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));
}