1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-08 16:02:39 +01:00

[Wilds] Rename "formatters" to "sinks" and restore the console output sufficiently to see which tests are failing

Summary:
Ref T13098. Since I plan to implement "send the results to Harbormaster" as another type of formatter/output/sink, just rename the objects which receive unit test results and print/write/transmit them into "Sinks" (in the sense of  Source/Sink).

Get the default console sink working well enough to see what's failing. As with all other changes in this series this is very rough, but the general idea is that I want to:

  - Let sinks stream both ongoing status information and final results.
  - For the console output, try to increase the signal-to-noise ratio of the output stream. Today, it's too easy to lose a failed test in the results. I want to improve this by outputting less frequently and summarizing passes ("93 tests passed.") so that the streaming output mostly shows failures and it's easier to make a decision to `^C` and revise if you see something you don't like.
  - Also, add a summary mode at the end which makes sure failures show up on the console and aren't scrolled up 30 pages. For now, this is quite rough.

Test Plan:
```
373 PASSED * 16 SKIPPED * 162 FAILED/BROKEN/UNSTABLE
```

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13098

Differential Revision: https://secure.phabricator.com/D19711
This commit is contained in:
epriestley 2018-09-26 08:30:44 -07:00
parent 493a5d1cc7
commit 59ef02d263
11 changed files with 300 additions and 76 deletions

View file

@ -165,7 +165,7 @@ phutil_register_library_map(array(
'ArcanistDeclarationParenthesesXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDeclarationParenthesesXHPASTLinterRuleTestCase.php', 'ArcanistDeclarationParenthesesXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDeclarationParenthesesXHPASTLinterRuleTestCase.php',
'ArcanistDefaultParametersXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDefaultParametersXHPASTLinterRule.php', 'ArcanistDefaultParametersXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDefaultParametersXHPASTLinterRule.php',
'ArcanistDefaultParametersXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDefaultParametersXHPASTLinterRuleTestCase.php', 'ArcanistDefaultParametersXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDefaultParametersXHPASTLinterRuleTestCase.php',
'ArcanistDefaultUnitFormatter' => 'unit/formatter/ArcanistDefaultUnitFormatter.php', 'ArcanistDefaultUnitSink' => 'unit/sink/ArcanistDefaultUnitSink.php',
'ArcanistDefaultsConfigurationSource' => 'config/source/ArcanistDefaultsConfigurationSource.php', 'ArcanistDefaultsConfigurationSource' => 'config/source/ArcanistDefaultsConfigurationSource.php',
'ArcanistDeprecationXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDeprecationXHPASTLinterRule.php', 'ArcanistDeprecationXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDeprecationXHPASTLinterRule.php',
'ArcanistDeprecationXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDeprecationXHPASTLinterRuleTestCase.php', 'ArcanistDeprecationXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDeprecationXHPASTLinterRuleTestCase.php',
@ -279,7 +279,7 @@ phutil_register_library_map(array(
'ArcanistJSONLintRenderer' => 'lint/renderer/ArcanistJSONLintRenderer.php', 'ArcanistJSONLintRenderer' => 'lint/renderer/ArcanistJSONLintRenderer.php',
'ArcanistJSONLinter' => 'lint/linter/ArcanistJSONLinter.php', 'ArcanistJSONLinter' => 'lint/linter/ArcanistJSONLinter.php',
'ArcanistJSONLinterTestCase' => 'lint/linter/__tests__/ArcanistJSONLinterTestCase.php', 'ArcanistJSONLinterTestCase' => 'lint/linter/__tests__/ArcanistJSONLinterTestCase.php',
'ArcanistJSONUnitFormatter' => 'unit/formatter/ArcanistJSONUnitFormatter.php', 'ArcanistJSONUnitSink' => 'unit/sink/ArcanistJSONUnitSink.php',
'ArcanistJscsLinter' => 'lint/linter/ArcanistJscsLinter.php', 'ArcanistJscsLinter' => 'lint/linter/ArcanistJscsLinter.php',
'ArcanistJscsLinterTestCase' => 'lint/linter/__tests__/ArcanistJscsLinterTestCase.php', 'ArcanistJscsLinterTestCase' => 'lint/linter/__tests__/ArcanistJscsLinterTestCase.php',
'ArcanistKeywordCasingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistKeywordCasingXHPASTLinterRule.php', 'ArcanistKeywordCasingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistKeywordCasingXHPASTLinterRule.php',
@ -473,9 +473,9 @@ phutil_register_library_map(array(
'ArcanistUnexpectedReturnValueXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistUnexpectedReturnValueXHPASTLinterRuleTestCase.php', 'ArcanistUnexpectedReturnValueXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistUnexpectedReturnValueXHPASTLinterRuleTestCase.php',
'ArcanistUnitConsoleRenderer' => 'unit/renderer/ArcanistUnitConsoleRenderer.php', 'ArcanistUnitConsoleRenderer' => 'unit/renderer/ArcanistUnitConsoleRenderer.php',
'ArcanistUnitEngine' => 'unit/engine/ArcanistUnitEngine.php', 'ArcanistUnitEngine' => 'unit/engine/ArcanistUnitEngine.php',
'ArcanistUnitFormatter' => 'unit/formatter/ArcanistUnitFormatter.php',
'ArcanistUnitOverseer' => 'unit/overseer/ArcanistUnitOverseer.php', 'ArcanistUnitOverseer' => 'unit/overseer/ArcanistUnitOverseer.php',
'ArcanistUnitRenderer' => 'unit/renderer/ArcanistUnitRenderer.php', 'ArcanistUnitRenderer' => 'unit/renderer/ArcanistUnitRenderer.php',
'ArcanistUnitSink' => 'unit/sink/ArcanistUnitSink.php',
'ArcanistUnitTestResult' => 'unit/ArcanistUnitTestResult.php', 'ArcanistUnitTestResult' => 'unit/ArcanistUnitTestResult.php',
'ArcanistUnitTestResultTestCase' => 'unit/__tests__/ArcanistUnitTestResultTestCase.php', 'ArcanistUnitTestResultTestCase' => 'unit/__tests__/ArcanistUnitTestResultTestCase.php',
'ArcanistUnitTestableLintEngine' => 'lint/engine/ArcanistUnitTestableLintEngine.php', 'ArcanistUnitTestableLintEngine' => 'lint/engine/ArcanistUnitTestableLintEngine.php',
@ -1276,7 +1276,7 @@ phutil_register_library_map(array(
'ArcanistDeclarationParenthesesXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistDeclarationParenthesesXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistDefaultParametersXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistDefaultParametersXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistDefaultParametersXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistDefaultParametersXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistDefaultUnitFormatter' => 'ArcanistUnitFormatter', 'ArcanistDefaultUnitSink' => 'ArcanistUnitSink',
'ArcanistDefaultsConfigurationSource' => 'ArcanistDictionaryConfigurationSource', 'ArcanistDefaultsConfigurationSource' => 'ArcanistDictionaryConfigurationSource',
'ArcanistDeprecationXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistDeprecationXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistDeprecationXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistDeprecationXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
@ -1390,7 +1390,7 @@ phutil_register_library_map(array(
'ArcanistJSONLintRenderer' => 'ArcanistLintRenderer', 'ArcanistJSONLintRenderer' => 'ArcanistLintRenderer',
'ArcanistJSONLinter' => 'ArcanistLinter', 'ArcanistJSONLinter' => 'ArcanistLinter',
'ArcanistJSONLinterTestCase' => 'ArcanistLinterTestCase', 'ArcanistJSONLinterTestCase' => 'ArcanistLinterTestCase',
'ArcanistJSONUnitFormatter' => 'ArcanistUnitFormatter', 'ArcanistJSONUnitSink' => 'ArcanistUnitSink',
'ArcanistJscsLinter' => 'ArcanistExternalLinter', 'ArcanistJscsLinter' => 'ArcanistExternalLinter',
'ArcanistJscsLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistJscsLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistKeywordCasingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistKeywordCasingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
@ -1584,9 +1584,9 @@ phutil_register_library_map(array(
'ArcanistUnexpectedReturnValueXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistUnexpectedReturnValueXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistUnitConsoleRenderer' => 'ArcanistUnitRenderer', 'ArcanistUnitConsoleRenderer' => 'ArcanistUnitRenderer',
'ArcanistUnitEngine' => 'Phobject', 'ArcanistUnitEngine' => 'Phobject',
'ArcanistUnitFormatter' => 'Phobject',
'ArcanistUnitOverseer' => 'Phobject', 'ArcanistUnitOverseer' => 'Phobject',
'ArcanistUnitRenderer' => 'Phobject', 'ArcanistUnitRenderer' => 'Phobject',
'ArcanistUnitSink' => 'Phobject',
'ArcanistUnitTestResult' => 'Phobject', 'ArcanistUnitTestResult' => 'Phobject',
'ArcanistUnitTestResultTestCase' => 'PhutilTestCase', 'ArcanistUnitTestResultTestCase' => 'PhutilTestCase',
'ArcanistUnitTestableLintEngine' => 'ArcanistLintEngine', 'ArcanistUnitTestableLintEngine' => 'ArcanistLintEngine',

View file

@ -175,13 +175,8 @@ final class ArcanistUnitTestResult extends Phobject {
} }
public static function getAllResultCodes() { public static function getAllResultCodes() {
return array( $map = self::getResultCodeSpecs();
self::RESULT_PASS, return array_keys($map);
self::RESULT_FAIL,
self::RESULT_SKIP,
self::RESULT_BROKEN,
self::RESULT_UNSOUND,
);
} }
public static function getResultCodeName($result_code) { public static function getResultCodeName($result_code) {
@ -205,31 +200,56 @@ final class ArcanistUnitTestResult extends Phobject {
return idx($specs, $result_code); return idx($specs, $result_code);
} }
public function getANSIColor() {
$spec = $this->getResultMap();
return idx($spec, 'color.ansi', 'red');
}
public function getResultLabel() {
$spec = $this->getResultMap();
return idx($spec, 'label', $this->getResult());
}
private function getResultMap() {
$map = self::getResultCodeSpecs();
return idx($map, $this->getResult(), array());
}
private static function getResultCodeSpecs() { private static function getResultCodeSpecs() {
return array( return array(
self::RESULT_PASS => array( self::RESULT_PASS => array(
'name' => pht('Pass'), 'name' => pht('Pass'),
'label' => pht('PASS'),
'color.ansi' => 'green',
'description' => pht( 'description' => pht(
'The test passed.'), 'The test passed.'),
), ),
self::RESULT_FAIL => array( self::RESULT_FAIL => array(
'name' => pht('Fail'), 'name' => pht('Fail'),
'label' => pht('FAIL'),
'color.ansi' => 'red',
'description' => pht( 'description' => pht(
'The test failed.'), 'The test failed.'),
), ),
self::RESULT_SKIP => array( self::RESULT_SKIP => array(
'name' => pht('Skip'), 'name' => pht('Skip'),
'label' => pht('SKIP'),
'color.ansi' => 'cyan',
'description' => pht( 'description' => pht(
'The test was not executed.'), 'The test was not executed.'),
), ),
self::RESULT_BROKEN => array( self::RESULT_BROKEN => array(
'name' => pht('Broken'), 'name' => pht('Broken'),
'label' => pht('BROKEN'),
'color.ansi' => 'red',
'description' => pht( 'description' => pht(
'The test failed in an abnormal or severe way. For example, the '. 'The test failed in an abnormal or severe way. For example, the '.
'harness crashed instead of reporting a failure.'), 'harness crashed instead of reporting a failure.'),
), ),
self::RESULT_UNSOUND => array( self::RESULT_UNSOUND => array(
'name' => pht('Unsound'), 'name' => pht('Unsound'),
'label' => pht('UNSOUND'),
'color.ansi' => 'yellow',
'description' => pht( 'description' => pht(
'The test failed, but this change is probably not what broke it. '. 'The test failed, but this change is probably not what broke it. '.
'For example, it might have already been failing.'), 'For example, it might have already been failing.'),
@ -237,5 +257,15 @@ final class ArcanistUnitTestResult extends Phobject {
); );
} }
public function getDisplayName() {
$name = $this->getName();
$namespace = $this->getNamespace();
if (strlen($namespace)) {
$name = $namespace.'::'.$name;
}
return $name;
}
} }

View file

@ -56,14 +56,7 @@ abstract class ArcanistUnitEngine
abstract public function runTests(); abstract public function runTests();
final protected function didRunTests(array $tests) { final protected function didRunTests(array $tests) {
assert_instances_of($tests, 'ArcanistUnitTestResult'); return $this->getOverseer()->didRunTests($tests);
// TOOLSETS: Pass this stuff to result output so it can print progress or
// stream results.
foreach ($tests as $test) {
echo "Ran Test: ".$test->getNamespace().'::'.$test->getName()."\n";
}
} }
} }

View file

@ -1,9 +0,0 @@
<?php
final class ArcanistDefaultUnitFormatter
extends ArcanistUnitFormatter {
const FORMATTER_KEY = 'default';
}

View file

@ -1,9 +0,0 @@
<?php
final class ArcanistJSONUnitFormatter
extends ArcanistUnitFormatter {
const FORMATTER_KEY = 'json';
}

View file

@ -1,17 +0,0 @@
<?php
abstract class ArcanistUnitFormatter
extends Phobject {
final public function getUnitFormatterKey() {
return $this->getPhobjectClassConstant('FORMATTER_KEY');
}
public static function getAllUnitFormatters() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getUnitFormatterKey')
->execute();
}
}

View file

@ -5,9 +5,9 @@ final class ArcanistUnitOverseer
private $directory; private $directory;
private $paths = array(); private $paths = array();
private $formatter; private $sinks = array();
public function setPaths($paths) { public function setPaths(array $paths) {
$this->paths = $paths; $this->paths = $paths;
return $this; return $this;
} }
@ -16,13 +16,14 @@ final class ArcanistUnitOverseer
return $this->paths; return $this->paths;
} }
public function setFormatter(ArcanistUnitFormatter $formatter) { public function setSinks(array $sinks) {
$this->formatter = $formatter; assert_instances_of($sinks, 'ArcanistUnitSink');
$this->sinks = $sinks;
return $this; return $this;
} }
public function getFormatter() { public function getSinks() {
return $this->formatter; return $this->sinks;
} }
public function setDirectory($directory) { public function setDirectory($directory) {
@ -50,9 +51,27 @@ final class ArcanistUnitOverseer
} }
} }
$this->didCompleteTests($results);
return $results; return $results;
} }
public function didRunTests(array $tests) {
assert_instances_of($tests, 'ArcanistUnitTestResult');
foreach ($this->getSinks() as $sink) {
$sink->sinkPartialResults($tests);
}
}
private function didCompleteTests(array $tests) {
assert_instances_of($tests, 'ArcanistUnitTestResult');
foreach ($this->getSinks() as $sink) {
$sink->sinkFinalResults($tests);
}
}
private function loadEngines() { private function loadEngines() {
$root = $this->getDirectory(); $root = $this->getDirectory();

View file

@ -0,0 +1,169 @@
<?php
final class ArcanistDefaultUnitSink
extends ArcanistUnitSink {
const SINKKEY = 'default';
private $lastUpdateTime;
private $buffer = array();
public function sinkPartialResults(array $results) {
// We want to show the user both regular progress reports and make sure
// that important results aren't scrolled off screen. We'll print a summary
// at the end so it's not critical that users can never miss important
// results, but they may (for example) want to ^C early if tests fail and
// their fate is sealed.
$failed = array();
foreach ($results as $result) {
$result_code = $result->getResult();
switch ($result_code) {
case ArcanistUnitTestResult::RESULT_PASS:
case ArcanistUnitTestResult::RESULT_SKIP:
break;
default:
$failed[] = $result;
break;
}
}
$now = microtime(true);
if (!$failed) {
if ($this->lastUpdateTime) {
$delay = 1;
if (($now - $this->lastUpdateTime) < $delay) {
$this->buffer[] = $results;
return;
}
}
}
$this->buffer[] = $results;
$results = array_mergev($this->buffer);
$this->buffer = array();
$pass_count = 0;
$skip_count = 0;
$failed = array();
foreach ($results as $result) {
$result_code = $result->getResult();
switch ($result_code) {
case ArcanistUnitTestResult::RESULT_PASS:
$pass_count++;
break;
case ArcanistUnitTestResult::RESULT_SKIP:
$skip_count++;
break;
default:
$failed[] = $result;
break;
}
}
if ($pass_count) {
echo tsprintf(
"%s\n",
pht('%s tests passed.', $pass_count));
}
if ($skip_count) {
echo tsprintf(
"%s\n",
pht('%s tests skipped.', $skip_count));
}
foreach ($failed as $result) {
echo $this->getDisplayForTest($result, false);
}
$this->lastUpdateTime = $now;
return $this;
}
public function sinkFinalResults(array $results) {
$passed = array();
$skipped = array();
$failed = array();
foreach ($results as $result) {
$result_code = $result->getResult();
switch ($result_code) {
case ArcanistUnitTestResult::RESULT_PASS:
$passed[] = $result;
break;
case ArcanistUnitTestResult::RESULT_SKIP:
$skipped[] = $result;
break;
default:
$failed[] = $result;
break;
}
}
echo tsprintf(
"%s\n",
pht('RESULT SUMMARY'));
if ($skipped) {
echo tsprintf(
"%s\n",
pht('SKIPPED TESTS'));
foreach ($skipped as $result) {
echo $this->getDisplayForTest($result);
}
}
if ($failed) {
echo tsprintf(
"%s\n",
pht('FAILED TESTS'));
foreach ($failed as $result) {
echo $this->getDisplayForTest($result);
}
}
echo tsprintf(
"**<bg:red> ~~~ %s </bg>**\n",
pht(
"%s PASSED * %s SKIPPED * %s FAILED/BROKEN/UNSTABLE",
phutil_count($passed),
phutil_count($skipped),
phutil_count($failed)));
}
private function getDisplayForTest(ArcanistUnitTestResult $result) {
$color = $result->getANSIColor();
$status = $result->getResultLabel();
$name = $result->getDisplayName();
// TOOLSETS: Restore timing information.
$timing = ' ';
$output = tsprintf(
"**<bg:".$color."> %s </bg>** %s %s\n",
$status,
$timing,
$name);
$user_data = $result->getUserData();
if (strlen($user_data)) {
$output = tsprintf(
"%s%B\n",
$output,
$user_data);
}
return $output;
}
}

View file

@ -0,0 +1,9 @@
<?php
final class ArcanistJSONUnitSink
extends ArcanistUnitSink {
const SINKKEY = 'json';
}

View file

@ -0,0 +1,31 @@
<?php
abstract class ArcanistUnitSink
extends Phobject {
private $results;
final public function getUnitSinkKey() {
return $this->getPhobjectClassConstant('SINKKEY');
}
public static function getAllUnitSinks() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getUnitSinkKey')
->execute();
}
public function sinkPartialResults(array $results) {
return $this;
}
public function sinkFinalResults(array $results) {
return $this;
}
public function getOutput() {
return null;
}
}

View file

@ -23,7 +23,7 @@ EOTEXT
return array( return array(
$this->newWorkflowArgument('commit') $this->newWorkflowArgument('commit')
->setParameter('commit'), ->setParameter('commit'),
$this->newWorkflowArgument('format') $this->newWorkflowArgument('sink')
->setParameter('format'), ->setParameter('format'),
$this->newWorkflowArgument('everything'), $this->newWorkflowArgument('everything'),
$this->newWorkflowArgument('paths') $this->newWorkflowArgument('paths')
@ -51,32 +51,40 @@ EOTEXT
// though it is "arc unit --everything", and ignoring the "--commit" flag // though it is "arc unit --everything", and ignoring the "--commit" flag
// and "paths" arguments. // and "paths" arguments.
$formatter = $this->newUnitFormatter(); $sinks = array();
$overseer->setFormatter($formatter); $sinks[] = $this->newUnitSink();
$overseer->setSinks($sinks);
$overseer->execute(); $overseer->execute();
foreach ($sinks as $sink) {
$result = $sink->getOutput();
if ($result !== null) {
echo $result;
}
}
return 0; return 0;
} }
private function newUnitFormatter() { private function newUnitSink() {
$formatters = ArcanistUnitFormatter::getAllUnitFormatters(); $sinks = ArcanistUnitSink::getAllUnitSinks();
$format_key = $this->getArgument('format'); $sink_key = $this->getArgument('sink');
if (!strlen($format_key)) { if (!strlen($sink_key)) {
$format_key = ArcanistDefaultUnitFormatter::FORMATTER_KEY; $sink_key = ArcanistDefaultUnitSink::SINKKEY;
} }
$formatter = idx($formatters, $format_key); $sink = idx($sinks, $sink_key);
if (!$formatter) { if (!$sink) {
throw new ArcanistUsageException( throw new ArcanistUsageException(
pht( pht(
'Unit test output format ("%s") is unknown. Supported formats '. 'Unit test output sink ("%s") is unknown. Supported sinks '.
'are: %s.', 'are: %s.',
$format_key, $sink_key,
implode(', ', array_keys($formatters)))); implode(', ', array_keys($sinks))));
} }
return $formatter; return $sink;
} }
} }