Issues (5)

src/Command/SmokeCommand.php (1 issue)

Labels
Severity
1
<?php
2
3
namespace LAG\SmokerBundle\Command;
4
5
use Goutte\Client;
6
use LAG\SmokerBundle\Message\MessageCollectorInterface;
7
use LAG\SmokerBundle\Response\Registry\ResponseHandlerRegistry;
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
    public function __construct(
91
        string $cacheDir,
92
        array $routing,
93
        array $routes,
94
        ResponseHandlerRegistry $responseHandlerRegistry,
95
        UrlProviderRegistry $urlProviderRegistry,
96
        MessageCollectorInterface $messageCollector,
97
        Environment $twig
98
    ) {
99
        parent::__construct();
100
101
        $this->cacheDir = $cacheDir;
102
        $this->responseHandlerRegistry = $responseHandlerRegistry;
103
        $this->twig = $twig;
104
        $this->fileSystem = new Filesystem();
105
        $this->urlProviderRegistry = $urlProviderRegistry;
106
        $this->messageCollector = $messageCollector;
107
        $this->routing = $routing;
108
        $this->routes = $routes;
109
    }
110
111
    protected function configure()
112
    {
113
        $this
114
            ->addOption('stop-on-failure', null, InputOption::VALUE_NONE, 'Stop all tests if an error is detected')
115
        ;
116
    }
117
118
    protected function execute(InputInterface $input, OutputInterface $output)
119
    {
120
        $this->io = new SymfonyStyle($input, $output);
121
        $this->io->title('Smoker Tests');
122
123
        $this->initializeCommand($input);
124
        $this->smoke();
125
        $this->generateResults();
126
    }
127
128
    protected function initializeCommand(InputInterface $input)
129
    {
130
        $this->io->note('Initialize results cache...');
131
        $this->cacheFile = $this->cacheDir.'/smoker/smoker.cache';
132
        $this->messageCollector->initialize();
133
134
        if ($input->getOption('stop-on-failure')) {
135
            $this->stopOnFailure = true;
136
        }
137
        // Create a new client. Cookies are by default enabled
138
        $this->client = new Client();
139
        $this->client->followRedirects(false);
140
    }
141
142
    protected function smoke()
143
    {
144
        if (!$this->fileSystem->exists($this->cacheFile)) {
145
            $this->io->warning('The cache file is not generated. Nothing will be done.');
146
            $this->io->note('The cache can be generated with the command bin/console smoker:generate-cache');
147
148
            return;
149
        }
150
        $handle = fopen($this->cacheFile, 'r');
151
152
        if (false !== $handle) {
153
            $this->io->text('Start reading urls in cache...');
154
155
            while (false !== ($row = fgets($handle, 4096))) {
156
                $url = Url::deserialize($row);
157
                $this->processRow($url);
158
159
                if (Output::VERBOSITY_DEBUG === $this->io->getVerbosity()) {
160
                    $this->io->write('  '.Helper::formatMemory(memory_get_usage(true)));
161
                }
162
                $this->io->newLine();
163
                gc_collect_cycles();
164
            }
165
166
            if (!feof($handle)) {
167
                $this->io->error('An error has occurred when reading the cache');
168
            }
169
            fclose($handle);
170
        }
171
    }
172
173
    protected function generateResults()
174
    {
175
        $this->io->note('Generating results report...');
176
        $this->messageCollector->flush();
177
        $messages = $this->messageCollector->read();
178
179
        $content = $this->twig->render('@LAGSmoker/Results/results.html.twig', [
180
            'messages' => $messages,
181
        ]);
182
        $this->fileSystem->dumpFile($this->cacheDir.'/smoker/results.html', $content);
183
        $this->io->text('The results report has been generated here file://'.$this->cacheDir.'/smoker/results.html');
184
    }
185
186
    protected function processRow(Url $url): void
187
    {
188
        $this->io->write('Processing '.$url->getLocation().'...');
189
190
        // Create a new empty client and fetch the request data
191
        $crawler = $this->client->request('get', $url->getLocation());
192
        /** @var Response $response */
193
        $response = $this->client->getResponse();
194
195
        try {
196
            $urlInfo = $this->urlProviderRegistry->match($url->getLocation());
197
        } catch (\Exception $exception) {
198
            $this
199
                ->messageCollector
200
                ->addError(
201
                    $url->getLocation(),
202
                    $exception->getMessage(),
203
                    500,
204
                    $exception
205
                );
206
            $this->io->write('...[<error>KO</error>]');
207
            $this->messageCollector->flush();
208
209
            return;
210
        }
211
        $responseHandled = $this->handleResponse($urlInfo, $url, $crawler, $response);
212
213
        if (!$responseHandled) {
214
            $this->io->write('...[<comment>WARN</comment>]');
215
            $this
216
                ->messageCollector
217
                ->addWarning($url->getLocation(), 'The response of the url is not handle by any response handler')
218
            ;
219
        }
220
221
        $this->messageCollector->flush();
222
    }
223
224
    /**
225
     * @throws \Exception
226
     */
227
    protected function handleResponse(UrlInfo $urlInfo, Url $url, Crawler $crawler, Response $response): bool
228
    {
229
        $responseHandled = false;
230
231
        foreach ($this->responseHandlerRegistry->all() as $responseHandler) {
232
            if (!$responseHandler->supports($urlInfo->getRouteName())) {
233
                continue;
234
            }
235
            $responseHandled = true;
236
237
            try {
238
                $routeOptions = $this->routes[$urlInfo->getRouteName()];
239
                $providerOptions = [];
240
241
                if (key_exists($responseHandler->getName(), $routeOptions['handlers'])) {
242
                    $providerOptions = $routeOptions['handlers'][$responseHandler->getName()];
243
244
                    if (!is_array($providerOptions)) {
245
                        $providerOptions = [
246
                            $providerOptions,
247
                        ];
248
                    }
249
                }
250
                $providerOptions['_url_info'] = $urlInfo;
251
252
                if ($url->hasOption('identifiers')) {
253
                    $providerOptions['_identifiers'] = $url->getOption('identifiers');
254
                }
255
                $responseHandler->handle($urlInfo->getRouteName(), $crawler, $this->client, $providerOptions);
256
257
                $this->io->write('...[<info>OK</info>]');
258
                $this
259
                    ->messageCollector
260
                    ->addSuccess($url->getLocation(), 'Success for handler', $response->getStatus())
0 ignored issues
show
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

260
                    ->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...
261
                ;
262
            } catch (\Exception $exception) {
263
                $message = sprintf('An error has occurred when processing the url %s', $url->getLocation());
264
                $this
265
                    ->messageCollector
266
                    ->addError($url->getLocation(), $message, $response->getStatus(), $exception)
267
                ;
268
269
                if ($this->io->getVerbosity() > OutputInterface::VERBOSITY_NORMAL) {
270
                    $this->io->error($message.': '.$exception->getMessage());
271
272
                    if ($this->io->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
273
                        $this->io->text($exception->getTraceAsString());
274
                    }
275
                }
276
277
                if ($this->stopOnFailure) {
278
                    $this->generateResults();
279
280
                    throw $exception;
281
                }
282
                $this->io->write('...[<error>KO</error>]');
283
            }
284
        }
285
286
        return $responseHandled;
287
    }
288
}
289