1 /*
   2  * Copyright (c) 2015, 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 package compiler.compilercontrol.share.scenario;
  25 
  26 import compiler.compilercontrol.share.method.MethodDescriptor;
  27 import compiler.compilercontrol.share.pool.PoolHelper;
  28 import compiler.compilercontrol.share.processors.CommandProcessor;
  29 import compiler.compilercontrol.share.processors.LogProcessor;
  30 import compiler.compilercontrol.share.processors.PrintDirectivesProcessor;
  31 import compiler.compilercontrol.share.processors.PrintProcessor;
  32 import jdk.test.lib.Asserts;
  33 import jdk.test.lib.process.OutputAnalyzer;
  34 import jdk.test.lib.util.Pair;
  35 
  36 import java.lang.reflect.Executable;
  37 import java.util.ArrayList;
  38 import java.util.Collections;
  39 import java.util.HashMap;
  40 import java.util.LinkedHashSet;
  41 import java.util.List;
  42 import java.util.Map;
  43 import java.util.Set;
  44 import java.util.concurrent.Callable;
  45 import java.util.function.Consumer;
  46 
  47 /**
  48  * Test scenario
  49  */
  50 public final class Scenario {
  51     private final boolean isValid;
  52     private final Map<Executable, State> states;
  53     private final List<Consumer<OutputAnalyzer>> processors;
  54     private final Executor executor;
  55     private final Consumer<List<OutputAnalyzer>> jcmdProcessor;
  56 
  57     private Scenario(boolean isValid,
  58                      List<String> vmopts,
  59                      Map<Executable, State> states,
  60                      List<CompileCommand> compileCommands,
  61                      List<JcmdCommand> jcmdCommands,
  62                      List<CompileCommand> directives) {
  63         this.isValid = isValid;
  64         this.states = states;
  65         processors = new ArrayList<>();
  66         processors.add(new LogProcessor(states));
  67         processors.add(new PrintProcessor(states));
  68         List<CompileCommand> nonQuieted = new ArrayList<>();
  69         List<CompileCommand> quieted = new ArrayList<>();
  70         boolean metQuiet = false;
  71         for (CompileCommand cc : compileCommands) {
  72             metQuiet |= cc.command == Command.QUIET;
  73             if (metQuiet) {
  74                 quieted.add(cc);
  75             } else {
  76                 nonQuieted.add(cc);
  77             }
  78         }
  79         processors.add(new CommandProcessor(nonQuieted, quieted));
  80         List<String> jcmdExecCommands = new ArrayList<>();
  81         boolean addCommandMet = false;
  82         boolean printCommandMet = false;
  83         for (JcmdCommand cmd : jcmdCommands) {
  84             switch (cmd.jcmdType) {
  85                 case ADD:
  86                     if (!addCommandMet) {
  87                         jcmdExecCommands.add(JcmdType.ADD.command);
  88                     }
  89                     addCommandMet = true;
  90                     break;
  91                 case PRINT:
  92                     printCommandMet = true;
  93                     break;
  94                 default:
  95                     jcmdExecCommands.add(cmd.jcmdType.command);
  96                     break;
  97             }
  98         }
  99         // Add print command only in the end to get directives printed
 100         if (printCommandMet) {
 101             jcmdExecCommands.add(JcmdType.PRINT.command);
 102         }
 103         jcmdProcessor = new PrintDirectivesProcessor(directives);
 104         executor = new Executor(isValid, vmopts, states, jcmdExecCommands);
 105     }
 106 
 107     /**
 108      * Executes scenario
 109      */
 110     public void execute() {
 111         List<OutputAnalyzer> outputList = executor.execute();
 112         // The first one contains output from the test VM
 113         OutputAnalyzer mainOuput = outputList.get(0);
 114         if (isValid) {
 115             mainOuput.shouldHaveExitValue(0);
 116             processors.forEach(processor -> processor.accept(mainOuput));
 117             // only the last output contains directives got from print command
 118             List<OutputAnalyzer> last = new ArrayList<>();
 119             last.add(outputList.get(outputList.size() - 1));
 120             jcmdProcessor.accept(last);
 121         } else {
 122             Asserts.assertNE(mainOuput.getExitValue(), 0, "VM should exit with "
 123                     + "error for incorrect directives");
 124             mainOuput.shouldContain("Parsing of compiler directives failed");
 125         }
 126     }
 127 
 128     /**
 129      * Gets states of methods for this scenario
 130      *
 131      * @return pairs of executable and its state
 132      */
 133     public Map<Executable, State> getStates() {
 134         return states;
 135     }
 136 
 137     public static enum Compiler {
 138         C1("c1"),
 139         C2("c2");
 140 
 141         public final String name;
 142 
 143         Compiler(String name) {
 144             this.name = name;
 145         }
 146     }
 147 
 148     /**
 149      * Type of diagnostic (jcmd) command
 150      */
 151     public static enum JcmdType {
 152         ADD("Compiler.directives_add " + Type.JCMD.fileName),
 153         PRINT("Compiler.directives_print"),
 154         CLEAR("Compiler.directives_clear"),
 155         REMOVE("Compiler.directives_remove");
 156 
 157         public final String command;
 158         private JcmdType(String command) {
 159             this.command = command;
 160         }
 161     }
 162 
 163     /**
 164      * Type of the compile command
 165      */
 166     public static enum Type {
 167         OPTION(""),
 168         FILE("command_file"),
 169         DIRECTIVE("directives.json"),
 170         JCMD("jcmd_directives.json") {
 171             @Override
 172             public CompileCommand createCompileCommand(Command command,
 173                     MethodDescriptor md, Compiler compiler) {
 174                 return new JcmdCommand(command, md, compiler, this,
 175                         JcmdType.ADD);
 176             }
 177         };
 178 
 179         public final String fileName;
 180 
 181         public CompileCommand createCompileCommand(Command command,
 182                 MethodDescriptor md, Compiler compiler) {
 183             return new CompileCommand(command, md, compiler, this);
 184         }
 185 
 186         private Type(String fileName) {
 187             this.fileName = fileName;
 188         }
 189     }
 190 
 191     public static Builder getBuilder() {
 192         return new Builder();
 193     }
 194 
 195     public static class Builder {
 196         private final Set<String> vmopts = new LinkedHashSet<>();
 197         private final Map<Type, StateBuilder<CompileCommand>> builders
 198                 = new HashMap<>();
 199         private final JcmdStateBuilder jcmdStateBuilder;
 200         private final List<JcmdCommand> jcmdCommands = new ArrayList<>();
 201         private boolean logCommandMet = false;
 202 
 203         public Builder() {
 204             addFlag("-Xmixed");
 205             builders.put(Type.FILE, new CommandFileBuilder(Type.FILE.fileName));
 206             builders.put(Type.OPTION, new CommandOptionsBuilder());
 207             builders.put(Type.DIRECTIVE, new DirectiveBuilder(
 208                     Type.DIRECTIVE.fileName));
 209             jcmdStateBuilder = new JcmdStateBuilder(Type.JCMD.fileName);
 210         }
 211 
 212         public void addFlag(String flag) {
 213             vmopts.add(flag);
 214         }
 215 
 216         public void add(CompileCommand compileCommand) {
 217             String[] vmOptions = compileCommand.command.vmOpts;
 218             Collections.addAll(vmopts, vmOptions);
 219             if (compileCommand.command == Command.LOG) {
 220                 logCommandMet = true;
 221             }
 222             if (compileCommand.type == Type.JCMD) {
 223                 jcmdStateBuilder.add((JcmdCommand) compileCommand);
 224                 jcmdCommands.add((JcmdCommand) compileCommand);
 225             } else {
 226                 StateBuilder<CompileCommand> builder = builders.get(
 227                         compileCommand.type);
 228                 if (builder == null) {
 229                     throw new Error("TESTBUG: Missing builder for the type: "
 230                             + compileCommand.type);
 231                 }
 232                 builder.add(compileCommand);
 233             }
 234         }
 235 
 236         public Scenario build() {
 237             boolean isValid = true;
 238 
 239             // Get states from each of the state builders
 240             Map<Executable, State> commandFileStates
 241                     = builders.get(Type.FILE).getStates();
 242             Map<Executable, State> commandOptionStates
 243                     = builders.get(Type.OPTION).getStates();
 244             Map<Executable, State> directiveFileStates
 245                     = builders.get(Type.DIRECTIVE).getStates();
 246 
 247             // check if directives stack was cleared by jcmd
 248             boolean isClearedState = false;
 249             if (jcmdContainsCommand(JcmdType.CLEAR)) {
 250                 isClearedState = true;
 251             }
 252 
 253             // Merge states
 254             List<Pair<Executable, Callable<?>>> methods = new PoolHelper()
 255                     .getAllMethods();
 256             Map<Executable, State> finalStates = new HashMap<>();
 257             Map<Executable, State> jcmdStates = jcmdStateBuilder.getStates();
 258             for (Pair<Executable, Callable<?>> pair : methods) {
 259                 Executable x = pair.first;
 260                 State commandOptionState = commandOptionStates.get(x);
 261                 State commandFileState = commandFileStates.get(x);
 262                 State st = State.merge(commandOptionState, commandFileState);
 263                 if (!isClearedState) {
 264                     State directiveState = directiveFileStates.get(x);
 265                     State jcmdState = jcmdStates.get(x);
 266                     if (jcmdState != null) {
 267                         st = State.merge(st, jcmdState);
 268                     } else if (directiveState != null) {
 269                         st = State.merge(st, directiveState);
 270                     }
 271                 }
 272                 finalStates.put(x, st);
 273             }
 274 
 275             /*
 276              * Create a list of commands from options and file
 277              * to handle quiet command
 278              */
 279             List<CompileCommand> ccList = new ArrayList<>();
 280             ccList.addAll(builders.get(Type.OPTION).getCompileCommands());
 281             ccList.addAll(builders.get(Type.FILE).getCompileCommands());
 282 
 283             // Create a list of directives to check which one was printed
 284             List<CompileCommand> directives = new ArrayList<>();
 285             if (jcmdContainsCommand(JcmdType.PRINT)) {
 286                 if (!isClearedState) {
 287                     directives.addAll(builders.get(Type.DIRECTIVE)
 288                             .getCompileCommands());
 289                 }
 290                 directives.addAll(jcmdStateBuilder.getCompileCommands());
 291             }
 292 
 293             // Get all VM options after we build all states and files
 294             List<String> options = new ArrayList<>();
 295             options.addAll(vmopts);
 296             for (StateBuilder<?> builder : builders.values()) {
 297                 options.addAll(builder.getOptions());
 298                 isValid &= builder.isValid();
 299             }
 300             options.addAll(jcmdStateBuilder.getOptions());
 301 
 302             /*
 303              * Update final states if LogCompilation is enabled and
 304              * there is no any log command, then all methods should be logged
 305              */
 306             boolean isLogComp = vmopts.stream()
 307                     .anyMatch(opt -> opt.contains("-XX:+LogCompilation"));
 308             if (isLogComp && !logCommandMet) {
 309                 finalStates.entrySet()
 310                         .forEach(entry -> entry.getValue().setLog(true));
 311             }
 312 
 313             return new Scenario(isValid, options, finalStates, ccList,
 314                     jcmdCommands, directives);
 315         }
 316 
 317         // shows if jcmd have passed a specified jcmd command type
 318         private boolean jcmdContainsCommand(JcmdType type) {
 319             for (JcmdCommand jcmdCommand : jcmdCommands) {
 320                 if (jcmdCommand.jcmdType == type) {
 321                     return true;
 322                 }
 323             }
 324             return false;
 325         }
 326     }
 327 }