From 0bbf7940f550551eaed68825e15b546c7419b633 Mon Sep 17 00:00:00 2001 From: Alex Skrypnyk Date: Tue, 21 Jan 2025 10:51:56 +1100 Subject: [PATCH] Added support for the base-less tests and symlinks. --- .gitattributes | 24 ++-- tests/.gitkeep | 0 .../files_equal/dir1/.ignorecontent | 2 + .../files_equal/dir1/d32f2_symlink_deep.txt | 1 + .../dir1/dir1_flat/d1f1_symlink.txt | 1 + .../dir32-unignored/d32f1_symlink.txt | 1 + .../files_equal/dir1/dir3_subdirs_symlink | 1 + .../files_equal/dir1/f2_symlink.txt | 1 + .../files_equal/dir2/d32f2_symlink_deep.txt | 1 + .../dir2/dir1_flat/d1f1_symlink.txt | 1 + .../dir32-unignored/d32f1_symlink.txt | 1 + .../files_equal/dir2/dir3_subdirs_symlink | 1 + .../files_equal/dir2/f2_symlink.txt | 1 + .../dir1/d32f2_symlink_deep.txt | 1 + .../dir1/dir1_flat/d1f1_symlink.txt | 1 + .../dir32-unignored/d32f1_symlink.txt | 1 + .../files_not_equal/dir1/dir3_subdirs_symlink | 1 + .../files_not_equal/dir1/f2_symlink.txt | 1 + .../post_install/.ignorecontent | 5 + .../post_install/CustomizeCommand.php | 1 + .../install_no_base/post_install/Plugin.php | 1 + .../install_no_base/post_install/README.md | 1 + .../install_no_base/post_install/SECURITY.md | 1 + .../post_install/composer.json | 64 ++++++++++ .../post_install/composer.lock | 3 + .../phpunit/Functional/CustomizerTestCase.php | 1 + .../phpunit/Functional/CreateProjectTest.php | 27 +++++ .../phpunit/Functional/CustomizerTestCase.php | 111 +++++++++++++++++- tests/phpunit/Unit/FilesTest.php | 4 +- tests/phpunit/Unit/SelfTest.php | 11 ++ 30 files changed, 254 insertions(+), 17 deletions(-) delete mode 100644 tests/.gitkeep create mode 100644 tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/d32f2_symlink_deep.txt create mode 120000 tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir1_flat/d1f1_symlink.txt create mode 120000 tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/dir32-unignored/d32f1_symlink.txt create mode 120000 tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs_symlink create mode 120000 tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/f2_symlink.txt create mode 100644 tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/d32f2_symlink_deep.txt create mode 120000 tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir1_flat/d1f1_symlink.txt create mode 120000 tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/dir32-unignored/d32f1_symlink.txt create mode 120000 tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs_symlink create mode 120000 tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/f2_symlink.txt create mode 100644 tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/d32f2_symlink_deep.txt create mode 120000 tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir1_flat/d1f1_symlink.txt create mode 120000 tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/dir32-unignored/d32f1_symlink.txt create mode 120000 tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs_symlink create mode 120000 tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/f2_symlink.txt create mode 100644 tests/phpunit/Fixtures/install_no_base/post_install/.ignorecontent create mode 120000 tests/phpunit/Fixtures/install_no_base/post_install/CustomizeCommand.php create mode 120000 tests/phpunit/Fixtures/install_no_base/post_install/Plugin.php create mode 120000 tests/phpunit/Fixtures/install_no_base/post_install/README.md create mode 120000 tests/phpunit/Fixtures/install_no_base/post_install/SECURITY.md create mode 100644 tests/phpunit/Fixtures/install_no_base/post_install/composer.json create mode 100644 tests/phpunit/Fixtures/install_no_base/post_install/composer.lock create mode 120000 tests/phpunit/Fixtures/install_no_base/post_install/tests/phpunit/Functional/CustomizerTestCase.php diff --git a/.gitattributes b/.gitattributes index 183bb72..77cdff1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -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 diff --git a/tests/.gitkeep b/tests/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/.ignorecontent b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/.ignorecontent index 571e5a2..0bb5fe5 100644 --- a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/.ignorecontent +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/.ignorecontent @@ -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/ diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/d32f2_symlink_deep.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/d32f2_symlink_deep.txt new file mode 100644 index 0000000..3b68466 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/d32f2_symlink_deep.txt @@ -0,0 +1 @@ +d32f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir1_flat/d1f1_symlink.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir1_flat/d1f1_symlink.txt new file mode 120000 index 0000000..a9bb98e --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir1_flat/d1f1_symlink.txt @@ -0,0 +1 @@ +d1f1.txt \ No newline at end of file diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/dir32-unignored/d32f1_symlink.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/dir32-unignored/d32f1_symlink.txt new file mode 120000 index 0000000..42c4f2f --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/dir32-unignored/d32f1_symlink.txt @@ -0,0 +1 @@ +d32f1.txt \ No newline at end of file diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs_symlink b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs_symlink new file mode 120000 index 0000000..9a2ddd7 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs_symlink @@ -0,0 +1 @@ +dir3_subdirs \ No newline at end of file diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/f2_symlink.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/f2_symlink.txt new file mode 120000 index 0000000..03c294e --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/f2_symlink.txt @@ -0,0 +1 @@ +f2.txt \ No newline at end of file diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/d32f2_symlink_deep.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/d32f2_symlink_deep.txt new file mode 100644 index 0000000..3b68466 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/d32f2_symlink_deep.txt @@ -0,0 +1 @@ +d32f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir1_flat/d1f1_symlink.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir1_flat/d1f1_symlink.txt new file mode 120000 index 0000000..a9bb98e --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir1_flat/d1f1_symlink.txt @@ -0,0 +1 @@ +d1f1.txt \ No newline at end of file diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/dir32-unignored/d32f1_symlink.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/dir32-unignored/d32f1_symlink.txt new file mode 120000 index 0000000..42c4f2f --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/dir32-unignored/d32f1_symlink.txt @@ -0,0 +1 @@ +d32f1.txt \ No newline at end of file diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs_symlink b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs_symlink new file mode 120000 index 0000000..9a2ddd7 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs_symlink @@ -0,0 +1 @@ +dir3_subdirs \ No newline at end of file diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/f2_symlink.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/f2_symlink.txt new file mode 120000 index 0000000..03c294e --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/f2_symlink.txt @@ -0,0 +1 @@ +f2.txt \ No newline at end of file diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/d32f2_symlink_deep.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/d32f2_symlink_deep.txt new file mode 100644 index 0000000..3b68466 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/d32f2_symlink_deep.txt @@ -0,0 +1 @@ +d32f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir1_flat/d1f1_symlink.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir1_flat/d1f1_symlink.txt new file mode 120000 index 0000000..a9bb98e --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir1_flat/d1f1_symlink.txt @@ -0,0 +1 @@ +d1f1.txt \ No newline at end of file diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/dir32-unignored/d32f1_symlink.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/dir32-unignored/d32f1_symlink.txt new file mode 120000 index 0000000..42c4f2f --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/dir32-unignored/d32f1_symlink.txt @@ -0,0 +1 @@ +d32f1.txt \ No newline at end of file diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs_symlink b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs_symlink new file mode 120000 index 0000000..9a2ddd7 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs_symlink @@ -0,0 +1 @@ +dir3_subdirs \ No newline at end of file diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/f2_symlink.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/f2_symlink.txt new file mode 120000 index 0000000..03c294e --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/f2_symlink.txt @@ -0,0 +1 @@ +f2.txt \ No newline at end of file diff --git a/tests/phpunit/Fixtures/install_no_base/post_install/.ignorecontent b/tests/phpunit/Fixtures/install_no_base/post_install/.ignorecontent new file mode 100644 index 0000000..2ed7d5f --- /dev/null +++ b/tests/phpunit/Fixtures/install_no_base/post_install/.ignorecontent @@ -0,0 +1,5 @@ +.idea/ +.coverage-html/ +.phpunit.cache/ +vendor/ +^composer.lock diff --git a/tests/phpunit/Fixtures/install_no_base/post_install/CustomizeCommand.php b/tests/phpunit/Fixtures/install_no_base/post_install/CustomizeCommand.php new file mode 120000 index 0000000..bed6e19 --- /dev/null +++ b/tests/phpunit/Fixtures/install_no_base/post_install/CustomizeCommand.php @@ -0,0 +1 @@ +../../../../../CustomizeCommand.php \ No newline at end of file diff --git a/tests/phpunit/Fixtures/install_no_base/post_install/Plugin.php b/tests/phpunit/Fixtures/install_no_base/post_install/Plugin.php new file mode 120000 index 0000000..3bcc8f6 --- /dev/null +++ b/tests/phpunit/Fixtures/install_no_base/post_install/Plugin.php @@ -0,0 +1 @@ +../../../../../Plugin.php \ No newline at end of file diff --git a/tests/phpunit/Fixtures/install_no_base/post_install/README.md b/tests/phpunit/Fixtures/install_no_base/post_install/README.md new file mode 120000 index 0000000..1dfab24 --- /dev/null +++ b/tests/phpunit/Fixtures/install_no_base/post_install/README.md @@ -0,0 +1 @@ +../../../../../README.md \ No newline at end of file diff --git a/tests/phpunit/Fixtures/install_no_base/post_install/SECURITY.md b/tests/phpunit/Fixtures/install_no_base/post_install/SECURITY.md new file mode 120000 index 0000000..32e7113 --- /dev/null +++ b/tests/phpunit/Fixtures/install_no_base/post_install/SECURITY.md @@ -0,0 +1 @@ +../../../../../SECURITY.md \ No newline at end of file diff --git a/tests/phpunit/Fixtures/install_no_base/post_install/composer.json b/tests/phpunit/Fixtures/install_no_base/post_install/composer.json new file mode 100644 index 0000000..c249048 --- /dev/null +++ b/tests/phpunit/Fixtures/install_no_base/post_install/composer.json @@ -0,0 +1,64 @@ +{ + "name": "testorg/testpackage", + "description": "Test description", + "license": "MIT", + "type": "composer-plugin", + "authors": [ + { + "name": "Alex Skrypnyk", + "email": "alex@drevops.com", + "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" + } +} diff --git a/tests/phpunit/Fixtures/install_no_base/post_install/composer.lock b/tests/phpunit/Fixtures/install_no_base/post_install/composer.lock new file mode 100644 index 0000000..c88b5f0 --- /dev/null +++ b/tests/phpunit/Fixtures/install_no_base/post_install/composer.lock @@ -0,0 +1,3 @@ +The contents of this file is ignored when compared with an actually produced +file in tests. +See .ignorecontent file diff --git a/tests/phpunit/Fixtures/install_no_base/post_install/tests/phpunit/Functional/CustomizerTestCase.php b/tests/phpunit/Fixtures/install_no_base/post_install/tests/phpunit/Functional/CustomizerTestCase.php new file mode 120000 index 0000000..a710969 --- /dev/null +++ b/tests/phpunit/Fixtures/install_no_base/post_install/tests/phpunit/Functional/CustomizerTestCase.php @@ -0,0 +1 @@ +../../../../../../../../tests/phpunit/Functional/CustomizerTestCase.php \ No newline at end of file diff --git a/tests/phpunit/Functional/CreateProjectTest.php b/tests/phpunit/Functional/CreateProjectTest.php index 0c0d257..24577c1 100644 --- a/tests/phpunit/Functional/CreateProjectTest.php +++ b/tests/phpunit/Functional/CreateProjectTest.php @@ -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 { diff --git a/tests/phpunit/Functional/CustomizerTestCase.php b/tests/phpunit/Functional/CustomizerTestCase.php index 123f0b6..0b53ae3 100644 --- a/tests/phpunit/Functional/CustomizerTestCase.php +++ b/tests/phpunit/Functional/CustomizerTestCase.php @@ -19,7 +19,7 @@ * 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 @@ -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) { @@ -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 + * 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 $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. */ @@ -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; @@ -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; diff --git a/tests/phpunit/Unit/FilesTest.php b/tests/phpunit/Unit/FilesTest.php index 3a0ccc4..14af8a6 100644 --- a/tests/phpunit/Unit/FilesTest.php +++ b/tests/phpunit/Unit/FilesTest.php @@ -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( @@ -235,7 +235,7 @@ public function testreplaceInPathBetweenMarkers(string $path, array $before, str } /** - * Data provider for testreplaceInPathBetweenMarkers. + * Data provider for testReplaceInPathBetweenMarkers. * * @return array * The data. diff --git a/tests/phpunit/Unit/SelfTest.php b/tests/phpunit/Unit/SelfTest.php index 995951b..b160df5 100644 --- a/tests/phpunit/Unit/SelfTest.php +++ b/tests/phpunit/Unit/SelfTest.php @@ -64,7 +64,18 @@ public static function dataProviderAssertDirectoriesEqual(): array { 'files_not_equal' => [ [ 'dir1' => [ + 'd32f2_symlink_deep.txt', + 'dir1_flat/d1f1_symlink.txt', 'dir1_flat/d1f3-only-src.txt', + 'dir3_subdirs/dir32-unignored/d32f1_symlink.txt', + 'dir3_subdirs_symlink/d3f1-ignored.txt', + 'dir3_subdirs_symlink/d3f2-ignored.txt', + 'dir3_subdirs_symlink/dir31/d31f1-ignored.txt', + 'dir3_subdirs_symlink/dir31/d31f2-ignored.txt', + 'dir3_subdirs_symlink/dir32-unignored/d32f1.txt', + 'dir3_subdirs_symlink/dir32-unignored/d32f1_symlink.txt', + 'dir3_subdirs_symlink/dir32-unignored/d32f2.txt', + 'f2_symlink.txt', ], 'dir2' => [ 'dir2_flat-present-dst/d2f1.txt',