Skip to content

Commit

Permalink
Added support for the base-less tests and symlinks.
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexSkrypnyk committed Jan 21, 2025
1 parent 8822a5e commit ceaa16e
Show file tree
Hide file tree
Showing 31 changed files with 265 additions and 19 deletions.
24 changes: 14 additions & 10 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
# Ignore files for distribution archives.

/.editorconfig export-ignore
/.gitattributes export-ignore
/.github export-ignore
/.gitignore export-ignore
/logo.png export-ignore
/phpcs.xml export-ignore
/phpmd.xml export-ignore
/phpstan.neon export-ignore
/rector.php export-ignore
/renovate.json export-ignore
/.editorconfig export-ignore
/.gitattributes export-ignore
/.github export-ignore
/.gitignore export-ignore
/logo.png export-ignore
/phpcs.xml export-ignore
/phpmd.xml export-ignore
/phpstan.neon export-ignore
/phpunit.xml export-ignore
/rector.php export-ignore
/renovate.json export-ignore
/tests/phpunit/Fixtures export-ignore
/tests/phpunit/Functional/CreateProjectTest.php export-ignore
/tests/phpunit/Unit export-ignore
2 changes: 1 addition & 1 deletion phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
displayDetailsOnTestsThatTriggerNotices="true"
displayDetailsOnTestsThatTriggerDeprecations="true"
displayDetailsOnPhpunitDeprecations="true">
<testsuites>
<testsuites>
<testsuite name="default">
<directory>tests/phpunit</directory>
</testsuite>
Expand Down
Empty file removed tests/.gitkeep
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ dir2_flat/*

# Ignore all files and subdirectories in the directory.
dir3_subdirs/*
dir3_subdirs_symlink/*
# But include sub-sub-directory.
!dir3_subdirs/dir32-unignored/
!dir3_subdirs_symlink/dir32-unignored/

# Ignore directory and all subdirectories.
dir4_full_ignore/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d32f2l1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d32f2l1
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
d32f2l1
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
vendor/
^composer.lock
# Ignore the contents of these 2 Customizer's own files which will not exist in
# the template project. We cannot symink them, because PHPUnit excludes the real
# files from coverage.
^CustomizeCommand.php
^Plugin.php
^tests/phpunit/Functional/CustomizerTestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
The contents of this file is ignored when compared with an actually produced
file in tests.
See .ignorecontent file
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
The contents of this file is ignored when compared with an actually produced
file in tests.
See .ignorecontent file
64 changes: 64 additions & 0 deletions tests/phpunit/Fixtures/install_no_base/post_install/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"name": "testorg/testpackage",
"description": "Test description",
"license": "MIT",
"type": "composer-plugin",
"authors": [
{
"name": "Alex Skrypnyk",
"email": "[email protected]",
"homepage": "https://alexskrypnyk.com",
"role": "Maintainer"
}
],
"homepage": "https://github.com/alexskrypnyk/customizer",
"support": {
"issues": "https://github.com/alexskrypnyk/customizer/issues",
"source": "https://github.com/alexskrypnyk/customizer"
},
"require": {
"php": ">=8.2",
"composer-plugin-api": "^2.0"
},
"require-dev": {
"composer/composer": "^2.7",
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
"drupal/coder": "^8.3",
"ergebnis/composer-normalize": "^2.42",
"phpcompatibility/php-compatibility": "^9.3",
"phpstan/phpstan": "^2",
"phpunit/phpunit": "^11.1",
"rector/rector": "^2"
},
"prefer-stable": true,
"autoload": {
"psr-4": {
"AlexSkrypnyk\\Customizer\\": ""
}
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true,
"ergebnis/composer-normalize": true
},
"sort-packages": true
},
"extra": {
"class": "AlexSkrypnyk\\Customizer\\Plugin",
"customizer": true
},
"scripts": {
"lint": [
"phpcs",
"phpstan",
"rector --clear-cache --dry-run"
],
"lint-fix": [
"rector --clear-cache",
"phpcbf"
],
"reset": "rm -Rf vendor vendor-bin composer.lock",
"test": "phpunit --no-coverage",
"test-coverage": "phpunit"
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
The contents of this file is ignored when compared with an actually produced
file in tests.
See .ignorecontent file
27 changes: 27 additions & 0 deletions tests/phpunit/Functional/CreateProjectTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,33 @@ public function testInstall(): void {
$this->assertComposerLockUpToDate();
}

/**
* Assert that fixtures without 'base' use the current directory as the base.
*/
#[Group('install')]
public function testInstallNoBase(): void {
static::customizerSetAnswers([
'testorg/testpackage',
'Test description',
'MIT',
self::TUI_ANSWER_NOTHING,
]);

$this->runComposerCreateProject();

$this->assertComposerCommandSuccessOutputContains('Welcome to the "alexskrypnyk/customizer" project customizer');
$this->assertComposerCommandSuccessOutputContains('Name');
$this->assertComposerCommandSuccessOutputContains('testorg/testpackage');
$this->assertComposerCommandSuccessOutputContains('Description');
$this->assertComposerCommandSuccessOutputContains('Test description');
$this->assertComposerCommandSuccessOutputContains('License');
$this->assertComposerCommandSuccessOutputContains('MIT');
$this->assertComposerCommandSuccessOutputContains('Project was customized');

$this->assertFixtureDirectoryEqualsSut('post_install');
$this->assertComposerLockUpToDate();
}

#[RunInSeparateProcess]
#[Group('install')]
public function testInstallAdditionalCleanup(): void {
Expand Down
113 changes: 107 additions & 6 deletions tests/phpunit/Functional/CustomizerTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@
* Base class for functional tests.
*
* This class is intended to be distributed with the Customizer package and
* used in consumer site's tests to allow easy testing of the integrated
* used in consumer package's tests to allow easy testing of the integrated
* Customizer command.
*
* Extend this class in your test case to get access to the Customizer command
* test runner and the necessary helper methods.
*/
class CustomizerTestCase extends TestCase {
abstract class CustomizerTestCase extends TestCase {

/**
* TUI answer to indicate that the user did not provide any input.
Expand Down Expand Up @@ -193,10 +193,29 @@ protected function initLocations(string $cwd, ?callable $cb = NULL): void {
static::$fixtures .= DIRECTORY_SEPARATOR . $this->dataName();
}

// Copy the 'base' fixture files to the repository if they were provided for
// this test.
// Copy the 'base' fixture to the 'local' fixture.
if (is_dir(static::$fixtures)) {
$this->fs->mirror(static::$fixtures . DIRECTORY_SEPARATOR . 'base', static::$repo);
$base_dir = static::$fixtures . DIRECTORY_SEPARATOR . 'base';

// Use this project's root directory as a base directory if the 'base'
// fixture was not provided. This allows to use the current project's
// files as a 'base' for the test.
//
// @note Composer uses .gitattributes to determine which files to include
// in the package when running `create-project`, so add the files that are
// not intended to be used in the consumer to the .gitattributes file
// of this project.
$allowed_files = [];
if (!is_dir($base_dir)) {
$base_dir = static::$root;
// Only use the git-tracked files to replicate a "clean" project as it
// would be seen by Composer at the code repository.
// Make sure to commit the changes locally before running
// the tests (even as a temporary commit).
$allowed_files = $this->getTrackedFiles($base_dir);
}

$this->mirrorFiltered($base_dir, static::$repo, $allowed_files);
}

if ($cb !== NULL && $cb instanceof \Closure) {
Expand Down Expand Up @@ -302,6 +321,87 @@ protected function runComposerCreateProject(array $options = []): void {
$this->tester->run($options + $defaults);
}

/**
* Get the tracked files in a Git repository.
*
* @param string $dir
* The directory to check.
*
* @return array<string>
* The list of tracked files.
*
* @throws \RuntimeException
* If the directory is not a Git repository.
*/
protected function getTrackedFiles(string $dir): array {
if (!is_dir($dir . '/.git')) {
throw new \RuntimeException("The directory is not a Git repository.");
}

$tracked_files = [];
$output = [];
$code = 0;
exec(sprintf('git --git-dir=%s --work-tree=%s ls-files', escapeshellarg($dir . '/.git'), escapeshellarg($dir)), $output, $code);
if ($code !== 0) {
throw new \RuntimeException("Failed to retrieve tracked files using git ls-files.");
}

foreach ($output as $file) {
$tracked_files[] = $dir . DIRECTORY_SEPARATOR . $file;
}

return $tracked_files;
}

/**
* Mirror a directory with filtering.
*
* @param string $src
* The source directory.
* @param string $dst
* The destination directory.
* @param array<string> $allowed_files
* The list of allowed files.
*/
protected function mirrorFiltered(string $src, string $dst, array $allowed_files = []): void {
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($src, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
);

foreach ($files as $file) {
if (!$file instanceof \SplFileInfo) {
continue;
}

if (!empty($allowed_files) && !in_array($file->getPathname(), $allowed_files, TRUE)) {
continue;
}

$relative_path = substr($file->getPathname(), strlen($src) + 1);
$target_path = $dst . DIRECTORY_SEPARATOR . $relative_path;

if (!is_dir(dirname($target_path))) {
mkdir(dirname($target_path), 0755, TRUE);
}

// Always copy the contents of the file as a regular file.
if (is_link($file->getPathname())) {
// Resolve the symlink to the actual file content.
$resolved_path = realpath($file->getPathname());
if ($resolved_path !== FALSE) {
copy($resolved_path, $target_path);
}
else {
throw new \RuntimeException("Failed to resolve symlink for: " . $file->getPathname());
}
}
else {
// Copy regular files.
copy($file->getPathname(), $target_path);
}
}
}

/**
* Assert that the Composer lock file is up to date.
*/
Expand Down Expand Up @@ -500,6 +600,7 @@ protected function assertDirectoriesEqual(string $dir1, string $dir2, ?callable
if (str_ends_with($pattern, DIRECTORY_SEPARATOR)) {
return str_starts_with($path, rtrim($pattern, DIRECTORY_SEPARATOR));
}

// Match direct children (e.g., "dir/*").
if (str_contains($pattern, '/*')) {
$parent_dir = rtrim($pattern, '/*') . DIRECTORY_SEPARATOR;
Expand All @@ -514,7 +615,7 @@ protected function assertDirectoriesEqual(string $dir1, string $dir2, ?callable
// Get the files in the directories.
$get_files = static function (string $dir, array $rules, callable $match_path, ?callable $match_content): array {
$files = [];
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS));
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS));
foreach ($iterator as $file) {
if (!$file instanceof \SplFileInfo) {
continue;
Expand Down
4 changes: 2 additions & 2 deletions tests/phpunit/Unit/FilesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ public static function dataProviderReplaceInPath(): array {
}

#[DataProvider('dataProviderreplaceInPathBetweenMarkers')]
public function testreplaceInPathBetweenMarkers(string $path, array $before, string $search, string $replace, string $start, string $end, array $after): void {
public function testReplaceInPathBetweenMarkers(string $path, array $before, string $search, string $replace, string $start, string $end, array $after): void {
$this->createFileTree(static::$sut, $before);

CustomizeCommand::replaceInPathBetweenMarkers(
Expand All @@ -235,7 +235,7 @@ public function testreplaceInPathBetweenMarkers(string $path, array $before, str
}

/**
* Data provider for testreplaceInPathBetweenMarkers.
* Data provider for testReplaceInPathBetweenMarkers.
*
* @return array
* The data.
Expand Down
Loading

0 comments on commit ceaa16e

Please sign in to comment.