Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions phpcs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@
<rule ref="WordPress-Docs" />
<rule ref="WordPress-VIP-Go">
<exclude name="WordPressVIPMinimum.JS" />
<exclude name="WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_fwrite" />
<exclude name="WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_file_put_contents" />
<exclude name="WordPressVIPMinimum.Performance.FetchingRemoteData.FileGetContentsUnknown" />
</rule>

<rule ref="WordPress">
Expand All @@ -17,6 +14,12 @@
<exclude name="Universal.Operators.DisallowStandalonePostIncrementDecrement.PostIncrementFound" />
<exclude name="WordPress.Security.EscapeOutput.ExceptionNotEscaped" />
</rule>
<rule ref="WordPress.Files.FileName.NotHyphenatedLowercase">
<exclude-pattern>*/tests/Unit/*Test.php</exclude-pattern>
</rule>
<rule ref="WordPress.Files.FileName.InvalidClassFileName">
<exclude-pattern>*/tests/Unit/*Test.php</exclude-pattern>
</rule>
<rule ref="Generic.Arrays.DisallowLongArraySyntax" />

<rule ref="PHPCompatibilityWP"/>
Expand All @@ -27,6 +30,8 @@

<exclude-pattern>*/vendor/*</exclude-pattern>
<exclude-pattern>*/.github/*</exclude-pattern>
<exclude-pattern>*/tests/bootstrap.php</exclude-pattern>

<file>scripts</file>
<file>tests/Unit</file>
</ruleset>
2 changes: 1 addition & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
>
<testsuites>
<testsuite name="unit">
<directory>tests/Unit</directory>
<directory suffix=".php">tests/Unit</directory>
</testsuite>
</testsuites>
</phpunit>
10 changes: 6 additions & 4 deletions scripts/bump-plugin-version.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,25 +30,25 @@ 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;
}

$bump_type = $argv[1] ?? 'patch';

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;
}
}
Expand Down Expand Up @@ -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}" );
}
Expand Down Expand Up @@ -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 ) {
Expand Down
88 changes: 77 additions & 11 deletions tests/Unit/BumpPluginVersionTest.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
<?php
/**
* Version bump helper tests.
*
* @package WPVDB_Scripts
*/

declare(strict_types=1);

namespace WPVDB_Scripts\Tests\Unit;
Expand All @@ -7,29 +13,60 @@
use RuntimeException;

use function bump_plugin_version;
use function read_required_file;

/**
* Tests plugin version bump behavior.
*
* @covers ::bump_plugin_version
*/
final class BumpPluginVersionTest extends TestCase {
/**
* Temporary fixture directories.
*
* @var list<string>
*/
private array $temp_dirs = [];

/**
* Previous environment values.
*
* @var array<string, string|false>
*/
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' => "<?php\n/**\n * Plugin Name: Demo plugin\n * Version: 0.1.2\n */\ndefine( 'DEMO_PLUGIN_VERSION', '0.1.2' );\n",
'package.json' => "{\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' => "<?php\n/**\n * Plugin Name: Demo plugin\n * Version: 0.1.2\n */\ndefine( 'DEMO_PLUGIN_VERSION', '0.1.2' );\n",
'package.json' => "{\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",
]
);
Expand All @@ -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(
[
Expand All @@ -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(
[
Expand All @@ -89,34 +136,51 @@ public function test_bump_fails_closed_when_a_surface_is_ambiguous(): void {
}

/**
* Make a temporary fixture directory.
*
* @param array<string, string> $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;

foreach ( $files as $relative_path => $contents ) {
$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 );
}

return $root;
}

/**
* Configure environment variables for the helper.
*
* @param array<string, string> $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;
Expand All @@ -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 );
}
}