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}