diff --git a/.github/workflows/test-php.yml b/.github/workflows/test-php.yml index 7b415e9..7f60a86 100644 --- a/.github/workflows/test-php.yml +++ b/.github/workflows/test-php.yml @@ -59,6 +59,7 @@ jobs: with: name: ${{github.job}}-code-coverage-report-${{ matrix.composer-channel }}-${{ matrix.php-versions }} path: ./.coverage-html + include-hidden-files: true if-no-files-found: error - name: Upload coverage report to Codecov diff --git a/CustomizeCommand.php b/CustomizeCommand.php index b8c1d87..4e16a1b 100644 --- a/CustomizeCommand.php +++ b/CustomizeCommand.php @@ -38,7 +38,7 @@ * Please keep this link in your project to help others find this tool. * Thank you! * - * @see https://github.com/alexSkrypnyk/customizer + * @see https://github.com/AlexSkrypnyk/customizer * * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.TooManyPublicMethods) @@ -71,7 +71,7 @@ class CustomizeCommand extends BaseCommand { public string $composerjson; /** - * Composer config data. + * Composer config data loaded before customization. * * @var array */ @@ -113,6 +113,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int if (empty($answers)) { $this->io->success($this->message('result_no_questions')); + $this->cleanupSelf(); + return 0; } @@ -235,7 +237,7 @@ protected function cleanupSelf(): void { $should_proceed = $this->configClass::cleanup($this); if ($should_proceed === FALSE) { - $this->debug("Customizer's was cleanup skipped by the config class."); + $this->debug("Customizer's cleanup was skipped by the config class."); return; } @@ -261,6 +263,13 @@ protected function cleanupSelf(): void { static::arrayUnsetDeep($json, ['autoload-dev', 'psr-4', 'AlexSkrypnyk\\Customizer\\Tests\\']); static::arrayUnsetDeep($json, ['config', 'allow-plugins', 'alexskrypnyk/customizer']); + // Remove the Customizer from the list of repositories if it was added as a + // local or overridden dependency. + // @todo Update to only remove if the package `url` path matches the current + // working directory. + static::arrayUnsetDeep($json, ['repositories']); + static::arrayUnsetDeep($json, ['minimum-stability']); + // If the package data has changed, update the composer.json file. if (strcmp(serialize($this->composerjsonData), serialize($json)) !== 0) { $this->writeComposerJson($this->cwd . '/composer.json', $json); @@ -309,10 +318,12 @@ protected function cleanupSelf(): void { * Message. */ protected function message(string $name, array $tokens = []): string { + // Default messages from this class. $messages = static::messages($this); - if (!empty($this->configClass)) { - if (method_exists($this->configClass, 'messages') && !is_callable([$this->configClass, 'messages'])) { + // Messages from the config class if 'messages' method exists. + if (!empty($this->configClass) && method_exists($this->configClass, 'messages')) { + if (!is_callable([$this->configClass, 'messages'])) { throw new \RuntimeException(sprintf('Optional method `messages()` exists in the config class %s but is not callable', $this->configClass)); } $messages = array_replace_recursive($messages, $this->configClass::messages($this)); @@ -673,7 +684,7 @@ public static function arrayUnsetDeep(array &$array, array $path, ?string $value */ public static function messages(CustomizeCommand $c): array { return [ - 'title' => 'Welcome to {{ package.name }} project customizer', + 'title' => 'Welcome to the "{{ package.name }}" project customizer', 'header' => [ 'Please answer the following questions to customize your project.', 'You will be able to review your answers before proceeding.', diff --git a/customize.php b/customize.php index 1545452..5fa258c 100644 --- a/customize.php +++ b/customize.php @@ -59,8 +59,7 @@ public static function questions(CustomizeCommand $c): array { // The discover callback function is used to discover the value from the // environment. In this case, we use the current directory name // and the GITHUB_ORG environment variable to generate the package name. - // @phpstan-ignore-next-line - 'discover' => static function (CustomizeCommand $c): ?string { + 'discover' => static function (CustomizeCommand $c): string { $name = basename((string) getcwd()); $org = getenv('GITHUB_ORG') ?: 'acme'; diff --git a/phpcs.xml b/phpcs.xml index fa40847..a008a6b 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -31,4 +31,9 @@ *.TestCase\.php *.test + + + + tests/phpunit/Fixtures + diff --git a/tests/phpunit/Dirs.php b/tests/phpunit/Dirs.php deleted file mode 100644 index 637b1b6..0000000 --- a/tests/phpunit/Dirs.php +++ /dev/null @@ -1,107 +0,0 @@ -fs = new Filesystem(); - } - - /** - * Initialize locations. - */ - public function initLocations(?callable $cb = NULL, string $root = NULL): void { - $this->root = $root ?: $this->fileFindDir('composer.json', dirname(__FILE__) . '/../..'); - - $this->fixtures = $this->root . '/tests/phpunit/Fixtures'; - if (!is_dir($this->fixtures)) { - throw new \RuntimeException('The fixtures directory does not exist: ' . $this->fixtures); - } - - $this->build = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'customizer-' . microtime(TRUE); - $this->sut = $this->build . '/sut'; - $this->repo = $this->build . '/local_repo'; - - $this->fs->mkdir($this->build); - $this->fs->mkdir($this->sut); - $this->fs->mkdir($this->repo); - - if (is_callable($cb)) { - // Pass the instance of Dirs to the callback instead of binding it so that - // the caller class could still use their own methods. - $cb($this); - } - } - - /** - * Delete locations. - */ - public function deleteLocations(): void { - $this->fs->remove($this->build); - } - - /** - * Print information about locations. - */ - public function printInfo(): string { - $lines[] = '-- LOCATIONS --'; - $lines[] = 'Root : ' . $this->root; - $lines[] = 'Fixtures : ' . $this->fixtures; - $lines[] = 'Build : ' . $this->build; - $lines[] = 'SUT : ' . $this->sut; - $lines[] = 'Local repo : ' . $this->repo; - - return implode(PHP_EOL, $lines) . PHP_EOL; - } - -} diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/.ignorecontent b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/.ignorecontent new file mode 100644 index 0000000..571e5a2 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/.ignorecontent @@ -0,0 +1,37 @@ +# Syntax of this file is similar to .gitignore with addition of content changes +# ignoring using ^ prefix: +# Comments start with #. +# file - ignore file +# dir/ - ignore directory and all subdirectories +# dir/* - ignore all files in directory, but not subdirectories +# ^file - ignore content changes in file, but not the file itself +# ^dir/ - ignore content changes in all files and subdirectories, but check that the directory itself exists +# ^dir/* - ignore content changes in all files, but not subdirectories and check that the directory itself exists +# !file - do not ignore file +# !dir/ - do not ignore directory, including all subdirectories +# !dir/* - do not ignore all files in directory, but not subdirectories +# !^file - do not ignore content changes in file +# !^dir/ - do not ignore content changes in all files and subdirectories +# !^dir/* - do not ignore content changes in all files, but not subdirectories + +# Ignore all files by extension. +*.log + +# Ignore file in root and all directories. +f3-new-file-ignore-everywhere.txt + +# Ignore dir with all files, but not subdirectories. +dir2_flat/* + +# Ignore all files and subdirectories in the directory. +dir3_subdirs/* +# But include sub-sub-directory. +!dir3_subdirs/dir32-unignored/ + +# Ignore directory and all subdirectories. +dir4_full_ignore/ + +# Ignore content in the directory. +^dir5_content_ignore/ +# But include this file content changes for comparison. +!^dir5_content_ignore/d5f2-unignored-content.txt diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir1_flat/d1f1.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir1_flat/d1f1.txt new file mode 100644 index 0000000..f158938 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir1_flat/d1f1.txt @@ -0,0 +1 @@ +d1f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir1_flat/d1f2.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir1_flat/d1f2.txt new file mode 100644 index 0000000..0b8bacd --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir1_flat/d1f2.txt @@ -0,0 +1 @@ +d1f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/d3f1-ignored.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/d3f1-ignored.txt new file mode 100644 index 0000000..13d181a --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/d3f1-ignored.txt @@ -0,0 +1 @@ +d3f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/d3f2-ignored.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/d3f2-ignored.txt new file mode 100644 index 0000000..af655c3 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/d3f2-ignored.txt @@ -0,0 +1,2 @@ +d3f2l1 + diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/dir31/d31f1-ignored.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/dir31/d31f1-ignored.txt new file mode 100644 index 0000000..69c9854 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/dir31/d31f1-ignored.txt @@ -0,0 +1 @@ +d31f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/dir31/d31f2-ignored.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/dir31/d31f2-ignored.txt new file mode 100644 index 0000000..bdfba81 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/dir31/d31f2-ignored.txt @@ -0,0 +1 @@ +d31f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/dir32-unignored/d32f1.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/dir32-unignored/d32f1.txt new file mode 100644 index 0000000..4f4ce52 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/dir32-unignored/d32f1.txt @@ -0,0 +1 @@ +d32f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/dir32-unignored/d32f2-only-src.log b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/dir32-unignored/d32f2-only-src.log new file mode 100644 index 0000000..aa31929 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/dir32-unignored/d32f2-only-src.log @@ -0,0 +1 @@ +d32f2l1.log only src diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/dir32-unignored/d32f2.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/dir32-unignored/d32f2.txt new file mode 100644 index 0000000..3b68466 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir3_subdirs/dir32-unignored/d32f2.txt @@ -0,0 +1 @@ +d32f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir5_content_ignore/d5f1-ignored-changed-content.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir5_content_ignore/d5f1-ignored-changed-content.txt new file mode 100644 index 0000000..e8db00e --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir5_content_ignore/d5f1-ignored-changed-content.txt @@ -0,0 +1 @@ +d5f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir5_content_ignore/d5f2-unignored-content.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir5_content_ignore/d5f2-unignored-content.txt new file mode 100644 index 0000000..2704e6e --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir5_content_ignore/d5f2-unignored-content.txt @@ -0,0 +1 @@ +d5f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir5_content_ignore/dir51/d51f1-changed-content.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir5_content_ignore/dir51/d51f1-changed-content.txt new file mode 100644 index 0000000..e8db00e --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/dir5_content_ignore/dir51/d51f1-changed-content.txt @@ -0,0 +1 @@ +d5f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/f1.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/f1.txt new file mode 100644 index 0000000..b0bac68 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/f1.txt @@ -0,0 +1 @@ +f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/f2.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/f2.txt new file mode 100644 index 0000000..47f6fa5 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/f2.txt @@ -0,0 +1 @@ +f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/f4-ignore-ext.log b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/f4-ignore-ext.log new file mode 100644 index 0000000..fa88711 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir1/f4-ignore-ext.log @@ -0,0 +1 @@ +f4l1.log diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir1_flat/d1f1.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir1_flat/d1f1.txt new file mode 100644 index 0000000..f158938 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir1_flat/d1f1.txt @@ -0,0 +1 @@ +d1f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir1_flat/d1f2.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir1_flat/d1f2.txt new file mode 100644 index 0000000..0b8bacd --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir1_flat/d1f2.txt @@ -0,0 +1 @@ +d1f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir2_flat/d2f1.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir2_flat/d2f1.txt new file mode 100644 index 0000000..a3ce084 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir2_flat/d2f1.txt @@ -0,0 +1 @@ +d2f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir2_flat/d2f2.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir2_flat/d2f2.txt new file mode 100644 index 0000000..ee17a17 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir2_flat/d2f2.txt @@ -0,0 +1 @@ +d2f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/d3f1-ignored.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/d3f1-ignored.txt new file mode 100644 index 0000000..13d181a --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/d3f1-ignored.txt @@ -0,0 +1 @@ +d3f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/d3f2-ignored.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/d3f2-ignored.txt new file mode 100644 index 0000000..1c4b57b --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/d3f2-ignored.txt @@ -0,0 +1 @@ +d3f2l1-changes diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/dir31/d31f1-ignored.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/dir31/d31f1-ignored.txt new file mode 100644 index 0000000..69c9854 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/dir31/d31f1-ignored.txt @@ -0,0 +1 @@ +d31f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/dir31/d31f2-ignored.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/dir31/d31f2-ignored.txt new file mode 100644 index 0000000..bdfba81 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/dir31/d31f2-ignored.txt @@ -0,0 +1 @@ +d31f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/dir31/f3-new-file-ignore-everywhere.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/dir31/f3-new-file-ignore-everywhere.txt new file mode 100644 index 0000000..d536218 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/dir31/f3-new-file-ignore-everywhere.txt @@ -0,0 +1 @@ +f3l1 - full ignore diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/dir32-unignored/d32f1.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/dir32-unignored/d32f1.txt new file mode 100644 index 0000000..4f4ce52 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/dir32-unignored/d32f1.txt @@ -0,0 +1 @@ +d32f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/dir32-unignored/d32f2-ignore-ext-only-dst.log b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/dir32-unignored/d32f2-ignore-ext-only-dst.log new file mode 100644 index 0000000..61ab7b8 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/dir32-unignored/d32f2-ignore-ext-only-dst.log @@ -0,0 +1 @@ +d32f2l1.log only dst diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/dir32-unignored/d32f2.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/dir32-unignored/d32f2.txt new file mode 100644 index 0000000..3b68466 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/dir32-unignored/d32f2.txt @@ -0,0 +1 @@ +d32f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/f3-new-file-ignore-everywhere.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/f3-new-file-ignore-everywhere.txt new file mode 100644 index 0000000..d536218 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir3_subdirs/f3-new-file-ignore-everywhere.txt @@ -0,0 +1 @@ +f3l1 - full ignore diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir4_full_ignore/d4f1.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir4_full_ignore/d4f1.txt new file mode 100644 index 0000000..e2139b5 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir4_full_ignore/d4f1.txt @@ -0,0 +1 @@ +d4f1l1-fully ignored diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir5_content_ignore/d5f1-ignored-changed-content.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir5_content_ignore/d5f1-ignored-changed-content.txt new file mode 100644 index 0000000..2c5d36a --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir5_content_ignore/d5f1-ignored-changed-content.txt @@ -0,0 +1 @@ +d5f1l1-changed diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir5_content_ignore/d5f2-unignored-content.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir5_content_ignore/d5f2-unignored-content.txt new file mode 100644 index 0000000..2704e6e --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir5_content_ignore/d5f2-unignored-content.txt @@ -0,0 +1 @@ +d5f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir5_content_ignore/dir51/d51f1-changed-content.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir5_content_ignore/dir51/d51f1-changed-content.txt new file mode 100644 index 0000000..2c5d36a --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/dir5_content_ignore/dir51/d51f1-changed-content.txt @@ -0,0 +1 @@ +d5f1l1-changed diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/f1.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/f1.txt new file mode 100644 index 0000000..b0bac68 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/f1.txt @@ -0,0 +1 @@ +f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/f2.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/f2.txt new file mode 100644 index 0000000..47f6fa5 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/f2.txt @@ -0,0 +1 @@ +f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/f3-new-file-ignore-everywhere.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/f3-new-file-ignore-everywhere.txt new file mode 100644 index 0000000..d536218 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/f3-new-file-ignore-everywhere.txt @@ -0,0 +1 @@ +f3l1 - full ignore diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/f4-ignore-ext.log b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/f4-ignore-ext.log new file mode 100644 index 0000000..fa88711 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/f4-ignore-ext.log @@ -0,0 +1 @@ +f4l1.log diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/f5-new-file-ignore-ext.log b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/f5-new-file-ignore-ext.log new file mode 100644 index 0000000..fa88711 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_equal/dir2/f5-new-file-ignore-ext.log @@ -0,0 +1 @@ +f4l1.log diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/.ignorecontent b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/.ignorecontent new file mode 100644 index 0000000..571e5a2 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/.ignorecontent @@ -0,0 +1,37 @@ +# Syntax of this file is similar to .gitignore with addition of content changes +# ignoring using ^ prefix: +# Comments start with #. +# file - ignore file +# dir/ - ignore directory and all subdirectories +# dir/* - ignore all files in directory, but not subdirectories +# ^file - ignore content changes in file, but not the file itself +# ^dir/ - ignore content changes in all files and subdirectories, but check that the directory itself exists +# ^dir/* - ignore content changes in all files, but not subdirectories and check that the directory itself exists +# !file - do not ignore file +# !dir/ - do not ignore directory, including all subdirectories +# !dir/* - do not ignore all files in directory, but not subdirectories +# !^file - do not ignore content changes in file +# !^dir/ - do not ignore content changes in all files and subdirectories +# !^dir/* - do not ignore content changes in all files, but not subdirectories + +# Ignore all files by extension. +*.log + +# Ignore file in root and all directories. +f3-new-file-ignore-everywhere.txt + +# Ignore dir with all files, but not subdirectories. +dir2_flat/* + +# Ignore all files and subdirectories in the directory. +dir3_subdirs/* +# But include sub-sub-directory. +!dir3_subdirs/dir32-unignored/ + +# Ignore directory and all subdirectories. +dir4_full_ignore/ + +# Ignore content in the directory. +^dir5_content_ignore/ +# But include this file content changes for comparison. +!^dir5_content_ignore/d5f2-unignored-content.txt diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir1_flat/d1f1.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir1_flat/d1f1.txt new file mode 100644 index 0000000..f158938 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir1_flat/d1f1.txt @@ -0,0 +1 @@ +d1f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir1_flat/d1f2.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir1_flat/d1f2.txt new file mode 100644 index 0000000..0b8bacd --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir1_flat/d1f2.txt @@ -0,0 +1 @@ +d1f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir1_flat/d1f3-only-src.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir1_flat/d1f3-only-src.txt new file mode 100644 index 0000000..61f88bc --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir1_flat/d1f3-only-src.txt @@ -0,0 +1 @@ +d1f3l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/d3f1-ignored.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/d3f1-ignored.txt new file mode 100644 index 0000000..13d181a --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/d3f1-ignored.txt @@ -0,0 +1 @@ +d3f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/d3f2-ignored.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/d3f2-ignored.txt new file mode 100644 index 0000000..af655c3 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/d3f2-ignored.txt @@ -0,0 +1,2 @@ +d3f2l1 + diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/dir31/d31f1-ignored.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/dir31/d31f1-ignored.txt new file mode 100644 index 0000000..69c9854 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/dir31/d31f1-ignored.txt @@ -0,0 +1 @@ +d31f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/dir31/d31f2-ignored.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/dir31/d31f2-ignored.txt new file mode 100644 index 0000000..bdfba81 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/dir31/d31f2-ignored.txt @@ -0,0 +1 @@ +d31f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/dir32-unignored/d32f1.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/dir32-unignored/d32f1.txt new file mode 100644 index 0000000..4f4ce52 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/dir32-unignored/d32f1.txt @@ -0,0 +1 @@ +d32f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/dir32-unignored/d32f2-only-src.log b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/dir32-unignored/d32f2-only-src.log new file mode 100644 index 0000000..aa31929 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/dir32-unignored/d32f2-only-src.log @@ -0,0 +1 @@ +d32f2l1.log only src diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/dir32-unignored/d32f2.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/dir32-unignored/d32f2.txt new file mode 100644 index 0000000..3b68466 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir3_subdirs/dir32-unignored/d32f2.txt @@ -0,0 +1 @@ +d32f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir5_content_ignore/d5f1-ignored-changed-content.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir5_content_ignore/d5f1-ignored-changed-content.txt new file mode 100644 index 0000000..e8db00e --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir5_content_ignore/d5f1-ignored-changed-content.txt @@ -0,0 +1 @@ +d5f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir5_content_ignore/d5f2-unignored-content.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir5_content_ignore/d5f2-unignored-content.txt new file mode 100644 index 0000000..2704e6e --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir5_content_ignore/d5f2-unignored-content.txt @@ -0,0 +1 @@ +d5f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir5_content_ignore/dir51/d51f1-changed-content.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir5_content_ignore/dir51/d51f1-changed-content.txt new file mode 100644 index 0000000..e8db00e --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/dir5_content_ignore/dir51/d51f1-changed-content.txt @@ -0,0 +1 @@ +d5f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/f1.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/f1.txt new file mode 100644 index 0000000..b0bac68 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/f1.txt @@ -0,0 +1 @@ +f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/f2.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/f2.txt new file mode 100644 index 0000000..47f6fa5 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/f2.txt @@ -0,0 +1 @@ +f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/f4-ignore-ext.log b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/f4-ignore-ext.log new file mode 100644 index 0000000..fa88711 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir1/f4-ignore-ext.log @@ -0,0 +1 @@ +f4l1.log diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir1_flat/d1f1.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir1_flat/d1f1.txt new file mode 100644 index 0000000..f158938 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir1_flat/d1f1.txt @@ -0,0 +1 @@ +d1f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir1_flat/d1f2.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir1_flat/d1f2.txt new file mode 100644 index 0000000..0b8bacd --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir1_flat/d1f2.txt @@ -0,0 +1 @@ +d1f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir2_flat-present-dst/d2f1.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir2_flat-present-dst/d2f1.txt new file mode 100644 index 0000000..a3ce084 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir2_flat-present-dst/d2f1.txt @@ -0,0 +1 @@ +d2f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir2_flat-present-dst/d2f2.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir2_flat-present-dst/d2f2.txt new file mode 100644 index 0000000..ee17a17 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir2_flat-present-dst/d2f2.txt @@ -0,0 +1 @@ +d2f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/d3f1-ignored.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/d3f1-ignored.txt new file mode 100644 index 0000000..13d181a --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/d3f1-ignored.txt @@ -0,0 +1 @@ +d3f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/d3f2-ignored.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/d3f2-ignored.txt new file mode 100644 index 0000000..1c4b57b --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/d3f2-ignored.txt @@ -0,0 +1 @@ +d3f2l1-changes diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/dir31/d31f1-ignored.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/dir31/d31f1-ignored.txt new file mode 100644 index 0000000..69c9854 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/dir31/d31f1-ignored.txt @@ -0,0 +1 @@ +d31f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/dir31/d31f2-ignored.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/dir31/d31f2-ignored.txt new file mode 100644 index 0000000..bdfba81 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/dir31/d31f2-ignored.txt @@ -0,0 +1 @@ +d31f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/dir31/f3-new-file-ignore-everywhere.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/dir31/f3-new-file-ignore-everywhere.txt new file mode 100644 index 0000000..d536218 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/dir31/f3-new-file-ignore-everywhere.txt @@ -0,0 +1 @@ +f3l1 - full ignore diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/dir31/f4-new-file-notignore-everywhere.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/dir31/f4-new-file-notignore-everywhere.txt new file mode 100644 index 0000000..d536218 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/dir31/f4-new-file-notignore-everywhere.txt @@ -0,0 +1 @@ +f3l1 - full ignore diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/dir32-unignored/d32f1.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/dir32-unignored/d32f1.txt new file mode 100644 index 0000000..4f4ce52 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/dir32-unignored/d32f1.txt @@ -0,0 +1 @@ +d32f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/dir32-unignored/d32f2-ignore-ext-only-dst.log b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/dir32-unignored/d32f2-ignore-ext-only-dst.log new file mode 100644 index 0000000..61ab7b8 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/dir32-unignored/d32f2-ignore-ext-only-dst.log @@ -0,0 +1 @@ +d32f2l1.log only dst diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/dir32-unignored/d32f2.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/dir32-unignored/d32f2.txt new file mode 100644 index 0000000..affaa01 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/dir32-unignored/d32f2.txt @@ -0,0 +1 @@ +d32f2l1-changed diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/f3-new-file-ignore-everywhere.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/f3-new-file-ignore-everywhere.txt new file mode 100644 index 0000000..d536218 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir3_subdirs/f3-new-file-ignore-everywhere.txt @@ -0,0 +1 @@ +f3l1 - full ignore diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir4_full_ignore/d4f1.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir4_full_ignore/d4f1.txt new file mode 100644 index 0000000..e2139b5 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir4_full_ignore/d4f1.txt @@ -0,0 +1 @@ +d4f1l1-fully ignored diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir5_content_ignore/d5f1-ignored-changed-content.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir5_content_ignore/d5f1-ignored-changed-content.txt new file mode 100644 index 0000000..2c5d36a --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir5_content_ignore/d5f1-ignored-changed-content.txt @@ -0,0 +1 @@ +d5f1l1-changed diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir5_content_ignore/d5f2-unignored-content.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir5_content_ignore/d5f2-unignored-content.txt new file mode 100644 index 0000000..2704e6e --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir5_content_ignore/d5f2-unignored-content.txt @@ -0,0 +1 @@ +d5f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir5_content_ignore/dir51/d51f1-changed-content.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir5_content_ignore/dir51/d51f1-changed-content.txt new file mode 100644 index 0000000..2c5d36a --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir5_content_ignore/dir51/d51f1-changed-content.txt @@ -0,0 +1 @@ +d5f1l1-changed diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir5_content_ignore/dir51/d51f2-new-file.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir5_content_ignore/dir51/d51f2-new-file.txt new file mode 100644 index 0000000..c698840 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/dir5_content_ignore/dir51/d51f2-new-file.txt @@ -0,0 +1 @@ +d5f1l1 new file in content-ignored directory - considered as present and should fail diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/f1.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/f1.txt new file mode 100644 index 0000000..b0bac68 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/f1.txt @@ -0,0 +1 @@ +f1l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/f2.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/f2.txt new file mode 100644 index 0000000..47f6fa5 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/f2.txt @@ -0,0 +1 @@ +f2l1 diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/f3-new-file-ignore-everywhere.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/f3-new-file-ignore-everywhere.txt new file mode 100644 index 0000000..d536218 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/f3-new-file-ignore-everywhere.txt @@ -0,0 +1 @@ +f3l1 - full ignore diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/f4-new-file-notignore-everywhere.txt b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/f4-new-file-notignore-everywhere.txt new file mode 100644 index 0000000..d536218 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/f4-new-file-notignore-everywhere.txt @@ -0,0 +1 @@ +f3l1 - full ignore diff --git a/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/f5-new-file-ignore-ext.log b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/f5-new-file-ignore-ext.log new file mode 100644 index 0000000..fa88711 --- /dev/null +++ b/tests/phpunit/Fixtures/assert_fixture_files/files_not_equal/dir2/f5-new-file-ignore-ext.log @@ -0,0 +1 @@ +f4l1.log diff --git a/tests/phpunit/Fixtures/command/install/base/customize.php b/tests/phpunit/Fixtures/command/install/base/customize.php deleted file mode 100644 index e783e62..0000000 --- a/tests/phpunit/Fixtures/command/install/base/customize.php +++ /dev/null @@ -1,193 +0,0 @@ -> - * An associative array of questions with question title as a key and the - * value of array with the following keys: - * - question: Required question callback function used to ask the question. - * The callback receives the following arguments: - * - discovered: A value discovered by the discover callback or NULL. - * - answers: An associative array of all answers received so far. - * - command: The CustomizeCommand object. - * - discover: Optional callback function used to discover the value from - * the environment. Can be an anonymous function or a method of this class - * as discover. If not provided, empty string will - * be passed to the question callback. The callback receives the following - * arguments: - * - command: The CustomizeCommand object. - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public static function questions(CustomizeCommand $c): array { - // This an example of questions that can be asked to customize the project. - // You can adjust this method to ask questions that are relevant to your - // project. - // - // In this example, we ask for the package name, description, and license. - // - // You may remove all the questions below and replace them with your own. - return [ - 'Name' => [ - // The discover callback function is used to discover the value from the - // environment. In this case, we use the current directory name - // and the GITHUB_ORG environment variable to generate the package name. - // @phpstan-ignore-next-line - 'discover' => static function (CustomizeCommand $c): ?string { - $name = basename((string) getcwd()); - $org = getenv('GITHUB_ORG') ?: 'acme'; - - return $org . '/' . $name; - }, - // The question callback function defines how the question is asked. - // In this case, we ask the user to provide a package name as a string. - // The discovery callback is used to provide a default value. - // The question callback provides a capability to validate the answer - // before it can be accepted by providing a validation callback. - 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->ask('Package name', $discovered, static function (string $value): string { - // This is a validation callback that checks if the package name is - // valid. If not, an \InvalidArgumentException exception is thrown - // with a message shown to the user. - if (!preg_match('/^[a-z0-9_.-]+\/[a-z0-9_.-]+$/', $value)) { - throw new \InvalidArgumentException(sprintf('The package name "%s" is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name.', $value)); - } - - return $value; - }), - ], - 'Description' => [ - // For this question, we use an answer from the previous question - // in the title of the question. - 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->ask(sprintf('Description for %s', $answers['Name'])), - ], - 'License' => [ - // For this question, we use a pre-defined list of options. - // For discovery, we use a separate method named 'discoverLicense' - // (only for the demonstration purposes; it could have been an - // anonymous function). - 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->choice('License type', - [ - 'MIT', - 'GPL-3.0-or-later', - 'Apache-2.0', - ], - // Note that the default value is the value discovered by the - // 'discoverLicense' method. If the discovery did not return a value, - // the default value of 'GPL-3.0-or-later' is used. - empty($discovered) ? 'GPL-3.0-or-later' : $discovered - ), - ], - ]; - } - - /** - * A callback to discover the `License` value from the environment. - * - * This is an example callback, and it can be safely removed if this question - * is not needed. - * - * @param \AlexSkrypnyk\Customizer\CustomizeCommand $c - * The Customizer instance. - */ - public static function discoverLicense(CustomizeCommand $c): string { - return isset($c->composerjsonData['license']) && is_string($c->composerjsonData['license']) ? $c->composerjsonData['license'] : ''; - } - - /** - * A required callback to process all answers. - * - * This method is called after all questions have been answered and a user - * has confirmed the intent to proceed with the customization. - * - * Note that any manipulation of the composer.json file should be done here - * and then written back to the file system. - * - * @param array $answers - * Gathered answers. - * @param \AlexSkrypnyk\Customizer\CustomizeCommand $c - * The Customizer instance. - */ - public static function process(array $answers, CustomizeCommand $c): void { - $c->debug('Updating composer configuration'); - $json = $c->readComposerJson($c->composerjson); - $json['name'] = $answers['Name']; - $json['description'] = $answers['Description']; - $json['license'] = $answers['License']; - $c->writeComposerJson($c->composerjson, $json); - - $c->debug('Removing an arbitrary file.'); - $files = $c->finder($c->cwd)->files()->name('LICENSE'); - foreach ($files as $file) { - $c->fs->remove($file->getRealPath()); - } - } - - /** - * Cleanup after the customization. - * - * By the time this method is called, all the necessary changes have been made - * to the project. - * - * The Customizer will remove itself from the project and will update the - * composer.json as required. This method allows to alter that process as - * needed and, if necessary, cancel the original self-cleanup. - * - * @param \AlexSkrypnyk\Customizer\CustomizeCommand $c - * The CustomizeCommand object. - * - * @return bool - * Return FALSE to skip the further self-cleanup. Returning TRUE will - * proceed with the self-cleanup. - */ - public static function cleanup(CustomizeCommand $c): bool { - return TRUE; - } - - /** - * Override some of the messages displayed to the user by Customizer. - * - * @param \AlexSkrypnyk\Customizer\CustomizeCommand $c - * The Customizer instance. - * - * @return array> - * An associative array of messages with message name as key and the message - * test as a string or an array of strings. - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public static function messages(CustomizeCommand $c): array { - return [ - // This is an example of a custom message that overrides the default - // message with name `welcome`. - 'title' => 'Welcome to the "{{ package.name }}" project customizer', - ]; - } - -} diff --git a/tests/phpunit/Fixtures/command/install_additional_cleanup/base/customize.php b/tests/phpunit/Fixtures/command/install_additional_cleanup/base/customize.php deleted file mode 100644 index 501ad90..0000000 --- a/tests/phpunit/Fixtures/command/install_additional_cleanup/base/customize.php +++ /dev/null @@ -1,199 +0,0 @@ -> - * An associative array of questions with question title as a key and the - * value of array with the following keys: - * - question: Required question callback function used to ask the question. - * The callback receives the following arguments: - * - discovered: A value discovered by the discover callback or NULL. - * - answers: An associative array of all answers received so far. - * - command: The CustomizeCommand object. - * - discover: Optional callback function used to discover the value from - * the environment. Can be an anonymous function or a method of this class - * as discover. If not provided, empty string will - * be passed to the question callback. The callback receives the following - * arguments: - * - command: The CustomizeCommand object. - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public static function questions(CustomizeCommand $c): array { - // This an example of questions that can be asked to customize the project. - // You can adjust this method to ask questions that are relevant to your - // project. - // - // In this example, we ask for the package name, description, and license. - // - // You may remove all the questions below and replace them with your own. - return [ - 'Name' => [ - // The discover callback function is used to discover the value from the - // environment. In this case, we use the current directory name - // and the GITHUB_ORG environment variable to generate the package name. - // @phpstan-ignore-next-line - 'discover' => static function (CustomizeCommand $c): ?string { - $name = basename((string) getcwd()); - $org = getenv('GITHUB_ORG') ?: 'acme'; - - return $org . '/' . $name; - }, - // The question callback function defines how the question is asked. - // In this case, we ask the user to provide a package name as a string. - // The discovery callback is used to provide a default value. - // The question callback provides a capability to validate the answer - // before it can be accepted by providing a validation callback. - 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->ask('Package name', $discovered, static function (string $value): string { - // This is a validation callback that checks if the package name is - // valid. If not, an \InvalidArgumentException exception is thrown - // with a message shown to the user. - if (!preg_match('/^[a-z0-9_.-]+\/[a-z0-9_.-]+$/', $value)) { - throw new \InvalidArgumentException(sprintf('The package name "%s" is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name.', $value)); - } - - return $value; - }), - ], - 'Description' => [ - // For this question, we use an answer from the previous question - // in the title of the question. - 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->ask(sprintf('Description for %s', $answers['Name'])), - ], - 'License' => [ - // For this question, we use a pre-defined list of options. - // For discovery, we use a separate method named 'discoverLicense' - // (only for the demonstration purposes; it could have been an - // anonymous function). - 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->choice('License type', - [ - 'MIT', - 'GPL-3.0-or-later', - 'Apache-2.0', - ], - // Note that the default value is the value discovered by the - // 'discoverLicense' method. If the discovery did not return a value, - // the default value of 'GPL-3.0-or-later' is used. - empty($discovered) ? 'GPL-3.0-or-later' : $discovered - ), - ], - ]; - } - - /** - * A callback to discover the `License` value from the environment. - * - * This is an example callback, and it can be safely removed if this question - * is not needed. - * - * @param \AlexSkrypnyk\Customizer\CustomizeCommand $c - * The Customizer instance. - */ - public static function discoverLicense(CustomizeCommand $c): string { - return isset($c->composerjsonData['license']) && is_string($c->composerjsonData['license']) ? $c->composerjsonData['license'] : ''; - } - - /** - * A required callback to process all answers. - * - * This method is called after all questions have been answered and a user - * has confirmed the intent to proceed with the customization. - * - * Note that any manipulation of the composer.json file should be done here - * and then written back to the file system. - * - * @param array $answers - * Gathered answers. - * @param \AlexSkrypnyk\Customizer\CustomizeCommand $c - * The Customizer instance. - */ - public static function process(array $answers, CustomizeCommand $c): void { - $c->debug('Updating composer configuration'); - $json = $c->readComposerJson($c->composerjson); - $json['name'] = $answers['Name']; - $json['description'] = $answers['Description']; - $json['license'] = $answers['License']; - $c->writeComposerJson($c->composerjson, $json); - - $c->debug('Removing an arbitrary file.'); - $files = $c->finder($c->cwd)->files()->name('LICENSE'); - foreach ($files as $file) { - $c->fs->remove($file->getRealPath()); - } - } - - /** - * Cleanup after the customization. - * - * By the time this method is called, all the necessary changes have been made - * to the project. - * - * The Customizer will remove itself from the project and will update the - * composer.json as required. This method allows to alter that process as - * needed and, if necessary, cancel the original self-cleanup. - * - * @param \AlexSkrypnyk\Customizer\CustomizeCommand $c - * The CustomizeCommand object. - * - * @return bool - * Return FALSE to skip the further self-cleanup. Returning TRUE will - * proceed with the self-cleanup. - */ - public static function cleanup(CustomizeCommand $c): bool { - if ($c->isComposerDependenciesInstalled) { - $json = $c->readComposerJson($c->composerjson); - $json['minimum-stability'] = 'beta'; - $c->writeComposerJson($c->composerjson, $json); - } - - return TRUE; - } - - /** - * Override some of the messages displayed to the user by Customizer. - * - * @param \AlexSkrypnyk\Customizer\CustomizeCommand $c - * The Customizer instance. - * - * @return array> - * An associative array of messages with message name as key and the message - * test as a string or an array of strings. - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public static function messages(CustomizeCommand $c): array { - return [ - // This is an example of a custom message that overrides the default - // message with name `welcome`. - 'title' => 'Welcome to the "{{ package.name }}" project customizer', - ]; - } - -} diff --git a/tests/phpunit/Fixtures/command/install_additional_cleanup/expected/composer.json b/tests/phpunit/Fixtures/command/install_additional_cleanup/expected/composer.json deleted file mode 100644 index 7e09f03..0000000 --- a/tests/phpunit/Fixtures/command/install_additional_cleanup/expected/composer.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "testorg/testpackage", - "description": "Test description", - "type": "project", - "license": "MIT", - "require": { - "php": ">=8.2", - "monolog/monolog": "^2.0" - }, - "minimum-stability": "beta" -} diff --git a/tests/phpunit/Fixtures/command/install_sub_dir/base/composer.json b/tests/phpunit/Fixtures/command/install_sub_dir/base/composer.json deleted file mode 100644 index 685dd21..0000000 --- a/tests/phpunit/Fixtures/command/install_sub_dir/base/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "yourorg/yourtempaltepackage", - "description": "Your template package description", - "type": "project", - "license": "proprietary", - "require": { - "php": ">=8.2", - "monolog/monolog": "^2.0" - }, - "require-dev": { - "alexskrypnyk/customizer": "^0.2" - }, - "minimum-stability": "dev", - "config": { - "allow-plugins": { - "alexskrypnyk/customizer": true - } - } -} diff --git a/tests/phpunit/Fixtures/command/install_sub_dir/base/customize.php b/tests/phpunit/Fixtures/command/install_sub_dir/base/customize.php deleted file mode 100644 index e783e62..0000000 --- a/tests/phpunit/Fixtures/command/install_sub_dir/base/customize.php +++ /dev/null @@ -1,193 +0,0 @@ -> - * An associative array of questions with question title as a key and the - * value of array with the following keys: - * - question: Required question callback function used to ask the question. - * The callback receives the following arguments: - * - discovered: A value discovered by the discover callback or NULL. - * - answers: An associative array of all answers received so far. - * - command: The CustomizeCommand object. - * - discover: Optional callback function used to discover the value from - * the environment. Can be an anonymous function or a method of this class - * as discover. If not provided, empty string will - * be passed to the question callback. The callback receives the following - * arguments: - * - command: The CustomizeCommand object. - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public static function questions(CustomizeCommand $c): array { - // This an example of questions that can be asked to customize the project. - // You can adjust this method to ask questions that are relevant to your - // project. - // - // In this example, we ask for the package name, description, and license. - // - // You may remove all the questions below and replace them with your own. - return [ - 'Name' => [ - // The discover callback function is used to discover the value from the - // environment. In this case, we use the current directory name - // and the GITHUB_ORG environment variable to generate the package name. - // @phpstan-ignore-next-line - 'discover' => static function (CustomizeCommand $c): ?string { - $name = basename((string) getcwd()); - $org = getenv('GITHUB_ORG') ?: 'acme'; - - return $org . '/' . $name; - }, - // The question callback function defines how the question is asked. - // In this case, we ask the user to provide a package name as a string. - // The discovery callback is used to provide a default value. - // The question callback provides a capability to validate the answer - // before it can be accepted by providing a validation callback. - 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->ask('Package name', $discovered, static function (string $value): string { - // This is a validation callback that checks if the package name is - // valid. If not, an \InvalidArgumentException exception is thrown - // with a message shown to the user. - if (!preg_match('/^[a-z0-9_.-]+\/[a-z0-9_.-]+$/', $value)) { - throw new \InvalidArgumentException(sprintf('The package name "%s" is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name.', $value)); - } - - return $value; - }), - ], - 'Description' => [ - // For this question, we use an answer from the previous question - // in the title of the question. - 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->ask(sprintf('Description for %s', $answers['Name'])), - ], - 'License' => [ - // For this question, we use a pre-defined list of options. - // For discovery, we use a separate method named 'discoverLicense' - // (only for the demonstration purposes; it could have been an - // anonymous function). - 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->choice('License type', - [ - 'MIT', - 'GPL-3.0-or-later', - 'Apache-2.0', - ], - // Note that the default value is the value discovered by the - // 'discoverLicense' method. If the discovery did not return a value, - // the default value of 'GPL-3.0-or-later' is used. - empty($discovered) ? 'GPL-3.0-or-later' : $discovered - ), - ], - ]; - } - - /** - * A callback to discover the `License` value from the environment. - * - * This is an example callback, and it can be safely removed if this question - * is not needed. - * - * @param \AlexSkrypnyk\Customizer\CustomizeCommand $c - * The Customizer instance. - */ - public static function discoverLicense(CustomizeCommand $c): string { - return isset($c->composerjsonData['license']) && is_string($c->composerjsonData['license']) ? $c->composerjsonData['license'] : ''; - } - - /** - * A required callback to process all answers. - * - * This method is called after all questions have been answered and a user - * has confirmed the intent to proceed with the customization. - * - * Note that any manipulation of the composer.json file should be done here - * and then written back to the file system. - * - * @param array $answers - * Gathered answers. - * @param \AlexSkrypnyk\Customizer\CustomizeCommand $c - * The Customizer instance. - */ - public static function process(array $answers, CustomizeCommand $c): void { - $c->debug('Updating composer configuration'); - $json = $c->readComposerJson($c->composerjson); - $json['name'] = $answers['Name']; - $json['description'] = $answers['Description']; - $json['license'] = $answers['License']; - $c->writeComposerJson($c->composerjson, $json); - - $c->debug('Removing an arbitrary file.'); - $files = $c->finder($c->cwd)->files()->name('LICENSE'); - foreach ($files as $file) { - $c->fs->remove($file->getRealPath()); - } - } - - /** - * Cleanup after the customization. - * - * By the time this method is called, all the necessary changes have been made - * to the project. - * - * The Customizer will remove itself from the project and will update the - * composer.json as required. This method allows to alter that process as - * needed and, if necessary, cancel the original self-cleanup. - * - * @param \AlexSkrypnyk\Customizer\CustomizeCommand $c - * The CustomizeCommand object. - * - * @return bool - * Return FALSE to skip the further self-cleanup. Returning TRUE will - * proceed with the self-cleanup. - */ - public static function cleanup(CustomizeCommand $c): bool { - return TRUE; - } - - /** - * Override some of the messages displayed to the user by Customizer. - * - * @param \AlexSkrypnyk\Customizer\CustomizeCommand $c - * The Customizer instance. - * - * @return array> - * An associative array of messages with message name as key and the message - * test as a string or an array of strings. - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public static function messages(CustomizeCommand $c): array { - return [ - // This is an example of a custom message that overrides the default - // message with name `welcome`. - 'title' => 'Welcome to the "{{ package.name }}" project customizer', - ]; - } - -} diff --git a/tests/phpunit/Fixtures/command/no_install/base/customize.php b/tests/phpunit/Fixtures/command/no_install/base/customize.php deleted file mode 100644 index 1545452..0000000 --- a/tests/phpunit/Fixtures/command/no_install/base/customize.php +++ /dev/null @@ -1,201 +0,0 @@ -> - * An associative array of questions with question title as a key and the - * value of array with the following keys: - * - question: Required question callback function used to ask the question. - * The callback receives the following arguments: - * - discovered: A value discovered by the discover callback or NULL. - * - answers: An associative array of all answers received so far. - * - command: The CustomizeCommand object. - * - discover: Optional callback function used to discover the value from - * the environment. Can be an anonymous function or a method of this class - * as discover. If not provided, empty string will - * be passed to the question callback. The callback receives the following - * arguments: - * - command: The CustomizeCommand object. - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public static function questions(CustomizeCommand $c): array { - // This an example of questions that can be asked to customize the project. - // You can adjust this method to ask questions that are relevant to your - // project. - // - // In this example, we ask for the package name, description, and license. - // - // You may remove all the questions below and replace them with your own. - return [ - 'Name' => [ - // The discover callback function is used to discover the value from the - // environment. In this case, we use the current directory name - // and the GITHUB_ORG environment variable to generate the package name. - // @phpstan-ignore-next-line - 'discover' => static function (CustomizeCommand $c): ?string { - $name = basename((string) getcwd()); - $org = getenv('GITHUB_ORG') ?: 'acme'; - - return $org . '/' . $name; - }, - // The question callback function defines how the question is asked. - // In this case, we ask the user to provide a package name as a string. - // The discovery callback is used to provide a default value. - // The question callback provides a capability to validate the answer - // before it can be accepted by providing a validation callback. - 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->ask('Package name', $discovered, static function (string $value): string { - // This is a validation callback that checks if the package name is - // valid. If not, an \InvalidArgumentException exception is thrown - // with a message shown to the user. - if (!preg_match('/^[a-z0-9_.-]+\/[a-z0-9_.-]+$/', $value)) { - throw new \InvalidArgumentException(sprintf('The package name "%s" is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name.', $value)); - } - - return $value; - }), - ], - 'Description' => [ - // For this question, we use an answer from the previous question - // in the title of the question. - 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->ask(sprintf('Description for %s', $answers['Name'])), - ], - 'License' => [ - // For this question, we use a pre-defined list of options. - // For discovery, we use a separate method named 'discoverLicense' - // (only for the demonstration purposes; it could have been an - // anonymous function). - 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->choice('License type', - [ - 'MIT', - 'GPL-3.0-or-later', - 'Apache-2.0', - ], - // Note that the default value is the value discovered by the - // 'discoverLicense' method. If the discovery did not return a value, - // the default value of 'GPL-3.0-or-later' is used. - empty($discovered) ? 'GPL-3.0-or-later' : $discovered - ), - ], - ]; - } - - /** - * A callback to discover the `License` value from the environment. - * - * This is an example callback, and it can be safely removed if this question - * is not needed. - * - * @param \AlexSkrypnyk\Customizer\CustomizeCommand $c - * The Customizer instance. - */ - public static function discoverLicense(CustomizeCommand $c): string { - return isset($c->composerjsonData['license']) && is_string($c->composerjsonData['license']) ? $c->composerjsonData['license'] : ''; - } - - /** - * A required callback to process all answers. - * - * This method is called after all questions have been answered and a user - * has confirmed the intent to proceed with the customization. - * - * Note that any manipulation of the composer.json file should be done here - * and then written back to the file system. - * - * @param array $answers - * Gathered answers. - * @param \AlexSkrypnyk\Customizer\CustomizeCommand $c - * The Customizer instance. - */ - public static function process(array $answers, CustomizeCommand $c): void { - $c->debug('Updating composer configuration'); - $json = $c->readComposerJson($c->composerjson); - $json['name'] = $answers['Name']; - $json['description'] = $answers['Description']; - $json['license'] = $answers['License']; - $c->writeComposerJson($c->composerjson, $json); - - $c->debug('Removing an arbitrary file.'); - $files = $c->finder($c->cwd)->files()->name('LICENSE'); - foreach ($files as $file) { - $c->fs->remove($file->getRealPath()); - } - } - - /** - * Cleanup after the customization. - * - * By the time this method is called, all the necessary changes have been made - * to the project. - * - * The Customizer will remove itself from the project and will update the - * composer.json as required. This method allows to alter that process as - * needed and, if necessary, cancel the original self-cleanup. - * - * @param \AlexSkrypnyk\Customizer\CustomizeCommand $c - * The CustomizeCommand object. - * - * @return bool - * Return FALSE to skip the further self-cleanup. Returning TRUE will - * proceed with the self-cleanup. - */ - public static function cleanup(CustomizeCommand $c): bool { - if ($c->isComposerDependenciesInstalled) { - $c->debug('Add an example flag to composer.json.'); - $json = $c->readComposerJson($c->composerjson); - $json['extra'] = is_array($json['extra']) ? $json['extra'] : []; - $json['extra']['customizer'] = TRUE; - $c->writeComposerJson($c->composerjson, $json); - } - - return TRUE; - } - - /** - * Override some of the messages displayed to the user by Customizer. - * - * @param \AlexSkrypnyk\Customizer\CustomizeCommand $c - * The Customizer instance. - * - * @return array> - * An associative array of messages with message name as key and the message - * test as a string or an array of strings. - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public static function messages(CustomizeCommand $c): array { - return [ - // This is an example of a custom message that overrides the default - // message with name `welcome`. - 'title' => 'Welcome to the "{{ package.name }}" project customizer', - ]; - } - -} diff --git a/tests/phpunit/Fixtures/command/no_install_sub_dir/base/customize.php b/tests/phpunit/Fixtures/command/no_install_sub_dir/base/customize.php deleted file mode 100644 index 1545452..0000000 --- a/tests/phpunit/Fixtures/command/no_install_sub_dir/base/customize.php +++ /dev/null @@ -1,201 +0,0 @@ -> - * An associative array of questions with question title as a key and the - * value of array with the following keys: - * - question: Required question callback function used to ask the question. - * The callback receives the following arguments: - * - discovered: A value discovered by the discover callback or NULL. - * - answers: An associative array of all answers received so far. - * - command: The CustomizeCommand object. - * - discover: Optional callback function used to discover the value from - * the environment. Can be an anonymous function or a method of this class - * as discover. If not provided, empty string will - * be passed to the question callback. The callback receives the following - * arguments: - * - command: The CustomizeCommand object. - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public static function questions(CustomizeCommand $c): array { - // This an example of questions that can be asked to customize the project. - // You can adjust this method to ask questions that are relevant to your - // project. - // - // In this example, we ask for the package name, description, and license. - // - // You may remove all the questions below and replace them with your own. - return [ - 'Name' => [ - // The discover callback function is used to discover the value from the - // environment. In this case, we use the current directory name - // and the GITHUB_ORG environment variable to generate the package name. - // @phpstan-ignore-next-line - 'discover' => static function (CustomizeCommand $c): ?string { - $name = basename((string) getcwd()); - $org = getenv('GITHUB_ORG') ?: 'acme'; - - return $org . '/' . $name; - }, - // The question callback function defines how the question is asked. - // In this case, we ask the user to provide a package name as a string. - // The discovery callback is used to provide a default value. - // The question callback provides a capability to validate the answer - // before it can be accepted by providing a validation callback. - 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->ask('Package name', $discovered, static function (string $value): string { - // This is a validation callback that checks if the package name is - // valid. If not, an \InvalidArgumentException exception is thrown - // with a message shown to the user. - if (!preg_match('/^[a-z0-9_.-]+\/[a-z0-9_.-]+$/', $value)) { - throw new \InvalidArgumentException(sprintf('The package name "%s" is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name.', $value)); - } - - return $value; - }), - ], - 'Description' => [ - // For this question, we use an answer from the previous question - // in the title of the question. - 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->ask(sprintf('Description for %s', $answers['Name'])), - ], - 'License' => [ - // For this question, we use a pre-defined list of options. - // For discovery, we use a separate method named 'discoverLicense' - // (only for the demonstration purposes; it could have been an - // anonymous function). - 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->choice('License type', - [ - 'MIT', - 'GPL-3.0-or-later', - 'Apache-2.0', - ], - // Note that the default value is the value discovered by the - // 'discoverLicense' method. If the discovery did not return a value, - // the default value of 'GPL-3.0-or-later' is used. - empty($discovered) ? 'GPL-3.0-or-later' : $discovered - ), - ], - ]; - } - - /** - * A callback to discover the `License` value from the environment. - * - * This is an example callback, and it can be safely removed if this question - * is not needed. - * - * @param \AlexSkrypnyk\Customizer\CustomizeCommand $c - * The Customizer instance. - */ - public static function discoverLicense(CustomizeCommand $c): string { - return isset($c->composerjsonData['license']) && is_string($c->composerjsonData['license']) ? $c->composerjsonData['license'] : ''; - } - - /** - * A required callback to process all answers. - * - * This method is called after all questions have been answered and a user - * has confirmed the intent to proceed with the customization. - * - * Note that any manipulation of the composer.json file should be done here - * and then written back to the file system. - * - * @param array $answers - * Gathered answers. - * @param \AlexSkrypnyk\Customizer\CustomizeCommand $c - * The Customizer instance. - */ - public static function process(array $answers, CustomizeCommand $c): void { - $c->debug('Updating composer configuration'); - $json = $c->readComposerJson($c->composerjson); - $json['name'] = $answers['Name']; - $json['description'] = $answers['Description']; - $json['license'] = $answers['License']; - $c->writeComposerJson($c->composerjson, $json); - - $c->debug('Removing an arbitrary file.'); - $files = $c->finder($c->cwd)->files()->name('LICENSE'); - foreach ($files as $file) { - $c->fs->remove($file->getRealPath()); - } - } - - /** - * Cleanup after the customization. - * - * By the time this method is called, all the necessary changes have been made - * to the project. - * - * The Customizer will remove itself from the project and will update the - * composer.json as required. This method allows to alter that process as - * needed and, if necessary, cancel the original self-cleanup. - * - * @param \AlexSkrypnyk\Customizer\CustomizeCommand $c - * The CustomizeCommand object. - * - * @return bool - * Return FALSE to skip the further self-cleanup. Returning TRUE will - * proceed with the self-cleanup. - */ - public static function cleanup(CustomizeCommand $c): bool { - if ($c->isComposerDependenciesInstalled) { - $c->debug('Add an example flag to composer.json.'); - $json = $c->readComposerJson($c->composerjson); - $json['extra'] = is_array($json['extra']) ? $json['extra'] : []; - $json['extra']['customizer'] = TRUE; - $c->writeComposerJson($c->composerjson, $json); - } - - return TRUE; - } - - /** - * Override some of the messages displayed to the user by Customizer. - * - * @param \AlexSkrypnyk\Customizer\CustomizeCommand $c - * The Customizer instance. - * - * @return array> - * An associative array of messages with message name as key and the message - * test as a string or an array of strings. - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public static function messages(CustomizeCommand $c): array { - return [ - // This is an example of a custom message that overrides the default - // message with name `welcome`. - 'title' => 'Welcome to the "{{ package.name }}" project customizer', - ]; - } - -} diff --git a/tests/phpunit/Fixtures/command/install_additional_cleanup/base/composer.json b/tests/phpunit/Fixtures/install/base/composer.json similarity index 83% rename from tests/phpunit/Fixtures/command/install_additional_cleanup/base/composer.json rename to tests/phpunit/Fixtures/install/base/composer.json index 685dd21..8720161 100644 --- a/tests/phpunit/Fixtures/command/install_additional_cleanup/base/composer.json +++ b/tests/phpunit/Fixtures/install/base/composer.json @@ -8,9 +8,8 @@ "monolog/monolog": "^2.0" }, "require-dev": { - "alexskrypnyk/customizer": "^0.2" + "alexskrypnyk/customizer": "*" }, - "minimum-stability": "dev", "config": { "allow-plugins": { "alexskrypnyk/customizer": true diff --git a/tests/phpunit/Fixtures/install/base/customize.php b/tests/phpunit/Fixtures/install/base/customize.php new file mode 100644 index 0000000..22c1e69 --- /dev/null +++ b/tests/phpunit/Fixtures/install/base/customize.php @@ -0,0 +1,78 @@ + [ + 'discover' => static function (CustomizeCommand $c): string { + $name = basename((string) getcwd()); + $org = getenv('GITHUB_ORG') ?: 'acme'; + + return $org . '/' . $name; + }, + 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->ask('Package name', $discovered, static function (string $value): string { + if (!preg_match('/^[a-z0-9_.-]+\/[a-z0-9_.-]+$/', $value)) { + throw new \InvalidArgumentException(sprintf('The package name "%s" is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name.', $value)); + } + + return $value; + }), + ], + 'Description' => [ + 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->ask(sprintf('Description for %s', $answers['Name'])), + ], + 'License' => [ + 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->choice('License type', + [ + 'MIT', + 'GPL-3.0-or-later', + 'Apache-2.0', + ], + empty($discovered) ? 'GPL-3.0-or-later' : $discovered + ), + ], + ]; + } + + public static function discoverLicense(CustomizeCommand $c): string { + return isset($c->composerjsonData['license']) && is_string($c->composerjsonData['license']) ? $c->composerjsonData['license'] : ''; + } + + public static function process(array $answers, CustomizeCommand $c): void { + $c->debug('Updating composer configuration'); + $json = $c->readComposerJson($c->composerjson); + $json['name'] = $answers['Name']; + $json['description'] = $answers['Description']; + $json['license'] = $answers['License']; + $c->writeComposerJson($c->composerjson, $json); + + $c->debug('Removing an arbitrary file.'); + $files = $c->finder($c->cwd)->files()->name('LICENSE'); + foreach ($files as $file) { + $c->fs->remove($file->getRealPath()); + } + } + + public static function cleanup(CustomizeCommand $c): bool { + return TRUE; + } + + public static function messages(CustomizeCommand $c): array { + return [ + 'title' => 'Greetings from the customizer for the "{{ package.name }}" project', + ]; + } + +} diff --git a/tests/phpunit/Fixtures/install/expected/.ignorecontent b/tests/phpunit/Fixtures/install/expected/.ignorecontent new file mode 100644 index 0000000..98610ec --- /dev/null +++ b/tests/phpunit/Fixtures/install/expected/.ignorecontent @@ -0,0 +1,2 @@ +vendor/ +^composer.lock diff --git a/tests/phpunit/Fixtures/command/no_install/expected/composer.json b/tests/phpunit/Fixtures/install/expected/composer.json similarity index 83% rename from tests/phpunit/Fixtures/command/no_install/expected/composer.json rename to tests/phpunit/Fixtures/install/expected/composer.json index 6540581..c9da9a7 100644 --- a/tests/phpunit/Fixtures/command/no_install/expected/composer.json +++ b/tests/phpunit/Fixtures/install/expected/composer.json @@ -6,6 +6,5 @@ "require": { "php": ">=8.2", "monolog/monolog": "^2.0" - }, - "minimum-stability": "dev" + } } diff --git a/tests/phpunit/Fixtures/install/expected/composer.lock b/tests/phpunit/Fixtures/install/expected/composer.lock new file mode 100644 index 0000000..d349f0c --- /dev/null +++ b/tests/phpunit/Fixtures/install/expected/composer.lock @@ -0,0 +1 @@ +IGNORECONTENT diff --git a/tests/phpunit/Fixtures/command/install/base/composer.json b/tests/phpunit/Fixtures/install_additional_cleanup/base/composer.json similarity index 83% rename from tests/phpunit/Fixtures/command/install/base/composer.json rename to tests/phpunit/Fixtures/install_additional_cleanup/base/composer.json index 685dd21..8720161 100644 --- a/tests/phpunit/Fixtures/command/install/base/composer.json +++ b/tests/phpunit/Fixtures/install_additional_cleanup/base/composer.json @@ -8,9 +8,8 @@ "monolog/monolog": "^2.0" }, "require-dev": { - "alexskrypnyk/customizer": "^0.2" + "alexskrypnyk/customizer": "*" }, - "minimum-stability": "dev", "config": { "allow-plugins": { "alexskrypnyk/customizer": true diff --git a/tests/phpunit/Fixtures/install_additional_cleanup/base/customize.php b/tests/phpunit/Fixtures/install_additional_cleanup/base/customize.php new file mode 100644 index 0000000..f6501a1 --- /dev/null +++ b/tests/phpunit/Fixtures/install_additional_cleanup/base/customize.php @@ -0,0 +1,78 @@ + [ + 'discover' => static function (CustomizeCommand $c): string { + $name = basename((string) getcwd()); + $org = getenv('GITHUB_ORG') ?: 'acme'; + + return $org . '/' . $name; + }, + 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->ask('Package name', $discovered, static function (string $value): string { + if (!preg_match('/^[a-z0-9_.-]+\/[a-z0-9_.-]+$/', $value)) { + throw new \InvalidArgumentException(sprintf('The package name "%s" is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name.', $value)); + } + + return $value; + }), + ], + 'Description' => [ + 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->ask(sprintf('Description for %s', $answers['Name'])), + ], + 'License' => [ + 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->choice('License type', + [ + 'MIT', + 'GPL-3.0-or-later', + 'Apache-2.0', + ], + empty($discovered) ? 'GPL-3.0-or-later' : $discovered + ), + ], + ]; + } + + public static function discoverLicense(CustomizeCommand $c): string { + return isset($c->composerjsonData['license']) && is_string($c->composerjsonData['license']) ? $c->composerjsonData['license'] : ''; + } + + public static function process(array $answers, CustomizeCommand $c): void { + $c->debug('Updating composer configuration'); + $json = $c->readComposerJson($c->composerjson); + $json['name'] = $answers['Name']; + $json['description'] = $answers['Description']; + $json['license'] = $answers['License']; + $c->writeComposerJson($c->composerjson, $json); + + $c->debug('Removing an arbitrary file.'); + $files = $c->finder($c->cwd)->files()->name('LICENSE'); + foreach ($files as $file) { + $c->fs->remove($file->getRealPath()); + } + } + + public static function cleanup(CustomizeCommand $c): bool { + if ($c->isComposerDependenciesInstalled) { + $json = $c->readComposerJson($c->composerjson); + $json['homepage'] = 'https://example.com'; + $c->writeComposerJson($c->composerjson, $json); + } + + return TRUE; + } + +} diff --git a/tests/phpunit/Fixtures/install_additional_cleanup/expected/.ignorecontent b/tests/phpunit/Fixtures/install_additional_cleanup/expected/.ignorecontent new file mode 100644 index 0000000..98610ec --- /dev/null +++ b/tests/phpunit/Fixtures/install_additional_cleanup/expected/.ignorecontent @@ -0,0 +1,2 @@ +vendor/ +^composer.lock diff --git a/tests/phpunit/Fixtures/command/install_sub_dir/expected/composer.json b/tests/phpunit/Fixtures/install_additional_cleanup/expected/composer.json similarity index 84% rename from tests/phpunit/Fixtures/command/install_sub_dir/expected/composer.json rename to tests/phpunit/Fixtures/install_additional_cleanup/expected/composer.json index 6540581..9b83e9f 100644 --- a/tests/phpunit/Fixtures/command/install_sub_dir/expected/composer.json +++ b/tests/phpunit/Fixtures/install_additional_cleanup/expected/composer.json @@ -7,5 +7,5 @@ "php": ">=8.2", "monolog/monolog": "^2.0" }, - "minimum-stability": "dev" + "homepage": "https://example.com" } diff --git a/tests/phpunit/Fixtures/install_additional_cleanup/expected/composer.lock b/tests/phpunit/Fixtures/install_additional_cleanup/expected/composer.lock new file mode 100644 index 0000000..d349f0c --- /dev/null +++ b/tests/phpunit/Fixtures/install_additional_cleanup/expected/composer.lock @@ -0,0 +1 @@ +IGNORECONTENT diff --git a/tests/phpunit/Fixtures/command/install_no_config_file/base/composer.json b/tests/phpunit/Fixtures/install_no_config_file/base/composer.json similarity index 83% rename from tests/phpunit/Fixtures/command/install_no_config_file/base/composer.json rename to tests/phpunit/Fixtures/install_no_config_file/base/composer.json index 685dd21..8720161 100644 --- a/tests/phpunit/Fixtures/command/install_no_config_file/base/composer.json +++ b/tests/phpunit/Fixtures/install_no_config_file/base/composer.json @@ -8,9 +8,8 @@ "monolog/monolog": "^2.0" }, "require-dev": { - "alexskrypnyk/customizer": "^0.2" + "alexskrypnyk/customizer": "*" }, - "minimum-stability": "dev", "config": { "allow-plugins": { "alexskrypnyk/customizer": true diff --git a/tests/phpunit/Fixtures/install_no_config_file/expected/.ignorecontent b/tests/phpunit/Fixtures/install_no_config_file/expected/.ignorecontent new file mode 100644 index 0000000..98610ec --- /dev/null +++ b/tests/phpunit/Fixtures/install_no_config_file/expected/.ignorecontent @@ -0,0 +1,2 @@ +vendor/ +^composer.lock diff --git a/tests/phpunit/Fixtures/command/install_no_config_file/expected/composer.json b/tests/phpunit/Fixtures/install_no_config_file/expected/composer.json similarity index 53% rename from tests/phpunit/Fixtures/command/install_no_config_file/expected/composer.json rename to tests/phpunit/Fixtures/install_no_config_file/expected/composer.json index 685dd21..a0a9763 100644 --- a/tests/phpunit/Fixtures/command/install_no_config_file/expected/composer.json +++ b/tests/phpunit/Fixtures/install_no_config_file/expected/composer.json @@ -6,14 +6,5 @@ "require": { "php": ">=8.2", "monolog/monolog": "^2.0" - }, - "require-dev": { - "alexskrypnyk/customizer": "^0.2" - }, - "minimum-stability": "dev", - "config": { - "allow-plugins": { - "alexskrypnyk/customizer": true - } } } diff --git a/tests/phpunit/Fixtures/install_no_config_file/expected/composer.lock b/tests/phpunit/Fixtures/install_no_config_file/expected/composer.lock new file mode 100644 index 0000000..d349f0c --- /dev/null +++ b/tests/phpunit/Fixtures/install_no_config_file/expected/composer.lock @@ -0,0 +1 @@ +IGNORECONTENT diff --git a/tests/phpunit/Fixtures/command/no_install/base/composer.json b/tests/phpunit/Fixtures/no_install/base/composer.json similarity index 100% rename from tests/phpunit/Fixtures/command/no_install/base/composer.json rename to tests/phpunit/Fixtures/no_install/base/composer.json diff --git a/tests/phpunit/Fixtures/no_install/base/customize.php b/tests/phpunit/Fixtures/no_install/base/customize.php new file mode 100644 index 0000000..22c1e69 --- /dev/null +++ b/tests/phpunit/Fixtures/no_install/base/customize.php @@ -0,0 +1,78 @@ + [ + 'discover' => static function (CustomizeCommand $c): string { + $name = basename((string) getcwd()); + $org = getenv('GITHUB_ORG') ?: 'acme'; + + return $org . '/' . $name; + }, + 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->ask('Package name', $discovered, static function (string $value): string { + if (!preg_match('/^[a-z0-9_.-]+\/[a-z0-9_.-]+$/', $value)) { + throw new \InvalidArgumentException(sprintf('The package name "%s" is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name.', $value)); + } + + return $value; + }), + ], + 'Description' => [ + 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->ask(sprintf('Description for %s', $answers['Name'])), + ], + 'License' => [ + 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->choice('License type', + [ + 'MIT', + 'GPL-3.0-or-later', + 'Apache-2.0', + ], + empty($discovered) ? 'GPL-3.0-or-later' : $discovered + ), + ], + ]; + } + + public static function discoverLicense(CustomizeCommand $c): string { + return isset($c->composerjsonData['license']) && is_string($c->composerjsonData['license']) ? $c->composerjsonData['license'] : ''; + } + + public static function process(array $answers, CustomizeCommand $c): void { + $c->debug('Updating composer configuration'); + $json = $c->readComposerJson($c->composerjson); + $json['name'] = $answers['Name']; + $json['description'] = $answers['Description']; + $json['license'] = $answers['License']; + $c->writeComposerJson($c->composerjson, $json); + + $c->debug('Removing an arbitrary file.'); + $files = $c->finder($c->cwd)->files()->name('LICENSE'); + foreach ($files as $file) { + $c->fs->remove($file->getRealPath()); + } + } + + public static function cleanup(CustomizeCommand $c): bool { + return TRUE; + } + + public static function messages(CustomizeCommand $c): array { + return [ + 'title' => 'Greetings from the customizer for the "{{ package.name }}" project', + ]; + } + +} diff --git a/tests/phpunit/Fixtures/command/no_install_sub_dir/expected/composer.json b/tests/phpunit/Fixtures/no_install/expected/composer.json similarity index 83% rename from tests/phpunit/Fixtures/command/no_install_sub_dir/expected/composer.json rename to tests/phpunit/Fixtures/no_install/expected/composer.json index 6540581..c9da9a7 100644 --- a/tests/phpunit/Fixtures/command/no_install_sub_dir/expected/composer.json +++ b/tests/phpunit/Fixtures/no_install/expected/composer.json @@ -6,6 +6,5 @@ "require": { "php": ">=8.2", "monolog/monolog": "^2.0" - }, - "minimum-stability": "dev" + } } diff --git a/tests/phpunit/Fixtures/command/no_install_sub_dir/base/composer.json b/tests/phpunit/Fixtures/no_install_sub_dir/base/composer.json similarity index 100% rename from tests/phpunit/Fixtures/command/no_install_sub_dir/base/composer.json rename to tests/phpunit/Fixtures/no_install_sub_dir/base/composer.json diff --git a/tests/phpunit/Fixtures/no_install_sub_dir/base/customize.php b/tests/phpunit/Fixtures/no_install_sub_dir/base/customize.php new file mode 100644 index 0000000..50f6e2b --- /dev/null +++ b/tests/phpunit/Fixtures/no_install_sub_dir/base/customize.php @@ -0,0 +1,80 @@ + [ + 'discover' => static function (CustomizeCommand $c): string { + $name = basename((string) getcwd()); + $org = getenv('GITHUB_ORG') ?: 'acme'; + + return $org . '/' . $name; + }, + 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->ask('Package name', $discovered, static function (string $value): string { + if (!preg_match('/^[a-z0-9_.-]+\/[a-z0-9_.-]+$/', $value)) { + throw new \InvalidArgumentException(sprintf('The package name "%s" is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name.', $value)); + } + + return $value; + }), + ], + 'Description' => [ + 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->ask(sprintf('Description for %s', $answers['Name'])), + ], + 'License' => [ + 'question' => static fn(string $discovered, array $answers, CustomizeCommand $c): mixed => $c->io->choice('License type', + [ + 'MIT', + 'GPL-3.0-or-later', + 'Apache-2.0', + ], + empty($discovered) ? 'GPL-3.0-or-later' : $discovered + ), + ], + ]; + } + + public static function discoverLicense(CustomizeCommand $c): string { + return isset($c->composerjsonData['license']) && is_string($c->composerjsonData['license']) ? $c->composerjsonData['license'] : ''; + } + + public static function process(array $answers, CustomizeCommand $c): void { + $c->debug('Updating composer configuration'); + $json = $c->readComposerJson($c->composerjson); + $json['name'] = $answers['Name']; + $json['description'] = $answers['Description']; + $json['license'] = $answers['License']; + $c->writeComposerJson($c->composerjson, $json); + + $c->debug('Removing an arbitrary file.'); + $files = $c->finder($c->cwd)->files()->name('LICENSE'); + foreach ($files as $file) { + $c->fs->remove($file->getRealPath()); + } + } + + public static function cleanup(CustomizeCommand $c): bool { + if ($c->isComposerDependenciesInstalled) { + $c->debug('Add an example flag to composer.json.'); + $json = $c->readComposerJson($c->composerjson); + $json['extra'] = is_array($json['extra']) ? $json['extra'] : []; + $json['extra']['customizer'] = TRUE; + $c->writeComposerJson($c->composerjson, $json); + } + + return TRUE; + } + +} diff --git a/tests/phpunit/Fixtures/command/install/expected/composer.json b/tests/phpunit/Fixtures/no_install_sub_dir/expected/composer.json similarity index 83% rename from tests/phpunit/Fixtures/command/install/expected/composer.json rename to tests/phpunit/Fixtures/no_install_sub_dir/expected/composer.json index 6540581..c9da9a7 100644 --- a/tests/phpunit/Fixtures/command/install/expected/composer.json +++ b/tests/phpunit/Fixtures/no_install_sub_dir/expected/composer.json @@ -6,6 +6,5 @@ "require": { "php": ">=8.2", "monolog/monolog": "^2.0" - }, - "minimum-stability": "dev" + } } diff --git a/tests/phpunit/Functional/CreateProjectCommandInstallTest.php b/tests/phpunit/Functional/CreateProjectCommandInstallTest.php index 0b3d385..3c0a485 100644 --- a/tests/phpunit/Functional/CreateProjectCommandInstallTest.php +++ b/tests/phpunit/Functional/CreateProjectCommandInstallTest.php @@ -13,82 +13,55 @@ * Test Customizer as a single-file drop-in during `composer create-project`. */ #[CoversClass(CustomizeCommand::class)] -#[Group('command')] -class CreateProjectCommandInstallTest extends CreateProjectCommandTestCase { +#[Group('install')] +class CreateProjectCommandInstallTest extends CustomizerTestCase { - #[Group('install')] #[RunInSeparateProcess] public function testInstall(): void { - $this->customizerSetAnswers([ + static::customizerSetAnswers([ 'testorg/testpackage', 'Test description', 'MIT', self::TUI_ANSWER_NOTHING, ]); - $this->composerCreateProject(); + $this->runComposerCreateProject(); - $this->assertComposerCommandSuccessOutputContains('Welcome to the "yourorg/yourtempaltepackage" project customizer'); + // Custom welcome message. + $this->assertComposerCommandSuccessOutputContains('Greetings from the customizer for the "yourorg/yourtempaltepackage" project',); $this->assertComposerCommandSuccessOutputContains('Project was customized'); - $this->assertComposerLockUpToDate(); - $this->assertFileEquals($this->dirs->fixtures . DIRECTORY_SEPARATOR . 'expected' . DIRECTORY_SEPARATOR . 'composer.json', $this->dirs->sut . DIRECTORY_SEPARATOR . 'composer.json'); - $this->assertDirectoryExists($this->dirs->sut . DIRECTORY_SEPARATOR . 'vendor'); - $this->assertDirectoryDoesNotExist($this->dirs->sut . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'alexskrypnyk/customizer'); - } - #[Group('install')] - #[RunInSeparateProcess] - public function testInstallSubDir(): void { - $this->dirs->fs->mkdir($this->dirs->repo . DIRECTORY_SEPARATOR . 'src'); - $this->dirs->fs->rename( - $this->dirs->repo . DIRECTORY_SEPARATOR . $this->customizerFile, - $this->dirs->repo . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . $this->customizerFile - ); - - $this->customizerSetAnswers([ - 'testorg/testpackage', - 'Test description', - 'MIT', - self::TUI_ANSWER_NOTHING, - ]); - - $this->composerCreateProject(); - - $this->assertComposerCommandSuccessOutputContains('Welcome to the "yourorg/yourtempaltepackage" project customizer'); - $this->assertComposerCommandSuccessOutputContains('Project was customized'); + $this->assertFixtureDirectoriesEqual(); $this->assertComposerLockUpToDate(); - $this->assertFileEquals($this->dirs->fixtures . DIRECTORY_SEPARATOR . 'expected' . DIRECTORY_SEPARATOR . 'composer.json', $this->dirs->sut . DIRECTORY_SEPARATOR . 'composer.json'); - $this->assertDirectoryExists($this->dirs->sut . DIRECTORY_SEPARATOR . 'vendor'); - $this->assertDirectoryDoesNotExist($this->dirs->sut . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'alexskrypnyk/customizer'); } - #[Group('install')] #[RunInSeparateProcess] public function testInstallAdditionalCleanup(): void { - $this->customizerSetAnswers([ + static::customizerSetAnswers([ 'testorg/testpackage', 'Test description', 'MIT', self::TUI_ANSWER_NOTHING, ]); - $this->composerCreateProject(); + $this->runComposerCreateProject(); $this->assertComposerCommandSuccessOutputContains('Welcome to the "yourorg/yourtempaltepackage" project customizer'); $this->assertComposerCommandSuccessOutputContains('Project was customized'); + + $this->assertFixtureDirectoriesEqual(); $this->assertComposerLockUpToDate(); - $this->assertFileEquals($this->dirs->fixtures . DIRECTORY_SEPARATOR . 'expected' . DIRECTORY_SEPARATOR . 'composer.json', $this->dirs->sut . DIRECTORY_SEPARATOR . 'composer.json'); } - #[Group('install')] #[RunInSeparateProcess] public function testInstallNoConfigFile(): void { - $this->composerCreateProject(); + $this->runComposerCreateProject(); + + $this->assertComposerCommandSuccessOutputContains('Welcome to the "yourorg/yourtempaltepackage" project customizer'); $this->assertComposerCommandSuccessOutputContains('No questions were found. No changes were made'); + + $this->assertFixtureDirectoriesEqual(); $this->assertComposerLockUpToDate(); - $this->assertFileEquals($this->dirs->fixtures . DIRECTORY_SEPARATOR . 'expected' . DIRECTORY_SEPARATOR . 'composer.json', $this->dirs->sut . DIRECTORY_SEPARATOR . 'composer.json'); - $this->assertDirectoryExists($this->dirs->sut . DIRECTORY_SEPARATOR . 'vendor'); - $this->assertDirectoryExists($this->dirs->sut . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'alexskrypnyk/customizer'); } } diff --git a/tests/phpunit/Functional/CreateProjectCommandNoInstallTest.php b/tests/phpunit/Functional/CreateProjectCommandNoInstallTest.php index e389913..68df937 100644 --- a/tests/phpunit/Functional/CreateProjectCommandNoInstallTest.php +++ b/tests/phpunit/Functional/CreateProjectCommandNoInstallTest.php @@ -13,8 +13,8 @@ * Test Customizer using `composer create-project --no-install`. */ #[CoversClass(CustomizeCommand::class)] -#[Group('command')] -class CreateProjectCommandNoInstallTest extends CreateProjectCommandTestCase { +#[Group('no-install')] +class CreateProjectCommandNoInstallTest extends CustomizerTestCase { /** * {@inheritdoc} @@ -23,59 +23,57 @@ protected function setUp(): void { parent::setUp(); // Update the 'autoload' to include the command file from the project // root to get code test coverage. - $json = CustomizeCommand::readComposerJson($this->dirs->repo . '/composer.json'); + $json = CustomizeCommand::readComposerJson(static::$repo . '/composer.json'); $json['autoload'] = is_array($json['autoload']) ? $json['autoload'] : []; - $json['autoload']['classmap'] = [$this->dirs->root . DIRECTORY_SEPARATOR . $this->customizerFile]; - CustomizeCommand::writeComposerJson($this->dirs->repo . '/composer.json', $json); + $json['autoload']['classmap'] = [static::$root . DIRECTORY_SEPARATOR . 'CustomizeCommand.php']; + CustomizeCommand::writeComposerJson(static::$repo . '/composer.json', $json); } - #[Group('no-install')] - #[Group('smoke')] #[RunInSeparateProcess] public function testNoInstall(): void { - $this->customizerSetAnswers([ + CustomizerTestCase::customizerSetAnswers([ 'testorg/testpackage', 'Test description', 'MIT', self::TUI_ANSWER_NOTHING, ]); - $this->composerCreateProject(['--no-install' => TRUE]); + $this->runComposerCreateProject(['--no-install' => TRUE]); - $this->assertComposerCommandSuccessOutputContains('Welcome to the "yourorg/yourtempaltepackage" project customizer'); + // Custom welcome message. + $this->assertComposerCommandSuccessOutputContains('Greetings from the customizer for the "yourorg/yourtempaltepackage" project',); $this->assertComposerCommandSuccessOutputContains('Project was customized'); - $this->assertFixtureFiles(); + $this->assertFixtureDirectoriesEqual(); } #[RunInSeparateProcess] - #[Group('no-install')] public function testNoInstallSubDir(): void { // Move the command stub pre-created in setUp() to the 'src' directory. - $this->dirs->fs->mkdir($this->dirs->repo . DIRECTORY_SEPARATOR . 'src'); - $this->dirs->fs->rename( - $this->dirs->repo . DIRECTORY_SEPARATOR . $this->customizerFile, - $this->dirs->repo . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . $this->customizerFile + $this->fs->mkdir(static::$repo . DIRECTORY_SEPARATOR . 'src'); + $this->fs->rename( + static::$repo . DIRECTORY_SEPARATOR . 'CustomizeCommand.php', + static::$repo . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'CustomizeCommand.php' ); - $json = CustomizeCommand::readComposerJson($this->dirs->repo . DIRECTORY_SEPARATOR . 'composer.json'); + $json = CustomizeCommand::readComposerJson(static::$repo . DIRECTORY_SEPARATOR . 'composer.json'); $json['autoload'] = is_array($json['autoload']) ? $json['autoload'] : []; - $json['autoload']['classmap'] = ['src/' . $this->customizerFile]; - CustomizeCommand::writeComposerJson($this->dirs->repo . DIRECTORY_SEPARATOR . 'composer.json', $json); + $json['autoload']['classmap'] = ['src/CustomizeCommand.php']; + CustomizeCommand::writeComposerJson(static::$repo . DIRECTORY_SEPARATOR . 'composer.json', $json); - $this->customizerSetAnswers([ + CustomizerTestCase::customizerSetAnswers([ 'testorg/testpackage', 'Test description', 'MIT', self::TUI_ANSWER_NOTHING, ]); - $this->composerCreateProject(['--no-install' => TRUE]); + $this->runComposerCreateProject(['--no-install' => TRUE]); $this->assertComposerCommandSuccessOutputContains('Welcome to the "yourorg/yourtempaltepackage" project customizer'); $this->assertComposerCommandSuccessOutputContains('Project was customized'); - $this->assertFixtureFiles(); + $this->assertFixtureDirectoriesEqual(); } } diff --git a/tests/phpunit/Functional/CreateProjectCommandTestCase.php b/tests/phpunit/Functional/CreateProjectCommandTestCase.php deleted file mode 100644 index 44dbf86..0000000 --- a/tests/phpunit/Functional/CreateProjectCommandTestCase.php +++ /dev/null @@ -1,65 +0,0 @@ -name(); - - $reflector = new \ReflectionClass(CustomizeCommand::class); - $this->customizerFile = basename((string) $reflector->getFileName()); - - // Initialize the Composer command tester. - $this->composerCommandInit(); - - // Initialize the directories. - $this->dirsInit(function (Dirs $dirs) use ($test_name): void { - $this->dirs->fixtures .= DIRECTORY_SEPARATOR . 'command' . DIRECTORY_SEPARATOR . static::toFixtureDirName($test_name); - if (!is_dir($this->dirs->fixtures)) { - throw new \RuntimeException('The fixtures directory does not exist: ' . $this->dirs->fixtures); - } - - $dirs->fs->mirror( - $this->dirs->fixtures . DIRECTORY_SEPARATOR . 'base', - $dirs->repo - ); - - // Create an empty command file in the 'system under test' to replicate a - // real scenario during test where the file is manually copied into a real - // project and then removed by the command after customization runs. - $dirs->fs->touch($dirs->repo . DIRECTORY_SEPARATOR . $this->customizerFile); - }); - - // Update the 'autoload' to include the command file from the project - // root to get code test coverage. - $json = CustomizeCommand::readComposerJson($this->dirs->repo . '/composer.json'); - - // Save the test package name for later use in tests. - $this->packageName = is_string($json['name']) ? $json['name'] : ''; - - // Change the current working directory to the 'system under test'. - chdir($this->dirs->sut); - } - -} diff --git a/tests/phpunit/Functional/CustomizerTestCase.php b/tests/phpunit/Functional/CustomizerTestCase.php index e880000..0b4ca5f 100644 --- a/tests/phpunit/Functional/CustomizerTestCase.php +++ b/tests/phpunit/Functional/CustomizerTestCase.php @@ -5,43 +5,83 @@ namespace AlexSkrypnyk\Customizer\Tests\Functional; use AlexSkrypnyk\Customizer\CustomizeCommand; -use AlexSkrypnyk\Customizer\Tests\Dirs; -use AlexSkrypnyk\Customizer\Tests\Traits\CmdTrait; -use AlexSkrypnyk\Customizer\Tests\Traits\ComposerTrait; -use AlexSkrypnyk\Customizer\Tests\Traits\DirsTrait; +use Composer\Console\Application; +use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestStatus\Error; use PHPUnit\Framework\TestStatus\Failure; +use Symfony\Component\Console\Tester\ApplicationTester; +use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; +use Symfony\Component\Process\Process; /** * 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 + * 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 { - use ComposerTrait; - use DirsTrait; - use CmdTrait; - /** * TUI answer to indicate that the user did not provide any input. */ const TUI_ANSWER_NOTHING = 'NOTHING'; + /** + * Path to the fixtures directory from the repository root. + */ + const FIXTURES_DIR = 'tests/phpunit/Fixtures'; + + /** + * Path to the root directory of this project. + */ + protected static string $root; + + /** + * Path to the fixtures directory from the root of this project. + */ + protected static string $fixtures; + + /** + * Main build directory where the rest of the directories located. + * + * The "build" in this context is a place to store assets produced by a single + * test run. + */ + protected static string $build; + + /** + * Directory used as a source in the operations. + * + * Could be a copy of the current repository with custom adjustments or a + * fixture repository. + */ + protected static string $repo; + + /** + * Directory where the test will run. + */ + protected static string $sut; + /** * The source package name used in tests. */ protected string $packageName; /** - * The Customizer file name. + * The application tester. */ - protected string $customizerFile; + protected ApplicationTester $tester; /** - * The Composer JSON file name. + * The file system. */ - protected static string $composerJsonFile = 'composer.json'; + protected Filesystem $fs; /** * {@inheritdoc} @@ -49,48 +89,131 @@ class CustomizerTestCase extends TestCase { protected function setUp(): void { parent::setUp(); - if (!isset(static::$composerJsonFile)) { - throw new \RuntimeException('The $composerJsonFile property must be set in the child class.'); - } - - $reflector = new \ReflectionClass(CustomizeCommand::class); - $this->customizerFile = basename((string) $reflector->getFileName()); - - // Initialize the Composer command tester. - $this->composerCommandInit(); + $this->initComposerTester(); - $this->dirsInit(static function (Dirs $dirs): void { - $dirs->fs->copy($dirs->root . DIRECTORY_SEPARATOR . static::$composerJsonFile, $dirs->repo . '/composer.json'); - // Copy the configuration file. - $dirs->fs->copy($dirs->root . DIRECTORY_SEPARATOR . CustomizeCommand::CONFIG_FILE, $dirs->repo . DIRECTORY_SEPARATOR . CustomizeCommand::CONFIG_FILE); - }, (string) getcwd()); + $this->initLocations((string) getcwd()); - // Projects using this project through a plugin need to have this + // Projects using this project through a plugin must have this // repository added to their composer.json to be able to download it // during the test. - $json = CustomizeCommand::readComposerJson($this->dirs->repo . '/composer.json'); + $json = CustomizeCommand::readComposerJson(static::$repo . '/composer.json'); + $json['minimum-stability'] = 'dev'; $json['repositories'] = [ [ 'type' => 'path', - 'url' => $this->dirs->root, + 'url' => static::$root, 'options' => ['symlink' => TRUE], ], ]; - CustomizeCommand::writeComposerJson($this->dirs->repo . '/composer.json', $json); + CustomizeCommand::writeComposerJson(static::$repo . '/composer.json', $json); // Save the package name for later use in tests. $this->packageName = is_string($json['name']) ? $json['name'] : ''; // Change the current working directory to the 'system under test'. - chdir($this->dirs->sut); + chdir(static::$sut); + } + + /** + * Initialize the Composer command tester. + */ + protected function initComposerTester(): void { + $application = new Application(); + $application->setAutoExit(FALSE); + $application->setCatchExceptions(FALSE); + if (method_exists($application, 'setCatchErrors')) { + $application->setCatchErrors(FALSE); + } + + $this->tester = new ApplicationTester($application); + + // Composer autoload uses per-project Composer binary, if the + // `composer/composer` is included in the project as a dependency. + // + // When the test runs and creates SUT, the Composer binary used is + // from the SUT's `vendor` directory. The Customizer may remove the + // `vendor/composer/composer` directory as a part of the cleanup, resulting + // in the Composer autoloader having an empty path to the Composer binary. + // + // This is extremely difficult to debug, because there is no clear error + // message apart from `Could not open input file`. + // + // To prevent this, we set the `COMPOSER_BINARY` environment variable to the + // Composer binary path found in the system. + // @see \Composer\EventDispatcher::doDispatch(). + $composer_bin = shell_exec(escapeshellcmd('which composer')); + if ($composer_bin === FALSE) { + throw new \RuntimeException('Composer binary not found'); + } + putenv('COMPOSER_BINARY=' . trim((string) $composer_bin)); + } + + /** + * Initialize the locations. + * + * @param string $cwd + * The current working directory. + * @param callable|null $cb + * Callback to run after initialization. + */ + protected function initLocations(string $cwd, ?callable $cb = NULL): void { + $this->fs = new Filesystem(); + + static::$root = (string) realpath($cwd); + if (!is_dir(static::$root)) { + throw new \RuntimeException('The repository root directory does not exist: ' . static::$root); + } + + static::$fixtures = static::$root . DIRECTORY_SEPARATOR . static::FIXTURES_DIR; + if (!is_dir(static::$fixtures)) { + throw new \RuntimeException('The fixtures directory does not exist: ' . static::$fixtures); + } + + static::$build = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'customizer-' . microtime(TRUE); + static::$sut = static::$build . '/sut'; + static::$repo = static::$build . '/local_repo'; + + $this->fs->mkdir(static::$build); + $this->fs->mkdir(static::$sut); + $this->fs->mkdir(static::$repo); + + // Set the fixtures directory based on the test name. + $fixture_dir = $this->name(); + $fixture_dir = str_contains($fixture_dir, '::') ? explode('::', $fixture_dir)[1] : $fixture_dir; + $fixture_dir = strtolower((string) preg_replace('/(?usesDataProvider() && !empty($this->dataName())) { + static::$fixtures .= DIRECTORY_SEPARATOR . $this->dataName(); + } + + // Copy the 'base' fixture files to the repository if they were provided for + // this test. + if (is_dir(static::$fixtures)) { + $this->fs->mirror(static::$fixtures . DIRECTORY_SEPARATOR . 'base', static::$repo); + } + + // Create an empty command file in the 'system under test' to replicate a + // real scenario during test where the file is manually copied into a real + // project and then removed by the command after customization runs. + $this->fs->touch(static::$repo . DIRECTORY_SEPARATOR . 'CustomizeCommand.php'); + + if ($cb !== NULL && is_callable($cb) && $cb instanceof \Closure) { + // @phpstan-ignore-next-line + \Closure::bind($cb, $this, self::class)(); + } } /** * {@inheritdoc} */ protected function tearDown(): void { - if (!$this->hasFailed()) { - $this->dirsClean(); + // Clean up the directories if the test passed. + if (!$this->status() instanceof Failure && !$this->status() instanceof Error) { + $this->fs->remove(static::$build); } parent::tearDown(); @@ -100,26 +223,56 @@ protected function tearDown(): void { * {@inheritdoc} */ protected function onNotSuccessfulTest(\Throwable $t): never { - fwrite(STDERR, 'see below' . PHP_EOL . PHP_EOL . $this->dirsInfo() . PHP_EOL . $t->getMessage() . PHP_EOL); + // Print the locations information and the exception message. + $lines[] = '-- LOCATIONS --'; + $lines[] = 'Root : ' . static::$root; + $lines[] = 'Fixtures : ' . static::$fixtures; + $lines[] = 'Build : ' . static::$build; + $lines[] = 'Local repo : ' . static::$repo; + $lines[] = 'SUT : ' . static::$sut; + $info = implode(PHP_EOL, $lines) . PHP_EOL; + + fwrite(STDERR, 'see below' . PHP_EOL . PHP_EOL . $info . PHP_EOL . $t->getMessage() . PHP_EOL); parent::onNotSuccessfulTest($t); } /** - * Check if the test has failed. + * Run an arbitrary command. + * + * @param string $cmd + * The command to execute (escaped as required) + * @param string $cwd + * The current working directory to run the command from. + * @param array $env + * Environment variables to define for the subprocess. * - * @return bool - * TRUE if the test has failed, FALSE otherwise. + * @return string + * Standard output from the command */ - public function hasFailed(): bool { - $status = $this->status(); + protected static function runCmd(string $cmd, ?string $cwd, array $env = []): string { + $env += $env + ['PATH' => getenv('PATH'), 'HOME' => getenv('HOME')]; + + $process = Process::fromShellCommandline($cmd, $cwd, $env); + $process->setTimeout(300)->setIdleTimeout(300)->run(); + + $code = $process->getExitCode(); + if (0 != $code) { + throw new \RuntimeException("Exit code: {$code}\n\n" . $process->getErrorOutput() . "\n\n" . $process->getOutput()); + } - return $status instanceof Failure || $status instanceof Error; + return $process->getOutput(); } - protected function customizerSetAnswers(array $answers): void { + /** + * Set the answers for the Customizer TUI. + * + * @param array $answers + * The answers to set. + */ + protected static function customizerSetAnswers(array $answers): void { foreach ($answers as $key => $answer) { - if ($answer === self::TUI_ANSWER_NOTHING) { + if ($answer === static::TUI_ANSWER_NOTHING) { $answers[$key] = "\n"; } } @@ -127,16 +280,22 @@ protected function customizerSetAnswers(array $answers): void { putenv('CUSTOMIZER_ANSWERS=' . json_encode($answers)); } - protected function composerCreateProject(array $options = []): void { + /** + * Run the `composer create-project` command with the given options. + * + * @param array> $options + * The command options. + */ + protected function runComposerCreateProject(array $options = []): void { $defaults = [ 'command' => 'create-project', 'package' => $this->packageName, - 'directory' => $this->dirs->sut, + 'directory' => static::$sut, 'version' => '@dev', '--repository' => [ json_encode([ 'type' => 'path', - 'url' => $this->dirs->repo, + 'url' => static::$repo, 'options' => ['symlink' => FALSE], ]), ], @@ -145,87 +304,296 @@ protected function composerCreateProject(array $options = []): void { $this->tester->run($options + $defaults); } + /** + * Assert that the Composer lock file is up to date. + */ protected function assertComposerLockUpToDate(): void { + if (!empty(getenv('UPDATE_TEST_FIXTURES'))) { + return; + } + $this->assertFileExists('composer.lock'); - $this->cmdRun('composer validate', $this->dirs->sut); + static::runCmd('composer validate', static::$sut); } /** - * Assert that the fixture files match the actual files. + * Assert that the Composer JSON files match. * - * @param array $exclude - * The list of files to exclude. + * @param string $expected + * The expected file. + * @param string $actual + * The actual file. */ - protected function assertFixtureFiles(array $exclude = []): void { - $expected = $this->dirs->fixtures . '/expected'; - $actual = $this->dirs->sut; - - if (!empty(getenv('UPDATE_TEST_FIXTURES'))) { - $this->dirs->fs->remove($expected); + protected function assertComposerJsonFilesEqual(string $expected, string $actual): void { + $this->assertFileExists($expected); + $this->assertFileExists($actual); - $finder = new Finder(); - $finder - ->ignoreDotFiles(FALSE) - ->ignoreVCS(TRUE) - ->files() - ->exclude($exclude) - ->in($actual); + $expected = json_decode((string) file_get_contents($expected), TRUE); - $this->dirs->fs->mirror($actual, $expected, $finder->getIterator()); + // Remove test data. + $data = json_decode((string) file_get_contents($actual), TRUE); + if (!is_array($data)) { + $this->fail('The actual file is not a valid JSON file.'); + } + unset($data['minimum-stability']); + foreach ($data['repositories'] as $key => $repository) { + if ($repository['type'] === 'path' && $repository['url'] === static::$root) { + unset($data['repositories'][$key]); + } } - else { - $this->assertDirsEqual($expected, $actual, $exclude); + if (empty($data['repositories'])) { + unset($data['repositories']); } + file_put_contents($actual, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . PHP_EOL); + + $this->assertSame($expected, $actual); } /** - * Compare directories. + * Assert successful Composer command output contains the expected strings. * - * @param string $expected - * The expected directory. - * @param string $actual - * The actual directory. - * @param array $exclude - * The list of files to exclude. - */ - protected function assertDirsEqual(string $expected, string $actual, array $exclude = []): void { - $finder_expected = new Finder(); - $finder_expected - ->ignoreDotFiles(FALSE) - ->ignoreVCS(TRUE) - ->exclude($exclude) - ->files() - ->in($expected); - - $finder_actual = new Finder(); - $finder_actual - ->ignoreDotFiles(FALSE) - ->ignoreVCS(TRUE) - ->exclude($exclude) - ->files() - ->in($actual); - - // Check that all files in expected are present in actual and are equal. - foreach ($finder_expected as $file) { - $this->assertFileExists($actual . '/' . $file->getRelativePathname()); - $this->assertFileEquals($file->getPathname(), $actual . '/' . $file->getRelativePathname()); - } - - // Check that there are no unexpected files in actual. - foreach ($finder_actual as $file) { - $this->assertFileExists($expected . '/' . $file->getRelativePathname(), 'Unexpected file found: ' . $file->getRelativePathname()); + * @param string|array $strings + * The expected strings. + */ + protected function assertComposerCommandSuccessOutputContains(string|array $strings): void { + $strings = is_array($strings) ? $strings : [$strings]; + + if ($this->tester->getStatusCode() !== 0) { + $this->fail($this->tester->getDisplay()); + } + $this->assertSame(0, $this->tester->getStatusCode(), sprintf("The Composer command should have completed successfully:\n%s", $this->tester->getInput()->__toString())); + + $output = $this->tester->getDisplay(TRUE); + foreach ($strings as $string) { + $this->assertStringContainsString($string, $output); } } /** - * Convert a test name to a fixture directory name. + * Assert that fixtures directories are equal. */ - protected static function toFixtureDirName(string $name): string { - $name = str_contains($name, '::') ? explode('::', $name)[1] : $name; - $name = strtolower((string) preg_replace('/(?assertDirectoriesEqual(static::$fixtures . DIRECTORY_SEPARATOR . 'expected', static::$sut); + } + + /** + * Assert that 2 directories have the same files and content, ignoring some. + * + * The main purpose of this method is to allow to create before/after file + * structures and compare them, ignoring some files or content changes. This + * allows to create fixture hierarchies fast. + * + * The first directory could be updated using the files from the second + * directory if the environment variable `UPDATE_TEST_FIXTURES` is set. + * This is useful to update the fixtures after the changes in the code. + * + * Files can be excluded from the comparison completely or only checked for + * presence and ignored for the content changes using a .gitignore-like + * file `.ignorecontent` that can be placed in the second directory. + * + * The syntax for the file is similar to .gitignore with addition of + * the content ignoring using ^ prefix: + * Comments start with #. + * file Ignore file. + * dir/ Ignore directory and all subdirectories. + * dir/* Ignore all files in directory, but not subdirectories. + * ^file Ignore content changes in file, but not the file itself. + * ^dir/ Ignore content changes in all files and subdirectories, but check + * that the directory itself exists. + * ^dir/* Ignore content changes in all files, but not subdirectories and + * check that the directory itself exists. + * !file Do not ignore file. + * !dir/ Do not ignore directory, including all subdirectories. + * !dir/* Do not ignore all files in directory, but not subdirectories. + * !^file Do not ignore content changes in file. + * !^dir/ Do not ignore content changes in all files and subdirectories. + * !^dir/* Do not ignore content changes in all files, but not subdirectories. + * + * This assertion method is deliberately used as a single assertion for + * portability. + * + * @param string $dir1 + * The first directory. + * @param string $dir2 + * The second directory. + * + * @throws \PHPUnit\Framework\AssertionFailedError + * If the directories are not equal. + */ + protected function assertDirectoriesEqual(string $dir1, string $dir2): void { + $rules_file = $dir1 . DIRECTORY_SEPARATOR . '.ignorecontent'; + + // Initialize the rules arrays: skip, presence, include, and global. + $rules = ['skip' => ['.ignorecontent'], 'content' => [], 'include' => [], 'global' => []]; + + // Parse the .ignorecontent file. + if (file_exists($rules_file)) { + $lines = file($rules_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + + if ($lines === FALSE) { + throw new \RuntimeException('Failed to read the .ignorecontent file.'); + } + + foreach ($lines as $line) { + $line = trim($line); + if ($line[0] === '#') { + continue; + } + elseif ($line[0] === '!') { + $rules['include'][] = $line[1] === '^' ? substr($line, 2) : substr($line, 1); + } + elseif ($line[0] === '^') { + $rules['content'][] = substr($line, 1); + } + elseif (!str_contains($line, DIRECTORY_SEPARATOR)) { + // Treat patterns without slashes as global patterns. + $rules['global'][] = $line; + } + else { + // Regular skip rule. + $rules['skip'][] = $line; + } + } + } + + // Match paths. + $match_path = static function (string $path, string $pattern, bool $is_directory): bool { + $path .= $is_directory ? DIRECTORY_SEPARATOR : ''; + // Match directory pattern (e.g., "dir/"). + 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; + + return str_starts_with($path, $parent_dir) && substr_count($path, DIRECTORY_SEPARATOR) === substr_count($parent_dir, DIRECTORY_SEPARATOR); + } + + // @phpcs:ignore Drupal.Functions.DiscouragedFunctions.Discouraged + return fnmatch($pattern, $path); + }; + + // Get the files in the directories. + $get_files = static function (string $dir, array $rules, callable $match_path): array { + $files = []; + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS)); + foreach ($iterator as $file) { + if (!$file instanceof \SplFileInfo) { + continue; + } + + $is_directory = $file->isDir(); + $path = str_replace($dir . DIRECTORY_SEPARATOR, '', $file->getPathname()); + $path .= $is_directory ? DIRECTORY_SEPARATOR : ''; + + foreach ($rules['global'] as $pattern) { + if ($match_path(basename($path), $pattern, $is_directory)) { + continue 2; + } + } + + $is_included = FALSE; + foreach ($rules['include'] as $pattern) { + if ($match_path($path, $pattern, $is_directory)) { + $is_included = TRUE; + break; + } + } + + if (!$is_included) { + foreach ($rules['skip'] as $pattern) { + if ($match_path($path, $pattern, $is_directory)) { + continue 2; + } + } + } + + $is_content = FALSE; + if (!$is_included) { + foreach ($rules['content'] as $pattern) { + if ($match_path($path, $pattern, $is_directory)) { + $is_content = TRUE; + break; + } + } + } + + if ($is_content) { + $files[$path] = 'content'; + } + else { + $files[$path] = $is_directory ? 'content' : md5_file($file->getPathname()); + } + } + ksort($files); + + return $files; + }; + + $dir1_files = $get_files($dir1, $rules, $match_path); + $dir2_files = $get_files($dir2, $rules, $match_path); + + // Allow updating the test fixtures. + if (getenv('UPDATE_TEST_FIXTURES')) { + $allowed_files = array_keys($dir2_files); + $finder = new Finder(); + $finder->files()->in($dir2)->filter(static function (\SplFileInfo $file) use ($allowed_files, $dir2): bool { + $relativePath = str_replace($dir2 . DIRECTORY_SEPARATOR, '', $file->getRealPath()); + + return in_array($relativePath, $allowed_files); + }); + + $this->fs->mirror($dir2, $dir1, $finder->getIterator(), [ + 'override' => TRUE, + ]); + + return; + } + + $diffs = [ + 'only_in_dir1' => array_diff_key($dir1_files, $dir2_files), + 'only_in_dir2' => array_diff_key($dir2_files, $dir1_files), + 'different_files' => [], + ]; + + // Compare files where content is not ignored. + foreach ($dir1_files as $file => $hash) { + if (isset($dir2_files[$file]) && $hash !== $dir2_files[$file] && !in_array($file, $rules['content'])) { + $diffs['different_files'][] = $file; + } + } + + // If differences exist, throw assertion error. + if (!empty($diffs['only_in_dir1']) || !empty($diffs['only_in_dir2']) || !empty($diffs['different_files'])) { + $message = "Differences between directories:\n"; + + if (!empty($diffs['only_in_dir1'])) { + $message .= "Files only in dir1:\n"; + foreach (array_keys($diffs['only_in_dir1']) as $file) { + $message .= sprintf(' %s%s', $file, PHP_EOL); + } + } + + if (!empty($diffs['only_in_dir2'])) { + $message .= "Files only in dir2:\n"; + foreach (array_keys($diffs['only_in_dir2']) as $file) { + $message .= sprintf(' %s%s', $file, PHP_EOL); + } + } + + if (!empty($diffs['different_files'])) { + $message .= "Files that differ in content:\n"; + foreach ($diffs['different_files'] as $file) { + $message .= sprintf(' %s%s', $file, PHP_EOL); + } + } + + throw new AssertionFailedError($message); + } - return str_replace('test_', '', $name); + $this->assertTrue(TRUE); } } diff --git a/tests/phpunit/Traits/CmdTrait.php b/tests/phpunit/Traits/CmdTrait.php deleted file mode 100644 index b0f38db..0000000 --- a/tests/phpunit/Traits/CmdTrait.php +++ /dev/null @@ -1,43 +0,0 @@ - getenv('PATH'), 'HOME' => getenv('HOME')]; - - $process = Process::fromShellCommandline($cmd, $cwd, $env); - $process->setTimeout(300)->setIdleTimeout(300)->run(); - - $exitCode = $process->getExitCode(); - if (0 != $exitCode) { - throw new \RuntimeException("Exit code: {$exitCode}\n\n" . $process->getErrorOutput() . "\n\n" . $process->getOutput()); - } - - return $process->getOutput(); - } - -} diff --git a/tests/phpunit/Traits/ComposerTrait.php b/tests/phpunit/Traits/ComposerTrait.php deleted file mode 100644 index 9e3e05a..0000000 --- a/tests/phpunit/Traits/ComposerTrait.php +++ /dev/null @@ -1,67 +0,0 @@ -setAutoExit(FALSE); - $application->setCatchExceptions(FALSE); - if (method_exists($application, 'setCatchErrors')) { - $application->setCatchErrors(FALSE); - } - - $this->tester = new ApplicationTester($application); - - // Composer autoload uses per-project Composer binary, if the - // `composer/composer` is included in the project as a dependency. - // - // When the test runs and creates SUT, the Composer binary used is - // from the SUT's `vendor` directory. The Customizer may remove the - // `vendor/composer/composer` directory as a part of the cleanup, resulting - // in the Composer autoloader having an empty path to the Composer binary. - // - // This is extremely difficult to debug, because there is no clear error - // message apart from `Could not open input file`. - // - // To prevent this, we set the `COMPOSER_BINARY` environment variable to the - // Composer binary path found in the system. - // @see \Composer\EventDispatcher::doDispatch(). - $composer_bin = shell_exec(escapeshellcmd('which composer')); - if ($composer_bin === FALSE) { - throw new \RuntimeException('Composer binary not found'); - } - putenv('COMPOSER_BINARY=' . trim((string) $composer_bin)); - } - - protected function assertComposerCommandSuccessOutputContains(string|array $strings): void { - $strings = is_array($strings) ? $strings : [$strings]; - - if ($this->tester->getStatusCode() !== 0) { - $this->fail($this->tester->getDisplay()); - } - $this->assertSame(0, $this->tester->getStatusCode(), sprintf("The Composer command should have completed successfully:\n%s", $this->tester->getInput()->__toString())); - - $output = $this->tester->getDisplay(TRUE); - foreach ($strings as $string) { - $this->assertStringContainsString($string, $output); - } - } - -} diff --git a/tests/phpunit/Traits/DirsTrait.php b/tests/phpunit/Traits/DirsTrait.php deleted file mode 100644 index 74318c7..0000000 --- a/tests/phpunit/Traits/DirsTrait.php +++ /dev/null @@ -1,50 +0,0 @@ -dirs = new Dirs(); - $this->dirs->initLocations($cb, $root); - } - - /** - * Print directories' information. - */ - protected function dirsInfo(): string { - return $this->dirs->printInfo(); - } - - /** - * Clean up directories. - */ - protected function dirsClean(): void { - $this->dirs->deleteLocations(); - } - -} diff --git a/tests/phpunit/Traits/FileTrait.php b/tests/phpunit/Traits/FileTrait.php deleted file mode 100644 index 6e03e66..0000000 --- a/tests/phpunit/Traits/FileTrait.php +++ /dev/null @@ -1,52 +0,0 @@ -exists($path)) { - return $current; - } - $current = dirname($current); - } - - throw new \RuntimeException('File not found: ' . $file); - } - -} diff --git a/tests/phpunit/Unit/FilesTest.php b/tests/phpunit/Unit/FilesTest.php index 1c2071c..bf6ee5b 100644 --- a/tests/phpunit/Unit/FilesTest.php +++ b/tests/phpunit/Unit/FilesTest.php @@ -5,78 +5,39 @@ namespace AlexSkrypnyk\Customizer\Tests\Functional; use AlexSkrypnyk\Customizer\CustomizeCommand; -use AlexSkrypnyk\Customizer\Tests\Traits\DirsTrait; use AlexSkrypnyk\Customizer\Tests\Traits\ReflectionTrait; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Group; -use PHPUnit\Framework\TestCase; -use PHPUnit\Framework\TestStatus\Failure; /** * Test for helpers used in operations on files. */ #[CoversClass(CustomizeCommand::class)] #[Group('unit')] -class FilesTest extends TestCase { +class FilesTest extends CustomizerTestCase { - use DirsTrait; use ReflectionTrait; /** * {@inheritdoc} */ protected function setUp(): void { - parent::setUp(); - - $this->dirsInit(); - } - - /** - * {@inheritdoc} - */ - protected function tearDown(): void { - if (!$this->hasFailed()) { - $this->dirsClean(); - } - - parent::tearDown(); - } - - /** - * {@inheritdoc} - */ - protected function onNotSuccessfulTest(\Throwable $t): never { - $this->dirsInfo(); - - // Rethrow the exception to allow the test to fail normally. - parent::onNotSuccessfulTest($t); - } - - /** - * Check if the test has failed. - * - * @return bool - * TRUE if the test has failed, FALSE otherwise. - */ - public function hasFailed(): bool { - $status = $this->status(); - - return $status instanceof Failure; + $this->initLocations((string) getcwd()); } #[DataProvider('dataProviderReplaceInPath')] public function testReplaceInPath(string $path, array $before, string $search, string $replace, bool $replaceLine, array $after): void { - $this->createFileTree($this->dirs->sut, $before); + $this->createFileTree(static::$sut, $before); CustomizeCommand::replaceInPath( - $this->dirs->sut . DIRECTORY_SEPARATOR . $path, + static::$sut . DIRECTORY_SEPARATOR . $path, $search, $replace, $replaceLine, ); - $this->assertFileTree($this->dirs->sut, $after); + $this->assertFileTree(static::$sut, $after); } /** @@ -264,17 +225,17 @@ public static function dataProviderReplaceInPath(): array { #[DataProvider('dataProviderReplaceInPathBetween')] public function testReplaceInPathBetween(string $path, array $before, string $search, string $replace, string $start, string $end, array $after): void { - $this->createFileTree($this->dirs->sut, $before); + $this->createFileTree(static::$sut, $before); CustomizeCommand::replaceInPathBetween( - $this->dirs->sut . DIRECTORY_SEPARATOR . $path, + static::$sut . DIRECTORY_SEPARATOR . $path, $search, $replace, $start, $end ); - $this->assertFileTree($this->dirs->sut, $after); + $this->assertFileTree(static::$sut, $after); } /** @@ -346,15 +307,15 @@ public static function dataProviderReplaceInPathBetween(): array { #[DataProvider('dataProviderUncommentLine')] public function testUncommentLine(string $path, array $before, string $search, string $marker, array $after): void { - $this->createFileTree($this->dirs->sut, $before); + $this->createFileTree(static::$sut, $before); CustomizeCommand::uncommentLine( - $this->dirs->sut . DIRECTORY_SEPARATOR . $path, + static::$sut . DIRECTORY_SEPARATOR . $path, $search, $marker ); - $this->assertFileTree($this->dirs->sut, $after); + $this->assertFileTree(static::$sut, $after); } /** @@ -404,7 +365,7 @@ public static function dataProviderUncommentLine(): array { #[DataProvider('dataProviderReadComposerJson')] public function testReadComposerJson(string $before, string $after, ?string $exception_message = NULL): void { if (!empty($before)) { - $this->createFileTree($this->dirs->sut, ['composer.json' => $before]); + $this->createFileTree(static::$sut, ['composer.json' => $before]); } if ($exception_message) { @@ -412,10 +373,10 @@ public function testReadComposerJson(string $before, string $after, ?string $exc $this->expectExceptionMessage($exception_message); } - CustomizeCommand::readComposerJson($this->dirs->sut . '/composer.json'); + CustomizeCommand::readComposerJson(static::$sut . '/composer.json'); if (!$exception_message) { - $this->assertFileTree($this->dirs->sut, ['composer.json' => $after]); + $this->assertFileTree(static::$sut, ['composer.json' => $after]); } } @@ -445,8 +406,8 @@ public static function dataProviderReadComposerJson(): array { } public function testWriteComposerJson(): void { - CustomizeCommand::writeComposerJson($this->dirs->sut . '/composer.json', [1, 2, 3]); - $this->assertFileTree($this->dirs->sut, [ + CustomizeCommand::writeComposerJson(static::$sut . '/composer.json', [1, 2, 3]); + $this->assertFileTree(static::$sut, [ 'composer.json' => "[ 1, 2, diff --git a/tests/phpunit/Unit/SelfTest.php b/tests/phpunit/Unit/SelfTest.php new file mode 100644 index 0000000..7277aa7 --- /dev/null +++ b/tests/phpunit/Unit/SelfTest.php @@ -0,0 +1,124 @@ +fs = new Filesystem(); + } + + /** + * {@inheritdoc} + */ + protected function tearDown(): void { + // Override the parent method as this is a unit test for a method of + // the parent test class. + } + + #[DataProvider('dataProviderAssertDirectoriesEqual')] + public function testAssertDirectoriesEqual(array $diffs = []): void { + $base = getcwd() . DIRECTORY_SEPARATOR . static::FIXTURES_DIR . DIRECTORY_SEPARATOR . 'assert_fixture_files' . DIRECTORY_SEPARATOR . $this->dataName() . DIRECTORY_SEPARATOR . 'dir1'; + $expected = getcwd() . DIRECTORY_SEPARATOR . static::FIXTURES_DIR . DIRECTORY_SEPARATOR . 'assert_fixture_files' . DIRECTORY_SEPARATOR . $this->dataName() . DIRECTORY_SEPARATOR . 'dir2'; + + try { + $this->assertDirectoriesEqual($base, $expected); + } + catch (AssertionFailedError $assertionFailedError) { + $this->assertExceptionMessage($assertionFailedError->getMessage(), $diffs); + } + } + + /** + * Data provider for testAssertDirectoriesEqual(). + * + * @return array + * The data provider. + */ + public static function dataProviderAssertDirectoriesEqual(): array { + return [ + 'files_equal' => [], + 'files_not_equal' => [ + [ + 'dir1' => [ + 'dir1_flat/d1f3-only-src.txt', + ], + 'dir2' => [ + 'dir2_flat-present-dst/d2f1.txt', + 'dir2_flat-present-dst/d2f2.txt', + 'dir3_subdirs/dir31/f4-new-file-notignore-everywhere.txt', + 'dir5_content_ignore/dir51/d51f2-new-file.txt', + 'f4-new-file-notignore-everywhere.txt', + ], + 'content' => [ + 'dir3_subdirs/dir32-unignored/d32f2.txt', + ], + ], + ], + ]; + } + + /** + * Assert that the exception message contains the expected differences. + * + * @param string $message + * The exception message. + * @param array $expected + * The expected differences. + */ + private function assertExceptionMessage(string $message, array $expected): void { + $actual = ['dir1' => [], 'dir2' => [], 'content' => []]; + + // Parse the exception message into sections. + $lines = explode("\n", trim($message)); + $section = NULL; + foreach ($lines as $line) { + $line = trim($line); + + if ($line === 'Files only in dir1:') { + $section = 'dir1'; + } + elseif ($line === 'Files only in dir2:') { + $section = 'dir2'; + } + elseif ($line === 'Files that differ in content:') { + $section = 'content'; + } + elseif ($section) { + $actual[$section][] = $line; + } + } + + // Compare the actual and expected sections. + foreach (['dir1', 'dir2', 'content'] as $section) { + $this->assertEquals($expected[$section] ?: [], $actual[$section] ?: [], sprintf("Files in section '%s' do not match expected.", $section)); + } + } + +}