runEnsembleYamlTestsCli function
Runs declarative YAML tests in an Ensemble app.
The host app must list ensemble_test_runner in dev_dependencies.
Options: --app-dir=
Implementation
Future<void> runEnsembleYamlTestsCli(List<String> arguments) async {
final verbose = isVerboseCli(arguments);
final quiet = arguments.contains('--quiet');
final reportMode = _resolveReportMode(arguments);
final jsonReport = reportMode == 'json';
final junitReport = reportMode == 'junit';
final machineReport = jsonReport || junitReport;
final streamLiveOutput = !quiet && !machineReport;
final reportFile = _resolveReportFile(arguments);
final appDir = _resolveAppDir(arguments);
final timeoutSeconds = _resolveTimeoutSeconds(arguments);
final jobs = _resolveJobsOverride(arguments);
final patcher = YamlTestAppPatcher(appDir);
if (!Directory(appDir).existsSync()) {
stderr.writeln('App directory not found: $appDir');
exit(2);
}
if (!File('${appDir}/pubspec.yaml').existsSync()) {
stderr.writeln('No pubspec.yaml in $appDir');
exit(2);
}
if (arguments.contains('--doctor')) {
final result = await EnsembleTestDoctor(appDir).run();
stdout.writeln(result.lines.join('\n'));
exit(result.hasErrors ? 1 : 0);
}
if (arguments.contains('--inspect-app')) {
try {
stdout.writeln(EnsembleAppInspector(appDir).inspect().toPrettyJson());
exit(0);
} on StateError catch (error) {
stderr.writeln(error.message);
exit(2);
}
}
if (arguments.contains('--validate-only')) {
final result = EnsembleTestValidator(appDir).validate();
stdout.writeln(jsonReport ? result.toPrettyJson() : result.formatText());
exit(result.hasErrors ? 2 : 0);
}
if (arguments.any((arg) =>
arg == '--scaffold-test' || arg.startsWith('--scaffold-test='))) {
try {
final result = EnsembleTestScaffold(appDir).create(arguments);
stdout.writeln(
result.created
? 'Created ${result.path}'
: 'Test file already exists: ${result.path}',
);
exit(result.created ? 0 : 2);
} on StateError catch (error) {
stderr.writeln(error.message);
exit(2);
}
}
final testsDirRelative = patcher.testsDirRelative;
if (testsDirRelative == null) {
stderr.writeln(
'Could not find definitions.local.path in ensemble/ensemble-config.yaml.\n'
'Declarative tests must live under definitions.local.path/tests.',
);
exit(2);
}
if (!patcher.hasTestYamlOnDisk) {
stderr.writeln(
'No declarative tests found. Add *.test.yaml files under '
'$testsDirRelative/',
);
exit(2);
}
final artifactLock = _RunArtifactLock(appDir);
if (!artifactLock.acquire()) {
stderr.writeln(
'Another Ensemble test run is already using the app artifact directory. '
'Wait for it to finish before starting another run.',
);
exit(3);
}
var exitCode = 0;
try {
_writeStatus(
'Preparing Ensemble YAML tests...',
quiet: quiet,
machineReport: machineReport,
);
patcher.enable();
if (patcher.pubspecChanged) {
_writeStatus(
'Resolving Flutter dependencies...',
quiet: quiet,
machineReport: machineReport,
);
final pubGet = await _runProcess(
'flutter',
['pub', 'get', '--suppress-analytics'],
workingDirectory: appDir,
);
if (pubGet.exitCode != 0 || verbose) {
_writeProcessStreams(pubGet);
}
if (pubGet.exitCode != 0) {
exitCode = 2;
return;
}
}
_writeStatus(
'Running Ensemble YAML tests...',
quiet: quiet,
machineReport: machineReport,
);
_cleanParallelRunArtifacts(appDir);
if (!machineReport) {
try {
await _withHtmlReport(
appDir,
const EnsembleTestRunResult(results: []),
wallTimeMs: 0,
isSuiteRunning: true,
);
} catch (_) {}
}
final runSerial = jobs == 1;
final serialWorkingDirectory = runSerial && patcher.hasTimerRewrites
? _prepareWorkerDirectory(appDir, 0, patcher)
: appDir;
final serialServiceOverrides = runSerial
? await _resolveServiceOverrides(
patcher: patcher,
usedPorts: <int>{},
)
: null;
final testRun = runSerial
? await _runFlutterTestProcess(
'flutter',
_buildFlutterTestArgs(
arguments,
reportMode: reportMode,
reportFile: reportFile,
timeoutSeconds: timeoutSeconds,
verbose: verbose,
appLogPath: patcher.hasTimerRewrites
? _appConsoleLogFile(appDir)
: _appConsoleLogPath(),
appLogDisplayPath:
patcher.hasTimerRewrites ? _appConsoleLogPath() : null,
artifactRoot:
patcher.hasTimerRewrites ? _artifactRootPath(appDir) : null,
serviceOverrides: serialServiceOverrides,
),
workingDirectory: serialWorkingDirectory,
streamOutput: streamLiveOutput,
verbose: verbose,
appLogFile: _appConsoleLogFile(appDir),
)
: await _runParallelFlutterTests(
arguments,
appDir: appDir,
patcher: patcher,
jobs: jobs,
reportMode: reportMode,
reportFile: reportFile,
timeoutSeconds: timeoutSeconds,
quiet: quiet,
machineReport: machineReport,
);
if (testRun.exitCode != 0 && !verbose) {
final output = '${testRun.stdout ?? ''}\n${testRun.stderr ?? ''}';
final rawJson = extractJsonReport(output);
final json = jsonReport ? rawJson : '';
final junit = junitReport ? extractJunitReport(output) : '';
final report = machineReport
? ''
: extractSuiteReport(
output,
includeScreenTracker: !streamLiveOutput,
);
final knownFailure = extractKnownFailure(output);
if (junit.isNotEmpty) {
stdout.writeln(junit);
} else if (json.isNotEmpty) {
stdout.writeln(json);
} else if (report.isNotEmpty) {
stdout.write(report);
if (!report.endsWith('\n')) stdout.writeln();
} else if (knownFailure.isNotEmpty) {
stderr.writeln(knownFailure);
} else {
_writeProcessStreams(testRun);
}
} else if (!verbose && testRun.exitCode != 0) {
_writeProcessStreams(testRun);
} else if (verbose) {
// Output was already streamed live.
} else {
final out = testRun.stdout?.toString() ?? '';
final rawJson = extractJsonReport(out);
final json = jsonReport ? rawJson : '';
final junit = junitReport ? extractJunitReport(out) : '';
final report = junit.isNotEmpty
? junit
: json.isNotEmpty
? json
: extractSuiteReport(
out,
includeScreenTracker: !streamLiveOutput,
);
if (report.isNotEmpty) {
stdout.write(report);
if (!report.endsWith('\n')) stdout.writeln();
} else {
_writeProcessStreams(testRun);
}
final err = testRun.stderr?.toString() ?? '';
if (err.isNotEmpty && !isBenignFlutterTestStderr(err)) {
stderr.write(err);
}
}
final machineResult = _runResultFromProcessOutput(testRun);
if (runSerial && machineResult != null) {
if (machineReport) {
await _recordHistory(appDir, machineResult);
} else {
await _withHtmlReport(appDir, machineResult);
}
}
exitCode = machineResult == null
? (testRun.exitCode == 0 ? 0 : 1)
: (machineResult.failedCount == 0 ? 0 : 1);
} on StateError catch (error) {
stderr.writeln(error.message);
exitCode = 3;
} finally {
// Serial timer-rewrite runs also use a worker sandbox; always drop leftovers.
_cleanWorkerDirectories(appDir);
patcher.restore();
artifactLock.release();
}
exit(exitCode);
}