diff --git a/.gitignore b/.gitignore
index 3c28a27..d29deb5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@
/bin/coveralls
/bin/phpunit
/bin/test-reporter
+/bin/validate-json
diff --git a/.travis.yml b/.travis.yml
index 9f38c0a..11329fe 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -7,9 +7,14 @@ php:
- 5.5
- 5.4
+matrix:
+ allow_failures:
+ - php: hhvm
+ fast_finish: true
+
install:
- composer install --dev --prefer-source
script:
- bin/phpunit
- - bin/psecio-parse scan src tests
+ - bin/psecio-parse scan src tests bin/psecio-parse
diff --git a/README.md b/README.md
index e9dd836..ca09f19 100644
--- a/README.md
+++ b/README.md
@@ -71,7 +71,7 @@ You can also get a listing of the current checks being done with the `rules` com
psecio-parse rules
-### Managing rules to run
+### Managing the rules to run
There are several ways to control which rules are run. You can specifically include rules using
the `--include-rules` option, specifically exclude them with `--exclude-rules`, turn them on and
@@ -114,6 +114,42 @@ To disable the use of annotations, use the `--disable-annotations` option.
See the `examples` directory for some examples of the use of annotations for *Parse*.
+### Using a configuration file
+
+Specify the name of your configuration file with the `--configuration` option. If no
+filename is given the default `psecio-parse.json` is used.
+
+To ignore the default configuration file use the `--no-configuration` option.
+
+#### Configuration file format
+```json
+{
+ "paths": [
+ "path/to/scan"
+ ],
+ "ignore-paths": [
+ "path/to/ignore"
+ ],
+ "extensions": [
+ "php",
+ "phps",
+ "phtml",
+ "php5"
+ ],
+ "whitelist-rules": [
+ "rule-name"
+ ],
+ "blacklist-rules": [
+ "rule-name"
+ ],
+ "disable-annotations": false,
+ "format": "dots|progress|lines|debug|xml"
+}
+```
+
+See example configurations for scanning parse itself [here](psecio-parse.json.dist). See the
+json schema used to validate configuration files [here](src/Conf/schema.json).
+
The Checks
----------
Here's the current list of checks:
diff --git a/composer.json b/composer.json
index d79e11f..9dffcc3 100644
--- a/composer.json
+++ b/composer.json
@@ -14,14 +14,16 @@
],
"require":{
"php": ">=5.4",
+ "justinrainbow/json-schema": "^4.0",
"nikic/php-parser": "^1.0|^2.0",
"symfony/console": "~2.5|~3.1",
- "symfony/event-dispatcher": "~2.4|~3.1"
+ "symfony/event-dispatcher": "~2.4|~3.1",
+ "symfony/finder": "^3.1"
},
"require-dev": {
- "phpunit/phpunit": "4.2.*",
+ "phpunit/phpunit": "~4.2",
"codeclimate/php-test-reporter": "dev-master",
- "mockery/mockery": "0.9.*",
+ "mockery/mockery": ">=0.9.3",
"akamon/mockery-callable-mock": "~1.0"
},
"autoload": {
diff --git a/psecio-parse.json.dist b/psecio-parse.json.dist
new file mode 100644
index 0000000..6adba54
--- /dev/null
+++ b/psecio-parse.json.dist
@@ -0,0 +1,8 @@
+{
+ "paths": [
+ "src",
+ "tests",
+ "bin/psecio-parse"
+ ],
+ "format": "progress"
+}
diff --git a/src/Command/ScanCommand.php b/src/Command/ScanCommand.php
index cfe8d8a..684f7c1 100644
--- a/src/Command/ScanCommand.php
+++ b/src/Command/ScanCommand.php
@@ -8,20 +8,14 @@
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\EventDispatcher\EventDispatcher;
-use Symfony\Component\Console\Helper\ProgressBar;
+use Psecio\Parse\Conf\ConfFactory;
+use Psecio\Parse\Subscriber\SubscriberFactory;
use Psecio\Parse\Subscriber\ExitCodeCatcher;
-use Psecio\Parse\Subscriber\ConsoleDots;
-use Psecio\Parse\Subscriber\ConsoleProgressBar;
-use Psecio\Parse\Subscriber\ConsoleLines;
-use Psecio\Parse\Subscriber\ConsoleDebug;
-use Psecio\Parse\Subscriber\ConsoleReport;
-use Psecio\Parse\Subscriber\Xml;
use Psecio\Parse\Event\Events;
use Psecio\Parse\Event\MessageEvent;
use Psecio\Parse\RuleFactory;
-use Psecio\Parse\RuleInterface;
-use Psecio\Parse\Scanner;
use Psecio\Parse\CallbackVisitor;
+use Psecio\Parse\Scanner;
use Psecio\Parse\FileIterator;
use Psecio\Parse\DocComment\DocCommentFactory;
use RuntimeException;
@@ -41,43 +35,55 @@ protected function configure()
->addArgument(
'path',
InputArgument::OPTIONAL|InputArgument::IS_ARRAY,
- 'Paths to scan',
- []
+ 'Path to scan'
)
->addOption(
'format',
'f',
InputOption::VALUE_REQUIRED,
- 'Output format (progress, dots or xml)',
- 'progress'
+ 'Output format (progress, dots, lines, debug or xml)'
)
->addOption(
'ignore-paths',
'i',
InputOption::VALUE_REQUIRED,
- 'Comma-separated list of paths to ignore',
- ''
+ 'Comma-separated list of paths to ignore'
)
->addOption(
'extensions',
'x',
InputOption::VALUE_REQUIRED,
- 'Comma-separated list of file extensions to parse',
- 'php,phps,phtml,php5'
+ 'Comma-separated list of file extensions to parse (default: php,phps,phtml,php5)'
)
->addOption(
'whitelist-rules',
'w',
InputOption::VALUE_REQUIRED,
- 'Comma-separated list of rules to use',
- ''
+ 'Comma-separated list of rules to whitelist'
)
->addOption(
'blacklist-rules',
'b',
InputOption::VALUE_REQUIRED,
- 'Comma-separated list of rules to skip',
- ''
+ 'Comma-separated list of rules to blacklist'
+ )
+ ->addOption(
+ 'disable-annotations',
+ 'd',
+ InputOption::VALUE_NONE,
+ 'Skip all annotation-based rule toggles'
+ )
+ ->addOption(
+ 'configuration',
+ 'c',
+ InputOption::VALUE_REQUIRED,
+ 'Read configuration from file'
+ )
+ ->addOption(
+ 'no-configuration',
+ null,
+ InputOption::VALUE_NONE,
+ 'Ignore default configuration file'
)
->addOption(
'disable-annotations',
@@ -95,95 +101,39 @@ protected function configure()
*
* @param InputInterface $input Input object
* @param OutputInterface $output Output object
- * @throws RuntimeException If output format is not valid
* @return void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
+ $conf = (new ConfFactory)->createConf($input, $confFileName);
$dispatcher = new EventDispatcher;
+ (new SubscriberFactory($conf->getFormat(), $output))->addSubscribersTo($dispatcher);
+
$exitCode = new ExitCodeCatcher;
$dispatcher->addSubscriber($exitCode);
- $fileIterator = new FileIterator(
- $input->getArgument('path'),
- $this->parseCsv($input->getOption('ignore-paths')),
- $this->parseCsv($input->getOption('extensions'))
- );
-
- $format = strtolower($input->getOption('format'));
- switch ($format) {
- case 'dots':
- case 'progress':
- $output->writeln("Parse: A PHP Security Scanner\n");
- if ($output->isVeryVerbose()) {
- $dispatcher->addSubscriber(
- new ConsoleDebug($output)
- );
- } elseif ($output->isVerbose()) {
- $dispatcher->addSubscriber(
- new ConsoleLines($output)
- );
- } elseif ('progress' == $format && $output->isDecorated()) {
- $dispatcher->addSubscriber(
- new ConsoleProgressBar(new ProgressBar($output, count($fileIterator)))
- );
- } else {
- $dispatcher->addSubscriber(
- new ConsoleDots($output)
- );
- }
- $dispatcher->addSubscriber(new ConsoleReport($output));
- break;
- case 'xml':
- $dispatcher->addSubscriber(new Xml($output));
- break;
- default:
- throw new RuntimeException("Unknown output format '{$input->getOption('format')}'");
+ if ($confFileName) {
+ $dispatcher->dispatch(Events::DEBUG, new MessageEvent("Reading configurations from $confFileName"));
}
- $ruleFactory = new RuleFactory(
- $this->parseCsv($input->getOption('whitelist-rules')),
- $this->parseCsv($input->getOption('blacklist-rules'))
- );
-
- $ruleCollection = $ruleFactory->createRuleCollection();
+ $rules = (new RuleFactory($conf->getRuleWhitelist(), $conf->getRuleBlacklist()))->createRuleCollection();
- $ruleNames = implode(',', array_map(
- function (RuleInterface $rule) {
- return $rule->getName();
- },
- $ruleCollection->toArray()
- ));
-
- $dispatcher->dispatch(Events::DEBUG, new MessageEvent("Using ruleset $ruleNames"));
+ $dispatcher->dispatch(Events::DEBUG, new MessageEvent("Using ruleset: $rules"));
$docCommentFactory = new DocCommentFactory();
$scanner = new Scanner(
$dispatcher,
new CallbackVisitor(
- $ruleCollection,
+ $rules,
$docCommentFactory,
!$input->getOption('disable-annotations')
)
);
- $scanner->scan($fileIterator);
- return $exitCode->getExitCode();
- }
+ $scanner->scan(new FileIterator($conf->getPaths(), $conf->getIgnorePaths(), $conf->getExtensions()));
- /**
- * Parse comma-separated values from string
- *
- * Using array_filter ensures that an empty array is returned when an empty
- * string is parsed.
- *
- * @param string $string
- * @return array
- */
- public function parseCsv($string)
- {
- return array_filter(explode(',', $string));
+ return $exitCode->getExitCode();
}
}
diff --git a/src/Conf/ConfFactory.php b/src/Conf/ConfFactory.php
new file mode 100644
index 0000000..1347e7f
--- /dev/null
+++ b/src/Conf/ConfFactory.php
@@ -0,0 +1,52 @@
+getConfFileInfo($input)) {
+ $confFileName = $confFileInfo->getFilename();
+ $conf = new DualConf($conf, new JsonConf((new File($confFileInfo))->getContents()));
+ }
+
+ return new DualConf($conf, new DefaultConf);
+ }
+
+ /**
+ * Get info on configuration file to use
+ *
+ * @param InputInterface $input
+ * @return SplFileInfo|void
+ */
+ private function getConfFileInfo(InputInterface $input)
+ {
+ if ($filename = $input->getOption('configuration')) {
+ return new SplFileInfo($filename);
+ }
+
+ if (!$input->getOption('no-configuration')) {
+ $confFileInfo = new SplFileInfo('psecio-parse.json');
+ if ($confFileInfo->isReadable()) {
+ return $confFileInfo;
+ }
+ }
+ }
+}
diff --git a/src/Conf/Configuration.php b/src/Conf/Configuration.php
new file mode 100644
index 0000000..e536b70
--- /dev/null
+++ b/src/Conf/Configuration.php
@@ -0,0 +1,58 @@
+primary = $primary;
+ $this->secondary = $secondary;
+ }
+
+ /**
+ * Get output format identifier
+ *
+ * @return string
+ */
+ public function getFormat()
+ {
+ return strtolower($this->primary->getFormat() ?: $this->secondary->getFormat());
+ }
+
+ /**
+ * Get list of paths to scan
+ *
+ * @return string[]
+ */
+ public function getPaths()
+ {
+ return $this->primary->getPaths() ?: $this->secondary->getPaths();
+ }
+
+ /**
+ * Get list of paths to ignore
+ *
+ * @return string[]
+ */
+ public function getIgnorePaths()
+ {
+ return $this->primary->getIgnorePaths() ?: $this->secondary->getIgnorePaths();
+ }
+
+ /**
+ * Get list of extensions to scan
+ *
+ * @return string[]
+ */
+ public function getExtensions()
+ {
+ return $this->primary->getExtensions() ?: $this->secondary->getExtensions();
+ }
+
+ /**
+ * Get list of whitelisted rules
+ *
+ * @return string[]
+ */
+ public function getRuleWhitelist()
+ {
+ return $this->primary->getRuleWhitelist() ?: $this->secondary->getRuleWhitelist();
+ }
+
+ /**
+ * Get list of blacklisted rules
+ *
+ * @return string[]
+ */
+ public function getRuleBlacklist()
+ {
+ return $this->primary->getRuleBlacklist() ?: $this->secondary->getRuleBlacklist();
+ }
+
+ /**
+ * Check if annotations should be disabled
+ *
+ * @return boolean
+ */
+ public function disableAnnotations()
+ {
+ return $this->primary->disableAnnotations() || $this->secondary->disableAnnotations();
+ }
+}
diff --git a/src/Conf/JsonConf.php b/src/Conf/JsonConf.php
new file mode 100644
index 0000000..3ef9793
--- /dev/null
+++ b/src/Conf/JsonConf.php
@@ -0,0 +1,98 @@
+data = json_decode($json);
+
+ $retriever = new UriRetriever;
+ $schema = $retriever->retrieve('file://' . realpath(__DIR__ . '/schema.json'));
+
+ $validator = new Validator;
+ $validator->check($this->data, $schema);
+
+ foreach ($validator->getErrors() as $error) {
+ throw new RuntimeException("Invalid configuration for {$error['property']}\n{$error['message']}");
+ }
+ }
+
+ public function getFormat()
+ {
+ return $this->read('format', '');
+ }
+
+ public function getPaths()
+ {
+ $finder = (new Finder())->directories();
+ foreach ($this->read('paths', []) as $path) {
+ $finder->in($path);
+ }
+ $out = [];
+ foreach ($finder as $path) {
+ $out[] = $path;
+ }
+ return $out;
+ }
+
+ public function getIgnorePaths()
+ {
+ return $this->read('ignore-paths', []);
+ }
+
+ public function getExtensions()
+ {
+ return $this->read('extensions', []);
+ }
+
+ public function getRuleWhitelist()
+ {
+ return $this->read('whitelist-rules', []);
+ }
+
+ public function getRuleBlacklist()
+ {
+ return $this->read('blacklist-rules', []);
+ }
+
+ public function disableAnnotations()
+ {
+ return $this->read('disable-annotations', false);
+ }
+
+ /**
+ * Read configuration options
+ *
+ * @param string $property Name of property to read
+ * @param mixed $default Returned if otion is not set
+ * @return mixed
+ */
+ private function read($property, $default)
+ {
+ if (property_exists($this->data, $property)) {
+ return $this->data->$property;
+ }
+ return $default;
+ }
+}
diff --git a/src/Conf/UserConf.php b/src/Conf/UserConf.php
new file mode 100644
index 0000000..6517cec
--- /dev/null
+++ b/src/Conf/UserConf.php
@@ -0,0 +1,75 @@
+input = $input;
+ }
+
+ public function getFormat()
+ {
+ return $this->input->getOption('format');
+ }
+
+ public function getPaths()
+ {
+ return $this->input->getArgument('path');
+ }
+
+ public function getIgnorePaths()
+ {
+ return $this->parseCsv($this->input->getOption('ignore-paths'));
+ }
+
+ public function getExtensions()
+ {
+ return $this->parseCsv($this->input->getOption('extensions'));
+ }
+
+ public function getRuleWhitelist()
+ {
+ return $this->parseCsv($this->input->getOption('whitelist-rules'));
+ }
+
+ public function getRuleBlacklist()
+ {
+ return $this->parseCsv($this->input->getOption('blacklist-rules'));
+ }
+
+ public function disableAnnotations()
+ {
+ return $this->input->getOption('disable-annotations');
+ }
+
+ /**
+ * Parse comma-separated values from string
+ *
+ * Using array_filter ensures that an empty array is returned when an empty
+ * string is parsed.
+ *
+ * @param string $string
+ * @return array
+ */
+ private function parseCsv($string)
+ {
+ return array_filter(explode(',', $string));
+ }
+}
diff --git a/src/Conf/schema.json b/src/Conf/schema.json
new file mode 100644
index 0000000..e7279cc
--- /dev/null
+++ b/src/Conf/schema.json
@@ -0,0 +1,57 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Configurations",
+ "description": "Configurations for the psecio-parse security scanner",
+ "type": "object",
+ "properties": {
+ "paths": {
+ "description": "List of paths to scan",
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minItems": 1
+ },
+ "ignore-paths": {
+ "description": "List of paths to ignore",
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minItems": 1
+ },
+ "extensions": {
+ "description": "List of file extensions to scan",
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minItems": 1
+ },
+ "whitelist-rules": {
+ "description": "List of rules to whitelist",
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minItems": 1
+ },
+ "blacklist-rules": {
+ "description": "List of rules to blacklist",
+ "type": "array",
+ "items": {
+ "type": "string"
+ },
+ "minItems": 1
+ },
+ "disable-annotations": {
+ "description": "Flag if all annotation-based rule toggles should be ignored",
+ "type": "boolean"
+ },
+ "format": {
+ "description": "Output format",
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+}
diff --git a/src/Event/Events.php b/src/Event/Events.php
index 406a5f7..1d53625 100644
--- a/src/Event/Events.php
+++ b/src/Event/Events.php
@@ -9,6 +9,9 @@ interface Events
{
/**
* A scan.start event is fired when the scan starts
+ *
+ * The event listener receives an \Psecio\Parse\Event\MessageEvent instance
+ * with the number of files to scan as message
*/
const SCAN_START = 'scan.start';
diff --git a/src/RuleCollection.php b/src/RuleCollection.php
index 3844d80..40bcaab 100644
--- a/src/RuleCollection.php
+++ b/src/RuleCollection.php
@@ -111,4 +111,19 @@ public function toArray()
{
return $this->rules;
}
+
+ /**
+ * Get a comma separated list of rules in collection
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return implode(',', array_map(
+ function (RuleInterface $rule) {
+ return $rule->getName();
+ },
+ $this->toArray()
+ ));
+ }
}
diff --git a/src/Scanner.php b/src/Scanner.php
index 7e3f5d0..f345099 100644
--- a/src/Scanner.php
+++ b/src/Scanner.php
@@ -79,7 +79,7 @@ public function onNodeFailure(RuleInterface $rule, Node $node, File $file)
*/
public function scan(FileIterator $fileIterator)
{
- $this->dispatcher->dispatch(self::SCAN_START);
+ $this->dispatcher->dispatch(self::SCAN_START, new Event\MessageEvent(count($fileIterator)));
foreach ($fileIterator as $file) {
$this->dispatcher->dispatch(self::FILE_OPEN, new Event\FileEvent($file));
diff --git a/src/Subscriber/Subscriber.php b/src/Subscriber/BaseSubscriber.php
similarity index 92%
rename from src/Subscriber/Subscriber.php
rename to src/Subscriber/BaseSubscriber.php
index 793f772..0c211ea 100644
--- a/src/Subscriber/Subscriber.php
+++ b/src/Subscriber/BaseSubscriber.php
@@ -14,7 +14,7 @@
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
-class Subscriber implements EventSubscriberInterface, Events
+abstract class BaseSubscriber implements EventSubscriberInterface, Events
{
/**
* Returns an array of event names this subscriber wants to listen to
@@ -37,9 +37,10 @@ public static function getSubscribedEvents()
/**
* Empty on scan start method
*
+ * @param MessageEvent $event
* @return void
*/
- public function onScanStart()
+ public function onScanStart(MessageEvent $event)
{
}
diff --git a/src/Subscriber/ConsoleDebug.php b/src/Subscriber/Console/Debug.php
similarity index 83%
rename from src/Subscriber/ConsoleDebug.php
rename to src/Subscriber/Console/Debug.php
index 8398f99..a99ae33 100644
--- a/src/Subscriber/ConsoleDebug.php
+++ b/src/Subscriber/Console/Debug.php
@@ -1,13 +1,13 @@
write("[DEBUG] Starting scan\n");
$this->startTime = microtime(true);
}
diff --git a/src/Subscriber/ConsoleDots.php b/src/Subscriber/Console/Dots.php
similarity index 90%
rename from src/Subscriber/ConsoleDots.php
rename to src/Subscriber/Console/Dots.php
index cd86cae..031cfb4 100644
--- a/src/Subscriber/ConsoleDots.php
+++ b/src/Subscriber/Console/Dots.php
@@ -1,18 +1,17 @@
fileCount = 0;
}
diff --git a/src/Subscriber/Console/Header.php b/src/Subscriber/Console/Header.php
new file mode 100644
index 0000000..d3bf399
--- /dev/null
+++ b/src/Subscriber/Console/Header.php
@@ -0,0 +1,24 @@
+writeln("Parse: A PHP Security Scanner\n");
+ $this->setOutput($output);
+ }
+}
diff --git a/src/Subscriber/ConsoleLines.php b/src/Subscriber/Console/Lines.php
similarity index 92%
rename from src/Subscriber/ConsoleLines.php
rename to src/Subscriber/Console/Lines.php
index 8503d13..1005d1d 100644
--- a/src/Subscriber/ConsoleLines.php
+++ b/src/Subscriber/Console/Lines.php
@@ -1,6 +1,6 @@
progressBar = $progressBar;
+ parent::__construct($output);
+ $this->progressBar = $progressBar ?: new ProgressBar($output);
$this->progressBar->setFormat(
$this->progressBar->getMaxSteps() ? self::FORMAT_STEPS_KNOWN : self::FORMAT_STEPS_UNKNOWN
);
@@ -40,11 +44,12 @@ public function __construct(ProgressBar $progressBar)
/**
* Reset progress bar on scan start
*
+ * @param MessageEvent $event
* @return void
*/
- public function onScanStart()
+ public function onScanStart(MessageEvent $event)
{
- $this->progressBar->start();
+ $this->progressBar->start((int)$event->getMessage());
}
/**
diff --git a/src/Subscriber/ConsoleReport.php b/src/Subscriber/Console/Report.php
similarity index 93%
rename from src/Subscriber/ConsoleReport.php
rename to src/Subscriber/Console/Report.php
index 0312745..4c43856 100644
--- a/src/Subscriber/ConsoleReport.php
+++ b/src/Subscriber/Console/Report.php
@@ -1,15 +1,18 @@
fileCount = 0;
$this->issues = [];
diff --git a/src/Subscriber/ExitCodeCatcher.php b/src/Subscriber/ExitCodeCatcher.php
index f626d51..19b71e8 100644
--- a/src/Subscriber/ExitCodeCatcher.php
+++ b/src/Subscriber/ExitCodeCatcher.php
@@ -8,7 +8,7 @@
/**
* Capture the exit status code of a scan
*/
-class ExitCodeCatcher extends Subscriber
+class ExitCodeCatcher extends BaseSubscriber
{
/**
* @var integer Suggested exit code
diff --git a/src/Subscriber/OutputTrait.php b/src/Subscriber/OutputTrait.php
index 30fcfa0..4c23fa7 100644
--- a/src/Subscriber/OutputTrait.php
+++ b/src/Subscriber/OutputTrait.php
@@ -20,6 +20,16 @@ trait OutputTrait
* @param OutputInterface $output
*/
public function __construct(OutputInterface $output)
+ {
+ $this->setOutput($output);
+ }
+
+ /**
+ * Register output interface
+ *
+ * @param OutputInterface $output
+ */
+ public function setOutput(OutputInterface $output)
{
$this->output = $output;
}
diff --git a/src/Subscriber/SubscriberFactory.php b/src/Subscriber/SubscriberFactory.php
new file mode 100644
index 0000000..1afa86c
--- /dev/null
+++ b/src/Subscriber/SubscriberFactory.php
@@ -0,0 +1,159 @@
+ [
+ self::VERBOSITY_VERBOSE => self::FORMAT_LINES,
+ self::VERBOSITY_DEBUG => self::FORMAT_DEBUG
+ ],
+ self::FORMAT_DOTS => [
+ self::VERBOSITY_VERBOSE => self::FORMAT_LINES,
+ self::VERBOSITY_DEBUG => self::FORMAT_DEBUG
+ ],
+ self::FORMAT_LINES => [
+ self::VERBOSITY_VERBOSE => self::FORMAT_LINES,
+ self::VERBOSITY_DEBUG => self::FORMAT_DEBUG
+ ],
+ self::FORMAT_DEBUG => [
+ self::VERBOSITY_VERBOSE => self::FORMAT_DEBUG,
+ self::VERBOSITY_DEBUG => self::FORMAT_DEBUG
+ ],
+ self::FORMAT_XML => [
+ self::VERBOSITY_VERBOSE => self::FORMAT_XML,
+ self::VERBOSITY_DEBUG => self::FORMAT_XML
+ ]
+ ];
+
+ /**
+ * @var array Maps subscribers to format identifiers
+ */
+ private $subscriberMap = [
+ self::FORMAT_PROGRESS => [
+ '\Psecio\Parse\Subscriber\Console\Progress',
+ '\Psecio\Parse\Subscriber\Console\Report'
+ ],
+ self::FORMAT_DOTS => [
+ '\Psecio\Parse\Subscriber\Console\Dots',
+ '\Psecio\Parse\Subscriber\Console\Report'
+ ],
+ self::FORMAT_LINES => [
+ '\Psecio\Parse\Subscriber\Console\Lines',
+ '\Psecio\Parse\Subscriber\Console\Report'
+ ],
+ self::FORMAT_DEBUG => [
+ '\Psecio\Parse\Subscriber\Console\Debug',
+ '\Psecio\Parse\Subscriber\Console\Report'
+ ],
+ self::FORMAT_XML => [
+ '\Psecio\Parse\Subscriber\Xml'
+ ]
+ ];
+
+ /**
+ * @var string Format identifier
+ */
+ private $format;
+
+ /**
+ * @var OutputInterface Output object
+ */
+ private $output;
+
+ /**
+ * Set requested format and output object
+ *
+ * @param string $format Requested format
+ * @param OutputInterface $output Output object
+ * @throws RuntimeException If format is not valid
+ */
+ public function __construct($format, OutputInterface $output)
+ {
+ if (!isset($this->formatTransitions[$format])) {
+ throw new RuntimeException("Unknown output format '{$format}'");
+ }
+
+ if ($output->isVeryVerbose()) {
+ $format = $this->formatTransitions[$format][self::VERBOSITY_DEBUG];
+ } elseif ($output->isVerbose()) {
+ $format = $this->formatTransitions[$format][self::VERBOSITY_VERBOSE];
+ }
+
+ if (self::FORMAT_PROGRESS == $format && !$output->isDecorated()) {
+ $format = self::FORMAT_DOTS;
+ }
+
+ $this->format = $format;
+ $this->output = $output;
+ }
+
+ /**
+ * Get format identifier
+ *
+ * @return string
+ */
+ public function getFormat()
+ {
+ return $this->format;
+ }
+
+ /**
+ * Add subscribers to event dispatcher
+ *
+ * @param EventDispatcherInterface $dispatcher
+ * @return void
+ */
+ public function addSubscribersTo(EventDispatcherInterface $dispatcher)
+ {
+ foreach ($this->subscriberMap[$this->getFormat()] as $classname) {
+ $dispatcher->addSubscriber(new $classname($this->output));
+ }
+ }
+}
diff --git a/src/Subscriber/Xml.php b/src/Subscriber/Xml.php
index 9a78e4b..8b719ce 100644
--- a/src/Subscriber/Xml.php
+++ b/src/Subscriber/Xml.php
@@ -6,11 +6,12 @@
use XMLWriter;
use Psecio\Parse\Event\IssueEvent;
use Psecio\Parse\Event\ErrorEvent;
+use Psecio\Parse\Event\MessageEvent;
/**
* Xml generating event subscriber
*/
-class Xml extends Subscriber
+class Xml extends BaseSubscriber
{
use OutputTrait;
@@ -22,9 +23,10 @@ class Xml extends Subscriber
/**
* Create document at scan start
*
+ * @param MessageEvent $event
* @return void
*/
- public function onScanStart()
+ public function onScanStart(MessageEvent $event)
{
$this->xmlWriter = new XMLWriter;
$this->xmlWriter->openMemory();
diff --git a/tests/Command/ScanCommandTest.php b/tests/Command/ScanCommandTest.php
index a1359ac..6d384a6 100644
--- a/tests/Command/ScanCommandTest.php
+++ b/tests/Command/ScanCommandTest.php
@@ -78,25 +78,6 @@ public function testExceptionOnUnknownFormat()
$this->executeCommand(['--format' => 'this-format-does-not-exist']);
}
- public function testParseCsv()
- {
- $this->assertSame(
- ['php', 'phps'],
- (new ScanCommand)->parseCsv('php,phps'),
- 'parsing comma separated values should work'
- );
- $this->assertSame(
- ['php', 'phps'],
- array_values((new ScanCommand)->parseCsv('php,,phps')),
- 'multiple commas should be skipped while parsing csv'
- );
- $this->assertSame(
- [],
- (new ScanCommand)->parseCsv(''),
- 'parsing an empty string should return an empty array'
- );
- }
-
private function executeCommand(array $input, array $options = array())
{
$application = new Application;
diff --git a/tests/Conf/ConfFactoryTest.php b/tests/Conf/ConfFactoryTest.php
new file mode 100644
index 0000000..3167253
--- /dev/null
+++ b/tests/Conf/ConfFactoryTest.php
@@ -0,0 +1,79 @@
+setExpectedException('RuntimeException');
+ (new ConfFactory)->createConf(
+ m::mock('Symfony\Component\Console\Input\InputInterface')
+ ->shouldReceive('getOption')
+ ->with('configuration')
+ ->andReturn('this-file-does-not-exist')
+ ->mock()
+ );
+ }
+
+ public function testCustomConfFile()
+ {
+ $filename = sys_get_temp_dir() . '/' . uniqid('psecio-parse') . '.json';
+ file_put_contents($filename, '{"extensions": ["foobar"]}');
+
+ $conf = (new ConfFactory)->createConf(
+ m::mock('Symfony\Component\Console\Input\InputInterface')
+ ->shouldReceive('getOption')->with('configuration')->andReturn($filename)
+ ->shouldReceive('getOption')->with('extensions')->andReturn('')
+ ->mock()
+ );
+ $this->assertSame(['foobar'], $conf->getExtensions());
+
+ unlink($filename);
+ }
+
+ public function testDefaultConfFile()
+ {
+ $cwd = getcwd();
+ chdir(sys_get_temp_dir());
+
+ $filename = sys_get_temp_dir() . '/psecio-parse.json';
+ file_put_contents($filename, '{"extensions": ["foobar"]}');
+
+ $confUsingDefaultFile = (new ConfFactory)->createConf(
+ m::mock('Symfony\Component\Console\Input\InputInterface')
+ ->shouldReceive('getOption')->with('configuration')->andReturn('')
+ ->shouldReceive('getOption')->with('no-configuration')->andReturn(false)
+ ->shouldReceive('getOption')->with('extensions')->andReturn('')
+ ->mock()
+ );
+ $this->assertSame(['foobar'], $confUsingDefaultFile->getExtensions());
+
+ $confIgnoringDefaultFile = (new ConfFactory)->createConf(
+ m::mock('Symfony\Component\Console\Input\InputInterface')
+ ->shouldReceive('getOption')->with('configuration')->andReturn('')
+ ->shouldReceive('getOption')->with('no-configuration')->andReturn(true)
+ ->shouldReceive('getOption')->with('extensions')->andReturn('')
+ ->mock()
+ );
+ $this->assertTrue(in_array('php', $confIgnoringDefaultFile->getExtensions()));
+
+ unlink($filename);
+
+ $confDefaultFileMissing = (new ConfFactory)->createConf(
+ m::mock('Symfony\Component\Console\Input\InputInterface')
+ ->shouldReceive('getOption')->with('configuration')->andReturn('')
+ ->shouldReceive('getOption')->with('no-configuration')->andReturn(false)
+ ->shouldReceive('getOption')->with('extensions')->andReturn('')
+ ->mock()
+ );
+ $this->assertTrue(in_array('php', $confDefaultFileMissing->getExtensions()));
+
+ chdir($cwd);
+ }
+}
diff --git a/tests/Conf/DefaultConfTest.php b/tests/Conf/DefaultConfTest.php
new file mode 100644
index 0000000..209f477
--- /dev/null
+++ b/tests/Conf/DefaultConfTest.php
@@ -0,0 +1,18 @@
+assertSame('progress', $conf->getFormat());
+ $this->assertSame([], $conf->getPaths());
+ $this->assertSame([], $conf->getIgnorePaths());
+ $this->assertSame(['php', 'phps', 'phtml', 'php5'], $conf->getExtensions());
+ $this->assertSame([], $conf->getRuleWhitelist());
+ $this->assertSame([], $conf->getRuleBlacklist());
+ $this->assertFalse($conf->disableAnnotations());
+ }
+}
diff --git a/tests/Conf/DualConfTest.php b/tests/Conf/DualConfTest.php
new file mode 100644
index 0000000..6b22850
--- /dev/null
+++ b/tests/Conf/DualConfTest.php
@@ -0,0 +1,41 @@
+shouldReceive($method)->andReturn($first)->mock(),
+ m::mock('Psecio\Parse\Conf\Configuration')->shouldReceive($method)->andReturn($second)->mock()
+ );
+ $this->assertSame($expected, $conf->$method());
+ }
+}
diff --git a/tests/Conf/JsonConfTest.php b/tests/Conf/JsonConfTest.php
new file mode 100644
index 0000000..c61c35f
--- /dev/null
+++ b/tests/Conf/JsonConfTest.php
@@ -0,0 +1,49 @@
+setExpectedException('RuntimeException');
+ new JsonConf('this-is-not-a-valid-json-string');
+ }
+
+ public function testExceptionSchemaViolation()
+ {
+ $this->setExpectedException('RuntimeException');
+ new JsonConf('{"undefined": "this property is not definied in the schema"}');
+ }
+
+ public function configurationProvider()
+ {
+ return [
+ ['{}', 'getFormat', ''],
+ ['{}', 'getPaths', []],
+ ['{}', 'getIgnorePaths', []],
+ ['{}', 'getExtensions', []],
+ ['{}', 'getRuleWhitelist', []],
+ ['{}', 'getRuleBlacklist', []],
+ ['{}', 'disableAnnotations', false],
+ ['{"format":"dots"}', 'getFormat', 'dots'],
+ ['{"paths":["path"]}', 'getPaths', ['path']],
+ ['{"ignore-paths":["path"]}', 'getIgnorePaths', ['path']],
+ ['{"extensions":["php"]}', 'getExtensions', ['php']],
+ ['{"whitelist-rules":["rule"]}', 'getRuleWhitelist', ['rule']],
+ ['{"blacklist-rules":["rule"]}', 'getRuleBlacklist', ['rule']],
+ ['{"disable-annotations": true}', 'disableAnnotations', true],
+ ];
+ }
+
+ /**
+ * @dataProvider configurationProvider
+ */
+ public function testConfiguration($json, $method, $expected)
+ {
+ $this->assertSame(
+ $expected,
+ (new JsonConf($json))->$method()
+ );
+ }
+}
diff --git a/tests/Conf/UserConfTest.php b/tests/Conf/UserConfTest.php
new file mode 100644
index 0000000..135d929
--- /dev/null
+++ b/tests/Conf/UserConfTest.php
@@ -0,0 +1,92 @@
+shouldReceive('getOption')
+ ->with('format')
+ ->andReturn('foobar')
+ ->mock()
+ );
+ $this->assertSame('foobar', $conf->getFormat());
+ }
+
+ public function testPaths()
+ {
+ $conf = new UserConf(
+ m::mock('Symfony\Component\Console\Input\InputInterface')
+ ->shouldReceive('getArgument')
+ ->with('path')
+ ->andReturn(['path'])
+ ->mock()
+ );
+ $this->assertSame(['path'], $conf->getPaths());
+ }
+
+ public function testIgnorePaths()
+ {
+ $conf = new UserConf(
+ m::mock('Symfony\Component\Console\Input\InputInterface')
+ ->shouldReceive('getOption')
+ ->with('ignore-paths')
+ ->andReturn('path1,path2')
+ ->mock()
+ );
+ $this->assertSame(['path1', 'path2'], $conf->getIgnorePaths());
+ }
+
+ public function testExtensions()
+ {
+ $conf = new UserConf(
+ m::mock('Symfony\Component\Console\Input\InputInterface')
+ ->shouldReceive('getOption')
+ ->with('extensions')
+ ->andReturn('php,,phps')
+ ->mock()
+ );
+ $this->assertSame(['php', 'phps'], array_values($conf->getExtensions()));
+ }
+
+ public function testRuleWhitelist()
+ {
+ $conf = new UserConf(
+ m::mock('Symfony\Component\Console\Input\InputInterface')
+ ->shouldReceive('getOption')
+ ->with('whitelist-rules')
+ ->andReturn('')
+ ->mock()
+ );
+ $this->assertSame([], $conf->getRuleWhitelist());
+ }
+
+ public function testRuleBlacklist()
+ {
+ $conf = new UserConf(
+ m::mock('Symfony\Component\Console\Input\InputInterface')
+ ->shouldReceive('getOption')
+ ->with('blacklist-rules')
+ ->andReturn('rule')
+ ->mock()
+ );
+ $this->assertSame(['rule'], $conf->getRuleBlacklist());
+ }
+
+ public function testDisableAnnotations()
+ {
+ $conf = new UserConf(
+ m::mock('Symfony\Component\Console\Input\InputInterface')
+ ->shouldReceive('getOption')
+ ->with('disable-annotations')
+ ->andReturn(true)
+ ->mock()
+ );
+ $this->assertTrue($conf->disableAnnotations());
+ }
+}
diff --git a/tests/RuleCollectionTest.php b/tests/RuleCollectionTest.php
index fb18c80..fbc9111 100644
--- a/tests/RuleCollectionTest.php
+++ b/tests/RuleCollectionTest.php
@@ -116,4 +116,14 @@ public function testExceptionInRemove()
$this->setExpectedException('RuntimeException');
(new RuleCollection)->remove('does-not-exist');
}
+
+ public function testTostring()
+ {
+ $ruleA = m::mock('\Psecio\Parse\RuleInterface')->shouldReceive('getName')->andReturn('a')->mock();
+ $ruleB = m::mock('\Psecio\Parse\RuleInterface')->shouldReceive('getName')->andReturn('b')->mock();
+ $this->assertSame(
+ 'a,b',
+ (string)(new RuleCollection([$ruleA, $ruleB]))
+ );
+ }
}
diff --git a/tests/ScannerTest.php b/tests/ScannerTest.php
index fa7bf7e..32f3c80 100644
--- a/tests/ScannerTest.php
+++ b/tests/ScannerTest.php
@@ -41,12 +41,12 @@ public function testErrorOnPhpsFile()
m::mock('\PhpParser\NodeTraverser')->shouldReceive('traverse', 'addVisitor')->mock()
);
- $scanner->scan(
- m::mock('\Psecio\Parse\FileIterator')
- ->shouldReceive('getIterator')
- ->andReturn(new \ArrayIterator([$file]))
- ->mock()
- );
+ $fileIterator = m::mock('\Psecio\Parse\FileIterator')
+ ->shouldReceive('getIterator')->andReturn(new \ArrayIterator([$file]))
+ ->shouldReceive('count')->andReturn(1)
+ ->mock();
+
+ $scanner->scan($fileIterator);
}
public function testErrorOnParseException()
@@ -65,18 +65,21 @@ public function testErrorOnParseException()
->shouldReceive('addVisitor')->shouldReceive('traverse')->mock()
);
- $scanner->scan(
- m::mock('\Psecio\Parse\FileIterator')
- ->shouldReceive('getIterator')
- ->andReturn(new \ArrayIterator([$file]))
- ->mock()
- );
+ $fileIterator = m::mock('\Psecio\Parse\FileIterator')
+ ->shouldReceive('getIterator')->andReturn(new \ArrayIterator([$file]))
+ ->shouldReceive('count')->andReturn(1)
+ ->mock();
+
+ $scanner->scan($fileIterator);
}
private function createErrorDispatcherMock()
{
$dispatcher = m::mock('\Symfony\Component\EventDispatcher\EventDispatcherInterface');
- $dispatcher->shouldReceive('dispatch')->ordered()->once()->with(Scanner::SCAN_START);
+ $dispatcher->shouldReceive('dispatch')->ordered()->once()->with(
+ Scanner::SCAN_START,
+ m::type('\Psecio\Parse\Event\MessageEvent')
+ );
$dispatcher->shouldReceive('dispatch')->ordered()->once()->with(
Scanner::FILE_OPEN,
m::type('\Psecio\Parse\Event\FileEvent')
diff --git a/tests/Subscriber/SubscriberTest.php b/tests/Subscriber/BaseSubscriberTest.php
similarity index 71%
rename from tests/Subscriber/SubscriberTest.php
rename to tests/Subscriber/BaseSubscriberTest.php
index 618b9b8..15ec30d 100644
--- a/tests/Subscriber/SubscriberTest.php
+++ b/tests/Subscriber/BaseSubscriberTest.php
@@ -4,20 +4,20 @@
use Mockery as m;
-class SubscriberTest extends \PHPUnit_Framework_TestCase
+class BaseSubscriberTest extends \PHPUnit_Framework_TestCase
{
public function testSubscription()
{
$this->assertInternalType(
'array',
- Subscriber::getSubscribedEvents()
+ BaseSubscriber::getSubscribedEvents()
);
}
public function testEmptyMethods()
{
- $subscriber = new Subscriber;
- $this->assertNull($subscriber->onScanStart());
+ $subscriber = m::mock('\Psecio\Parse\Subscriber\BaseSubscriber[]');
+ $this->assertNull($subscriber->onScanStart(m::mock('\Psecio\Parse\Event\MessageEvent')));
$this->assertNull($subscriber->onScanComplete());
$this->assertNull($subscriber->onFileOpen(m::mock('\Psecio\Parse\Event\FileEvent')));
$this->assertNull($subscriber->onFileClose());
diff --git a/tests/Subscriber/ConsoleDebugTest.php b/tests/Subscriber/Console/DebugTest.php
similarity index 77%
rename from tests/Subscriber/ConsoleDebugTest.php
rename to tests/Subscriber/Console/DebugTest.php
index 104168c..014b4de 100644
--- a/tests/Subscriber/ConsoleDebugTest.php
+++ b/tests/Subscriber/Console/DebugTest.php
@@ -1,16 +1,17 @@
shouldReceive('writeln')->once()->with("/Parse/");
$output->shouldReceive('write')->ordered()->once()->with("[DEBUG] Starting scan\n");
$output->shouldReceive('write')->ordered()->once()->with("[DEBUG] debug message\n");
$output->shouldReceive('write')->ordered()->once()->with("/\[DEBUG\] Scan completed in \d+\.\d+ seconds/");
@@ -19,10 +20,10 @@ public function testOutput()
$messageEvent = m::mock('\Psecio\Parse\Event\MessageEvent');
$messageEvent->shouldReceive('getMessage')->andReturn('debug message');
- $console = new ConsoleDebug($output);
+ $console = new Debug($output);
// Should write debug start
- $console->onScanStart();
+ $console->onScanStart(m::mock('\Psecio\Parse\Event\MessageEvent'));
// Writes debug message
$console->onDebug($messageEvent);
diff --git a/tests/Subscriber/ConsoleDotsTest.php b/tests/Subscriber/Console/DotsTest.php
similarity index 83%
rename from tests/Subscriber/ConsoleDotsTest.php
rename to tests/Subscriber/Console/DotsTest.php
index 298d973..58d412e 100644
--- a/tests/Subscriber/ConsoleDotsTest.php
+++ b/tests/Subscriber/Console/DotsTest.php
@@ -1,25 +1,26 @@
shouldReceive('writeln')->once()->with("/Parse/");
$output->shouldReceive('write')->ordered()->once()->with(".");
$output->shouldReceive('write')->ordered()->once()->with("E");
$output->shouldReceive('write')->ordered()->once()->with("\n");
$output->shouldReceive('write')->ordered()->once()->with("I");
- $console = new ConsoleDots($output);
+ $console = new Dots($output);
$console->setLineLength(2);
- $console->onScanStart();
+ $console->onScanStart(m::mock('\Psecio\Parse\Event\MessageEvent'));
// Writes a dot as a file is scanned
$console->onFileOpen(m::mock('\Psecio\Parse\Event\FileEvent'));
diff --git a/tests/Subscriber/ConsoleLinesTest.php b/tests/Subscriber/Console/LinesTest.php
similarity index 88%
rename from tests/Subscriber/ConsoleLinesTest.php
rename to tests/Subscriber/Console/LinesTest.php
index 14e8243..bfb98f7 100644
--- a/tests/Subscriber/ConsoleLinesTest.php
+++ b/tests/Subscriber/Console/LinesTest.php
@@ -1,16 +1,17 @@
shouldReceive('writeln')->once()->with("/Parse/");
$output->shouldReceive('write')->ordered()->once()->with("[PARSE] /path/to/file\n");
$output->shouldReceive('write')->ordered()->once()->with("[PARSE] /path/to/file\n");
$output->shouldReceive('write')->ordered()->once()->with("[ERROR] message in /path/to/file\n");
@@ -32,9 +33,9 @@ public function testOutput()
$issueEvent->shouldReceive('getRule->getName')->andReturn('Rule');
$issueEvent->shouldReceive('getFile->getPath')->andReturn('path');
- $console = new ConsoleLines($output);
+ $console = new Lines($output);
- $console->onScanStart();
+ $console->onScanStart(m::mock('\Psecio\Parse\Event\MessageEvent'));
// File open writes [PARSE] line
$console->onFileOpen($fileEvent);
diff --git a/tests/Subscriber/ConsoleProgressBarTest.php b/tests/Subscriber/Console/ProgressTest.php
similarity index 58%
rename from tests/Subscriber/ConsoleProgressBarTest.php
rename to tests/Subscriber/Console/ProgressTest.php
index 7b05714..de9b85b 100644
--- a/tests/Subscriber/ConsoleProgressBarTest.php
+++ b/tests/Subscriber/Console/ProgressTest.php
@@ -1,10 +1,10 @@
shouldReceive('advance')->ordered()->once();
$bar->shouldReceive('finish')->ordered()->once();
- $console = new ConsoleProgressBar($bar);
- $console->onScanStart();
+ $output = m::mock('\Symfony\Component\Console\Output\OutputInterface');
+ $output->shouldReceive('writeln')->once()->with("/Parse/");
+
+ $console = new Progress($output, $bar);
+ $console->onScanStart(
+ m::mock('\Psecio\Parse\Event\MessageEvent')->shouldReceive('getMessage')->mock()
+ );
$console->onFileClose();
$console->onScanComplete();
}
diff --git a/tests/Subscriber/ConsoleReportTest.php b/tests/Subscriber/Console/ReportTest.php
similarity index 88%
rename from tests/Subscriber/ConsoleReportTest.php
rename to tests/Subscriber/Console/ReportTest.php
index 62362ec..4c58cc4 100644
--- a/tests/Subscriber/ConsoleReportTest.php
+++ b/tests/Subscriber/Console/ReportTest.php
@@ -1,14 +1,14 @@
shouldReceive('writeln')
->once()
@@ -16,7 +16,7 @@ public function testPassReport()
->mock()
);
- $report->onScanStart();
+ $report->onScanStart(m::mock('\Psecio\Parse\Event\MessageEvent'));
$report->onFileOpen(m::mock('\Psecio\Parse\Event\FileEvent'));
$report->onFileOpen(m::mock('\Psecio\Parse\Event\FileEvent'));
$report->onScanComplete();
@@ -41,7 +41,7 @@ public function testFailureReport()
FAILURES!
Scanned: 0, Errors: 1, Issues: 1.";
- $report = new ConsoleReport(
+ $report = new Report(
m::mock('\Symfony\Component\Console\Output\OutputInterface')
->shouldReceive('writeln')
->once()
@@ -49,7 +49,7 @@ public function testFailureReport()
->mock()
);
- $report->onScanStart();
+ $report->onScanStart(m::mock('\Psecio\Parse\Event\MessageEvent'));
$errorEvent = m::mock('\Psecio\Parse\Event\ErrorEvent');
$errorEvent->shouldReceive('getMessage')->once()->andReturn('error description');
diff --git a/tests/Subscriber/SubscriberFactoryTest.php b/tests/Subscriber/SubscriberFactoryTest.php
new file mode 100644
index 0000000..84cbd66
--- /dev/null
+++ b/tests/Subscriber/SubscriberFactoryTest.php
@@ -0,0 +1,84 @@
+setExpectedException('RuntimeException');
+ new SubscriberFactory(
+ 'invalid-format-identifier',
+ m::mock('Symfony\Component\Console\Output\OutputInterface')
+ );
+ }
+
+ public function testDebugTransition()
+ {
+ $factory = new SubscriberFactory(
+ SubscriberFactory::FORMAT_DOTS,
+ m::mock('Symfony\Component\Console\Output\OutputInterface')
+ ->shouldReceive('isVeryVerbose')
+ ->andReturn(true)
+ ->mock()
+ );
+ $this->assertSame(
+ SubscriberFactory::FORMAT_DEBUG,
+ $factory->getFormat()
+ );
+ }
+
+ public function testVerboseTransition()
+ {
+ $factory = new SubscriberFactory(
+ SubscriberFactory::FORMAT_DOTS,
+ m::mock('Symfony\Component\Console\Output\OutputInterface')
+ ->shouldReceive('isVeryVerbose')->andReturn(false)
+ ->shouldReceive('isVerbose')->andReturn(true)
+ ->mock()
+ );
+ $this->assertSame(
+ SubscriberFactory::FORMAT_LINES,
+ $factory->getFormat()
+ );
+ }
+
+ public function testNoAnsiTransition()
+ {
+ $factory = new SubscriberFactory(
+ SubscriberFactory::FORMAT_PROGRESS,
+ m::mock('Symfony\Component\Console\Output\OutputInterface')
+ ->shouldReceive('isVeryVerbose')->andReturn(false)
+ ->shouldReceive('isVerbose')->andReturn(false)
+ ->shouldReceive('isDecorated')->andReturn(false)
+ ->mock()
+ );
+ $this->assertSame(
+ SubscriberFactory::FORMAT_DOTS,
+ $factory->getFormat()
+ );
+ }
+
+ public function testAddSubscribersToDispatcher()
+ {
+ $factory = new SubscriberFactory(
+ SubscriberFactory::FORMAT_XML,
+ m::mock('Symfony\Component\Console\Output\OutputInterface')
+ ->shouldReceive('isVeryVerbose')->andReturn(false)
+ ->shouldReceive('isVerbose')->andReturn(false)
+ ->shouldReceive('isDecorated')->andReturn(true)
+ ->mock()
+ );
+ $factory->addSubscribersTo(
+ m::mock('Symfony\Component\EventDispatcher\EventDispatcherInterface')
+ ->shouldReceive('addSubscriber')
+ ->once()
+ ->mock()
+ );
+ }
+}
diff --git a/tests/Subscriber/XmlTest.php b/tests/Subscriber/XmlTest.php
index e76bbb3..53d8f01 100644
--- a/tests/Subscriber/XmlTest.php
+++ b/tests/Subscriber/XmlTest.php
@@ -33,7 +33,7 @@ public function testGenerateXml()
$xml = new Xml($output);
- $xml->onScanStart();
+ $xml->onScanStart(m::mock('\Psecio\Parse\Event\MessageEvent'));
$errorEvent = m::mock('\Psecio\Parse\Event\ErrorEvent');
$errorEvent->shouldReceive('getMessage')->once()->andReturn('error description');