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 }