An attempt at making the codeception plugin a little more complete.

Codeception JS plugin and theme changes.
Improvements to the display. Extra total information plus some test file locations.

Close #588
This commit is contained in:
Adam Cooper 2014-09-08 11:45:19 +01:00 committed by Tobias van Beek
parent 209454c5f6
commit 408eb5b974
9 changed files with 294 additions and 103 deletions

View file

@ -172,6 +172,7 @@ Services</a> του Bitbucket αποθετηρίου σας.',
'codeception_errors' => 'Λάθη Codeception',
'phpmd_warnings' => 'Προειδοποιήσεις PHPMD',
'phpcs_warnings' => 'Προειδοποιήσεις PHPCS ',
'codeception_errors' => 'Λάθη Codeception',
'phpcs_errors' => 'Λάθη PHPCS',
'phplint_errors' => 'Λάθη Lint',
'phpunit_errors' => 'Λάθη PHPUnit ',

View file

@ -190,6 +190,12 @@ PHPCI',
'technical_debt' => 'Technical Debt',
'behat' => 'Behat',
'codeception_feature' => 'Feature',
'codeception_suite' => 'Suite',
'codeception_time' => 'Time',
'codeception_synopsis' => '<strong>%1$d</strong> tests carried out in <strong>%2$f</strong> seconds.
<strong>%3$d</strong> failures.',
'file' => 'File',
'line' => 'Line',
'class' => 'Class',

View file

@ -9,50 +9,76 @@
namespace PHPCI\Plugin;
use PHPCI;
use PHPCI\Builder;
use PHPCI\Helper\Lang;
use PHPCI\Model\Build;
use PHPCI\Plugin\Util\TapParser;
use PHPCI\Plugin\Util\TestResultParsers\Codeception as Parser;
use Psr\Log\LogLevel;
/**
* Codeception Plugin - Enables full acceptance, unit, and functional testing
*
* Codeception Plugin - Enables full acceptance, unit, and functional testing.
* @author Don Gilbert <don@dongilbert.net>
* @author Igor Timoshenko <contact@igortimoshenko.com>
* @author Adam Cooper <adam@networkpie.co.uk>
* @package PHPCI
* @subpackage Plugins
*/
class Codeception implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
class Codeception implements \PHPCI\Plugin, \PHPCI\ZeroConfigPlugin
{
/**
* @var string
*/
/** @var string */
protected $args = '';
/**
* @var Build
*/
/** @var Builder */
protected $phpci;
/** @var Build */
protected $build;
/**
* @var Builder
* @var string $ymlConfigFile The path of a yml config for Codeception
*/
protected $phpci;
protected $ymlConfigFile;
/**
* @var string|string[] The path (or array of paths) of an yml config for Codeception
* @var string $path The path to the codeception tests folder.
*/
protected $configFile;
protected $path;
/**
* @var string The path where the reports and logs are stored
* @param $stage
* @param Builder $builder
* @param Build $build
* @return bool
*/
protected $logPath = 'tests/_output';
public static function canExecute($stage, Builder $builder, Build $build)
{
if ($stage == 'test' && !is_null(self::findConfigFile($builder->buildPath))) {
return true;
}
return false;
}
/**
* Try and find the codeception YML config file.
* @param $buildPath
* @return null|string
*/
public static function findConfigFile($buildPath)
{
if (file_exists($buildPath . 'codeception.yml')) {
return 'codeception.yml';
}
if (file_exists($buildPath . 'codeception.dist.yml')) {
return 'codeception.dist.yml';
}
return null;
}
/**
* Set up the plugin, configure options, etc.
*
* @param Builder $phpci
* @param Build $build
* @param array $options
@ -61,104 +87,96 @@ class Codeception implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
{
$this->phpci = $phpci;
$this->build = $build;
$this->path = 'tests/';
if (isset($options['config'])) {
$this->configFile = $options['config'];
if (empty($options['config'])) {
$this->ymlConfigFile = self::findConfigFile($this->phpci->buildPath);
}
if (isset($options['config'])) {
$this->ymlConfigFile = $options['config'];
}
if (isset($options['args'])) {
$this->args = (string) $options['args'];
}
if (isset($options['log_path'])) {
$this->logPath = $options['log_path'];
if (isset($options['path'])) {
$this->path = $options['path'];
}
}
/**
* {@inheritDoc}
* Runs Codeception tests, optionally using specified config file(s).
*/
public function execute()
{
if (empty($this->ymlConfigFile)) {
$this->phpci->logFailure('No configuration file found');
return false;
}
$success = true;
$this->phpci->logExecOutput(false);
// Run any config files first. This can be either a single value or an array
if ($this->configFile !== null) {
$success &= $this->runConfigFile($this->configFile);
}
$tapString = file_get_contents(
$this->phpci->buildPath . $this->logPath . DIRECTORY_SEPARATOR . 'report.tap.log'
);
try {
$tapParser = new TapParser($tapString);
$output = $tapParser->parse();
} catch (\Exception $ex) {
$this->phpci->logFailure($tapString);
throw $ex;
}
$failures = $tapParser->getTotalFailures();
$this->build->storeMeta('codeception-errors', $failures);
$this->build->storeMeta('codeception-data', $output);
$this->phpci->logExecOutput(true);
// Run any config files first. This can be either a single value or an array.
$success &= $this->runConfigFile($this->ymlConfigFile);
return $success;
}
/**
* {@inheritDoc}
*/
public static function canExecute($stage, Builder $builder, Build $build)
{
return $stage === 'test';
}
/**
* Run tests from a Codeception config file
*
* @param string $configPath
* Run tests from a Codeception config file.
* @param $configPath
* @return bool|mixed
* @throws \Exception
*/
protected function runConfigFile($configPath)
{
if (is_array($configPath)) {
return $this->recurseArg($configPath, array($this, 'runConfigFile'));
} else {
$this->phpci->logExecOutput(false);
$codecept = $this->phpci->findBinary('codecept');
$cmd = 'cd "%s" && ' . $codecept . ' run -c "%s" --tap ' . $this->args;
if (!$codecept) {
$this->phpci->logFailure(Lang::get('could_not_find', 'codecept'));
return false;
}
$cmd = 'cd "%s" && ' . $codecept . ' run -c "%s" --xml ' . $this->args;
if (IS_WIN) {
$cmd = 'cd /d "%s" && ' . $codecept . ' run -c "%s" --tap ' . $this->args;
$cmd = 'cd /d "%s" && ' . $codecept . ' run -c "%s" --xml ' . $this->args;
}
$configPath = $this->phpci->buildPath . $configPath;
$success = $this->phpci->executeCommand($cmd, $this->phpci->buildPath, $configPath);
$this->phpci->log(
'Codeception XML path: '. $this->phpci->buildPath . $this->path . '_output/report.xml',
Loglevel::DEBUG
);
$xml = file_get_contents($this->phpci->buildPath . $this->path . '_output/report.xml', false);
try {
$parser = new Parser($this->phpci, $xml);
$output = $parser->parse();
} catch (\Exception $ex) {
throw $ex;
}
$meta = array(
'tests' => $parser->getTotalTests(),
'timetaken' => $parser->getTotalTimeTaken(),
'failures' => $parser->getTotalFailures()
);
$this->build->storeMeta('codeception-meta', $meta);
$this->build->storeMeta('codeception-data', $output);
$this->build->storeMeta('codeception-errors', $parser->getTotalFailures());
$this->phpci->logExecOutput(true);
return $success;
}
}
/**
* @param array $array
* @param \Callback $callable
* @return bool|mixed
*/
protected function recurseArg(array $array, $callable)
{
$success = true;
foreach ($array as $subItem) {
$success &= call_user_func($callable, $subItem);
}
return $success;
}
}

View file

@ -0,0 +1,108 @@
<?php
namespace PHPCI\Plugin\Util\TestResultParsers;
use PHPCI\Builder;
/**
* Class Codeception
*
* @author Adam Cooper <adam@networkpie.co.uk>
* @package PHPCI\Plugin\Util\TestResultParsers
*/
class Codeception implements ParserInterface
{
protected $phpci;
protected $resultsXml;
protected $results;
protected $totalTests;
protected $totalTimeTaken;
protected $totalFailures;
/**
* @param Builder $phpci
* @param $resultsXml
*/
public function __construct(Builder $phpci, $resultsXml)
{
$this->phpci = $phpci;
$this->resultsXml = $resultsXml;
$this->totalTests = 0;
}
/**
* @return array An array of key/value pairs for storage in the plugins result metadata
*/
public function parse()
{
$rtn = array();
$this->results = new \SimpleXMLElement($this->resultsXml);
// calculate total results
foreach ($this->results->testsuite as $testsuite) {
$this->totalTests += (int) $testsuite['tests'];
$this->totalTimeTaken += (float) $testsuite['time'];
$this->totalFailures += (int) $testsuite['failures'];
foreach ($testsuite->testcase as $testcase) {
$testresult = array(
'suite' => (string) $testsuite['name'],
'file' => str_replace($this->phpci->buildPath, '/', (string) $testcase['file']),
'name' => (string) $testcase['name'],
'feature' => (string) $testcase['feature'],
'assertions' => (int) $testcase['assertions'],
'time' => (float) $testcase['time']
);
if (isset($testcase['class'])) {
$testresult['class'] = (string) $testcase['class'];
}
if (isset($testcase->failure)) {
$testresult['pass'] = false;
$testresult['message'] = (string) $testcase->failure;
} else {
$testresult['pass'] = true;
}
$rtn[] = $testresult;
}
}
return $rtn;
}
/**
* Get the total number of tests performed.
*
* @return int
*/
public function getTotalTests()
{
return $this->totalTests;
}
/**
* The time take to complete all tests
*
* @return mixed
*/
public function getTotalTimeTaken()
{
return $this->totalTimeTaken;
}
/**
* A count of the test failures
*
* @return mixed
*/
public function getTotalFailures()
{
return $this->totalFailures;
}
}

View file

@ -0,0 +1,16 @@
<?php
namespace PHPCI\Plugin\Util\TestResultParsers;
interface ParserInterface
{
/**
* @return array An array of key/value pairs for storage in the plugins result metadata
*/
public function parse();
public function getTotalTests();
public function getTotalTimeTaken();
public function getTotalFailures();
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,39 +1,45 @@
var codeceptionPlugin = ActiveBuild.UiPlugin.extend({
id: 'build-codeception-errors',
css: 'col-lg-6 col-md-12 col-sm-12 col-xs-12',
css: 'col-lg-12 col-md-12 col-sm-12 col-xs-12',
title: Lang.get('codeception'),
lastData: null,
lastMeta: null,
displayOnUpdate: false,
box: true,
rendered: false,
register: function() {
var self = this;
var query = ActiveBuild.registerQuery('codeception-data', -1, {key: 'codeception-data'})
var query_data = ActiveBuild.registerQuery('codeception-data', -1, {key: 'codeception-data'});
var query_meta_data = ActiveBuild.registerQuery('codeception-meta', -1, {key: 'codeception-meta'});
$(window).on('codeception-data', function(data) {
self.onUpdate(data);
self.onUpdateData(data);
});
$(window).on('codeception-meta', function(data) {
self.onUpdateMeta(data);
});
$(window).on('build-updated', function() {
if (!self.rendered) {
self.displayOnUpdate = true;
query();
query_data();
query_meta_data();
}
});
},
render: function() {
return $('<table class="table" id="codeception-data">' +
'<thead>' +
'<tr>' +
' <th>'+Lang.get('test')+'</th>' +
'</tr>' +
'</thead><tbody></tbody></table>');
'<tr><th>'+Lang.get('codeception_suite')+'</th>' +
'<th>'+Lang.get('codeception_feature')+'</th>' +
'<th>'+Lang.get('codeception_time')+'</th></tr>' +
'</thead><tbody></tbody><tfoot></tfoot></table>');
},
onUpdate: function(e) {
onUpdateData: function(e) {
if (!e.queryData) {
$('#build-codeception-errors').hide();
return;
@ -53,22 +59,54 @@ var codeceptionPlugin = ActiveBuild.UiPlugin.extend({
for (var i in tests) {
var row = $('<tr>' +
'<td><strong>'+tests[i].suite+'' +
'::'+tests[i].test+'</strong><br>' +
''+(tests[i].message || '')+'</td>' +
var rows = $('<tr data-toggle="collapse" data-target="#collapse'+i+'">' +
'<td><strong>'+tests[i].suite+'</strong</td>' +
'<td>'+tests[i].feature+'</td>' +
'<td>'+tests[i].time+'</td>'+
'</tr>' +
'<tr id="collapse'+i+'" class="collapse" >' +
'<td></td><td colspan="2">' +
'<small><strong>'+Lang.get('name')+':</strong> '+tests[i].name+'</small><br />' +
'<small><strong>'+Lang.get('file')+':</strong> '+tests[i].file+'</small><br />' +
(tests[i].message
? '<small><strong>'+Lang.get('message')+':</strong> '+tests[i].message+'</small>'
: '') +
'</td>' +
'</tr>');
if (!tests[i].pass) {
row.addClass('danger');
rows.first().addClass('danger');
} else {
row.addClass('success');
rows.first().addClass('success');
}
tbody.append(row);
tbody.append(rows);
}
$('#build-codeception-errors').show();
},
onUpdateMeta: function(e) {
if (!e.queryData) {
return;
}
$('#build-codeception-errors').show();
$('#build-codeception-errors td').tooltip();
this.lastMeta = e.queryData;
var data = this.lastMeta[0].meta_value;
var tfoot = $('#codeception-data tfoot');
tfoot.empty();
var row = $('<tr>' +
'<td colspan="3">' +
Lang.get('codeception_synopsis', data.tests, data.timetaken, data.failures) +
'</td>' +
'</tr>');
tfoot.append(row);
}
});