package edu.pdx.cs410J.grader; import java.io.*; import java.lang.reflect.*; import java.net.*; import java.util.*; /** * This class contains a number of helper methods for testing projects * in CS410J. * * @author David Whitlock */ public class Tester { /** * The original <code>System.err</code> */ PrintStream consoleErr = System.err; /** * The original <code>System.out</code> */ PrintStream consoleOut = System.out; /** * The number of character columns in the log file. */ private static final int WIDTH = 72; /** * Write output of tests and other stuff to here. */ private PrintWriter log; /** * The student's id for whom we are loggin. */ private String id; /** * Where to look for students' classes */ private URL[] urls; /** * Are we finished? */ private boolean done = false; private PrintStream ps; /** * Creates a new <code>Tester</code> that sends its output to a * given <code>PrintWriter</code>. This is useful when you want to * dump to the console. The "student's" id is * <code>"writer"</code>. */ public Tester(PrintWriter pw) { this.id = "writer"; this.log = pw; } /** * Creates a new <code>Tester</code> for a given student. It will * create a log file in given directory. * * @param id * The student's login id * @param logDir * Directory into which the log file is placed. */ public Tester(final String id, File logDir) { this.id = id; if (!logDir.isDirectory()) { throw new IllegalArgumentException(logDir + " is not a directory"); } if (!logDir.exists()) { logDir.mkdirs(); } String logFileName = id + ".log"; File logFile = new File(logDir, logFileName); // Create a PrintStream around the log file. Set System.out and // System.err to refer to this guy. FileOutputStream fos = null; try { fos = new FileOutputStream(logFile); } catch (IOException ex) { throw new IllegalArgumentException("Could not open " + logFileName); } /** * Inner class used to prevent file stream from being closed until * I say so. Other code may attempt to close System.out which * result in this file stream being closed. */ class CheckedPrintStream extends PrintStream { public CheckedPrintStream(OutputStream stream) { super(stream); } public void close() { if(Tester.this.done) { // Tester.this.consoleOut.println("Tester " + id + // " is closing stream"); super.close(); } // Tester.this.consoleOut.println("Tester " + id + // " is not done yet"); } } // this.ps = System.out; this.ps = new CheckedPrintStream(fos); System.setOut(ps); System.setErr(ps); // Create a PrintWriter around the PrintStream this.log = new PrintWriter(ps, true); } /** * Parses a file path and creates an array of <code>URL</code>s * representing each file on the path. */ @SuppressWarnings("deprecation") public static URL[] parseURLPath(String path) { if (path == null) { return new URL[0]; } StringTokenizer st = new StringTokenizer(path, File.pathSeparator); URL[] urls = new URL[st.countTokens()]; for (int i = 0; st.hasMoreTokens(); i++) { File file = new File(st.nextToken()); try { urls[i] = file.toURL(); } catch (MalformedURLException ex) { System.err.println("** Malformatted URL: " + file); } } return urls; } /** * Sets the URLs on which to search for students' classes */ public void setURLs(URL[] urls) { this.urls = urls; } /** * Executes a static method of a given name in a given class with a * given set of parameters. */ public Object executeStatic(String className, String methodName, Class[] paramTypes, Object[] params) { // By default, print a banner return executeStatic(className, methodName, paramTypes, params, true); } /** * Executes a static method of a given name in a given class with a * given set of parameters. * * @param printBanner * Do we print a banner message? */ public Object executeStatic(String className, String methodName, Class[] paramTypes, Object[] params, boolean printBanner) { ClassLoader loader = new URLClassLoader(this.urls); // First load the class and the desired method Class c = null; try { c = Class.forName(className, true, loader); } catch (ClassNotFoundException ex) { System.err.println("** Could not load " + className); ex.printStackTrace(System.err); return null; } Method m = null; try { m = c.getMethod(methodName, paramTypes); } catch (NoSuchMethodException ex) { System.err.println("** Could not find method " + methodName + " in " + className); ex.printStackTrace(System.err); return null; } if (printBanner) { // Print out some information this.printBanner("Executing " + className + "." + methodName + "()", '-'); } // Run the method Object result = null; try { try { result = m.invoke(null, params); } catch (InvocationTargetException ex) { throw ex.getTargetException(); } } catch (TesterExitException ex) { // That's okay, we're just exiting from one invocation of the // main method } catch (IllegalAccessException ex) { System.err.println("** IllegalAccessException while invoking " + className + "." + methodName + "()"); ex.printStackTrace(System.err); result = null; } catch (Exception ex) { // Log everything else System.err.println("** Exception while invoking " + className + "." + methodName + "()"); ex.printStackTrace(System.err); result = null; } catch (Throwable ex) { // EEEP!!! System.err.println("*** SEVERE ERROR!!!"); ex.printStackTrace(System.err); System.exit(1); } System.out.flush(); System.err.flush(); this.log.println(""); this.log.flush(); return result; } /** * Executes the <code>main</code> method of the class with the given * name in its own thread. * * @return The <code>Thread</code> in which the <code>main</code> * method is running. */ public Thread executeMainInThread(final String className, final String[] args) { this.printlnCentered("Executing " + className); Runnable r = new Runnable() { public void run() { try { Tester.this.executeMain(className, args); } catch (Throwable t) { return; } } }; Thread thread = new Thread(r); thread.start(); return thread; } /** * Waits for a given number of seconds. Returns <code>false</code> * if the thread was interrupted. */ public boolean wait(int seconds) { try { Thread.sleep(1000 * seconds); } catch (InterruptedException ex) { ex.printStackTrace(); return false; } return true; } /** * Executes the <code>main</code> method of the class with the given * name with the given arguments. */ public void executeMain(String className, String[] args) { // this.consoleOut.println("out: " + System.out); // this.consoleOut.println("err: " + System.err); // System.setOut(this.ps); // System.setErr(this.ps); // Create a new ClassLoader to load the student's classes. This // way the class is initialized each time this method is invoked // and we don't need to worry about the old values of static // fields. ClassLoader loader = new URLClassLoader(urls); // ClassLoader loader = sysLoader; // First load the class and get its main method Class c = null; try { c = Class.forName(className, true, loader); } catch (ClassNotFoundException ex) { System.err.println("** Could not load " + className); ex.printStackTrace(System.err); return; } Method main = null; try { Class stringArray = args.getClass(); Class[] paramTypes = {stringArray}; main = c.getMethod("main", paramTypes); } catch (NoSuchMethodException ex) { System.err.println("** Could not find main method in " + className); ex.printStackTrace(System.err); return; } // Process the arguments to replace any macros args = expandMacros(args); // Print out args StringBuffer sb = new StringBuffer(); for (int j = 0; j < args.length; j++) { boolean needQuotes = false; if (args[j].indexOf(" ") != -1) { needQuotes = true; } if (needQuotes) { sb.append("\""); } sb.append(args[j]); if (needQuotes) { sb.append("\""); } sb.append(" "); } this.log.println("Command line: " + sb.toString().trim()); this.log.println(""); this.log.println("Program output:"); // Invoke the main method with the given arguments. try { Object[] actuals = {args}; try { main.invoke(null, actuals); } catch (InvocationTargetException ex) { throw ex.getTargetException(); } System.exit(42); } catch (TesterExitException ex) { // That's okay... // this.consoleOut.println(thr); } catch (IllegalAccessException ex) { System.err.println("** IllegalAccessException while invoking " + "main"); ex.printStackTrace(System.err); return; } catch (Exception ex) { System.err.println("** Exception while invoking main"); ex.printStackTrace(System.err); return; } catch (Throwable ex) { // EEEP!!! System.err.println("*** SEVERE ERROR!!!"); ex.printStackTrace(System.err); System.exit(1); } // this.consoleOut.println("Finally: " + args); // System.out.println("Finally: " + args); System.out.flush(); System.err.flush(); this.log.println(""); } /** * Go through an array of <code>String</code>s and expand macros * such as "{STUDENT}". * * @return A clone of the <code>args</code> array with macros * expanded */ String[] expandMacros(String[] args) { Map<String, String> map = new HashMap<String, String>(); map.put("{STUDENT}", this.id); args = (String[]) args.clone(); for (int i = 0; i < args.length; i++) { Iterator keys = map.keySet().iterator(); while (keys.hasNext()) { String key = (String) keys.next(); String value = (String) map.get(key); int index = args[i].indexOf(key); for(int j = 0; j < 20 && index != -1; j++) { // Replace key with value StringBuffer sb = new StringBuffer(args[i]); sb.replace(index, index + key.length(), value); args[i] = sb.toString(); index = args[i].indexOf(key); } } } return args; } /** * Prints a left justified line of text to the log file. */ public void println(Object o) { this.log.println(o); } /** * Prints a left justified line of text to the log file. */ public void println(String text) { this.log.println(text); } /** * Prints a line of text to the log file and centers it. */ public void printlnCentered(String text) { printBanner(text, ' '); } /** * Prints a line of text to the log file, centers it, around * surrounds it with a given character. */ public void printBanner(String text, char c) { int length = text.length(); // Don't bother centering if the text is wider than the log. if (length < WIDTH) { int indent = (WIDTH - length) / 2; for (int i = 0; i < indent - 1; i++) { this.log.print(c); } this.log.print(' '); this.log.flush(); } this.log.print(text); if (length < WIDTH) { int indent = (WIDTH - length) / 2; this.log.print(' '); for (int i = 0; i < indent - 1; i++) { this.log.print(c); } this.log.flush(); } this.log.println(""); } /** * Copies one file to another. If the destination file exists, it * is overridden. */ public void cp(File srcFile, File destFile) { // Copy using streams in case we ever need to copy binary files. FileInputStream src = null; try { src = new FileInputStream(srcFile); } catch (FileNotFoundException ex) { String s = "Could not find file " + srcFile; throw new IllegalArgumentException(s); } try { FileOutputStream dest = new FileOutputStream(destFile); byte[] buffer = new byte[1024]; int count = src.read(buffer); while (count != -1) { dest.write(buffer, 0, count); count = src.read(buffer); } src.close(); dest.close(); } catch (IOException ex) { String s = "IOException " + ex; throw new IllegalArgumentException(s); } } /** * "Cats" a file to the log. That is, copies its contents to the * log. * * @param file * <code>File</code> to be catted */ public void cat(File file) { printlnCentered("File: " + file.getName()); this.log.println(""); if (!file.exists()) { printlnCentered("Does not exist!"); return; } try { FileReader fr = new FileReader(file); BufferedReader br = new BufferedReader(fr); while (br.ready()) { String line = br.readLine(); this.log.println(line); } } catch (FileNotFoundException ex) { String s = "Could not find file: " + file; throw new IllegalArgumentException(s); } catch (IOException ex) { throw new IllegalArgumentException(ex.toString()); } this.log.println("\n"); } /** * Signifies that this <code>Tester</code> is done */ void done() { this.done = true; } /** * Make sure we're done when this <code>Tester</code> gets garbage * collected. This way, the stream will always get closed. */ public void finalize() { this.done = true; } /** * Load a class from a given directory */ public static void main(String[] args) { String url = args[0]; String className = args[1]; // Install a TesterSecurityManager that will allow us to run a // main method multiple times without exiting. TesterSecurityManager tsm = new TesterSecurityManager(); System.setSecurityManager(tsm); Tester tester = new Tester(new PrintWriter(System.out, true)); tester.setURLs(Tester.parseURLPath(url)); String[] classArgs = { "Hello", "World" }; tester.executeMain(className, classArgs); } /** * Old Test Program. */ public static void main0(String[] args) { // Make a new Tester String userDir = System.getProperty("user.dir"); File cwd = new File(userDir); Tester tester = new Tester("test", cwd); System.out.println("Does System.out work?"); System.err.println("Does System.err work?"); tester.printBanner("Catting a file!", '*'); // Cat the file given in args[0] if (args.length > 0) { File file = new File(args[0]); tester.cat(file); } } }