osd-contiki/tools/cooja/java/se/sics/cooja/plugins/ScriptRunner.java
fros4943 e3d3634249 merged script runner plugins:
* in GUI-mode the script is editable
* without GUI, the script loads the simulation and activates the test script
2008-12-17 13:12:07 +00:00

685 lines
23 KiB
Java

/*
* Copyright (c) 2008, Swedish Institute of Computer Science.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the Institute nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: ScriptRunner.java,v 1.9 2008/12/17 13:12:07 fros4943 Exp $
*/
package se.sics.cooja.plugins;
import java.awt.BorderLayout;
import java.awt.Insets;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.*;
import javax.swing.event.InternalFrameEvent;
import javax.swing.event.InternalFrameListener;
import org.apache.log4j.Logger;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import se.sics.cooja.*;
import se.sics.cooja.GUI.SimulationCreationException;
import se.sics.cooja.dialogs.MessageList;
@ClassDescription("Contiki Test Editor")
@PluginType(PluginType.COOJA_PLUGIN)
public class ScriptRunner implements Plugin {
private static final long serialVersionUID = 1L;
private static Logger logger = Logger.getLogger(ScriptRunner.class);
private static final int DEFAULT_TIMEOUT = 1200000; /* 1200s = 20 minutes */
private static final String EXAMPLE_SCRIPT =
"/* Script is run for each mote log output, for example printf()'s */\n" +
"/* Input variables: Mote mote, int id, String msg, Hashtable global */\n" +
"\n" +
"log.log('MOTE=' + mote + '\\n');\n" +
"log.log('ID=' + id + '\\n');\n" +
"log.log('TIME=' + mote.getSimulation().getSimulationTime() + '\\n');\n" +
"log.log('MSG=' + msg + '\\n');\n" +
"\n" +
"/* Hashtable global may be used to store state across script invokes */\n" +
"log.log('LAST MSG=' + global.get('lastMsg') + '\\n');\n" +
"global.put('lastMsg', msg);\n" +
"\n" +
"/* Contiki test script example */\n" +
"if (msg.startsWith('Hello, world')) {\n" +
" log.testOK(); /* Report test success and quit */\n" +
"} else {\n" +
" log.testFailed(); /* Report test failed and quit */\n" +
"}\n" +
"\n" +
"//mote.getSimulation().getGUI().reloadCurrentSimulation(true); /* Reload simulation */\n";
private GUI gui;
private Object coojaTag = null; /* Used by Cooja for book-keeping */
private LogScriptEngine engine = null;
private BufferedWriter logWriter = null;
/* GUI components */
private JInternalFrame pluginGUI = null;
private JTextArea scriptTextArea = null;
private JTextArea logTextArea = null;
private JButton toggleButton = null;
private String oldTestName = null;
private String oldInfo = null;
public ScriptRunner(GUI gui) {
this.gui = gui;
if (!GUI.isVisualized()) {
/* Wait for activateTest(...) */
return;
}
/* GUI components */
pluginGUI = new JInternalFrame(
"Contiki Test Editor",
true, true, true, true);
pluginGUI.addInternalFrameListener(new InternalFrameListener() {
public void internalFrameClosing(InternalFrameEvent e) {
ScriptRunner.this.gui.removePlugin(ScriptRunner.this, true);
}
public void internalFrameClosed(InternalFrameEvent e) { }
public void internalFrameOpened(InternalFrameEvent e) { }
public void internalFrameIconified(InternalFrameEvent e) { }
public void internalFrameDeiconified(InternalFrameEvent e) { }
public void internalFrameActivated(InternalFrameEvent e) { }
public void internalFrameDeactivated(InternalFrameEvent e) { }
}
);
scriptTextArea = new JTextArea(8,50);
scriptTextArea.setMargin(new Insets(5,5,5,5));
scriptTextArea.setEditable(true);
scriptTextArea.setCursor(null);
scriptTextArea.setText(EXAMPLE_SCRIPT);
logTextArea = new JTextArea(8,50);
logTextArea.setMargin(new Insets(5,5,5,5));
logTextArea.setEditable(true);
logTextArea.setCursor(null);
toggleButton = new JButton("Activate");
toggleButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ev) {
if (toggleButton.getText().equals("Activate")) {
engine = new LogScriptEngine(ScriptRunner.this.gui, scriptTextArea.getText());
engine.setScriptLogObserver(new Observer() {
public void update(Observable obs, Object obj) {
logTextArea.append((String) obj);
logTextArea.setCaretPosition(logTextArea.getText().length());
}
});
engine.activateScript();
toggleButton.setText("Deactivate");
scriptTextArea.setEnabled(false);
} else {
if (engine != null) {
engine.deactiveScript();
engine = null;
}
toggleButton.setText("Activate");
scriptTextArea.setEnabled(true);
}
}
});
JButton importButton = new JButton("Import Contiki test");
importButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ev) {
Runnable doImport = new Runnable() {
public void run() {
importContikiTest();
}
};
new Thread(doImport).start();
}
});
JButton exportButton = new JButton("Export as Contiki test");
exportButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ev) {
exportAsContikiTest();
}
});
JSplitPane centerPanel = new JSplitPane(
JSplitPane.VERTICAL_SPLIT,
new JScrollPane(scriptTextArea),
new JScrollPane(logTextArea)
);
centerPanel.setOneTouchExpandable(true);
centerPanel.setResizeWeight(0.5);
JPanel buttonPanel = new JPanel(new BorderLayout());
buttonPanel.add(BorderLayout.WEST, importButton);
buttonPanel.add(BorderLayout.CENTER, toggleButton);
buttonPanel.add(BorderLayout.EAST, exportButton);
JPanel southPanel = new JPanel(new BorderLayout());
southPanel.add(BorderLayout.EAST, buttonPanel);
pluginGUI.getContentPane().add(BorderLayout.CENTER, centerPanel);
pluginGUI.getContentPane().add(BorderLayout.SOUTH, southPanel);
pluginGUI.pack();
}
public JInternalFrame getGUI() {
return pluginGUI;
}
private void importContikiTest() {
new Thread(new Runnable() {
public void run() {
/* Load config from test directory */
final File proposedDir = new File(GUI.getExternalToolsSetting("PATH_CONTIKI") + "/tools/cooja/contiki_tests");
if (!proposedDir.exists()) {
logger.fatal("Test directory does not exist: " + proposedDir.getPath());
return;
}
scriptTextArea.setText("");
logTextArea.setText("");
gui.doLoadConfig(false, true, proposedDir);
Vector<File> history = gui.getFileHistory();
File cscFile = history.firstElement();
String testName = cscFile.getName().substring(0, cscFile.getName().length()-4);
File testDir = cscFile.getParentFile();
File jsFile = new File(testDir, testName + ".js");
File infoFile = new File(testDir, testName + ".info");
oldTestName = testName;
if (!cscFile.exists()) {
logger.fatal("Can't locate config file: " + cscFile.getAbsolutePath());
return;
}
if (!jsFile.exists()) {
logger.fatal("Can't locate .js file: " + jsFile.getAbsolutePath());
return;
}
/* Import .js */
try {
BufferedReader reader =
new BufferedReader(new InputStreamReader(new FileInputStream(jsFile)));
String line;
while ((line = reader.readLine()) != null) {
scriptTextArea.append(line + "\n");
}
reader.close();
} catch (Exception ex) {
ex.printStackTrace();
return;
}
/* Import .info */
if (infoFile.exists()) {
try {
oldInfo = "";
BufferedReader reader =
new BufferedReader(new InputStreamReader(new FileInputStream(infoFile)));
String line;
while ((line = reader.readLine()) != null) {
oldInfo += line + "\n";
}
reader.close();
} catch (Exception ex) {
ex.printStackTrace();
return;
}
}
}
}).start();
}
private void exportAsContikiTest() {
Simulation simulation = ScriptRunner.this.gui.getSimulation();
if (simulation == null) {
JOptionPane.showMessageDialog(GUI.getTopParentContainer(),
"Create a simulation setup to test.", "No simulation to export", JOptionPane.ERROR_MESSAGE);
return;
}
/* Confirm test directory */
File testDir = new File(GUI.getExternalToolsSetting("PATH_CONTIKI") + "/tools/cooja/contiki_tests");
String s1 = "Ok";
String s2 = "Cancel";
Object[] options = { s1, s2 };
int n = JOptionPane.showOptionDialog(GUI.getTopParentContainer(),
"The current simulation config (.csc) and test script (.js)\n" +
"will be stored in directory '" + testDir.getPath() + "'",
"Export Contiki test", JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE, null, options, s1);
if (n != JOptionPane.YES_OPTION) {
return;
}
if (!testDir.exists()) {
logger.fatal("Test directory does not exist: " + testDir.getPath());
return;
}
/* Name test to export */
if (oldTestName == null) {
oldTestName = "mytest";
}
String testName = (String) JOptionPane.showInputDialog(GUI.getTopParentContainer(),
"The test name correponds to the exported test files:\n" +
"[testname].csc, [testname].js, [testname].info\n",
"Enter test name", JOptionPane.PLAIN_MESSAGE, null, null,
oldTestName);
if (testName == null) {
return;
}
oldTestName = testName;
if (testName.equals("") || testName.contains(" ")) {
JOptionPane.showMessageDialog(GUI.getTopParentContainer(),
"Bad test name: '" + testName + "'", "Bad test name", JOptionPane.ERROR_MESSAGE);
return;
}
File cscFile = new File(testDir, testName + ".csc");
File jsFile = new File(testDir, testName + ".js");
File infoFile = new File(testDir, testName + ".info");
final File logFile = new File(testDir, testName + ".log");
/* Overwrite existing test */
if (cscFile.exists() || jsFile.exists() || infoFile.exists()) {
s1 = "Overwrite";
s2 = "Cancel";
n = JOptionPane.showOptionDialog(GUI.getTopParentContainer(),
"Some output files already exist. Overwrite?",
"Test already exist", JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE, null, options, s1);
if (n != JOptionPane.YES_OPTION) {
return;
}
}
if (cscFile.exists()) {
cscFile.delete();
}
if (jsFile.exists()) {
jsFile.delete();
}
if (infoFile.exists()) {
infoFile.delete();
}
/* Get current simulation configuration */
Element root = new Element("simconf");
Element simulationElement = new Element("simulation");
simulationElement.addContent(simulation.getConfigXML());
root.addContent(simulationElement);
/* Strip plugins */
Collection<Element> pluginsConfig = ScriptRunner.this.gui.getPluginsConfigXML();
if (pluginsConfig != null) {
root.addContent(pluginsConfig);
}
// if (pluginsConfig != null) {
// JOptionPane.showMessageDialog(GUI.getTopParentContainer(),
// "Stripping plugin configuration.\n" +
// "(Exporting non-GUI plugins not implemented.)",
// "Plugins detected", JOptionPane.WARNING_MESSAGE);
// }
/* Fix simulation delay */
root.detach();
String configString = new XMLOutputter().outputString(new Document(root));
String identifierExtraction = "<delaytime>([^<]*)</delaytime>";
Matcher matcher = Pattern.compile(identifierExtraction).matcher(configString);
while (matcher.find()) {
int delay = Integer.parseInt(matcher.group(1));
if (delay != 0) {
JOptionPane.showMessageDialog(GUI.getTopParentContainer(),
"Simulation delay currently set to " + delay + ".\n" +
"Changing delay time to 0 in exported test.",
"Non-zero delay time detected", JOptionPane.WARNING_MESSAGE);
}
configString = configString.replace(
"<delaytime>" + matcher.group(1) + "</delaytime>",
"<delaytime>0</delaytime>");
}
/* Export .csc */
try {
Element newRoot = new SAXBuilder().build(new StringReader(configString)).getRootElement();
newRoot.detach();
Document doc = new Document(newRoot);
FileOutputStream out = new FileOutputStream(cscFile);
XMLOutputter outputter = new XMLOutputter();
outputter.setFormat(Format.getPrettyFormat());
outputter.output(doc, out);
out.close();
} catch (JDOMException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
/* Export .js */
try {
BufferedWriter writer =
new BufferedWriter(new OutputStreamWriter(new FileOutputStream(jsFile)));
writer.write(scriptTextArea.getText());
writer.close();
} catch (Exception ex) {
ex.printStackTrace();
return;
}
/* Export .info (optional) */
try {
if (oldInfo == null) {
oldInfo = "";
}
String info = (String) JOptionPane.showInputDialog(GUI.getTopParentContainer(),
"This text describes the Contiki test and may contain\n" +
"information about the simulation setup, radio medium,\n" +
"node types, Contiki processes etc.\n\n",
"Enter test description", JOptionPane.PLAIN_MESSAGE, null, null,
oldInfo);
if (info != null && !info.equals("")) {
oldInfo = info;
BufferedWriter writer =
new BufferedWriter(new OutputStreamWriter(new FileOutputStream(infoFile)));
writer.write(info);
writer.write("\n");
writer.close();
} else {
oldInfo = null;
}
} catch (Exception ex) {
ex.printStackTrace();
return;
}
/* Run exported test (optional) */
s1 = "Run test";
s2 = "No";
n = JOptionPane.showOptionDialog(GUI.getTopParentContainer(),
"Run exported test in forked Cooja now?",
"Run test?", JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE, null, options, s1);
if (n != JOptionPane.YES_OPTION) {
return;
}
try {
final Process externalCoojaProcess;
final MessageList testOutput = new MessageList();
JPanel progressPanel = new JPanel(new BorderLayout());
final JDialog progressDialog = new JDialog((Window)GUI.getTopParentContainer(), (String) null);
progressDialog.setTitle("Running test...");
String command[] = {
"java",
"-jar",
"../dist/cooja.jar",
"-nogui",
"-test=" + testName
};
externalCoojaProcess = Runtime.getRuntime().exec(command, null, testDir);
final BufferedReader input = new BufferedReader(new InputStreamReader(externalCoojaProcess.getInputStream()));
final BufferedReader err = new BufferedReader(new InputStreamReader(externalCoojaProcess.getErrorStream()));
final JButton button = new JButton("Abort test");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
externalCoojaProcess.destroy();
if (progressDialog.isDisplayable()) {
progressDialog.dispose();
}
}
});
progressPanel.add(BorderLayout.CENTER, new JScrollPane(testOutput));
progressPanel.add(BorderLayout.SOUTH, button);
progressPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
progressPanel.setVisible(true);
progressDialog.getContentPane().add(progressPanel);
progressDialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
progressDialog.getRootPane().setDefaultButton(button);
progressDialog.setSize(500, 300);
progressDialog.setLocationRelativeTo(ScriptRunner.this.pluginGUI);
progressDialog.setVisible(true);
Thread readInput = new Thread(new Runnable() {
public void run() {
String readLine;
try {
while ((readLine = input.readLine()) != null) {
if (testOutput != null) {
testOutput.addMessage(readLine, MessageList.NORMAL);
}
}
} catch (IOException e) {
logger.warn("Error while reading from process");
}
testOutput.addMessage("", MessageList.NORMAL);
testOutput.addMessage("", MessageList.NORMAL);
testOutput.addMessage("", MessageList.NORMAL);
/* Parse log file for success info */
try {
BufferedReader in = new BufferedReader(new InputStreamReader(
new FileInputStream(logFile)));
boolean testSucceeded = false;
while (in.ready()) {
String line = in.readLine();
if (line == null) {
line = "";
}
testOutput.addMessage("", MessageList.NORMAL);
if (line.contains("TEST OK")) {
testSucceeded = true;
break;
}
}
if (testSucceeded) {
progressDialog.setTitle("Test run completed. Test succeeded!");
button.setText("Test OK");
} else {
progressDialog.setTitle("Test run completed. Test failed!");
button.setText("Test failed");
}
} catch (FileNotFoundException e) {
logger.fatal("File not found: " + e);
progressDialog.setTitle("Test run completed. Test failed! (no logfile)");
button.setText("Test failed");
} catch (IOException e) {
logger.fatal("IO error: " + e);
progressDialog.setTitle("Test run completed. Test failed! (IO exception)");
button.setText("Test failed");
}
}
}, "read input stream thread");
Thread readError = new Thread(new Runnable() {
public void run() {
String readLine;
try {
while ((readLine = err.readLine()) != null) {
if (testOutput != null) {
testOutput.addMessage(readLine, MessageList.ERROR);
}
}
} catch (IOException e) {
logger.warn("Error while reading from process");
}
}
}, "read input stream thread");
readInput.start();
readError.start();
} catch (IOException e) {
e.printStackTrace();
}
}
public boolean activateTest(File config, File script, File log) {
try {
/* Load simulation */
final Simulation sim = gui.loadSimulationConfig(config, true);
if (sim == null) {
gui.doQuit(false);
return false;
}
gui.setSimulation(sim);
/* Load test script */
BufferedReader in = new BufferedReader(new FileReader(script));
String line, code = "";
while ((line = in.readLine()) != null) {
code += line + "\n";
}
in.close();
/* Prepare test log */
logWriter = new BufferedWriter(new FileWriter(log));
/* Create script engine */
engine = new LogScriptEngine(gui, code);
engine.activateScript();
engine.setScriptLogObserver(new Observer() {
public void update(Observable obs, Object obj) {
try {
if (logWriter != null) {
logWriter.write((String) obj);
logWriter.flush();
}
} catch (IOException e) {
logger.fatal("Error when writing to test log file: " + obj);
}
}
});
/* Create timeout event */
sim.scheduleEvent(new TimeEvent(0) {
public void execute(long t) {
try {
logWriter.write("TEST TIMEOUT");
logWriter.flush();
} catch (IOException e) {
}
gui.doQuit(false);
}
}, DEFAULT_TIMEOUT);
/* Start simulation and leave control to script */
sim.startSimulation();
} catch (IOException e) {
logger.fatal("Error when running script: " + e);
System.exit(1);
return false;
} catch (UnsatisfiedLinkError e) {
logger.fatal("Error when running script: " + e);
System.exit(1);
return false;
} catch (SimulationCreationException e) {
System.exit(1);
logger.fatal("Error when running script: " + e);
return false;
}
return true;
}
public void closePlugin() {
if (engine != null) {
engine.deactiveScript();
engine.setScriptLogObserver(null);
engine = null;
}
if (logWriter != null) {
try {
logWriter.close();
} catch (IOException e) {
}
logWriter = null;
}
}
public Collection<Element> getConfigXML() {
return null;
}
public boolean setConfigXML(Collection<Element> configXML, boolean visAvailable) {
return true;
}
public void tagWithObject(Object tag) {
this.coojaTag = tag;
}
public Object getTag() {
return coojaTag;
}
}