/* File: AutoTester.java * Contains: A class for simulating Kqml input * Author: Jeff Dalton * Created: April 1998 * Updated: Sun Jul 8 19:25:48 2001 by Jeff Dalton * Copyright: (c) 1998, 2000, 2001, AIAI, University of Edinburgh */ package ix.ileed; import java.util.*; import java.io.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JMenu; import javax.swing.JMenuItem; import ix.util.*; import ix.util.lisp.*; /** * A class that can be used to replay recorded messages for testing * and demonstration. */ public class AutoTester { protected Vector allTestRunners = new Vector(); public static int defaultDelay = 5000; // milliseconds between messages public int initialDelay = 10*1000; // extra delay before 1st message public int maxDelay = 10000; // milliseconds between messages public int minDelay = 0; // milliseconds between messages public boolean askUserBeforeSend = false; BasicIleed ileed; public AutoTester(BasicIleed ileed) { this.ileed = ileed; } public void processParameters() { defaultDelay = Parameters.getInt("autotester-delay", defaultDelay); askUserBeforeSend = Parameters.getBoolean ("autotester-ask-user-before-send"); } public class TestRunner extends Thread { public LList testMessages; public TestRunner(LList testMessages) { this.testMessages = testMessages; AutoTester.this.allTestRunners.addElement(this); } public void run() { // Here's where we send the messages that constitute a test. Debug.noteln("AutoTester will send", testMessages); Debug.noteln("First sleeping for " + initialDelay/1000 + " seconds"); try { Thread.sleep(initialDelay); } catch (InterruptedException e) {} long time = 0; for (LList ms = testMessages; ms != Lisp.NIL; ms = ms.cdr()) { String event = (String)ms.car(); Debug.noteln("\n---------- AutoTester sending", event); if (askUserBeforeSend) { Util.askLine("--- press return to send"); } else { long delay = defaultDelay; if (delay > maxDelay) { Debug.noteln("Delay would be " + delay / 1000.0 + " seconds."); Debug.noteln("Using " + maxDelay / 1000.0 + " seconds instead."); delay = maxDelay; } if (delay > 0) { try { Thread.sleep(delay); } catch (InterruptedException e) {} } } // Here's where we actually "send" it. AutoTester.this.ileed.interpreter.interpret(event); } // The test is complete from our POV. Debug.noteln("\n\n- - - - - - - - - - - - - - - - - - - -\n"); Debug.noteln("Autotester sent", testMessages); Debug.noteln(""); } } // end of class TestRunner /* * Reset */ public void stopAll() { for (Enumeration e = allTestRunners.elements(); e.hasMoreElements();) { TestRunner t = (TestRunner)e.nextElement(); if (t.isAlive()) t.stop(); } } /* * Test table, etc. */ protected List testNames = new LinkedList(); protected Hashtable testTable = new Hashtable() { public Object put(Object key, Object value) { testNames.add(key); // to remember the order in which added return super.put(key, value); } }; { testTable.put("Default messages", ""); testTable.put("Generate messages", ""); testTable.put("Generate message permutation", ""); // testTable.put("Ask for a file name",""); } public void addTestMenuItems(JMenu testMenu) { JMenu menu = new JMenu("Auto-test"); testMenu.add(menu); for (Iterator i = testNames.iterator(); i.hasNext();) { String name = (String)i.next(); JMenuItem item = new JMenuItem(name); item.addActionListener(testMenuActionListener); menu.add(item); } } ActionListener testMenuActionListener = new ActionListener() { public void actionPerformed(ActionEvent e) { String command = e.getActionCommand(); Debug.noteln("AutoTester action:", command); runTest(command); } }; public void runTest(final String testName) { // New thread because we're called from a MainFrame menu-slection // and hence in an AWT thread. // /\/: Not clear this is needed, since we start another // new thread anyway for each test run. new Thread() { public void run() { Debug.noteln("Test requested", testName); String filename = (String)testTable.get(testName); Debug.assert(filename != null); LList messages = Lisp.NIL; if (testName == "Default messages") { messages = Seq.toLList(Seq.elements(defaultMessageSequence)); } else if (testName == "Generate messages") { permuteSuccessors = false; messages = generateTestMessageList(); } else if (testName == "Generate message permutation") { permuteSuccessors = true; messages = generateTestMessageList(); } else { Debug.warn("Unknown test: " + testName); } // Send some messages. new TestRunner(messages).start(); } }.start(); } /* * Order of messages: * * Start --> S1M1a --> S1M2a --> S1M3a --> S1M4a * | | | | * V V V V * S1M1b --> S1M2b --> S1M3b S1M4b * | ^ * V | * I1a ----> I1b * * John Levine, 9th June 2000. */ static String[][] messageOrderSpecs = new String[][] { {"Start", "S1M1a"}, {"S1M1a", "S1M1b", "S1M2a"}, {"S1M1b", "S1M2b"}, {"S1M2a", "S1M2b", "S1M3a"}, {"S1M2b", "S1M3b"}, {"S1M3a", "S1M3b", "S1M4a"}, {"S1M3b", "I1a"}, {"S1M4a", "S1M4b"}, {"I1a", "I1b"}, {"I1b", "S1M4b"}, // Not sure the rest is right. {"S1M4b", "S2M1a"}, {"S2M1a", "S2M1b", "S2M2a"}, {"S2M1b", "S2M2b"}, {"S2M2a", "S2M2b"}, {"S2M2b"}}; static String[] defaultMessageSequence = new String[] { "S1M1a", "S1M1b", "S1M2a", "S1M2b", "S1M3a", "S1M3b", "I1a", "I1b", "S1M4a", "S1M4b", "S2M1a", "S2M1b", "S2M2a", "S2M2b" }; static Hashtable messageSuccessors = new Hashtable(); public static LList defaultMessageList = Seq.toLList(Seq.elements(defaultMessageSequence)); // /\/: Having this global variable is a terrible way to do it. static boolean permuteSuccessors = false; static { for (int i = 0; i < messageOrderSpecs.length; i++) { LList spec = Seq.toLList(Seq.elements(messageOrderSpecs[i])); String from = (String)spec.car(); LList to = spec.cdr(); messageSuccessors.put(from, to); } } static LList generateTestMessageList() { LListCollector result = new LListCollector(); walkSampleTestMessages("Start", result.elementPusher()); LList messages = result.contents(); Debug.assert(((String)messages.car()).equals("Start")); return messages.cdr(); // minus "Start" } static void walkSampleTestMessages(String at, Function1 f) { // Think "topological sort". Hashtable marks = new Hashtable(); messageWalk(at, f, marks); } static void messageWalk(String at, Function1 f, Hashtable marks) { if (marks.get(at) == null) { marks.put(at, "start"); LList next = (LList)messageSuccessors.get(at); // Can put them in some other order at this point. if (permuteSuccessors) next = next.permute(); for (; next != Lisp.NIL; next = next.cdr()) { messageWalk((String)next.car(), f, marks); } f.funcall(at); // Could change mark to "finish" for debugging } } }