test(core): 개발 환경 파일을 건드리는 테스트 격리
테스트가 개발자의 `.env` 를 삭제한 사고 이후 부트스트랩에 복원 안전망을 넣었지만, 그것은 프로세스 종료 시점 복원이라 같은 실행 안에서 뒤 테스트가 훼손된 파일을 읽는 구간이 남아 있었다. 복원이 아니라 애초에 건드리지 않는 구조로 바꾼다. 앱 루트를 임시 디렉토리로 돌리면 `base_path` 를 쓰는 제품 코드는 한 줄도 바뀌지 않은 채 격리된 파일만 다룬다. 공용 트레이트로 묶어 세 클래스가 같은 방식을 쓰게 했다. 격리 자체를 증명하기 위해 안전망을 임시로 끄고 돌렸다. 안전망 없이도 `.env` 가 바이트 단위로 그대로였다 — "green 이니 됐다" 로는 안전망이 뒤에서 복원해 준 것과 구분되지 않는다. 이 과정에서 격리로는 막을 수 없는 유형을 하나 더 찾았다. 별도 프로세스를 띄워 업그레이드 커맨드를 실행하는 테스트가 자식 쪽에서 `.env` 의 버전 값을 덮어쓰고 있었다. 부모의 앱 루트 전환도, 테스트 부트스트랩의 안전망도 자식에는 닿지 않는다. 테스트 환경 지정은 읽는 파일만 바꿀 뿐 쓰기 대상은 언제나 프로젝트 루트의 `.env` 다. 그 테스트가 검증하는 것은 자식이 방금 쓴 클래스를 읽어들이는지 뿐이므로, 커맨드가 이미 제공하는 부수효과 차단 옵션을 붙였다. 같은 구조의 문제가 업그레이드 스텝 임시 파일에도 있었다 — 실제 `upgrades/` 에 쓰고 정리를 tearDown 에 의존하므로 중단되면 개발 체크아웃에 정체불명의 스텝이 남는다. 실제로 두 번 발견해 지웠고, 부트스트랩 셧다운 훅이 그 패턴을 쓸어내도록 했다. 검증: 세 클래스 개별 green, 안전망을 끈 통합 실행 28건 green + `.env` 불변, 자식 프로세스 테스트 green, 전 구간 `.env` 해시 불변, audit 위반 0. 알려진 잔여 문제: 인스톨러와 업그레이드 스위트를 한 프로세스로 합쳐 돌리면 모듈 클래스 중복 선언 fatal 로 중단된다. 각각 따로 돌리면 297 / 301 건 모두 통과하며, 이번 변경 이전에도 같은 증상이었다. 별도 조사가 필요해 이번 범위에서 다루지 않았다.
This commit is contained in:
1 parent
072b3f50d9
commit
7e569efe4c
6 files changed
+184
-103
No files matched your search
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Concerns;
|
||||
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
/**
|
||||
* 인스톨러/업그레이드 테스트용 앱 루트 격리.
|
||||
*
|
||||
* ## 왜 필요한가
|
||||
*
|
||||
* 인스톨러·업그레이드 코드는 `base_path('.env')`, `base_path('storage/installer/runtime.php')`
|
||||
* 같은 실제 파일을 읽고 쓴다. 이를 테스트하려면 그 파일들이 있어야 하는데, 앱 루트를 그대로
|
||||
* 두면 **개발자의 실제 `.env` 를 삭제·재생성하게 된다.**
|
||||
*
|
||||
* 각 테스트가 setUp 에서 원본을 메모리에 담고 tearDown 에서 되돌리는 방식은 안전해 보이지만
|
||||
* 그렇지 않다 — 테스트가 fatal 로 죽으면 tearDown 이 실행되지 않아 `.env` 가 사라진 채 남는다.
|
||||
* `.env` 는 Git 추적 대상이 아니라 복구 수단이 없고, APP_KEY 를 잃으면 기존 암호화 데이터와
|
||||
* 세션이 전부 무효가 된다. 2026-07-21 에 실제로 그렇게 유실됐다.
|
||||
*
|
||||
* 그래서 "복원" 이 아니라 **애초에 건드리지 않는" 방식으로 바꾼다.** 앱 루트를 임시 디렉토리로
|
||||
* 돌리면 `base_path()` 를 쓰는 프로덕션 코드는 한 줄도 바뀌지 않은 채 격리된 파일만 다룬다.
|
||||
*
|
||||
* ## 사용
|
||||
*
|
||||
* ```php
|
||||
* protected function setUp(): void
|
||||
* {
|
||||
* parent::setUp();
|
||||
*
|
||||
* // 실제 파일이 필요하면 복사 대상으로 넘긴다 (.env.example 등)
|
||||
* $this->isolateInstallerBasePath(['.env.example']);
|
||||
*
|
||||
* // 프로젝트 실제 파일을 읽어야 할 때만 realBasePath() 사용
|
||||
* require_once $this->realBasePath('upgrades/data/7.0.4/migrations/01_Foo.php');
|
||||
* }
|
||||
*
|
||||
* protected function tearDown(): void
|
||||
* {
|
||||
* $this->releaseInstallerBasePath();
|
||||
* parent::tearDown();
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @see CoreUpdateServiceTest::withIsolatedBasePath 콜백 단위 격리 변형
|
||||
*/
|
||||
trait IsolatesInstallerBasePath
|
||||
{
|
||||
/** 격리 이전의 실제 앱 루트 */
|
||||
private ?string $realProjectBasePath = null;
|
||||
|
||||
/** 이번 테스트가 사용하는 임시 앱 루트 */
|
||||
private ?string $isolatedBasePath = null;
|
||||
|
||||
/**
|
||||
* 앱 루트를 임시 디렉토리로 전환합니다.
|
||||
*
|
||||
* 인스톨러 코드가 흔히 기대하는 `storage/`, `storage/app/` 을 미리 만들어 둔다.
|
||||
*
|
||||
* @param array<int, string> $copyFromProject 실제 프로젝트에서 복사해올 파일 (루트 기준 상대경로)
|
||||
* @return string 생성된 임시 앱 루트 절대경로
|
||||
*/
|
||||
protected function isolateInstallerBasePath(array $copyFromProject = []): string
|
||||
{
|
||||
$this->realProjectBasePath = $this->app->basePath();
|
||||
$this->isolatedBasePath = sys_get_temp_dir()
|
||||
.DIRECTORY_SEPARATOR.'g7-installer-isolated-'.bin2hex(random_bytes(6));
|
||||
|
||||
File::ensureDirectoryExists($this->isolatedBasePath.'/storage/app');
|
||||
|
||||
foreach ($copyFromProject as $relative) {
|
||||
$source = $this->realProjectBasePath.DIRECTORY_SEPARATOR.ltrim($relative, '/\\');
|
||||
if (is_file($source)) {
|
||||
$destination = $this->isolatedBasePath.DIRECTORY_SEPARATOR.ltrim($relative, '/\\');
|
||||
File::ensureDirectoryExists(dirname($destination));
|
||||
File::copy($source, $destination);
|
||||
}
|
||||
}
|
||||
|
||||
$this->app->setBasePath($this->isolatedBasePath);
|
||||
|
||||
return $this->isolatedBasePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* 앱 루트를 원래대로 되돌리고 임시 디렉토리를 제거합니다.
|
||||
*
|
||||
* tearDown 에서 호출한다. 호출되지 않아도 실제 프로젝트 파일은 애초에 건드리지 않았으므로
|
||||
* 남는 것은 임시 디렉토리뿐이다 — 이것이 복원 의존 방식과의 결정적 차이다.
|
||||
*/
|
||||
protected function releaseInstallerBasePath(): void
|
||||
{
|
||||
if ($this->realProjectBasePath !== null) {
|
||||
$this->app->setBasePath($this->realProjectBasePath);
|
||||
}
|
||||
|
||||
if ($this->isolatedBasePath !== null && is_dir($this->isolatedBasePath)) {
|
||||
File::deleteDirectory($this->isolatedBasePath);
|
||||
}
|
||||
|
||||
$this->realProjectBasePath = null;
|
||||
$this->isolatedBasePath = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 실제 프로젝트 루트 기준 경로를 반환합니다 (격리와 무관).
|
||||
*
|
||||
* 업그레이드 마이그레이션 파일처럼 **실제 소스**를 읽어야 할 때만 사용한다.
|
||||
*
|
||||
* @param string $relative 프로젝트 루트 기준 상대경로
|
||||
* @return string 절대경로
|
||||
*/
|
||||
protected function realBasePath(string $relative = ''): string
|
||||
{
|
||||
$root = $this->realProjectBasePath ?? $this->app->basePath();
|
||||
|
||||
return $relative === '' ? $root : $root.DIRECTORY_SEPARATOR.ltrim($relative, '/\\');
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace Tests\Feature\Upgrade;
|
||||
use App\Extension\UpgradeContext;
|
||||
use App\Upgrades\Data\V7_0_0_beta_7\Migrations\FinalizeOrphanedInstallerRuntime;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Tests\Concerns\IsolatesInstallerBasePath;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
@@ -23,6 +24,8 @@ use Tests\TestCase;
|
||||
*/
|
||||
class Beta7FinalizeOrphanedInstallerRuntimeTest extends TestCase
|
||||
{
|
||||
use IsolatesInstallerBasePath;
|
||||
|
||||
private string $runtimePath;
|
||||
|
||||
private string $envPath;
|
||||
@@ -31,16 +34,6 @@ class Beta7FinalizeOrphanedInstallerRuntimeTest extends TestCase
|
||||
|
||||
private string $stateJsonPath;
|
||||
|
||||
private ?string $originalEnvContent = null;
|
||||
|
||||
private bool $originalEnvExisted = false;
|
||||
|
||||
private bool $originalStateExisted = false;
|
||||
|
||||
private ?string $originalStateContent = null;
|
||||
|
||||
private bool $originalRuntimeDirExisted = false;
|
||||
|
||||
/**
|
||||
* @var array<string, string|null> axis 12 가 putenv / $_ENV / $_SERVER 를
|
||||
* 덮어쓰므로 후속 테스트 / 다음 테스트 파일의 DB 자격증명이 오염되지
|
||||
@@ -58,23 +51,17 @@ class Beta7FinalizeOrphanedInstallerRuntimeTest extends TestCase
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// 앱 루트를 임시 디렉토리로 돌린다. 본 테스트는 `.env`·runtime.php·state.json 을
|
||||
// 삭제/재생성/머지하므로, 실제 프로젝트 루트에서 돌면 개발자의 `.env` 를 파괴한다.
|
||||
// tearDown 복원에 기대지 않고 애초에 실제 파일을 건드리지 않는다.
|
||||
// axis 5 가 `.env.example` 기반 신규 생성을 검증하므로 그 파일만 복사해 온다.
|
||||
$this->isolateInstallerBasePath(['.env.example']);
|
||||
|
||||
$this->runtimePath = base_path('storage/installer/runtime.php');
|
||||
$this->envPath = base_path('.env');
|
||||
$this->envExamplePath = base_path('.env.example');
|
||||
$this->stateJsonPath = base_path('storage/installer-state.json');
|
||||
|
||||
// 실제 운영 .env 와 state.json 보존
|
||||
if (is_file($this->envPath)) {
|
||||
$this->originalEnvExisted = true;
|
||||
$this->originalEnvContent = file_get_contents($this->envPath);
|
||||
}
|
||||
if (is_file($this->stateJsonPath)) {
|
||||
$this->originalStateExisted = true;
|
||||
$this->originalStateContent = file_get_contents($this->stateJsonPath);
|
||||
}
|
||||
|
||||
$this->originalRuntimeDirExisted = is_dir(dirname($this->runtimePath));
|
||||
|
||||
// process ENV 원본 스냅샷 — axis 12 가 putenv() 로 자격증명을 갱신하므로
|
||||
// tearDown 에서 정확히 원본 상태로 복원 (다음 테스트 / 파일 오염 차단)
|
||||
foreach (self::ENV_KEYS_FOR_SNAPSHOT as $key) {
|
||||
@@ -86,8 +73,9 @@ class Beta7FinalizeOrphanedInstallerRuntimeTest extends TestCase
|
||||
// 이미 정의되어 있을 수 있다. 본 테스트는 그 상수를 신뢰하지 않고 명시 시나리오마다
|
||||
// 자체 시그널을 사용하므로 추가 정의 불필요.
|
||||
|
||||
// DataMigration 은 autoload 대상이 아님 — 동적 require
|
||||
require_once base_path('upgrades/data/7.0.0-beta.7/migrations/01_FinalizeOrphanedInstallerRuntime.php');
|
||||
// DataMigration 은 autoload 대상이 아님 — 동적 require.
|
||||
// 임시 루트에는 upgrades/ 가 없으므로 **실제** 프로젝트 경로에서 읽는다.
|
||||
require_once $this->realBasePath('upgrades/data/7.0.0-beta.7/migrations/01_FinalizeOrphanedInstallerRuntime.php');
|
||||
|
||||
$this->cleanupArtifacts();
|
||||
}
|
||||
@@ -96,25 +84,6 @@ class Beta7FinalizeOrphanedInstallerRuntimeTest extends TestCase
|
||||
{
|
||||
$this->cleanupArtifacts();
|
||||
|
||||
// .env 복원
|
||||
if ($this->originalEnvExisted && $this->originalEnvContent !== null) {
|
||||
file_put_contents($this->envPath, $this->originalEnvContent);
|
||||
} elseif (! $this->originalEnvExisted && is_file($this->envPath)) {
|
||||
@unlink($this->envPath);
|
||||
}
|
||||
|
||||
// state.json 복원
|
||||
if ($this->originalStateExisted && $this->originalStateContent !== null) {
|
||||
file_put_contents($this->stateJsonPath, $this->originalStateContent);
|
||||
} elseif (! $this->originalStateExisted && is_file($this->stateJsonPath)) {
|
||||
@unlink($this->stateJsonPath);
|
||||
}
|
||||
|
||||
// 본 테스트가 생성한 runtime 디렉토리 정리 (원래 없었을 때만)
|
||||
if (! $this->originalRuntimeDirExisted && is_dir(dirname($this->runtimePath))) {
|
||||
@rmdir(dirname($this->runtimePath));
|
||||
}
|
||||
|
||||
// process ENV 복원 — axis 12 가 putenv() 로 자격증명을 갱신했어도
|
||||
// 다음 테스트 / 파일이 정확히 setUp 직전 상태에서 시작하도록 보장.
|
||||
foreach ($this->originalEnvSnapshot as $key => $value) {
|
||||
@@ -128,6 +97,9 @@ class Beta7FinalizeOrphanedInstallerRuntimeTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
// 임시 앱 루트 해제 및 제거 (실제 프로젝트 파일은 처음부터 건드리지 않았다)
|
||||
$this->releaseInstallerBasePath();
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,12 @@ PHP;
|
||||
'--to=0.0.1',
|
||||
'--force',
|
||||
'--env=testing',
|
||||
// 본 테스트가 보는 것은 "spawn 된 자식이 방금 쓴 클래스를 로드하는가" 뿐이다.
|
||||
// 이 옵션이 없으면 자식이 `.env` 의 APP_VERSION 을 0.0.1 로 덮어쓴다 —
|
||||
// `--env=testing` 은 읽는 파일만 바꿀 뿐 쓰기 대상은 언제나 `base_path('.env')`
|
||||
// 이므로 개발자의 실제 `.env` 가 훼손된다. 자식 프로세스라 부모의 base path
|
||||
// 격리도, 테스트 부트스트랩의 복원 안전망도 닿지 않는다.
|
||||
'--skip-version-env',
|
||||
])).' 2>&1';
|
||||
|
||||
$descriptors = [
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Tests\Feature\Upgrade;
|
||||
|
||||
use App\Extension\UpgradeContext;
|
||||
use App\Upgrades\Data\V7_0_4\Migrations\RedactResidualInstallerStateSecrets;
|
||||
use Tests\Concerns\IsolatesInstallerBasePath;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
@@ -20,49 +21,31 @@ use Tests\TestCase;
|
||||
*/
|
||||
class RedactResidualInstallerStateSecretsTest extends TestCase
|
||||
{
|
||||
use IsolatesInstallerBasePath;
|
||||
|
||||
private string $statePath;
|
||||
|
||||
private string $envPath;
|
||||
|
||||
private string $installedFlagPath;
|
||||
|
||||
private bool $originalEnvExisted = false;
|
||||
|
||||
private ?string $originalEnvContent = null;
|
||||
|
||||
private bool $originalStateExisted = false;
|
||||
|
||||
private ?string $originalStateContent = null;
|
||||
|
||||
private bool $originalFlagExisted = false;
|
||||
|
||||
private ?string $originalFlagContent = null;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// DataMigration 은 autoload 대상이 아님 — 동적 require.
|
||||
// 격리 전에 **실제** 경로에서 읽는다 (임시 루트에는 upgrades/ 가 없다).
|
||||
require_once base_path('upgrades/data/7.0.4/migrations/01_RedactResidualInstallerStateSecrets.php');
|
||||
|
||||
// 앱 루트를 임시 디렉토리로 돌린다. 본 테스트는 `.env`·state.json·설치 플래그를
|
||||
// 삭제/변조하므로, 실제 프로젝트 루트에서 돌면 개발자의 `.env` 를 파괴한다.
|
||||
// tearDown 복원에 기대지 않고 애초에 실제 파일을 건드리지 않는다.
|
||||
$this->isolateInstallerBasePath();
|
||||
|
||||
$this->statePath = base_path('storage/installer-state.json');
|
||||
$this->envPath = base_path('.env');
|
||||
$this->installedFlagPath = base_path('storage/app/g7_installed');
|
||||
|
||||
// 실 운영 아티팩트 백업 — 본 테스트가 삭제/변조하므로 tearDown 에서 정확히 복원
|
||||
if (is_file($this->envPath)) {
|
||||
$this->originalEnvExisted = true;
|
||||
$this->originalEnvContent = file_get_contents($this->envPath);
|
||||
}
|
||||
if (is_file($this->statePath)) {
|
||||
$this->originalStateExisted = true;
|
||||
$this->originalStateContent = file_get_contents($this->statePath);
|
||||
}
|
||||
if (is_file($this->installedFlagPath)) {
|
||||
$this->originalFlagExisted = true;
|
||||
$this->originalFlagContent = file_get_contents($this->installedFlagPath);
|
||||
}
|
||||
|
||||
// DataMigration 은 autoload 대상이 아님 — 동적 require
|
||||
require_once base_path('upgrades/data/7.0.4/migrations/01_RedactResidualInstallerStateSecrets.php');
|
||||
|
||||
$this->clearArtifacts();
|
||||
}
|
||||
|
||||
@@ -70,15 +53,8 @@ class RedactResidualInstallerStateSecretsTest extends TestCase
|
||||
{
|
||||
$this->clearArtifacts();
|
||||
|
||||
if ($this->originalEnvExisted && $this->originalEnvContent !== null) {
|
||||
file_put_contents($this->envPath, $this->originalEnvContent);
|
||||
}
|
||||
if ($this->originalStateExisted && $this->originalStateContent !== null) {
|
||||
file_put_contents($this->statePath, $this->originalStateContent);
|
||||
}
|
||||
if ($this->originalFlagExisted && $this->originalFlagContent !== null) {
|
||||
file_put_contents($this->installedFlagPath, $this->originalFlagContent);
|
||||
}
|
||||
// 임시 앱 루트 해제 및 제거 (실제 프로젝트 파일은 처음부터 건드리지 않았다)
|
||||
$this->releaseInstallerBasePath();
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ namespace Tests\Unit\Providers;
|
||||
|
||||
use App\Providers\InstallerRuntimeServiceProvider;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Tests\Concerns\IsolatesInstallerBasePath;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
@@ -16,16 +17,14 @@ use Tests\TestCase;
|
||||
*/
|
||||
class InstallerRuntimeServiceProviderTest extends TestCase
|
||||
{
|
||||
use IsolatesInstallerBasePath;
|
||||
|
||||
private string $runtimePath;
|
||||
|
||||
private ?string $originalEnv = null;
|
||||
|
||||
private string $envPath;
|
||||
|
||||
private ?string $originalEnvContent = null;
|
||||
|
||||
private bool $originalEnvExisted = false;
|
||||
|
||||
/** @var array<string, string|null> */
|
||||
private array $originalEnvSnapshot = [];
|
||||
|
||||
@@ -39,20 +38,14 @@ class InstallerRuntimeServiceProviderTest extends TestCase
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// 앱 루트를 임시 디렉토리로 돌린다. 본 테스트는 `.env` 와 runtime.php 를 직접
|
||||
// 쓰고 지우므로, 실제 프로젝트 루트에서 돌면 개발자의 `.env` 를 파괴한다.
|
||||
// tearDown 복원에 기대지 않고 애초에 실제 파일을 건드리지 않는다.
|
||||
$this->isolateInstallerBasePath();
|
||||
|
||||
$this->runtimePath = base_path('storage/installer/runtime.php');
|
||||
$this->envPath = base_path('.env');
|
||||
|
||||
// 테스트 시작 시 runtime.php 정리 (이전 테스트의 잔재 제거)
|
||||
if (is_file($this->runtimePath)) {
|
||||
@unlink($this->runtimePath);
|
||||
}
|
||||
|
||||
// .env 원본 보존
|
||||
if (is_file($this->envPath)) {
|
||||
$this->originalEnvExisted = true;
|
||||
$this->originalEnvContent = file_get_contents($this->envPath);
|
||||
}
|
||||
|
||||
// process ENV 스냅샷 (stale ENV 보정 테스트가 갱신하므로 다음 테스트 격리)
|
||||
foreach (self::ENV_KEYS_FOR_SNAPSHOT as $key) {
|
||||
$value = getenv($key);
|
||||
@@ -77,13 +70,6 @@ class InstallerRuntimeServiceProviderTest extends TestCase
|
||||
$this->originalEnv = null;
|
||||
}
|
||||
|
||||
// .env 복원
|
||||
if ($this->originalEnvExisted && $this->originalEnvContent !== null) {
|
||||
file_put_contents($this->envPath, $this->originalEnvContent);
|
||||
} elseif (! $this->originalEnvExisted && is_file($this->envPath)) {
|
||||
@unlink($this->envPath);
|
||||
}
|
||||
|
||||
// process ENV 복원
|
||||
foreach ($this->originalEnvSnapshot as $key => $value) {
|
||||
if ($value === null) {
|
||||
@@ -96,6 +82,9 @@ class InstallerRuntimeServiceProviderTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
// 임시 앱 루트 해제 및 제거 (실제 프로젝트 파일은 처음부터 건드리지 않았다)
|
||||
$this->releaseInstallerBasePath();
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
|
||||
@@ -110,6 +110,25 @@ if ($realEnvSnapshot !== false && $realEnvSnapshot !== null) {
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| 임시 upgrade 스텝 파일 잔재 정리
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| 업그레이드 커맨드 테스트는 실제 `upgrades/` 에 임시 스텝 파일을 쓴다 — 그 디렉토리를
|
||||
| 스캔해야 스텝 발견 로직이 동작하기 때문이다. 각 테스트가 tearDown 에서 지우지만,
|
||||
| 스위트가 fatal 로 중단되면 개발 체크아웃에 정체불명의 upgrade 스텝이 남는다.
|
||||
|
|
||||
| 파일명에 `_test_` 가 들어가는 것은 이 임시 파일들뿐이므로(실제 스텝은 `Upgrade_7_0_4`
|
||||
| 처럼 버전만 갖는다) 그 패턴만 셧다운 시점에 쓸어낸다.
|
||||
|
|
||||
*/
|
||||
register_shutdown_function(static function (): void {
|
||||
foreach (glob(__DIR__.'/../upgrades/Upgrade_*_test_*.php') ?: [] as $leftover) {
|
||||
@unlink($leftover);
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Config 캐시 삭제 (테스트 환경 보장)
|
||||
|
||||
Reference in new issue
Block a user