├── .gitignore
├── tests
└── ParallelScenarioExtension
│ ├── ScenarioInfo
│ ├── Fixtures
│ │ └── ScenarioInfoExtractor
│ │ │ ├── single_scenario
│ │ │ ├── test.feature
│ │ │ └── expected.json
│ │ │ ├── not_parallel_parallel
│ │ │ ├── test.feature
│ │ │ └── expected.json
│ │ │ ├── parallel_not_parallel
│ │ │ ├── test.feature
│ │ │ └── expected.json
│ │ │ ├── parallel_parallel
│ │ │ ├── test.feature
│ │ │ └── expected.json
│ │ │ ├── parallel_parallel_wait
│ │ │ ├── test.feature
│ │ │ └── expected.json
│ │ │ ├── parallel_wait_parallel
│ │ │ ├── test.feature
│ │ │ └── expected.json
│ │ │ ├── parallel_parallel_examples
│ │ │ ├── expected.json
│ │ │ └── test.feature
│ │ │ ├── parallel_parallel_examples_split
│ │ │ ├── test.feature
│ │ │ └── expected.json
│ │ │ └── parallel_wait_parallel_examples_split
│ │ │ ├── test.feature
│ │ │ └── expected.json
│ ├── ScenarioInfoTest.php
│ └── ScenarioInfoExtractorTest.php
│ ├── Listener
│ ├── StopOnFailureTest.php
│ └── OutputPrinterTest.php
│ ├── ScenarioProcess
│ ├── ScenarioProcessProfileBalanceTest.php
│ ├── ScenarioProcessTest.php
│ └── ScenarioProcessFactoryTest.php
│ ├── Feature
│ └── FeatureRunnerTest.php
│ └── Cli
│ └── ParallelScenarioControllerTest.php
├── src
└── ParallelScenarioExtension
│ ├── ScenarioProcess
│ ├── Option
│ │ ├── ProcessOptionInterface.php
│ │ ├── ProcessOption.php
│ │ ├── ProcessOptionArray.php
│ │ ├── ProcessOptionScalar.php
│ │ ├── ProcessOptionCollection.php
│ │ └── ProcessOptionOut.php
│ ├── ScenarioProcessProfileBalance.php
│ ├── ScenarioProcess.php
│ └── ScenarioProcessFactory.php
│ ├── Event
│ └── ParallelScenarioEventType.php
│ ├── ScenarioInfo
│ ├── ScenarioInfo.php
│ └── ScenarioInfoExtractor.php
│ ├── Listener
│ ├── StopOnFailure.php
│ └── OutputPrinter.php
│ ├── Feature
│ ├── FeatureExtractor.php
│ └── FeatureRunner.php
│ ├── Cli
│ └── ParallelScenarioController.php
│ └── ServiceContainer
│ └── ParallelScenarioExtension.php
├── phpunit.xml.dist
├── composer.json
├── README.md
└── composer.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea/
2 | /vendor/
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioInfo/Fixtures/ScenarioInfoExtractor/single_scenario/test.feature:
--------------------------------------------------------------------------------
1 | Feature: Test
2 |
3 | @parallel
4 | Scenario: first
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioInfo/Fixtures/ScenarioInfoExtractor/single_scenario/expected.json:
--------------------------------------------------------------------------------
1 | [
2 | [
3 | {
4 | "line": 4,
5 | "file": "test.feature"
6 | }
7 | ]
8 | ]
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioInfo/Fixtures/ScenarioInfoExtractor/not_parallel_parallel/test.feature:
--------------------------------------------------------------------------------
1 | Feature: Test
2 |
3 |
4 | Scenario: first
5 |
6 | @parallel-scenario
7 | Scenario: second
8 |
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioInfo/Fixtures/ScenarioInfoExtractor/parallel_not_parallel/test.feature:
--------------------------------------------------------------------------------
1 | Feature: Test
2 |
3 | @parallel-scenario
4 | Scenario: first
5 |
6 | @parallel
7 | Scenario: second
8 |
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioInfo/Fixtures/ScenarioInfoExtractor/parallel_parallel/test.feature:
--------------------------------------------------------------------------------
1 | Feature: Test
2 |
3 | @parallel-scenario
4 | Scenario: first
5 |
6 | @parallel-scenario
7 | Scenario: second
8 |
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioInfo/Fixtures/ScenarioInfoExtractor/parallel_parallel_wait/test.feature:
--------------------------------------------------------------------------------
1 | Feature: Test
2 |
3 | @parallel-scenario
4 | Scenario: first
5 |
6 | @parallel-scenario @parallel-wait
7 | Scenario: second
8 |
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioInfo/Fixtures/ScenarioInfoExtractor/parallel_wait_parallel/test.feature:
--------------------------------------------------------------------------------
1 | Feature: Test
2 |
3 | @parallel-scenario @parallel-wait
4 | Scenario: first
5 |
6 | @parallel-scenario
7 | Scenario: second
8 |
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioInfo/Fixtures/ScenarioInfoExtractor/parallel_parallel/expected.json:
--------------------------------------------------------------------------------
1 | [
2 | [
3 | {
4 | "line": 4,
5 | "file": "test.feature"
6 | },
7 | {
8 | "line": 7,
9 | "file": "test.feature"
10 | }
11 | ]
12 | ]
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioInfo/Fixtures/ScenarioInfoExtractor/parallel_wait_parallel/expected.json:
--------------------------------------------------------------------------------
1 | [
2 | [
3 | {
4 | "line": 4,
5 | "file": "test.feature"
6 | },
7 | {
8 | "line": 7,
9 | "file": "test.feature"
10 | }
11 | ]
12 | ]
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioInfo/Fixtures/ScenarioInfoExtractor/parallel_parallel_examples/expected.json:
--------------------------------------------------------------------------------
1 | [
2 | [
3 | {
4 | "line": 4,
5 | "file": "test.feature"
6 | },
7 | {
8 | "line": 7,
9 | "file": "test.feature"
10 | }
11 | ]
12 | ]
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioInfo/Fixtures/ScenarioInfoExtractor/not_parallel_parallel/expected.json:
--------------------------------------------------------------------------------
1 | [
2 | [
3 | {
4 | "line": 4,
5 | "file": "test.feature"
6 | }
7 | ],
8 | [
9 | {
10 | "line": 7,
11 | "file": "test.feature"
12 | }
13 | ]
14 | ]
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioInfo/Fixtures/ScenarioInfoExtractor/parallel_not_parallel/expected.json:
--------------------------------------------------------------------------------
1 | [
2 | [
3 | {
4 | "line": 4,
5 | "file": "test.feature"
6 | }
7 | ],
8 | [
9 | {
10 | "line": 7,
11 | "file": "test.feature"
12 | }
13 | ]
14 | ]
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioInfo/Fixtures/ScenarioInfoExtractor/parallel_parallel_wait/expected.json:
--------------------------------------------------------------------------------
1 | [
2 | [
3 | {
4 | "line": 4,
5 | "file": "test.feature"
6 | }
7 | ],
8 | [
9 | {
10 | "line": 7,
11 | "file": "test.feature"
12 | }
13 | ]
14 | ]
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioInfo/Fixtures/ScenarioInfoExtractor/parallel_parallel_examples/test.feature:
--------------------------------------------------------------------------------
1 | Feature: Test
2 |
3 | @parallel-scenario
4 | Scenario: first
5 |
6 | @parallel-scenario
7 | Scenario Outline: second
8 | Examples:
9 | | parameter |
10 | | value1 |
11 | | value2 |
12 |
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioInfo/Fixtures/ScenarioInfoExtractor/parallel_parallel_examples_split/test.feature:
--------------------------------------------------------------------------------
1 | Feature: Test
2 |
3 | @parallel-scenario
4 | Scenario: first
5 |
6 | @parallel-examples
7 | Scenario Outline: second
8 | Examples:
9 | | parameter |
10 | | value1 |
11 | | value2 |
12 |
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioInfo/Fixtures/ScenarioInfoExtractor/parallel_wait_parallel_examples_split/test.feature:
--------------------------------------------------------------------------------
1 | Feature: Test
2 |
3 | @parallel-scenario
4 | Scenario: first
5 |
6 | @parallel-examples @parallel-wait
7 | Scenario Outline: second
8 | Examples:
9 | | parameter |
10 | | value1 |
11 | | value2 |
12 |
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioInfo/Fixtures/ScenarioInfoExtractor/parallel_parallel_examples_split/expected.json:
--------------------------------------------------------------------------------
1 | [
2 | [
3 | {
4 | "line": 4,
5 | "file": "test.feature"
6 | },
7 | {
8 | "line": 10,
9 | "file": "test.feature"
10 | },
11 | {
12 | "line": 11,
13 | "file": "test.feature"
14 | }
15 | ]
16 | ]
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioInfo/Fixtures/ScenarioInfoExtractor/parallel_wait_parallel_examples_split/expected.json:
--------------------------------------------------------------------------------
1 | [
2 | [
3 | {
4 | "line": 4,
5 | "file": "test.feature"
6 | }
7 | ],
8 | [
9 | {
10 | "line": 10,
11 | "file": "test.feature"
12 | },
13 | {
14 | "line": 11,
15 | "file": "test.feature"
16 | }
17 | ]
18 | ]
--------------------------------------------------------------------------------
/src/ParallelScenarioExtension/ScenarioProcess/Option/ProcessOptionInterface.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | interface ProcessOptionInterface
11 | {
12 | /**
13 | * @return string
14 | */
15 | public function __toString();
16 |
17 | /**
18 | * @return string
19 | */
20 | public function getOptionName();
21 | }
22 |
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioInfo/ScenarioInfoTest.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class ScenarioInfoTest extends \PHPUnit_Framework_TestCase
11 | {
12 | /**
13 | * @see ScenarioInfo::__toString
14 | */
15 | public function testToString()
16 | {
17 | $scenarioInfo = new ScenarioInfo('file', '80');
18 | $this->assertEquals('file:80', (string) $scenarioInfo);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 | ./tests/
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/ParallelScenarioExtension/Event/ParallelScenarioEventType.php:
--------------------------------------------------------------------------------
1 |
11 | */
12 | class ParallelScenarioEventType
13 | {
14 | const FEATURE_TESTED_BEFORE = 'parallel_scenario.feature_tested.before';
15 | const FEATURE_TESTED_AFTER = 'parallel_scenario.feature_tested.after';
16 |
17 | const PROCESS_BEFORE_START = ParallelProcessRunnerEventType::PROCESS_START_BEFORE;
18 | const PROCESS_AFTER_STOP = ParallelProcessRunnerEventType::PROCESS_STOP_AFTER;
19 | const PROCESS_OUT = ParallelProcessRunnerEventType::PROCESS_OUT;
20 | }
21 |
--------------------------------------------------------------------------------
/src/ParallelScenarioExtension/ScenarioInfo/ScenarioInfo.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class ScenarioInfo
11 | {
12 | /**
13 | * @var string
14 | */
15 | private $file;
16 | /**
17 | * @var int
18 | */
19 | private $line;
20 |
21 | /**
22 | * ScenarioInfo constructor.
23 | *
24 | * @param string $file
25 | * @param int $line
26 | */
27 | public function __construct($file, $line)
28 | {
29 | $this->file = $file;
30 | $this->line = $line;
31 | }
32 |
33 | /**
34 | * @return string
35 | */
36 | public function __toString()
37 | {
38 | return sprintf('%s:%d', $this->file, $this->line);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/ParallelScenarioExtension/ScenarioProcess/Option/ProcessOption.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class ProcessOption implements ProcessOptionInterface
11 | {
12 | /**
13 | * @var string
14 | */
15 | private $optionName;
16 |
17 | /**
18 | * ProcessOption constructor.
19 | *
20 | * @param string $optionName
21 | */
22 | public function __construct($optionName)
23 | {
24 | $this->optionName = $optionName;
25 | }
26 |
27 | /**
28 | * @return string
29 | */
30 | public function getOptionName()
31 | {
32 | return $this->optionName;
33 | }
34 |
35 | /**
36 | * @return string
37 | */
38 | public function __toString()
39 | {
40 | return sprintf('--%s', $this->getOptionName());
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tonicforhealth/behat-parallel-scenario",
3 | "description": "Behat parallel scenario",
4 | "keywords": ["bdd", "behat", "parallel", "process", "testing"],
5 | "authors": [
6 | {
7 | "name": "oleksii.storozhylov",
8 | "email": "oleksii.storozhylov@tonicforhealth.com"
9 | }
10 | ],
11 |
12 | "autoload": {
13 | "psr-4": { "Tonic\\Behat\\": "src/" }
14 | },
15 |
16 | "require": {
17 | "behat/behat" : "3.*",
18 | "tonicforhealth/parallel-process-runner" : "1.*"
19 | },
20 |
21 | "require-dev": {
22 | "phpunit/phpunit": "4.1.*",
23 | "symfony/finder": "3.*"
24 | },
25 |
26 | "repositories": [
27 | {
28 | "type": "vcs",
29 | "url": "https://github.com/brandonroberts/Mailinator"
30 | },
31 | {
32 | "type": "vcs",
33 | "url": "https://github.com/tonicforhealth/MinkExtension"
34 | }
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/src/ParallelScenarioExtension/ScenarioProcess/Option/ProcessOptionArray.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class ProcessOptionArray extends ProcessOption
11 | {
12 | /**
13 | * @var array
14 | */
15 | protected $optionValues = [];
16 |
17 | /**
18 | * ProcessOptionArray constructor.
19 | *
20 | * @param string $optionName
21 | * @param array $optionValues
22 | */
23 | public function __construct($optionName, array $optionValues)
24 | {
25 | $this->optionValues = $optionValues;
26 | parent::__construct($optionName);
27 | }
28 |
29 | /**
30 | * @return string
31 | */
32 | public function __toString()
33 | {
34 | $options = array_map(function ($optionValue) {
35 | return sprintf('%s %s', parent::__toString(), escapeshellarg($optionValue));
36 | }, $this->optionValues);
37 |
38 | return implode(' ', $options);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/ParallelScenarioExtension/ScenarioProcess/Option/ProcessOptionScalar.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class ProcessOptionScalar extends ProcessOption
11 | {
12 | /**
13 | * @var string|int
14 | */
15 | private $optionValue;
16 |
17 | /**
18 | * ProcessOptionString constructor.
19 | *
20 | * @param string $optionName
21 | * @param mixed $optionValue
22 | */
23 | public function __construct($optionName, $optionValue)
24 | {
25 | $this->optionValue = $optionValue;
26 | parent::__construct($optionName);
27 | }
28 |
29 | /**
30 | * @return int|string
31 | */
32 | public function getOptionValue()
33 | {
34 | return $this->optionValue;
35 | }
36 |
37 | /**
38 | * @return string
39 | */
40 | public function __toString()
41 | {
42 | return sprintf('%s %s', parent::__toString(), escapeshellarg($this->getOptionValue()));
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Install
2 |
3 | Install via composer
4 | ```bash
5 | $ composer require tonicforhealth/behat-parallel-scenario
6 | ```
7 | # Config
8 |
9 | #### Load extenstion into config:
10 | ```yml
11 | default:
12 | extensions:
13 | Tonic\Behat\ParallelScenarioExtension: ~
14 | ```
15 | #### or
16 | ```yml
17 | default:
18 | extensions:
19 | Tonic\Behat\ParallelScenarioExtension:
20 | profiles:
21 | - profile_name_for_worker_1
22 | - profile_name_for_worker_2
23 | - profile_name_for_worker_3
24 | options:
25 | skip:
26 | - any-behat-option-for-skiping-in-worker
27 | ```
28 | #### Mark scenarios with tags
29 | * run scenario in parallel
30 |
31 | ```
32 | @parallel-scenario
33 | ```
34 |
35 | * wait all parallel scenarios are done
36 |
37 | ```
38 | @parallel-wait
39 | ```
40 | * run examples in parallel
41 |
42 | ```
43 | @parallel-examples
44 | ```
45 | # Run
46 | ```bash
47 | $ bin/behat --parallel-process 2
48 | ```
49 | When parameter is absent or equal to 1 then test will be run in usual mode
50 |
51 | [](https://codeclimate.com/github/tonicforhealth/behat-parallel-scenario)
52 |
--------------------------------------------------------------------------------
/src/ParallelScenarioExtension/ScenarioProcess/Option/ProcessOptionCollection.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class ProcessOptionCollection
11 | {
12 | /**
13 | * @var ProcessOptionInterface[]
14 | */
15 | private $options = [];
16 |
17 | /**
18 | * @param ProcessOptionInterface $option
19 | */
20 | public function set(ProcessOptionInterface $option)
21 | {
22 | $this->options[$option->getOptionName()] = $option;
23 | }
24 |
25 | /**
26 | * @param string $optionName
27 | *
28 | * @return null|ProcessOptionInterface
29 | */
30 | public function get($optionName)
31 | {
32 | return array_key_exists($optionName, $this->options) ? $this->options[$optionName] : null;
33 | }
34 |
35 | /**
36 | * @return ProcessOptionInterface[]
37 | */
38 | public function toArray()
39 | {
40 | return $this->options;
41 | }
42 |
43 | /**
44 | * @return string
45 | */
46 | public function __toString()
47 | {
48 | return implode(' ', array_map(function (ProcessOptionInterface $option) {
49 | return (string) $option;
50 | }, $this->options));
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/ParallelScenarioExtension/ScenarioProcess/Option/ProcessOptionOut.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class ProcessOptionOut extends ProcessOptionArray
11 | {
12 | /**
13 | * @var string
14 | */
15 | private $outSuffix;
16 |
17 | /**
18 | * @param string $outSuffix
19 | */
20 | public function setOutSuffix($outSuffix)
21 | {
22 | $this->outSuffix = $outSuffix;
23 | }
24 |
25 | /**
26 | * @return string
27 | */
28 | public function __toString()
29 | {
30 | $options = array_map(function ($optionValue) {
31 | if (!$this->isStandardOutput($optionValue)) {
32 | $optionValue = sprintf('%s/%s', $optionValue, $this->outSuffix);
33 | }
34 |
35 | return sprintf('--%s %s', $this->getOptionName(), escapeshellarg($optionValue));
36 | }, $this->optionValues);
37 |
38 | return implode(' ', $options);
39 | }
40 |
41 | /**
42 | * Checks if provided output identifier represents standard output.
43 | *
44 | * @param string $outputId
45 | *
46 | * @see \Behat\Testwork\Output\Cli\OutputController::isStandardOutput
47 | *
48 | * @return Boolean
49 | */
50 | private function isStandardOutput($outputId)
51 | {
52 | return in_array($outputId, [
53 | 'std',
54 | 'null',
55 | 'false',
56 | ]);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/ParallelScenarioExtension/Listener/StopOnFailure.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | class StopOnFailure implements EventSubscriberInterface
17 | {
18 | /**
19 | * @var ParallelProcessRunner
20 | */
21 | private $parallelProcessRunner;
22 |
23 | /**
24 | * StopOnFailureListener constructor.
25 | *
26 | * @param ParallelProcessRunner $processRunner
27 | */
28 | public function __construct(ParallelProcessRunner $processRunner)
29 | {
30 | $this->parallelProcessRunner = $processRunner;
31 | }
32 |
33 | /**
34 | * {@inheritdoc}
35 | */
36 | public static function getSubscribedEvents()
37 | {
38 | return [
39 | ParallelScenarioEventType::PROCESS_AFTER_STOP => 'stopOnFailure',
40 | ];
41 | }
42 |
43 | /**
44 | * @param ProcessEvent $event
45 | */
46 | public function stopOnFailure(ProcessEvent $event)
47 | {
48 | /** @var ScenarioProcess $process */
49 | $process = $event->getProcess();
50 | if ($process->withError()) {
51 | $this->parallelProcessRunner->stop();
52 | $this->terminate(1);
53 | }
54 | }
55 |
56 | /**
57 | * @param int $code
58 | *
59 | * @codeCoverageIgnore
60 | */
61 | protected function terminate($code)
62 | {
63 | exit($code);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/ParallelScenarioExtension/Listener/OutputPrinter.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | class OutputPrinter implements EventSubscriberInterface
17 | {
18 | /**
19 | * @var OutputInterface
20 | */
21 | private $output;
22 |
23 | /**
24 | * @param OutputInterface $output
25 | */
26 | public function init(OutputInterface $output)
27 | {
28 | $this->output = $output;
29 | }
30 |
31 | /**
32 | * {@inheritdoc}
33 | */
34 | public static function getSubscribedEvents()
35 | {
36 | return [
37 | ParallelScenarioEventType::PROCESS_BEFORE_START => 'beforeStart',
38 | ParallelScenarioEventType::PROCESS_AFTER_STOP => 'afterStop',
39 | ];
40 | }
41 |
42 | /**
43 | * @param ProcessEvent $event
44 | */
45 | public function beforeStart(ProcessEvent $event)
46 | {
47 | $this->output->writeln(sprintf('START ::: %s', $event->getProcess()->getCommandLine()));
48 | }
49 |
50 | /**
51 | * @param ProcessEvent $event
52 | */
53 | public function afterStop(ProcessEvent $event)
54 | {
55 | /** @var ScenarioProcess $process */
56 | $process = $event->getProcess();
57 | if ($process->withError()) {
58 | $this->output->writeln(sprintf('%s', $process->getOutput()));
59 | $this->output->writeln(sprintf('%s', $process->getErrorOutput()));
60 | } else {
61 | $this->output->writeln(sprintf('%s', $process->getOutput()));
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/ParallelScenarioExtension/Feature/FeatureExtractor.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | class FeatureExtractor
17 | {
18 | /**
19 | * @var SuiteRepository
20 | */
21 | private $suiteRepository;
22 |
23 | /**
24 | * @var SpecificationFinder
25 | */
26 | private $specificationFinder;
27 |
28 | /**
29 | * FeatureExtractor constructor.
30 | *
31 | * @param SuiteRepository $suiteRepository
32 | * @param SpecificationFinder $specificationFinder
33 | */
34 | public function __construct(SuiteRepository $suiteRepository, SpecificationFinder $specificationFinder)
35 | {
36 | $this->suiteRepository = $suiteRepository;
37 | $this->specificationFinder = $specificationFinder;
38 | }
39 |
40 | /**
41 | * @param string $locator
42 | *
43 | * @return FeatureNode[]
44 | */
45 | public function extract($locator)
46 | {
47 | $features = [];
48 |
49 | $specifications = $this->findSuitesSpecifications($locator);
50 |
51 | foreach (GroupedSpecificationIterator::group($specifications) as $iterator) {
52 | foreach ($iterator as $featureNode) {
53 | $features[] = $featureNode;
54 | }
55 | }
56 |
57 | return $features;
58 | }
59 |
60 | /**
61 | * Finds specification iterators for all provided suites using locator.
62 | *
63 | * @param null|string $locator
64 | *
65 | * @return SpecificationIterator[]
66 | */
67 | private function findSuitesSpecifications($locator)
68 | {
69 | return $this->specificationFinder->findSuitesSpecifications(
70 | $this->suiteRepository->getSuites(),
71 | $locator
72 | );
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/ParallelScenarioExtension/ScenarioProcess/ScenarioProcessProfileBalance.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class ScenarioProcessProfileBalance implements EventSubscriberInterface
16 | {
17 | /**
18 | * @var array
19 | */
20 | protected $balance = [];
21 |
22 | /**
23 | * @param array $profiles
24 | */
25 | public function __construct(array $profiles)
26 | {
27 | $this->balance = array_fill_keys($profiles, 0);
28 | }
29 |
30 | /**
31 | * {@inheritdoc}
32 | */
33 | public static function getSubscribedEvents()
34 | {
35 | return [
36 | ParallelScenarioEventType::PROCESS_BEFORE_START => 'increment',
37 | ParallelScenarioEventType::PROCESS_AFTER_STOP => 'decrement',
38 | ];
39 | }
40 |
41 | /**
42 | * @param ProcessEvent $event
43 | */
44 | public function increment(ProcessEvent $event)
45 | {
46 | if ($this->balance) {
47 | $profile = $this->getProfileNameWithMinimumBalance();
48 | $this->balance[$profile]++;
49 | /** @var ScenarioProcess $process */
50 | $process = $event->getProcess();
51 | $process->setProcessOption(new ProcessOptionScalar('profile', $profile));
52 | }
53 | }
54 |
55 | /**
56 | * @param ProcessEvent $event
57 | */
58 | public function decrement(ProcessEvent $event)
59 | {
60 | if ($this->balance) {
61 | /** @var ScenarioProcess $process */
62 | $process = $event->getProcess();
63 | /** @var ProcessOptionScalar $profileOption */
64 | $profileOption = $process->getProcessOption('profile');
65 | $this->balance[$profileOption->getOptionValue()]--;
66 | }
67 | }
68 |
69 | /**
70 | * @return string
71 | */
72 | protected function getProfileNameWithMinimumBalance()
73 | {
74 | return array_keys($this->balance, min($this->balance))[0];
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/Listener/StopOnFailureTest.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class StopOnFailureTest extends \PHPUnit_Framework_TestCase
16 | {
17 | /**
18 | * @see StopOnFailure::getSubscribedEvents
19 | */
20 | public function testGetSubscribedEvents()
21 | {
22 | $this->assertEquals([
23 | ParallelScenarioEventType::PROCESS_AFTER_STOP => 'stopOnFailure',
24 | ], StopOnFailure::getSubscribedEvents());
25 | }
26 |
27 | /**
28 | * @see StopOnFailure::stopOnFailure
29 | */
30 | public function testStopOnFailureWithError()
31 | {
32 | $parallelProcessRunner = $this->getMock(ParallelProcessRunner::class, ['stop']);
33 | $parallelProcessRunner->expects($this->once())->method('stop');
34 |
35 | $process = $this->getMock(ScenarioProcess::class, ['withError'], [], '', false);
36 | $process->expects($this->once())->method('withError')->willReturn(true);
37 |
38 | $event = $this->getMock(ProcessEvent::class, null, [$process]);
39 |
40 | $listener = $this->getMock(StopOnFailure::class, ['terminate'], [$parallelProcessRunner]);
41 | $listener->expects($this->once())->method('terminate')->with(1);
42 |
43 | /** @var StopOnFailure $listener */
44 | /** @var ProcessEvent $event */
45 | $listener->stopOnFailure($event);
46 | }
47 |
48 | /**
49 | * @see StopOnFailure::stopOnFailure
50 | */
51 | public function testStopOnFailureWithoutError()
52 | {
53 | $parallelProcessRunner = $this->getMock(ParallelProcessRunner::class, ['stop']);
54 | $parallelProcessRunner->expects($this->never())->method('stop');
55 |
56 | $process = $this->getMock(ScenarioProcess::class, ['withError'], [], '', false);
57 | $process->expects($this->once())->method('withError')->willReturn(false);
58 |
59 | $event = $this->getMock(ProcessEvent::class, null, [$process]);
60 |
61 | $listener = $this->getMock(StopOnFailure::class, ['terminate'], [$parallelProcessRunner]);
62 | $listener->expects($this->never())->method('terminate');
63 |
64 | /** @var StopOnFailure $listener */
65 | /** @var ProcessEvent $event */
66 | $listener->stopOnFailure($event);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioInfo/ScenarioInfoExtractorTest.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | class ScenarioInfoExtractorTest extends \PHPUnit_Framework_TestCase
18 | {
19 | /**
20 | * @return array
21 | */
22 | public function provider()
23 | {
24 | $cases = [];
25 | $parser = $this->getParser();
26 | $finder = new Finder();
27 | foreach ($finder->in(sprintf('%s/Fixtures/ScenarioInfoExtractor', __DIR__))->directories() as $directory) {
28 | /** @var SplFileInfo $directory */
29 | $featureFile = sprintf('%s/test.feature', $directory->getPathname());
30 | $expectedFile = sprintf('%s/expected.json', $directory->getPathname());
31 |
32 | $cases[$directory->getBasename()] = [
33 | $parser->parse(file_get_contents($featureFile), $featureFile),
34 | $this->getScenarioInfo(json_decode(file_get_contents($expectedFile), true), $directory->getPathname()),
35 | ];
36 | }
37 |
38 | return $cases;
39 | }
40 |
41 | /**
42 | * @param FeatureNode $feature
43 | * @param array $expected
44 | *
45 | * @dataProvider provider
46 | */
47 | public function test(FeatureNode $feature, array $expected)
48 | {
49 | $extractor = new ScenarioInfoExtractor();
50 | $result = $extractor->extract($feature);
51 |
52 | $this->assertEquals($expected, $result);
53 | }
54 |
55 | private function getScenarioInfo(array $data, $path)
56 | {
57 | foreach ($data as &$group) {
58 | $group = array_map(function ($data) use ($path) {
59 | return new ScenarioInfo(sprintf('%s/%s', $path, $data['file']), $data['line']);
60 | }, $group);
61 | }
62 |
63 | return $data;
64 | }
65 |
66 | /**
67 | * @return Parser
68 | */
69 | private function getParser()
70 | {
71 | $keywords = [
72 | 'en' => [
73 | 'and' => 'And',
74 | 'background' => 'Background',
75 | 'but' => 'But',
76 | 'examples' => 'Examples|Scenarios',
77 | 'feature' => 'Feature|Business Need|Ability',
78 | 'given' => 'Given',
79 | 'name' => 'English',
80 | 'native' => 'English',
81 | 'scenario' => 'Scenario',
82 | 'scenario_outline' => 'Scenario Outline|Scenario Template',
83 | 'then' => 'Then',
84 | 'when' => 'When',
85 | ],
86 | ];
87 |
88 | return new Parser(new Lexer(new ArrayKeywords($keywords)));
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/ParallelScenarioExtension/ScenarioInfo/ScenarioInfoExtractor.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | class ScenarioInfoExtractor
15 | {
16 | const TAG_PARALLEL_SCENARIO = 'parallel-scenario';
17 | const TAG_PARALLEL_WAIT = 'parallel-wait';
18 | const TAG_PARALLEL_EXAMPLES = 'parallel-examples';
19 |
20 | /**
21 | * @param FeatureNode $feature
22 | *
23 | * @return array
24 | */
25 | public function extract(FeatureNode $feature)
26 | {
27 | $allScenarios = [];
28 |
29 | foreach ($feature->getScenarios() as $scenario) {
30 | $scenarios = [];
31 |
32 | switch (true) {
33 | case $scenario instanceof OutlineNode && $this->isParallelExamples($scenario):
34 | foreach ($scenario->getExamples() as $exampleNode) {
35 | $scenarios[] = new ScenarioInfo($feature->getFile(), $exampleNode->getLine());
36 | }
37 | break;
38 | case $scenario instanceof OutlineNode:
39 | case $scenario instanceof ScenarioInterface:
40 | $scenarios[] = new ScenarioInfo($feature->getFile(), $scenario->getLine());
41 | }
42 |
43 | if ($this->isParallelWait($scenario) || !$this->isParallel($scenario)) {
44 | $allScenarios[] = [];
45 | }
46 |
47 | $lastIndex = empty($allScenarios) ? 0 : count($allScenarios) - 1;
48 |
49 | if (!array_key_exists($lastIndex, $allScenarios)) {
50 | $allScenarios[$lastIndex] = [];
51 | }
52 |
53 | $allScenarios[$lastIndex] = array_merge($allScenarios[$lastIndex], $scenarios);
54 |
55 | if (!$this->isParallel($scenario)) {
56 | $allScenarios[] = [];
57 | }
58 | }
59 |
60 | return array_values(array_filter($allScenarios));
61 | }
62 |
63 | /**
64 | * @param ScenarioInterface $scenario
65 | *
66 | * @return bool
67 | */
68 | private function isParallel(ScenarioInterface $scenario)
69 | {
70 | return in_array(self::TAG_PARALLEL_SCENARIO, $scenario->getTags()) || $this->isParallelExamples($scenario);
71 | }
72 |
73 | /**
74 | * @param ScenarioInterface $scenario
75 | *
76 | * @return bool
77 | */
78 | private function isParallelWait(ScenarioInterface $scenario)
79 | {
80 | return in_array(self::TAG_PARALLEL_WAIT, $scenario->getTags());
81 | }
82 |
83 | /**
84 | * @param ScenarioInterface $scenario
85 | *
86 | * @return bool
87 | */
88 | private function isParallelExamples(ScenarioInterface $scenario)
89 | {
90 | return $scenario instanceof OutlineNode && in_array(self::TAG_PARALLEL_EXAMPLES, $scenario->getTags());
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/ParallelScenarioExtension/Cli/ParallelScenarioController.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | class ParallelScenarioController implements Controller
22 | {
23 | const OPTION_PARALLEL_PROCESS = 'parallel-process';
24 |
25 | /**
26 | * @var FeatureRunner
27 | */
28 | private $featureRunner;
29 | /**
30 | * @var FeatureExtractor
31 | */
32 | private $featureExtractor;
33 | /**
34 | * @var ScenarioProcessFactory
35 | */
36 | private $processFactory;
37 | /**
38 | * @var OutputPrinter
39 | */
40 | private $outputPrinter;
41 | /**
42 | * @var InputDefinition
43 | */
44 | private $inputDefinition;
45 |
46 | /**
47 | * ParallelScenarioController constructor.
48 | *
49 | * @param FeatureRunner $featureRunner
50 | * @param FeatureExtractor $featureExtractor
51 | * @param ScenarioProcessFactory $processFactory
52 | * @param OutputPrinter $outputPrinter
53 | */
54 | public function __construct(FeatureRunner $featureRunner, FeatureExtractor $featureExtractor, ScenarioProcessFactory $processFactory, OutputPrinter $outputPrinter)
55 | {
56 | $this->featureRunner = $featureRunner;
57 | $this->featureExtractor = $featureExtractor;
58 | $this->processFactory = $processFactory;
59 | $this->outputPrinter = $outputPrinter;
60 | }
61 |
62 | /**
63 | * {@inheritdoc}
64 | */
65 | public function configure(SymfonyCommand $command)
66 | {
67 | $command->addOption(self::OPTION_PARALLEL_PROCESS, null, InputOption::VALUE_OPTIONAL, 'Max parallel processes amount', 1);
68 | $this->inputDefinition = $command->getDefinition();
69 | }
70 |
71 | /**
72 | * {@inheritdoc}
73 | */
74 | public function execute(InputInterface $input, OutputInterface $output)
75 | {
76 | $result = null;
77 |
78 | $maxProcessesAmount = max(1, $input->getOption(self::OPTION_PARALLEL_PROCESS));
79 | $locator = $input->getArgument('paths');
80 |
81 | if ($maxProcessesAmount > 1) {
82 | $this->outputPrinter->init($output);
83 | $this->processFactory->init($this->inputDefinition, $input);
84 | $this->featureRunner->setMaxParallelProcess($maxProcessesAmount);
85 |
86 | $result = 0;
87 |
88 | foreach ($this->featureExtractor->extract($locator) as $featureNode) {
89 | $result = max($result, $this->featureRunner->run($featureNode));
90 | }
91 | }
92 |
93 | return $result;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/ParallelScenarioExtension/ScenarioProcess/ScenarioProcess.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class ScenarioProcess extends Process
16 | {
17 | /**
18 | * @var ScenarioInfo
19 | */
20 | private $scenarioInfo;
21 | /**
22 | * @var string
23 | */
24 | private $commandLine;
25 | /**
26 | * @var ProcessOptionCollection
27 | */
28 | private $optionCollection;
29 |
30 | /**
31 | * ScenarioProcess constructor.
32 | *
33 | * @param ScenarioInfo $scenarioInfo
34 | * @param null|string $commandline
35 | * @param null $cwd
36 | * @param array|null $env
37 | * @param null $input
38 | * @param int $timeout
39 | * @param array $options
40 | */
41 | public function __construct(ScenarioInfo $scenarioInfo, $commandline, $cwd = null, array $env = null, $input = null, $timeout = 0, array $options = array())
42 | {
43 | $this->scenarioInfo = $scenarioInfo;
44 | $this->commandLine = $commandline;
45 | $this->optionCollection = new ProcessOptionCollection();
46 | parent::__construct($this->getCommandLineWithOptions(), $cwd, $env, $input, $timeout, $options);
47 | }
48 |
49 | /**
50 | * @param ProcessOptionInterface $option
51 | */
52 | public function setProcessOption(ProcessOptionInterface $option)
53 | {
54 | $this->optionCollection->set($option);
55 | }
56 |
57 | /**
58 | * @param string $optionName
59 | *
60 | * @return null|ProcessOptionInterface
61 | */
62 | public function getProcessOption($optionName)
63 | {
64 | return $this->optionCollection->get($optionName);
65 | }
66 |
67 | /**
68 | * {@inheritdoc}
69 | */
70 | public function setCommandLine($commandline)
71 | {
72 | $this->commandLine = $commandline;
73 | }
74 |
75 | /**
76 | * @return bool
77 | */
78 | public function withError()
79 | {
80 | return (bool) $this->getExitCode();
81 | }
82 |
83 | /**
84 | * {@inheritdoc}
85 | */
86 | public function start(callable $callback = null)
87 | {
88 | $this->updateCommandLine();
89 | parent::start($callback);
90 | }
91 |
92 | /**
93 | * {@inheritdoc}
94 | */
95 | public function getCommandLine()
96 | {
97 | $this->updateCommandLine();
98 |
99 | return parent::getCommandLine();
100 | }
101 |
102 | /**
103 | * {@inheritdoc}
104 | */
105 | protected function getCommandLineWithOptions()
106 | {
107 | return implode(' ', array_filter([
108 | $this->commandLine,
109 | (string) $this->optionCollection,
110 | ]));
111 | }
112 |
113 | protected function updateCommandLine()
114 | {
115 | parent::setCommandLine($this->getCommandLineWithOptions());
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/ParallelScenarioExtension/Feature/FeatureRunner.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | class FeatureRunner
20 | {
21 | /**
22 | * @var EventDispatcherInterface
23 | */
24 | private $eventDispatcher;
25 | /**
26 | * @var ScenarioInfoExtractor
27 | */
28 | private $scenarioInfoExtractor;
29 | /**
30 | * @var ScenarioProcessFactory
31 | */
32 | private $scenarioProcessFactory;
33 | /**
34 | * @var ParallelProcessRunner
35 | */
36 | private $parallelProcessRunner;
37 |
38 | /**
39 | * FeatureRunner constructor.
40 | *
41 | * @param EventDispatcherInterface $eventDispatcher
42 | * @param ScenarioInfoExtractor $infoExtractor
43 | * @param ScenarioProcessFactory $processFactory
44 | * @param ParallelProcessRunner $processRunner
45 | */
46 | public function __construct(EventDispatcherInterface $eventDispatcher, ScenarioInfoExtractor $infoExtractor, ScenarioProcessFactory $processFactory, ParallelProcessRunner $processRunner)
47 | {
48 | $this->eventDispatcher = $eventDispatcher;
49 | $this->scenarioInfoExtractor = $infoExtractor;
50 | $this->scenarioProcessFactory = $processFactory;
51 | $this->parallelProcessRunner = $processRunner;
52 | }
53 |
54 | /**
55 | * @param FeatureNode $featureNode
56 | *
57 | * @return int
58 | */
59 | public function run(FeatureNode $featureNode)
60 | {
61 | $result = 0;
62 |
63 | $this->eventDispatcher->dispatch(ParallelScenarioEventType::FEATURE_TESTED_BEFORE);
64 | $scenarioGroups = $this->scenarioInfoExtractor->extract($featureNode);
65 |
66 | foreach ($scenarioGroups as $scenarios) {
67 | $result = max($result, $this->runScenarios($scenarios));
68 | }
69 | $this->eventDispatcher->dispatch(ParallelScenarioEventType::FEATURE_TESTED_BEFORE);
70 |
71 | return $result;
72 | }
73 |
74 | /**
75 | * @param int $maxParallelProcess
76 | *
77 | * @return $this
78 | */
79 | public function setMaxParallelProcess($maxParallelProcess)
80 | {
81 | $this->parallelProcessRunner->setMaxParallelProcess($maxParallelProcess);
82 |
83 | return $this;
84 | }
85 |
86 | /**
87 | * @param ScenarioInfo[] $scenarios
88 | *
89 | * @return int
90 | */
91 | protected function runScenarios(array $scenarios)
92 | {
93 | $result = 0;
94 |
95 | /** @var ScenarioProcess[] $processes */
96 | $processes = array_map(function (ScenarioInfo $scenarioInfo) {
97 | return $this->scenarioProcessFactory->make($scenarioInfo);
98 | }, $scenarios);
99 |
100 | $this->parallelProcessRunner->reset()->add($processes)->run();
101 |
102 | foreach ($processes as $process) {
103 | $result = max($result, $process->getExitCode());
104 | }
105 |
106 | return $result;
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/Listener/OutputPrinterTest.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | class OutputPrinterTest extends \PHPUnit_Framework_TestCase
17 | {
18 | /**
19 | * @see OutputPrinter::getSubscribedEvents
20 | */
21 | public function testGetSubscribedEvents()
22 | {
23 | $this->assertEquals([
24 | ParallelScenarioEventType::PROCESS_BEFORE_START => 'beforeStart',
25 | ParallelScenarioEventType::PROCESS_AFTER_STOP => 'afterStop',
26 | ], OutputPrinter::getSubscribedEvents());
27 | }
28 |
29 | /**
30 | * @see OutputPrinter::beforeStart
31 | */
32 | public function testBeforeStart()
33 | {
34 | $output = $this->getMock(ConsoleOutput::class, ['writeln']);
35 | $output->expects($this->once())->method('writeln')->with('START ::: command');
36 |
37 | $process = $this->getMock(ScenarioProcess::class, ['getCommandLine'], [], '', false);
38 | $process->expects($this->once())->method('getCommandLine')->willReturn('command');
39 |
40 | $event = $this->getMock(ProcessEvent::class, null, [$process]);
41 |
42 | /** @var OutputInterface $output */
43 | /** @var ProcessEvent $event */
44 | $printer = new OutputPrinter();
45 | $printer->init($output);
46 |
47 | $printer->beforeStart($event);
48 | }
49 |
50 | /**
51 | * @return array
52 | */
53 | public function providerAfterStep()
54 | {
55 | return [
56 | [
57 | true,
58 | [
59 | 'output',
60 | 'error.output',
61 | ],
62 | ],
63 | [
64 | false,
65 | [
66 | 'output',
67 | ],
68 | ],
69 | ];
70 | }
71 |
72 | /**
73 | * @param bool $error
74 | * @param array $expected
75 | *
76 | * @see OutputPrinter::afterStop
77 | *
78 | * @dataProvider providerAfterStep
79 | */
80 | public function testAfterStop($error, array $expected)
81 | {
82 | $output = $this->getMock(ConsoleOutput::class, ['writeln']);
83 | foreach ($expected as $index => $line) {
84 | $output->expects($this->at($index))->method('writeln')->with($line);
85 | }
86 | $output->expects($this->exactly(count($expected)))->method('writeln');
87 |
88 | $process = $this->getMock(ScenarioProcess::class, ['withError', 'getOutput', 'getErrorOutput'], [], '', false);
89 | $process->expects($this->once())->method('withError')->willReturn($error);
90 | $process->expects($this->once())->method('getOutput')->willReturn('output');
91 | if ($error) {
92 | $process->expects($this->once())->method('getErrorOutput')->willReturn('error.output');
93 | } else {
94 | $process->expects($this->never())->method('getErrorOutput');
95 | }
96 |
97 | $event = $this->getMock(ProcessEvent::class, null, [$process]);
98 |
99 | /** @var OutputInterface $output */
100 | /** @var ProcessEvent $event */
101 | $printer = new OutputPrinter();
102 | $printer->init($output);
103 |
104 | $printer->afterStop($event);
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioProcess/ScenarioProcessProfileBalanceTest.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class ScenarioProcessProfileBalanceTest extends \PHPUnit_Framework_TestCase
16 | {
17 | /**
18 | * @see ScenarioProcessProfileBalance::getSubscribedEvents
19 | */
20 | public function testGetSubscribedEvents()
21 | {
22 | $this->assertEquals([
23 | ParallelScenarioEventType::PROCESS_BEFORE_START => 'increment',
24 | ParallelScenarioEventType::PROCESS_AFTER_STOP => 'decrement',
25 | ], ScenarioProcessProfileBalance::getSubscribedEvents());
26 | }
27 |
28 | /**
29 | * @see ScenarioProcessProfileBalance::increase
30 | * @see ScenarioProcessProfileBalance::decrease
31 | */
32 | public function test()
33 | {
34 | $profiles = [
35 | 'profile1',
36 | 'profile2',
37 | ];
38 |
39 | $events = $this->getEvents(9);
40 |
41 | $balance = new ScenarioProcessProfileBalance($profiles);
42 | foreach ($events as $event) {
43 | $balance->increment($event);
44 | }
45 |
46 | $this->assertEquals([
47 | 'profile1' => 5,
48 | 'profile2' => 4,
49 | ], $this->countProfilesFromEvents($events));
50 |
51 | foreach ($this->filterEventsByProfile($events, 'profile1') as $event) {
52 | $balance->decrement($event);
53 | }
54 |
55 | $newEvents = $this->getEvents(5);
56 | foreach ($newEvents as $event) {
57 | $balance->increment($event);
58 | }
59 | $this->assertEquals([
60 | 'profile1' => 5,
61 | ], $this->countProfilesFromEvents($newEvents));
62 | }
63 |
64 | /**
65 | * @param ProcessEvent[] $events
66 | * @param string $profile
67 | *
68 | * @return ProcessEvent[]
69 | */
70 | public function filterEventsByProfile(array $events, $profile)
71 | {
72 | return array_filter($events, function (ProcessEvent $event) use ($profile) {
73 | /** @var ScenarioProcess $process */
74 | $process = $event->getProcess();
75 | /** @var ProcessOptionScalar $profileOption */
76 | $profileOption = $process->getProcessOption('profile');
77 |
78 | return $profileOption->getOptionValue() == $profile;
79 | });
80 | }
81 |
82 | /**
83 | * @param int $amount
84 | *
85 | * @return ProcessEvent[]
86 | */
87 | private function getEvents($amount)
88 | {
89 | $events = [];
90 | while ($amount--) {
91 | $scenarioInfo = $this->getMock(ScenarioInfo::class, null, [], '', false);
92 | $process = $this->getMock(ScenarioProcess::class, null, [$scenarioInfo, (string) $scenarioInfo]);
93 | $event = $this->getMock(ProcessEvent::class, null, [$process]);
94 |
95 | $events[] = $event;
96 | }
97 |
98 | return $events;
99 | }
100 |
101 | /**
102 | * @param ProcessEvent[] $events
103 | *
104 | * @return array
105 | */
106 | private function countProfilesFromEvents(array $events)
107 | {
108 | $profilesCount = [];
109 | foreach ($events as $event) {
110 | /** @var ScenarioProcess $process */
111 | $process = $event->getProcess();
112 | /** @var ProcessOptionScalar $profileOption */
113 | $profileOption = $process->getProcessOption('profile');
114 | @$profilesCount[$profileOption->getOptionValue()]++;
115 | }
116 |
117 | return $profilesCount;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioProcess/ScenarioProcessTest.php:
--------------------------------------------------------------------------------
1 |
14 | */
15 | class ScenarioProcessTest extends \PHPUnit_Framework_TestCase
16 | {
17 | /**
18 | * @return array
19 | */
20 | public function providerSetGetProcessOption()
21 | {
22 | return [
23 | [
24 | 'any',
25 | ],
26 |
27 | [
28 | 'out',
29 | new ProcessOptionOut('out', ['std']),
30 | ],
31 | ];
32 | }
33 |
34 | /**
35 | * @param string $name
36 | * @param ProcessOptionInterface|null $option
37 | *
38 | * @dataProvider providerSetGetProcessOption
39 | */
40 | public function testSetGetProcessOption($name, ProcessOptionInterface $option = null)
41 | {
42 | $process = new ScenarioProcess(new ScenarioInfo('file', 0), '');
43 | if ($option) {
44 | $process->setProcessOption($option);
45 | }
46 |
47 | $this->assertEquals($option, $process->getProcessOption($name));
48 | }
49 |
50 | /**
51 | * @param string $commandLine
52 | */
53 | public function testSetCommandLine($commandLine = 'test')
54 | {
55 | $process = new ScenarioProcess(new ScenarioInfo('file', 0), '');
56 | $process->setCommandLine($commandLine);
57 |
58 | $this->assertEquals($commandLine, $process->getCommandLine());
59 | }
60 |
61 | /**
62 | * @return array
63 | */
64 | public function providerWithError()
65 | {
66 | return [
67 | [true],
68 | [false],
69 | ];
70 | }
71 |
72 | /**
73 | * @param bool $withError
74 | *
75 | * @dataProvider providerWithError
76 | */
77 | public function testWithError($withError)
78 | {
79 | $process = $this->getMock(ScenarioProcess::class, ['getExitCode'], [], '', false);
80 | $process->expects($this->once())->method('getExitCode')->willReturn($withError);
81 | /** @var ScenarioProcess $process */
82 | $this->assertEquals($withError, $process->withError());
83 | }
84 |
85 | /**
86 | * @param string $optionName
87 | * @param string $optionValue
88 | */
89 | public function testCommon($optionName = 'option', $optionValue = 'test')
90 | {
91 | $process = new ScenarioProcess(new ScenarioInfo('file', 0), 'cmd');
92 | $process->setProcessOption(new ProcessOptionScalar($optionName, $optionValue));
93 |
94 | $this->assertEquals(
95 | sprintf('cmd --%s %s', $optionName, escapeshellarg($optionValue)),
96 | $process->getCommandLine()
97 | );
98 |
99 | $process->setProcessOption(new ProcessOptionScalar($optionName, $optionValue.$optionValue));
100 | $this->assertEquals(
101 | sprintf('cmd --%s %s', $optionName, escapeshellarg($optionValue.$optionValue)),
102 | $process->getCommandLine()
103 | );
104 | }
105 |
106 | /**
107 | * @return array
108 | */
109 | public function providerUpdateCommandLineCall()
110 | {
111 | return [
112 | ['run'],
113 | ['start'],
114 | ['getCommandLine'],
115 | ];
116 | }
117 |
118 | /**
119 | * @param string $method
120 | *
121 | * @dataProvider providerUpdateCommandLineCall
122 | */
123 | public function testUpdateCommandLineCall($method)
124 | {
125 | $command = sprintf('%s', PHP_BINARY);
126 |
127 | $process = $this->getMock(ScenarioProcess::class, ['updateCommandLine'], [new ScenarioInfo('file', 0), $command]);
128 | $process->expects($this->once())->method('updateCommandLine');
129 | /** @var ScenarioProcess $process */
130 | $process->$method();
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/ParallelScenarioExtension/ScenarioProcess/ScenarioProcessFactory.php:
--------------------------------------------------------------------------------
1 |
20 | */
21 | class ScenarioProcessFactory
22 | {
23 | /**
24 | * @var string
25 | */
26 | private $behatBinaryPath;
27 | /**
28 | * @var array
29 | */
30 | private $skipOptions = [
31 | ParallelScenarioController::OPTION_PARALLEL_PROCESS,
32 | ];
33 | /**
34 | * @var ProcessOptionCollection
35 | */
36 | private $optionCollection;
37 |
38 | /**
39 | * ScenarioProcessFactory constructor.
40 | *
41 | * @param string|null $behatBinaryPath
42 | */
43 | public function __construct($behatBinaryPath = null)
44 | {
45 | $this->behatBinaryPath = is_null($behatBinaryPath) ? reset($_SERVER['argv']) : $behatBinaryPath;
46 | }
47 |
48 | /**
49 | * @param array $options
50 | */
51 | public function addSkipOptions(array $options)
52 | {
53 | $this->skipOptions = array_unique(array_merge($this->skipOptions, $options));
54 | }
55 |
56 | /**
57 | * @param InputDefinition $inputDefinition
58 | * @param InputInterface $input
59 | */
60 | public function init(InputDefinition $inputDefinition, InputInterface $input)
61 | {
62 | $options = new ProcessOptionCollection();
63 |
64 | foreach ($inputDefinition->getOptions() as $optionName => $inputOption) {
65 | $optionValue = $input->getOption($optionName);
66 | if ($inputOption->getDefault() != $optionValue) {
67 | switch (true) {
68 | case in_array($optionName, $this->skipOptions):
69 | $option = null;
70 | break;
71 | case $inputOption->isArray() && $optionName == 'out':
72 | $option = new ProcessOptionOut($optionName, $optionValue);
73 | break;
74 | case $inputOption->isArray():
75 | $option = new ProcessOptionArray($optionName, $optionValue);
76 | break;
77 | case $inputOption->isValueRequired():
78 | case $inputOption->isValueOptional():
79 | $option = new ProcessOptionScalar($optionName, $optionValue);
80 | break;
81 | default:
82 | $option = new ProcessOption($optionName);
83 | }
84 |
85 | if ($option) {
86 | $options->set($option);
87 | }
88 | }
89 | }
90 |
91 | $this->optionCollection = $options;
92 | }
93 |
94 | /**
95 | * @param ScenarioInfo $scenarioInfo
96 | *
97 | * @return Process
98 | */
99 | public function make(ScenarioInfo $scenarioInfo)
100 | {
101 | $fileLine = (string) $scenarioInfo;
102 |
103 | $options = $this->optionCollection->toArray();
104 |
105 | $outOption = $this->optionCollection->get('out');
106 | if ($outOption instanceof ProcessOptionOut) {
107 | $outOption->setOutSuffix(md5($fileLine));
108 | $options['out'] = clone $outOption;
109 | }
110 |
111 | $commandLine = sprintf('%s %s %s', PHP_BINARY, $this->behatBinaryPath, escapeshellarg($fileLine));
112 | $process = new ScenarioProcess($scenarioInfo, $commandLine);
113 |
114 | foreach ($options as $option) {
115 | $process->setProcessOption($option);
116 | }
117 |
118 | return $process;
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/ScenarioProcess/ScenarioProcessFactoryTest.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | class ScenarioProcessFactoryTest extends \PHPUnit_Framework_TestCase
17 | {
18 | /**
19 | * @return array
20 | */
21 | public function providerMake()
22 | {
23 | $inputDefinition = new InputDefinition();
24 | $inputDefinition->addOption(new InputOption('option_optional_default', null, InputOption::VALUE_OPTIONAL, '', 'default'));
25 | $inputDefinition->addOption(new InputOption('option_array_default_array', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY));
26 | $inputDefinition->addOption(new InputOption('option_none', null, InputOption::VALUE_NONE));
27 | $inputDefinition->addOption(new InputOption('out', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY));
28 |
29 | return [
30 | 'option_optional_default_default' => [
31 | $inputDefinition,
32 | new ArrayInput([
33 | '--option_optional_default' => 'default',
34 | ], $inputDefinition),
35 | ],
36 |
37 | 'option_optional_default_value' => [
38 | $inputDefinition,
39 | new ArrayInput([
40 | '--option_optional_default' => 'value',
41 | ], $inputDefinition),
42 | '--option_optional_default \'value\'',
43 | ],
44 |
45 | 'option_array_default_array_default' => [
46 | $inputDefinition,
47 | new ArrayInput([
48 | '--option_array_default_array' => [],
49 | ], $inputDefinition),
50 | ],
51 |
52 | 'option_array_default_array_value' => [
53 | $inputDefinition,
54 | new ArrayInput([
55 | '--option_array_default_array' => ['value1', 'value2'],
56 | ], $inputDefinition),
57 | '--option_array_default_array \'value1\' --option_array_default_array \'value2\'',
58 | ],
59 |
60 | 'option_none_default' => [
61 | $inputDefinition,
62 | new ArrayInput([], $inputDefinition),
63 | ],
64 |
65 | 'option_none_default_value' => [
66 | $inputDefinition,
67 | new ArrayInput([
68 | '--option_none' => 'any',
69 | ], $inputDefinition),
70 | '--option_none',
71 | ],
72 |
73 | 'out_folder_skip' => [
74 | $inputDefinition,
75 | new ArrayInput([
76 | '--out' => ['folder', 'std'],
77 | ], $inputDefinition),
78 | '',
79 | ['out'],
80 | ],
81 |
82 | 'out_folder' => [
83 | $inputDefinition,
84 | new ArrayInput([
85 | '--out' => ['folder', 'std'],
86 | ], $inputDefinition),
87 | '--out \'folder/59adaf3f0820898ecf0da97ceab30eab\' --out \'std\'',
88 | ],
89 | ];
90 | }
91 |
92 | /**
93 | * @param InputDefinition $inputDefinition
94 | * @param InputInterface $input
95 | * @param string $expectedCommandLine
96 | * @param array $skipOptions
97 | *
98 | * @see ScenarioProcessFactory::init
99 | * @see ScenarioProcessFactory::make
100 | *
101 | * @dataProvider providerMake
102 | */
103 | public function testMake(InputDefinition $inputDefinition, InputInterface $input, $expectedCommandLine = '', array $skipOptions = [])
104 | {
105 | $scenarioProcessFactory = new ScenarioProcessFactory('bin/behat');
106 | $scenarioProcessFactory->addSkipOptions($skipOptions);
107 | $scenarioProcessFactory->init($inputDefinition, $input);
108 |
109 | $scenarioInfo = new ScenarioInfo('file', 1);
110 | $process = $scenarioProcessFactory->make($scenarioInfo);
111 |
112 | $this->assertEquals(trim(sprintf('%s bin/behat \'file:1\' %s', PHP_BINARY, $expectedCommandLine)), $process->getCommandLine());
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/Feature/FeatureRunnerTest.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | class FeatureRunnerTest extends \PHPUnit_Framework_TestCase
19 | {
20 | /**
21 | * @return array
22 | */
23 | public function providerSetMaxParallelProcess()
24 | {
25 | return [
26 | [1],
27 | [2],
28 | ];
29 | }
30 |
31 | /**
32 | * @param int $maxParallelProcess
33 | *
34 | * @dataProvider providerSetMaxParallelProcess
35 | */
36 | public function testSetMaxParallelProcess($maxParallelProcess)
37 | {
38 | $eventDispatcher = $this->getMock(EventDispatcherInterface::class);
39 | $scenarioInfoExtractor = $this->getMock(ScenarioInfoExtractor::class);
40 | $scenarioProcessFactory = $this->getMock(ScenarioProcessFactory::class);
41 | $parallelProcessRunner = $this->getMock(ParallelProcessRunner::class, ['setMaxParallelProcess']);
42 |
43 | $parallelProcessRunner->expects($this->once())->method('setMaxParallelProcess')->with($maxParallelProcess);
44 |
45 | /** @var $eventDispatcher EventDispatcherInterface $featureRunner */
46 | /** @var $scenarioInfoExtractor ScenarioInfoExtractor $featureRunner */
47 | /** @var $scenarioProcessFactory ScenarioProcessFactory $featureRunner */
48 | /** @var $parallelProcessRunner ParallelProcessRunner $featureRunner */
49 | $featureRunner = new FeatureRunner(
50 | $eventDispatcher,
51 | $scenarioInfoExtractor,
52 | $scenarioProcessFactory,
53 | $parallelProcessRunner
54 | );
55 |
56 | $featureRunner->setMaxParallelProcess($maxParallelProcess);
57 | }
58 |
59 | /**
60 | * @return array
61 | */
62 | public function providerRun()
63 | {
64 | return [
65 | 'empty' => [
66 | [],
67 | ],
68 | '1 scenario in 1 group' => [
69 | [
70 | [
71 | new ScenarioInfo('file', 1),
72 | ],
73 | ],
74 | ],
75 | '2 scenario in 1 group' => [
76 | [
77 | [
78 | new ScenarioInfo('file', 1),
79 | new ScenarioInfo('file', 2),
80 | ],
81 | ],
82 | ],
83 | '2 scenario in 2 group' => [
84 | [
85 | [
86 | new ScenarioInfo('file', 1),
87 | ],
88 | [
89 | new ScenarioInfo('file', 1),
90 | ],
91 | ],
92 | ],
93 | ];
94 | }
95 |
96 | /**
97 | * @param array $scenarioGroups
98 | *
99 | * @dataProvider providerRun
100 | */
101 | public function testRun(array $scenarioGroups)
102 | {
103 | $eventDispatcher = $this->getMock(EventDispatcherInterface::class);
104 | $scenarioInfoExtractor = $this->getMock(ScenarioInfoExtractor::class, ['extract']);
105 | $scenarioInfoExtractor->expects($this->once())->method('extract')->willReturn($scenarioGroups);
106 |
107 | $scenarioProcessFactory = $this->getMock(ScenarioProcessFactory::class, ['make']);
108 |
109 | $index = 0;
110 | $processGroups = [];
111 | foreach ($scenarioGroups as $groupId => $scenarios) {
112 | $processGroups[$groupId] = [];
113 | foreach ($scenarios as $scenarioInfo) {
114 | $process = $this->getMock(ScenarioProcess::class, null, [$scenarioInfo, '']);
115 | $processGroups[$groupId][] = $process;
116 |
117 | $scenarioProcessFactory->expects($this->at($index))->method('make')->with($scenarioInfo)->willReturn($process);
118 | $index++;
119 | }
120 | }
121 | $scenarioProcessFactory->expects($this->exactly($index))->method('make');
122 |
123 | $parallelProcessRunner = $this->getMock(ParallelProcessRunner::class, ['reset', 'add', 'run']);
124 |
125 | $parallelProcessRunner->expects($this->exactly(count($processGroups)))->method('reset')->willReturn($parallelProcessRunner);
126 | foreach ($processGroups as $index => $processes) {
127 | // count index with methods amount correction
128 | $parallelProcessRunner->expects($this->at($index * 3 + 1))->method('add')->with($processes)->willReturn($parallelProcessRunner);
129 | }
130 |
131 | $parallelProcessRunner->expects($this->exactly(count($processGroups)))->method('add');
132 | $parallelProcessRunner->expects($this->exactly(count($processGroups)))->method('run')->willReturn($parallelProcessRunner);
133 |
134 | $featureNode = $this->getMock(FeatureNode::class, [], [], '', false);
135 |
136 | /** @var EventDispatcherInterface $eventDispatcher */
137 | /** @var ScenarioInfoExtractor $scenarioInfoExtractor */
138 | /** @var ScenarioProcessFactory $scenarioProcessFactory */
139 | /** @var ParallelProcessRunner $parallelProcessRunner */
140 | /** @var FeatureNode $featureNode */
141 | $featureRunner = new FeatureRunner(
142 | $eventDispatcher,
143 | $scenarioInfoExtractor,
144 | $scenarioProcessFactory,
145 | $parallelProcessRunner
146 | );
147 |
148 | $featureRunner->run($featureNode);
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/tests/ParallelScenarioExtension/Cli/ParallelScenarioControllerTest.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | class ParallelScenarioControllerTest extends \PHPUnit_Framework_TestCase
24 | {
25 | /**
26 | * @see ParallelScenarioController::configure
27 | */
28 | public function testConfigure()
29 | {
30 | $controller = $this->getMock(ParallelScenarioController::class, null, [], '', false);
31 |
32 | $command = $this->getMock(Command::class, ['addOption'], [], '', false);
33 | $command->expects($this->once())->method('addOption')
34 | ->with('parallel-process', null, InputOption::VALUE_OPTIONAL, 'Max parallel processes amount', 1);
35 |
36 | /** @var ParallelScenarioController $controller */
37 | /** @var Command $command */
38 | $controller->configure($command);
39 | }
40 |
41 | /**
42 | * @return array
43 | */
44 | public function providerExecuteMultiProcess()
45 | {
46 | return [
47 | [
48 | 5,
49 | 'locator',
50 | [
51 | $this->getMock(FeatureNode::class, [], [], '', false),
52 | ],
53 | 0,
54 | ],
55 |
56 | [
57 | 2,
58 | 'locator',
59 | [],
60 | 0,
61 | ],
62 |
63 | [
64 | 3,
65 | 'locator',
66 | [
67 | $this->getMock(FeatureNode::class, [], [], '', false),
68 | $this->getMock(FeatureNode::class, [], [], '', false),
69 | ],
70 | 1,
71 | ],
72 | ];
73 | }
74 |
75 | /**
76 | * @param int $parallelProcess
77 | * @param string $paths
78 | * @param array $featureNodes
79 | * @param int $expectedResult
80 | *
81 | * @see ParallelScenarioController::execute
82 | *
83 | * @dataProvider providerExecuteMultiProcess
84 | */
85 | public function testExecuteMultiProcess($parallelProcess, $paths, array $featureNodes, $expectedResult)
86 | {
87 | $inputDefinition = $this->getMock(InputDefinition::class);
88 |
89 | $input = $this->getMock(ArgvInput::class, ['getArgument', 'getOption']);
90 | $input->expects($this->once())->method('getOption')
91 | ->with(ParallelScenarioController::OPTION_PARALLEL_PROCESS)->willReturn($parallelProcess);
92 | $input->expects($this->once())->method('getArgument')
93 | ->with('paths')->willReturn($paths);
94 |
95 | $output = $this->getMock(ConsoleOutput::class);
96 |
97 | $featureRunner = $this->getMock(FeatureRunner::class, ['run', 'setMaxParallelProcess'], [], '', false);
98 | $featureRunner->expects($this->once())->method('setMaxParallelProcess')
99 | ->with($parallelProcess);
100 | foreach ($featureNodes as $index => $featureNode) {
101 | $featureRunner->expects($this->at($index + 1))->method('run')->with($featureNode)->willReturn($expectedResult);
102 | }
103 | $featureRunner->expects($this->exactly(count($featureNodes)))->method('run');
104 |
105 | $featureExtractor = $this->getMock(FeatureExtractor::class, ['extract'], [], '', false);
106 | $featureExtractor->expects($this->once())->method('extract')
107 | ->with($paths)->willReturn($featureNodes);
108 |
109 | $scenarioProcessFactory = $this->getMock(ScenarioProcessFactory::class, ['init'], [], '', false);
110 |
111 | $outputPrinter = $this->getMock(OutputPrinter::class, ['init'], [], '', false);
112 | $outputPrinter->expects($this->once())->method('init')
113 | ->with($output);
114 |
115 | $command = $this->getMock(Command::class, ['getDefinition', 'addOption'], [], '', false);
116 | $command->expects($this->once())->method('getDefinition')
117 | ->willReturn($inputDefinition);
118 |
119 | $controller = $this->getMock(ParallelScenarioController::class, null, [
120 | $featureRunner,
121 | $featureExtractor,
122 | $scenarioProcessFactory,
123 | $outputPrinter,
124 | ]);
125 |
126 | /** @var ParallelScenarioController $controller */
127 | /** @var Command $command */
128 | /** @var InputInterface $input */
129 | /** @var OutputInterface $output */
130 | $controller->configure($command);
131 | $result = $controller->execute($input, $output);
132 |
133 | $this->assertEquals($result, $expectedResult);
134 | }
135 |
136 | /**
137 | * @return array
138 | */
139 | public function providerExecuteSingleProcess()
140 | {
141 | return [
142 | [0],
143 | [-1],
144 | [1],
145 | [null],
146 | ];
147 | }
148 |
149 | /**
150 | * @param mixed $parallelProcess
151 | *
152 | * @see ParallelScenarioController::execute
153 | * @dataProvider providerExecuteSingleProcess
154 | */
155 | public function testExecuteSingleProcess($parallelProcess)
156 | {
157 | $inputDefinition = $this->getMock(InputDefinition::class);
158 |
159 | $input = $this->getMock(ArgvInput::class, ['getArgument', 'getOption']);
160 | $input->expects($this->once())->method('getOption')
161 | ->with(ParallelScenarioController::OPTION_PARALLEL_PROCESS)->willReturn($parallelProcess);
162 | $input->expects($this->once())->method('getArgument')
163 | ->with('paths');
164 |
165 | $output = $this->getMock(ConsoleOutput::class);
166 |
167 | $featureRunner = $this->getMock(FeatureRunner::class, ['run', 'setMaxParallelProcess'], [], '', false);
168 | $featureRunner->expects($this->never())->method('setMaxParallelProcess');
169 | $featureRunner->expects($this->never())->method('run');
170 |
171 | $featureExtractor = $this->getMock(FeatureExtractor::class, ['extract'], [], '', false);
172 | $featureExtractor->expects($this->never())->method('extract');
173 |
174 | $scenarioProcessFactory = $this->getMock(ScenarioProcessFactory::class, ['init'], [], '', false);
175 |
176 | $outputPrinter = $this->getMock(OutputPrinter::class, ['init'], [], '', false);
177 | $outputPrinter->expects($this->never())->method('init');
178 |
179 | $command = $this->getMock(Command::class, ['getDefinition', 'addOption'], [], '', false);
180 | $command->expects($this->once())->method('getDefinition')
181 | ->willReturn($inputDefinition);
182 |
183 | $controller = $this->getMock(ParallelScenarioController::class, null, [
184 | $featureRunner,
185 | $featureExtractor,
186 | $scenarioProcessFactory,
187 | $outputPrinter,
188 | ]);
189 |
190 | /** @var ParallelScenarioController $controller */
191 | /** @var Command $command */
192 | /** @var InputInterface $input */
193 | /** @var OutputInterface $output */
194 | $controller->configure($command);
195 | $result = $controller->execute($input, $output);
196 |
197 | $this->assertNull($result);
198 | }
199 | }
200 |
--------------------------------------------------------------------------------
/src/ParallelScenarioExtension/ServiceContainer/ParallelScenarioExtension.php:
--------------------------------------------------------------------------------
1 |
29 | */
30 | class ParallelScenarioExtension implements ExtensionInterface
31 | {
32 | const FEATURE_EXTRACTOR = 'parallel_scenario.feature.extractor';
33 | const FEATURE_RUNNER = 'parallel_scenario.feature.runner';
34 |
35 | const SCENARIO_INFO_EXTRACTOR = 'parallel_scenario.scenario.info.extractor';
36 |
37 | const PROCESS_RUNNER = 'parallel_scenario.process.runner';
38 | const PROCESS_FACTORY = 'parallel_scenario.process.factory';
39 | const PROCESS_PROFILE_BALANCE = 'parallel_scenario.process.profile_balance';
40 | const OUTPUT_PRINTER = 'parallel_scenario.output.printer';
41 | const STOP_ON_FAILURE = 'parallel_scenario.stop_on_failure';
42 |
43 | const CONFIG_OPTIONS = 'options';
44 | const CONFIG_SKIP = 'skip';
45 | const CONFIG_PROFILES = 'profiles';
46 |
47 | /**
48 | * {@inheritdoc}
49 | */
50 | public function process(ContainerBuilder $container)
51 | {
52 | }
53 |
54 | /**
55 | * {@inheritdoc}
56 | */
57 | public function getConfigKey()
58 | {
59 | return 'parallel_scenario';
60 | }
61 |
62 | /**
63 | * {@inheritdoc}
64 | */
65 | public function initialize(ExtensionManager $extensionManager)
66 | {
67 | }
68 |
69 | /**
70 | * {@inheritdoc}
71 | */
72 | public function configure(ArrayNodeDefinition $builder)
73 | {
74 | $builder
75 | ->children()
76 | ->arrayNode(self::CONFIG_OPTIONS)
77 | ->addDefaultsIfNotSet()
78 | ->children()
79 | ->arrayNode(self::CONFIG_SKIP)
80 | ->prototype('scalar')
81 | ->end()
82 | ->defaultValue([])
83 | ->end();
84 |
85 | $builder
86 | ->children()
87 | ->arrayNode(self::CONFIG_PROFILES)
88 | ->prototype('scalar')
89 | ->end();
90 | }
91 |
92 | /**
93 | * {@inheritdoc}
94 | */
95 | public function load(ContainerBuilder $containerBuilder, array $config)
96 | {
97 | $this->loadScenarioInfoExtractor($containerBuilder);
98 | $this->loadFeatureExtractor($containerBuilder);
99 | $this->loadFeatureRunner($containerBuilder);
100 | $this->loadProcessProfileBalance($containerBuilder, $config);
101 |
102 | $this->loadProcessRunner($containerBuilder);
103 | $this->loadProcessFactory($containerBuilder, $config);
104 | $this->loadController($containerBuilder);
105 |
106 | $this->loadOutputPrinter($containerBuilder);
107 | }
108 |
109 | /**
110 | * @param ContainerBuilder $containerBuilder
111 | * @param array $config
112 | */
113 | protected function loadProcessFactory(ContainerBuilder $containerBuilder, array $config)
114 | {
115 | $skipOptions = $config[self::CONFIG_OPTIONS][self::CONFIG_SKIP];
116 |
117 | $definition = new Definition(ScenarioProcessFactory::class);
118 | $definition->addMethodCall('addSkipOptions', [
119 | $skipOptions,
120 | ]);
121 |
122 | $containerBuilder->setDefinition(self::PROCESS_FACTORY, $definition);
123 | }
124 |
125 | /**
126 | * @param ContainerBuilder $containerBuilder
127 | * @param array $config
128 | */
129 | protected function loadProcessProfileBalance(ContainerBuilder $containerBuilder, array $config)
130 | {
131 | $profiles = $config[self::CONFIG_PROFILES];
132 | $definition = new Definition(ScenarioProcessProfileBalance::class, [
133 | $profiles,
134 | ]);
135 | $definition->addTag(EventDispatcherExtension::SUBSCRIBER_TAG, [
136 | 'priority' => 800,
137 | ]);
138 |
139 | $containerBuilder->setDefinition(self::PROCESS_PROFILE_BALANCE, $definition);
140 | }
141 |
142 | /**
143 | * @param ContainerBuilder $containerBuilder
144 | */
145 | protected function loadController(ContainerBuilder $containerBuilder)
146 | {
147 | $definition = new Definition(ParallelScenarioController::class, [
148 | new Reference(self::FEATURE_RUNNER),
149 | new Reference(self::FEATURE_EXTRACTOR),
150 | new Reference(self::PROCESS_FACTORY),
151 | new Reference(self::OUTPUT_PRINTER),
152 | ]);
153 |
154 | $definition->addTag(CliExtension::CONTROLLER_TAG, [
155 | 'priority' => 1,
156 | ]);
157 |
158 | $containerBuilder->setDefinition(CliExtension::CONTROLLER_TAG.'.parallel-scenario', $definition);
159 | }
160 |
161 | /**
162 | * @param ContainerBuilder $containerBuilder
163 | */
164 | protected function loadOutputPrinter(ContainerBuilder $containerBuilder)
165 | {
166 | $definition = new Definition(OutputPrinter::class);
167 | $definition->addTag(EventDispatcherExtension::SUBSCRIBER_TAG, [
168 | 'priority' => -1,
169 | ]);
170 |
171 | $containerBuilder->setDefinition(self::OUTPUT_PRINTER, $definition);
172 | }
173 |
174 | /**
175 | * @param ContainerBuilder $containerBuilder
176 | */
177 | protected function loadProcessRunner(ContainerBuilder $containerBuilder)
178 | {
179 | $definition = new Definition(ParallelProcessRunner::class, [
180 | new Reference('event_dispatcher'),
181 | ]);
182 |
183 | $containerBuilder->setDefinition(self::PROCESS_RUNNER, $definition);
184 | }
185 |
186 | /**
187 | * @param ContainerBuilder $containerBuilder
188 | */
189 | protected function loadStopOnFailure(ContainerBuilder $containerBuilder)
190 | {
191 | $definition = new Definition(StopOnFailure::class, [
192 | new Reference(self::PROCESS_RUNNER),
193 | ]);
194 | $definition->addTag(EventDispatcherExtension::SUBSCRIBER_TAG);
195 |
196 | $containerBuilder->setDefinition(self::STOP_ON_FAILURE, $definition);
197 | }
198 |
199 | /**
200 | * @param ContainerBuilder $containerBuilder
201 | */
202 | protected function loadFeatureExtractor(ContainerBuilder $containerBuilder)
203 | {
204 | $definition = new Definition(FeatureExtractor::class, [
205 | new Reference(SuiteExtension::REGISTRY_ID),
206 | new Reference(SpecificationExtension::FINDER_ID),
207 | ]);
208 |
209 | $containerBuilder->setDefinition(self::FEATURE_EXTRACTOR, $definition);
210 | }
211 |
212 | /**
213 | * @param ContainerBuilder $containerBuilder
214 | */
215 | protected function loadFeatureRunner(ContainerBuilder $containerBuilder)
216 | {
217 | $definition = new Definition(FeatureRunner::class, [
218 | new Reference('event_dispatcher'),
219 | new Reference(self::SCENARIO_INFO_EXTRACTOR),
220 | new Reference(self::PROCESS_FACTORY),
221 | new Reference(self::PROCESS_RUNNER),
222 | ]);
223 |
224 | $containerBuilder->setDefinition(self::FEATURE_RUNNER, $definition);
225 | }
226 |
227 | /**
228 | * @param ContainerBuilder $containerBuilder
229 | */
230 | protected function loadScenarioInfoExtractor(ContainerBuilder $containerBuilder)
231 | {
232 | $definition = new Definition(ScenarioInfoExtractor::class);
233 |
234 | $containerBuilder->setDefinition(self::SCENARIO_INFO_EXTRACTOR, $definition);
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5 | "This file is @generated automatically"
6 | ],
7 | "hash": "5aaa97acee722941895766dd2f349af6",
8 | "content-hash": "c36897894196ad8c994afcecf50db51e",
9 | "packages": [
10 | {
11 | "name": "behat/behat",
12 | "version": "v3.0.15",
13 | "source": {
14 | "type": "git",
15 | "url": "https://github.com/Behat/Behat.git",
16 | "reference": "b35ae3d45332d80c532af69cc36f780a9397a996"
17 | },
18 | "dist": {
19 | "type": "zip",
20 | "url": "https://api.github.com/repos/Behat/Behat/zipball/b35ae3d45332d80c532af69cc36f780a9397a996",
21 | "reference": "b35ae3d45332d80c532af69cc36f780a9397a996",
22 | "shasum": ""
23 | },
24 | "require": {
25 | "behat/gherkin": "~4.3",
26 | "behat/transliterator": "~1.0",
27 | "ext-mbstring": "*",
28 | "php": ">=5.3.3",
29 | "symfony/class-loader": "~2.1",
30 | "symfony/config": "~2.3",
31 | "symfony/console": "~2.1",
32 | "symfony/dependency-injection": "~2.1",
33 | "symfony/event-dispatcher": "~2.1",
34 | "symfony/translation": "~2.3",
35 | "symfony/yaml": "~2.1"
36 | },
37 | "require-dev": {
38 | "phpspec/prophecy-phpunit": "~1.0",
39 | "phpunit/phpunit": "~4.0",
40 | "symfony/process": "~2.1"
41 | },
42 | "suggest": {
43 | "behat/mink-extension": "for integration with Mink testing framework",
44 | "behat/symfony2-extension": "for integration with Symfony2 web framework",
45 | "behat/yii-extension": "for integration with Yii web framework"
46 | },
47 | "bin": [
48 | "bin/behat"
49 | ],
50 | "type": "library",
51 | "extra": {
52 | "branch-alias": {
53 | "dev-master": "3.0.x-dev"
54 | }
55 | },
56 | "autoload": {
57 | "psr-0": {
58 | "Behat\\Behat": "src/",
59 | "Behat\\Testwork": "src/"
60 | }
61 | },
62 | "notification-url": "https://packagist.org/downloads/",
63 | "license": [
64 | "MIT"
65 | ],
66 | "authors": [
67 | {
68 | "name": "Konstantin Kudryashov",
69 | "email": "ever.zet@gmail.com",
70 | "homepage": "http://everzet.com"
71 | }
72 | ],
73 | "description": "Scenario-oriented BDD framework for PHP 5.3",
74 | "homepage": "http://behat.org/",
75 | "keywords": [
76 | "Agile",
77 | "BDD",
78 | "ScenarioBDD",
79 | "Scrum",
80 | "StoryBDD",
81 | "User story",
82 | "business",
83 | "development",
84 | "documentation",
85 | "examples",
86 | "symfony",
87 | "testing"
88 | ],
89 | "time": "2015-02-22 14:10:33"
90 | },
91 | {
92 | "name": "behat/gherkin",
93 | "version": "v4.4.0",
94 | "source": {
95 | "type": "git",
96 | "url": "https://github.com/Behat/Gherkin.git",
97 | "reference": "6b3f8cf3560dc4909c4cddd4f1af3e1f6e9d80af"
98 | },
99 | "dist": {
100 | "type": "zip",
101 | "url": "https://api.github.com/repos/Behat/Gherkin/zipball/6b3f8cf3560dc4909c4cddd4f1af3e1f6e9d80af",
102 | "reference": "6b3f8cf3560dc4909c4cddd4f1af3e1f6e9d80af",
103 | "shasum": ""
104 | },
105 | "require": {
106 | "php": ">=5.3.1"
107 | },
108 | "require-dev": {
109 | "phpunit/phpunit": "~4.0",
110 | "symfony/yaml": "~2.1"
111 | },
112 | "suggest": {
113 | "symfony/yaml": "If you want to parse features, represented in YAML files"
114 | },
115 | "type": "library",
116 | "extra": {
117 | "branch-alias": {
118 | "dev-master": "4.4-dev"
119 | }
120 | },
121 | "autoload": {
122 | "psr-0": {
123 | "Behat\\Gherkin": "src/"
124 | }
125 | },
126 | "notification-url": "https://packagist.org/downloads/",
127 | "license": [
128 | "MIT"
129 | ],
130 | "authors": [
131 | {
132 | "name": "Konstantin Kudryashov",
133 | "email": "ever.zet@gmail.com",
134 | "homepage": "http://everzet.com"
135 | }
136 | ],
137 | "description": "Gherkin DSL parser for PHP 5.3",
138 | "homepage": "http://behat.org/",
139 | "keywords": [
140 | "BDD",
141 | "Behat",
142 | "Cucumber",
143 | "DSL",
144 | "gherkin",
145 | "parser"
146 | ],
147 | "time": "2015-09-29 13:41:19"
148 | },
149 | {
150 | "name": "behat/transliterator",
151 | "version": "v1.1.0",
152 | "source": {
153 | "type": "git",
154 | "url": "https://github.com/Behat/Transliterator.git",
155 | "reference": "868e05be3a9f25ba6424c2dd4849567f50715003"
156 | },
157 | "dist": {
158 | "type": "zip",
159 | "url": "https://api.github.com/repos/Behat/Transliterator/zipball/868e05be3a9f25ba6424c2dd4849567f50715003",
160 | "reference": "868e05be3a9f25ba6424c2dd4849567f50715003",
161 | "shasum": ""
162 | },
163 | "require": {
164 | "php": ">=5.3.3"
165 | },
166 | "type": "library",
167 | "extra": {
168 | "branch-alias": {
169 | "dev-master": "1.1-dev"
170 | }
171 | },
172 | "autoload": {
173 | "psr-0": {
174 | "Behat\\Transliterator": "src/"
175 | }
176 | },
177 | "notification-url": "https://packagist.org/downloads/",
178 | "license": [
179 | "Artistic-1.0"
180 | ],
181 | "description": "String transliterator",
182 | "keywords": [
183 | "i18n",
184 | "slug",
185 | "transliterator"
186 | ],
187 | "time": "2015-09-28 16:26:35"
188 | },
189 | {
190 | "name": "symfony/class-loader",
191 | "version": "v2.8.0",
192 | "source": {
193 | "type": "git",
194 | "url": "https://github.com/symfony/class-loader.git",
195 | "reference": "51f83451bf0ddfc696e47e4642d6cd10fcfce160"
196 | },
197 | "dist": {
198 | "type": "zip",
199 | "url": "https://api.github.com/repos/symfony/class-loader/zipball/51f83451bf0ddfc696e47e4642d6cd10fcfce160",
200 | "reference": "51f83451bf0ddfc696e47e4642d6cd10fcfce160",
201 | "shasum": ""
202 | },
203 | "require": {
204 | "php": ">=5.3.9"
205 | },
206 | "require-dev": {
207 | "symfony/finder": "~2.0,>=2.0.5|~3.0.0"
208 | },
209 | "type": "library",
210 | "extra": {
211 | "branch-alias": {
212 | "dev-master": "2.8-dev"
213 | }
214 | },
215 | "autoload": {
216 | "psr-4": {
217 | "Symfony\\Component\\ClassLoader\\": ""
218 | },
219 | "exclude-from-classmap": [
220 | "/Tests/"
221 | ]
222 | },
223 | "notification-url": "https://packagist.org/downloads/",
224 | "license": [
225 | "MIT"
226 | ],
227 | "authors": [
228 | {
229 | "name": "Fabien Potencier",
230 | "email": "fabien@symfony.com"
231 | },
232 | {
233 | "name": "Symfony Community",
234 | "homepage": "https://symfony.com/contributors"
235 | }
236 | ],
237 | "description": "Symfony ClassLoader Component",
238 | "homepage": "https://symfony.com",
239 | "time": "2015-11-26 07:00:59"
240 | },
241 | {
242 | "name": "symfony/config",
243 | "version": "v2.8.0",
244 | "source": {
245 | "type": "git",
246 | "url": "https://github.com/symfony/config.git",
247 | "reference": "f21c97aec1b5302d2dc0d17047ea8f4e4ff93aae"
248 | },
249 | "dist": {
250 | "type": "zip",
251 | "url": "https://api.github.com/repos/symfony/config/zipball/f21c97aec1b5302d2dc0d17047ea8f4e4ff93aae",
252 | "reference": "f21c97aec1b5302d2dc0d17047ea8f4e4ff93aae",
253 | "shasum": ""
254 | },
255 | "require": {
256 | "php": ">=5.3.9",
257 | "symfony/filesystem": "~2.3|~3.0.0"
258 | },
259 | "type": "library",
260 | "extra": {
261 | "branch-alias": {
262 | "dev-master": "2.8-dev"
263 | }
264 | },
265 | "autoload": {
266 | "psr-4": {
267 | "Symfony\\Component\\Config\\": ""
268 | },
269 | "exclude-from-classmap": [
270 | "/Tests/"
271 | ]
272 | },
273 | "notification-url": "https://packagist.org/downloads/",
274 | "license": [
275 | "MIT"
276 | ],
277 | "authors": [
278 | {
279 | "name": "Fabien Potencier",
280 | "email": "fabien@symfony.com"
281 | },
282 | {
283 | "name": "Symfony Community",
284 | "homepage": "https://symfony.com/contributors"
285 | }
286 | ],
287 | "description": "Symfony Config Component",
288 | "homepage": "https://symfony.com",
289 | "time": "2015-11-23 20:38:01"
290 | },
291 | {
292 | "name": "symfony/console",
293 | "version": "v2.8.0",
294 | "source": {
295 | "type": "git",
296 | "url": "https://github.com/symfony/console.git",
297 | "reference": "d232bfc100dfd32b18ccbcab4bcc8f28697b7e41"
298 | },
299 | "dist": {
300 | "type": "zip",
301 | "url": "https://api.github.com/repos/symfony/console/zipball/d232bfc100dfd32b18ccbcab4bcc8f28697b7e41",
302 | "reference": "d232bfc100dfd32b18ccbcab4bcc8f28697b7e41",
303 | "shasum": ""
304 | },
305 | "require": {
306 | "php": ">=5.3.9",
307 | "symfony/polyfill-mbstring": "~1.0"
308 | },
309 | "require-dev": {
310 | "psr/log": "~1.0",
311 | "symfony/event-dispatcher": "~2.1|~3.0.0",
312 | "symfony/process": "~2.1|~3.0.0"
313 | },
314 | "suggest": {
315 | "psr/log": "For using the console logger",
316 | "symfony/event-dispatcher": "",
317 | "symfony/process": ""
318 | },
319 | "type": "library",
320 | "extra": {
321 | "branch-alias": {
322 | "dev-master": "2.8-dev"
323 | }
324 | },
325 | "autoload": {
326 | "psr-4": {
327 | "Symfony\\Component\\Console\\": ""
328 | },
329 | "exclude-from-classmap": [
330 | "/Tests/"
331 | ]
332 | },
333 | "notification-url": "https://packagist.org/downloads/",
334 | "license": [
335 | "MIT"
336 | ],
337 | "authors": [
338 | {
339 | "name": "Fabien Potencier",
340 | "email": "fabien@symfony.com"
341 | },
342 | {
343 | "name": "Symfony Community",
344 | "homepage": "https://symfony.com/contributors"
345 | }
346 | ],
347 | "description": "Symfony Console Component",
348 | "homepage": "https://symfony.com",
349 | "time": "2015-11-30 12:35:10"
350 | },
351 | {
352 | "name": "symfony/dependency-injection",
353 | "version": "v2.8.0",
354 | "source": {
355 | "type": "git",
356 | "url": "https://github.com/symfony/dependency-injection.git",
357 | "reference": "1ac8ce1a1cff7ff9467d44bc71b0f71dfa751ba4"
358 | },
359 | "dist": {
360 | "type": "zip",
361 | "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/1ac8ce1a1cff7ff9467d44bc71b0f71dfa751ba4",
362 | "reference": "1ac8ce1a1cff7ff9467d44bc71b0f71dfa751ba4",
363 | "shasum": ""
364 | },
365 | "require": {
366 | "php": ">=5.3.9"
367 | },
368 | "conflict": {
369 | "symfony/expression-language": "<2.6"
370 | },
371 | "require-dev": {
372 | "symfony/config": "~2.2|~3.0.0",
373 | "symfony/expression-language": "~2.6|~3.0.0",
374 | "symfony/yaml": "~2.1|~3.0.0"
375 | },
376 | "suggest": {
377 | "symfony/config": "",
378 | "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them",
379 | "symfony/yaml": ""
380 | },
381 | "type": "library",
382 | "extra": {
383 | "branch-alias": {
384 | "dev-master": "2.8-dev"
385 | }
386 | },
387 | "autoload": {
388 | "psr-4": {
389 | "Symfony\\Component\\DependencyInjection\\": ""
390 | },
391 | "exclude-from-classmap": [
392 | "/Tests/"
393 | ]
394 | },
395 | "notification-url": "https://packagist.org/downloads/",
396 | "license": [
397 | "MIT"
398 | ],
399 | "authors": [
400 | {
401 | "name": "Fabien Potencier",
402 | "email": "fabien@symfony.com"
403 | },
404 | {
405 | "name": "Symfony Community",
406 | "homepage": "https://symfony.com/contributors"
407 | }
408 | ],
409 | "description": "Symfony DependencyInjection Component",
410 | "homepage": "https://symfony.com",
411 | "time": "2015-11-30 06:56:28"
412 | },
413 | {
414 | "name": "symfony/event-dispatcher",
415 | "version": "v2.8.0",
416 | "source": {
417 | "type": "git",
418 | "url": "https://github.com/symfony/event-dispatcher.git",
419 | "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc"
420 | },
421 | "dist": {
422 | "type": "zip",
423 | "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a5eb815363c0388e83247e7e9853e5dbc14999cc",
424 | "reference": "a5eb815363c0388e83247e7e9853e5dbc14999cc",
425 | "shasum": ""
426 | },
427 | "require": {
428 | "php": ">=5.3.9"
429 | },
430 | "require-dev": {
431 | "psr/log": "~1.0",
432 | "symfony/config": "~2.0,>=2.0.5|~3.0.0",
433 | "symfony/dependency-injection": "~2.6|~3.0.0",
434 | "symfony/expression-language": "~2.6|~3.0.0",
435 | "symfony/stopwatch": "~2.3|~3.0.0"
436 | },
437 | "suggest": {
438 | "symfony/dependency-injection": "",
439 | "symfony/http-kernel": ""
440 | },
441 | "type": "library",
442 | "extra": {
443 | "branch-alias": {
444 | "dev-master": "2.8-dev"
445 | }
446 | },
447 | "autoload": {
448 | "psr-4": {
449 | "Symfony\\Component\\EventDispatcher\\": ""
450 | },
451 | "exclude-from-classmap": [
452 | "/Tests/"
453 | ]
454 | },
455 | "notification-url": "https://packagist.org/downloads/",
456 | "license": [
457 | "MIT"
458 | ],
459 | "authors": [
460 | {
461 | "name": "Fabien Potencier",
462 | "email": "fabien@symfony.com"
463 | },
464 | {
465 | "name": "Symfony Community",
466 | "homepage": "https://symfony.com/contributors"
467 | }
468 | ],
469 | "description": "Symfony EventDispatcher Component",
470 | "homepage": "https://symfony.com",
471 | "time": "2015-10-30 20:15:42"
472 | },
473 | {
474 | "name": "symfony/filesystem",
475 | "version": "v3.0.0",
476 | "source": {
477 | "type": "git",
478 | "url": "https://github.com/symfony/filesystem.git",
479 | "reference": "692d98d813e4ef314b9c22775c86ddbeb0f44884"
480 | },
481 | "dist": {
482 | "type": "zip",
483 | "url": "https://api.github.com/repos/symfony/filesystem/zipball/692d98d813e4ef314b9c22775c86ddbeb0f44884",
484 | "reference": "692d98d813e4ef314b9c22775c86ddbeb0f44884",
485 | "shasum": ""
486 | },
487 | "require": {
488 | "php": ">=5.5.9"
489 | },
490 | "type": "library",
491 | "extra": {
492 | "branch-alias": {
493 | "dev-master": "3.0-dev"
494 | }
495 | },
496 | "autoload": {
497 | "psr-4": {
498 | "Symfony\\Component\\Filesystem\\": ""
499 | },
500 | "exclude-from-classmap": [
501 | "/Tests/"
502 | ]
503 | },
504 | "notification-url": "https://packagist.org/downloads/",
505 | "license": [
506 | "MIT"
507 | ],
508 | "authors": [
509 | {
510 | "name": "Fabien Potencier",
511 | "email": "fabien@symfony.com"
512 | },
513 | {
514 | "name": "Symfony Community",
515 | "homepage": "https://symfony.com/contributors"
516 | }
517 | ],
518 | "description": "Symfony Filesystem Component",
519 | "homepage": "https://symfony.com",
520 | "time": "2015-11-23 10:41:47"
521 | },
522 | {
523 | "name": "symfony/polyfill-mbstring",
524 | "version": "v1.0.0",
525 | "source": {
526 | "type": "git",
527 | "url": "https://github.com/symfony/polyfill-mbstring.git",
528 | "reference": "0b6a8940385311a24e060ec1fe35680e17c74497"
529 | },
530 | "dist": {
531 | "type": "zip",
532 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0b6a8940385311a24e060ec1fe35680e17c74497",
533 | "reference": "0b6a8940385311a24e060ec1fe35680e17c74497",
534 | "shasum": ""
535 | },
536 | "require": {
537 | "php": ">=5.3.3"
538 | },
539 | "type": "library",
540 | "extra": {
541 | "branch-alias": {
542 | "dev-master": "1.0-dev"
543 | }
544 | },
545 | "autoload": {
546 | "psr-4": {
547 | "Symfony\\Polyfill\\Mbstring\\": ""
548 | },
549 | "files": [
550 | "bootstrap.php"
551 | ]
552 | },
553 | "notification-url": "https://packagist.org/downloads/",
554 | "license": [
555 | "MIT"
556 | ],
557 | "authors": [
558 | {
559 | "name": "Nicolas Grekas",
560 | "email": "p@tchwork.com"
561 | },
562 | {
563 | "name": "Symfony Community",
564 | "homepage": "https://symfony.com/contributors"
565 | }
566 | ],
567 | "description": "Symfony polyfill for the Mbstring extension",
568 | "homepage": "https://symfony.com",
569 | "keywords": [
570 | "compatibility",
571 | "mbstring",
572 | "polyfill",
573 | "portable",
574 | "shim"
575 | ],
576 | "time": "2015-11-04 20:28:58"
577 | },
578 | {
579 | "name": "symfony/process",
580 | "version": "v3.0.0",
581 | "source": {
582 | "type": "git",
583 | "url": "https://github.com/symfony/process.git",
584 | "reference": "01383ed02a1020759bc8ee5d975fcec04ba16fbf"
585 | },
586 | "dist": {
587 | "type": "zip",
588 | "url": "https://api.github.com/repos/symfony/process/zipball/01383ed02a1020759bc8ee5d975fcec04ba16fbf",
589 | "reference": "01383ed02a1020759bc8ee5d975fcec04ba16fbf",
590 | "shasum": ""
591 | },
592 | "require": {
593 | "php": ">=5.5.9"
594 | },
595 | "type": "library",
596 | "extra": {
597 | "branch-alias": {
598 | "dev-master": "3.0-dev"
599 | }
600 | },
601 | "autoload": {
602 | "psr-4": {
603 | "Symfony\\Component\\Process\\": ""
604 | },
605 | "exclude-from-classmap": [
606 | "/Tests/"
607 | ]
608 | },
609 | "notification-url": "https://packagist.org/downloads/",
610 | "license": [
611 | "MIT"
612 | ],
613 | "authors": [
614 | {
615 | "name": "Fabien Potencier",
616 | "email": "fabien@symfony.com"
617 | },
618 | {
619 | "name": "Symfony Community",
620 | "homepage": "https://symfony.com/contributors"
621 | }
622 | ],
623 | "description": "Symfony Process Component",
624 | "homepage": "https://symfony.com",
625 | "time": "2015-11-30 12:36:17"
626 | },
627 | {
628 | "name": "symfony/translation",
629 | "version": "v2.8.0",
630 | "source": {
631 | "type": "git",
632 | "url": "https://github.com/symfony/translation.git",
633 | "reference": "6772657767649fc3b31df12705194fb4af11ef98"
634 | },
635 | "dist": {
636 | "type": "zip",
637 | "url": "https://api.github.com/repos/symfony/translation/zipball/6772657767649fc3b31df12705194fb4af11ef98",
638 | "reference": "6772657767649fc3b31df12705194fb4af11ef98",
639 | "shasum": ""
640 | },
641 | "require": {
642 | "php": ">=5.3.9",
643 | "symfony/polyfill-mbstring": "~1.0"
644 | },
645 | "conflict": {
646 | "symfony/config": "<2.7"
647 | },
648 | "require-dev": {
649 | "psr/log": "~1.0",
650 | "symfony/config": "~2.8",
651 | "symfony/intl": "~2.4|~3.0.0",
652 | "symfony/yaml": "~2.2|~3.0.0"
653 | },
654 | "suggest": {
655 | "psr/log": "To use logging capability in translator",
656 | "symfony/config": "",
657 | "symfony/yaml": ""
658 | },
659 | "type": "library",
660 | "extra": {
661 | "branch-alias": {
662 | "dev-master": "2.8-dev"
663 | }
664 | },
665 | "autoload": {
666 | "psr-4": {
667 | "Symfony\\Component\\Translation\\": ""
668 | },
669 | "exclude-from-classmap": [
670 | "/Tests/"
671 | ]
672 | },
673 | "notification-url": "https://packagist.org/downloads/",
674 | "license": [
675 | "MIT"
676 | ],
677 | "authors": [
678 | {
679 | "name": "Fabien Potencier",
680 | "email": "fabien@symfony.com"
681 | },
682 | {
683 | "name": "Symfony Community",
684 | "homepage": "https://symfony.com/contributors"
685 | }
686 | ],
687 | "description": "Symfony Translation Component",
688 | "homepage": "https://symfony.com",
689 | "time": "2015-11-18 13:45:00"
690 | },
691 | {
692 | "name": "symfony/yaml",
693 | "version": "v2.8.0",
694 | "source": {
695 | "type": "git",
696 | "url": "https://github.com/symfony/yaml.git",
697 | "reference": "f79824187de95064a2f5038904c4d7f0227fedb5"
698 | },
699 | "dist": {
700 | "type": "zip",
701 | "url": "https://api.github.com/repos/symfony/yaml/zipball/f79824187de95064a2f5038904c4d7f0227fedb5",
702 | "reference": "f79824187de95064a2f5038904c4d7f0227fedb5",
703 | "shasum": ""
704 | },
705 | "require": {
706 | "php": ">=5.3.9"
707 | },
708 | "type": "library",
709 | "extra": {
710 | "branch-alias": {
711 | "dev-master": "2.8-dev"
712 | }
713 | },
714 | "autoload": {
715 | "psr-4": {
716 | "Symfony\\Component\\Yaml\\": ""
717 | },
718 | "exclude-from-classmap": [
719 | "/Tests/"
720 | ]
721 | },
722 | "notification-url": "https://packagist.org/downloads/",
723 | "license": [
724 | "MIT"
725 | ],
726 | "authors": [
727 | {
728 | "name": "Fabien Potencier",
729 | "email": "fabien@symfony.com"
730 | },
731 | {
732 | "name": "Symfony Community",
733 | "homepage": "https://symfony.com/contributors"
734 | }
735 | ],
736 | "description": "Symfony Yaml Component",
737 | "homepage": "https://symfony.com",
738 | "time": "2015-11-30 12:35:10"
739 | },
740 | {
741 | "name": "tonicforhealth/parallel-process-runner",
742 | "version": "v1.0.3",
743 | "source": {
744 | "type": "git",
745 | "url": "https://github.com/tonicforhealth/parallel-process-runner.git",
746 | "reference": "9adce2e7f2f48b102a6983ddc7cb1bc46c96db85"
747 | },
748 | "dist": {
749 | "type": "zip",
750 | "url": "https://api.github.com/repos/tonicforhealth/parallel-process-runner/zipball/9adce2e7f2f48b102a6983ddc7cb1bc46c96db85",
751 | "reference": "9adce2e7f2f48b102a6983ddc7cb1bc46c96db85",
752 | "shasum": ""
753 | },
754 | "require": {
755 | "symfony/event-dispatcher": ">2.7 <= 3",
756 | "symfony/process": ">2.7 <= 3"
757 | },
758 | "require-dev": {
759 | "phpunit/phpunit": "4.1.*"
760 | },
761 | "type": "library",
762 | "autoload": {
763 | "psr-4": {
764 | "Tonic\\": "src"
765 | }
766 | },
767 | "notification-url": "https://packagist.org/downloads/",
768 | "authors": [
769 | {
770 | "name": "oleksii.storozhylov",
771 | "email": "oleksii.storozhylov@tonicforhealth.com"
772 | }
773 | ],
774 | "time": "2015-12-11 15:47:41"
775 | }
776 | ],
777 | "packages-dev": [
778 | {
779 | "name": "phpunit/php-code-coverage",
780 | "version": "2.2.4",
781 | "source": {
782 | "type": "git",
783 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
784 | "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
785 | },
786 | "dist": {
787 | "type": "zip",
788 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
789 | "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
790 | "shasum": ""
791 | },
792 | "require": {
793 | "php": ">=5.3.3",
794 | "phpunit/php-file-iterator": "~1.3",
795 | "phpunit/php-text-template": "~1.2",
796 | "phpunit/php-token-stream": "~1.3",
797 | "sebastian/environment": "^1.3.2",
798 | "sebastian/version": "~1.0"
799 | },
800 | "require-dev": {
801 | "ext-xdebug": ">=2.1.4",
802 | "phpunit/phpunit": "~4"
803 | },
804 | "suggest": {
805 | "ext-dom": "*",
806 | "ext-xdebug": ">=2.2.1",
807 | "ext-xmlwriter": "*"
808 | },
809 | "type": "library",
810 | "extra": {
811 | "branch-alias": {
812 | "dev-master": "2.2.x-dev"
813 | }
814 | },
815 | "autoload": {
816 | "classmap": [
817 | "src/"
818 | ]
819 | },
820 | "notification-url": "https://packagist.org/downloads/",
821 | "license": [
822 | "BSD-3-Clause"
823 | ],
824 | "authors": [
825 | {
826 | "name": "Sebastian Bergmann",
827 | "email": "sb@sebastian-bergmann.de",
828 | "role": "lead"
829 | }
830 | ],
831 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
832 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
833 | "keywords": [
834 | "coverage",
835 | "testing",
836 | "xunit"
837 | ],
838 | "time": "2015-10-06 15:47:00"
839 | },
840 | {
841 | "name": "phpunit/php-file-iterator",
842 | "version": "1.3.4",
843 | "source": {
844 | "type": "git",
845 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
846 | "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb"
847 | },
848 | "dist": {
849 | "type": "zip",
850 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/acd690379117b042d1c8af1fafd61bde001bf6bb",
851 | "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb",
852 | "shasum": ""
853 | },
854 | "require": {
855 | "php": ">=5.3.3"
856 | },
857 | "type": "library",
858 | "autoload": {
859 | "classmap": [
860 | "File/"
861 | ]
862 | },
863 | "notification-url": "https://packagist.org/downloads/",
864 | "include-path": [
865 | ""
866 | ],
867 | "license": [
868 | "BSD-3-Clause"
869 | ],
870 | "authors": [
871 | {
872 | "name": "Sebastian Bergmann",
873 | "email": "sb@sebastian-bergmann.de",
874 | "role": "lead"
875 | }
876 | ],
877 | "description": "FilterIterator implementation that filters files based on a list of suffixes.",
878 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
879 | "keywords": [
880 | "filesystem",
881 | "iterator"
882 | ],
883 | "time": "2013-10-10 15:34:57"
884 | },
885 | {
886 | "name": "phpunit/php-text-template",
887 | "version": "1.2.1",
888 | "source": {
889 | "type": "git",
890 | "url": "https://github.com/sebastianbergmann/php-text-template.git",
891 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
892 | },
893 | "dist": {
894 | "type": "zip",
895 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
896 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
897 | "shasum": ""
898 | },
899 | "require": {
900 | "php": ">=5.3.3"
901 | },
902 | "type": "library",
903 | "autoload": {
904 | "classmap": [
905 | "src/"
906 | ]
907 | },
908 | "notification-url": "https://packagist.org/downloads/",
909 | "license": [
910 | "BSD-3-Clause"
911 | ],
912 | "authors": [
913 | {
914 | "name": "Sebastian Bergmann",
915 | "email": "sebastian@phpunit.de",
916 | "role": "lead"
917 | }
918 | ],
919 | "description": "Simple template engine.",
920 | "homepage": "https://github.com/sebastianbergmann/php-text-template/",
921 | "keywords": [
922 | "template"
923 | ],
924 | "time": "2015-06-21 13:50:34"
925 | },
926 | {
927 | "name": "phpunit/php-timer",
928 | "version": "1.0.7",
929 | "source": {
930 | "type": "git",
931 | "url": "https://github.com/sebastianbergmann/php-timer.git",
932 | "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b"
933 | },
934 | "dist": {
935 | "type": "zip",
936 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b",
937 | "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b",
938 | "shasum": ""
939 | },
940 | "require": {
941 | "php": ">=5.3.3"
942 | },
943 | "type": "library",
944 | "autoload": {
945 | "classmap": [
946 | "src/"
947 | ]
948 | },
949 | "notification-url": "https://packagist.org/downloads/",
950 | "license": [
951 | "BSD-3-Clause"
952 | ],
953 | "authors": [
954 | {
955 | "name": "Sebastian Bergmann",
956 | "email": "sb@sebastian-bergmann.de",
957 | "role": "lead"
958 | }
959 | ],
960 | "description": "Utility class for timing",
961 | "homepage": "https://github.com/sebastianbergmann/php-timer/",
962 | "keywords": [
963 | "timer"
964 | ],
965 | "time": "2015-06-21 08:01:12"
966 | },
967 | {
968 | "name": "phpunit/php-token-stream",
969 | "version": "1.4.8",
970 | "source": {
971 | "type": "git",
972 | "url": "https://github.com/sebastianbergmann/php-token-stream.git",
973 | "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da"
974 | },
975 | "dist": {
976 | "type": "zip",
977 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
978 | "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
979 | "shasum": ""
980 | },
981 | "require": {
982 | "ext-tokenizer": "*",
983 | "php": ">=5.3.3"
984 | },
985 | "require-dev": {
986 | "phpunit/phpunit": "~4.2"
987 | },
988 | "type": "library",
989 | "extra": {
990 | "branch-alias": {
991 | "dev-master": "1.4-dev"
992 | }
993 | },
994 | "autoload": {
995 | "classmap": [
996 | "src/"
997 | ]
998 | },
999 | "notification-url": "https://packagist.org/downloads/",
1000 | "license": [
1001 | "BSD-3-Clause"
1002 | ],
1003 | "authors": [
1004 | {
1005 | "name": "Sebastian Bergmann",
1006 | "email": "sebastian@phpunit.de"
1007 | }
1008 | ],
1009 | "description": "Wrapper around PHP's tokenizer extension.",
1010 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
1011 | "keywords": [
1012 | "tokenizer"
1013 | ],
1014 | "time": "2015-09-15 10:49:45"
1015 | },
1016 | {
1017 | "name": "phpunit/phpunit",
1018 | "version": "4.1.6",
1019 | "source": {
1020 | "type": "git",
1021 | "url": "https://github.com/sebastianbergmann/phpunit.git",
1022 | "reference": "241116219bb7e3b8111a36ffd8f37546888738d6"
1023 | },
1024 | "dist": {
1025 | "type": "zip",
1026 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/241116219bb7e3b8111a36ffd8f37546888738d6",
1027 | "reference": "241116219bb7e3b8111a36ffd8f37546888738d6",
1028 | "shasum": ""
1029 | },
1030 | "require": {
1031 | "ext-dom": "*",
1032 | "ext-json": "*",
1033 | "ext-pcre": "*",
1034 | "ext-reflection": "*",
1035 | "ext-spl": "*",
1036 | "php": ">=5.3.3",
1037 | "phpunit/php-code-coverage": "~2.0",
1038 | "phpunit/php-file-iterator": "~1.3.1",
1039 | "phpunit/php-text-template": "~1.2",
1040 | "phpunit/php-timer": "~1.0.2",
1041 | "phpunit/phpunit-mock-objects": "2.1.5",
1042 | "sebastian/comparator": "~1.0",
1043 | "sebastian/diff": "~1.1",
1044 | "sebastian/environment": "~1.0",
1045 | "sebastian/exporter": "~1.0",
1046 | "sebastian/version": "~1.0",
1047 | "symfony/yaml": "~2.0"
1048 | },
1049 | "suggest": {
1050 | "phpunit/php-invoker": "~1.1"
1051 | },
1052 | "bin": [
1053 | "phpunit"
1054 | ],
1055 | "type": "library",
1056 | "extra": {
1057 | "branch-alias": {
1058 | "dev-master": "4.1.x-dev"
1059 | }
1060 | },
1061 | "autoload": {
1062 | "classmap": [
1063 | "src/"
1064 | ]
1065 | },
1066 | "notification-url": "https://packagist.org/downloads/",
1067 | "include-path": [
1068 | "",
1069 | "../../symfony/yaml/"
1070 | ],
1071 | "license": [
1072 | "BSD-3-Clause"
1073 | ],
1074 | "authors": [
1075 | {
1076 | "name": "Sebastian Bergmann",
1077 | "email": "sebastian@phpunit.de",
1078 | "role": "lead"
1079 | }
1080 | ],
1081 | "description": "The PHP Unit Testing framework.",
1082 | "homepage": "http://www.phpunit.de/",
1083 | "keywords": [
1084 | "phpunit",
1085 | "testing",
1086 | "xunit"
1087 | ],
1088 | "time": "2014-08-17 08:07:02"
1089 | },
1090 | {
1091 | "name": "phpunit/phpunit-mock-objects",
1092 | "version": "2.1.5",
1093 | "source": {
1094 | "type": "git",
1095 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
1096 | "reference": "7878b9c41edb3afab92b85edf5f0981014a2713a"
1097 | },
1098 | "dist": {
1099 | "type": "zip",
1100 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/7878b9c41edb3afab92b85edf5f0981014a2713a",
1101 | "reference": "7878b9c41edb3afab92b85edf5f0981014a2713a",
1102 | "shasum": ""
1103 | },
1104 | "require": {
1105 | "php": ">=5.3.3",
1106 | "phpunit/php-text-template": "~1.2"
1107 | },
1108 | "require-dev": {
1109 | "phpunit/phpunit": "~4.1"
1110 | },
1111 | "suggest": {
1112 | "ext-soap": "*"
1113 | },
1114 | "type": "library",
1115 | "extra": {
1116 | "branch-alias": {
1117 | "dev-master": "2.1.x-dev"
1118 | }
1119 | },
1120 | "autoload": {
1121 | "classmap": [
1122 | "src/"
1123 | ]
1124 | },
1125 | "notification-url": "https://packagist.org/downloads/",
1126 | "include-path": [
1127 | ""
1128 | ],
1129 | "license": [
1130 | "BSD-3-Clause"
1131 | ],
1132 | "authors": [
1133 | {
1134 | "name": "Sebastian Bergmann",
1135 | "email": "sb@sebastian-bergmann.de",
1136 | "role": "lead"
1137 | }
1138 | ],
1139 | "description": "Mock Object library for PHPUnit",
1140 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
1141 | "keywords": [
1142 | "mock",
1143 | "xunit"
1144 | ],
1145 | "time": "2014-06-12 07:22:15"
1146 | },
1147 | {
1148 | "name": "sebastian/comparator",
1149 | "version": "1.2.0",
1150 | "source": {
1151 | "type": "git",
1152 | "url": "https://github.com/sebastianbergmann/comparator.git",
1153 | "reference": "937efb279bd37a375bcadf584dec0726f84dbf22"
1154 | },
1155 | "dist": {
1156 | "type": "zip",
1157 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22",
1158 | "reference": "937efb279bd37a375bcadf584dec0726f84dbf22",
1159 | "shasum": ""
1160 | },
1161 | "require": {
1162 | "php": ">=5.3.3",
1163 | "sebastian/diff": "~1.2",
1164 | "sebastian/exporter": "~1.2"
1165 | },
1166 | "require-dev": {
1167 | "phpunit/phpunit": "~4.4"
1168 | },
1169 | "type": "library",
1170 | "extra": {
1171 | "branch-alias": {
1172 | "dev-master": "1.2.x-dev"
1173 | }
1174 | },
1175 | "autoload": {
1176 | "classmap": [
1177 | "src/"
1178 | ]
1179 | },
1180 | "notification-url": "https://packagist.org/downloads/",
1181 | "license": [
1182 | "BSD-3-Clause"
1183 | ],
1184 | "authors": [
1185 | {
1186 | "name": "Jeff Welch",
1187 | "email": "whatthejeff@gmail.com"
1188 | },
1189 | {
1190 | "name": "Volker Dusch",
1191 | "email": "github@wallbash.com"
1192 | },
1193 | {
1194 | "name": "Bernhard Schussek",
1195 | "email": "bschussek@2bepublished.at"
1196 | },
1197 | {
1198 | "name": "Sebastian Bergmann",
1199 | "email": "sebastian@phpunit.de"
1200 | }
1201 | ],
1202 | "description": "Provides the functionality to compare PHP values for equality",
1203 | "homepage": "http://www.github.com/sebastianbergmann/comparator",
1204 | "keywords": [
1205 | "comparator",
1206 | "compare",
1207 | "equality"
1208 | ],
1209 | "time": "2015-07-26 15:48:44"
1210 | },
1211 | {
1212 | "name": "sebastian/diff",
1213 | "version": "1.4.1",
1214 | "source": {
1215 | "type": "git",
1216 | "url": "https://github.com/sebastianbergmann/diff.git",
1217 | "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e"
1218 | },
1219 | "dist": {
1220 | "type": "zip",
1221 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e",
1222 | "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e",
1223 | "shasum": ""
1224 | },
1225 | "require": {
1226 | "php": ">=5.3.3"
1227 | },
1228 | "require-dev": {
1229 | "phpunit/phpunit": "~4.8"
1230 | },
1231 | "type": "library",
1232 | "extra": {
1233 | "branch-alias": {
1234 | "dev-master": "1.4-dev"
1235 | }
1236 | },
1237 | "autoload": {
1238 | "classmap": [
1239 | "src/"
1240 | ]
1241 | },
1242 | "notification-url": "https://packagist.org/downloads/",
1243 | "license": [
1244 | "BSD-3-Clause"
1245 | ],
1246 | "authors": [
1247 | {
1248 | "name": "Kore Nordmann",
1249 | "email": "mail@kore-nordmann.de"
1250 | },
1251 | {
1252 | "name": "Sebastian Bergmann",
1253 | "email": "sebastian@phpunit.de"
1254 | }
1255 | ],
1256 | "description": "Diff implementation",
1257 | "homepage": "https://github.com/sebastianbergmann/diff",
1258 | "keywords": [
1259 | "diff"
1260 | ],
1261 | "time": "2015-12-08 07:14:41"
1262 | },
1263 | {
1264 | "name": "sebastian/environment",
1265 | "version": "1.3.3",
1266 | "source": {
1267 | "type": "git",
1268 | "url": "https://github.com/sebastianbergmann/environment.git",
1269 | "reference": "6e7133793a8e5a5714a551a8324337374be209df"
1270 | },
1271 | "dist": {
1272 | "type": "zip",
1273 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6e7133793a8e5a5714a551a8324337374be209df",
1274 | "reference": "6e7133793a8e5a5714a551a8324337374be209df",
1275 | "shasum": ""
1276 | },
1277 | "require": {
1278 | "php": ">=5.3.3"
1279 | },
1280 | "require-dev": {
1281 | "phpunit/phpunit": "~4.4"
1282 | },
1283 | "type": "library",
1284 | "extra": {
1285 | "branch-alias": {
1286 | "dev-master": "1.3.x-dev"
1287 | }
1288 | },
1289 | "autoload": {
1290 | "classmap": [
1291 | "src/"
1292 | ]
1293 | },
1294 | "notification-url": "https://packagist.org/downloads/",
1295 | "license": [
1296 | "BSD-3-Clause"
1297 | ],
1298 | "authors": [
1299 | {
1300 | "name": "Sebastian Bergmann",
1301 | "email": "sebastian@phpunit.de"
1302 | }
1303 | ],
1304 | "description": "Provides functionality to handle HHVM/PHP environments",
1305 | "homepage": "http://www.github.com/sebastianbergmann/environment",
1306 | "keywords": [
1307 | "Xdebug",
1308 | "environment",
1309 | "hhvm"
1310 | ],
1311 | "time": "2015-12-02 08:37:27"
1312 | },
1313 | {
1314 | "name": "sebastian/exporter",
1315 | "version": "1.2.1",
1316 | "source": {
1317 | "type": "git",
1318 | "url": "https://github.com/sebastianbergmann/exporter.git",
1319 | "reference": "7ae5513327cb536431847bcc0c10edba2701064e"
1320 | },
1321 | "dist": {
1322 | "type": "zip",
1323 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e",
1324 | "reference": "7ae5513327cb536431847bcc0c10edba2701064e",
1325 | "shasum": ""
1326 | },
1327 | "require": {
1328 | "php": ">=5.3.3",
1329 | "sebastian/recursion-context": "~1.0"
1330 | },
1331 | "require-dev": {
1332 | "phpunit/phpunit": "~4.4"
1333 | },
1334 | "type": "library",
1335 | "extra": {
1336 | "branch-alias": {
1337 | "dev-master": "1.2.x-dev"
1338 | }
1339 | },
1340 | "autoload": {
1341 | "classmap": [
1342 | "src/"
1343 | ]
1344 | },
1345 | "notification-url": "https://packagist.org/downloads/",
1346 | "license": [
1347 | "BSD-3-Clause"
1348 | ],
1349 | "authors": [
1350 | {
1351 | "name": "Jeff Welch",
1352 | "email": "whatthejeff@gmail.com"
1353 | },
1354 | {
1355 | "name": "Volker Dusch",
1356 | "email": "github@wallbash.com"
1357 | },
1358 | {
1359 | "name": "Bernhard Schussek",
1360 | "email": "bschussek@2bepublished.at"
1361 | },
1362 | {
1363 | "name": "Sebastian Bergmann",
1364 | "email": "sebastian@phpunit.de"
1365 | },
1366 | {
1367 | "name": "Adam Harvey",
1368 | "email": "aharvey@php.net"
1369 | }
1370 | ],
1371 | "description": "Provides the functionality to export PHP variables for visualization",
1372 | "homepage": "http://www.github.com/sebastianbergmann/exporter",
1373 | "keywords": [
1374 | "export",
1375 | "exporter"
1376 | ],
1377 | "time": "2015-06-21 07:55:53"
1378 | },
1379 | {
1380 | "name": "sebastian/recursion-context",
1381 | "version": "1.0.2",
1382 | "source": {
1383 | "type": "git",
1384 | "url": "https://github.com/sebastianbergmann/recursion-context.git",
1385 | "reference": "913401df809e99e4f47b27cdd781f4a258d58791"
1386 | },
1387 | "dist": {
1388 | "type": "zip",
1389 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791",
1390 | "reference": "913401df809e99e4f47b27cdd781f4a258d58791",
1391 | "shasum": ""
1392 | },
1393 | "require": {
1394 | "php": ">=5.3.3"
1395 | },
1396 | "require-dev": {
1397 | "phpunit/phpunit": "~4.4"
1398 | },
1399 | "type": "library",
1400 | "extra": {
1401 | "branch-alias": {
1402 | "dev-master": "1.0.x-dev"
1403 | }
1404 | },
1405 | "autoload": {
1406 | "classmap": [
1407 | "src/"
1408 | ]
1409 | },
1410 | "notification-url": "https://packagist.org/downloads/",
1411 | "license": [
1412 | "BSD-3-Clause"
1413 | ],
1414 | "authors": [
1415 | {
1416 | "name": "Jeff Welch",
1417 | "email": "whatthejeff@gmail.com"
1418 | },
1419 | {
1420 | "name": "Sebastian Bergmann",
1421 | "email": "sebastian@phpunit.de"
1422 | },
1423 | {
1424 | "name": "Adam Harvey",
1425 | "email": "aharvey@php.net"
1426 | }
1427 | ],
1428 | "description": "Provides functionality to recursively process PHP variables",
1429 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
1430 | "time": "2015-11-11 19:50:13"
1431 | },
1432 | {
1433 | "name": "sebastian/version",
1434 | "version": "1.0.6",
1435 | "source": {
1436 | "type": "git",
1437 | "url": "https://github.com/sebastianbergmann/version.git",
1438 | "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
1439 | },
1440 | "dist": {
1441 | "type": "zip",
1442 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
1443 | "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
1444 | "shasum": ""
1445 | },
1446 | "type": "library",
1447 | "autoload": {
1448 | "classmap": [
1449 | "src/"
1450 | ]
1451 | },
1452 | "notification-url": "https://packagist.org/downloads/",
1453 | "license": [
1454 | "BSD-3-Clause"
1455 | ],
1456 | "authors": [
1457 | {
1458 | "name": "Sebastian Bergmann",
1459 | "email": "sebastian@phpunit.de",
1460 | "role": "lead"
1461 | }
1462 | ],
1463 | "description": "Library that helps with managing the version number of Git-hosted PHP projects",
1464 | "homepage": "https://github.com/sebastianbergmann/version",
1465 | "time": "2015-06-21 13:59:46"
1466 | },
1467 | {
1468 | "name": "symfony/finder",
1469 | "version": "v3.0.0",
1470 | "source": {
1471 | "type": "git",
1472 | "url": "https://github.com/symfony/finder.git",
1473 | "reference": "3577eb98dba90721d1a0a3edfc6956ab8b1aecee"
1474 | },
1475 | "dist": {
1476 | "type": "zip",
1477 | "url": "https://api.github.com/repos/symfony/finder/zipball/3577eb98dba90721d1a0a3edfc6956ab8b1aecee",
1478 | "reference": "3577eb98dba90721d1a0a3edfc6956ab8b1aecee",
1479 | "shasum": ""
1480 | },
1481 | "require": {
1482 | "php": ">=5.5.9"
1483 | },
1484 | "type": "library",
1485 | "extra": {
1486 | "branch-alias": {
1487 | "dev-master": "3.0-dev"
1488 | }
1489 | },
1490 | "autoload": {
1491 | "psr-4": {
1492 | "Symfony\\Component\\Finder\\": ""
1493 | },
1494 | "exclude-from-classmap": [
1495 | "/Tests/"
1496 | ]
1497 | },
1498 | "notification-url": "https://packagist.org/downloads/",
1499 | "license": [
1500 | "MIT"
1501 | ],
1502 | "authors": [
1503 | {
1504 | "name": "Fabien Potencier",
1505 | "email": "fabien@symfony.com"
1506 | },
1507 | {
1508 | "name": "Symfony Community",
1509 | "homepage": "https://symfony.com/contributors"
1510 | }
1511 | ],
1512 | "description": "Symfony Finder Component",
1513 | "homepage": "https://symfony.com",
1514 | "time": "2015-10-30 23:35:59"
1515 | }
1516 | ],
1517 | "aliases": [],
1518 | "minimum-stability": "stable",
1519 | "stability-flags": [],
1520 | "prefer-stable": false,
1521 | "prefer-lowest": false,
1522 | "platform": [],
1523 | "platform-dev": []
1524 | }
1525 |
--------------------------------------------------------------------------------