This commit is contained in:
Pablo Tejada 2016-11-09 02:33:18 +00:00 committed by GitHub
commit 7ffe6b212b
13 changed files with 1352 additions and 183 deletions

View file

@ -181,6 +181,7 @@ PHPCI',
'phpcs_errors' => 'PHPCS Errors',
'phplint_errors' => 'Lint Errors',
'phpunit_errors' => 'PHPUnit Errors',
'phpunit_fail_init' => 'Neither a configuration file nor a test directory found.',
'phpdoccheck_warnings' => 'Missing Docblocks',
'issues' => 'Issues',

View file

@ -176,6 +176,7 @@ PHPCI',
'phpcs_errors' => 'PHPCS Errors',
'phplint_errors' => 'Lint Errors',
'phpunit_errors' => 'PHPUnit Errors',
'phpunit_fail_init' => 'No se encontro archivo o folder de pruevas.',
'phpdoccheck_warnings' => 'Docblocks faltantes',
'issues' => 'Incidencias',

View file

@ -0,0 +1,272 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2014, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link https://www.phptesting.org/
*/
namespace PHPCI\Plugin\Option;
/**
* Class PhpUnitOptions validates and parse the option for the PhpUnitV2 plugin
*
* @author Pablo Tejada <pablo@ptejada.com>
* @package PHPCI
* @subpackage Plugin
*/
class PhpUnitOptions
{
protected $options;
protected $arguments = array();
public function __construct($options)
{
$this->options = $options;
}
/**
* Remove a command argument
*
* @param $argumentName
*
* @return $this
*/
public function removeArgument($argumentName)
{
unset($this->arguments[$argumentName]);
return $this;
}
/**
* Combine all the argument into a string for the phpunit command
*
* @return string
*/
public function buildArgumentString()
{
$argumentString = '';
foreach ($this->getCommandArguments() as $argumentName => $argumentValues) {
$prefix = $argumentName[0] == '-' ? '' : '--';
if (!is_array($argumentValues)) {
$argumentValues = array($argumentValues);
}
foreach ($argumentValues as $argValue) {
$postfix = ' ';
if (!empty($argValue)) {
$postfix = ' "' . $argValue . '" ';
}
$argumentString .= $prefix . $argumentName . $postfix;
}
}
return $argumentString;
}
/**
* Get all the command arguments
*
* @return string[]
*/
public function getCommandArguments()
{
/*
* Return the full list of arguments
*/
return $this->parseArguments()->arguments;
}
/**
* Parse the arguments from the config options
*
* @return $this
*/
private function parseArguments()
{
if (empty($this->arguments)) {
/*
* Parse the arguments from the YML options file
*/
if (isset($this->options['args'])) {
$rawArgs = $this->options['args'];
if (is_array($rawArgs)) {
$this->arguments = $rawArgs;
} else {
/*
* Try to parse old arguments in a single string
*/
preg_match_all('@--([a-z\-]+)([\s=]+)?[\'"]?((?!--)[-\w/.,\\\]+)?[\'"]?@', (string)$rawArgs, $argsMatch);
if (!empty($argsMatch) && sizeof($argsMatch) > 2) {
foreach ($argsMatch[1] as $index => $argName) {
$this->addArgument($argName, $argsMatch[3][$index]);
}
}
}
}
/*
* Handles command aliases outside of the args option
*/
if (isset($this->options['coverage'])) {
$this->addArgument('coverage-html', $this->options['coverage']);
}
/*
* Handles command aliases outside of the args option
*/
if (isset($this->options['config'])) {
$this->addArgument('configuration', $this->options['config']);
}
}
return $this;
}
/**
* Add an argument to the collection
* Note: adding argument before parsing the options will prevent the other options from been parsed.
*
* @param string $argumentName
* @param string $argumentValue
*/
public function addArgument($argumentName, $argumentValue)
{
if (isset($this->arguments[$argumentName])) {
if (!is_array($this->arguments[$argumentName])) {
// Convert existing argument values into an array
$this->arguments[$argumentName] = array($this->arguments[$argumentName]);
}
// Appends the new argument to the list
$this->arguments[$argumentName][] = $argumentValue;
} else {
// Adds new argument
$this->arguments[$argumentName] = $argumentValue;
}
}
/**
* Get the list of directory to run phpunit in
*
* @return string[] List of directories
*/
public function getDirectories()
{
$directories = $this->getOption('directory');
if (is_string($directories)) {
$directories = array($directories);
} else {
if (is_null($directories)) {
$directories = array();
}
}
return is_array($directories) ? $directories : array($directories);
}
/**
* Get an option if defined
*
* @param $optionName
*
* @return string[]|string|null
*/
public function getOption($optionName)
{
if (isset($this->options[$optionName])) {
return $this->options[$optionName];
}
return null;
}
/**
* Get the directory to execute the command from
*
* @return mixed|null
*/
public function getRunFrom()
{
return $this->getOption('run_from');
}
/**
* Ge the directory name where tests file reside
*
* @return string|null
*/
public function getTestsPath()
{
return $this->getOption('path');
}
/**
* Get the PHPUnit configuration from the options, or the optional path
*
* @param string $altPath
*
* @return string[] path of files
*/
public function getConfigFiles($altPath = '')
{
$configFiles = $this->getArgument('configuration');
if (empty($configFiles)) {
$configFile = self::findConfigFile($altPath);
if ($configFile) {
$configFiles[] = $configFile;
}
}
return $configFiles;
}
/**
* Get options for a given argument
*
* @param $argumentName
*
* @return string[] All the options for given argument
*/
public function getArgument($argumentName)
{
$this->parseArguments();
if (isset($this->arguments[$argumentName])) {
return is_array(
$this->arguments[$argumentName]
) ? $this->arguments[$argumentName] : array($this->arguments[$argumentName]);
}
return array();
}
/**
* Find a PHPUnit configuration file in a directory
*
* @param string $buildPath The path to configuration file
*
* @return null|string
*/
public static function findConfigFile($buildPath)
{
$files = array(
'phpunit.xml',
'phpunit.xml.dist',
'tests/phpunit.xml',
'tests/phpunit.xml.dist',
);
foreach ($files as $file) {
if (is_file($buildPath . $file)) {
return $file;
}
}
return null;
}
}

View file

@ -11,54 +11,59 @@ namespace PHPCI\Plugin;
use PHPCI;
use PHPCI\Builder;
use PHPCI\Helper\Lang;
use PHPCI\Model\Build;
use PHPCI\Plugin\Util\TapParser;
use PHPCI\Model\BuildError;
use PHPCI\Plugin\Option\PhpUnitOptions;
use PHPCI\Plugin\Util\PhpUnitResult;
/**
* PHP Unit Plugin - Allows PHP Unit testing.
* @author Dan Cryer <dan@block8.co.uk>
* @package PHPCI
* @subpackage Plugins
*/
* PHP Unit Plugin - A rewrite of the original PHP Unit plugin
*
* @author Dan Cryer <dan@block8.co.uk>
* @author Pablo Tejada <pablo@ptejada.com>
* @package PHPCI
* @subpackage Plugins
*/
class PhpUnit implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
{
protected $args;
protected $phpci;
protected $build;
/** @var string[] Raw options from the PHPCI config file */
protected $options = array();
/**
* @var string|string[] $directory The directory (or array of dirs) to run PHPUnit on
* Standard Constructor
* $options['config'] Path to a PHPUnit XML configuration file.
* $options['run_from'] The directory where the phpunit command will run from when using 'config'.
* $options['coverage'] Value for the --coverage-html command line flag.
* $options['directory'] Optional directory or list of directories to run PHPUnit on.
* $options['args'] Command line args (in string format) to pass to PHP Unit
*
* @param Builder $phpci
* @param Build $build
* @param string[] $options
*/
protected $directory;
public function __construct(Builder $phpci, Build $build, array $options = array())
{
$this->phpci = $phpci;
$this->build = $build;
$this->options = new PhpUnitOptions($options);
}
/**
* @var string $runFrom When running PHPUnit with an XML config, the command is run from this directory
*/
protected $runFrom;
/**
* @var string, in cases where tests files are in a sub path of the /tests path,
* allows this path to be set in the config.
*/
protected $path;
protected $coverage = "";
/**
* @var string|string[] $xmlConfigFile The path (or array of paths) of an xml config for PHPUnit
*/
protected $xmlConfigFile;
/**
* Check if this plugin can be executed.
* @param $stage
* Check if the plugin can be executed without any configurations
*
* @param $stage
* @param Builder $builder
* @param Build $build
* @param Build $build
*
* @return bool
*/
public static function canExecute($stage, Builder $builder, Build $build)
{
if ($stage == 'test' && !is_null(self::findConfigFile($builder->buildPath))) {
if ($stage == 'test' && !is_null(PhpUnitOptions::findConfigFile($build->getBuildPath()))) {
return true;
}
@ -66,183 +71,138 @@ class PhpUnit implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
}
/**
* Try and find the phpunit XML config file.
* @param $buildPath
* @return null|string
* Runs PHP Unit tests in a specified directory, optionally using specified config file(s).
*/
public static function findConfigFile($buildPath)
{
if (file_exists($buildPath . 'phpunit.xml')) {
return 'phpunit.xml';
}
if (file_exists($buildPath . 'tests' . DIRECTORY_SEPARATOR . 'phpunit.xml')) {
return 'tests' . DIRECTORY_SEPARATOR . 'phpunit.xml';
}
if (file_exists($buildPath . 'phpunit.xml.dist')) {
return 'phpunit.xml.dist';
}
if (file_exists($buildPath . 'tests/phpunit.xml.dist')) {
return 'tests' . DIRECTORY_SEPARATOR . 'phpunit.xml.dist';
}
return null;
}
/**
* Standard Constructor
*
* $options['directory'] Output Directory. Default: %BUILDPATH%
* $options['filename'] Phar Filename. Default: build.phar
* $options['regexp'] Regular Expression Filename Capture. Default: /\.php$/
* $options['stub'] Stub Content. No Default Value
*
* @param Builder $phpci
* @param Build $build
* @param array $options
*/
public function __construct(Builder $phpci, Build $build, array $options = array())
{
$this->phpci = $phpci;
$this->build = $build;
if (empty($options['config']) && empty($options['directory'])) {
$this->xmlConfigFile = self::findConfigFile($phpci->buildPath);
}
if (isset($options['directory'])) {
$this->directory = $options['directory'];
}
if (isset($options['config'])) {
$this->xmlConfigFile = $options['config'];
}
if (isset($options['run_from'])) {
$this->runFrom = $options['run_from'];
}
if (isset($options['args'])) {
$this->args = $this->phpci->interpolate($options['args']);
}
if (isset($options['path'])) {
$this->path = $options['path'];
}
if (isset($options['coverage'])) {
$this->coverage = ' --coverage-html ' . $this->phpci->interpolate($options['coverage']) . ' ';
}
}
/**
* Runs PHP Unit tests in a specified directory, optionally using specified config file(s).
*/
public function execute()
{
if (empty($this->xmlConfigFile) && empty($this->directory)) {
$this->phpci->logFailure('Neither configuration file nor test directory found.');
$xmlConfigFiles = $this->options->getConfigFiles($this->build->getBuildPath());
$directories = $this->options->getDirectories();
if (empty($xmlConfigFiles) && empty($directories)) {
$this->phpci->logFailure(Lang::get('phpunit_fail_init'));
return false;
}
$success = true;
$success = array();
$this->phpci->logExecOutput(false);
// Run any config files first. This can be either a single value or an array.
if ($this->xmlConfigFile !== null) {
$success &= $this->runConfigFile($this->xmlConfigFile);
// Run any directories
if (!empty($directories)) {
foreach ($directories as $directory) {
$success[] = $this->runDir($directory);
}
} else {
// Run any config files
if (!empty($xmlConfigFiles)) {
foreach ($xmlConfigFiles as $configFile) {
$success[] = $this->runConfigFile($configFile);
}
}
}
// Run any dirs next. Again this can be either a single value or an array.
if ($this->directory !== null) {
$success &= $this->runDir($this->directory);
}
return !in_array(false, $success);
}
$tapString = $this->phpci->getLastOutput();
$tapString = mb_convert_encoding($tapString, "UTF-8", "ISO-8859-1");
/**
* Run the PHPUnit tests in a specific directory or array of directories.
*
* @param $directory
*
* @return bool|mixed
*/
protected function runDir($directory)
{
$options = clone $this->options;
try {
$tapParser = new TapParser($tapString);
$output = $tapParser->parse();
} catch (\Exception $ex) {
$this->phpci->logFailure($tapString);
throw $ex;
}
$buildPath = $this->build->getBuildPath() . DIRECTORY_SEPARATOR;
$failures = $tapParser->getTotalFailures();
$currentPath = getcwd();
// Change the directory
chdir($buildPath);
$this->build->storeMeta('phpunit-errors', $failures);
$this->build->storeMeta('phpunit-data', $output);
// Save the results into a json file
$jsonFile = tempnam(dirname($buildPath), 'jLog_');
$options->addArgument('log-json', $jsonFile);
$this->phpci->logExecOutput(true);
// Removes any current configurations files
$options->removeArgument('configuration');
$arguments = $this->phpci->interpolate($options->buildArgumentString());
$cmd = $this->phpci->findBinary('phpunit') . ' %s "%s"';
$success = $this->phpci->executeCommand($cmd, $arguments, $directory);
// Change to che original path
chdir($currentPath);
$this->processResults($jsonFile);
return $success;
}
/**
* Run the tests defined in a PHPUnit config file.
* @param $configPath
*
* @param $configFile
*
* @return bool|mixed
*/
protected function runConfigFile($configPath)
protected function runConfigFile($configFile)
{
if (is_array($configPath)) {
return $this->recurseArg($configPath, array($this, "runConfigFile"));
} else {
if ($this->runFrom) {
$curdir = getcwd();
chdir($this->phpci->buildPath . DIRECTORY_SEPARATOR . $this->runFrom);
}
$options = clone $this->options;
$runFrom = $options->getRunFrom();
$phpunit = $this->phpci->findBinary('phpunit');
$cmd = $phpunit . ' --tap %s -c "%s" ' . $this->coverage . $this->path;
$success = $this->phpci->executeCommand($cmd, $this->args, $this->phpci->buildPath . $configPath);
if ($this->runFrom) {
chdir($curdir);
}
return $success;
$buildPath = $this->build->getBuildPath() . DIRECTORY_SEPARATOR;
if ($runFrom) {
$originalPath = getcwd();
// Change the directory
chdir($buildPath . $runFrom);
}
}
/**
* Run the PHPUnit tests in a specific directory or array of directories.
* @param $directory
* @return bool|mixed
*/
protected function runDir($directory)
{
if (is_array($directory)) {
return $this->recurseArg($directory, array($this, "runDir"));
} else {
$curdir = getcwd();
chdir($this->phpci->buildPath);
// Save the results into a json file
$jsonFile = tempnam($this->phpci->buildPath, 'jLog_');
$options->addArgument('log-json', $jsonFile);
$phpunit = $this->phpci->findBinary('phpunit');
// Removes any current configurations files
$options->removeArgument('configuration');
// Only the add the configuration file been passed
$options->addArgument('configuration', $buildPath . $configFile);
$cmd = $phpunit . ' --tap %s "%s"';
$success = $this->phpci->executeCommand($cmd, $this->args, $this->phpci->buildPath . $directory);
chdir($curdir);
return $success;
$arguments = $this->phpci->interpolate($options->buildArgumentString());
$cmd = $this->phpci->findBinary('phpunit') . ' %s %s';
$success = $this->phpci->executeCommand($cmd, $arguments, $options->getTestsPath());
if (!empty($originalPath)) {
// Change to che original path
chdir($originalPath);
}
}
/**
* @param $array
* @param $callable
* @return bool|mixed
*/
protected function recurseArg($array, $callable)
{
$success = true;
foreach ($array as $subItem) {
$success &= call_user_func($callable, $subItem);
}
$this->processResults($jsonFile);
return $success;
}
/**
* Saves the test results
*
* @param string $jsonFile
*
* @throws \Exception If the failed to parse the JSON file
*/
protected function processResults($jsonFile)
{
if (is_file($jsonFile)) {
$parser = new PhpUnitResult($jsonFile, $this->build->getBuildPath());
$this->build->storeMeta('phpunit-data', $parser->parse()->getResults());
$this->build->storeMeta('phpunit-errors', $parser->getFailures());
foreach ($parser->getErrors() as $error) {
$severity = $error['severity'] == $parser::SEVERITY_ERROR ? BuildError::SEVERITY_CRITICAL : BuildError::SEVERITY_HIGH;
$this->build->reportError(
$this->phpci, 'php_unit', $error['message'], $severity, $error['file'], $error['line']
);
}
} else {
throw new \Exception('JSON output file does not exist: ' . $jsonFile);
}
}
}

View file

@ -0,0 +1,230 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2014, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link https://www.phptesting.org/
*/
namespace PHPCI\Plugin\Util;
/**
* Class PhpUnitResult parses the results for the PhpUnitV2 plugin
*
* @author Pablo Tejada <pablo@ptejada.com>
* @package PHPCI
* @subpackage Plugin
*/
class PhpUnitResult
{
const EVENT_TEST = 'test';
const EVENT_TEST_START = 'testStart';
const EVENT_SUITE_START = 'suiteStart';
const SEVERITY_PASS = 'success';
const SEVERITY_FAIL = 'fail';
const SEVERITY_ERROR = 'error';
const SEVERITY_SKIPPED = 'skipped';
protected $options;
protected $arguments = array();
protected $results;
protected $failures = 0;
protected $errors = array();
public function __construct($outputFile, $buildPath = '')
{
$this->outputFile = $outputFile;
$this->buildPath = $buildPath;
}
/**
* Parse the results
*
* @return $this
* @throws \Exception If fails to parse the output
*/
public function parse()
{
$rawResults = file_get_contents($this->outputFile);
if (empty($rawResults)) {
throw new \Exception('No test executed.');
}
if ($rawResults[0] == '{') {
$fixedJson = '[' . str_replace('}{', '},{', $rawResults) . ']';
$events = json_decode($fixedJson, true);
} else {
$events = json_decode($rawResults, true);
}
// Reset the parsing variables
$this->results = array();
$this->errors = array();
$this->failures = 0;
if (is_array($events)) {
foreach ($events as $event) {
if ($event['event'] == self::EVENT_TEST) {
$this->results[] = $this->parseEvent($event);
}
}
} else {
throw new \Exception('Failed to parse the JSON output.');
}
return $this;
}
/**
* Parse a test event
*
* @param array $event
*
* @return string[]
*/
protected function parseEvent($event)
{
list($pass, $severity) = $this->getStatus($event);
$data = array(
'pass' => $pass,
'severity' => $severity,
'message' => $this->buildMessage($event),
'trace' => $pass ? array() : $this->buildTrace($event),
'output' => $event['output'],
);
if (!$pass) {
$this->failures++;
$this->addError($data, $event);
}
return $data;
}
/**
* Build the status of the event
*
* @param $event
*
* @return mixed[bool,string] - The pass and severity flags
* @throws \Exception
*/
protected function getStatus($event)
{
$status = $event['status'];
switch ($status) {
case 'fail':
$pass = false;
$severity = self::SEVERITY_FAIL;
break;
case 'error':
if (strpos($event['message'], 'Skipped') === 0 || strpos($event['message'], 'Incomplete') === 0) {
$pass = true;
$severity = self::SEVERITY_SKIPPED;
} else {
$pass = false;
$severity = self::SEVERITY_ERROR;
}
break;
case 'pass':
$pass = true;
$severity = self::SEVERITY_PASS;
break;
default:
throw new \Exception("Unexpected PHPUnit test status: {$status}");
break;
}
return array($pass, $severity);
}
/**
* Build the message string for an event
*
* @param array $event
*
* @return string
*/
protected function buildMessage($event)
{
$message = $event['test'];
if ($event['message']) {
$message .= PHP_EOL . $event ['message'];
}
return $message;
}
/**
* Build a string base trace of the failure
*
* @param array $event
*
* @return string[]
*/
protected function buildTrace($event)
{
$formattedTrace = array();
if (!empty($event['trace'])) {
foreach ($event['trace'] as $step){
$line = str_replace($this->buildPath, '', $step['file']) . ':' . $step['line'];
$formattedTrace[] = $line;
}
}
return $formattedTrace;
}
/**
* Saves additional info for a failing test
*
* @param array $data
* @param array $event
*/
protected function addError($data, $event)
{
$firstTrace = end($event['trace']);
reset($event['trace']);
$this->errors[] = array(
'message' => $data['message'],
'severity' => $data['severity'],
'file' => str_replace($this->buildPath, '', $firstTrace['file']),
'line' => $firstTrace['line'],
);
}
/**
* Get the parse results
*
* @return string[]
*/
public function getResults()
{
return $this->results;
}
/**
* Get the total number of failing tests
*
* @return int
*/
public function getFailures()
{
return $this->failures;
}
/**
* Get the tests with failing status
*
* @return string[]
*/
public function getErrors()
{
return $this->errors;
}
}

View file

@ -3,7 +3,7 @@ use PHPCI\Helper\Lang;
$linkTemplate = $build->getFileLinkTemplate();
/** @var \PHPCI\Model\BuildError $error */
/** @var \PHPCI\Model\BuildError[] $errors */
foreach ($errors as $error):
$link = str_replace('{FILE}', $error->getFile(), $linkTemplate);
@ -30,8 +30,8 @@ foreach ($errors as $error):
?>
</a>
</td>
<td><?php print $error->getMessage(); ?></td>
<td class="visible-line-breaks"><?php print $error->getMessage(); ?></td>
</tr>
<?php endforeach; ?>
<?php endforeach; ?>

View file

@ -18,7 +18,11 @@
<link href="<?php print PHPCI_URL; ?>assets/css/AdminLTE.min.css" rel="stylesheet" type="text/css" />
<link href="<?php print PHPCI_URL; ?>assets/css/AdminLTE-skins.min.css" rel="stylesheet" type="text/css" />
<link href="<?php print PHPCI_URL; ?>assets/css/AdminLTE-custom.css" rel="stylesheet" type="text/css" />
<style type="text/css">
.visible-line-breaks {
white-space: pre-wrap;
}
</style>
<script>
var PHPCI_URL = '<?php print PHPCI_URL; ?>';
var PHPCI_LANGUAGE = <?php print json_encode(Lang::getLanguage()); ?>;

View file

@ -0,0 +1,131 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2013, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link https://www.phptesting.org/
*/
namespace Tests\PHPCI\Plugin;
use PHPCI\Plugin\Option\PhpUnitOptions;
/**
* Unit test for the PHPUnitOptions parser
*
* @author Pablo Tejada <pablo@ptejada.com>
*/
class PhpUnitOptionsTest extends \PHPUnit_Framework_TestCase
{
public function validOptionsProvider()
{
return array(
array(
array(
'config' => 'tests/phpunit.xml',
'args' => '--stop-on-error --log-junit /path/to/log/',
),
array(
'stop-on-error' => '',
'log-junit' => '/path/to/log/',
'configuration' => 'tests/phpunit.xml',
),
),
array(
array(
'coverage' => '/path/to/coverage2/',
'args' => array(
'coverage-html' => '/path/to/coverage1/',
),
),
array(
'coverage-html' => array(
'/path/to/coverage1/',
'/path/to/coverage2/',
),
),
),
array(
array(
'directory' => array(
'/path/to/test1/',
'/path/to/test2/',
),
'args' => array(
'coverage-html' => '/path/to/coverage1/',
),
),
array(
'coverage-html' => '/path/to/coverage1/',
),
),
array(
array(
'config' => array('tests/phpunit.xml'),
'args' => "--testsuite=unit --bootstrap=vendor/autoload.php",
),
array(
'testsuite' => 'unit',
'bootstrap' => 'vendor/autoload.php',
'configuration' => array('tests/phpunit.xml'),
),
),
array(
array(
'config' => array('tests/phpunit.xml'),
'args' => "--testsuite='unit' --bootstrap 'vendor/autoload.php'",
),
array(
'testsuite' => 'unit',
'bootstrap' => 'vendor/autoload.php',
'configuration' => array('tests/phpunit.xml'),
),
),
array(
array(
'config' => array('tests/phpunit.xml'),
'args' => '--testsuite="unit" --bootstrap "vendor/autoload.php"',
),
array(
'testsuite' => 'unit',
'bootstrap' => 'vendor/autoload.php',
'configuration' => array('tests/phpunit.xml'),
),
),
);
}
/**
* @param $rawOptions
* @param $parsedArguments
*
* @dataProvider validOptionsProvider
*/
public function testCommandArguments($rawOptions, $parsedArguments)
{
$options = new PhpUnitOptions($rawOptions);
$this->assertSame($parsedArguments, $options->getCommandArguments());
}
public function testGetters()
{
$options = new PhpUnitOptions(
array(
'run_from' => '/path/to/run/from',
'path' => 'subTest',
)
);
$this->assertEquals('/path/to/run/from', $options->getRunFrom());
$this->assertEquals('subTest', $options->getTestsPath());
$this->assertNull($options->getOption('random'));
$this->assertEmpty($options->getDirectories());
$this->assertEmpty($options->getConfigFiles());
$files = $options->getConfigFiles(PHPCI_DIR);
$this->assertFileExists(PHPCI_DIR . $files[0]);
}
}

View file

@ -0,0 +1,127 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2013, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link https://www.phptesting.org/
*/
namespace Tests\PHPCI\Plugin;
/**
* Unit test for the PHPUnit plugin.
*
* @author Pablo Tejada <pablo@ptejada.com>
*/
class PhpUnitTest extends \PHPUnit_Framework_TestCase
{
public function testSingleConfigFile()
{
$options = array(
'config' => PHPCI_DIR . 'phpunit.xml'
);
$mockPlugin = $this->getPluginBuilder($options)->setMethods(array('runConfigFile'))->getMock();
$mockPlugin->expects($this->once())->method('runConfigFile')->with(PHPCI_DIR . 'phpunit.xml');
$mockPlugin->execute();
}
public function testMultiConfigFile()
{
$options = array(
'config' => array(
PHPCI_DIR . 'phpunit1.xml',
PHPCI_DIR . 'phpunit2.xml',
)
);
$mockPlugin = $this->getPluginBuilder($options)->setMethods(array('runConfigFile'))->getMock();
$mockPlugin->expects($this->exactly(2))->method('runConfigFile')->withConsecutive(
array(PHPCI_DIR . 'phpunit1.xml'), array(PHPCI_DIR . 'phpunit2.xml')
);
$mockPlugin->execute();
}
/**
* @param array $options
*
* @return \PHPUnit_Framework_MockObject_MockBuilder
*/
protected function getPluginBuilder($options = array())
{
$loggerMock = $this->getMockBuilder('\Monolog\Logger')
->setConstructorArgs(array('Test'))
->setMethods(array('addRecord'))
->getMock();
$mockBuild = $this->getMockBuilder('\PHPCI\Model\Build')->getMock();
$mockBuilder = $this->getMockBuilder('\PHPCI\Builder')
->setConstructorArgs(array($mockBuild, $loggerMock))
->setMethods(array('executeCommand'))->getMock();
return $this->getMockBuilder('PHPCI\Plugin\PhpUnitV2')->setConstructorArgs(
array($mockBuilder, $mockBuild, $options)
);
}
public function testSingleDir()
{
$options = array(
'directory' => '/test/directory/one'
);
$mockPlugin = $this->getPluginBuilder($options)->setMethods(array('runDir'))->getMock();
$mockPlugin->expects($this->once())->method('runDir')->with('/test/directory/one');
$mockPlugin->execute();
}
public function testMultiDir()
{
$options = array(
'directory' => array(
'/test/directory/one',
'/test/directory/two',
)
);
$mockPlugin = $this->getPluginBuilder($options)->setMethods(array('runDir'))->getMock();
$mockPlugin->expects($this->exactly(2))->method('runDir')->withConsecutive(
array('/test/directory/one'), array('/test/directory/two')
);
$mockPlugin->execute();
}
public function testProcessResultsFromConfig()
{
$options = array(
'config' => PHPCI_DIR . 'phpunit.xml'
);
$mockPlugin = $this->getPluginBuilder($options)->setMethods(array('processResults'))->getMock();
$mockPlugin->expects($this->once())->method('processResults')->with($this->isType('string'));
$mockPlugin->execute();
}
public function testProcessResultsFromDir()
{
$options = array(
'directory' => PHPCI_DIR . 'Tests'
);
$mockPlugin = $this->getPluginBuilder($options)->setMethods(array('processResults'))->getMock();
$mockPlugin->expects($this->once())->method('processResults')->with($this->isType('string'));
$mockPlugin->execute();
}
}

View file

@ -0,0 +1,10 @@
{
"event": "suiteStart",
"suite": "Money Test Suite",
"tests": 61
}
{
"event": "suiteStart",
"suite": "Tests\\Money\\MoneyTest",
"tests": 15
}

View file

@ -0,0 +1,355 @@
{
"event": "suiteStart",
"suite": "Money Test Suite",
"tests": 61
}{
"event": "suiteStart",
"suite": "Tests\\Money\\MoneyTest",
"tests": 15
}{
"event": "testStart",
"suite": "Tests\\Money\\MoneyTest",
"test": "Tests\\Money\\MoneyTest::testFactoryMethods"
}{
"event": "test",
"suite": "Tests\\Money\\MoneyTest",
"test": "Tests\\Money\\MoneyTest::testFactoryMethods",
"status": "pass",
"time": 0.051446914672852,
"trace": [],
"message": "",
"output": ""
}{
"event": "testStart",
"suite": "Tests\\Money\\MoneyTest",
"test": "Tests\\Money\\MoneyTest::testJsonEncoding"
}{
"event": "test",
"suite": "Tests\\Money\\MoneyTest",
"test": "Tests\\Money\\MoneyTest::testJsonEncoding",
"status": "pass",
"time": 0.00051498413085938,
"trace": [],
"message": "",
"output": ""
}{
"event": "testStart",
"suite": "Tests\\Money\\MoneyTest",
"test": "Tests\\Money\\MoneyTest::testMaxInit"
}{
"event": "test",
"suite": "Tests\\Money\\MoneyTest",
"test": "Tests\\Money\\MoneyTest::testMaxInit",
"status": "pass",
"time": 0.0023708343505859,
"trace": [],
"message": "",
"output": ""
}{
"event": "testStart",
"suite": "Tests\\Money\\MoneyTest",
"test": "Tests\\Money\\MoneyTest::testFailure"
}{
"event": "test",
"suite": "Tests\\Money\\MoneyTest",
"test": "Tests\\Money\\MoneyTest::testFailure",
"status": "error",
"time": 0.0025370121002197,
"trace": [
{
"file": "\/path\/to\/build\/src\/Money.php",
"line": 335
},
{
"file": "\/path\/to\/build\/tests\/MoneyTest.php",
"line": 43,
"function": "divide",
"class": "Money\\Money",
"type": "->"
}
],
"message": "Division by zero",
"output": ""
}{
"event": "testStart",
"suite": "Tests\\Money\\MoneyTest",
"test": "Tests\\Money\\MoneyTest::testFailure2"
}{
"event": "test",
"suite": "Tests\\Money\\MoneyTest",
"test": "Tests\\Money\\MoneyTest::testFailure2",
"status": "fail",
"time": 0.008944034576416,
"trace": [
{
"file": "\/path\/to\/build\/tests\/MoneyTest.php",
"line": 61,
"function": "assertEquals",
"class": "PHPUnit_Framework_Assert",
"type": "::"
}
],
"message": "Failed asserting that two objects are equal.",
"output": ""
}{
"event": "testStart",
"suite": "Tests\\Money\\MoneyTest",
"test": "Tests\\Money\\MoneyTest::testFailure3"
}{
"event": "testStart",
"suite": "Tests\\Money\\MoneyTest",
"test": "Tests\\Money\\MoneyTest::testMaxInit"
}{
"event": "test",
"suite": "Tests\\Money\\MoneyTest",
"test": "Tests\\Money\\MoneyTest::testMaxInit",
"status": "error",
"time": 0.0074319839477539,
"trace": [
{
"file": "\/path\/to\/build\/tests\/MoneyTest.php",
"line": 36,
"function": "markTestIncomplete",
"class": "PHPUnit_Framework_Assert",
"type": "::"
}
],
"message": "Incomplete Test: No yet finished...",
"output": ""
}{
"event": "test",
"suite": "Tests\\Money\\MoneyTest",
"test": "Tests\\Money\\MoneyTest::testFailure3",
"status": "fail",
"time": 0.0014960765838623,
"trace": [
{
"file": "\/path\/to\/build\/tests\/MoneyTest.php",
"line": 66,
"function": "assertEquals",
"class": "PHPUnit_Framework_Assert",
"type": "::"
}
],
"message": "Failed asserting that two arrays are equal.",
"output": ""
}{
"event": "testStart",
"suite": "Tests\\Money\\MoneyTest",
"test": "Tests\\Money\\MoneyTest::testFailure4"
}{
"event": "test",
"suite": "Tests\\Money\\MoneyTest",
"test": "Tests\\Money\\MoneyTest::testFailure4",
"status": "fail",
"time": 0.0023319721221924,
"trace": [
{
"file": "\/path\/to\/build\/tests\/MoneyTest.php",
"line": 71,
"function": "assertTrue",
"class": "PHPUnit_Framework_Assert",
"type": "::"
}
],
"message": "Really? It should have been true\nFailed asserting that false is true.",
"output": ""
}{
"event": "suiteStart",
"suite": "Tests\\Money\\MoneyTest::testFailure5",
"tests": 3
}{
"event": "testStart",
"suite": "Tests\\Money\\MoneyTest::testFailure5",
"test": "Tests\\Money\\MoneyTest::testFailure5 with data set #0 (1, 2, 3, 4, 5, 6)"
}{
"event": "test",
"suite": "Tests\\Money\\MoneyTest::testFailure5",
"test": "Tests\\Money\\MoneyTest::testFailure5 with data set #0 (1, 2, 3, 4, 5, 6)",
"status": "fail",
"time": 0.0025498867034912,
"trace": [
{
"file": "\/path\/to\/build\/tests\/MoneyTest.php",
"line": 82,
"function": "assertSame",
"class": "PHPUnit_Framework_Assert",
"type": "::"
}
],
"message": "Failed asserting that 3 is identical to 1.",
"output": ""
}{
"event": "testStart",
"suite": "Tests\\Money\\MoneyTest::testFailure5",
"test": "Tests\\Money\\MoneyTest::testFailure5 with data set #1 ('one', 'two', 'three', 'four', 'five', 'six')"
}{
"event": "test",
"suite": "Tests\\Money\\MoneyTest::testFailure5",
"test": "Tests\\Money\\MoneyTest::testFailure5 with data set #1 ('one', 'two', 'three', 'four', 'five', 'six')",
"status": "fail",
"time": 0.00087904930114746,
"trace": [
{
"file": "\/path\/to\/build\/tests\/MoneyTest.php",
"line": 82,
"function": "assertSame",
"class": "PHPUnit_Framework_Assert",
"type": "::"
}
],
"message": "Failed asserting that two strings are identical.",
"output": ""
}{
"event": "testStart",
"suite": "Tests\\Money\\MoneyTest::testFailure5",
"test": "Tests\\Money\\MoneyTest::testFailure5 with data set #2 (array(1, 'one'), array(2, 'two'), array(3, 'three'), array(4, 'four'))"
}{
"event": "test",
"suite": "Tests\\Money\\MoneyTest::testFailure5",
"test": "Tests\\Money\\MoneyTest::testFailure5 with data set #2 (array(1, 'one'), array(2, 'two'), array(3, 'three'), array(4, 'four'))",
"status": "fail",
"time": 0.0010340213775635,
"trace": [
{
"file": "\/path\/to\/build\/tests\/MoneyTest.php",
"line": 82,
"function": "assertSame",
"class": "PHPUnit_Framework_Assert",
"type": "::"
}
],
"message": "Failed asserting that Array &0 (\n 0 => 3\n 1 => 'three'\n) is identical to Array &0 (\n 0 => 1\n 1 => 'one'\n).",
"output": ""
}{
"event": "suiteStart",
"suite": "Tests\\Money\\MoneyTest::testSkipped",
"tests": 3
}{
"event": "testStart",
"suite": "Tests\\Money\\MoneyTest::testSkipped",
"test": "Tests\\Money\\MoneyTest::testSkipped with data set #0 (1, 2, 3, 4, 5, 6)"
}{
"event": "test",
"suite": "Tests\\Money\\MoneyTest::testSkipped",
"test": "Tests\\Money\\MoneyTest::testSkipped with data set #0 (1, 2, 3, 4, 5, 6)",
"status": "error",
"time": 0.0041179656982422,
"trace": [
{
"file": "\/path\/to\/build\/tests\/MoneyTest.php",
"line": 99,
"function": "markTestSkipped",
"class": "PHPUnit_Framework_Assert",
"type": "::"
}
],
"message": "Skipped Test: This test is currently failing",
"output": ""
}{
"event": "testStart",
"suite": "Tests\\Money\\MoneyTest::testSkipped",
"test": "Tests\\Money\\MoneyTest::testSkipped with data set #1 ('one', 'two', 'three', 'four', 'five', 'six')"
}{
"event": "test",
"suite": "Tests\\Money\\MoneyTest::testSkipped",
"test": "Tests\\Money\\MoneyTest::testSkipped with data set #1 ('one', 'two', 'three', 'four', 'five', 'six')",
"status": "error",
"time": 0.00082302093505859,
"trace": [
{
"file": "\/path\/to\/build\/tests\/MoneyTest.php",
"line": 99,
"function": "markTestSkipped",
"class": "PHPUnit_Framework_Assert",
"type": "::"
}
],
"message": "Skipped Test: This test is currently failing",
"output": ""
}{
"event": "testStart",
"suite": "Tests\\Money\\MoneyTest::testSkipped",
"test": "Tests\\Money\\MoneyTest::testSkipped with data set #2 (array(1, 'one'), array(2, 'two'), array(3, 'three'), array(4, 'four'))"
}{
"event": "test",
"suite": "Tests\\Money\\MoneyTest::testSkipped",
"test": "Tests\\Money\\MoneyTest::testSkipped with data set #2 (array(1, 'one'), array(2, 'two'), array(3, 'three'), array(4, 'four'))",
"status": "error",
"time": 0.00070905685424805,
"trace": [
{
"file": "\/path\/to\/build\/tests\/MoneyTest.php",
"line": 99,
"function": "markTestSkipped",
"class": "PHPUnit_Framework_Assert",
"type": "::"
}
],
"message": "Skipped Test: This test is currently failing",
"output": ""
}{
"event": "testStart",
"suite": "",
"test": "Tests\\Money\\MoneyTest::testDepends1"
}{
"event": "test",
"suite": "",
"test": "Tests\\Money\\MoneyTest::testDepends1",
"status": "fail",
"time": 0.00078010559082031,
"trace": [
{
"file": "\/path\/to\/build\/tests\/MoneyTest.php",
"line": 105,
"function": "assertTrue",
"class": "PHPUnit_Framework_Assert",
"type": "::"
}
],
"message": "Failed asserting that false is true.",
"output": ""
}{
"event": "test",
"suite": "",
"test": "Tests\\Money\\MoneyTest::testDepends1",
"status": "error",
"time": 0,
"trace": [],
"message": "Skipped Test: This test depends on \"Tests\\Money\\MoneyTest::testDepends1\" to pass.",
"output": ""
}{
"event": "suiteStart",
"suite": "Tests\\Money\\Parser\\IntlMoneyParserTest",
"tests": 23
}{
"event": "suiteStart",
"suite": "Tests\\Money\\Parser\\IntlMoneyParserTest::testIntlParser",
"tests": 18
}{
"event": "testStart",
"suite": "Tests\\Money\\Parser\\IntlMoneyParserTest::testIntlParser",
"test": "Tests\\Money\\Parser\\IntlMoneyParserTest::testIntlParser with data set #0 ('$1000.50', 100050)"
}{
"event": "test",
"suite": "Tests\\Money\\Parser\\IntlMoneyParserTest::testIntlParser",
"test": "Tests\\Money\\Parser\\IntlMoneyParserTest::testIntlParser with data set #0 ('$1000.50', 100050)",
"status": "pass",
"time": 0.0069050788879395,
"trace": [],
"message": "",
"output": ""
}{
"event": "testStart",
"suite": "Tests\\Money\\Parser\\IntlMoneyParserTest::testIntlParser",
"test": "Tests\\Money\\Parser\\IntlMoneyParserTest::testIntlParser with data set #1 ('$1000.00', 100000)"
}{
"event": "test",
"suite": "Tests\\Money\\Parser\\IntlMoneyParserTest::testIntlParser",
"test": "Tests\\Money\\Parser\\IntlMoneyParserTest::testIntlParser with data set #1 ('$1000.00', 100000)",
"status": "pass",
"time": 0.00067996978759766,
"trace": [],
"message": "",
"output": ""
}

View file

@ -0,0 +1,55 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2014, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link https://www.phptesting.org/
*/
namespace Tests\PHPCI\Plugin\Util;
use PHPCI\Plugin\Util\PhpUnitResult;
/**
* Class PhpUnitResultTest parses the results for the PhpUnitV2 plugin
* @author Pablo Tejada <pablo@ptejada.com>
* @package PHPCI
* @subpackage Plugin
*/
class PhpUnitResultTest extends \PHPUnit_Framework_TestCase
{
public function testInitParse()
{
$buildPath = '/path/to/build';
$parser = new PhpUnitResult(PHPCI_DIR . 'Tests/PHPCI/Plugin/SampleFiles/phpunit_money.txt', $buildPath);
$output = $parser->parse()->getResults();
$errors = $parser->getErrors();
$this->assertEquals(8, $parser->getFailures());
$this->assertInternalType('array', $output);
$this->assertInternalType('array', $errors);
$this->assertNotEmpty($output);
$this->assertNotEmpty($errors);
// The trace elements should not include the build path
$this->assertStringStartsNotWith($buildPath, $output[3]['trace'][0]);
$this->assertStringStartsNotWith($buildPath, $output[3]['trace'][1]);
$this->assertEquals(PhpUnitResult::SEVERITY_SKIPPED, $output[5]['severity']);
$this->assertContains('Incomplete Test:', $output[5]['message']);
$this->assertEquals(PhpUnitResult::SEVERITY_SKIPPED, $output[11]['severity']);
$this->assertContains('Skipped Test:', $output[11]['message']);
}
public function testParseFailure()
{
$this->setExpectedException('\Exception', 'Failed to parse the JSON output');
$buildPath = '/path/to/build';
$parser = new PhpUnitResult(PHPCI_DIR . 'Tests/PHPCI/Plugin/SampleFiles/invalid_format.txt', $buildPath);
$parser->parse();
}
}

View file

@ -33,6 +33,11 @@ var phpunitPlugin = ActiveBuild.UiPlugin.extend({
var input = $(ev.target);
$('#phpunit-data tbody ' + input.data('target')).toggle(input.prop('checked'));
});
$(document).on('click', '#phpunit-data button.trace', function() {
var $btn = $(this);
$($btn).replaceWith(self.buildTrace($btn.data('trace')));
});
},
render: function() {
@ -69,7 +74,7 @@ var phpunitPlugin = ActiveBuild.UiPlugin.extend({
for (var i in tests) {
var content = $('<td colspan="3"></td>'),
message = $('<div></div>').appendTo(content),
message = $('<div class="visible-line-breaks"></div>').appendTo(content),
severity = tests[i].severity || (tests[i].pass ? 'success' : 'failed');
if (tests[i].message) {
@ -84,6 +89,13 @@ var phpunitPlugin = ActiveBuild.UiPlugin.extend({
content.append('<div>' + this.repr(tests[i].data) + '</div>');
}
if (tests[i].trace && tests[i].trace.length) {
var $traceBtn = $('<button class="btn btn-default btn-xs trace" type="button" title="Expand Trace">...</button>');
$traceBtn.data('trace', tests[i].trace);
content.append('Trace: ');
content.append($traceBtn);
}
$('<tr class="'+ severity + '"></tr>').append(content).appendTo(tbody);
counts[severity]++;
@ -141,6 +153,17 @@ var phpunitPlugin = ActiveBuild.UiPlugin.extend({
'</table>';
}
return '???';
},
buildTrace: function(trace){
var list = '<ol reversed>';
trace.forEach(function(line){
list += '<li>' + line + '</li>';
});
list += '</ol>';
return list;
}
});