blob: 29167b83dc239571bd8ba0e057af9ad773276e49 [file] [log] [blame]
/** Copyright (C) Conformiq Software Oy, Ltd.
* All rights reserved.
*
* Created Wed Aug 27 16:49:21 2008.
*
* @file
*
* @author Tommi Vainikainen
*
*
*/
package com.conformiq.adaptation.ttcn;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import com.conformiq.qtronic2.Checkpoint;
import com.conformiq.qtronic2.MetaDataDictionary;
import com.conformiq.qtronic2.NotificationSink;
import com.conformiq.qtronic2.QMLArray;
import com.conformiq.qtronic2.QMLBoolean;
import com.conformiq.qtronic2.QMLNumber;
import com.conformiq.qtronic2.QMLOptional;
import com.conformiq.qtronic2.QMLRecord;
import com.conformiq.qtronic2.QMLRecordType;
import com.conformiq.qtronic2.QMLRecordTypeField;
import com.conformiq.qtronic2.QMLString;
import com.conformiq.qtronic2.QMLValue;
import com.conformiq.qtronic2.QMLValueVisitor;
import com.conformiq.qtronic2.ScriptBackend;
import com.conformiq.qtronic2.TimeStamp;
import com.conformiq.qtronic2.Checkpoint.CheckpointStatus;
import com.conformiq.qtronic2.Checkpoint.CheckpointType;
public class TTCNScripter extends ScriptBackend
{
class TemplateDefinition
{
public TemplateDefinition(final String type, final String templatename,
QMLRecord record)
{
this.typeName = type;
this.templateName = templatename;
this.record = record;
}
private String typeName;
private String templateName;
public String getTypeName()
{
return typeName;
}
public String getTemplateName()
{
return templateName;
}
private QMLRecord record;
public QMLRecord getRecord()
{
return record;
}
}
/** Convert adapter datums into TTCN3 code. */
class TTCN3PortVisitor implements QMLValueVisitor
{
public TTCN3PortVisitor(PrettyPrinter pp, boolean isInbound)
{
this.out = pp;
this.isInbound = isInbound;
assert (out != null);
typeNameMap = new HashMap<String, String>();
/* Booleans */
typeNameMap.put("boolean", "boolean");
/* Integers. */
typeNameMap.put("int", "integer");
typeNameMap.put("byte", "integer");
typeNameMap.put("char", "integer");
typeNameMap.put("short", "integer");
typeNameMap.put("long", "integer");
/* Floating point. */
typeNameMap.put("float", "float");
typeNameMap.put("double", "float");
/* Strinags. */
typeNameMap.put("String", "charstring");
}
protected PrettyPrinter out;
protected final boolean isInbound;
protected Map<String, String> typeNameMap;
@Override
public void visit(QMLArray x)
{
/*
* Port types are tuples with just Strings inside referring to
* types.
*/
assert (x != null);
final int size = x.getNumberOfElements();
for (int i = 0; i < size; i++)
{
QMLString portname = (QMLString) x.getValue(i);
if (portname != null)
{
final String origname = portname.getValue();
final String name = TTCNScripter
.makeValidTTCN3Identifier(origname);
/* Skip AnyRecord.. */
if (name.equals("AnyRecord"))
{
continue;
}
/*
* It seems that record fields are separated using commas
* but ports are separated using semicolons in TTCN3..?
*/
if (eptfBidirectionalPort)
out.print("inout");
else
out.print(isInbound ? "in" : "out");
out.print(" ");
out.print(name);
out.print(";");
out.endl();
}
}
}
@Override
public void visit(QMLBoolean b)
{
}
@Override
public void visit(QMLNumber n)
{
}
@Override
public void visit(QMLRecord r)
{
}
@Override
public void visit(QMLString s)
{
}
@Override
public void visit(QMLOptional p)
{
}
}
// My port visitor
class MyTTCN3PortVisitor implements QMLValueVisitor
{
public MyTTCN3PortVisitor(PrettyPrinter pp, boolean isInbound,
String port, String indent)
{
this.out = pp;
this.isInbound = isInbound;
this.port = port;
this.indent = indent;
assert (out != null);
typeNameMap = new HashMap<String, String>();
/* Booleans */
typeNameMap.put("boolean", "boolean");
/* Integers. */
typeNameMap.put("int", "integer");
typeNameMap.put("byte", "integer");
typeNameMap.put("char", "integer");
typeNameMap.put("short", "integer");
typeNameMap.put("long", "integer");
/* Floating point. */
typeNameMap.put("float", "float");
typeNameMap.put("double", "float");
/* Strinags. */
typeNameMap.put("String", "charstring");
}
protected PrettyPrinter out;
protected final boolean isInbound;
protected Map<String, String> typeNameMap;
private String port;
private String indent;
@Override
public void visit(QMLArray x)
{
/*
* Port types are tuples with just Strings inside referring to
* types.
*/
assert (x != null);
final int size = x.getNumberOfElements();
for (int i = 0; i < size; i++)
{
QMLString portname = (QMLString) x.getValue(i);
if (portname != null)
{
final String origname = portname.getValue();
final String name = TTCNScripter
.makeValidTTCN3Identifier(origname);
/* Skip AnyRecord.. */
if (name.equals("AnyRecord"))
{
continue;
}
// Harness generation
out.print(indent + "function qtronic");
out.print(isInbound ? "_receive_" : "_send_");
out.print(name);
out.print(isInbound ? "_from_" : "_to_");
out.print(this.port);
out.print("(template " + name + " ");
out.print((isInbound ? "tmplToMatch" : "msgToSend")
+ ") runs on " + runsOnName);
out.endl();
out.print(indent + "{");
out.endl();
out.print(indent + indent
+ "// Do whatever manipulation is needed to '");
out.print((isInbound ? "tmplToMatch" : "msgToSend"));
out.print("'");
out.endl();
if (isInbound)
{
out
.print(indent
+ indent
+ "// before matching it against the expected value template.");
out.endl();
out
.print(indent
+ indent
+ "// This manipulation typically means removing fields not present");
out.endl();
out.print(indent + indent
+ "// in the abstract model system interface.");
out.endl();
if (eptfEnable)
{
if (this.port.equals(eptfInboundPort)
|| this.port.equals(eptfOutboundPort))
{
out
.print(indent
+ indent
+ "EPTF_MBT_TESTER_PCO.receive(tmplToMatch) from vc_lgen;");
out.endl();
}
else
{
out.print(indent + indent + this.port
+ ".receive(tmplToMatch);");
out.endl();
}
}
}
else
{
out
.print(indent
+ indent
+ "// before it is delivered to the SUT through the real test interface.");
out.endl();
out
.print(indent
+ indent
+ "// This can include addition of fields not present in the model or");
out.endl();
out
.print(indent
+ indent
+ "// manipulation of unique identifiers in the messages that Qtronic");
out.endl();
out
.print(indent
+ indent
+ "// cannot possible know at the time of test generation.");
out.endl();
if (eptfEnable)
{
if (this.port.equals(eptfInboundPort)
|| this.port.equals(eptfOutboundPort))
{
out
.print(indent
+ indent
+ "EPTF_MBT_TESTER_PCO.send(msgToSend) to vc_lgen;");
out.endl();
}
else
{
out.print(indent + indent + this.port
+ ".send(msgToSend);");
out.endl();
}
}
}
out.print(indent + "}");
out.endl();
}
}
}
@Override
public void visit(QMLBoolean b)
{
}
@Override
public void visit(QMLNumber n)
{
}
@Override
public void visit(QMLRecord r)
{
}
@Override
public void visit(QMLString s)
{
}
@Override
public void visit(QMLOptional p)
{
}
}
private PrettyPrinter out;
private NotificationSink notifications;
private MetaDataDictionary metadata;
private int testCaseIndex;
private int templateIndex;
private boolean isCompact;
private TimeStamp timeStamp;
private String logCommand;
private boolean generateTypes;
private boolean generateTestComponent;
private String filename;
private String timerName;
private String defaultName;
private String startHook;
private String commSlack;
private String extraImports;
private String runsOnName;
private String systemTypeName;
private String endHook;
private String portExtensions;
private String moduleName;
private String dataTypesFile;
private String testHarnessFile;
private boolean generateTestHarness;
private boolean firstStep;
private Map<QMLRecord, Integer> generatedTemplates;
private Vector<TemplateDefinition> postponedTemplates;
private Vector<String> testCaseNames;
private String extendsComponent;
private boolean eptfEnable;
private String eptfInboundPort;
private String eptfOutboundPort;
private boolean eptfBidirectionalPort;
static private String SCRIPTER_NAME = "TTCN-3 scripter 0.1";
public TTCNScripter()
{
this.testCaseIndex = 0;
this.templateIndex = 0;
this.isCompact = false;
this.timeStamp = new TimeStamp();
this.timeStamp.seconds = 0;
this.timeStamp.nanoseconds = 0;
this.logCommand = "log";
this.generateTypes = true;
this.generateTestComponent = true;
this.filename = "";
this.timerName = "";
this.defaultName = "";
this.startHook = "";
this.commSlack = "";
this.extraImports = "";
this.runsOnName = "";
this.systemTypeName = "";
this.endHook = "";
this.portExtensions = "";
this.moduleName = "";
this.dataTypesFile = "";
this.testHarnessFile = "";
this.generateTestHarness = true;
this.firstStep = false;
this.generatedTemplates = new HashMap<QMLRecord, Integer>();
this.postponedTemplates = new Vector<TemplateDefinition>();
this.testCaseNames = new Vector<String>();
this.extendsComponent = "";
this.eptfEnable = false;
this.eptfInboundPort = "";
this.eptfOutboundPort = "";
this.eptfBidirectionalPort = false;
}
@Override
public void setNotificationSink(NotificationSink sink)
{
notifications = sink;
}
@Override
public boolean setMetaData(MetaDataDictionary dict)
{
metadata = dict;
return true;
}
@Override
public boolean setConfigurationOption(java.lang.String property,
java.lang.String value)
{
if (property.equals("Main.Generated TTCN-3 file"))
{
filename = value;
moduleName = getModuleName(filename);
}
else if (property.equals("Main.Generated types and test component"))
{
dataTypesFile = value;
}
else if (property.equals("Main.Generated harness template file"))
{
testHarnessFile = value;
}
else if (property.equals("Main.Generate types"))
{
generateTypes = value.equals("true");
}
else if (property.equals("Main.Generate ports and test component"))
{
generateTestComponent = value.equals("true");
}
else if (property.equals("Main.Test component extension"))
{
extendsComponent = value;
}
else if (property.equals("Main.Generate test harness template"))
{
generateTestHarness = value.equals("true");
}
else if (property.equals("Logging.Name of the 'log' command"))
{
logCommand = value;
}
else if (property.equals("Customization.Global timer name"))
{
timerName = value;
}
else if (property.equals("Customization.Activated default"))
{
defaultName = value;
}
else if (property.equals("Customization.Start test case hook call"))
{
startHook = value;
}
else if (property.equals("Timing.Communications slack limit"))
{
commSlack = value;
}
else if (property.equals("Customization.Extra import statements"))
{
extraImports = value;
}
else if (property.equals("Customization.Runs on type name"))
{
runsOnName = value;
}
else if (property.equals("Customization.System type name"))
{
systemTypeName = value;
}
else if (property.equals("Customization.End test case hook call"))
{
endHook = value;
}
else if (property.equals("Extensions.Use port type extension"))
{
portExtensions = value;
}
else if (property.equals("Logging.Log checkpoints"))
{
isCompact = !value.equals("true");
}
else if (property.equals("EPTF_MBT_Applib.Enable"))
{
eptfEnable = value.equals("true");
}
else if (property.equals("EPTF_MBT_Applib.Inbound Port"))
{
eptfInboundPort = value;
}
else if (property.equals("EPTF_MBT_Applib.Outbound Port"))
{
eptfOutboundPort = value;
}
else if (property.equals("EPTF_MBT_Applib.Bidirectional Ports"))
{
eptfBidirectionalPort = value.equals("true");
}
return true;
}
private String getModuleName(String path)
{
int slash = path.lastIndexOf("/");
int backslash = path.lastIndexOf("\\");
String module;
if (slash != -1 && backslash != -1)
{
module = path
.substring((slash > backslash ? slash : backslash) + 1);
}
else if (slash != -1)
{
module = path.substring(slash + 1);
}
else if (backslash != -1)
{
module = path.substring(backslash + 1);
}
else
{
module = path;
}
int size = module.length();
String upper = module.toUpperCase();
if (upper.lastIndexOf(".TTCN3") == (size - 6))
{
return module.substring(0, size - 6);
}
if (upper.lastIndexOf(".TTCN") == (size - 5))
{
return module.substring(0, size - 5);
}
if (upper.lastIndexOf(".3MP") == (size - 4))
{
return module.substring(0, size - 4);
}
return "QtronicTest";
}
@Override
public boolean beginScript(String testsuiteName)
{
if (notifications != null)
{
notifications.notify("info", SCRIPTER_NAME
+ ": Export test cases for test " + "design configuration "
+ testsuiteName);
}
String msg = null;
if (filename.equals(""))
{
msg = "TTCN-3 backend must be configured with a file name.";
}
else
{
try
{
out = new PrettyPrinter(new BufferedWriter(new FileWriter(
filename)));
} catch (IOException e)
{
StringBuffer msgbuf = new StringBuffer();
msgbuf.append("Failed to open file '");
msgbuf.append(filename);
msgbuf.append("' for writing TTCN-3 script.");
msg = msgbuf.toString();
}
}
if (msg != null)
{
if (notifications != null)
{
notifications.notify("error", SCRIPTER_NAME + ": " + msg);
}
else
{
System.err.println(msg);
}
return false;
}
dumpHeader(filename);
out.print("module ");
out.print(moduleName);
out.endl();
out.print("{");
out.endl();
out.print(" ");
out.block();
out.print("import from ");
out.print(getModuleName(dataTypesFile));
out.print(" all;");
out.endl();
// import generated test harness
if (eptfEnable && generateTestHarness && !testHarnessFile.equals(""))
{
out.print("import from ");
out.print(getModuleName(testHarnessFile));
out.print(" all;");
out.endl();
}
out.print("/* User provided imports begin */");
out.endl();
out.print(extraImports);
out.endl();
out.print("/* User provided imports end */");
out.endl();
// print out the test harness template
try
{
// should the harness template be written
if (generateTestHarness)
{
// check if file already exists. If it does ask user
// to remove it first
try
{
new FileReader(testHarnessFile);
if (notifications != null)
{
notifications.notify("info", SCRIPTER_NAME + ": File "
+ testHarnessFile + " exists, NOT overwriting it.");
}
return true;
} catch (FileNotFoundException not_found)
{
// create the template file
dumpTestHarness();
if (notifications != null)
{
notifications.notify("info", SCRIPTER_NAME
+ ": Generated TTCN-3 test harness template to "
+ "<hyperlink=\"" + testHarnessFile + "\">"
+ testHarnessFile + "</hyperlink>.");
}
}
}
} catch (Exception e)
{
out.print("/* EXCEPTION CAUGHT */");
out.endl();
if (notifications != null)
{
notifications
.notify(
"error",
SCRIPTER_NAME
+ ": Caught exception while "
+ "rendering TTCN test harness template. Please reload model "
+ "and try again (" + e.getMessage() + ")");
}
return false;
}
if (generateTypes)
{
// out.print("/* Qtronic generated data types begin */");
// out.endl();
/* First dump all the types used in the module. */
try
{
dumpTypes();
dumpTemplates();
// out.print("/* Qtronic generated data types end */");
// out.endl();
} catch (Exception e)
{
out.print("/* EXCEPTION CAUGHT */");
out.endl();
if (notifications != null)
{
notifications
.notify(
"error",
SCRIPTER_NAME
+ ": Caught exception while "
+ "rendering TTCN type definitions. Please reload model "
+ "and try again (" + e.getMessage()
+ ")");
}
return false;
}
}
out.print("/* Qtronic generated alt step */");
out.endl();
/*
* The GENERATED default is always called QtronicDefaultAlt(), but the
* user can use alternative default by changing configuration option. In
* this case the user provides the default.
*/
out.print("altstep QtronicDefaultAlt() runs on ");
out.print(runsOnName);
out.endl();
out.print("{");
out.endl();
out.print(" ");
printAltStepBody();
deleteCopies();
templateIndex = 0;
testCaseIndex = 0;
firstStep = false;
return true;
}
private void printAltStepBody()
{
printAltStepBody("");
}
private void printAltStepBody(String indent)
{
out.print(indent);
out.block();
out.print("[] any port.receive");
out.endl();
out.print("{");
out.endl();
out.print(" ");
out.block();
out.print(timerName);
out.print(".stop;");
out.endl();
out.print("setverdict(fail);");
out.endl();
if (!endHook.equals(""))
{
out.print(endHook);
out.print(";");
out.endl();
}
out.print("stop;");
out.endl();
out.resume();
out.print("}");
out.endl();
out.print("[] ");
out.print(timerName);
out.print(".timeout");
out.endl();
out.print("{");
out.endl();
out.print(" ");
out.block();
out.print("setverdict(fail);");
out.endl();
if (!endHook.equals(""))
{
out.print(endHook);
out.print(";");
out.endl();
}
out.print("stop;");
out.endl();
out.resume();
out.print("}");
out.endl();
out.resume();
out.print("}");
out.endl();
}
private void dumpType(final QMLRecordType type, Set<String> arrays)
{
out.print("type record ");
out.print(makeValidTTCN3Identifier(type.getTypeName()));
out.endl();
out.print("{");
out.endl();
out.print(" ");
out.block();
for (int i = 0; i < type.getNumberOfFields(); i++)
{
final QMLRecordTypeField field = type.getField(i);
TTCNTypeName v = new TTCNTypeName(arrays);
field.getType().accept(v);
assert (!v.getName().equals(""));
if (i != 0)
{
out.print(",");
out.endl();
}
out.print(v.getName());
out.print(" ");
out.print(makeValidTTCN3Identifier(field.getFieldName()));
if (v.getOptional())
{
out.print(" optional");
}
}
out.endl();
out.resume();
out.print("}");
out.endl();
for (int i = 0; i < type.getNumberOfInnerRecords(); i++)
{
dumpType(type.getInnerType(i), arrays);
}
}
private void dumpTypes() throws Exception
{
assert (metadata != null);
Set<String> arrays = new HashSet<String>();
List<String> ports = new Vector<String>();
PrettyPrinter oldOut = out;
String msg = null;
Exception rethrowMe = null;
if (dataTypesFile.equals(""))
{
msg = "TTCN-3 backend must be configured with a data type file "
+ "name.";
}
else
{
try
{
out = new PrettyPrinter(new BufferedWriter(new FileWriter(
dataTypesFile)));
} catch (IOException e)
{
StringBuffer msgbuf = new StringBuffer();
msgbuf.append("Failed to open file '");
msgbuf.append(dataTypesFile);
msgbuf.append("' for writing data types.");
msg = msgbuf.toString();
rethrowMe = e;
}
}
if (msg != null)
{
if (notifications != null)
{
notifications.notify("error", msg);
}
else
{
System.err.println(msg);
}
out = oldOut;
// Pass the exception forward for proper error handling further
// up the call stack.
throw (rethrowMe);
}
dumpHeader(dataTypesFile);
out.print("module ");
out.print(getModuleName(dataTypesFile));
out.endl();
out.print("{");
out.endl();
out.print(" ");
out.block();
out.print("/* User provided imports begin */");
out.endl();
out.print(extraImports);
out.endl();
out.print("/* User provided imports end */");
out.endl();
try
{
if (generateTestComponent)
{
final String portinfo = "portinfo:";
String key = metadata.getNextKey(portinfo);
while (key != null)
{
String data = key;
if (data.indexOf(portinfo) != 0)
{
break;
}
final QMLValue definition = metadata.get(key);
assert (definition != null);
QMLArray tuple = (QMLArray) definition;
if (tuple != null)
{
String origname;
boolean inbound = false;
int pos = data.indexOf(":inbound:");
inbound = (pos != -1);
origname = data.substring(portinfo.length()
+ (inbound ? "inbound:".length() : "outbound:"
.length()));
if (eptfEnable
&& (origname.equals(eptfInboundPort) || origname
.equals(eptfOutboundPort)))
{ // Port definitions is not generated for the MBT port
System.err.print("//Skipping port"
+ makeValidTTCN3Identifier(origname) + "\n");
}
else
{
String name = makeValidTTCN3Identifier(origname);
out.print("type port ");
out.print(name);
out.print("Port message");
ports.add(name);
out.endl();
out.print("{");
out.endl();
out.print(" ");
out.block();
TTCN3PortVisitor converter = new TTCN3PortVisitor(
out, !inbound);
tuple.accept(converter);
// if (converter.hasArrays()) {
// Iterator<String> it =
// converter.getArrays().keySet()
// .iterator();
// while (it.hasNext()) {
// arrays.add(it.next());
// }
// }
out.resume();
out.print("}");
if (!portExtensions.equals(""))
{
out.print(" with {extension \"");
out.print(portExtensions);
out.print("\"}");
}
out.endl();
}
}
key = metadata.getNextKey(key);
}
}
if (generateTypes)
{
Vector<QMLRecordType> types = metadata.getTypes();
Iterator<QMLRecordType> rit = types.iterator();
while (rit.hasNext())
{
QMLRecordType type = rit.next();
if (eptfEnable && type.getTypeName().length() >= 5
&& type.getTypeName().substring(0, 5).equals("EPTF_"))
{ // Do not generate type definitions for EPTF modules
}
else
{
dumpType(type, arrays);
}
}
Iterator<String> it = arrays.iterator();
while (it.hasNext())
{
String x = it.next();
// TODO: reconsider changing this because now both
// TTCNTypeName class and this piece of code here has
// to know how array names are constructed.
out.print("type record of ");
out.print(x);
out.print(" ");
out.print(x);
out.print("Array;");
out.endl();
}
}
if (generateTestComponent)
{
out.endl();
out.print("type component ");
out.print(runsOnName);
if (!extendsComponent.equals(""))
{
out.print(" extends ");
out.print(extendsComponent);
}
out.endl();
out.print("{");
out.endl();
out.print(" ");
out.block();
Iterator<String> it = ports.iterator();
while (it.hasNext())
{
String x = it.next();
out.print("port ");
out.print(x);
out.print("Port ");
out.print(x);
out.print(";");
out.endl();
}
out.print("timer ");
out.print(timerName);
out.print(" := 0.0;");
out.endl();
out.resume();
out.print("}");
out.endl();
}
/*
* altstep QtronicDefaultAlt() runs on SystemType { [] any
* port.receive { mytimer.stop; setverdict(fail); stop; } []
* mytimer.timeout { setverdict(fail); stop; } }
*/
// Must close the module block.
out.resume();
out.print("}");
out.endl();
out.flush();
out.close();
out = oldOut;
} catch (Exception e)
{
out.flush();
out.close();
out = oldOut;
}
}
private void dumpTemplates()
{
Iterator<TemplateDefinition> it = postponedTemplates.iterator();
while (it.hasNext())
{
final TemplateDefinition def = it.next();
out.print("template ");
out.print(makeValidTTCN3Identifier(def.getTypeName()));
out.print(" ");
out.print(def.getTemplateName());
out.ws();
out.print(":=");
out.endl();
QMLRecord r = def.getRecord();
TemplateDumper t = new TemplateDumper(out);
r.accept(t);
out.endl();
}
}
/**
* prints out the test harness template, that contains:
*
* - import from QtronicTypes;
*
* - type component for the harness system
*
* - MTC component the tests should run on
*
* - harness alt step
*
* - start and end test hook calls
*
* - port and message mapping functions
*/
private void dumpTestHarness() throws Exception
{
assert (metadata != null);
PrettyPrinter oldOut = out;
String msg = null;
Exception rethrowMe = null;
if (testHarnessFile.equals(""))
{
msg = "TTCN-3 backend must be configured with a test harness "
+ " file name.";
}
else
{
try
{
out = new PrettyPrinter(new BufferedWriter(new FileWriter(
testHarnessFile)));
} catch (IOException e)
{
StringBuffer msgbuf = new StringBuffer();
msgbuf.append("Failed to open file '");
msgbuf.append(testHarnessFile);
msgbuf.append("' for writing test harness template.");
msg = msgbuf.toString();
rethrowMe = e;
}
}
if (msg != null)
{
if (notifications != null)
{
notifications.notify("error", msg);
}
else
{
System.err.println(msg);
}
out = oldOut;
// Pass the exception forward for proper error handling further
// up the call stack.
throw (rethrowMe);
}
String indent = " ";
dumpHeader(testHarnessFile);
out.print("module ");
out.print(getModuleName(testHarnessFile));
out.endl();
out.print("{");
out.endl();
out.block();
// import all data types
out.print(indent);
out.print("import from ");
out.print(getModuleName(dataTypesFile));
out.print(" all;");
out.endl();
out.print("/* User provided imports begin */");
out.endl();
out.print(extraImports);
out.endl();
out.print("/* User provided imports end */");
out.endl();
out.block();
// system mapping
if (!systemTypeName.equals("") && !eptfEnable)
{
out.print(indent + "type component ");
out.print(systemTypeName);
out.endl();
out.print(indent + "{");
out.endl();
out.print(indent + indent
+ "// Add port definitions for your system here");
out.endl();
out.print(indent + "}");
out.endl();
out.block();
}
// MTC port definitions
if (!runsOnName.equals(""))
{
// The component type should be already generated in the type
// definitions module.
if (!eptfEnable)
{
out.print(indent);
out.print("type component ");
out.print(runsOnName);
out.endl();
out.print(indent + "{");
out.endl();
out.print(indent + indent
+ "// Add port definitions for your MTC here");
out.endl();
out.print(indent + indent + "timer ");
out.print(timerName);
out.print(" := 0.0;");
out.endl();
out.print(indent + "}");
out.endl();
}
// alt step
String moduleName = getModuleName(dataTypesFile);
if (!moduleName.equals(""))
{
out.print(indent);
out.print("altstep ");
out.print(defaultName);
out.print(" runs on ");
out.print(runsOnName);
out.endl();
out.block();
out.print(indent + "{");
out.endl();
out.print(indent);
out.block();
printAltStepBody(indent);
out.resume();
out.resume();
}
// default start and stop functions
if (!startHook.equals("") && !eptfEnable)
{
out.print(indent);
out.print("function ");
out.print(startHook);
out.print(" runs on ");
out.print(runsOnName);
out.endl();
out.print(indent + "{");
out.endl();
out.print(indent + indent
+ "// Do port mapping between MTC and abstract"
+ " test system interface");
out.endl();
out.print(indent + indent
+ "// map(mtc:<someport>, system:<someport>);");
out.endl();
out.print(indent + "}");
out.endl();
out.block();
}
if (!endHook.equals("") && !eptfEnable)
{
out.print(indent);
out.print("function ");
out.print(endHook);
out.print(" runs on ");
out.print(runsOnName);
out.endl();
out.print(indent + "{");
out.endl();
out.print(indent + indent + "// Do port unmapping e.g.");
out.endl();
out.print(indent + indent
+ "// map(mtc:<someport>, system:<someport>);");
out.endl();
out.print(indent + "}");
out.endl();
out.block();
}
// port mapping
try
{
final String portinfo = "portinfo:";
String key = metadata.getNextKey(portinfo);
while (key != null)
{
String data = key;
if (data.indexOf(portinfo) != 0)
{
break;
}
final QMLValue definition = metadata.get(key);
assert (definition != null);
boolean inbound = false;
int pos = data.indexOf(":inbound:");
inbound = (pos != -1);
String origname = data
.substring(portinfo.length()
+ (inbound ? "inbound:".length() : "outbound:"
.length()));
String port = makeValidTTCN3Identifier(origname);
QMLArray tuple = (QMLArray) definition;
if (tuple != null)
{
MyTTCN3PortVisitor converter = new MyTTCN3PortVisitor(
out, !inbound, port, indent);
tuple.accept(converter);
}
key = metadata.getNextKey(key);
}
} catch (Exception e)
{
out.print("/* EXCEPTION CAUGHT in dumpTestHarness */");
out.flush();
out.close();
out = oldOut;
throw e;
}
}
// Must close the module block.
out.resume();
out.print("}");
out.endl();
out.flush();
out.close();
out = oldOut;
}
private void dumpHeader(String filename)
{
assert (out != null);
out.print("/* -*- ttcn3 -*- */");
out.endl();
out.emptyline();
out.print("/** ");
out.print("@file ");
out.print(filename);
out.endl();
out.print(" *");
out.block();
out.endl();
out.print(" @author Conformiq TTCN3 Script Backend 0.1");
out.endl();
out.print(" @date ");
out.print(new Date().toString());
out.endl();
out.print("");
out.endl();
out.print(" WARNING! This file has been automatically generated using");
out.endl();
out
.print(" Ericsson Qtronic TTCN3 Script Backend (based on the original Conformiq Backend).");
out.print("DO NOT EDIT.");
out.endl();
out.resume();
out.print(" */");
out.endl();
out.emptyline();
}
private void deleteCopies()
{
generatedTemplates.clear();
}
@Override
public boolean beginCase(String testcaseName)
{
firstStep = true;
testCaseIndex++;
out.print("/* Generated test case #");
out.print(testCaseIndex);
out.print(" */");
out.endl();
out.print("testcase ");
out.print(recordAndRenderTestCaseName(testcaseName));
out.print("() runs on ");
out.print(runsOnName);
if (systemTypeName != null && !systemTypeName.equals(""))
{
out.print(" system ");
out.print(systemTypeName);
}
out.endl();
out.print("{");
out.endl();
out.print(" ");
out.block();
out.print("var float oldtimer := 0.0;");
out.endl();
out.print("var float SLACK := ");
out.print(commSlack);
out.print(";");
out.endl();
if (!defaultName.equals(""))
{
out.print("var default default_behaviour_ref;");
out.endl();
}
if (!startHook.equals(""))
{
out.print(startHook);
out.print(";");
out.endl();
}
if (!defaultName.equals(""))
{
out.print("default_behaviour_ref := activate(");
out.print(defaultName);
out.print(");");
out.endl();
}
return true;
}
// @Override
public void caseProbability(double probability)
{
}
final private String escapeString(String s)
{
// Checkpoint strings from the model must be escaped by "backslashing"
// double quotations and backslashes themselves.
StringBuffer escaped = new StringBuffer();
final int size = s.length();
if (size == 0)
return s;
for (int i = 0; i < size; i++)
{
if (s.charAt(i) == '\\')
{
escaped.append("\\\\");
}
else if (s.charAt(i) == '"')
{
escaped.append("\\\"");
}
else
{
escaped.append(s.charAt(i));
}
}
return escaped.toString();
}
@Override
public boolean checkpointInfo(Checkpoint checkpoint, int status,
TimeStamp ts)
{
if (isCompact || !firstStep || status == CheckpointStatus.UNCOVERED
|| status == CheckpointStatus.MAYBE_COVERED)
{
return true;
}
if (checkpoint.getType() == CheckpointType.USUAL_CHECKPOINT)
{
// TODO: we might want to have something configurable here or
// maybe something based on which CPs are selected as targets;
// now we just include methods, states and transtions into
// reports CPs to avoid "CP-bloat".
String cp = checkpoint.getName();
String method = new String("method:");
String transition = new String("transition:");
String state = new String("state:");
if ((cp.length() > method.length() && cp.substring(0,
method.length()).equals(method))
|| (cp.length() > transition.length() && cp.substring(0,
transition.length()).equals(transition))
|| (cp.length() > state.length() && cp.substring(0,
state.length()).equals(state)))
{
out.print(logCommand);
out.print("(\"Structural feature: ");
out.print(escapeString(cp));
out.print("\");");
out.endl();
}
}
else if (checkpoint.getType() == CheckpointType.REQUIREMENT)
{
out.print(logCommand);
out.print("(\"Requirement: ");
out.print(escapeString(checkpoint.getName()));
out.print("\");");
out.endl();
}
return true;
}
@Override
public boolean endCase()
{
out.print("setverdict(pass);");
out.endl();
/*
* Default is used only if the provided name is different from "", so if
* the default is not used let's not deactivate it either.
*/
if (!defaultName.equals(""))
{
out.print("deactivate(default_behaviour_ref);");
out.endl();
}
if (!endHook.equals(""))
{
out.print(endHook);
out.print(";");
out.endl();
}
out.resume();
out.print("}");
out.endl();
/* Generate templates out of the datums stored in the TestStep. */
dumpTemplates();
postponedTemplates.clear();
return true;
}
@Override
public boolean endScript()
{
out.print("control");
out.endl();
out.print("{");
out.endl();
out.print(" ");
out.block();
for (int i = 1; i <= testCaseIndex; i++)
{
out.print("execute(");
out.print(getTestCaseName(i));
out.print("());");
out.endl();
}
out.resume();
out.print("}");
out.endl();
out.resume();
out.print("}");
out.endl();
try
{
out.flush();
out.close();
} catch (Exception e)
{
if (notifications != null)
{
notifications.notify("error", SCRIPTER_NAME + ": "
+ e.toString());
}
}
out = null;
StringBuffer ss = new StringBuffer();
ss.append("Generated TTCN-3 script to <hyperlink=\"");
ss.append(filename);
ss.append("\">");
ss.append(filename);
ss.append("</hyperlink>.");
if (notifications != null)
{
notifications.notify("info", SCRIPTER_NAME + ": " + ss.toString());
}
else
{
System.out.println(ss.toString());
}
testCaseNames.clear();
return true;
}
@Override
public boolean testStep(QMLRecord r, String thread, String port,
boolean isFromTester, TimeStamp ts)
{
/*
* We have an altstep defined:
*
* altstep QtronicDefaultAlt() runs on SystemType { [] any port.receive
* { mytimer.stop; setverdict(fail); stop; } [] mytimer.timeout {
* setverdict(fail); stop; } }
*
* which removes the need for "[] any port.receive" and timeout from alt
* statements.
*/
// Remember the datum so that we can generate a template out of it.
// Generate a new template only if it has not been generated before.
String name;
Integer idx = generatedTemplates.get(r);
if (idx != null)
{
StringBuffer ss = new StringBuffer();
ss.append(r.getName());
ss.append("Template");
ss.append(idx);
name = ss.toString();
}
else
{
++templateIndex;
StringBuffer ss = new StringBuffer();
ss.append(r.getName());
ss.append("Template");
ss.append(templateIndex);
name = ss.toString();
postponedTemplates
.add(new TemplateDefinition(r.getName(), name, r));
generatedTemplates.put(r, templateIndex);
}
if (isFromTester)
{
/*
* Send datum to SUT. Generate:
*
* mytimer.start(<send time>); alt { [] mytimer.timeout { // OK } }
* <port>.send(template_<index>);
*/
if (ts.seconds > timeStamp.seconds
|| ts.nanoseconds > timeStamp.nanoseconds)
{
out.print(timerName);
out.print(".start(");
out.print(ts.seconds);
out.print(".");
out.print(ts.nanoseconds);
out.print(" - oldtimer);");
out.endl();
out.print("alt");
out.endl();
out.print("{");
out.endl();
out.print(" ");
out.block();
out.print("[] ");
out.print(timerName);
out.print(".timeout");
out.endl();
out.print("{");
out.endl();
out.print("}");
out.endl();
out.resume();
out.print("}");
out.endl();
;
out.print(timerName);
out.print(".stop;");
out.endl();
}
out.print("qtronic_send_");
out.print(r.getName());
out.print("_to_");
out.print(port);
out.print("(");
out.print(name);
out.print(");");
out.endl();
}
else
{
out.print(timerName);
out.print(".start((");
out.print(ts.seconds);
out.print(".");
out.print(ts.nanoseconds);
out.print(" - oldtimer) + SLACK);");
out.endl();
out.print("qtronic_receive_");
out.print(r.getName());
out.print("_from_");
out.print(port);
out.print("(");
out.print(name);
out.print(");");
out.endl();
out.print(timerName);
out.print(".stop;");
out.endl();
}
out.print("oldtimer := ");
out.print(ts.seconds);
out.print(".");
out.print(ts.nanoseconds);
out.print(";");
out.endl();
this.timeStamp.seconds = ts.seconds;
this.timeStamp.nanoseconds = ts.nanoseconds;
return true;
}
private String recordAndRenderTestCaseName(String testCaseName)
{
// Remove whitespace
StringBuffer rendered = new StringBuffer();
for (int i = 0; i < testCaseName.length(); i++)
{
// Inverted the logic here to be safer by replacing everything
// but valid identifier chars with an underscore. According to the
// spec: "TTCN-3 identifiers are case sensitive and may
// only contain lowercase letters (a-z) uppercase letters
// (A-Z) and numeric digits (0-9). Use of the underscore (
// _ ) symbol is also allowed. An identifier shall begin
// with a letter (i.e., not a number and not an
// underscore)."
char c = testCaseName.substring(i, i + 1).charAt(0);
// If the original test case name starts with a digit, we want
// to preserve that but need to prefix the test case name with
// an underscore.
if (c >= '0' && c <= '9' && i == 0)
rendered.append('_');
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
|| (c >= '0' && c <= '9') || c == '_')
rendered.append(c);
else
rendered.append('_');
}
// Put into test case name list
testCaseNames.add(rendered.toString());
return rendered.toString();
}
private String getTestCaseName(int i)
{
// Get test case name at index i of the test case list
return testCaseNames.get(i - 1);
}
@Override
public boolean internalCommunicationsInfo(QMLRecord datum, String sender,
String receiver, String port, TimeStamp time)
{
return true;
}
@Override
public boolean trace(String message, TimeStamp time)
{
return true;
}
private static boolean isalpha(char c)
{
return (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'));
}
private static boolean isalnum(char c)
{
return (isalpha(c) || ('0' <= c && c <= '9'));
}
public static String makeValidTTCN3Identifier(String id)
{
/* "Identifier ::= <alpha> (<alnum> | <underscore>)*" */
StringBuffer valid_ = new StringBuffer();
final int size = id.length();
if (size == 0)
{
System.err
.println("Warning: Empty string is not a valid TTCN3 identifier.");
return id;
}
if (isalpha(id.charAt(0)))
{
valid_.append(id.substring(0, 1));
}
else
{
valid_.append("X" + id.substring(0, 1));
}
for (int i = 1; i < size; i++)
{
if (!isalnum(id.charAt(i)) && id.charAt(i) != '_')
{
valid_.append("_");
}
else
{
valid_.append(id.charAt(i));
}
}
/* Postfix TTCN3 reserved words with '_' */
String valid = valid_.toString();
if (valid.equals("action") || valid.equals("fail")
|| valid.equals("named") || valid.equals("self")
|| valid.equals("activate") || valid.equals("false")
|| valid.equals("none") || valid.equals("send")
|| valid.equals("address") || valid.equals("float")
|| valid.equals("nonrecursive") || valid.equals("sender")
|| valid.equals("all") || valid.equals("for")
|| valid.equals("not") || valid.equals("set")
|| valid.equals("alt") || valid.equals("from")
|| valid.equals("not4b") || valid.equals("signature")
|| valid.equals("and") || valid.equals("function")
|| valid.equals("nowait") || valid.equals("start")
|| valid.equals("and4b") || valid.equals("null")
|| valid.equals("stop") || valid.equals("any")
|| valid.equals("get") || valid.equals("sut")
|| valid.equals("getcall") || valid.equals("objid")
|| valid.equals("system") || valid.equals("bitstring")
|| valid.equals("getreply") || valid.equals("octetstring")
|| valid.equals("boolean") || valid.equals("goto")
|| valid.equals("of") || valid.equals("template")
|| valid.equals("group") || valid.equals("omit")
|| valid.equals("testcase") || valid.equals("call")
|| valid.equals("on") || valid.equals("timeout")
|| valid.equals("catch") || valid.equals("hexstring")
|| valid.equals("optional") || valid.equals("timer")
|| valid.equals("char") || valid.equals("or") || valid.equals("to")
|| valid.equals("charstring") || valid.equals("if")
|| valid.equals("or4b") || valid.equals("trigger")
|| valid.equals("check") || valid.equals("ifpresent")
|| valid.equals("out") || valid.equals("true")
|| valid.equals("clear") || valid.equals("import")
|| valid.equals("override") || valid.equals("type")
|| valid.equals("complement") || valid.equals("in")
|| valid.equals("component") || valid.equals("inconc")
|| valid.equals("param") || valid.equals("union")
|| valid.equals("connect") || valid.equals("infinity")
|| valid.equals("pass") || valid.equals("universal")
|| valid.equals("const") || valid.equals("inout")
|| valid.equals("pattern") || valid.equals("unmap")
|| valid.equals("control") || valid.equals("integer")
|| valid.equals("port") || valid.equals("create")
|| valid.equals("interleave") || valid.equals("procedure")
|| valid.equals("value") || valid.equals("valueof")
|| valid.equals("deactivate") || valid.equals("label")
|| valid.equals("raise") || valid.equals("var")
|| valid.equals("disconnect") || valid.equals("language")
|| valid.equals("read") || valid.equals("verdict")
|| valid.equals("display") || valid.equals("length")
|| valid.equals("receive") || valid.equals("verdicttype")
|| valid.equals("do") || valid.equals("log")
|| valid.equals("record") || valid.equals("done")
|| valid.equals("rem") || valid.equals("while")
|| valid.equals("map") || valid.equals("repeat")
|| valid.equals("with") || valid.equals("else")
|| valid.equals("match") || valid.equals("reply")
|| valid.equals("encode") || valid.equals("message")
|| valid.equals("return") || valid.equals("xor")
|| valid.equals("enumerated") || valid.equals("mixed")
|| valid.equals("running") || valid.equals("xor4b")
|| valid.equals("error") || valid.equals("mod")
|| valid.equals("runs") || valid.equals("exception")
|| valid.equals("modifies") || valid.equals("execute")
|| valid.equals("module") || valid.equals("expand")
|| valid.equals("mtc") || valid.equals("extension")
|| valid.equals("external") ||
/* special identifiers reserved for the predefined functions */
valid.equals("int2char") || valid.equals("char2int")
|| valid.equals("int2unichar") || valid.equals("unichar2int")
|| valid.equals("bit2int") || valid.equals("hex2int")
|| valid.equals("int2bit") || valid.equals("int2hex")
|| valid.equals("int2oct") || valid.equals("int2str")
|| valid.equals("oct2int") || valid.equals("str2int")
|| valid.equals("lengthof") || valid.equals("sizeof")
|| valid.equals("ischosen") || valid.equals("ispresent"))
{
return valid + "_";
}
return valid;
}
}