WIP 시험 · 앱이 %TEMP% 에 고정 이름을 남기지 않게 (관리자 지시 10/20): 시험의 앱 임시 폴더는 파일마다 jj_store_ (flutter_test_config) · 업데이트 시험은 시험 폴더 (GitHubUpdater.tempRoot, 기록은 스크립트 옆) · check.py 는 임시 폴더 · 앱이 켜질 때 지난 업데이트가 남긴 새 판 폴더 · .ps1 · .log 를 지움 (2분 지난 것) · 검사는 jj 로 시작하는 것 모두 실패 · 마그넷 멈춘 막대 "Stalled" 따로 열쇠 (28 지적) · explorer_test 82 부하 때 목록 기다리기 (검토 대기)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
review_d2dc267
RyuJongJin 2026-10-20 13:25:15 +09:00
parent 2f4ad6c146
commit e228e84adb
16 changed files with 126 additions and 29 deletions

View File

@ -1196,6 +1196,7 @@
"먼저 mDNS 로 묻고 (이름을 알리는 NAS), 이어서 이 기기가 연결된 망 (Wi-Fi · 유선) 의 기기들에 SMB (445) 연결만 해 보고 바로 닫습니다. 파일 · 계정 정보는 보내지 않습니다.": "First asks via mDNS (NAS devices that announce their names), then tries an SMB (445) connection to devices on the network this device is on (Wi-Fi · wired) and closes it right away. No files or account details are sent.",
"멈추고 옮기기": "Stop and move",
"멈춤": "Stopped",
"멈춤 (시작 안 됨)": "Stalled",
"멈춤 (이번 실행 동안)": "Pause (this session)",
"멈춤 (이번 실행 동안) · 시작 버튼을 누르면 다시 동기화합니다": "Paused (this session) · press the start button to sync again",
"멈춤 · {0}": "Stopped · {0}",
@ -2426,6 +2427,7 @@
"지금까지 찾은 {0}개 모두": "All {0} found so far",
"지금은 듀얼 앱 (복제한 앱) 입니다. Android 가 듀얼 앱에는 이 권한을 주지 않을 수 있습니다. 허용 화면에서 켤 수 없으면 배지 없는 원래 JJ_MKVMaker 아이콘으로 여세요.": "This is the Dual App (cloned app). Android may not grant this permission to dual apps. If you can't turn it on, open the original JJ_MKVMaker icon without the badge.",
"지난 실행이 정상적으로 끝나지 않았습니다 (마지막 확인 {0}:{1}). 작업 기록에 남겨 두었습니다.": "The last run did not end normally (last check {0}:{1}). It has been noted in the job log.",
"지난 업데이트가 남긴 임시 파일을 지움: {0}": "Removed temporary files left by the last update: {0}",
"지난 작업 기록": "Old job logs",
"지문 확인하고 연결": "Check fingerprint and connect",
"지우고 초기화": "Delete and reset",

View File

@ -1196,6 +1196,7 @@
"먼저 mDNS 로 묻고 (이름을 알리는 NAS), 이어서 이 기기가 연결된 망 (Wi-Fi · 유선) 의 기기들에 SMB (445) 연결만 해 보고 바로 닫습니다. 파일 · 계정 정보는 보내지 않습니다.": "まず mDNS で問い合わせ (名前を知らせる NAS)、続いてこの端末が接続しているネットワーク (Wi-Fi · 有線) の機器に SMB (445) で接続だけ試し、すぐに閉じます。ファイルやアカウント情報は送りません。",
"멈추고 옮기기": "止めて移す",
"멈춤": "停止",
"멈춤 (시작 안 됨)": "停滞 (開始されません)",
"멈춤 (이번 실행 동안)": "停止 (今回の実行中)",
"멈춤 (이번 실행 동안) · 시작 버튼을 누르면 다시 동기화합니다": "停止中 (今回の実行中) · 開始ボタンで再び同期します",
"멈춤 · {0}": "停止 · {0}",
@ -2426,6 +2427,7 @@
"지금까지 찾은 {0}개 모두": "これまでに見つかった {0} 件すべて",
"지금은 듀얼 앱 (복제한 앱) 입니다. Android 가 듀얼 앱에는 이 권한을 주지 않을 수 있습니다. 허용 화면에서 켤 수 없으면 배지 없는 원래 JJ_MKVMaker 아이콘으로 여세요.": "これはデュアルアプリ (複製したアプリ) です。Android はデュアルアプリにこの権限を与えない場合があります。許可画面でオンにできない場合は、バッジのない元の JJ_MKVMaker アイコンから開いてください。",
"지난 실행이 정상적으로 끝나지 않았습니다 (마지막 확인 {0}:{1}). 작업 기록에 남겨 두었습니다.": "前回の実行が正常に終了しませんでした(最終確認 {0}:{1})。作業記録に残しました。",
"지난 업데이트가 남긴 임시 파일을 지움: {0}": "前回のアップデートが残した一時ファイルを削除: {0}",
"지난 작업 기록": "過去の作業記録",
"지문 확인하고 연결": "フィンガープリントを確認して接続",
"지우고 초기화": "削除して初期化",

View File

@ -1196,6 +1196,7 @@
"먼저 mDNS 로 묻고 (이름을 알리는 NAS), 이어서 이 기기가 연결된 망 (Wi-Fi · 유선) 의 기기들에 SMB (445) 연결만 해 보고 바로 닫습니다. 파일 · 계정 정보는 보내지 않습니다.": "先通过 mDNS 询问 (会公布名称的 NAS),再仅尝试与本设备所在网络 (Wi-Fi · 有线) 中的设备建立 SMB (445) 连接并立即关闭。不会发送文件或账户信息。",
"멈추고 옮기기": "停止并移动",
"멈춤": "已停止",
"멈춤 (시작 안 됨)": "停滞 (未开始)",
"멈춤 (이번 실행 동안)": "暂停 (本次运行期间)",
"멈춤 (이번 실행 동안) · 시작 버튼을 누르면 다시 동기화합니다": "已暂停 (本次运行期间) · 按开始按钮重新同步",
"멈춤 · {0}": "已停止 · {0}",
@ -2426,6 +2427,7 @@
"지금까지 찾은 {0}개 모두": "目前找到的全部 {0} 个",
"지금은 듀얼 앱 (복제한 앱) 입니다. Android 가 듀얼 앱에는 이 권한을 주지 않을 수 있습니다. 허용 화면에서 켤 수 없으면 배지 없는 원래 JJ_MKVMaker 아이콘으로 여세요.": "当前是双开应用 (复制的应用)。Android 可能不会给双开应用此权限。如果无法开启,请从没有徽标的原 JJ_MKVMaker 图标打开。",
"지난 실행이 정상적으로 끝나지 않았습니다 (마지막 확인 {0}:{1}). 작업 기록에 남겨 두었습니다.": "上次运行未正常结束(最后检查 {0}:{1})。已记录在任务记录中。",
"지난 업데이트가 남긴 임시 파일을 지움: {0}": "已删除上次更新留下的临时文件: {0}",
"지난 작업 기록": "旧任务记录",
"지문 확인하고 연결": "确认指纹并连接",
"지우고 초기화": "删除并重置",

View File

@ -80,7 +80,7 @@ void main() {
expect(t.takeException(), isNull, reason: '넘침');
expect(find.textContaining('[시험] 아주 긴 이름'), findsOneWidget);
expect(find.textContaining('Ubuntu 24.04.1 Desktop amd64'), findsOneWidget);
expect(find.text(tr('멈춤')), findsOneWidget);
expect(find.text(tr('멈춤 (시작 안 됨)')), findsOneWidget);
final b = shotKey.currentContext!.findRenderObject()! as RenderRepaintBoundary;
final img = await t.runAsync(() => b.toImage());
final bytes = await t.runAsync(() => img!.toByteData(format: ui.ImageByteFormat.png));

View File

@ -31,6 +31,7 @@ import '../services/model_store.dart';
import '../services/platform_services.dart';
import '../services/system_usage.dart';
import 'settings.dart';
import 'cleanup.dart' show removeUpdateLeftovers;
import '../l10n/tr.dart';
extension<T> on Set<T> {
@ -205,6 +206,10 @@ class AppController extends ChangeNotifier {
);
_applySettings();
}
if (Platform.isWindows) {
final gone = removeUpdateLeftovers(Directory.systemTemp.path);
if (gone.isNotEmpty) _log(trf('지난 업데이트가 남긴 임시 파일을 지움: {0}', [gone.join(', ')]));
}
ffmpegVersion = await _tool.version();
if (ffmpegVersion == null) {
_log(tr('⚠ FFmpeg 를 찾을 수 없습니다. MKV 만들기를 사용할 수 없습니다.'));

View File

@ -7,6 +7,29 @@ import '../l10n/tr.dart';
import 'app_controller.dart';
import 'download_manager.dart';
/// 관리자 지시 (10/20): Windows 업데이트가 %TEMP% 에 남긴 것 - 풀어 둔 새 판 (jj_mkvmaker_update_<판>, 수백 MB) ·
/// 교체 스크립트 (jj_mkvmaker_update.ps1) · 기록 (jj_mkvmaker_update.log). 예전에는 [정리] 를 누르기 전까지 남았다.
/// 앱이 켜질 때 부른다. 교체 스크립트가 아직 끝나는 중일 수 있어 [minAge] 보다 오래된 것만 지운다. 지운 이름을 돌려준다
List<String> removeUpdateLeftovers(String sysTmp, {Duration minAge = const Duration(minutes: 2), DateTime? now}) {
final mine = RegExp(r'^jj_mkvmaker_update(_.+|\.ps1|\.log)$');
final before = (now ?? DateTime.now()).subtract(minAge);
final removed = <String>[];
try {
for (final e in Directory(sysTmp).listSync(followLinks: false)) {
final name = p.basename(e.path);
if (!mine.hasMatch(name)) continue;
try {
if (!e.statSync().modified.isBefore(before)) continue;
e.deleteSync(recursive: true);
removed.add(name);
} catch (_) {
// 잡혀 있으면 (기록을 메모장으로 열어 둔 것 등) 다음에
}
}
} catch (_) {}
return removed;
}
/// 정리할 파일 · 폴더 하나
class CleanupItem {
final String path;

View File

@ -9,6 +9,10 @@ import '../../services/storage_service.dart';
import '../../l10n/tr.dart';
class DesktopStorageService implements StorageService {
/// [tempDirectory] 의 기준 폴더 (null = 시스템 임시 폴더). 시험이 파일마다 따로 쓰고 끝에 지우게 한다
/// (test/flutter_test_config.dart - 예전에는 시험이 %TEMP%\jj_mkvmaker 를 남겼다, 관리자 지시 10/20)
static String? tempRoot;
@override
Future<List<String>> pickVideos() async {
final files = await FilePicker.pickFiles(
@ -108,7 +112,7 @@ class DesktopStorageService implements StorageService {
@override
Future<String> tempDirectory() async {
final dir = Directory(p.join(Directory.systemTemp.path, 'jj_mkvmaker'));
final dir = Directory(p.join(tempRoot ?? Directory.systemTemp.path, 'jj_mkvmaker'));
await dir.create(recursive: true);
return dir.path;
}

View File

@ -23,12 +23,19 @@ class GitHubUpdater implements Updater {
..userAgent = 'JJ_MKVMaker-updater'
..connectionTimeout = const Duration(seconds: 20);
/// 받은 업데이트 · 교체 스크립트 · 기록을 둘 곳 (null = 시스템 임시 폴더). 시험은 시험 폴더를 준다
/// (예전에는 시험이 %TEMP% 에 jj_mkvmaker_update_1.9.0 · .ps1 · .log 를 남겼다, 관리자 지시 10/20)
final String? tempRoot;
GitHubUpdater({
this.apiBase = 'https://api.github.com',
String? appDir,
this.versionOverride,
this.tempRoot,
}) : appDir = appDir ?? AppPaths.root; // Lib 구조면 배포 폴더 맨 위
String get _temp => tempRoot ?? Directory.systemTemp.path;
@override
bool get installsInPlace => false;
@ -81,7 +88,7 @@ class GitHubUpdater implements Updater {
Future<String> download(ReleaseInfo r, void Function(double progress) onProgress) async {
final url = r.zipUrl;
if (url == null) throw UpdateException(tr('이 버전에는 Windows 용 zip 이 없습니다.'));
final work = Directory(p.join(Directory.systemTemp.path, 'jj_mkvmaker_update_${r.version}'));
final work = Directory(p.join(_temp, 'jj_mkvmaker_update_${r.version}'));
if (await work.exists()) await work.delete(recursive: true);
await work.create(recursive: true);
final zip = File(p.join(work.path, r.zipName ?? 'update.zip'));
@ -134,7 +141,7 @@ param([int]$ParentPid, [string]$Source, [string]$Target, [string]$Exe, [switch]$
$ErrorActionPreference = 'Continue'
try { Wait-Process -Id $ParentPid -Timeout 90 -ErrorAction Stop } catch {}
Start-Sleep -Milliseconds 800
$log = Join-Path $env:TEMP 'jj_mkvmaker_update.log'
$log = Join-Path (Split-Path -Parent $PSCommandPath) 'jj_mkvmaker_update.log'
# keep downloads, models and user data
$lib = Join-Path $Source 'Lib'
if (Test-Path $lib) {
@ -154,7 +161,7 @@ exit 0
/// 스크립트 실행. [waitPid] 가 끝나기를 기다렸다가 교체한다.
Future<Process> runInstallScript(String source, {required int waitPid, bool restart = true}) async {
final ps1 = File(p.join(Directory.systemTemp.path, 'jj_mkvmaker_update.ps1'));
final ps1 = File(p.join(_temp, 'jj_mkvmaker_update.ps1'));
await ps1.writeAsString(script);
return Process.start(
'powershell.exe',

View File

@ -419,7 +419,8 @@ class DownloadProgressBar extends StatelessWidget {
: t.state == DownloadState.queued || t.extra['waiting'] == true
? tr('대기 중')
: stalled
? tr('멈춤')
// 관리자 지시 (10/20, 28 지적): 영어 "Stopped" 는 일시 정지 · 취소로 읽혀 따로 열쇠 ("Stalled")
? tr('멈춤 (시작 안 됨)')
: tr('준비 중…'))
: '${(value * 100).toStringAsFixed(1)}%${size.isEmpty ? '' : ' · $size'}';
return ClipRRect(

View File

@ -143,4 +143,30 @@ void main() {
expect(Cleaner.sizeText(5 * 1024 * 1024), '5.0 MB');
expect(Cleaner.sizeText(3 * 1024 * 1024 * 1024), '3.0 GB');
});
test('관리자 지시 (10/20): 앱이 켜질 때 지난 업데이트가 남긴 것 (풀어 둔 새 판 · .ps1 · .log) 을 지운다 - 막 생긴 것 · 다른 것은 남김', () {
final tmp = Directory.systemTemp.createTempSync('jj_upd_left_');
addTearDown(() => tmp.deleteSync(recursive: true));
final old = DateTime.now().subtract(const Duration(hours: 1));
void make(String rel, {bool fresh = false}) {
final f = File(p.join(tmp.path, rel))..createSync(recursive: true);
if (!fresh) f.setLastModifiedSync(old);
}
make(p.join('jj_mkvmaker_update_2026.10.18_979', 'extract', 'jj_mkvmaker.exe'));
make('jj_mkvmaker_update.ps1');
make('jj_mkvmaker_update.log', fresh: true); // 교체 스크립트가 막 쓴 것
make('jj_mkvmaker_updater.txt');
make(p.join('jj_mkvmaker', 'a.wav'));
// 폴더는 안에 파일을 만들어 바뀐 때가 지금이다 - 5분 뒤로 보고 지우는지 본다
final dir = Directory(p.join(tmp.path, 'jj_mkvmaker_update_2026.10.18_979'));
final gone = removeUpdateLeftovers(tmp.path, now: DateTime.now().add(const Duration(minutes: 5)));
expect(gone..sort(), ['jj_mkvmaker_update.log', 'jj_mkvmaker_update.ps1', 'jj_mkvmaker_update_2026.10.18_979'],
reason: '5분 뒤로 보면 막 생긴 기록도 2분이 지남');
expect(dir.existsSync(), isFalse);
expect(File(p.join(tmp.path, 'jj_mkvmaker_updater.txt')).existsSync(), isTrue);
expect(Directory(p.join(tmp.path, 'jj_mkvmaker')).existsSync(), isTrue);
make('jj_mkvmaker_update.log', fresh: true);
expect(removeUpdateLeftovers(tmp.path), isEmpty, reason: '2분이 안 된 것은 남김 (스크립트가 끝나는 중일 수 있음)');
});
}

View File

@ -235,7 +235,7 @@ void main() {
d.selectNone();
await tester.pump();
expect(find.textContaining('트래커 · 피어에 닿지 않습니다'), findsOneWidget);
expect(find.text('멈춤'), findsOneWidget);
expect(find.text('멈춤 (시작 안 됨)'), findsOneWidget);
expect(find.text('준비 중…'), findsNothing);
expect(bar().value, 0, reason: '움직이지 않는 막대');
expect(bar().color, Colors.orangeAccent.withValues(alpha: 0.85));

View File

@ -1429,6 +1429,8 @@ void main() {
..explorerDropAction = 'move'
..copyConflict = 'overwrite';
await open(tester);
// 전체 시험 중 (부하) 에는 목록을 늦게 읽어 e.txt 를 못 찾았다 - 두 파일이 보일 때까지
await settle(tester, () => find.text('e.txt').evaluate().isNotEmpty && find.text('doc.txt').evaluate().isNotEmpty);
await act(tester, () => tester.tap(find.text('선택').first));
await act(tester, () => tester.tap(markOf('doc.txt')));
await act(tester, () => tester.tap(markOf('e.txt')));

View File

@ -0,0 +1,19 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:jj_mkvmaker/platform/windows/desktop_storage_service.dart';
/// 모든 단위 시험 파일 앞에 돈다 (flutter_test 의 flutter_test_config).
/// 관리자 지시 (10/20): 앱 임시 폴더 ([DesktopStorageService.tempDirectory] = %TEMP%\jj_mkvmaker) 를 시험이 남겼다.
/// 시험 파일들은 나란히 돌아 같은 폴더를 끝에 지우면 서로 부딪치므로, 파일마다 따로 jj_store_… 를 주고 그 파일이 끝나면 지운다
Future<void> testExecutable(FutureOr<void> Function() testMain) async {
final root = Directory.systemTemp.createTempSync('jj_store_');
DesktopStorageService.tempRoot = root.path;
tearDownAll(() {
try {
root.deleteSync(recursive: true);
} catch (_) {}
});
await testMain();
}

View File

@ -150,7 +150,7 @@ void main() {
});
test('최신 확인 → 받기 · SHA256 확인 · 압축 풀기', () async {
final up = GitHubUpdater(apiBase: 'http://127.0.0.1:${server.port}', appDir: tmp.path, versionOverride: '1.0.1');
final up = GitHubUpdater(apiBase: 'http://127.0.0.1:${server.port}', appDir: tmp.path, versionOverride: '1.0.1', tempRoot: tmp.path);
final r = (await up.latest())!;
expect(r.isNewerThan(await up.currentVersion()), isTrue);
final progress = <double>[];
@ -160,7 +160,7 @@ void main() {
});
test('SHA256 이 다르면 설치 거부', () async {
final up = GitHubUpdater(apiBase: 'http://127.0.0.1:${server.port}', appDir: tmp.path);
final up = GitHubUpdater(apiBase: 'http://127.0.0.1:${server.port}', appDir: tmp.path, tempRoot: tmp.path);
final good = (await up.latest())!;
final bad = ReleaseInfo(
version: good.version, tag: good.tag, name: '', notes: '', pageUrl: '',
@ -178,7 +178,7 @@ void main() {
Directory(p.join(app.path, 'models')).createSync();
File(p.join(app.path, 'models', 'm.bin')).writeAsStringSync('model');
final up = GitHubUpdater(apiBase: 'http://127.0.0.1:${server.port}', appDir: app.path);
final up = GitHubUpdater(apiBase: 'http://127.0.0.1:${server.port}', appDir: app.path, tempRoot: tmp.path);
final dir = await up.download((await up.latest())!, (_) {});
// 이미 끝난 프로세스를 "앱" 으로 두고 실행 (바로 교체 진행)
final dummy = await Process.start('cmd', ['/c', 'exit']);
@ -192,6 +192,9 @@ void main() {
expect(File(p.join(app.path, 'old_only.txt')).readAsStringSync(), 'keep me');
expect(File(p.join(app.path, 'jj_yt-dlp', 'video.mp4')).readAsStringSync(), 'my download');
expect(File(p.join(app.path, 'models', 'm.bin')).readAsStringSync(), 'model');
// 기록 · 스크립트는 스크립트 옆 (실제로는 %TEMP%, 시험은 시험 폴더 - 시험이 %TEMP% 에 남기지 않음)
expect(File(p.join(tmp.path, 'jj_mkvmaker_update.log')).existsSync(), isTrue);
expect(File(p.join(tmp.path, 'jj_mkvmaker_update.ps1')).existsSync(), isTrue);
}, timeout: const Timeout(Duration(minutes: 2)));
});
}

View File

@ -8,15 +8,22 @@
"""
import json
import os
import shutil
import subprocess
import sys
import tempfile
here = os.path.dirname(os.path.abspath(__file__))
out = os.path.join(tempfile.gettempdir(), 'jj_l10n_keys.json')
out2 = os.path.join(tempfile.gettempdir(), 'jj_l10n_unwrapped.json')
subprocess.run([sys.executable, os.path.join(here, 'extract_keys.py'), out, out2], check=True)
keys = json.load(open(out, encoding='utf-8'))
# 관리자 지시 (10/20): 예전에는 %TEMP% 에 jj_l10n_keys.json · jj_l10n_unwrapped.json 을 남겼다 - 임시 폴더를 만들고 끝에 지운다
work = tempfile.mkdtemp(prefix='jj_l10n_check_')
try:
out = os.path.join(work, 'keys.json')
out2 = os.path.join(work, 'unwrapped.json')
subprocess.run([sys.executable, os.path.join(here, 'extract_keys.py'), out, out2], check=True)
keys = json.load(open(out, encoding='utf-8'))
unwrapped = json.load(open(out2, encoding='utf-8'))
finally:
shutil.rmtree(work, ignore_errors=True)
missing = 0
for lang in ['en', 'ja', 'zh-Hans']:
d = json.load(open(os.path.join('assets', 'l10n', lang + '.json'), encoding='utf-8'))
@ -26,7 +33,7 @@ for lang in ['en', 'ja', 'zh-Hans']:
print('%s 사전에 없음: %r' % (lang, k))
# 60: tr 로 감싸지 않은 한글 글. 사전에 있으면 tr(변수) 로 번역되는 것으로 본다 - 없으면 번역이 빠진 것
en = json.load(open(os.path.join('assets', 'l10n', 'en.json'), encoding='utf-8'))
loose = [(p, n, t) for p, n, t in json.load(open(out2, encoding='utf-8')) if t not in en]
loose = [(p, n, t) for p, n, t in unwrapped if t not in en]
for p, n, t in loose:
print('번역 안 된 글 (tr 로 감싸거나 사전에 넣기, 자료면 // l10n-skip): %s:%d %r' % (p, n, t[:80]))
missing += len(loose)

View File

@ -5,8 +5,8 @@
# - 앱 코드 (lib) 가 시험 중에 만든 임시 폴더 (jj_up_ · jj_7z_ 등) 도 같은 곳에 생기므로 함께 잡힌다.
# - 표는 환경 변수 JJ_TEST_TMP_TAG 로도 시험에 넘긴다: 시험 도우미 (test/support/temp_dirs.dart · integration_test/support/temp_dir.dart)
# 가 임시 폴더가 그 안에 생기는지 확인한다 (통합 시험 -d windows 에서 앱까지 환경 변수가 가는지).
# - 끝나면 남은 것을 보이고 실행 폴더를 통째로 지운다. 실패 (exit 2) 는 시험 소스가 만드는 이름 (createTemp('jj_x_') 등) 의 폴더가 남았을 때만
# (앱 코드의 고정 이름 jj_mkvmaker · 파일 · 앱이 끝날 때까지 잡힌 jj_held_ 는 참고로만).
# - 끝나면 남은 것을 보이고 실행 폴더를 통째로 지운다. jj 로 시작하는 것 (폴더 · 파일, 앱 고정 이름 포함) 이 남으면 실패 (exit 2)
# (앱이 끝날 때까지 잡힌 jj_held_ 와 jj 가 아닌 것은 참고로만).
# 사용 (app 폴더에서): powershell -ExecutionPolicy Bypass -File tool\test_temp_check.ps1 [flutter test 인자…]
# 통합 시험 · 캡처도 (관리자 지시 10/19, 28 이 찾음): … test_temp_check.ps1 integration_test\windows_b6_shot_test.dart -d windows
$ErrorActionPreference = 'Stop'
@ -38,19 +38,13 @@ Start-Sleep -Seconds 3
$items = @(Get-ChildItem -LiteralPath $run -Force -ErrorAction SilentlyContinue)
# jj_held_ 는 앱이 끝날 때까지 놓지 않는 폴더 (integration_test/support/temp_dir.dart 의 heldByApp - Edge 사용자 데이터) - 보이기만 하고 아래에서 함께 지운다
$held = @($items | Where-Object { $_.Name -like 'jj_held_*' } | ForEach-Object { $_.Name })
# 실패로 세는 것: 시험 소스가 만드는 이름 (createTemp('jj_x_') · testTempDir('jj_x_') · tempDirUntilEnd('jj_x_')) 의 폴더 (예전 검사와 같은 기준)
$prefixes = @(@('test', 'integration_test') | ForEach-Object { Join-Path $app $_ } | Where-Object { Test-Path $_ } |
ForEach-Object { Get-ChildItem $_ -Recurse -Filter *.dart } |
ForEach-Object { [regex]::Matches((Get-Content $_.FullName -Raw -Encoding utf8), '(?:createTemp(?:Sync)?|testTempDir|tempDirUntilEnd)\(''(jj[^''$]*)''') } |
ForEach-Object { $_.Groups[1].Value } | Sort-Object -Unique)
$left = @($items | Where-Object { $_.PSIsContainer -and $_.Name -notlike 'jj_held_*' } | Where-Object {
$n = $_.Name
@($prefixes | Where-Object { $n.StartsWith($_) }).Count -gt 0
} | ForEach-Object { $_.Name })
# 참고: 그 밖의 것 (flutter 도구 · 앱 코드의 고정 이름 jj_mkvmaker 등 · 파일 - 실패로 세지 않고 보이기만, 실행 폴더와 함께 지움)
$other = @($items | Where-Object { $_.Name -notlike 'jj_held_*' -and $left -notcontains $_.Name } | ForEach-Object { $_.Name })
# 실패로 세는 것: jj 로 시작하는 것 모두 (폴더 · 파일 - 시험 소스 이름 · 앱 코드의 고정 이름 jj_mkvmaker · jj_mkvmaker_update_* 등).
# 관리자 판단 (10/20): 앱 고정 이름도 사용자 PC 의 실제 %TEMP% 에 쌓이던 것이라 새면 잡는다
$left = @($items | Where-Object { $_.Name -like 'jj*' -and $_.Name -notlike 'jj_held_*' } | ForEach-Object { $_.Name })
# 참고: jj 가 아닌 것 (flutter 도구 · MSBuild 등 - 실패로 세지 않고 보이기만, 실행 폴더와 함께 지움)
$other = @($items | Where-Object { $_.Name -notlike 'jj*' } | ForEach-Object { $_.Name })
if ($other.Count -gt 0) {
Write-Host ("참고 - 시험 이름이 아닌 남은 것 {0}개 (실행 폴더와 함께 지움):" -f $other.Count)
Write-Host ("참고 - jj 가 아닌 남은 것 {0}개 (실행 폴더와 함께 지움):" -f $other.Count)
$other | Sort-Object | ForEach-Object { Write-Host " $_" }
}
if ($held.Count -gt 0) {