org.drools.testframework.ScenarioRunner Maven / Gradle / Ivy
package org.drools.testframework;
import static org.mvel2.MVEL.eval;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import org.drools.FactHandle;
import org.drools.RuleBase;
import org.drools.base.ClassTypeResolver;
import org.drools.base.TypeResolver;
import org.drools.common.InternalRuleBase;
import org.drools.common.InternalWorkingMemory;
import org.drools.guvnor.client.modeldriven.testing.ExecutionTrace;
import org.drools.guvnor.client.modeldriven.testing.Expectation;
import org.drools.guvnor.client.modeldriven.testing.FactData;
import org.drools.guvnor.client.modeldriven.testing.FieldData;
import org.drools.guvnor.client.modeldriven.testing.Fixture;
import org.drools.guvnor.client.modeldriven.testing.RetractFact;
import org.drools.guvnor.client.modeldriven.testing.Scenario;
import org.drools.guvnor.client.modeldriven.testing.VerifyFact;
import org.drools.guvnor.client.modeldriven.testing.VerifyField;
import org.drools.guvnor.client.modeldriven.testing.VerifyRuleFired;
import org.drools.guvnor.server.util.ScenarioXMLPersistence;
import org.drools.rule.Package;
import org.drools.rule.TimeMachine;
import org.mvel2.MVEL;
/**
* This actually runs the test scenarios.
*
* @author Michael Neale
*
*/
public class ScenarioRunner {
final Scenario scenario;
final Map populatedData = new HashMap();
final Map globalData = new HashMap();
final Map factHandles = new HashMap();
final InternalWorkingMemory workingMemory;
/**
* This constructor is normally used by Guvnor for running tests on a users request.
* @param scenario
* The scenario to run.
* @param resolver
* A populated type resolved to be used to resolve the types in
* the scenario.
*
* For info on how to invoke this, see
* ContentPackageAssemblerTest.testPackageWithRuleflow in drools-guvnor This
* requires that the classloader for the thread context be set
* appropriately. The PackageBuilder can provide a suitable TypeResolver for
* a given package header, and the Package config can provide a classloader.
*
*/
public ScenarioRunner(final Scenario scenario, final TypeResolver resolver,
final InternalWorkingMemory wm) throws ClassNotFoundException {
this.scenario = scenario;
this.workingMemory = wm;
runScenario(scenario, resolver, wm);
}
/**
* Use this constructor if you have a scenario in a file, for instance.
* @throws ClassNotFoundException
*/
public ScenarioRunner(String xml, RuleBase rb) throws ClassNotFoundException {
this.scenario = ScenarioXMLPersistence.getInstance().unmarshal(xml);
this.workingMemory = (InternalWorkingMemory) rb.newStatefulSession();
Package pk = rb.getPackages()[0];
ClassLoader cl = ((InternalRuleBase) rb).getRootClassLoader();
HashSet imports = new HashSet();
imports.add(pk.getName() + ".*");
imports.addAll(pk.getImports().keySet());
TypeResolver resolver = new ClassTypeResolver( imports, cl );
runScenario(scenario, resolver, this.workingMemory);
}
private void runScenario(final Scenario scenario,
final TypeResolver resolver, final InternalWorkingMemory wm)
throws ClassNotFoundException {
MVEL.COMPILER_OPT_ALLOW_NAKED_METH_CALL = true;
scenario.lastRunResult = new Date();
//stub out any rules we don't want to have the consequences firing of.
HashSet ruleList = new HashSet();
ruleList.addAll(scenario.rules);
//TestingEventListener.stubOutRules(ruleList, wm.getRuleBase(), scenario.inclusive);
TestingEventListener listener = null;
for (Iterator iterator = scenario.globals.iterator(); iterator.hasNext();) {
FactData fact = (FactData) iterator.next();
Object f = eval("new " + resolver.getFullTypeName(fact.type) + "()");
populateFields(fact, globalData, f);
globalData.put(fact.name, f);
wm.setGlobal(fact.name, f);
}
for (Iterator iterator = scenario.fixtures.iterator(); iterator.hasNext();) {
Fixture fx = iterator.next();
if (fx instanceof FactData) {
//deal with facts and globals
FactData fact = (FactData)fx;
Object f = (fact.isModify)? this.populatedData.get(fact.name) : eval("new " + resolver.getFullTypeName(fact.type) + "()");
if (fact.isModify) {
if (!this.factHandles.containsKey(fact.name)) {
throw new IllegalArgumentException("Was not a previously inserted fact. [" + fact.name + "]");
}
populateFields(fact, populatedData, f);
this.workingMemory.update(this.factHandles.get(fact.name), f);
} else /* a new one */ {
populateFields(fact, populatedData, f);
populatedData.put(fact.name, f);
this.factHandles.put(fact.name, wm.insert(f));
}
} else if (fx instanceof RetractFact) {
RetractFact f = (RetractFact)fx;
this.workingMemory.retract(this.factHandles.get(f.name));
this.populatedData.remove(f.name);
} else if (fx instanceof ExecutionTrace) {
ExecutionTrace executionTrace = (ExecutionTrace)fx;
//create the listener to trace rules
if (listener != null) wm.removeEventListener(listener); //remove the old
listener = new TestingEventListener();
wm.addEventListener(listener);
//set up the time machine
applyTimeMachine(wm, executionTrace);
//love you
long time = System.currentTimeMillis();
wm.fireAllRules(listener.getAgendaFilter(ruleList, scenario.inclusive),scenario.maxRuleFirings);
executionTrace.executionTimeResult = System.currentTimeMillis() - time;
executionTrace.numberOfRulesFired = listener.totalFires;
executionTrace.rulesFired = listener.getRulesFiredSummary();
} else if (fx instanceof Expectation) {
Expectation assertion = (Expectation) fx;
if (assertion instanceof VerifyFact) {
verify((VerifyFact) assertion);
} else if (assertion instanceof VerifyRuleFired) {
verify((VerifyRuleFired) assertion,
(listener.firingCounts != null) ? listener.firingCounts : new HashMap());
}
} else {
throw new IllegalArgumentException("Not sure what to do with " + fx);
}
}
}
private void applyTimeMachine(final InternalWorkingMemory wm,
ExecutionTrace executionTrace) {
if (executionTrace.scenarioSimulatedDate != null) {
final Calendar now = Calendar.getInstance();
now.setTimeInMillis(executionTrace.scenarioSimulatedDate.getTime());
wm.setTimeMachine(new TimeMachine() {
@Override
public Calendar getNow() {
return now;
}
});
} else {
//normal time.
wm.setTimeMachine(new TimeMachine());
}
}
void verify(VerifyRuleFired assertion, Map firingCounts) {
assertion.actualResult = firingCounts.containsKey(assertion.ruleName) ? firingCounts
.get(assertion.ruleName)
: 0;
if (assertion.expectedFire != null) {
if (assertion.expectedFire) {
if (assertion.actualResult > 0) {
assertion.successResult = true;
assertion.explanation = "Rule [" + assertion.ruleName + "] was actived " + assertion.actualResult + " times.";
} else {
assertion.successResult = false;
assertion.explanation = "Rule [" + assertion.ruleName + "] was not activated. Expected it to be activated.";
}
} else {
if (assertion.actualResult == 0) {
assertion.successResult = true;
assertion.explanation = "Rule [" + assertion.ruleName + "] was not activated.";
} else {
assertion.successResult = false;
assertion.explanation = "Rule [" + assertion.ruleName + "] was activated " + assertion.actualResult + " times, but expected none.";
}
}
}
if (assertion.expectedCount != null) {
if (assertion.actualResult.equals(assertion.expectedCount)) {
assertion.successResult = true;
assertion.explanation = "Rule [" + assertion.ruleName + "] activated " + assertion.actualResult + " times.";
} else {
assertion.successResult = false;
assertion.explanation = "Rule [" + assertion.ruleName + "] activated " + assertion.actualResult + " times. Expected " + assertion.expectedCount + " times.";
}
}
}
void verify(VerifyFact value) {
if (!value.anonymous) {
Object fact = this.populatedData.get(value.name);
if (fact == null) fact = this.globalData.get(value.name);
checkFact(value, fact);
} else {
Iterator obs = this.workingMemory.iterateObjects();
while(obs.hasNext()) {
Object fact = obs.next();
if (fact.getClass().getSimpleName().equals(value.name)) {
checkFact(value, fact);
if (value.wasSuccessful()) return;
}
}
for (Iterator iterator = value.fieldValues.iterator(); iterator.hasNext();) {
VerifyField vfl = (VerifyField) iterator.next();
if (vfl.successResult == null) {
vfl.successResult = Boolean.FALSE;
vfl.actualResult = "No match";
}
}
}
}
private void checkFact(VerifyFact value, Object fact) {
for (int i = 0; i < value.fieldValues.size(); i++) {
VerifyField fld = (VerifyField) value.fieldValues.get(i);
Map st = new HashMap();
st.put("__fact__", fact);
if (fld.expected != null) {
Object expectedVal = fld.expected.trim();
if (fld.expected.startsWith("=")) {
expectedVal = eval(fld.expected.substring(1), this.populatedData);
}
st.put("__expected__", expectedVal);
fld.successResult = (Boolean) eval("__fact__." + fld.fieldName
+ " " + fld.operator + " __expected__", st);
if (!fld.successResult) {
Object actual = eval("__fact__." + fld.fieldName, st);
fld.actualResult = (actual != null) ? actual.toString() : "";
if (fld.operator.equals("==")) {
fld.explanation = "[" + value.name + "] field [" + fld.fieldName + "] was [" + fld.actualResult
+ "] expected [" + fld.expected + "].";
} else {
fld.explanation = "[" + value.name + "] field [" + fld.fieldName + "] was not expected to be [" + fld.actualResult
+ "].";
}
} else {
if (fld.operator.equals("==")) {
fld.explanation = "[" + value.name + "] field [" + fld.fieldName + "] was [" + fld.expected + "].";
} else if (fld.operator.equals("!=")){
fld.explanation = "[" + value.name + "] field [" + fld.fieldName + "] was not [" + fld.expected + "].";
}
}
}
}
}
Object populateFields(FactData fact, Map factData, Object factObject) {
for (int i = 0; i < fact.fieldData.size(); i++) {
FieldData field = (FieldData) fact.fieldData.get(i);
Object val;
if (field.value != null && !field.value.equals("")) {
if (field.value.startsWith("=")) {
// eval the val into existence
val = eval(field.value.substring(1), factData);
} else {
val = field.value;
}
Map vars = new HashMap();
vars.putAll(factData);
vars.put("__val__", val);
vars.put("__fact__", factObject);
eval("__fact__." + field.name + " = __val__", vars);
}
}
return factObject;
}
/**
* True if the scenario was run with 100% success.
*/
public boolean wasSuccess() {
return this.scenario.wasSuccessful();
}
/**
* @return A pretty printed report detailing any failures that occured
* when running the scenario (unmet expectations).
*/
public String getReport() {
return this.scenario.printFailureReport();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy