mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-12-01 19:22:41 +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',
|
'ArcanistTextLinterTestCase' => 'lint/linter/__tests__/ArcanistTextLinterTestCase.php',
|
||||||
'ArcanistTodoWorkflow' => 'workflow/ArcanistTodoWorkflow.php',
|
'ArcanistTodoWorkflow' => 'workflow/ArcanistTodoWorkflow.php',
|
||||||
'ArcanistUncommittedChangesException' => 'exception/usage/ArcanistUncommittedChangesException.php',
|
'ArcanistUncommittedChangesException' => 'exception/usage/ArcanistUncommittedChangesException.php',
|
||||||
|
'ArcanistUnitConsoleRenderer' => 'unit/renderer/ArcanistUnitConsoleRenderer.php',
|
||||||
|
'ArcanistUnitRenderer' => 'unit/renderer/ArcanistUnitRenderer.php',
|
||||||
'ArcanistUnitTestResult' => 'unit/ArcanistUnitTestResult.php',
|
'ArcanistUnitTestResult' => 'unit/ArcanistUnitTestResult.php',
|
||||||
'ArcanistUnitWorkflow' => 'workflow/ArcanistUnitWorkflow.php',
|
'ArcanistUnitWorkflow' => 'workflow/ArcanistUnitWorkflow.php',
|
||||||
'ArcanistUpgradeWorkflow' => 'workflow/ArcanistUpgradeWorkflow.php',
|
'ArcanistUpgradeWorkflow' => 'workflow/ArcanistUpgradeWorkflow.php',
|
||||||
|
@ -271,6 +273,7 @@ phutil_register_library_map(array(
|
||||||
'ArcanistTextLinterTestCase' => 'ArcanistArcanistLinterTestCase',
|
'ArcanistTextLinterTestCase' => 'ArcanistArcanistLinterTestCase',
|
||||||
'ArcanistTodoWorkflow' => 'ArcanistBaseWorkflow',
|
'ArcanistTodoWorkflow' => 'ArcanistBaseWorkflow',
|
||||||
'ArcanistUncommittedChangesException' => 'ArcanistUsageException',
|
'ArcanistUncommittedChangesException' => 'ArcanistUsageException',
|
||||||
|
'ArcanistUnitConsoleRenderer' => 'ArcanistUnitRenderer',
|
||||||
'ArcanistUnitWorkflow' => 'ArcanistBaseWorkflow',
|
'ArcanistUnitWorkflow' => 'ArcanistBaseWorkflow',
|
||||||
'ArcanistUpgradeWorkflow' => 'ArcanistBaseWorkflow',
|
'ArcanistUpgradeWorkflow' => 'ArcanistBaseWorkflow',
|
||||||
'ArcanistUploadWorkflow' => 'ArcanistBaseWorkflow',
|
'ArcanistUploadWorkflow' => 'ArcanistBaseWorkflow',
|
||||||
|
|
|
@ -50,18 +50,6 @@ final class ArcanistUnitTestResult {
|
||||||
return $this->result;
|
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) {
|
public function setDuration($duration) {
|
||||||
$this->duration = $duration;
|
$this->duration = $duration;
|
||||||
return $this;
|
return $this;
|
||||||
|
|
|
@ -14,6 +14,7 @@ abstract class ArcanistBaseUnitTestEngine {
|
||||||
private $enableAsyncTests;
|
private $enableAsyncTests;
|
||||||
private $enableCoverage;
|
private $enableCoverage;
|
||||||
private $runAllTests;
|
private $runAllTests;
|
||||||
|
protected $renderer;
|
||||||
|
|
||||||
|
|
||||||
public function setRunAllTests($run_all_tests) {
|
public function setRunAllTests($run_all_tests) {
|
||||||
|
@ -86,6 +87,11 @@ abstract class ArcanistBaseUnitTestEngine {
|
||||||
return $this->enableCoverage;
|
return $this->enableCoverage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setRenderer(ArcanistUnitRenderer $renderer) {
|
||||||
|
$this->renderer = $renderer;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
abstract public function run();
|
abstract public function run();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -45,6 +45,9 @@ final class PhutilUnitTestEngine extends ArcanistBaseUnitTestEngine {
|
||||||
if ($this->getPaths()) {
|
if ($this->getPaths()) {
|
||||||
$test_case->setPaths($this->getPaths());
|
$test_case->setPaths($this->getPaths());
|
||||||
}
|
}
|
||||||
|
if ($this->renderer) {
|
||||||
|
$test_case->setRenderer($this->renderer);
|
||||||
|
}
|
||||||
$results[] = $test_case->run();
|
$results[] = $test_case->run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,4 +172,8 @@ final class PhutilUnitTestEngine extends ArcanistBaseUnitTestEngine {
|
||||||
return $run_tests;
|
return $run_tests;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function shouldEchoTestResults() {
|
||||||
|
return !$this->renderer;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ abstract class ArcanistPhutilTestCase {
|
||||||
private $coverage = array();
|
private $coverage = array();
|
||||||
private $projectRoot;
|
private $projectRoot;
|
||||||
private $paths;
|
private $paths;
|
||||||
|
private $renderer;
|
||||||
|
|
||||||
|
|
||||||
/* -( Making Test Assertions )--------------------------------------------- */
|
/* -( Making Test Assertions )--------------------------------------------- */
|
||||||
|
@ -372,6 +373,10 @@ abstract class ArcanistPhutilTestCase {
|
||||||
$result->setDuration(microtime(true) - $this->testStartTime);
|
$result->setDuration(microtime(true) - $this->testStartTime);
|
||||||
$result->setUserData($reason);
|
$result->setUserData($reason);
|
||||||
$this->results[] = $result;
|
$this->results[] = $result;
|
||||||
|
|
||||||
|
if ($this->renderer) {
|
||||||
|
echo $this->renderer->renderUnitResult($result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -530,4 +535,9 @@ abstract class ArcanistPhutilTestCase {
|
||||||
return null;
|
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'));
|
$this->engine->setArguments($this->getPassthruArgumentsAsMap('unit'));
|
||||||
|
|
||||||
|
$renderer = new ArcanistUnitConsoleRenderer();
|
||||||
|
$this->engine->setRenderer($renderer);
|
||||||
|
|
||||||
$enable_coverage = null; // Means "default".
|
$enable_coverage = null; // Means "default".
|
||||||
if ($this->getArgument('coverage') ||
|
if ($this->getArgument('coverage') ||
|
||||||
$this->getArgument('detailed-coverage')) {
|
$this->getArgument('detailed-coverage')) {
|
||||||
|
@ -175,19 +178,9 @@ EOTEXT
|
||||||
$unresolved[] = $result;
|
$unresolved[] = $result;
|
||||||
} else {
|
} else {
|
||||||
if ($this->engine->shouldEchoTestResults()) {
|
if ($this->engine->shouldEchoTestResults()) {
|
||||||
$duration = '';
|
$console->writeOut('%s', $renderer->renderUnitResult($result));
|
||||||
if ($result_code == ArcanistUnitTestResult::RESULT_PASS) {
|
|
||||||
$duration = ' '.self::formatTestDuration($result->getDuration());
|
|
||||||
}
|
|
||||||
$console->writeOut(
|
|
||||||
" %s %s\n",
|
|
||||||
$result->getConsoleFormattedResult().$duration,
|
|
||||||
$result->getName());
|
|
||||||
}
|
}
|
||||||
if ($result_code != ArcanistUnitTestResult::RESULT_PASS) {
|
if ($result_code != ArcanistUnitTestResult::RESULT_PASS) {
|
||||||
if ($this->engine->shouldEchoTestResults()) {
|
|
||||||
$console->writeOut("%s\n", $result->getUserData());
|
|
||||||
}
|
|
||||||
$unresolved[] = $result;
|
$unresolved[] = $result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -198,12 +191,9 @@ EOTEXT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($postponed_count) {
|
if ($postponed_count) {
|
||||||
$postponed = id(new ArcanistUnitTestResult())
|
|
||||||
->setResult(ArcanistUnitTestResult::RESULT_POSTPONED);
|
|
||||||
$console->writeOut(
|
$console->writeOut(
|
||||||
"%s %s\n",
|
'%s',
|
||||||
$postponed->getConsoleFormattedResult(),
|
$renderer->renderPostponedResult($postponed_count));
|
||||||
pht('%d test(s)', $postponed_count));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($coverage) {
|
if ($coverage) {
|
||||||
|
@ -286,44 +276,6 @@ EOTEXT
|
||||||
return $this->testResults;
|
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) {
|
private function renderDetailedCoverageReport($data, $report) {
|
||||||
$data = explode("\n", $data);
|
$data = explode("\n", $data);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue