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 #2389 : Parallel use of mocks with deep stubbing may lead to ConcurrentModificationException #2444

Merged
merged 1 commit into from Oct 13, 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 @@ -163,13 +163,20 @@ public Object invokedMock() {
return invocationForStubbing.getInvocation().getMock();
}

public MatchableInvocation getInvocationForStubbing() {
return invocationForStubbing;
}

private RegisteredInvocations createRegisteredInvocations(MockCreationSettings mockSettings) {
return mockSettings.isStubOnly()
? new SingleRegisteredInvocation()
: new DefaultRegisteredInvocations();
}

public Answer findStubbedAnswer() {
synchronized (stubbed) {
for (StubbedInvocationMatcher s : stubbed) {
if (invocationForStubbing.matches(s.getInvocation())) {
return s;
}
}
}
return null;
}
}
Expand Up @@ -20,7 +20,6 @@
import org.mockito.invocation.InvocationOnMock;
import org.mockito.mock.MockCreationSettings;
import org.mockito.stubbing.Answer;
import org.mockito.stubbing.Stubbing;

/**
* Returning deep stub implementation.
Expand Down Expand Up @@ -80,12 +79,9 @@ private Object deepStub(
throws Throwable {
InvocationContainerImpl container = MockUtil.getInvocationContainer(invocation.getMock());

// matches invocation for verification
// TODO why don't we do container.findAnswer here?
for (Stubbing stubbing : container.getStubbingsDescending()) {
if (container.getInvocationForStubbing().matches(stubbing.getInvocation())) {
return stubbing.answer(invocation);
}
Answer existingAnswer = container.findStubbedAnswer();
if (existingAnswer != null) {
return existingAnswer.answer(invocation);
}

// record deep stub answer
Expand Down
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2021 Mockito contributors
* This program is made available under the terms of the MIT License.
*/
package org.mockito.internal.stubbing.defaultanswers;

import static org.mockito.Mockito.mock;

import java.util.List;
import java.util.stream.IntStream;

import org.junit.Test;
import org.mockito.Answers;

public class ReturnsDeepStubsConcurrentTest {

@Test
public void
given_mock_with_returns_deep_stubs__when_called_concurrently__then_does_not_throw_concurrent_modification_exception() {
for (int i = 0; i < 1000; i++) {
Service mock = mock(Service.class, Answers.RETURNS_DEEP_STUBS);
IntStream.range(1, 100).parallel().forEach(index -> mock.doSomething());
}
}

interface Service {
List<String> doSomething();
}
}