From a19fd72db549ddc5d07998d38671a6843dcad6f9 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Wed, 22 Jul 2020 18:40:12 -0700 Subject: [PATCH] [flutter_tools] check in script for generating per library unit coverage (#61996) Allows generating a per-library coverage summary like https://gist.github.com/jonahwilliams/f298381c3fb9f472b2dfe54b82a20a88 --- .../integration.shard/unit_coverage_test.dart | 68 +++++++++++++++++++ .../flutter_tools/tool/unit_coverage.dart | 51 ++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 packages/flutter_tools/test/integration.shard/unit_coverage_test.dart create mode 100644 packages/flutter_tools/tool/unit_coverage.dart diff --git a/packages/flutter_tools/test/integration.shard/unit_coverage_test.dart b/packages/flutter_tools/test/integration.shard/unit_coverage_test.dart new file mode 100644 index 00000000000..7433d5422fa --- /dev/null +++ b/packages/flutter_tools/test/integration.shard/unit_coverage_test.dart @@ -0,0 +1,68 @@ +// 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 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/io.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:process/process.dart'; + +import '../src/common.dart'; +import 'test_utils.dart'; + +void main() { + Directory tempDir; + + setUp(() async { + tempDir = createResolvedTempDirectorySync('unit_coverage_test.'); + }); + + tearDown(() async { + tryToDelete(tempDir); + }); + + test('Can parse and output summaries for code coverage', () async { + final File coverageFile = tempDir.childFile('info.lcov') + ..writeAsStringSync(''' +SF:lib/src/artifacts.dart +DA:15,10 +DA:17,7 +DA:19,7 +DA:20,7 +DA:22,7 +DA:324,10 +DA:724,0 +DA:727,2 +DA:729,3 +DA:732,0 +DA:737,1 +LF:443 +LH:292 +end_of_record +SF:lib/src/base/common.dart +DA:13,7 +DA:14,7 +DA:22,7 +DA:27,3 +DA:28,6 +DA:40,7 +LF:6 +LH:6 +end_of_record +'''); + + final String dartScript = globals.fs.path.join(getFlutterRoot(), 'bin', 'dart'); + final String coverageScript = globals.fs.path.join(getFlutterRoot(), 'packages', 'flutter_tools', 'tool', 'unit_coverage.dart'); + final ProcessResult result = await const LocalProcessManager().run([ + dartScript, + coverageScript, + coverageFile.path, + ]); + + // May contain other output if building flutter tool. + expect(result.stdout.toString().split('\n'), containsAll([ + contains('lib/src/artifacts.dart: 81.82%'), + contains('lib/src/base/common.dart: 100.00%'), + ])); + }); +} diff --git a/packages/flutter_tools/tool/unit_coverage.dart b/packages/flutter_tools/tool/unit_coverage.dart new file mode 100644 index 00000000000..f55b4f48b2a --- /dev/null +++ b/packages/flutter_tools/tool/unit_coverage.dart @@ -0,0 +1,51 @@ +// 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:io'; + +/// Produces a per-library coverage summary when fed an lcov file, sorted by +/// increasing code coverage percentage. +/// +/// Usage: `dart tool/unit_coverage lcov.info` +void main(List args) { + if (args.isEmpty || args.length > 1) { + print('Usage: dart tool/unit_coverage lcov.info'); + return; + } + final List lines = File(args.single).readAsLinesSync(); + final List coverages = []; + Coverage currentCoverage; + + for (final String line in lines) { + if (line.startsWith('SF:')) { + final String library = line.split('SF:')[1]; + currentCoverage = Coverage()..library = library; + coverages.add(currentCoverage); + } + if (line.startsWith('DA')) { + currentCoverage.totalLines += 1; + if (!line.endsWith(',0')) { + currentCoverage.testedLines += 1; + } + } + if (line == 'end_of_record') { + currentCoverage = null; + } + } + coverages.sort((Coverage left, Coverage right) { + final double leftPercent = left.testedLines / left.totalLines; + final double rightPercent = right.testedLines / right.totalLines; + return leftPercent.compareTo(rightPercent); + }); + for (final Coverage coverage in coverages) { + final String coveragePercent = (coverage.testedLines / coverage.totalLines * 100).toStringAsFixed(2); + print('${coverage.library}: $coveragePercent%'); + } +} + +class Coverage { + String library; + int totalLines = 0; + int testedLines = 0; +}