| OLD | NEW |
| (Empty) |
| 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 library unittest.expected_function; | |
| 6 | |
| 7 import '../unittest.dart'; | |
| 8 | |
| 9 import 'internal_test_case.dart'; | |
| 10 | |
| 11 /// An object used to detect unpassed arguments. | |
| 12 const _PLACEHOLDER = const Object(); | |
| 13 | |
| 14 // Functions used to check how many arguments a callback takes. | |
| 15 typedef _Func0(); | |
| 16 typedef _Func1(a); | |
| 17 typedef _Func2(a, b); | |
| 18 typedef _Func3(a, b, c); | |
| 19 typedef _Func4(a, b, c, d); | |
| 20 typedef _Func5(a, b, c, d, e); | |
| 21 typedef _Func6(a, b, c, d, e, f); | |
| 22 | |
| 23 typedef bool _IsDoneCallback(); | |
| 24 | |
| 25 /// A wrapper for a function that ensures that it's called the appropriate | |
| 26 /// number of times. | |
| 27 /// | |
| 28 /// The containing test won't be considered to have completed successfully until | |
| 29 /// this function has been called the appropriate number of times. | |
| 30 /// | |
| 31 /// The wrapper function is accessible via [func]. It supports up to six | |
| 32 /// optional and/or required positional arguments, but no named arguments. | |
| 33 class ExpectedFunction { | |
| 34 /// The wrapped callback. | |
| 35 final Function _callback; | |
| 36 | |
| 37 /// The minimum number of calls that are expected to be made to the function. | |
| 38 /// | |
| 39 /// If fewer calls than this are made, the test will fail. | |
| 40 final int _minExpectedCalls; | |
| 41 | |
| 42 /// The maximum number of calls that are expected to be made to the function. | |
| 43 /// | |
| 44 /// If more calls than this are made, the test will fail. | |
| 45 final int _maxExpectedCalls; | |
| 46 | |
| 47 /// A callback that should return whether the function is not expected to have | |
| 48 /// any more calls. | |
| 49 /// | |
| 50 /// This will be called after every time the function is run. The test case | |
| 51 /// won't be allowed to terminate until it returns `true`. | |
| 52 /// | |
| 53 /// This may be `null`. If so, the function is considered to be done after | |
| 54 /// it's been run once. | |
| 55 final _IsDoneCallback _isDone; | |
| 56 | |
| 57 /// A descriptive name for the function. | |
| 58 final String _id; | |
| 59 | |
| 60 /// An optional description of why the function is expected to be called. | |
| 61 /// | |
| 62 /// If not passed, this will be an empty string. | |
| 63 final String _reason; | |
| 64 | |
| 65 /// The number of times the function has been called. | |
| 66 int _actualCalls = 0; | |
| 67 | |
| 68 /// The test case in which this function was wrapped. | |
| 69 final InternalTestCase _testCase; | |
| 70 | |
| 71 /// Whether this function has been called the requisite number of times. | |
| 72 bool _complete; | |
| 73 | |
| 74 /// Wraps [callback] in a function that asserts that it's called at least | |
| 75 /// [minExpected] times and no more than [maxExpected] times. | |
| 76 /// | |
| 77 /// If passed, [id] is used as a descriptive name fo the function and [reason] | |
| 78 /// as a reason it's expected to be called. If [isDone] is passed, the test | |
| 79 /// won't be allowed to complete until it returns `true`. | |
| 80 ExpectedFunction(Function callback, int minExpected, int maxExpected, | |
| 81 {String id, String reason, bool isDone()}) | |
| 82 : this._callback = callback, | |
| 83 _minExpectedCalls = minExpected, | |
| 84 _maxExpectedCalls = (maxExpected == 0 && minExpected > 0) | |
| 85 ? minExpected | |
| 86 : maxExpected, | |
| 87 this._isDone = isDone, | |
| 88 this._reason = reason == null ? '' : '\n$reason', | |
| 89 this._testCase = currentTestCase as InternalTestCase, | |
| 90 this._id = _makeCallbackId(id, callback) { | |
| 91 ensureInitialized(); | |
| 92 if (_testCase == null) { | |
| 93 throw new StateError("No valid test. Did you forget to run your test " | |
| 94 "inside a call to test()?"); | |
| 95 } | |
| 96 | |
| 97 if (isDone != null || minExpected > 0) { | |
| 98 _testCase.callbackFunctionsOutstanding++; | |
| 99 _complete = false; | |
| 100 } else { | |
| 101 _complete = true; | |
| 102 } | |
| 103 } | |
| 104 | |
| 105 /// Tries to find a reasonable name for [callback]. | |
| 106 /// | |
| 107 /// If [id] is passed, uses that. Otherwise, tries to determine a name from | |
| 108 /// calling `toString`. If no name can be found, returns the empty string. | |
| 109 static String _makeCallbackId(String id, Function callback) { | |
| 110 if (id != null) return "$id "; | |
| 111 | |
| 112 // If the callback is not an anonymous closure, try to get the | |
| 113 // name. | |
| 114 var toString = callback.toString(); | |
| 115 var prefix = "Function '"; | |
| 116 var start = toString.indexOf(prefix); | |
| 117 if (start == -1) return ''; | |
| 118 | |
| 119 start += prefix.length; | |
| 120 var end = toString.indexOf("'", start); | |
| 121 if (end == -1) return ''; | |
| 122 return "${toString.substring(start, end)} "; | |
| 123 } | |
| 124 | |
| 125 /// Returns a function that has the same number of positional arguments as the | |
| 126 /// wrapped function (up to a total of 6). | |
| 127 Function get func { | |
| 128 if (_callback is _Func6) return _max6; | |
| 129 if (_callback is _Func5) return _max5; | |
| 130 if (_callback is _Func4) return _max4; | |
| 131 if (_callback is _Func3) return _max3; | |
| 132 if (_callback is _Func2) return _max2; | |
| 133 if (_callback is _Func1) return _max1; | |
| 134 if (_callback is _Func0) return _max0; | |
| 135 | |
| 136 throw new ArgumentError( | |
| 137 'The wrapped function has more than 6 required arguments'); | |
| 138 } | |
| 139 | |
| 140 // This indirection is critical. It ensures the returned function has an | |
| 141 // argument count of zero. | |
| 142 _max0() => _max6(); | |
| 143 | |
| 144 _max1([a0 = _PLACEHOLDER]) => _max6(a0); | |
| 145 | |
| 146 _max2([a0 = _PLACEHOLDER, a1 = _PLACEHOLDER]) => _max6(a0, a1); | |
| 147 | |
| 148 _max3([a0 = _PLACEHOLDER, a1 = _PLACEHOLDER, a2 = _PLACEHOLDER]) => | |
| 149 _max6(a0, a1, a2); | |
| 150 | |
| 151 _max4([a0 = _PLACEHOLDER, a1 = _PLACEHOLDER, a2 = _PLACEHOLDER, | |
| 152 a3 = _PLACEHOLDER]) => _max6(a0, a1, a2, a3); | |
| 153 | |
| 154 _max5([a0 = _PLACEHOLDER, a1 = _PLACEHOLDER, a2 = _PLACEHOLDER, | |
| 155 a3 = _PLACEHOLDER, a4 = _PLACEHOLDER]) => _max6(a0, a1, a2, a3, a4); | |
| 156 | |
| 157 _max6([a0 = _PLACEHOLDER, a1 = _PLACEHOLDER, a2 = _PLACEHOLDER, | |
| 158 a3 = _PLACEHOLDER, a4 = _PLACEHOLDER, a5 = _PLACEHOLDER]) => | |
| 159 _run([a0, a1, a2, a3, a4, a5].where((a) => a != _PLACEHOLDER)); | |
| 160 | |
| 161 /// Runs the wrapped function with [args] and returns its return value. | |
| 162 /// | |
| 163 /// This will pass any errors on to [_testCase] and return `null`. | |
| 164 _run(Iterable args) { | |
| 165 try { | |
| 166 _actualCalls++; | |
| 167 if (_testCase.isComplete) { | |
| 168 // Don't run the callback if the test is done. We don't throw here as | |
| 169 // this is not the current test, but we do mark the old test as having | |
| 170 // an error if it previously passed. | |
| 171 if (_testCase.result == PASS) { | |
| 172 _testCase.error( | |
| 173 'Callback ${_id}called ($_actualCalls) after test case ' | |
| 174 '${_testCase.description} had already been marked as ' | |
| 175 '${_testCase.result}.$_reason'); | |
| 176 } | |
| 177 return null; | |
| 178 } else if (_maxExpectedCalls >= 0 && _actualCalls > _maxExpectedCalls) { | |
| 179 throw new TestFailure('Callback ${_id}called more times than expected ' | |
| 180 '($_maxExpectedCalls).$_reason'); | |
| 181 } | |
| 182 | |
| 183 return Function.apply(_callback, args.toList()); | |
| 184 } catch (error, stackTrace) { | |
| 185 _testCase.registerException(error, stackTrace); | |
| 186 return null; | |
| 187 } finally { | |
| 188 _afterRun(); | |
| 189 } | |
| 190 } | |
| 191 | |
| 192 /// After each time the function is run, check to see if it's complete. | |
| 193 void _afterRun() { | |
| 194 if (_complete) return; | |
| 195 if (_minExpectedCalls > 0 && _actualCalls < _minExpectedCalls) return; | |
| 196 if (_isDone != null && !_isDone()) return; | |
| 197 | |
| 198 // Mark this callback as complete and remove it from the test case's | |
| 199 // oustanding callback count; if that hits zero the test is done. | |
| 200 _complete = true; | |
| 201 _testCase.markCallbackComplete(); | |
| 202 } | |
| 203 } | |
| OLD | NEW |