// 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.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>, int) createRandomRanges(int len) { final ranges = >[]; 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> 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)); } } }); }