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.actions.BaseAction;
  27 import compiler.compilercontrol.share.method.MethodDescriptor;
  28 import compiler.compilercontrol.share.processors.CommandProcessor;
  29 import compiler.compilercontrol.share.processors.LogProcessor;
  30 import compiler.compilercontrol.share.processors.PrintProcessor;
  31 import compiler.compilercontrol.share.processors.QuietProcessor;
  32 import jdk.test.lib.Asserts;
  33 import jdk.test.lib.OutputAnalyzer;
  34 import jdk.test.lib.Pair;
  35 import jdk.test.lib.ProcessTools;
  36 import jdk.test.lib.dcmd.CommandExecutorException;
  37 import jdk.test.lib.dcmd.JcmdExecutor;
  38 import pool.PoolHelper;
  39 
  40 import java.io.BufferedReader;
  41 import java.io.IOException;
  42 import java.io.InputStreamReader;
  43 import java.io.PrintWriter;
  44 import java.lang.reflect.Executable;
  45 import java.net.ServerSocket;
  46 import java.net.Socket;
  47 import java.util.ArrayList;
  48 import java.util.Arrays;
  49 import java.util.Collections;
  50 import java.util.HashMap;
  51 import java.util.LinkedHashSet;
  52 import java.util.List;
  53 import java.util.Map;
  54 import java.util.Set;
  55 import java.util.concurrent.Callable;
  56 import java.util.function.Consumer;
  57 
  58 /**
  59  * Test scenario
  60  */
  61 public final class Scenario {
  62     private final boolean isValid;
  63     private final List<String> vmopts;
  64     private final Map<Executable, State> states;
  65     private final List<Consumer<OutputAnalyzer>> processors;
  66     private final List<String> jcmdExecCommands;
  67 
  68     private Scenario(boolean isValid,
  69                      List<String> vmopts,
  70                      Map<Executable, State> states,
  71                      List<CompileCommand> compileCommands,
  72                      List<JcmdCommand> jcmdCommands) {
  73         this.isValid = isValid;
  74         this.vmopts = vmopts;
  75         this.states = states;
  76         processors = new ArrayList<>();
  77         processors.add(new LogProcessor(states));
  78         processors.add(new PrintProcessor(states));
  79         List<CompileCommand> nonQuieted = new ArrayList<>();
  80         List<CompileCommand> quieted = new ArrayList<>();
  81         boolean metQuiet = false;
  82         for (CompileCommand cc : compileCommands) {
  83             metQuiet |= cc.command == Command.QUIET;
  84             if (metQuiet) {
  85                 quieted.add(cc);
  86             } else {
  87                 nonQuieted.add(cc);
  88             }
  89         }
  90         processors.add(new CommandProcessor(nonQuieted));
  91         processors.add(new QuietProcessor(quieted));
  92         jcmdExecCommands = new ArrayList<>();
  93         boolean addCommandMet = false;
  94         for (JcmdCommand cmd : jcmdCommands) {
  95             switch (cmd.jcmdType) {
  96                 case ADD:
  97                     if (!addCommandMet) {
  98                         jcmdExecCommands.add(JcmdType.ADD.command);
  99                     }
 100                     addCommandMet = true;
 101                     break;
 102                 default:
 103                     jcmdExecCommands.add(cmd.jcmdType.command);
 104                     break;
 105             }
 106         }
 107     }
 108 
 109     /**
 110      * Executes scenario
 111      */
 112     public void execute() {
 113         // Construct execution command with CompileCommand and class
 114         List<String> argsList = new ArrayList<>();
 115         // Add VM options
 116         argsList.addAll(vmopts);
 117         // Add class name that would be executed in a separate VM
 118         String classCmd = BaseAction.class.getName();
 119         argsList.add(classCmd);
 120         OutputAnalyzer output;
 121         try (ServerSocket serverSocket = new ServerSocket(0)) {
 122             if (isValid) {
 123                 // Get port test VM will connect to
 124                 int port = serverSocket.getLocalPort();
 125                 if (port == -1) {
 126                     throw new Error("Socket is not bound: " + port);
 127                 }
 128                 argsList.add(String.valueOf(port));
 129                 // Start separate thread to connect with test VM
 130                 new Thread(() -> connectTestVM(serverSocket)).start();
 131             }
 132             // Start test VM
 133             output = ProcessTools.executeTestJvmAllArgs(
 134                     argsList.toArray(new String[argsList.size()]));
 135         } catch (Throwable thr) {
 136             throw new Error("Execution failed", thr);
 137         }
 138         if (isValid) {
 139             output.shouldHaveExitValue(0);
 140             for (Consumer<OutputAnalyzer> processor : processors) {
 141                 processor.accept(output);
 142             }
 143         } else {
 144             Asserts.assertNE(output.getExitValue(), 0, "VM should exit with "
 145                     + "error for incorrect directives");
 146             output.shouldContain("Parsing of compiler directives failed");
 147         }
 148     }
 149 
 150     /*
 151      * Performs connection with a test VM, sends method states and performs
 152      * JCMD operations on a test VM.
 153      */
 154     private void connectTestVM(ServerSocket serverSocket) {
 155         /*
 156          * There are no way to prove that accept was invoked before we started
 157          * test VM that connects to this serverSocket. Connection timeout is
 158          * enough
 159          */
 160         try (
 161                 Socket socket = serverSocket.accept();
 162                 PrintWriter pw = new PrintWriter(socket.getOutputStream(),
 163                         true);
 164                 BufferedReader in = new BufferedReader(new InputStreamReader(
 165                         socket.getInputStream()))) {
 166             // Get pid of the executed process
 167             int pid = Integer.parseInt(in.readLine());
 168             Asserts.assertNE(pid, 0, "Got incorrect pid");
 169             executeJCMD(pid);
 170             // serialize and send state map
 171             for (Executable x : states.keySet()) {
 172                 pw.println("{");
 173                 pw.println(x.toGenericString());
 174                 pw.println(states.get(x).toString());
 175                 pw.println("}");
 176             }
 177         } catch (IOException e) {
 178             throw new Error("Failed to write data", e);
 179         }
 180     }
 181 
 182     // Executes all diagnostic commands
 183     private void executeJCMD(int pid) {
 184         for (String command : jcmdExecCommands) {
 185             new JcmdExecutor() {
 186                 @Override
 187                 protected List<String> createCommandLine(String cmd)
 188                         throws CommandExecutorException {
 189                     return Arrays.asList(jcmdBinary, Integer.toString(pid),
 190                             cmd);
 191                 }
 192             }.execute(command);
 193         }
 194     }
 195 
 196     /**
 197      * Gets states of methods for this scenario
 198      *
 199      * @return pairs of executable and its state
 200      */
 201     public Map<Executable, State> getStates() {
 202         return states;
 203     }
 204 
 205     public static enum Compiler {
 206         C1("c1"),
 207         C2("c2");
 208 
 209         public final String name;
 210 
 211         Compiler(String name) {
 212             this.name = name;
 213         }
 214     }
 215 
 216     /**
 217      * Type of diagnostic (jcmd) command
 218      */
 219     public static enum JcmdType {
 220         ADD("Compiler.directives_add " + Type.JCMD.fileName),
 221         PRINT("Compiler.directives_print"),
 222         CLEAR("Compiler.directives_clear"),
 223         REMOVE("Compiler.directives_remove");
 224 
 225         public final String command;
 226         private JcmdType(String command) {
 227             this.command = command;
 228         }
 229     }
 230 
 231     /**
 232      * Type of the compile command
 233      */
 234     public static enum Type {
 235         OPTION(""),
 236         FILE("command_file"),
 237         DIRECTIVE("directives.json"),
 238         JCMD("jcmd_directives.json") {
 239             @Override
 240             public CompileCommand createCompileCommand(Command command,
 241                     MethodDescriptor md, Compiler compiler) {
 242                 return new JcmdCommand(command, md, compiler, this,
 243                         JcmdType.ADD);
 244             }
 245         };
 246 
 247         public final String fileName;
 248 
 249         public CompileCommand createCompileCommand(Command command,
 250                 MethodDescriptor md, Compiler compiler) {
 251             return new CompileCommand(command, md, compiler, this);
 252         }
 253 
 254         private Type(String fileName) {
 255             this.fileName = fileName;
 256         }
 257     }
 258 
 259     public static Builder getBuilder() {
 260         return new Builder();
 261     }
 262 
 263     public static class Builder {
 264         private final Set<String> vmopts = new LinkedHashSet<>();
 265         private final Map<Type, StateBuilder<CompileCommand>> builders
 266                 = new HashMap<>();
 267         private final JcmdStateBuilder jcmdStateBuilder;
 268 
 269         public Builder() {
 270             builders.put(Type.FILE, new CommandFileBuilder(Type.FILE.fileName));
 271             builders.put(Type.OPTION, new CommandOptionsBuilder());
 272             builders.put(Type.DIRECTIVE, new DirectiveBuilder(
 273                     Type.DIRECTIVE.fileName));
 274             jcmdStateBuilder = new JcmdStateBuilder(Type.JCMD.fileName);
 275         }
 276 
 277         public void add(CompileCommand compileCommand) {
 278             String[] vmOptions = compileCommand.command.vmOpts;
 279             Collections.addAll(vmopts, vmOptions);
 280             if (compileCommand.type == Type.JCMD) {
 281                 jcmdStateBuilder.add((JcmdCommand) compileCommand);
 282             } else {
 283                 StateBuilder<CompileCommand> builder = builders.get(
 284                         compileCommand.type);
 285                 if (builder == null) {
 286                     throw new Error("TESTBUG: Missing builder for the type: "
 287                             + compileCommand.type);
 288                 }
 289                 builder.add(compileCommand);
 290             }
 291         }
 292 
 293         public Scenario build() {
 294             boolean isValid = true;
 295 
 296             // Get states from each of the state builders
 297             Map<Executable, State> commandFileStates
 298                     = builders.get(Type.FILE).getStates();
 299             Map<Executable, State> commandOptionStates
 300                     = builders.get(Type.OPTION).getStates();
 301             Map<Executable, State> directiveFileStates
 302                     = builders.get(Type.DIRECTIVE).getStates();
 303 
 304             // get all jcmd commands
 305             List<JcmdCommand> jcmdCommands = jcmdStateBuilder
 306                     .getCompileCommands();
 307             boolean isClearedState = false;
 308             if (jcmdClearedState(jcmdCommands)) {
 309                 isClearedState = true;
 310             }
 311 
 312             // Merge states
 313             List<Pair<Executable, Callable<?>>> methods = new PoolHelper()
 314                     .getAllMethods();
 315             Map<Executable, State> finalStates = new HashMap<>();
 316             Map<Executable, State> jcmdStates = jcmdStateBuilder.getStates();
 317             for (Pair<Executable, Callable<?>> pair : methods) {
 318                 Executable x = pair.first;
 319                 State commandOptionState = commandOptionStates.get(x);
 320                 State commandFileState = commandFileStates.get(x);
 321                 State st = State.merge(commandOptionState, commandFileState);
 322                 if (!isClearedState) {
 323                     State directiveState = directiveFileStates.get(x);
 324                     if (directiveState != null) {
 325                         st = directiveState;
 326                     }
 327                 }
 328                 State jcmdState = jcmdStates.get(x);
 329                 st = State.merge(st, jcmdState);
 330 
 331                 finalStates.put(x, st);
 332             }
 333 
 334             /*
 335              * Create a list of commands from options and file
 336              * to handle quiet command
 337              */
 338             List<CompileCommand> ccList = new ArrayList<>();
 339             ccList.addAll(builders.get(Type.OPTION).getCompileCommands());
 340             ccList.addAll(builders.get(Type.FILE).getCompileCommands());
 341 
 342             // Get all VM options after we build all states and files
 343             List<String> options = new ArrayList<>();
 344             options.addAll(vmopts);
 345             for (StateBuilder<?> builder : builders.values()) {
 346                 options.addAll(builder.getOptions());
 347                 isValid &= builder.isValid();
 348             }
 349             options.addAll(jcmdStateBuilder.getOptions());
 350             return new Scenario(isValid, options, finalStates, ccList,
 351                     jcmdCommands);
 352         }
 353 
 354         // shows if jcmd have passed a clear command
 355         private boolean jcmdClearedState(List<JcmdCommand> jcmdCommands) {
 356             for (JcmdCommand jcmdCommand : jcmdCommands) {
 357                 if (jcmdCommand.jcmdType == JcmdType.CLEAR) {
 358                     return true;
 359                 }
 360             }
 361             return false;
 362         }
 363     }
 364 }