diff --git a/lib/tests/filelib_test.php b/lib/tests/filelib_test.php index 4bed342c6d6..f0414ad7473 100644 --- a/lib/tests/filelib_test.php +++ b/lib/tests/filelib_test.php @@ -2076,6 +2076,56 @@ EOF; ], ]; } + + /** + * Tests that readfile_accel() triggers the expected debugging message when a non-empty + * output buffer is detected, using both a file path and a stored_file input. + * + * This test runs a CLI script in a separate process to isolate buffer manipulation. + * This is necessary because readfile_accel() uses ob_get_clean() and ob_end_flush(), + * which interfere with PHPUnit's internal output buffer enforcement and cause risky + * test errors. + * + * The CLI script simulates a non-empty output buffer, calls the readfile_accel(), and + * prints any debugging output. The test then captures that output and asserts that the + * correct debugging message was generated. + * + * @covers ::readfile_accel + */ + public function test_readfile_accel_with_path_and_stored_file(): void { + $this->resetAfterTest(); + + // Construct the command to run the CLI script with a custom constant defined. + $scriptpath = __DIR__ . '/fixtures/readfile_accel_debug_cli.php'; + $cmd = 'php -r ' . escapeshellarg("define('PHPUNIT_READFILE_ACCEL_TEST', true); require '$scriptpath';"); + + $pipes = []; + $process = proc_open($cmd, [ + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ], $pipes); + + $stdout = stream_get_contents($pipes[1]); + $stderr = stream_get_contents($pipes[2]); + + fclose($pipes[1]); + fclose($pipes[2]); + + $exitcode = proc_close($process); + + $output = $stdout . $stderr; + + // Debug just in case the subprocess fails. + $this->assertSame(0, $exitcode); + + // Validate that both path-based and stored_file debugging messages are present. + $filename = "readfile_accel.txt"; + $filepath = '/tmp/' . $filename; + $this->assertStringContainsString('Non-empty default output handler buffer detected while serving the file ' . + $filepath . '. Buffer contents (first 20 characters): test text', $output); + $this->assertStringContainsString('Non-empty default output handler buffer detected while serving the file ' . + $filename . '. Buffer contents (first 20 characters): test text', $output); + } } /** diff --git a/lib/tests/fixtures/readfile_accel_debug_cli.php b/lib/tests/fixtures/readfile_accel_debug_cli.php new file mode 100644 index 00000000000..4731bf3fa96 --- /dev/null +++ b/lib/tests/fixtures/readfile_accel_debug_cli.php @@ -0,0 +1,112 @@ +. + +/** + * Cli script for testing readfile_accel function. + * + * @package core + * @subpackage fixtures + * @copyright 2025 Catalyst IT + * @author Trisha Milan + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +define('CLI_SCRIPT', true); + +require_once(__DIR__ . '/../../../config.php'); +require_once($CFG->libdir . '/filelib.php'); + +if (!defined('PHPUNIT_READFILE_ACCEL_TEST')) { + echo 'This script is only intended to be run via PHPUnit.'; + exit(1); +} + +$testdb = moodle_database::get_driver_instance($CFG->dbtype, $CFG->dblibrary); +$testdb->connect($CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->phpunit_prefix); +$DB = $testdb; + +set_debugging(DEBUG_DEVELOPER, true); +$CFG->tempdir = '/tmp'; + +/** + * Runs readfile_accel() with a file path or a stored_file to trigger the buffer check. + * + * @param string|stored_file $input + * @param string $mimetype + * @param bool $accelerate + */ +function run_readfile_accel_test(string|stored_file $input, string $mimetype, bool $accelerate): void { + try { + ob_start(); + echo "test text"; + $_SERVER['REQUEST_METHOD'] = 'GET'; + readfile_accel($input, $mimetype, $accelerate); + } catch (Exception $e) { + echo $e->getMessage() . "\n"; + } +} + +try { + // Prepare test file. + $filename = "readfile_accel.txt"; + // Generate temporary local file for testing. + $path = "$CFG->tempdir/$filename"; + file_put_contents($path, "\nMoodle test data\n"); + + // Populate {files} table. + $fs = get_file_storage(); + $filerecord = [ + 'contextid' => context_system::instance()->id, + 'component' => 'test', + 'filearea' => 'readfile', + 'itemid' => 0, + 'filepath' => '/', + 'filename' => $filename, + ]; + $storedfile = null; + $filerecord['filename'] = $fs->get_unused_filename( + $filerecord['contextid'], + $filerecord['component'], + $filerecord['filearea'], + $filerecord['itemid'], + $filerecord['filepath'], + $filerecord['filename'] + ); + $storedfile = $fs->create_file_from_pathname($filerecord, $path); + $mimetype = get_mimetype_for_sending($storedfile->get_filename()); + $accelerate = true; + + // Run the test with direct path. + run_readfile_accel_test($path, $mimetype, $accelerate); + + // Run the test with direct stored_file. + run_readfile_accel_test($storedfile, $mimetype, $accelerate); +} finally { + // Clean up {files} table. + if (!is_null($fs)) { + @$fs->delete_area_files( + $filerecord['contextid'], + $filerecord['component'], + $filerecord['filearea'], + $filerecord['itemid'] + ); + } + + // Clean up testing file. + if ($path !== "") { + @unlink($path); + } +}