Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14078365
D20988.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
54 KB
Referenced Files
None
Subscribers
None
D20988.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D20988: Merge utility/support changes from "wilds" to "master"
Attached
Detach File
Event Timeline
Log In to Comment