Skip to content

Commit

Permalink
Support for multiple reporters. (#202)
Browse files Browse the repository at this point in the history
  • Loading branch information
ezzatron authored Jan 19, 2017
1 parent 25ec1ce commit ee10264
Show file tree
Hide file tree
Showing 10 changed files with 323 additions and 26 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- **[NEW]** Introduced focused specs for powerful test isolation ([#199],
[#204], [#197], [#194], [#188], [#185], [#181])
- **[NEW]** Support for multiple reporters ([#202], [#186])
- **[IMPROVED]** Stack trace output now excludes irrelevant information ([#203],
[#170])
- **[FIXED]** Fixed error handler signature ([#198] - thanks [@YuraLukashik])
Expand All @@ -14,12 +15,14 @@
[#171]: https://github.com/peridot-php/peridot/issues/171
[#181]: https://github.com/peridot-php/peridot/issues/181
[#185]: https://github.com/peridot-php/peridot/issues/185
[#186]: https://github.com/peridot-php/peridot/issues/186
[#188]: https://github.com/peridot-php/peridot/pull/188
[#194]: https://github.com/peridot-php/peridot/pull/194
[#197]: https://github.com/peridot-php/peridot/pull/197
[#198]: https://github.com/peridot-php/peridot/pull/198
[#199]: https://github.com/peridot-php/peridot/pull/199
[#201]: https://github.com/peridot-php/peridot/pull/201
[#202]: https://github.com/peridot-php/peridot/pull/202
[#203]: https://github.com/peridot-php/peridot/pull/203
[#204]: https://github.com/peridot-php/peridot/pull/204

Expand Down
13 changes: 7 additions & 6 deletions specs/command.spec.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,13 @@
});
});

context('when passing a reporter name', function() {
it('should set the reporter name on the configuration object', function() {
$this->factory->register('test', 'desc', function() {});
$this->command->run(new ArrayInput(['-r' => 'test'], $this->definition), $this->output);
$reporter = $this->configuration->getReporter();
assert($reporter == 'test', 'reporter name should be "test"');
context('when passing reporter names', function() {
it('should set the reporter names on the configuration object', function() {
$this->factory->register('test-a', 'desc', function() {});
$this->factory->register('test-b', 'desc', function() {});
$this->command->run(new ArrayInput(['-r' => ['test-a', 'test-b']], $this->definition), $this->output);
$reporters = $this->configuration->getReporters();
assert($reporters === ['test-a', 'test-b'], 'reporter names should be ["test-a", "test-b"]');
});
});

Expand Down
63 changes: 63 additions & 0 deletions specs/composite-reporter.spec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php
use Evenement\EventEmitter;
use Peridot\Configuration;
use Peridot\Reporter\AbstractBaseReporter;
use Peridot\Reporter\CompositeReporter;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\NullOutput;

describe('CompositeReporter', function() {

beforeEach(function() {
$this->configuration = new Configuration();
$this->output = new BufferedOutput();
$this->emitter = new EventEmitter();
$this->reporterA = new FakeReporter($this->configuration, new BufferedOutput(), $this->emitter);
$this->reporterB = new FakeReporter($this->configuration, new NullOutput(), $this->emitter);
$this->reporterC = new FakeReporter($this->configuration, new BufferedOutput(), $this->emitter);
$this->reporters = [$this->reporterA, $this->reporterB, $this->reporterC];
$this->reporter = new CompositeReporter($this->reporters, $this->configuration, $this->output, $this->emitter);
});

context('->setEventEmitter()', function() {
beforeEach(function () {
$this->emitter2 = new EventEmitter();
$this->reporter->setEventEmitter($this->emitter2);
});

it('should set the event emitter', function() {
assert($this->reporter->getEventEmitter() === $this->emitter2, 'should be the same event emitter');
});

it('should set the event emitter for each wrapped reporter', function() {
assert($this->reporterA->getEventEmitter() === $this->emitter2, 'should be the same event emitter');
assert($this->reporterB->getEventEmitter() === $this->emitter2, 'should be the same event emitter');
assert($this->reporterC->getEventEmitter() === $this->emitter2, 'should be the same event emitter');
});
});

context('when runner.end is emitted', function() {
it('should include an error number and the test description', function() {
$this->emitter->emit('runner.end', [1.0]);
$content = $this->output->fetch();
$expected = implode([
PHP_EOL,
spl_object_hash($this->reporterA),
PHP_EOL,
PHP_EOL,
spl_object_hash($this->reporterC),
PHP_EOL
]);
assert($content === $expected, 'output should contain wrapped reporter output');
});
});

});

class FakeReporter extends AbstractBaseReporter
{
public function init()
{
$this->getOutput()->writeln(spl_object_hash($this));
}
}
28 changes: 28 additions & 0 deletions specs/configuration.spec.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,34 @@
});
});

describe('->setReporter()', function() {
it('should set both reporter and reporters', function() {
$this->configuration->setReporter('reporter-a');

assert($this->configuration->getReporter() === 'reporter-a', 'should have set reporter');
assert($this->configuration->getReporters() === ['reporter-a'], 'should have set reporters');
});
});

describe('->setReporters()', function() {
it('should set both reporter and reporters', function() {
$this->configuration->setReporters(['reporter-a', 'reporter-b']);

assert($this->configuration->getReporter() === 'reporter-a', 'should have set reporter');
assert($this->configuration->getReporters() === ['reporter-a', 'reporter-b'], 'should have set reporters');
});

it('should disallow setting an empty reporters array', function() {
$exception = null;
try {
$this->configuration->setReporters([]);
} catch (InvalidArgumentException $e) {
$exception = $e;
}
assert(!is_null($exception), 'expected exception to be thrown');
});
});

describe('->enableColorsExplicit()', function() {
it('should enable colors when explicit is set', function() {
$this->configuration->enableColorsExplicit();
Expand Down
50 changes: 50 additions & 0 deletions specs/reporter-factory.spec.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
use Peridot\Configuration;
use Peridot\Core\Suite;
use Peridot\Reporter\AnonymousReporter;
use Peridot\Reporter\CompositeReporter;
use Peridot\Reporter\ReporterFactory;
use Peridot\Reporter\SpecReporter;
use Peridot\Runner\Runner;
Expand Down Expand Up @@ -55,6 +56,55 @@
});
});

describe('->createComposite()', function() {
context('using valid reporter names', function() {
it('should return a composite of the named reporters', function() {
$this->factory->register('spec2', 'desc', function($reporter) {});
$reporter = $this->factory->createComposite(['spec', 'spec2']);
$reporters = $reporter->getReporters();
assert($reporter instanceof CompositeReporter, 'should create CompositeReporter');
assert($reporters[0] instanceof SpecReporter, 'first reporter should be a SpecReporter');
});
});

context('using valid names with invalid factories', function() {
it('should throw an exception', function() {
$this->factory->register('nope', 'doesnt work', 'Not\A\Class');
$exception = null;
try {
$this->factory->createComposite(['spec', 'nope']);
} catch (RuntimeException $e) {
$exception = $e;
}
assert(!is_null($exception), 'exception should have been thrown');
});
});

context('using invalid names', function() {
it('should throw an exception', function() {
$exception = null;
try {
$this->factory->createComposite(['spec', 'nope']);
} catch (RuntimeException $e) {
$exception = $e;
}
assert(!is_null($exception), 'exception should have been thrown');
});
});

context('using an empty name list', function() {
it('should throw an exception', function() {
$exception = null;
try {
$this->factory->createComposite([]);
} catch (InvalidArgumentException $e) {
$exception = $e;
}
assert(!is_null($exception), 'exception should have been thrown');
});
});
});

describe('->getReporters()', function() {
it("should return an array of reporter information", function() {
$reporters = $this->factory->getReporters();
Expand Down
47 changes: 43 additions & 4 deletions src/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ class Configuration
protected $grep = '*.spec.php';

/**
* @var string
* @var array
*/
protected $reporter = 'spec';
protected $reporters = ['spec'];

/**
* @var string
Expand Down Expand Up @@ -137,7 +137,7 @@ public function getSkipPattern()
*/
public function setReporter($reporter)
{
return $this->write('reporter', $reporter);
return $this->writeReporters([$reporter]);
}

/**
Expand All @@ -147,7 +147,32 @@ public function setReporter($reporter)
*/
public function getReporter()
{
return $this->reporter;
return $this->reporters[0];
}

/**
* Set the names of the reporters to use
*
* @param array $reporters
* @return $this
*/
public function setReporters(array $reporters)
{
if (empty($reporters)) {
throw new \InvalidArgumentException('Reporters cannot be empty.');
}

return $this->writeReporters($reporters);
}

/**
* Return the names of the reporters configured for use
*
* @return array
*/
public function getReporters()
{
return $this->reporters;
}

/**
Expand Down Expand Up @@ -328,4 +353,18 @@ protected function normalizeRegexPattern($pattern)

return '~\b' . preg_quote($pattern, '~') . '\b~';
}

/**
* Write the reporters and persist them to the current environment.
*
* @param array $reporters
* @return $this
*/
protected function writeReporters(array $reporters)
{
$this->reporters = $reporters;
putenv('PERIDOT_REPORTER=' . $reporters[0]);
putenv('PERIDOT_REPORTERS=' . implode(',', $reporters));
return $this;
}
}
7 changes: 2 additions & 5 deletions src/Console/Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
return 0;
}

if ($reporter = $input->getOption('reporter')) {
$this->configuration->setReporter($reporter);
}

$this->configuration->setReporters($input->getOption('reporter'));
$this->eventEmitter->emit('peridot.load', [$this, $this->configuration]);

return $this->getResult();
Expand Down Expand Up @@ -171,7 +168,7 @@ protected function getResult()
{
$result = new TestResult($this->eventEmitter);
$this->getLoader()->load($this->configuration->getPath());
$this->factory->create($this->configuration->getReporter());
$this->factory->createComposite($this->configuration->getReporters());
$this->runner->run($result);

if ($result->getFailureCount() > 0) {
Expand Down
2 changes: 1 addition & 1 deletion src/Console/InputDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public function __construct()
$this->addOption(new InputOption('grep', 'g', InputOption::VALUE_REQUIRED, 'Run tests with filenames matching <pattern> <comment>(default: *.spec.php)</comment>'));
$this->addOption(new InputOption('no-colors', 'C', InputOption::VALUE_NONE, 'Disable output colors'));
$this->addOption(new InputOption('--force-colors', null, InputOption::VALUE_NONE, 'Force output colors'));
$this->addOption(new InputOption('reporter', 'r', InputOption::VALUE_REQUIRED, 'Select which reporter to use <comment>(default: spec)</comment>'));
$this->addOption(new InputOption('reporter', 'r', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Select which reporter(s) to use', ['spec']));
$this->addOption(new InputOption('bail', 'b', InputOption::VALUE_NONE, 'Stop on failure'));
$this->addOption(new InputOption('configuration', 'c', InputOption::VALUE_REQUIRED, 'A php file containing peridot configuration'));
$this->addOption(new InputOption('reporters', null, InputOption::VALUE_NONE, 'List all available reporters'));
Expand Down
86 changes: 86 additions & 0 deletions src/Reporter/CompositeReporter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php
namespace Peridot\Reporter;

use Evenement\EventEmitterInterface;
use Peridot\Configuration;
use Peridot\Core\HasEventEmitterTrait;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;

/**
* Combines multiple reporters.
*
* @package Peridot\Reporter
*/
class CompositeReporter extends AbstractBaseReporter
{
/**
* @var array
*/
private $reporters;

/**
* @param array $reporters
* @param Configuration $configuration
* @param OutputInterface $output
* @param EventEmitterInterface $eventEmitter
*/
public function __construct(
array $reporters,
Configuration $configuration,
OutputInterface $output,
EventEmitterInterface $eventEmitter
) {
$this->reporters = $reporters;

parent::__construct($configuration, $output, $eventEmitter);
}

/**
* Return the wrapped reporters.
*
* @return array
*/
public function getReporters()
{
return $this->reporters;
}

/**
* Initialize reporter. Setup and listen for runner events.
*
* @return void
*/
public function init()
{
$this->eventEmitter->on('runner.end', [$this, 'onRunnerEnd']);
}

/**
* @param \Evenement\EventEmitterInterface $eventEmitter
*/
public function setEventEmitter(EventEmitterInterface $eventEmitter)
{
parent::setEventEmitter($eventEmitter);

array_map(function (ReporterInterface $reporter) use ($eventEmitter) {
$reporter->setEventEmitter($eventEmitter);
}, $this->reporters);

return $this;
}

public function onRunnerEnd()
{
$stdout = $this->getOutput();

array_map(function (ReporterInterface $reporter) use ($stdout) {
$output = $reporter->getOutput();

if ($output instanceof BufferedOutput && $content = $output->fetch()) {
$stdout->writeln('');
$stdout->write($content);
}
}, $this->reporters);
}
}
Loading

0 comments on commit ee10264

Please sign in to comment.