blob: 8677ece10584e92aad0e94ce5ab8ca08f7f23d5c [file] [log] [blame]
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. 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:convert';
import 'package:path/path.dart' as p;
import 'package:test_descriptor/test_descriptor.dart' as d;
import 'package:test/test.dart';
import 'package:test_core/src/runner/version.dart';
/// Asserts that the outputs from running tests with a JSON reporter match the
/// given expectations.
///
/// Verifies that [outputLines] matches each set of matchers in [expected],
/// includes the [testPid] from the test process, and ends with [done].
Future expectJsonReport(List<String> outputLines, int testPid,
List<List<dynamic /*Map|Matcher*/ >> expected, Map done) async {
// Ensure the output is of the same length, including start, done and all
// suites messages.
expect(outputLines.length, equals(expected.fold(3, (a, m) => a + m.length)),
reason: 'Expected $outputLines to match $expected.');
dynamic decodeLine(String l) =>
jsonDecode(l)..remove('time')..remove('stackTrace');
// Should contain all suites message.
expect(outputLines.map(decodeLine), containsAll([allSuitesJson()]));
// A single start event is emitted first.
final _start = {
'type': 'start',
'protocolVersion': '0.1.1',
'runnerVersion': testVersion,
'pid': testPid,
};
expect(decodeLine(outputLines.first), equals(_start));
// A single done event is emmited last.
expect(decodeLine(outputLines.last), equals(done));
for (var value in expected) {
expect(outputLines.map(decodeLine), containsAllInOrder(value));
}
}
/// Returns the event emitted by the JSON reporter providing information about
/// all suites.
///
/// The [count] defaults to 1.
Map allSuitesJson({int count}) {
return {'type': 'allSuites', 'count': count ?? 1};
}
/// Returns the event emitted by the JSON reporter indicating that a suite has
/// begun running.
///
/// The [platform] defaults to `"vm"`, the [path] defaults to `"test.dart"`.
Map suiteJson(int id, {String platform, String path}) {
return {
'type': 'suite',
'suite': {
'id': id,
'platform': platform ?? 'vm',
'path': path ?? 'test.dart'
}
};
}
/// Returns the event emitted by the JSON reporter indicating that a group has
/// begun running.
///
/// If [skip] is `true`, the group is expected to be marked as skipped without a
/// reason. If it's a [String], the group is expected to be marked as skipped
/// with that reason.
///
/// The [testCount] parameter indicates the number of tests in the group. It
/// defaults to 1.
Map groupJson(int id,
{String name,
int suiteID,
int parentID,
skip,
int testCount,
int line,
int column}) {
if ((line == null) != (column == null)) {
throw ArgumentError(
'line and column must either both be null or both be passed');
}
return {
'type': 'group',
'group': {
'id': id,
'name': name,
'suiteID': suiteID ?? 0,
'parentID': parentID,
'metadata': metadataJson(skip: skip),
'testCount': testCount ?? 1,
'line': line,
'column': column,
'url': line == null
? null
: p.toUri(p.join(d.sandbox, 'test.dart')).toString()
}
};
}
/// Returns the event emitted by the JSON reporter indicating that a test has
/// begun running.
///
/// If [parentIDs] is passed, it's the IDs of groups containing this test. If
/// [skip] is `true`, the test is expected to be marked as skipped without a
/// reason. If it's a [String], the test is expected to be marked as skipped
/// with that reason.
Map testStartJson(int id, String name,
{int suiteID,
Iterable<int> groupIDs,
int line,
int column,
String url,
skip,
int root_line,
int root_column,
String root_url}) {
if ((line == null) != (column == null)) {
throw ArgumentError(
'line and column must either both be null or both be passed');
}
url ??=
line == null ? null : p.toUri(p.join(d.sandbox, 'test.dart')).toString();
var expected = {
'type': 'testStart',
'test': {
'id': id,
'name': name,
'suiteID': suiteID ?? 0,
'groupIDs': groupIDs ?? [2],
'metadata': metadataJson(skip: skip),
'line': line,
'column': column,
'url': url,
}
};
var testObj = expected['test'] as Map<String, dynamic>;
if (root_line != null) {
testObj['root_line'] = root_line;
}
if (root_column != null) {
testObj['root_column'] = root_column;
}
if (root_url != null) {
testObj['root_url'] = root_url;
}
return expected;
}
/// Returns the event emitted by the JSON reporter indicating that a test
/// printed [message].
Matcher printJson(int id, dynamic /*String|Matcher*/ message, {String type}) {
return allOf(
hasLength(4),
containsPair('type', 'print'),
containsPair('testID', id),
containsPair('message', message),
containsPair('messageType', type ?? 'print'),
);
}
/// Returns the event emitted by the JSON reporter indicating that a test
/// emitted [error].
///
/// The [isFailure] parameter indicates whether the error was a [TestFailure] or
/// not.
Map errorJson(int id, String error, {bool isFailure = false}) {
return {
'type': 'error',
'testID': id,
'error': error,
'isFailure': isFailure
};
}
/// Returns the event emitted by the JSON reporter indicating that a test
/// finished.
///
/// The [result] parameter indicates the result of the test. It defaults to
/// `"success"`.
///
/// The [hidden] parameter indicates whether the test should not be displayed
/// after finishing. The [skipped] parameter indicates whether the test was
/// skipped.
Map testDoneJson(int id,
{String result, bool hidden = false, bool skipped = false}) {
result ??= 'success';
return {
'type': 'testDone',
'testID': id,
'result': result,
'hidden': hidden,
'skipped': skipped
};
}
/// Returns the event emitted by the JSON reporter indicating that the entire
/// run finished.
Map doneJson({bool success = true}) => {'type': 'done', 'success': success};
/// Returns the serialized metadata corresponding to [skip].
Map metadataJson({skip}) {
if (skip == true) {
return {'skip': true, 'skipReason': null};
} else if (skip is String) {
return {'skip': true, 'skipReason': skip};
} else {
return {'skip': false, 'skipReason': null};
}
}