Completed
Pull Request — master (#93)
by Alessandro
02:47
created

Runner::createProcessStackFromFiles()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
crap 2
1
<?php
2
declare(strict_types=1);
3
4
namespace Paraunit\Runner;
5
6
use Paraunit\Filter\Filter;
7
use Paraunit\Lifecycle\EngineEvent;
8
use Paraunit\Lifecycle\ProcessEvent;
9
use Paraunit\Printer\DebugPrinter;
10
use Paraunit\Process\AbstractParaunitProcess;
11
use Paraunit\Process\ParaunitProcessInterface;
12
use Paraunit\Process\ProcessFactory;
13
use Symfony\Component\Console\Output\OutputInterface;
14
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
15
16
/**
17
 * Class Runner
18
 * @package Paraunit\Runner
19
 */
20
class Runner
21
{
22
    /** @var int */
23
    private $maxProcessNumber;
24
25
    /** @var AbstractParaunitProcess[] */
26
    private $processStack;
27
28
    /** @var AbstractParaunitProcess[] */
29
    private $processCompleted;
30
31
    /** @var AbstractParaunitProcess[] */
32
    private $processRunning;
33
34
    /** @var  ProcessFactory */
35
    private $processFactory;
36
37
    /** @var EventDispatcherInterface */
38
    private $eventDispatcher;
39
40
    /** @var Filter */
41
    private $filter;
42
43
    /**
44
     * @param EventDispatcherInterface $eventDispatcher
45
     * @param ProcessFactory $processFactory
46
     * @param Filter $filter
47
     * @param int $maxProcessNumber
48
     */
49 16
    public function __construct(
50
        EventDispatcherInterface $eventDispatcher,
51
        ProcessFactory $processFactory,
52
        Filter $filter,
53
        int $maxProcessNumber = 10
54
    ) {
55
56 16
        $this->eventDispatcher = $eventDispatcher;
57 16
        $this->processFactory = $processFactory;
58 16
        $this->filter = $filter;
59 16
        $this->maxProcessNumber = $maxProcessNumber;
60
61 16
        $this->processStack = [];
62 16
        $this->processCompleted = [];
63 16
        $this->processRunning = [];
64
    }
65
66
    /**
67
     * @param OutputInterface $outputInterface
68
     * @param bool $debug
69
     * @return int
70
     * @throws \RuntimeException
71
     */
72 16
    public function run(OutputInterface $outputInterface, bool $debug = false): int
73
    {
74 16
        $this->eventDispatcher->dispatch(EngineEvent::BEFORE_START, new EngineEvent($outputInterface));
75 16
        $start = new \Datetime('now');
76
77 16
        $files = $this->filter->filterTestFiles();
78 16
        $this->createProcessStackFromFiles($files);
79
80 16
        $this->eventDispatcher->dispatch(
81 16
            EngineEvent::START,
82 16
            new EngineEvent($outputInterface, ['start' => $start])
83
        );
84
85 16
        while (count($this->processStack) > 0 || count($this->processRunning) > 0) {
86 16
            if ($process = $this->runProcess($debug)) {
87 16
                $this->eventDispatcher->dispatch(ProcessEvent::PROCESS_STARTED, new ProcessEvent($process));
88
            }
89
90 16
            foreach ($this->processRunning as $process) {
91 16
                if ($process->isTerminated()) {
92 16
                    $this->eventDispatcher->dispatch(
93 16
                        ProcessEvent::PROCESS_TERMINATED,
94 16
                        new ProcessEvent($process, ['output_interface' => $outputInterface,])
95
                    );
96
                    // Completed or back to the stack
97 16
                    $this->markProcessCompleted($process);
98
                }
99
100 16
                usleep(500);
101
            }
102
        }
103
104 16
        $end = new \Datetime('now');
105
106 16
        $this->eventDispatcher->dispatch(
107 16
            EngineEvent::END,
108 16
            new EngineEvent(
109 16
                $outputInterface,
110 16
                ['end' => $end, 'start' => $start, 'process_completed' => $this->processCompleted]
111
            )
112
        );
113
114 16
        return $this->getReturnCode();
115
    }
116
117 16
    private function getReturnCode(): int
118
    {
119 16
        foreach ($this->processCompleted as $process) {
120 16
            if ($process->getExitCode() !== 0) {
121 16
                return 10;
122
            }
123
        }
124
125 6
        return 0;
126
    }
127
128
    /**
129
     * @param string[] $files
130
     * @throws \Symfony\Component\Process\Exception\RuntimeException
131
     */
132 16
    private function createProcessStackFromFiles(array $files)
133
    {
134 16
        foreach ($files as $file) {
135 16
            $process = $this->processFactory->createProcess($file);
136 16
            $this->processStack[$process->getUniqueId()] = $process;
137
        }
138
    }
139
140
    /**
141
     * @param $debug
142
     * @return ParaunitProcessInterface | null
143
     */
144 16
    private function runProcess(bool $debug)
145
    {
146 16
        if ($this->maxProcessNumber > count($this->processRunning) && count($this->processStack) > 0) {
147
            /** @var ParaunitProcessInterface $process */
148 16
            $process = array_pop($this->processStack);
149 16
            $process->start();
150 16
            $this->processRunning[$process->getUniqueId()] = $process;
151
152 16
            if ($debug) {
153
                DebugPrinter::printDebugOutput($process, $this->processRunning);
154
            }
155
156 16
            return $process;
157
        }
158
159 16
        return null;
160
    }
161
162
    /**
163
     * @param AbstractParaunitProcess $process
164
     * @throws \RuntimeException
165
     */
166 16
    private function markProcessCompleted(AbstractParaunitProcess $process)
167
    {
168 16
        $pHash = $process->getUniqueId();
169
170 16
        if (array_key_exists($pHash, $this->processRunning)) {
171 16
            unset($this->processRunning[$pHash]);
172
        } else {
173
            throw new \RuntimeException('Trying to remove a non-existing process from running stack\! ID: ' . $pHash);
174
        }
175
176 16
        if ($process->isToBeRetried()) {
177 5
            $process->reset();
178 5
            $process->increaseRetryCount();
179 5
            $this->processStack[$pHash] = $process;
180
        } else {
181 16
            $this->processCompleted[$pHash] = $process;
182
        }
183
    }
184
}
185