Skip to content
This repository was archived by the owner on Oct 22, 2024. It is now read-only.

Strong mode fixes #61

Merged
merged 5 commits into from
Sep 5, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 0.12.1+3

* Make `predicate` and `pairwiseCompare` generic methods to allow typed
functions to be passed to them as arguments.

* Make internal implementations take better advantage of type promotion to avoid
dynamic call overhead.

## 0.12.1+2

* Fixed small documentation issues.
Expand Down
111 changes: 61 additions & 50 deletions lib/src/core_matchers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -110,59 +110,70 @@ Matcher equals(expected, [int limit = 100]) => expected is String
? new _StringEqualsMatcher(expected)
: new _DeepMatcher(expected, limit);

typedef _RecursiveMatcher = List<String> Function(
dynamic, dynamic, String, int);

class _DeepMatcher extends Matcher {
final _expected;
final int _limit;

_DeepMatcher(this._expected, [int limit = 1000]) : this._limit = limit;

// Returns a pair (reason, location)
List _compareIterables(expected, actual, matcher, depth, location) {
if (actual is! Iterable) return ['is not Iterable', location];

var expectedIterator = expected.iterator;
var actualIterator = actual.iterator;
for (var index = 0;; index++) {
// Advance in lockstep.
var expectedNext = expectedIterator.moveNext();
var actualNext = actualIterator.moveNext();

// If we reached the end of both, we succeeded.
if (!expectedNext && !actualNext) return null;

// Fail if their lengths are different.
var newLocation = '$location[$index]';
if (!expectedNext) return ['longer than expected', newLocation];
if (!actualNext) return ['shorter than expected', newLocation];

// Match the elements.
var rp = matcher(
expectedIterator.current, actualIterator.current, newLocation, depth);
if (rp != null) return rp;
List<String> _compareIterables(Iterable expected, Object actual,
_RecursiveMatcher matcher, int depth, String location) {
if (actual is Iterable) {
var expectedIterator = expected.iterator;
var actualIterator = actual.iterator;
for (var index = 0;; index++) {
// Advance in lockstep.
var expectedNext = expectedIterator.moveNext();
var actualNext = actualIterator.moveNext();

// If we reached the end of both, we succeeded.
if (!expectedNext && !actualNext) return null;

// Fail if their lengths are different.
var newLocation = '$location[$index]';
if (!expectedNext) return ['longer than expected', newLocation];
if (!actualNext) return ['shorter than expected', newLocation];

// Match the elements.
var rp = matcher(expectedIterator.current, actualIterator.current,
newLocation, depth);
if (rp != null) return rp;
}
} else {
return ['is not Iterable', location];
}
}

List _compareSets(Set expected, actual, matcher, depth, location) {
if (actual is! Iterable) return ['is not Iterable', location];
actual = actual.toSet();
List<String> _compareSets(Set expected, Object actual,
_RecursiveMatcher matcher, int depth, String location) {
if (actual is Iterable) {
Set other = actual.toSet();

for (var expectedElement in expected) {
if (actual.every((actualElement) =>
matcher(expectedElement, actualElement, location, depth) != null)) {
return ['does not contain $expectedElement', location];
for (var expectedElement in expected) {
if (other.every((actualElement) =>
matcher(expectedElement, actualElement, location, depth) != null)) {
return ['does not contain $expectedElement', location];
}
}
}

if (actual.length > expected.length) {
return ['larger than expected', location];
} else if (actual.length < expected.length) {
return ['smaller than expected', location];
if (other.length > expected.length) {
return ['larger than expected', location];
} else if (other.length < expected.length) {
return ['smaller than expected', location];
} else {
return null;
}
} else {
return null;
return ['is not Iterable', location];
}
}

List _recursiveMatch(expected, actual, String location, int depth) {
List<String> _recursiveMatch(
Object expected, Object actual, String location, int depth) {
// If the expected value is a matcher, try to match it.
if (expected is Matcher) {
var matchState = {};
Expand Down Expand Up @@ -193,25 +204,24 @@ class _DeepMatcher extends Matcher {
expected, actual, _recursiveMatch, depth + 1, location);
} else if (expected is Map) {
if (actual is! Map) return ['expected a map', location];

var err = (expected.length == actual.length)
? ''
: 'has different length and ';
var map = (actual as Map);
var err =
(expected.length == map.length) ? '' : 'has different length and ';
for (var key in expected.keys) {
if (!actual.containsKey(key)) {
if (!map.containsKey(key)) {
return ["${err}is missing map key '$key'", location];
}
}

for (var key in actual.keys) {
for (var key in map.keys) {
if (!expected.containsKey(key)) {
return ["${err}has extra map key '$key'", location];
}
}

for (var key in expected.keys) {
var rp = _recursiveMatch(
expected[key], actual[key], "$location['$key']", depth + 1);
expected[key], map[key], "$location['$key']", depth + 1);
if (rp != null) return rp;
}

Expand Down Expand Up @@ -239,7 +249,7 @@ class _DeepMatcher extends Matcher {
String _match(expected, actual, Map matchState) {
var rp = _recursiveMatch(expected, actual, '', 0);
if (rp == null) return null;
var reason;
String reason;
if (rp[0].length > 0) {
if (rp[1].length > 0) {
reason = "${rp[0]} at location ${rp[1]}";
Expand Down Expand Up @@ -567,18 +577,19 @@ class _In extends Matcher {
/// For example:
///
/// expect(v, predicate((x) => ((x % 2) == 0), "is even"))
Matcher predicate(bool f(value), [String description = 'satisfies function']) =>
Matcher predicate<T>(bool f(T value),
[String description = 'satisfies function']) =>
new _Predicate(f, description);

typedef bool _PredicateFunction(value);
typedef bool _PredicateFunction<T>(T value);

class _Predicate extends Matcher {
final _PredicateFunction _matcher;
class _Predicate<T> extends Matcher {
final _PredicateFunction<T> _matcher;
final String _description;

const _Predicate(this._matcher, this._description);
_Predicate(this._matcher, this._description);

bool matches(item, Map matchState) => _matcher(item);
bool matches(item, Map matchState) => _matcher(item as T);

Description describe(Description description) =>
description.add(_description);
Expand Down
102 changes: 54 additions & 48 deletions lib/src/iterable_matchers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -155,42 +155,45 @@ class _UnorderedMatches extends Matcher {
: _expected = expected.map(wrapMatcher).toList();

String _test(item) {
if (item is! Iterable) return 'not iterable';
item = item.toList();

// Check the lengths are the same.
if (_expected.length > item.length) {
return 'has too few elements (${item.length} < ${_expected.length})';
} else if (_expected.length < item.length) {
return 'has too many elements (${item.length} > ${_expected.length})';
}
if (item is Iterable) {
var list = item.toList();

// Check the lengths are the same.
if (_expected.length > list.length) {
return 'has too few elements (${list.length} < ${_expected.length})';
} else if (_expected.length < list.length) {
return 'has too many elements (${list.length} > ${_expected.length})';
}

var matched = new List<bool>.filled(item.length, false);
var expectedPosition = 0;
for (var expectedMatcher in _expected) {
var actualPosition = 0;
var gotMatch = false;
for (var actualElement in item) {
if (!matched[actualPosition]) {
if (expectedMatcher.matches(actualElement, {})) {
matched[actualPosition] = gotMatch = true;
break;
var matched = new List<bool>.filled(list.length, false);
var expectedPosition = 0;
for (var expectedMatcher in _expected) {
var actualPosition = 0;
var gotMatch = false;
for (var actualElement in list) {
if (!matched[actualPosition]) {
if (expectedMatcher.matches(actualElement, {})) {
matched[actualPosition] = gotMatch = true;
break;
}
}
++actualPosition;
}
++actualPosition;
}

if (!gotMatch) {
return new StringDescription()
.add('has no match for ')
.addDescriptionOf(expectedMatcher)
.add(' at index $expectedPosition')
.toString();
}
if (!gotMatch) {
return new StringDescription()
.add('has no match for ')
.addDescriptionOf(expectedMatcher)
.add(' at index $expectedPosition')
.toString();
}

++expectedPosition;
++expectedPosition;
}
return null;
} else {
return 'not iterable';
}
return null;
}

bool matches(item, Map mismatchState) => _test(item) == null;
Expand All @@ -210,34 +213,37 @@ class _UnorderedMatches extends Matcher {
/// The [comparator] function, taking an expected and an actual argument, and
/// returning whether they match, will be applied to each pair in order.
/// [description] should be a meaningful name for the comparator.
Matcher pairwiseCompare(
Iterable expected, bool comparator(a, b), String description) =>
Matcher pairwiseCompare<S, T>(
Iterable<S> expected, bool comparator(S a, T b), String description) =>
new _PairwiseCompare(expected, comparator, description);

typedef bool _Comparator(a, b);
typedef bool _Comparator<S, T>(S a, T b);

class _PairwiseCompare extends _IterableMatcher {
final Iterable _expected;
final _Comparator _comparator;
class _PairwiseCompare<S, T> extends _IterableMatcher {
final Iterable<S> _expected;
final _Comparator<S, T> _comparator;
final String _description;

_PairwiseCompare(this._expected, this._comparator, this._description);

bool matches(item, Map matchState) {
if (item is! Iterable) return false;
if (item.length != _expected.length) return false;
var iterator = item.iterator;
var i = 0;
for (var e in _expected) {
iterator.moveNext();
if (!_comparator(e, iterator.current)) {
addStateInfo(matchState,
{'index': i, 'expected': e, 'actual': iterator.current});
return false;
if (item is Iterable) {
if (item.length != _expected.length) return false;
var iterator = item.iterator;
var i = 0;
for (var e in _expected) {
iterator.moveNext();
if (!_comparator(e, iterator.current)) {
addStateInfo(matchState,
{'index': i, 'expected': e, 'actual': iterator.current});
return false;
}
i++;
}
i++;
return true;
} else {
return false;
}
return true;
}

Description describe(Description description) =>
Expand Down
41 changes: 22 additions & 19 deletions lib/src/numeric_matchers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ class _IsCloseTo extends Matcher {
const _IsCloseTo(this._value, this._delta);

bool matches(item, Map matchState) {
if (item is! num) return false;

var diff = item - _value;
if (diff < 0) diff = -diff;
return (diff <= _delta);
if (item is num) {
var diff = item - _value;
if (diff < 0) diff = -diff;
return (diff <= _delta);
} else {
return false;
}
}

Description describe(Description description) => description
Expand All @@ -32,12 +34,12 @@ class _IsCloseTo extends Matcher {

Description describeMismatch(
item, Description mismatchDescription, Map matchState, bool verbose) {
if (item is! num) {
return mismatchDescription.add(' not numeric');
} else {
if (item is num) {
var diff = item - _value;
if (diff < 0) diff = -diff;
return mismatchDescription.add(' differs by ').addDescriptionOf(diff);
} else {
return mismatchDescription.add(' not numeric');
}
}
}
Expand Down Expand Up @@ -70,19 +72,20 @@ class _InRange extends Matcher {
this._low, this._high, this._lowMatchValue, this._highMatchValue);

bool matches(value, Map matchState) {
if (value is! num) {
return false;
}
if (value < _low || value > _high) {
if (value is num) {
if (value < _low || value > _high) {
return false;
}
if (value == _low) {
return _lowMatchValue;
}
if (value == _high) {
return _highMatchValue;
}
return true;
} else {
return false;
}
if (value == _low) {
return _lowMatchValue;
}
if (value == _high) {
return _highMatchValue;
}
return true;
}

Description describe(Description description) =>
Expand Down
Loading