Fixed plugins and tests

This commit is contained in:
Dmitry Khomutov 2017-01-06 11:14:45 +07:00
parent f514cdafc3
commit 712243660c
14 changed files with 385 additions and 60 deletions

1
.gitignore vendored
View file

@ -2,5 +2,6 @@
/composer.phar
/runtime
/app/loggerconfig.php
/app/pluginconfig.php
/app/config.yml
/public/assets/vendor

View file

@ -0,0 +1,23 @@
<?php
return function (PHPCensor\Plugin\Util\Factory $factory) {
$factory->registerResource(
// This function will be called when the resource is needed.
function() {
return [
'Foo' => "Stuff",
'Bar' => "More Stuff"
];
},
// In addition to the function for building the resource the system
// also needs to be told when to load the resource. Either or both
// of the following arguments can be used (null to ignore)
// This resource will only be given when the argument name is:
"ResourceArray",
// The resource will only be given when the type hint is:
PHPCensor\Plugin\Util\Factory::TYPE_ARRAY
);
};

View file

@ -1,4 +1,5 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
@ -99,7 +100,7 @@ class Builder implements LoggerAwareInterface
/**
* Set up the builder.
*
*
* @param \PHPCensor\Model\Build $build
* @param LoggerInterface $logger
*/
@ -110,7 +111,9 @@ class Builder implements LoggerAwareInterface
$this->buildLogger = new BuildLogger($logger, $build);
$this->pluginExecutor = new Plugin\Util\Executor($this, $build, $this->buildLogger);
$pluginFactory = $this->buildPluginFactory($build);
$pluginFactory->addConfigFromFile(APP_DIR . "pluginconfig.php");
$this->pluginExecutor = new Plugin\Util\Executor($pluginFactory, $this->buildLogger);
$executorClass = 'PHPCensor\Helper\UnixCommandExecutor';
if (IS_WIN) {
@ -230,7 +233,7 @@ class Builder implements LoggerAwareInterface
$this->build->setStatus(Build::STATUS_FAILED);
$this->buildLogger->logFailure(Lang::get('exception') . $ex->getMessage());
}
if (Build::STATUS_FAILED === $this->build->getStatus()) {
$this->buildLogger->logFailure("\n" . Lang::get('build_failed'));
} else {
@ -361,7 +364,7 @@ class Builder implements LoggerAwareInterface
/**
* Add a success-coloured message to the log.
*
*
* @param string
*/
public function logSuccess($message)
@ -371,7 +374,7 @@ class Builder implements LoggerAwareInterface
/**
* Add a failure-coloured message to the log.
*
*
* @param string $message
* @param \Exception $exception The exception that caused the error.
*/
@ -382,11 +385,59 @@ class Builder implements LoggerAwareInterface
/**
* Add a debug message to the log.
*
*
* @param string
*/
public function logDebug($message)
{
$this->buildLogger->logDebug($message);
}
/**
* Returns a configured instance of the plugin factory.
*
* @param Build $build
* @return PluginFactory
*/
private function buildPluginFactory(Build $build)
{
$pluginFactory = new PluginFactory();
$self = $this;
$pluginFactory->registerResource(
function () use ($self) {
return $self;
},
null,
'PHPCensor\Builder'
);
$pluginFactory->registerResource(
function () use ($build) {
return $build;
},
null,
'PHPCensor\Model\Build'
);
$logger = $this->logger;
$pluginFactory->registerResource(
function () use ($logger) {
return $logger;
},
null,
'Psr\Log\LoggerInterface'
);
$pluginFactory->registerResource(
function () use ($self) {
$factory = new MailerFactory($self->getSystemConfig('php-censor'));
return $factory->getSwiftMailerFromConfig();
},
null,
'Swift_Mailer'
);
return $pluginFactory;
}
}

View file

@ -47,6 +47,22 @@ abstract class Plugin
$this->builder->logDebug('Plugin options: ' . json_encode($options));
}
/**
* @return Build
*/
public function getBuild()
{
return $this->build;
}
/**
* @return Builder
*/
public function getBuilder()
{
return $this->builder;
}
/**
* @return boolean
*/

View file

@ -6,7 +6,6 @@ use PHPCensor\Plugin;
/**
* Class ComposerPluginInformation
*
* @package PHPCensor\Plugin\Util
*/
class ComposerPluginInformation implements InstalledPluginInformation
@ -35,6 +34,15 @@ class ComposerPluginInformation implements InstalledPluginInformation
return new self($installed);
}
/**
* @param \stdClass[] $composerPackages This should be the contents of the
* installed.json file created by composer
*/
public function __construct(array $composerPackages)
{
$this->composerPackages = $composerPackages;
}
/**
* Returns an array of objects. Each one represents an available plugin
* and will have the following properties:
@ -44,6 +52,7 @@ class ComposerPluginInformation implements InstalledPluginInformation
*/
public function getInstalledPlugins()
{
$this->loadPluginInfo();
return $this->pluginInfo;
}
@ -63,6 +72,44 @@ class ComposerPluginInformation implements InstalledPluginInformation
);
}
/**
* Load a list of available plugins from the installed composer packages.
*/
protected function loadPluginInfo()
{
if ($this->pluginInfo !== null) {
return;
}
$this->pluginInfo = [];
foreach ($this->composerPackages as $package) {
$this->addPluginsFromPackage($package);
}
}
/**
* @param \stdClass $package
*/
protected function addPluginsFromPackage($package)
{
if (isset($package->extra->phpci)) {
$phpciData = $package->extra->phpci;
if (isset($phpciData->pluginNamespace)) {
$rootNamespace = $phpciData->pluginNamespace;
} else {
$rootNamespace = "";
}
if (is_array($phpciData->suppliedPlugins)) {
$this->addPlugins(
$phpciData->suppliedPlugins,
$package->name,
$rootNamespace
);
}
}
}
/**
* @param \stdClass[] $plugins
* @param string $sourcePackageName

View file

@ -8,46 +8,37 @@ use PHPCensor\Helper\Lang;
use PHPCensor\Logging\BuildLogger;
use PHPCensor\Model\Build;
use PHPCensor\Store\BuildStore;
use PHPCensor\Builder;
/**
* Plugin Executor - Runs the configured plugins for a given build stage.
*
* @package PHPCensor\Plugin\Util
*/
class Executor
{
/**
* @var \PHPCensor\Builder
*/
protected $builder;
/**
* @var \PHPCensor\Model\Build
*/
protected $build;
/**
* @var BuildLogger
*/
protected $logger;
/**
* @var Factory
*/
protected $pluginFactory;
/**
* @var BuildStore
*/
protected $store;
/**
* @param Builder $builder
* @param Build $build
* @param Factory $pluginFactory
* @param BuildLogger $logger
*/
public function __construct(Builder $builder, Build $build, BuildLogger $logger)
public function __construct(Factory $pluginFactory, BuildLogger $logger, BuildStore $store = null)
{
$this->builder = $builder;
$this->build = $build;
$this->logger = $logger;
$this->store = StoreFactory::getStore('Build');
$this->pluginFactory = $pluginFactory;
$this->logger = $logger;
$this->store = $store ?: StoreFactory::getStore('Build');
}
/**
@ -87,7 +78,7 @@ class Executor
protected function getBranchSpecificPlugins(&$config, $stage, $pluginsToExecute)
{
/** @var \PHPCensor\Model\Build $build */
$build = $this->build;
$build = $this->pluginFactory->getResourceFor('PHPCensor\Model\Build');
$branch = $build->getBranch();
// If we don't have any branch-specific plugins:
@ -199,7 +190,10 @@ class Executor
}
try {
return (new $class($this->builder, $this->build, $options))->execute();
// Build and run it
$obj = $this->pluginFactory->buildPlugin($class, $options);
return $obj->execute();
} catch (\Exception $ex) {
$this->logger->logFailure(Lang::get('exception') . $ex->getMessage(), $ex);
@ -241,7 +235,7 @@ class Executor
private function getBuildSummary()
{
/** @var Build $build */
$build = $this->build;
$build = $this->pluginFactory->getResourceFor('PHPCensor\Model\Build');
$metas = $this->store->getMeta('plugin-summary', $build->getProjectId(), $build->getId());
return isset($metas[0]['meta_value']) ? $metas[0]['meta_value'] : [];
}
@ -254,7 +248,7 @@ class Executor
private function setBuildSummary($summary)
{
/** @var Build $build */
$build = $this->build;
$build = $this->pluginFactory->getResourceFor('PHPCensor\Model\Build');
$this->store->setMeta($build->getProjectId(), $build->getId(), 'plugin-summary', json_encode($summary));
}
}

View file

@ -0,0 +1,210 @@
<?php
namespace PHPCensor\Plugin\Util;
use Pimple\Container;
/**
* Plugin Factory - Loads Plugins and passes required dependencies.
* @package PHPCensor\Plugin\Util
*/
class Factory
{
const TYPE_ARRAY = "array";
const TYPE_CALLABLE = "callable";
const INTERFACE_PLUGIN = '\PHPCensor\Plugin';
private $currentPluginOptions;
/**
* @var Container
*/
private $container;
/**
* @param Container $container
*/
public function __construct(Container $container = null)
{
if ($container) {
$this->container = $container;
} else {
$this->container = new Container();
}
}
/**
* Trys to get a function from the file path specified. If the
* file returns a function then $this will be passed to it.
* This enables the config file to call any public methods.
*
* @param $configPath
* @return bool - true if the function exists else false.
*/
public function addConfigFromFile($configPath)
{
// The file is expected to return a function which can
// act on the pluginFactory to register any resources needed.
if (file_exists($configPath)) {
$configFunction = require($configPath);
if (is_callable($configFunction)) {
$configFunction($this);
return true;
}
}
return false;
}
/**
* Get most recently used factory options.
* @return mixed
*/
public function getLastOptions()
{
return $this->currentPluginOptions;
}
/**
* Builds an instance of plugin of class $className. $options will
* be passed along with any resources registered with the factory.
*
* @param $className
* @param array|null $options
* @throws \InvalidArgumentException if $className doesn't represent a valid plugin
* @return \PHPCensor\Plugin
*/
public function buildPlugin($className, $options = [])
{
$this->currentPluginOptions = $options;
$reflectedPlugin = new \ReflectionClass($className);
$constructor = $reflectedPlugin->getConstructor();
if ($constructor) {
$argsToUse = [];
foreach ($constructor->getParameters() as $param) {
if ('options' === $param->getName()) {
$argsToUse[] = $options;
} else {
$argsToUse = $this->addArgFromParam($argsToUse, $param);
}
}
$plugin = $reflectedPlugin->newInstanceArgs($argsToUse);
} else {
$plugin = $reflectedPlugin->newInstance();
}
return $plugin;
}
/**
* @param callable $loader
* @param string|null $name
* @param string|null $type
* @throws \InvalidArgumentException
* @internal param mixed $resource
*/
public function registerResource(
$loader,
$name = null,
$type = null
) {
if ($name === null && $type === null) {
throw new \InvalidArgumentException(
"Type or Name must be specified"
);
}
if (!($loader instanceof \Closure)) {
throw new \InvalidArgumentException(
'$loader is expected to be a function'
);
}
$resourceID = $this->getInternalID($type, $name);
$this->container[$resourceID] = $loader;
}
/**
* Get an internal resource ID.
* @param null $type
* @param null $name
* @return string
*/
private function getInternalID($type = null, $name = null)
{
$type = $type ? : "";
$name = $name ? : "";
return $type . "-" . $name;
}
/**
* @param string $type
* @param string $name
* @return mixed
*/
public function getResourceFor($type = null, $name = null)
{
$fullId = $this->getInternalID($type, $name);
if (isset($this->container[$fullId])) {
return $this->container[$fullId];
}
$typeOnlyID = $this->getInternalID($type, null);
if (isset($this->container[$typeOnlyID])) {
return $this->container[$typeOnlyID];
}
$nameOnlyID = $this->getInternalID(null, $name);
if (isset($this->container[$nameOnlyID])) {
return $this->container[$nameOnlyID];
}
return null;
}
/**
* @param \ReflectionParameter $param
* @return null|string
*/
private function getParamType(\ReflectionParameter $param)
{
$class = $param->getClass();
if ($class) {
return $class->getName();
} elseif ($param->isArray()) {
return self::TYPE_ARRAY;
} elseif (is_callable($param)) {
return self::TYPE_CALLABLE;
} else {
return null;
}
}
/**
* @param $existingArgs
* @param \ReflectionParameter $param
* @return array
* @throws \DomainException
*/
private function addArgFromParam($existingArgs, \ReflectionParameter $param)
{
$name = $param->getName();
$type = $this->getParamType($param);
$arg = $this->getResourceFor($type, $name);
if ($arg !== null) {
$existingArgs[] = $arg;
} elseif ($arg === null && $param->isOptional()) {
$existingArgs[] = $param->getDefaultValue();
} else {
throw new \DomainException(
"Unsatisfied dependency: " . $param->getName()
);
}
return $existingArgs;
}
}

View file

@ -95,13 +95,13 @@ class PharTest extends \PHPUnit_Framework_TestCase
$plugin = $this->getPlugin();
$this->assertInstanceOf('PHPCensor\Plugin', $plugin);
$this->assertInstanceOf('PHPCensor\Model\Build', $plugin->getBuild());
$this->assertInstanceOf('PHPCensor\Builder', $plugin->getPHPCI());
$this->assertInstanceOf('PHPCensor\Builder', $plugin->getBuilder());
}
public function testDirectory()
{
$plugin = $this->getPlugin();
$plugin->getPHPCI()->buildPath = 'foo';
$plugin->getBuilder()->buildPath = 'foo';
$this->assertEquals('foo', $plugin->getDirectory());
$plugin = $this->getPlugin(['directory' => 'dirname']);
@ -141,7 +141,7 @@ class PharTest extends \PHPUnit_Framework_TestCase
$plugin = $this->getPlugin();
$path = $this->buildSource();
$plugin->getPHPCI()->buildPath = $path;
$plugin->getBuilder()->buildPath = $path;
$this->assertTrue($plugin->execute());
@ -159,7 +159,7 @@ class PharTest extends \PHPUnit_Framework_TestCase
$plugin = $this->getPlugin(['regexp' => '/\.(php|phtml)$/']);
$path = $this->buildSource();
$plugin->getPHPCI()->buildPath = $path;
$plugin->getBuilder()->buildPath = $path;
$this->assertTrue($plugin->execute());
@ -185,7 +185,7 @@ STUB;
file_put_contents($path . '/stub.php', $content);
$plugin = $this->getPlugin(['stub' => 'stub.php']);
$plugin->getPHPCI()->buildPath = $path;
$plugin->getBuilder()->buildPath = $path;
$this->assertTrue($plugin->execute());
@ -201,7 +201,7 @@ STUB;
$directory = $this->buildTemp();
$plugin = $this->getPlugin(['directory' => $directory]);
$plugin->getPHPCI()->buildPath = $this->buildSource();
$plugin->getBuilder()->buildPath = $this->buildSource();
$this->assertFalse($plugin->execute());
}

View file

@ -40,7 +40,6 @@ class FactoryTest extends \PHPUnit_Framework_TestCase {
// Nothing to do.
}
public function testRegisterResourceThrowsExceptionWithoutTypeAndName()
{
$this->setExpectedException('InvalidArgumentException', 'Type or Name must be specified');
@ -53,19 +52,6 @@ class FactoryTest extends \PHPUnit_Framework_TestCase {
$this->testedFactory->registerResource(["dummy"], "TestName", "TestClass");
}
public function testBuildPluginWorksWithConstructorlessPlugins()
{
$pluginClass = $this->getFakePluginClassName('ExamplePluginWithNoConstructorArgs');
$plugin = $this->testedFactory->buildPlugin($pluginClass);
$this->assertInstanceOf($pluginClass, $plugin);
}
public function testBuildPluginFailsForNonPluginClasses()
{
$this->setExpectedException('InvalidArgumentException', 'Requested class must implement \PHPCensor\Plugin');
$plugin = $this->testedFactory->buildPlugin("stdClass");
}
public function testBuildPluginWorksWithSingleOptionalArgConstructor()
{
$pluginClass = $this->getFakePluginClassName('ExamplePluginWithSingleOptionalArg');

View file

@ -14,17 +14,14 @@ use PHPCensor\Builder;
use PHPCensor\Model\Build;
use PHPCensor\Plugin;
class ExamplePluginFull implements Plugin {
class ExamplePluginFull extends Plugin {
/**
* @var array
*/
public $Options;
public function __construct(
Builder $builder,
Build $build,
array $options = []
) {
public function __construct(Builder $builder, Build $build, array $options = [])
{
$this->Options = $options;
}

View file

@ -12,7 +12,7 @@ namespace Tests\PHPCensor\Plugin\Util\Fake;
use PHPCensor\Plugin;
class ExamplePluginWithNoConstructorArgs implements Plugin
class ExamplePluginWithNoConstructorArgs extends Plugin
{
public function execute()
{

View file

@ -12,7 +12,7 @@ namespace Tests\PHPCensor\Plugin\Util\Fake;
use PHPCensor\Plugin;
class ExamplePluginWithSingleOptionalArg implements Plugin
class ExamplePluginWithSingleOptionalArg extends Plugin
{
function __construct($optional = null)
{

View file

@ -12,7 +12,7 @@ namespace Tests\PHPCensor\Plugin\Util\Fake;
use PHPCensor\Plugin;
class ExamplePluginWithSingleRequiredArg implements Plugin
class ExamplePluginWithSingleRequiredArg extends Plugin
{
public $RequiredArgument;

View file

@ -12,7 +12,7 @@ namespace Tests\PHPCensor\Plugin\Util\Fake;
use PHPCensor\Plugin;
class ExamplePluginWithSingleTypedRequiredArg implements Plugin
class ExamplePluginWithSingleTypedRequiredArg extends Plugin
{
public $RequiredArgument;