php-censor/src/Plugin/PhpUnit.php
2018-05-01 19:57:11 +07:00

265 lines
8.6 KiB
PHP

<?php
namespace PHPCensor\Plugin;
use PHPCensor\Config;
use PHPCensor;
use PHPCensor\Builder;
use PHPCensor\Model\Build;
use PHPCensor\Model\BuildError;
use PHPCensor\Plugin\Option\PhpUnitOptions;
use PHPCensor\Plugin\Util\PhpUnitResultJson;
use PHPCensor\Plugin\Util\PhpUnitResultJunit;
use PHPCensor\Plugin;
use PHPCensor\ZeroConfigPluginInterface;
use Symfony\Component\Filesystem\Filesystem;
/**
* PHP Unit Plugin - A rewrite of the original PHP Unit plugin
*
* @author Dan Cryer <dan@block8.co.uk>
* @author Pablo Tejada <pablo@ptejada.com>
*/
class PhpUnit extends Plugin implements ZeroConfigPluginInterface
{
/**
* @var string
*/
protected $buildDirectory;
/**
* @var string
*/
protected $buildBranchDirectory;
/**
* @var string
*/
protected $buildLocation;
/**
* @var string
*/
protected $buildBranchLocation;
/** @var PhpUnitOptions*/
protected $options;
/**
* @return string
*/
public static function pluginName()
{
return 'php_unit';
}
/**
* 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 $builder
* @param Build $build
* @param string[] $options
*/
public function __construct(Builder $builder, Build $build, array $options = [])
{
parent::__construct($builder, $build, $options);
$this->buildDirectory = $build->getBuildDirectory();
$this->buildBranchDirectory = $build->getBuildBranchDirectory();
$this->buildLocation = PUBLIC_DIR . 'artifacts/phpunit/' . $this->buildDirectory;
$this->buildBranchLocation = PUBLIC_DIR . 'artifacts/phpunit/' . $this->buildBranchDirectory;
$this->options = new PhpUnitOptions($options, $this->buildLocation);
}
/**
* Check if the plugin can be executed without any configurations
*
* @param string $stage
* @param Builder $builder
* @param Build $build
*
* @return bool
*/
public static function canExecute($stage, Builder $builder, Build $build)
{
if ($stage == Build::STAGE_TEST && !is_null(PhpUnitOptions::findConfigFile($build->getBuildPath()))) {
return true;
}
return false;
}
/**
* Runs PHP Unit tests in a specified directory, optionally using specified config file(s).
*/
public function execute()
{
$xmlConfigFiles = $this->options->getConfigFiles($this->build->getBuildPath());
$directories = $this->options->getDirectories();
if (empty($xmlConfigFiles) && empty($directories)) {
$this->builder->logFailure('Neither a configuration file nor a test directory found.');
return false;
}
$cmd = $this->findBinary('phpunit');
$lastLine = exec($cmd.' --log-json . --version');
if (false !== strpos($lastLine, '--log-json')) {
$logFormat = 'junit'; // --log-json is not supported
} else {
$logFormat = 'json';
}
$success = [];
// Run any directories
if (!empty($directories)) {
foreach ($directories as $directory) {
$success[] = $this->runConfig($directory, null, $logFormat);
}
} else {
// Run any config files
if (!empty($xmlConfigFiles)) {
foreach ($xmlConfigFiles as $configFile) {
$success[] = $this->runConfig($this->options->getTestsPath(), $configFile, $logFormat);
}
}
}
return !in_array(false, $success);
}
/**
* Run the tests defined in a PHPUnit config file or in a specific directory.
*
* @param string $directory
* @param string|null $configFile
* @param string $logFormat
*
* @return bool|mixed
*
* @throws \Exception
*/
protected function runConfig($directory, $configFile, $logFormat)
{
$allowPublicArtifacts = (bool)Config::getInstance()->get(
'php-censor.build.allow_public_artifacts',
true
);
$fileSystem = new Filesystem();
$options = clone $this->options;
$buildPath = $this->build->getBuildPath();
// Save the results into a log file
$logFile = tempnam(sys_get_temp_dir(), 'jlog_');
$options->addArgument('log-' . $logFormat, $logFile);
// Removes any current configurations files
$options->removeArgument('configuration');
if (null !== $configFile) {
// Only the add the configuration file been passed
$options->addArgument('configuration', $buildPath . $configFile);
}
if ($options->getOption('coverage') && $allowPublicArtifacts) {
if (!$fileSystem->exists($this->buildLocation)) {
$fileSystem->mkdir($this->buildLocation, (0777 & ~umask()));
}
if (!is_writable($this->buildLocation)) {
throw new \Exception(sprintf(
'The location %s is not writable or does not exist.',
$this->buildLocation
));
}
}
$arguments = $this->builder->interpolate($options->buildArgumentString());
$cmd = $this->findBinary('phpunit') . ' %s %s';
$success = $this->builder->executeCommand($cmd, $arguments, $directory);
$output = $this->builder->getLastOutput();
if (
$fileSystem->exists($this->buildLocation) &&
$options->getOption('coverage') &&
$allowPublicArtifacts
) {
$fileSystem->remove($this->buildBranchLocation);
$fileSystem->mirror($this->buildLocation, $this->buildBranchLocation);
}
$this->processResults($logFile, $logFormat);
$config = $this->builder->getSystemConfig('php-censor');
if ($options->getOption('coverage')) {
preg_match(
'#Classes:[\s]*(.*?)%[^M]*?Methods:[\s]*(.*?)%[^L]*?Lines:[\s]*(.*?)\%#s',
$output,
$matches
);
$this->build->storeMeta('phpunit-coverage', [
'classes' => !empty($matches[1]) ? $matches[1] : '0.00',
'methods' => !empty($matches[2]) ? $matches[2] : '0.00',
'lines' => !empty($matches[3]) ? $matches[3] : '0.00',
]);
if ($allowPublicArtifacts) {
$this->builder->logSuccess(
sprintf(
"\nPHPUnit successful build coverage report.\nYou can use coverage report for this build: %s\nOr coverage report for last build in the branch: %s",
$config['url'] . '/artifacts/phpunit/' . $this->buildDirectory . '/index.html',
$config['url'] . '/artifacts/phpunit/' . $this->buildBranchDirectory . '/index.html'
)
);
}
}
return $success;
}
/**
* Saves the test results
*
* @param string $logFile
* @param string $logFormat
*
* @throws \Exception If failed to parse the log file
*/
protected function processResults($logFile, $logFormat)
{
if (file_exists($logFile)) {
if ('json' === $logFormat) {
$parser = new PhpUnitResultJson($logFile, $this->build->getBuildPath());
} else {
$parser = new PhpUnitResultJunit($logFile, $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->builder, 'php_unit', $error['message'], $severity, $error['file'], $error['line']
);
}
unlink($logFile);
} else {
throw new \Exception('log output file does not exist: ' . $logFile);
}
}
}