Completed
Pull Request — master (#16)
by Arnaud
48:07 queued 29:02
created

SmokeCommand::execute()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
c 3
b 0
f 0
nc 1
nop 2
dl 0
loc 8
rs 10
1
<?php
2
3
namespace LAG\SmokerBundle\Command;
4
5
use LAG\SmokerBundle\Message\MessageCollectorInterface;
6
use LAG\SmokerBundle\Response\Registry\ResponseHandlerRegistry;
7
use Goutte\Client;
8
use LAG\SmokerBundle\Url\Registry\UrlProviderRegistry;
9
use LAG\SmokerBundle\Url\Url;
10
use LAG\SmokerBundle\Url\UrlInfo;
11
use Symfony\Component\BrowserKit\Response;
12
use Symfony\Component\Console\Command\Command;
13
use Symfony\Component\Console\Helper\Helper;
14
use Symfony\Component\Console\Input\InputInterface;
15
use Symfony\Component\Console\Input\InputOption;
16
use Symfony\Component\Console\Output\Output;
17
use Symfony\Component\Console\Output\OutputInterface;
18
use Symfony\Component\Console\Style\SymfonyStyle;
19
use Symfony\Component\DomCrawler\Crawler;
20
use Symfony\Component\Filesystem\Filesystem;
21
use Twig\Environment;
22
23
class SmokeCommand extends Command
24
{
25
    protected static $defaultName = 'smoker:smoke';
26
27
    /**
28
     * @var string
29
     */
30
    protected $cacheDir;
31
32
    /**
33
     * @var ResponseHandlerRegistry
34
     */
35
    protected $responseHandlerRegistry;
36
37
    /**
38
     * @var string
39
     */
40
    protected $cacheFile;
41
42
    /**
43
     * @var Environment
44
     */
45
    protected $twig;
46
47
    /**
48
     * @var Filesystem
49
     */
50
    protected $fileSystem;
51
52
    /**
53
     * @var SymfonyStyle
54
     */
55
    protected $io;
56
57
    /**
58
     * @var UrlProviderRegistry
59
     */
60
    protected $urlProviderRegistry;
61
62
    /**
63
     * @var MessageCollectorInterface
64
     */
65
    protected $messageCollector;
66
67
    /**
68
     * @var Client
69
     */
70
    protected $client;
71
72
    /**
73
     * @var array
74
     */
75
    protected $routing;
76
77
    /**
78
     * @var bool
79
     */
80
    protected $stopOnFailure = false;
81
82
    /**
83
     * @var array
84
     */
85
    protected $routes;
86
87
    /**
88
     * SmokeCommand constructor.
89
     *
90
     * @param string                    $cacheDir
91
     * @param array                     $routing
92
     * @param array                     $routes
93
     * @param ResponseHandlerRegistry   $responseHandlerRegistry
94
     * @param UrlProviderRegistry       $urlProviderRegistry
95
     * @param MessageCollectorInterface $messageCollector
96
     * @param Environment               $twig
97
     */
98
    public function __construct(
99
        string $cacheDir,
100
        array $routing,
101
        array $routes,
102
        ResponseHandlerRegistry $responseHandlerRegistry,
103
        UrlProviderRegistry $urlProviderRegistry,
104
        MessageCollectorInterface $messageCollector,
105
        Environment $twig
106
    ) {
107
        parent::__construct();
108
109
        $this->cacheDir = $cacheDir;
110
        $this->responseHandlerRegistry = $responseHandlerRegistry;
111
        $this->twig = $twig;
112
        $this->fileSystem = new Filesystem();
113
        $this->urlProviderRegistry = $urlProviderRegistry;
114
        $this->messageCollector = $messageCollector;
115
        $this->routing = $routing;
116
        $this->routes = $routes;
117
    }
118
119
    protected function configure()
120
    {
121
        $this
122
            ->addOption('stop-on-failure', null, InputOption::VALUE_NONE, 'Stop all tests if an error is detected')
123
        ;
124
    }
125
126
    protected function execute(InputInterface $input, OutputInterface $output)
127
    {
128
        $this->io = new SymfonyStyle($input, $output);
129
        $this->io->title('Smoker Tests');
130
131
        $this->initializeCommand($input);
132
        $this->smoke();
133
        $this->generateResults();
134
    }
135
136
    protected function initializeCommand(InputInterface $input)
137
    {
138
        $this->io->note('Initialize results cache...');
139
        $this->cacheFile = $this->cacheDir.'/smoker/smoker.cache';
140
        $this->messageCollector->initialize();
141
142
        if ($input->getOption('stop-on-failure')) {
143
            $this->stopOnFailure = true;
144
        }
145
        // Create a new client. Cookies are by default enabled
146
        $this->client = new Client();
147
        $this->client->followRedirects(false);
148
    }
149
150
    protected function smoke()
151
    {
152
        if (!$this->fileSystem->exists($this->cacheFile)) {
153
            $this->io->warning('The cache file is not generated. Nothing will be done.');
154
            $this->io->note('The cache can be generated with the command bin/console smoker:generate-cache');
155
156
            return;
157
        }
158
        $handle = fopen($this->cacheFile, 'r');
159
160
        if (false !== $handle) {
161
            $this->io->text('Start reading urls in cache...');
162
163
            while (false !== ($row = fgets($handle, 4096))) {
164
                $url = Url::deserialize($row);
165
                $this->processRow($url);
166
167
                if (Output::VERBOSITY_DEBUG === $this->io->getVerbosity()) {
168
                    $this->io->write('  '.Helper::formatMemory(memory_get_usage(true)));
169
                }
170
                $this->io->newLine();
171
                gc_collect_cycles();
172
            }
173
174
            if (!feof($handle)) {
175
                $this->io->error('An error has occurred when reading the cache');
176
            }
177
            fclose($handle);
178
        }
179
    }
180
181
    protected function generateResults()
182
    {
183
        $this->io->note('Generating results report...');
184
        $this->messageCollector->flush();
185
        $messages = $this->messageCollector->read();
186
187
        $content = $this->twig->render('@LAGSmoker/Results/results.html.twig', [
188
            'messages' => $messages,
189
        ]);
190
        $this->fileSystem->dumpFile($this->cacheDir.'/smoker/results.html', $content);
191
        $this->io->text('The results report has been generated here file://'.$this->cacheDir.'/smoker/results.html');
192
    }
193
194
    protected function processRow(Url $url): void
195
    {
196
        $this->io->write('Processing '.$url->getLocation().'...');
197
198
        // Create a new empty client and fetch the request data
199
        $crawler = $this->client->request('get', $url->getLocation());
200
        /** @var Response $response */
201
        $response = $this->client->getResponse();
202
203
        try {
204
            $urlInfo = $this->urlProviderRegistry->match($url->getLocation());
205
        } catch (\Exception $exception) {
206
            $this
207
                ->messageCollector
208
                ->addError(
209
                    $url->getLocation(),
210
                    $exception->getMessage(),
211
                    500,
212
                    $exception
213
                );
214
            $this->io->write('...[<error>KO</error>]');
215
            $this->messageCollector->flush();
216
217
            return;
218
        }
219
        $responseHandled = $this->handleResponse($urlInfo, $url, $crawler, $response);
220
221
        if (!$responseHandled) {
222
            $this->io->write('...[<comment>WARN</comment>]');
223
            $this
224
                ->messageCollector
225
                ->addWarning($url->getLocation(), 'The response of the url is not handle by any response handler')
226
            ;
227
        }
228
229
        $this->messageCollector->flush();
230
    }
231
232
    /**
233
     * @param UrlInfo  $urlInfo
234
     * @param Url      $url
235
     * @param Crawler  $crawler
236
     * @param Response $response
237
     *
238
     * @return bool
239
     *
240
     * @throws \Exception
241
     */
242
    protected function handleResponse(UrlInfo $urlInfo, Url $url, Crawler $crawler, Response $response): bool
243
    {
244
        $responseHandled = false;
245
246
        foreach ($this->responseHandlerRegistry->all() as $responseHandler) {
247
            if (!$responseHandler->supports($urlInfo->getRouteName())) {
248
                continue;
249
            }
250
            $responseHandled = true;
251
252
            try {
253
                $routeOptions = $this->routes[$urlInfo->getRouteName()];
254
                $providerOptions = [];
255
256
                if (key_exists($responseHandler->getName(), $routeOptions['handlers'])) {
257
                    $providerOptions = $routeOptions['handlers'][$responseHandler->getName()];
258
259
                    if (!is_array($providerOptions)) {
260
                        $providerOptions = [
261
                            $providerOptions,
262
                        ];
263
                    }
264
                }
265
                $providerOptions['_url_info'] = $urlInfo;
266
267
                if ($url->hasOption('identifiers')) {
268
                    $providerOptions['_identifiers'] = $url->getOption('identifiers');
269
                }
270
                $responseHandler->handle($urlInfo->getRouteName(), $crawler, $this->client, $providerOptions);
271
272
                $this->io->write('...[<info>OK</info>]');
273
                $this
274
                    ->messageCollector
275
                    ->addSuccess($url->getLocation(), 'Success for handler', $response->getStatus())
0 ignored issues
show
Bug introduced by
The method getStatus() does not exist on Symfony\Component\BrowserKit\Response. Did you maybe mean getStatusCode()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

275
                    ->addSuccess($url->getLocation(), 'Success for handler', $response->/** @scrutinizer ignore-call */ getStatus())

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
276
                ;
277
            } catch (\Exception $exception) {
278
                $message = sprintf('An error has occurred when processing the url %s', $url->getLocation());
279
                $this
280
                    ->messageCollector
281
                    ->addError($url->getLocation(), $message, $response->getStatus(), $exception)
282
                ;
283
284
                if ($this->io->getVerbosity() > OutputInterface::VERBOSITY_NORMAL) {
285
                    $this->io->error($message.': '.$exception->getMessage());
286
287
                    if ($this->io->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
288
                        $this->io->text($exception->getTraceAsString());
289
                    }
290
                }
291
292
                if ($this->stopOnFailure) {
293
                    $this->generateResults();
294
295
                    throw $exception;
296
                }
297
                $this->io->write('...[<error>KO</error>]');
298
            }
299
        }
300
301
        return $responseHandled;
302
    }
303
}
304