
org.organicdesign.testUtils.ComparatorContract.kt Maven / Gradle / Ivy
// Copyright 2015-07-03 PlanBase Inc. & Glen Peterson
//
// 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.
package org.organicdesign.testUtils
import org.junit.Assert
import org.organicdesign.testUtils.EqualsContract.permutations
import java.util.Comparator
/**
* Tests the various properties the Comparable contract is supposed to uphold.
*/
object ComparatorContract {
private fun pairComp(
first: Named,
comp: CompToZero,
second: Named,
comparator: Comparator
) {
Assert.assertTrue("The " + first.name + " item must be " + comp.english() +
" the " + second.name,
comp.vsZero(comparator.compare(first.a, second.a)))
}
/**
* Tests the various properties the Comparable contract is supposed to uphold. Also tests that
* the behavior of compareTo() is compatible with equals() and hashCode() which is strongly
* suggested, but not actually required, but keeps things much simpler (your class will behave the same
* way in a sorted and unsorted set or map).
*
* It's safest to also make your comparator serializable. Instead of a static class, use an enum singleton.
* If your comparator must be instantiated with parameters, ensure that it is serializable, for instance
* with [Serialization.serializeDeserialize] before passing it to this method.
*
* Expects three unique objects.
* The first must be less than the second, which in turn is less than the third.
*
* See note in class documentation.
*/
// Many of the comments in this method are paraphrases or direct quotes from the Javadocs for
// the Comparable interface. That is where this contract is specified.
// https://docs.oracle.com/javase/8/docs/api/
@JvmStatic
fun testComparator(
least1: T,
middle1: T,
greatest1: T,
comparator: Comparator
) {
// TODO: Do we want to ensure that comparators are serializable?
var anySame = false
permutations(listOf(least1, middle1, greatest1)
) { a: T, b: T ->
if (a === b) {
anySame = true
}
}
require(!anySame) { "You must provide three pair of different objects in order" }
var i = 0
for (item in listOf(least1, middle1, greatest1)) {
i++
// null is not an instance of any class, and e.compareTo(null) should throw a
// NullPointerException
try {
comparator.compare(item, null)
Assert.fail("comparator.compare(item, null) should throw some kind of RuntimeException" +
" (NullPointer/IllegalArgument/IllegalState, etc.)" +
" even though e.equals(null) returns false." +
" Item " + i + " threw no exception.")
} catch (ignore: RuntimeException) {
// Previously we had allowed NullPointerException and IllegalArgumentException.
// Kotlin throws IllegalStateException, so we now expect any RuntimeException
// to be thrown.
//
// This reports no-test-coverage, but if you uncomment this, you can
// prove that it *is* covered.
// System.out.println("Pass: " + comparator + " " + item + " " + ignore);
}
try {
comparator.compare(null, item)
Assert.fail("comparator.compare(null, item) should throw some kind of RuntimeException" +
" (NullPointer/IllegalArgument/IllegalState, etc.)" +
" even though e.equals(null) returns false." +
" Item " + i + " threw no exception.")
} catch (ignore: RuntimeException) {
// Previously we had allowed NullPointerException and IllegalArgumentException.
// Kotlin throws IllegalStateException, so we now expect any RuntimeException
// to be thrown.
//
// This reports no-test-coverage, but if you uncomment this, you can
// prove that it *is* covered.
// System.out.println("Pass2: " + comparator + " " + item);
}
}
val least = Named(least1, "Least")
val middle = Named(middle1, "Middle")
val greatest = Named(greatest1, "Greatest")
for (pair in listOf(least, middle, greatest)) {
// Consistent with equals: (e1.compareTo(e2) == 0) if and only if e1.equals(e2)
pairComp(pair, CompToZero.EQZ, pair, comparator)
}
pairComp(least, CompToZero.LTZ, middle, comparator)
pairComp(least, CompToZero.LTZ, greatest, comparator)
pairComp(middle, CompToZero.LTZ, greatest, comparator)
pairComp(greatest, CompToZero.GTZ, middle, comparator)
pairComp(greatest, CompToZero.GTZ, least, comparator)
pairComp(middle, CompToZero.GTZ, least, comparator)
}
internal enum class CompToZero {
LTZ {
override fun english(): String = "less than"
override fun vsZero(i: Int): Boolean = i < 0
},
GTZ {
override fun english(): String = "greater than"
override fun vsZero(i: Int): Boolean = i > 0
},
EQZ {
override fun english(): String = "equal to"
override fun vsZero(i: Int): Boolean = i == 0
};
abstract fun english(): String
abstract fun vsZero(i: Int): Boolean
}
private class Named(
val a: T,
val name: String
)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy