설치 단계와 코어 부팅의 정책이 엇갈려 있었다. 인스톨러는 root 를 자유롭게 허용하면서, 설치 후 부팅은 같은 값을 보고 확장 로딩 전체를 조용히 스킵했다. 설치는 성공하지만 이후 깨진 화면만 뜨고 원인은 로그 한 줄에만 남았다. 판정을 프레임워크 의존성 0 인 공용 SSoT 로 모아 인스톨러와 코어가 같은 답을 내게 하고, 설치 단계에서 서버 2경로로 차단하며, 설치 후 변경은 전용 에러 페이지로 드러낸다. 가드를 boot 가 아닌 미들웨어에 두어 artisan 이 죽지 않게 해 복구 수단을 남겼다. admin 은 일반 권한 운영 계정 오차단을 막기 위해 차단 목록에서 제외했다. OPcache 는 상태 조회 경로 자체가 없어 성능 저하를 인지할 방법이 없었다. 설치 단계 경고(비차단)와 관리자 정보 탭 표시를 같은 판정 SSoT 로 추가했다. 확장 로드 여부만으로는 부족해 opcache.enable 지시자까지 확인하며, ini_get 이 차단된 환경은 "확인 불가"로 폴백해 경고도 차단도 하지 않는다.
162 lines
5.5 KiB
PHP
162 lines
5.5 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\Support;
|
|
|
|
use App\Support\OpcacheStatus;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
/**
|
|
* OPcache 상태 판정 SSoT 단위 테스트
|
|
*
|
|
* 인스톨러(요구사항 화면)와 코어(관리자 정보 탭)가 같은 답을 내야 하므로,
|
|
* 이 클래스는 Laravel 부트 없이 동작해야 한다. 본 테스트가
|
|
* `PHPUnit\Framework\TestCase` 를 직접 상속하는 것은 그 "의존성 0" 속성을
|
|
* 검증하기 위함이다.
|
|
*
|
|
* 검증 축:
|
|
* - 확장 미로드 → enabled=false (확인 불가 아님)
|
|
* - 확장 로드 + opcache.enable=1 → enabled=true
|
|
* - 확장 로드 + opcache.enable=0 → enabled=false (로드만으로 판정하면 안 되는 케이스)
|
|
* - 확장 로드 + ini_get 차단(disable_functions) → enabled=null (확인 불가)
|
|
*/
|
|
class OpcacheStatusTest extends TestCase
|
|
{
|
|
/**
|
|
* @scenario opcache_state=not_loaded, surface=judgement_ssot
|
|
*
|
|
* @effects not_loaded_reports_disabled_not_unknown
|
|
*/
|
|
public function test_extension_not_loaded_reports_disabled(): void
|
|
{
|
|
$result = OpcacheStatus::probe(
|
|
extensionLoaded: fn () => false,
|
|
iniGet: fn () => $this->fail('확장 미로드 시 ini_get 을 호출하면 안 됩니다.'),
|
|
);
|
|
|
|
$this->assertSame(['loaded' => false, 'enabled' => false], $result);
|
|
}
|
|
|
|
/**
|
|
* @scenario opcache_state=enabled, surface=judgement_ssot
|
|
*
|
|
* @effects on_off_directive_values_parsed
|
|
*/
|
|
public function test_loaded_and_enabled_reports_enabled(): void
|
|
{
|
|
$result = OpcacheStatus::probe(
|
|
extensionLoaded: fn (string $name) => $name === OpcacheStatus::EXTENSION_NAME,
|
|
iniGet: fn () => '1',
|
|
);
|
|
|
|
$this->assertSame(['loaded' => true, 'enabled' => true], $result);
|
|
}
|
|
|
|
/**
|
|
* 확장이 로드돼 있어도 opcache.enable=0 이면 실제로는 동작하지 않는다.
|
|
* 확장 로드 여부만으로 판정하는 회귀를 차단한다.
|
|
*
|
|
* @scenario opcache_state=loaded_but_disabled, surface=judgement_ssot
|
|
*
|
|
* @effects loaded_but_disabled_directive_reports_disabled
|
|
*/
|
|
public function test_loaded_but_disabled_directive_reports_disabled(): void
|
|
{
|
|
$result = OpcacheStatus::probe(
|
|
extensionLoaded: fn () => true,
|
|
iniGet: fn () => '0',
|
|
);
|
|
|
|
$this->assertSame(['loaded' => true, 'enabled' => false], $result);
|
|
}
|
|
|
|
/**
|
|
* ini_get 이 disable_functions 로 차단돼 Error 를 던지는 환경.
|
|
* 경고도 차단도 아닌 "확인 불가"(null) 로 폴백해야 한다.
|
|
*
|
|
* @scenario opcache_state=ini_get_blocked, surface=judgement_ssot
|
|
*
|
|
* @effects ini_get_blocked_reports_unknown_null
|
|
*/
|
|
public function test_ini_get_blocked_reports_unknown(): void
|
|
{
|
|
$result = OpcacheStatus::probe(
|
|
extensionLoaded: fn () => true,
|
|
iniGet: fn () => throw new \Error('Call to undefined function ini_get()'),
|
|
);
|
|
|
|
$this->assertSame(['loaded' => true, 'enabled' => null], $result);
|
|
}
|
|
|
|
/**
|
|
* ini_get 은 지시자를 못 읽으면 false 를 반환한다. 값 '0'(비활성 확정) 과
|
|
* 구분해 "확인 불가" 로 처리해야 한다.
|
|
*
|
|
* @effects ini_get_false_distinguished_from_value_zero
|
|
*/
|
|
public function test_ini_get_returning_false_reports_unknown(): void
|
|
{
|
|
$result = OpcacheStatus::probe(
|
|
extensionLoaded: fn () => true,
|
|
iniGet: fn () => false,
|
|
);
|
|
|
|
$this->assertSame(['loaded' => true, 'enabled' => null], $result);
|
|
}
|
|
|
|
public function test_ini_get_returning_empty_string_reports_unknown(): void
|
|
{
|
|
$result = OpcacheStatus::probe(
|
|
extensionLoaded: fn () => true,
|
|
iniGet: fn () => '',
|
|
);
|
|
|
|
$this->assertSame(['loaded' => true, 'enabled' => null], $result);
|
|
}
|
|
|
|
/**
|
|
* php.ini 는 On/Off 표기도 허용한다.
|
|
*/
|
|
public function test_on_off_directive_values_are_parsed(): void
|
|
{
|
|
$this->assertTrue(
|
|
OpcacheStatus::probe(fn () => true, fn () => 'On')['enabled']
|
|
);
|
|
$this->assertFalse(
|
|
OpcacheStatus::probe(fn () => true, fn () => 'Off')['enabled']
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 인수 없이 호출해도 예외 없이 계약된 구조를 반환해야 한다
|
|
* (실행 환경의 실제 OPcache 상태와 무관하게 형태만 검증).
|
|
*/
|
|
public function test_default_probe_returns_contracted_shape(): void
|
|
{
|
|
$result = OpcacheStatus::probe();
|
|
|
|
$this->assertArrayHasKey('loaded', $result);
|
|
$this->assertArrayHasKey('enabled', $result);
|
|
$this->assertIsBool($result['loaded']);
|
|
$this->assertTrue($result['enabled'] === null || is_bool($result['enabled']));
|
|
}
|
|
|
|
/**
|
|
* 인스톨러가 Laravel 오토로드 없이 `require_once` 로 로드할 수 있어야 하므로,
|
|
* 소스에 프레임워크 심볼 참조가 없어야 한다.
|
|
*/
|
|
public function test_source_has_no_framework_dependency(): void
|
|
{
|
|
$source = file_get_contents(dirname(__DIR__, 3).'/app/Support/OpcacheStatus.php');
|
|
|
|
$this->assertIsString($source);
|
|
|
|
foreach (['use Illuminate', 'Illuminate\\', 'config(', 'app(', '__(', 'trans('] as $forbidden) {
|
|
$this->assertStringNotContainsString(
|
|
$forbidden,
|
|
$source,
|
|
sprintf('인스톨러 공용 클래스에 프레임워크 의존("%s")이 포함됐습니다.', $forbidden)
|
|
);
|
|
}
|
|
}
|
|
}
|