| // Copyright (c) 2015, 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. | |
| // ignore_for_file: only_throw_errors | |
| import 'dart:async'; | |
| import 'dart:math'; | |
| import 'package:coverage/src/util.dart'; | |
| import 'package:test/test.dart'; | |
| const _failCount = 5; | |
| const _delay = Duration(milliseconds: 10); | |
| void main() { | |
| test('retry', () async { | |
| var count = 0; | |
| final stopwatch = Stopwatch()..start(); | |
| Future failCountTimes() async { | |
| expect(stopwatch.elapsed, greaterThanOrEqualTo(_delay * count)); | |
| while (count < _failCount) { | |
| count++; | |
| throw 'not yet!'; | |
| } | |
| return 42; | |
| } | |
| final value = await retry(failCountTimes, _delay) as int; | |
| expect(value, 42); | |
| expect(count, _failCount); | |
| expect(stopwatch.elapsed, greaterThanOrEqualTo(_delay * count)); | |
| }); | |
| group('retry with timeout', () { | |
| test('if it finishes', () async { | |
| var count = 0; | |
| final stopwatch = Stopwatch()..start(); | |
| Future failCountTimes() async { | |
| expect(stopwatch.elapsed, greaterThanOrEqualTo(_delay * count)); | |
| while (count < _failCount) { | |
| count++; | |
| throw 'not yet!'; | |
| } | |
| return 42; | |
| } | |
| final safeTimoutDuration = _delay * _failCount * 10; | |
| final value = | |
| await retry(failCountTimes, _delay, timeout: safeTimoutDuration) | |
| as int; | |
| expect(value, 42); | |
| expect(count, _failCount); | |
| expect(stopwatch.elapsed, greaterThanOrEqualTo(_delay * count)); | |
| }); | |
| test('if it does not finish', () async { | |
| var count = 0; | |
| final stopwatch = Stopwatch()..start(); | |
| var caught = false; | |
| var countAfterError = 0; | |
| Future failCountTimes() async { | |
| if (caught) { | |
| countAfterError++; | |
| } | |
| expect(stopwatch.elapsed, greaterThanOrEqualTo(_delay * count)); | |
| count++; | |
| throw 'never'; | |
| } | |
| final unsafeTimeoutDuration = _delay * (_failCount / 2); | |
| try { | |
| await retry(failCountTimes, _delay, timeout: unsafeTimeoutDuration); | |
| // ignore: avoid_catching_errors | |
| } on StateError catch (e) { | |
| expect(e.message, 'Failed to complete within 25ms'); | |
| caught = true; | |
| expect( | |
| countAfterError, | |
| 0, | |
| reason: 'Execution should stop after a timeout', | |
| ); | |
| await Future<dynamic>.delayed(_delay * 3); | |
| expect(countAfterError, 0, reason: 'Even after a delay'); | |
| } | |
| expect(caught, isTrue); | |
| }); | |
| }); | |
| group('extractVMServiceUri', () { | |
| test('returns null when not found', () { | |
| expect(extractVMServiceUri('foo bar baz'), isNull); | |
| }); | |
| test('returns null for an incorrectly formatted URI', () { | |
| const msg = 'Observatory listening on :://'; | |
| expect(extractVMServiceUri(msg), null); | |
| }); | |
| test('returns URI at end of string', () { | |
| const msg = 'Observatory listening on http://foo.bar:9999/'; | |
| expect(extractVMServiceUri(msg), Uri.parse('http://foo.bar:9999/')); | |
| }); | |
| test('returns URI with auth token at end of string', () { | |
| const msg = 'Observatory listening on http://foo.bar:9999/cG90YXRv/'; | |
| expect( | |
| extractVMServiceUri(msg), | |
| Uri.parse('http://foo.bar:9999/cG90YXRv/'), | |
| ); | |
| }); | |
| test('return URI embedded within string', () { | |
| const msg = '1985-10-26 Observatory listening on http://foo.bar:9999/ **'; | |
| expect(extractVMServiceUri(msg), Uri.parse('http://foo.bar:9999/')); | |
| }); | |
| test('return URI with auth token embedded within string', () { | |
| const msg = | |
| '1985-10-26 Observatory listening on http://foo.bar:9999/cG90YXRv/ **'; | |
| expect( | |
| extractVMServiceUri(msg), | |
| Uri.parse('http://foo.bar:9999/cG90YXRv/'), | |
| ); | |
| }); | |
| test('handles new Dart VM service message format', () { | |
| const msg = | |
| 'The Dart VM service is listening on http://foo.bar:9999/cG90YXRv/'; | |
| expect( | |
| extractVMServiceUri(msg), | |
| Uri.parse('http://foo.bar:9999/cG90YXRv/'), | |
| ); | |
| }); | |
| }); | |
| group('getIgnoredLines', () { | |
| const invalidSources = [ | |
| '''final str = ''; // coverage:ignore-start | |
| final str = ''; | |
| final str = ''; // coverage:ignore-start | |
| ''', | |
| '''final str = ''; // coverage:ignore-start | |
| final str = ''; | |
| final str = ''; // coverage:ignore-start | |
| final str = ''; // coverage:ignore-end | |
| final str = ''; | |
| final str = ''; // coverage:ignore-end | |
| ''', | |
| '''final str = ''; // coverage:ignore-start | |
| final str = ''; | |
| final str = ''; // coverage:ignore-end | |
| final str = ''; | |
| final str = ''; // coverage:ignore-end | |
| ''', | |
| '''final str = ''; // coverage:ignore-end | |
| final str = ''; | |
| final str = ''; // coverage:ignore-start | |
| final str = ''; | |
| final str = ''; // coverage:ignore-end | |
| ''', | |
| '''final str = ''; // coverage:ignore-end | |
| final str = ''; | |
| final str = ''; // coverage:ignore-end | |
| ''', | |
| '''final str = ''; // coverage:ignore-end | |
| final str = ''; | |
| final str = ''; // coverage:ignore-start | |
| ''', | |
| '''final str = ''; // coverage:ignore-end | |
| ''', | |
| '''final str = ''; // coverage:ignore-start | |
| ''', | |
| ]; | |
| test('throws FormatException when the annotations are not balanced', () { | |
| void runTest(int index, String errMsg) { | |
| final content = invalidSources[index].split('\n'); | |
| expect( | |
| () => getIgnoredLines('content-$index.dart', content), | |
| throwsA( | |
| allOf( | |
| isFormatException, | |
| predicate((FormatException e) => e.message == errMsg), | |
| ), | |
| ), | |
| reason: 'expected FormatException with message "$errMsg"', | |
| ); | |
| } | |
| runTest( | |
| 0, | |
| 'coverage:ignore-start found at content-0.dart:' | |
| '3 before previous coverage:ignore-start ended', | |
| ); | |
| runTest( | |
| 1, | |
| 'coverage:ignore-start found at content-1.dart:' | |
| '3 before previous coverage:ignore-start ended', | |
| ); | |
| runTest(2, 'unmatched coverage:ignore-end found at content-2.dart:5'); | |
| runTest(3, 'unmatched coverage:ignore-end found at content-3.dart:1'); | |
| runTest(4, 'unmatched coverage:ignore-end found at content-4.dart:1'); | |
| runTest(5, 'unmatched coverage:ignore-end found at content-5.dart:1'); | |
| runTest(6, 'unmatched coverage:ignore-end found at content-6.dart:1'); | |
| runTest( | |
| 7, | |
| 'coverage:ignore-start found at content-7.dart:' | |
| '1 has no matching coverage:ignore-end', | |
| ); | |
| }); | |
| test('returns null when the annotations are not ' | |
| 'balanced but the whole file is ignored', () { | |
| for (final content in invalidSources) { | |
| final lines = content.split('\n'); | |
| lines.add(' // coverage:ignore-file'); | |
| expect(getIgnoredLines('', lines), null); | |
| } | |
| }); | |
| test('returns null when the whole file is ignored', () { | |
| final lines = | |
| '''final str = ''; // coverage:ignore-start | |
| final str = ''; // coverage:ignore-end | |
| final str = ''; // coverage:ignore-file | |
| ''' | |
| .split('\n'); | |
| expect(getIgnoredLines('', lines), null); | |
| }); | |
| test('return the correct range of lines ignored', () { | |
| final lines = | |
| ''' | |
| final str = ''; // coverage:ignore-start | |
| final str = ''; // coverage:ignore-line | |
| final str = ''; // coverage:ignore-end | |
| final str = ''; // coverage:ignore-start | |
| final str = ''; // coverage:ignore-line | |
| final str = ''; // coverage:ignore-end | |
| ''' | |
| .split('\n'); | |
| expect(getIgnoredLines('', lines), [ | |
| [1, 3], | |
| [4, 6], | |
| ]); | |
| }); | |
| test('return the correct list of lines ignored', () { | |
| final lines = | |
| ''' | |
| final str = ''; // coverage:ignore-line | |
| final str = ''; // coverage:ignore-line | |
| final str = ''; // coverage:ignore-line | |
| ''' | |
| .split('\n'); | |
| expect(getIgnoredLines('', lines), [ | |
| [1, 1], | |
| [2, 2], | |
| [3, 3], | |
| ]); | |
| }); | |
| test('ignore comments have no effect inside string literals', () { | |
| final lines = | |
| ''' | |
| final str = '// coverage:ignore-file'; | |
| final str = '// coverage:ignore-line'; | |
| final str = ''; // coverage:ignore-line | |
| final str = '// coverage:ignore-start'; | |
| final str = '// coverage:ignore-end'; | |
| ''' | |
| .split('\n'); | |
| expect(getIgnoredLines('', lines), [ | |
| [3, 3], | |
| ]); | |
| }); | |
| test('allow white-space after ignore comments', () { | |
| // Using multiple strings, rather than splitting a multi-line string, | |
| // because many code editors remove trailing white-space. | |
| final lines = [ | |
| "final str = ''; // coverage:ignore-start ", | |
| "final str = ''; // coverage:ignore-line\t", | |
| "final str = ''; // coverage:ignore-end \t \t ", | |
| "final str = ''; // coverage:ignore-line \t ", | |
| "final str = ''; // coverage:ignore-start \t ", | |
| "final str = ''; // coverage:ignore-end \t \t ", | |
| ]; | |
| expect(getIgnoredLines('', lines), [ | |
| [1, 3], | |
| [4, 4], | |
| [5, 6], | |
| ]); | |
| }); | |
| test('allow omitting space after //', () { | |
| final lines = [ | |
| "final str = ''; //coverage:ignore-start", | |
| "final str = ''; //coverage:ignore-line", | |
| "final str = ''; //coverage:ignore-end", | |
| "final str = ''; //coverage:ignore-line", | |
| "final str = ''; //coverage:ignore-start", | |
| "final str = ''; //coverage:ignore-end", | |
| ]; | |
| expect(getIgnoredLines('', lines), [ | |
| [1, 3], | |
| [4, 4], | |
| [5, 6], | |
| ]); | |
| }); | |
| test('allow text after ignore comments', () { | |
| final lines = [ | |
| "final str = ''; // coverage:ignore-start due to XYZ", | |
| "final str = ''; // coverage:ignore-line", | |
| "final str = ''; // coverage:ignore-end due to XYZ", | |
| "final str = ''; // coverage:ignore-line due to 123", | |
| "final str = ''; // coverage:ignore-start", | |
| "final str = ''; // coverage:ignore-end it is done", | |
| ]; | |
| expect(getIgnoredLines('', lines), [ | |
| [1, 3], | |
| [4, 4], | |
| [5, 6], | |
| ]); | |
| }); | |
| }); | |
| test('getAllWorkspaceNames', () { | |
| // Uses the workspace_names directory: | |
| // workspace_names | |
| // └── pkgs | |
| // ├── foo | |
| // │ └── foo_example // Not part of foo's workspace. | |
| // └── bar | |
| // └── bar_example // Part of bar's workspace. | |
| expect( | |
| getAllWorkspaceNames('test/workspace_names'), | |
| unorderedEquals(['workspace_names', 'foo', 'bar', 'bar_example']), | |
| ); | |
| }); | |
| test('IgnoredLinesContains', () { | |
| (List<List<int>>, int) createRandomRanges(int len) { | |
| final ranges = <List<int>>[]; | |
| var line = 0; | |
| final rand = Random(); | |
| while (ranges.length < len) { | |
| final start = line += 1 + rand.nextInt(5); | |
| final end = line += rand.nextInt(5); | |
| ranges.add([start, end]); | |
| } | |
| return (ranges, line); | |
| } | |
| bool naiveIgnoredContains(List<List<int>> ranges, int line) => | |
| ranges.any((range) => range[0] <= line && range[1] >= line); | |
| for (final len in [0, 1, 2, 3, 10, 100, 1000]) { | |
| final (ranges, end) = createRandomRanges(len); | |
| for (var line = 0; line < end + 3; ++line) { | |
| expect( | |
| ranges.ignoredContains(line), | |
| naiveIgnoredContains(ranges, line), | |
| ); | |
| } | |
| } | |
| }); | |
| } |