vault_page_test 흔들림 고침 (기대값 그대로): 기다림을 실제 시간으로 · 보관함 쓰기가 다 끝날 때까지

- 원인: until 이 횟수 (200번) 로 끊겨 부하 때 일이 덜 끝난 채 넘어갔고, 끝맺음의 '.tmp 없음' 한 번 확인이 목록 저장과
  빈 폴더 목록 저장 사이에 맞아 folders.jjd.tmp 쓰기가 연 채 남아 임시 폴더를 지우지 못했다 (errno 32, 6/18 재현)
- until: 최대 60초 실제 시간, 안 되면 그 자리에서 실패 (조용히 넘어가지 않음). vaultQuiet: .tmp 없음이 10번 잇달아일 때
- 확인: 6개 동시 × 3회 18/18, 혼자 10회 연속 통과

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
modular
RyuJongJin 2026-10-23 01:36:54 +09:00
parent 8eaa83b69d
commit aea1ec5eba
1 changed files with 20 additions and 5 deletions

View File

@ -52,8 +52,12 @@ void main() {
return c;
}
Future<void> until(WidgetTester t, bool Function() ok, {int rounds = 200}) async {
for (var i = 0; i < rounds && !ok(); i++) {
/// 실제 시간으로 기다린다 (암호화 · 파일 쓰기는 실제 IO - 시험을 여럿 함께 돌려 느릴 때 횟수로 끊으면 일이 덜 끝난 채 넘어가,
/// 시험이 끝난 뒤 남은 쓰기가 파일을 연 채 멈춰 임시 폴더를 지우지 못했다 - errno 32). 끝내 안 되면 그 자리에서 실패
Future<void> until(WidgetTester t, bool Function() ok, {Duration limit = const Duration(seconds: 60)}) async {
final w = Stopwatch()..start();
while (!ok()) {
if (w.elapsed > limit) fail('기다린 일이 ${limit.inSeconds}초 안에 끝나지 않음');
await t.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 20)));
await t.pump(const Duration(milliseconds: 20));
}
@ -63,6 +67,17 @@ void main() {
}
}
/// 보관함의 쓰기가 다 끝날 때까지: 쓰는 중인 파일 (.tmp) 이 없는 것이 10번 잇달아일 때 (사이마다 pump).
/// 한 번만 보면 목록 저장 → 빈 폴더 목록 저장 사이에 맞아, 남은 쓰기가 파일을 연 채 시험이 끝났다 (errno 32)
Future<void> vaultQuiet(WidgetTester t) async {
var calm = 0;
await until(t, () {
final busy = Directory(p.join(tmp.path, 'vault')).listSync().any((f) => f.path.endsWith('.tmp'));
calm = busy ? 0 : calm + 1;
return calm >= 10;
});
}
/// 진행 막대 · 글자 칸 깜빡임이 있으면 pumpAndSettle 이 끝나지 않는다 - 정한 만큼만
Future<void> settle(WidgetTester t) async {
for (var i = 0; i < 12; i++) {
@ -141,7 +156,7 @@ void main() {
await until(t, () => find.text('보관함이 비어 있습니다').evaluate().isNotEmpty);
expect(find.text('보관함이 비어 있습니다'), findsOneWidget);
// 목록 쓰기 (index.jjv.tmp → index.jjv) 가 끝날 때까지 (화면은 먼저 바뀔 수 있다)
await until(t, () => !Directory(p.join(tmp.path, 'vault')).listSync().any((f) => f.path.endsWith('.tmp')));
await vaultQuiet(t);
await t.pumpWidget(const SizedBox());
});
@ -202,7 +217,7 @@ void main() {
await until(t, () => find.text('보관함이 비어 있습니다').evaluate().isNotEmpty);
expect(find.text('보관함이 비어 있습니다'), findsOneWidget);
// 목록 쓰기 (index.jjv.tmp → index.jjv) 가 끝날 때까지 (화면은 먼저 바뀔 수 있다)
await until(t, () => !Directory(p.join(tmp.path, 'vault')).listSync().any((f) => f.path.endsWith('.tmp')));
await vaultQuiet(t);
await t.pumpWidget(const SizedBox());
});
testWidgets('파일 탐색기: [보관함에 넣기] 는 보관함을 켰을 때 · 파일과 폴더 (2단계)', (t) async {
@ -414,7 +429,7 @@ void main() {
await t.tap(find.widgetWithText(FilledButton, '지우기'));
await until(t, () => find.text('album').evaluate().isEmpty);
expect(find.text('remote.txt'), findsOneWidget);
await until(t, () => !Directory(p.join(tmp.path, 'vault')).listSync().any((f) => f.path.endsWith('.tmp')));
await vaultQuiet(t);
await t.pumpWidget(const SizedBox());
});
}