001package edu.pdx.cs.joy; 002 003import java.io.ByteArrayOutputStream; 004import java.io.PrintStream; 005import java.lang.reflect.Field; 006import java.lang.reflect.InvocationTargetException; 007import java.lang.reflect.Method; 008import java.lang.reflect.Modifier; 009import java.util.ArrayList; 010import java.util.List; 011 012/** 013 * The superclass of test classes that invoke a main method to test a Java program. 014 * 015 * @author David Whitlock 016 * @since Summer 2010 017 */ 018public abstract class InvokeMainTestCase 019{ 020 /** 021 * Invokes the <code>main</code> method of the given class with the given arguments and returns an object 022 * that represents the result of invoking that method. 023 * @param mainClass The class whose main method is invoked 024 * @param args The arguments passed to the main method 025 * @return The result of the method invocation 026 */ 027 protected MainMethodResult invokeMain( Class<?> mainClass, String... args ) 028 { 029 return new MainMethodResult(mainClass, args).invoke(false); 030 } 031 032 /** 033 * Invokes the <code>main</code> method of the given class with the given arguments and returns an object 034 * that represents the result of invoking that method. 035 * 036 * This method will not throw a {@link MainClassContainsMutableStaticFields} exception 037 * if the <code>mainClass</code> has mutable <code>static</code> fields. 038 * 039 * @param mainClass The class whose main method is invoked 040 * @param args The arguments passed to the main method 041 * @return The result of the method invocation 042 */ 043 protected MainMethodResult invokeMainAllowingMutableStaticFields( Class<?> mainClass, String... args ) 044 { 045 return new MainMethodResult(mainClass, args).invoke(true); 046 } 047 048 /** 049 * Invokes the <code>main</code> method of a class and captures information about the invocation such as the data 050 * written to standard out and standard error and the exit code. 051 */ 052 protected static class MainMethodResult 053 { 054 private final Class<?> mainClass; 055 private final String[] args; 056 private String out; 057 private String err; 058 059 MainMethodResult( Class<?> mainClass, String[] args ) 060 { 061 this.mainClass = mainClass; 062 this.args = args; 063 } 064 065 /** 066 * Invokes the main method 067 * @return This <code>MainMethodResult</code> 068 */ 069 public MainMethodResult invoke(boolean allowMutableStaticFields) 070 { 071 Method main; 072 try 073 { 074 main = mainClass.getMethod("main", String[].class); 075 } 076 catch ( NoSuchMethodException e ) 077 { 078 throw new IllegalArgumentException( "Class " + mainClass.getName() + " does not have a main method" ); 079 } 080 081 if (!allowMutableStaticFields) { 082 checkForMutableStaticFields(); 083 } 084 085 try 086 { 087 invokeMain( main ); 088 } 089 catch ( IllegalAccessException e ) 090 { 091 throw new IllegalArgumentException( "Cannot invoke main method of " + mainClass.getName(), e); 092 } 093 catch ( InvocationTargetException e ) 094 { 095 throw new UncaughtExceptionInMain(e.getCause()); 096 } 097 return this; 098 } 099 100 private void checkForMutableStaticFields() { 101 List<String> mutableStaticFieldNames = new ArrayList<>(); 102 for (Field field : mainClass.getDeclaredFields()) { 103 int modifiers = field.getModifiers(); 104 if (Modifier.isStatic(modifiers) && !Modifier.isFinal(modifiers)) { 105 mutableStaticFieldNames.add(field.getName()); 106 } 107 } 108 109 if (!mutableStaticFieldNames.isEmpty()) { 110 throw new MainClassContainsMutableStaticFields(mainClass.getName(), mutableStaticFieldNames); 111 } 112 113 } 114 115 private void invokeMain( Method main ) 116 throws IllegalAccessException, InvocationTargetException 117 { 118 PrintStream oldOut = System.out; 119 PrintStream oldErr = System.err; 120 try { 121 ByteArrayOutputStream newOut = new ByteArrayOutputStream(); 122 ByteArrayOutputStream newErr = new ByteArrayOutputStream(); 123 System.setOut( new PrintStream(newOut) ); 124 System.setErr( new PrintStream(newErr) ); 125 126 main.invoke( null, (Object) copyOfArgs()); 127 128 this.out = newOut.toString(); 129 this.err = newErr.toString(); 130 131 } finally { 132 System.setOut( oldOut ); 133 System.setErr( oldErr ); 134 } 135 } 136 137 @SuppressWarnings("StringOperationCanBeSimplified") 138 private String[] copyOfArgs() { 139 String[] copy = new String[this.args.length]; 140 String[] strings = this.args; 141 for (int i = 0; i < strings.length; i++) { 142 String arg = strings[i]; 143 copy[i] = new String(arg); 144 } 145 return copy; 146 } 147 148 /** 149 * Returns the data written to standard out 150 * @return the data written to standard out 151 */ 152 public String getTextWrittenToStandardOut() 153 { 154 return out; 155 } 156 157 /** 158 * Returns the data written to standard err 159 * @return the data written to standard err 160 */ 161 public String getTextWrittenToStandardError() 162 { 163 return err; 164 } 165 166 } 167}