MDL-72582 core_files: Add unit test for readfile_accel debugging output
Adds a unit test for the debugging message triggered when readfile_accel() detects a non-empty output buffer. The readfile_accel() function directly manipulates the output buffers, so calling it from within PHPUnit triggers an error like "Test code or tested code closed output buffers other than its own". As a workaround, the test runs a CLI script in a separate process, allowing buffer behaviour to be isolated and debugging output to be captured for assertions.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Cli script for testing readfile_accel function.
|
||||
*
|
||||
* @package core
|
||||
* @subpackage fixtures
|
||||
* @copyright 2025 Catalyst IT
|
||||
* @author Trisha Milan <[email protected]>
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user