1 /* 2 * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import com.sun.tools.javac.api.JavacTool; 25 import com.sun.tools.javac.file.JavacFileManager; 26 import com.sun.tools.javac.util.Context; 27 import java.io.ByteArrayOutputStream; 28 import java.io.FileWriter; 29 import java.io.IOException; 30 import java.io.PrintStream; 31 import java.io.PrintWriter; 32 import java.io.StringWriter; 33 import java.io.UncheckedIOException; 34 import java.lang.annotation.Annotation; 35 import java.lang.annotation.Retention; 36 import java.lang.annotation.RetentionPolicy; 37 import java.lang.reflect.InvocationTargetException; 38 import java.lang.reflect.Method; 39 import java.nio.file.Files; 40 import java.nio.file.Path; 41 import java.nio.file.Paths; 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.EnumMap; 45 import java.util.Iterator; 46 import java.util.List; 47 import java.util.Map; 48 import java.util.stream.Collectors; 49 import javax.tools.JavaFileManager; 50 import javax.tools.JavaFileObject; 51 import javax.tools.StandardJavaFileManager; 52 import javax.tools.ToolProvider; 53 54 public class OptionModesTester { 55 56 /** Marker annotation for test methods to be invoked by runTests. */ 57 @Retention(RetentionPolicy.RUNTIME) 58 @interface Test { } 59 60 /** 61 * Run all methods annotated with @Test, and throw an exception if any 62 * errors are reported.. 63 * Typically called on a tester object in main() 64 * @throws Exception if any errors occurred 65 */ runTests()66 void runTests() throws Exception { 67 for (Method m: getClass().getDeclaredMethods()) { 68 Annotation a = m.getAnnotation(Test.class); 69 if (a != null) { 70 try { 71 out.println("Running test " + m.getName()); 72 m.invoke(this); 73 } catch (InvocationTargetException e) { 74 Throwable cause = e.getCause(); 75 throw (cause instanceof Exception) ? ((Exception) cause) : e; 76 } 77 out.println(); 78 } 79 } 80 if (errors > 0) 81 throw new Exception(errors + " errors occurred"); 82 } 83 runMain(String[] opts, String[] files)84 TestResult runMain(String[] opts, String[] files) { 85 out.println("Main " + Arrays.toString(opts) + " " + Arrays.toString(files)); 86 return run(new TestResult(opts), (tr, c, pw) -> { 87 com.sun.tools.javac.main.Main compiler = 88 new com.sun.tools.javac.main.Main("javac", pw); 89 int rc = compiler.compile(join(opts, files), c).exitCode; 90 tr.setResult(rc); 91 }); 92 } 93 runCall(String[] opts, String[] files)94 TestResult runCall(String[] opts, String[] files) { 95 out.println("Call " + Arrays.toString(opts) + " " + Arrays.toString(files)); 96 return run(new TestResult(opts), (tr, c, pw) -> { 97 boolean ok = JavacTool.create() 98 .getTask(pw, null, null, Arrays.asList(opts), null, getFiles(files), c) 99 .call(); 100 tr.setResult(ok); 101 }); 102 } 103 104 TestResult runParse(String[] opts, String[] files) { 105 out.println("Parse " + Arrays.toString(opts) + " " + Arrays.toString(files)); 106 return run(new TestResult(opts), (tr, c, pw) -> { 107 JavacTool.create() 108 .getTask(pw, null, null, Arrays.asList(opts), null, getFiles(files), c) 109 .parse(); 110 tr.setResult(true); 111 }); 112 } 113 114 TestResult runAnalyze(String[] opts, String[] files) { 115 out.println("Analyze " + Arrays.toString(opts) + " " + Arrays.toString(files)); 116 return run(new TestResult(opts), (tr, c, pw) -> { 117 JavacTool.create() 118 .getTask(pw, null, null, Arrays.asList(opts), null, getFiles(files), c) 119 .analyze(); 120 tr.setResult(true); 121 }); 122 } 123 124 interface Runnable { 125 void run(TestResult tr, Context c, PrintWriter pw) throws IOException; 126 } 127 128 TestResult run(TestResult tr, Runnable r) { 129 StringWriter sw = new StringWriter(); 130 PrintWriter pw = new PrintWriter(sw); 131 StreamOutput sysOut = new StreamOutput(System.out, System::setOut); 132 StreamOutput sysErr = new StreamOutput(System.err, System::setErr); 133 Context context = new Context(); 134 JavacFileManager.preRegister(context); 135 try { 136 r.run(tr, context, pw); 137 } catch (IllegalArgumentException | IllegalStateException | IOException e) { 138 tr.setThrown(e); 139 } finally { 140 try { 141 ((JavacFileManager) context.get(JavaFileManager.class)).close(); 142 } catch (IOException e) { 143 throw new UncheckedIOException(e); 144 } 145 tr.setLogs(sw.toString(), sysOut.close(), sysErr.close()); 146 } 147 tr.setContext(context); 148 tr.show(); 149 return tr; 150 } 151 152 enum Log { DIRECT, STDOUT, STDERR }; 153 154 class TestResult { 155 final List<String> args; 156 Throwable thrown; 157 List<Throwable> suppressed = new ArrayList<>(); 158 Object rc; // Number or Boolean 159 Map<Log, String> logs; 160 Context context; 161 162 TestResult(String... args) { 163 this.args = Arrays.asList(args); 164 } 165 166 TestResult(List<String> args, Iterable<? extends JavaFileObject> files) { 167 this.args = new ArrayList<>(); 168 this.args.addAll(args); 169 for (JavaFileObject f: files) 170 this.args.add(f.getName()); 171 } 172 173 void setResult(int rc) { 174 this.rc = rc; 175 } 176 177 void setResult(boolean ok) { 178 this.rc = ok ? 0 : 1; 179 } 180 181 void setSuppressed(Throwable thrown) { 182 this.suppressed.add(thrown); 183 } 184 185 void setThrown(Throwable thrown) { 186 this.thrown = thrown; 187 } 188 189 void setLogs(String direct, String stdOut, String stdErr) { 190 logs = new EnumMap<>(Log.class); 191 logs.put(Log.DIRECT, direct); 192 logs.put(Log.STDOUT, stdOut); 193 logs.put(Log.STDERR, stdErr); 194 } 195 196 void setContext(Context context) { 197 this.context = context; 198 } 199 200 final void show() { 201 String NL = System.getProperty("line.separator"); 202 boolean needSep = false; 203 if (rc != null) { 204 out.print("rc:" + rc); 205 needSep = true; 206 } 207 if (thrown != null) { 208 if (needSep) out.print("; "); 209 out.print("thrown:" + thrown); 210 needSep = true; 211 } 212 if (!suppressed.isEmpty()) { 213 if (needSep) out.print("; "); 214 out.print("suppressed:" + suppressed); 215 needSep = true; 216 } 217 if (needSep) 218 out.println(); 219 logs.forEach((k, v) -> { 220 if (!v.isEmpty()) { 221 out.println("javac/" + k + ":"); 222 if (v.endsWith(NL)) 223 out.print(v); 224 else 225 out.println(v); 226 } 227 228 }); 229 } 230 231 TestResult checkOK() { 232 if (thrown != null) { 233 error("unexpected exception thrown: " + thrown); 234 } else if (rc == null) { 235 error("no result set"); 236 } else if (rc != (Integer) 0 && rc != (Boolean) true) { 237 error("compilation failed unexpectedly; rc=" + rc); 238 } 239 return this; 240 } 241 242 TestResult checkResult(int expect) { 243 if (thrown != null) { 244 error("unexpected exception thrown: " + thrown); 245 } else if (rc != (Integer) expect) { 246 error("unexpected result: " + rc +", expected:" + expect); 247 } 248 return this; 249 } 250 251 TestResult checkResult(boolean expect) { 252 if (thrown != null) { 253 error("unexpected exception thrown: " + thrown); 254 } else if (rc != (Integer) (expect ? 0 : 1)) { 255 error("unexpected result: " + rc +", expected:" + expect); 256 } 257 return this; 258 } 259 260 TestResult checkLog(String... expects) { 261 return checkLog(Log.DIRECT, expects); 262 } 263 264 TestResult checkLog(Log l, String... expects) { 265 for (String e: expects) { 266 if (!logs.get(l).contains(e)) 267 error("expected string not found: " + e); 268 } 269 return this; 270 } 271 272 TestResult checkIllegalArgumentException() { 273 return checkThrown(IllegalArgumentException.class); 274 } 275 276 TestResult checkIllegalStateException() { 277 return checkThrown(IllegalStateException.class); 278 } 279 280 TestResult checkThrown(Class<? extends Throwable> t) { 281 if (thrown == null) 282 error("expected exception not thrown: " + t); 283 else if (!t.isAssignableFrom(thrown.getClass())) 284 error("unexpected exception thrown: " + thrown + "; expected: " + t); 285 return this; 286 } 287 288 TestResult checkClass(String name) { 289 Path p = getOutDir().resolve(name.replace(".", "/") + ".class"); 290 if (!Files.exists(p)) 291 error("expected class not found: " + name + " (" + p + ")"); 292 return this; 293 } 294 295 Path getOutDir() { 296 Iterator<String> iter = args.iterator(); 297 while (iter.hasNext()) { 298 if (iter.next().equals("-d")) { 299 return Paths.get(iter.next()); 300 } 301 } 302 return null; 303 } 304 } 305 306 /** 307 * Utility class to simplify the handling of temporarily setting a 308 * new stream for System.out or System.err. 309 */ 310 private static class StreamOutput { 311 // functional interface to set a stream. 312 private interface Initializer { 313 void set(PrintStream s); 314 } 315 316 private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 317 private final PrintStream ps = new PrintStream(baos); 318 private final PrintStream prev; 319 private final Initializer init; 320 321 StreamOutput(PrintStream s, Initializer init) { 322 prev = s; 323 init.set(ps); 324 this.init = init; 325 } 326 327 String close() { 328 init.set(prev); 329 ps.close(); 330 return baos.toString(); 331 } 332 } 333 334 List<JavaFileObject> getFiles(String... paths) { 335 List<JavaFileObject> files = new ArrayList<>(); 336 for (JavaFileObject f : fm.getJavaFileObjects(paths)) 337 files.add(f); 338 return files; 339 } 340 341 String toString(List<JavaFileObject> files) { 342 return files.stream().map(f -> f.getName()).collect(Collectors.toList()).toString(); 343 } 344 345 void mkdirs(String path) throws IOException { 346 Files.createDirectories(Paths.get(path)); 347 } 348 349 void writeFile(String path, String body) throws IOException { 350 Path p = Paths.get(path); 351 if (p.getParent() != null) 352 Files.createDirectories(p.getParent()); 353 try (FileWriter w = new FileWriter(path)) { 354 w.write(body); 355 } 356 } 357 358 String[] join(String[] a, String[] b) { 359 String[] result = new String[a.length + b.length]; 360 System.arraycopy(a, 0, result, 0, a.length); 361 System.arraycopy(b, 0, result, a.length, b.length); 362 return result; 363 } 364 365 void error(String message) { 366 out.print(">>>>> "); 367 out.println(message); 368 errors++; 369 } 370 371 StandardJavaFileManager fm = 372 ToolProvider.getSystemJavaCompiler().getStandardFileManager(null, null, null); 373 PrintStream out = System.err; 374 int errors; 375 376 } 377