Skip to content

Commit

Permalink
Merge pull request #175 from PHPCSStandards/feature/tests-new-rootpac…
Browse files Browse the repository at this point in the history
…kage-handling-test-and-bugfix

Tests: add new RootPackageHandlingTest + bugfix
  • Loading branch information
jrfnl committed May 28, 2022
2 parents 53f18db + 2de5e4b commit 8504433
Show file tree
Hide file tree
Showing 3 changed files with 219 additions and 12 deletions.
27 changes: 15 additions & 12 deletions src/Plugin.php
Expand Up @@ -444,9 +444,17 @@ private function cleanInstalledPaths()
*/
private function updateInstalledPaths()
{
$changes = false;
$changes = false;
$searchPaths = array();

// Add root package only if it has the expected package type.
if (
$this->composer->getPackage() instanceof RootPackageInterface
&& $this->composer->getPackage()->getType() === self::PACKAGE_TYPE
) {
$searchPaths[] = $this->cwd;
}

$searchPaths = array($this->cwd);
$codingStandardPackages = $this->getPHPCodingStandardPackages();
foreach ($codingStandardPackages as $package) {
$installPath = $this->composer->getInstallationManager()->getInstallPath($package);
Expand All @@ -458,6 +466,11 @@ private function updateInstalledPaths()
$searchPaths[] = $installPath;
}

// Nothing to do.
if ($searchPaths === array()) {
return false;
}

$finder = new Finder();
$finder->files()
->depth('<= ' . $this->getMaxDepth())
Expand Down Expand Up @@ -499,9 +512,6 @@ private function updateInstalledPaths()
* Iterates through Composers' local repository looking for valid Coding
* Standard packages.
*
* If the package is the RootPackage (the one the plugin is installed into),
* the package is ignored for now since it needs a different install path logic.
*
* @return array Composer packages containing coding standard(s)
*/
private function getPHPCodingStandardPackages()
Expand All @@ -516,13 +526,6 @@ function (PackageInterface $package) {
}
);

if (
! $this->composer->getPackage() instanceof RootPackageInterface
&& $this->composer->getPackage()->getType() === self::PACKAGE_TYPE
) {
$codingStandardPackages[] = $this->composer->getPackage();
}

return $codingStandardPackages;
}

Expand Down
145 changes: 145 additions & 0 deletions tests/IntegrationTest/RootPackageHandlingTest.php
@@ -0,0 +1,145 @@
<?php

/**
* This file is part of the Dealerdirect PHP_CodeSniffer Standards
* Composer Installer Plugin package.
*
* @copyright 2022 PHPCodeSniffer Composer Installer Contributors
* @license MIT
*/

namespace Dealerdirect\Composer\Plugin\Installers\PHPCodeSniffer\Tests\IntegrationTest;

use Dealerdirect\Composer\Plugin\Installers\PHPCodeSniffer\Plugin;
use Dealerdirect\Composer\Plugin\Installers\PHPCodeSniffer\Tests\TestCase;

/**
* Test that the plugin correctly registers standards found in the root package if it is an external standard,
* but doesn't act on root packages which do not have the correct "type".
*
* This test is about Composer and the plugin, so does not need to be tested against multiple PHPCS versions.
*
* @link https://github.com/PHPCSStandards/composer-installer/issues/19
* @link https://github.com/PHPCSStandards/composer-installer/pull/21
* @link https://github.com/PHPCSStandards/composer-installer/issues/20
* @link https://github.com/PHPCSStandards/composer-installer/pull/25
* @link https://github.com/PHPCSStandards/composer-installer/issues/32
*/
final class RootPackageHandlingTest extends TestCase
{
/**
* Set up test environment.
*/
protected function set_up()
{
$this->createTestEnvironment();
}

/**
* Clean up.
*/
protected function tear_down()
{
$this->removeTestEnvironment();
}

/**
* Test that the plugin registers a standard found in the root package.
*
* @return void
*/
public function testSetInstalledPathsWhenRootPackageIsExternalStandard()
{
/*
* Copy one of the fixtures to the test directory.
*/
$this->recursiveDirectoryCopy(dirname(__DIR__) . '/fixtures/multistandard/', static::$tempLocalPath);

// Install the dependencies, including the plugin.
$command = sprintf('composer install -v --no-ansi --working-dir=%s', escapeshellarg(static::$tempLocalPath));
$this->assertExecute(
$command,
0, // Expected exit code.
Plugin::MESSAGE_RUNNING_INSTALLER, // Expected stdout.
null, // No stderr expectation.
'Failed to install the plugin.'
);

// Verify that the root package standard is registered correctly.
$result = $this->executeCliCommand('"vendor/bin/phpcs" --config-show', static::$tempLocalPath);
$this->assertSame(0, $result['exitcode'], 'Exitcode for "phpcs --config-show" did not match 0 (first run)');

$this->assertSame(
1,
preg_match('`installed_paths:\s+([^\n\r]+)\s+`', $result['stdout'], $matches),
'Could not find the installed paths in the config'
);

// Work around differences in paths being returned between *nix and Windows.
$hasExpectedPath = false;
if ($matches[1] === '../../../') { // Most common.
$hasExpectedPath = true;
} else {
$needle = str_replace(sys_get_temp_dir(), '', static::$tempLocalPath);
if (substr_compare($matches[1], $needle, -\strlen($needle)) === 0) {
$hasExpectedPath = true;
}
}

$this->assertTrue($hasExpectedPath, $matches[1], 'PHPCS configuration does not contain the root standard');
}

/**
* Test that the plugin does not act on root packages which aren't valid PHPCS external standards
* for the purposes of this plugin.
*
* @dataProvider dataInvalidRootPackages
*
* @param string $srcDir The path to the fixture to use as the root package.
*
* @return void
*/
public function testDontSetInstalledPathsForInvalidPackages($srcDir)
{
// Copy the fixture to the test directory.
$this->recursiveDirectoryCopy($srcDir, static::$tempLocalPath);

// Install the dependencies, including the plugin.
$command = sprintf('composer install -v --no-ansi --working-dir=%s', escapeshellarg(static::$tempLocalPath));
$this->assertExecute(
$command,
0, // Expected exit code.
Plugin::MESSAGE_RUNNING_INSTALLER, // Expected stdout.
null, // No stderr expectation.
'Failed to install the plugin.'
);

// Verify that the root package is not registered as a standard.
$result = $this->executeCliCommand('"vendor/bin/phpcs" --config-show', static::$tempLocalPath);
$this->assertSame(0, $result['exitcode'], 'Exitcode for "phpcs --config-show" did not match 0 (first run)');

// As the fixture doesn't contain other external standards, the installed_paths config should not exist.
$this->assertStringNotContainsString(
'installed_paths:',
$result['stdout'],
'Root package registered as an external standard with PHPCS, while it shouldn\'t be'
);
}

/**
* Data provider.
*
* @return array
*/
public function dataInvalidRootPackages()
{
return array(
'Root package without ruleset file' => array(
'srcDir' => dirname(__DIR__) . '/fixtures/no-ruleset/',
),
'Root package with incorrect type' => array(
'srcDir' => dirname(__DIR__) . '/fixtures/incorrect-type/',
),
);
}
}
59 changes: 59 additions & 0 deletions tests/TestCase.php
Expand Up @@ -10,6 +10,8 @@

namespace Dealerdirect\Composer\Plugin\Installers\PHPCodeSniffer\Tests;

use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RuntimeException;
use Yoast\PHPUnitPolyfills\TestCases\TestCase as PolyfillTestCase;

Expand Down Expand Up @@ -552,6 +554,63 @@ protected function createFile($path, $contents = '')
}
}

/**
* Helper function to recursively copy a directory with all its content to another location.
*
* Includes making any potentially needed tweaks to `composer.json` files.
*
* @param string $src Full path to the source directory.
* @param string $dest Full path to the destination directory.
*
* @return void
*
* @throws RuntimeException When either or the passed arguments is not a string.
* @throws RuntimeException When a (sub)directory could not be created.
* @throws RuntimeException When a file could not be copied.
*/
protected function recursiveDirectoryCopy($srcDir, $destDir)
{
if (is_string($srcDir) === false || is_dir($srcDir) === false) {
throw new RuntimeException('The source directory must be a string pointing to an existing directory.');
}

if (is_string($destDir) === false) {
throw new RuntimeException('The destination directory must be a string.');
}

$directory = new RecursiveDirectoryIterator(
realpath($srcDir),
RecursiveDirectoryIterator::SKIP_DOTS | RecursiveDirectoryIterator::UNIX_PATHS
);
$iterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);

foreach ($iterator as $path => $fileInfo) {
$subPath = $iterator->getSubPathname();

// Special case the Composer config.
if ($subPath === 'composer.json') {
$composerConfig = json_decode(file_get_contents($fileInfo->getPathname()), true);
$this->writeComposerJsonFile($composerConfig, $destDir);
continue;
}

$target = $destDir . '/' . $subPath;

if ($fileInfo->isDir()) {
if (mkdir($target, 0766, true) === false || is_dir($target) === false) {
throw new RuntimeException("Failed to create the $target directory for the test");
}
continue;
}

if ($fileInfo->isFile()) {
if (copy($fileInfo->getPathname(), $target) === false || is_file($target) === false) {
throw new RuntimeException("Failed to copy $src to $target ");
}
}
}
}

/**
* Retrieve a list of the standards recognized by PHPCS based on the output of `phpcs -i`.
*
Expand Down

0 comments on commit 8504433

Please sign in to comment.