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

Interactions in when blocks are not preserved #1761

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ public class AstNodeCache {
public final MethodNode MockController_LeaveScope =
MockController.getDeclaredMethods(org.spockframework.mock.runtime.MockController.LEAVE_SCOPE).get(0);

public final MethodNode MockController_FreezeScope =
MockController.getDeclaredMethods(org.spockframework.mock.runtime.MockController.FREEZE_SCOPE).get(0);

public final MethodNode SpecificationContext_GetMockController =
SpecificationContext.getDeclaredMethods(org.spockframework.runtime.SpecificationContext.GET_MOCK_CONTROLLER).get(0);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,10 +439,17 @@ private void moveInteractions(List<Statement> interactions, ThenBlock block) {

statsBeforeWhenBlock.addAll(interactions);

if (block.isFirstInChain())
if (block.isLastInChain()) {
//Insert a freezeScope after the last moved interaction from "then" block to guarantee correct Scope behavior for interactions in the "when" block
//See Ticket #1759: Interactions in when blocks are not preserved if the then block contains an interaction
statsBeforeWhenBlock.add(createMockControllerCall(nodeCache.MockController_FreezeScope));
}

if (block.isFirstInChain()) {
// insert at beginning of then-block rather than end of when-block
// s.t. it's outside of try-block inserted for exception conditions
block.getAst().add(0, createMockControllerCall(nodeCache.MockController_LeaveScope));
}
}

private Statement createMockControllerCall(MethodNode method) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,5 +80,9 @@ public boolean isFirstInChain() {
return isFirst() || getClass() != prev.getClass();
}

public boolean isLastInChain() {
return isLast() || getClass() != next.getClass();
}

public abstract BlockParseInfo getParseInfo();
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.spockframework.mock.runtime;

import org.spockframework.mock.*;
import org.spockframework.util.InternalSpockError;

import java.util.*;

Expand All @@ -26,6 +27,7 @@
public class MockController implements IMockController {
private final Deque<IInteractionScope> scopes = new LinkedList<>();
private final List<InteractionNotSatisfiedError> errors = new ArrayList<>();
private boolean firstScopeFrozen;

public MockController() {
scopes.addFirst(new InteractionScope());
Expand Down Expand Up @@ -53,7 +55,20 @@ public synchronized Object handle(IMockInvocation invocation) {
public static final String ADD_INTERACTION = "addInteraction";

public synchronized void addInteraction(IMockInteraction interaction) {
scopes.getFirst().addInteraction(interaction);
IInteractionScope scope;
if (firstScopeFrozen) {
//We shall skip the first scope and use the next one, because the current first scope was frozen for interactions
//See Ticket #1759: Interactions in when blocks are not preserved if the then block contains an interaction
if (scopes.size() <= 1) {
throw new InternalSpockError();
}
Iterator<IInteractionScope> it = scopes.iterator();
it.next();
scope = it.next();
} else {
scope = scopes.getFirst();
}
scope.addInteraction(interaction);
}

public static final String ADD_BARRIER = "addBarrier";
Expand All @@ -66,13 +81,25 @@ public synchronized void addBarrier() {

public synchronized void enterScope() {
throwAnyPreviousError();
firstScopeFrozen = false;
scopes.addFirst(new InteractionScope());
}

public static final String FREEZE_SCOPE = "freezeScope";

/**
* Freeze the currently first scope, which means no more iterations can be added with {@link #addInteraction(IMockInteraction)}.
* The interactions added after that, are then added to the next scope in the {@code scopes}.
*/
public synchronized void freezeScope() {
firstScopeFrozen = true;
}

public static final String LEAVE_SCOPE = "leaveScope";

public synchronized void leaveScope() {
throwAnyPreviousError();
firstScopeFrozen = false;
IInteractionScope scope = scopes.removeFirst();
scope.verifyInteractions();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package org.spockframework.smoke.mock

import spock.lang.Issue
import spock.lang.PendingFeature
import spock.lang.Specification

@Issue("https://github.com/spockframework/spock/pull/1754")
class MockInteractionsIssue1754Spec extends Specification {

def "When block interaction still active after when block without verification"() {
given:
def m = Mock(Engine)
when:
m.isStarted() >> true
def result = m.isStarted()
m.start()
then:
m.isStarted()
result
when: "Here the isStarted() still reflect the when block above"
result = m.isStarted()
then:
m.isStarted()
result
}

def "When block interactions shall be preserved also if there are then interactions"() {
given:
def m = Mock(Engine)
when:
m.isStarted() >> true
def result = m.isStarted()
m.start()
then:
1 * m.start()
m.isStarted()
result
when:
result = m.isStarted()
then:
m.isStarted()
result
}

def "then block is last block in feature"() {
given:
def m = Mock(Engine)
when:
m.isStarted() >> true
def result = m.isStarted()
m.start()
then:
result
m.isStarted()
1 * m.start()
}

def "When block interactions with spread over overlapping ordered interactions"() {
def list = Mock(List)

when:
list.size() >> 2
list.add(1)
list.add(2)
list.add(2)
list.add(1)
assert list.size() == 2

then:
1 * list.add(!0)
list.size() == 2

then:
2 * list.add(2)
list.size() == 2

then:
1 * list.add(_)
list.size() == 2
}

static class Engine {
private boolean started

boolean isStarted() { return started }

void start() { started = true }
}
}