blob: fd7dc5589ce866bfef3cf7e68c51cdea7f8acbd6 [file] [log] [blame]
// Copyright 2016 The Vanadium 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:async';
import 'dart:convert';
import 'dart:io';
import '../base/common.dart';
import '../mobile/device.dart';
import '../mobile/device_spec.dart';
import '../mobile/android.dart';
import '../test/coverage_collector.dart';
import '../globals.dart';
import '../util.dart';
class MDTestRunner {
List<Process> appProcesses;
MDTestRunner() {
appProcesses = <Process>[];
}
/// Invoke runApp function for each device spec to device mapping in parallel
Future<int> runAllApps(Map<DeviceSpec, Device> deviceMapping) async {
List<Future<int>> runAppList = <Future<int>>[];
for (DeviceSpec deviceSpec in deviceMapping.keys) {
Device device = deviceMapping[deviceSpec];
runAppList.add(runApp(deviceSpec, device));
}
int res = 0;
List<int> results = await Future.wait(runAppList);
for (int result in results)
res += result;
return res == 0 ? 0 : 1;
}
/// Create a process that runs 'flutter run ...' command which installs and
/// starts the app on the device. The function finds a observatory port
/// through the process output. If no observatory port is found, then report
/// error.
Future<int> runApp(DeviceSpec deviceSpec, Device device) async {
if (await unlockDevice(device) != 0) {
printError('Device ${device.id} fails to wake up.');
return 1;
}
Process process = await Process.start(
'flutter',
['run', '-d', '${device.id}', '--target=${deviceSpec.appPath}'],
workingDirectory: deviceSpec.appRootPath
);
appProcesses.add(process);
Stream lineStream = process.stdout
.transform(new Utf8Decoder())
.transform(new LineSplitter());
RegExp portPattern = new RegExp(r'Observatory listening on (http.*)');
await for (var line in lineStream) {
print(line.toString().trim());
Match portMatch = portPattern.firstMatch(line.toString());
if (portMatch != null) {
deviceSpec.observatoryUrl = portMatch.group(1);
break;
}
}
process.stderr.drain();
if (deviceSpec.observatoryUrl == null) {
printError('No observatory url is found.');
return 1;
}
return 0;
}
/// Run all tests
Future<int> runAllTests(Iterable<String> testPaths) async {
int result = 0;
for (String testPath in testPaths) {
result += await runTest(testPath);
}
return result == 0 ? 0 : 1;
}
/// Create a process and invoke 'dart testPath' to run the test script. After
/// test result is returned (either pass or fail), kill all app processes and
/// return the current process exit code
Future<int> runTest(String testPath) async {
Process process = await Process.start('dart', ['$testPath']);
RegExp testStopPattern = new RegExp(r'All tests passed|Some tests failed');
Stream stdoutStream = process.stdout
.transform(new Utf8Decoder())
.transform(new LineSplitter());
await for (var line in stdoutStream) {
print(line.toString().trim());
if (testStopPattern.hasMatch(line.toString()))
break;
}
process.stderr.drain();
return await process.exitCode;
}
/// Kill all app processes
Future<Null> killAppProcesses() async {
for (Process process in appProcesses) {
process.kill();
}
}
}
/// Create a coverage collector for each application and assign a coverage
/// collection task for the coverage collector
void buildCoverageCollectionTasks(
Map<DeviceSpec, Device> deviceMapping,
Map<String, CoverageCollector> collectorPool
) {
assert(collectorPool != null);
// Build app path to coverage collector mapping and add collection tasks
deviceMapping.keys.forEach((DeviceSpec spec) {
collectorPool.putIfAbsent(
spec.appRootPath,
() => new CoverageCollector()
).collectCoverage(spec.observatoryUrl);
});
}
/// Run coverage collection tasks for each application
Future<Null> runCoverageCollectionTasks(
Map<String, CoverageCollector> collectorPool
) async {
assert(collectorPool.isNotEmpty);
// Collect coverage for every application
for (CoverageCollector collector in collectorPool.values) {
await collector.finishPendingJobs();
}
}
/// Compute application code coverage and write coverage info in lcov format
Future<int> computeAppsCoverage(
Map<String, CoverageCollector> collectorPool,
String commandName
) async {
if (collectorPool.isEmpty)
return 1;
// Write coverage info to coverage/code_coverage folder under each
// application folder
for (String appRootPath in collectorPool.keys) {
CoverageCollector collector = collectorPool[appRootPath];
String coverageData = await collector.finalizeCoverage(appRootPath);
if (coverageData == null)
return 1;
String coveragePath = normalizePath(
appRootPath,
'$defaultCodeCoverageDirectoryPath',
'cov_${commandName}_${generateTimeStamp()}.info'
);
try {
// Write coverage info to code_coverage folder
new File(coveragePath)
..createSync(recursive: true)
..writeAsStringSync(coverageData, flush: true);
print('Writing code coverage to $coveragePath');
} on FileSystemException {
printError('Cannot write code coverage info to $coveragePath');
return 1;
}
}
return 0;
}