Behat/Behat/Formatter/JUnitFormatter.php
namespace Behat\Behat\Formatter;
use Behat\Behat\Event\EventInterface,
Behat\Behat\Event\FeatureEvent,
Behat\Behat\Event\ScenarioEvent,
Behat\Behat\Event\OutlineExampleEvent,
Behat\Behat\Event\StepEvent;
use Behat\Gherkin\Node\FeatureNode,
Behat\Gherkin\Node\ScenarioNode,
Behat\Gherkin\Node\StepNode,
Behat\Behat\Exception\FormatterException;
/*
* This file is part of the Behat.
* (c) Konstantin Kudryashov
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Progress formatter.
*
* @author Konstantin Kudryashov
*/
class JUnitFormatter extends ConsoleFormatter
{
/**
* Current XML filename.
*
* @var string
*/
protected $filename;
/**
* Test cases.
*
* @var array
*/
protected $testcases = array();
/**
* Total steps count.
*
* @var integer
*/
protected $stepsCount = 0;
/**
* Total exceptions count.
*
* @var integer
*/
protected $exceptionsCount = 0;
/**
* Step exceptions.
*
* @var array
*/
protected $exceptions = array();
/**
* Feature start time.
*
* @var float
*/
protected $featureStartTime;
/**
* Scenario start time.
*
* @var float
*/
protected $scenarioStartTime;
/**
* {@inheritdoc}
*/
protected function getDefaultParameters()
{
return array();
}
/**
* Returns an array of event names this subscriber wants to listen to.
*
* The array keys are event names and the value can be:
*
* * The method name to call (priority defaults to 0)
* * An array composed of the method name to call and the priority
* * An array of arrays composed of the method names to call and respective
* priorities, or 0 if unset
*
* For instance:
*
* * array('eventName' => 'methodName')
* * array('eventName' => array('methodName', $priority))
* * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
*
* @return array The event names to listen to
*/
public static function getSubscribedEvents()
{
$events = array(
'beforeFeature', 'afterFeature', 'beforeScenario', 'afterScenario',
'beforeOutlineExample', 'afterOutlineExample', 'afterStep'
);
return array_combine($events, $events);
}
/**
* Listens to "feature.before" event.
*
* @param FeatureEvent $event
*
* @uses printTestSuiteHeader()
*/
public function beforeFeature(FeatureEvent $event)
{
$feature = $event->getFeature();
$this->filename = 'TEST-' . basename($feature->getFile(), '.feature') . '.xml';
$this->printTestSuiteHeader($feature);
$this->stepsCount = 0;
$this->testcases = array();
$this->exceptionsCount = 0;
$this->featureStartTime = microtime(true);
}
/**
* Listens to "feature.after" event.
*
* @param FeatureEvent $event
*
* @uses printTestSuiteFooter()
* @uses flushOutputConsole()
*/
public function afterFeature(FeatureEvent $event)
{
$this->printTestSuiteFooter($event->getFeature(), microtime(true) - $this->featureStartTime);
$this->flushOutputConsole();
}
/**
* Listens to "scenario.before" event.
*
* @param ScenarioEvent $event
*/
public function beforeScenario(ScenarioEvent $event)
{
$this->scenarioStartTime = microtime(true);
}
/**
* Listens to "scenario.after" event.
*
* @param ScenarioEvent $event
*
* @uses printTestCase()
*/
public function afterScenario(ScenarioEvent $event)
{
$this->printTestCase($event->getScenario(), microtime(true) - $this->scenarioStartTime, $event);
}
/**
* Listens to "outline.example.before" event.
*
* @param OutlineExampleEvent $event
*/
public function beforeOutlineExample(OutlineExampleEvent $event)
{
$this->scenarioStartTime = microtime(true);
}
/**
* Listens to "outline.example.after" event.
*
* @param OutlineExampleEvent $event
*
* @uses printTestCase()
*/
public function afterOutlineExample(OutlineExampleEvent $event)
{
$this->printTestCase($event->getOutline(), microtime(true) - $this->scenarioStartTime, $event);
}
/**
* Listens to "step.after" event.
*
* @param StepEvent $event
*/
public function afterStep(StepEvent $event)
{
if ($event->hasException()) {
$this->exceptions[] = $event->getException();
$this->exceptionsCount++;
}
++$this->stepsCount;
}
/**
* Prints testsuite header.
*
* @param FeatureNode $feature
*/
protected function printTestSuiteHeader(FeatureNode $feature)
{
$this->writeln('');
}
/**
* Prints testsuite footer.
*
* @param FeatureNode $feature
* @param float $time
*/
protected function printTestSuiteFooter(FeatureNode $feature, $time)
{
$suiteStats = sprintf('classname="behat.features" errors="0" failures="%d" name="%s" file="%s" tests="%d" time="%F"',
$this->exceptionsCount,
htmlspecialchars($feature->getTitle()),
htmlspecialchars($feature->getFile()),
$this->stepsCount,
$time
);
$this->writeln("");
$this->writeln(implode("\n", $this->testcases));
$this->writeln('');
}
/**
* Prints testcase.
*
* @param ScenarioNode $scenario
* @param float $time
* @param EventInterface $event
*/
protected function printTestCase(ScenarioNode $scenario, $time, EventInterface $event)
{
$className = $scenario->getFeature()->getTitle();
$name = $scenario->getTitle();
$name .= $event instanceof OutlineExampleEvent
? ', Ex #' . ($event->getIteration() + 1)
: '';
$caseStats = sprintf('classname="%s" name="%s" time="%F"',
htmlspecialchars($className),
htmlspecialchars($name),
$time
);
$xml = " \n";
foreach ($this->exceptions as $exception) {
$error = $this->exceptionToString($exception);
$xml .= sprintf(
' ',
htmlspecialchars($error),
$this->getResultColorCode($event->getResult())
);
$exception = str_replace(array(''), '', (string) $exception);
$xml .= "\n";
}
$this->exceptions = array();
$xml .= " ";
$this->testcases[] = $xml;
}
/**
* {@inheritdoc}
*/
protected function createOutputStream()
{
$outputPath = $this->parameters->get('output_path');
if (null === $outputPath) {
throw new FormatterException(sprintf(
'You should specify "output_path" parameter for %s', get_class($this)
));
} elseif (is_file($outputPath)) {
throw new FormatterException(sprintf(
'Directory path expected as "output_path" parameter of %s, but got: %s',
get_class($this),
$outputPath
));
}
if (!is_dir($outputPath)) {
mkdir($outputPath, 0777, true);
}
return fopen($outputPath . DIRECTORY_SEPARATOR . $this->filename, 'w');
}
}