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 (#152)
by joseph
17:58
created

FullProjectBuildLargeTest::setFields()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

712
            $this->/** @scrutinizer ignore-call */ 
713
                   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...
713
        }
714
    }
715
716
    protected function setRelation(string $entity1, string $type, string $entity2, bool $required): void
717
    {
718
        $namespace = self::TEST_PROJECT_ROOT_NAMESPACE;
719
        $this->execDoctrine(
720
            <<<DOCTRINE
721
dsm:set:relation \
722
    --project-root-path="{$this->workDir}" \
723
    --project-root-namespace="{$namespace}" \
724
    --entity1="{$entity1}" \
725
    --hasType="{$type}" \
726
    --entity2="{$entity2}" \
727
    --required-relation="{$required}"
728
DOCTRINE
729
        );
730
    }
731
732
    /**
733
     * Generate one field per common type
734
     *
735
     * @return void
736
     */
737
    protected function generateFields(): void
738
    {
739
        foreach (MappingHelper::COMMON_TYPES as $type) {
740
            $fieldFqn = self::TEST_FIELD_TRAIT_NAMESPACE . '\\' . $type;
741
            $this->generateField($fieldFqn, $type);
742
        }
743
        foreach (self::UNIQUEABLE_FIELD_TYPES as $uniqueableType) {
744
            $fieldFqn = self::TEST_FIELD_TRAIT_NAMESPACE . '\\Unique' . ucwords($uniqueableType);
745
            $this->generateField($fieldFqn, $uniqueableType, null, true);
746
        }
747
        foreach (self::DUPLICATE_SHORT_NAME_FIELDS as $duplicateShortName) {
748
            $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

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