mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-08 07:52:39 +01:00
Promote 2024.19 to stable
This commit is contained in:
commit
1eee3da56e
14 changed files with 67 additions and 126 deletions
|
@ -275,7 +275,7 @@ final class Filesystem extends Phobject {
|
||||||
$trap->destroy();
|
$trap->destroy();
|
||||||
|
|
||||||
if (!$ok) {
|
if (!$ok) {
|
||||||
if (strlen($err)) {
|
if ($err !== null && strlen($err)) {
|
||||||
throw new FilesystemException(
|
throw new FilesystemException(
|
||||||
$to,
|
$to,
|
||||||
pht(
|
pht(
|
||||||
|
@ -307,7 +307,7 @@ final class Filesystem extends Phobject {
|
||||||
* @task file
|
* @task file
|
||||||
*/
|
*/
|
||||||
public static function remove($path) {
|
public static function remove($path) {
|
||||||
if (!strlen($path)) {
|
if ($path == null || !strlen($path)) {
|
||||||
// Avoid removing PWD.
|
// Avoid removing PWD.
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
pht(
|
pht(
|
||||||
|
@ -673,7 +673,7 @@ final class Filesystem extends Phobject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we come back with an encoding, strip it off.
|
// If we come back with an encoding, strip it off.
|
||||||
if (strpos($mime_type, ';') !== false) {
|
if ($mime_type !== null && strpos($mime_type, ';') !== false) {
|
||||||
list($type, $encoding) = explode(';', $mime_type, 2);
|
list($type, $encoding) = explode(';', $mime_type, 2);
|
||||||
$mime_type = $type;
|
$mime_type = $type;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,8 @@ final class HTTPFutureHTTPResponseStatus extends HTTPFutureResponseStatus {
|
||||||
|
|
||||||
$content_type = BaseHTTPFuture::getHeader($headers, 'Content-Type');
|
$content_type = BaseHTTPFuture::getHeader($headers, 'Content-Type');
|
||||||
$match = null;
|
$match = null;
|
||||||
if (preg_match('/;\s*charset=([^;]+)/', $content_type, $match)) {
|
if (phutil_nonempty_string($content_type) &&
|
||||||
|
preg_match('/;\s*charset=([^;]+)/', $content_type, $match)) {
|
||||||
$encoding = trim($match[1], "\"'");
|
$encoding = trim($match[1], "\"'");
|
||||||
try {
|
try {
|
||||||
$excerpt = phutil_utf8_convert($excerpt, 'UTF-8', $encoding);
|
$excerpt = phutil_utf8_convert($excerpt, 'UTF-8', $encoding);
|
||||||
|
|
|
@ -18,7 +18,8 @@ final class PhutilMissingSymbolException extends Exception {
|
||||||
'moved, your library map may need to be rebuilt. You can rebuild '.
|
'moved, your library map may need to be rebuilt. You can rebuild '.
|
||||||
'the map by running "arc liberate".'.
|
'the map by running "arc liberate".'.
|
||||||
"\n\n".
|
"\n\n".
|
||||||
'For more information, see: https://phurl.io/u/newclasses',
|
'For more information, see: '.
|
||||||
|
'https://we.phorge.it/book/contrib/article/adding_new_classes/',
|
||||||
$symbol,
|
$symbol,
|
||||||
$type,
|
$type,
|
||||||
$reason);
|
$reason);
|
||||||
|
|
|
@ -332,11 +332,16 @@ final class PhutilLibraryMapBuilder extends Phobject {
|
||||||
'xmap' => array(),
|
'xmap' => array(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$type_translation = array(
|
||||||
|
'interface' => 'class',
|
||||||
|
'trait' => 'class',
|
||||||
|
);
|
||||||
|
|
||||||
// Detect duplicate symbols within the library.
|
// Detect duplicate symbols within the library.
|
||||||
foreach ($symbol_map as $file => $info) {
|
foreach ($symbol_map as $file => $info) {
|
||||||
foreach ($info['have'] as $type => $symbols) {
|
foreach ($info['have'] as $type => $symbols) {
|
||||||
foreach ($symbols as $symbol => $declaration) {
|
foreach ($symbols as $symbol => $declaration) {
|
||||||
$lib_type = ($type == 'interface') ? 'class' : $type;
|
$lib_type = idx($type_translation, $type, $type);
|
||||||
if (!empty($library_map[$lib_type][$symbol])) {
|
if (!empty($library_map[$lib_type][$symbol])) {
|
||||||
$prior = $library_map[$lib_type][$symbol];
|
$prior = $library_map[$lib_type][$symbol];
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
|
|
|
@ -187,6 +187,11 @@ final class ArcanistDiffParser extends Phobject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function parseDiff($diff) {
|
public function parseDiff($diff) {
|
||||||
|
// Remove leading UTF-8 Byte Order Mark (BOM)
|
||||||
|
if (substr($diff, 0, 3) == pack('CCC', 0xEF, 0xBB, 0xBF)) {
|
||||||
|
$diff = substr($diff, 3);
|
||||||
|
}
|
||||||
|
|
||||||
if (!strlen(trim($diff))) {
|
if (!strlen(trim($diff))) {
|
||||||
throw new Exception(pht("Can't parse an empty diff!"));
|
throw new Exception(pht("Can't parse an empty diff!"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -188,7 +188,7 @@ final class ArcanistRuntime {
|
||||||
tsprintf(
|
tsprintf(
|
||||||
'%s <__%s__>',
|
'%s <__%s__>',
|
||||||
pht('Learn More:'),
|
pht('Learn More:'),
|
||||||
'https://phurl.io/u/noninteractive'));
|
'https://secure.phabricator.com/T13491'));
|
||||||
|
|
||||||
throw new PhutilArgumentUsageException(
|
throw new PhutilArgumentUsageException(
|
||||||
pht('Missing required "--" in argument list.'));
|
pht('Missing required "--" in argument list.'));
|
||||||
|
|
|
@ -398,6 +398,12 @@ final class PhutilSymbolLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static function classLikeExists($name) {
|
||||||
|
return class_exists($name, false) ||
|
||||||
|
interface_exists($name, false) ||
|
||||||
|
trait_exists($name, false);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @task internal
|
* @task internal
|
||||||
*/
|
*/
|
||||||
|
@ -411,7 +417,7 @@ final class PhutilSymbolLoader {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (class_exists($name, false) || interface_exists($name, false)) {
|
if (self::classLikeExists($name)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -431,7 +437,7 @@ final class PhutilSymbolLoader {
|
||||||
$load_failed = pht('function');
|
$load_failed = pht('function');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!class_exists($name, false) && !interface_exists($name, false)) {
|
if (!self::classLikeExists($name)) {
|
||||||
$load_failed = pht('class or interface');
|
$load_failed = pht('class or interface');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ final class PhpunitTestEngine extends ArcanistUnitTestEngine {
|
||||||
if (!Filesystem::pathExists($test_path)) {
|
if (!Filesystem::pathExists($test_path)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$json_tmp = new TempFile();
|
$xml_tmp = new TempFile();
|
||||||
$clover_tmp = null;
|
$clover_tmp = null;
|
||||||
$clover = null;
|
$clover = null;
|
||||||
if ($this->getEnableCoverage() !== false) {
|
if ($this->getEnableCoverage() !== false) {
|
||||||
|
@ -64,10 +64,10 @@ final class PhpunitTestEngine extends ArcanistUnitTestEngine {
|
||||||
|
|
||||||
$stderr = '-d display_errors=stderr';
|
$stderr = '-d display_errors=stderr';
|
||||||
|
|
||||||
$futures[$test_path] = new ExecFuture('%C %C %C --log-json %s %C %s',
|
$futures[$test_path] = new ExecFuture('%C %C %C --log-junit %s %C %s',
|
||||||
$this->phpunitBinary, $config, $stderr, $json_tmp, $clover, $test_path);
|
$this->phpunitBinary, $config, $stderr, $xml_tmp, $clover, $test_path);
|
||||||
$tmpfiles[$test_path] = array(
|
$tmpfiles[$test_path] = array(
|
||||||
'json' => $json_tmp,
|
'xml' => $xml_tmp,
|
||||||
'clover' => $clover_tmp,
|
'clover' => $clover_tmp,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ final class PhpunitTestEngine extends ArcanistUnitTestEngine {
|
||||||
|
|
||||||
$results[] = $this->parseTestResults(
|
$results[] = $this->parseTestResults(
|
||||||
$test,
|
$test,
|
||||||
$tmpfiles[$test]['json'],
|
$tmpfiles[$test]['xml'],
|
||||||
$tmpfiles[$test]['clover'],
|
$tmpfiles[$test]['clover'],
|
||||||
$stderr);
|
$stderr);
|
||||||
}
|
}
|
||||||
|
@ -99,8 +99,8 @@ final class PhpunitTestEngine extends ArcanistUnitTestEngine {
|
||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
private function parseTestResults($path, $json_tmp, $clover_tmp, $stderr) {
|
private function parseTestResults($path, $xml_tmp, $clover_tmp, $stderr) {
|
||||||
$test_results = Filesystem::readFile($json_tmp);
|
$test_results = Filesystem::readFile($xml_tmp);
|
||||||
return id(new ArcanistPhpunitTestResultParser())
|
return id(new ArcanistPhpunitTestResultParser())
|
||||||
->setEnableCoverage($this->getEnableCoverage())
|
->setEnableCoverage($this->getEnableCoverage())
|
||||||
->setProjectRoot($this->projectRoot)
|
->setProjectRoot($this->projectRoot)
|
||||||
|
|
|
@ -25,80 +25,19 @@ final class ArcanistPhpunitTestResultParser extends ArcanistTestResultParser {
|
||||||
return array($result);
|
return array($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
$report = $this->getJsonReport($test_results);
|
|
||||||
|
|
||||||
// coverage is for all testcases in the executed $path
|
// coverage is for all testcases in the executed $path
|
||||||
$coverage = array();
|
$coverage = array();
|
||||||
if ($this->enableCoverage !== false) {
|
if ($this->enableCoverage !== false) {
|
||||||
$coverage = $this->readCoverage();
|
$coverage = $this->readCoverage();
|
||||||
}
|
}
|
||||||
|
|
||||||
$last_test_finished = true;
|
$xunit_result_parser = new ArcanistXUnitTestResultParser();
|
||||||
|
$results = $xunit_result_parser->parseTestResults($test_results);
|
||||||
|
|
||||||
$results = array();
|
foreach ($results as $result) {
|
||||||
foreach ($report as $event) {
|
|
||||||
switch (idx($event, 'event')) {
|
|
||||||
case 'test':
|
|
||||||
break;
|
|
||||||
case 'testStart':
|
|
||||||
$last_test_finished = false;
|
|
||||||
// fall through
|
|
||||||
default:
|
|
||||||
continue 2; // switch + loop
|
|
||||||
}
|
|
||||||
|
|
||||||
$status = ArcanistUnitTestResult::RESULT_PASS;
|
|
||||||
$user_data = '';
|
|
||||||
|
|
||||||
if ('fail' == idx($event, 'status')) {
|
|
||||||
$status = ArcanistUnitTestResult::RESULT_FAIL;
|
|
||||||
$user_data .= idx($event, 'message')."\n";
|
|
||||||
foreach (idx($event, 'trace') as $trace) {
|
|
||||||
$user_data .= sprintf(
|
|
||||||
"\n%s:%s",
|
|
||||||
idx($trace, 'file'),
|
|
||||||
idx($trace, 'line'));
|
|
||||||
}
|
|
||||||
} else if ('error' == idx($event, 'status')) {
|
|
||||||
if (strpos(idx($event, 'message'), 'Skipped Test') !== false) {
|
|
||||||
$status = ArcanistUnitTestResult::RESULT_SKIP;
|
|
||||||
$user_data .= idx($event, 'message');
|
|
||||||
} else if (strpos(
|
|
||||||
idx($event, 'message'),
|
|
||||||
'Incomplete Test') !== false) {
|
|
||||||
$status = ArcanistUnitTestResult::RESULT_SKIP;
|
|
||||||
$user_data .= idx($event, 'message');
|
|
||||||
} else {
|
|
||||||
$status = ArcanistUnitTestResult::RESULT_BROKEN;
|
|
||||||
$user_data .= idx($event, 'message');
|
|
||||||
foreach (idx($event, 'trace') as $trace) {
|
|
||||||
$user_data .= sprintf(
|
|
||||||
"\n%s:%s",
|
|
||||||
idx($trace, 'file'),
|
|
||||||
idx($trace, 'line'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$name = preg_replace('/ \(.*\)/s', '', idx($event, 'test'));
|
|
||||||
|
|
||||||
$result = new ArcanistUnitTestResult();
|
|
||||||
$result->setName($name);
|
|
||||||
$result->setResult($status);
|
|
||||||
$result->setDuration(idx($event, 'time'));
|
|
||||||
$result->setCoverage($coverage);
|
$result->setCoverage($coverage);
|
||||||
$result->setUserData($user_data);
|
|
||||||
|
|
||||||
$results[] = $result;
|
|
||||||
$last_test_finished = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$last_test_finished) {
|
|
||||||
$results[] = id(new ArcanistUnitTestResult())
|
|
||||||
->setName(idx($event, 'test')) // use last event
|
|
||||||
->setUserData($this->stderr)
|
|
||||||
->setResult(ArcanistUnitTestResult::RESULT_BROKEN);
|
|
||||||
}
|
|
||||||
return $results;
|
return $results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,28 +100,4 @@ final class ArcanistPhpunitTestResultParser extends ArcanistTestResultParser {
|
||||||
return $reports;
|
return $reports;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* We need this non-sense to make json generated by phpunit
|
|
||||||
* valid.
|
|
||||||
*
|
|
||||||
* @param string $json String containing JSON report
|
|
||||||
* @return array JSON decoded array
|
|
||||||
*/
|
|
||||||
private function getJsonReport($json) {
|
|
||||||
|
|
||||||
if (empty($json)) {
|
|
||||||
throw new Exception(
|
|
||||||
pht(
|
|
||||||
'JSON report file is empty, it probably means that phpunit '.
|
|
||||||
'failed to run tests. Try running %s with %s option and then run '.
|
|
||||||
'generated phpunit command yourself, you might get the answer.',
|
|
||||||
'arc unit',
|
|
||||||
'--trace'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$json = preg_replace('/}{\s*"/', '},{"', $json);
|
|
||||||
$json = '['.$json.']';
|
|
||||||
return phutil_json_decode($json);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ final class PhutilSortVector
|
||||||
}
|
}
|
||||||
|
|
||||||
public function addString($value) {
|
public function addString($value) {
|
||||||
if (strlen($value) && (strpos("\0", $value) !== false)) {
|
if (strlen($value) && (strpos($value, "\0") !== false)) {
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
pht(
|
pht(
|
||||||
'String components of a sort vector must not contain NULL bytes.'));
|
'String components of a sort vector must not contain NULL bytes.'));
|
||||||
|
|
|
@ -13,8 +13,9 @@
|
||||||
*
|
*
|
||||||
* id(new Thing())->doStuff();
|
* id(new Thing())->doStuff();
|
||||||
*
|
*
|
||||||
* @param wild Anything.
|
* @template T
|
||||||
* @return wild Unmodified argument.
|
* @param T $x Anything
|
||||||
|
* @return T Unmodified argument.
|
||||||
*/
|
*/
|
||||||
function id($x) {
|
function id($x) {
|
||||||
return $x;
|
return $x;
|
||||||
|
|
|
@ -1516,7 +1516,14 @@ abstract class ArcanistWorkflow extends Phobject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string|null $revision_id
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
final protected function normalizeRevisionID($revision_id) {
|
final protected function normalizeRevisionID($revision_id) {
|
||||||
|
if ($revision_id === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
return preg_replace('/^D/i', '', $revision_id);
|
return preg_replace('/^D/i', '', $revision_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -289,7 +289,7 @@ final class ArcanistWorkingCopyIdentity extends Phobject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function readLocalArcConfig() {
|
public function readLocalArcConfig() {
|
||||||
if (strlen($this->localMetaDir)) {
|
if ($this->localMetaDir !== null && strlen($this->localMetaDir)) {
|
||||||
$local_path = Filesystem::resolvePath('arc/config', $this->localMetaDir);
|
$local_path = Filesystem::resolvePath('arc/config', $this->localMetaDir);
|
||||||
|
|
||||||
$console = PhutilConsole::getConsole();
|
$console = PhutilConsole::getConsole();
|
||||||
|
|
|
@ -112,18 +112,6 @@ foreach ($namespaces as $namespace) {
|
||||||
$namespace, $path, pht('namespace `%s` statements', 'use'));
|
$namespace, $path, pht('namespace `%s` statements', 'use'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$possible_traits = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
|
|
||||||
foreach ($possible_traits as $possible_trait) {
|
|
||||||
$attributes = $possible_trait->getChildByIndex(0);
|
|
||||||
// Can't use getChildByIndex here because not all classes have attributes
|
|
||||||
foreach ($attributes->getChildren() as $attribute) {
|
|
||||||
if (strtolower($attribute->getConcreteString()) === 'trait') {
|
|
||||||
phutil_fail_on_unsupported_feature($possible_trait, $path, pht('traits'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// -( Marked Externals )------------------------------------------------------
|
// -( Marked Externals )------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@ -256,17 +244,29 @@ foreach ($calls as $call) {
|
||||||
|
|
||||||
// Find classes declared by this file.
|
// Find classes declared by this file.
|
||||||
|
|
||||||
|
|
||||||
// This is "class X ... { ... }".
|
// This is "class X ... { ... }".
|
||||||
$classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
|
function build_have_element_for_class_declaration(XHPASTNode $class_node) {
|
||||||
foreach ($classes as $class) {
|
$class_name = $class_node->getChildByIndex(1);
|
||||||
$class_name = $class->getChildByIndex(1);
|
|
||||||
$have[] = array(
|
$type = 'class';
|
||||||
'type' => 'class',
|
$attributes = $class_node->getChildByIndex(0);
|
||||||
|
foreach ($attributes->getChildren() as $attribute) {
|
||||||
|
if (strtolower($attribute->getConcreteString()) === 'trait') {
|
||||||
|
$type = 'trait';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'type' => $type,
|
||||||
'symbol' => $class_name,
|
'symbol' => $class_name,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$classes = $root->selectDescendantsOfType('n_CLASS_DECLARATION');
|
||||||
|
foreach ($classes as $class) {
|
||||||
|
$have[] = build_have_element_for_class_declaration($class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Find classes used by this file. We identify these:
|
// Find classes used by this file. We identify these:
|
||||||
//
|
//
|
||||||
|
|
Loading…
Reference in a new issue