Skip to content

Commit aa6586a

Browse files
MC-5374: Adds static test that checks PHP compatibility
1 parent 5a82cc9 commit aa6586a

File tree

6 files changed

+279
-8
lines changed

6 files changed

+279
-8
lines changed

composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,13 @@
8484
},
8585
"require-dev": {
8686
"allure-framework/allure-phpunit": "~1.2.0",
87+
"dealerdirect/phpcodesniffer-composer-installer": "^0.5.0",
8788
"friendsofphp/php-cs-fixer": "~2.14.0",
8889
"lusitanian/oauth": "~0.8.10",
8990
"magento/magento-coding-standard": "~4.0.0",
9091
"magento/magento2-functional-testing-framework": "2.5.0",
9192
"pdepend/pdepend": "2.5.2",
93+
"phpcompatibility/php-compatibility": "^9.3",
9294
"phpmd/phpmd": "@stable",
9395
"phpunit/phpunit": "~6.5.0",
9496
"sebastian/phpcpd": "~3.0.0",

composer.lock

Lines changed: 126 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\TestFramework\CodingStandard\Tool;
9+
10+
/**
11+
* Defines an interface for compatibility checks against a specific version.
12+
*/
13+
interface CompatibilityInterface
14+
{
15+
/**
16+
* Sets the version against which to test code.
17+
*
18+
* @param string $version
19+
* @return void
20+
*/
21+
public function setTestVersion(string $version): void;
22+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\TestFramework\CodingStandard\Tool;
9+
10+
/**
11+
* Implements a wrapper around `phpcs` for usage with the `PHPCompatibility` sniffs against a specific PHP version.
12+
*/
13+
class PhpCompatibility extends CodeSniffer implements CompatibilityInterface
14+
{
15+
/**
16+
* Sets the version against which to test code.
17+
*
18+
* @param string $version
19+
* @return void
20+
*/
21+
public function setTestVersion(string $version): void
22+
{
23+
\PHP_CodeSniffer\Config::setConfigData('testVersion', $version);
24+
}
25+
}

dev/tests/static/testsuite/Magento/Test/Php/LiveCodeTest.php

Lines changed: 91 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88
namespace Magento\Test\Php;
99

1010
use Magento\Framework\App\Utility\Files;
11-
use Magento\Framework\Component\ComponentRegistrar;
1211
use Magento\TestFramework\CodingStandard\Tool\CodeMessDetector;
1312
use Magento\TestFramework\CodingStandard\Tool\CodeSniffer;
1413
use Magento\TestFramework\CodingStandard\Tool\CodeSniffer\Wrapper;
1514
use Magento\TestFramework\CodingStandard\Tool\CopyPasteDetector;
15+
use Magento\TestFramework\CodingStandard\Tool\PhpCompatibility;
1616
use PHPMD\TextUI\Command;
1717

1818
/**
@@ -164,6 +164,7 @@ private static function getFilesFromListFile($listsBaseDir, $listFilePattern, $n
164164
$listFiles = glob($globFilesListPattern);
165165
if (!empty($listFiles)) {
166166
foreach ($listFiles as $listFile) {
167+
// phpcs:ignore Magento2.Performance.ForeachArrayMerge.ForeachArrayMerge
167168
$filesDefinedInList = array_merge(
168169
$filesDefinedInList,
169170
file($listFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES)
@@ -218,9 +219,12 @@ private static function filterFiles(array $files, array $allowedFileTypes, array
218219
};
219220
} else {
220221
$allowedDirectories = array_map('realpath', $allowedDirectories);
221-
usort($allowedDirectories, function ($dir1, $dir2) {
222-
return strlen($dir1) - strlen($dir2);
223-
});
222+
usort(
223+
$allowedDirectories,
224+
function ($dir1, $dir2) {
225+
return strlen($dir1) - strlen($dir2);
226+
}
227+
);
224228
$fileIsInAllowedDirectory = function ($file) use ($allowedDirectories) {
225229
foreach ($allowedDirectories as $directory) {
226230
if (strpos($file, $directory) === 0) {
@@ -260,18 +264,70 @@ private function getFullWhitelist()
260264
}
261265
}
262266

267+
/**
268+
* Retrieves the lowest PHP version specified in <kbd>composer.json</var> of project.
269+
*
270+
* @return string
271+
*/
272+
private function getLowestPhpVersion(): string
273+
{
274+
$composerJson = json_decode(file_get_contents(BP . '/composer.json'), true);
275+
$phpVersion = '7.0';
276+
277+
if (isset($composerJson['require']['php'])) {
278+
$versions = explode('||', $composerJson['require']['php']);
279+
280+
//normalize version constraints
281+
foreach ($versions as $key => $version) {
282+
$version = ltrim($version, '^~');
283+
$version = str_replace('*', '999', $version);
284+
285+
$versions[$key] = $version;
286+
}
287+
288+
//sort versions
289+
usort($versions, 'version_compare');
290+
291+
$lowestVersion = array_shift($versions);
292+
$versionParts = explode('.', $lowestVersion);
293+
$phpVersion = sprintf('%s.%s', $versionParts[0], $versionParts[1] ?? '0');
294+
}
295+
296+
return $phpVersion;
297+
}
298+
299+
/**
300+
* Returns whether a full scan was requested.
301+
*
302+
* This can be set in the `phpunit.xml` used to run these test cases, by setting the constant
303+
* `TESTCODESTYLE_IS_FULL_SCAN` to `1`, e.g.:
304+
* ```xml
305+
* <php>
306+
* <!-- TESTCODESTYLE_IS_FULL_SCAN - specify if full scan should be performed for test code style test -->
307+
* <const name="TESTCODESTYLE_IS_FULL_SCAN" value="0"/>
308+
* </php>
309+
* ```
310+
*
311+
* @return bool
312+
*/
313+
private function isFullScan(): bool
314+
{
315+
return defined('TESTCODESTYLE_IS_FULL_SCAN') && TESTCODESTYLE_IS_FULL_SCAN === '1';
316+
}
317+
263318
/**
264319
* Test code quality using phpcs
265320
*/
266321
public function testCodeStyle()
267322
{
268-
$isFullScan = defined('TESTCODESTYLE_IS_FULL_SCAN') && TESTCODESTYLE_IS_FULL_SCAN === '1';
269323
$reportFile = self::$reportDir . '/phpcs_report.txt';
270324
if (!file_exists($reportFile)) {
271325
touch($reportFile);
272326
}
273327
$codeSniffer = new CodeSniffer('Magento', $reportFile, new Wrapper());
274-
$result = $codeSniffer->run($isFullScan ? $this->getFullWhitelist() : self::getWhitelist(['php', 'phtml']));
328+
$result = $codeSniffer->run(
329+
$this->isFullScan() ? $this->getFullWhitelist() : self::getWhitelist(['php', 'phtml'])
330+
);
275331
$report = file_get_contents($reportFile);
276332
$this->assertEquals(
277333
0,
@@ -325,6 +381,7 @@ public function testCopyPaste()
325381

326382
$blackList = [];
327383
foreach (glob(__DIR__ . '/_files/phpcpd/blacklist/*.txt') as $list) {
384+
// phpcs:ignore Magento2.Performance.ForeachArrayMerge.ForeachArrayMerge
328385
$blackList = array_merge($blackList, file($list, FILE_IGNORE_NEW_LINES));
329386
}
330387

@@ -380,4 +437,32 @@ public function testStrictTypes()
380437
. implode(PHP_EOL, $filesMissingStrictTyping)
381438
);
382439
}
440+
441+
/**
442+
* Test for compatibility to lowest PHP version declared in <kbd>composer.json</kbd>.
443+
*/
444+
public function testPhpCompatibility()
445+
{
446+
$targetVersion = $this->getLowestPhpVersion();
447+
$reportFile = self::$reportDir . '/phpcompatibility_report.txt';
448+
$rulesetDir = __DIR__ . '/_files/PHPCompatibilityMagento';
449+
450+
if (!file_exists($reportFile)) {
451+
touch($reportFile);
452+
}
453+
454+
$codeSniffer = new PhpCompatibility($rulesetDir, $reportFile, new Wrapper());
455+
$codeSniffer->setTestVersion($targetVersion);
456+
457+
$result = $codeSniffer->run(
458+
$this->isFullScan() ? $this->getFullWhitelist() : self::getWhitelist(['php', 'phtml'])
459+
);
460+
$report = file_get_contents($reportFile);
461+
462+
$this->assertEquals(
463+
0,
464+
$result,
465+
'PHP Compatibility detected violation(s):' . PHP_EOL . $report
466+
);
467+
}
383468
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
<ruleset name="PHPCompatibilityMagento">
9+
<description>Magento 2 specific ruleset which checks for PHP cross version compatibility.</description>
10+
<rule ref="PHPCompatibility">
11+
<exclude name="PHPCompatibility.Miscellaneous.RemovedAlternativePHPTags.MaybeASPOpenTagFound" />
12+
</rule>
13+
</ruleset>

0 commit comments

Comments
 (0)