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
Push — master ( 84892e...749633 )
by joseph
25:37 queued 22:57
created

FullProjectBuildLargeTest::addToRebuildFile()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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

596
            $this->/** @scrutinizer ignore-call */ 
597
                   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...
597
        }
598
    }
599
600
    protected function setRelation(string $entity1, string $type, string $entity2): void
601
    {
602
        $namespace = self::TEST_PROJECT_ROOT_NAMESPACE;
603
        $this->execDoctrine(
604
            <<<DOCTRINE
605
dsm:set:relation \
606
    --project-root-path="{$this->workDir}" \
607
    --project-root-namespace="{$namespace}" \
608
    --entity1="{$entity1}" \
609
    --hasType="{$type}" \
610
    --entity2="{$entity2}"    
611
DOCTRINE
612
        );
613
    }
614
615
    /**
616
     * Generate one field per common type
617
     *
618
     * @return void
619
     */
620
    protected function generateFields(): void
621
    {
622
        foreach (MappingHelper::COMMON_TYPES as $type) {
623
            $fieldFqn = self::TEST_FIELD_TRAIT_NAMESPACE . '\\' . $type;
624
            $this->generateField($fieldFqn, $type);
625
        }
626
        foreach (self::UNIQUEABLE_FIELD_TYPES as $uniqueableType) {
627
            $fieldFqn = self::TEST_FIELD_TRAIT_NAMESPACE . '\\Unique' . ucwords($uniqueableType);
628
            $this->generateField($fieldFqn, $uniqueableType, null, true);
629
        }
630
        foreach (self::DUPLICATE_SHORT_NAME_FIELDS as $duplicateShortName) {
631
            $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

631
            $this->/** @scrutinizer ignore-call */ 
632
                   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...
632
        }
633
    }
634
635
    /**
636
     * @param string     $propertyName
637
     * @param string     $type
638
     * @param mixed|null $default
639
     * @param bool       $isUnique
640
     * @SuppressWarnings(PHPMD.BooleanArgumentFlag)
641
     */
642
    protected function generateField(
643
        string $propertyName,
644
        string $type,
645
        $default = null,
646
        bool $isUnique = false
647
    ): void {
648
        $namespace   = self::TEST_PROJECT_ROOT_NAMESPACE;
649
        $doctrineCmd = <<<DOCTRINE
650
 dsm:generate:field \
651
    --project-root-path="{$this->workDir}" \
652
    --project-root-namespace="{$namespace}" \
653
    --field-fully-qualified-name="{$propertyName}" \
654
    --field-property-doctrine-type="{$type}"
655
DOCTRINE;
656
        if (null !== $default) {
657
            $doctrineCmd .= ' --' . GenerateFieldCommand::OPT_DEFAULT_VALUE . '="' . $default . '"' . "\\\n";
658
        }
659
        if (true === $isUnique) {
660
            $doctrineCmd .= ' --' . GenerateFieldCommand::OPT_IS_UNIQUE . "\\\n";
661
        }
662
        $this->execDoctrine($doctrineCmd);
663
    }
664
665
    /**
666
     * Set each field type on each entity type
667
     *
668
     * @param array $entities
669
     * @param array $fields
670
     *
671
     * @return void
672
     */
673
    protected function setFields(array $entities, array $fields): void
674
    {
675
        foreach ($entities as $entityFqn) {
676
            foreach ($fields as $fieldFqn) {
677
                $this->setField($entityFqn, $fieldFqn);
678
            }
679
        }
680
    }
681
682
    protected function setField(string $entityFqn, string $fieldFqn): void
683
    {
684
        $namespace   = self::TEST_PROJECT_ROOT_NAMESPACE;
685
        $doctrineCmd = <<<DOCTRINE
686
 dsm:set:field \
687
    --project-root-path="{$this->workDir}" \
688
    --project-root-namespace="{$namespace}" \
689
    --entity="{$entityFqn}" \
690
    --field="{$fieldFqn}"
691
DOCTRINE;
692
        $this->execDoctrine($doctrineCmd);
693
    }
694
695
    /**
696
     * @return array
697
     * @SuppressWarnings(PHPMD.StaticAccess)
698
     */
699
    protected function getFieldFqns(): array
700
    {
701
        $fieldFqns = [];
702
        foreach (MappingHelper::COMMON_TYPES as $type) {
703
            $fieldFqns[] = self::TEST_FIELD_TRAIT_NAMESPACE . Inflector::classify($type) . 'FieldTrait';
704
        }
705
        foreach (self::UNIQUEABLE_FIELD_TYPES as $type) {
706
            $fieldFqns[] = self::TEST_FIELD_TRAIT_NAMESPACE . Inflector::classify('unique_' . $type) . 'FieldTrait';
707
        }
708
709
        return $fieldFqns;
710
    }
711
712
    protected function setTheDuplicateNamedFields(array $entities)
713
    {
714
        foreach ($entities as $k => $entityFqn) {
715
            $fieldKey = ($k % 2 === 0) ? 0 : 1;
716
            $this->setField($entityFqn, self::DUPLICATE_SHORT_NAME_FIELDS[$fieldKey][0]);
717
        }
718
    }
719
720
    protected function setEmbeddable(string $entityFqn, string $embeddableTraitFqn): void
721
    {
722
        $doctrineCmd = <<<DOCTRINE
723
 dsm:set:embeddable \
724
    --entity="{$entityFqn}" \
725
    --embeddable="{$embeddableTraitFqn}"
726
DOCTRINE;
727
        $this->execDoctrine($doctrineCmd);
728
    }
729
730
    protected function generateEmbeddable(string $archetypeFqn, string $newClassName): void
731
    {
732
        $doctrineCmd = <<<DOCTRINE
733
dsm:generate:embeddable \
734
    --classname="{$newClassName}" \
735
    --archetype="{$archetypeFqn}"
736
DOCTRINE;
737
        $this->execDoctrine($doctrineCmd);
738
    }
739
740
    protected function removeUnusedRelations(): void
741
    {
742
        $namespace   = self::TEST_PROJECT_ROOT_NAMESPACE;
743
        $doctrineCmd = <<<DOCTRINE
744
 dsm:remove:unusedRelations \
745
    --project-root-namespace="{$namespace}"
746
DOCTRINE;
747
        $this->execDoctrine($doctrineCmd);
748
    }
749
750
    /**
751
     * @SuppressWarnings(PHPMD.Superglobals)
752
     * @throws \Exception
753
     */
754
    public function testRunTests(): void
755
    {
756
        $this->assertWeCheckAllPossibleRelationTypes();
757
        if ($this->isQuickTests()) {
758
            $this->markTestSkipped('Quick tests is enabled');
759
        }
760
        /** @lang bash */
761
        $bashCmds = <<<BASH
762
763
set +x
764
765
echo "
766
767
--------------------------------------------------
768
STARTS Running Tests In {$this->workDir}
769
--------------------------------------------------
770
771
"
772
773
#Prevent the retry tool dialogue etc
774
export CI=true
775
776
bash -x bin/qa
777
778
echo "
779
780
--------------------------------------------------
781
DONE Running Tests In {$this->workDir}
782
--------------------------------------------------
783
784
"
785
set -x
786
BASH;
787
        $this->execBash($bashCmds);
788
    }
789
790
    protected function assertWeCheckAllPossibleRelationTypes(): void
791
    {
792
        $included = $toTest = [];
793
        foreach (RelationsGenerator::HAS_TYPES as $hasType) {
794
            if (0 === \strpos($hasType, RelationsGenerator::PREFIX_INVERSE)) {
795
                continue;
796
            }
797
            $toTest[$hasType] = true;
798
        }
799
        \ksort($toTest);
800
        foreach (self::TEST_RELATIONS as $relation) {
801
            $included[$relation[1]] = true;
802
        }
803
        \ksort($included);
804
        $missing = \array_diff(\array_keys($toTest), \array_keys($included));
805
        self::assertEmpty(
806
            $missing,
807
            'We are not testing all relation types - '
808
            . 'these ones have not been included: '
809
            . print_r($missing, true)
810
        );
811
    }
812
}
813