Skip to content

Commit

Permalink
Allow to ignore custom equals method by fields.
Browse files Browse the repository at this point in the history
  • Loading branch information
joel-costigliola committed Feb 13, 2019
1 parent 72ab032 commit 371032c
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 50 deletions.
@@ -1,7 +1,11 @@
package org.assertj.core.api.recursive.comparison;

import static java.util.stream.Collectors.toList;

import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

// TODO should understand Map keys as field
public class FieldLocation implements Comparable<FieldLocation> {
Expand Down Expand Up @@ -48,4 +52,8 @@ public boolean matches(String concatenatedPath) {
return fieldPath.equals(concatenatedPath);
}

static List<FieldLocation> from(String... fieldPaths) {
return Stream.of(fieldPaths).map(FieldLocation::new).collect(toList());
}

}
Expand Up @@ -16,27 +16,26 @@

import org.assertj.core.annotations.Beta;
import org.assertj.core.presentation.Representation;
import org.assertj.core.util.VisibleForTesting;

@Beta
public class RecursiveComparisonConfiguration {

public static final String INDENT_LEVEL_2 = "---";
// private boolean strictTypeCheck = true;

// fields to ignore section
private boolean ignoreAllActualNullFields = false;
private Set<FieldLocation> ignoredFields = new LinkedHashSet<>();

private List<Pattern> ignoredFieldsRegexes = new ArrayList<>();
// overridden equals method to ignore section
private List<Class<?>> ignoredOverriddenEqualsForTypes = new ArrayList<>();
private List<FieldLocation> ignoredOverriddenEqualsForFields = new ArrayList<>();
private List<Pattern> ignoredOverriddenEqualsRegexes = new ArrayList<>();
private List<Class> ignoredOverriddenEqualsForTypes = new ArrayList<>();

// private Set<Class> forceRecursiveComparisonForTypes = new HashSet<>();
// private Set<FieldLocation> forceRecursiveComparisonForFields = new HashSet<>();

// private TypeComparators comparatorForTypes = new TypeComparators();
// private FieldComparators comparatorForFields = new FieldComparators();

private List<Pattern> ignoredFieldsRegexes = new ArrayList<>();

public Comparator getComparatorForField(String fieldName) {
return null;
}
Expand All @@ -57,10 +56,6 @@ public boolean hasNoCustomComparators() {
return false;
}

public boolean shouldIgnoreAllActualNullFields() {
return ignoreAllActualNullFields;
}

/**
* Sets whether actual null fields are ignored in the recursive comparison.
* <p>
Expand All @@ -80,7 +75,7 @@ public void setIgnoreAllActualNullFields(boolean ignoreAllActualNullFields) {
* @param fieldPaths the field paths to be ignored in the comparison
*/
public void ignoreFields(String... fieldPaths) {
List<FieldLocation> fieldLocations = Stream.of(fieldPaths).map(FieldLocation::new).collect(toList());
List<FieldLocation> fieldLocations = FieldLocation.from(fieldPaths);
ignoredFields.addAll(fieldLocations);
}

Expand All @@ -102,34 +97,43 @@ public void ignoreFieldsByRegexes(String... regexes) {

public void ignoreOverriddenEqualsByRegexes(String... regexes) {
this.ignoredOverriddenEqualsRegexes = Stream.of(regexes)
.map(Pattern::compile)
.collect(toList());
.map(Pattern::compile)
.collect(toList());
}

public void ignoreOverriddenEqualsForTypes(Class... types) {
this.ignoredOverriddenEqualsForTypes = list(types);
}

public boolean shouldIgnoreOverriddenEqualsOf(Class<? extends Object> clazz) {
return matchesAnIgnoredOverriddenEqualsRegex(clazz)
|| matchesAnIgnoredOverriddenEqualsType(clazz);
public void ignoreOverriddenEqualsForFields(String... fieldPaths) {
List<FieldLocation> fieldLocations = FieldLocation.from(fieldPaths);
this.ignoredOverriddenEqualsForFields.addAll(fieldLocations); // TODO or reset ?
}

public boolean shouldIgnoreOverriddenEqualsOf(DualKey dualKey) {
return matchesAnIgnoredOverriddenEqualsField(dualKey) || shouldIgnoreOverriddenEqualsOf(dualKey.key1.getClass());
}

@Override
public String toString() {
return multiLineDescription(CONFIGURATION_PROVIDER.representation());
}

public String multiLineDescription(Representation representation) { // TODO use representation ?
public String multiLineDescription(Representation representation) {
StringBuilder description = new StringBuilder();
describeIgnoreAllActualNullFields(description);
describeIgnoredFields(description);
describeIgnoredFieldsRegexes(description);
describeOverriddenEqualsMethods(description, representation);
describeOverriddenEqualsMethodsUsage(description, representation);
return description.toString();
}

// private stuff
// non public stuff

@VisibleForTesting
boolean shouldIgnoreOverriddenEqualsOf(Class<? extends Object> clazz) {
return matchesAnIgnoredOverriddenEqualsRegex(clazz) || matchesAnIgnoredOverriddenEqualsType(clazz);
}

private void describeIgnoredFieldsRegexes(StringBuilder description) {
if (!ignoredFieldsRegexes.isEmpty())
Expand All @@ -146,7 +150,7 @@ private void describeIgnoreAllActualNullFields(StringBuilder description) {
if (ignoreAllActualNullFields) description.append(format("- all actual null fields were ignored in the comparison%n"));
}

private void describeOverriddenEqualsMethods(StringBuilder description, Representation representation) {
private void describeOverriddenEqualsMethodsUsage(StringBuilder description, Representation representation) {
description.append(format("- overridden equals methods were used in the comparison"));
if (isConfiguredToIgnoreSomeOverriddenEqualsMethods()) {
description.append(format(", except for:%n"));
Expand All @@ -157,6 +161,9 @@ private void describeOverriddenEqualsMethods(StringBuilder description, Represen
}

private void describeIgnoredOverriddenEqualsMethods(StringBuilder description, Representation representation) {
if (!ignoredOverriddenEqualsForFields.isEmpty())
description.append(format("%s the following fields: %s%n", INDENT_LEVEL_2,
describeIgnoredOverriddenEqualsForFields()));
if (!ignoredOverriddenEqualsForTypes.isEmpty())
description.append(format("%s the following types: %s%n", INDENT_LEVEL_2,
describeIgnoredOverriddenEqualsForTypes(representation)));
Expand All @@ -170,20 +177,31 @@ private String describeIgnoredOverriddenEqualsForTypes(Representation representa
.map(representation::toStringOf)
.collect(toList());
return join(fieldsDescription).with(", ");
}

private String describeIgnoredOverriddenEqualsForFields() {
List<String> fieldsDescription = ignoredOverriddenEqualsForFields.stream()
.map(FieldLocation::getFieldPath)
.collect(toList());
return join(fieldsDescription).with(", ");
}

private boolean matchesAnIgnoredOverriddenEqualsRegex(Class<?> clazz) {
if (this.ignoredOverriddenEqualsRegexes.isEmpty()) return false; // shortcut
String canonicalName = clazz.getCanonicalName();
return this.ignoredOverriddenEqualsRegexes.stream()
.anyMatch(regex -> regex.matcher(canonicalName).matches());
.anyMatch(regex -> regex.matcher(canonicalName).matches());
}

private boolean matchesAnIgnoredOverriddenEqualsType(Class<?> clazz) {
return this.ignoredOverriddenEqualsForTypes.contains(clazz);
}

private boolean matchesAnIgnoredOverriddenEqualsField(DualKey dualKey) {
return ignoredOverriddenEqualsForFields.stream()
.anyMatch(fieldLocation -> fieldLocation.matches(dualKey.concatenatedPath));
}

private boolean matchesAnIgnoredNullField(DualKey dualKey) {
return ignoreAllActualNullFields && dualKey.key1 == null;
}
Expand Down Expand Up @@ -213,7 +231,9 @@ private String describeRegexes(List<Pattern> regexes) {
}

private boolean isConfiguredToIgnoreSomeOverriddenEqualsMethods() {
return !ignoredOverriddenEqualsRegexes.isEmpty() || !ignoredOverriddenEqualsForTypes.isEmpty();
return !ignoredOverriddenEqualsRegexes.isEmpty()
|| !ignoredOverriddenEqualsForTypes.isEmpty()
|| !ignoredOverriddenEqualsForFields.isEmpty();
}

}
Expand Up @@ -209,7 +209,7 @@ private static List<ComparisonDifference> determineDifferences(Object actual, Ob
continue;
}

if (!recursiveComparisonConfiguration.shouldIgnoreOverriddenEqualsOf(key1.getClass())
if (!recursiveComparisonConfiguration.shouldIgnoreOverriddenEqualsOf(dualKey)
&& hasCustomEquals(key1.getClass())) {
if (!key1.equals(key2)) {
differences.add(new ComparisonDifference(currentPath, key1, key2));
Expand Down
@@ -0,0 +1,23 @@
package org.assertj.core.api.recursive.comparison;

import static org.assertj.core.api.Assertions.assertThat;

import java.util.List;

import org.junit.jupiter.api.Test;

public class FieldLocation_from_Test {

@Test
public void should_build_fieldLocations_from_given_strings() {
// GIVEN
String[] locations = { "foo", "bar", "foo.bar" };
// WHEN
List<FieldLocation> fieldLocations = FieldLocation.from(locations);
// THEN
assertThat(fieldLocations).containsExactly(new FieldLocation("foo"),
new FieldLocation("bar"),
new FieldLocation("foo.bar"));
}

}
Expand Up @@ -72,6 +72,19 @@ public void should_show_the_ignored_overridden_equals_methods_types() {
// @format:on
}

@Test
public void should_show_the_ignored_overridden_equals_methods_fields() {
// WHEN
recursiveComparisonConfiguration.ignoreOverriddenEqualsForFields("foo", "baz", "foo.baz");
String multiLineDescription = recursiveComparisonConfiguration.multiLineDescription(STANDARD_REPRESENTATION);
// THEN
// @format:off
assertThat(multiLineDescription).contains(format(
"- overridden equals methods were used in the comparison, except for:%n" +
"--- the following fields: foo, baz, foo.baz%n"));
// @format:on
}

@Test
public void should_show_a_complete_multiline_description() {
// WHEN
Expand All @@ -80,13 +93,15 @@ public void should_show_a_complete_multiline_description() {
recursiveComparisonConfiguration.ignoreFieldsByRegexes("f.*", ".ba.", "..b%sr..");
recursiveComparisonConfiguration.ignoreOverriddenEqualsByRegexes(".*oo", ".ar", "oo.ba");
recursiveComparisonConfiguration.ignoreOverriddenEqualsForTypes(String.class, Multimap.class);
recursiveComparisonConfiguration.ignoreOverriddenEqualsForFields("foo", "baz", "foo.baz");
String multiLineDescription = recursiveComparisonConfiguration.multiLineDescription(STANDARD_REPRESENTATION);
// THEN
// @format:off
assertThat(multiLineDescription).isEqualTo(format("- all actual null fields were ignored in the comparison%n" +
"- the following fields were ignored in the comparison: foo, bar, foo.bar%n" +
"- the fields matching the following regexes were ignored in the comparison: f.*, .ba., ..b%%sr..%n"+
"- overridden equals methods were used in the comparison, except for:%n" +
"--- the following fields: foo, baz, foo.baz%n" +
"--- the following types: java.lang.String, com.google.common.collect.Multimap%n" +
"--- the types matching the following regexes: .*oo, .ar, oo.ba%n"));
// @format:on
Expand Down
Expand Up @@ -70,10 +70,10 @@ public void should_ignore_specified_fields(DualKey dualKey, List<String> ignored

@SuppressWarnings("unused")
private static Stream<Arguments> ignoringSpecifiedFieldsSource() {
return Stream.of(Arguments.of(randomDualKeyWithPath("name"), list("name")),
Arguments.of(randomDualKeyWithPath("name"), list("foo", "name", "foo")),
Arguments.of(randomDualKeyWithPath("name", "first"), list("name.first")),
Arguments.of(randomDualKeyWithPath("father", "name", "first"),
return Stream.of(Arguments.of(dualKeyWithPath("name"), list("name")),
Arguments.of(dualKeyWithPath("name"), list("foo", "name", "foo")),
Arguments.of(dualKeyWithPath("name", "first"), list("name.first")),
Arguments.of(dualKeyWithPath("father", "name", "first"),
list("father", "name.first", "father.name.first")));

}
Expand All @@ -91,17 +91,17 @@ public void should_ignore_fields_specified_with_regex(DualKey dualKey, List<Stri

@SuppressWarnings("unused")
private static Stream<Arguments> ignoringRegexSpecifiedFieldsSource() {
return Stream.of(Arguments.of(randomDualKeyWithPath("name"), list(".*name")),
Arguments.of(randomDualKeyWithPath("name"), list("foo", "n.m.", "foo")),
Arguments.of(randomDualKeyWithPath("name", "first"), list("name\\.first")),
Arguments.of(randomDualKeyWithPath("name", "first"), list(".*first")),
Arguments.of(randomDualKeyWithPath("name", "first"), list("name.*")),
Arguments.of(randomDualKeyWithPath("father", "name", "first"),
return Stream.of(Arguments.of(dualKeyWithPath("name"), list(".*name")),
Arguments.of(dualKeyWithPath("name"), list("foo", "n.m.", "foo")),
Arguments.of(dualKeyWithPath("name", "first"), list("name\\.first")),
Arguments.of(dualKeyWithPath("name", "first"), list(".*first")),
Arguments.of(dualKeyWithPath("name", "first"), list("name.*")),
Arguments.of(dualKeyWithPath("father", "name", "first"),
list("father", "name.first", "father\\.name\\.first")));

}

private static DualKey randomDualKeyWithPath(String... pathElements) {
private static DualKey dualKeyWithPath(String... pathElements) {
return new DualKey(list(pathElements), new Object(), new Object());
}

Expand Down
Expand Up @@ -24,41 +24,68 @@ public void setup() {
recursiveComparisonConfiguration = new RecursiveComparisonConfiguration();
}

@ParameterizedTest(name = "{0} should be ignored with these regexes {1}")
@MethodSource("ignoringCustomEqualsByRegexesSource")
public void should_ignore_custom_equals_by_regexes(Class<?> clazz, List<String> ignoredCustomEqualsByRegexes) {
@ParameterizedTest(name = "{0} overridden equals should be ignored with these regexes {1}")
@MethodSource("ignoringOverriddenEqualsByRegexesSource")
public void should_ignore_overridden_equals_by_regexes(Class<?> clazz, List<String> ignoredOverriddenEqualsByRegexes) {
// GIVEN
recursiveComparisonConfiguration.ignoreOverriddenEqualsByRegexes(ignoredCustomEqualsByRegexes.toArray(new String[0]));
recursiveComparisonConfiguration.ignoreOverriddenEqualsByRegexes(ignoredOverriddenEqualsByRegexes.toArray(new String[0]));
// WHEN
boolean ignored = recursiveComparisonConfiguration.shouldIgnoreOverriddenEqualsOf(clazz);
// THEN
assertThat(ignored).as("%s should be ignored with these regexes %s", clazz, ignoredCustomEqualsByRegexes)
assertThat(ignored).as("%s overridden equals should be ignored with these regexes %s",
clazz, ignoredOverriddenEqualsByRegexes)
.isTrue();
}

@SuppressWarnings("unused")
private static Stream<Arguments> ignoringCustomEqualsByRegexesSource() {
private static Stream<Arguments> ignoringOverriddenEqualsByRegexesSource() {
return Stream.of(Arguments.of(Person.class, list("foo", ".*Person")),
Arguments.of(Human.class, list("org.assertj.core.internal.*.data\\.Human", "foo")),
Arguments.of(Multimap.class, list("com.google.common.collect.*")));
}

@ParameterizedTest(name = "{0} should be ignored with these types {1}")
@MethodSource("ignoringCustomEqualsForTypesSource")
public void should_ignore_custom_equals_by_types(Class<?> clazz, List<Class<?>> ignoredCustomEqualsByTypes) {
@ParameterizedTest(name = "{0} overridden equals should be ignored for these types {1}")
@MethodSource("ignoringOverriddenEqualsForTypesSource")
public void should_ignore_overridden_equals_by_types(Class<?> clazz, List<Class<?>> ignoredOverriddenEqualsByTypes) {
// GIVEN
recursiveComparisonConfiguration.ignoreOverriddenEqualsForTypes(ignoredCustomEqualsByTypes.toArray(new Class[0]));
recursiveComparisonConfiguration.ignoreOverriddenEqualsForTypes(ignoredOverriddenEqualsByTypes.toArray(new Class[0]));
// WHEN
boolean ignored = recursiveComparisonConfiguration.shouldIgnoreOverriddenEqualsOf(clazz);
// THEN
assertThat(ignored).as("%s should be ignored with these types %s", clazz, ignoredCustomEqualsByTypes)
assertThat(ignored).as("%s overridden equals should be ignored for these types %s", clazz, ignoredOverriddenEqualsByTypes)
.isTrue();
}

@SuppressWarnings("unused")
private static Stream<Arguments> ignoringCustomEqualsForTypesSource() {
private static Stream<Arguments> ignoringOverriddenEqualsForTypesSource() {
return Stream.of(Arguments.of(Person.class, list(Human.class, Person.class, String.class)),
Arguments.of(Human.class, list(Human.class)));
}

@ParameterizedTest(name = "{0} overridden equals should be ignored for these fields {1}")
@MethodSource("ignoringOverriddenEqualsForFieldsSource")
public void should_ignore_overridden_equals_by_fields(DualKey dualKey, List<String> ignoredOverriddenEqualsByFields) {
// GIVEN
recursiveComparisonConfiguration.ignoreOverriddenEqualsForFields(ignoredOverriddenEqualsByFields.toArray(new String[0]));
// WHEN
boolean ignored = recursiveComparisonConfiguration.shouldIgnoreOverriddenEqualsOf(dualKey);
// THEN
assertThat(ignored).as("%s overridden equals should be ignored for these fields %s", dualKey, ignoredOverriddenEqualsByFields)
.isTrue();
}

@SuppressWarnings("unused")
private static Stream<Arguments> ignoringOverriddenEqualsForFieldsSource() {
return Stream.of(Arguments.of(dualKeyWithPath("name"), list("name")),
Arguments.of(dualKeyWithPath("name"), list("foo", "name", "foo")),
Arguments.of(dualKeyWithPath("name", "first"), list("name.first")),
Arguments.of(dualKeyWithPath("father", "name", "first"),
list("father", "name.first", "father.name.first")));

}

private static DualKey dualKeyWithPath(String... pathElements) {
return new DualKey(list(pathElements), new Object(), new Object());
}

}

0 comments on commit 371032c

Please sign in to comment.