    001    package junit.runner;
    002
    003    import java.io.BufferedReader;
    004    import java.io.File;
    005    import java.io.FileInputStream;
    006    import java.io.FileOutputStream;
    007    import java.io.IOException;
    008    import java.io.InputStream;
    009    import java.io.PrintWriter;
    010    import java.io.StringReader;
    011    import java.io.StringWriter;
    012    import java.lang.reflect.InvocationTargetException;
    013    import java.lang.reflect.Method;
    014    import java.lang.reflect.Modifier;
    015    import java.text.NumberFormat;
    016    import java.util.Properties;
    017
    018    import junit.framework.AssertionFailedError;
    019    import junit.framework.Test;
    020    import junit.framework.TestListener;
    021    import junit.framework.TestSuite;
    022
    023    import org.junit.internal.Throwables;
    024
    025    /**
    026     * Base class for all test runners.
    027     * This class was born live on stage in Sardinia during XP2000.
    028     */
    029    public abstract class BaseTestRunner implements TestListener {
    030        public static final String SUITE_METHODNAME = "suite";
    031
    032        private static Properties fPreferences;
    033        static int fgMaxMessageLength = 500;
    034        static boolean fgFilterStack = true;
    035        boolean fLoading = true;
    036
    037        /*
    038        * Implementation of TestListener
    039        */
    040        public synchronized void startTest(Test test) {
    041            testStarted(test.toString());
    042        }
    043
    044        protected static void setPreferences(Properties preferences) {
    045            fPreferences = preferences;
    046        }
    047
    048        protected static Properties getPreferences() {
    049            if (fPreferences == null) {
    050                fPreferences = new Properties();
    051                fPreferences.put("loading", "true");
    052                fPreferences.put("filterstack", "true");
    053                readPreferences();
    054            }
    055            return fPreferences;
    056        }
    057
    058        public static void savePreferences() throws IOException {
    059            FileOutputStream fos = new FileOutputStream(getPreferencesFile());
    060            try {
    061                getPreferences().store(fos, "");
    062            } finally {
    063                fos.close();
    064            }
    065        }
    066
    067        public static void setPreference(String key, String value) {
    068            getPreferences().put(key, value);
    069        }
    070
    071        public synchronized void endTest(Test test) {
    072            testEnded(test.toString());
    073        }
    074
    075        public synchronized void addError(final Test test, final Throwable e) {
    076            testFailed(TestRunListener.STATUS_ERROR, test, e);
    077        }
    078
    079        public synchronized void addFailure(final Test test, final AssertionFailedError e) {
    080            testFailed(TestRunListener.STATUS_FAILURE, test, e);
    081        }
    082
    083        // TestRunListener implementation
    084
    085        public abstract void testStarted(String testName);
    086
    087        public abstract void testEnded(String testName);
    088
    089        public abstract void testFailed(int status, Test test, Throwable e);
    090
    091        /**
    092         * Returns the Test corresponding to the given suite. This is
    093         * a template method, subclasses override runFailed(), clearStatus().
    094         */
    095        public Test getTest(String suiteClassName) {
    096            if (suiteClassName.length() <= 0) {
    097                clearStatus();
    098                return null;
    099            }
    100            Class<?> testClass = null;
    101            try {
    102                testClass = loadSuiteClass(suiteClassName);
    103            } catch (ClassNotFoundException e) {
    104                String clazz = e.getMessage();
    105                if (clazz == null) {
    106                    clazz = suiteClassName;
    107                }
    108                runFailed("Class not found \"" + clazz + "\"");
    109                return null;
    110            } catch (Exception e) {
    111                runFailed("Error: " + e.toString());
    112                return null;
    113            }
    114            Method suiteMethod = null;
    115            try {
    116                suiteMethod = testClass.getMethod(SUITE_METHODNAME);
    117            } catch (Exception e) {
    118                // try to extract a test suite automatically
    119                clearStatus();
    120                return new TestSuite(testClass);
    121            }
    122            if (!Modifier.isStatic(suiteMethod.getModifiers())) {
    123                runFailed("Suite() method must be static");
    124                return null;
    125            }
    126            Test test = null;
    127            try {
    128                test = (Test) suiteMethod.invoke(null); // static method
    129                if (test == null) {
    130                    return test;
    131                }
    132            } catch (InvocationTargetException e) {
    133                runFailed("Failed to invoke suite():" + e.getTargetException().toString());
    134                return null;
    135            } catch (IllegalAccessException e) {
    136                runFailed("Failed to invoke suite():" + e.toString());
    137                return null;
    138            }
    139
    140            clearStatus();
    141            return test;
    142        }
    143
    144        /**
    145         * Returns the formatted string of the elapsed time.
    146         */
    147        public String elapsedTimeAsString(long runTime) {
    148            return NumberFormat.getInstance().format((double) runTime / 1000);
    149        }
    150
    151        /**
    152         * Processes the command line arguments and
    153         * returns the name of the suite class to run or null
    154         */
    155        protected String processArguments(String[] args) {
    156            String suiteName = null;
    157            for (int i = 0; i < args.length; i++) {
    158                if (args[i].equals("-noloading")) {
    159                    setLoading(false);
    160                } else if (args[i].equals("-nofilterstack")) {
    161                    fgFilterStack = false;
    162                } else if (args[i].equals("-c")) {
    163                    if (args.length > i + 1) {
    164                        suiteName = extractClassName(args[i + 1]);
    165                    } else {
    166                        System.out.println("Missing Test class name");
    167                    }
    168                    i++;
    169                } else {
    170                    suiteName = args[i];
    171                }
    172            }
    173            return suiteName;
    174        }
    175
    176        /**
    177         * Sets the loading behaviour of the test runner
    178         */
    179        public void setLoading(boolean enable) {
    180            fLoading = enable;
    181        }
    182
    183        /**
    184         * Extract the class name from a String in VA/Java style
    185         */
    186        public String extractClassName(String className) {
    187            if (className.startsWith("Default package for")) {
    188                return className.substring(className.lastIndexOf(".") + 1);
    189            }
    190            return className;
    191        }
    192
    193        /**
    194         * Truncates a String to the maximum length.
    195         */
    196        public static String truncate(String s) {
    197            if (fgMaxMessageLength != -1 && s.length() > fgMaxMessageLength) {
    198                s = s.substring(0, fgMaxMessageLength) + "...";
    199            }
    200            return s;
    201        }
    202
    203        /**
    204         * Override to define how to handle a failed loading of
    205         * a test suite.
    206         */
    207        protected abstract void runFailed(String message);
    208
    209        /**
    210         * Returns the loaded Class for a suite name.
    211         */
    212        protected Class<?> loadSuiteClass(String suiteClassName) throws ClassNotFoundException {
    213            return Class.forName(suiteClassName);
    214        }
    215
    216        /**
    217         * Clears the status message.
    218         */
    219        protected void clearStatus() { // Belongs in the GUI TestRunner class
    220        }
    221
    222        protected boolean useReloadingTestSuiteLoader() {
    223            return getPreference("loading").equals("true") && fLoading;
    224        }
    225
    226        private static File getPreferencesFile() {
    227            String home = System.getProperty("user.home");
    228            return new File(home, "junit.properties");
    229        }
    230
    231        private static void readPreferences() {
    232            InputStream is = null;
    233            try {
    234                is = new FileInputStream(getPreferencesFile());
    235                setPreferences(new Properties(getPreferences()));
    236                getPreferences().load(is);
    237            } catch (IOException ignored) {
    238            } catch (SecurityException ignored) {
    239            } finally {
    240                try {
    241                    if (is != null) {
    242                        is.close();
    243                    }
    244                } catch (IOException e1) {
    245                }
    246            }
    247        }
    248
    249        public static String getPreference(String key) {
    250            return getPreferences().getProperty(key);
    251        }
    252
    253        public static int getPreference(String key, int dflt) {
    254            String value = getPreference(key);
    255            int intValue = dflt;
    256            if (value == null) {
    257                return intValue;
    258            }
    259            try {
    260                intValue = Integer.parseInt(value);
    261            } catch (NumberFormatException ne) {
    262            }
    263            return intValue;
    264        }
    265
    266        /**
    267         * Returns a filtered stack trace
    268         */
    269        public static String getFilteredTrace(Throwable e) {
    270            return BaseTestRunner.getFilteredTrace(Throwables.getStacktrace(e));
    271        }
    272
    273        /**
    274         * Filters stack frames from internal JUnit classes
    275         */
    276        public static String getFilteredTrace(String stack) {
    277            if (showStackRaw()) {
    278                return stack;
    279            }
    280
    281            StringWriter sw = new StringWriter();
    282            PrintWriter pw = new PrintWriter(sw);
    283            StringReader sr = new StringReader(stack);
    284            BufferedReader br = new BufferedReader(sr);
    285
    286            String line;
    287            try {
    288                while ((line = br.readLine()) != null) {
    289                    if (!filterLine(line)) {
    290                        pw.println(line);
    291                    }
    292                }
    293            } catch (Exception IOException) {
    294                return stack; // return the stack unfiltered
    295            }
    296            return sw.toString();
    297        }
    298
    299        protected static boolean showStackRaw() {
    300            return !getPreference("filterstack").equals("true") || fgFilterStack == false;
    301        }
    302
    303        static boolean filterLine(String line) {
    304            String[] patterns = new String[]{
    305                    "junit.framework.TestCase",
    306                    "junit.framework.TestResult",
    307                    "junit.framework.TestSuite",
    308                    "junit.framework.Assert.", // don't filter AssertionFailure
    309                    "junit.swingui.TestRunner",
    310                    "junit.awtui.TestRunner",
    311                    "junit.textui.TestRunner",
    312                    "java.lang.reflect.Method.invoke("
    313            };
    314            for (int i = 0; i < patterns.length; i++) {
    315                if (line.indexOf(patterns[i]) > 0) {
    316                    return true;
    317                }
    318            }
    319            return false;
    320        }
    321
    322        static {
    323            fgMaxMessageLength = getPreference("maxmessage", fgMaxMessageLength);
    324        }
    325
    326    }