blob: dea3cb2afc0883cdb17539ba15bbad47d7de2efd [file] [log] [blame]
// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the 'License');
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an 'AS IS' BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
library quiver.testing.util.equalstester;
import 'package:quiver/testing/equality.dart';
import 'package:test/test.dart';
main() {
group('expectEquals', () {
_ValidTestObject reference;
_ValidTestObject equalObject1;
_ValidTestObject equalObject2;
_ValidTestObject notEqualObject1;
setUp(() {
reference = new _ValidTestObject(1, 2);
equalObject1 = new _ValidTestObject(1, 2);
equalObject2 = new _ValidTestObject(1, 2);
notEqualObject1 = new _ValidTestObject(0, 2);
});
test('Test null reference yields error', () {
try {
expect(null, areEqualityGroups);
fail('Should fail with null reference');
} catch (e) {
expect(e.toString(), contains('Equality Group must not be null'));
}
});
test('Test null group name yields error', () {
try {
expect({
'null': [reference],
null: [reference]
}, areEqualityGroups);
fail('Should fail with null group name');
} catch (e) {
expect(e.toString(), contains('Group name must not be null'));
}
});
test('Test null group yields error', () {
try {
expect({'bad group': null}, areEqualityGroups);
fail('Should fail with null group');
} catch (e) {
expect(e.toString(), contains('Group must not be null'));
}
});
test('Test after adding multiple instances at once with a null', () {
try {
expect({
'bad group': [reference, equalObject1, null]
}, areEqualityGroups);
fail('Should fail with null group');
} catch (e) {
expect(
e.toString(),
contains("$reference [group 'bad group', item 1]"
" must be equal to null [group 'bad group', item 3]"));
}
});
test('Test adding non-equal objects only in single group.', () {
try {
expect({
'not equal': [equalObject1, notEqualObject1]
}, areEqualityGroups);
fail("Should get not equal to equal object error");
} catch (e) {
expect(
e.toString(),
contains("$equalObject1 [group 'not equal', item"
" 1] must be equal to $notEqualObject1 [group 'not equal'"
", item 2]"));
}
});
test(
'Test with no equals or not equals objects. This checks'
' proper handling of null, incompatible class and reflexive tests', () {
expect({
'single object': [reference]
}, areEqualityGroups);
});
test(
'Test after populating equal objects. This checks proper'
' handling of equality and verifies hashCode for valid objects', () {
expect({
'all equal': [reference, equalObject1, equalObject2]
}, areEqualityGroups);
});
test('Test proper handling of case where an object is not equal to itself',
() {
Object obj = new _NonReflexiveObject();
try {
expect({
'non-reflexive': [obj]
}, areEqualityGroups);
fail("Should get non-reflexive error");
} catch (e) {
expect(e.toString(), contains("$obj must be equal to itself"));
}
});
test('Test proper handling of case where hashcode is not idempotent', () {
Object obj = new _InconsistentHashCodeObject(1, 2);
try {
expect({
'non-reflexive': [obj]
}, areEqualityGroups);
fail("Should get non-reflexive error");
} catch (e) {
expect(
e.toString(),
contains(
"the implementation of hashCode of $obj must be idempotent"));
}
});
test(
'Test proper handling where an object incorrectly tests for an '
'incompatible class', () {
Object obj = new _InvalidEqualsIncompatibleClassObject();
try {
expect({
'equals method broken': [obj]
}, areEqualityGroups);
fail("Should get equal to incompatible class error");
} catch (e) {
expect(
e.toString(),
contains("$obj must not be equal to an "
"arbitrary object of another class"));
}
});
test(
'Test proper handling where an object is not equal to one the user '
'has said should be equal', () {
try {
expect({
'non-equal': [reference, notEqualObject1]
}, areEqualityGroups);
fail("Should get not equal to equal object error");
} catch (e) {
expect(
e.toString(), contains("$reference [group 'non-equal', item 1]"));
expect(e.toString(),
contains("$notEqualObject1 [group 'non-equal', item 2]"));
}
});
test(
'Test for an invalid hashCode method, i.e., one that returns '
'different value for objects that are equal according to the equals '
'method', () {
Object a = new _InvalidHashCodeObject(1, 2);
Object b = new _InvalidHashCodeObject(1, 2);
try {
expect({
'invalid hashcode': [a, b]
}, areEqualityGroups);
fail("Should get invalid hashCode error");
} catch (e) {
expect(
e.toString(),
contains("the hashCode (${a.hashCode}) of $a"
" [group 'invalid hashcode', item 1] must be equal to the"
" hashCode (${b.hashCode}) of $b"));
}
});
test('Symmetry Broken', () {
try {
expect({
'broken symmetry': [
named('foo')..addPeers(['bar']),
named('bar')
]
}, areEqualityGroups);
fail("should fail because symmetry is broken");
} catch (e) {
expect(
e.toString(),
contains("bar [group 'broken symmetry', item 2] "
"must be equal to foo [group 'broken symmetry', item 1]"));
}
});
test('Transitivity Broken In EqualityGroup', () {
try {
expect({
'transitivity broken': [
named('foo')..addPeers(['bar', 'baz']),
named('bar')..addPeers(['foo']),
named('baz')..addPeers(['foo'])
]
}, areEqualityGroups);
fail("should fail because transitivity is broken");
} catch (e) {
expect(
e.toString(),
contains("bar [group 'transitivity broken', "
"item 2] must be equal to baz [group 'transitivity "
"broken', item 3]"));
}
});
test('Unequal Objects In EqualityGroup', () {
try {
expect({
'unequal objects': [named('foo'), named('bar')]
}, areEqualityGroups);
fail('should fail because of unequal objects in the same equality '
'group');
} catch (e) {
expect(
e.toString(),
contains("foo [group 'unequal objects', item 1] "
"must be equal to bar [group 'unequal objects', item 2]"));
}
});
test('Transitivity Broken Across EqualityGroups', () {
try {
expect({
'transitivity one': [
named('foo')..addPeers(['bar']),
named('bar')..addPeers(['foo', 'x'])
],
'transitivity two': [
named('baz')..addPeers(['x']),
named('x')..addPeers(['baz', 'bar'])
]
}, areEqualityGroups);
fail('should fail because transitivity is broken');
} catch (e) {
expect(
e.toString(),
contains("bar [group 'transitivity one', item 2]"
" must not be equal to x [group 'transitivity two',"
" item 2]"));
}
});
test('EqualityGroups', () {
expect({
'valid groups one': [
named('foo')..addPeers(['bar']),
named('bar')..addPeers(['foo'])
],
'valid groups two': [named('baz'), named('baz')]
}, areEqualityGroups);
});
});
}
/// Test class that violates reflexitivity. It is not equal to itself.
class _NonReflexiveObject {
@override
bool operator ==(Object o) => false;
@override
int get hashCode => super.hashCode;
}
/// Test class with valid equals and hashCode methods. Testers created
/// with instances of this class should always pass.
class _ValidTestObject {
int aspect1;
int aspect2;
_ValidTestObject(this.aspect1, this.aspect2);
@override
bool operator ==(Object o) {
if (!(o is _ValidTestObject)) {
return false;
}
_ValidTestObject other = o as _ValidTestObject;
if (aspect1 != other.aspect1) {
return false;
}
if (aspect2 != other.aspect2) {
return false;
}
return true;
}
@override
int get hashCode {
int result = 17;
result = 37 * result + aspect1;
result = 37 * result + aspect2;
return result;
}
}
///Test class that returns true even if the test object is of the wrong class.
class _InvalidEqualsIncompatibleClassObject {
@override
bool operator ==(Object o) {
return true;
}
@override
int get hashCode => 0;
}
/// Test class with inconsistent hashCode method.
class _InconsistentHashCodeObject {
int _aspect1;
int _aspect2;
int _hashCode = 0;
_InconsistentHashCodeObject(this._aspect1, this._aspect2);
@override
int get hashCode => _hashCode++;
@override
bool operator ==(Object o) {
if (!(o is _InconsistentHashCodeObject)) {
return false;
}
_InconsistentHashCodeObject other = o as _InconsistentHashCodeObject;
if (_aspect1 != other._aspect1) return false;
if (_aspect2 != other._aspect2) return false;
return true;
}
}
/// Test class with invalid hashCode method.
class _InvalidHashCodeObject {
static int hashCodeSource = 0;
int _aspect1;
int _aspect2;
int _hashCode = hashCodeSource++;
_InvalidHashCodeObject(this._aspect1, this._aspect2);
@override
int get hashCode => _hashCode;
@override
bool operator ==(Object o) {
if (!(o is _InvalidHashCodeObject)) {
return false;
}
_InvalidHashCodeObject other = o as _InvalidHashCodeObject;
if (_aspect1 != other._aspect1) return false;
if (_aspect2 != other._aspect2) return false;
return true;
}
}
_NamedObject named(String name) => new _NamedObject(name);
class _NamedObject {
final Set<String> peerNames = new Set();
final String name;
_NamedObject(this.name);
void addPeers(List<String> names) {
peerNames.addAll(names);
}
@override
bool operator ==(Object obj) {
if (obj is _NamedObject) {
_NamedObject that = obj;
return name == that.name || peerNames.contains(that.name);
}
return false;
}
@override
int get hashCode => 0;
@override
String toString() => name;
}