diff --git a/.gitignore b/.gitignore
index 79784a60..63e81c5b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,4 @@ PHPCI/Model/Base/MigrationBase.php
PHPCI/Store/MigrationStore.php
PHPCI/Store/Base/MigrationStoreBase.php
local_vars.php
+Tests/PHPCI/config.yml
diff --git a/PHPCI/Command/CreateAdminCommand.php b/PHPCI/Command/CreateAdminCommand.php
index 3b69afd3..265f03b4 100644
--- a/PHPCI/Command/CreateAdminCommand.php
+++ b/PHPCI/Command/CreateAdminCommand.php
@@ -9,21 +9,36 @@
namespace PHPCI\Command;
-use PHPCI\Helper\Lang;
use PHPCI\Service\UserService;
+use PHPCI\Helper\Lang;
+use PHPCI\Store\UserStore;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
-use b8\Store\Factory;
/**
-* Create admin command - creates an admin user
-* @author Wogan May (@woganmay)
-* @package PHPCI
-* @subpackage Console
-*/
+ * Create admin command - creates an admin user
+ * @author Wogan May (@woganmay)
+ * @package PHPCI
+ * @subpackage Console
+ */
class CreateAdminCommand extends Command
{
+ /**
+ * @var UserStore
+ */
+ protected $userStore;
+
+ /**
+ * @param UserStore $userStore
+ */
+ public function __construct(UserStore $userStore)
+ {
+ parent::__construct();
+
+ $this->userStore = $userStore;
+ }
+
protected function configure()
{
$this
@@ -32,92 +47,36 @@ class CreateAdminCommand extends Command
}
/**
- * Creates an admin user in the existing PHPCI database
- */
+ * Creates an admin user in the existing PHPCI database
+ *
+ * {@inheritDoc}
+ */
protected function execute(InputInterface $input, OutputInterface $output)
{
- $userStore = Factory::getStore('User');
- $userService = new UserService($userStore);
+ $userService = new UserService($this->userStore);
- require(PHPCI_DIR . 'bootstrap.php');
+ /** @var $dialog \Symfony\Component\Console\Helper\DialogHelper */
+ $dialog = $this->getHelperSet()->get('dialog');
- // Try to create a user account:
- $adminEmail = $this->ask(Lang::get('enter_email'), true, FILTER_VALIDATE_EMAIL);
+ // Function to validate mail address.
+ $mailValidator = function ($answer) {
+ if (!filter_var($answer, FILTER_VALIDATE_EMAIL)) {
+ throw new \InvalidArgumentException(Lang::get('must_be_valid_email'));
+ }
- if (empty($adminEmail)) {
- return;
- }
+ return $answer;
+ };
- $adminPass = $this->ask(Lang::get('enter_pass'));
- $adminName = $this->ask(Lang::get('enter_name'));
+ $adminEmail = $dialog->askAndValidate($output, Lang::get('enter_email'), $mailValidator, false);
+ $adminName = $dialog->ask($output, Lang::get('enter_name'));
+ $adminPass = $dialog->askHiddenResponse($output, Lang::get('enter_password'));
try {
- $userService->createUser($adminName, $adminEmail, $adminPass, 1);
- print Lang::get('user_created') . PHP_EOL;
- } catch (\Exception $ex) {
- print Lang::get('failed_to_create') . PHP_EOL;
- print $ex->getMessage();
- print PHP_EOL;
+ $userService->createUser($adminName, $adminEmail, $adminPass, true);
+ $output->writeln(Lang::get('user_created'));
+ } catch (\Exception $e) {
+ $output->writeln(sprintf('
%s", $logText) - ); - } else { - $view = new View('Email/failed'); - $view->build = $this->build; - $view->project = $this->build->getProject(); + $view = new View($mailTemplate); + $view->build = $this->build; + $view->project = $this->build->getProject(); + $body = $view->render(); - $emailHtml = $view->render(); - - $sendFailures = $this->sendSeparateEmails( - $addresses, - sprintf($subjectTemplate, $projectName, Lang::get('failing_build')), - $emailHtml - ); - } + $sendFailures = $this->sendSeparateEmails( + $addresses, + sprintf("PHPCI - %s - %s", $projectName, $buildStatus), + $body + ); // This is a success if we've not failed to send anything. + $this->phpci->log(sprintf("%d emails sent", (count($addresses) - $sendFailures))); + $this->phpci->log(sprintf("%d emails failed to send", $sendFailures)); - $this->phpci->log(Lang::get('n_emails_sent', (count($addresses) - count($sendFailures)))); - $this->phpci->log(Lang::get('n_emails_failed', count($sendFailures))); - - return (count($sendFailures) == 0); + return ($sendFailures === 0); } /** - * @param string[]|string $toAddresses Array or single address to send to + * @param string $toAddress Single address to send to * @param string[] $ccList * @param string $subject Email subject * @param string $body Email body * @return array Array of failed addresses */ - public function sendEmail($toAddresses, $ccList, $subject, $body) + public function sendEmail($toAddress, $ccList, $subject, $body) { - $message = \Swift_Message::newInstance($subject) - ->setFrom($this->fromAddress) - ->setTo($toAddresses) - ->setBody($body) - ->setContentType("text/html"); + $email = new EmailHelper(); + + $email->setEmailTo($toAddress, $toAddress); + $email->setSubject($subject); + $email->setBody($body); + $email->setHtml(true); if (is_array($ccList) && count($ccList)) { - $message->setCc($ccList); + foreach ($ccList as $address) { + $email->addCc($address, $address); + } } - $failedAddresses = array(); - $this->mailer->send($message, $failedAddresses); - - return $failedAddresses; + return $email->send(); } /** - * Send out build status emails. + * Send an email to a list of specified subjects. + * * @param array $toAddresses - * @param $subject - * @param $body - * @return array + * List of destinatary of message. + * @param string $subject + * Mail subject + * @param string $body + * Mail body + * + * @return int number of failed messages */ public function sendSeparateEmails(array $toAddresses, $subject, $body) { - $failures = array(); + $failures = 0; $ccList = $this->getCcAddresses(); foreach ($toAddresses as $address) { - $newFailures = $this->sendEmail($address, $ccList, $subject, $body); - foreach ($newFailures as $failure) { - $failures[] = $failure; + if (!$this->sendEmail($address, $ccList, $subject, $body)) { + $failures++; } } return $failures; diff --git a/PHPCI/Plugin/TechnicalDebt.php b/PHPCI/Plugin/TechnicalDebt.php new file mode 100755 index 00000000..fed76566 --- /dev/null +++ b/PHPCI/Plugin/TechnicalDebt.php @@ -0,0 +1,208 @@ + +* @package PHPCI +* @subpackage Plugins +*/ +class TechnicalDebt implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin +{ + /** + * @var \PHPCI\Builder + */ + protected $phpci; + + /** + * @var array + */ + protected $suffixes; + + /** + * @var string + */ + protected $directory; + + /** + * @var int + */ + protected $allowed_errors; + + /** + * @var int + */ + protected $allowed_warnings; + + /** + * @var string, based on the assumption the root may not hold the code to be + * tested, extends the base path + */ + protected $path; + + /** + * @var array - paths to ignore + */ + protected $ignore; + + /** + * @var array - terms to search for + */ + protected $searches; + + + /** + * Check if this plugin can be executed. + * + * @param $stage + * @param Builder $builder + * @param Build $build + * @return bool + */ + public static function canExecute($stage, Builder $builder, Build $build) + { + if ($stage == 'test') { + return true; + } + + return false; + } + + /** + * @param \PHPCI\Builder $phpci + * @param \PHPCI\Model\Build $build + * @param array $options + */ + public function __construct(Builder $phpci, Build $build, array $options = array()) + { + $this->phpci = $phpci; + $this->build = $build; + $this->suffixes = array('php'); + $this->directory = $phpci->buildPath; + $this->path = ''; + $this->ignore = $this->phpci->ignore; + $this->allowed_warnings = 0; + $this->allowed_errors = 0; + $this->searches = array('TODO', 'FIXME', 'TO DO', 'FIX ME'); + + if (isset($options['searches']) && is_array($options['searches'])) { + $this->searches = $options['searches']; + } + + if (isset($options['zero_config']) && $options['zero_config']) { + $this->allowed_warnings = -1; + $this->allowed_errors = -1; + } + } + + /** + * Handle this plugin's options. + * @param $options + */ + protected function setOptions($options) + { + foreach (array('directory', 'path', 'ignore', 'allowed_warnings', 'allowed_errors') as $key) { + if (array_key_exists($key, $options)) { + $this->{$key} = $options[$key]; + } + } + } + + /** + * Runs the plugin + */ + public function execute() + { + $success = true; + $this->phpci->logExecOutput(false); + + list($errorCount, $data) = $this->getErrorList(); + + $this->phpci->log("Found $errorCount instances of " . implode(', ', $this->searches)); + + $this->build->storeMeta('technical_debt-warnings', $errorCount); + $this->build->storeMeta('technical_debt-data', $data); + + if ($this->allowed_errors != -1 && $errorCount > $this->allowed_errors) { + $success = false; + } + + return $success; + } + + /** + * Gets the number and list of errors returned from the search + * + * @return array + */ + public function getErrorList() + { + $dirIterator = new \RecursiveDirectoryIterator($this->directory); + $iterator = new \RecursiveIteratorIterator($dirIterator, \RecursiveIteratorIterator::SELF_FIRST); + $files = []; + + $ignores = $this->ignore; + $ignores[] = 'phpci.yml'; + + foreach ($iterator as $file) { + $filePath = $file->getRealPath(); + $skipFile = false; + foreach ($ignores as $ignore) { + if (stripos($filePath, $ignore) !== false) { + $skipFile = true; + break; + } + } + + // Ignore hidden files, else .git, .sass_cache, etc. all get looped over + if (stripos($filePath, '/.') !== false) { + $skipFile = true; + } + + if ($skipFile == false) { + $files[] = $file->getRealPath(); + } + } + + $files = array_filter(array_unique($files)); + $errorCount = 0; + $data = array(); + + foreach ($files as $file) { + foreach ($this->searches as $search) { + $fileContent = file_get_contents($file); + $allLines = explode(PHP_EOL, $fileContent); + $beforeString = strstr($fileContent, $search, true); + + if (false !== $beforeString) { + $lines = explode(PHP_EOL, $beforeString); + $lineNumber = count($lines); + $content = trim($allLines[$lineNumber - 1]); + + $errorCount++; + $this->phpci->log("Found $search on line $lineNumber of $file:\n$content"); + $data[] = array( + 'file' => str_replace($this->directory, '', $file), + 'line' => $lineNumber, + 'message' => $content + ); + } + } + } + + return array($errorCount, $data); + } +} diff --git a/PHPCI/View/Email/success.phtml b/PHPCI/View/Email/success.phtml new file mode 100644 index 00000000..a6dfccea --- /dev/null +++ b/PHPCI/View/Email/success.phtml @@ -0,0 +1,15 @@ +
Your commit getCommitId(); ?> genrate a successfull build in project getTitle(); ?>.
+ +getCommitMessage(); ?>
+getLog(); ?>+
You can review your commit and the build log.
+Build Log", - "PHPCI - Test-Project - Passing Build", - $actualMail + /** + * @covers PHPUnit::execute + */ + public function testBuildsDefaultEmails() + { + $this->loadEmailPluginWithOptions( + array( + 'default_mailto_address' => 'default-mailto-address@example.com' + ), + Build::STATUS_SUCCESS ); - $this->assertEquals($expectedReturn, $returnValue); + $this->testedEmailPlugin->execute(); + + $this->assertContains('default-mailto-address@example.com', $this->message['to']); } /** @@ -168,17 +220,13 @@ class EmailTest extends \PHPUnit_Framework_TestCase ) ); - $actualMails = []; - $this->catchMailPassedToSend($actualMails); - $returnValue = $this->testedEmailPlugin->execute(); $this->assertTrue($returnValue); - $this->assertCount(2, $actualMails); + $this->assertCount(2, $this->message['to']); - $actualTos = array(key($actualMails[0]->getTo()), key($actualMails[1]->getTo())); - $this->assertContains('test-receiver@example.com', $actualTos); - $this->assertContains('test-receiver2@example.com', $actualTos); + $this->assertContains('test-receiver@example.com', $this->message['to']); + $this->assertContains('test-receiver2@example.com', $this->message['to']); } /** @@ -193,133 +241,164 @@ class EmailTest extends \PHPUnit_Framework_TestCase ) ); - $actualMails = []; - $this->catchMailPassedToSend($actualMails); - $returnValue = $this->testedEmailPlugin->execute(); $this->assertTrue($returnValue); - $actualTos = array(key($actualMails[0]->getTo()), key($actualMails[1]->getTo())); - $this->assertContains('test-receiver@example.com', $actualTos); - $this->assertContains('committer@test.com', $actualTos); + $this->assertContains('test-receiver@example.com', $this->message['to']); + $this->assertContains('committer@test.com', $this->message['to']); } /** - * @covers PHPUnit::sendEmail + * @covers PHPUnit::execute */ - public function testSendEmail_CallsMailerSend() + public function testCcDefaultEmails() { - $this->mockMailer->expects($this->once()) - ->method('send'); - $this->testedEmailPlugin->sendEmail("test@email.com", array(), "hello", "body"); - } - - /** - * @covers PHPUnit::sendEmail - */ - public function testSendEmail_BuildsAMessageObject() - { - $subject = "Test mail"; - $body = "Message Body"; - $toAddress = "test@example.com"; - - $this->mockMailer->expects($this->once()) - ->method('send') - ->with($this->isInstanceOf('\Swift_Message'), $this->anything()); - $this->testedEmailPlugin->sendEmail($toAddress, array(), $subject, $body); - } - - /** - * @covers PHPUnit::sendEmail - */ - public function testSendEmail_BuildsExpectedMessage() - { - $subject = "Test mail"; - $body = "Message Body"; - $toAddress = "test@example.com"; - $expectedMessage = \Swift_Message::newInstance($subject) - ->setFrom('test-from-address@example.com') - ->setTo($toAddress) - ->setBody($body); - - /** @var \Swift_Message $actualMail */ - $actualMail = null; - $this->catchMailPassedToSend($actualMail); - - $this->testedEmailPlugin->sendEmail($toAddress, array(), $subject, $body); - - $this->assertSystemMail( - $toAddress, - 'test-from-address@example.com', - $body, - $subject, - $actualMail + $this->loadEmailPluginWithOptions( + array( + 'default_mailto_address' => 'default-mailto-address@example.com', + 'cc' => array( + 'cc-email-1@example.com', + 'cc-email-2@example.com', + 'cc-email-3@example.com', + ), + ), + Build::STATUS_SUCCESS ); - } - /** - * @param \Swift_Message $actualMail passed by ref and populated with - * the message object the mock mailer - * receives. - */ - protected function catchMailPassedToSend(&$actualMail) - { - $this->mockMailer->expects(is_array($actualMail) ? $this->atLeast(1) : $this->once()) - ->method('send') - ->will( - $this->returnCallback( - function ($passedMail) use (&$actualMail) { - if(is_array($actualMail)) { - $actualMail[] = $passedMail; - } else { - $actualMail = $passedMail; - } - return array(); - } - ) - ); - } - - /** - * Asserts that the actual mail object is populated as expected. - * - * @param string $expectedToAddress - * @param $expectedFromAddress - * @param string $expectedBody - * @param string $expectedSubject - * @param \Swift_Message $actualMail - */ - protected function assertSystemMail( - $expectedToAddress, - $expectedFromAddress, - $expectedBody, - $expectedSubject, - $actualMail - ) { - if (!($actualMail instanceof \Swift_Message)) { - $type = is_object($actualMail) ? get_class($actualMail) : gettype( - $actualMail - ); - throw new \Exception("Expected Swift_Message got " . $type); - } - $this->assertEquals( - array($expectedFromAddress => null), - $actualMail->getFrom() - ); + $this->testedEmailPlugin->execute(); $this->assertEquals( - array($expectedToAddress => null), - $actualMail->getTo() - ); - - $this->assertEquals( - $expectedBody, - $actualMail->getBody() - ); - - $this->assertEquals( - $expectedSubject, - $actualMail->getSubject() + array( + 'cc-email-1@example.com', + 'cc-email-2@example.com', + 'cc-email-3@example.com', + ), + $this->message['cc'] ); } -} \ No newline at end of file + + /** + * @covers PHPUnit::execute + */ + public function testBuildsCommitterEmails() + { + $this->loadEmailPluginWithOptions( + array( + 'committer' => true + ), + Build::STATUS_SUCCESS + ); + + $this->testedEmailPlugin->execute(); + + $this->assertContains('committer-email@example.com', $this->message['to']); + } + + /** + * @covers PHPUnit::execute + */ + public function testMailSuccessfulBuildHaveProjectName() + { + $this->loadEmailPluginWithOptions( + array( + 'addresses' => array('test-receiver@example.com') + ), + Build::STATUS_SUCCESS + ); + + $this->testedEmailPlugin->execute(); + + $this->assertContains('Test-Project', $this->message['subject']); + $this->assertContains('Test-Project', $this->message['body']); + } + + /** + * @covers PHPUnit::execute + */ + public function testMailFailingBuildHaveProjectName() + { + $this->loadEmailPluginWithOptions( + array( + 'addresses' => array('test-receiver@example.com') + ), + Build::STATUS_FAILED + ); + + $this->testedEmailPlugin->execute(); + + $this->assertContains('Test-Project', $this->message['subject']); + $this->assertContains('Test-Project', $this->message['body']); + } + + /** + * @covers PHPUnit::execute + */ + public function testMailSuccessfulBuildHaveStatus() + { + $this->loadEmailPluginWithOptions( + array( + 'addresses' => array('test-receiver@example.com') + ), + Build::STATUS_SUCCESS + ); + + $this->testedEmailPlugin->execute(); + + $this->assertContains('Passing', $this->message['subject']); + $this->assertContains('successfull', $this->message['body']); + } + + /** + * @covers PHPUnit::execute + */ + public function testMailFailingBuildHaveStatus() + { + $this->loadEmailPluginWithOptions( + array( + 'addresses' => array('test-receiver@example.com') + ), + Build::STATUS_FAILED + ); + + $this->testedEmailPlugin->execute(); + + $this->assertContains('Failing', $this->message['subject']); + $this->assertContains('failed', $this->message['body']); + } + + /** + * @covers PHPUnit::execute + */ + public function testMailDeliverySuccess() + { + $this->loadEmailPluginWithOptions( + array( + 'addresses' => array('test-receiver@example.com') + ), + Build::STATUS_FAILED, + true + ); + + $returnValue = $this->testedEmailPlugin->execute(); + + $this->assertEquals(true, $returnValue); + } + + /** + * @covers PHPUnit::execute + */ + public function testMailDeliveryFail() + { + $this->loadEmailPluginWithOptions( + array( + 'addresses' => array('test-receiver@example.com') + ), + Build::STATUS_FAILED, + false + ); + + $returnValue = $this->testedEmailPlugin->execute(); + + $this->assertEquals(false, $returnValue); + } +} diff --git a/Tests/bootstrap.php b/Tests/bootstrap.php index a8b71034..601c7d7b 100644 --- a/Tests/bootstrap.php +++ b/Tests/bootstrap.php @@ -13,12 +13,6 @@ date_default_timezone_set(@date_default_timezone_get()); // Load Composer autoloader: require_once(dirname(__DIR__) . '/vendor/autoload.php'); -// Load configuration if present: -$conf = array(); -$conf['b8']['app']['namespace'] = 'PHPCI'; -$conf['b8']['app']['default_controller'] = 'Home'; -$conf['b8']['view']['path'] = dirname(__DIR__) . '/PHPCI/View/'; - // If the PHPCI config file is not where we expect it, try looking in // env for an alternative config path. $configFile = dirname(__FILE__) . '/../PHPCI/config.yml'; @@ -31,6 +25,12 @@ if (!file_exists($configFile)) { } } +// Load configuration if present: +$conf = array(); +$conf['b8']['app']['namespace'] = 'PHPCI'; +$conf['b8']['app']['default_controller'] = 'Home'; +$conf['b8']['view']['path'] = dirname(__DIR__) . '/PHPCI/View/'; + $config = new b8\Config($conf); if (file_exists($configFile)) { diff --git a/console b/console index 43fc2b8f..782cc2d0 100755 --- a/console +++ b/console @@ -20,6 +20,7 @@ use PHPCI\Command\DaemonCommand; use PHPCI\Command\PollCommand; use PHPCI\Command\CreateAdminCommand; use Symfony\Component\Console\Application; +use b8\Store\Factory; $application = new Application(); @@ -29,6 +30,6 @@ $application->add(new UpdateCommand($loggerConfig->getFor('UpdateCommand'))); $application->add(new GenerateCommand); $application->add(new DaemonCommand($loggerConfig->getFor('DaemonCommand'))); $application->add(new PollCommand($loggerConfig->getFor('PollCommand'))); -$application->add(new CreateAdminCommand); +$application->add(new CreateAdminCommand(Factory::getStore('User'))); -$application->run(); \ No newline at end of file +$application->run(); diff --git a/public/assets/js/build-plugins/technical_debt.js b/public/assets/js/build-plugins/technical_debt.js new file mode 100755 index 00000000..a187401d --- /dev/null +++ b/public/assets/js/build-plugins/technical_debt.js @@ -0,0 +1,79 @@ +var TechnicalDebtPlugin = ActiveBuild.UiPlugin.extend({ + id: 'build-technical_debt', + css: 'col-lg-6 col-md-12 col-sm-12 col-xs-12', + title: Lang.get('technical_debt'), + lastData: null, + box: true, + rendered: false, + + register: function() { + var self = this; + var query = ActiveBuild.registerQuery('technical_debt-data', -1, {key: 'technical_debt-data'}) + + $(window).on('technical_debt-data', function(data) { + self.onUpdate(data); + }); + + $(window).on('build-updated', function() { + if (!self.rendered) { + query(); + } + }); + }, + + render: function() { + return $('
| '+Lang.get('file')+' | ' + + ''+Lang.get('line')+' | ' + + ''+Lang.get('message')+' | ' + + '
|---|