blob: 9fe70bd581f4a2dd8bebcf18153cc568ce11aada [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'dart:io';
class TestSpecs {
TestSpecs({
required this.path,
required this.startTime,
});
final String path;
int startTime;
int? _endTime;
int get milliseconds => endTime - startTime;
set endTime(int value) {
_endTime = value;
}
int get endTime => _endTime ?? 0;
String toJson() {
return json.encode(
<String, String>{'path': path, 'runtime': milliseconds.toString()}
);
}
}
class TestFileReporterResults {
TestFileReporterResults._({
required this.allTestSpecs,
required this.hasFailedTests,
required this.errors,
});
/// Intended to parse the output file of `dart test --file-reporter json:file_name
factory TestFileReporterResults.fromFile(File metrics) {
if (!metrics.existsSync()) {
throw Exception('${metrics.path} does not exist');
}
final Map<int, TestSpecs> testSpecs = <int, TestSpecs>{};
bool hasFailedTests = true;
final List<String> errors = <String>[];
for (final String metric in metrics.readAsLinesSync()) {
/// Using print within a test adds the printed content to the json file report
/// as \u0000 making the file parsing step fail. The content of the json file
/// is expected to be a json dictionary per line and the following line removes
/// all the additional content at the beginning of the line until it finds the
/// first opening curly bracket.
// TODO(godofredoc): remove when https://github.com/flutter/flutter/issues/145553 is fixed.
final String sanitizedMetric = metric.replaceAll(RegExp(r'$.*{'), '{');
final Map<String, Object?> entry = json.decode(sanitizedMetric) as Map<String, Object?>;
if (entry.containsKey('suite')) {
final Map<String, Object?> suite = entry['suite']! as Map<String, Object?>;
addTestSpec(suite, entry['time']! as int, testSpecs);
} else if (isMetricDone(entry, testSpecs)) {
final Map<String, Object?> group = entry['group']! as Map<String, Object?>;
final int suiteID = group['suiteID']! as int;
addMetricDone(suiteID, entry['time']! as int, testSpecs);
} else if (entry.containsKey('error')) {
final String stackTrace = entry.containsKey('stackTrace') ? entry['stackTrace']! as String : '';
errors.add('${entry['error']}\n $stackTrace');
} else if (entry.containsKey('success') && entry['success'] == true) {
hasFailedTests = false;
}
}
return TestFileReporterResults._(allTestSpecs: testSpecs, hasFailedTests: hasFailedTests, errors: errors);
}
final Map<int, TestSpecs> allTestSpecs;
final bool hasFailedTests;
final List<String> errors;
static void addTestSpec(Map<String, Object?> suite, int time, Map<int, TestSpecs> allTestSpecs) {
allTestSpecs[suite['id']! as int] = TestSpecs(
path: suite['path']! as String,
startTime: time,
);
}
static void addMetricDone(int suiteID, int time, Map<int, TestSpecs> allTestSpecs) {
final TestSpecs testSpec = allTestSpecs[suiteID]!;
testSpec.endTime = time;
}
static bool isMetricDone(Map<String, Object?> entry, Map<int, TestSpecs> allTestSpecs) {
if (entry.containsKey('group') && entry['type']! as String == 'group') {
final Map<String, Object?> group = entry['group']! as Map<String, Object?>;
return allTestSpecs.containsKey(group['suiteID']! as int);
}
return false;
}
}