TestContext::thereIsFile()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Tests\Behat\Context;
6
7
use Behat\Behat\Context\Context;
8
use Symfony\Component\Filesystem\Filesystem;
9
use Symfony\Component\Process\PhpExecutableFinder;
10
use Symfony\Component\Process\Process;
11
use Symfony\Component\Yaml\Yaml;
12
13
final class TestContext implements Context
14
{
15
    /** @var string */
16
    private static $workingDir;
17
18
    /** @var Filesystem */
19
    private static $filesystem;
20
21
    /** @var string */
22
    private static $phpBin;
23
24
    /** @var Process */
25
    private $process;
26
27
    /** @var array */
28
    private $variables = [];
29
30
    /**
31
     * @BeforeFeature
32
     */
33
    public static function beforeFeature(): void
34
    {
35
        self::$workingDir = sprintf('%s/%s/', sys_get_temp_dir(), uniqid('', true));
36
        self::$filesystem = new Filesystem();
37
        self::$phpBin = self::findPhpBinary();
38
    }
39
40
    /**
41
     * @BeforeScenario
42
     */
43
    public function beforeScenario(): void
44
    {
45
        self::$filesystem->remove(self::$workingDir);
46
        self::$filesystem->mkdir(self::$workingDir, 0777);
47
    }
48
49
    /**
50
     * @AfterScenario
51
     */
52
    public function afterScenario(): void
53
    {
54
        self::$filesystem->remove(self::$workingDir);
55
    }
56
57
    /**
58
     * @Given a standard Symfony autoloader configured
59
     */
60
    public function standardSymfonyAutoloaderConfigured(): void
61
    {
62
        $this->thereIsFile('vendor/autoload.php', sprintf(<<<'CON'
63
<?php
64
65
declare(strict_types=1);
66
67
$loader = require '%s';
68
$loader->addPsr4('App\\', __DIR__ . '/../src/');
69
$loader->addPsr4('App\\Tests\\', __DIR__ . '/../tests/');
70
71
return $loader; 
72
CON
73
            , __DIR__ . '/../../../vendor/autoload.php'));
74
    }
75
76
    /**
77
     * @Given a working Symfony application with SymfonyExtension configured
78
     */
79
    public function workingSymfonyApplicationWithExtension(): void
80
    {
81
        $this->thereIsConfiguration(<<<'CON'
82
default:
83
    extensions:
84
        FriendsOfBehat\SymfonyExtension:
85
            kernel:
86
                class: App\Kernel
87
CON
88
        );
89
90
        $this->standardSymfonyAutoloaderConfigured();
91
92
        $this->thereIsFile('src/Kernel.php', <<<'CON'
93
<?php
94
95
declare(strict_types=1);
96
97
namespace App;
98
99
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
100
use Symfony\Component\Config\Loader\LoaderInterface;
101
use Symfony\Component\DependencyInjection\ContainerBuilder;
102
use Symfony\Component\HttpKernel\Kernel as HttpKernel;
103
use Symfony\Component\Routing\RouteCollectionBuilder;
104
105
class Kernel extends HttpKernel
106
{
107
    use MicroKernelTrait;
108
109
    public function registerBundles(): iterable
110
    {
111
        return [
112
            new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
113
            new \FriendsOfBehat\SymfonyExtension\Bundle\FriendsOfBehatSymfonyExtensionBundle(),
114
        ];
115
    }
116
117
    protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
118
    {
119
        $container->loadFromExtension('framework', [
120
            'test' => $this->getEnvironment() === 'test',
121
            'secret' => 'Pigeon',
122
        ]);
123
        
124
        $loader->load(__DIR__ . '/../config/default.yaml');
125
        $loader->load(__DIR__ . '/../config/services.yaml');
126
    }
127
    
128
    protected function configureRoutes(RouteCollectionBuilder $routes): void
129
    {
130
        $routes->add('/hello-world', 'App\Controller:helloWorld');
131
    }
132
}
133
CON
134
        );
135
136
        $this->thereIsFile('src/Controller.php', <<<'CON'
137
<?php
138
139
declare(strict_types=1);
140
141
namespace App;
142
143
use Symfony\Component\HttpFoundation\Response;
144
145
final class Controller
146
{
147
    private $counter;
148
149
    public function __construct(Counter $counter)
150
    {
151
        $this->counter = $counter;
152
    }
153
154
    public function helloWorld(): Response
155
    {
156
        $this->counter->increase();
157
    
158
        return new Response('Hello world!');
159
    }
160
}
161
CON
162
        );
163
164
        $this->thereIsFile('src/Counter.php', <<<'CON'
165
<?php
166
167
declare(strict_types=1);
168
169
namespace App;
170
171
final class Counter
172
{
173
    private $counter = 0;
174
    
175
    public function increase(): void
176
    {
177
        $this->counter++;
178
    }
179
    
180
    public function get(): int
181
    {
182
        return $this->counter;
183
    }
184
}
185
CON
186
        );
187
188
        $this->thereIsFile('config/default.yaml', <<<'YML'
189
services:
190
    App\Controller:
191
        arguments:
192
            - '@App\Counter'
193
        public: true
194
195
    App\Counter:
196
        public: false
197
YML
198
        );
199
200
        $this->thereIsFile('config/services.yaml', '');
201
    }
202
203
    /**
204
     * @Given /^an? (server|environment) variable "([^"]++)" set to "([^"]++)"$/
205
     */
206
    public function variableSetTo(string $type, string $name, string $value): void
207
    {
208
        $this->variables[$type][$name] = $value;
209
    }
210
211
    /**
212
     * @Given /^a YAML services file containing:$/
213
     */
214
    public function yamlServicesFile($content): void
215
    {
216
        $this->thereIsFile('config/services.yaml', (string) $content);
217
    }
218
219
    /**
220
     * @Given /^a Behat configuration containing(?: "([^"]+)"|:)$/
221
     */
222
    public function thereIsConfiguration($content): void
223
    {
224
        $mainConfigFile = sprintf('%s/behat.yml', self::$workingDir);
225
        $newConfigFile = sprintf('%s/behat-%s.yml', self::$workingDir, md5((string) $content));
226
227
        self::$filesystem->dumpFile($newConfigFile, (string) $content);
228
229
        if (!file_exists($mainConfigFile)) {
230
            self::$filesystem->dumpFile($mainConfigFile, Yaml::dump(['imports' => []]));
231
        }
232
233
        $mainBehatConfiguration = Yaml::parseFile($mainConfigFile);
234
        $mainBehatConfiguration['imports'][] = $newConfigFile;
235
236
        self::$filesystem->dumpFile($mainConfigFile, Yaml::dump($mainBehatConfiguration));
237
    }
238
239
    /**
240
     * @Given /^a (?:.+ |)file "([^"]+)" containing(?: "([^"]+)"|:)$/
241
     */
242
    public function thereIsFile($file, $content): string
243
    {
244
        $path = self::$workingDir . '/' . $file;
245
246
        self::$filesystem->dumpFile($path, (string) $content);
247
248
        return $path;
249
    }
250
251
    /**
252
     * @Given /^a feature file containing(?: "([^"]+)"|:)$/
253
     */
254
    public function thereIsFeatureFile($content): void
255
    {
256
        $this->thereIsFile(sprintf('features/%s.feature', md5(uniqid('', true))), $content);
257
    }
258
259
    /**
260
     * @When /^I run Behat$/
261
     */
262
    public function iRunBehat(): void
263
    {
264
        $executablePath = BEHAT_BIN_PATH;
265
266
        if ($this->variables !== []) {
267
            $content = '<?php ';
268
269
            foreach ($this->variables['server'] ?? [] as $name => $value) {
270
                $content .= sprintf('$_SERVER["%s"] = "%s"; ', $name, $value);
271
            }
272
273
            foreach ($this->variables['environment'] ?? [] as $name => $value) {
274
                $content .= sprintf('$_ENV["%s"] = "%s"; ', $name, $value);
275
            }
276
277
            $content .= sprintf('require_once("%s"); ', $executablePath);
278
279
            $executablePath = $this->thereIsFile('__executable.php', $content);
280
        }
281
282
        $this->process = new Process([self::$phpBin, $executablePath, '--strict', '-vvv', '--no-interaction', '--lang=en'], self::$workingDir);
283
        $this->process->start();
284
        $this->process->wait();
285
    }
286
287
    /**
288
     * @Then /^it should pass$/
289
     */
290 View Code Duplication
    public function itShouldPass(): void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
291
    {
292
        if (0 === $this->getProcessExitCode()) {
293
            return;
294
        }
295
296
        throw new \DomainException(
297
            'Behat was expecting to pass, but failed with the following output:' . \PHP_EOL . \PHP_EOL . $this->getProcessOutput()
298
        );
299
    }
300
301
    /**
302
     * @Then /^it should pass with(?: "([^"]+)"|:)$/
303
     */
304
    public function itShouldPassWith($expectedOutput): void
305
    {
306
        $this->itShouldPass();
307
        $this->assertOutputMatches((string) $expectedOutput);
308
    }
309
310
    /**
311
     * @Then /^it should fail$/
312
     */
313 View Code Duplication
    public function itShouldFail(): void
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
314
    {
315
        if (0 !== $this->getProcessExitCode()) {
316
            return;
317
        }
318
319
        throw new \DomainException(
320
            'Behat was expecting to fail, but passed with the following output:' . \PHP_EOL . \PHP_EOL . $this->getProcessOutput()
321
        );
322
    }
323
324
    /**
325
     * @Then /^it should fail with(?: "([^"]+)"|:)$/
326
     */
327
    public function itShouldFailWith($expectedOutput): void
328
    {
329
        $this->itShouldFail();
330
        $this->assertOutputMatches((string) $expectedOutput);
331
    }
332
333
    /**
334
     * @Then /^it should end with(?: "([^"]+)"|:)$/
335
     */
336
    public function itShouldEndWith($expectedOutput): void
337
    {
338
        $this->assertOutputMatches((string) $expectedOutput);
339
    }
340
341
    /**
342
     * @param string $expectedOutput
343
     */
344
    private function assertOutputMatches($expectedOutput): void
345
    {
346
        $pattern = '/' . preg_quote($expectedOutput, '/') . '/sm';
347
        $output = $this->getProcessOutput();
348
349
        $result = preg_match($pattern, $output);
350
        if (false === $result) {
351
            throw new \InvalidArgumentException('Invalid pattern given:' . $pattern);
352
        }
353
354
        if (0 === $result) {
355
            throw new \DomainException(sprintf(
356
                'Pattern "%s" does not match the following output:' . \PHP_EOL . \PHP_EOL . '%s',
357
                $pattern,
358
                $output
359
            ));
360
        }
361
    }
362
363
    private function getProcessOutput(): string
364
    {
365
        $this->assertProcessIsAvailable();
366
367
        return $this->process->getErrorOutput() . $this->process->getOutput();
368
    }
369
370
    private function getProcessExitCode(): int
371
    {
372
        $this->assertProcessIsAvailable();
373
374
        return $this->process->getExitCode();
375
    }
376
377
    /**
378
     * @throws \BadMethodCallException
379
     */
380
    private function assertProcessIsAvailable(): void
381
    {
382
        if (null === $this->process) {
383
            throw new \BadMethodCallException('Behat proccess cannot be found. Did you run it before making assertions?');
384
        }
385
    }
386
387
    /**
388
     * @throws \RuntimeException
389
     */
390
    private static function findPhpBinary(): string
391
    {
392
        $phpBinary = (new PhpExecutableFinder())->find();
393
        if (false === $phpBinary) {
394
            throw new \RuntimeException('Unable to find the PHP executable.');
395
        }
396
397
        return $phpBinary;
398
    }
399
}
400