![JAR search and dependency download from the Maven repository](/logo.png)
com.github.dakusui.jcunit.fsm.FSMUtils Maven / Gradle / Ivy
package com.github.dakusui.jcunit.fsm;
import com.github.dakusui.jcunit.core.utils.Checks;
import com.github.dakusui.jcunit.core.utils.Utils;
import com.github.dakusui.jcunit.core.factor.Factor;
import com.github.dakusui.jcunit.core.factor.FactorSpace;
import com.github.dakusui.jcunit.core.reflect.ReflectionUtils;
import com.github.dakusui.jcunit.core.tuples.Tuple;
import com.github.dakusui.jcunit.exceptions.JCUnitException;
import com.github.dakusui.jcunit.exceptions.UndefinedSymbol;
import com.github.dakusui.jcunit.fsm.spec.FSMSpec;
import com.github.dakusui.jcunit.plugins.caengines.CoveringArray;
import com.github.dakusui.jcunit.plugins.caengines.CoveringArrayEngine;
import com.github.dakusui.jcunit.plugins.caengines.Ipo2CoveringArrayEngine;
import com.github.dakusui.jcunit.plugins.caengines.SimpleCoveringArrayEngine;
import com.github.dakusui.jcunit.plugins.constraints.ConstraintChecker;
import org.hamcrest.CoreMatchers;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.*;
import java.util.concurrent.*;
import static org.junit.Assert.assertThat;
/**
* A utility class for FSM (finite state machine) support of JCUnit intended to be
* used by users of JCUnit.
*/
public enum FSMUtils {
;
/**
* Resets all stories in {@code testObject} object.
*
* @param testObject A test object whose stories to be reset.
*/
public static void resetStories(final T testObject) {
for (Story each : Utils.transform(FSMUtils.getStoryFields(Checks.checknotnull(testObject)), new Utils.Form() {
@Override
public Story apply(Field in) {
return ReflectionUtils.getFieldValue(testObject, in);
}
})) {
Checks.checknotnull(each, "Probably your test class is not annotated with '@RunWith(JCUnit.class)'.");
each.reset();
}
}
/**
* Invokes {@code FSMUtils#performStory(Object, String, Object, Story.Observer.Factory)}
* with a new {@code Story.Observer.Factory.ForSimple} object.
*
* @see FSMUtils#performStory(Object, String, Object, ScenarioSequence.Observer.Factory)
*/
public static void performStory(T testObject, String fsmName, SUT sut) {
performStory(testObject, fsmName, sut, ScenarioSequence.Observer.Factory.ForSimple.INSTANCE);
}
/**
* Invokes {@code FSMUtils#performStory(Object, String, Object, Story.Observer.Factory)}
* with a new {@code Story.Observer.Factory.ForSimple} object.
*
* @see FSMUtils#performStory(Object, String, Object, ScenarioSequence.Observer.Factory)
*/
public static void performStory(T testObject, String fsmName, SUTFactory sut) {
performStory(testObject, fsmName, sut, ScenarioSequence.Observer.Factory.ForSimple.INSTANCE);
}
/**
* Performs a story object on {@code sut} in an object {@code testObject} specified
* by {@code fsmName}.
*
* This method looks up a field whose name is {@code fsmName} and will perform
* a value of the field as an appropriate {@code Story}. Before performing the story,
* this method validates the field and its value, e.g., the type is correct or
* not, value is assigned, etc.
* If the field doesn't meet the condition an exception will be thrown.
*
* It is recommended to use this method to invoke a story rather than directly
* calling {@code Story#perform} method.
*
* @param testObject A test object which encloses fsm field(s)
* @param fsmName A name of FSM. A field story object is assigned to.
* @param sutFactory A factory to create an object on which the story specified by {@code fsmName}
* will be performed
* @param observerFactory A factory that creates an observer to which activities
* done by JCUnit are reported.
* @param A test class's type.
* @param The type of SUT
*/
public static void performStory(T testObject, String fsmName, SUTFactory sutFactory, ScenarioSequence.Observer.Factory observerFactory) {
Checks.checktest(testObject != null, "testObject mustn't be null. Simply give your test object.");
Checks.checktest(fsmName != null, "fsmName mustn't be null. Give factor field name whose type is Story of your test object.");
Checks.checktest(sutFactory != null, "SUT mustn't be null. Give your object to be tested.");
Checks.checktest(observerFactory != null, "");
////
// Ensure stories are reset. By design policy, fields should be immutable.
// but I couldn't make FSM stories so to implement "nested-FSM" feature.
// In order to guarantee FSM objects' states are always the same at the
// beginning of each test (method), I'm calling FSMUtils.resetStories
// method here. (Issue-#14)
FSMUtils.resetStories(testObject);
//noinspection unchecked
Story> story = FSMUtils.lookupStory(testObject, fsmName);
////
// If story is null, it only happens because of JCUnit framework bug since JCUnit/JUnit framework
// should assign an appropriate value to the factor field.
Checks.checktest(story != null, "story parameter must not be null.");
//noinspection unchecked
Story.Performer.Default.INSTANCE.perform(story, testObject, sutFactory, Synchronizer.DUMMY, observerFactory.createObserver(fsmName));
////
// This path shouldn't be executed because IllegalAccessException is already rethrown
}
/**
* This method has the same effect as the following line.
*
*
* performStory(testObject, fsmName, new SUTFactory.Dummy(sut), observerFactory);
*
*
* @param testObject A test object which encloses fsm field(s)
* @param fsmName A name of FSM. A field story object is assigned to.
* @param sut An object on which the story specified by {@code fsmName}
* will be performed
* @param observerFactory A factory that creates an observer to which activities
* done by JCUnit are reported.
* @param A test class's type.
* @param The type of SUT
*/
public static void performStory(T testObject, String fsmName, SUT sut, ScenarioSequence.Observer.Factory observerFactory) {
performStory(testObject, fsmName, new SUTFactory.Dummy(sut), observerFactory);
}
/**
* Performs a story object on {@code sut} in an object {@code testObject} specified
* by {@code fsmName}.
*
* This method looks up a field whose name is {@code fsmName} and will perform
* a value of the field as an appropriate {@code Story}. Before performing the story,
* this method validates the field and its value, e.g., the type is correct or
* not, value is assigned, etc.
* If the field doesn't meet the condition an exception will be thrown.
*
* It is recommended to use this method to invoke a story rather than directly
* calling {@code Story#perform} method.
*
* @param testObject A test object which encloses fsm field(s)
* @param stories StoryRequest objects each of which holds information
* about which story is performed with what SUT object
* @param A test class's type.
*/
public static void performStoriesConcurrently(final T testObject, Story.Request[] stories) {
Checks.checktest(testObject != null, "testObject mustn't be null. Simply give your test object.");
Checks.checktest(stories != null, "Stories mustn't be null. Give factor field name whose type is Story of your test object.");
////
// Validate story fields in advance
for (String eachFSMmName : Utils.transform(Arrays.asList(stories), new Utils.Form() {
@Override
public String apply(Story.Request in) {
return in.fsmName;
}
})) {
Field storyField = lookupStoryField(testObject, eachFSMmName);
Checks.checktest(storyField != null, "The field '%s' was not found or not public in the testObject '%s'", eachFSMmName, testObject);
}
////
// Ensure stories are reset. By design policy, fields should be immutable.
// but I couldn't make FSM stories so to implement "nested-FSM" feature.
// In order to guarantee FSM objects' states are always the same at the
// beginning of each test (method), I'm calling FSMUtils.resetStories
// method here. (Issue-#14)
FSMUtils.resetStories(testObject);
////
// Perform scenario sequences concurrently.
ExecutorService executorService = Executors.newFixedThreadPool(stories.length);
try {
@SuppressWarnings("RedundantTypeArguments")
final Synchronizer synchronizer = new Synchronizer.Builder(
Utils.transform(
Arrays.asList(stories),
new Utils.Form() {
@Override
public Story apply(Story.Request in) {
return ReflectionUtils.getFieldValue(testObject, Checks.checknotnull(FSMUtils.lookupStoryField(testObject, in.fsmName)));
}
})).build();
//noinspection unchecked
List> callables = Utils.transform(
Arrays.asList(stories), new Utils.Form>() {
@Override
public Callable apply(Story.Request in) {
//noinspection unchecked
return in.createCallable(Story.Performer.Default.INSTANCE, synchronizer, testObject);
}
});
for (Future f : executorService.invokeAll(callables)) {
assertThat(f.get(), CoreMatchers.is(true));
}
} catch (InterruptedException e) {
throw Checks.wrap(e);
} catch (ExecutionException e) {
Throwable t = e.getCause();
try {
throw t;
} catch (JCUnitException ee) {
throw ee;
} catch (RuntimeException ee) {
throw ee;
} catch (Error ee) {
throw ee;
} catch (Throwable ee) {
throw Checks.wrap(ee);
}
} finally {
executorService.shutdown();
}
}
/**
* {@code f} Must be annotated with {@code FactorField}. Its {@code levelsProvider} must be an FSMLevelsProvider.
* Typed with {@code Story} class.
*
* @param f A field from which an FSM is created.
* @return Created FSM object
*/
public static FSM
© 2015 - 2025 Weber Informatics LLC | Privacy Policy