runIterativeFixture function
Runs an iterative (external functions) fixture through platform.
Handles resumeValues, resumeErrors, and async/futures paths.
Implementation
Future<void> runIterativeFixture(
MontyPlatform platform,
Map<String, dynamic> fixture,
) async {
final code = fixture['code'] as String;
final extFns = (fixture['externalFunctions'] as List).cast<String>();
final resumeValues = (fixture['resumeValues'] as List?)?.cast<Object>();
final resumeErrors = (fixture['resumeErrors'] as List?)?.cast<String>();
final asyncResumeMap = fixture['asyncResumeMap'] as Map<String, dynamic>?;
final asyncErrorMap = fixture['asyncErrorMap'] as Map<String, dynamic>?;
final scriptName = fixture['scriptName'] as String?;
final expectError = fixture['expectError'] as bool? ?? false;
var progress = await platform.start(
code,
externalFunctions: extFns,
scriptName: scriptName,
);
final callIds = <int>[];
if (asyncResumeMap != null) {
if (platform is! MontyFutureCapable) {
markTestSkipped('Platform does not support MontyFutureCapable');
return;
}
final futurePlatform = platform as MontyFutureCapable;
try {
while (progress is! MontyComplete) {
if (progress is MontyPending) {
callIds.add(progress.callId);
progress = await futurePlatform.resumeAsFuture();
} else if (progress is MontyResolveFutures) {
final pending = progress.pendingCallIds;
final results = <int, Object?>{};
final errors = <int, String>{};
for (final id in pending) {
final key = id.toString();
if (asyncErrorMap != null && asyncErrorMap.containsKey(key)) {
errors[id] = asyncErrorMap[key] as String;
} else if (asyncResumeMap.containsKey(key)) {
results[id] = asyncResumeMap[key];
}
}
progress = await futurePlatform.resolveFutures(
results,
errors: errors.isNotEmpty ? errors : null,
);
} else {
fail('Unexpected progress type: $progress');
}
}
} on MontyException catch (e) {
if (expectError) {
final errorContains = fixture['errorContains'] as String?;
if (errorContains != null) {
expect(
e.message.contains(errorContains),
isTrue,
reason: 'Expected error containing "$errorContains", '
'got: "${e.message}"',
);
}
return;
}
rethrow;
}
if (expectError) {
final errorContains = fixture['errorContains'] as String?;
final completeResult = progress.result;
expect(
completeResult.error,
isNotNull,
reason: 'Fixture #${fixture['id']}: expected error result',
);
if (errorContains != null) {
final errorMessage = completeResult.error?.message ?? '';
expect(
errorMessage.contains(errorContains),
isTrue,
reason: 'Expected error containing "$errorContains", '
'got: "$errorMessage"',
);
}
return;
}
} else if (resumeErrors != null) {
for (var i = 0; i < resumeErrors.length; i++) {
expect(progress, isA<MontyPending>());
if (i == 0) assertPendingFields(progress as MontyPending, fixture);
callIds.add((progress as MontyPending).callId);
progress = await platform.resumeWithError(resumeErrors[i]);
}
} else if (resumeValues != null) {
for (var i = 0; i < resumeValues.length; i++) {
expect(progress, isA<MontyPending>());
if (i == 0) assertPendingFields(progress as MontyPending, fixture);
callIds.add((progress as MontyPending).callId);
progress = await platform.resume(resumeValues[i]);
}
}
if (fixture['expectedDistinctCallIds'] == true && callIds.length > 1) {
expect(
callIds.toSet().length,
callIds.length,
reason: 'Fixture #${fixture['id']}: expected distinct call_ids, '
'got: $callIds',
);
}
expect(progress, isA<MontyComplete>());
final complete = progress as MontyComplete;
assertLadderResult(complete.result.value, fixture);
}