- Split BuildIOS command into build_ios_archive and build_ios_build - Split Git command into git_branch, git_remote, git_revision, git_stash - Add cli_style.dart and printer.dart for consistent CLI output - Add macos_signing.dart for macOS/iOS code signing - Refactor pipeline system (pipeline, pipeline_condition, pipeline_contain, etc.) - Update tag_system, defined_data, pipeline_data - Update application and command files for new structure - Add tests for new command catalog and core functionality - Update README with new features
199 lines
6.1 KiB
Dart
199 lines
6.1 KiB
Dart
import 'package:oto/oto/application.dart';
|
|
import 'package:oto/oto/commands/command.dart';
|
|
import 'package:oto/oto/commands/command_registry.dart';
|
|
import 'package:oto/oto/core/execution_context.dart';
|
|
import 'package:oto/oto/data/command_data.dart';
|
|
import 'package:oto/oto/pipeline/pipeline.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
void main() {
|
|
setUp(() {
|
|
Application.instance.property = {};
|
|
Application.instance.commandStates = {};
|
|
Application.instance.dataCommandMap = {};
|
|
});
|
|
|
|
test('parses minimal build yaml into DataBuild', () {
|
|
const yaml = '''
|
|
property:
|
|
workspace: .
|
|
commands:
|
|
- command: Print
|
|
id: hello
|
|
param: hi
|
|
pipeline:
|
|
id: main
|
|
workflow:
|
|
- exe: hello
|
|
''';
|
|
final map = Application.getMapFromYamlA(yaml);
|
|
expect(map, isNotNull);
|
|
|
|
final build = DataBuild.fromJson(map!);
|
|
expect(build.property!['workspace'], '.');
|
|
expect(build.commands, hasLength(1));
|
|
expect(build.commands.first.id, 'hello');
|
|
expect(build.commands.first.command, CommandType.Print);
|
|
expect(build.pipeline!.id, 'main');
|
|
expect(build.pipeline!.workflow, hasLength(1));
|
|
});
|
|
|
|
test('tag system preserves single tag object values', () {
|
|
Application.instance.property['items'] = [1, 2, 3];
|
|
Application.instance.property['config'] = {'a': 1};
|
|
|
|
final listValue = Command.replaceTagValue('<!property.items>');
|
|
expect(listValue, isA<List>());
|
|
expect(listValue, equals([1, 2, 3]));
|
|
|
|
final mapValue = Command.replaceTagValue('<!property.config>');
|
|
expect(mapValue, isA<Map>());
|
|
expect(mapValue, equals({'a': 1}));
|
|
|
|
final embedded = Command.replaceTagValue('values=<!property.items>');
|
|
expect(embedded, isA<String>());
|
|
expect(embedded, contains('1'));
|
|
});
|
|
|
|
test('pipeline validation rejects missing command id', () {
|
|
final result = Pipeline.pipelineInitialize([
|
|
{'exe': 'doesNotExist'}
|
|
]);
|
|
expect(result.enable, isFalse);
|
|
expect(result.message, contains('doesNotExist'));
|
|
});
|
|
|
|
// ── Explicit context regression tests ──────────────────────────────────────
|
|
// Each test puts a different value in the singleton vs. the explicit context
|
|
// and asserts that flow-control uses the explicit context exclusively.
|
|
|
|
ExecutionContext makeCtx(Map<String, dynamic> props,
|
|
List<String> commandIds) {
|
|
registerAllCommands();
|
|
final ctx = ExecutionContext();
|
|
ctx.property.addAll(props);
|
|
for (final id in commandIds) {
|
|
final cmd = DataCommand()
|
|
..command = CommandType.Print
|
|
..id = id
|
|
..param = {'message': id};
|
|
ctx.dataCommandMap[id] = cmd;
|
|
}
|
|
return ctx;
|
|
}
|
|
|
|
test('PipelineIf evaluates condition from explicit context not singleton',
|
|
() async {
|
|
// Singleton: env = 'dev' → on-false; explicit: env = 'prod' → on-true
|
|
Application.instance.property['env'] = 'dev';
|
|
final ctx = makeCtx({'env': 'prod'}, ['onTrue', 'onFalse']);
|
|
|
|
final result = Pipeline.pipelineInitialize([
|
|
{
|
|
'if': {
|
|
'condition-string': '<!property.env> == prod',
|
|
'on-true': [
|
|
{'exe': 'onTrue'}
|
|
],
|
|
'on-false': [
|
|
{'exe': 'onFalse'}
|
|
],
|
|
}
|
|
}
|
|
], context: ctx);
|
|
|
|
expect(result.enable, isTrue, reason: result.message);
|
|
await result.pipeline!.execute();
|
|
|
|
expect(ctx.commandStates['onTrue'], CommandState.complete);
|
|
expect(ctx.commandStates['onFalse'], CommandState.ready);
|
|
});
|
|
|
|
test('PipelineForeach iterates list from explicit context not singleton',
|
|
() async {
|
|
// Singleton: items = [] (empty); explicit: items = [10, 20, 30]
|
|
Application.instance.property['items'] = <dynamic>[];
|
|
final ctx = makeCtx({'items': [10, 20, 30]}, ['print']);
|
|
|
|
final result = Pipeline.pipelineInitialize([
|
|
{
|
|
'foreach': {
|
|
'iterator': '<!property.items>',
|
|
'setValue': '<@property.current>',
|
|
'on-do': [
|
|
{'exe': 'print'}
|
|
],
|
|
}
|
|
}
|
|
], context: ctx);
|
|
|
|
expect(result.enable, isTrue, reason: result.message);
|
|
await result.pipeline!.execute();
|
|
|
|
// Loop ran 3 times using explicit context; last assigned value = 30
|
|
expect(ctx.property['current'], 30);
|
|
// Singleton must not have been modified
|
|
expect(Application.instance.property.containsKey('current'), isFalse);
|
|
});
|
|
|
|
test('PipelineContain evaluates target from explicit context not singleton',
|
|
() async {
|
|
// Singleton: target = 'xyz' (not in list); explicit: target = 'foo' (in list)
|
|
Application.instance.property['target'] = 'xyz';
|
|
final ctx = makeCtx({'target': 'foo'}, ['onTrue', 'onFalse']);
|
|
|
|
final result = Pipeline.pipelineInitialize([
|
|
{
|
|
'contain': {
|
|
'target': '<!property.target>',
|
|
'list': ['foo', 'bar'],
|
|
'on-true': [
|
|
{'exe': 'onTrue'}
|
|
],
|
|
'on-false': [
|
|
{'exe': 'onFalse'}
|
|
],
|
|
}
|
|
}
|
|
], context: ctx);
|
|
|
|
expect(result.enable, isTrue, reason: result.message);
|
|
await result.pipeline!.execute();
|
|
|
|
expect(ctx.commandStates['onTrue'], CommandState.complete);
|
|
expect(ctx.commandStates['onFalse'], CommandState.ready);
|
|
});
|
|
|
|
test(
|
|
'PipelineSwitch validate uses explicit context command map not singleton',
|
|
() {
|
|
// Singleton dataCommandMap is empty; only explicit context has the command.
|
|
registerAllCommands();
|
|
final ctx = ExecutionContext();
|
|
final cmd = DataCommand()
|
|
..command = CommandType.Print
|
|
..id = 'deployProd'
|
|
..param = {'message': 'prod'};
|
|
ctx.dataCommandMap['deployProd'] = cmd;
|
|
|
|
final result = Pipeline.pipelineInitialize([
|
|
{
|
|
'switch': {
|
|
'condition-string': 'prod',
|
|
'cases': [
|
|
{
|
|
'value': 'prod',
|
|
'tasks': [
|
|
{'exe': 'deployProd'}
|
|
],
|
|
}
|
|
],
|
|
}
|
|
}
|
|
], context: ctx);
|
|
|
|
// Should succeed: deployProd exists in explicit context.
|
|
// Would fail if singleton (empty) context were used.
|
|
expect(result.enable, isTrue, reason: result.message);
|
|
});
|
|
}
|