package edu.pdx.cs410J; import java.lang.reflect.Method; import java.lang.reflect.InvocationTargetException; import java.io.PrintStream; import java.io.ByteArrayOutputStream; import java.security.Permission; /** * The superclass of test classes that invoke a main method to test a Java program. * * @author David Whitlock * @since Summer 2010 */ public abstract class InvokeMainTestCase { /** * Invokes the <code>main</code> method of the given class with the given arguments and returns an object * that represents the result of invoking that method. * @param mainClass The class whose main method is invoked * @param args The arguments passed to the main method * @return The result of the method invocation */ protected MainMethodResult invokeMain( Class mainClass, String... args ) { return new MainMethodResult( mainClass, args ).invoke(); } /** * Invokes the <code>main</code> method of a class and captures information about the invocation such as the data * written to standard out and standard error and the exit code. */ protected class MainMethodResult { private final Class mainClass; private final String[] args; private Integer exitCode; private String out; private String err; MainMethodResult( Class mainClass, String[] args ) { this.mainClass = mainClass; this.args = args; } /** * Invokes the main method * @return This <code>MainMethodResult</code> */ public MainMethodResult invoke() { Method main; try { main = mainClass.getMethod("main", String[].class); } catch ( NoSuchMethodException e ) { throw new IllegalArgumentException( "Class " + mainClass.getName() + " does not have a main method" ); } try { invokeMain( main ); } catch ( IllegalAccessException e ) { throw new IllegalArgumentException( "Cannot invoke main method of " + mainClass.getName(), e); } catch ( InvocationTargetException e ) { throw new IllegalArgumentException( "Error while invoking main method of " + mainClass.getName(), e); } return this; } private void invokeMain( Method main ) throws IllegalAccessException, InvocationTargetException { SecurityManager oldSecurityManager = System.getSecurityManager(); PrintStream oldOut = System.out; PrintStream oldErr = System.err; try { MainMethodResult.ExitStatusSecurityManager essm = new MainMethodResult.ExitStatusSecurityManager( oldSecurityManager ); System.setSecurityManager( essm ); ByteArrayOutputStream newOut = new ByteArrayOutputStream(); ByteArrayOutputStream newErr = new ByteArrayOutputStream(); System.setOut( new PrintStream(newOut) ); System.setErr( new PrintStream(newErr) ); try { main.invoke( null, (Object) this.args ); } catch ( InvocationTargetException ex ) { if ( ex.getCause() instanceof ExitException ) { this.exitCode = ((ExitException) ex.getCause()).getExitCode(); } else { throw ex; } } this.out = newOut.toString(); this.err = newErr.toString(); } finally { System.setSecurityManager( oldSecurityManager ); System.setOut( oldOut ); System.setErr( oldErr ); } } /** * Returns the exit code of this program (the argument to {@link System#exit(int)} * @return <code>null</code> if {@link System#exit(int)} was not invoked */ public Integer getExitCode() { return this.exitCode; } /** * Returns the data written to standard out * @return the data written to standard out */ public String getOut() { return out; } /** * Returns the data written to standard err * @return the data written to standard err */ public String getErr() { return err; } /** * A {@link SecurityManager} that delegates security checks to another {@link SecurityManager}, but captures * the exit code called by {@link System#exit(int)} */ private class ExitStatusSecurityManager extends SecurityManager { private final SecurityManager delegate; public ExitStatusSecurityManager( SecurityManager manager ) { this.delegate = manager; } @Override public void checkPermission( Permission perm ) { if (this.delegate != null) { this.delegate.checkPermission( perm ); } } @Override public void checkPermission( Permission perm, Object context ) { if (this.delegate != null) { this.delegate.checkPermission( perm, context ); } } @Override public void checkExit( int status ) { if (this.delegate != null) { this.delegate.checkExit( status ); } throw new ExitException( status ); } } /** * An exception that is thrown when the the main method calls {@link System#exit(int)}. This lets us capture * the exit code without the VM actually exiting. */ private class ExitException extends SecurityException { private final int exitCode; public ExitException( int exitCode ) { this.exitCode = exitCode; } public int getExitCode() { return exitCode; } } } }