1 /* 2 * Copyright (c) 2008, 2020, 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 java.io.OutputStream; 25 import java.io.InputStream; 26 import java.lang.annotation.ElementType; 27 import java.lang.annotation.Retention; 28 import java.lang.annotation.RetentionPolicy; 29 import java.lang.annotation.Target; 30 import java.lang.reflect.Method; 31 import java.util.regex.Pattern; 32 import java.io.StringWriter; 33 import java.io.PrintWriter; 34 import java.util.Set; 35 import java.io.BufferedReader; 36 import java.io.File; 37 import java.io.FileFilter; 38 import java.io.FileNotFoundException; 39 import java.io.FileOutputStream; 40 import java.io.IOException; 41 import java.io.InputStreamReader; 42 import java.io.PrintStream; 43 import java.nio.charset.Charset; 44 import java.nio.file.attribute.BasicFileAttributes; 45 import java.nio.file.Files; 46 import java.nio.file.FileVisitResult; 47 import java.nio.file.SimpleFileVisitor; 48 import java.nio.file.Path; 49 import java.util.ArrayList; 50 import java.util.List; 51 import java.util.Locale; 52 import java.util.Map; 53 import java.util.Arrays; 54 import java.util.spi.ToolProvider; 55 56 import static java.nio.file.StandardCopyOption.*; 57 import static java.nio.file.StandardOpenOption.*; 58 59 /** 60 * This class provides some common utilities for the launcher tests. 61 */ 62 public class TestHelper { 63 // commonly used jtreg constants 64 static final File TEST_CLASSES_DIR; 65 static final File TEST_SOURCES_DIR; 66 67 static final String JAVAHOME = System.getProperty("java.home"); 68 static final String JAVA_BIN; 69 static final String JAVA_LIB; 70 static final String javaCmd; 71 static final String javawCmd; 72 static final String javacCmd; 73 static final String jarCmd; 74 static final boolean haveServerVM; 75 static final boolean haveClientVM; 76 77 static final ToolProvider compiler = ToolProvider.findFirst("javac").orElse(null); 78 79 static final boolean debug = Boolean.getBoolean("TestHelper.Debug"); 80 static final boolean isWindows = 81 System.getProperty("os.name", "unknown").startsWith("Windows"); 82 static final boolean isMacOSX = 83 System.getProperty("os.name", "unknown").contains("OS X"); 84 static final boolean is64Bit = 85 System.getProperty("sun.arch.data.model").equals("64"); 86 static final boolean is32Bit = 87 System.getProperty("sun.arch.data.model").equals("32"); 88 static final boolean isLinux = 89 System.getProperty("os.name", "unknown").startsWith("Linux"); 90 static final boolean isAIX = 91 System.getProperty("os.name", "unknown").startsWith("AIX"); 92 static final String LIBJVM = isWindows 93 ? "jvm.dll" 94 : "libjvm" + (isMacOSX ? ".dylib" : ".so"); 95 96 // make a note of the golden default locale 97 static final Locale DefaultLocale = Locale.getDefault(); 98 99 static final String JAVA_FILE_EXT = ".java"; 100 static final String CLASS_FILE_EXT = ".class"; 101 static final String JAR_FILE_EXT = ".jar"; 102 static final String EXE_FILE_EXT = ".exe"; 103 static final String MAC_DSYM_EXT = ".dsym"; 104 static final String NIX_DBGINFO_EXT = ".debuginfo"; 105 static final String JLDEBUG_KEY = "_JAVA_LAUNCHER_DEBUG"; 106 static final String EXPECTED_MARKER = "TRACER_MARKER:About to EXEC"; 107 static final String TEST_PREFIX = "###TestError###: "; 108 109 static int testExitValue = 0; 110 111 static { 112 String tmp = System.getProperty("test.classes", null); 113 if (tmp == null) { 114 throw new Error("property test.classes not defined ??"); 115 } 116 TEST_CLASSES_DIR = new File(tmp).getAbsoluteFile(); 117 118 tmp = System.getProperty("test.src", null); 119 if (tmp == null) { 120 throw new Error("property test.src not defined ??"); 121 } 122 TEST_SOURCES_DIR = new File(tmp).getAbsoluteFile(); 123 124 if (is64Bit && is32Bit) { 125 throw new RuntimeException("arch model cannot be both 32 and 64 bit"); 126 } 127 if (!is64Bit && !is32Bit) { 128 throw new RuntimeException("arch model is not 32 or 64 bit ?"); 129 } 130 131 File binDir = new File(JAVAHOME, "bin"); 132 JAVA_BIN = binDir.getAbsolutePath(); 133 File libDir = new File(JAVAHOME, "lib"); 134 JAVA_LIB = libDir.getAbsolutePath(); 135 136 File javaCmdFile = (isWindows) 137 ? new File(binDir, "java.exe") 138 : new File(binDir, "java"); 139 javaCmd = javaCmdFile.getAbsolutePath(); 140 if (!javaCmdFile.canExecute()) { 141 throw new RuntimeException("java <" + TestHelper.javaCmd + 142 "> must exist and should be executable"); 143 } 144 145 File javacCmdFile = (isWindows) 146 ? new File(binDir, "javac.exe") 147 : new File(binDir, "javac"); 148 javacCmd = javacCmdFile.getAbsolutePath(); 149 150 File jarCmdFile = (isWindows) 151 ? new File(binDir, "jar.exe") 152 : new File(binDir, "jar"); 153 jarCmd = jarCmdFile.getAbsolutePath(); 154 if (!jarCmdFile.canExecute()) { 155 throw new RuntimeException("java <" + TestHelper.jarCmd + 156 "> must exist and should be executable"); 157 } 158 159 if (isWindows) { 160 File javawCmdFile = new File(binDir, "javaw.exe"); 161 javawCmd = javawCmdFile.getAbsolutePath(); 162 if (!javawCmdFile.canExecute()) { 163 throw new RuntimeException("java <" + javawCmd + 164 "> must exist and should be executable"); 165 } 166 } else { 167 javawCmd = null; 168 } 169 170 if (!javacCmdFile.canExecute()) { 171 throw new RuntimeException("java <" + javacCmd + 172 "> must exist and should be executable"); 173 } 174 175 haveClientVM = haveVmVariant("client"); 176 haveServerVM = haveVmVariant("server"); 177 } 178 private static boolean haveVmVariant(String type) { 179 if (isWindows) { 180 File vmDir = new File(JAVA_BIN, type); 181 File jvmFile = new File(vmDir, LIBJVM); 182 return jvmFile.exists(); 183 } else { 184 File vmDir = new File(JAVA_LIB, type); 185 File jvmFile = new File(vmDir, LIBJVM); 186 return jvmFile.exists(); 187 } 188 } 189 void run(String[] args) throws Exception { 190 int passed = 0, failed = 0; 191 final Pattern p = (args != null && args.length > 0) 192 ? Pattern.compile(args[0]) 193 : null; 194 for (Method m : this.getClass().getDeclaredMethods()) { 195 boolean selected = (p == null) 196 ? m.isAnnotationPresent(Test.class) 197 : p.matcher(m.getName()).matches(); 198 if (selected) { 199 try { 200 m.invoke(this, (Object[]) null); 201 System.out.println(m.getName() + ": OK"); 202 passed++; 203 System.out.printf("Passed: %d, Failed: %d, ExitValue: %d%n", 204 passed, failed, testExitValue); 205 } catch (Throwable ex) { 206 System.out.printf("Test %s failed: %s %n", m, ex); 207 System.out.println("----begin detailed exceptions----"); 208 ex.printStackTrace(System.out); 209 for (Throwable t : ex.getSuppressed()) { 210 t.printStackTrace(System.out); 211 } 212 System.out.println("----end detailed exceptions----"); 213 failed++; 214 } 215 } 216 } 217 System.out.printf("Total: Passed: %d, Failed %d%n", passed, failed); 218 if (failed > 0) { 219 throw new RuntimeException("Tests failed: " + failed); 220 } 221 if (passed == 0 && failed == 0) { 222 throw new AssertionError("No test(s) selected: passed = " + 223 passed + ", failed = " + failed + " ??????????"); 224 } 225 } 226 227 /* 228 * usually the jre/lib/arch-name is the same as os.arch, except for x86. 229 */ 230 static String getJreArch() { 231 String arch = System.getProperty("os.arch"); 232 return arch.equals("x86") ? "i386" : arch; 233 } 234 static String getArch() { 235 return System.getProperty("os.arch"); 236 } 237 static File getClassFile(File javaFile) { 238 String s = javaFile.getAbsolutePath().replace(JAVA_FILE_EXT, CLASS_FILE_EXT); 239 return new File(s); 240 } 241 242 static File getJavaFile(File classFile) { 243 String s = classFile.getAbsolutePath().replace(CLASS_FILE_EXT, JAVA_FILE_EXT); 244 return new File(s); 245 } 246 247 static String baseName(File f) { 248 String s = f.getName(); 249 return s.substring(0, s.indexOf(".")); 250 } 251 252 /* 253 * A convenience method to create a jar with jar file name and defs 254 */ 255 static void createJar(File jarName, String... mainDefs) 256 throws FileNotFoundException{ 257 createJar(null, jarName, new File("Foo"), mainDefs); 258 } 259 260 /* 261 * A convenience method to create a java file, compile and jar it up, using 262 * the sole class file name in the jar, as the Main-Class attribute value. 263 */ 264 static void createJar(File jarName, File mainClass, String... mainDefs) 265 throws FileNotFoundException { 266 createJar(null, jarName, mainClass, mainDefs); 267 } 268 269 /* 270 * A convenience method to compile java files. 271 */ 272 static void compile(String... compilerArgs) { 273 if (compiler.run(System.out, System.err, compilerArgs) != 0) { 274 String sarg = ""; 275 for (String x : compilerArgs) { 276 sarg.concat(x + " "); 277 } 278 throw new Error("compilation failed: " + sarg); 279 } 280 } 281 282 /* 283 * A generic jar file creator to create a java file, compile it 284 * and jar it up, a specific Main-Class entry name in the 285 * manifest can be specified or a null to use the sole class file name 286 * as the Main-Class attribute value. 287 */ 288 static void createJar(String mEntry, File jarName, File mainClass, 289 String... mainDefs) throws FileNotFoundException { 290 if (jarName.exists()) { 291 jarName.delete(); 292 } 293 try (PrintStream ps = new PrintStream(new FileOutputStream(mainClass + ".java"))) { 294 ps.println("public class Foo {"); 295 if (mainDefs != null) { 296 for (String x : mainDefs) { 297 ps.println(x); 298 } 299 } 300 ps.println("}"); 301 } 302 303 String compileArgs[] = { 304 mainClass + ".java" 305 }; 306 if (compiler.run(System.out, System.err, compileArgs) != 0) { 307 throw new RuntimeException("compilation failed " + mainClass + ".java"); 308 } 309 if (mEntry == null) { 310 mEntry = mainClass.getName(); 311 } 312 String jarArgs[] = { 313 (debug) ? "cvfe" : "cfe", 314 jarName.getAbsolutePath(), 315 mEntry, 316 mainClass.getName() + ".class" 317 }; 318 createJar(jarArgs); 319 } 320 321 static void createJar(String... args) { 322 List<String> cmdList = new ArrayList<>(); 323 cmdList.add(jarCmd); 324 cmdList.addAll(Arrays.asList(args)); 325 doExec(cmdList.toArray(new String[cmdList.size()])); 326 } 327 328 static void copyStream(InputStream in, OutputStream out) throws IOException { 329 byte[] buf = new byte[8192]; 330 int n = in.read(buf); 331 while (n > 0) { 332 out.write(buf, 0, n); 333 n = in.read(buf); 334 } 335 } 336 337 static void copyFile(File src, File dst) throws IOException { 338 Path parent = dst.toPath().getParent(); 339 if (parent != null) { 340 Files.createDirectories(parent); 341 } 342 Files.copy(src.toPath(), dst.toPath(), COPY_ATTRIBUTES, REPLACE_EXISTING); 343 } 344 345 /** 346 * Attempt to create a file at the given location. If an IOException 347 * occurs then back off for a moment and try again. When a number of 348 * attempts fail, give up and throw an exception. 349 */ 350 void createAFile(File aFile, List<String> lines) throws IOException { 351 createAFile(aFile, lines, true); 352 } 353 354 void createAFile(File aFile, List<String> lines, boolean endWithNewline) throws IOException { 355 IOException cause = null; 356 for (int attempts = 0; attempts < 10; attempts++) { 357 try { 358 if (endWithNewline) { 359 Files.write(aFile.getAbsoluteFile().toPath(), 360 lines, Charset.defaultCharset(), 361 CREATE, TRUNCATE_EXISTING, WRITE); 362 } else { 363 Files.write(aFile.getAbsoluteFile().toPath(), 364 String.join(System.lineSeparator(), lines).getBytes(Charset.defaultCharset()), 365 CREATE, TRUNCATE_EXISTING, WRITE); 366 } 367 if (cause != null) { 368 /* 369 * report attempts and errors that were encountered 370 * for diagnostic purposes 371 */ 372 System.err.println("Created batch file " + 373 aFile + " in " + (attempts + 1) + 374 " attempts"); 375 System.err.println("Errors encountered: " + cause); 376 cause.printStackTrace(); 377 } 378 return; 379 } catch (IOException ioe) { 380 if (cause != null) { 381 // chain the exceptions so they all get reported for diagnostics 382 cause.addSuppressed(ioe); 383 } else { 384 cause = ioe; 385 } 386 } 387 388 try { 389 Thread.sleep(500); 390 } catch (InterruptedException ie) { 391 if (cause != null) { 392 // cause should alway be non-null here 393 ie.addSuppressed(cause); 394 } 395 throw new RuntimeException("Interrupted while creating batch file", ie); 396 } 397 } 398 throw new RuntimeException("Unable to create batch file", cause); 399 } 400 401 static void createFile(File outFile, List<String> content) throws IOException { 402 Files.write(outFile.getAbsoluteFile().toPath(), content, 403 Charset.defaultCharset(), CREATE_NEW); 404 } 405 406 static void recursiveDelete(File target) throws IOException { 407 if (!target.exists()) { 408 return; 409 } 410 Files.walkFileTree(target.toPath(), new SimpleFileVisitor<Path>() { 411 @Override 412 public FileVisitResult postVisitDirectory(Path dir, IOException exc) { 413 try { 414 Files.deleteIfExists(dir); 415 } catch (IOException ex) { 416 System.out.println("Error: could not delete: " + dir.toString()); 417 System.out.println(ex.getMessage()); 418 return FileVisitResult.TERMINATE; 419 } 420 return FileVisitResult.CONTINUE; 421 } 422 @Override 423 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { 424 try { 425 Files.deleteIfExists(file); 426 } catch (IOException ex) { 427 System.out.println("Error: could not delete: " + file.toString()); 428 System.out.println(ex.getMessage()); 429 return FileVisitResult.TERMINATE; 430 } 431 return FileVisitResult.CONTINUE; 432 } 433 }); 434 } 435 436 static TestResult doExec(String...cmds) { 437 return doExec(null, null, cmds); 438 } 439 440 static TestResult doExec(Map<String, String> envToSet, String...cmds) { 441 return doExec(envToSet, null, cmds); 442 } 443 /* 444 * A method which executes a java cmd and returns the results in a container 445 */ 446 static TestResult doExec(Map<String, String> envToSet, 447 Set<String> envToRemove, String...cmds) { 448 String cmdStr = ""; 449 for (String x : cmds) { 450 cmdStr = cmdStr.concat(x + " "); 451 } 452 ProcessBuilder pb = new ProcessBuilder(cmds); 453 Map<String, String> env = pb.environment(); 454 if (envToRemove != null) { 455 for (String key : envToRemove) { 456 env.remove(key); 457 } 458 } 459 if (envToSet != null) { 460 env.putAll(envToSet); 461 } 462 BufferedReader rdr = null; 463 try { 464 List<String> outputList = new ArrayList<>(); 465 pb.redirectErrorStream(true); 466 Process p = pb.start(); 467 rdr = new BufferedReader(new InputStreamReader(p.getInputStream())); 468 String in = rdr.readLine(); 469 while (in != null) { 470 outputList.add(in); 471 in = rdr.readLine(); 472 } 473 p.waitFor(); 474 p.destroy(); 475 476 return new TestHelper.TestResult(cmdStr, p.exitValue(), outputList, 477 env, new Throwable("current stack of the test")); 478 } catch (Exception ex) { 479 ex.printStackTrace(); 480 throw new RuntimeException(ex.getMessage()); 481 } 482 } 483 484 static FileFilter createFilter(final String extension) { 485 return new FileFilter() { 486 @Override 487 public boolean accept(File pathname) { 488 String name = pathname.getName(); 489 if (name.endsWith(extension)) { 490 return true; 491 } 492 return false; 493 } 494 }; 495 } 496 497 static boolean isEnglishLocale() { 498 return Locale.getDefault().getLanguage().equals("en"); 499 } 500 501 /** 502 * Helper method to initialize a simple module that just prints the passed in arguments 503 */ 504 static void createEchoArgumentsModule(File modulesDir) throws IOException { 505 if (modulesDir.exists()) { 506 recursiveDelete(modulesDir); 507 } 508 509 modulesDir.mkdirs(); 510 511 File srcDir = new File(modulesDir, "src"); 512 File modsDir = new File(modulesDir, "mods"); 513 File testDir = new File(srcDir, "test"); 514 File launcherTestDir = new File(testDir, "launcher"); 515 srcDir.mkdir(); 516 modsDir.mkdir(); 517 testDir.mkdir(); 518 launcherTestDir.mkdir(); 519 520 String[] moduleInfoCode = { "module test {}" }; 521 createFile(new File(testDir, "module-info.java"), Arrays.asList(moduleInfoCode)); 522 523 String[] moduleCode = { 524 "package launcher;", 525 "import java.util.Arrays;", 526 "public class Main {", 527 "public static void main(String[] args) {", 528 "System.out.println(Arrays.toString(args));", 529 "}", 530 "}" 531 }; 532 createFile(new File(launcherTestDir, "Main.java"), Arrays.asList(moduleCode)); 533 } 534 535 static class ToolFilter implements FileFilter { 536 final List<String> exclude = new ArrayList<>(); 537 protected ToolFilter(String... exclude) { 538 for (String x : exclude) { 539 String str = x + ((isWindows) ? EXE_FILE_EXT : ""); 540 this.exclude.add(str.toLowerCase()); 541 } 542 } 543 544 @Override 545 public boolean accept(File pathname) { 546 if (!pathname.isFile() || !pathname.canExecute()) { 547 return false; 548 } 549 String name = pathname.getName().toLowerCase(); 550 if (isWindows) { 551 if (!name.endsWith(EXE_FILE_EXT)) { 552 return false; 553 } 554 } else if (isMacOSX) { 555 if (name.endsWith(MAC_DSYM_EXT)) { 556 return false; 557 } 558 } else { 559 if (name.endsWith(NIX_DBGINFO_EXT)) { 560 return false; 561 } 562 } 563 for (String x : exclude) { 564 if (name.endsWith(x)) { 565 return false; 566 } 567 } 568 return true; 569 } 570 } 571 572 /* 573 * A class to encapsulate the test results and stuff, with some ease 574 * of use methods to check the test results. 575 */ 576 static class TestResult { 577 PrintWriter status; 578 StringWriter sw; 579 int exitValue; 580 List<String> testOutput; 581 Map<String, String> env; 582 Throwable t; 583 boolean testStatus; 584 585 public TestResult(String str, int rv, List<String> oList, 586 Map<String, String> env, Throwable t) { 587 sw = new StringWriter(); 588 status = new PrintWriter(sw); 589 status.println("Executed command: " + str + "\n"); 590 exitValue = rv; 591 testOutput = oList; 592 this.env = env; 593 this.t = t; 594 testStatus = true; 595 } 596 597 void appendError(String x) { 598 testStatus = false; 599 testExitValue++; 600 status.println(TEST_PREFIX + x); 601 } 602 603 void indentStatus(String x) { 604 status.println(" " + x); 605 } 606 607 void checkNegative() { 608 if (exitValue == 0) { 609 appendError("test must not return 0 exit value"); 610 } 611 } 612 613 void checkPositive() { 614 if (exitValue != 0) { 615 appendError("test did not return 0 exit value"); 616 } 617 } 618 619 boolean isOK() { 620 return exitValue == 0; 621 } 622 623 boolean isZeroOutput() { 624 if (!testOutput.isEmpty()) { 625 appendError("No message from cmd please"); 626 return false; 627 } 628 return true; 629 } 630 631 boolean isNotZeroOutput() { 632 if (testOutput.isEmpty()) { 633 appendError("Missing message"); 634 return false; 635 } 636 return true; 637 } 638 639 @Override 640 public String toString() { 641 status.println("++++Begin Test Info++++"); 642 status.println("Test Status: " + (testStatus ? "PASS" : "FAIL")); 643 status.println("++++Test Environment++++"); 644 for (String x : env.keySet()) { 645 indentStatus(x + "=" + env.get(x)); 646 } 647 status.println("++++Test Output++++"); 648 for (String x : testOutput) { 649 indentStatus(x); 650 } 651 status.println("++++Test Stack Trace++++"); 652 status.println(t.toString()); 653 for (StackTraceElement e : t.getStackTrace()) { 654 indentStatus(e.toString()); 655 } 656 status.println("++++End of Test Info++++"); 657 status.flush(); 658 String out = sw.toString(); 659 status.close(); 660 return out; 661 } 662 663 boolean contains(String str) { 664 for (String x : testOutput) { 665 if (x.contains(str)) { 666 return true; 667 } 668 } 669 appendError("string <" + str + "> not found"); 670 return false; 671 } 672 673 boolean notContains(String str) { 674 for (String x : testOutput) { 675 if (x.contains(str)) { 676 appendError("string <" + str + "> found"); 677 return false; 678 } 679 } 680 return true; 681 } 682 683 boolean matches(String regexToMatch) { 684 for (String x : testOutput) { 685 if (x.matches(regexToMatch)) { 686 return true; 687 } 688 } 689 appendError("regex <" + regexToMatch + "> not matched"); 690 return false; 691 } 692 693 boolean notMatches(String regexToMatch) { 694 for (String x : testOutput) { 695 if (!x.matches(regexToMatch)) { 696 return true; 697 } 698 } 699 appendError("regex <" + regexToMatch + "> matched"); 700 return false; 701 } 702 } 703 /** 704 * Indicates that the annotated method is a test method. 705 */ 706 @Retention(RetentionPolicy.RUNTIME) 707 @Target(ElementType.METHOD) 708 public @interface Test {} 709 }