1
0
Fork 0
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:
vrana 2013-02-27 13:59:12 -08:00
parent 8412f7cfd7
commit 2e87419c7b
8 changed files with 128 additions and 66 deletions

View file

@ -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',

View file

@ -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;

View file

@ -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();
/** /**

View file

@ -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;
}
} }

View file

@ -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;
}
} }

View 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';
}
}

View file

@ -0,0 +1,11 @@
<?php
/**
* @group unit
*/
abstract class ArcanistUnitRenderer {
abstract public function renderUnitResult(ArcanistUnitTestResult $result);
abstract public function renderPostponedResult($count);
}

View file

@ -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);