1 /* 2 * Copyright (c) 2010, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package jdk.nashorn.internal.test.framework; 27 28 import static jdk.nashorn.internal.test.framework.TestConfig.TEST_FAILED_LIST_FILE; 29 import static jdk.nashorn.internal.test.framework.TestConfig.TEST_JS_ENABLE_STRICT_MODE; 30 import static jdk.nashorn.internal.test.framework.TestConfig.TEST_JS_EXCLUDES_FILE; 31 import static jdk.nashorn.internal.test.framework.TestConfig.TEST_JS_EXCLUDE_LIST; 32 import static jdk.nashorn.internal.test.framework.TestConfig.TEST_JS_FRAMEWORK; 33 import static jdk.nashorn.internal.test.framework.TestConfig.TEST_JS_ROOTS; 34 import java.io.BufferedReader; 35 import java.io.ByteArrayOutputStream; 36 import java.io.File; 37 import java.io.FileInputStream; 38 import java.io.FileOutputStream; 39 import java.io.FileReader; 40 import java.io.FileWriter; 41 import java.io.IOException; 42 import java.io.InputStreamReader; 43 import java.io.OutputStream; 44 import java.io.PrintStream; 45 import java.io.PrintWriter; 46 import java.io.StringReader; 47 import java.nio.file.FileSystems; 48 import java.nio.file.Files; 49 import java.nio.file.StandardCopyOption; 50 import java.util.ArrayList; 51 import java.util.Collections; 52 import java.util.Comparator; 53 import java.util.List; 54 import java.util.Locale; 55 import java.util.Map; 56 import java.util.Properties; 57 import java.util.Set; 58 import java.util.TreeSet; 59 import java.util.concurrent.Callable; 60 import java.util.concurrent.CancellationException; 61 import java.util.concurrent.CountDownLatch; 62 import java.util.concurrent.ExecutionException; 63 import java.util.concurrent.ExecutorService; 64 import java.util.concurrent.Executors; 65 import java.util.concurrent.Future; 66 import java.util.concurrent.TimeUnit; 67 import java.util.regex.Matcher; 68 import java.util.regex.Pattern; 69 import jdk.nashorn.internal.test.framework.TestFinder.TestFactory; 70 71 /** 72 * Parallel test runner runs tests in multiple threads - but avoids any dependency 73 * on third-party test framework library such as TestNG. 74 */ 75 @SuppressWarnings("javadoc") 76 public class ParallelTestRunner { 77 78 // ParallelTestRunner-specific 79 private static final String TEST_JS_THREADS = "test.js.threads"; 80 private static final String TEST_JS_REPORT_FILE = "test.js.report.file"; 81 // test262 does a lot of eval's and the JVM hates multithreaded class definition, so lower thread count is usually faster. 82 private static final int THREADS = Integer.getInteger(TEST_JS_THREADS, Runtime.getRuntime().availableProcessors() > 4 ? 4 : 2); 83 84 private final List<ScriptRunnable> tests = new ArrayList<>(); 85 private final Set<String> orphans = new TreeSet<>(); 86 private final ExecutorService executor = Executors.newFixedThreadPool(THREADS); 87 88 // Ctrl-C handling 89 private final CountDownLatch finishedLatch = new CountDownLatch(1); 90 private final Thread shutdownHook = new Thread() { 91 @Override 92 public void run() { 93 if (!executor.isTerminated()) { 94 executor.shutdownNow(); 95 try { 96 executor.awaitTermination(25, TimeUnit.SECONDS); 97 finishedLatch.await(5, TimeUnit.SECONDS); 98 } catch (final InterruptedException e) { 99 // empty 100 } 101 } 102 } 103 }; 104 105 public ParallelTestRunner() throws Exception { 106 suite(); 107 } 108 109 private static PrintStream outputStream() { 110 final String reportFile = System.getProperty(TEST_JS_REPORT_FILE, ""); 111 PrintStream output = System.out; 112 113 if (!reportFile.isEmpty()) { 114 try { 115 output = new PrintStream(new OutputStreamDelegator(System.out, new FileOutputStream(reportFile))); 116 } catch (final IOException e) { 117 System.err.println(e); 118 } 119 } 120 121 return output; 122 } 123 124 public static final class ScriptRunnable extends AbstractScriptRunnable implements Callable<ScriptRunnable.Result> { 125 private final Result result = new Result(); 126 127 public class Result { 128 private boolean passed = true; 129 public String expected; 130 public String out; 131 public String err; 132 public Throwable exception; 133 134 public ScriptRunnable getTest() { 135 return ScriptRunnable.this; 136 } 137 138 public boolean passed() { 139 return passed; 140 } 141 142 @Override 143 public String toString() { 144 return getTest().toString(); 145 } 146 } 147 148 public ScriptRunnable(final String framework, final File testFile, final List<String> engineOptions, final Map<String, String> testOptions, final List<String> scriptArguments) { 149 super(framework, testFile, engineOptions, testOptions, scriptArguments); 150 } 151 152 @Override 153 protected void log(final String msg) { 154 System.err.println(msg); 155 } 156 157 @Override 158 protected void fail(final String message) { 159 throw new TestFailedError(message); 160 } 161 162 @Override 163 protected void compile() throws IOException { 164 final ByteArrayOutputStream out = new ByteArrayOutputStream(); 165 final ByteArrayOutputStream err = new ByteArrayOutputStream(); 166 final List<String> args = getCompilerArgs(); 167 int errors; 168 try { 169 errors = evaluateScript(out, err, args.toArray(new String[0])); 170 } catch (final AssertionError e) { 171 final PrintWriter writer = new PrintWriter(err); 172 e.printStackTrace(writer); 173 writer.flush(); 174 errors = 1; 175 } 176 if (errors != 0 || checkCompilerMsg) { 177 result.err = err.toString(); 178 if (expectCompileFailure || checkCompilerMsg) { 179 final PrintStream outputDest = new PrintStream(new FileOutputStream(getErrorFileName())); 180 TestHelper.dumpFile(outputDest, new StringReader(new String(err.toByteArray()))); 181 outputDest.println("--"); 182 } 183 if (errors != 0 && !expectCompileFailure) { 184 fail(String.format("%d errors compiling %s", errors, testFile)); 185 } 186 if (checkCompilerMsg) { 187 compare(getErrorFileName(), expectedFileName, true); 188 } 189 } 190 if (expectCompileFailure && errors == 0) { 191 fail(String.format("No errors encountered compiling negative test %s", testFile)); 192 } 193 } 194 195 @Override 196 protected void execute() { 197 final List<String> args = getRuntimeArgs(); 198 final ByteArrayOutputStream out = new ByteArrayOutputStream(); 199 final ByteArrayOutputStream err = new ByteArrayOutputStream(); 200 201 try { 202 final int errors = evaluateScript(out, err, args.toArray(new String[0])); 203 204 if (errors != 0 || err.size() > 0) { 205 if (expectRunFailure) { 206 return; 207 } 208 if (!ignoreStdError) { 209 210 try (OutputStream outputFile = new FileOutputStream(getOutputFileName()); OutputStream errorFile = new FileOutputStream(getErrorFileName())) { 211 outputFile.write(out.toByteArray()); 212 errorFile.write(err.toByteArray()); 213 } 214 215 result.out = out.toString(); 216 result.err = err.toString(); 217 fail(err.toString()); 218 } 219 } 220 221 if (compare) { 222 final File expectedFile = new File(expectedFileName); 223 try { 224 BufferedReader expected; 225 if (expectedFile.exists()) { 226 expected = new BufferedReader(new FileReader(expectedFile)); 227 } else { 228 expected = new BufferedReader(new StringReader("")); 229 } 230 compare(new BufferedReader(new StringReader(out.toString())), expected, false); 231 } catch (final Throwable ex) { 232 if (expectedFile.exists()) { 233 copyExpectedFile(); 234 } 235 try (OutputStream outputFile = new FileOutputStream(getOutputFileName()); OutputStream errorFile = new FileOutputStream(getErrorFileName())) { 236 outputFile.write(out.toByteArray()); 237 errorFile.write(err.toByteArray()); 238 } 239 ex.printStackTrace(); 240 throw ex; 241 } 242 } 243 } catch (final IOException e) { 244 if (!expectRunFailure) { 245 fail("Failure running test " + testFile + ": " + e.getMessage()); 246 } // else success 247 } 248 } 249 250 private void compare(final String fileName, final String expected, final boolean compareCompilerMsg) throws IOException { 251 final File expectedFile = new File(expected); 252 253 BufferedReader expectedReader; 254 if (expectedFile.exists()) { 255 expectedReader = new BufferedReader(new InputStreamReader(new FileInputStream(expectedFileName))); 256 } else { 257 expectedReader = new BufferedReader(new StringReader("")); 258 } 259 260 final BufferedReader actual = new BufferedReader(new InputStreamReader(new FileInputStream(fileName))); 261 262 compare(actual, expectedReader, compareCompilerMsg); 263 } 264 265 private void copyExpectedFile() { 266 if (!new File(expectedFileName).exists()) { 267 return; 268 } 269 // copy expected file overwriting existing file and preserving last 270 // modified time of source 271 try { 272 Files.copy(FileSystems.getDefault().getPath(expectedFileName), FileSystems.getDefault().getPath(getCopyExpectedFileName()), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES); 273 } catch (final IOException ex) { 274 fail("failed to copy expected " + expectedFileName + " to " + getCopyExpectedFileName() + ": " + ex.getMessage()); 275 } 276 } 277 278 @Override 279 public Result call() { 280 try { 281 runTest(); 282 } catch (final Throwable ex) { 283 result.exception = ex; 284 result.passed = false; 285 ex.printStackTrace(); 286 } 287 return result; 288 } 289 290 private String getOutputFileName() { 291 buildDir.mkdirs(); 292 return outputFileName; 293 } 294 295 private String getErrorFileName() { 296 buildDir.mkdirs(); 297 return errorFileName; 298 } 299 300 private String getCopyExpectedFileName() { 301 buildDir.mkdirs(); 302 return copyExpectedFileName; 303 } 304 } 305 306 private void suite() throws Exception { 307 Locale.setDefault(new Locale("")); 308 System.setOut(outputStream()); 309 310 final TestFactory<ScriptRunnable> testFactory = new TestFactory<ScriptRunnable>() { 311 @Override 312 public ScriptRunnable createTest(final String framework, final File testFile, final List<String> engineOptions, final Map<String, String> testOptions, final List<String> arguments) { 313 return new ScriptRunnable(framework, testFile, engineOptions, testOptions, arguments); 314 } 315 316 @Override 317 public void log(final String msg) { 318 System.err.println(msg); 319 } 320 }; 321 322 TestFinder.findAllTests(tests, orphans, testFactory); 323 324 Collections.sort(tests, new Comparator<ScriptRunnable>() { 325 @Override 326 public int compare(final ScriptRunnable o1, final ScriptRunnable o2) { 327 return o1.testFile.compareTo(o2.testFile); 328 } 329 }); 330 } 331 332 @SuppressWarnings("resource") 333 public boolean run() throws IOException { 334 final int testCount = tests.size(); 335 int passCount = 0; 336 int doneCount = 0; 337 System.out.printf("Found %d tests.\n", testCount); 338 final long startTime = System.nanoTime(); 339 340 Runtime.getRuntime().addShutdownHook(shutdownHook); 341 342 final List<Future<ScriptRunnable.Result>> futures = new ArrayList<>(); 343 for (final ScriptRunnable test : tests) { 344 futures.add(executor.submit(test)); 345 } 346 347 executor.shutdown(); 348 try { 349 executor.awaitTermination(60, TimeUnit.MINUTES); 350 } catch (final InterruptedException ex) { 351 // empty 352 } 353 354 final List<ScriptRunnable.Result> results = new ArrayList<>(); 355 for (final Future<ScriptRunnable.Result> future : futures) { 356 if (future.isDone()) { 357 try { 358 final ScriptRunnable.Result result = future.get(); 359 results.add(result); 360 doneCount++; 361 if (result.passed()) { 362 passCount++; 363 } 364 } catch (CancellationException | ExecutionException ex) { 365 ex.printStackTrace(); 366 } catch (final InterruptedException ex) { 367 assert false : "should not reach here"; 368 } 369 } 370 } 371 372 Collections.sort(results, new Comparator<ScriptRunnable.Result>() { 373 @Override 374 public int compare(final ScriptRunnable.Result o1, final ScriptRunnable.Result o2) { 375 return o1.getTest().testFile.compareTo(o2.getTest().testFile); 376 } 377 }); 378 379 boolean hasFailed = false; 380 final String failedList = System.getProperty(TEST_FAILED_LIST_FILE); 381 final boolean hasFailedList = failedList != null; 382 final boolean hadPreviouslyFailingTests = hasFailedList && new File(failedList).length() > 0; 383 final FileWriter failedFileWriter = hasFailedList ? new FileWriter(failedList) : null; 384 try { 385 final PrintWriter failedListWriter = failedFileWriter == null ? null : new PrintWriter(failedFileWriter); 386 for (final ScriptRunnable.Result result : results) { 387 if (!result.passed()) { 388 if (hasFailed == false) { 389 hasFailed = true; 390 System.out.println(); 391 System.out.println("FAILED TESTS"); 392 } 393 394 System.out.println(result.getTest()); 395 if(failedFileWriter != null) { 396 failedListWriter.println(result.getTest().testFile.getPath()); 397 } 398 if (result.exception != null) { 399 final String exceptionString = result.exception instanceof TestFailedError ? result.exception.getMessage() : result.exception.toString(); 400 System.out.print(exceptionString.endsWith("\n") ? exceptionString : exceptionString + "\n"); 401 System.out.print(result.out != null ? result.out : ""); 402 } 403 } 404 } 405 } finally { 406 if(failedFileWriter != null) { 407 failedFileWriter.close(); 408 } 409 } 410 final double timeElapsed = (System.nanoTime() - startTime) / 1e9; // [s] 411 System.out.printf("Tests run: %d/%d tests, passed: %d (%.2f%%), failed: %d. Time elapsed: %.0fmin %.0fs.\n", doneCount, testCount, passCount, 100d * passCount / doneCount, doneCount - passCount, timeElapsed / 60, timeElapsed % 60); 412 System.out.flush(); 413 414 finishedLatch.countDown(); 415 416 if (hasFailed) { 417 throw new AssertionError("TEST FAILED"); 418 } 419 420 if(hasFailedList) { 421 new File(failedList).delete(); 422 } 423 424 if(hadPreviouslyFailingTests) { 425 System.out.println(); 426 System.out.println("Good job on getting all your previously failing tests pass!"); 427 System.out.println("NOW re-running all tests to make sure you haven't caused any NEW test failures."); 428 System.out.println(); 429 } 430 431 return hadPreviouslyFailingTests; 432 } 433 434 public static void main(final String[] args) throws Exception { 435 parseArgs(args); 436 437 while (new ParallelTestRunner().run()) { 438 //empty 439 } 440 } 441 442 private static void parseArgs(final String[] args) { 443 if (args.length > 0) { 444 String roots = ""; 445 String reportFile = ""; 446 for (int i = 0; i < args.length; i++) { 447 if (args[i].equals("--roots") && i != args.length - 1) { 448 roots += args[++i] + " "; 449 } else if (args[i].equals("--report-file") && i != args.length - 1) { 450 reportFile = args[++i]; 451 } else if (args[i].equals("--test262")) { 452 try { 453 setTest262Properties(); 454 } catch (final IOException ex) { 455 System.err.println(ex); 456 } 457 } 458 } 459 if (!roots.isEmpty()) { 460 System.setProperty(TEST_JS_ROOTS, roots.trim()); 461 } 462 if (!reportFile.isEmpty()) { 463 System.setProperty(TEST_JS_REPORT_FILE, reportFile); 464 } 465 } 466 } 467 468 private static void setTest262Properties() throws IOException { 469 System.setProperty(TEST_JS_ROOTS, "test/nashorn/script/external/test262/test/suite/"); 470 System.setProperty(TEST_JS_FRAMEWORK, "test/nashorn/script/test262.js test/nashorn/script/external/test262/test/harness/framework.js test/nashorn/script/external/test262/test/harness/sta.js"); 471 System.setProperty(TEST_JS_EXCLUDES_FILE, "test/nashorn/script/external/test262/test/config/excludelist.xml"); 472 System.setProperty(TEST_JS_ENABLE_STRICT_MODE, "true"); 473 474 final Properties projectProperties = new Properties(); 475 projectProperties.load(new FileInputStream("project.properties")); 476 String excludeList = projectProperties.getProperty("test262-test-sys-prop.test.js.exclude.list", ""); 477 final Pattern pattern = Pattern.compile("\\$\\{([^}]+)}"); 478 for (;;) { 479 final Matcher matcher = pattern.matcher(excludeList); 480 if (!matcher.find()) { 481 break; 482 } 483 final String propertyValue = projectProperties.getProperty(matcher.group(1), ""); 484 excludeList = excludeList.substring(0, matcher.start()) + propertyValue + excludeList.substring(matcher.end()); 485 } 486 System.setProperty(TEST_JS_EXCLUDE_LIST, excludeList); 487 } 488 489 public static final class OutputStreamDelegator extends OutputStream { 490 private final OutputStream[] streams; 491 492 public OutputStreamDelegator(final OutputStream... streams) { 493 this.streams = streams; 494 } 495 496 @Override 497 public void write(final int b) throws IOException { 498 for (final OutputStream stream : streams) { 499 stream.write(b); 500 } 501 } 502 503 @Override 504 public void flush() throws IOException { 505 for (final OutputStream stream : streams) { 506 stream.flush(); 507 } 508 } 509 } 510 } 511 512 final class TestFailedError extends Error { 513 private static final long serialVersionUID = 1L; 514 515 public TestFailedError(final String message) { 516 super(message); 517 } 518 519 public TestFailedError(final String message, final Throwable cause) { 520 super(message, cause); 521 } 522 523 public TestFailedError(final Throwable cause) { 524 super(cause); 525 } 526 }