증거물 B-1 근거: 커밋 fe60ced
커밋 시각 · fe60ceda3838463629f575bff5faca3478045833
PHP 경고문이 모든 페이지 위에 찍혔습니다
저장소 자체는 비공개입니다. 이 페이지에 커밋을 기록된 그대로 옮겼습니다.
커밋 메시지
후속: 예전 설정이 남긴 공개 debug.log 삭제
변경 내용
git에 기록된 텍스트 파일만 보여 줍니다. +로 시작하는 줄은 추가, −로 시작하는 줄은 삭제입니다.
바뀐 파일
---
site/wp-content/mu-plugins/hollis-migrations.php | 17 +++++++++++++++++
tests/phpunit/repair/Php8DeprecationsTest.php | 10 ++++++++++
2 files changed, 27 insertions(+)
diff --git a/site/wp-content/mu-plugins/hollis-migrations.php b/site/wp-content/mu-plugins/hollis-migrations.php
index 6a79548..772c141 100644
--- a/site/wp-content/mu-plugins/hollis-migrations.php
+++ b/site/wp-content/mu-plugins/hollis-migrations.php
@@ -20,6 +20,7 @@ function hollis_migrations_steps() {
return apply_filters(
'hollis_migrations_steps',
array(
+ 'remove-public-debug-log' => 'hollis_migration_remove_public_debug_log',
'remove-social-feed-caches' => 'hollis_migration_remove_social_feed_caches',
)
);
@@ -39,6 +40,22 @@ function hollis_migrations_run() {
return $results;
}
+/**
+ * B-1: the old configuration wrote PHP errors to wp-content/debug.log, a public URL. Logging now goes to
+ * /var/www/logs; the old file, with its server paths, is deleted.
+ *
+ * @return string
+ */
+function hollis_migration_remove_public_debug_log() {
+ $file = WP_CONTENT_DIR . '/debug.log';
+ if ( ! file_exists( $file ) ) {
+ return 'no public debug.log';
+ }
+ $size = (int) filesize( $file );
+ wp_delete_file( $file );
+ return file_exists( $file ) ? 'could not delete wp-content/debug.log' : sprintf( 'deleted wp-content/debug.log (%s)', size_format( $size, 1 ) );
+}
+
/**
* C-2: HC Social Feed is removed; its cached responses (autoloaded, ~3.5 MB) go with it.
*
diff --git a/tests/phpunit/repair/Php8DeprecationsTest.php b/tests/phpunit/repair/Php8DeprecationsTest.php
index 8e2b90f..44e0cb3 100644
--- a/tests/phpunit/repair/Php8DeprecationsTest.php
+++ b/tests/phpunit/repair/Php8DeprecationsTest.php
@@ -48,6 +48,16 @@ class Php8DeprecationsTest extends WP_UnitTestCase {
$this->assertStringNotContainsString( 'Deprecated:', $html );
}
+ public function test_migration_deletes_a_leftover_public_debug_log() {
+ $file = WP_CONTENT_DIR . '/debug.log';
+ file_put_contents( $file, "PHP Deprecated: something in /var/www/after/wp-content/themes/x.php
+" );
+ $results = hollis_migrations_run();
+ $this->assertStringContainsString( 'deleted wp-content/debug.log', $results['remove-public-debug-log'] );
+ $this->assertFileDoesNotExist( $file );
+ $this->assertSame( 'no public debug.log', hollis_migrations_run()['remove-public-debug-log'] );
+ }
+
public function test_errors_are_logged_outside_the_web_root_and_not_displayed() {
$config = file_get_contents( HOLLIS_CODE . '/hollis-config.php' );
$this->assertMatchesRegularExpression( "/define\(\s*'WP_DEBUG_DISPLAY',\s*false\s*\)/", $config );
증명 테스트
tests/phpunit/repair/Php8DeprecationsTest.php
tests/phpunit/repair/Php8DeprecationsTest.php, 커밋된 그대로.
<?php
/**
* Exhibit B-1: the code runs on PHP 8.3 without deprecation notices, and errors never reach the page.
*
* @package Hollis\Tests
*/
/**
* PHP 8 deprecations.
*/
class Php8DeprecationsTest extends WP_UnitTestCase {
/**
* Every PHP file of the repaired code.
*
* @return string[]
*/
private function php_files() {
$files = array();
$it = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( HOLLIS_CODE, FilesystemIterator::SKIP_DOTS ) );
foreach ( $it as $file ) {
if ( 'php' === $file->getExtension() ) {
$files[] = $file->getPathname();
}
}
return $files;
}
public function test_no_file_has_compile_time_deprecations() {
$files = $this->php_files();
$this->assertGreaterThan( 10, count( $files ) );
foreach ( $files as $file ) {
// A fresh PHP process compiles each file, so notices raised at compile time are seen too.
$out = shell_exec( escapeshellarg( PHP_BINARY ) . ' -d error_reporting=-1 -d display_errors=1 -l ' . escapeshellarg( $file ) . ' 2>&1' );
$this->assertStringNotContainsString( 'Deprecated', (string) $out, $out );
$this->assertStringContainsString( 'No syntax errors', (string) $out, $out );
}
}
public function test_page_header_renders_without_notices() {
// Deprecations, notices and warnings become exceptions in this suite (phpunit.xml.dist).
$this->go_to( home_url( '/our-team/' ) );
ob_start();
include get_template_directory() . '/header.php';
include get_template_directory() . '/footer.php';
$html = ob_get_clean();
$this->assertStringContainsString( 'Hollis Creek', $html );
$this->assertStringNotContainsString( 'Deprecated:', $html );
}
public function test_migration_deletes_a_leftover_public_debug_log() {
$file = WP_CONTENT_DIR . '/debug.log';
file_put_contents( $file, "PHP Deprecated: something in /var/www/after/wp-content/themes/x.php
" );
$results = hollis_migrations_run();
$this->assertStringContainsString( 'deleted wp-content/debug.log', $results['remove-public-debug-log'] );
$this->assertFileDoesNotExist( $file );
$this->assertSame( 'no public debug.log', hollis_migrations_run()['remove-public-debug-log'] );
}
public function test_errors_are_logged_outside_the_web_root_and_not_displayed() {
$config = file_get_contents( HOLLIS_CODE . '/hollis-config.php' );
$this->assertMatchesRegularExpression( "/define\(\s*'WP_DEBUG_DISPLAY',\s*false\s*\)/", $config );
$this->assertMatchesRegularExpression( "#define\(\s*'WP_DEBUG_LOG',\s*'/var/www/logs/#", $config );
}
}
e2e/tests/b-php8.spec.ts
e2e/tests/b-php8.spec.ts, 커밋된 그대로.
import { test, expect } from '@playwright/test';
import { BEFORE, AFTER } from './helpers';
// Exhibit B: PHP 8 errors as a visitor meets them.
test('B-3 before: the Insurance page is a white screen with a fatal error', async ({ page }) => {
const res = await page.goto(BEFORE + '/insurance/');
expect(res!.status()).toBe(500);
await expect(page.locator('body')).toContainText('There has been a critical error on this website.');
await expect(page.getByRole('heading', { name: 'Insurance and payment' })).toHaveCount(0);
await expect(page.getByRole('table')).toHaveCount(0);
});
test('B-3 after: the Insurance page shows the plans, sorted by name', async ({ page }) => {
const res = await page.goto(AFTER + '/insurance/');
expect(res!.status()).toBe(200);
await expect(page.getByRole('heading', { level: 1, name: 'Insurance and payment' })).toBeVisible();
const table = page.getByRole('table', { name: 'Insurance we work with' });
const plans = await table.locator('tbody tr td:first-child').allTextContents();
expect(plans.length).toBe(7);
expect(plans).toEqual([...plans].sort((a, b) => a.localeCompare(b)));
expect(plans[0]).toBe('Alder County Employees Plan');
});
test('B-1 before: PHP deprecation notices are printed above the page and the log is public', async ({ page, request }) => {
await page.goto(BEFORE + '/');
await expect(page.locator('body')).toContainText('Deprecated:');
const log = await request.get(BEFORE + '/wp-content/debug.log');
expect(log.status()).toBe(200);
expect(await log.text()).toContain('PHP Deprecated');
});
test('B-1 after: no notice on any page and no public debug log', async ({ page, request }) => {
for (const path of ['/', '/our-team/', '/services/', '/request-appointment/', '/news/']) {
await page.goto(AFTER + path);
await expect(page.locator('body')).not.toContainText(/Deprecated:|Warning:|Notice:/);
}
const log = await request.get(AFTER + '/wp-content/debug.log');
expect(log.status()).toBe(404);
});