From 824fbd728fe0379f9b7b7f5c17530632d7beab69 Mon Sep 17 00:00:00 2001 From: Szczepan Faber Date: Sun, 27 Jan 2008 23:44:04 +0000 Subject: [PATCH] added refEq matcher based on reflectionEquals from apache commons lang --HG-- extra : convert_revision : svn%3Aaa2aecf3-ea3e-0410-9d70-716747e7c967/trunk%40330 --- license.txt => LICENSE | 0 NOTICE | 8 + replace_headers.rb | 11 +- src/org/mockito/Matchers.java | 19 + .../matchers/apachecommons/EqualsBuilder.java | 831 ++++++++++++++ .../apachecommons/ReflectionEquals.java | 24 + .../apachecommons/EqualsBuilderTest.java | 1013 +++++++++++++++++ .../mockitousage/ReflectionMatchersTest.java | 79 ++ 8 files changed, 1980 insertions(+), 5 deletions(-) rename license.txt => LICENSE (100%) create mode 100644 NOTICE create mode 100644 src/org/mockito/internal/matchers/apachecommons/EqualsBuilder.java create mode 100644 src/org/mockito/internal/matchers/apachecommons/ReflectionEquals.java create mode 100644 test/org/mockito/internal/matchers/apachecommons/EqualsBuilderTest.java create mode 100644 test/org/mockitousage/ReflectionMatchersTest.java diff --git a/license.txt b/LICENSE similarity index 100% rename from license.txt rename to LICENSE diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000000..bd0627c9df --- /dev/null +++ b/NOTICE @@ -0,0 +1,8 @@ +Mockito license - MIT. + +Libraries used: + +EasyMock - MIT license. +Cglib - Apache License 2.0. +Objenesis - MIT license. +Apache Commons Lang - Apache License 2.0. \ No newline at end of file diff --git a/replace_headers.rb b/replace_headers.rb index 4d96ea091a..daa9e9754e 100644 --- a/replace_headers.rb +++ b/replace_headers.rb @@ -28,13 +28,14 @@ File.open(path, 'r+') do |f| out = '' f.each { |line| out << line } - + current_hdr = Regexp.new('.*\*/.*package org\.', Regexp::MULTILINE) - - if (out =~ current_hdr) - out.gsub!(current_hdr, header + "package org.") - else + + if (out !~ current_hdr) out = header + out + else + first_hdr = Regexp.new('.*?\*/.*?[\n\r]+?', Regexp::MULTILINE) + out.sub!(first_hdr, header) end f.pos = 0 diff --git a/src/org/mockito/Matchers.java b/src/org/mockito/Matchers.java index c14b9ffcf2..fad134b834 100644 --- a/src/org/mockito/Matchers.java +++ b/src/org/mockito/Matchers.java @@ -15,6 +15,7 @@ import org.mockito.internal.matchers.Null; import org.mockito.internal.matchers.Same; import org.mockito.internal.matchers.StartsWith; +import org.mockito.internal.matchers.apachecommons.ReflectionEquals; import org.mockito.internal.progress.LastArguments; import org.mockito.internal.progress.ReturnValues; @@ -317,6 +318,24 @@ public static T eq(T value) { return reportMatcher(new Equals(value)).returnNull(); } + /** + * Object argument that is reflection-equal to the given value. + *

+ * This matcher can be used when equals() is not implemented on compared objects. + * Matcher uses java reflection API to compare fields of wanted and actual object. + *

+ * Works similarly to EqualsBuilder.reflectionEquals(this, other) from apache commons library. + *

+ * See examples in javadoc for {@link Matchers} + * + * @param value + * the given value. + * @return null. + */ + public static T refEq(T value) { + return reportMatcher(new ReflectionEquals(value)).returnNull(); + } + /** * Object argument that is the same as the given value. *

diff --git a/src/org/mockito/internal/matchers/apachecommons/EqualsBuilder.java b/src/org/mockito/internal/matchers/apachecommons/EqualsBuilder.java new file mode 100644 index 0000000000..181a5f226a --- /dev/null +++ b/src/org/mockito/internal/matchers/apachecommons/EqualsBuilder.java @@ -0,0 +1,831 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ + +//Class comes from Apache Commons Lang, added some tiny changes + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.mockito.internal.matchers.apachecommons; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + *

Assists in implementing {@link Object#equals(Object)} methods.

+ * + *

This class provides methods to build a good equals method for any + * class. It follows rules laid out in + * Effective Java + * , by Joshua Bloch. In particular the rule for comparing doubles, + * floats, and arrays can be tricky. Also, making sure that + * equals() and hashCode() are consistent can be + * difficult.

+ * + *

Two Objects that compare as equals must generate the same hash code, + * but two Objects with the same hash code do not have to be equal.

+ * + *

All relevant fields should be included in the calculation of equals. + * Derived fields may be ignored. In particular, any field used in + * generating a hash code must be used in the equals method, and vice + * versa.

+ * + *

Typical use for the code is as follows:

+ *
+ * public boolean equals(Object obj) {
+ *   if (obj == null) { return false; }
+ *   if (obj == this) { return true; }
+ *   if (obj.getClass() != getClass()) {
+ *     return false;
+ *   }
+ *   MyClass rhs = (MyClass) obj;
+ *   return new EqualsBuilder()
+ *                 .appendSuper(super.equals(obj))
+ *                 .append(field1, rhs.field1)
+ *                 .append(field2, rhs.field2)
+ *                 .append(field3, rhs.field3)
+ *                 .isEquals();
+ *  }
+ * 
+ * + *

Alternatively, there is a method that uses reflection to determine + * the fields to test. Because these fields are usually private, the method, + * reflectionEquals, uses AccessibleObject.setAccessible to + * change the visibility of the fields. This will fail under a security + * manager, unless the appropriate permissions are set up correctly. It is + * also slower than testing explicitly.

+ * + *

A typical invocation for this method would look like:

+ *
+ * public boolean equals(Object obj) {
+ *   return EqualsBuilder.reflectionEquals(this, obj);
+ * }
+ * 
+ * + * @author Steve Downey + * @author Stephen Colebourne + * @author Gary Gregory + * @author Pete Gieser + * @author Arun Mammen Thomas + * @since 1.0 + * @version $Id: EqualsBuilder.java 611543 2008-01-13 07:00:22Z bayard $ + */ +@SuppressWarnings("unchecked") +public class EqualsBuilder { + + /** + * If the fields tested are equals. + * The default value is true. + */ + private boolean isEquals = true; + + /** + *

Constructor for EqualsBuilder.

+ * + *

Starts off assuming that equals is true.

+ * @see Object#equals(Object) + */ + public EqualsBuilder() { + // do nothing for now. + } + + //------------------------------------------------------------------------- + + /** + *

This method uses reflection to determine if the two Objects + * are equal.

+ * + *

It uses AccessibleObject.setAccessible to gain access to private + * fields. This means that it will throw a security exception if run under + * a security manager, if the permissions are not set up correctly. It is also + * not as efficient as testing explicitly.

+ * + *

Transient members will be not be tested, as they are likely derived + * fields, and not part of the value of the Object.

+ * + *

Static fields will not be tested. Superclass fields will be included.

+ * + * @param lhs this object + * @param rhs the other object + * @return true if the two Objects have tested equals. + */ + public static boolean reflectionEquals(Object lhs, Object rhs) { + return reflectionEquals(lhs, rhs, false, null, null); + } + + /** + *

This method uses reflection to determine if the two Objects + * are equal.

+ * + *

It uses AccessibleObject.setAccessible to gain access to private + * fields. This means that it will throw a security exception if run under + * a security manager, if the permissions are not set up correctly. It is also + * not as efficient as testing explicitly.

+ * + *

Transient members will be not be tested, as they are likely derived + * fields, and not part of the value of the Object.

+ * + *

Static fields will not be tested. Superclass fields will be included.

+ * + * @param lhs this object + * @param rhs the other object + * @param excludeFields array of field names to exclude from testing + * @return true if the two Objects have tested equals. + */ + public static boolean reflectionEquals(Object lhs, Object rhs, String[] excludeFields) { + return reflectionEquals(lhs, rhs, false, null, excludeFields); + } + + /** + *

This method uses reflection to determine if the two Objects + * are equal.

+ * + *

It uses AccessibleObject.setAccessible to gain access to private + * fields. This means that it will throw a security exception if run under + * a security manager, if the permissions are not set up correctly. It is also + * not as efficient as testing explicitly.

+ * + *

If the TestTransients parameter is set to true, transient + * members will be tested, otherwise they are ignored, as they are likely + * derived fields, and not part of the value of the Object.

+ * + *

Static fields will not be tested. Superclass fields will be included.

+ * + * @param lhs this object + * @param rhs the other object + * @param testTransients whether to include transient fields + * @return true if the two Objects have tested equals. + */ + public static boolean reflectionEquals(Object lhs, Object rhs, boolean testTransients) { + return reflectionEquals(lhs, rhs, testTransients, null, null); + } + + /** + *

This method uses reflection to determine if the two Objects + * are equal.

+ * + *

It uses AccessibleObject.setAccessible to gain access to private + * fields. This means that it will throw a security exception if run under + * a security manager, if the permissions are not set up correctly. It is also + * not as efficient as testing explicitly.

+ * + *

If the testTransients parameter is set to true, transient + * members will be tested, otherwise they are ignored, as they are likely + * derived fields, and not part of the value of the Object.

+ * + *

Static fields will not be included. Superclass fields will be appended + * up to and including the specified superclass. A null superclass is treated + * as java.lang.Object.

+ * + * @param lhs this object + * @param rhs the other object + * @param testTransients whether to include transient fields + * @param reflectUpToClass the superclass to reflect up to (inclusive), + * may be null + * @return true if the two Objects have tested equals. + * @since 2.0 + */ + public static boolean reflectionEquals(Object lhs, Object rhs, boolean testTransients, Class reflectUpToClass) { + return reflectionEquals(lhs, rhs, testTransients, reflectUpToClass, null); + } + + /** + *

This method uses reflection to determine if the two Objects + * are equal.

+ * + *

It uses AccessibleObject.setAccessible to gain access to private + * fields. This means that it will throw a security exception if run under + * a security manager, if the permissions are not set up correctly. It is also + * not as efficient as testing explicitly.

+ * + *

If the testTransients parameter is set to true, transient + * members will be tested, otherwise they are ignored, as they are likely + * derived fields, and not part of the value of the Object.

+ * + *

Static fields will not be included. Superclass fields will be appended + * up to and including the specified superclass. A null superclass is treated + * as java.lang.Object.

+ * + * @param lhs this object + * @param rhs the other object + * @param testTransients whether to include transient fields + * @param reflectUpToClass the superclass to reflect up to (inclusive), + * may be null + * @param excludeFields array of field names to exclude from testing + * @return true if the two Objects have tested equals. + * @since 2.0 + */ + public static boolean reflectionEquals(Object lhs, Object rhs, boolean testTransients, Class reflectUpToClass, + String[] excludeFields) { + if (lhs == rhs) { + return true; + } + if (lhs == null || rhs == null) { + return false; + } + // Find the leaf class since there may be transients in the leaf + // class or in classes between the leaf and root. + // If we are not testing transients or a subclass has no ivars, + // then a subclass can test equals to a superclass. + Class lhsClass = lhs.getClass(); + Class rhsClass = rhs.getClass(); + Class testClass; + if (lhsClass.isInstance(rhs)) { + testClass = lhsClass; + if (!rhsClass.isInstance(lhs)) { + // rhsClass is a subclass of lhsClass + testClass = rhsClass; + } + } else if (rhsClass.isInstance(lhs)) { + testClass = rhsClass; + if (!lhsClass.isInstance(rhs)) { + // lhsClass is a subclass of rhsClass + testClass = lhsClass; + } + } else { + // The two classes are not related. + return false; + } + EqualsBuilder equalsBuilder = new EqualsBuilder(); + try { + reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields); + while (testClass.getSuperclass() != null && testClass != reflectUpToClass) { + testClass = testClass.getSuperclass(); + reflectionAppend(lhs, rhs, testClass, equalsBuilder, testTransients, excludeFields); + } + } catch (IllegalArgumentException e) { + // In this case, we tried to test a subclass vs. a superclass and + // the subclass has ivars or the ivars are transient and + // we are testing transients. + // If a subclass has ivars that we are trying to test them, we get an + // exception and we know that the objects are not equal. + return false; + } + return equalsBuilder.isEquals(); + } + + /** + *

Appends the fields and values defined by the given object of the + * given Class.

+ * + * @param lhs the left hand object + * @param rhs the right hand object + * @param clazz the class to append details of + * @param builder the builder to append to + * @param useTransients whether to test transient fields + * @param excludeFields array of field names to exclude from testing + */ + private static void reflectionAppend( + Object lhs, + Object rhs, + Class clazz, + EqualsBuilder builder, + boolean useTransients, + String[] excludeFields) { + Field[] fields = clazz.getDeclaredFields(); + List excludedFieldList = excludeFields != null ? Arrays.asList(excludeFields) : Collections.EMPTY_LIST; + AccessibleObject.setAccessible(fields, true); + for (int i = 0; i < fields.length && builder.isEquals; i++) { + Field f = fields[i]; + if (!excludedFieldList.contains(f.getName()) + && (f.getName().indexOf('$') == -1) + && (useTransients || !Modifier.isTransient(f.getModifiers())) + && (!Modifier.isStatic(f.getModifiers()))) { + try { + builder.append(f.get(lhs), f.get(rhs)); + } catch (IllegalAccessException e) { + //this can't happen. Would get a Security exception instead + //throw a runtime exception in case the impossible happens. + throw new InternalError("Unexpected IllegalAccessException"); + } + } + } + } + + //------------------------------------------------------------------------- + + /** + *

Adds the result of super.equals() to this builder.

+ * + * @param superEquals the result of calling super.equals() + * @return EqualsBuilder - used to chain calls. + * @since 2.0 + */ + public EqualsBuilder appendSuper(boolean superEquals) { + if (isEquals == false) { + return this; + } + isEquals = superEquals; + return this; + } + + //------------------------------------------------------------------------- + + /** + *

Test if two Objects are equal using their + * equals method.

+ * + * @param lhs the left hand object + * @param rhs the right hand object + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(Object lhs, Object rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + Class lhsClass = lhs.getClass(); + if (!lhsClass.isArray()) { + if (lhs instanceof java.math.BigDecimal && rhs instanceof java.math.BigDecimal) { + isEquals = (((java.math.BigDecimal) lhs).compareTo((java.math.BigDecimal) rhs) == 0); + } else { + // The simple case, not an array, just test the element + isEquals = lhs.equals(rhs); + } + } else if (lhs.getClass() != rhs.getClass()) { + // Here when we compare different dimensions, for example: a boolean[][] to a boolean[] + this.setEquals(false); + + // 'Switch' on type of array, to dispatch to the correct handler + // This handles multi dimensional arrays of the same depth + } else if (lhs instanceof long[]) { + append((long[]) lhs, (long[]) rhs); + } else if (lhs instanceof int[]) { + append((int[]) lhs, (int[]) rhs); + } else if (lhs instanceof short[]) { + append((short[]) lhs, (short[]) rhs); + } else if (lhs instanceof char[]) { + append((char[]) lhs, (char[]) rhs); + } else if (lhs instanceof byte[]) { + append((byte[]) lhs, (byte[]) rhs); + } else if (lhs instanceof double[]) { + append((double[]) lhs, (double[]) rhs); + } else if (lhs instanceof float[]) { + append((float[]) lhs, (float[]) rhs); + } else if (lhs instanceof boolean[]) { + append((boolean[]) lhs, (boolean[]) rhs); + } else { + // Not an array of primitives + append((Object[]) lhs, (Object[]) rhs); + } + return this; + } + + /** + *

+ * Test if two long s are equal. + *

+ * + * @param lhs + * the left hand long + * @param rhs + * the right hand long + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(long lhs, long rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } + + /** + *

Test if two ints are equal.

+ * + * @param lhs the left hand int + * @param rhs the right hand int + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(int lhs, int rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } + + /** + *

Test if two shorts are equal.

+ * + * @param lhs the left hand short + * @param rhs the right hand short + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(short lhs, short rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } + + /** + *

Test if two chars are equal.

+ * + * @param lhs the left hand char + * @param rhs the right hand char + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(char lhs, char rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } + + /** + *

Test if two bytes are equal.

+ * + * @param lhs the left hand byte + * @param rhs the right hand byte + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(byte lhs, byte rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } + + /** + *

Test if two doubles are equal by testing that the + * pattern of bits returned by doubleToLong are equal.

+ * + *

This handles NaNs, Infinities, and -0.0.

+ * + *

It is compatible with the hash code generated by + * HashCodeBuilder.

+ * + * @param lhs the left hand double + * @param rhs the right hand double + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(double lhs, double rhs) { + if (isEquals == false) { + return this; + } + return append(Double.doubleToLongBits(lhs), Double.doubleToLongBits(rhs)); + } + + /** + *

Test if two floats are equal byt testing that the + * pattern of bits returned by doubleToLong are equal.

+ * + *

This handles NaNs, Infinities, and -0.0.

+ * + *

It is compatible with the hash code generated by + * HashCodeBuilder.

+ * + * @param lhs the left hand float + * @param rhs the right hand float + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(float lhs, float rhs) { + if (isEquals == false) { + return this; + } + return append(Float.floatToIntBits(lhs), Float.floatToIntBits(rhs)); + } + + /** + *

Test if two booleanss are equal.

+ * + * @param lhs the left hand boolean + * @param rhs the right hand boolean + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(boolean lhs, boolean rhs) { + if (isEquals == false) { + return this; + } + isEquals = (lhs == rhs); + return this; + } + + /** + *

Performs a deep comparison of two Object arrays.

+ * + *

This also will be called for the top level of + * multi-dimensional, ragged, and multi-typed arrays.

+ * + * @param lhs the left hand Object[] + * @param rhs the right hand Object[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(Object[] lhs, Object[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

Deep comparison of array of long. Length and all + * values are compared.

+ * + *

The method {@link #append(long, long)} is used.

+ * + * @param lhs the left hand long[] + * @param rhs the right hand long[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(long[] lhs, long[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

Deep comparison of array of int. Length and all + * values are compared.

+ * + *

The method {@link #append(int, int)} is used.

+ * + * @param lhs the left hand int[] + * @param rhs the right hand int[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(int[] lhs, int[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

Deep comparison of array of short. Length and all + * values are compared.

+ * + *

The method {@link #append(short, short)} is used.

+ * + * @param lhs the left hand short[] + * @param rhs the right hand short[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(short[] lhs, short[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

Deep comparison of array of char. Length and all + * values are compared.

+ * + *

The method {@link #append(char, char)} is used.

+ * + * @param lhs the left hand char[] + * @param rhs the right hand char[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(char[] lhs, char[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

Deep comparison of array of byte. Length and all + * values are compared.

+ * + *

The method {@link #append(byte, byte)} is used.

+ * + * @param lhs the left hand byte[] + * @param rhs the right hand byte[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(byte[] lhs, byte[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

Deep comparison of array of double. Length and all + * values are compared.

+ * + *

The method {@link #append(double, double)} is used.

+ * + * @param lhs the left hand double[] + * @param rhs the right hand double[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(double[] lhs, double[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

Deep comparison of array of float. Length and all + * values are compared.

+ * + *

The method {@link #append(float, float)} is used.

+ * + * @param lhs the left hand float[] + * @param rhs the right hand float[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(float[] lhs, float[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

Deep comparison of array of boolean. Length and all + * values are compared.

+ * + *

The method {@link #append(boolean, boolean)} is used.

+ * + * @param lhs the left hand boolean[] + * @param rhs the right hand boolean[] + * @return EqualsBuilder - used to chain calls. + */ + public EqualsBuilder append(boolean[] lhs, boolean[] rhs) { + if (isEquals == false) { + return this; + } + if (lhs == rhs) { + return this; + } + if (lhs == null || rhs == null) { + this.setEquals(false); + return this; + } + if (lhs.length != rhs.length) { + this.setEquals(false); + return this; + } + for (int i = 0; i < lhs.length && isEquals; ++i) { + append(lhs[i], rhs[i]); + } + return this; + } + + /** + *

Returns true if the fields that have been checked + * are all equal.

+ * + * @return boolean + */ + public boolean isEquals() { + return this.isEquals; + } + + /** + * Sets the isEquals value. + * + * @param isEquals The value to set. + * @since 2.1 + */ + protected void setEquals(boolean isEquals) { + this.isEquals = isEquals; + } +} diff --git a/src/org/mockito/internal/matchers/apachecommons/ReflectionEquals.java b/src/org/mockito/internal/matchers/apachecommons/ReflectionEquals.java new file mode 100644 index 0000000000..c813ec90fa --- /dev/null +++ b/src/org/mockito/internal/matchers/apachecommons/ReflectionEquals.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockito.internal.matchers.apachecommons; + +import org.mockito.internal.matchers.ArgumentMatcher; + + +public class ReflectionEquals implements ArgumentMatcher{ + private final Object wanted; + + public ReflectionEquals(Object wanted) { + this.wanted = wanted; + } + + public boolean matches(Object actual) { + return EqualsBuilder.reflectionEquals(wanted, actual); + } + + public void appendTo(StringBuilder buffer) { + buffer.append("refEq(" + wanted + ")"); + } +} \ No newline at end of file diff --git a/test/org/mockito/internal/matchers/apachecommons/EqualsBuilderTest.java b/test/org/mockito/internal/matchers/apachecommons/EqualsBuilderTest.java new file mode 100644 index 0000000000..87adbe9111 --- /dev/null +++ b/test/org/mockito/internal/matchers/apachecommons/EqualsBuilderTest.java @@ -0,0 +1,1013 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ + +//Class comes from Apache Commons Lang, added some tiny changes + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.mockito.internal.matchers.apachecommons; + +import static org.junit.Assert.*; + +import java.math.BigDecimal; +import java.util.Arrays; + +import org.junit.Test; +import org.mockito.RequiresValidState; + +/** + * @author Steve Downey + * @author Stephen Colebourne + * @author Gary Gregory + * @author Maarten Coene + * @version $Id: EqualsBuilderTest.java 611543 2008-01-13 07:00:22Z bayard $ + */ +public class EqualsBuilderTest extends RequiresValidState { + + @Test + public void testname() throws Exception { + + } + + static class TestObject { + private int a; + public TestObject() { + } + public TestObject(int a) { + this.a = a; + } + public boolean equals(Object o) { + if (o == null) { return false; } + if (o == this) { return true; } + if (o.getClass() != getClass()) { + return false; + } + + TestObject rhs = (TestObject) o; + return (a == rhs.a); + } + public int hashCode() { + return super.hashCode(); + } + + public void setA(int a) { + this.a = a; + } + + public int getA() { + return a; + } + } + + static class TestSubObject extends TestObject { + private int b; + public TestSubObject() { + super(0); + } + public TestSubObject(int a, int b) { + super(a); + this.b = b; + } + public boolean equals(Object o) { + if (o == null) { return false; } + if (o == this) { return true; } + if (o.getClass() != getClass()) { + return false; + } + + TestSubObject rhs = (TestSubObject) o; + return super.equals(o) && (b == rhs.b); + } + public int hashCode() { + return 1; + } + + public void setB(int b) { + this.b = b; + } + + public int getB() { + return b; + } + } + + static class TestEmptySubObject extends TestObject { + public TestEmptySubObject(int a) { + super(a); + } + } + + @SuppressWarnings("unused") + static class TestTSubObject extends TestObject { + private transient int t; + public TestTSubObject(int a, int t) { + super(a); + this.t = t; + } + } + + @SuppressWarnings("unused") + static class TestTTSubObject extends TestTSubObject { + private transient int tt; + public TestTTSubObject(int a, int t, int tt) { + super(a, t); + this.tt = tt; + } + } + + @SuppressWarnings("unused") + static class TestTTLeafObject extends TestTTSubObject { + private int leafValue; + public TestTTLeafObject(int a, int t, int tt, int leafValue) { + super(a, t, tt); + this.leafValue = leafValue; + } + } + + static class TestTSubObject2 extends TestObject { + private transient int t; + public TestTSubObject2(int a, int t) { + super(a); + } + public int getT() { + return t; + } + public void setT(int t) { + this.t = t; + } + } + + @Test public void testReflectionEquals() { + TestObject o1 = new TestObject(4); + TestObject o2 = new TestObject(5); + assertTrue(EqualsBuilder.reflectionEquals(o1, o1)); + assertTrue(!EqualsBuilder.reflectionEquals(o1, o2)); + o2.setA(4); + assertTrue(EqualsBuilder.reflectionEquals(o1, o2)); + + assertTrue(!EqualsBuilder.reflectionEquals(o1, this)); + + assertTrue(!EqualsBuilder.reflectionEquals(o1, null)); + assertTrue(!EqualsBuilder.reflectionEquals(null, o2)); + assertTrue(EqualsBuilder.reflectionEquals((Object) null, (Object) null)); + } + + @Test public void testReflectionHierarchyEquals() { + testReflectionHierarchyEquals(false); + testReflectionHierarchyEquals(true); + // Transients + assertTrue(EqualsBuilder.reflectionEquals(new TestTTLeafObject(1, 2, 3, 4), new TestTTLeafObject(1, 2, 3, 4), true)); + assertTrue(EqualsBuilder.reflectionEquals(new TestTTLeafObject(1, 2, 3, 4), new TestTTLeafObject(1, 2, 3, 4), false)); + assertTrue(!EqualsBuilder.reflectionEquals(new TestTTLeafObject(1, 0, 0, 4), new TestTTLeafObject(1, 2, 3, 4), true)); + assertTrue(!EqualsBuilder.reflectionEquals(new TestTTLeafObject(1, 2, 3, 4), new TestTTLeafObject(1, 2, 3, 0), true)); + assertTrue(!EqualsBuilder.reflectionEquals(new TestTTLeafObject(0, 2, 3, 4), new TestTTLeafObject(1, 2, 3, 4), true)); + } + + private void testReflectionHierarchyEquals(boolean testTransients) { + TestObject to1 = new TestObject(4); + TestObject to1Bis = new TestObject(4); + TestObject to1Ter = new TestObject(4); + TestObject to2 = new TestObject(5); + TestEmptySubObject teso = new TestEmptySubObject(4); + TestTSubObject ttso = new TestTSubObject(4, 1); + TestTTSubObject tttso = new TestTTSubObject(4, 1, 2); + TestTTLeafObject ttlo = new TestTTLeafObject(4, 1, 2, 3); + TestSubObject tso1 = new TestSubObject(1, 4); + TestSubObject tso1bis = new TestSubObject(1, 4); + TestSubObject tso1ter = new TestSubObject(1, 4); + TestSubObject tso2 = new TestSubObject(2, 5); + + testReflectionEqualsEquivalenceRelationship(to1, to1Bis, to1Ter, to2, new TestObject(), testTransients); + testReflectionEqualsEquivalenceRelationship(tso1, tso1bis, tso1ter, tso2, new TestSubObject(), testTransients); + + // More sanity checks: + + // same values + assertTrue(EqualsBuilder.reflectionEquals(ttlo, ttlo, testTransients)); + assertTrue(EqualsBuilder.reflectionEquals(new TestSubObject(1, 10), new TestSubObject(1, 10), testTransients)); + // same super values, diff sub values + assertTrue(!EqualsBuilder.reflectionEquals(new TestSubObject(1, 10), new TestSubObject(1, 11), testTransients)); + assertTrue(!EqualsBuilder.reflectionEquals(new TestSubObject(1, 11), new TestSubObject(1, 10), testTransients)); + // diff super values, same sub values + assertTrue(!EqualsBuilder.reflectionEquals(new TestSubObject(0, 10), new TestSubObject(1, 10), testTransients)); + assertTrue(!EqualsBuilder.reflectionEquals(new TestSubObject(1, 10), new TestSubObject(0, 10), testTransients)); + + // mix super and sub types: equals + assertTrue(EqualsBuilder.reflectionEquals(to1, teso, testTransients)); + assertTrue(EqualsBuilder.reflectionEquals(teso, to1, testTransients)); + + assertTrue(EqualsBuilder.reflectionEquals(to1, ttso, false)); // Force testTransients = false for this assert + assertTrue(EqualsBuilder.reflectionEquals(ttso, to1, false)); // Force testTransients = false for this assert + + assertTrue(EqualsBuilder.reflectionEquals(to1, tttso, false)); // Force testTransients = false for this assert + assertTrue(EqualsBuilder.reflectionEquals(tttso, to1, false)); // Force testTransients = false for this assert + + assertTrue(EqualsBuilder.reflectionEquals(ttso, tttso, false)); // Force testTransients = false for this assert + assertTrue(EqualsBuilder.reflectionEquals(tttso, ttso, false)); // Force testTransients = false for this assert + + // mix super and sub types: NOT equals + assertTrue(!EqualsBuilder.reflectionEquals(new TestObject(0), new TestEmptySubObject(1), testTransients)); + assertTrue(!EqualsBuilder.reflectionEquals(new TestEmptySubObject(1), new TestObject(0), testTransients)); + + assertTrue(!EqualsBuilder.reflectionEquals(new TestObject(0), new TestTSubObject(1, 1), testTransients)); + assertTrue(!EqualsBuilder.reflectionEquals(new TestTSubObject(1, 1), new TestObject(0), testTransients)); + + assertTrue(!EqualsBuilder.reflectionEquals(new TestObject(1), new TestSubObject(0, 10), testTransients)); + assertTrue(!EqualsBuilder.reflectionEquals(new TestSubObject(0, 10), new TestObject(1), testTransients)); + + assertTrue(!EqualsBuilder.reflectionEquals(to1, ttlo)); + assertTrue(!EqualsBuilder.reflectionEquals(tso1, this)); + } + + /** + * Equivalence relationship tests inspired by "Effective Java": + *
    + *
  • reflection
  • + *
  • symmetry
  • + *
  • transitive
  • + *
  • consistency
  • + *
  • non-null reference
  • + *
+ * @param to a TestObject + * @param toBis a TestObject, equal to to and toTer + * @param toTer Left hand side, equal to to and toBis + * @param to2 a different TestObject + * @param oToChange a TestObject that will be changed + */ + private void testReflectionEqualsEquivalenceRelationship( + TestObject to, + TestObject toBis, + TestObject toTer, + TestObject to2, + TestObject oToChange, + boolean testTransients) { + + // reflection test + assertTrue(EqualsBuilder.reflectionEquals(to, to, testTransients)); + assertTrue(EqualsBuilder.reflectionEquals(to2, to2, testTransients)); + + // symmetry test + assertTrue(EqualsBuilder.reflectionEquals(to, toBis, testTransients) && EqualsBuilder.reflectionEquals(toBis, to, testTransients)); + + // transitive test + assertTrue( + EqualsBuilder.reflectionEquals(to, toBis, testTransients) + && EqualsBuilder.reflectionEquals(toBis, toTer, testTransients) + && EqualsBuilder.reflectionEquals(to, toTer, testTransients)); + + // consistency test + oToChange.setA(to.getA()); + if (oToChange instanceof TestSubObject) { + ((TestSubObject) oToChange).setB(((TestSubObject) to).getB()); + } + assertTrue(EqualsBuilder.reflectionEquals(oToChange, to, testTransients)); + assertTrue(EqualsBuilder.reflectionEquals(oToChange, to, testTransients)); + oToChange.setA(to.getA() + 1); + if (oToChange instanceof TestSubObject) { + ((TestSubObject) oToChange).setB(((TestSubObject) to).getB() + 1); + } + assertTrue(!EqualsBuilder.reflectionEquals(oToChange, to, testTransients)); + assertTrue(!EqualsBuilder.reflectionEquals(oToChange, to, testTransients)); + + // non-null reference test + assertTrue(!EqualsBuilder.reflectionEquals(to, null, testTransients)); + assertTrue(!EqualsBuilder.reflectionEquals(to2, null, testTransients)); + assertTrue(!EqualsBuilder.reflectionEquals(null, to, testTransients)); + assertTrue(!EqualsBuilder.reflectionEquals(null, to2, testTransients)); + assertTrue(EqualsBuilder.reflectionEquals((Object) null, (Object) null, testTransients)); + } + + @Test public void testSuper() { + TestObject o1 = new TestObject(4); + TestObject o2 = new TestObject(5); + assertEquals(true, new EqualsBuilder().appendSuper(true).append(o1, o1).isEquals()); + assertEquals(false, new EqualsBuilder().appendSuper(false).append(o1, o1).isEquals()); + assertEquals(false, new EqualsBuilder().appendSuper(true).append(o1, o2).isEquals()); + assertEquals(false, new EqualsBuilder().appendSuper(false).append(o1, o2).isEquals()); + } + + @Test public void testObject() { + TestObject o1 = new TestObject(4); + TestObject o2 = new TestObject(5); + assertTrue(new EqualsBuilder().append(o1, o1).isEquals()); + assertTrue(!new EqualsBuilder().append(o1, o2).isEquals()); + o2.setA(4); + assertTrue(new EqualsBuilder().append(o1, o2).isEquals()); + + assertTrue(!new EqualsBuilder().append(o1, this).isEquals()); + + assertTrue(!new EqualsBuilder().append(o1, null).isEquals()); + assertTrue(!new EqualsBuilder().append(null, o2).isEquals()); + assertTrue(new EqualsBuilder().append((Object) null, (Object) null).isEquals()); + } + + @Test public void testLong() { + long o1 = 1L; + long o2 = 2L; + assertTrue(new EqualsBuilder().append(o1, o1).isEquals()); + assertTrue(!new EqualsBuilder().append(o1, o2).isEquals()); + } + + @Test public void testInt() { + int o1 = 1; + int o2 = 2; + assertTrue(new EqualsBuilder().append(o1, o1).isEquals()); + assertTrue(!new EqualsBuilder().append(o1, o2).isEquals()); + } + + @Test public void testShort() { + short o1 = 1; + short o2 = 2; + assertTrue(new EqualsBuilder().append(o1, o1).isEquals()); + assertTrue(!new EqualsBuilder().append(o1, o2).isEquals()); + } + + @Test public void testChar() { + char o1 = 1; + char o2 = 2; + assertTrue(new EqualsBuilder().append(o1, o1).isEquals()); + assertTrue(!new EqualsBuilder().append(o1, o2).isEquals()); + } + + @Test public void testByte() { + byte o1 = 1; + byte o2 = 2; + assertTrue(new EqualsBuilder().append(o1, o1).isEquals()); + assertTrue(!new EqualsBuilder().append(o1, o2).isEquals()); + } + + @Test public void testDouble() { + double o1 = 1; + double o2 = 2; + assertTrue(new EqualsBuilder().append(o1, o1).isEquals()); + assertTrue(!new EqualsBuilder().append(o1, o2).isEquals()); + assertTrue(!new EqualsBuilder().append(o1, Double.NaN).isEquals()); + assertTrue(new EqualsBuilder().append(Double.NaN, Double.NaN).isEquals()); + assertTrue(new EqualsBuilder().append(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY).isEquals()); + } + + @Test public void testFloat() { + float o1 = 1; + float o2 = 2; + assertTrue(new EqualsBuilder().append(o1, o1).isEquals()); + assertTrue(!new EqualsBuilder().append(o1, o2).isEquals()); + assertTrue(!new EqualsBuilder().append(o1, Float.NaN).isEquals()); + assertTrue(new EqualsBuilder().append(Float.NaN, Float.NaN).isEquals()); + assertTrue(new EqualsBuilder().append(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY).isEquals()); + } + + // https://issues.apache.org/jira/browse/LANG-393 + @Test public void testBigDecimal() { + BigDecimal o1 = new BigDecimal("2.0"); + BigDecimal o2 = new BigDecimal("2.00"); + assertTrue(new EqualsBuilder().append(o1, o1).isEquals()); + assertTrue(new EqualsBuilder().append(o1, o2).isEquals()); + } + + @Test public void testAccessors() { + EqualsBuilder equalsBuilder = new EqualsBuilder(); + assertTrue(equalsBuilder.isEquals()); + equalsBuilder.setEquals(true); + assertTrue(equalsBuilder.isEquals()); + equalsBuilder.setEquals(false); + assertFalse(equalsBuilder.isEquals()); + } + + @Test public void testBoolean() { + boolean o1 = true; + boolean o2 = false; + assertTrue(new EqualsBuilder().append(o1, o1).isEquals()); + assertTrue(!new EqualsBuilder().append(o1, o2).isEquals()); + } + + @Test public void testObjectArray() { + TestObject[] obj1 = new TestObject[3]; + obj1[0] = new TestObject(4); + obj1[1] = new TestObject(5); + obj1[2] = null; + TestObject[] obj2 = new TestObject[3]; + obj2[0] = new TestObject(4); + obj2[1] = new TestObject(5); + obj2[2] = null; + + assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals()); + assertTrue(new EqualsBuilder().append(obj2, obj2).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + obj1[1].setA(6); + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + obj1[1].setA(5); + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + obj1[2] = obj1[1]; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + obj1[2] = null; + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + + obj2 = null; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + obj1 = null; + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + } + + @Test public void testLongArray() { + long[] obj1 = new long[2]; + obj1[0] = 5L; + obj1[1] = 6L; + long[] obj2 = new long[2]; + obj2[0] = 5L; + obj2[1] = 6L; + assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + obj1[1] = 7; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + + obj2 = null; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + obj1 = null; + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + } + + @Test public void testIntArray() { + int[] obj1 = new int[2]; + obj1[0] = 5; + obj1[1] = 6; + int[] obj2 = new int[2]; + obj2[0] = 5; + obj2[1] = 6; + assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + obj1[1] = 7; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + + obj2 = null; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + obj1 = null; + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + } + + @Test public void testShortArray() { + short[] obj1 = new short[2]; + obj1[0] = 5; + obj1[1] = 6; + short[] obj2 = new short[2]; + obj2[0] = 5; + obj2[1] = 6; + assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + obj1[1] = 7; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + + obj2 = null; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + obj1 = null; + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + } + + @Test public void testCharArray() { + char[] obj1 = new char[2]; + obj1[0] = 5; + obj1[1] = 6; + char[] obj2 = new char[2]; + obj2[0] = 5; + obj2[1] = 6; + assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + obj1[1] = 7; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + + obj2 = null; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + obj1 = null; + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + } + + @Test public void testByteArray() { + byte[] obj1 = new byte[2]; + obj1[0] = 5; + obj1[1] = 6; + byte[] obj2 = new byte[2]; + obj2[0] = 5; + obj2[1] = 6; + assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + obj1[1] = 7; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + + obj2 = null; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + obj1 = null; + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + } + + @Test public void testDoubleArray() { + double[] obj1 = new double[2]; + obj1[0] = 5; + obj1[1] = 6; + double[] obj2 = new double[2]; + obj2[0] = 5; + obj2[1] = 6; + assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + obj1[1] = 7; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + + obj2 = null; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + obj1 = null; + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + } + + @Test public void testFloatArray() { + float[] obj1 = new float[2]; + obj1[0] = 5; + obj1[1] = 6; + float[] obj2 = new float[2]; + obj2[0] = 5; + obj2[1] = 6; + assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + obj1[1] = 7; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + + obj2 = null; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + obj1 = null; + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + } + + @Test public void testBooleanArray() { + boolean[] obj1 = new boolean[2]; + obj1[0] = true; + obj1[1] = false; + boolean[] obj2 = new boolean[2]; + obj2[0] = true; + obj2[1] = false; + assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + obj1[1] = true; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + + obj2 = null; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + obj1 = null; + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + } + + @Test public void testMultiLongArray() { + long[][] array1 = new long[2][2]; + long[][] array2 = new long[2][2]; + for (int i = 0; i < array1.length; ++i) { + for (int j = 0; j < array1[0].length; j++) { + array1[i][j] = (i + 1) * (j + 1); + array2[i][j] = (i + 1) * (j + 1); + } + } + assertTrue(new EqualsBuilder().append(array1, array1).isEquals()); + assertTrue(new EqualsBuilder().append(array1, array2).isEquals()); + array1[1][1] = 0; + assertTrue(!new EqualsBuilder().append(array1, array2).isEquals()); + } + + @Test public void testMultiIntArray() { + int[][] array1 = new int[2][2]; + int[][] array2 = new int[2][2]; + for (int i = 0; i < array1.length; ++i) { + for (int j = 0; j < array1[0].length; j++) { + array1[i][j] = (i + 1) * (j + 1); + array2[i][j] = (i + 1) * (j + 1); + } + } + assertTrue(new EqualsBuilder().append(array1, array1).isEquals()); + assertTrue(new EqualsBuilder().append(array1, array2).isEquals()); + array1[1][1] = 0; + assertTrue(!new EqualsBuilder().append(array1, array2).isEquals()); + } + + @Test public void testMultiShortArray() { + short[][] array1 = new short[2][2]; + short[][] array2 = new short[2][2]; + for (short i = 0; i < array1.length; ++i) { + for (short j = 0; j < array1[0].length; j++) { + array1[i][j] = i; + array2[i][j] = i; + } + } + assertTrue(new EqualsBuilder().append(array1, array1).isEquals()); + assertTrue(new EqualsBuilder().append(array1, array2).isEquals()); + array1[1][1] = 0; + assertTrue(!new EqualsBuilder().append(array1, array2).isEquals()); + } + + @Test public void testMultiCharArray() { + char[][] array1 = new char[2][2]; + char[][] array2 = new char[2][2]; + for (char i = 0; i < array1.length; ++i) { + for (char j = 0; j < array1[0].length; j++) { + array1[i][j] = i; + array2[i][j] = i; + } + } + assertTrue(new EqualsBuilder().append(array1, array1).isEquals()); + assertTrue(new EqualsBuilder().append(array1, array2).isEquals()); + array1[1][1] = 0; + assertTrue(!new EqualsBuilder().append(array1, array2).isEquals()); + } + + @Test public void testMultiByteArray() { + byte[][] array1 = new byte[2][2]; + byte[][] array2 = new byte[2][2]; + for (byte i = 0; i < array1.length; ++i) { + for (byte j = 0; j < array1[0].length; j++) { + array1[i][j] = i; + array2[i][j] = i; + } + } + assertTrue(new EqualsBuilder().append(array1, array1).isEquals()); + assertTrue(new EqualsBuilder().append(array1, array2).isEquals()); + array1[1][1] = 0; + assertTrue(!new EqualsBuilder().append(array1, array2).isEquals()); + } + @Test public void testMultiFloatArray() { + float[][] array1 = new float[2][2]; + float[][] array2 = new float[2][2]; + for (int i = 0; i < array1.length; ++i) { + for (int j = 0; j < array1[0].length; j++) { + array1[i][j] = (i + 1) * (j + 1); + array2[i][j] = (i + 1) * (j + 1); + } + } + assertTrue(new EqualsBuilder().append(array1, array1).isEquals()); + assertTrue(new EqualsBuilder().append(array1, array2).isEquals()); + array1[1][1] = 0; + assertTrue(!new EqualsBuilder().append(array1, array2).isEquals()); + } + + @Test public void testMultiDoubleArray() { + double[][] array1 = new double[2][2]; + double[][] array2 = new double[2][2]; + for (int i = 0; i < array1.length; ++i) { + for (int j = 0; j < array1[0].length; j++) { + array1[i][j] = (i + 1) * (j + 1); + array2[i][j] = (i + 1) * (j + 1); + } + } + assertTrue(new EqualsBuilder().append(array1, array1).isEquals()); + assertTrue(new EqualsBuilder().append(array1, array2).isEquals()); + array1[1][1] = 0; + assertTrue(!new EqualsBuilder().append(array1, array2).isEquals()); + } + + @Test public void testMultiBooleanArray() { + boolean[][] array1 = new boolean[2][2]; + boolean[][] array2 = new boolean[2][2]; + for (int i = 0; i < array1.length; ++i) { + for (int j = 0; j < array1[0].length; j++) { + array1[i][j] = (i == 1) || (j == 1); + array2[i][j] = (i == 1) || (j == 1); + } + } + assertTrue(new EqualsBuilder().append(array1, array1).isEquals()); + assertTrue(new EqualsBuilder().append(array1, array2).isEquals()); + array1[1][1] = false; + assertTrue(!new EqualsBuilder().append(array1, array2).isEquals()); + + // compare 1 dim to 2. + boolean[] array3 = new boolean[]{true, true}; + assertFalse(new EqualsBuilder().append(array1, array3).isEquals()); + assertFalse(new EqualsBuilder().append(array3, array1).isEquals()); + assertFalse(new EqualsBuilder().append(array2, array3).isEquals()); + assertFalse(new EqualsBuilder().append(array3, array2).isEquals()); + } + + @Test public void testRaggedArray() { + long[][] array1 = new long[2][]; + long[][] array2 = new long[2][]; + for (int i = 0; i < array1.length; ++i) { + array1[i] = new long[2]; + array2[i] = new long[2]; + for (int j = 0; j < array1[i].length; ++j) { + array1[i][j] = (i + 1) * (j + 1); + array2[i][j] = (i + 1) * (j + 1); + } + } + assertTrue(new EqualsBuilder().append(array1, array1).isEquals()); + assertTrue(new EqualsBuilder().append(array1, array2).isEquals()); + array1[1][1] = 0; + assertTrue(!new EqualsBuilder().append(array1, array2).isEquals()); + } + + @Test public void testMixedArray() { + Object[] array1 = new Object[2]; + Object[] array2 = new Object[2]; + for (int i = 0; i < array1.length; ++i) { + array1[i] = new long[2]; + array2[i] = new long[2]; + for (int j = 0; j < 2; ++j) { + ((long[]) array1[i])[j] = (i + 1) * (j + 1); + ((long[]) array2[i])[j] = (i + 1) * (j + 1); + } + } + assertTrue(new EqualsBuilder().append(array1, array1).isEquals()); + assertTrue(new EqualsBuilder().append(array1, array2).isEquals()); + ((long[]) array1[1])[1] = 0; + assertTrue(!new EqualsBuilder().append(array1, array2).isEquals()); + } + + @Test public void testObjectArrayHiddenByObject() { + TestObject[] array1 = new TestObject[2]; + array1[0] = new TestObject(4); + array1[1] = new TestObject(5); + TestObject[] array2 = new TestObject[2]; + array2[0] = new TestObject(4); + array2[1] = new TestObject(5); + Object obj1 = array1; + Object obj2 = array2; + assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, array1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, array2).isEquals()); + array1[1].setA(6); + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + } + + @Test public void testLongArrayHiddenByObject() { + long[] array1 = new long[2]; + array1[0] = 5L; + array1[1] = 6L; + long[] array2 = new long[2]; + array2[0] = 5L; + array2[1] = 6L; + Object obj1 = array1; + Object obj2 = array2; + assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, array1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, array2).isEquals()); + array1[1] = 7; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + } + + @Test public void testIntArrayHiddenByObject() { + int[] array1 = new int[2]; + array1[0] = 5; + array1[1] = 6; + int[] array2 = new int[2]; + array2[0] = 5; + array2[1] = 6; + Object obj1 = array1; + Object obj2 = array2; + assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, array1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, array2).isEquals()); + array1[1] = 7; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + } + + @Test public void testShortArrayHiddenByObject() { + short[] array1 = new short[2]; + array1[0] = 5; + array1[1] = 6; + short[] array2 = new short[2]; + array2[0] = 5; + array2[1] = 6; + Object obj1 = array1; + Object obj2 = array2; + assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, array1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, array2).isEquals()); + array1[1] = 7; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + } + + @Test public void testCharArrayHiddenByObject() { + char[] array1 = new char[2]; + array1[0] = 5; + array1[1] = 6; + char[] array2 = new char[2]; + array2[0] = 5; + array2[1] = 6; + Object obj1 = array1; + Object obj2 = array2; + assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, array1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, array2).isEquals()); + array1[1] = 7; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + } + + @Test public void testByteArrayHiddenByObject() { + byte[] array1 = new byte[2]; + array1[0] = 5; + array1[1] = 6; + byte[] array2 = new byte[2]; + array2[0] = 5; + array2[1] = 6; + Object obj1 = array1; + Object obj2 = array2; + assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, array1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, array2).isEquals()); + array1[1] = 7; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + } + + @Test public void testDoubleArrayHiddenByObject() { + double[] array1 = new double[2]; + array1[0] = 5; + array1[1] = 6; + double[] array2 = new double[2]; + array2[0] = 5; + array2[1] = 6; + Object obj1 = array1; + Object obj2 = array2; + assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, array1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, array2).isEquals()); + array1[1] = 7; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + } + + @Test public void testFloatArrayHiddenByObject() { + float[] array1 = new float[2]; + array1[0] = 5; + array1[1] = 6; + float[] array2 = new float[2]; + array2[0] = 5; + array2[1] = 6; + Object obj1 = array1; + Object obj2 = array2; + assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, array1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, array2).isEquals()); + array1[1] = 7; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + } + + @Test public void testBooleanArrayHiddenByObject() { + boolean[] array1 = new boolean[2]; + array1[0] = true; + array1[1] = false; + boolean[] array2 = new boolean[2]; + array2[0] = true; + array2[1] = false; + Object obj1 = array1; + Object obj2 = array2; + assertTrue(new EqualsBuilder().append(obj1, obj1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, array1).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, obj2).isEquals()); + assertTrue(new EqualsBuilder().append(obj1, array2).isEquals()); + array1[1] = true; + assertTrue(!new EqualsBuilder().append(obj1, obj2).isEquals()); + } + + public static class TestACanEqualB { + private int a; + + public TestACanEqualB(int a) { + this.a = a; + } + + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o instanceof TestACanEqualB) { + return this.a == ((TestACanEqualB) o).getA(); + } + if (o instanceof TestBCanEqualA) { + return this.a == ((TestBCanEqualA) o).getB(); + } + return false; + } + public int hashCode() { + return 1; + } + + public int getA() { + return this.a; + } + } + + public static class TestBCanEqualA { + private int b; + + public TestBCanEqualA(int b) { + this.b = b; + } + + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o instanceof TestACanEqualB) { + return this.b == ((TestACanEqualB) o).getA(); + } + if (o instanceof TestBCanEqualA) { + return this.b == ((TestBCanEqualA) o).getB(); + } + return false; + } + public int hashCode() { + return 1; + } + + public int getB() { + return this.b; + } + } + + /** + * Tests two instances of classes that can be equal and that are not "related". The two classes are not subclasses + * of each other and do not share a parent aside from Object. + * See http://issues.apache.org/bugzilla/show_bug.cgi?id=33069 + */ + @Test public void testUnrelatedClasses() { + Object[] x = new Object[]{new TestACanEqualB(1)}; + Object[] y = new Object[]{new TestBCanEqualA(1)}; + + // sanity checks: + assertTrue(Arrays.equals(x, x)); + assertTrue(Arrays.equals(y, y)); + assertTrue(Arrays.equals(x, y)); + assertTrue(Arrays.equals(y, x)); + // real tests: + assertTrue(x[0].equals(x[0])); + assertTrue(y[0].equals(y[0])); + assertTrue(x[0].equals(y[0])); + assertTrue(y[0].equals(x[0])); + assertTrue(new EqualsBuilder().append(x, x).isEquals()); + assertTrue(new EqualsBuilder().append(y, y).isEquals()); + assertTrue(new EqualsBuilder().append(x, y).isEquals()); + assertTrue(new EqualsBuilder().append(y, x).isEquals()); + } + + /** + * Test from http://issues.apache.org/bugzilla/show_bug.cgi?id=33067 + */ + @Test public void testNpeForNullElement() { + Object[] x1 = new Object[] { new Integer(1), null, new Integer(3) }; + Object[] x2 = new Object[] { new Integer(1), new Integer(2), new Integer(3) }; + + // causes an NPE in 2.0 according to: + // http://issues.apache.org/bugzilla/show_bug.cgi?id=33067 + new EqualsBuilder().append(x1, x2); + } + + @Test public void testReflectionEqualsExcludeFields() throws Exception { + TestObjectWithMultipleFields x1 = new TestObjectWithMultipleFields(1, 2, 3); + TestObjectWithMultipleFields x2 = new TestObjectWithMultipleFields(1, 3, 4); + + // not equal when including all fields + assertTrue(!EqualsBuilder.reflectionEquals(x1, x2)); + + // doesn't barf on null, empty array, or non-existent field, but still tests as not equal + assertTrue(!EqualsBuilder.reflectionEquals(x1, x2, (String[]) null)); + assertTrue(!EqualsBuilder.reflectionEquals(x1, x2, new String[] {})); + assertTrue(!EqualsBuilder.reflectionEquals(x1, x2, new String[] {"xxx"})); + + // not equal if only one of the differing fields excluded + assertTrue(!EqualsBuilder.reflectionEquals(x1, x2, new String[] {"two"})); + assertTrue(!EqualsBuilder.reflectionEquals(x1, x2, new String[] {"three"})); + + // equal if both differing fields excluded + assertTrue(EqualsBuilder.reflectionEquals(x1, x2, new String[] {"two", "three"})); + + // still equal as long as both differing fields are among excluded + assertTrue(EqualsBuilder.reflectionEquals(x1, x2, new String[] {"one", "two", "three"})); + assertTrue(EqualsBuilder.reflectionEquals(x1, x2, new String[] {"one", "two", "three", "xxx"})); + } + + @SuppressWarnings("unused") + static class TestObjectWithMultipleFields { + private TestObject one; + private TestObject two; + private TestObject three; + + public TestObjectWithMultipleFields(int one, int two, int three) { + this.one = new TestObject(one); + this.two = new TestObject(two); + this.three = new TestObject(three); + } + } +} \ No newline at end of file diff --git a/test/org/mockitousage/ReflectionMatchersTest.java b/test/org/mockitousage/ReflectionMatchersTest.java new file mode 100644 index 0000000000..77cad37c07 --- /dev/null +++ b/test/org/mockitousage/ReflectionMatchersTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2007 Mockito contributors + * This program is made available under the terms of the MIT License. + */ +package org.mockitousage; + +import static org.mockito.Mockito.*; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.RequiresValidState; +import org.mockito.exceptions.verification.InvocationDiffersFromActual; + +@SuppressWarnings("all") +public class ReflectionMatchersTest extends RequiresValidState { + + class Parent { + private int parentField; + protected String protectedParentField; + public Parent(int parentField, String protectedParentField) { + this.parentField = parentField; + this.protectedParentField = protectedParentField; + } + } + + class Child extends Parent { + private int childFieldOne; + private Object childFieldTwo; + public Child(int parentField, String protectedParentField, int childFieldOne, Object childFieldTwo) { + super(parentField, protectedParentField); + this.childFieldOne = childFieldOne; + this.childFieldTwo = childFieldTwo; + } + } + + interface MockMe { + void run(Child child); + } + + MockMe mock; + + @Before + public void setup() { + mock = mock(MockMe.class); + + Child actual = new Child(1, "foo", 2, "bar"); + mock.run(actual); + } + + @Test + public void shouldMatchWhenFieldValuesEqual() throws Exception { + Child wanted = new Child(1, "foo", 2, "bar"); + verify(mock).run(refEq(wanted)); + } + + @Test(expected=InvocationDiffersFromActual.class) + public void shouldNotMatchWhenFieldValuesDiffer() throws Exception { + Child wanted = new Child(1, "foo", 2, "bar XXX"); + verify(mock).run(refEq(wanted)); + } + + @Test(expected=InvocationDiffersFromActual.class) + public void shouldNotMatchAgain() throws Exception { + Child wanted = new Child(1, "foo", 999, "bar"); + verify(mock).run(refEq(wanted)); + } + + @Test(expected=InvocationDiffersFromActual.class) + public void shouldNotMatchYetAgain() throws Exception { + Child wanted = new Child(1, "XXXXX", 2, "bar"); + verify(mock).run(refEq(wanted)); + } + + @Test(expected=InvocationDiffersFromActual.class) + public void shouldNotMatch() throws Exception { + Child wanted = new Child(234234, "foo", 2, "bar"); + verify(mock).run(refEq(wanted)); + } +} \ No newline at end of file