From ef258931ed1f294210bd7685396fca5ec116fba0 Mon Sep 17 00:00:00 2001 From: Andy Fragen Date: Fri, 13 Dec 2024 11:25:10 -0800 Subject: [PATCH 01/11] Add workflow to create new branch and PR for generating POT automatically (#242) Signed-off-by: Andy Fragen --------- Signed-off-by: Andy Fragen Co-authored-by: Colin Stewart <79332690+costdev@users.noreply.github.com> --- .github/workflows/generate-pot-pr.yml | 71 +++++++++++++++++++++++++++ .github/workflows/generate-pot.yml | 26 ---------- 2 files changed, 71 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/generate-pot-pr.yml delete mode 100644 .github/workflows/generate-pot.yml diff --git a/.github/workflows/generate-pot-pr.yml b/.github/workflows/generate-pot-pr.yml new file mode 100644 index 0000000..04077e3 --- /dev/null +++ b/.github/workflows/generate-pot-pr.yml @@ -0,0 +1,71 @@ +name: Generate POT PR + +on: + workflow_dispatch: + push: + branches: + - main + +jobs: + generate-pot: + name: Generate POT PR + if: github.repository == 'aspirepress/aspireupdate' + permissions: + contents: write + pull-requests: write + runs-on: ubuntu-latest + steps: + - name: Check out source code + uses: actions/checkout@v4 + + - name: Set up PHP environment + uses: shivammathur/setup-php@v2 + with: + php-version: "7.4" + + - name: Setup WP-CLI + uses: godaddy-wordpress/setup-wp-cli@1 + + - name: Configure git user + run: | + git config --global user.email "andy@thefragens.com" + git config --global user.name "Andy Fragen" + + - name: Check if remote branch exists + run: echo "REMOTE_BRANCH_EXISTS=$([[ -z $(git ls-remote --heads origin generate-pot) ]] && echo "0" || echo "1")" >> $GITHUB_ENV + + - name: Create branch to base pull request on + if: env.REMOTE_BRANCH_EXISTS == 0 + run: | + git checkout -b generate-pot + + - name: Fetch existing branch to add commits to + if: env.REMOTE_BRANCH_EXISTS == 1 + run: | + git fetch --all --prune + git checkout generate-pot + git pull --no-rebase + + - name: Generate POT + run: | + wp i18n make-pot . "./languages/${{ github.event.repository.name }}.pot" --headers='{"Report-Msgid-Bugs-To":"https://github.com/${{ github.event.repository.full_name }}/issues"}' + + - name: Check if there are changes + run: echo "CHANGES_DETECTED=$([[ -z $(git status --porcelain) ]] && echo "0" || echo "1")" >> $GITHUB_ENV + + - name: Commit changes + if: env.CHANGES_DETECTED == 1 + run: | + git add "./languages/${{ github.event.repository.name }}.pot" + git commit -m "Generate POT - $(date +'%Y-%m-%d')" + git push origin generate-pot + + - name: Create pull request + if: env.CHANGES_DETECTED == 1 + uses: repo-sync/pull-request@v2 + with: + source_branch: generate-pot + destination_branch: ${{ github.event.repository.default_branch }} + github_token: ${{ secrets.GITHUB_TOKEN }} + pr_title: Generate POT + pr_body: "This is an automated pull-request" diff --git a/.github/workflows/generate-pot.yml b/.github/workflows/generate-pot.yml deleted file mode 100644 index e3e39bf..0000000 --- a/.github/workflows/generate-pot.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Generate POT - -on: - push: - branches: - - main - - translations - -jobs: - WP_POT_Generator: - if: github.repository == 'aspirepress/aspireupdate' - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/checkout@master - - name: WordPress POT Generator - uses: varunsridharan/action-wp-pot-generator@2.0 - with: - save_path: "./languages" - item_slug: "${{ github.event.repository.name }}" - domain: "${{ github.event.repository.name }}" - package_name: "${{ github.event.repository.name }}" - headers: '{"Report-Msgid-Bugs-To":"https://github.com/${{ github.event.repository.full_name }}/issues"}' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 056914f66b6b9d06392429701912b0b758c8b587 Mon Sep 17 00:00:00 2001 From: Colin Stewart <79332690+costdev@users.noreply.github.com> Date: Thu, 19 Dec 2024 17:25:48 +0000 Subject: [PATCH 02/11] Tests: Add test resources for `AspireUpdate\Debug` (#244) * Tests: Introduce Debug_UnitTestCase and AP_FakeFilesystem. * Coding Standards: Exclude test files from filesystem sniffs. * Coding Standards: Exclude all test PHP files, not just test classes. --- phpcs.xml.dist | 18 ++- tests/phpunit/bootstrap.php | 4 + tests/phpunit/includes/Debug_UnitTestCase.php | 116 ++++++++++++++++++ .../includes/class-ap-fake-filesystem.php | 82 +++++++++++++ 4 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 tests/phpunit/includes/Debug_UnitTestCase.php create mode 100644 tests/phpunit/includes/class-ap-fake-filesystem.php diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 081e4b7..66ac36d 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -61,9 +61,25 @@ - /tests/phpunit/tests/* + /tests/phpunit/* + /tests/phpunit/* + + + + /tests/phpunit/tests/* + + + + /tests/phpunit/* + + + /tests/phpunit/* + + + /tests/phpunit/* + diff --git a/tests/phpunit/bootstrap.php b/tests/phpunit/bootstrap.php index 3899953..186c80a 100644 --- a/tests/phpunit/bootstrap.php +++ b/tests/phpunit/bootstrap.php @@ -40,5 +40,9 @@ function _manually_load_plugin() { // Start up the WP testing environment. require "{$_tests_dir}/includes/bootstrap.php"; +// Load unit test mocks and fakes. +require __DIR__ . '/includes/class-ap-fake-filesystem.php'; + // Load unit test abstract classes. require __DIR__ . '/includes/AdminSettings_UnitTestCase.php'; +require __DIR__ . '/includes/Debug_UnitTestCase.php'; diff --git a/tests/phpunit/includes/Debug_UnitTestCase.php b/tests/phpunit/includes/Debug_UnitTestCase.php new file mode 100644 index 0000000..0058fd0 --- /dev/null +++ b/tests/phpunit/includes/Debug_UnitTestCase.php @@ -0,0 +1,116 @@ +setAccessible( true ); + self::$log_file = $get_file_path->invoke( null ); + $get_file_path->setAccessible( false ); + + if ( file_exists( self::$log_file ) ) { + unlink( self::$log_file ); + } + + if ( isset( $GLOBALS['wp_filesystem'] ) ) { + self::$default_filesystem = $GLOBALS['wp_filesystem']; + } else { + self::$default_filesystem = false; + } + } + + /** + * Filters the filesystem method before each test runs. + * + * Filters are removed in the tear_down() parent method. + * + * @return void + */ + public function set_up() { + parent::set_up(); + + add_filter( + 'filesystem_method', + static function () { + return 'direct'; + } + ); + } + + /** + * Delete the log file and restores the filesystem after each test runs. + * + * @return void + */ + public function tear_down() { + if ( file_exists( self::$log_file ) ) { + unlink( self::$log_file ); + } + + if ( false === self::$default_filesystem ) { + unset( $GLOBALS['wp_filesystem'] ); + } else { + $GLOBALS['wp_filesystem'] = self::$default_filesystem; + } + + parent::tear_down(); + } + + /** + * Creates a fake filesystem. + * + * @param bool|null $exists Whether paths should exist. + * Default null uses default implemenation. + * @param bool|null $is_readable Whether paths should be readable. + * Default null uses default implemenation. + * @param bool|null $is_writable Whether paths should be writable. + * Default null uses default implemenation. + */ + public function get_fake_filesystem( $exists = null, $is_readable = null, $is_writable = null ) { + $hash = ( null === $exists ? '-1' : (int) $exists ) . ',' . + ( null === $is_readable ? '-1' : (int) $is_readable ) . ',' . + ( null === $is_writable ? '-1' : (int) $is_writable ); + + if ( ! isset( self::$filesystems[ $hash ] ) ) { + self::$filesystems[ $hash ] = new AP_FakeFilesystem( $exists, $is_readable, $is_writable ); + } + + return self::$filesystems[ $hash ]; + } +} diff --git a/tests/phpunit/includes/class-ap-fake-filesystem.php b/tests/phpunit/includes/class-ap-fake-filesystem.php new file mode 100644 index 0000000..d2de759 --- /dev/null +++ b/tests/phpunit/includes/class-ap-fake-filesystem.php @@ -0,0 +1,82 @@ +exists = $exists; + $this->is_readable = $is_readable; + $this->is_writable = $is_writable; + } + + /** + * Checks whether a path exists. + * + * @param string $path The path to check. + * @return bool Whether the path exists. + */ + public function exists( $path ) { + if ( null === $this->exists ) { + return parent::exists( $path ); + } + return $this->exists; + } + + /** + * Checks whether a path is readable. + * + * @param string $path The path to check. + * @return bool Whether the path is readable. + */ + public function is_readable( $path ) { + if ( null === $this->is_readable ) { + return parent::is_readable( $path ); + } + return $this->is_readable; + } + + /** + * Checks whether a path is writable. + * + * @param string $path The path to check. + * @return bool Whether the path is writable. + */ + public function is_writable( $path ) { + if ( null === $this->is_writable ) { + return parent::is_writable( $path ); + } + return $this->is_writable; + } +} From e8ce9c09b0f91735fe4b0b0b8a2209c7aaa360ce Mon Sep 17 00:00:00 2001 From: Colin Stewart <79332690+costdev@users.noreply.github.com> Date: Thu, 19 Dec 2024 17:53:28 +0000 Subject: [PATCH 03/11] Add tests for `Debug::read()` (#245) * Add tests for `Debug::read()`. * Coverage: Ignore an untestable line. --- includes/class-debug.php | 2 +- tests/phpunit/tests/Debug/Debug_ReadTest.php | 187 +++++++++++++++++++ 2 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 tests/phpunit/tests/Debug/Debug_ReadTest.php diff --git a/includes/class-debug.php b/includes/class-debug.php index eba51fd..81ab276 100644 --- a/includes/class-debug.php +++ b/includes/class-debug.php @@ -63,7 +63,7 @@ private static function verify_filesystem( $wp_filesystem ) { /** * Log error in file write fails only if debug is set to true. This is a valid use case. */ - error_log( 'AspireUpdate - Could not open or write to the file system. Check file system permissions to debug log directory.' ); + error_log( 'AspireUpdate - Could not open or write to the file system. Check file system permissions to debug log directory.' ); // @codeCoverageIgnore // phpcs:enable } return false; diff --git a/tests/phpunit/tests/Debug/Debug_ReadTest.php b/tests/phpunit/tests/Debug/Debug_ReadTest.php new file mode 100644 index 0000000..5f275e1 --- /dev/null +++ b/tests/phpunit/tests/Debug/Debug_ReadTest.php @@ -0,0 +1,187 @@ +assertWPError( AspireUpdate\Debug::read() ); + } + + /** + * Test that a WP_Error object is returned when the log file doesn't exist. + * + * @covers \AspireUpdate\Debug::init_filesystem + * @covers \AspireUpdate\Debug::verify_filesystem + * @covers \AspireUpdate\Debug::get_file_path + */ + public function test_should_return_wp_error_when_log_file_does_not_exist() { + $this->assertWPError( AspireUpdate\Debug::read() ); + } + + /** + * Test that a WP_Error object is returned when the log file isn't readable. + * + * @covers \AspireUpdate\Debug::init_filesystem + * @covers \AspireUpdate\Debug::verify_filesystem + * @covers \AspireUpdate\Debug::get_file_path + */ + public function test_should_return_wp_error_when_log_file_is_not_readable() { + global $wp_filesystem; + + // Create the log file. + file_put_contents( self::$log_file, '' ); + + // Backup and replace the filesystem object. + $wp_filesystem = $this->get_fake_filesystem( true, false, true ); + + $actual = AspireUpdate\Debug::read(); + + $this->assertWPError( $actual ); + } + + /** + * Test that an empty log message is returned when the log file is empty. + */ + public function test_should_return_an_empty_log_message_when_log_file_is_empty() { + file_put_contents( self::$log_file, '' ); + + $actual = AspireUpdate\Debug::read(); + $this->assertIsString( + $actual, + 'A string was not returned.' + ); + + $this->assertStringContainsString( + 'Log file is empty', + $actual, + 'The empty log file message was not returned.' + ); + } + + /** + * Test that an empty log message is returned when the log file's content + * is only empty space. + */ + public function test_should_return_an_empty_log_message_when_log_file_only_has_empty_space() { + file_put_contents( self::$log_file, " \n\r\t\v\x00" ); + + $actual = AspireUpdate\Debug::read(); + $this->assertIsString( + $actual, + 'A string was not returned.' + ); + + $this->assertStringContainsString( + 'Log file is empty', + $actual, + 'The empty log file message was not returned.' + ); + } + + /** + * Test that an empty log message is not returned when the log file + * has contents. + */ + public function test_should_not_return_an_empty_log_message_when_log_file_has_contents() { + file_put_contents( self::$log_file, 'Some contents' ); + + $actual = AspireUpdate\Debug::read(); + $this->assertIsString( + $actual, + 'A string was not returned.' + ); + + $this->assertStringNotContainsString( + 'Log file is empty', + $actual, + 'The empty log file message was returned.' + ); + } + + /** + * Test that a truncation message is added when the log file has more + * lines than requested. + */ + public function test_should_add_a_truncation_message_when_log_file_has_more_lines_than_requested() { + file_put_contents( + self::$log_file, + "First line\r\nSecond line\r\nThird line" + ); + + $actual = AspireUpdate\Debug::read( 2 ); + + $this->assertIsString( + $actual, + 'A string was not returned.' + ); + + $this->assertStringContainsString( + 'Log truncated', + $actual, + 'The truncation message was not returned.' + ); + } + + /** + * Test that no truncation message is added when the log file has the same + * number of lines as requested. + */ + public function test_should_not_add_a_truncation_message_when_log_file_has_the_same_number_of_lines_as_requested() { + file_put_contents( + self::$log_file, + "First line\r\nSecond line\r\nThird line" + ); + + $actual = AspireUpdate\Debug::read( 3 ); + + $this->assertIsString( + $actual, + 'A string was not returned.' + ); + + $this->assertStringNotContainsString( + 'Log truncated', + $actual, + 'The truncation message was added.' + ); + } + + /** + * Test that no truncation message is added when the log file has fewer than + * lines than requested. + */ + public function test_should_not_add_a_truncation_message_when_log_file_has_fewer_lines_than_requested() { + file_put_contents( + self::$log_file, + "First line\r\nSecond line\r\nThird line" + ); + + $actual = AspireUpdate\Debug::read( 4 ); + + $this->assertIsString( + $actual, + 'A string was not returned.' + ); + + $this->assertStringNotContainsString( + 'Log truncated', + $actual, + 'The truncation message was added.' + ); + } +} From 8dc1a4b142d38f1254b4877d6956ae3c04df5184 Mon Sep 17 00:00:00 2001 From: Colin Stewart <79332690+costdev@users.noreply.github.com> Date: Thu, 19 Dec 2024 17:53:55 +0000 Subject: [PATCH 04/11] Add tests for `Debug::clear()`. (#246) --- tests/phpunit/tests/Debug/Debug_ClearTest.php | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 tests/phpunit/tests/Debug/Debug_ClearTest.php diff --git a/tests/phpunit/tests/Debug/Debug_ClearTest.php b/tests/phpunit/tests/Debug/Debug_ClearTest.php new file mode 100644 index 0000000..d6882c9 --- /dev/null +++ b/tests/phpunit/tests/Debug/Debug_ClearTest.php @@ -0,0 +1,106 @@ +assertWPError( + AspireUpdate\Debug::clear(), + 'A WP_Error object was not returned.' + ); + + $this->assertFileDoesNotExist( + self::$log_file, + 'The log file was created.' + ); + } + + /** + * Test that a WP_Error object is returned when the log file doesn't exist. + * + * @covers \AspireUpdate\Debug::init_filesystem + * @covers \AspireUpdate\Debug::verify_filesystem + * @covers \AspireUpdate\Debug::get_file_path + */ + public function test_should_return_wp_error_when_log_file_does_not_exist() { + $this->assertWPError( + AspireUpdate\Debug::clear(), + 'A WP_Error object was not returned.' + ); + + $this->assertFileDoesNotExist( + self::$log_file, + 'The log file was created.' + ); + } + + /** + * Test that a WP_Error object is returned when the log file isn't writable. + * + * @covers \AspireUpdate\Debug::init_filesystem + * @covers \AspireUpdate\Debug::verify_filesystem + * @covers \AspireUpdate\Debug::get_file_path + */ + public function test_should_return_wp_error_when_log_file_is_not_writable() { + global $wp_filesystem; + + // Backup and replace the filesystem object. + $wp_filesystem = $this->get_fake_filesystem( true, true, false ); + + $actual = AspireUpdate\Debug::clear(); + + $this->assertWPError( + $actual, + 'A WP_Error was not returned.' + ); + + $this->assertFileDoesNotExist( + self::$log_file, + 'The log file was created.' + ); + } + + /** + * Test that the log file is cleared. + */ + public function test_should_clear_log_file() { + file_put_contents( + self::$log_file, + "First line\r\nSecond line\r\nThird line" + ); + + $this->assertFileExists( + self::$log_file, + 'The log file was not created before testing.' + ); + + AspireUpdate\Debug::clear(); + + $this->assertFileExists( + self::$log_file, + 'The log file was deleted.' + ); + + $this->assertSame( + '', + file_get_contents( self::$log_file ), + 'The log file was not cleared.' + ); + } +} From 578fac908cfa53295281f18ce00e8f454a19a2bb Mon Sep 17 00:00:00 2001 From: Colin Stewart <79332690+costdev@users.noreply.github.com> Date: Thu, 19 Dec 2024 17:54:06 +0000 Subject: [PATCH 05/11] Add tests for `Debug::log()`. (#247) --- tests/phpunit/tests/Debug/Debug_LogTest.php | 210 ++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 tests/phpunit/tests/Debug/Debug_LogTest.php diff --git a/tests/phpunit/tests/Debug/Debug_LogTest.php b/tests/phpunit/tests/Debug/Debug_LogTest.php new file mode 100644 index 0000000..c95036f --- /dev/null +++ b/tests/phpunit/tests/Debug/Debug_LogTest.php @@ -0,0 +1,210 @@ +assertFileDoesNotExist( + self::$log_file, + 'The log file was created.' + ); + } + + /** + * Test that the log file is created when it doesn't already exist. + */ + public function test_should_create_log_file_if_it_does_not_already_exist() { + $this->assertFileDoesNotExist( + self::$log_file, + 'The log file already exists before testing.' + ); + + $message = 'Test log message.'; + + AspireUpdate\Debug::log( $message ); + + $this->assertFileExists( + self::$log_file, + 'The log file was not created.' + ); + } + + /** + * Test that the message is added to the log file. + * + * @covers \AspireUpdate\Debug::format_message + */ + public function test_should_add_message_to_log_file() { + $this->assertFileDoesNotExist( + self::$log_file, + 'The log file already exists before testing.' + ); + + $message = 'Test log message.'; + + AspireUpdate\Debug::log( $message ); + + $this->assertFileExists( + self::$log_file, + 'The log file was not created.' + ); + + $this->assertStringContainsString( + $message, + file_get_contents( self::$log_file ), + 'The message was not added.' + ); + } + + /** + * Test that the message is prepended to an existing log file. + * + * @covers \AspireUpdate\Debug::format_message + */ + public function test_should_add_message_to_an_existing_log_file() { + $existing_content = 'An existing log file.'; + file_put_contents( self::$log_file, $existing_content ); + + $message = 'Test log message.'; + + AspireUpdate\Debug::log( $message ); + + $this->assertFileExists( + self::$log_file, + 'The log file was not created.' + ); + + $this->assertStringContainsString( + "$message\n$existing_content", + file_get_contents( self::$log_file ), + 'The message was not prepended to the log file.' + ); + } + + /** + * Test that the message is prefixed with the timestamp. + * + * @covers \AspireUpdate\Debug::format_message + */ + public function test_should_prefix_message_with_timestamp() { + AspireUpdate\Debug::log( 'Test log message.' ); + + $this->assertFileExists( + self::$log_file, + 'The log file was not created.' + ); + + $this->assertMatchesRegularExpression( + '/^\[[0-9]{4}\-[0-9]{2}\-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\]/', + file_get_contents( self::$log_file ), + 'The message was not prefixed with the timestamp.' + ); + } + + /** + * Test that the message is prefixed with its type. + * + * @dataProvider data_message_types + * + * @covers \AspireUpdate\Debug::format_message + * + * @param string $type The type of message. + */ + public function test_should_prefix_message_with_type( $type ) { + $message = 'Test log message.'; + + AspireUpdate\Debug::log( $message, $type ); + + $this->assertFileExists( + self::$log_file, + 'The log file was not created.' + ); + + $this->assertStringContainsString( + '[' . strtoupper( $type ) . ']: ' . $message, + file_get_contents( self::$log_file ), + 'The message was not prefixed with its type.' + ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_message_types() { + return $this->text_array_to_dataprovider( + [ + 'string', + 'request', + 'response', + 'custom', + ] + ); + } + + /** + * Test that array and object messages are expanded. + * + * @dataProvider data_arrays_and_objects + * + * @covers \AspireUpdate\Debug::format_message + * + * @param array|object $message The message. + */ + public function test_should_expand_array_or_object_messages( $message ) { + AspireUpdate\Debug::log( $message ); + + $this->assertFileExists( + self::$log_file, + 'The log file was not created.' + ); + + $this->assertStringContainsString( + // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r + print_r( $message, true ), + file_get_contents( self::$log_file ), + 'The array message was not expanded.' + ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_arrays_and_objects() { + return [ + 'an array' => [ + 'message' => [], + ], + 'a non-empty array' => [ + 'message' => [ 'First line', 'Second line', 'Third line' ], + ], + 'an object with no properties' => [ + 'message' => (object) [], + ], + 'an object with properties' => [ + 'message' => (object) [ 'First line', 'Second line', 'Third line' ], + ], + ]; + } +} From 9414ef3a8bab42b0cc6b9dc287f51854fe795a9b Mon Sep 17 00:00:00 2001 From: Colin Stewart <79332690+costdev@users.noreply.github.com> Date: Thu, 19 Dec 2024 17:54:16 +0000 Subject: [PATCH 06/11] Add tests for `Debug::log_string()`. (#248) --- .../tests/Debug/Debug_LogStringTest.php | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 tests/phpunit/tests/Debug/Debug_LogStringTest.php diff --git a/tests/phpunit/tests/Debug/Debug_LogStringTest.php b/tests/phpunit/tests/Debug/Debug_LogStringTest.php new file mode 100644 index 0000000..14508a6 --- /dev/null +++ b/tests/phpunit/tests/Debug/Debug_LogStringTest.php @@ -0,0 +1,104 @@ +assertFileDoesNotExist( self::$log_file ); + } + + /** + * Test that nothing is written to the log file when debug types are not an array. + */ + public function test_should_not_write_to_log_file_when_debug_types_are_not_an_array() { + define( 'AP_DEBUG', true ); + define( 'AP_DEBUG_TYPES', 'string' ); + + AspireUpdate\Debug::log_string( 'Test log message.' ); + + $this->assertFileDoesNotExist( self::$log_file ); + } + + /** + * Test that nothing is written to the log file when string debugging is disabled. + */ + public function test_should_not_write_to_log_file_when_string_debugging_is_disabled() { + define( 'AP_DEBUG', true ); + define( 'AP_DEBUG_TYPES', [ 'request', 'response' ] ); + + AspireUpdate\Debug::log_string( 'Test log message.' ); + + $this->assertFileDoesNotExist( self::$log_file ); + } + + /** + * Test that the message is written to the log file. + * + * @dataProvider data_debug_types + * + * @param array $debug_types An array of enabled debug types. + */ + public function test_should_write_to_log_file( $debug_types ) { + define( 'AP_DEBUG', true ); + define( 'AP_DEBUG_TYPES', $debug_types ); + + $message = 'Test log message.'; + + AspireUpdate\Debug::log_string( $message ); + + $this->assertFileExists( + self::$log_file, + 'The log file was created.' + ); + + $this->assertStringContainsString( + $message, + file_get_contents( self::$log_file ), + 'The message was not logged.' + ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_debug_types() { + return [ + 'just "string"' => [ + 'debug_types' => [ 'string' ], + ], + '"string" at the start of the array"' => [ + 'debug_types' => [ 'string', 'request' ], + ], + '"string" in the middle of the array"' => [ + 'debug_types' => [ 'request', 'string', 'response' ], + ], + '"string" at the end of the array"' => [ + 'debug_types' => [ 'request', 'response', 'string' ], + ], + ]; + } +} From 03d2f556d89cde4693084be1871229ad8f121962 Mon Sep 17 00:00:00 2001 From: Colin Stewart <79332690+costdev@users.noreply.github.com> Date: Thu, 19 Dec 2024 17:54:29 +0000 Subject: [PATCH 07/11] Add tests for `Debug::log_request()`. (#249) --- .../tests/Debug/Debug_LogRequestTest.php | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 tests/phpunit/tests/Debug/Debug_LogRequestTest.php diff --git a/tests/phpunit/tests/Debug/Debug_LogRequestTest.php b/tests/phpunit/tests/Debug/Debug_LogRequestTest.php new file mode 100644 index 0000000..e16ba57 --- /dev/null +++ b/tests/phpunit/tests/Debug/Debug_LogRequestTest.php @@ -0,0 +1,104 @@ +assertFileDoesNotExist( self::$log_file ); + } + + /** + * Test that nothing is written to the log file when debug types are not an array. + */ + public function test_should_not_write_to_log_file_when_debug_types_are_not_an_array() { + define( 'AP_DEBUG', true ); + define( 'AP_DEBUG_TYPES', 'request' ); + + AspireUpdate\Debug::log_request( 'Test log message.' ); + + $this->assertFileDoesNotExist( self::$log_file ); + } + + /** + * Test that nothing is written to the log file when request debugging is disabled. + */ + public function test_should_not_write_to_log_file_when_request_debugging_is_disabled() { + define( 'AP_DEBUG', true ); + define( 'AP_DEBUG_TYPES', [ 'response', 'string' ] ); + + AspireUpdate\Debug::log_request( 'Test log message.' ); + + $this->assertFileDoesNotExist( self::$log_file ); + } + + /** + * Test that the message is written to the log file. + * + * @dataProvider data_debug_types + * + * @param array $debug_types An array of enabled debug types. + */ + public function test_should_write_to_log_file( $debug_types ) { + define( 'AP_DEBUG', true ); + define( 'AP_DEBUG_TYPES', $debug_types ); + + $message = 'Test log message.'; + + AspireUpdate\Debug::log_request( $message ); + + $this->assertFileExists( + self::$log_file, + 'The log file was created.' + ); + + $this->assertStringContainsString( + $message, + file_get_contents( self::$log_file ), + 'The message was not logged.' + ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_debug_types() { + return [ + 'just "request"' => [ + 'debug_types' => [ 'request' ], + ], + '"request" at the start of the array"' => [ + 'debug_types' => [ 'request', 'response' ], + ], + '"request" in the middle of the array"' => [ + 'debug_types' => [ 'string', 'request', 'response' ], + ], + '"request" at the end of the array"' => [ + 'debug_types' => [ 'string', 'response', 'request' ], + ], + ]; + } +} From d350e05e5c98186554f577506c025d61b1c6ece8 Mon Sep 17 00:00:00 2001 From: Colin Stewart <79332690+costdev@users.noreply.github.com> Date: Thu, 19 Dec 2024 17:54:41 +0000 Subject: [PATCH 08/11] Add tests for `Debug::log_response()`. (#250) --- .../tests/Debug/Debug_LogResponseTest.php | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 tests/phpunit/tests/Debug/Debug_LogResponseTest.php diff --git a/tests/phpunit/tests/Debug/Debug_LogResponseTest.php b/tests/phpunit/tests/Debug/Debug_LogResponseTest.php new file mode 100644 index 0000000..8be3605 --- /dev/null +++ b/tests/phpunit/tests/Debug/Debug_LogResponseTest.php @@ -0,0 +1,104 @@ +assertFileDoesNotExist( self::$log_file ); + } + + /** + * Test that nothing is written to the log file when debug types are not an array. + */ + public function test_should_not_write_to_log_file_when_debug_types_are_not_an_array() { + define( 'AP_DEBUG', true ); + define( 'AP_DEBUG_TYPES', 'response' ); + + AspireUpdate\Debug::log_response( 'Test log message.' ); + + $this->assertFileDoesNotExist( self::$log_file ); + } + + /** + * Test that nothing is written to the log file when response debugging is disabled. + */ + public function test_should_not_write_to_log_file_when_response_debugging_is_disabled() { + define( 'AP_DEBUG', true ); + define( 'AP_DEBUG_TYPES', [ 'request', 'string' ] ); + + AspireUpdate\Debug::log_response( 'Test log message.' ); + + $this->assertFileDoesNotExist( self::$log_file ); + } + + /** + * Test that the message is written to the log file. + * + * @dataProvider data_debug_types + * + * @param array $debug_types An array of enabled debug types. + */ + public function test_should_write_to_log_file( $debug_types ) { + define( 'AP_DEBUG', true ); + define( 'AP_DEBUG_TYPES', $debug_types ); + + $message = 'Test log message.'; + + AspireUpdate\Debug::log_response( $message ); + + $this->assertFileExists( + self::$log_file, + 'The log file was created.' + ); + + $this->assertStringContainsString( + $message, + file_get_contents( self::$log_file ), + 'The message was not logged.' + ); + } + + /** + * Data provider. + * + * @return array[] + */ + public function data_debug_types() { + return [ + 'just "response"' => [ + 'debug_types' => [ 'response' ], + ], + '"response" at the start of the array"' => [ + 'debug_types' => [ 'response', 'request' ], + ], + '"response" in the middle of the array"' => [ + 'debug_types' => [ 'string', 'response', 'request' ], + ], + '"response" at the end of the array"' => [ + 'debug_types' => [ 'string', 'request', 'response' ], + ], + ]; + } +} From c91e36bb8fb9631ae4ec27b0b533f72eb685ed62 Mon Sep 17 00:00:00 2001 From: Colin Stewart <79332690+costdev@users.noreply.github.com> Date: Thu, 19 Dec 2024 18:27:36 +0000 Subject: [PATCH 09/11] Code Coverage: Ignore `Admin_Settings::add_settings_field_callback()`. (#251) This should be tested with E2E tests instead. --- includes/class-admin-settings.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/includes/class-admin-settings.php b/includes/class-admin-settings.php index 414b604..b075cc3 100644 --- a/includes/class-admin-settings.php +++ b/includes/class-admin-settings.php @@ -506,6 +506,8 @@ public function register_settings() { /** * The Fields API which any CMS should have in its core but something we dont, hence this ugly hack. * + * @codeCoverageIgnore Test with E2E tests instead. + * * @param array $args The Field Parameters. * * @return void Echos the Field HTML. From 3206ff013f6b16eacbb76ee80dbe1b184d70a9d9 Mon Sep 17 00:00:00 2001 From: Colin Stewart <79332690+costdev@users.noreply.github.com> Date: Wed, 8 Jan 2025 23:21:35 +0000 Subject: [PATCH 10/11] Workflows: Install SVN before running tests. (#254) Subversion was removed from GitHub Actions' default packages for Ubuntu 24. The tests installer uses Subversion internally. This change installs Subversion. --- .github/workflows/phpunit-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/phpunit-tests.yml b/.github/workflows/phpunit-tests.yml index 9aabbb7..d433dd4 100644 --- a/.github/workflows/phpunit-tests.yml +++ b/.github/workflows/phpunit-tests.yml @@ -35,6 +35,9 @@ jobs: php-version: ${{ matrix.php-version }} tools: phpunit-polyfills:1.1 + - name: Install SVN + run: sudo apt install -y subversion + - name: Setup tests run: bash bin/install-wp-tests.sh wordpress_tests root root 127.0.0.1 latest true From e91ebec7df4469ca7e07e62e75fcc7f2fda1c278 Mon Sep 17 00:00:00 2001 From: Colin Stewart <79332690+costdev@users.noreply.github.com> Date: Thu, 9 Jan 2025 00:37:08 +0000 Subject: [PATCH 11/11] Workflows: Prime a WP install and skip install for the actual test run. (#256) The PHPUnit test workflow now performs a dummy run to prime the WordPress install for tests. When this completes, the actual test run is performed, with the `WP_TESTS_SKIP_INSTALL` environment variable set to `1`. Since many of the tests must run in separate processes, this change means that installation occurs only once, instead of once per process. --- .github/workflows/phpunit-tests.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/phpunit-tests.yml b/.github/workflows/phpunit-tests.yml index d433dd4..eef2b75 100644 --- a/.github/workflows/phpunit-tests.yml +++ b/.github/workflows/phpunit-tests.yml @@ -41,5 +41,10 @@ jobs: - name: Setup tests run: bash bin/install-wp-tests.sh wordpress_tests root root 127.0.0.1 latest true + - name: Prime the WordPress install for tests + id: prime-tests + run: XDEBUG_MODE=off phpunit${{ matrix.multisite && ' -c tests/phpunit/multisite.xml' || '' }} --filter gha_install_wp > /dev/null 2>&1 + - name: Run tests - run: XDEBUG_MODE=off phpunit${{ matrix.multisite && ' -c tests/phpunit/multisite.xml' || '' }} + if: steps.prime-tests.outcome == 'success' + run: XDEBUG_MODE=off WP_TESTS_SKIP_INSTALL=1 phpunit${{ matrix.multisite && ' -c tests/phpunit/multisite.xml' || '' }}