001/*
002 * Copyright (c) 2014, 2014, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.
008 *
009 * This code is distributed in the hope that it will be useful, but WITHOUT
010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
011 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
012 * version 2 for more details (a copy is included in the LICENSE file that
013 * accompanied this code).
014 *
015 * You should have received a copy of the GNU General Public License version
016 * 2 along with this work; if not, write to the Free Software Foundation,
017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
018 *
019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
020 * or visit www.oracle.com if you need additional information or have any
021 * questions.
022 */
023package com.oracle.mxtool.junit;
024
025import java.io.*;
026import java.lang.reflect.Modifier;
027import java.nio.file.*;
028import java.util.*;
029
030import junit.runner.*;
031
032import org.junit.internal.*;
033import org.junit.runner.*;
034import org.junit.runner.notification.*;
035import org.junit.runners.*;
036import org.junit.runners.model.*;
037
038public class MxJUnitWrapper {
039
040    /**
041     * Run the tests contained in the classes named in the <code>args</code>. A single test method
042     * can be specified by adding #method after the class name. Only a single test can be run in
043     * this way. If all tests run successfully, exit with a status of 0. Otherwise exit with a
044     * status of 1. Write feedback while tests are running and write stack traces for all failed
045     * tests after the tests all complete.
046     *
047     * @param args names of classes in which to find tests to run
048     */
049    public static void main(String... args) {
050        JUnitSystem system = new RealSystem();
051        JUnitCore junitCore = new JUnitCore();
052        system.out().println("MxJUnitCore");
053        system.out().println("JUnit version " + Version.id());
054        List<Class<?>> classes = new ArrayList<>();
055        String methodName = null;
056        List<Failure> missingClasses = new ArrayList<>();
057        boolean verbose = false;
058        boolean enableTiming = false;
059        boolean failFast = false;
060        boolean color = false;
061        boolean eagerStackTrace = false;
062        boolean gcAfterTest = false;
063
064        String[] expandedArgs = expandArgs(args);
065        for (int i = 0; i < expandedArgs.length; i++) {
066            String each = expandedArgs[i];
067            if (each.charAt(0) == '-') {
068                // command line arguments
069                if (each.contentEquals("-JUnitVerbose")) {
070                    verbose = true;
071                } else if (each.contentEquals("-JUnitFailFast")) {
072                    failFast = true;
073                } else if (each.contentEquals("-JUnitEnableTiming")) {
074                    enableTiming = true;
075                } else if (each.contentEquals("-JUnitColor")) {
076                    color = true;
077                } else if (each.contentEquals("-JUnitEagerStackTrace")) {
078                    eagerStackTrace = true;
079                } else if (each.contentEquals("-JUnitGCAfterTest")) {
080                    gcAfterTest = true;
081                } else {
082                    system.out().println("Unknown command line argument: " + each);
083                }
084
085            } else {
086                /*
087                 * Entries of the form class#method are handled specially. Only one can be specified
088                 * on the command line as there's no obvious way to build a runner for multiple
089                 * ones.
090                 */
091                if (methodName != null) {
092                    system.out().println("Only a single class and method can be specified: " + each);
093                    System.exit(1);
094                } else if (each.contains("#")) {
095                    String[] pair = each.split("#");
096                    if (pair.length != 2) {
097                        system.out().println("Malformed class and method request: " + each);
098                        System.exit(1);
099                    } else if (classes.size() != 0) {
100                        system.out().println("Only a single class and method can be specified: " + each);
101                        System.exit(1);
102                    } else {
103                        methodName = pair[1];
104                        each = pair[0];
105                    }
106                }
107                try {
108                    Class<?> cls = Class.forName(each, false, MxJUnitWrapper.class.getClassLoader());
109                    if ((cls.getModifiers() & Modifier.ABSTRACT) == 0) {
110                        classes.add(cls);
111                    }
112                } catch (ClassNotFoundException e) {
113                    system.out().println("Could not find class: " + each);
114                    Description description = Description.createSuiteDescription(each);
115                    Failure failure = new Failure(description, e);
116                    missingClasses.add(failure);
117                }
118            }
119        }
120        final TextRunListener textListener;
121        if (!verbose) {
122            textListener = new TextRunListener(system);
123        } else {
124            textListener = new VerboseTextListener(system);
125        }
126        MxRunListener mxListener = textListener;
127        if (enableTiming) {
128            mxListener = new TimingDecorator(mxListener);
129        }
130        if (color) {
131            mxListener = new AnsiTerminalDecorator(mxListener);
132        }
133        if (eagerStackTrace) {
134            mxListener = new EagerStackTraceDecorator(mxListener);
135        }
136        if (gcAfterTest) {
137            mxListener = new GCAfterTestDecorator(mxListener);
138        }
139        junitCore.addListener(TextRunListener.createRunListener(mxListener));
140        Request request;
141        if (methodName == null) {
142            request = Request.classes(classes.toArray(new Class[0]));
143            if (failFast) {
144                Runner runner = request.getRunner();
145                if (runner instanceof ParentRunner) {
146                    ParentRunner<?> parentRunner = (ParentRunner<?>) runner;
147                    parentRunner.setScheduler(new RunnerScheduler() {
148                        public void schedule(Runnable childStatement) {
149                            if (textListener.getLastFailure() == null) {
150                                childStatement.run();
151                            }
152                        }
153
154                        public void finished() {
155                        }
156                    });
157                } else {
158                    system.out().println("Unexpected Runner subclass " + runner.getClass().getName() + " - fail fast not supported");
159                }
160            }
161        } else {
162            if (failFast) {
163                system.out().println("Single method selected - fail fast not supported");
164            }
165            request = Request.method(classes.get(0), methodName);
166        }
167        Result result = junitCore.run(request);
168        for (Failure each : missingClasses) {
169            result.getFailures().add(each);
170        }
171        System.exit(result.wasSuccessful() ? 0 : 1);
172    }
173
174    /**
175     * Gets the command line for the current process.
176     *
177     * @return the command line arguments for the current process or {@code null} if they are not
178     *         available
179     */
180    public static List<String> getProcessCommandLine() {
181        String processArgsFile = System.getenv().get("MX_SUBPROCESS_COMMAND_FILE");
182        if (processArgsFile != null) {
183            try {
184                return Files.readAllLines(new File(processArgsFile).toPath());
185            } catch (IOException e) {
186            }
187        }
188        return null;
189    }
190
191    /**
192     * Expand any arguments starting with @ and return the resulting argument array.
193     *
194     * @param args
195     * @return the expanded argument array
196     */
197    private static String[] expandArgs(String[] args) {
198        List<String> result = null;
199        for (int i = 0; i < args.length; i++) {
200            String arg = args[i];
201            if (arg.length() > 0 && arg.charAt(0) == '@') {
202                if (result == null) {
203                    result = new ArrayList<>();
204                    for (int j = 0; j < i; j++) {
205                        result.add(args[j]);
206                    }
207                    expandArg(arg.substring(1), result);
208                }
209            } else if (result != null) {
210                result.add(arg);
211            }
212        }
213        return result != null ? result.toArray(new String[0]) : args;
214    }
215
216    /**
217     * Add each line from {@code filename} to the list {@code args}.
218     *
219     * @param filename
220     * @param args
221     */
222    private static void expandArg(String filename, List<String> args) {
223        BufferedReader br = null;
224        try {
225            br = new BufferedReader(new FileReader(filename));
226
227            String buf;
228            while ((buf = br.readLine()) != null) {
229                args.add(buf);
230            }
231            br.close();
232        } catch (IOException ioe) {
233            ioe.printStackTrace();
234            System.exit(2);
235        } finally {
236            try {
237                if (br != null) {
238                    br.close();
239                }
240            } catch (IOException ioe) {
241                ioe.printStackTrace();
242                System.exit(3);
243            }
244        }
245    }
246}