Laravel PackageManifest 는 bootstrap/cache/packages.php 가 있으면 stale 여부를 검사하지 않고 그대로 읽어 등재된 provider 를 new 한다. 코어 업데이트는 Step 6/8 에서 vendor 를 --no-dev 로 교체하지만 그 파일은 Step 11 까지 이전 설치본의 것이 남으므로, 옵션 없는 `composer install` 로 깔린 사이트에서는 Step 10 spawn 자식이 새 vendor 에 없는 provider 를 찾다 부팅 단계에서 죽는다. 부팅 전이라 앱 로그에 흔적이 없고 부모에게는 자식의 비정상 종료로만 보여, 운영자에게는 「Class ... not found」 와 수동 재개 안내만 남는다. (sir.kr 커뮤니티 제보, 7.0.9 → 7.0.10) 3계층으로 막는다. 1. 부모 — spawn 직전 PackageManifestCacheHelper::clear 2. 자식 — bootstrap/app.php 가 G7_UPDATE_IN_PROGRESS 를 보고 스스로 정리한다. 이미 배포된 7.0.9·7.0.10 부모는 고칠 수 없으므로 그 아래에서 도는 신버전 자식의 유일한 방어다. App\ 클래스를 참조하지 않고 실패는 무시한다. 3. 범위 — CoreVersionChecker 의 env APP_VERSION 우선을 CoreUpdateContext 트리 안으로 축소한다. 업데이트 전에 뜬 artisan serve·큐 워커가 옛 값을 물고 확장을 incompatible_core 로 끄던 경로를 닫는다(관리자 템플릿이 대상이면 복구 UI 자체에 도달할 수 없다). 업데이트 트리 판정은 App\Support\CoreUpdateContext 가 단독 소유하고 CoreServiceProvider::isCoreUpdateInProgress 는 위임으로 남는다. bootstrap/app.php 의 복제본은 부팅 전이라 불가피한 예외이며, 두 조건의 동형성을 테스트가 단언한다. 실측 중 드러난 결함 2건을 함께 고쳤다. - ConfigCacheHelper::withPreservedContainer 가 파사드 애플리케이션을 되돌리지 않아 Step 11 이 `Target class [command.tinker] does not exist` 로 실패·롤백했다. - updateVersionInEnv 가 프로세스 환경을 갱신하지 않아 config 캐시에 이전 버전이 구워졌다 (Laravel env 저장소가 불변이라 재부팅으로도 덮이지 않는다). 인스톨러는 재사용 vendor 의 개발용 패키지를 installed.json 으로 감지해 설치 환경 확인 카드·설치 로그로 알리되 설치를 차단하지 않고( 결정 D1), 재사용 경로에서도 컴파일 캐시를 정리한다. 실행되는 명령만이 아니라 실패 시 안내하는 수동 명령까지 --no-dev 로 맞췄다. 코어 업데이트 완료·핸드오프·단독 재개 사후 단계에서 queue:restart 신호를 보낸다(D3). 코어 7.0.10 → 7.0.11.
146 lines
5.0 KiB
PHP
146 lines
5.0 KiB
PHP
<?php
|
|
|
|
namespace Tests\Unit\Support;
|
|
|
|
use App\Support\PackageManifestCacheHelper;
|
|
use Illuminate\Support\Facades\Artisan;
|
|
use Illuminate\Support\Facades\File;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use Tests\TestCase;
|
|
|
|
/**
|
|
* 패키지 매니페스트 캐시 헬퍼 단위 테스트 (dev-g7 #658).
|
|
*
|
|
* 이 헬퍼가 삭제 로직의 단일 지점이다 — spawn 직전 선정리(`CoreUpdateCommand`)와 업데이트
|
|
* 마무리(`CoreUpdateService::clearAllCaches()`)가 같은 코드를 쓴다.
|
|
*
|
|
* 격리: 실제 `bootstrap/cache/*` 를 건드리지 않도록 `APP_PACKAGES_CACHE`/`APP_SERVICES_CACHE`
|
|
* 를 base_path 상대 경로로 재지정한다(`Application::normalizeCachePath` 규칙).
|
|
*/
|
|
class PackageManifestCacheHelperTest extends TestCase
|
|
{
|
|
/** @var array<string, string|null> 테스트가 덮어쓴 env 의 원값 (3채널 복원용) */
|
|
private array $originalEnv = [];
|
|
|
|
private string $packagesPath;
|
|
|
|
private string $servicesPath;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
|
|
$uniq = uniqid();
|
|
$relPackages = "storage/framework/testing/manifest-helper-{$uniq}/packages.php";
|
|
$relServices = "storage/framework/testing/manifest-helper-{$uniq}/services.php";
|
|
|
|
$this->packagesPath = base_path($relPackages);
|
|
$this->servicesPath = base_path($relServices);
|
|
|
|
File::ensureDirectoryExists(dirname($this->packagesPath));
|
|
|
|
$this->setEnv('APP_PACKAGES_CACHE', $relPackages);
|
|
$this->setEnv('APP_SERVICES_CACHE', $relServices);
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
foreach ($this->originalEnv as $key => $value) {
|
|
if ($value === null) {
|
|
unset($_ENV[$key], $_SERVER[$key]);
|
|
putenv($key);
|
|
} else {
|
|
$_ENV[$key] = $value;
|
|
$_SERVER[$key] = $value;
|
|
putenv($key.'='.$value);
|
|
}
|
|
}
|
|
$this->originalEnv = [];
|
|
|
|
$dir = dirname($this->packagesPath);
|
|
if (File::isDirectory($dir)) {
|
|
File::deleteDirectory($dir);
|
|
}
|
|
|
|
parent::tearDown();
|
|
}
|
|
|
|
/**
|
|
* env 로 재지정한 경로의 두 파일을 지운다 (하드코딩된 bootstrap/cache 가 아니라).
|
|
*
|
|
* @effects PackageManifestCacheHelper_clear_unlinks_packages_and_services_at_configured_paths
|
|
*/
|
|
#[Test]
|
|
public function clear_는_설정된_경로의_packages_와_services_를_지운다(): void
|
|
{
|
|
File::put($this->packagesPath, "<?php return [];\n");
|
|
File::put($this->servicesPath, "<?php return [];\n");
|
|
|
|
$this->assertSame($this->packagesPath, $this->app->getCachedPackagesPath(), '전제: env 재지정이 경로를 정한다');
|
|
$this->assertSame($this->servicesPath, $this->app->getCachedServicesPath(), '전제: env 재지정이 경로를 정한다');
|
|
|
|
PackageManifestCacheHelper::clear();
|
|
|
|
$this->assertFileDoesNotExist($this->packagesPath);
|
|
$this->assertFileDoesNotExist($this->servicesPath);
|
|
}
|
|
|
|
/**
|
|
* 파일이 없어도 예외를 던지지 않는다 (부팅 직전에 불리므로 실패가 부팅을 막으면 안 된다).
|
|
*/
|
|
#[Test]
|
|
public function clear_는_파일이_없으면_아무_일도_하지_않는다(): void
|
|
{
|
|
$this->assertFileDoesNotExist($this->packagesPath, '전제: 파일이 없다');
|
|
|
|
PackageManifestCacheHelper::clear();
|
|
|
|
$this->assertFileDoesNotExist($this->packagesPath);
|
|
$this->assertFileDoesNotExist($this->servicesPath);
|
|
}
|
|
|
|
/**
|
|
* rebuild() 는 삭제 후 package:discover 로 다시 만든다.
|
|
*
|
|
* @effects clearAllCaches_rebuilds_package_manifest_via_helper
|
|
*/
|
|
#[Test]
|
|
public function rebuild_는_지운_뒤_package_discover_를_한_번_호출한다(): void
|
|
{
|
|
File::put($this->packagesPath, "<?php return [];\n");
|
|
File::put($this->servicesPath, "<?php return [];\n");
|
|
|
|
Artisan::shouldReceive('call')->once()->with('package:discover')->andReturn(0);
|
|
|
|
PackageManifestCacheHelper::rebuild();
|
|
|
|
$this->assertFileDoesNotExist($this->packagesPath, 'rebuild 는 먼저 지운다');
|
|
$this->assertFileDoesNotExist($this->servicesPath, 'rebuild 는 먼저 지운다');
|
|
}
|
|
|
|
/**
|
|
* env 를 세 채널($_ENV/$_SERVER/putenv)에 세우고 원값을 복원용으로 보관합니다.
|
|
*
|
|
* @param string $key 환경변수 이름
|
|
* @param string|null $value 세울 값. null 이면 제거
|
|
*/
|
|
private function setEnv(string $key, ?string $value): void
|
|
{
|
|
if (! array_key_exists($key, $this->originalEnv)) {
|
|
$current = $_ENV[$key] ?? $_SERVER[$key] ?? getenv($key);
|
|
$this->originalEnv[$key] = ($current === false || $current === null) ? null : (string) $current;
|
|
}
|
|
|
|
if ($value === null) {
|
|
unset($_ENV[$key], $_SERVER[$key]);
|
|
putenv($key);
|
|
|
|
return;
|
|
}
|
|
|
|
$_ENV[$key] = $value;
|
|
$_SERVER[$key] = $value;
|
|
putenv($key.'='.$value);
|
|
}
|
|
}
|