Line data Source code
1 : // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 : // for details. All rights reserved. Use of this source code is governed by a 3 : // BSD-style license that can be found in the LICENSE file. 4 : 5 : import 'package:meta/meta_meta.dart'; 6 : import 'package:string_scanner/string_scanner.dart'; 7 : 8 : /// A regular expression that matches text until a letter or whitespace. 9 : /// 10 : /// This is intended to scan through a number without actually encoding the full 11 : /// Dart number grammar. It doesn't stop on "e" because that can be a component 12 : /// of numbers. 13 0 : final _untilUnit = RegExp(r'[^a-df-z\s]+', caseSensitive: false); 14 : 15 : /// A regular expression that matches a time unit. 16 0 : final _unit = RegExp(r'([um]s|[dhms])', caseSensitive: false); 17 : 18 : /// A regular expression that matches a section of whitespace. 19 0 : final _whitespace = RegExp(r'\s+'); 20 : 21 : /// A class representing a modification to the default timeout for a test. 22 : /// 23 : /// By default, a test will time out after 30 seconds. With [new Timeout], that 24 : /// can be overridden entirely; with [Timeout.factor], it can be scaled 25 : /// relative to the default. 26 : @Target({TargetKind.library}) 27 : class Timeout { 28 : /// A constant indicating that a test should never time out. 29 : static const none = Timeout._none(); 30 : 31 : /// The timeout duration. 32 : /// 33 : /// If set, this overrides the default duration entirely. It's `null` for 34 : /// timeouts with a non-null [scaleFactor] and for [Timeout.none]. 35 : final Duration? duration; 36 : 37 : /// The timeout factor. 38 : /// 39 : /// The default timeout will be multiplied by this to get the new timeout. 40 : /// Thus a factor of 2 means that the test will take twice as long to time 41 : /// out, and a factor of 0.5 means that it will time out twice as quickly. 42 : /// 43 : /// This is `null` for timeouts with a non-null [duration] and for 44 : /// [Timeout.none]. 45 : final num? scaleFactor; 46 : 47 : /// Declares an absolute timeout that overrides the default. 48 11 : const Timeout(this.duration) : scaleFactor = null; 49 : 50 : /// Declares a relative timeout that scales the default. 51 22 : const Timeout.factor(this.scaleFactor) : duration = null; 52 : 53 11 : const Timeout._none() 54 : : scaleFactor = null, 55 : duration = null; 56 : 57 : /// Parse the timeout from a user-provided string. 58 : /// 59 : /// This supports the following formats: 60 : /// 61 : /// * `Number "x"`, which produces a relative timeout with the given scale 62 : /// factor. 63 : /// 64 : /// * `(Number ("d" | "h" | "m" | "s" | "ms" | "us") (" ")?)+`, which produces 65 : /// an absolute timeout with the duration given by the sum of the given 66 : /// units. 67 : /// 68 : /// * `"none"`, which produces [Timeout.none]. 69 : /// 70 : /// Throws a [FormatException] if [timeout] is not in a valid format 71 0 : factory Timeout.parse(String timeout) { 72 0 : var scanner = StringScanner(timeout); 73 : 74 : // First check for the string "none". 75 0 : if (scanner.scan('none')) { 76 0 : scanner.expectDone(); 77 : return Timeout.none; 78 : } 79 : 80 : // Scan a number. This will be either a time unit or a scale factor. 81 0 : scanner.expect(_untilUnit, name: 'number'); 82 0 : var number = double.parse((scanner.lastMatch![0])!); 83 : 84 : // A number followed by "x" is a scale factor. 85 0 : if (scanner.scan('x') || scanner.scan('X')) { 86 0 : scanner.expectDone(); 87 0 : return Timeout.factor(number); 88 : } 89 : 90 : // Parse time units until none are left. The condition is in the middle of 91 : // the loop because we've already parsed the first number. 92 : var microseconds = 0.0; 93 : while (true) { 94 0 : scanner.expect(_unit, name: 'unit'); 95 0 : microseconds += _microsecondsFor(number, (scanner.lastMatch![0])!); 96 : 97 0 : scanner.scan(_whitespace); 98 : 99 : // Scan the next number, if it's avaialble. 100 0 : if (!scanner.scan(_untilUnit)) break; 101 0 : number = double.parse((scanner.lastMatch![0])!); 102 : } 103 : 104 0 : scanner.expectDone(); 105 0 : return Timeout(Duration(microseconds: microseconds.round())); 106 : } 107 : 108 : /// Returns the number of microseconds in [number] [unit]s. 109 0 : static double _microsecondsFor(double number, String unit) { 110 : switch (unit) { 111 0 : case 'd': 112 0 : return number * 24 * 60 * 60 * 1000000; 113 0 : case 'h': 114 0 : return number * 60 * 60 * 1000000; 115 0 : case 'm': 116 0 : return number * 60 * 1000000; 117 0 : case 's': 118 0 : return number * 1000000; 119 0 : case 'ms': 120 0 : return number * 1000; 121 0 : case 'us': 122 : return number; 123 : default: 124 0 : throw ArgumentError('Unknown unit $unit.'); 125 : } 126 : } 127 : 128 : /// Returns a new [Timeout] that merges [this] with [other]. 129 : /// 130 : /// [Timeout.none] takes precedence over everything. If timeout is 131 : /// [Timeout.none] and [other] declares a [duration], that takes precedence. 132 : /// Otherwise, this timeout's [duration] or [factor] are multiplied by 133 : /// [other]'s [factor]. 134 11 : Timeout merge(Timeout other) { 135 22 : if (this == none || other == none) return none; 136 11 : if (other.duration != null) return Timeout(other.duration); 137 11 : if (duration != null) return Timeout(duration! * other.scaleFactor!); 138 44 : return Timeout.factor(scaleFactor! * other.scaleFactor!); 139 : } 140 : 141 : /// Returns a new [Duration] from applying [this] to [base]. 142 : /// 143 : /// If this is [none], returns `null`. 144 11 : Duration? apply(Duration base) { 145 11 : if (this == none) return null; 146 33 : return duration ?? base * scaleFactor!; 147 : } 148 : 149 0 : @override 150 0 : int get hashCode => duration.hashCode ^ 5 * scaleFactor.hashCode; 151 : 152 11 : @override 153 : bool operator ==(other) => 154 11 : other is Timeout && 155 33 : other.duration == duration && 156 33 : other.scaleFactor == scaleFactor; 157 : 158 0 : @override 159 : String toString() { 160 0 : if (duration != null) return duration.toString(); 161 0 : if (scaleFactor != null) return '${scaleFactor}x'; 162 : return 'none'; 163 : } 164 : }