All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.organicdesign.testUtils.EqualsContract.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

/**
 * Tests Reflexive, Symmetric, Transitive, Consistent, and non-nullity properties of the equals()
 * contract.  If you think this is confusing, realize that there is no way to implement a
 * one-sided equals() correctly with inheritance - it's a broken concept, but it's still used so
 * often that you have to do your best with it.
 *
 * I got the idea of contract-based testing from watching Bill Venners:
 * https://www.youtube.com/watch?v=bCTZQi2dpl8
 */
object EqualsContract {
    /**
     * Apply the given function against all unique pairings of items in the list.  Does this belong on Function2 instead
     * of List?
     */
    @JvmStatic
    fun  permutations(
            items: List,
            f: (T, T) -> Any?) {
        for (i in items.indices) {
            for (j in i + 1 until items.size) {
                f.invoke(items[i], items[j])
            }
        }
    }

    /**
     * Tests Reflexive, Symmetric, Transitive, Consistent, and non-nullity properties of the equals()
     * contract.  See note in class documentation.
     *
     * @param equiv1 First equivalent (but unique) object
     * @param equiv2 Second equivalent (but unique) object (could be a different class)
     * @param equiv3 Third equivalent (but unique) object (could be a different class)
     * @param different Non-equivalent object with a (maybe) different hashCode (should be an otherwise compatible class)
     * @param requireDistinctHashes if true, require that the fourth object have a different hashCode.  Otherwise,
     * require that it have the same hashCode.
     * @param  The super-class of all these objects - an interface or super-class within which they should be equal.
     */
    @JvmStatic
    fun equalsHashCode(
            equiv1: Any,
            equiv2: Any,
            equiv3: Any,
            different: Any,
            requireDistinctHashes: Boolean
    ) {
        require(!(equiv1 === equiv2 ||
                  equiv1 === equiv3 ||
                  equiv1 === different ||
                  equiv2 === equiv3 ||
                  equiv2 === different ||
                  equiv3 === different)) { "You must provide four different (having different memory locations) but 3 equivalent objects" }
        val equivs: List = listOf(equiv1, equiv2, equiv3)
        Assert.assertFalse("The different param should not allow itself to equal null", different as Any? == null)
        Assert.assertEquals("The different param must have the same hashCode as itself",
                            different.hashCode().toLong(), different.hashCode().toLong())
        Assert.assertTrue("The different param must equal itself", different == different)
        var i = 0
        // Reflexive
        for (equiv in equivs) {
            i++
            Assert.assertEquals("Param $i must have the same hashCode as itself",
                                equiv.hashCode().toLong(), equiv.hashCode().toLong())
            if (requireDistinctHashes) {
                Assert.assertNotEquals("The hashCode of param " + i + " must not equal the" +
                                       " hashCode of the different param.  If you meant to do that, use equalsSameHashCode()" +
                                       " instead.",
                                       equiv.hashCode().toLong(), different.hashCode().toLong())
            } else {
                Assert.assertEquals("The hashCode of param " + i + " must equal the" +
                                    " hashCode of the different param  If you meant to do that, use equalsDistinctHashCode()" +
                                    " instead.",
                                    equiv.hashCode().toLong(), different.hashCode().toLong())
            }
            Assert.assertTrue("Param $i must be equal to itself", equiv == equiv)
            Assert.assertFalse("Param $i cannot be equal to the different param", equiv == different)
            Assert.assertFalse("The different param cannot be equal to param $i", different == equiv)

            // Check null
            Assert.assertFalse("Param $i cannot allow itself to equal null", equiv as Any? == null)
        }

        // Symmetric (effectively covers Transitive as well)
        permutations(equivs) { a: Any, b: Any ->
            Assert.assertEquals("Found an unequal hashCode while inspecting permutations: a=$a b=$b",
                                a.hashCode().toLong(), b.hashCode().toLong())
            Assert.assertTrue("Failed equals while inspecting permutations: a=$a b=$b", a == b)
            Assert.assertTrue("Failed reflexive equals while inspecting permutations", b == a)
        }
    }

    /**
     * Tests Reflexive, Symmetric, Transitive, Consistent, and non-nullity properties of the equals()
     * contract.  See note in class documentation.
     *
     * @param equiv1 First equivalent (but unique) object
     * @param equiv2 Second equivalent (but unique) object (could be a different class)
     * @param equiv3 Third equivalent (but unique) object (could be a different class)
     * @param different Non-equivalent object with the same hashCode as the previous three
     * @param  The super-class of all these objects - an interface or super-class within which they should be equal.
     */
    @JvmStatic
    fun equalsSameHashCode(
            equiv1: Any,
            equiv2: Any,
            equiv3: Any,
            different: Any
    ) {
        equalsHashCode(equiv1, equiv2, equiv3, different, false)
    }

    /**
     * Tests Reflexive, Symmetric, Transitive, Consistent, and non-nullity properties of the equals()
     * contract.  See note in class documentation.
     *
     * @param equiv1 First equivalent (but unique) object
     * @param equiv2 Second equivalent (but unique) object (could be a different class)
     * @param equiv3 Third equivalent (but unique) object (could be a different class)
     * @param different Non-equivalent object with a different hashCode (should be an otherwise compatible class)
     * @param  The super-class of all these objects - an interface or super-class within which they should be equal.
     */
    @JvmStatic
    fun equalsDistinctHashCode(
            equiv1: Any,
            equiv2: Any,
            equiv3: Any,
            different: Any
    ) {
        equalsHashCode(equiv1, equiv2, equiv3, different, true)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy