diff --git a/assets/js/aspire-update.js b/assets/js/aspire-update.js
index eeaa95c..0c0ca1b 100644
--- a/assets/js/aspire-update.js
+++ b/assets/js/aspire-update.js
@@ -97,7 +97,7 @@ class ViewLog {
jQuery.ajax(parameters)
.done(function (response) {
if ((true == response.success) && ('' != response.data.content)) {
- let lines = response.data.content.split(aspireupdate.line_ending);
+ let lines = response.data.content;
jQuery.each(lines, function (index, line) {
jQuery('
')
.append(
diff --git a/includes/class-debug.php b/includes/class-debug.php
index 81ab276..cab628e 100644
--- a/includes/class-debug.php
+++ b/includes/class-debug.php
@@ -19,6 +19,13 @@ class Debug {
*/
private static $log_file = 'debug-aspire-update.log';
+ /**
+ * The filesystem.
+ *
+ * @var Filesystem_Direct
+ */
+ private static $filesystem;
+
/**
* Get the Log file path.
*
@@ -31,44 +38,17 @@ private static function get_file_path() {
/**
* Initializes the WordPress Filesystem.
*
- * @return WP_Filesystem_Base|false The filesystem object or false on failure.
+ * @return Filesystem_Direct The filesystem object.
*/
private static function init_filesystem() {
- global $wp_filesystem;
-
- if ( ! $wp_filesystem ) {
+ if ( ! self::$filesystem instanceof Filesystem_Direct ) {
require_once ABSPATH . 'wp-admin/includes/file.php';
+ require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
+ require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php';
WP_Filesystem();
+ self::$filesystem = new Filesystem_Direct( false );
}
-
- return $wp_filesystem;
- }
-
- /**
- * Checks the filesystem status and logs error to debug log.
- *
- * @param WP_Filesystem_Base $wp_filesystem The filesystem object.
- *
- * @return boolean true on success and false on failure.
- */
- private static function verify_filesystem( $wp_filesystem ) {
- if ( ! $wp_filesystem ) {
- if (
- defined( 'WP_DEBUG' ) &&
- ( true === WP_DEBUG ) &&
- defined( 'WP_DEBUG_LOG' ) &&
- ( true === WP_DEBUG_LOG )
- ) {
- // phpcs:disable WordPress.PHP.DevelopmentFunctions
- /**
- * 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.' ); // @codeCoverageIgnore
- // phpcs:enable
- }
- return false;
- }
- return true;
+ return self::$filesystem;
}
/**
@@ -76,34 +56,23 @@ private static function verify_filesystem( $wp_filesystem ) {
*
* @param integer $limit Max no of lines to return. Defaults to a 1000 lines.
*
- * @return string|WP_Error The File content truncate upto the number of lines set in the limit parameter.
+ * @return array|WP_Error An array of lines in the file, limited to $limit, or a WP_Error object on failure.
*/
public static function read( $limit = 1000 ) {
$wp_filesystem = self::init_filesystem();
$file_path = self::get_file_path();
- if ( ! self::verify_filesystem( $wp_filesystem ) || ! $wp_filesystem->exists( $file_path ) || ! $wp_filesystem->is_readable( $file_path ) ) {
+
+ if ( ! $wp_filesystem->exists( $file_path ) || ! $wp_filesystem->is_readable( $file_path ) ) {
return new \WP_Error( 'not_readable', __( 'Error: Unable to read the log file.', 'aspireupdate' ) );
}
- $file_content = $wp_filesystem->get_contents_array( $file_path );
- $content = '';
- $index = 0;
- foreach ( $file_content as $file_content_lines ) {
- if ( ( $index < $limit ) ) {
- $content .= $file_content_lines . PHP_EOL;
- ++$index;
- }
- }
- if ( '' === trim( $content ) ) {
- $content = esc_html__( '*****Log file is empty.*****', 'aspireupdate' );
- } elseif ( $limit < count( $file_content ) ) {
- $content .= PHP_EOL . sprintf(
- /* translators: 1: The number of lines at which the content was truncated. */
- esc_html__( '*****Log truncated at %s lines.*****', 'aspireupdate' ),
- $limit
- );
+ $file_content = $wp_filesystem->get_contents_array( $file_path, $limit, true );
+
+ if ( ( false === $file_content ) || ( 0 === count( array_filter( $file_content ) ) ) ) {
+ $file_content = [ esc_html__( '*****Log file is empty.*****', 'aspireupdate' ) ];
}
- return $content;
+
+ return $file_content;
}
/**
@@ -114,7 +83,8 @@ public static function read( $limit = 1000 ) {
public static function clear() {
$wp_filesystem = self::init_filesystem();
$file_path = self::get_file_path();
- if ( ! self::verify_filesystem( $wp_filesystem ) || ! $wp_filesystem->exists( $file_path ) || ! $wp_filesystem->is_writable( $file_path ) ) {
+
+ if ( ! $wp_filesystem->exists( $file_path ) || ! $wp_filesystem->is_writable( $file_path ) ) {
return new \WP_Error( 'not_accessible', __( 'Error: Unable to access the log file.', 'aspireupdate' ) );
}
@@ -133,30 +103,22 @@ public static function clear() {
* @param string $type The log level ('string', 'request', 'response').
*/
public static function log( $message, $type = 'string' ) {
- $wp_filesystem = self::init_filesystem();
- if ( self::verify_filesystem( $wp_filesystem ) ) {
- $timestamp = gmdate( 'Y-m-d H:i:s' );
- $formatted_message = sprintf(
- '[%s] [%s]: %s',
- $timestamp,
- strtoupper( $type ),
- self::format_message( $message )
- ) . PHP_EOL;
-
- $file_path = self::get_file_path();
-
- $content = '';
- if ( $wp_filesystem->exists( $file_path ) ) {
- if ( $wp_filesystem->is_readable( $file_path ) ) {
- $content = $wp_filesystem->get_contents( $file_path );
- }
- }
- $wp_filesystem->put_contents(
- $file_path,
- $formatted_message . $content,
- FS_CHMOD_FILE
- );
- }
+ $wp_filesystem = self::init_filesystem();
+ $timestamp = gmdate( 'Y-m-d H:i:s' );
+ $formatted_message = sprintf(
+ '[%s] [%s]: %s',
+ $timestamp,
+ strtoupper( $type ),
+ self::format_message( $message )
+ ) . PHP_EOL;
+
+ $file_path = self::get_file_path();
+ $wp_filesystem->put_contents(
+ $file_path,
+ $formatted_message,
+ FS_CHMOD_FILE,
+ 'a'
+ );
}
/**
diff --git a/includes/class-filesystem-direct.php b/includes/class-filesystem-direct.php
new file mode 100644
index 0000000..9f7e097
--- /dev/null
+++ b/includes/class-filesystem-direct.php
@@ -0,0 +1,104 @@
+exists( $file ) ) {
+ return false;
+ }
+
+ if ( -1 === $number_of_lines ) {
+ return @file( $file );
+ }
+
+ $handle = @fopen( $file, 'r' );
+ if ( ! $handle ) {
+ return false;
+ }
+
+ $lines = [];
+ $line_count = 0;
+
+ // phpcs:disable Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
+ /**
+ * This is a valid and intentional use.
+ */
+ while ( ( $line = fgets( $handle ) ) !== false ) {
+ $lines[] = trim( $line );
+ ++$line_count;
+
+ if ( $count_bottom_to_top ) {
+ if ( $number_of_lines > 0 && $line_count > $number_of_lines ) {
+ array_shift( $lines );
+ }
+ } elseif ( $number_of_lines > 0 && $line_count >= $number_of_lines ) {
+ break;
+ }
+ }
+ // phpcs:enable
+
+ fclose( $handle );
+
+ return $lines;
+ }
+
+ /**
+ * Write contents to a file with additional modes.
+ *
+ * @param string $file The path to the file.
+ * @param string $contents The content to write.
+ * @param int|false $mode Optional. The file permissions as octal number, usually 0644.
+ * Default false.
+ * @param string $write_mode The write mode:
+ * 'w' - Overwrite the file (default).
+ * 'a' - Append to the file.
+ * 'x' - Create a new file and write, fail if the file exists.
+ * 'c' - Open the file for writing, but do not truncate.
+ * @return bool True on success, false on failure.
+ */
+ public function put_contents( $file, $contents, $mode = false, $write_mode = 'w' ) {
+ $valid_write_modes = [ 'w', 'a', 'x', 'c' ];
+ if ( ! in_array( $write_mode, $valid_write_modes, true ) ) {
+ return false;
+ }
+
+ $handle = @fopen( $file, $write_mode );
+ if ( ! $handle ) {
+ return false;
+ }
+
+ mbstring_binary_safe_encoding();
+ $data_length = strlen( $contents );
+ $bytes_written = fwrite( $handle, $contents );
+ reset_mbstring_encoding();
+
+ fclose( $handle );
+
+ if ( $data_length !== $bytes_written ) {
+ return false;
+ }
+
+ $this->chmod( $file, $mode );
+
+ return true;
+ }
+}
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index 66ac36d..75aaf4e 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -59,6 +59,23 @@
+
+
+ includes/class-filesystem-direct\.php
+
+
+
+ includes/class-filesystem-direct\.php
+
+
+
+ includes/class-filesystem-direct\.php
+
+
+
+ includes/class-filesystem-direct\.php
+
+
/tests/phpunit/*
@@ -82,4 +99,10 @@
/tests/phpunit/*
+
+ /tests/phpunit/*
+
+
+ /tests/phpunit/*
+
diff --git a/tests/phpunit/includes/Debug_UnitTestCase.php b/tests/phpunit/includes/Debug_UnitTestCase.php
index 0058fd0..a5a9df0 100644
--- a/tests/phpunit/includes/Debug_UnitTestCase.php
+++ b/tests/phpunit/includes/Debug_UnitTestCase.php
@@ -13,19 +13,26 @@ abstract class Debug_UnitTestCase extends WP_UnitTestCase {
protected static $log_file;
/**
- * Previously created filesystems.
+ * The class for use with the Reflection API.
*
- * @var array
+ * @var \ReflectionClass
*/
- protected static $filesystems = [];
+ protected static $reflection;
/**
- * The original value of $wp_filesystem before any tests run.
+ * The default filesystem.
*
- * @var WP_Filesystem_Base|null|false False if not already set.
+ * @var \AspireUpdate\Filesystem_Direct|null
*/
protected static $default_filesystem;
+ /**
+ * Previously created filesystems.
+ *
+ * @var array
+ */
+ protected static $filesystems = [];
+
/**
* Gets the log file's path, and deletes if it exists before any tests run.
* Backs up the default filesystem.
@@ -48,11 +55,8 @@ public static function set_up_before_class() {
unlink( self::$log_file );
}
- if ( isset( $GLOBALS['wp_filesystem'] ) ) {
- self::$default_filesystem = $GLOBALS['wp_filesystem'];
- } else {
- self::$default_filesystem = false;
- }
+ self::$reflection = new ReflectionClass( '\AspireUpdate\Debug' );
+ self::$default_filesystem = self::$reflection->getStaticPropertyValue( 'filesystem' );
}
/**
@@ -83,11 +87,7 @@ public function tear_down() {
unlink( self::$log_file );
}
- if ( false === self::$default_filesystem ) {
- unset( $GLOBALS['wp_filesystem'] );
- } else {
- $GLOBALS['wp_filesystem'] = self::$default_filesystem;
- }
+ self::$reflection->setStaticPropertyValue( 'filesystem', self::$default_filesystem );
parent::tear_down();
}
diff --git a/tests/phpunit/includes/class-ap-fake-filesystem.php b/tests/phpunit/includes/class-ap-fake-filesystem.php
index d2de759..0f09ab8 100644
--- a/tests/phpunit/includes/class-ap-fake-filesystem.php
+++ b/tests/phpunit/includes/class-ap-fake-filesystem.php
@@ -3,7 +3,7 @@
require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php';
require_once ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php';
-class AP_FakeFilesystem extends WP_Filesystem_Direct {
+class AP_FakeFilesystem extends AspireUpdate\Filesystem_Direct {
/**
* Whether paths should exist.
*
diff --git a/tests/phpunit/tests/Debug/Debug_ClearTest.php b/tests/phpunit/tests/Debug/Debug_ClearTest.php
index d6882c9..3bc1bba 100644
--- a/tests/phpunit/tests/Debug/Debug_ClearTest.php
+++ b/tests/phpunit/tests/Debug/Debug_ClearTest.php
@@ -8,34 +8,19 @@
/**
* Tests for Debug::clear()
*
+ * These tests cause constants to be defined.
+ * They must run in separate processes and must not preserve global state.
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ *
* @covers \AspireUpdate\Debug::clear
*/
class Debug_ClearTest extends Debug_UnitTestCase {
- /**
- * Test that a WP_Error object is returned when the filesystem isn't available.
- *
- * @covers \AspireUpdate\Debug::init_filesystem
- * @covers \AspireUpdate\Debug::verify_filesystem
- */
- public function test_should_return_wp_error_when_filesystem_is_not_available() {
- add_filter( 'filesystem_method', '__return_false' );
-
- $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 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() {
@@ -54,14 +39,13 @@ public function test_should_return_wp_error_when_log_file_does_not_exist() {
* 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;
+ file_put_contents( self::$log_file, '' );
- // Backup and replace the filesystem object.
- $wp_filesystem = $this->get_fake_filesystem( true, true, false );
+ // Replace the filesystem object.
+ self::$reflection->setStaticPropertyValue( 'filesystem', $this->get_fake_filesystem( true, true, false ) );
$actual = AspireUpdate\Debug::clear();
@@ -69,11 +53,6 @@ public function test_should_return_wp_error_when_log_file_is_not_writable() {
$actual,
'A WP_Error was not returned.'
);
-
- $this->assertFileDoesNotExist(
- self::$log_file,
- 'The log file was created.'
- );
}
/**
diff --git a/tests/phpunit/tests/Debug/Debug_LogTest.php b/tests/phpunit/tests/Debug/Debug_LogTest.php
index c95036f..4876d20 100644
--- a/tests/phpunit/tests/Debug/Debug_LogTest.php
+++ b/tests/phpunit/tests/Debug/Debug_LogTest.php
@@ -8,25 +8,15 @@
/**
* Tests for Debug::log()
*
+ * These tests cause constants to be defined.
+ * They must run in separate processes and must not preserve global state.
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ *
* @covers \AspireUpdate\Debug::log
*/
class Debug_LogTest extends Debug_UnitTestCase {
- /**
- * Test that nothing is written to the log file when the filesystem isn't available.
- *
- * @covers \AspireUpdate\Debug::init_filesystem
- * @covers \AspireUpdate\Debug::verify_filesystem
- */
- public function test_should_not_write_to_log_file_when_filesystem_is_not_available() {
- add_filter( 'filesystem_method', '__return_false' );
-
- AspireUpdate\Debug::log( 'Test log message.' );
-
- $this->assertFileDoesNotExist(
- self::$log_file,
- 'The log file was created.'
- );
- }
/**
* Test that the log file is created when it doesn't already exist.
@@ -75,27 +65,41 @@ public function test_should_add_message_to_log_file() {
}
/**
- * Test that the message is prepended to an existing log file.
+ * Test that the message is appended 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 );
+ public function test_should_append_message_to_an_existing_log_file() {
+ $previous_message = "A previously logged message.\n";
+ file_put_contents( self::$log_file, $previous_message );
- $message = 'Test log message.';
+ $new_message = 'New log message.';
- AspireUpdate\Debug::log( $message );
+ AspireUpdate\Debug::log( $new_message );
$this->assertFileExists(
self::$log_file,
'The log file was not created.'
);
+ $actual = file_get_contents( self::$log_file );
+
$this->assertStringContainsString(
- "$message\n$existing_content",
- file_get_contents( self::$log_file ),
- 'The message was not prepended to the log file.'
+ $previous_message,
+ $actual,
+ 'The previous message does not exist.'
+ );
+
+ $this->assertStringContainsString(
+ $new_message,
+ $actual,
+ 'The new message does not exist.'
+ );
+
+ $this->assertLessThan(
+ strpos( $actual, $new_message ),
+ strpos( $actual, $previous_message ),
+ 'The new message was not appended to the log file.'
);
}
diff --git a/tests/phpunit/tests/Debug/Debug_ReadTest.php b/tests/phpunit/tests/Debug/Debug_ReadTest.php
index 5f275e1..591a354 100644
--- a/tests/phpunit/tests/Debug/Debug_ReadTest.php
+++ b/tests/phpunit/tests/Debug/Debug_ReadTest.php
@@ -11,22 +11,10 @@
* @covers \AspireUpdate\Debug::read
*/
class Debug_ReadTest extends Debug_UnitTestCase {
- /**
- * Test that a WP_Error object is returned when the filesystem isn't available.
- *
- * @covers \AspireUpdate\Debug::init_filesystem
- * @covers \AspireUpdate\Debug::verify_filesystem
- */
- public function test_should_return_wp_error_when_filesystem_is_not_available() {
- add_filter( 'filesystem_method', '__return_false' );
- $this->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() {
@@ -37,17 +25,14 @@ public function test_should_return_wp_error_when_log_file_does_not_exist() {
* 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 );
+ // Replace the filesystem object.
+ self::$reflection->setStaticPropertyValue( 'filesystem', $this->get_fake_filesystem( true, false, true ) );
$actual = AspireUpdate\Debug::read();
@@ -61,14 +46,26 @@ 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(
+ $this->assertIsArray(
$actual,
- 'A string was not returned.'
+ 'An array was not returned.'
+ );
+
+ $this->assertCount(
+ 1,
+ $actual,
+ 'An incorrect number of entries was returned.'
+ );
+
+ $entry = reset( $actual );
+ $this->assertIsString(
+ $entry,
+ 'The entry is not a string.'
);
$this->assertStringContainsString(
'Log file is empty',
- $actual,
+ $entry,
'The empty log file message was not returned.'
);
}
@@ -81,14 +78,26 @@ public function test_should_return_an_empty_log_message_when_log_file_only_has_e
file_put_contents( self::$log_file, " \n\r\t\v\x00" );
$actual = AspireUpdate\Debug::read();
- $this->assertIsString(
+ $this->assertIsArray(
+ $actual,
+ 'An array was not returned.'
+ );
+
+ $this->assertCount(
+ 1,
$actual,
- 'A string was not returned.'
+ 'An incorrect number of entries was returned.'
+ );
+
+ $entry = reset( $actual );
+ $this->assertIsString(
+ $entry,
+ 'The entry is not a string.'
);
$this->assertStringContainsString(
'Log file is empty',
- $actual,
+ $entry,
'The empty log file message was not returned.'
);
}
@@ -101,23 +110,35 @@ public function test_should_not_return_an_empty_log_message_when_log_file_has_co
file_put_contents( self::$log_file, 'Some contents' );
$actual = AspireUpdate\Debug::read();
- $this->assertIsString(
+ $this->assertIsArray(
$actual,
- 'A string was not returned.'
+ 'An array was not returned.'
+ );
+
+ $this->assertCount(
+ 1,
+ $actual,
+ 'An incorrect number of entries was returned.'
+ );
+
+ $entry = reset( $actual );
+ $this->assertIsString(
+ $entry,
+ 'The entry is not a string.'
);
$this->assertStringNotContainsString(
'Log file is empty',
- $actual,
+ $entry,
'The empty log file message was returned.'
);
}
/**
- * Test that a truncation message is added when the log file has more
+ * Test that truncation is performed 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() {
+ public function test_should_add_truncate_when_log_file_has_more_lines_than_requested() {
file_put_contents(
self::$log_file,
"First line\r\nSecond line\r\nThird line"
@@ -125,23 +146,23 @@ public function test_should_add_a_truncation_message_when_log_file_has_more_line
$actual = AspireUpdate\Debug::read( 2 );
- $this->assertIsString(
+ $this->assertIsArray(
$actual,
- 'A string was not returned.'
+ 'An array was not returned.'
);
- $this->assertStringContainsString(
- 'Log truncated',
+ $this->assertCount(
+ 2,
$actual,
- 'The truncation message was not returned.'
+ 'An incorrect number of entries was returned.'
);
}
/**
- * Test that no truncation message is added when the log file has the same
+ * Test that the whole log is returned 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() {
+ public function test_should_return_whole_log_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"
@@ -149,23 +170,23 @@ public function test_should_not_add_a_truncation_message_when_log_file_has_the_s
$actual = AspireUpdate\Debug::read( 3 );
- $this->assertIsString(
+ $this->assertIsArray(
$actual,
- 'A string was not returned.'
+ 'An array was not returned.'
);
- $this->assertStringNotContainsString(
- 'Log truncated',
+ $this->assertCount(
+ 3,
$actual,
- 'The truncation message was added.'
+ 'An incorrect number of entries was returned.'
);
}
/**
- * Test that no truncation message is added when the log file has fewer than
+ * Test that truncation is not performed 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() {
+ public function test_should_not_truncate_when_log_file_has_fewer_lines_than_requested() {
file_put_contents(
self::$log_file,
"First line\r\nSecond line\r\nThird line"
@@ -173,15 +194,15 @@ public function test_should_not_add_a_truncation_message_when_log_file_has_fewer
$actual = AspireUpdate\Debug::read( 4 );
- $this->assertIsString(
+ $this->assertIsArray(
$actual,
- 'A string was not returned.'
+ 'An array was not returned.'
);
- $this->assertStringNotContainsString(
- 'Log truncated',
+ $this->assertCount(
+ 3,
$actual,
- 'The truncation message was added.'
+ 'An incorrect number of entries was returned.'
);
}
}
diff --git a/tests/phpunit/tests/FilesystemDirect/GetContentsArrayTest.php b/tests/phpunit/tests/FilesystemDirect/GetContentsArrayTest.php
new file mode 100644
index 0000000..73ba579
--- /dev/null
+++ b/tests/phpunit/tests/FilesystemDirect/GetContentsArrayTest.php
@@ -0,0 +1,263 @@
+assertFalse( $filesystem->get_contents_array( 'non_existent_file.txt', 1 ) );
+ }
+
+ /**
+ * Test that false is returned when the file is not readable.
+ *
+ * This test fakes a true exists() result, despite the file not existing.
+ *
+ * Since the file doesn't exist, read checks should fail.
+ */
+ public function test_should_return_false_when_the_file_cannot_be_read() {
+ $filesystem = new AP_FakeFilesystem( true, false, false );
+
+ $this->assertFalse( $filesystem->get_contents_array( self::$test_file, 1 ) );
+ }
+
+ /**
+ * Test that the entire log is returned if the number of requested lines is -1.
+ */
+ public function test_should_return_the_entire_log_if_number_of_requested_lines_is_minus_one() {
+ $filesystem = new AP_FakeFilesystem( true, true, true );
+ $contents = 'First line' . PHP_EOL . 'Second line' . PHP_EOL . 'Third line';
+ file_put_contents( self::$test_file, $contents );
+
+ $actual = $filesystem->get_contents_array( self::$test_file, -1 );
+
+ $this->assertIsArray(
+ $actual,
+ 'The contents were not read into an array.'
+ );
+
+ $this->assertSame(
+ [
+ 'First line' . PHP_EOL,
+ 'Second line' . PHP_EOL,
+ 'Third line',
+ ],
+ $actual,
+ 'The entire log was not read.'
+ );
+ }
+
+ /**
+ * Test that the lines returned are from the bottom of the log file up when requested.
+ *
+ * @dataProvider data_count_bottom_to_top_enabled
+ *
+ * @param mixed $count_bottom_to_top Whether to count the lines from the bottom up.
+ */
+ public function test_should_read_from_bottom_to_top_when_requested( $count_bottom_to_top ) {
+ $filesystem = new AP_FakeFilesystem( true, true, true );
+ $contents = 'First line' . PHP_EOL . 'Second line' . PHP_EOL . 'Third line';
+ file_put_contents( self::$test_file, $contents );
+
+ $actual = $filesystem->get_contents_array( self::$test_file, 2, $count_bottom_to_top );
+
+ $this->assertIsArray(
+ $actual,
+ 'The contents were not read into an array.'
+ );
+
+ $this->assertCount(
+ 2,
+ $actual,
+ 'The number of lines read does not match the requested number of lines.'
+ );
+
+ $this->assertSame(
+ [
+ 'Second line',
+ 'Third line',
+ ],
+ $actual,
+ 'The lines were not read from the bottom up.'
+ );
+ }
+
+ /**
+ * Test that the whole log is returned when the log file has the same
+ * number of lines as requested.
+ *
+ * @dataProvider data_count_bottom_to_top_enabled
+ * @dataProvider data_count_bottom_to_top_disabled
+ *
+ * @param mixed $count_bottom_to_top Whether to count the lines from the bottom up.
+ */
+ public function test_should_return_whole_log_when_log_file_has_the_same_number_of_lines_as_requested( $count_bottom_to_top ) {
+ $filesystem = new AP_FakeFilesystem( true, true, true );
+ $contents = 'First line' . PHP_EOL . 'Second line' . PHP_EOL . 'Third line';
+ file_put_contents( self::$test_file, $contents );
+
+ $actual = $filesystem->get_contents_array( self::$test_file, 3, $count_bottom_to_top );
+
+ $this->assertIsArray(
+ $actual,
+ 'The contents were not read into an array.'
+ );
+
+ $this->assertSame(
+ [
+ 'First line',
+ 'Second line',
+ 'Third line',
+ ],
+ $actual,
+ 'The entire log was not read.'
+ );
+ }
+
+ /**
+ * Test that only the requested number of lines is read.
+ *
+ * @dataProvider data_count_bottom_to_top_enabled
+ * @dataProvider data_count_bottom_to_top_disabled
+ *
+ * @param mixed $count_bottom_to_top Whether to count the lines from the bottom up.
+ */
+ public function test_should_only_read_the_requested_number_of_lines( $count_bottom_to_top ) {
+ $filesystem = new AP_FakeFilesystem( true, true, true );
+ $contents = 'First line' . PHP_EOL . 'Second line' . PHP_EOL . 'Third line';
+ file_put_contents( self::$test_file, $contents );
+
+ $actual = $filesystem->get_contents_array( self::$test_file, 2, $count_bottom_to_top );
+
+ $this->assertIsArray(
+ $actual,
+ 'The contents were not read into an array.'
+ );
+
+ if ( $count_bottom_to_top ) {
+ $expected = [
+ 'Second line',
+ 'Third line',
+ ];
+ } else {
+ $expected = [
+ 'First line',
+ 'Second line',
+ ];
+ }
+
+ $this->assertSame(
+ $expected,
+ $actual,
+ 'The lines read do not match the expected lines.'
+ );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array[]
+ */
+ public function data_count_bottom_to_top_enabled() {
+ return [
+ '$count_bottom_to_top as (bool) true' => [
+ 'count_bottom_to_top' => true,
+ ],
+ '$count_bottom_to_top as (int) 1' => [
+ 'count_bottom_to_top' => 1,
+ ],
+ '$count_bottom_to_top as (float) 1.0' => [
+ 'count_bottom_to_top' => 1.0,
+ ],
+ '$count_bottom_to_top as (float) -1.0' => [
+ 'count_bottom_to_top' => -1.0,
+ ],
+ '$count_bottom_to_top as (string) "1"' => [
+ 'count_bottom_to_top' => '1',
+ ],
+ '$count_bottom_to_top as a string with spaces' => [
+ 'count_bottom_to_top' => " \t\r\n",
+ ],
+ '$count_bottom_to_top as a non-empty array' => [
+ 'count_bottom_to_top' => [ 'not empty' ],
+ ],
+ '$count_bottom_to_top as an object' => [
+ 'count_bottom_to_top' => new stdClass(),
+ ],
+ '$count_bottom_to_top as NAN' => [
+ 'count_bottom_to_top' => NAN,
+ ],
+ '$count_bottom_to_top as INF' => [
+ 'count_bottom_to_top' => INF,
+ ],
+ ];
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array[]
+ */
+ public function data_count_bottom_to_top_disabled() {
+ return [
+ '$count_bottom_to_top as (bool) false' => [
+ 'count_bottom_to_top' => false,
+ ],
+ '$count_bottom_to_top as (int) 0' => [
+ 'count_bottom_to_top' => 0,
+ ],
+ '$count_bottom_to_top as (string) "0"' => [
+ 'count_bottom_to_top' => '0',
+ ],
+ '$count_bottom_to_top as (float) 0.0' => [
+ 'count_bottom_to_top' => 0.0,
+ ],
+ '$count_bottom_to_top as (float) -0.0' => [
+ 'count_bottom_to_top' => -0.0,
+ ],
+ '$count_bottom_to_top as an empty string' => [
+ 'count_bottom_to_top' => '',
+ ],
+ '$count_bottom_to_top as an empty array' => [
+ 'count_bottom_to_top' => [],
+ ],
+ '$count_bottom_to_top as NULL' => [
+ 'count_bottom_to_top' => null,
+ ],
+ ];
+ }
+}
diff --git a/tests/phpunit/tests/FilesystemDirect/PutContentsTest.php b/tests/phpunit/tests/FilesystemDirect/PutContentsTest.php
new file mode 100644
index 0000000..5c6e50d
--- /dev/null
+++ b/tests/phpunit/tests/FilesystemDirect/PutContentsTest.php
@@ -0,0 +1,131 @@
+assertFalse( $filesystem->put_contents( self::$test_file, '', false, 'g' ) );
+ }
+
+ /**
+ * Test that the log file is created when it doesn't already exist.
+ *
+ * This test causes constants to be defined.
+ * It must run in a separate process and must not preserve global state.
+ *
+ * @runInSeparateProcess
+ * @preserveGlobalState disabled
+ */
+ public function test_should_create_log_file_if_it_does_not_already_exist() {
+ define( 'FS_CHMOD_FILE', 0644 );
+
+ $filesystem = new AP_FakeFilesystem( false, true, true );
+ $filesystem->put_contents( self::$test_file, '', false, 'w' );
+
+ $this->assertFileExists(
+ self::$test_file,
+ 'The log file was not created.'
+ );
+ }
+
+ /**
+ * Test that false is returned when the path is a directory.
+ */
+ public function test_should_return_false_when_the_path_is_a_directory() {
+ $test_dir = '/tmp/aspireupdate-putcontents-test-dir';
+ $filesystem = new AP_FakeFilesystem( false, true, true );
+ mkdir( $test_dir );
+
+ $this->assertDirectoryExists(
+ $test_dir,
+ 'The test directory was not created.'
+ );
+
+ $actual = $filesystem->put_contents( $test_dir, '', false, 'w' );
+ rmdir( $test_dir );
+
+ $this->assertFalse(
+ $actual,
+ 'Passing a directory path did not return false.'
+ );
+ }
+
+ /**
+ * Test that content is appended to the file when the write mode is 'a'.
+ *
+ * This test causes constants to be defined.
+ * It must run in a separate process and must not preserve global state.
+ *
+ * @runInSeparateProcess
+ * @preserveGlobalState disabled
+ */
+ public function test_should_append_to_file_when_the_write_mode_is_a() {
+ define( 'FS_CHMOD_FILE', 0644 );
+
+ $existing_content = 'This is existing content.';
+ $new_content = PHP_EOL . 'This is new content';
+ file_put_contents( self::$test_file, $existing_content );
+
+ $this->assertFileExists(
+ self::$test_file,
+ 'The file was not created before testing.'
+ );
+
+ $this->assertSame(
+ $existing_content,
+ file_get_contents( self::$test_file ),
+ 'The contents of the test file are not correct before testing.'
+ );
+
+ $filesystem = new AP_FakeFilesystem( true, true, true );
+ $filesystem->put_contents( self::$test_file, $new_content, false, 'a' );
+ $contents = file_get_contents( self::$test_file );
+
+ $this->assertSame(
+ $contents,
+ $existing_content . $new_content,
+ 'The contents of the file are unexpected.'
+ );
+
+ $this->assertLessThan(
+ strpos( $contents, $new_content ),
+ strpos( $contents, $existing_content ),
+ 'The new content was not appended to the file.'
+ );
+ }
+}