diff --git a/lib/src/collect.dart b/lib/src/collect.dart index a2249a70..c1d337b5 100644 --- a/lib/src/collect.dart +++ b/lib/src/collect.dart @@ -258,11 +258,10 @@ Future _processFunction(VmService service, IsolateRef isolateRef, Script script, FuncRef funcRef, HitMap hits) async { final func = await service.getObject(isolateRef.id!, funcRef.id!) as Func; final location = func.location; - if (location != null) { + if (!(func.implicit ?? false) && location != null) { final funcName = await _getFuncName(service, isolateRef, func); final tokenPos = location.tokenPos!; final line = _getLineFromTokenPos(script, tokenPos); - if (line == null) { stderr.writeln( 'tokenPos $tokenPos in function ${funcRef.name} has no line mapping ' diff --git a/test/collect_coverage_test.dart b/test/collect_coverage_test.dart index 26c1fe74..91d64801 100644 --- a/test/collect_coverage_test.dart +++ b/test/collect_coverage_test.dart @@ -157,11 +157,10 @@ void main() { 68: 1 }; expect(isolateFile?.lineHits, expectedHits); - expect(isolateFile?.funcHits, {11: 1, 19: 1, 21: 0, 23: 1, 28: 1, 38: 1}); + expect(isolateFile?.funcHits, {11: 1, 19: 1, 23: 1, 28: 1, 38: 1}); expect(isolateFile?.funcNames, { 11: 'fooSync', 19: 'BarClass.BarClass', - 21: 'BarClass.x=', 23: 'BarClass.baz', 28: 'fooAsync', 38: 'isolateTask' @@ -225,11 +224,10 @@ void main() { 68: 1 }; expect(isolateFile?.lineHits, expectedHits); - expect(isolateFile?.funcHits, {11: 1, 19: 1, 21: 0, 23: 1, 28: 1, 38: 1}); + expect(isolateFile?.funcHits, {11: 1, 19: 1, 23: 1, 28: 1, 38: 1}); expect(isolateFile?.funcNames, { 11: 'fooSync', 19: 'BarClass.BarClass', - 21: 'BarClass.x=', 23: 'BarClass.baz', 28: 'fooAsync', 38: 'isolateTask' diff --git a/test/function_coverage_test.dart b/test/function_coverage_test.dart new file mode 100644 index 00000000..a653abde --- /dev/null +++ b/test/function_coverage_test.dart @@ -0,0 +1,125 @@ +// Copyright (c) 2022, 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:async'; +import 'dart:convert' show json, LineSplitter, utf8; +import 'dart:io'; + +import 'package:coverage/coverage.dart'; +import 'package:coverage/src/util.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; + +import 'test_util.dart'; + +final _collectAppPath = p.join('bin', 'collect_coverage.dart'); +final _funcCovApp = p.join('test', 'test_files', 'function_coverage_app.dart'); +final _sampleAppFileUri = p.toUri(p.absolute(_funcCovApp)).toString(); + +void main() { + test('Function coverage', () async { + final resultString = await _collectCoverage(); + final jsonResult = json.decode(resultString) as Map; + final coverage = jsonResult['coverage'] as List; + final hitMap = await HitMap.parseJson( + coverage.cast>(), + ); + + // function_coverage_app.dart. + expect(hitMap, contains(_sampleAppFileUri)); + final isolateFile = hitMap[_sampleAppFileUri]!; + expect(isolateFile.funcHits, { + 7: 1, + 12: 0, // TODO(#398): This abstract method should be ignored. + 16: 1, + 21: 1, + 25: 1, + 29: 1, + 36: 1, + 42: 1, + 47: 1, + }); + expect(isolateFile.funcNames, { + 7: 'normalFunction', + 12: 'BaseClass.abstractMethod', + 16: 'SomeClass.SomeClass', + 21: 'SomeClass.normalMethod', + 25: 'SomeClass.staticMethod', + 29: 'SomeClass.abstractMethod', + 36: 'SomeExtension.extensionMethod', + 42: 'OtherClass.otherMethod', + 47: 'main', + }); + + // test_library.dart. + final testLibraryPath = + p.absolute(p.join('test', 'test_files', 'test_library.dart')); + final testLibraryUri = p.toUri(testLibraryPath).toString(); + expect(hitMap, contains(testLibraryUri)); + final libraryfile = hitMap[testLibraryUri]!; + expect(libraryfile.funcHits, {9: 1}); + expect(libraryfile.funcNames, {9: 'libraryFunction'}); + + // test_library_part.dart. + final testLibraryPartPath = + p.absolute(p.join('test', 'test_files', 'test_library_part.dart')); + final testLibraryPartUri = p.toUri(testLibraryPartPath).toString(); + expect(hitMap, contains(testLibraryPartUri)); + // TODO(#399): Fix handling of part files then re-enable this check. + // final libraryPartFile = hitMap[testLibraryPartUri]!; + // expect(libraryPartFile.funcHits, {7: 1}); + // expect(libraryPartFile.funcNames, {7: 'otherLibraryFunction'}); + }); +} + +Future _collectCoverage() async { + expect(FileSystemEntity.isFileSync(_funcCovApp), isTrue); + + final openPort = await getOpenPort(); + + // Run the sample app with the right flags. + final sampleProcess = await Process.start(Platform.resolvedExecutable, [ + '--enable-vm-service=$openPort', + '--pause_isolates_on_exit', + _funcCovApp + ]); + + // Capture the VM service URI. + final serviceUriCompleter = Completer(); + sampleProcess.stdout + .transform(utf8.decoder) + .transform(LineSplitter()) + .listen((line) { + if (!serviceUriCompleter.isCompleted) { + final serviceUri = extractVMServiceUri(line); + if (serviceUri != null) { + serviceUriCompleter.complete(serviceUri); + } + } + }); + final serviceUri = await serviceUriCompleter.future; + + // Run the collection tool. + final toolResult = await Process.run(Platform.resolvedExecutable, [ + _collectAppPath, + '--function-coverage', + '--uri', + '$serviceUri', + '--resume-isolates', + '--wait-paused' + ]).timeout(timeout, onTimeout: () { + throw 'We timed out waiting for the tool to finish.'; + }); + + print(toolResult.stderr); + if (toolResult.exitCode != 0) { + print(toolResult.stdout); + fail('Tool failed with exit code ${toolResult.exitCode}.'); + } + + await sampleProcess.exitCode; + await sampleProcess.stderr.drain(); + + return toolResult.stdout as String; +} diff --git a/test/test_files/function_coverage_app.dart b/test/test_files/function_coverage_app.dart new file mode 100644 index 00000000..e3ffe096 --- /dev/null +++ b/test/test_files/function_coverage_app.dart @@ -0,0 +1,56 @@ +// Copyright (c) 2022, 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 'test_library.dart'; + +int normalFunction() { + return 123; +} + +abstract class BaseClass { + int abstractMethod(); +} + +class SomeClass extends BaseClass { + SomeClass() : x = 123; + + // Creates an implicit getter and setter that should be ignored. + int x; + + int normalMethod() { + return 123; + } + + static int staticMethod() { + return 123; + } + + @override + int abstractMethod() { + return 123; + } +} + +extension SomeExtension on SomeClass { + int extensionMethod() { + return 123; + } +} + +class OtherClass { + int otherMethod() { + return 123; + } +} + +void main() { + print(normalFunction()); + print(SomeClass().normalMethod()); + print(SomeClass.staticMethod()); + print(SomeClass().extensionMethod()); + print(SomeClass().abstractMethod()); + print(OtherClass().otherMethod()); + print(libraryFunction()); + print(otherLibraryFunction()); +} diff --git a/test/test_files/test_library.dart b/test/test_files/test_library.dart new file mode 100644 index 00000000..33542942 --- /dev/null +++ b/test/test_files/test_library.dart @@ -0,0 +1,11 @@ +// Copyright (c) 2022, 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. + +library test_library; + +part 'test_library_part.dart'; + +int libraryFunction() { + return 123; +} diff --git a/test/test_files/test_library_part.dart b/test/test_files/test_library_part.dart new file mode 100644 index 00000000..d587ff25 --- /dev/null +++ b/test/test_files/test_library_part.dart @@ -0,0 +1,9 @@ +// Copyright (c) 2022, 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. + +part of test_library; + +int otherLibraryFunction() { + return 123; +}