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}