diff --git a/phpcs.xml b/phpcs.xml index 417f01c..abe6fa4 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -6,9 +6,6 @@ - - - @@ -17,6 +14,12 @@ + + */tests/Unit/*Test.php + + + */tests/Unit/*Test.php + @@ -27,6 +30,8 @@ */vendor/* */.github/* + */tests/bootstrap.php scripts + tests/Unit diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9282139..2387ceb 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -7,7 +7,7 @@ > - tests/Unit + tests/Unit diff --git a/scripts/bump-plugin-version.php b/scripts/bump-plugin-version.php index 611e4ad..fa0c40c 100644 --- a/scripts/bump-plugin-version.php +++ b/scripts/bump-plugin-version.php @@ -30,14 +30,14 @@ function is_bump_plugin_version_entrypoint( array $argv ): bool { */ function bump_plugin_version_cli( array $argv ): int { if ( 'cli' !== PHP_SAPI ) { - fwrite( STDERR, "This script must run from the command line.\n" ); + fwrite( STDERR, "This script must run from the command line.\n" ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_fwrite -- CLI scripts write status messages to STDERR. return 1; } $root = getcwd(); if ( false === $root ) { - fwrite( STDERR, "Unable to determine repository root.\n" ); + fwrite( STDERR, "Unable to determine repository root.\n" ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_fwrite -- CLI scripts write status messages to STDERR. return 1; } @@ -45,10 +45,10 @@ function bump_plugin_version_cli( array $argv ): int { try { $new_version = bump_plugin_version( $root, $bump_type ); - fwrite( STDOUT, "Bumped plugin version to {$new_version}\n" ); + fwrite( STDOUT, "Bumped plugin version to {$new_version}\n" ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_fwrite -- CLI scripts write status messages to STDOUT. return 0; } catch ( Throwable $throwable ) { - fwrite( STDERR, $throwable->getMessage() . "\n" ); + fwrite( STDERR, $throwable->getMessage() . "\n" ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_fwrite -- CLI scripts write status messages to STDERR. return 1; } } @@ -143,6 +143,7 @@ function bump_plugin_version( string $root, string $bump_type ): string { } foreach ( $writes as $path => $contents ) { + // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_file_put_contents -- This CLI helper intentionally updates version metadata files. if ( file_put_contents( $path, $contents ) === false ) { throw new RuntimeException( "Unable to write {$path}" ); } @@ -194,6 +195,7 @@ function path_join( string $base, string $path ): string { * @throws RuntimeException When the file cannot be read. */ function read_required_file( string $path ): string { + // phpcs:ignore WordPressVIPMinimum.Performance.FetchingRemoteData.FileGetContentsUnknown -- Local filesystem reads are this CLI helper's purpose. $contents = file_get_contents( $path ); if ( false === $contents ) { diff --git a/tests/Unit/BumpPluginVersionTest.php b/tests/Unit/BumpPluginVersionTest.php index c206b79..6e61e5a 100644 --- a/tests/Unit/BumpPluginVersionTest.php +++ b/tests/Unit/BumpPluginVersionTest.php @@ -1,4 +1,10 @@ */ private array $temp_dirs = []; + /** + * Previous environment values. + * + * @var array + */ + private array $previous_env = []; + + /** + * Clean temporary fixtures and environment. + */ protected function tearDown(): void { foreach ( $this->temp_dirs as $dir ) { $this->delete_tree( $dir ); } - foreach ( [ 'PLUGIN_FILE', 'VERSION_CONSTANT', 'PACKAGE_FILE', 'POT_FILE', 'POT_PROJECT', 'BLOCK_JSON_GLOB' ] as $name ) { - putenv( $name ); + foreach ( $this->previous_env as $name => $previous_value ) { + if ( false !== $previous_value ) { + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_putenv -- Tests isolate CLI helper environment variables. + putenv( "{$name}={$previous_value}" ); + } else { + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_putenv -- Tests isolate CLI helper environment variables. + putenv( $name ); + } } + + $this->previous_env = []; } + /** + * Test configured version surfaces are bumped together. + * + * @covers ::bump_plugin_version + */ public function test_bump_updates_all_configured_version_surfaces(): void { $root = $this->make_fixture( [ - 'demo-plugin.php' => " "{\n\t\"name\": \"demo-plugin\",\n\t\"version\": \"0.1.2\"\n}\n", - 'languages/demo.pot' => "msgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: Demo plugin 0.1.2\\n\"\n", + 'demo-plugin.php' => " "{\n\t\"name\": \"demo-plugin\",\n\t\"version\": \"0.1.2\"\n}\n", + 'languages/demo.pot' => "msgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: Demo plugin 0.1.2\\n\"\n", 'src/example/block.json' => "{\n\t\"name\": \"demo/example\",\n\t\"version\": \"0.1.2\"\n}\n", ] ); @@ -44,14 +81,19 @@ public function test_bump_updates_all_configured_version_surfaces(): void { ] ); - self::assertSame( '0.2.0', bump_plugin_version( $root, 'minor' ) ); - self::assertStringContainsString( "* Version: 0.2.0\n", file_get_contents( $root . '/demo-plugin.php' ) ?: '' ); - self::assertStringContainsString( "define( 'DEMO_PLUGIN_VERSION', '0.2.0' );", file_get_contents( $root . '/demo-plugin.php' ) ?: '' ); - self::assertStringContainsString( '"version": "0.2.0"', file_get_contents( $root . '/package.json' ) ?: '' ); - self::assertStringContainsString( 'Project-Id-Version: Demo plugin 0.2.0\\n', file_get_contents( $root . '/languages/demo.pot' ) ?: '' ); - self::assertStringContainsString( '"version": "0.2.0"', file_get_contents( $root . '/src/example/block.json' ) ?: '' ); + self::assertSame( '0.2.0', bump_plugin_version( $root, 'minor' ), 'Minor bumps should update the returned version.' ); + self::assertStringContainsString( "* Version: 0.2.0\n", read_required_file( $root . '/demo-plugin.php' ), 'Plugin headers should be bumped.' ); + self::assertStringContainsString( "define( 'DEMO_PLUGIN_VERSION', '0.2.0' );", read_required_file( $root . '/demo-plugin.php' ), 'Version constants should be bumped.' ); + self::assertStringContainsString( '"version": "0.2.0"', read_required_file( $root . '/package.json' ), 'Package versions should be bumped.' ); + self::assertStringContainsString( 'Project-Id-Version: Demo plugin 0.2.0\\n', read_required_file( $root . '/languages/demo.pot' ), 'POT project versions should be bumped.' ); + self::assertStringContainsString( '"version": "0.2.0"', read_required_file( $root . '/src/example/block.json' ), 'Block metadata versions should be bumped.' ); } + /** + * Test prerelease versions are rejected. + * + * @covers ::bump_plugin_version + */ public function test_bump_rejects_prerelease_versions(): void { $root = $this->make_fixture( [ @@ -70,6 +112,11 @@ public function test_bump_rejects_prerelease_versions(): void { bump_plugin_version( $root, 'patch' ); } + /** + * Test ambiguous version surfaces fail closed. + * + * @covers ::bump_plugin_version + */ public function test_bump_fails_closed_when_a_surface_is_ambiguous(): void { $root = $this->make_fixture( [ @@ -89,10 +136,13 @@ public function test_bump_fails_closed_when_a_surface_is_ambiguous(): void { } /** + * Make a temporary fixture directory. + * * @param array $files Files keyed by relative path. */ private function make_fixture( array $files ): string { $root = sys_get_temp_dir() . '/wpvdb-scripts-test-' . bin2hex( random_bytes( 6 ) ); + // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.directory_mkdir -- Tests create isolated temporary fixture directories. mkdir( $root, 0777, true ); $this->temp_dirs[] = $root; @@ -100,8 +150,10 @@ private function make_fixture( array $files ): string { $path = $root . '/' . $relative_path; $dir = dirname( $path ); if ( ! is_dir( $dir ) ) { + // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.directory_mkdir -- Tests create isolated temporary fixture directories. mkdir( $dir, 0777, true ); } + // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_file_put_contents -- Tests create isolated temporary fixture files. file_put_contents( $path, $contents ); } @@ -109,14 +161,26 @@ private function make_fixture( array $files ): string { } /** + * Configure environment variables for the helper. + * * @param array $env Environment values. */ private function configure_env( array $env ): void { foreach ( $env as $name => $value ) { + if ( ! array_key_exists( $name, $this->previous_env ) ) { + $this->previous_env[ $name ] = getenv( $name ); + } + + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.runtime_configuration_putenv -- Tests configure CLI helper environment variables. putenv( "{$name}={$value}" ); } } + /** + * Delete a temporary fixture tree. + * + * @param string $path Directory path. + */ private function delete_tree( string $path ): void { if ( ! is_dir( $path ) ) { return; @@ -128,9 +192,11 @@ private function delete_tree( string $path ): void { ); foreach ( $iterator as $item ) { + // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.directory_rmdir, WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_unlink -- Tests clean isolated temporary fixtures. $item->isDir() ? rmdir( $item->getPathname() ) : unlink( $item->getPathname() ); } + // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.directory_rmdir -- Tests clean isolated temporary fixture directories. rmdir( $path ); } }