OLD | NEW |
1 // Copyright 2014 Google Inc. All Rights Reserved. | 1 // Copyright 2014 Google Inc. All Rights Reserved. |
2 // | 2 // |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
4 // you may not use this file except in compliance with the License. | 4 // you may not use this file except in compliance with the License. |
5 // You may obtain a copy of the License at | 5 // You may obtain a copy of the License at |
6 // | 6 // |
7 // http://www.apache.org/licenses/LICENSE-2.0 | 7 // http://www.apache.org/licenses/LICENSE-2.0 |
8 // | 8 // |
9 // Unless required by applicable law or agreed to in writing, software | 9 // Unless required by applicable law or agreed to in writing, software |
10 // distributed under the License is distributed on an "AS IS" BASIS, | 10 // distributed under the License is distributed on an "AS IS" BASIS, |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 // See the License for the specific language governing permissions and | 12 // See the License for the specific language governing permissions and |
13 // limitations under the License. | 13 // limitations under the License. |
14 | 14 |
15 part of quiver.testing.equality; | 15 part of quiver.testing.equality; |
16 | 16 |
17 /** | 17 /// Matcher for == and hashCode methods of a class. |
18 * Matcher for == and hashCode methods of a class. | 18 /// |
19 * | 19 /// To use, invoke areEqualityGroups with a list of equality groups where each |
20 * To use, invoke areEqualityGroups with a list of equality groups where each | 20 /// group contains objects that are supposed to be equal to each other, and |
21 * group contains objects that are supposed to be equal to each other, and | 21 /// objects of different groups are expected to be unequal. For example: |
22 * objects of different groups are expected to be unequal. For example: | 22 /// |
23 * | 23 /// expect({ |
24 * expect({ | 24 /// 'hello': ["hello", "h" + "ello"], |
25 * 'hello': ["hello", "h" + "ello"], | 25 /// 'world': ["world", "wor" + "ld"], |
26 * 'world': ["world", "wor" + "ld"], | 26 /// 'three': [2, 1 + 1] |
27 * 'three': [2, 1 + 1] | 27 /// }, areEqualityGroups); |
28 * }, areEqualityGroups); | 28 /// |
29 * | 29 /// This tests that: |
30 * This tests that: | 30 /// |
31 * | 31 /// * comparing each object against itself returns true |
32 * * comparing each object against itself returns true | 32 /// * comparing each object against an instance of an incompatible class |
33 * * comparing each object against an instance of an incompatible class | 33 /// returns false |
34 * returns false | 34 /// * comparing each pair of objects within the same equality group returns |
35 * * comparing each pair of objects within the same equality group returns | 35 /// true |
36 * true | 36 /// * comparing each pair of objects from different equality groups returns |
37 * * comparing each pair of objects from different equality groups returns | 37 /// false |
38 * false | 38 /// * the hash codes of any two equal objects are equal |
39 * * the hash codes of any two equal objects are equal | 39 /// * equals implementation is idempotent |
40 * * equals implementation is idempotent | 40 /// |
41 * | 41 /// The format of the Map passed to expect is such that the map keys are used in |
42 * The format of the Map passed to expect is such that the map keys are used in | 42 /// error messages to identify the group described by the map value. |
43 * error messages to identify the group described by the map value. | 43 /// |
44 * | 44 /// When a test fails, the error message labels the objects involved in |
45 * When a test fails, the error message labels the objects involved in | 45 /// the failed comparison as follows: |
46 * the failed comparison as follows: | 46 /// |
47 * | 47 /// "`[group x, item j]`" refers to the ith item in the xth equality group, |
48 * "`[group x, item j]`" refers to the ith item in the xth equality group, | 48 /// where both equality groups and the items within equality groups are |
49 * where both equality groups and the items within equality groups are | 49 /// numbered starting from 1. When either a constructor argument or an |
50 * numbered starting from 1. When either a constructor argument or an | 50 /// equal object is provided, that becomes group 1. |
51 * equal object is provided, that becomes group 1. | |
52 * | |
53 */ | |
54 const Matcher areEqualityGroups = const _EqualityGroupMatcher(); | 51 const Matcher areEqualityGroups = const _EqualityGroupMatcher(); |
55 | 52 |
56 const _repetitions = 3; | 53 const _repetitions = 3; |
57 | 54 |
58 class _EqualityGroupMatcher extends Matcher { | 55 class _EqualityGroupMatcher extends Matcher { |
59 static const failureReason = 'failureReason'; | 56 static const failureReason = 'failureReason'; |
60 const _EqualityGroupMatcher(); | 57 const _EqualityGroupMatcher(); |
61 | 58 |
62 @override | 59 @override |
63 Description describe(Description description) => | 60 Description describe(Description description) => |
64 description.add('to be equality groups'); | 61 description.add('to be equality groups'); |
65 | 62 |
66 @override | 63 @override |
67 bool matches(Map<String, List> item, Map matchState) { | 64 bool matches(item, Map matchState) { |
68 try { | 65 try { |
69 _verifyEqualityGroups(item, matchState); | 66 _verifyEqualityGroups(item as Map<String, List>, matchState); |
70 return true; | 67 return true; |
71 } on MatchError catch (e) { | 68 } on MatchError catch (e) { |
72 matchState[failureReason] = e.toString(); | 69 matchState[failureReason] = e.toString(); |
73 return false; | 70 return false; |
74 } | 71 } |
75 } | 72 } |
76 | 73 |
77 Description describeMismatch(item, Description mismatchDescription, | 74 Description describeMismatch(item, Description mismatchDescription, |
78 Map matchState, bool verbose) => | 75 Map matchState, bool verbose) => |
79 mismatchDescription.add(" ${matchState[failureReason]}"); | 76 mismatchDescription.add(" ${matchState[failureReason]}"); |
80 | 77 |
81 void _verifyEqualityGroups(Map<String, List> equalityGroups, Map matchState) { | 78 void _verifyEqualityGroups(Map<String, List> equalityGroups, Map matchState) { |
82 if (equalityGroups == null) { | 79 if (equalityGroups == null) { |
83 throw new MatchError('Equality Group must not be null'); | 80 throw new MatchError('Equality Group must not be null'); |
84 } | 81 } |
85 var equalityGroupsCopy = {}; | 82 final equalityGroupsCopy = <String, List>{}; |
86 equalityGroups.forEach((String groupName, List group) { | 83 equalityGroups.forEach((String groupName, List group) { |
87 if (groupName == null) { | 84 if (groupName == null) { |
88 throw new MatchError('Group name must not be null'); | 85 throw new MatchError('Group name must not be null'); |
89 } | 86 } |
90 if (group == null) { | 87 if (group == null) { |
91 throw new MatchError('Group must not be null'); | 88 throw new MatchError('Group must not be null'); |
92 } | 89 } |
93 equalityGroupsCopy[groupName] = new List.from(group); | 90 equalityGroupsCopy[groupName] = new List.from(group); |
94 }); | 91 }); |
95 | 92 |
96 // Run the test multiple times to ensure deterministic equals | 93 // Run the test multiple times to ensure deterministic equals |
97 for (var run in range(_repetitions)) { | 94 for (var i = 0; i < _repetitions; i++) { |
98 _checkBasicIdentity(equalityGroupsCopy, matchState); | 95 _checkBasicIdentity(equalityGroupsCopy, matchState); |
99 _checkGroupBasedEquality(equalityGroupsCopy); | 96 _checkGroupBasedEquality(equalityGroupsCopy); |
100 } | 97 } |
101 } | 98 } |
102 | 99 |
103 void _checkBasicIdentity(Map<String, List> equalityGroups, Map matchState) { | 100 void _checkBasicIdentity(Map<String, List> equalityGroups, Map matchState) { |
104 var flattened = equalityGroups.values.expand((group) => group); | 101 var flattened = equalityGroups.values.expand((group) => group); |
105 for (var item in flattened) { | 102 for (var item in flattened) { |
106 if (item == _NotAnInstance.equalToNothing) { | 103 if (item == _NotAnInstance.equalToNothing) { |
107 throw new MatchError( | 104 throw new MatchError( |
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
206 } | 203 } |
207 | 204 |
208 class MatchError extends Error { | 205 class MatchError extends Error { |
209 final message; | 206 final message; |
210 | 207 |
211 /// The [message] describes the match error. | 208 /// The [message] describes the match error. |
212 MatchError([this.message]); | 209 MatchError([this.message]); |
213 | 210 |
214 String toString() => message; | 211 String toString() => message; |
215 } | 212 } |
OLD | NEW |