Add test framework skeleton
- Relates to #49 - New project spring-statemachine-test which contains public classes to easy generic state machine testing. - Concepts of StateMachineTestPlan and StateMachineTestPlanBuilder. - Replaced one zk test to use this framework.
This commit is contained in:
14
build.gradle
14
build.gradle
@@ -125,6 +125,19 @@ project('spring-statemachine-core') {
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-statemachine-test') {
|
||||
description = "Spring State Machine Test"
|
||||
|
||||
dependencies {
|
||||
compile project(":spring-statemachine-core")
|
||||
|
||||
compile "org.springframework:spring-test:$springVersion"
|
||||
compile "org.hamcrest:hamcrest-core:$hamcrestVersion"
|
||||
compile "org.hamcrest:hamcrest-library:$hamcrestVersion"
|
||||
compile "junit:junit:$junitVersion"
|
||||
}
|
||||
}
|
||||
|
||||
project('spring-statemachine-zookeeper') {
|
||||
description = "Spring State Machine Zookeeper"
|
||||
|
||||
@@ -133,6 +146,7 @@ project('spring-statemachine-zookeeper') {
|
||||
compile "org.apache.curator:curator-recipes:$curatorVersion"
|
||||
compile "com.esotericsoftware.kryo:kryo:$kryoVersion"
|
||||
|
||||
testCompile project(":spring-statemachine-test")
|
||||
testCompile "org.apache.curator:curator-test:$curatorVersion"
|
||||
testCompile "org.springframework:spring-test:$springVersion"
|
||||
testCompile "org.hamcrest:hamcrest-core:$hamcrestVersion"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
rootProject.name = 'spring-statemachine'
|
||||
|
||||
include 'spring-statemachine-core'
|
||||
include 'spring-statemachine-test'
|
||||
include 'spring-statemachine-zookeeper'
|
||||
|
||||
include 'spring-statemachine-recipes'
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.statemachine.test;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
|
||||
/**
|
||||
* Base helper class for state machine tests.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractStateMachineTests {
|
||||
|
||||
protected AnnotationConfigApplicationContext context;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
context = buildContext();
|
||||
}
|
||||
|
||||
@After
|
||||
public void clean() {
|
||||
if (context != null) {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
protected AnnotationConfigApplicationContext buildContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void registerAndRefresh(Class<?>... annotatedClasses) {
|
||||
context.register(annotatedClasses);
|
||||
context.refresh();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.statemachine.test;
|
||||
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.hamcrest.Matcher;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.test.StateMachineTestPlanBuilder.StateMachineTestPlanStep;
|
||||
import org.springframework.statemachine.test.support.LatchStateMachineListener;
|
||||
|
||||
/**
|
||||
* {@code StateMachineTestPlan} is fully constructed plan how
|
||||
* a {@link StateMachine} should be tested.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public class StateMachineTestPlan<S, E> {
|
||||
|
||||
private final List<StateMachine<S, E>> stateMachines;
|
||||
private final List<StateMachineTestPlanStep<S, E>> steps;
|
||||
|
||||
/**
|
||||
* Instantiates a new state machine test plan.
|
||||
*
|
||||
* @param stateMachines the state machines
|
||||
* @param steps the steps
|
||||
*/
|
||||
public StateMachineTestPlan(List<StateMachine<S, E>> stateMachines, List<StateMachineTestPlanStep<S, E>> steps) {
|
||||
this.stateMachines = stateMachines;
|
||||
this.steps = steps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run test plan.
|
||||
*
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
public void test() throws Exception {
|
||||
|
||||
List<LatchStateMachineListener<S, E>> listeners = new ArrayList<LatchStateMachineListener<S, E>>();
|
||||
for (StateMachine<S, E> stateMachine : stateMachines) {
|
||||
LatchStateMachineListener<S, E> listener = new LatchStateMachineListener<S, E>();
|
||||
listeners.add(listener);
|
||||
stateMachine.addStateListener(listener);
|
||||
stateMachine.start();
|
||||
}
|
||||
|
||||
|
||||
for (StateMachineTestPlanStep<S, E> step : steps) {
|
||||
for (LatchStateMachineListener<S, E> listener : listeners) {
|
||||
listener.reset(step.expectStateChanged != null ? step.expectStateChanged : 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
if (step.sendEvent != null) {
|
||||
stateMachines.get(0).sendEvent(step.sendEvent);
|
||||
}
|
||||
|
||||
if (step.expectStateChanged != null) {
|
||||
for (LatchStateMachineListener<S, E> listener : listeners) {
|
||||
assertThat(listener.getStateChangedLatch().await(5, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener.getStateChanged().size(), is(step.expectStateChanged));
|
||||
}
|
||||
}
|
||||
|
||||
if (step.expectState != null) {
|
||||
for (StateMachine<S, E> stateMachine : stateMachines) {
|
||||
assertThat(stateMachine.getState(), notNullValue());
|
||||
Collection<Matcher<? super S>> itemMatchers = new ArrayList<Matcher<? super S>>();
|
||||
itemMatchers.add(is(step.expectState));
|
||||
assertThat(stateMachine.getState().getIds(), containsInAnyOrder(itemMatchers));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.statemachine.test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
|
||||
/**
|
||||
* A builder for {@link StateMachineTestPlan}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public class StateMachineTestPlanBuilder<S, E> {
|
||||
|
||||
private List<StateMachine<S, E>> stateMachines = new ArrayList<StateMachine<S, E>>();
|
||||
private final List<StateMachineTestPlanStep<S, E>> steps = new ArrayList<StateMachineTestPlanStep<S, E>>();
|
||||
|
||||
/**
|
||||
* Gets a new instance of this builder.
|
||||
*
|
||||
* @return the state machine test plan builder
|
||||
*/
|
||||
public static <S, E> StateMachineTestPlanBuilder<S, E> builder() {
|
||||
return new StateMachineTestPlanBuilder<S, E>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a state machine with this builder.
|
||||
*
|
||||
* @param stateMachine the state machine
|
||||
* @return the state machine test plan builder
|
||||
*/
|
||||
public StateMachineTestPlanBuilder<S, E> stateMachine(StateMachine<S, E> stateMachine) {
|
||||
this.stateMachines.add(stateMachine);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a new step builder.
|
||||
*
|
||||
* @return the state machine test plan step builder
|
||||
*/
|
||||
public StateMachineTestPlanStepBuilder step() {
|
||||
return new StateMachineTestPlanStepBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the state machine test plan.
|
||||
*
|
||||
* @return the state machine test plan
|
||||
*/
|
||||
public StateMachineTestPlan<S, E> build() {
|
||||
return new StateMachineTestPlan<S, E>(stateMachines, steps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for individual plan steps.
|
||||
*/
|
||||
public class StateMachineTestPlanStepBuilder {
|
||||
|
||||
E sendEvent;
|
||||
S expectState;
|
||||
Integer expectStateChanged;
|
||||
|
||||
/**
|
||||
* Expect a state {@code S}.
|
||||
*
|
||||
* @param state the state
|
||||
* @return the state machine test plan step builder
|
||||
*/
|
||||
public StateMachineTestPlanStepBuilder expectState(S state) {
|
||||
this.expectState = state;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an event {@code E}.
|
||||
*
|
||||
* @param event the event
|
||||
* @return the state machine test plan step builder
|
||||
*/
|
||||
public StateMachineTestPlanStepBuilder sendEvent(E event) {
|
||||
this.sendEvent = event;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expect state changed happening {@code count} times.
|
||||
*
|
||||
* @param count the count
|
||||
* @return the state machine test plan step builder
|
||||
*/
|
||||
public StateMachineTestPlanStepBuilder expectStateChanged(int count) {
|
||||
if (count < 0) {
|
||||
throw new IllegalArgumentException("Expected count cannot be negative, was " + count);
|
||||
}
|
||||
this.expectStateChanged = count;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new step and return {@link StateMachineTestPlanBuilder}
|
||||
* for chaining.
|
||||
*
|
||||
* @return the state machine test plan builder for chaining
|
||||
*/
|
||||
public StateMachineTestPlanBuilder<S, E> and() {
|
||||
steps.add(new StateMachineTestPlanStep<S, E>(sendEvent, expectState, expectStateChanged));
|
||||
return StateMachineTestPlanBuilder.this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class StateMachineTestPlanStep<S, E> {
|
||||
E sendEvent;
|
||||
S expectState;
|
||||
Integer expectStateChanged;
|
||||
public StateMachineTestPlanStep(E sendEvent, S expectState, Integer expectStateChanged) {
|
||||
this.sendEvent = sendEvent;
|
||||
this.expectState = expectState;
|
||||
this.expectStateChanged = expectStateChanged;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.statemachine.test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Utils for tests.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
*/
|
||||
public class TestUtils {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T readField(String name, Object target) throws Exception {
|
||||
Field field = null;
|
||||
Class<?> clazz = target.getClass();
|
||||
do {
|
||||
try {
|
||||
field = clazz.getDeclaredField(name);
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
|
||||
clazz = clazz.getSuperclass();
|
||||
} while (field == null && !clazz.equals(Object.class));
|
||||
|
||||
if (field == null)
|
||||
throw new IllegalArgumentException("Cannot find field '" + name + "' in the class hierarchy of "
|
||||
+ target.getClass());
|
||||
field.setAccessible(true);
|
||||
return (T) field.get(target);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T callMethod(String name, Object target) throws Exception {
|
||||
Class<?> clazz = target.getClass();
|
||||
Method method = ReflectionUtils.findMethod(clazz, name);
|
||||
|
||||
if (method == null)
|
||||
throw new IllegalArgumentException("Cannot find method '" + method + "' in the class hierarchy of "
|
||||
+ target.getClass());
|
||||
method.setAccessible(true);
|
||||
return (T) ReflectionUtils.invokeMethod(method, target);
|
||||
}
|
||||
|
||||
public static void setField(String name, Object target, Object value) throws Exception {
|
||||
Field field = null;
|
||||
Class<?> clazz = target.getClass();
|
||||
do {
|
||||
try {
|
||||
field = clazz.getDeclaredField(name);
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
|
||||
clazz = clazz.getSuperclass();
|
||||
} while (field == null && !clazz.equals(Object.class));
|
||||
|
||||
if (field == null)
|
||||
throw new IllegalArgumentException("Cannot find field '" + name + "' in the class hierarchy of "
|
||||
+ target.getClass());
|
||||
field.setAccessible(true);
|
||||
field.set(target, value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T callMethod(String name, Object target, Object[] args, Class<?>[] argsTypes) throws Exception {
|
||||
Class<?> clazz = target.getClass();
|
||||
Method method = ReflectionUtils.findMethod(clazz, name, argsTypes);
|
||||
|
||||
if (method == null)
|
||||
throw new IllegalArgumentException("Cannot find method '" + method + "' in the class hierarchy of "
|
||||
+ target.getClass());
|
||||
method.setAccessible(true);
|
||||
return (T) ReflectionUtils.invokeMethod(method, target, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.statemachine.test.support;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.listener.StateMachineListener;
|
||||
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
|
||||
import org.springframework.statemachine.state.State;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
|
||||
/**
|
||||
* A {@link StateMachineListener} which is used during the tests
|
||||
* to assert correct count of listener callbacks.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public class LatchStateMachineListener<S, E> extends StateMachineListenerAdapter<S, E> {
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
private volatile CountDownLatch stateChangedLatch = new CountDownLatch(1);
|
||||
private volatile CountDownLatch stateEnteredLatch = new CountDownLatch(1);
|
||||
private volatile CountDownLatch stateExitedLatch = new CountDownLatch(1);
|
||||
private volatile CountDownLatch eventNotAcceptedLatch = new CountDownLatch(1);
|
||||
private volatile CountDownLatch transitionLatch = new CountDownLatch(1);
|
||||
private volatile CountDownLatch transitionStartedLatch = new CountDownLatch(1);
|
||||
private volatile CountDownLatch transitionEndedLatch = new CountDownLatch(1);
|
||||
private volatile CountDownLatch stateMachineStartedLatch = new CountDownLatch(1);
|
||||
private volatile CountDownLatch stateMachineStoppedLatch = new CountDownLatch(1);
|
||||
|
||||
private final List<StateChangedWrapper<S, E>> stateChanged = new ArrayList<StateChangedWrapper<S, E>>();
|
||||
private final List<State<S, E>> stateEntered = new ArrayList<State<S, E>>();
|
||||
private final List<State<S, E>> stateExited = new ArrayList<State<S, E>>();
|
||||
private final List<Message<E>> eventNotAccepted = new ArrayList<Message<E>>();
|
||||
private final List<Transition<S, E>> transition = new ArrayList<Transition<S, E>>();
|
||||
private final List<Transition<S, E>> transitionStarted = new ArrayList<Transition<S, E>>();
|
||||
private final List<Transition<S, E>> transitionEnded = new ArrayList<Transition<S, E>>();
|
||||
private final List<StateMachine<S, E>> stateMachineStarted = new ArrayList<StateMachine<S, E>>();
|
||||
private final List<StateMachine<S, E>> stateMachineStopped = new ArrayList<StateMachine<S, E>>();
|
||||
|
||||
@Override
|
||||
public void stateChanged(State<S, E> from, State<S, E> to) {
|
||||
synchronized (lock) {
|
||||
this.stateChanged.add(new StateChangedWrapper<>(from, to));
|
||||
this.stateChangedLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateEntered(State<S, E> state) {
|
||||
synchronized (lock) {
|
||||
this.stateEntered.add(state);
|
||||
this.stateEnteredLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateExited(State<S, E> state) {
|
||||
synchronized (lock) {
|
||||
this.stateExited.add(state);
|
||||
this.stateExitedLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventNotAccepted(Message<E> event) {
|
||||
synchronized (lock) {
|
||||
this.eventNotAccepted.add(event);
|
||||
this.eventNotAcceptedLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transition(Transition<S, E> transition) {
|
||||
synchronized (lock) {
|
||||
this.transition.add(transition);
|
||||
this.transitionLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transitionStarted(Transition<S, E> transition) {
|
||||
synchronized (lock) {
|
||||
this.transitionStarted.add(transition);
|
||||
this.transitionStartedLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void transitionEnded(Transition<S, E> transition) {
|
||||
synchronized (lock) {
|
||||
this.transitionEnded.add(transition);
|
||||
this.transitionEndedLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateMachineStarted(StateMachine<S, E> stateMachine) {
|
||||
synchronized (lock) {
|
||||
this.stateMachineStarted.add(stateMachine);
|
||||
this.stateMachineStartedLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stateMachineStopped(StateMachine<S, E> stateMachine) {
|
||||
synchronized (lock) {
|
||||
this.stateMachineStopped.add(stateMachine);
|
||||
this.stateMachineStoppedLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
public void reset(int stateChangedCount, int stateEnteredCount, int stateExitedCount, int eventNotAcceptedCount,
|
||||
int transitionCount, int transitionStartedCount, int transitionEndedCount, int stateMachineStartedCount,
|
||||
int stateMachineStoppedCount) {
|
||||
synchronized (lock) {
|
||||
this.stateChangedLatch = new CountDownLatch(stateChangedCount);
|
||||
this.stateEnteredLatch = new CountDownLatch(stateEnteredCount);
|
||||
this.stateExitedLatch = new CountDownLatch(stateExitedCount);
|
||||
this.eventNotAcceptedLatch = new CountDownLatch(eventNotAcceptedCount);
|
||||
this.transitionLatch = new CountDownLatch(transitionCount);
|
||||
this.transitionStartedLatch = new CountDownLatch(transitionStartedCount);
|
||||
this.transitionEndedLatch = new CountDownLatch(transitionEndedCount);
|
||||
this.stateMachineStartedLatch = new CountDownLatch(stateMachineStartedCount);
|
||||
this.stateMachineStoppedLatch = new CountDownLatch(stateMachineStoppedCount);
|
||||
this.stateChanged.clear();
|
||||
this.stateEntered.clear();
|
||||
this.stateExited.clear();
|
||||
this.eventNotAccepted.clear();
|
||||
this.transition.clear();
|
||||
this.transitionStarted.clear();
|
||||
this.transitionEnded.clear();
|
||||
this.stateMachineStarted.clear();
|
||||
this.stateMachineStopped.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public CountDownLatch getStateChangedLatch() {
|
||||
return stateChangedLatch;
|
||||
}
|
||||
|
||||
public CountDownLatch getStateEnteredLatch() {
|
||||
return stateEnteredLatch;
|
||||
}
|
||||
|
||||
public CountDownLatch getStateExitedLatch() {
|
||||
return stateExitedLatch;
|
||||
}
|
||||
|
||||
public CountDownLatch getEventNotAcceptedLatch() {
|
||||
return eventNotAcceptedLatch;
|
||||
}
|
||||
|
||||
public CountDownLatch getTransitionLatch() {
|
||||
return transitionLatch;
|
||||
}
|
||||
|
||||
public CountDownLatch getTransitionStartedLatch() {
|
||||
return transitionStartedLatch;
|
||||
}
|
||||
|
||||
public CountDownLatch getTransitionEndedLatch() {
|
||||
return transitionEndedLatch;
|
||||
}
|
||||
|
||||
public CountDownLatch getStateMachineStartedLatch() {
|
||||
return stateMachineStartedLatch;
|
||||
}
|
||||
|
||||
public CountDownLatch getStateMachineStoppedLatch() {
|
||||
return stateMachineStoppedLatch;
|
||||
}
|
||||
|
||||
public List<StateChangedWrapper<S, E>> getStateChanged() {
|
||||
return stateChanged;
|
||||
}
|
||||
|
||||
public List<State<S, E>> getStateEntered() {
|
||||
return stateEntered;
|
||||
}
|
||||
|
||||
public List<State<S, E>> getStateExited() {
|
||||
return stateExited;
|
||||
}
|
||||
|
||||
public List<Message<E>> getEventNotAccepted() {
|
||||
return eventNotAccepted;
|
||||
}
|
||||
|
||||
public List<Transition<S, E>> getTransition() {
|
||||
return transition;
|
||||
}
|
||||
|
||||
public List<Transition<S, E>> getTransitionStarted() {
|
||||
return transitionStarted;
|
||||
}
|
||||
|
||||
public List<Transition<S, E>> getTransitionEnded() {
|
||||
return transitionEnded;
|
||||
}
|
||||
|
||||
public List<StateMachine<S, E>> getStateMachineStarted() {
|
||||
return stateMachineStarted;
|
||||
}
|
||||
|
||||
public List<StateMachine<S, E>> getStateMachineStopped() {
|
||||
return stateMachineStopped;
|
||||
}
|
||||
|
||||
public static class StateChangedWrapper<S, E> {
|
||||
final State<S, E> from;
|
||||
final State<S, E> to;
|
||||
|
||||
public StateChangedWrapper(State<S, E> from, State<S, E> to) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.statemachine.test;
|
||||
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.statemachine.test.StateMachineTestPlanBuilder.StateMachineTestPlanStep;
|
||||
|
||||
public class StateMachineTestPlanBuilderTests {
|
||||
|
||||
@Test
|
||||
public void testBuilderNoSteps() throws Exception {
|
||||
StateMachineTestPlan<String, String> plan =
|
||||
StateMachineTestPlanBuilder.<String, String>builder()
|
||||
.build();
|
||||
assertThat(plan, notNullValue());
|
||||
List<StateMachineTestPlanStep<?, ?>> steps = TestUtils.readField("steps", plan);
|
||||
assertThat(steps.size(), is(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuilderOneStep() throws Exception {
|
||||
StateMachineTestPlan<String, String> plan =
|
||||
StateMachineTestPlanBuilder.<String, String>builder()
|
||||
.step().expectState("SI").and()
|
||||
.build();
|
||||
assertThat(plan, notNullValue());
|
||||
List<StateMachineTestPlanStep<?, ?>> steps = TestUtils.readField("steps", plan);
|
||||
assertThat(steps.size(), is(1));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.statemachine.test;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.config.EnableStateMachine;
|
||||
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
|
||||
public class StateMachineTestingTests extends AbstractStateMachineTests {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void testSimpleTestingConcept() throws Exception {
|
||||
registerAndRefresh(Config1.class);
|
||||
StateMachine<String, String> machine = context.getBean(StateMachine.class);
|
||||
|
||||
StateMachineTestPlan<String, String> plan =
|
||||
StateMachineTestPlanBuilder.<String, String>builder()
|
||||
.stateMachine(machine)
|
||||
.step().expectState("SI").and()
|
||||
.step().sendEvent("E1").expectStateChanged(1).expectState("S1").and()
|
||||
.step().sendEvent("E2").expectStateChanged(1).expectState("S2").and()
|
||||
.build();
|
||||
|
||||
plan.test();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AnnotationConfigApplicationContext buildContext() {
|
||||
return new AnnotationConfigApplicationContext();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine
|
||||
static class Config1 extends StateMachineConfigurerAdapter<String, String> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<String, String> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial("SI")
|
||||
.state("S1")
|
||||
.state("S2");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source("SI")
|
||||
.target("S1")
|
||||
.event("E1")
|
||||
.and()
|
||||
.withExternal()
|
||||
.source("S1")
|
||||
.target("S2")
|
||||
.event("E2");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,6 +39,8 @@ import org.springframework.statemachine.ensemble.DistributedStateMachine;
|
||||
import org.springframework.statemachine.ensemble.StateMachineEnsemble;
|
||||
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
|
||||
import org.springframework.statemachine.state.State;
|
||||
import org.springframework.statemachine.test.StateMachineTestPlan;
|
||||
import org.springframework.statemachine.test.StateMachineTestPlanBuilder;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
|
||||
public class ZookeeperStateMachineTests extends AbstractZookeeperTests {
|
||||
@@ -137,37 +139,16 @@ public class ZookeeperStateMachineTests extends AbstractZookeeperTests {
|
||||
StateMachine<String, String> machine2 =
|
||||
context.getBean("sm2", StateMachine.class);
|
||||
|
||||
TestListener listener1 =
|
||||
context.getBean("listener1", TestListener.class);
|
||||
TestListener listener2 =
|
||||
context.getBean("listener2", TestListener.class);
|
||||
StateMachineTestPlan<String, String> plan =
|
||||
StateMachineTestPlanBuilder.<String, String>builder()
|
||||
.stateMachine(machine1)
|
||||
.stateMachine(machine2)
|
||||
.step().expectState("SI").and()
|
||||
.step().sendEvent("E1").expectStateChanged(1).expectState("S1").and()
|
||||
.step().sendEvent("E2").expectStateChanged(1).expectState("S2").and()
|
||||
.build();
|
||||
|
||||
assertThat(listener1.stateMachineStartedLatch.await(1, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener2.stateMachineStartedLatch.await(1, TimeUnit.SECONDS), is(true));
|
||||
|
||||
assertThat(machine1.getState().getIds(), containsInAnyOrder("SI"));
|
||||
assertThat(machine2.getState().getIds(), containsInAnyOrder("SI"));
|
||||
|
||||
listener1.reset(1);
|
||||
listener2.reset(1);
|
||||
machine1.sendEvent("E1");
|
||||
|
||||
assertThat(listener1.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener1.stateChangedCount, is(1));
|
||||
assertThat(listener2.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener2.stateChangedCount, is(1));
|
||||
assertThat(machine1.getState().getIds(), containsInAnyOrder("S1"));
|
||||
assertThat(machine2.getState().getIds(), containsInAnyOrder("S1"));
|
||||
|
||||
listener1.reset(1);
|
||||
listener2.reset(1);
|
||||
machine1.sendEvent("E2");
|
||||
assertThat(listener1.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener1.stateChangedCount, is(1));
|
||||
assertThat(listener2.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||
assertThat(listener2.stateChangedCount, is(1));
|
||||
assertThat(machine1.getState().getIds(), containsInAnyOrder("S2"));
|
||||
assertThat(machine2.getState().getIds(), containsInAnyOrder("S2"));
|
||||
plan.test();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user