GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#214)
by joseph
21:10
created

FullProjectBuildLargeTest::execBash()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 33
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 23
nc 4
nop 1
dl 0
loc 33
rs 9.552
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace EdmondsCommerce\DoctrineStaticMeta\Tests\Large\A;
4
5
use Doctrine\Common\Inflector\Inflector;
6
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Command\GenerateFieldCommand;
7
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\AbstractGenerator;
8
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\Generator\RelationsGenerator;
9
use EdmondsCommerce\DoctrineStaticMeta\Config;
10
use EdmondsCommerce\DoctrineStaticMeta\ConfigInterface;
11
use EdmondsCommerce\DoctrineStaticMeta\Entity\Embeddable\Objects\Financial\MoneyEmbeddable;
12
use EdmondsCommerce\DoctrineStaticMeta\Entity\Embeddable\Objects\Geo\AddressEmbeddable;
13
use EdmondsCommerce\DoctrineStaticMeta\Entity\Embeddable\Objects\Identity\FullNameEmbeddable;
14
use EdmondsCommerce\DoctrineStaticMeta\Entity\Embeddable\Traits\Financial\HasMoneyEmbeddableTrait;
15
use EdmondsCommerce\DoctrineStaticMeta\Entity\Embeddable\Traits\Geo\HasAddressEmbeddableTrait;
16
use EdmondsCommerce\DoctrineStaticMeta\Entity\Embeddable\Traits\Identity\HasFullNameEmbeddableTrait;
17
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\String\BusinessIdentifierCodeFieldTrait;
18
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Traits\String\NullableStringFieldTrait;
19
use EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException;
20
use EdmondsCommerce\DoctrineStaticMeta\MappingHelper;
21
use EdmondsCommerce\DoctrineStaticMeta\Tests\Assets\AbstractLargeTest;
22
use EdmondsCommerce\DoctrineStaticMeta\Tests\Assets\AbstractTest;
23
use Exception;
24
use Psr\Container\ContainerExceptionInterface;
25
use Psr\Container\NotFoundExceptionInterface;
26
use RuntimeException;
27
use function array_diff;
28
use function array_keys;
29
use function ksort;
30
31
/**
32
 * Class GeneratedCodeTest
33
 *
34
 * @package EdmondsCommerce\DoctrineStaticMeta\GeneratedCode
35
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
36
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
37
 * @coversNothing
38
 */
39
class FullProjectBuildLargeTest extends AbstractLargeTest
40
{
41
    public const TEST_ENTITY_NAMESPACE_BASE = self::TEST_PROJECT_ROOT_NAMESPACE
42
                                              . '\\' . AbstractGenerator::ENTITIES_FOLDER_NAME;
43
44
    public const TEST_FIELD_TRAIT_NAMESPACE = self::TEST_FIELD_NAMESPACE_BASE . '\\Traits\\';
45
46
    public const TEST_ENTITY_PERSON                      = self::TEST_ENTITY_NAMESPACE_BASE . '\\Person';
47
    public const TEST_ENTITY_ADDRESS                     = self::TEST_ENTITY_NAMESPACE_BASE . '\\Attributes\\Address';
48
    public const TEST_ENTITY_EMAIL                       = self::TEST_ENTITY_NAMESPACE_BASE . '\\Attributes\\Email';
49
    public const TEST_ENTITY_COMPANY                     = self::TEST_ENTITY_NAMESPACE_BASE . '\\Company';
50
    public const TEST_ENTITY_DIRECTOR                    = self::TEST_ENTITY_NAMESPACE_BASE . '\\Company\\Director';
51
    public const TEST_ENTITY_ORDER                       = self::TEST_ENTITY_NAMESPACE_BASE . '\\Order';
52
    public const TEST_ENTITY_ORDER_ADDRESS               = self::TEST_ENTITY_NAMESPACE_BASE . '\\Order\\Address';
53
    public const TEST_ENTITY_NAME_SPACING_SOME_CLIENT    = self::TEST_ENTITY_NAMESPACE_BASE . '\\Some\\Client';
54
    public const TEST_ENTITY_NAME_SPACING_ANOTHER_CLIENT = self::TEST_ENTITY_NAMESPACE_BASE
55
                                                           . '\\Another\\Deeply\\Nested\\Client';
56
    public const TEST_ENTITY_PRODUCT                     = self::TEST_ENTITY_NAMESPACE_BASE . '\\Product';
57
    public const TEST_ENTITY_PRODUCT_STOCK               = self::TEST_ENTITY_NAMESPACE_BASE . '\\Product\\Stock';
58
    public const TEST_ENTITY_PRODUCT_REVIEW              = self::TEST_ENTITY_NAMESPACE_BASE . '\\Product\\Review';
59
    public const TEST_ENTITY_PRODUCT_DATA                = self::TEST_ENTITY_NAMESPACE_BASE . '\\Product\\Data';
60
    public const TEST_ENTITY_PRODUCT_DATA_ITEM           = self::TEST_ENTITY_NAMESPACE_BASE . '\\Product\\Data\\Item';
61
    public const TEST_ENTITY_PRODUCT_DATA_ITEM_THING     = self::TEST_ENTITY_NAMESPACE_BASE .
62
                                                           '\\Product\\Data\\Item\\Thing';
63
    public const TEST_ENTITY_PRODUCT_DATA_ITEM_FOO       = self::TEST_ENTITY_NAMESPACE_BASE .
64
                                                           '\\Product\\Data\\Item\\Foo';
65
    public const TEST_ENTITY_PRODUCT_EXTRA               = self::TEST_ENTITY_NAMESPACE_BASE . '\\Product\\Extra';
66
67
    public const TEST_ENTITIES = [
68
        self::TEST_ENTITY_PERSON,
69
        self::TEST_ENTITY_ADDRESS,
70
        self::TEST_ENTITY_EMAIL,
71
        self::TEST_ENTITY_COMPANY,
72
        self::TEST_ENTITY_DIRECTOR,
73
        self::TEST_ENTITY_ORDER,
74
        self::TEST_ENTITY_ORDER_ADDRESS,
75
        self::TEST_ENTITY_NAME_SPACING_SOME_CLIENT,
76
        self::TEST_ENTITY_NAME_SPACING_ANOTHER_CLIENT,
77
        self::TEST_ENTITY_PRODUCT,
78
        self::TEST_ENTITY_PRODUCT_STOCK,
79
        self::TEST_ENTITY_PRODUCT_REVIEW,
80
        self::TEST_ENTITY_PRODUCT_DATA,
81
        self::TEST_ENTITY_PRODUCT_DATA_ITEM,
82
        self::TEST_ENTITY_PRODUCT_DATA_ITEM_THING,
83
        self::TEST_ENTITY_PRODUCT_DATA_ITEM_FOO,
84
        self::TEST_ENTITY_PRODUCT_EXTRA,
85
    ];
86
87
    public const TEST_RELATIONS = [
88
        [
89
            self::TEST_ENTITY_PERSON,
90
            RelationsGenerator::HAS_UNIDIRECTIONAL_MANY_TO_ONE,
91
            self::TEST_ENTITY_ADDRESS,
92
            true,
93
        ],
94
        [
95
            self::TEST_ENTITY_PERSON,
96
            RelationsGenerator::HAS_ONE_TO_MANY,
97
            self::TEST_ENTITY_EMAIL,
98
            true,
99
        ],
100
        [
101
            self::TEST_ENTITY_COMPANY,
102
            RelationsGenerator::HAS_MANY_TO_MANY,
103
            self::TEST_ENTITY_DIRECTOR,
104
            false,
105
        ],
106
        [
107
            self::TEST_ENTITY_COMPANY,
108
            RelationsGenerator::HAS_ONE_TO_MANY,
109
            self::TEST_ENTITY_ADDRESS,
110
            false,
111
        ],
112
        [
113
            self::TEST_ENTITY_COMPANY,
114
            RelationsGenerator::HAS_UNIDIRECTIONAL_ONE_TO_MANY,
115
            self::TEST_ENTITY_EMAIL,
116
            true,
117
        ],
118
        [
119
            self::TEST_ENTITY_DIRECTOR,
120
            RelationsGenerator::HAS_ONE_TO_ONE,
121
            self::TEST_ENTITY_PERSON,
122
            false,
123
        ],
124
        [
125
            self::TEST_ENTITY_ORDER,
126
            RelationsGenerator::HAS_MANY_TO_ONE,
127
            self::TEST_ENTITY_PERSON,
128
            true,
129
        ],
130
        [
131
            self::TEST_ENTITY_ORDER,
132
            RelationsGenerator::HAS_ONE_TO_MANY,
133
            self::TEST_ENTITY_ORDER_ADDRESS,
134
            false,
135
        ],
136
        [
137
            self::TEST_ENTITY_ORDER_ADDRESS,
138
            RelationsGenerator::HAS_UNIDIRECTIONAL_ONE_TO_ONE,
139
            self::TEST_ENTITY_ADDRESS,
140
            true,
141
        ],
142
        [
143
            self::TEST_ENTITY_COMPANY,
144
            RelationsGenerator::HAS_ONE_TO_ONE,
145
            self::TEST_ENTITY_NAME_SPACING_SOME_CLIENT,
146
            false,
147
        ],
148
        [
149
            self::TEST_ENTITY_COMPANY,
150
            RelationsGenerator::HAS_ONE_TO_ONE,
151
            self::TEST_ENTITY_NAME_SPACING_ANOTHER_CLIENT,
152
            true,
153
        ],
154
        [
155
            self::TEST_ENTITY_PRODUCT,
156
            RelationsGenerator::HAS_REQUIRED_MANY_TO_MANY,
157
            self::TEST_ENTITY_PRODUCT_STOCK,
158
            true,
159
        ],
160
        [
161
            self::TEST_ENTITY_PRODUCT_REVIEW,
162
            RelationsGenerator::HAS_REQUIRED_MANY_TO_ONE,
163
            self::TEST_ENTITY_PRODUCT,
164
            false,
165
        ],
166
        [
167
            self::TEST_ENTITY_PRODUCT,
168
            RelationsGenerator::HAS_REQUIRED_ONE_TO_ONE,
169
            self::TEST_ENTITY_PRODUCT_EXTRA,
170
            true,
171
        ],
172
        [
173
            self::TEST_ENTITY_PRODUCT,
174
            RelationsGenerator::HAS_ONE_TO_ONE,
175
            self::TEST_ENTITY_PRODUCT_DATA,
176
            true,
177
        ],
178
        [
179
            self::TEST_ENTITY_PRODUCT_DATA_ITEM,
180
            RelationsGenerator::HAS_REQUIRED_UNIDIRECTIONAL_MANY_TO_ONE,
181
            self::TEST_ENTITY_PRODUCT_DATA,
182
            false,
183
        ],
184
        [
185
            self::TEST_ENTITY_PRODUCT_DATA_ITEM_THING,
186
            RelationsGenerator::HAS_REQUIRED_UNIDIRECTIONAL_ONE_TO_ONE,
187
            self::TEST_ENTITY_PRODUCT_DATA,
188
            false,
189
        ],
190
        [
191
            self::TEST_ENTITY_PRODUCT_DATA_ITEM_FOO,
192
            RelationsGenerator::HAS_REQUIRED_UNIDIRECTIONAL_ONE_TO_MANY,
193
            self::TEST_ENTITY_PRODUCT_DATA,
194
            false,
195
        ],
196
        [
197
            self::TEST_ENTITY_PRODUCT_DATA_ITEM_FOO,
198
            RelationsGenerator::HAS_REQUIRED_ONE_TO_MANY,
199
            self::TEST_ENTITY_PERSON,
200
            false,
201
        ],
202
    ];
203
204
    public const TEST_FIELD_NAMESPACE_BASE = self::TEST_PROJECT_ROOT_NAMESPACE . '\\Entity\\Fields';
205
206
    public const UNIQUEABLE_FIELD_TYPES = [
207
        MappingHelper::TYPE_INTEGER,
208
        MappingHelper::TYPE_STRING,
209
    ];
210
211
    public const DUPLICATE_SHORT_NAME_FIELDS = [
212
        [self::TEST_FIELD_NAMESPACE_BASE . '\\Traits\\Something\\FooFieldTrait', NullableStringFieldTrait::class],
213
        [
214
            self::TEST_FIELD_NAMESPACE_BASE . '\\Traits\\Otherthing\\FooFieldTrait',
215
            BusinessIdentifierCodeFieldTrait::class,
216
        ],
217
    ];
218
219
    public const EMBEDDABLE_TRAIT_BASE = self::TEST_PROJECT_ROOT_NAMESPACE . '\\Entity\\Embeddable\\Traits';
220
221
    public const TEST_EMBEDDABLES          = [
222
        [
223
            MoneyEmbeddable::class,
224
            self::EMBEDDABLE_TRAIT_BASE . '\\Financial\\HasPriceEmbeddableTrait',
225
            'PriceEmbeddable',
226
        ],
227
        [
228
            AddressEmbeddable::class,
229
            self::EMBEDDABLE_TRAIT_BASE . '\\Geo\\HasHeadOfficeEmbeddableTrait',
230
            'HeadOfficeEmbeddable',
231
        ],
232
        [
233
            FullNameEmbeddable::class,
234
            self::EMBEDDABLE_TRAIT_BASE . '\\Identity\\HasPersonEmbeddableTrait',
235
            'PersonEmbeddable',
236
        ],
237
    ];
238
    public const BASH_PHPNOXDEBUG_FUNCTION = <<<'BASH'
239
function phpNoXdebug {
240
    debugMode="off"
241
    if [[ "$-" == *x* ]]
242
    then
243
        debugMode='on'
244
    fi
245
    if [[ "$debugMode" == "on" ]]
246
    then
247
        set +x
248
    fi
249
    local returnCode;
250
    local temporaryPath="$(mktemp -t php.XXXX).ini"
251
    # Using awk to ensure that files ending without newlines do not lead to configuration error
252
    /usr/bin/php -i | grep "\.ini" | grep -o -e '\(/[a-z0-9._-]\+\)\+\.ini' | grep -v xdebug \
253
        | xargs awk 'FNR==1{print ""}1' > "$temporaryPath"
254
    #Run PHP with temp config with no xdebug, display errors on stderr
255
    set +e
256
    /usr/bin/php -n -c "$temporaryPath" "$@"    
257
    returnCode=$?
258
    set -e
259
    rm -f "$temporaryPath"
260
    if [[ "$debugMode" == "on" ]]
261
    then
262
        set -x
263
    fi
264
    return ${returnCode};    
265
}
266
267
BASH;
268
    /**
269
     * @var string
270
     */
271
    private $workDir;
272
273
    /**
274
     * @throws Exception
275
     * @throws ContainerExceptionInterface
276
     * @throws NotFoundExceptionInterface
277
     * @SuppressWarnings(PHPMD.Superglobals)
278
     * @SuppressWarnings(PHPMD.StaticAccess)
279
     */
280
    public function setup()
281
    {
282
        if ($this->isQuickTests()) {
283
            return;
284
        }
285
        $this->assertNoUncommitedChanges();
286
        $this->assertWeCheckAllPossibleRelationTypes();
287
        $this->workDir      = $this->isTravis() ?
288
            AbstractTest::VAR_PATH . '/GeneratedCodeTest'
289
            : sys_get_temp_dir() . '/dsm/test-project';
290
        $this->entitiesPath = $this->workDir . '/src/Entities';
291
        $this->getFileSystem()->mkdir($this->workDir);
292
        $this->emptyDirectory($this->workDir . '');
293
        $this->getFileSystem()->mkdir($this->entitiesPath);
294
        $this->setupContainer($this->entitiesPath);
295
        $this->initRebuildFile();
296
        $this->setupGeneratedDb();
297
        $this->initComposerAndInstall();
298
        $fileSystem = $this->getFileSystem();
299
        $fileSystem->mkdir(
300
            [
301
                $this->workDir . '/tests/',
302
                $this->workDir . '/cache/Proxies',
303
                $this->workDir . '/cache/qa',
304
                $this->workDir . '/qaConfig',
305
                $this->workDir . '/var/qa/phpunit_logs/',
306
            ]
307
        );
308
        file_put_contents(
309
            $this->workDir . '/qaConfig/phpunit.xml',
310
            <<<XML
311
<phpunit
312
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
313
        xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/3.7/phpunit.xsd"
314
        cacheTokens="false"
315
        colors="true"
316
        verbose="true"
317
        bootstrap="../tests/bootstrap.php"
318
        printerClass="\EdmondsCommerce\PHPQA\PHPUnit\TestDox\CliTestDoxPrinter"
319
        cacheResult="true"
320
        cacheResultFile="../var/qa/.phpunit.result.cache"
321
        executionOrder="depends,defects"
322
>
323
    <testsuites>
324
        <testsuite name="tests">
325
            <directory suffix="Test.php">../tests/</directory>
326
        </testsuite>
327
    </testsuites>
328
</phpunit>
329
XML
330
        );
331
        $fileSystem->symlink($this->workDir . '/qaConfig/phpunit.xml', $this->workDir . '/phpunit.xml');
332
333
        $fileSystem->copy(
334
            __DIR__ . '/../../../qaConfig/qaConfig.inc.bash',
335
            $this->workDir . '/qaConfig/qaConfig.inc.bash'
336
        );
337
        $fileSystem->copy(__DIR__ . '/../../../cli-config.php', $this->workDir . '/cli-config.php');
338
        file_put_contents($this->workDir . '/README.md', '#Generated Code');
339
340
        $entities = $this->generateEntities();
341
        $this->generateRelations();
342
        $this->generateFields();
343
        $this->setFields(
344
            $entities,
345
            $this->getFieldFqns()
346
        );
347
        $this->setTheDuplicateNamedFields($entities);
348
        foreach ($entities as $entityFqn) {
349
            foreach ([
350
                         HasMoneyEmbeddableTrait::class,
351
                         HasFullNameEmbeddableTrait::class,
352
                         HasAddressEmbeddableTrait::class,
353
                     ] as $embeddableTraitFqn) {
354
                $this->setEmbeddable($entityFqn, $embeddableTraitFqn);
355
            }
356
            foreach (self::TEST_EMBEDDABLES as $embeddable) {
357
                [$archetypeObject, $traitFqn, $className] = $embeddable;
358
                $this->generateEmbeddable(
359
                    '\\' . $archetypeObject,
360
                    $className
361
                );
362
                $this->setEmbeddable($entityFqn, $traitFqn);
363
            }
364
        }
365
        $this->removeUnusedRelations();
366
        $this->execDoctrine('dsm:finalise:build');
367
        $this->execDoctrine('orm:clear-cache:metadata');
368
        $this->execDoctrine('orm:schema-tool:update --force');
369
        $this->execDoctrine('orm:validate-schema');
370
    }
371
372
    /**
373
     * We need to check for uncommited changes in the main project. If there are, then the generated code tests will
374
     * not get them as it works by cloning this repo via the filesystem
375
     */
376
    protected function assertNoUncommitedChanges(): void
377
    {
378
        if ($this->isTravis()) {
379
            return;
380
        }
381
        exec("git status | grep -E 'nothing to commit, working .*? clean' ", $output, $exitCode);
382
        if (0 !== $exitCode) {
383
            $this->markTestSkipped(
384
                'uncommitted changes detected in this project, '
385
                . 'there is no point running the generated code test as it will not have your uncommitted changes.'
386
                . "\n\n" . implode("\n", $output)
387
            );
388
        }
389
    }
390
391
    protected function assertWeCheckAllPossibleRelationTypes(): void
392
    {
393
        $included = $toTest = [];
394
        foreach (RelationsGenerator::HAS_TYPES as $hasType) {
395
            if (false !== \ts\stringContains($hasType, RelationsGenerator::PREFIX_INVERSE)) {
396
                continue;
397
            }
398
            $toTest[$hasType] = true;
399
        }
400
        ksort($toTest);
401
        foreach (self::TEST_RELATIONS as $relation) {
402
            $included[$relation[1]] = true;
403
        }
404
        ksort($included);
405
        $missing = array_diff(array_keys($toTest), array_keys($included));
406
        self::assertEmpty(
407
            $missing,
408
            'We are not testing all relation types - '
409
            . 'these ones have not been included: '
410
            . print_r($missing, true)
411
        );
412
    }
413
414
    protected function initRebuildFile(): void
415
    {
416
        $bash =
417
            <<<'BASH'
418
#!/usr/bin/env bash
419
readonly DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )";
420
cd "$DIR";
421
set -e
422
set -u
423
set -o pipefail
424
standardIFS="$IFS"
425
IFS=$'\n\t'
426
echo "
427
===========================================
428
$(hostname) $0 $@
429
===========================================
430
"
431
# Error Handling
432
backTraceExit () {
433
    local err=$?
434
    set +o xtrace
435
    local code="${1:-1}"
436
    printf "\n\nError in ${BASH_SOURCE[1]}:${BASH_LINENO[0]}. '${BASH_COMMAND}'\n\n exited with status: \n\n$err\n\n"
437
    # Print out the stack trace described by $function_stack
438
    if [ ${#FUNCNAME[@]} -gt 2 ]
439
    then
440
        echo "Call tree:"
441
        for ((i=1;i<${#FUNCNAME[@]}-1;i++))
442
        do
443
            echo " $i: ${BASH_SOURCE[$i+1]}:${BASH_LINENO[$i]} ${FUNCNAME[$i]}(...)"
444
        done
445
    fi
446
    echo "Exiting with status ${code}"
447
    exit "${code}"
448
}
449
trap 'backTraceExit' ERR
450
set -o errtrace
451
# Error Handling Ends
452
453
echo "clearing out generated code and cache"
454
rm -rf src/* tests/* cache/*
455
456
echo "preparing empty Entities directory"
457
mkdir src/Entities
458
459
echo "making sure we have the latest version of code"
460
(cd vendor/edmondscommerce/doctrine-static-meta && git pull)
461
462
BASH;
463
        if (!$this->isTravis()) {
464
            $bash .= self::BASH_PHPNOXDEBUG_FUNCTION;
465
        }
466
        file_put_contents(
467
            $this->workDir . '/rebuild.bash',
468
            "\n\n" . $bash
469
        );
470
    }
471
472
    /**
473
     * @return string Generated Database Name
474
     * @throws Exception
475
     * @throws ContainerExceptionInterface
476
     * @throws NotFoundExceptionInterface
477
     */
478
    protected function setupGeneratedDb(): string
479
    {
480
        $dbHost = $this->container->get(Config::class)->get(ConfigInterface::PARAM_DB_HOST);
481
        $dbUser = $this->container->get(Config::class)->get(ConfigInterface::PARAM_DB_USER);
482
        $dbPass = $this->container->get(Config::class)->get(ConfigInterface::PARAM_DB_PASS);
483
        $dbName = $this->container->get(Config::class)->get(ConfigInterface::PARAM_DB_NAME);
484
        $link   = mysqli_connect($dbHost, $dbUser, $dbPass);
485
        if (!$link) {
486
            throw new DoctrineStaticMetaException('Failed getting connection in ' . __METHOD__);
487
        }
488
        $generatedDbName = $dbName . '_generated';
489
        mysqli_query($link, "DROP DATABASE IF EXISTS $generatedDbName");
490
        mysqli_query(
491
            $link,
492
            "CREATE DATABASE $generatedDbName 
493
        CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci"
494
        );
495
        mysqli_close($link);
496
497
        $rebuildBash = <<<BASH
498
echo "Dropping and creating the DB $generatedDbName"        
499
mysql -u $dbUser -p$dbPass -h $dbHost -e "DROP DATABASE IF EXISTS $generatedDbName";
500
mysql -u $dbUser -p$dbPass -h $dbHost -e "CREATE DATABASE $generatedDbName 
501
CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci";
502
BASH;
503
        $this->addToRebuildFile($rebuildBash);
504
        file_put_contents(
505
            $this->workDir . '/.env',
506
            <<<EOF
507
export dbUser="{$dbUser}"
508
export dbPass="{$dbPass}"
509
export dbHost="{$dbHost}"
510
export dbName="$generatedDbName"
511
export devMode=0
512
export dbDebug=0
513
EOF
514
        );
515
516
        return $generatedDbName;
517
    }
518
519
    /**
520
     * @param string $bash
521
     *
522
     * @return bool
523
     * @throws Exception
524
     */
525
    protected function addToRebuildFile(string $bash): bool
526
    {
527
        $result = file_put_contents(
528
            $this->workDir . '/rebuild.bash',
529
            "\n\n" . $bash . "\n\n",
530
            FILE_APPEND
531
        );
532
        if (!$result) {
533
            throw new RuntimeException('Failed writing to rebuild file');
534
        }
535
536
        return true;
537
    }
538
539
    protected function initComposerAndInstall(): void
540
    {
541
        $relativePath = __DIR__ . '/../../../../doctrine-static-meta/';
542
        $vcsPath      = realpath($relativePath);
543
        if (false === $vcsPath) {
544
            throw new RuntimeException('Failed getting realpath to main project at ' . $relativePath);
545
        }
546
        $namespace    = str_replace('\\', '\\\\', self::TEST_PROJECT_ROOT_NAMESPACE);
547
        $composerJson = <<<JSON
548
{
549
  "require": {
550
    "edmondscommerce/doctrine-static-meta": "dev-%s",
551
    "php": ">=7.2"
552
  },
553
  "repositories": [
554
    {
555
      "type": "vcs",
556
      "url": "%s"
557
    },
558
    {
559
      "type": "vcs",
560
      "url": "https://github.com/edmondscommerce/Faker.git"
561
    }
562
  ],
563
  "minimum-stability": "stable",
564
  "require-dev": {
565
    "fzaninotto/faker": "dev-dsm-patches@dev",
566
    "edmondscommerce/phpqa": "~1"
567
  },
568
  "autoload": {
569
    "psr-4": {
570
      "$namespace\\\\": [
571
        "src/"
572
      ]
573
    }
574
  },
575
  "autoload-dev": {
576
    "psr-4": {
577
      "$namespace\\\\": [
578
        "tests/"
579
      ]
580
    }
581
  },
582
  "config": {
583
    "bin-dir": "bin",
584
    "preferred-install": {
585
      "edmondscommerce/*": "source",
586
      "fzaninotto/faker": "source",
587
      "*": "dist"
588
    },
589
    "optimize-autoloader": true
590
  }
591
}
592
JSON;
593
594
        $gitCurrentBranchName = trim(shell_exec("git branch | grep '*' | cut -d ' ' -f 2-"));
595
        echo "446: \$gitCurrentBranchName $gitCurrentBranchName";
596
        if (\ts\stringContains($gitCurrentBranchName, 'HEAD detached at')) {
597
            $gitCurrentBranchName = trim(str_replace('HEAD detached at', '', $gitCurrentBranchName), " \t\n\r\0\x0B()");
598
            echo "449: \$gitCurrentBranchName $gitCurrentBranchName";
599
        } elseif (\ts\stringContains($gitCurrentBranchName, 'detached from')) {
600
            $gitCurrentBranchName = trim(str_replace('detached from', '', $gitCurrentBranchName), " \t\n\r\0\x0B()");
601
            echo "452: \$gitCurrentBranchName $gitCurrentBranchName";
602
        }
603
        file_put_contents(
604
            $this->workDir . '/composer.json',
605
            sprintf($composerJson, $gitCurrentBranchName, $vcsPath)
606
        );
607
608
        $phpCmd   = $this->isTravis() ? 'php' : 'phpNoXdebug';
609
        $bashCmds = <<<BASH
610
           
611
$phpCmd $(which composer) install \
612
    --prefer-dist
613
614
$phpCmd $(which composer) dump-autoload --optimize
615
616
BASH;
617
        $this->execBash($bashCmds);
618
    }
619
620
    /**
621
     * Runs bash with strict error handling and verbose logging
622
     *
623
     * Will ensure the phpNoXdebugFunction is available and will CD into the correct directory before running commands
624
     *
625
     * Asserts that the command returns with an exit code of 0
626
     *
627
     * Appends to the rebuild file allowing easy rerunning of the commmands in the test project
628
     *
629
     * @param string $bashCmds
630
     *
631
     * @throws Exception
632
     */
633
    protected function execBash(string $bashCmds): void
634
    {
635
        fwrite(STDERR, "\n\t# Executing:\n\t$bashCmds");
636
        $startTime = microtime(true);
637
638
        $fullCmds = '';
639
        if (!$this->isTravis()) {
640
            $fullCmds .= "\n" . self::BASH_PHPNOXDEBUG_FUNCTION . "\n\n";
641
        }
642
        $fullCmds .= "set -e;\n";
643
        $fullCmds .= "cd {$this->workDir};\n";
644
        #$fullCmds .= "exec 2>&1;\n";
645
        $fullCmds .= "$bashCmds\n";
646
647
        $output   = [];
648
        $exitCode = 0;
649
        exec($fullCmds, $output, $exitCode);
650
651
        if (0 !== $exitCode) {
652
            throw new RuntimeException(
653
                "Error running bash commands:\n\nOutput:\n----------\n\n"
654
                . implode("\n", $output)
655
                . "\n\nCommands:\n----------\n"
656
                . str_replace(
657
                    "\n",
658
                    "\n\t",
659
                    "\n$bashCmds"
660
                ) . "\n\n"
661
            );
662
        }
663
664
        $seconds = round(microtime(true) - $startTime, 2);
665
        fwrite(STDERR, "\n\t\t#Completed in $seconds seconds\n");
666
    }
667
668
    /**
669
     * Generate all test entities
670
     *
671
     * @return array
672
     */
673
    protected function generateEntities(): array
674
    {
675
        foreach (self::TEST_ENTITIES as $entityFqn) {
676
            $this->generateEntity($entityFqn);
677
        }
678
679
        return self::TEST_ENTITIES;
680
    }
681
682
    protected function generateEntity(string $entityFqn): void
683
    {
684
        $namespace   = self::TEST_PROJECT_ROOT_NAMESPACE;
685
        $doctrineCmd = <<<DOCTRINE
686
 dsm:generate:entity \
687
    --project-root-namespace="{$namespace}" \
688
    --entity-fully-qualified-name="{$entityFqn}"
689
DOCTRINE;
690
        $this->execDoctrine($doctrineCmd);
691
    }
692
693
    protected function execDoctrine(string $doctrineCmd): void
694
    {
695
        $phpCmd  = $this->isTravis() ? 'php' : 'phpNoXdebug';
696
        $bash    = <<<BASH
697
$phpCmd bin/doctrine $doctrineCmd    
698
BASH;
699
        $error   = false;
700
        $message = '';
701
        try {
702
            $this->execBash($bash);
703
        } catch (RuntimeException $e) {
704
            $this->addToRebuildFile("\n\n#The command below failed...\n\n");
705
            $error   = true;
706
            $message = $e->getMessage();
707
        }
708
        $this->addToRebuildFile($bash);
709
        self::assertFalse($error, $message);
710
    }
711
712
    /**
713
     * Generate all test relations
714
     *
715
     * @return void
716
     */
717
    protected function generateRelations(): void
718
    {
719
        foreach (self::TEST_RELATIONS as $relation) {
720
            $this->setRelation(...$relation);
0 ignored issues
show
Bug introduced by
The call to EdmondsCommerce\Doctrine...argeTest::setRelation() has too few arguments starting with type. ( Ignorable by Annotation )

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

720
            $this->/** @scrutinizer ignore-call */ 
721
                   setRelation(...$relation);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
721
        }
722
    }
723
724
    protected function setRelation(string $entity1, string $type, string $entity2, bool $required): void
725
    {
726
        $namespace = self::TEST_PROJECT_ROOT_NAMESPACE;
727
        $this->execDoctrine(
728
            <<<DOCTRINE
729
dsm:set:relation \
730
    --project-root-path="{$this->workDir}" \
731
    --project-root-namespace="{$namespace}" \
732
    --entity1="{$entity1}" \
733
    --hasType="{$type}" \
734
    --entity2="{$entity2}" \
735
    --required-relation="{$required}"
736
DOCTRINE
737
        );
738
    }
739
740
    /**
741
     * Generate one field per common type
742
     *
743
     * @return void
744
     */
745
    protected function generateFields(): void
746
    {
747
        foreach (MappingHelper::COMMON_TYPES as $type) {
748
            $fieldFqn = self::TEST_FIELD_TRAIT_NAMESPACE . '\\' . ucfirst($type);
749
            $this->generateField($fieldFqn, $type);
750
        }
751
        foreach (self::UNIQUEABLE_FIELD_TYPES as $uniqueableType) {
752
            $fieldFqn = self::TEST_FIELD_TRAIT_NAMESPACE . '\\Unique' . ucwords($uniqueableType);
753
            $this->generateField($fieldFqn, $uniqueableType, null, true);
754
        }
755
        foreach (self::DUPLICATE_SHORT_NAME_FIELDS as $duplicateShortName) {
756
            $this->generateField(...$duplicateShortName);
0 ignored issues
show
Bug introduced by
The call to EdmondsCommerce\Doctrine...geTest::generateField() has too few arguments starting with type. ( Ignorable by Annotation )

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

756
            $this->/** @scrutinizer ignore-call */ 
757
                   generateField(...$duplicateShortName);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
757
        }
758
    }
759
760
    /**
761
     * @param string     $propertyName
762
     * @param string     $type
763
     * @param mixed|null $default
764
     * @param bool       $isUnique
765
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
766
     */
767
    protected function generateField(
768
        string $propertyName,
769
        string $type,
770
        $default = null,
771
        bool $isUnique = false
772
    ): void {
773
        $namespace   = self::TEST_PROJECT_ROOT_NAMESPACE;
774
        $doctrineCmd = <<<DOCTRINE
775
 dsm:generate:field \
776
    --project-root-path="{$this->workDir}" \
777
    --project-root-namespace="{$namespace}" \
778
    --field-fully-qualified-name="{$propertyName}" \
779
    --field-property-doctrine-type="{$type}"
780
DOCTRINE;
781
        if (null !== $default) {
782
            $doctrineCmd .= ' --' . GenerateFieldCommand::OPT_DEFAULT_VALUE . '="' . $default . '"' . "\\\n";
783
        }
784
        if (true === $isUnique) {
785
            $doctrineCmd .= ' --' . GenerateFieldCommand::OPT_IS_UNIQUE . "\\\n";
786
        }
787
        $this->execDoctrine($doctrineCmd);
788
    }
789
790
    /**
791
     * Set each field type on each entity type
792
     *
793
     * @param array $entities
794
     * @param array $fields
795
     *
796
     * @return void
797
     */
798
    protected function setFields(array $entities, array $fields): void
799
    {
800
        foreach ($entities as $entityFqn) {
801
            foreach ($fields as $fieldFqn) {
802
                $this->setField($entityFqn, $fieldFqn);
803
            }
804
        }
805
    }
806
807
    protected function setField(string $entityFqn, string $fieldFqn): void
808
    {
809
        $namespace   = self::TEST_PROJECT_ROOT_NAMESPACE;
810
        $doctrineCmd = <<<DOCTRINE
811
 dsm:set:field \
812
    --project-root-path="{$this->workDir}" \
813
    --project-root-namespace="{$namespace}" \
814
    --entity="{$entityFqn}" \
815
    --field="{$fieldFqn}"
816
DOCTRINE;
817
        $this->execDoctrine($doctrineCmd);
818
    }
819
820
    /**
821
     * @return array
822
     * @SuppressWarnings(PHPMD.StaticAccess)
823
     */
824
    protected function getFieldFqns(): array
825
    {
826
        $fieldFqns = [];
827
        foreach (MappingHelper::COMMON_TYPES as $type) {
828
            $fieldFqns[] = self::TEST_FIELD_TRAIT_NAMESPACE . Inflector::classify($type) . 'FieldTrait';
829
        }
830
        foreach (self::UNIQUEABLE_FIELD_TYPES as $type) {
831
            $fieldFqns[] = self::TEST_FIELD_TRAIT_NAMESPACE . Inflector::classify('unique_' . $type) . 'FieldTrait';
832
        }
833
834
        return $fieldFqns;
835
    }
836
837
    protected function setTheDuplicateNamedFields(array $entities): void
838
    {
839
        foreach ($entities as $k => $entityFqn) {
840
            $fieldKey = ($k % 2 === 0) ? 0 : 1;
841
            $this->setField($entityFqn, self::DUPLICATE_SHORT_NAME_FIELDS[$fieldKey][0]);
842
        }
843
    }
844
845
    protected function setEmbeddable(string $entityFqn, string $embeddableTraitFqn): void
846
    {
847
        $doctrineCmd = <<<DOCTRINE
848
 dsm:set:embeddable \
849
    --entity="{$entityFqn}" \
850
    --embeddable="{$embeddableTraitFqn}"
851
DOCTRINE;
852
        $this->execDoctrine($doctrineCmd);
853
    }
854
855
    protected function generateEmbeddable(string $archetypeFqn, string $newClassName): void
856
    {
857
        $doctrineCmd = <<<DOCTRINE
858
dsm:generate:embeddable \
859
    --classname="{$newClassName}" \
860
    --archetype="{$archetypeFqn}"
861
DOCTRINE;
862
        $this->execDoctrine($doctrineCmd);
863
    }
864
865
    protected function removeUnusedRelations(): void
866
    {
867
        $namespace   = self::TEST_PROJECT_ROOT_NAMESPACE;
868
        $doctrineCmd = <<<DOCTRINE
869
 dsm:remove:unusedRelations \
870
    --project-root-namespace="{$namespace}"
871
DOCTRINE;
872
        $this->execDoctrine($doctrineCmd);
873
    }
874
875
    /**
876
     * @SuppressWarnings(PHPMD.Superglobals)
877
     * @throws Exception
878
     */
879
    public function testRunTests(): void
880
    {
881
        if ($this->isQuickTests()) {
882
            $this->markTestSkipped('Quick tests is enabled');
883
        }
884
        /** @lang bash */
885
        $bashCmds = <<<BASH
886
887
set +x
888
889
echo "
890
891
--------------------------------------------------
892
STARTS Running Tests In {$this->workDir}
893
--------------------------------------------------
894
895
"
896
897
#Prevent the retry tool dialogue etc
898
export CI=true
899
900
bash bin/qa
901
902
echo "
903
904
--------------------------------------------------
905
DONE Running Tests In {$this->workDir}
906
--------------------------------------------------
907
908
"
909
set -x
910
BASH;
911
        $this->execBash($bashCmds);
912
    }
913
}
914