/*
* jPOS Project [http://jpos.org]
* Copyright (C) 2000-2017 jPOS Software SRL
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jpos.simulator;
import java.io.File;
import java.io.IOException;
import java.io.FileInputStream;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
import org.jpos.iso.ISOMsg;
import org.jpos.iso.ISOComponent;
import org.jpos.iso.ISOUtil;
import org.jpos.iso.ISOField;
import org.jpos.iso.ISOPackager;
import org.jpos.iso.ISOException;
import org.jpos.iso.packager.XMLPackager;
import org.jpos.util.Logger;
import org.jpos.util.LogEvent;
import org.jdom2.Element;
import org.jpos.iso.MUX;
import org.jpos.util.NameRegistrar;
import bsh.Interpreter;
import bsh.BshClassManager;
import bsh.EvalError;
import bsh.UtilEvalError;
public class TestRunner
extends org.jpos.q2.QBeanSupport
implements Runnable
{
MUX mux;
ISOPackager packager;
Interpreter bsh;
private static final String NL = System.getProperty("line.separator");
public static final long TIMEOUT = 60000;
public TestRunner () {
super();
}
protected void initService() throws ISOException {
packager = new XMLPackager();
}
protected void startService() {
for (int i=0; i<cfg.getInt("sessions", 1); i++)
new Thread(this).start();
}
public void run () {
try {
Interpreter bsh = initBSH ();
mux = (MUX) NameRegistrar.get ("mux." + cfg.get ("mux"));
List suite = initSuite (getPersist().getChild ("test-suite"));
runSuite (suite, mux, bsh);
} catch (NameRegistrar.NotFoundException e) {
LogEvent evt = getLog().createError ();
evt.addMessage (e);
evt.addMessage (NameRegistrar.getInstance());
Logger.log (evt);
} catch (Throwable t) {
getLog().error (t);
}
if (cfg.getBoolean ("shutdown"))
shutdownQ2();
}
private void runSuite (List suite, MUX mux, Interpreter bsh)
throws ISOException, IOException, EvalError
{
LogEvent evt = getLog().createLogEvent ("results");
LogEvent evt_error = null;
Iterator iter = suite.iterator();
long start = System.currentTimeMillis();
long serverTime = 0;
for (int i=1; iter.hasNext(); i++) {
evt_error = getLog().createLogEvent("error");
TestCase tc = (TestCase) iter.next();
for (long repetition = 0; repetition < tc.getCount(); repetition++) {
getLog().trace (
"---------------------------[ "
+ tc.getName()
+ " ]---------------------------" );
ISOMsg m = (ISOMsg) tc.getRequest().clone();
if (tc.getPreEvaluationScript() != null) {
bsh.set ("testcase", tc);
bsh.set ("request", m);
bsh.eval (tc.getPreEvaluationScript());
}
tc.setExpandedRequest (applyRequestProps (m, bsh));
tc.start();
if (tc.getExpectedResponse() != null) {
tc.setResponse(mux.request(m, tc.getTimeout()));
tc.end();
assertResponse(tc, bsh, evt_error);
evt.addMessage(i + ": " + tc.toString());
if (evt_error.getPayLoad().size() != 0) {
evt_error.addMessage("filename", tc.getFilename());
evt.addMessage(NL + evt_error.toString(" "));
}
serverTime += tc.elapsed();
if (!tc.ok() && !tc.isContinueOnErrors())
break;
} else {
// response not expected - fire and forget
mux.send(m);
tc.end();
tc.setResultCode(TestCase.OK);
evt.addMessage(i + ": " + tc.toString() + " (response ignored)");
}
}
if (!tc.ok()) {
getLog().error (tc);
if (!tc.isContinueOnErrors())
break;
}
}
long end = System.currentTimeMillis();
long simulatorTime = end - start - serverTime;
long total = end - start;
evt.addMessage (
"elapsed server=" + serverTime
+ "ms(" + percentage (serverTime, total) + "%)"
+ ", simulator=" + simulatorTime
+ "ms(" + percentage (simulatorTime, total) + "%)"
+ ", total=" + total + "ms, shutdown="
+ cfg.getBoolean("shutdown")
);
ISOUtil.sleep (100); // let the channel do its logging first
if (cfg.getBoolean ("shutdown"))
evt.addMessage ("Shutting down");
Logger.log (evt);
}
private List<TestCase> initSuite (Element suite)
throws IOException, ISOException
{
List<TestCase> l = new ArrayList<>();
String prefix = suite.getChildTextTrim ("path");
Iterator iter = suite.getChildren ("test").iterator();
while (iter.hasNext ()) {
Element e = (Element) iter.next ();
boolean cont = "yes".equals (e.getAttributeValue ("continue"));
String s = e.getAttributeValue ("count");
int count = s != null ? Integer.parseInt (s) : 1;
String path = e.getAttributeValue ("file");
String name = e.getAttributeValue ("name");
if (name == null)
name = path;
TestCase tc = new TestCase (name);
tc.setCount(count);
tc.setContinueOnErrors (cont);
tc.setRequest (getMessage (prefix + path + "_s"));
tc.setExpectedResponse (getMessage (prefix + path + "_r"));
tc.setPreEvaluationScript (e.getChildTextTrim ("init"));
tc.setPostEvaluationScript (e.getChildTextTrim ("post"));
tc.setFilename(prefix + path);
String to = e.getAttributeValue ("timeout");
if (to != null)
tc.setTimeout (Long.parseLong (to));
else
tc.setTimeout (cfg.getLong ("timeout", TIMEOUT));
l.add (tc);
}
return l;
}
private ISOMsg getMessage (String filename)
throws IOException, ISOException
{
File f = new File (filename);
ISOMsg m = null;
if (f.canRead()) {
try (FileInputStream fis = new FileInputStream (f)) {
byte[] b = new byte[fis.available()];
fis.read (b);
m = new ISOMsg ();
m.setPackager (packager);
m.unpack (b);
}
}
return m;
}
private boolean processResponse
(ISOMsg er, ISOMsg m, ISOMsg expected, Interpreter bsh, LogEvent evt)
throws ISOException, EvalError
{
int maxField = Math.max(m.getMaxField(), expected.getMaxField());
for (int i=0; i<=maxField; i++) {
if (expected.hasField (i)) {
ISOComponent c = expected.getComponent (i);
if (c instanceof ISOField) {
String value = expected.getString (i);
if (value.charAt (0) == '!' && value.length() > 1)
{
bsh.set ("value", m.getString (i));
Object ret = bsh.eval (value.substring (1));
if (ret instanceof Boolean) {
if (!(Boolean) ret) {
evt.addMessage("field", "[" + i+ "] Boolean eval returned false");
//return false;
}
} else if (ret instanceof String) {
if (m.hasField(i) && !ret.equals(m.getString(i))) {
evt.addMessage("field", "[" + i + "] Received:[" + m.getString(i) + "]" + " Expected:["
+ ret + "]");
}
}
m.unset (i);
expected.unset (i);
}
else if (value.startsWith("*M")) {
if (m.hasField (i)) {
expected.unset (i);
m.unset (i);
} else {
evt.addMessage("field","[" + i+ "] Mandatory field missing");
//return false;
}
}
else if (value.startsWith("*E")) {
if (m.hasField (i) && er.hasField (i)) {
expected.set (i, er.getString (i));
} else {
evt.addMessage("field", "[" + i+ "] Echo field missing");
//return false;
}
}
else if (value.startsWith("*A")) {
if (m.hasField(i)) {
// To make sure value is not returned for this field
evt.addMessage("field", "[" + i + "] Received:[" + m.getString(i) + "]"
+ " Expected: Not to be set");
}
else {
m.unset(i);
expected.unset(i);
}
}
else if (m.hasField(i) && !m.getString(i).equals(value)) {
evt.addMessage("field", "[" + i+ "] Received:[" + m.getString(i) + "]" + " Expected:[" + value + "]");
// return false;
}
}
else if (c instanceof ISOMsg) {
ISOMsg rc = (ISOMsg) m.getComponent (i);
ISOMsg innerExpectedResponse = (ISOMsg) er.getComponent (i);
if (rc instanceof ISOMsg) {
processResponse (innerExpectedResponse, (ISOMsg) rc, (ISOMsg) c, bsh, evt);
}
}
} else {
m.unset (i);
}
}
if (evt.getPayLoad().size()!=0) {
return false;
}
return true;
}
private boolean assertResponse (TestCase tc, Interpreter bsh, LogEvent evt)
throws ISOException, EvalError
{
if (tc.getResponse() == null) {
tc.setResultCode (TestCase.TIMEOUT);
return false;
}
ISOMsg c = (ISOMsg) tc.getResponse().clone();
ISOMsg expected = (ISOMsg) tc.getExpectedResponse().clone();
ISOMsg er = (ISOMsg) tc.getExpandedRequest().clone();
c.setHeader(tc.getResponse().getHeader());
if (!processResponse(er, c, expected, bsh, evt)) {
tc.setResultCode (TestCase.FAILURE);
return false;
}
ISOPackager p = new XMLPackager();
expected.setPackager(p);
c.setPackager(p);
if (tc.getPostEvaluationScript() != null) {
bsh.set ("testcase", tc);
bsh.set ("response", tc.getResponse());
Object ret = bsh.eval (tc.getPostEvaluationScript());
if (ret instanceof Boolean) {
if (!((Boolean)ret).booleanValue()){
tc.setResultCode (TestCase.FAILURE);
return false;
}
}
}
if (!(new String(c.pack())).equals(new String(expected.pack()))) {
tc.setResultCode (TestCase.FAILURE);
return false;
}
tc.setResultCode (TestCase.OK);
return true;
}
private void eval (Element e, String name, Interpreter bsh)
throws EvalError
{
Element ee = e.getChild (name);
if (ee != null)
bsh.eval (ee.getText());
}
private Interpreter initBSH () throws UtilEvalError, EvalError {
Interpreter bsh = new Interpreter ();
BshClassManager bcm = bsh.getClassManager();
bcm.setClassPath(getServer().getLoader().getURLs());
bcm.setClassLoader(getServer().getLoader());
bsh.set ("qbean", this);
bsh.set ("log", getLog());
bsh.eval (getPersist().getChildTextTrim ("init"));
return bsh;
}
private ISOMsg applyRequestProps (ISOMsg m, Interpreter bsh)
throws ISOException, EvalError
{
int maxField = m.getMaxField();
for (int i=0; i<=maxField; i++) {
if (m.hasField(i)) {
ISOComponent c = m.getComponent (i);
if (c instanceof ISOMsg) {
applyRequestProps ((ISOMsg) c, bsh);
} else if (c instanceof ISOField) {
String value = (String) c.getValue();
if (value.length() > 0) {
try {
if (value.charAt (0) == '!') {
m.set (i, bsh.eval (value.substring(1)).toString());
}
else if (value.charAt (0) == '#') {
m.set (i, ISOUtil.hex2byte(bsh.eval (value.substring(1)).toString()));
}
} catch (NullPointerException e) {
m.unset (i);
}
}
}
}
}
return m;
}
private long percentage (long a, long b) {
double d = (double) a / b;
return (long) (d * 100.00);
}
}