JJ_MKVMaker/integration_test/windows_mkvend_shot_test.dart

138 lines
5.7 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:jj_mkvmaker/app/app_controller.dart';
import 'package:jj_mkvmaker/app/i18n_controller.dart';
import 'package:jj_mkvmaker/core/models.dart';
import 'package:jj_mkvmaker/l10n/tr.dart';
import 'package:jj_mkvmaker/platform/windows/desktop_storage_service.dart';
import 'package:jj_mkvmaker/services/media_tool.dart';
import 'package:jj_mkvmaker/services/platform_services.dart';
import 'package:jj_mkvmaker/ui/home_page.dart';
import 'package:jj_mkvmaker/ui/theme.dart';
import 'package:path/path.dart' as p;
import 'support/temp_dir.dart';
/// 시험이 끝내라고 할 때까지 FFmpeg 이 도는 척 (끝나면 임시 출력 파일을 씀) - test/batch_test.dart 의 _EndTool 과 같음
class _EndTool implements MediaTool {
final runs = <Object?, Completer<void>>{};
@override
Future<String?> version() async => 'ffmpeg test';
@override
Future<Set<String>> encoders() async => {'libx264'};
@override
Future<MediaInfo> probe(String path) async => const MediaInfo(duration: Duration(seconds: 1));
@override
Future<void> runFfmpeg(List<String> args, {Duration? duration, ProgressCallback? onProgress, Object? tag}) async {
final c = runs[tag] = Completer<void>();
try {
await c.future;
} finally {
runs.remove(tag);
}
File(args.last).writeAsStringSync('NEW');
}
void _stop(Completer<void>? c) {
if (c != null && !c.isCompleted) c.completeError(MediaToolException('사용자가 취소했습니다.'));
}
void finish(Object tag) => runs[tag]!.complete();
@override
void cancel() => runs.values.toList().forEach(_stop);
@override
void cancelTagged(Object tag) => _stop(runs[tag]);
}
/// 262 (관리자 지시 10/21): MKV 묶음에서 한 동영상이 끝나는 바로 그때 - 그 줄의 ■ 자리에 오는 ⟳ 는 묶음이 끝날 때까지 눌리지 않고
/// (✕ 도 밀려오지 않음), 끝나면 둘 다 성공. ko · en · ja × 412 · 960 (가짜 FFmpeg, 시험 폴더 안)
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
final out = Platform.environment['JJ_SHOT_DIR'] ?? '';
for (final lang in ['ko', 'en', 'ja']) {
for (final (w, h) in [(412, 900), (960, 600)]) {
testWidgets('262 MKV 끝나는 순간 $lang ${w}x$h', (t) async {
t.view.physicalSize = Size(w.toDouble(), h.toDouble());
t.view.devicePixelRatio = 1;
addTearDown(t.view.reset);
await t.runAsync(() => i18n.apply(lang, save: false));
addTearDown(() => i18n.apply('ko', save: false));
final tmp = testTempDir('jj_mkvend_');
appTempInto(tmp);
final files = [for (final n in ['episode_01', 'episode_02']) (File(p.join(tmp.path, '$n.mp4'))..writeAsBytesSync([0])).path];
final tool = _EndTool();
final c = AppController(PlatformServices(mediaTool: tool, storage: DesktopStorageService()));
late Future<void> building;
await t.runAsync(() async {
await c.init();
c.settings.maxParallelJobs = 1;
await c.addVideos(files);
building = c.buildVideos([...c.videos]);
for (var i = 0; i < 200 && tool.runs.isEmpty; i++) {
await Future<void>.delayed(const Duration(milliseconds: 10));
}
});
final [a, b] = c.videos;
final shotKey = GlobalKey();
await t.pumpWidget(RepaintBoundary(
key: shotKey,
child: MaterialApp(theme: buildTheme(), debugShowCheckedModeBanner: false, home: HomePage(c: c)),
));
Future<void> settle([int n = 6]) async {
for (var i = 0; i < n; i++) {
await t.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 50)));
await t.pump();
}
}
Future<void> shot(String name) async {
await settle(2);
expect(t.takeException(), isNull, reason: '$name 넘침');
if (out.isEmpty) return;
final r = shotKey.currentContext!.findRenderObject()! as RenderRepaintBoundary;
final img = await t.runAsync(() => r.toImage());
final bytes = await t.runAsync(() => img!.toByteData(format: ui.ImageByteFormat.png));
Directory(out).createSync(recursive: true);
File(p.join(out, 'mkvend_${name}_${lang}_$w.png')).writeAsBytesSync(bytes!.buffer.asUint8List());
}
await settle();
final stop = find.byTooltip(tr('이 동영상만 취소'));
expect(stop, findsWidgets, reason: '도는 줄의 ■');
final stopAt = t.getCenter(stop.first);
await shot('1_running');
// a 가 끝나고 b 가 시작 - a 의 ■ 자리를 누른다 (손가락이 ■ 를 누르려던 것)
await t.runAsync(() async {
tool.finish(a);
for (var i = 0; i < 200 && !tool.runs.containsKey(b); i++) {
await Future<void>.delayed(const Duration(milliseconds: 10));
}
});
await settle();
expect(a.status, JobStatus.done);
await t.tapAt(stopAt);
await settle();
expect(a.status, JobStatus.done, reason: '⟳ 로 대기가 되지 않음');
expect(c.videos, contains(a), reason: '✕ 로 빠지지 않음');
await shot('2_a_done_b_running');
await t.runAsync(() async {
tool.finish(b);
await building;
});
await settle();
expect((a.status, b.status), (JobStatus.done, JobStatus.done));
await shot('3_all_done');
await t.pumpWidget(const SizedBox());
await t.pump(const Duration(seconds: 2));
});
}
}
}