772 lines
20 KiB
Dart
772 lines
20 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:oto/cli/commands/command_exe.dart';
|
|
import 'package:oto/oto/application.dart';
|
|
import 'package:oto/oto/core/build_result.dart';
|
|
import 'package:oto/oto/core/execution_context.dart';
|
|
import 'package:oto/oto/data/command_data.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
void main() {
|
|
setUp(() {
|
|
Application.instance.property = {};
|
|
Application.instance.commandStates = {};
|
|
Application.instance.dataCommandMap = {};
|
|
});
|
|
|
|
test('build returns failure instead of exiting on invalid yaml', () async {
|
|
const invalidYaml = '''
|
|
property:
|
|
workspace: .
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
id: main
|
|
workflow:
|
|
- exe: doesNotExist
|
|
''';
|
|
|
|
final result = await Application.instance
|
|
.build(BuildType.file, yamlContent: invalidYaml);
|
|
|
|
expect(result.success, isFalse);
|
|
expect(result.exitCode, 10);
|
|
expect(result.error, isNotNull);
|
|
});
|
|
|
|
test('file build returns success for print-only pipeline', () async {
|
|
const yaml = '''
|
|
property:
|
|
workspace: .
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
id: main
|
|
workflow:
|
|
- exe: hello
|
|
''';
|
|
|
|
final result =
|
|
await Application.instance.build(BuildType.file, yamlContent: yaml);
|
|
|
|
expect(result.success, isTrue);
|
|
expect(result.exitCode, 0);
|
|
});
|
|
|
|
test('file build without property workspace uses local workspace without Jenkins env',
|
|
() async {
|
|
const yaml = '''
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
id: main
|
|
workflow:
|
|
- exe: hello
|
|
''';
|
|
|
|
final result = await Application.instance.build(
|
|
BuildType.file,
|
|
yamlContent: yaml,
|
|
logEnable: false,
|
|
);
|
|
|
|
expect(result.success, isTrue);
|
|
expect(Application.instance.property['workspace'], isNotNull);
|
|
expect(Application.instance.property['workspace'],
|
|
Directory.current.path.replaceAll('\\', '/'));
|
|
});
|
|
|
|
test('file build can run twice in same process', () async {
|
|
const yaml = '''
|
|
property:
|
|
workspace: .
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
id: main
|
|
workflow:
|
|
- exe: hello
|
|
''';
|
|
|
|
final first =
|
|
await Application.instance.build(BuildType.file, yamlContent: yaml);
|
|
final second =
|
|
await Application.instance.build(BuildType.file, yamlContent: yaml);
|
|
|
|
expect(first.success, isTrue);
|
|
expect(second.success, isTrue);
|
|
});
|
|
|
|
test('build fails with message when YAML root is not a map', () async {
|
|
const yaml = '''
|
|
- command: Print
|
|
id: hello
|
|
''';
|
|
final result =
|
|
await Application.instance.build(BuildType.file, yamlContent: yaml);
|
|
expect(result.success, isFalse);
|
|
expect(result.error.toString(), contains('Build YAML root must be a map'));
|
|
});
|
|
|
|
test('build fails with message when property is not a map', () async {
|
|
const yaml = '''
|
|
property:
|
|
- bad
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
id: main
|
|
workflow:
|
|
- exe: hello
|
|
''';
|
|
final result =
|
|
await Application.instance.build(BuildType.file, yamlContent: yaml);
|
|
expect(result.success, isFalse);
|
|
expect(result.error.toString(), contains('Validate build yaml'));
|
|
});
|
|
|
|
test('build fails with message when command id is not a string', () async {
|
|
const yaml = '''
|
|
commands:
|
|
- command: Print
|
|
id: 123
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
id: main
|
|
workflow:
|
|
- exe: hello
|
|
''';
|
|
final result =
|
|
await Application.instance.build(BuildType.file, yamlContent: yaml);
|
|
expect(result.success, isFalse);
|
|
expect(result.error.toString(), contains('Validate command list'));
|
|
});
|
|
|
|
test('build fails with message when command type is not a string', () async {
|
|
const yaml = '''
|
|
commands:
|
|
- command: 999
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
id: main
|
|
workflow:
|
|
- exe: hello
|
|
''';
|
|
final result =
|
|
await Application.instance.build(BuildType.file, yamlContent: yaml);
|
|
expect(result.success, isFalse);
|
|
expect(result.error.toString(), contains('Validate command list'));
|
|
});
|
|
|
|
test('build fails with validation message when workflow key is not a string',
|
|
() async {
|
|
const yaml = '''
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
id: main
|
|
workflow:
|
|
- 123: hello
|
|
''';
|
|
final result =
|
|
await Application.instance.build(BuildType.file, yamlContent: yaml);
|
|
expect(result.success, isFalse);
|
|
expect(result.error.toString(),
|
|
isNot(contains('Converting object to an encodable object failed')));
|
|
expect(result.error.toString(), contains('workflow[0]'));
|
|
});
|
|
|
|
test('build fails with validation message when exe-handle branch is missing',
|
|
() async {
|
|
const yaml = '''
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
id: main
|
|
workflow:
|
|
- exe-handle:
|
|
id: hello
|
|
on-fail:
|
|
- exe: hello
|
|
''';
|
|
final result =
|
|
await Application.instance.build(BuildType.file, yamlContent: yaml);
|
|
expect(result.success, isFalse);
|
|
expect(result.error.toString(),
|
|
contains('exe-handle syntax requires on-success'));
|
|
});
|
|
|
|
// REVIEW_VALIDATE-1: exe command id type guard
|
|
test(
|
|
'build fails with validation message when exe command id is not a string',
|
|
() async {
|
|
const yaml = '''
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
id: main
|
|
workflow:
|
|
- exe: 123
|
|
''';
|
|
final result =
|
|
await Application.instance.build(BuildType.file, yamlContent: yaml);
|
|
expect(result.success, isFalse);
|
|
expect(result.error.toString(), isNot(contains('is not a subtype')));
|
|
expect(result.error.toString(), contains('exe'));
|
|
});
|
|
|
|
// REVIEW_VALIDATE-1: wait-until-string condition type guard
|
|
test(
|
|
'build fails with validation message when wait-until-string value is not a string',
|
|
() async {
|
|
const yaml = '''
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
id: main
|
|
workflow:
|
|
- wait-until-string: 123
|
|
''';
|
|
final result =
|
|
await Application.instance.build(BuildType.file, yamlContent: yaml);
|
|
expect(result.success, isFalse);
|
|
expect(result.error.toString(), isNot(contains('is not a subtype')));
|
|
expect(result.error.toString(), contains('wait-until'));
|
|
});
|
|
|
|
// REVIEW_VALIDATE-3: nested pipeline malformed cases
|
|
test('build fails with validation message when exe-handle id is not a string',
|
|
() async {
|
|
const yaml = '''
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
id: main
|
|
workflow:
|
|
- exe-handle:
|
|
id: 123
|
|
on-success:
|
|
- exe: hello
|
|
on-fail:
|
|
- exe: hello
|
|
''';
|
|
final result =
|
|
await Application.instance.build(BuildType.file, yamlContent: yaml);
|
|
expect(result.success, isFalse);
|
|
expect(result.error.toString(), isNot(contains('is not a subtype')));
|
|
expect(result.error.toString(), contains('exe-handle'));
|
|
expect(result.error.toString(), contains('id'));
|
|
});
|
|
|
|
// REVIEW_REVIEW_VALIDATE-1: pipeline.id validation
|
|
test('build fails with validation message when pipeline.id is missing',
|
|
() async {
|
|
const yaml = '''
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
workflow:
|
|
- exe: hello
|
|
''';
|
|
final result =
|
|
await Application.instance.build(BuildType.file, yamlContent: yaml);
|
|
expect(result.success, isFalse);
|
|
expect(result.error.toString(), isNot(contains('is not a subtype')));
|
|
expect(result.error.toString(), contains('pipeline.id'));
|
|
});
|
|
|
|
test('build fails with validation message when pipeline.id is not a string',
|
|
() async {
|
|
const yaml = '''
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
id: 123
|
|
workflow:
|
|
- exe: hello
|
|
''';
|
|
final result =
|
|
await Application.instance.build(BuildType.file, yamlContent: yaml);
|
|
expect(result.success, isFalse);
|
|
expect(result.error.toString(), isNot(contains('is not a subtype')));
|
|
expect(result.error.toString(), contains('pipeline.id'));
|
|
});
|
|
|
|
test('build fails with validation message when workflow is empty', () async {
|
|
const yaml = '''
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
id: main
|
|
workflow: []
|
|
''';
|
|
final result =
|
|
await Application.instance.build(BuildType.file, yamlContent: yaml);
|
|
expect(result.success, isFalse);
|
|
expect(result.error.toString(), isNot(contains('Null check operator')));
|
|
expect(result.error.toString(), contains('pipeline.workflow'));
|
|
});
|
|
|
|
test('CommandExe exe -f handles missing file without LateInitializationError',
|
|
() async {
|
|
final missing =
|
|
'/tmp/oto_missing_${DateTime.now().microsecondsSinceEpoch}.yaml';
|
|
expect(File(missing).existsSync(), isFalse);
|
|
|
|
final command = CommandExe();
|
|
final priorExitCode = exitCode;
|
|
try {
|
|
await command.execute(['-f', missing]);
|
|
} finally {
|
|
exitCode = priorExitCode;
|
|
}
|
|
});
|
|
|
|
group('Application.validateYamlContent', () {
|
|
const validYaml = '''
|
|
property:
|
|
workspace: .
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
id: main
|
|
workflow:
|
|
- exe: hello
|
|
''';
|
|
|
|
test('validateYamlContent returns success contract for valid yaml', () {
|
|
final result = Application.validateYamlContent(validYaml);
|
|
expect(result.valid, isTrue);
|
|
expect(result.phase, 'Validate YAML');
|
|
expect(result.message, 'YAML is valid.');
|
|
expect(result.exitCode, 0);
|
|
});
|
|
|
|
test('validateYamlContent returns build yaml failure contract', () {
|
|
const invalidBuildYaml = '''
|
|
- command: Print
|
|
id: hello
|
|
''';
|
|
final result = Application.validateYamlContent(invalidBuildYaml);
|
|
expect(result.valid, isFalse);
|
|
expect(result.phase, 'Validate build yaml');
|
|
expect(result.message, contains('Build YAML root must be a map'));
|
|
expect(result.exitCode, 10);
|
|
});
|
|
|
|
test('validateYamlContent returns command list failure contract for duplicate id', () {
|
|
const duplicateIdYaml = '''
|
|
property:
|
|
workspace: .
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hello again
|
|
pipeline:
|
|
id: main
|
|
workflow:
|
|
- exe: hello
|
|
''';
|
|
final result = Application.validateYamlContent(duplicateIdYaml);
|
|
expect(result.valid, isFalse);
|
|
expect(result.phase, 'Validate command list');
|
|
expect(result.message, contains('Duplicate command id exists: "hello"'));
|
|
expect(result.exitCode, 10);
|
|
});
|
|
|
|
test('validateYamlContent returns pipeline failure contract', () {
|
|
const invalidPipelineYaml = '''
|
|
property:
|
|
workspace: .
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
id: main
|
|
workflow:
|
|
- exe: doesNotExist
|
|
''';
|
|
final result = Application.validateYamlContent(invalidPipelineYaml);
|
|
expect(result.valid, isFalse);
|
|
expect(result.phase, 'Validate Pipeline');
|
|
expect(result.message, contains('workflow[0]'));
|
|
expect(result.exitCode, 10);
|
|
});
|
|
|
|
test('validateYamlContent exposes stable json shape', () {
|
|
final result = Application.validateYamlContent(validYaml);
|
|
final json = result.toJson();
|
|
|
|
expect(json['schemaVersion'], 1);
|
|
expect(json['type'], 'yamlValidation');
|
|
expect(json['valid'], isTrue);
|
|
expect(json['phase'], 'Validate YAML');
|
|
expect(json['message'], 'YAML is valid.');
|
|
expect(json['exitCode'], 0);
|
|
});
|
|
|
|
test('validateYamlContent returns failure contract for malformed scheduler section', () {
|
|
const malformedSchedulerYaml = '''
|
|
property:
|
|
workspace: .
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
id: main
|
|
workflow:
|
|
- exe: hello
|
|
scheduler: "not_a_map"
|
|
''';
|
|
final result = Application.validateYamlContent(malformedSchedulerYaml);
|
|
expect(result.valid, isFalse);
|
|
expect(result.phase, 'Validate build yaml');
|
|
expect(result.exitCode, 10);
|
|
expect(result.message, isNotEmpty);
|
|
});
|
|
});
|
|
|
|
group('BuildResult.toJson', () {
|
|
test('exposes success execution envelope', () {
|
|
const result = BuildResult.success();
|
|
final json = result.toJson();
|
|
|
|
expect(json['schemaVersion'], 1);
|
|
expect(json['type'], 'executionResult');
|
|
expect(json['success'], isTrue);
|
|
expect(json['exitCode'], 0);
|
|
expect(json['message'], 'Build completed successfully.');
|
|
expect(json['error'], isNull);
|
|
expect(json['stepEvents'], isEmpty);
|
|
});
|
|
|
|
test('exposes failure execution envelope', () {
|
|
final error = Exception('Some critical error');
|
|
final stack = StackTrace.current;
|
|
final result = BuildResult.failure(error, stack, exitCode: 15);
|
|
final json = result.toJson();
|
|
|
|
expect(json['schemaVersion'], 1);
|
|
expect(json['type'], 'executionResult');
|
|
expect(json['success'], isFalse);
|
|
expect(json['exitCode'], 15);
|
|
expect(json['message'], contains('Some critical error'));
|
|
expect(json['error'], isNotNull);
|
|
expect(json['error']['type'], '_Exception');
|
|
expect(json['error']['message'], contains('Some critical error'));
|
|
expect(json['stepEvents'], isEmpty);
|
|
});
|
|
|
|
test('exposes populated step events', () {
|
|
final events = [
|
|
StepEvent(
|
|
stepId: 0,
|
|
workflowIndex: 0,
|
|
stepType: 'exe',
|
|
event: 'started',
|
|
timestamp: '2026-05-22T05:00:00Z',
|
|
commandId: 'hello',
|
|
commandType: 'Print',
|
|
),
|
|
];
|
|
final result = BuildResult.success(stepEvents: events);
|
|
final json = result.toJson();
|
|
|
|
expect(json['stepEvents'], hasLength(1));
|
|
expect(json['stepEvents'][0]['stepId'], 0);
|
|
expect(json['stepEvents'][0]['event'], 'started');
|
|
expect(json['stepEvents'][0]['commandId'], 'hello');
|
|
expect(json['stepEvents'][0]['commandType'], 'Print');
|
|
});
|
|
});
|
|
|
|
group('BuildResult regression - stepEvents snapshot', () {
|
|
test('first build result retains stepEvents after second build execution', () async {
|
|
const validYaml = '''
|
|
property:
|
|
workspace: .
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
id: main
|
|
workflow:
|
|
- exe: hello
|
|
''';
|
|
|
|
// 1. Run first build
|
|
final result1 = await Application.instance.build(
|
|
BuildType.file,
|
|
yamlContent: validYaml,
|
|
logEnable: false,
|
|
);
|
|
|
|
expect(result1.success, isTrue);
|
|
final events1 = result1.toJson()['stepEvents'] as List;
|
|
expect(events1, isNotEmpty);
|
|
final originalEventsCount = events1.length;
|
|
|
|
// 2. Run second build (which triggers context.resetStepEvents())
|
|
final result2 = await Application.instance.build(
|
|
BuildType.file,
|
|
yamlContent: validYaml,
|
|
logEnable: false,
|
|
);
|
|
|
|
expect(result2.success, isTrue);
|
|
|
|
// 3. Assert first build result still retains its original stepEvents
|
|
final events1AfterSecondBuild = result1.toJson()['stepEvents'] as List;
|
|
expect(events1AfterSecondBuild, hasLength(originalEventsCount));
|
|
expect(events1AfterSecondBuild[0]['event'], 'started');
|
|
expect(events1AfterSecondBuild[0]['commandId'], 'hello');
|
|
});
|
|
});
|
|
|
|
group('logEnable false', () {
|
|
test('build with logEnable false suppresses failure stdout', () async {
|
|
const invalidYaml = '''
|
|
property:
|
|
workspace: .
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
id: main
|
|
workflow:
|
|
- exe: doesNotExist
|
|
''';
|
|
|
|
final prints = <String>[];
|
|
final result = await runZoned(() async {
|
|
return await Application.instance.build(
|
|
BuildType.file,
|
|
yamlContent: invalidYaml,
|
|
logEnable: false,
|
|
);
|
|
}, zoneSpecification: ZoneSpecification(
|
|
print: (self, parent, zone, line) {
|
|
prints.add(line);
|
|
},
|
|
));
|
|
|
|
expect(result.success, isFalse);
|
|
expect(prints, isEmpty);
|
|
});
|
|
|
|
test('build with logEnable false suppresses command stdout logs', () async {
|
|
const yaml = '''
|
|
property:
|
|
workspace: .
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
id: main
|
|
workflow:
|
|
- exe: hello
|
|
''';
|
|
|
|
final prints = <String>[];
|
|
final result = await runZoned(() async {
|
|
return await Application.instance.build(
|
|
BuildType.file,
|
|
yamlContent: yaml,
|
|
logEnable: false,
|
|
);
|
|
}, zoneSpecification: ZoneSpecification(
|
|
print: (self, parent, zone, line) {
|
|
prints.add(line);
|
|
},
|
|
));
|
|
|
|
expect(result.success, isTrue);
|
|
expect(prints, isEmpty);
|
|
});
|
|
});
|
|
|
|
test('scheduler build data runs without Jenkins env', () async {
|
|
final logFile = File(
|
|
'${Directory.systemTemp.path}/oto_scheduler_${DateTime.now().microsecondsSinceEpoch}.log');
|
|
addTearDown(() {
|
|
if (logFile.existsSync()) logFile.deleteSync();
|
|
});
|
|
const yaml = '''
|
|
scheduler:
|
|
alias: local-test
|
|
interval: 1000
|
|
enableLog: true
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
id: main
|
|
workflow:
|
|
- exe: hello
|
|
''';
|
|
final build = DataBuild.fromJson(Application.getMapFromYamlA(yaml)!);
|
|
|
|
final result = await Application.instance.build(
|
|
BuildType.scheduler,
|
|
buildData: build,
|
|
logEnable: false,
|
|
logPath: logFile.path,
|
|
);
|
|
|
|
expect(result.success, isTrue);
|
|
});
|
|
|
|
group('CommandExe JSON Mode', () {
|
|
test('CommandExe shouldPrintExecuteLog suppresses execute log for json', () {
|
|
final command = CommandExe();
|
|
expect(command.shouldPrintExecuteLog(['--json']), isFalse);
|
|
expect(command.shouldPrintExecuteLog(['-f', 'file.yaml']), isTrue);
|
|
});
|
|
|
|
test('CommandExe exe -f missing file --json returns execution result json', () async {
|
|
final missing = '/tmp/oto_missing_${DateTime.now().microsecondsSinceEpoch}.yaml';
|
|
expect(File(missing).existsSync(), isFalse);
|
|
|
|
final prints = <String>[];
|
|
final command = CommandExe(printString: (value) async {
|
|
prints.add(value);
|
|
});
|
|
|
|
final priorExitCode = exitCode;
|
|
try {
|
|
await command.execute(['-f', missing, '--json']);
|
|
} finally {
|
|
exitCode = priorExitCode;
|
|
}
|
|
|
|
expect(prints, hasLength(1));
|
|
final json = jsonDecode(prints[0]);
|
|
expect(json['schemaVersion'], 1);
|
|
expect(json['type'], 'executionResult');
|
|
expect(json['success'], isFalse);
|
|
expect(json['exitCode'], 10);
|
|
expect(json['message'], contains('There are no files in path'));
|
|
expect(json['stepEvents'], isEmpty);
|
|
});
|
|
|
|
test('actual bin exe --json output has parseable execution result json and no execute logs', () async {
|
|
final tempFile = File('${Directory.systemTemp.path}/oto_temp_${DateTime.now().microsecondsSinceEpoch}.yaml');
|
|
const yamlContent = '''
|
|
property:
|
|
workspace: .
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param:
|
|
message: hi
|
|
pipeline:
|
|
id: main
|
|
workflow:
|
|
- exe: hello
|
|
''';
|
|
tempFile.writeAsStringSync(yamlContent);
|
|
|
|
try {
|
|
final result = await Process.run('dart', [
|
|
'run',
|
|
'bin/main.dart',
|
|
'exe',
|
|
'-f',
|
|
tempFile.path,
|
|
'--json',
|
|
]);
|
|
|
|
expect(result.exitCode, 0);
|
|
|
|
final stdoutStr = result.stdout.toString().trim();
|
|
|
|
expect(stdoutStr, isNotEmpty);
|
|
|
|
final json = jsonDecode(stdoutStr);
|
|
expect(json['schemaVersion'], 1);
|
|
expect(json['type'], 'executionResult');
|
|
expect(json['success'], isTrue);
|
|
expect(json['exitCode'], 0);
|
|
|
|
expect(json['stepEvents'], isA<List>());
|
|
expect(json['stepEvents'].length, greaterThanOrEqualTo(2));
|
|
expect(json['stepEvents'][0]['type'], 'stepEvent');
|
|
expect(json['stepEvents'][0]['event'], 'started');
|
|
expect(json['stepEvents'][0]['commandId'], 'hello');
|
|
expect(json['stepEvents'].last['event'], 'completed');
|
|
expect(json['stepEvents'].last['commandId'], 'hello');
|
|
|
|
expect(stdoutStr, isNot(contains('Execute command:')));
|
|
expect(stdoutStr, isNot(contains('hi')));
|
|
} finally {
|
|
if (tempFile.existsSync()) {
|
|
tempFile.deleteSync();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|