mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-25 16:22:42 +01:00
Render unit results progressively
Summary: Some tests take longer (fixtures usually around 1 second for me) and also FB runs all tests on deploy. I want to see all results immediately. Test Plan: Added `usleep(200000)` to `resultTest()`, then: $ arc unit Saw results printed one by one. Also didn't pass `$renderer` to `ArcanistPhutilTestCase` and saw empty output. Reviewers: epriestley Reviewed By: epriestley CC: aran, Korvin Differential Revision: https://secure.phabricator.com/D5141
This commit is contained in:
parent
8412f7cfd7
commit
2e87419c7b
8 changed files with 128 additions and 66 deletions
|
@ -139,6 +139,8 @@ phutil_register_library_map(array(
|
|||
'ArcanistTextLinterTestCase' => 'lint/linter/__tests__/ArcanistTextLinterTestCase.php',
|
||||
'ArcanistTodoWorkflow' => 'workflow/ArcanistTodoWorkflow.php',
|
||||
'ArcanistUncommittedChangesException' => 'exception/usage/ArcanistUncommittedChangesException.php',
|
||||
'ArcanistUnitConsoleRenderer' => 'unit/renderer/ArcanistUnitConsoleRenderer.php',
|
||||
'ArcanistUnitRenderer' => 'unit/renderer/ArcanistUnitRenderer.php',
|
||||
'ArcanistUnitTestResult' => 'unit/ArcanistUnitTestResult.php',
|
||||
'ArcanistUnitWorkflow' => 'workflow/ArcanistUnitWorkflow.php',
|
||||
'ArcanistUpgradeWorkflow' => 'workflow/ArcanistUpgradeWorkflow.php',
|
||||
|
@ -271,6 +273,7 @@ phutil_register_library_map(array(
|
|||
'ArcanistTextLinterTestCase' => 'ArcanistArcanistLinterTestCase',
|
||||
'ArcanistTodoWorkflow' => 'ArcanistBaseWorkflow',
|
||||
'ArcanistUncommittedChangesException' => 'ArcanistUsageException',
|
||||
'ArcanistUnitConsoleRenderer' => 'ArcanistUnitRenderer',
|
||||
'ArcanistUnitWorkflow' => 'ArcanistBaseWorkflow',
|
||||
'ArcanistUpgradeWorkflow' => 'ArcanistBaseWorkflow',
|
||||
'ArcanistUploadWorkflow' => 'ArcanistBaseWorkflow',
|
||||
|
|
|
@ -50,18 +50,6 @@ final class ArcanistUnitTestResult {
|
|||
return $this->result;
|
||||
}
|
||||
|
||||
public function getConsoleFormattedResult() {
|
||||
static $status_codes = array(
|
||||
self::RESULT_PASS => '<bg:green>** PASS **</bg>',
|
||||
self::RESULT_FAIL => '<bg:red>** FAIL **</bg>',
|
||||
self::RESULT_SKIP => '<bg:yellow>** SKIP **</bg>',
|
||||
self::RESULT_BROKEN => '<bg:red>** BROKEN **</bg>',
|
||||
self::RESULT_UNSOUND => '<bg:yellow>** UNSOUND **</bg>',
|
||||
self::RESULT_POSTPONED => '<bg:yellow>** POSTPONED **</bg>',
|
||||
);
|
||||
return phutil_console_format($status_codes[$this->result]);
|
||||
}
|
||||
|
||||
public function setDuration($duration) {
|
||||
$this->duration = $duration;
|
||||
return $this;
|
||||
|
|
|
@ -14,6 +14,7 @@ abstract class ArcanistBaseUnitTestEngine {
|
|||
private $enableAsyncTests;
|
||||
private $enableCoverage;
|
||||
private $runAllTests;
|
||||
protected $renderer;
|
||||
|
||||
|
||||
public function setRunAllTests($run_all_tests) {
|
||||
|
@ -86,6 +87,11 @@ abstract class ArcanistBaseUnitTestEngine {
|
|||
return $this->enableCoverage;
|
||||
}
|
||||
|
||||
public function setRenderer(ArcanistUnitRenderer $renderer) {
|
||||
$this->renderer = $renderer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
abstract public function run();
|
||||
|
||||
/**
|
||||
|
|
|
@ -45,6 +45,9 @@ final class PhutilUnitTestEngine extends ArcanistBaseUnitTestEngine {
|
|||
if ($this->getPaths()) {
|
||||
$test_case->setPaths($this->getPaths());
|
||||
}
|
||||
if ($this->renderer) {
|
||||
$test_case->setRenderer($this->renderer);
|
||||
}
|
||||
$results[] = $test_case->run();
|
||||
}
|
||||
|
||||
|
@ -169,4 +172,8 @@ final class PhutilUnitTestEngine extends ArcanistBaseUnitTestEngine {
|
|||
return $run_tests;
|
||||
}
|
||||
|
||||
public function shouldEchoTestResults() {
|
||||
return !$this->renderer;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ abstract class ArcanistPhutilTestCase {
|
|||
private $coverage = array();
|
||||
private $projectRoot;
|
||||
private $paths;
|
||||
private $renderer;
|
||||
|
||||
|
||||
/* -( Making Test Assertions )--------------------------------------------- */
|
||||
|
@ -372,6 +373,10 @@ abstract class ArcanistPhutilTestCase {
|
|||
$result->setDuration(microtime(true) - $this->testStartTime);
|
||||
$result->setUserData($reason);
|
||||
$this->results[] = $result;
|
||||
|
||||
if ($this->renderer) {
|
||||
echo $this->renderer->renderUnitResult($result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -530,4 +535,9 @@ abstract class ArcanistPhutilTestCase {
|
|||
return null;
|
||||
}
|
||||
|
||||
public function setRenderer(ArcanistUnitRenderer $renderer) {
|
||||
$this->renderer = $renderer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
85
src/unit/renderer/ArcanistUnitConsoleRenderer.php
Normal file
85
src/unit/renderer/ArcanistUnitConsoleRenderer.php
Normal file
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @group unit
|
||||
*/
|
||||
final class ArcanistUnitConsoleRenderer extends ArcanistUnitRenderer {
|
||||
|
||||
public function renderUnitResult(ArcanistUnitTestResult $result) {
|
||||
$result_code = $result->getResult();
|
||||
|
||||
$duration = '';
|
||||
if ($result_code == ArcanistUnitTestResult::RESULT_PASS) {
|
||||
$duration = ' '.$this->formatTestDuration($result->getDuration());
|
||||
}
|
||||
$return = sprintf(
|
||||
" %s %s\n",
|
||||
$this->getFormattedResult($result->getResult()).$duration,
|
||||
$result->getName());
|
||||
|
||||
if ($result_code != ArcanistUnitTestResult::RESULT_PASS) {
|
||||
$return .= $result->getUserData()."\n";
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
public function renderPostponedResult($count) {
|
||||
return sprintf(
|
||||
"%s %s\n",
|
||||
$this->getFormattedResult(ArcanistUnitTestResult::RESULT_POSTPONED),
|
||||
pht('%d test(s)', $count));
|
||||
}
|
||||
|
||||
private function getFormattedResult($result) {
|
||||
static $status_codes = array(
|
||||
ArcanistUnitTestResult::RESULT_PASS => '<bg:green>** PASS **</bg>',
|
||||
ArcanistUnitTestResult::RESULT_FAIL => '<bg:red>** FAIL **</bg>',
|
||||
ArcanistUnitTestResult::RESULT_SKIP => '<bg:yellow>** SKIP **</bg>',
|
||||
ArcanistUnitTestResult::RESULT_BROKEN => '<bg:red>** BROKEN **</bg>',
|
||||
ArcanistUnitTestResult::RESULT_UNSOUND => '<bg:yellow>** UNSOUND **</bg>',
|
||||
ArcanistUnitTestResult::RESULT_POSTPONED =>
|
||||
'<bg:yellow>** POSTPONED **</bg>',
|
||||
);
|
||||
return phutil_console_format($status_codes[$result]);
|
||||
}
|
||||
|
||||
private function formatTestDuration($seconds) {
|
||||
// Very carefully define inclusive upper bounds on acceptable unit test
|
||||
// durations. Times are in milliseconds and are in increasing order.
|
||||
$acceptableness = array(
|
||||
50 => "<fg:green>%s</fg><fg:yellow>\xE2\x98\x85</fg> ",
|
||||
200 => '<fg:green>%s</fg> ',
|
||||
500 => '<fg:yellow>%s</fg> ',
|
||||
INF => '<fg:red>%s</fg> ',
|
||||
);
|
||||
|
||||
$milliseconds = $seconds * 1000;
|
||||
$duration = $this->formatTime($seconds);
|
||||
foreach ($acceptableness as $upper_bound => $formatting) {
|
||||
if ($milliseconds <= $upper_bound) {
|
||||
return phutil_console_format($formatting, $duration);
|
||||
}
|
||||
}
|
||||
return phutil_console_format(end($acceptableness), $duration);
|
||||
}
|
||||
|
||||
private function formatTime($seconds) {
|
||||
if ($seconds >= 60) {
|
||||
$minutes = floor($seconds / 60);
|
||||
return sprintf('%dm%02ds', $minutes, round($seconds % 60));
|
||||
}
|
||||
|
||||
if ($seconds >= 1) {
|
||||
return sprintf('%4.1fs', $seconds);
|
||||
}
|
||||
|
||||
$milliseconds = $seconds * 1000;
|
||||
if ($milliseconds >= 1) {
|
||||
return sprintf('%3dms', round($milliseconds));
|
||||
}
|
||||
|
||||
return ' <1ms';
|
||||
}
|
||||
|
||||
}
|
11
src/unit/renderer/ArcanistUnitRenderer.php
Normal file
11
src/unit/renderer/ArcanistUnitRenderer.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @group unit
|
||||
*/
|
||||
abstract class ArcanistUnitRenderer {
|
||||
|
||||
abstract public function renderUnitResult(ArcanistUnitTestResult $result);
|
||||
abstract public function renderPostponedResult($count);
|
||||
|
||||
}
|
|
@ -138,6 +138,9 @@ EOTEXT
|
|||
}
|
||||
$this->engine->setArguments($this->getPassthruArgumentsAsMap('unit'));
|
||||
|
||||
$renderer = new ArcanistUnitConsoleRenderer();
|
||||
$this->engine->setRenderer($renderer);
|
||||
|
||||
$enable_coverage = null; // Means "default".
|
||||
if ($this->getArgument('coverage') ||
|
||||
$this->getArgument('detailed-coverage')) {
|
||||
|
@ -175,19 +178,9 @@ EOTEXT
|
|||
$unresolved[] = $result;
|
||||
} else {
|
||||
if ($this->engine->shouldEchoTestResults()) {
|
||||
$duration = '';
|
||||
if ($result_code == ArcanistUnitTestResult::RESULT_PASS) {
|
||||
$duration = ' '.self::formatTestDuration($result->getDuration());
|
||||
}
|
||||
$console->writeOut(
|
||||
" %s %s\n",
|
||||
$result->getConsoleFormattedResult().$duration,
|
||||
$result->getName());
|
||||
$console->writeOut('%s', $renderer->renderUnitResult($result));
|
||||
}
|
||||
if ($result_code != ArcanistUnitTestResult::RESULT_PASS) {
|
||||
if ($this->engine->shouldEchoTestResults()) {
|
||||
$console->writeOut("%s\n", $result->getUserData());
|
||||
}
|
||||
$unresolved[] = $result;
|
||||
}
|
||||
}
|
||||
|
@ -198,12 +191,9 @@ EOTEXT
|
|||
}
|
||||
}
|
||||
if ($postponed_count) {
|
||||
$postponed = id(new ArcanistUnitTestResult())
|
||||
->setResult(ArcanistUnitTestResult::RESULT_POSTPONED);
|
||||
$console->writeOut(
|
||||
"%s %s\n",
|
||||
$postponed->getConsoleFormattedResult(),
|
||||
pht('%d test(s)', $postponed_count));
|
||||
'%s',
|
||||
$renderer->renderPostponedResult($postponed_count));
|
||||
}
|
||||
|
||||
if ($coverage) {
|
||||
|
@ -286,44 +276,6 @@ EOTEXT
|
|||
return $this->testResults;
|
||||
}
|
||||
|
||||
private static function formatTestDuration($seconds) {
|
||||
// Very carefully define inclusive upper bounds on acceptable unit test
|
||||
// durations. Times are in milliseconds and are in increasing order.
|
||||
$acceptableness = array(
|
||||
50 => "<fg:green>%s</fg><fg:yellow>\xE2\x98\x85</fg> ",
|
||||
200 => '<fg:green>%s</fg> ',
|
||||
500 => '<fg:yellow>%s</fg> ',
|
||||
INF => '<fg:red>%s</fg> ',
|
||||
);
|
||||
|
||||
$milliseconds = $seconds * 1000;
|
||||
$duration = self::formatTime($seconds);
|
||||
foreach ($acceptableness as $upper_bound => $formatting) {
|
||||
if ($milliseconds <= $upper_bound) {
|
||||
return phutil_console_format($formatting, $duration);
|
||||
}
|
||||
}
|
||||
return phutil_console_format(end($acceptableness), $duration);
|
||||
}
|
||||
|
||||
private static function formatTime($seconds) {
|
||||
if ($seconds >= 60) {
|
||||
$minutes = floor($seconds / 60);
|
||||
return sprintf('%dm%02ds', $minutes, round($seconds % 60));
|
||||
}
|
||||
|
||||
if ($seconds >= 1) {
|
||||
return sprintf('%4.1fs', $seconds);
|
||||
}
|
||||
|
||||
$milliseconds = $seconds * 1000;
|
||||
if ($milliseconds >= 1) {
|
||||
return sprintf('%3dms', round($milliseconds));
|
||||
}
|
||||
|
||||
return ' <1ms';
|
||||
}
|
||||
|
||||
private function renderDetailedCoverageReport($data, $report) {
|
||||
$data = explode("\n", $data);
|
||||
|
||||
|
|
Loading…
Reference in a new issue