138 lines
5.7 KiB
Dart
138 lines
5.7 KiB
Dart
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));
|
||
});
|
||
}
|
||
}
|
||
}
|