Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tests: add new RootPackageHandlingTest + bugfix #175

Merged
merged 3 commits into from May 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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