run method
Implementation
Future<EnsembleTestDoctorResult> run() async {
final lines = <String>['Ensemble test runner doctor'];
var hasErrors = false;
void ok(String message) => lines.add('[OK] $message');
void warn(String message) => lines.add('[WARN] $message');
void error(String message) {
hasErrors = true;
lines.add('[ERROR] $message');
}
final pubspecFile = File(p.join(appDir, 'pubspec.yaml'));
if (!pubspecFile.existsSync()) {
error('No pubspec.yaml found in $appDir');
return EnsembleTestDoctorResult(lines: lines, hasErrors: hasErrors);
}
ok('Found pubspec.yaml');
final pubspec = pubspecFile.readAsStringSync();
if (pubspec.contains('ensemble_test_runner:')) {
ok('pubspec.yaml includes ensemble_test_runner');
} else {
warn('pubspec.yaml does not list ensemble_test_runner in dependencies');
}
final configFile = File(p.join(appDir, 'ensemble', 'ensemble-config.yaml'));
if (!configFile.existsSync()) {
error('Missing ensemble/ensemble-config.yaml');
return EnsembleTestDoctorResult(lines: lines, hasErrors: hasErrors);
}
ok('Found ensemble/ensemble-config.yaml');
final dynamic config = loadYaml(configFile.readAsStringSync());
if (config is! YamlMap) {
error('ensemble-config.yaml root must be a map');
return EnsembleTestDoctorResult(lines: lines, hasErrors: hasErrors);
}
final definitions = config['definitions'];
final local = definitions is YamlMap ? definitions['local'] : null;
if (local is! YamlMap) {
error('ensemble-config.yaml must define definitions.local');
return EnsembleTestDoctorResult(lines: lines, hasErrors: hasErrors);
}
final appPath = local['path']?.toString();
final appHome = local['appHome']?.toString();
if (appPath == null || appPath.isEmpty) {
error('definitions.local.path is required');
return EnsembleTestDoctorResult(lines: lines, hasErrors: hasErrors);
}
if (appHome == null || appHome.isEmpty) {
error('definitions.local.appHome is required');
}
ok('Using local app path $appPath');
final appPathOnDisk = Directory(p.join(appDir, appPath));
if (!appPathOnDisk.existsSync()) {
error('definitions.local.path does not exist: $appPath');
return EnsembleTestDoctorResult(lines: lines, hasErrors: hasErrors);
}
final testsDirRelative =
p.posix.join(_withoutTrailingSlash(appPath), 'tests');
final testsDir = Directory(p.join(appDir, testsDirRelative));
if (!testsDir.existsSync()) {
error(
'No declarative tests found. Add *.test.yaml files under $testsDirRelative/',
);
return EnsembleTestDoctorResult(lines: lines, hasErrors: hasErrors);
}
final testFiles = testsDir
.listSync(recursive: true)
.whereType<File>()
.where((file) => file.path.endsWith('.test.yaml'))
.toList()
..sort((a, b) => a.path.compareTo(b.path));
if (testFiles.isEmpty) {
error(
'No declarative tests found. Add *.test.yaml files under $testsDirRelative/',
);
return EnsembleTestDoctorResult(lines: lines, hasErrors: hasErrors);
}
ok('Found ${testFiles.length} YAML test file(s)');
final testConfigFile = File(p.join(testsDir.path, 'config.yaml'));
if (testConfigFile.existsSync()) {
final relativePath = p.relative(testConfigFile.path, from: appDir);
final content = testConfigFile.readAsStringSync();
if (!content.contains(hostedConfigSchemaUrl)) {
warn('$relativePath does not reference the hosted config schema URL');
}
try {
EnsembleTestParser.parseConfigString(
content,
sourcePath: relativePath,
);
ok('Found tests/config.yaml');
} catch (failure) {
error('$relativePath: $failure');
}
}
final ids = <String, String>{};
final sessions = <String, String>{};
final referencedWidgetIds = <String>{};
for (final file in testFiles) {
final relativePath = p.relative(file.path, from: appDir);
final content = file.readAsStringSync();
if (!content.contains(hostedSchemaUrl)) {
warn('$relativePath does not reference the hosted schema URL');
}
final test = _parseDoctorTest(content);
if (test.error != null) {
error('$relativePath: ${test.error}');
continue;
}
try {
final existing = ids[test.id];
if (existing != null) {
error(
'Duplicate test id "${test.id}" in $existing and $relativePath');
} else {
ids[test.id] = relativePath;
}
if (test.session != null) {
sessions[test.id] = test.session!;
}
referencedWidgetIds.addAll(test.referencedWidgetIds);
} catch (failure) {
error('$relativePath: $failure');
}
}
for (final entry in sessions.entries) {
if (!ids.containsKey(entry.value)) {
error(
'Test "${entry.key}" references unknown session "${entry.value}"');
}
}
final knownWidgetIds = _collectKnownWidgetIds(appPathOnDisk);
if (knownWidgetIds.isNotEmpty) {
final missing = referencedWidgetIds
.where((id) => !knownWidgetIds.contains(id))
.toList()
..sort();
if (missing.isNotEmpty) {
warn(
'Could not find obvious widget id/testId definitions for: ${missing.join(", ")}',
);
} else {
ok('All obvious widget id/testId references were found');
}
}
if (!hasErrors) {
ok('Doctor completed without blocking errors');
}
return EnsembleTestDoctorResult(lines: lines, hasErrors: hasErrors);
}