// 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({'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 testSpecs = {}; bool hasFailedTests = true; final List errors = []; 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 entry = json.decode(sanitizedMetric) as Map; if (entry.containsKey('suite')) { final Map suite = entry['suite']! as Map; addTestSpec(suite, entry['time']! as int, testSpecs); } else if (isMetricDone(entry, testSpecs)) { final Map group = entry['group']! as Map; 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 allTestSpecs; final bool hasFailedTests; final List errors; static void addTestSpec(Map suite, int time, Map allTestSpecs) { allTestSpecs[suite['id']! as int] = TestSpecs(path: suite['path']! as String, startTime: time); } static void addMetricDone(int suiteID, int time, Map allTestSpecs) { final TestSpecs testSpec = allTestSpecs[suiteID]!; testSpec.endTime = time; } static bool isMetricDone(Map entry, Map allTestSpecs) { if (entry.containsKey('group') && entry['type']! as String == 'group') { final Map group = entry['group']! as Map; return allTestSpecs.containsKey(group['suiteID']! as int); } return false; } }