Skip to content

Commit

Permalink
Merge pull request #20 from xp-framework/refactor/extract-ouput
Browse files Browse the repository at this point in the history
Extract output into a dedicated class
  • Loading branch information
thekid authored Apr 22, 2023
2 parents b65a23f + 79efea7 commit e2d74fd
Show file tree
Hide file tree
Showing 12 changed files with 519 additions and 129 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,4 @@ jobs:
echo "vendor/autoload.php" > composer.pth
- name: Run test suite
run: sh xp-run xp.test.Runner src/test/php
run: sh xp-run xp.test.Runner -r Dots src/test/php
7 changes: 7 additions & 0 deletions src/main/php/test/Outcome.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@

abstract class Outcome {
public $test;
public $elapsed= null;

public function __construct($test) {
$this->test= $test;
}

/** Sets elapsed time to the given value */
public function timed(float $elapsed): self {
$this->elapsed= $elapsed;
return $this;
}

public abstract function kind();
}
7 changes: 7 additions & 0 deletions src/main/php/test/execution/Group.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ abstract class Group {
*/
public abstract function name();

/**
* Returns this group's declaring file, or NULL.
*
* @return ?string
*/
public function declaringFile() { return null; }

/**
* Yields prerequisites for the tests in this group. Defaults to no
* prerequisites, overwrite in subclasses!
Expand Down
12 changes: 10 additions & 2 deletions src/main/php/test/execution/Metrics.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@
class Metrics {
public $count= ['success' => 0, 'failure' => 0, 'skipped' => 0];
public $elapsed= 0.0;
public $memoryUsed= 0;
public $peakMemoryUsed= 0;

public function record(Outcome $outcome, float $elapsed): Outcome {
public function record(Outcome $outcome): Outcome {
$this->count[$outcome->kind()]++;
$this->elapsed+= $elapsed;
$this->elapsed+= $outcome->elapsed;
return $outcome;
}

public function using(int $usage, int $peakUsage): self {
$this->memoryUsed= $usage;
$this->peakMemoryUsed= $peakUsage;
return $this;
}

/** Returns whether no tests were run */
public function empty(): bool {
return 0 === array_sum($this->count);
Expand Down
18 changes: 14 additions & 4 deletions src/main/php/test/execution/RunTest.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

use lang\Runnable;
use test\Outcome;
use util\profiling\Timer;

class RunTest implements Runnable {
private $case, $arguments;
public $case, $arguments;

/**
* Runs a given test case with the supplied arguments
Expand All @@ -20,8 +21,17 @@ public function __construct($case, $arguments) {
/** @return string */
public function name() { return $this->case->name(); }

/** Runs this test case and returns its outcome */
public function run(): Outcome {
return $this->case->run($this->arguments);
/**
* Runs this test case and returns its outcome. If given a timer, the
* outcome will have the elapsed time attached to it.
*/
public function run(Timer $timer= null): Outcome {
if (null === $timer) return $this->case->run($this->arguments);

$timer->start();
$outcome= $this->case->run($this->arguments);
$timer->stop();

return $outcome->timed($timer->elapsedTime());
}
}
3 changes: 3 additions & 0 deletions src/main/php/test/execution/TestClass.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ public function __construct($arg, $selection= null) {
/** @return string */
public function name() { return $this->context->type->name(); }

/** @return ?string */
public function declaringFile() { return $this->context->type->class()->reflect()->getFileName(); }

/** @return iterable */
public function prerequisites() {
foreach ($this->context->annotations(Verification::class) as $verify) {
Expand Down
45 changes: 45 additions & 0 deletions src/main/php/xp/test/Dots.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php namespace xp\test;

use util\cmd\Console;

/**
* Dot output adds a "." to the console for every finished test,
* wrapping the lines at 72 characters.
*/
class Dots extends Report {
use Summary;

private $offset;

/** Called when test run starts */
public function start($sources) {
Console::write('[');
$this->offset= 1;
}

/** Called when a test finished */
public function finished($group, $test, $outcome) {
Console::write('.');

if (++$this->offset > 72) {
Console::writeLine();
$this->offset= 0;
}
}

/**
* Print out summary of test run
*
* @param test.execution.Metrics $metrices
* @param float $overall
* @param [:test.Outcome] $failures
* @return void
*/
public function summary($metrics, $overall, $failures) {
Console::writeLine(']');
Console::writeLine();

$this->failures($failures);
$this->metrics($metrics, $overall);
}
}
145 changes: 145 additions & 0 deletions src/main/php/xp/test/Grouped.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<?php namespace xp\test;

use util\cmd\Console;

/**
* Grouped output, the default output mechanism, using console colors
* and a progress bar while running a test group.
*/
class Grouped extends Report {
use Summary;

const PROGRESS= ['', '', '', '', '', '', '', ''];
const GROUPS= [
'success' => "\033[42;1;37m PASS \033[0m",
'failure' => "\033[41;1;37m FAIL \033[0m",
'stopped' => "\033[47;1;30m STOP \033[0m",
'skipped' => "\033[43;1;37m SKIP \033[0m",
];
const CASES= [
'success' => "\033[32m✓\033[0m",
'failure' => "\033[31m⨯\033[0m",
'skipped' => "\033[33m⦾\033[0m",
];
const COUNTS= [
'success' => "\033[32m%d succeeded\033[0m",
'failure' => "\033[31m%d failed\033[0m",
'skipped' => "\033[33m%d skipped\033[0m",
];

/**
* Enter a group
*
* @param test.execution.TestClass $group
* @return void
*/
public function enter($group) {
Console::writef("\r> \033[44;1;37m RUN… \033[0m \033[37m%s\033[0m", $group->name());
}

/**
* Running a given test
*
* @param test.execution.TestClass $group
* @param test.execution.TestCase $test
* @param int $n
* @return void
*/
public function running($group, $test, $n) {
Console::writef("\r%s", self::PROGRESS[$n % 8]); // sizeof(self::PROGRESS)
}

/**
* Report test case summary. Used by `pass()` and `fail()`.
*
* @param test.Outcome[] $results
* @return void
*/
private function summarize($results) {
foreach ($results as $outcome) {
$kind= $outcome->kind();
Console::write(' ', self::CASES[$kind], ' ', str_replace("\n", "\n ", $outcome->test));
switch ($kind) {
case 'success': Console::writeLine(); break;
case 'skipped': {
Console::writeLinef("\033[1;32;3m // Skip%s\033[0m", $outcome->reason ? ": {$outcome->reason}" : '');
break;
}
case 'failure': {
Console::writeLinef("\033[1;32;3m // Fail: %s\033[0m", $outcome->reason);
break;
}
}
}
Console::writeLine();
}

/**
* Pass an entire group
*
* @param test.execution.TestClass $group
* @param test.Outcome[] $results
* @return void
*/
public function pass($group, $results) {
Console::writeLinef("\r> %s \033[37m%s\033[0m", self::GROUPS['success'], $group->name());
$this->summarize($results);
}

/**
* Fail an entire group
*
* @param test.execution.TestClass $group
* @param test.Outcome[] $results
* @return void
*/
public function fail($group, $results) {
Console::writeLinef("\r> %s \033[37m%s\033[0m", self::GROUPS['failure'], $group->name());
$this->summarize($results);
}

/**
* Skip an entire group
*
* @param test.execution.TestClass $group
* @param string $reason
* @return void
*/
public function skip($group, $reason) {
Console::writeLinef(
"\r> %s \033[37m%s\033[1;32;3m // %s\033[0m\n",
self::GROUPS['skipped'],
$group->name(),
$reason
);
}

/**
* Stop an entire group
*
* @param test.execution.TestClass $group
* @param string $reason
* @return void
*/
public function stop($group, $reason) {
Console::writeLinef(
"\r> %s \033[37m%s\033[1;32;3m // %s\033[0m",
self::GROUPS['stopped'],
$group->name(),
$reason
);
}

/**
* Print out summary of test run
*
* @param test.execution.Metrics $metrices
* @param float $overall
* @param [:test.Outcome] $failures
* @return void
*/
public function summary($metrics, $overall, $failures) {
$this->failures($failures);
$this->metrics($metrics, $overall);
}
}
91 changes: 91 additions & 0 deletions src/main/php/xp/test/Report.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php namespace xp\test;

abstract class Report {

/**
* Called when the test run starts
*
* @param test.source.Sources $sources
* @return void
*/
public function start($sources) { }

/**
* Called when entering a group. The group ends with one of the following:
*
* - `pass()` - All of the tests in this group passed
* - `fail()` - At least one of the tests failed
* - `skip()` - The entire group was skipped
* - `stop()` - The entire group errored
*
* @param test.execution.TestClass $group
* @return void
*/
public function enter($group) { }

/**
* Running a given test
*
* @param test.execution.TestClass $group
* @param test.execution.TestCase $test
* @param int $n
* @return void
*/
public function running($group, $test, $n) { }

/**
* Finished running a given test
*
* @param test.execution.TestClass $group
* @param test.execution.TestCase $test
* @param test.Outcome $outcome
* @return void
*/
public function finished($group, $test, $outcome) { }

/**
* Pass an entire group
*
* @param test.execution.TestClass $group
* @param test.Outcome[] $results
* @return void
*/
public function pass($group, $results) { }

/**
* Fail an entire group
*
* @param test.execution.TestClass $group
* @param test.Outcome[] $results
* @return void
*/
public function fail($group, $results) { }

/**
* Skip an entire group
*
* @param test.execution.TestClass $group
* @param string $reason
* @return void
*/
public function skip($group, $reason) { }

/**
* Stop an entire group
*
* @param test.execution.TestClass $group
* @param string $reason
* @return void
*/
public function stop($group, $reason) { }

/**
* Print out summary of test run
*
* @param test.execution.Metrics $metrices
* @param float $overall
* @param [:test.Outcome] $failures
* @return void
*/
public function summary($metrics, $overall, $failures) { }
}
Loading

0 comments on commit e2d74fd

Please sign in to comment.