oto/test/oto_scheduler_runtime_test.dart

239 lines
6.3 KiB
Dart

// ignore_for_file: depend_on_referenced_packages
import 'dart:convert';
import 'dart:io';
import 'package:dart_framework/platform/process.dart';
import 'package:oto/cli/commands/command_scheduler.dart';
import 'package:oto/cli/commands/scheduler/scheduler_manager.dart';
import 'package:oto/oto/core/system_runtime.dart';
import 'package:test/test.dart';
class StartExecutableCall {
final String program;
final List<String> args;
final ProcessStartMode mode;
StartExecutableCall(this.program, this.args, this.mode);
}
class FakeSystemRuntime implements SystemRuntime {
final List<StringBuffer> runShellCalls = [];
final List<StartExecutableCall> startExecutableCalls = [];
final List<int> killedPids = [];
final bool _isWindows;
FakeSystemRuntime({bool isWindows = false}) : _isWindows = isWindows;
@override
bool get isWindows => _isWindows;
@override
bool get isMacOS => !_isWindows;
@override
bool get isLinux => !_isWindows;
@override
Map<String, String> get environment => {};
@override
Future<ProcessData> startShell(
StringBuffer shell, {
String? workspace,
bool printStdout = true,
bool printStderr = true,
Converter<List<int>, String>? decoder,
LogHandler? logHandler,
}) async {
throw UnimplementedError();
}
@override
Future<ProcessResult> runShell(
StringBuffer shell, {
String? workspace,
bool printStdout = true,
bool printStderr = true,
Converter<List<int>, String>? decoder,
LogHandler? logHandler,
}) async {
runShellCalls.add(shell);
return ProcessResult(0, 0, '', '');
}
@override
Future<ProcessResult> runExecutable(
String exe,
List<String> args, {
String? workingDirectory,
Encoding? stdoutEncoding,
Encoding? stderrEncoding,
}) async {
throw UnimplementedError();
}
@override
Future<Process> startExecutable(
String program,
List<String> args, {
String? workingDirectory,
ProcessStartMode mode = ProcessStartMode.normal,
}) async {
startExecutableCalls.add(StartExecutableCall(program, args, mode));
return _DummyProcess();
}
@override
bool killPid(int pid, [ProcessSignal signal = ProcessSignal.sigterm]) {
killedPids.add(pid);
return true;
}
}
class _DummyProcess implements Process {
@override
Future<int> get exitCode => Future.value(0);
@override
bool kill([ProcessSignal signal = ProcessSignal.sigterm]) => true;
@override
int get pid => 12345;
@override
Stream<List<int>> get stderr => const Stream.empty();
@override
IOSink get stdin => throw UnimplementedError();
@override
Stream<List<int>> get stdout => const Stream.empty();
}
class TestSchedulerManager extends SchedulerManager {
TestSchedulerManager({
super.runtime,
super.processFinder,
super.exitHandler,
});
@override
String get exePath => '/bin/oto';
}
class FakeSchedulerManager extends SchedulerManager {
bool showListCalled = false;
bool terminateCalled = false;
bool enableSchedulerCalled = false;
String? enabledAlias;
bool? enabledValue;
bool registCalled = false;
String? registeredFilePath;
@override
Future showList() async {
showListCalled = true;
}
@override
Future terminate() async {
terminateCalled = true;
}
@override
Future enableScheduler(String alias, bool enable) async {
enableSchedulerCalled = true;
enabledAlias = alias;
enabledValue = enable;
}
@override
Future regist(String filePath) async {
registCalled = true;
registeredFilePath = filePath;
}
}
void main() {
group('SchedulerManager Process Control via Injected Runtime', () {
test('Windows starts detached executable', () async {
final fakeRuntime = FakeSystemRuntime(isWindows: true);
final manager = TestSchedulerManager(runtime: fakeRuntime);
await manager.testStartProcess();
expect(fakeRuntime.startExecutableCalls, hasLength(1));
expect(fakeRuntime.startExecutableCalls.single.program,
contains(Platform.resolvedExecutable));
expect(fakeRuntime.startExecutableCalls.single.mode,
ProcessStartMode.detached);
expect(fakeRuntime.runShellCalls, isEmpty);
});
test('Unix starts background shell process via nohup', () async {
final fakeRuntime = FakeSystemRuntime(isWindows: false);
final manager = TestSchedulerManager(runtime: fakeRuntime);
await manager.testStartProcess();
expect(fakeRuntime.runShellCalls, hasLength(1));
expect(fakeRuntime.runShellCalls.single.toString(), contains('nohup'));
expect(fakeRuntime.runShellCalls.single.toString(),
contains('scheduler -s &'));
expect(fakeRuntime.startExecutableCalls, isEmpty);
});
test('terminate kills current scheduler process', () async {
final fakeRuntime = FakeSystemRuntime();
final manager = TestSchedulerManager(
runtime: fakeRuntime,
processFinder: (name) async => [],
);
var p = ProcessInfo();
p.pid = 98765;
p.name = 'scheduler -s';
manager.currentScheduler = p;
await manager.terminate();
expect(fakeRuntime.killedPids, [98765]);
});
test('_checkProcess uses injected processFinder', () async {
bool finderCalled = false;
final manager = TestSchedulerManager(
processFinder: (query) async {
finderCalled = true;
var p = ProcessInfo();
p.pid = 11111;
p.name = 'main.dart scheduler -s oto scheduler -s';
return [p];
},
);
await manager.testCheckProcess();
expect(finderCalled, isTrue);
expect(manager.currentScheduler?.pid, 11111);
});
});
group('CommandScheduler routing', () {
test(
'command_scheduler accepts injected manager and exit handler for routing',
() async {
final fakeManager = FakeSchedulerManager();
int? exitedCode;
final cmd = CommandScheduler(
scheduler: fakeManager,
exitHandler: (code) {
exitedCode = code;
},
);
await cmd.execute(['-l']);
expect(fakeManager.showListCalled, isTrue);
await cmd.execute(['-t']);
expect(fakeManager.terminateCalled, isTrue);
await cmd.execute(['-r', 'dummy.yaml']);
expect(fakeManager.registCalled, isTrue);
expect(fakeManager.registeredFilePath, 'dummy.yaml');
expect(exitedCode, 0);
});
});
}