2011-01-09 15:22:25 -08:00
|
|
|
<?php
|
|
|
|
|
2011-02-19 11:36:08 -08:00
|
|
|
/**
|
|
|
|
* Runs unit tests which cover your changes.
|
|
|
|
*
|
|
|
|
* @group workflow
|
|
|
|
*/
|
2012-01-31 12:07:05 -08:00
|
|
|
final class ArcanistUnitWorkflow extends ArcanistBaseWorkflow {
|
2011-01-09 15:22:25 -08:00
|
|
|
|
2011-06-08 16:36:17 -07:00
|
|
|
const RESULT_OKAY = 0;
|
|
|
|
const RESULT_UNSOUND = 1;
|
|
|
|
const RESULT_FAIL = 2;
|
|
|
|
const RESULT_SKIP = 3;
|
|
|
|
const RESULT_POSTPONED = 4;
|
2011-01-09 15:22:25 -08:00
|
|
|
|
2011-01-11 22:13:31 -08:00
|
|
|
private $unresolvedTests;
|
2012-01-31 12:07:19 -08:00
|
|
|
private $testResults;
|
2011-05-27 16:15:43 -07:00
|
|
|
private $engine;
|
2011-01-11 22:13:31 -08:00
|
|
|
|
Make Arcanist workflow names explicit
Summary:
Currently, adding a new workflow requires you to override ArcanistConfiguration, which is messy. Instead, just load everything that extends ArcanistBaseWorkflow.
Remove all the rules tying workflow names to class names through arcane incantations.
This has a very small performance cost in that we need to load every Workflow class every time now, but we don't hit __init__ and such anymore and it was pretty negligible on my machine (98ms vs 104ms or something).
Test Plan: Ran "arc help", "arc which", "arc diff", etc.
Reviewers: edward, vrana, btrahan
Reviewed By: edward
CC: aran, zeeg
Differential Revision: https://secure.phabricator.com/D3691
2012-10-17 08:35:03 -07:00
|
|
|
public function getWorkflowName() {
|
|
|
|
return 'unit';
|
|
|
|
}
|
|
|
|
|
2012-03-05 10:02:37 -08:00
|
|
|
public function getCommandSynopses() {
|
2011-01-09 15:22:25 -08:00
|
|
|
return phutil_console_format(<<<EOTEXT
|
Unify arguments for 'arc lint', 'arc unit'
Summary: See T645. These commands take inconsistent and overly-magical arguments
right now. Instead, make them behave consistently and allow them both to operate
on "arc <workflow> path path2 path3 ...", which is a generally useful workflow.
Test Plan: Ran "arc lint <path>", "arc unit <path>", "arc lint --rev
HEAD^^^^^^", "arc unit --rev HEAD^^^^^^^^^^^^", etc. Ran "arc diff --trace" and
verified --rev argument to child workflows.
Reviewers: btrahan, jungejason
Reviewed By: btrahan
CC: aran, epriestley, btrahan
Maniphest Tasks: T645
Differential Revision: https://secure.phabricator.com/D1348
2012-01-09 12:40:50 -08:00
|
|
|
**unit** [__options__] [__paths__]
|
|
|
|
**unit** [__options__] --rev [__rev__]
|
2012-03-05 10:02:37 -08:00
|
|
|
EOTEXT
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getCommandHelp() {
|
|
|
|
return phutil_console_format(<<<EOTEXT
|
Unify arguments for 'arc lint', 'arc unit'
Summary: See T645. These commands take inconsistent and overly-magical arguments
right now. Instead, make them behave consistently and allow them both to operate
on "arc <workflow> path path2 path3 ...", which is a generally useful workflow.
Test Plan: Ran "arc lint <path>", "arc unit <path>", "arc lint --rev
HEAD^^^^^^", "arc unit --rev HEAD^^^^^^^^^^^^", etc. Ran "arc diff --trace" and
verified --rev argument to child workflows.
Reviewers: btrahan, jungejason
Reviewed By: btrahan
CC: aran, epriestley, btrahan
Maniphest Tasks: T645
Differential Revision: https://secure.phabricator.com/D1348
2012-01-09 12:40:50 -08:00
|
|
|
Supports: git, svn, hg
|
2011-11-30 12:56:18 -08:00
|
|
|
Run unit tests that cover specified paths. If no paths are specified,
|
|
|
|
unit tests covering all modified files will be run.
|
2011-01-09 15:22:25 -08:00
|
|
|
EOTEXT
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getArguments() {
|
|
|
|
return array(
|
Unify arguments for 'arc lint', 'arc unit'
Summary: See T645. These commands take inconsistent and overly-magical arguments
right now. Instead, make them behave consistently and allow them both to operate
on "arc <workflow> path path2 path3 ...", which is a generally useful workflow.
Test Plan: Ran "arc lint <path>", "arc unit <path>", "arc lint --rev
HEAD^^^^^^", "arc unit --rev HEAD^^^^^^^^^^^^", etc. Ran "arc diff --trace" and
verified --rev argument to child workflows.
Reviewers: btrahan, jungejason
Reviewed By: btrahan
CC: aran, epriestley, btrahan
Maniphest Tasks: T645
Differential Revision: https://secure.phabricator.com/D1348
2012-01-09 12:40:50 -08:00
|
|
|
'rev' => array(
|
|
|
|
'param' => 'revision',
|
|
|
|
'help' => "Run unit tests covering changes since a specific revision.",
|
|
|
|
'supports' => array(
|
|
|
|
'git',
|
|
|
|
'hg',
|
|
|
|
),
|
|
|
|
'nosupport' => array(
|
|
|
|
'svn' => "Arc unit does not currently support --rev in SVN.",
|
|
|
|
),
|
|
|
|
),
|
2011-02-16 11:53:13 -08:00
|
|
|
'engine' => array(
|
|
|
|
'param' => 'classname',
|
|
|
|
'help' =>
|
|
|
|
"Override configured unit engine for this project."
|
|
|
|
),
|
2012-01-31 12:07:19 -08:00
|
|
|
'coverage' => array(
|
|
|
|
'help' => 'Always enable coverage information.',
|
|
|
|
'conflicts' => array(
|
|
|
|
'no-coverage' => null,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
'no-coverage' => array(
|
|
|
|
'help' => 'Always disable coverage information.',
|
|
|
|
),
|
|
|
|
'detailed-coverage' => array(
|
|
|
|
'help' => "Show a detailed coverage report on the CLI. Implies ".
|
|
|
|
"--coverage.",
|
|
|
|
),
|
2012-12-17 12:56:41 -08:00
|
|
|
'json' => array(
|
|
|
|
'help' => 'Report results in JSON format.',
|
|
|
|
),
|
|
|
|
'everything' => array(
|
|
|
|
'help' => 'Run every test.',
|
|
|
|
'conflicts' => array(
|
|
|
|
'rev' => '--everything runs all tests.',
|
|
|
|
),
|
|
|
|
),
|
|
|
|
'ugly' => array(
|
|
|
|
'help' => 'With --json, use uglier (but more efficient) formatting.',
|
|
|
|
),
|
2011-01-09 15:22:25 -08:00
|
|
|
'*' => 'paths',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function requiresWorkingCopy() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function requiresRepositoryAPI() {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2011-09-08 17:36:20 -07:00
|
|
|
public function getEngine() {
|
|
|
|
return $this->engine;
|
|
|
|
}
|
|
|
|
|
2011-01-09 15:22:25 -08:00
|
|
|
public function run() {
|
|
|
|
|
|
|
|
$working_copy = $this->getWorkingCopy();
|
|
|
|
|
|
|
|
$engine_class = $this->getArgument(
|
|
|
|
'engine',
|
Allow global config to load libraries and set test engines
Summary:
Khan Academy is looking into lint configuration, but doesn't use ".arcconfig" because they have a large number of repositories. Making configuration more flexible generally gives us more options for onboarding installs.
- Currently, only project config (".arcconfig") can load libraries. Allow user config ("~/.arcrc") to load libraries as well.
- Currently, only project config can set lint/unit engines. Allow user config to set default lint/unit engines.
- Add some type checking to "arc set-config".
- Add "arc set-config --show".
Test Plan:
- **load**
- Ran `arc set-config load xxx`, got error about format.
- Ran `arc set-config load ["apple"]`, got warning on running 'arc' commands (no such library) but was able to run 'arc set-config' again to clear it.
- Ran `arc set-config load ["/path/to/a/lib/src/"]`, worked.
- Ran `arc list --trace`, verified my library loaded in addition to `.arcconfig` libraries.
- Ran `arc list --load-phutil-library=xxx --trace`, verified only that library loaded.
- Ran `arc list --trace --load-phutil-library=apple --trace`, got hard error about bad library.
- Set `.arcconfig` to point at a bad library, verified hard error.
- **lint.engine** / **unit.engine**
- Removed lint engine from `.arcconfig`, ran "arc lint", got a run with specified engine.
- Removed unit engine from `.arcconfig`, ran "arc unit", got a run with specified engine.
- **--show**
- Ran `arc set-config --show`.
- **misc**
- Ran `arc get-config`.
Reviewers: csilvers, btrahan, vrana
Reviewed By: csilvers
CC: aran
Differential Revision: https://secure.phabricator.com/D2618
2012-05-31 11:41:39 -07:00
|
|
|
$working_copy->getConfigFromAnySource('unit.engine'));
|
2011-01-09 15:22:25 -08:00
|
|
|
|
|
|
|
if (!$engine_class) {
|
|
|
|
throw new ArcanistNoEngineException(
|
|
|
|
"No unit test engine is configured for this project. Edit .arcconfig ".
|
|
|
|
"to specify a unit test engine.");
|
|
|
|
}
|
|
|
|
|
Unify arguments for 'arc lint', 'arc unit'
Summary: See T645. These commands take inconsistent and overly-magical arguments
right now. Instead, make them behave consistently and allow them both to operate
on "arc <workflow> path path2 path3 ...", which is a generally useful workflow.
Test Plan: Ran "arc lint <path>", "arc unit <path>", "arc lint --rev
HEAD^^^^^^", "arc unit --rev HEAD^^^^^^^^^^^^", etc. Ran "arc diff --trace" and
verified --rev argument to child workflows.
Reviewers: btrahan, jungejason
Reviewed By: btrahan
CC: aran, epriestley, btrahan
Maniphest Tasks: T645
Differential Revision: https://secure.phabricator.com/D1348
2012-01-09 12:40:50 -08:00
|
|
|
$paths = $this->getArgument('paths');
|
|
|
|
$rev = $this->getArgument('rev');
|
2012-12-17 12:56:41 -08:00
|
|
|
$everything = $this->getArgument('everything');
|
|
|
|
if ($everything && $paths) {
|
|
|
|
throw new ArcanistUsageException(
|
|
|
|
"You can not specify paths with --everything. The --everything ".
|
|
|
|
"flag runs every test.");
|
|
|
|
}
|
2011-02-18 17:08:10 -08:00
|
|
|
|
Unify arguments for 'arc lint', 'arc unit'
Summary: See T645. These commands take inconsistent and overly-magical arguments
right now. Instead, make them behave consistently and allow them both to operate
on "arc <workflow> path path2 path3 ...", which is a generally useful workflow.
Test Plan: Ran "arc lint <path>", "arc unit <path>", "arc lint --rev
HEAD^^^^^^", "arc unit --rev HEAD^^^^^^^^^^^^", etc. Ran "arc diff --trace" and
verified --rev argument to child workflows.
Reviewers: btrahan, jungejason
Reviewed By: btrahan
CC: aran, epriestley, btrahan
Maniphest Tasks: T645
Differential Revision: https://secure.phabricator.com/D1348
2012-01-09 12:40:50 -08:00
|
|
|
$paths = $this->selectPathsForWorkflow($paths, $rev);
|
2011-01-09 15:22:25 -08:00
|
|
|
|
2012-05-30 15:47:16 -07:00
|
|
|
if (!class_exists($engine_class) ||
|
|
|
|
!is_subclass_of($engine_class, 'ArcanistBaseUnitTestEngine')) {
|
2011-12-21 08:51:34 -08:00
|
|
|
throw new ArcanistUsageException(
|
|
|
|
"Configured unit test engine '{$engine_class}' is not a subclass of ".
|
|
|
|
"'ArcanistBaseUnitTestEngine'.");
|
|
|
|
}
|
|
|
|
|
2011-05-27 16:15:43 -07:00
|
|
|
$this->engine = newv($engine_class, array());
|
|
|
|
$this->engine->setWorkingCopy($working_copy);
|
2012-12-17 12:56:41 -08:00
|
|
|
if ($everything) {
|
|
|
|
$this->engine->setRunAllTests(true);
|
|
|
|
} else {
|
|
|
|
$this->engine->setPaths($paths);
|
|
|
|
}
|
2011-05-27 16:15:43 -07:00
|
|
|
$this->engine->setArguments($this->getPassthruArgumentsAsMap('unit'));
|
2011-01-09 15:22:25 -08:00
|
|
|
|
2012-01-31 12:07:19 -08:00
|
|
|
$enable_coverage = null; // Means "default".
|
|
|
|
if ($this->getArgument('coverage') ||
|
|
|
|
$this->getArgument('detailed-coverage')) {
|
|
|
|
$enable_coverage = true;
|
|
|
|
} else if ($this->getArgument('no-coverage')) {
|
|
|
|
$enable_coverage = false;
|
|
|
|
}
|
|
|
|
$this->engine->setEnableCoverage($enable_coverage);
|
|
|
|
|
2011-06-10 12:40:01 -07:00
|
|
|
// Enable possible async tests only for 'arc diff' not 'arc unit'
|
|
|
|
if ($this->getParentWorkflow()) {
|
|
|
|
$this->engine->setEnableAsyncTests(true);
|
|
|
|
} else {
|
|
|
|
$this->engine->setEnableAsyncTests(false);
|
|
|
|
}
|
|
|
|
|
2011-05-27 16:15:43 -07:00
|
|
|
$results = $this->engine->run();
|
2012-01-31 12:07:19 -08:00
|
|
|
$this->testResults = $results;
|
2011-01-09 15:22:25 -08:00
|
|
|
|
2012-08-04 19:23:53 -07:00
|
|
|
$console = PhutilConsole::getConsole();
|
|
|
|
|
2012-12-17 12:56:41 -08:00
|
|
|
$json_output = $this->getArgument('json');
|
|
|
|
|
|
|
|
if ($json_output) {
|
|
|
|
$console->disableOut();
|
|
|
|
}
|
|
|
|
|
2011-01-11 22:13:31 -08:00
|
|
|
$unresolved = array();
|
2012-01-31 12:07:19 -08:00
|
|
|
$coverage = array();
|
2011-04-28 11:46:20 -07:00
|
|
|
$postponed_count = 0;
|
2011-01-09 15:22:25 -08:00
|
|
|
foreach ($results as $result) {
|
|
|
|
$result_code = $result->getResult();
|
2011-04-28 11:46:20 -07:00
|
|
|
if ($result_code == ArcanistUnitTestResult::RESULT_POSTPONED) {
|
|
|
|
$postponed_count++;
|
2011-06-08 16:36:17 -07:00
|
|
|
$unresolved[] = $result;
|
2011-04-28 11:46:20 -07:00
|
|
|
} else {
|
2011-06-06 16:43:36 -07:00
|
|
|
if ($this->engine->shouldEchoTestResults()) {
|
2012-08-04 19:23:53 -07:00
|
|
|
$duration = '';
|
2011-07-03 23:31:14 -07:00
|
|
|
if ($result_code == ArcanistUnitTestResult::RESULT_PASS) {
|
2012-08-04 19:23:53 -07:00
|
|
|
$duration = ' '.self::formatTestDuration($result->getDuration());
|
2011-07-03 23:31:14 -07:00
|
|
|
}
|
2012-08-04 19:23:53 -07:00
|
|
|
$console->writeOut(
|
|
|
|
" %s %s\n",
|
2012-09-13 13:25:26 -07:00
|
|
|
$result->getConsoleFormattedResult().$duration,
|
2012-08-04 19:23:53 -07:00
|
|
|
$result->getName());
|
2011-06-06 16:43:36 -07:00
|
|
|
}
|
2011-04-28 11:46:20 -07:00
|
|
|
if ($result_code != ArcanistUnitTestResult::RESULT_PASS) {
|
2011-06-06 16:43:36 -07:00
|
|
|
if ($this->engine->shouldEchoTestResults()) {
|
2012-08-04 19:23:53 -07:00
|
|
|
$console->writeOut("%s\n", $result->getUserData());
|
2011-06-06 16:43:36 -07:00
|
|
|
}
|
2011-04-28 11:46:20 -07:00
|
|
|
$unresolved[] = $result;
|
|
|
|
}
|
2011-01-09 15:22:25 -08:00
|
|
|
}
|
2012-01-31 12:07:19 -08:00
|
|
|
if ($result->getCoverage()) {
|
|
|
|
foreach ($result->getCoverage() as $file => $report) {
|
|
|
|
$coverage[$file][] = $report;
|
|
|
|
}
|
|
|
|
}
|
2011-01-09 15:22:25 -08:00
|
|
|
}
|
2011-04-28 11:46:20 -07:00
|
|
|
if ($postponed_count) {
|
2012-09-13 13:25:26 -07:00
|
|
|
$postponed = id(new ArcanistUnitTestResult())
|
|
|
|
->setResult(ArcanistUnitTestResult::RESULT_POSTPONED);
|
2012-08-04 19:23:53 -07:00
|
|
|
$console->writeOut(
|
|
|
|
"%s %s\n",
|
2012-09-13 13:25:26 -07:00
|
|
|
$postponed->getConsoleFormattedResult(),
|
2012-08-04 19:23:53 -07:00
|
|
|
pht('%d test(s)', $postponed_count));
|
2011-04-28 11:46:20 -07:00
|
|
|
}
|
|
|
|
|
2012-01-31 12:07:19 -08:00
|
|
|
if ($coverage) {
|
2012-02-01 17:36:25 -08:00
|
|
|
$file_coverage = array_fill_keys(
|
|
|
|
$paths,
|
|
|
|
0);
|
2012-01-31 12:07:19 -08:00
|
|
|
$file_reports = array();
|
|
|
|
foreach ($coverage as $file => $reports) {
|
|
|
|
$report = ArcanistUnitTestResult::mergeCoverage($reports);
|
|
|
|
$cov = substr_count($report, 'C');
|
|
|
|
$uncov = substr_count($report, 'U');
|
|
|
|
if ($cov + $uncov) {
|
|
|
|
$coverage = $cov / ($cov + $uncov);
|
|
|
|
} else {
|
|
|
|
$coverage = 0;
|
|
|
|
}
|
|
|
|
$file_coverage[$file] = $coverage;
|
|
|
|
$file_reports[$file] = $report;
|
|
|
|
}
|
2012-08-04 19:23:53 -07:00
|
|
|
$console->writeOut("\n__COVERAGE REPORT__\n");
|
2012-01-31 12:07:19 -08:00
|
|
|
|
|
|
|
asort($file_coverage);
|
|
|
|
foreach ($file_coverage as $file => $coverage) {
|
2012-08-04 19:23:53 -07:00
|
|
|
$console->writeOut(
|
2012-01-31 12:07:19 -08:00
|
|
|
" **%s%%** %s\n",
|
|
|
|
sprintf('% 3d', (int)(100 * $coverage)),
|
|
|
|
$file);
|
|
|
|
|
2012-02-01 17:36:25 -08:00
|
|
|
$full_path = $working_copy->getProjectRoot().'/'.$file;
|
|
|
|
if ($this->getArgument('detailed-coverage') &&
|
|
|
|
Filesystem::pathExists($full_path) &&
|
|
|
|
is_file($full_path)) {
|
2012-08-04 19:23:53 -07:00
|
|
|
$console->writeOut(
|
|
|
|
'%s',
|
|
|
|
$this->renderDetailedCoverageReport(
|
|
|
|
Filesystem::readFile($full_path),
|
|
|
|
$file_reports[$file]));
|
2012-01-31 12:07:19 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-01-11 22:13:31 -08:00
|
|
|
$this->unresolvedTests = $unresolved;
|
2011-01-09 15:22:25 -08:00
|
|
|
|
|
|
|
$overall_result = self::RESULT_OKAY;
|
|
|
|
foreach ($results as $result) {
|
|
|
|
$result_code = $result->getResult();
|
|
|
|
if ($result_code == ArcanistUnitTestResult::RESULT_FAIL ||
|
|
|
|
$result_code == ArcanistUnitTestResult::RESULT_BROKEN) {
|
|
|
|
$overall_result = self::RESULT_FAIL;
|
|
|
|
break;
|
2011-01-11 22:13:31 -08:00
|
|
|
} else if ($result_code == ArcanistUnitTestResult::RESULT_UNSOUND) {
|
2011-01-09 15:22:25 -08:00
|
|
|
$overall_result = self::RESULT_UNSOUND;
|
2011-06-08 16:36:17 -07:00
|
|
|
} else if ($result_code == ArcanistUnitTestResult::RESULT_POSTPONED &&
|
|
|
|
$overall_result != self::RESULT_UNSOUND) {
|
|
|
|
$overall_result = self::RESULT_POSTPONED;
|
2011-01-09 15:22:25 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-17 12:56:41 -08:00
|
|
|
if ($json_output) {
|
|
|
|
$console->enableOut();
|
|
|
|
|
|
|
|
$data = array_values(mpull($results, 'toDictionary'));
|
|
|
|
|
|
|
|
if ($this->getArgument('ugly')) {
|
|
|
|
$console->writeOut('%s', json_encode($data));
|
|
|
|
} else {
|
|
|
|
$json = new PhutilJSON();
|
|
|
|
$console->writeOut('%s', $json->encodeFormatted($data));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-01-09 15:22:25 -08:00
|
|
|
return $overall_result;
|
|
|
|
}
|
2011-01-11 22:13:31 -08:00
|
|
|
|
|
|
|
public function getUnresolvedTests() {
|
|
|
|
return $this->unresolvedTests;
|
|
|
|
}
|
|
|
|
|
2012-01-31 12:07:19 -08:00
|
|
|
public function getTestResults() {
|
|
|
|
return $this->testResults;
|
|
|
|
}
|
|
|
|
|
2011-07-03 23:31:14 -07:00
|
|
|
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';
|
|
|
|
}
|
2012-01-31 12:07:19 -08:00
|
|
|
|
2012-02-01 17:36:25 -08:00
|
|
|
private function renderDetailedCoverageReport($data, $report) {
|
2012-01-31 12:07:19 -08:00
|
|
|
$data = explode("\n", $data);
|
|
|
|
|
|
|
|
$out = '';
|
|
|
|
|
|
|
|
$n = 0;
|
|
|
|
foreach ($data as $line) {
|
|
|
|
$out .= sprintf('% 5d ', $n + 1);
|
|
|
|
$line = str_pad($line, 80, ' ');
|
|
|
|
if (empty($report[$n])) {
|
|
|
|
$c = 'N';
|
|
|
|
} else {
|
|
|
|
$c = $report[$n];
|
|
|
|
}
|
|
|
|
switch ($c) {
|
|
|
|
case 'C':
|
|
|
|
$out .= phutil_console_format(
|
|
|
|
'<bg:green> %s </bg>',
|
|
|
|
$line);
|
|
|
|
break;
|
|
|
|
case 'U':
|
|
|
|
$out .= phutil_console_format(
|
|
|
|
'<bg:red> %s </bg>',
|
|
|
|
$line);
|
|
|
|
break;
|
|
|
|
case 'X':
|
|
|
|
$out .= phutil_console_format(
|
|
|
|
'<bg:magenta> %s </bg>',
|
|
|
|
$line);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
$out .= ' '.$line.' ';
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
$out .= "\n";
|
|
|
|
$n++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $out;
|
|
|
|
}
|
2011-01-09 15:22:25 -08:00
|
|
|
}
|