Page MenuHomePhabricator

D20988.diff
No OneTemporary

D20988.diff

diff --git a/.gitignore b/.gitignore
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,6 @@
# This is an OS X build artifact.
/support/xhpast/xhpast.dSYM
+
+# Generated shell completion rulesets.
+/support/shell/rules/
diff --git a/resources/shell/bash-completion b/resources/shell/bash-completion
deleted file mode 100644
--- a/resources/shell/bash-completion
+++ /dev/null
@@ -1,26 +0,0 @@
-if [[ -n ${ZSH_VERSION-} ]]; then
- autoload -U +X bashcompinit && bashcompinit
-fi
-
-_arc ()
-{
- CUR="${COMP_WORDS[COMP_CWORD]}"
- COMPREPLY=()
- OPTS=$(echo | arc shell-complete --current ${COMP_CWORD} -- ${COMP_WORDS[@]})
-
- if [ $? -ne 0 ]; then
- return $?
- fi
-
- if [ "$OPTS" = "FILE" ]; then
- COMPREPLY=( $(compgen -f -- ${CUR}) )
- return 0
- fi
-
- if [ "$OPTS" = "ARGUMENT" ]; then
- return 0
- fi
-
- COMPREPLY=( $(compgen -W "${OPTS}" -- ${CUR}) )
-}
-complete -F _arc -o filenames arc
diff --git a/scripts/__init_script__.php b/scripts/__init_script__.php
--- a/scripts/__init_script__.php
+++ b/scripts/__init_script__.php
@@ -1,3 +1,3 @@
<?php
-require_once dirname(dirname(__FILE__)).'/scripts/init/init-script.php';
+require_once dirname(dirname(__FILE__)).'/support/init/init-script.php';
diff --git a/scripts/breakout.py b/scripts/breakout.py
--- a/scripts/breakout.py
+++ b/scripts/breakout.py
@@ -141,11 +141,11 @@
height, width = stdscr.getmaxyx()
- if height < 15 or width < 30:
+ if height < 15 or width < 32:
raise PowerOverwhelmingException(
- "Your computer is not powerful enough to run 'arc anoid'. "
- "It must support at least 30 columns and 15 rows of next-gen "
- "full-color 3D graphics.")
+ 'Your computer is not powerful enough to run "arc anoid". '
+ 'It must support at least 32 columns and 15 rows of next-gen '
+ 'full-color 3D graphics.')
status = curses.newwin(1, width, 0, 0)
height -= 1
@@ -194,7 +194,15 @@
status.addstr('%s/%s ' % (Block.killed, Block.total), curses.A_BOLD)
status.addch(curses.ACS_VLINE)
status.addstr(' DEATHS: ', curses.A_BOLD | curses.color_pair(4))
- status.addstr('%s ' % Ball.killed, curses.A_BOLD)
+
+ # See T8693. At the minimum display size, we only have room to render
+ # two characters for the death count, so just display "99" if the
+ # player has more than 99 deaths.
+ display_deaths = Ball.killed
+ if (display_deaths > 99):
+ display_deaths = 99
+
+ status.addstr('%s ' % display_deaths, curses.A_BOLD)
status.addch(curses.ACS_LTEE)
if Block.killed == Block.total:
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
@@ -232,7 +232,6 @@
'ArcanistJSHintLinter' => 'lint/linter/ArcanistJSHintLinter.php',
'ArcanistJSHintLinterTestCase' => 'lint/linter/__tests__/ArcanistJSHintLinterTestCase.php',
'ArcanistJSONLintLinter' => 'lint/linter/ArcanistJSONLintLinter.php',
- 'ArcanistJSONLintLinterTestCase' => 'lint/linter/__tests__/ArcanistJSONLintLinterTestCase.php',
'ArcanistJSONLintRenderer' => 'lint/renderer/ArcanistJSONLintRenderer.php',
'ArcanistJSONLinter' => 'lint/linter/ArcanistJSONLinter.php',
'ArcanistJSONLinterTestCase' => 'lint/linter/__tests__/ArcanistJSONLinterTestCase.php',
@@ -1130,7 +1129,6 @@
'ArcanistJSHintLinter' => 'ArcanistExternalLinter',
'ArcanistJSHintLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistJSONLintLinter' => 'ArcanistExternalLinter',
- 'ArcanistJSONLintLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistJSONLintRenderer' => 'ArcanistLintRenderer',
'ArcanistJSONLinter' => 'ArcanistLinter',
'ArcanistJSONLinterTestCase' => 'ArcanistLinterTestCase',
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,8 @@
}
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/error/__tests__/PhutilOpaqueEnvelopeTestCase.php b/src/error/__tests__/PhutilOpaqueEnvelopeTestCase.php
--- a/src/error/__tests__/PhutilOpaqueEnvelopeTestCase.php
+++ b/src/error/__tests__/PhutilOpaqueEnvelopeTestCase.php
@@ -8,9 +8,13 @@
// the diff itself, and thus this source code. Since we look for the secret
// in traces later on, split it apart here so that invocation via
// "arc diff" doesn't create a false test failure.
-
$secret = 'hunter'.'2';
+ // Also split apart this "signpost" value which we are not going to put in
+ // an envelope. We expect to be able to find it in the argument lists in
+ // stack traces, and don't want a false positive.
+ $signpost = 'shaman'.'3';
+
$envelope = new PhutilOpaqueEnvelope($secret);
$this->assertFalse(strpos(var_export($envelope, true), $secret));
@@ -24,23 +28,34 @@
$this->assertFalse(strpos($dump, $secret));
try {
- $this->throwTrace($envelope);
+ $this->throwTrace($envelope, $signpost);
} catch (Exception $ex) {
$trace = $ex->getTrace();
- $this->assertFalse(strpos(print_r($trace, true), $secret));
+
+ // NOTE: The entire trace may be very large and contain complex
+ // recursive datastructures. Look at only the last few frames: we expect
+ // to see the signpost value but not the secret.
+ $trace = array_slice($trace, 0, 2);
+ $trace = print_r($trace, true);
+
+ $this->assertTrue(strpos($trace, $signpost) !== false);
+ $this->assertFalse(strpos($trace, $secret));
}
- $backtrace = $this->getBacktrace($envelope);
+ $backtrace = $this->getBacktrace($envelope, $signpost);
+ $backtrace = array_slice($backtrace, 0, 2);
+
+ $this->assertTrue(strpos($trace, $signpost) !== false);
$this->assertFalse(strpos(print_r($backtrace, true), $secret));
$this->assertEqual($secret, $envelope->openEnvelope());
}
- private function throwTrace($v) {
+ private function throwTrace($v, $w) {
throw new Exception('!');
}
- private function getBacktrace($v) {
+ private function getBacktrace($v, $w) {
return debug_backtrace();
}
diff --git a/src/filesystem/Filesystem.php b/src/filesystem/Filesystem.php
--- a/src/filesystem/Filesystem.php
+++ b/src/filesystem/Filesystem.php
@@ -488,9 +488,8 @@
pht(
'%s requires the PHP OpenSSL extension to be installed and enabled '.
'to access an entropy source. On Windows, this extension is usually '.
- 'installed but not enabled by default. Enable it in your "s".',
- __METHOD__.'()',
- 'php.ini'));
+ 'installed but not enabled by default. Enable it in your "php.ini".',
+ __METHOD__.'()'));
}
throw new Exception(
@@ -950,8 +949,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);
@@ -1104,6 +1118,18 @@
return ($u == $v);
}
+ public static function concatenatePaths(array $components) {
+ $components = implode($components, DIRECTORY_SEPARATOR);
+
+ // Replace any extra sequences of directory separators with a single
+ // separator, so we don't end up with "path//to///thing.c".
+ $components = preg_replace(
+ '('.preg_quote(DIRECTORY_SEPARATOR).'{2,})',
+ DIRECTORY_SEPARATOR,
+ $components);
+
+ return $components;
+ }
/* -( Assert )------------------------------------------------------------- */
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.
@@ -211,6 +221,7 @@
'php',
'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
@@ -127,6 +127,12 @@
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__/PhutilFileLockTestCase.php b/src/filesystem/__tests__/PhutilFileLockTestCase.php
--- a/src/filesystem/__tests__/PhutilFileLockTestCase.php
+++ b/src/filesystem/__tests__/PhutilFileLockTestCase.php
@@ -170,16 +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/test/lock-file.php';
-
- $flags = (array)$flags;
+ 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 -f %R -- %Ls %R', $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/ExecFuture.php b/src/future/exec/ExecFuture.php
--- a/src/future/exec/ExecFuture.php
+++ b/src/future/exec/ExecFuture.php
@@ -42,7 +42,6 @@
private $profilerCallID;
private $killedByTimeout;
- private $useWindowsFileStreams = false;
private $windowsStdoutTempFile = null;
private $windowsStderrTempFile = null;
@@ -181,21 +180,6 @@
}
- /**
- * Set whether to use non-blocking streams on Windows.
- *
- * @param bool Whether to use non-blocking streams.
- * @return this
- * @task config
- */
- public function setUseWindowsFileStreams($use_streams) {
- if (phutil_is_windows()) {
- $this->useWindowsFileStreams = $use_streams;
- }
- return $this;
- }
-
-
/* -( Interacting With Commands )------------------------------------------ */
@@ -587,6 +571,7 @@
// classes are always available.
if (!$this->pipes) {
+ $is_windows = phutil_is_windows();
// NOTE: See note above about Phage.
if (class_exists('PhutilServiceProfiler')) {
@@ -610,18 +595,6 @@
$pipes = array();
- if (phutil_is_windows()) {
- // See T4395. proc_open under Windows uses "cmd /C [cmd]", which will
- // strip the first and last quote when there aren't exactly two quotes
- // (and some other conditions as well). This results in a command that
- // looks like `command" "path to my file" "something something` which is
- // clearly wrong. By surrounding the command string with quotes we can
- // be sure this process is harmless.
- if (strpos($unmasked_command, '"') !== false) {
- $unmasked_command = '"'.$unmasked_command.'"';
- }
- }
-
if ($this->hasEnv()) {
$env = $this->getEnv();
} else {
@@ -638,21 +611,31 @@
}
$spec = self::$descriptorSpec;
- if ($this->useWindowsFileStreams) {
- $this->windowsStdoutTempFile = new TempFile();
- $this->windowsStderrTempFile = new TempFile();
+ if ($is_windows) {
+ $stdout_file = new TempFile();
+ $stderr_file = new TempFile();
+
+ $stdout_handle = fopen($stdout_file, 'wb');
+ if (!$stdout_handle) {
+ throw new Exception(
+ pht(
+ 'Unable to open stdout temporary file ("%s") for writing.',
+ $stdout_file));
+ }
+
+ $stderr_handle = fopen($stderr_file, 'wb');
+ if (!$stderr_handle) {
+ throw new Exception(
+ pht(
+ 'Unable to open stderr temporary file ("%s") for writing.',
+ $stderr_file));
+ }
$spec = array(
- 0 => self::$descriptorSpec[0], // stdin
- 1 => fopen($this->windowsStdoutTempFile, 'wb'), // stdout
- 2 => fopen($this->windowsStderrTempFile, 'wb'), // stderr
+ 0 => self::$descriptorSpec[0],
+ 1 => $stdout_handle,
+ 2 => $stderr_handle,
);
-
- if (!$spec[1] || !$spec[2]) {
- throw new Exception(pht(
- 'Unable to create temporary files for '.
- 'Windows stdout / stderr streams'));
- }
}
$proc = @proc_open(
@@ -660,23 +643,10 @@
$spec,
$pipes,
$cwd,
- $env);
-
- if ($this->useWindowsFileStreams) {
- fclose($spec[1]);
- fclose($spec[2]);
- $pipes = array(
- 0 => head($pipes), // stdin
- 1 => fopen($this->windowsStdoutTempFile, 'rb'), // stdout
- 2 => fopen($this->windowsStderrTempFile, 'rb'), // stderr
- );
-
- if (!$pipes[1] || !$pipes[2]) {
- throw new Exception(pht(
- 'Unable to open temporary files for '.
- 'reading Windows stdout / stderr streams'));
- }
- }
+ $env,
+ array(
+ 'bypass_shell' => true,
+ ));
if ($trap) {
$err = $trap->getErrorsAsString();
@@ -685,12 +655,56 @@
$err = error_get_last();
}
+ if ($is_windows) {
+ fclose($stdout_handle);
+ fclose($stderr_handle);
+ }
+
if (!is_resource($proc)) {
- throw new Exception(
+ // When you run an invalid command on a Linux system, the "proc_open()"
+ // works and then the process (really a "/bin/sh -c ...") exits after
+ // it fails to resolve the command.
+
+ // When you run an invalid command on a Windows system, we bypass the
+ // shell and the "proc_open()" itself fails. Throw a "CommandException"
+ // here for consistency with the Linux behavior in this common failure
+ // case.
+
+ throw new CommandException(
pht(
- 'Failed to `%s`: %s',
- 'proc_open()',
- $err));
+ 'Call to "proc_open()" to open a subprocess failed: %s',
+ $err),
+ $this->command,
+ 1,
+ '',
+ '');
+ }
+
+ if ($is_windows) {
+ $stdout_handle = fopen($stdout_file, 'rb');
+ if (!$stdout_handle) {
+ throw new Exception(
+ pht(
+ 'Unable to open stdout temporary file ("%s") for reading.',
+ $stdout_file));
+ }
+
+ $stderr_handle = fopen($stderr_file, 'rb');
+ if (!$stderr_handle) {
+ throw new Exception(
+ pht(
+ 'Unable to open stderr temporary file ("%s") for reading.',
+ $stderr_file));
+ }
+
+ $pipes = array(
+ 0 => $pipes[0],
+ 1 => $stdout_handle,
+ 2 => $stderr_handle,
+ );
+
+ $this->windowsStdoutTempFile = $stdout_file;
+ $this->windowsStderrTempFile = $stderr_file;
}
$this->pipes = $pipes;
@@ -698,11 +712,11 @@
list($stdin, $stdout, $stderr) = $pipes;
- if (!phutil_is_windows()) {
+ if (!$is_windows) {
// On Windows, we redirect process standard output and standard error
- // through temporary files, and then use stream_select to determine
- // if there's more data to read.
+ // through temporary files. Files don't block, so we don't need to make
+ // these streams nonblocking.
if ((!stream_set_blocking($stdout, false)) ||
(!stream_set_blocking($stderr, false)) ||
@@ -780,11 +794,6 @@
}
if ($is_done) {
- if ($this->useWindowsFileStreams) {
- fclose($stdout);
- fclose($stderr);
- }
-
// If the subprocess got nuked with `kill -9`, we get a -1 exitcode.
// Upgrade this to a slightly more informative value by examining the
// terminating signal code.
@@ -864,7 +873,10 @@
@proc_close($this->proc);
$this->proc = null;
}
- $this->stdin = null;
+ $this->stdin = null;
+
+ unset($this->windowsStdoutTempFile);
+ unset($this->windowsStderrTempFile);
if ($this->profilerCallID !== null) {
$profiler = PhutilServiceProfiler::getInstance();
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
diff --git a/src/internationalization/ArcanistUSEnglishTranslation.php b/src/internationalization/ArcanistUSEnglishTranslation.php
--- a/src/internationalization/ArcanistUSEnglishTranslation.php
+++ b/src/internationalization/ArcanistUSEnglishTranslation.php
@@ -81,6 +81,11 @@
'This commit will be landed:',
'These commits will be landed:',
),
+
+ 'Updated %s librarie(s).' => array(
+ 'Updated library.',
+ 'Updated %s libraries.',
+ ),
);
}
diff --git a/src/lint/linter/__tests__/ArcanistJSONLintLinterTestCase.php b/src/lint/linter/__tests__/ArcanistJSONLintLinterTestCase.php
deleted file mode 100644
--- a/src/lint/linter/__tests__/ArcanistJSONLintLinterTestCase.php
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-final class ArcanistJSONLintLinterTestCase
- extends ArcanistExternalLinterTestCase {
-
- public function testLinter() {
- $this->executeTestsInDirectory(dirname(__FILE__).'/jsonlint/');
- }
-
-}
diff --git a/src/lint/linter/__tests__/ArcanistJSONLinterTestCase.php b/src/lint/linter/__tests__/ArcanistJSONLinterTestCase.php
--- a/src/lint/linter/__tests__/ArcanistJSONLinterTestCase.php
+++ b/src/lint/linter/__tests__/ArcanistJSONLinterTestCase.php
@@ -3,7 +3,7 @@
final class ArcanistJSONLinterTestCase extends ArcanistLinterTestCase {
public function testLinter() {
- $this->executeTestsInDirectory(dirname(__FILE__).'/jsonlint/');
+ $this->executeTestsInDirectory(dirname(__FILE__).'/json/');
}
}
diff --git a/src/lint/linter/__tests__/jsonlint/1.lint-test b/src/lint/linter/__tests__/json/1.lint-test
rename from src/lint/linter/__tests__/jsonlint/1.lint-test
rename to src/lint/linter/__tests__/json/1.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/10.lint-test b/src/lint/linter/__tests__/json/10.lint-test
rename from src/lint/linter/__tests__/jsonlint/10.lint-test
rename to src/lint/linter/__tests__/json/10.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/11.lint-test b/src/lint/linter/__tests__/json/11.lint-test
rename from src/lint/linter/__tests__/jsonlint/11.lint-test
rename to src/lint/linter/__tests__/json/11.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/12.lint-test b/src/lint/linter/__tests__/json/12.lint-test
rename from src/lint/linter/__tests__/jsonlint/12.lint-test
rename to src/lint/linter/__tests__/json/12.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/13.lint-test b/src/lint/linter/__tests__/json/13.lint-test
rename from src/lint/linter/__tests__/jsonlint/13.lint-test
rename to src/lint/linter/__tests__/json/13.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/14.lint-test b/src/lint/linter/__tests__/json/14.lint-test
rename from src/lint/linter/__tests__/jsonlint/14.lint-test
rename to src/lint/linter/__tests__/json/14.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/15.lint-test b/src/lint/linter/__tests__/json/15.lint-test
rename from src/lint/linter/__tests__/jsonlint/15.lint-test
rename to src/lint/linter/__tests__/json/15.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/16.lint-test b/src/lint/linter/__tests__/json/16.lint-test
rename from src/lint/linter/__tests__/jsonlint/16.lint-test
rename to src/lint/linter/__tests__/json/16.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/17.lint-test b/src/lint/linter/__tests__/json/17.lint-test
rename from src/lint/linter/__tests__/jsonlint/17.lint-test
rename to src/lint/linter/__tests__/json/17.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/19.lint-test b/src/lint/linter/__tests__/json/19.lint-test
rename from src/lint/linter/__tests__/jsonlint/19.lint-test
rename to src/lint/linter/__tests__/json/19.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/2.lint-test b/src/lint/linter/__tests__/json/2.lint-test
rename from src/lint/linter/__tests__/jsonlint/2.lint-test
rename to src/lint/linter/__tests__/json/2.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/20.lint-test b/src/lint/linter/__tests__/json/20.lint-test
rename from src/lint/linter/__tests__/jsonlint/20.lint-test
rename to src/lint/linter/__tests__/json/20.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/21.lint-test b/src/lint/linter/__tests__/json/21.lint-test
rename from src/lint/linter/__tests__/jsonlint/21.lint-test
rename to src/lint/linter/__tests__/json/21.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/22.lint-test b/src/lint/linter/__tests__/json/22.lint-test
rename from src/lint/linter/__tests__/jsonlint/22.lint-test
rename to src/lint/linter/__tests__/json/22.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/23.lint-test b/src/lint/linter/__tests__/json/23.lint-test
rename from src/lint/linter/__tests__/jsonlint/23.lint-test
rename to src/lint/linter/__tests__/json/23.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/24.lint-test b/src/lint/linter/__tests__/json/24.lint-test
rename from src/lint/linter/__tests__/jsonlint/24.lint-test
rename to src/lint/linter/__tests__/json/24.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/25.lint-test b/src/lint/linter/__tests__/json/25.lint-test
rename from src/lint/linter/__tests__/jsonlint/25.lint-test
rename to src/lint/linter/__tests__/json/25.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/26.lint-test b/src/lint/linter/__tests__/json/26.lint-test
rename from src/lint/linter/__tests__/jsonlint/26.lint-test
rename to src/lint/linter/__tests__/json/26.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/27.lint-test b/src/lint/linter/__tests__/json/27.lint-test
rename from src/lint/linter/__tests__/jsonlint/27.lint-test
rename to src/lint/linter/__tests__/json/27.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/28.lint-test b/src/lint/linter/__tests__/json/28.lint-test
rename from src/lint/linter/__tests__/jsonlint/28.lint-test
rename to src/lint/linter/__tests__/json/28.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/29.lint-test b/src/lint/linter/__tests__/json/29.lint-test
rename from src/lint/linter/__tests__/jsonlint/29.lint-test
rename to src/lint/linter/__tests__/json/29.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/3.lint-test b/src/lint/linter/__tests__/json/3.lint-test
rename from src/lint/linter/__tests__/jsonlint/3.lint-test
rename to src/lint/linter/__tests__/json/3.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/30.lint-test b/src/lint/linter/__tests__/json/30.lint-test
rename from src/lint/linter/__tests__/jsonlint/30.lint-test
rename to src/lint/linter/__tests__/json/30.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/31.lint-test b/src/lint/linter/__tests__/json/31.lint-test
rename from src/lint/linter/__tests__/jsonlint/31.lint-test
rename to src/lint/linter/__tests__/json/31.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/32.lint-test b/src/lint/linter/__tests__/json/32.lint-test
rename from src/lint/linter/__tests__/jsonlint/32.lint-test
rename to src/lint/linter/__tests__/json/32.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/33.lint-test b/src/lint/linter/__tests__/json/33.lint-test
rename from src/lint/linter/__tests__/jsonlint/33.lint-test
rename to src/lint/linter/__tests__/json/33.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/34.lint-test b/src/lint/linter/__tests__/json/34.lint-test
rename from src/lint/linter/__tests__/jsonlint/34.lint-test
rename to src/lint/linter/__tests__/json/34.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/4.lint-test b/src/lint/linter/__tests__/json/4.lint-test
rename from src/lint/linter/__tests__/jsonlint/4.lint-test
rename to src/lint/linter/__tests__/json/4.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/5.lint-test b/src/lint/linter/__tests__/json/5.lint-test
rename from src/lint/linter/__tests__/jsonlint/5.lint-test
rename to src/lint/linter/__tests__/json/5.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/6.lint-test b/src/lint/linter/__tests__/json/6.lint-test
rename from src/lint/linter/__tests__/jsonlint/6.lint-test
rename to src/lint/linter/__tests__/json/6.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/7.lint-test b/src/lint/linter/__tests__/json/7.lint-test
rename from src/lint/linter/__tests__/jsonlint/7.lint-test
rename to src/lint/linter/__tests__/json/7.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/8.lint-test b/src/lint/linter/__tests__/json/8.lint-test
rename from src/lint/linter/__tests__/jsonlint/8.lint-test
rename to src/lint/linter/__tests__/json/8.lint-test
diff --git a/src/lint/linter/__tests__/jsonlint/9.lint-test b/src/lint/linter/__tests__/json/9.lint-test
rename from src/lint/linter/__tests__/jsonlint/9.lint-test
rename to src/lint/linter/__tests__/json/9.lint-test
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/moduleutils/PhutilLibraryMapBuilder.php b/src/moduleutils/PhutilLibraryMapBuilder.php
--- a/src/moduleutils/PhutilLibraryMapBuilder.php
+++ b/src/moduleutils/PhutilLibraryMapBuilder.php
@@ -183,7 +183,7 @@
* Load the library symbol cache, if it exists and is readable and valid.
*
* @return dict Map of content hashes to cache of output from
- * `phutil_symbols.php`.
+ * `extract-symbols.php`.
*
* @task symbol
*/
@@ -256,7 +256,7 @@
}
/**
- * Build a future which returns a `phutil_symbols.php` analysis of a source
+ * Build a future which returns a `extract-symbols.php` analysis of a source
* file.
*
* @param string Relative path to the source file to analyze.
@@ -442,7 +442,7 @@
$symbol_cache = $this->loadSymbolCache();
// If the XHPAST binary is not up-to-date, build it now. Otherwise,
- // `phutil_symbols.php` will attempt to build the binary and will fail
+ // `extract-symbols.php` will attempt to build the binary and will fail
// miserably because it will be trying to build the same file multiple
// times in parallel.
if (!PhutilXHPASTBinary::isAvailable()) {
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<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 @@
if ($found_root) {
break;
}
- } while ($path != $root && Filesystem::isDescendant($path, $root));
+ }
return $configs;
}
diff --git a/src/parser/PhutilJSONParser.php b/src/parser/PhutilJSONParser.php
--- a/src/parser/PhutilJSONParser.php
+++ b/src/parser/PhutilJSONParser.php
@@ -16,8 +16,8 @@
}
public function parse($json) {
- $jsonlint_root = phutil_get_library_root('arcanist');
- $jsonlint_root = $jsonlint_root.'/../externals/jsonlint';
+ $arcanist_root = phutil_get_library_root('arcanist');
+ $jsonlint_root = $arcanist_root.'/../externals/jsonlint';
require_once $jsonlint_root.'/src/Seld/JsonLint/JsonParser.php';
require_once $jsonlint_root.'/src/Seld/JsonLint/Lexer.php';
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/symbols/PhutilClassMapQuery.php b/src/symbols/PhutilClassMapQuery.php
--- a/src/symbols/PhutilClassMapQuery.php
+++ b/src/symbols/PhutilClassMapQuery.php
@@ -45,6 +45,7 @@
private $filterNull = false;
private $uniqueMethod;
private $sortMethod;
+ private $continueOnFailure;
// NOTE: If you add more configurable properties here, make sure that
// cache key construction in getCacheKey() is updated properly.
@@ -162,6 +163,10 @@
return $this;
}
+ public function setContinueOnFailure($continue) {
+ $this->continueOnFailure = $continue;
+ return $this;
+ }
/* -( Executing the Query )------------------------------------------------ */
@@ -236,6 +241,7 @@
$objects = id(new PhutilSymbolLoader())
->setAncestorClass($ancestor)
+ ->setContinueOnFailure($this->continueOnFailure)
->loadObjects();
// Apply the "expand" mechanism, if it is configured.
diff --git a/src/symbols/PhutilSymbolLoader.php b/src/symbols/PhutilSymbolLoader.php
--- a/src/symbols/PhutilSymbolLoader.php
+++ b/src/symbols/PhutilSymbolLoader.php
@@ -49,6 +49,7 @@
private $pathPrefix;
private $suppressLoad;
+ private $continueOnFailure;
/**
@@ -148,6 +149,10 @@
return $this;
}
+ public function setContinueOnFailure($continue) {
+ $this->continueOnFailure = $continue;
+ return $this;
+ }
/* -( Load )--------------------------------------------------------------- */
@@ -250,19 +255,55 @@
}
if (!$this->suppressLoad) {
+ // Loading a class may trigger the autoloader to load more classes
+ // (usually, the parent class), so we need to keep track of whether we
+ // are currently loading in "continue on failure" mode. Otherwise, we'll
+ // fail anyway if we fail to load a parent class.
+
+ // The driving use case for the "continue on failure" mode is to let
+ // "arc liberate" run so it can rebuild the library map, even if you have
+ // made changes to Workflow or Config classes which it must load before
+ // it can operate. If we don't let it continue on failure, it is very
+ // difficult to remove or move Workflows.
+
+ static $continue_depth = 0;
+ if ($this->continueOnFailure) {
+ $continue_depth++;
+ }
+
$caught = null;
- foreach ($symbols as $symbol) {
+ foreach ($symbols as $key => $symbol) {
try {
$this->loadSymbol($symbol);
} catch (Exception $ex) {
+ // If we failed to load this symbol, remove it from the results.
+ // Otherwise, we may fatal below when trying to reflect it.
+ unset($symbols[$key]);
+
$caught = $ex;
}
}
+
+ $should_continue = ($continue_depth > 0);
+
+ if ($this->continueOnFailure) {
+ $continue_depth--;
+ }
+
if ($caught) {
// NOTE: We try to load everything even if we fail to load something,
// primarily to make it possible to remove functions from a libphutil
// library without breaking library startup.
- throw $caught;
+ if ($should_continue) {
+ // We may not have `pht()` yet.
+ fprintf(
+ STDERR,
+ "%s: %s\n",
+ 'IGNORING CLASS LOAD FAILURE',
+ $caught->getMessage());
+ } else {
+ throw $caught;
+ }
}
}
@@ -386,11 +427,11 @@
$load_failed = null;
if ($is_function) {
if (!function_exists($name)) {
- $load_failed = 'function';
+ $load_failed = pht('function');
}
} else {
if (!class_exists($name, false) && !interface_exists($name, false)) {
- $load_failed = 'class/interface';
+ $load_failed = pht('class or interface');
}
}
@@ -400,13 +441,14 @@
$name,
$load_failed,
pht(
- 'The symbol map for library "%s" (at "%s") claims this symbol '.
- '(of type "%s") is defined in "%s", but loading that source file '.
- 'did not cause the symbol to become defined.',
+ "The symbol map for library '%s' (at '%s') claims this %s is ".
+ "defined in '%s', but loading that source file did not cause the ".
+ "%s to become defined.",
$lib_name,
$lib_path,
$load_failed,
- $where));
+ $where,
+ $load_failed));
}
}
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,7 @@
private $paths;
private $renderer;
+ private static $executables = array();
/* -( Making Test Assertions )--------------------------------------------- */
@@ -748,4 +749,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/utils/PhutilExecutionEnvironment.php b/src/utils/PhutilExecutionEnvironment.php
--- a/src/utils/PhutilExecutionEnvironment.php
+++ b/src/utils/PhutilExecutionEnvironment.php
@@ -13,4 +13,34 @@
return php_uname('r');
}
+ /**
+ * If the PHP configuration setting "variables_order" does not include "E",
+ * the `$_ENV` superglobal is not populated with the containing environment.
+ * For details, see T12071.
+ *
+ * This can be fixed by adding "E" to the configuration, but we can also
+ * repair it ourselves by re-executing a subprocess with the configuration
+ * option defined to include "E". This is clumsy, but saves users from
+ * needing to go find and edit their PHP files.
+ *
+ * @return void
+ */
+ public static function repairMissingVariablesOrder() {
+ $variables_order = ini_get('variables_order');
+ $variables_order = strtoupper($variables_order);
+
+ if (strpos($variables_order, 'E') !== false) {
+ // The "variables_order" option already has "E", so we don't need to
+ // repair $_ENV.
+ return;
+ }
+
+ list($env) = execx(
+ 'php -d variables_order=E -r %s',
+ 'echo json_encode($_ENV);');
+ $env = phutil_json_decode($env);
+
+ $_ENV = $_ENV + $env;
+ }
+
}
diff --git a/src/utils/__tests__/PhutilUTF8TestCase.php b/src/utils/__tests__/PhutilUTF8TestCase.php
--- a/src/utils/__tests__/PhutilUTF8TestCase.php
+++ b/src/utils/__tests__/PhutilUTF8TestCase.php
@@ -61,6 +61,13 @@
);
foreach ($map as $input => $expect) {
+ if ($input !== $expect) {
+ $this->assertEqual(
+ false,
+ phutil_is_utf8_slowly($input),
+ pht('Slowly reject overlong form of: %s', $input));
+ }
+
$actual = phutil_utf8ize($input);
$this->assertEqual(
$expect,
@@ -77,6 +84,13 @@
);
foreach ($map as $input => $expect) {
+ if ($input !== $expect) {
+ $this->assertEqual(
+ false,
+ phutil_is_utf8_slowly($input),
+ pht('Slowly reject surrogate: %s', $input));
+ }
+
$actual = phutil_utf8ize($input);
$this->assertEqual(
$expect,
diff --git a/src/utils/__tests__/PhutilUtilsTestCase.php b/src/utils/__tests__/PhutilUtilsTestCase.php
--- a/src/utils/__tests__/PhutilUtilsTestCase.php
+++ b/src/utils/__tests__/PhutilUtilsTestCase.php
@@ -570,6 +570,7 @@
} catch (Exception $ex) {
$caught = $ex;
}
+
$this->assertTrue($caught instanceof PhutilJSONParserException);
}
}
@@ -965,5 +966,4 @@
}
}
-
}
diff --git a/src/utils/utf8.php b/src/utils/utf8.php
--- a/src/utils/utf8.php
+++ b/src/utils/utf8.php
@@ -149,6 +149,34 @@
continue;
}
return false;
+ } else if ($chr == 0xED) {
+ // See T11525. Some sequences in this block are surrogate codepoints
+ // that are reserved for use in UTF16. We should reject them.
+ $codepoint = ($chr & 0x0F) << 12;
+ ++$ii;
+ if ($ii >= $len) {
+ return false;
+ }
+ $chr = ord($string[$ii]);
+ $codepoint += ($chr & 0x3F) << 6;
+ if ($chr >= 0x80 && $chr <= 0xBF) {
+ ++$ii;
+ if ($ii >= $len) {
+ return false;
+ }
+ $chr = ord($string[$ii]);
+ $codepoint += ($chr & 0x3F);
+
+ if ($codepoint >= 0xD800 && $codepoint <= 0xDFFF) {
+ // Reject these surrogate codepoints.
+ return false;
+ }
+
+ if ($chr >= 0x80 && $chr <= 0xBF) {
+ continue;
+ }
+ }
+ return false;
} else if ($chr > 0xE0 && $chr <= 0xEF) {
++$ii;
if ($ii >= $len) {
diff --git a/src/utils/utils.php b/src/utils/utils.php
--- a/src/utils/utils.php
+++ b/src/utils/utils.php
@@ -1065,8 +1065,8 @@
// the stream, write to it again if PHP claims that it's writable, and
// consider the pipe broken if the write fails.
- // (Signals received signals during the "fwrite()" do not appear to affect
- // anything, see D20083.)
+ // (Signals received during the "fwrite()" do not appear to affect anything,
+ // see D20083.)
$read = array();
$write = array($stream);
diff --git a/src/xsprintf/PhutilTerminalString.php b/src/xsprintf/PhutilTerminalString.php
--- a/src/xsprintf/PhutilTerminalString.php
+++ b/src/xsprintf/PhutilTerminalString.php
@@ -70,6 +70,13 @@
$value = preg_replace('/\r(?!\n)/', '<CR>', $value);
}
+ // See T13209. If we print certain invalid unicode byte sequences to the
+ // terminal under "cmd.exe", the entire string is silently dropped. Avoid
+ // printing invalid sequences.
+ if (phutil_is_windows()) {
+ $value = phutil_utf8ize($value);
+ }
+
return $value;
}
}
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/scripts/init/init-script.php b/support/init/init-script.php
rename from scripts/init/init-script.php
rename to support/init/init-script.php
--- a/scripts/init/init-script.php
+++ b/support/init/init-script.php
@@ -1,12 +1,6 @@
<?php
-if (function_exists('pcntl_async_signals')) {
- pcntl_async_signals(true);
-} else {
- declare(ticks = 1);
-}
-
-function __phutil_init_script__() {
+function __arcanist_init_script__() {
// Adjust the runtime language configuration to be reasonable and inline with
// expectations. We do this first, then load libraries.
@@ -88,6 +82,13 @@
require_once $root.'/src/init/init-library.php';
PhutilErrorHandler::initialize();
+
+ PhutilErrorHandler::initialize();
+
+ // If "variables_order" excludes "E", silently repair it so that $_ENV has
+ // the values we expect.
+ PhutilExecutionEnvironment::repairMissingVariablesOrder();
+
$router = PhutilSignalRouter::initialize();
$handler = new PhutilBacktraceSignalHandler();
@@ -97,4 +98,4 @@
$router->installHandler('phutil.winch', $handler);
}
-__phutil_init_script__();
+__arcanist_init_script__();
diff --git a/support/lib/extract-symbols.php b/support/lib/extract-symbols.php
--- a/support/lib/extract-symbols.php
+++ b/support/lib/extract-symbols.php
@@ -6,7 +6,7 @@
$builtins = phutil_symbols_get_builtins();
$root = dirname(dirname(dirname(__FILE__)));
-require_once $root.'/scripts/init/init-script.php';
+require_once $root.'/support/init/init-script.php';
$args = new PhutilArgumentParser($argv);
$args->setTagline(pht('identify symbols in a PHP source file'));
diff --git a/support/lib/rebuild-map.php b/support/lib/rebuild-map.php
--- a/support/lib/rebuild-map.php
+++ b/support/lib/rebuild-map.php
@@ -2,7 +2,7 @@
<?php
$root = dirname(dirname(dirname(__FILE__)));
-require_once $root.'/scripts/init/init-script.php';
+require_once $root.'/support/init/init-script.php';
$args = new PhutilArgumentParser($argv);
$args->setTagline(pht('rebuild the library map file'));
diff --git a/support/shell/hooks/bash-completion.sh b/support/shell/hooks/bash-completion.sh
new file mode 100644
--- /dev/null
+++ b/support/shell/hooks/bash-completion.sh
@@ -0,0 +1,9 @@
+SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
+
+# Try to generate the shell completion rules if they do not yet exist.
+if [ ! -f "${SCRIPTDIR}/bash-rules.sh" ]; then
+ arc shell-complete --generate >/dev/null 2>/dev/null
+fi;
+
+# Source the shell completion rules.
+source "${SCRIPTDIR}/../rules/bash-rules.sh"
diff --git a/support/shell/rules/.keep b/support/shell/rules/.keep
new file mode 100644
diff --git a/support/shell/templates/bash-template.sh b/support/shell/templates/bash-template.sh
new file mode 100644
--- /dev/null
+++ b/support/shell/templates/bash-template.sh
@@ -0,0 +1,22 @@
+_arcanist_complete_{{{BIN}}} ()
+{
+ COMPREPLY=()
+
+ RESULT=$(echo | {{{BIN}}} shell-complete \
+ --current ${COMP_CWORD} \
+ -- \
+ "${COMP_WORDS[@]}" \
+ 2>/dev/null)
+
+ if [ $? -ne 0 ]; then
+ return $?
+ fi
+
+ if [ "$RESULT" == "<compgen:file>" ]; then
+ RESULT=$( compgen -A file -- ${COMP_WORDS[COMP_CWORD]} )
+ fi
+
+ local IFS=$'\n'
+ COMPREPLY=( $RESULT )
+}
+complete -F _arcanist_complete_{{{BIN}}} -o filenames {{{BIN}}}
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,4 @@
+#!/usr/bin/env php
+<?php
+
+echo file_get_contents('php://stdin');
diff --git a/support/unit/echo.php b/support/unit/echo.php
new file mode 100755
--- /dev/null
+++ b/support/unit/echo.php
@@ -0,0 +1,9 @@
+#!/usr/bin/env php
+<?php
+
+$args = array_slice($argv, 1);
+foreach ($args as $key => $arg) {
+ $args[$key] = addcslashes($arg, "\\\n");
+}
+$args = implode("\n", $args);
+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,4 @@
+#!/usr/bin/env php
+<?php
+
+exit(0);
diff --git a/support/test/lock-file.php b/support/unit/lock.php
old mode 100644
new mode 100755
rename from support/test/lock-file.php
rename to support/unit/lock.php
--- a/support/test/lock-file.php
+++ b/support/unit/lock.php
@@ -1,7 +1,8 @@
#!/usr/bin/env php
<?php
-require_once dirname(__FILE__).'/../../scripts/init/init-script.php';
+$arcanist_root = dirname(dirname(dirname(__FILE__)));
+require_once $arcanist_root.'/support/init/init-script.php';
$args = new PhutilArgumentParser($argv);
$args->setTagline(pht('acquire and hold a lockfile'));
diff --git a/support/unit/sleep.php b/support/unit/sleep.php
new file mode 100755
--- /dev/null
+++ b/support/unit/sleep.php
@@ -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));
+}
diff --git a/support/xhpast/build-xhpast.php b/support/xhpast/build-xhpast.php
--- a/support/xhpast/build-xhpast.php
+++ b/support/xhpast/build-xhpast.php
@@ -2,7 +2,7 @@
<?php
$root = dirname(dirname(dirname(__FILE__)));
-require_once $root.'/scripts/init/init-script.php';
+require_once $root.'/support/init/init-script.php';
PhutilXHPASTBinary::build();

File Metadata

Mime Type
text/plain
Expires
Sat, Nov 23, 3:26 AM (16 h, 51 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6777007
Default Alt Text
D20988.diff (54 KB)

Event Timeline