com.github.dakusui.jcunit.fsm.ScenarioSequence Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jcunit Show documentation
Show all versions of jcunit Show documentation
Automated combinatorial testing framework on top of JUnit
package com.github.dakusui.jcunit.fsm;
import com.github.dakusui.jcunit.core.utils.Checks;
import com.github.dakusui.jcunit.core.utils.StringUtils;
import com.github.dakusui.jcunit.core.tuples.Tuple;
import com.github.dakusui.jcunit.exceptions.JCUnitException;
import java.io.PrintStream;
import java.io.Serializable;
import static com.github.dakusui.jcunit.core.factor.FactorDef.Fsm.*;
/**
* An interface that represents a sequence of scenarios, each of which consists
* of "given", "when", and "then" conditions.
*
* @param A software (class) under test.
* @see Scenario
*/
public interface ScenarioSequence extends Serializable {
/**
* Performs this scenario with given {@code sut} object.
*
* @param token An object to synchronize scenario sequence execution.
*/
void perform(Story.Context context, FSMUtils.Synchronizer synchronizer, FSMUtils.Synchronizable token, Observer observer);
/**
* Returns the number of scenarios in this sequence
*/
int size();
/**
* Returns the {@code i}-th scenario in this sequence.
*
* @param i history index
*/
Scenario get(int i);
/**
* Returns the {@code i}-th state in this sequence.
* Since {@code null} isn't allowed as a level for state factors, you can tell if the corresponding
* factor already has a value or not by simply checking this method returns non-null.
*
* @param i history index
*/
State state(int i);
/**
* Returns the {@code i}-th action in this sequence.
* Since {@code null} isn't allowed as a level for action factors, you can tell if the corresponding
* factor already has a value or not by simply checking this method returns non-null.
*
* @param i history index
*/
Action action(int i);
/**
* Returns {@code j}-th element of {@code i}-th argument list.
*
* @param i history index
* @param j index for argument
*/
Object arg(int i, int j);
/**
* Checks if {@code i}-th argument list has the {@code i}-th element.
*
* @param i history index
* @param j index for argument
*/
boolean hasArg(int i, int j);
/**
* Returns arguments object of {@code i}-th action.
*
* @param i history index
*/
Args args(int i);
abstract class Base implements ScenarioSequence {
public Base() {
}
@Override
public void perform(Story.Context context, FSMUtils.Synchronizer synchronizer, FSMUtils.Synchronizable token, Observer observer) {
Checks.checknotnull(context);
Checks.checknotnull(synchronizer);
Checks.checknotnull(token);
Checks.checknotnull(observer);
Story.Stage stage = context.currentStage();
observer.startSequence(stage, this);
SUT sut = context.sut;
InteractionHistory interactionHistory = context.interactionHistory;
try {
for (int i = 0; i < this.size(); i++) {
Scenario each = this.get(i);
////
// Only for the first scenario, make sure SUT is in the 'given' state.
// We'll see broken test results later on in case it doesn't meet the
// precondition described as the state, otherwise.
if (i == 0) {
if (!each.given.check(sut)) {
throw new Expectation.Result.Builder("Precondition was not satisfied.")
.addFailedReason(StringUtils.format("SUT(%s) isn't in state '%s'", sut, each.given)).build();
}
}
synchronizer = performEachScenario(context, synchronizer, token, observer, stage, sut, interactionHistory, each);
}
} finally {
synchronizer.unregister(token);
observer.endSequence(stage, this);
}
}
private FSMUtils.Synchronizer performEachScenario(
Story.Context context,
FSMUtils.Synchronizer synchronizer,
FSMUtils.Synchronizable token,
Observer observer,
Story.Stage stage,
SUT sut,
InteractionHistory interactionHistory,
Scenario scenario
) {
Expectation.Result result = null;
observer.run(stage, scenario, sut);
boolean passed = false;
try {
////
// Invoke a method in SUT through action corresponding to it.
// - Invoke the method action in SUT.
Object r = scenario.perform(sut);
// 'passed' only means the method in SUT finished without any exceptions.
// The returned value will be validated by 'checkReturnedValue'. (if
// an exception is thrown, the thrown exception will be validated by
// 'checkThrownException'. And if the thrown exception is an expected
// one, it conforms the spec.)
passed = true;
// Author considers that normally application inputs that generatedTuples
// in failure should not affect any internal state of a software module.
// Therefore this try clause should not include the statement above,
// "each.perform(sut)", because if we do so, the input to the method
// held by 'each' will be recorded in inputHistory.
try {
////
// each.perform(sut) didn't throw an exception
//noinspection unchecked,ThrowableResultOfMethodCallIgnored
result = scenario.then().checkReturnedValue(context, r, stage, observer);
} finally {
////
// - Record input history before invoking the action.
interactionHistory.add(scenario.when, scenario.with);
}
} catch (Expectation.Result r) {
result = r;
} catch (JCUnitException e) {
throw e;
} catch (Throwable t) {
if (!passed) {
//noinspection unchecked,ThrowableResultOfMethodCallIgnored
result = scenario.then().checkThrownException(context, t, observer);
} else {
////
// Since the previous catch clause ensures the thrown exception is not
// a JCUnitException, rethrow it without checking.
throw Checks.wrap(t);
}
} finally {
try {
////
// In case unexpected error is detected, e.g., scenario was not executed
// because of insufficient privilege to access SUT, generatedTuples will not be
// created, generatedTuples can be null.
if (result != null) {
if (result.isSuccessful()) {
observer.passed(stage, scenario, sut);
} else {
observer.failed(stage, scenario, sut, result);
}
result.throwIfFailed();
}
} finally {
synchronizer = synchronizer.finishAndSynchronize(token);
}
}
return synchronizer;
}
@Override
public Scenario get(int i) {
Checks.checkcond(i >= 0);
Checks.checkcond(i < this.size());
State given = this.state(i);
Action when = this.action(i);
Args with = this.args(i);
return new Scenario(given, when, with);
}
@Override
public int hashCode() {
return this.toString().hashCode();
}
@Override
public boolean equals(Object another) {
if (another instanceof ScenarioSequence) {
ScenarioSequence anotherSequence = (ScenarioSequence) another;
return PrivateUtils.toString(this).equals(PrivateUtils.toString(anotherSequence));
}
return false;
}
@Override
public String toString() {
return PrivateUtils.toString(this);
}
}
class Empty implements ScenarioSequence {
public static Empty getInstance() {
//noinspection unchecked
return (Empty) INSTANCE;
}
private Empty() {
}
@Override
public void perform(Story.Context context, FSMUtils.Synchronizer synchronizer, FSMUtils.Synchronizable token, Observer observer) {
}
@Override
public int size() {
return 0;
}
@Override
public Scenario get(int i) {
throw new IllegalStateException();
}
@Override
public State state(int i) {
throw new IllegalStateException();
}
@Override
public Action action(int i) {
throw new IllegalStateException();
}
@Override
public Object arg(int i, int j) {
throw new IllegalStateException();
}
@Override
public boolean hasArg(int i, int j) {
throw new IllegalStateException();
}
@Override
public Args args(int i) {
throw new IllegalStateException();
}
@Override
public String toString() {
return PrivateUtils.toString(this);
}
@Override
public int hashCode() {
return this.toString().hashCode();
}
@Override
public boolean equals(Object another) {
if (another instanceof ScenarioSequence) {
ScenarioSequence anotherSequence = (ScenarioSequence) another;
return PrivateUtils.toString(this).equals(PrivateUtils.toString(anotherSequence));
}
return false;
}
private static Empty> INSTANCE = new Empty
© 2015 - 2025 Weber Informatics LLC | Privacy Policy