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 #2331 #2369

Merged
merged 2 commits into from Jul 30, 2021
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -393,7 +393,8 @@ public <T> Class<? extends T> createMockType(MockCreationSettings<T> settings) {
settings.getTypeToMock(),
settings.getExtraInterfaces(),
settings.getSerializableMode(),
settings.isStripAnnotations()));
settings.isStripAnnotations(),
settings.getDefaultAnswer()));
} catch (Exception bytecodeGenerationFailed) {
throw prettifyFailure(settings, bytecodeGenerationFailed);
}
Expand Down
Expand Up @@ -8,30 +8,36 @@
import java.util.Set;

import org.mockito.mock.SerializableMode;
import org.mockito.stubbing.Answer;

class MockFeatures<T> {

final Class<T> mockedType;
final Set<Class<?>> interfaces;
final SerializableMode serializableMode;
final boolean stripAnnotations;
final Answer defaultAnswer;

private MockFeatures(
Class<T> mockedType,
Set<Class<?>> interfaces,
SerializableMode serializableMode,
boolean stripAnnotations) {
boolean stripAnnotations,
Answer defaultAnswer) {
this.mockedType = mockedType;
this.interfaces = Collections.unmodifiableSet(interfaces);
this.serializableMode = serializableMode;
this.stripAnnotations = stripAnnotations;
this.defaultAnswer = defaultAnswer;
}

public static <T> MockFeatures<T> withMockFeatures(
Class<T> mockedType,
Set<Class<?>> interfaces,
SerializableMode serializableMode,
boolean stripAnnotations) {
return new MockFeatures<T>(mockedType, interfaces, serializableMode, stripAnnotations);
boolean stripAnnotations,
Answer defaultAnswer) {
return new MockFeatures<T>(
mockedType, interfaces, serializableMode, stripAnnotations, defaultAnswer);
}
}
Expand Up @@ -79,7 +79,8 @@ public <T> Class<? extends T> createMockType(MockCreationSettings<T> settings) {
settings.getTypeToMock(),
settings.getExtraInterfaces(),
settings.getSerializableMode(),
settings.isStripAnnotations()));
settings.isStripAnnotations(),
settings.getDefaultAnswer()));
} catch (Exception bytecodeGenerationFailed) {
throw prettifyFailure(settings, bytecodeGenerationFailed);
}
Expand Down
Expand Up @@ -44,6 +44,7 @@
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.attribute.MethodAttributeAppender;
import net.bytebuddy.matcher.ElementMatcher;
import org.mockito.Answers;
import org.mockito.codegen.InjectionBase;
import org.mockito.exceptions.base.MockitoException;
import org.mockito.internal.creation.bytebuddy.ByteBuddyCrossClassLoaderSerializationSupport.CrossClassLoaderSerializableMock;
Expand Down Expand Up @@ -237,14 +238,24 @@ public <T> Class<? extends T> mockClass(MockFeatures<T> features) {
features.stripAnnotations
? MethodAttributeAppender.NoOp.INSTANCE
: INCLUDING_RECEIVER)
.method(isHashCode())
.intercept(hashCode)
.method(isEquals())
.intercept(equals)
.serialVersionUid(42L)
.defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE)
.implement(MockAccess.class)
.intercept(FieldAccessor.ofBeanProperty());

if (features.defaultAnswer != Answers.CALLS_REAL_METHODS) {
builder =
builder.method(isHashCode())
.intercept(hashCode)
.method(isEquals())
.intercept(equals);
} else {
builder =
builder.method(isHashCode())
.intercept(dispatcher)
.method(isEquals())
.intercept(dispatcher);
}
if (features.serializableMode == SerializableMode.ACROSS_CLASSLOADERS) {
builder =
builder.implement(CrossClassLoaderSerializableMock.class)
Expand Down
Expand Up @@ -77,7 +77,7 @@ public String toString() {

@Override
public boolean matches(Invocation candidate) {
return invocation.getMock().equals(candidate.getMock())
return invocation.getMock() == candidate.getMock()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if I follow why this line was changed. Could you elaborate as to why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider the test PartialMockingWithSpiesTest.shouldAllowStubbingOfMethodsThatDelegateToOtherMethods.
Now the call to getName is handled by MockMethodInterceptor.DispatcherDefaultingToRealMethod.interceptSuperCallable here the interceptor will not be null and it will go to interceptor.doIntercept. This finally leads to InvocationMatcher.matches

Now with changes in this pull request the call to equals is again delegated to MockMethodInterceptor.DispatcherDefaultingToRealMethod and it will come at same place. And this way it leads to stackoverflow. Earlier equals of spy was this==that. So i just replced equals with ==.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, thanks for the explanation! That makes sense to me 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha Thanks. Had missed to put links to the classes.

&& hasSameMethod(candidate)
&& argumentsMatch(candidate);
}
Expand Down
Expand Up @@ -18,6 +18,7 @@

import org.junit.Before;
import org.junit.Test;
import org.mockito.Answers;
import org.mockito.mock.SerializableMode;
import org.mockitoutil.VmArgAssumptions;

Expand Down Expand Up @@ -46,7 +47,8 @@ public void ensure_cache_is_cleared_if_no_reference_to_classloader_and_classes()
classloader_with_life_shorter_than_cache.loadClass("foo.Bar"),
Collections.<Class<?>>emptySet(),
SerializableMode.NONE,
false));
false,
Answers.RETURNS_DEFAULTS));

ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
Reference<Object> typeReference =
Expand Down Expand Up @@ -79,15 +81,17 @@ public void ensure_cache_returns_same_instance() throws Exception {
classloader_with_life_shorter_than_cache.loadClass("foo.Bar"),
Collections.<Class<?>>emptySet(),
SerializableMode.NONE,
false));
false,
Answers.RETURNS_DEFAULTS));

Class<?> other_mock_type =
cachingMockBytecodeGenerator.mockClass(
withMockFeatures(
classloader_with_life_shorter_than_cache.loadClass("foo.Bar"),
Collections.<Class<?>>emptySet(),
SerializableMode.NONE,
false));
false,
Answers.RETURNS_DEFAULTS));

assertThat(other_mock_type).isSameAs(the_mock_type);

Expand Down Expand Up @@ -123,15 +127,17 @@ public void ensure_cache_returns_different_instance_serializableMode() throws Ex
classloader_with_life_shorter_than_cache.loadClass("foo.Bar"),
Collections.<Class<?>>emptySet(),
SerializableMode.NONE,
false));
false,
Answers.RETURNS_DEFAULTS));

Class<?> other_mock_type =
cachingMockBytecodeGenerator.mockClass(
withMockFeatures(
classloader_with_life_shorter_than_cache.loadClass("foo.Bar"),
Collections.<Class<?>>emptySet(),
SerializableMode.BASIC,
false));
false,
Answers.RETURNS_DEFAULTS));

assertThat(other_mock_type).isNotSameAs(the_mock_type);
}
Expand Down
10 changes: 10 additions & 0 deletions src/test/java/org/mockitousage/spies/SpyingOnRealObjectsTest.java
Expand Up @@ -9,6 +9,7 @@
import static org.junit.Assume.assumeTrue;
import static org.mockito.Mockito.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
Expand Down Expand Up @@ -192,4 +193,13 @@ public void shouldSayNiceMessageWhenSpyingOnPrivateClass() throws Exception {
"Most likely it is due to mocking a private class that is not visible to Mockito");
}
}

@Test
public void spysHashCodeEqualsDelegatedToActualMethods() {
List<String> real = new ArrayList<>();
real.add("one");
List<String> spy = spy(real);
assertEquals(real.hashCode(), spy.hashCode());
assertTrue(spy.equals(real));
}
}