1 /*
   2  * Copyright (c) 1998, 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 com.sun.javatest.regtest.exec;
  27 
  28 import java.io.BufferedReader;
  29 import java.io.BufferedWriter;
  30 import java.io.ByteArrayOutputStream;
  31 import java.io.File;
  32 import java.io.FileNotFoundException;
  33 import java.io.FileReader;
  34 import java.io.FileWriter;
  35 import java.io.IOException;
  36 import java.io.PrintStream;
  37 import java.io.PrintWriter;
  38 import java.io.StringReader;
  39 import java.lang.reflect.Constructor;
  40 import java.lang.reflect.InvocationTargetException;
  41 import java.lang.reflect.Method;
  42 import java.util.ArrayList;
  43 import java.util.Arrays;
  44 import java.util.Collections;
  45 import java.util.Iterator;
  46 import java.util.LinkedHashMap;
  47 import java.util.LinkedHashSet;
  48 import java.util.List;
  49 import java.util.Map;
  50 import java.util.Set;
  51 import java.util.concurrent.TimeUnit;
  52 
  53 import com.sun.javatest.Status;
  54 import com.sun.javatest.regtest.TimeoutHandler;
  55 import com.sun.javatest.regtest.agent.CompileActionHelper;
  56 import com.sun.javatest.regtest.agent.JDK_Version;
  57 import com.sun.javatest.regtest.agent.SearchPath;
  58 import com.sun.javatest.regtest.config.ExecMode;
  59 import com.sun.javatest.regtest.config.JDK;
  60 import com.sun.javatest.regtest.config.JDKOpts;
  61 import com.sun.javatest.regtest.config.Locations;
  62 import com.sun.javatest.regtest.config.Locations.LibLocn;
  63 import com.sun.javatest.regtest.config.Modules;
  64 import com.sun.javatest.regtest.config.ParseException;
  65 import com.sun.javatest.regtest.exec.RegressionScript.PathKind;
  66 import com.sun.javatest.regtest.util.StringUtils;
  67 
  68 import static com.sun.javatest.regtest.agent.RStatus.createStatus;
  69 import static com.sun.javatest.regtest.agent.RStatus.error;
  70 import static com.sun.javatest.regtest.agent.RStatus.failed;
  71 import static com.sun.javatest.regtest.agent.RStatus.normalize;
  72 import static com.sun.javatest.regtest.agent.RStatus.passed;
  73 
  74 /**
  75  * This class implements the "compile" action as described by the JDK tag
  76  * specification. It is also invoked implicitly as needed by the "build"
  77  * action.
  78  *
  79  * @author Iris A Garcia
  80  * @see Action
  81  * @see com.sun.javatest.regtest.agent.MainActionHelper
  82  */
  83 public class CompileAction extends Action {
  84     public static final String NAME = "compile";
  85 
  86     /**
  87      * {@inheritDoc}
  88      * @return "compile"
  89      */
  90     @Override
  91     public String getName() {
  92         return NAME;
  93     }
  94 
  95     /**
  96      * A method used by sibling classes to run both the init() and run()
  97      * method of CompileAction.
  98      *
  99      * @param destDir Where to place the compiled classes
 100      * @param opts The options for the action.
 101      * @param args The arguments for the actions.
 102      * @param reason Indication of why this action was invoked.
 103      * @param script The script.
 104      * @return     The result of the action.
 105      * @throws TestRunException if an error occurs while executing this action
 106      * @see #init
 107      * @see #run
 108      */
 109     Status compile(LibLocn libLocn, Map<String,String> opts, List<String> args, String reason,
 110             RegressionScript script) throws TestRunException {
 111         this.libLocn = libLocn;
 112         init(opts, args, reason, script);
 113         return run();
 114     } // compile()
 115 
 116     /**
 117      * This method does initial processing of the options and arguments for the
 118      * action.  Processing is determined by the requirements of run() and
 119      * getSourceFiles(). If run will be called, script.hasEnv() will be true.
 120      * If script.hasEnv() is false, there is no context available to determine
 121      * any class directories.
 122      *
 123      * Verify that the options are valid for the "compile" action.
 124      *
 125      * Verify that there is at least one argument.  Find the class names to
 126      * compile (via presence of ".java") and modify to contain fully qualified
 127      * path.
 128      *
 129      * If one of the JVM options is "-classpath" or "-cp", add the test classes
 130      * and test sources directory to the provided path.
 131      *
 132      * @param opts The options for the action.
 133      * @param args The arguments for the actions.
 134      * @param reason Indication of why this action was invoked.
 135      * @param script The script.
 136      * @exception  ParseException If the options or arguments are not expected
 137      *             for the action or are improperly formated.
 138      */
 139     @Override
 140     public void init(Map<String,String> opts, List<String> args, String reason,
 141                 RegressionScript script)
 142             throws ParseException {
 143         super.init(opts, args, reason, script);
 144 
 145         if (reason.startsWith(SREASON_USER_SPECIFIED))
 146             addDebugOpts = true;
 147 
 148         if (args.isEmpty())
 149             throw new ParseException(COMPILE_NO_CLASSNAME);
 150 
 151         for (Map.Entry<String,String> e: opts.entrySet()) {
 152             String optName  = e.getKey();
 153             String optValue = e.getValue();
 154 
 155             switch (optName) {
 156                 case "fail":
 157                     reverseStatus = parseFail(optValue);
 158                     break;
 159                 case "timeout":
 160                     timeout = parseTimeout(optValue);
 161                     break;
 162                 case "ref":
 163                     ref = parseRef(optValue);
 164                     break;
 165                 case "process":
 166                     process = true;
 167                     break;
 168                 case "module":
 169                     module = parseModule(optValue);
 170                     modules = Collections.singleton(module);
 171                     break;
 172                 case "modules":
 173                     if (optValue != null)
 174                         throw new ParseException(COMPILE_MODULES_UEXPECT + optValue);
 175                     multiModule = true;
 176                     modules = new LinkedHashSet<>();
 177                     break;
 178                 default:
 179                     throw new ParseException(COMPILE_BAD_OPT + optName);
 180             }
 181         }
 182 
 183         if (module != null && multiModule) {
 184             throw new ParseException("Bad combination of options: /module=" + module + ", /modules");
 185         }
 186 
 187         if (module == null && !multiModule)
 188             modules = Collections.<String>emptySet();
 189 
 190         if (timeout < 0)
 191             timeout = script.getActionTimeout(-1);
 192 
 193         // add absolute path name to all the .java files create appropriate
 194         // class directories
 195         Locations locations = script.locations;
 196         if (libLocn == null) {
 197             destDir = multiModule ? locations.absTestModulesDir() : locations.absTestClsDir(module);
 198         } else {
 199             destDir = (module == null) ? libLocn.absClsDir : new File(libLocn.absClsDir, module);
 200         }
 201         if (!script.isCheck())
 202             mkdirs(destDir);
 203 
 204         boolean foundJavaFile = false;
 205         boolean foundAsmFile = false;
 206 
 207         for (int i = 0; i < args.size(); i++) {
 208             // note: in the following code, some args are overrwritten in place
 209             String currArg = args.get(i);
 210 
 211             if (currArg.endsWith(".java")) {
 212                 foundJavaFile = true;
 213                 File sourceFile = new File(currArg.replace('/', File.separatorChar));
 214                 if (!sourceFile.isAbsolute()) {
 215                     // User must have used @compile, so file must be
 216                     // in the same directory as the defining file.
 217                     if (multiModule)
 218                         addModule(currArg);
 219                     File absSourceFile = locations.absTestSrcFile(module, sourceFile);
 220                     if (!absSourceFile.exists())
 221                         throw new ParseException(CANT_FIND_SRC + currArg);
 222                     args.set(i, absSourceFile.getPath());
 223                 }
 224             } else if (currArg.endsWith(".jasm") || currArg.endsWith("jcod")) {
 225                 if (module != null) {
 226                     throw new ParseException(COMPILE_OPT_DISALLOW);
 227                 }
 228                 foundAsmFile = true;
 229                 File sourceFile = new File(currArg.replace('/', File.separatorChar));
 230                 if (!sourceFile.isAbsolute()) {
 231                     // User must have used @compile, so file must be
 232                     // in the same directory as the defining file.
 233                     if (multiModule)
 234                         addModule(currArg);
 235                     File absSourceFile = locations.absTestSrcFile(null, sourceFile);
 236                     if (!absSourceFile.exists())
 237                         throw new ParseException(CANT_FIND_SRC + currArg);
 238                     args.set(i, absSourceFile.getPath());
 239                 }
 240             }
 241 
 242             if (currArg.equals("-classpath") || currArg.equals("-cp")
 243                     || currArg.equals("--class-path") || currArg.startsWith("--class-path=")) {
 244                 if (module != null || multiModule) {
 245                     throw new ParseException(COMPILE_OPT_DISALLOW);
 246                 }
 247                 classpathp = true;
 248                 if (!currArg.startsWith("--class-path=")) {
 249                     i++;
 250                 }
 251             } else if (currArg.equals("-sourcepath")
 252                     || currArg.equals("--source-path") || currArg.startsWith("--source-path=")) {
 253                 if (module != null || multiModule) {
 254                     throw new ParseException(COMPILE_OPT_DISALLOW);
 255                 }
 256                 sourcepathp = true;
 257                 if (!currArg.startsWith("--source-path=")) {
 258                     i++;
 259                 }
 260             } else if (currArg.equals("-d")) {
 261                 throw new ParseException(COMPILE_OPT_DISALLOW);
 262             }
 263         }
 264 
 265         if (!foundJavaFile && !process && !foundAsmFile) {
 266             throw new ParseException(COMPILE_NO_DOT_JAVA);
 267         }
 268         if (foundAsmFile) {
 269             if (sourcepathp || classpathp || process) {
 270                 throw new ParseException(COMPILE_OPT_DISALLOW);
 271             }
 272             if (reverseStatus || ref != null) {
 273                 throw new ParseException(COMPILE_OPT_DISALLOW);
 274             }
 275         }
 276     } // init()
 277 
 278     @Override
 279     public Set<File> getSourceFiles() {
 280         Set<File> files = new LinkedHashSet<>();
 281 
 282         for (String currArg : args) {
 283             if (currArg.endsWith(".java")
 284                     || currArg.endsWith(".jasm")
 285                     || currArg.endsWith(".jcod")) {
 286                 files.add(new File(currArg));
 287             }
 288         }
 289 
 290         return files;
 291     }
 292 
 293     @Override
 294     public Set<String> getModules() {
 295         return modules;
 296     }
 297 
 298     /**
 299      * The method that does the work of the action.  The necessary work for the
 300      * given action is defined by the tag specification.
 301      *
 302      * Invoke the compiler on the given arguments which may possibly include
 303      * compiler options.  Equivalent to "javac arg+".
 304      *
 305      * Each named class will be compiled if its corresponding class file doesn't
 306      * exist or is older than its source file.  The class name is fully
 307      * qualified as necessary and the ".java" extension is added before
 308      * compilation.
 309      *
 310      * Build is allowed to search anywhere in the library-list.  Compile is
 311      * allowed to search only in the directory containing the defining file of
 312      * the test.  Thus, compile will always make files absolute by adding the
 313      * directory path of the defining file to the passed filename.
 314      * Build must pass an absolute filename to handle files found in the
 315      * library-list.
 316      *
 317      * @return  The result of the action.
 318      * @throws  TestRunException If an unexpected error occurs while executing
 319      *          the action.
 320      */
 321     @Override
 322     public Status run() throws TestRunException {
 323         startAction(true);
 324 
 325         List<String> javacArgs = new ArrayList<>();
 326         List<String> jasmArgs = new ArrayList<>();
 327         List<String> jcodArgs = new ArrayList<>();
 328         boolean runJavac = process;
 329 
 330         for (String currArg : args) {
 331             if (currArg.endsWith(".java")) {
 332                 if (!(new File(currArg)).exists())
 333                     throw new TestRunException(CANT_FIND_SRC + currArg);
 334                 javacArgs.add(currArg);
 335                 runJavac = true;
 336             } else if (currArg.endsWith(".jasm")) {
 337                 jasmArgs.add(currArg);
 338             } else if (currArg.endsWith(".jcod")) {
 339                 jcodArgs.add(currArg);
 340             } else
 341                 javacArgs.add(currArg);
 342         }
 343 
 344         Status status;
 345 
 346         if (script.isCheck()) {
 347             status = passed(CHECK_PASS);
 348         } else {
 349             // run jasm and jcod first (if needed) in case the resulting class
 350             // files will be required when compiling the .java files.
 351             status = passed("Not yet run");
 352             if (status.isPassed() && !jasmArgs.isEmpty())
 353                 status = jasm(jasmArgs);
 354             if (status.isPassed() && !jcodArgs.isEmpty())
 355                 status = jcod(jcodArgs);
 356             if (status.isPassed() && runJavac) {
 357                 javacArgs = getJavacCommandArgs(javacArgs);
 358                 if (explicitAnnotationProcessingRequested(javacArgs)
 359                         && !getExtraModuleConfigOptions(Modules.Phase.DYNAMIC).isEmpty()) {
 360                     othervmOverrideReasons.add("additional runtime exports needed for annotation processing");
 361                 }
 362                 switch (!othervmOverrideReasons.isEmpty() ? ExecMode.OTHERVM : script.getExecMode()) {
 363                     case AGENTVM:
 364                         showMode(ExecMode.AGENTVM);
 365                         status = runAgentJVM(javacArgs);
 366                         break;
 367                     case OTHERVM:
 368                         showMode(ExecMode.OTHERVM, othervmOverrideReasons);
 369                         status = runOtherJVM(javacArgs);
 370                         break;
 371                     default:
 372                         throw new AssertionError();
 373                 }
 374             }
 375         }
 376 
 377         endAction(status);
 378         return status;
 379     } // run()
 380 
 381     //----------internal methods------------------------------------------------
 382 
 383     private Status jasm(List<String> files) {
 384         return asmtools("jasm", files);
 385     }
 386 
 387     private Status jcod(List<String> files) {
 388         return asmtools("jcoder", files);
 389     }
 390 
 391     private Status asmtools(String toolName, List<String> files) {
 392         if (files.isEmpty())
 393             return Status.passed(toolName + ": no files");
 394 
 395         List<String> toolArgs = new ArrayList<>();
 396         toolArgs.add("-d");
 397         toolArgs.add(destDir.getPath());
 398         toolArgs.addAll(files);
 399         try {
 400             String toolClassName = "org.openjdk.asmtools." + toolName + ".Main";
 401             recorder.asmtools(toolClassName, toolArgs);
 402             Class<?> toolClass = Class.forName(toolClassName);
 403             Constructor<?> c = toolClass.getConstructor(new Class<?>[] { PrintStream.class, String.class });
 404             ByteArrayOutputStream baos = new ByteArrayOutputStream();
 405             PrintStream ps = new PrintStream(baos);
 406             try {
 407                 Object tool = c.newInstance(ps, toolName);
 408                 Method m = toolClass.getMethod("compile", new Class<?>[] { String[].class });
 409                 Object r = m.invoke(tool, new Object[] { toolArgs.toArray(new String[0]) });
 410                 if (r instanceof Boolean) {
 411                     boolean ok = (Boolean) r;
 412                     return ok ? Status.passed(toolName + " OK") : Status.failed(toolName + " failed");
 413                 } else
 414                     return Status.error("unexpected result from " + toolName + ": " + r.toString());
 415             } finally {
 416                 try (PrintWriter out = section.createOutput(toolName)) {
 417                     out.write(baos.toString());
 418                 }
 419             }
 420         } catch (ClassNotFoundException e) {
 421             return Status.error("can't find " + toolName);
 422         } catch (NoSuchMethodException e) {
 423             return Status.error("error invoking " + toolName + ": " + e);
 424         } catch (InstantiationException e) {
 425             return Status.error("error invoking " + toolName + ": " + e);
 426         } catch (IllegalAccessException e) {
 427             return Status.error("error invoking " + toolName + ": " + e);
 428         } catch (InvocationTargetException e) {
 429             return Status.error("error invoking " + toolName + ": " + e);
 430         } catch (IllegalArgumentException t) {
 431             return Status.error("error invoking " + toolName + ": " + t);
 432         } catch (SecurityException t) {
 433             return Status.error("error invoking " + toolName + ": " + t);
 434         }
 435     }
 436 
 437     /**
 438      * Determine the arguments for the compilation.
 439      * Three different types of compilation are supported.
 440      * <ul>
 441      * <li>Compilation of classes in the unnamed module.
 442      *     This is the default "classic" compilation.
 443      *     The output directory should be a package-oriented directory.
 444      *     Sources and classes for the unnamed module are put on the
 445      *     sourcepath and classpath.
 446      * <li>Compilation of classes in a single named user module.
 447      *     This mode is indicated by the option /module=module-name
 448      *     where module-name is not the name of a system module.
 449      *     The output directory should be the appropriate subdirectory
 450      *     of a module-oriented directory.
 451      *     The output directory should appear on the classpath.
 452      *     Sources and classes for the unnamed module are <i>not</i> available.
 453      * <li>Compilation of classes to patch those in a system module.
 454      *     This mode is indicated by the option /module=module-name
 455      *     where module-name is the name of a system module.
 456      * <li>Compilation of classes in one or more named user modules.
 457      *     This mode is indicated by the option /modules.
 458      *     The output directory should be a module-oriented directory.
 459      *     Sources and classes for the unnamed module are put on the
 460      *     sourcepath and classpath.
 461      * </ul>
 462      */
 463     private List<String> getJavacCommandArgs(List<String> args) throws TestRunException {
 464         Map<PathKind, SearchPath> compilePaths = script.getCompilePaths(libLocn, multiModule, module);
 465 
 466         JDKOpts javacArgs = new JDKOpts();
 467         javacArgs.addAll(script.getTestCompilerOptions());
 468 
 469         if (isModuleOptionsAllowed(args)) {
 470             javacArgs.addAll(getExtraModuleConfigOptions(Modules.Phase.STATIC));
 471         }
 472 
 473         if (destDir != null) {
 474             javacArgs.add("-d");
 475             javacArgs.add(destDir.toString());
 476         }
 477 
 478         if (module != null && script.systemModules.contains(module)
 479                 && !script.useNewPatchModule()) {
 480             javacArgs.add("-Xmodule:" + module);
 481         }
 482 
 483         // modulesourcepath and sourcepath are mutually exclusive
 484         if (multiModule) {
 485             javacArgs.addPath("--module-source-path", compilePaths.get(PathKind.MODULESOURCEPATH));
 486         } else if (module != null && script.useNewPatchModule()) {
 487             // Note: any additional patches for this module will be
 488             // automatically merged with this one.
 489             javacArgs.addAll("--patch-module", module + "=" + compilePaths.get(PathKind.SOURCEPATH));
 490         } else {
 491             javacArgs.addPath("--source-path", compilePaths.get(PathKind.SOURCEPATH));
 492         }
 493 
 494         // Need to refine what it means to put absTestClsDir unconditionally on the compilePath
 495         SearchPath cp = compilePaths.get(PathKind.CLASSPATH);
 496         javacArgs.addPath("--class-path", cp);
 497 
 498         javacArgs.addPath("--module-path", compilePaths.get(PathKind.MODULEPATH));
 499 
 500         SearchPath pp = compilePaths.get(PathKind.PATCHPATH);
 501         javacArgs.addAllPatchModules(pp); // will merge as needed with any similar preceding options
 502         if (pp != null && !pp.isEmpty() && cp != null && !cp.isEmpty()) {
 503             // provide addReads from patch modules to unnamed module(s).
 504             for (String s: getModules(pp)) {
 505                 javacArgs.add("--add-reads=" + s + "=ALL-UNNAMED");
 506             }
 507         }
 508 
 509         Set<String> userMods = getModules(compilePaths.get(PathKind.MODULEPATH));
 510         if (!userMods.isEmpty()) {
 511             javacArgs.add("--add-modules");
 512             javacArgs.add(StringUtils.join(userMods, ","));
 513         }
 514 
 515         javacArgs.addAll(args);
 516 
 517         return javacArgs.toList();
 518     }
 519 
 520     private boolean isModuleOptionsAllowed(List<String> args) {
 521         Iterator<String> iter = args.iterator();
 522         while (iter.hasNext()) {
 523             String option = iter.next();
 524             switch (option) {
 525                 case "-source":
 526                 case "-target":
 527                 case "--release":
 528                     if (iter.hasNext()) {
 529                         JDK_Version v = JDK_Version.forName(iter.next());
 530                         return v.compareTo(JDK_Version.V9) >= 0;
 531                     }
 532                     break;
 533 
 534                 default:
 535                     if (option.startsWith("--release=")) {
 536                         JDK_Version v = JDK_Version.forName(
 537                                 option.substring(option.indexOf("=") + 1));
 538                         return v.compareTo(JDK_Version.V9) >= 0;
 539                     }
 540                     break;
 541             }
 542         }
 543         return true;
 544     }
 545 
 546     private Status runOtherJVM(List<String> javacArgs) throws TestRunException {
 547         Status status;
 548 
 549         // Set test.src and test.classes for the benefit of annotation processors
 550         Map<String, String> javacProps = script.getTestProperties();
 551 
 552         // CONSTRUCT THE COMMAND LINE
 553         Map<String, String> env = new LinkedHashMap<>();
 554         env.putAll(script.getEnvVars());
 555 
 556         String javacCmd = script.getJavacProg();
 557 
 558         JDKOpts javacVMOpts = new JDKOpts();
 559         javacVMOpts.addAll(script.getTestVMOptions());
 560         if (addDebugOpts && script.getCompileJDK().equals(script.getTestJDK()))
 561             javacVMOpts.addAll(script.getTestDebugOptions());
 562 
 563         if (explicitAnnotationProcessingRequested(javacArgs)) {
 564             javacVMOpts.addAll(getExtraModuleConfigOptions(Modules.Phase.DYNAMIC));
 565         }
 566 
 567         // WRITE ARGUMENT FILE
 568         List<String> fullJavacArgs = javacArgs;
 569         if (javacArgs.size() >= 10) {
 570             File argFile = getArgFile();
 571             try (BufferedWriter w = new BufferedWriter(new FileWriter(argFile))) {
 572                 for (String arg: javacArgs) {
 573                     w.write(arg);
 574                     w.newLine();
 575                 }
 576             } catch (IOException e) {
 577                 return error(COMPILE_CANT_WRITE_ARGS);
 578             } catch (SecurityException e) {
 579                 // shouldn't happen since JavaTestSecurityManager allows file ops
 580                 return error(COMPILE_SECMGR_FILEOPS);
 581             }
 582             javacArgs = Arrays.asList("@" + argFile);
 583         }
 584 
 585         List<String> command = new ArrayList<>();
 586         command.add(javacCmd);
 587         for (String opt: javacVMOpts.toList())
 588             command.add("-J" + opt);
 589         for (Map.Entry<String,String> e: javacProps.entrySet())
 590             command.add("-J-D" + e.getKey() + "=" + e.getValue());
 591         command.addAll(javacArgs);
 592 
 593         if (showMode)
 594             showMode("compile", ExecMode.OTHERVM, section);
 595         if (showCmd)
 596             showCmd("compile", command, section);
 597 
 598         new ModuleConfig("Boot Layer (javac runtime environment)")
 599                 .setFromOpts(javacVMOpts)
 600                 .write(configWriter);
 601         new ModuleConfig("javac compilation environment")
 602                 .setFromOpts(fullJavacArgs)
 603                 .write(configWriter);
 604         recorder.javac(env, javacCmd, javacVMOpts.toList(), javacProps, javacArgs);
 605 
 606         // PASS TO PROCESSCOMMAND
 607         PrintStringWriter stdOut = new PrintStringWriter();
 608         PrintStringWriter stdErr = new PrintStringWriter();
 609         ProcessCommand cmd = new ProcessCommand() {
 610             @Override
 611             protected Status getStatus(int exitCode, Status logStatus) {
 612                 // logStatus is never used by javac, so ignore it
 613                 JDK_Version v = script.getCompileJDKVersion();
 614                 return CompileActionHelper.getStatusForJavacExitCode(v, exitCode);
 615             }
 616         };
 617 
 618         TimeoutHandler timeoutHandler =
 619             script.getTimeoutHandlerProvider().createHandler(script, section);
 620 
 621         cmd.setExecDir(script.absTestScratchDir())
 622             .setCommand(command)
 623             .setEnvironment(env)
 624             .setStreams(stdOut, stdErr)
 625             .setTimeout(timeout, TimeUnit.SECONDS)
 626             .setTimeoutHandler(timeoutHandler);
 627 
 628         status = normalize(cmd.exec());
 629 
 630         try (PrintWriter sysOut = section.createOutput("System.out")) {
 631             sysOut.write(stdOut.getOutput());
 632         }
 633 
 634         try (PrintWriter sysErr = section.createOutput("System.err")) {
 635             sysErr.write(stdErr.getOutput());
 636         }
 637 
 638         // EVALUATE THE RESULTS
 639         status = checkReverse(status, reverseStatus);
 640 
 641         // COMPARE OUTPUT TO GOLDENFILE IF REQUIRED
 642         // tag-spec says that "standard error is redirected to standard out
 643         // so that /ref can be used."  Simulate this by concatenating streams.
 644         if ((ref != null) && status.isPassed()) {
 645             String combined = stdOut.getOutput() + stdErr.getOutput();
 646             status = checkGoldenFile(combined, status);
 647         }
 648 
 649         return status;
 650     } // runOtherJVM()
 651 
 652     private Status runAgentJVM(List<String> javacArgs) throws TestRunException {
 653         // TAG-SPEC:  "The source and class directories of a test are made
 654         // available to main and applet actions via the system properties
 655         // "test.src" and "test.classes", respectively"
 656         Map<String, String> javacProps = script.getTestProperties();
 657 
 658         if (showMode)
 659             showMode("compile", ExecMode.AGENTVM, section);
 660         if (showCmd)
 661             showCmd("compile", javacArgs, section);
 662 
 663         String javacProg = script.getJavacProg();
 664         List<String> javacVMOpts = script.getTestVMJavaOptions();
 665         recorder.javac(script.getEnvVars(), javacProg, javacVMOpts, javacProps, javacArgs);
 666 
 667         Agent agent;
 668         try {
 669             JDK jdk = script.getCompileJDK();
 670             SearchPath agentClasspath = new SearchPath(jdk.getJDKClassPath(), script.getJavaTestClassPath());
 671             List<String> vmOpts = addDebugOpts && jdk.equals(script.getTestJDK())
 672                     ? join(script.getTestVMOptions(), script.getTestDebugOptions())
 673                     : script.getTestVMOptions();
 674             agent = script.getAgent(jdk, agentClasspath, vmOpts);
 675             section.getMessageWriter().println("Agent id: " + agent.getId());
 676             new ModuleConfig("Boot Layer (javac runtime environment)")
 677                     .setFromOpts(agent.vmOpts)
 678                     .write(configWriter);
 679         } catch (Agent.Fault e) {
 680             return error(AGENTVM_CANT_GET_VM + ": " + e.getCause());
 681         }
 682 
 683         TimeoutHandler timeoutHandler =
 684             script.getTimeoutHandlerProvider().createHandler(script, section);
 685 
 686         Status status;
 687         try {
 688             new ModuleConfig("javac compilation environment")
 689                     .setFromOpts(javacArgs)
 690                     .write(configWriter);
 691             status = agent.doCompileAction(
 692                     script.getTestResult().getTestName(),
 693                     javacProps,
 694                     javacArgs,
 695                     timeout,
 696                     timeoutHandler,
 697                     section);
 698         } catch (Agent.Fault e) {
 699             if (e.getCause() instanceof IOException)
 700                 status = error(String.format(AGENTVM_IO_EXCEPTION, e.getCause()));
 701             else
 702                 status = error(String.format(AGENTVM_EXCEPTION, e.getCause()));
 703         }
 704         if (status.isError()) {
 705             script.closeAgent(agent);
 706         }
 707 
 708         // EVALUATE THE RESULTS
 709         status = checkReverse(status, reverseStatus);
 710 
 711         // COMPARE OUTPUT TO GOLDENFILE IF REQUIRED
 712         // tag-spec says that "standard error is redirected to standard out
 713         // so that /ref can be used."  Simulate this by concatenating streams.
 714         if ((ref != null) && status.isPassed()) {
 715             String outString = getOutput(OutputHandler.OutputKind.DIRECT);
 716             String errString = getOutput(OutputHandler.OutputKind.DIRECT_LOG);
 717             String stdOutString = getOutput(OutputHandler.OutputKind.STDOUT);
 718             String stdErrString = getOutput(OutputHandler.OutputKind.STDERR);
 719             String combined = (outString + errString + stdOutString + stdErrString);
 720             status = checkGoldenFile(combined, status);
 721         }
 722 
 723         return status;
 724     } // runAgentJVM()
 725 
 726     private String getOutput(OutputHandler.OutputKind kind) {
 727         String s = section.getOutput(kind.name);
 728         return (s == null) ? "" : s;
 729     }
 730 
 731     // See JavaCompiler.explicitAnnotationProcessingRequested
 732     private boolean explicitAnnotationProcessingRequested(List<String> javacArgs) {
 733         for (String arg: javacArgs) {
 734             if (arg.equals("-processor")
 735                     || arg.equals("-processorpath")
 736                     || arg.equals("-processormodulepath")
 737                     || arg.equals("-proc:only")
 738                     || arg.equals("-Xprint")) {
 739                 return true;
 740             }
 741         }
 742         return false;
 743     }
 744 
 745     //----------internal methods------------------------------------------------
 746 
 747     /**
 748      * This method parses the <em>ref</em> action option used by the compile
 749      * action. It verifies that the indicated reference file exists in the
 750      * directory containing the defining file of the test.
 751      *
 752      * @param value The proposed filename for the reference file.
 753      * @return     A string indicating the name of the reference file for the
 754      *             test.
 755      * @exception  ParseException If the passed filename is null, the empty
 756      *             string, or does not exist.
 757      */
 758     private String parseRef(String value) throws ParseException {
 759         if ((value == null) || (value.equals("")))
 760             throw new ParseException(COMPILE_NO_REF_NAME);
 761         File refFile = new File(script.absTestSrcDir(), value);
 762         if (!refFile.exists())
 763             throw new ParseException(COMPILE_CANT_FIND_REF + refFile);
 764         return value;
 765     } // parseRef()
 766 
 767     private Status checkReverse(Status status, boolean reverseStatus) {
 768         if (!status.isError()) {
 769             boolean ok = status.isPassed();
 770             int st = status.getType();
 771             String sr;
 772             if (ok && reverseStatus) {
 773                 sr = COMPILE_PASS_UNEXPECT;
 774                 st = Status.FAILED;
 775             } else if (ok && !reverseStatus) {
 776                 sr = COMPILE_PASS;
 777             } else if (!ok && reverseStatus) {
 778                 sr = COMPILE_FAIL_EXPECT;
 779                 st = Status.PASSED;
 780             } else { /* !ok && !reverseStatus */
 781                 sr = COMPILE_FAIL;
 782             }
 783             if ((st == Status.FAILED) && ! (status.getReason() == null) &&
 784                     !status.getReason().equals(EXEC_PASS))
 785                 sr += ": " + status.getReason();
 786             status = createStatus(st, sr);
 787         }
 788         return status;
 789     }
 790 
 791     /**
 792      * Compare output against a reference file.
 793      * @param status default result if no differences found
 794      * @param actual the text to be compared against the reference file
 795      * @return a status indicating the first difference, or the default status
 796      *          if no differences found
 797      * @throws TestRunException if the reference file can't be found
 798      */
 799     private Status checkGoldenFile(String actual, Status status) throws TestRunException {
 800         File refFile = new File(script.absTestSrcDir(), ref);
 801         try (BufferedReader r1 = new BufferedReader(new StringReader(actual));
 802             BufferedReader r2 = new BufferedReader(new FileReader(refFile)) ) {
 803             int lineNum;
 804             if ((lineNum = compareGoldenFile(r1, r2)) != 0) {
 805                 return failed(COMPILE_GOLD_FAIL + ref +
 806                         COMPILE_GOLD_LINE + lineNum);
 807             }
 808             return status;
 809         } catch (FileNotFoundException e) {
 810             throw new TestRunException(COMPILE_CANT_FIND_REF + refFile);
 811         } catch (IOException e) {
 812             throw new TestRunException(COMPILE_CANT_READ_REF + refFile);
 813         }
 814     }
 815 
 816     /**
 817      * Line by line comparison of compile output and a reference file.  If no
 818      * differences are found, then 0 is returned.  Otherwise, the line number
 819      * where differences are first detected is returned.
 820      *
 821      * @param r1   The first item for comparison.
 822      * @param r2   The second item for comparison.
 823      * @return 0   If no differences are returned.  Otherwise, the line number
 824      *             where differences were first detected.
 825      */
 826     private int compareGoldenFile(BufferedReader r1, BufferedReader r2)
 827     throws TestRunException {
 828         try {
 829             int lineNum = 0;
 830             for ( ; ; ) {
 831                 String s1 = r1.readLine();
 832                 String s2 = r2.readLine();
 833                 lineNum++;
 834 
 835                 if ((s1 == null) && (s2 == null))
 836                     return 0;
 837                 if ((s1 == null) || (s2 == null) || !s1.equals(s2)) {
 838                     return lineNum;
 839                 }
 840             }
 841         } catch (IOException e) {
 842             File refFile = new File(script.absTestSrcDir(), ref);
 843             throw new TestRunException(COMPILE_GOLD_READ_PROB + refFile);
 844         }
 845     } // compareGoldenFile()
 846 
 847     private void addModule(String file) {
 848         int sep = file.indexOf('/');
 849         if (sep > 0)
 850             modules.add(file.substring(0, sep));
 851     }
 852 
 853     //----------member variables------------------------------------------------
 854 
 855     private LibLocn libLocn;
 856     private File destDir;
 857 
 858     private boolean reverseStatus = false;
 859     private String  ref = null;
 860     private int     timeout = -1;
 861     private boolean classpathp  = false;
 862     private boolean sourcepathp = false;
 863     private boolean process = false;
 864     private String module = null;
 865     private boolean multiModule = false;
 866     private Set<String> modules;
 867     private boolean addDebugOpts = false;
 868     protected Set<String> othervmOverrideReasons = new LinkedHashSet<>();
 869 }