Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes mockito#2311 #2320

Merged
merged 4 commits into from Jun 26, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -9,18 +9,25 @@
* When ArgumentMatcher fails, chance is that the actual object has the same output of toString() than
* the wanted object. This looks weird when failures are reported.
* Therefore when matcher fails but toString() yields the same outputs,
* we will try to use the {@link #toStringWithType()} method.
* we will try to use the {@link #toStringWithType(boolean)} method.
*/
public interface ContainsExtraTypeInfo {

/**
* @param className - name of the class to be printed in description
* Returns more verbose description of the object which include type information
*/
String toStringWithType();
String toStringWithType(String className);

/**
* Checks if target target has matching type.
* If the type matches, there is no point in rendering result from {@link #toStringWithType()}
* If the type matches, there is no point in rendering result from {@link #toStringWithType(String)}
*/
boolean typeMatches(Object target);

/**
*
* @return Returns the wanted argument
*/
Object getWanted();
}
8 changes: 5 additions & 3 deletions src/main/java/org/mockito/internal/matchers/Equals.java
Expand Up @@ -31,7 +31,8 @@ private String describe(Object object) {
return ValuePrinter.print(object);
}

protected final Object getWanted() {
@Override
public final Object getWanted() {
return wanted;
}

Expand All @@ -51,12 +52,13 @@ public int hashCode() {
}

@Override
public String toStringWithType() {
return "(" + wanted.getClass().getSimpleName() + ") " + describe(wanted);
public String toStringWithType(String className) {
return "(" + className + ") " + describe(wanted);
}

@Override
public boolean typeMatches(Object target) {
return wanted != null && target != null && target.getClass() == wanted.getClass();
}

}
Expand Up @@ -30,8 +30,18 @@ private Iterator<FormattedText> applyPrintSettings(
List<FormattedText> out = new LinkedList<>();
int i = 0;
for (final ArgumentMatcher matcher : matchers) {
if (matcher instanceof ContainsExtraTypeInfo && printSettings.extraTypeInfoFor(i)) {
out.add(new FormattedText(((ContainsExtraTypeInfo) matcher).toStringWithType()));
if (matcher instanceof ContainsExtraTypeInfo) {
ContainsExtraTypeInfo typeInfoMatcher = (ContainsExtraTypeInfo) matcher;
Class<?> wantedClass = typeInfoMatcher.getWanted().getClass();
String simpleNameOfArgument = wantedClass.getSimpleName();

if (printSettings.extraTypeInfoFor(i)) {
out.add(new FormattedText(typeInfoMatcher.toStringWithType(wantedClass.getSimpleName())));
} else if (printSettings.fullyQualifiedNameFor(simpleNameOfArgument)) {
out.add(new FormattedText(typeInfoMatcher.toStringWithType(wantedClass.getCanonicalName())));
} else {
out.add(new FormattedText(MatcherToString.toString(matcher)));
}
} else {
out.add(new FormattedText(MatcherToString.toString(matcher)));
}
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/org/mockito/internal/reporting/PrintSettings.java
Expand Up @@ -5,8 +5,10 @@
package org.mockito.internal.reporting;

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.mockito.ArgumentMatcher;
import org.mockito.internal.matchers.text.MatchersPrinter;
Expand All @@ -19,6 +21,7 @@ public class PrintSettings {
public static final int MAX_LINE_LENGTH = 45;
private boolean multiline;
private List<Integer> withTypeInfo = new LinkedList<>();
private Set<String> withFullyQualifiedName = Collections.emptySet();

public void setMultiline(boolean multiline) {
this.multiline = multiline;
Expand All @@ -38,10 +41,18 @@ public boolean extraTypeInfoFor(int argumentIndex) {
return withTypeInfo.contains(argumentIndex);
}

public boolean fullyQualifiedNameFor(String simpleClassName) {
return withFullyQualifiedName.contains(simpleClassName);
}

public void setMatchersToBeDescribedWithExtraTypeInfo(Integer[] indexesOfMatchers) {
this.withTypeInfo = Arrays.asList(indexesOfMatchers);
}

public void setMatchersToBeDescribedWithFullName(Set<String> indexesOfMatchers) {
this.withFullyQualifiedName= indexesOfMatchers;
}

public String print(List<ArgumentMatcher> matchers, Invocation invocation) {
MatchersPrinter matchersPrinter = new MatchersPrinter();
String qualifiedName =
Expand Down
Expand Up @@ -7,6 +7,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import org.mockito.invocation.Invocation;
import org.mockito.invocation.MatchableInvocation;
Expand All @@ -28,17 +29,21 @@ public SmartPrinter(
this(
wanted,
Collections.singletonList(actual),
indexesOfMatchersToBeDescribedWithExtraTypeInfo);
indexesOfMatchersToBeDescribedWithExtraTypeInfo,
Collections.emptySet());
}

public SmartPrinter(
MatchableInvocation wanted,
List<Invocation> allActualInvocations,
Integer... indexesOfMatchersToBeDescribedWithExtraTypeInfo) {
Integer[] indexesOfMatchersToBeDescribedWithExtraTypeInfo,
Set<String> classNamesToBeDescribedWithFullName) {
PrintSettings printSettings = new PrintSettings();
printSettings.setMultiline(isMultiLine(wanted, allActualInvocations));
printSettings.setMatchersToBeDescribedWithExtraTypeInfo(
indexesOfMatchersToBeDescribedWithExtraTypeInfo);
printSettings.setMatchersToBeDescribedWithFullName(
classNamesToBeDescribedWithFullName);

this.wanted = printSettings.print(wanted);

Expand Down
Expand Up @@ -4,8 +4,13 @@
*/
package org.mockito.internal.verification.argumentmatching;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.mockito.ArgumentMatcher;
import org.mockito.internal.matchers.ContainsExtraTypeInfo;
Expand Down Expand Up @@ -48,4 +53,30 @@ private static boolean safelyMatches(ArgumentMatcher m, Object arg) {
private static boolean toStringEquals(ArgumentMatcher m, Object arg) {
return m.toString().equals(String.valueOf(arg));
}

/**
* Suspiciously not matching arguments are those that don't match, and the classes have same simple name.
*/
public static Set<String> getNotMatchingArgsWithSameName(
List<ArgumentMatcher> matchers, Object[] arguments) {
Set<String> repeatedClassNames = new HashSet<>();
Map<String, Set<String>> classesHavingSameName = new HashMap<>();
for (ArgumentMatcher m : matchers) {
if (m instanceof ContainsExtraTypeInfo) {
Class wantedClass = ((ContainsExtraTypeInfo) m).getWanted().getClass();
classesHavingSameName.computeIfAbsent(wantedClass.getSimpleName(), className -> new HashSet<>())
.add(wantedClass.getCanonicalName());
}
}
for (Object argument : arguments) {
Class wantedClass = argument.getClass();
classesHavingSameName.computeIfAbsent(wantedClass.getSimpleName(), className -> new HashSet<>())
.add(wantedClass.getCanonicalName());
}
return classesHavingSameName.entrySet().stream()
.filter(classEntry -> classEntry.getValue().size() > 1)
.map(classEntry -> classEntry.getKey())
.collect(Collectors.toSet());
}

}
Expand Up @@ -11,9 +11,11 @@
import static org.mockito.internal.invocation.InvocationsFinder.findInvocations;
import static org.mockito.internal.invocation.InvocationsFinder.findPreviousVerifiedInOrder;
import static org.mockito.internal.invocation.InvocationsFinder.findSimilarInvocation;
import static org.mockito.internal.verification.argumentmatching.ArgumentMatchingTool.getNotMatchingArgsWithSameName;
import static org.mockito.internal.verification.argumentmatching.ArgumentMatchingTool.getSuspiciouslyNotMatchingArgsIndexes;

import java.util.List;
import java.util.Set;

import org.mockito.internal.reporting.SmartPrinter;
import org.mockito.internal.util.collections.ListUtil;
Expand Down Expand Up @@ -41,7 +43,9 @@ public static void checkMissingInvocation(

Integer[] indexesOfSuspiciousArgs =
getSuspiciouslyNotMatchingArgsIndexes(wanted.getMatchers(), similar.getArguments());
SmartPrinter smartPrinter = new SmartPrinter(wanted, invocations, indexesOfSuspiciousArgs);
Set<String> classesWithSameSimpleName =
getNotMatchingArgsWithSameName(wanted.getMatchers(), similar.getArguments());
SmartPrinter smartPrinter = new SmartPrinter(wanted, invocations, indexesOfSuspiciousArgs,classesWithSameSimpleName);
List<Location> actualLocations =
ListUtil.convert(
invocations,
Expand Down
6 changes: 3 additions & 3 deletions src/test/java/org/mockito/internal/matchers/EqualsTest.java
Expand Up @@ -28,21 +28,21 @@ public void shouldArraysBeEqual() {

@Test
public void shouldDescribeWithExtraTypeInfo() throws Exception {
String descStr = new Equals(100).toStringWithType();
String descStr = new Equals(100).toStringWithType(Integer.class.getSimpleName());

assertEquals("(Integer) 100", descStr);
}

@Test
public void shouldDescribeWithExtraTypeInfoOfLong() throws Exception {
String descStr = new Equals(100L).toStringWithType();
String descStr = new Equals(100L).toStringWithType(Long.class.getSimpleName());

assertEquals("(Long) 100L", descStr);
}

@Test
public void shouldDescribeWithTypeOfString() throws Exception {
String descStr = new Equals("x").toStringWithType();
String descStr = new Equals("x").toStringWithType(String.class.getSimpleName());

assertEquals("(String) \"x\"", descStr);
}
Expand Down
Expand Up @@ -111,14 +111,20 @@ public boolean matches(String item) {
}

@Override
public String toStringWithType() {
public String toStringWithType(String className) {
return "";
}

@Override
public boolean typeMatches(Object target) {
return true;
}

@Override
public Object getWanted() {
return "";
}

}

// given
Expand Down
50 changes: 50 additions & 0 deletions src/test/java/org/mockitousage/IMethods.java
Expand Up @@ -238,4 +238,54 @@ String simpleMethod(
String forObject(Object object);

<T> String genericToString(T arg);

void overloadedMethodWithSameClassNameArguments(java.sql.Date javaDate, Date date);

void overloadedMethodWithSameClassNameArguments(Date date, java.sql.Date javaDate);

void overloadedMethodWithDifferentClassNameArguments(String String, Integer i);

void overloadedMethodWithDifferentClassNameArguments(Integer i, String string);

void overloadedMethodWithSameClassNameArguments(java.sql.Date javaDate, String string, Date date);

void overloadedMethodWithSameClassNameArguments(Date date, String string, java.sql.Date javaDate);

/**
* Using this class to test cases where two classes have same simple name
*/
public static class Date {

private int value;

public Date(int value) {
this.value = value;
}

@Override
public String toString() {
return String.valueOf(value);
}

@Override
public boolean equals(Object o) {
if (this == o){
return true;
}
if (o == null || getClass() != o.getClass()){
return false;
}
Date date = (Date) o;
return value == date.value;
}

@Override
public int hashCode() {
return Objects.hash(value);
}

}


}

25 changes: 25 additions & 0 deletions src/test/java/org/mockitousage/MethodsImpl.java
Expand Up @@ -448,4 +448,29 @@ public Void voidReturningMethod() {
public <T> String genericToString(T arg) {
return null;
}

@Override
public void overloadedMethodWithSameClassNameArguments(java.sql.Date javaDate, Date date) {
}

@Override
public void overloadedMethodWithSameClassNameArguments(Date date, java.sql.Date javaDate) {
}

@Override
public void overloadedMethodWithDifferentClassNameArguments(String string, Integer i) {
}

@Override
public void overloadedMethodWithDifferentClassNameArguments(Integer i, String string) {
}

@Override
public void overloadedMethodWithSameClassNameArguments(java.sql.Date javaDate, String string, Date date) {
}

@Override
public void overloadedMethodWithSameClassNameArguments(Date date, String string, java.sql.Date javaDate) {
}

}