forked from flutter/flutter
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
initial work on coverage generating script for tool (flutter#29494)
- Loading branch information
1 parent
f5672b9
commit 65f4599
Showing
4 changed files
with
178 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
// Copyright 2019 The Chromium 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 'package:args/args.dart'; | ||
import 'package:flutter_tools/src/context_runner.dart'; | ||
import 'package:flutter_tools/src/project.dart'; | ||
import 'package:flutter_tools/src/test/coverage_collector.dart'; | ||
import 'package:pool/pool.dart'; | ||
import 'package:path/path.dart' as path; | ||
|
||
final ArgParser argParser = ArgParser() | ||
..addOption('output-html', | ||
defaultsTo: 'coverage/report.html', | ||
help: 'The output path for the genhtml report.' | ||
) | ||
..addOption('output-lcov', | ||
defaultsTo: 'coverage/lcov.info', | ||
help: 'The output path for the lcov data.' | ||
) | ||
..addOption('test-directory', | ||
defaultsTo: 'test/', | ||
help: 'The path to the test directory.' | ||
) | ||
..addOption('packages', | ||
defaultsTo: '.packages', | ||
help: 'The path to the .packages file.' | ||
) | ||
..addOption('genhtml', | ||
defaultsTo: 'genhtml', | ||
help: 'The genhtml executable.'); | ||
|
||
|
||
/// Generates an html coverage report for the flutter_tool. | ||
/// | ||
/// Example invocation: | ||
/// | ||
/// dart tool/tool_coverage.dart --packages=.packages --test-directory=test | ||
Future<void> main(List<String> arguments) async { | ||
final ArgResults argResults = argParser.parse(arguments); | ||
await runInContext(() async { | ||
final CoverageCollector coverageCollector = CoverageCollector( | ||
flutterProject: await FlutterProject.current(), | ||
); | ||
/// A temp directory to create synthetic test files in. | ||
final Directory tempDirectory = Directory.systemTemp.createTempSync('_flutter_coverage') | ||
..createSync(); | ||
final String flutterRoot = File(Platform.script.toFilePath()).parent.parent.parent.parent.path; | ||
await ToolCoverageRunner(tempDirectory, coverageCollector, flutterRoot, argResults).collectCoverage(); | ||
}); | ||
} | ||
|
||
class ToolCoverageRunner { | ||
ToolCoverageRunner( | ||
this.tempDirectory, | ||
this.coverageCollector, | ||
this.flutterRoot, | ||
this.argResults, | ||
); | ||
|
||
final ArgResults argResults; | ||
final Pool pool = Pool(Platform.numberOfProcessors); | ||
final Directory tempDirectory; | ||
final CoverageCollector coverageCollector; | ||
final String flutterRoot; | ||
|
||
Future<void> collectCoverage() async { | ||
final List<Future<void>> pending = <Future<void>>[]; | ||
|
||
final Directory testDirectory = Directory(argResults['test-directory']); | ||
final List<FileSystemEntity> fileSystemEntities = testDirectory.listSync(recursive: true); | ||
for (FileSystemEntity fileSystemEntity in fileSystemEntities) { | ||
if (!fileSystemEntity.path.endsWith('_test.dart')) { | ||
continue; | ||
} | ||
pending.add(_runTest(fileSystemEntity)); | ||
} | ||
await Future.wait(pending); | ||
|
||
final String lcovData = await coverageCollector.finalizeCoverage(); | ||
final String outputLcovPath = argResults['output-lcov']; | ||
final String outputHtmlPath = argResults['output-html']; | ||
final String genHtmlExecutable = argResults['genhtml']; | ||
File(outputLcovPath) | ||
..createSync(recursive: true) | ||
..writeAsStringSync(lcovData); | ||
await Process.run(genHtmlExecutable, <String>[outputLcovPath, '-o', outputHtmlPath], runInShell: true); | ||
} | ||
|
||
// Creates a synthetic test file to wrap the test main in a group invocation. | ||
// This will set up several fields used by the test methods on the context. Normally | ||
// this would be handled automatically by the test runner, but since we're executing | ||
// the files directly with dart we need to handle it manually. | ||
String _createTest(File testFile) { | ||
final File fakeTest = File(path.join(tempDirectory.path, testFile.path)) | ||
..createSync(recursive: true) | ||
..writeAsStringSync(''' | ||
import "package:test/test.dart"; | ||
import "${path.absolute(testFile.path)}" as entrypoint; | ||
void main() { | ||
group('', entrypoint.main); | ||
} | ||
'''); | ||
return fakeTest.path; | ||
} | ||
|
||
Future<void> _runTest(File testFile) async { | ||
final PoolResource resource = await pool.request(); | ||
final String testPath = _createTest(testFile); | ||
final int port = await _findPort(); | ||
final Uri coverageUri = Uri.parse('http://127.0.0.1:$port'); | ||
final Completer<void> completer = Completer<void>(); | ||
final String packagesPath = argResults['packages']; | ||
final Process testProcess = await Process.start( | ||
Platform.resolvedExecutable, | ||
<String>[ | ||
'--packages=$packagesPath', | ||
'--pause-isolates-on-exit', | ||
'--enable-asserts', | ||
'--enable-vm-service=${coverageUri.port}', | ||
testPath, | ||
], | ||
runInShell: true, | ||
environment: <String, String>{ | ||
'FLUTTER_ROOT': flutterRoot, | ||
}).timeout(const Duration(seconds: 30)); | ||
testProcess.stdout | ||
.transform(utf8.decoder) | ||
.transform(const LineSplitter()) | ||
.listen((String line) { | ||
print(line); | ||
if (line.contains('All tests passed') || line.contains('Some tests failed')) { | ||
completer.complete(null); | ||
} | ||
}); | ||
try { | ||
await completer.future; | ||
await coverageCollector.collectCoverage(testProcess, coverageUri).timeout(const Duration(seconds: 30)); | ||
testProcess?.kill(); | ||
} on TimeoutException { | ||
print('Failed to collect coverage for ${testFile.path} after 30 seconds'); | ||
} finally { | ||
resource.release(); | ||
} | ||
} | ||
|
||
Future<int> _findPort() async { | ||
int port = 0; | ||
ServerSocket serverSocket; | ||
try { | ||
serverSocket = await ServerSocket.bind(InternetAddress.loopbackIPv4.address, 0); | ||
port = serverSocket.port; | ||
} catch (e) { | ||
// Failures are signaled by a return value of 0 from this function. | ||
print('_findPort failed: $e'); | ||
} | ||
if (serverSocket != null) { | ||
await serverSocket.close(); | ||
} | ||
return port; | ||
} | ||
} |