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