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 pool.PoolHelper;
  37 
  38 import java.io.BufferedReader;
  39 import java.io.IOException;
  40 import java.io.InputStreamReader;
  41 import java.io.PrintWriter;
  42 import java.lang.reflect.Executable;
  43 import java.net.ServerSocket;
  44 import java.net.Socket;
  45 import java.util.ArrayList;
  46 import java.util.Collections;
  47 import java.util.HashMap;
  48 import java.util.LinkedHashSet;
  49 import java.util.List;
  50 import java.util.Map;
  51 import java.util.Set;
  52 import java.util.concurrent.Callable;
  53 import java.util.function.Consumer;
  54 
  55 /**
  56  * Test scenario
  57  */
  58 public final class Scenario {
  59     private final boolean isValid;
  60     private final List<String> vmopts;
  61     private final Map<Executable, State> states;
  62     private final List<Consumer<OutputAnalyzer>> processors;
  63 
  64     private Scenario(boolean isValid,
  65                      List<String> vmopts,
  66                      Map<Executable, State> states,
  67                      List<CompileCommand> compileCommands) {
  68         this.isValid = isValid;
  69         this.vmopts = vmopts;
  70         this.states = states;
  71         processors = new ArrayList<>();
  72         processors.add(new LogProcessor(states));
  73         processors.add(new PrintProcessor(states));
  74         List<CompileCommand> nonQuieted = new ArrayList<>();
  75         List<CompileCommand> quieted = new ArrayList<>();
  76         boolean metQuiet = false;
  77         for (CompileCommand cc : compileCommands) {
  78             metQuiet |= cc.command == Command.QUIET;
  79             if (metQuiet) {
  80                 quieted.add(cc);
  81             } else {
  82                 nonQuieted.add(cc);
  83             }
  84         }
  85         processors.add(new CommandProcessor(nonQuieted));
  86         processors.add(new QuietProcessor(quieted));
  87     }
  88 
  89     /**
  90      * Executes scenario
  91      */
  92     public void execute() {
  93         // Construct execution command with CompileCommand and class
  94         List<String> argsList = new ArrayList<>();
  95         // Add VM options
  96         argsList.addAll(vmopts);
  97         // Add class name that would be executed in a separate VM
  98         String classCmd = BaseAction.class.getName();
  99         argsList.add(classCmd);
 100         OutputAnalyzer output;
 101         try (ServerSocket serverSocket = new ServerSocket(0)) {
 102             if (isValid) {
 103                 // Get port test VM will connect to
 104                 int port = serverSocket.getLocalPort();
 105                 if (port == -1) {
 106                     throw new Error("Socket is not bound: " + port);
 107                 }
 108                 argsList.add(String.valueOf(port));
 109                 // Start separate thread to connect with test VM
 110                 new Thread(() -> connectTestVM(serverSocket)).start();
 111             }
 112             // Start test VM
 113             output = ProcessTools.executeTestJvmAllArgs(
 114                     argsList.toArray(new String[argsList.size()]));
 115         } catch (Throwable thr) {
 116             throw new Error("Execution failed", thr);
 117         }
 118         if (isValid) {
 119             output.shouldHaveExitValue(0);
 120             for (Consumer<OutputAnalyzer> processor : processors) {
 121                 processor.accept(output);
 122             }
 123         } else {
 124             Asserts.assertNE(output.getExitValue(), 0, "VM should exit with "
 125                     + "error for incorrect directives");
 126             output.shouldContain("Parsing of compiler directives failed");
 127         }
 128     }
 129 
 130     /*
 131      * Performs connection with a test VM, sends method states
 132      */
 133     private void connectTestVM(ServerSocket serverSocket) {
 134         /*
 135          * There are no way to prove that accept was invoked before we started
 136          * test VM that connects to this serverSocket. Connection timeout is
 137          * enough
 138          */
 139         try (
 140                 Socket socket = serverSocket.accept();
 141                 PrintWriter pw = new PrintWriter(socket.getOutputStream(),
 142                         true);
 143                 BufferedReader in = new BufferedReader(new InputStreamReader(
 144                         socket.getInputStream()))) {
 145             // Get pid of the executed process
 146             int pid = Integer.parseInt(in.readLine());
 147             Asserts.assertNE(pid, 0, "Got incorrect pid");
 148             // serialize and send state map
 149             for (Executable x : states.keySet()) {
 150                 pw.println("{");
 151                 pw.println(x.toGenericString());
 152                 pw.println(states.get(x).toString());
 153                 pw.println("}");
 154             }
 155         } catch (IOException e) {
 156             throw new Error("Failed to write data", e);
 157         }
 158     }
 159 
 160     /**
 161      * Gets states of methods for this scenario
 162      *
 163      * @return pairs of executable and its state
 164      */
 165     public Map<Executable, State> getStates() {
 166         return states;
 167     }
 168 
 169     public static enum Compiler {
 170         C1("c1"),
 171         C2("c2");
 172 
 173         public final String name;
 174 
 175         Compiler(String name) {
 176             this.name = name;
 177         }
 178     }
 179 
 180     /**
 181      * Type of the compile command
 182      */
 183     public static enum Type {
 184         OPTION(""),
 185         FILE("command_file"),
 186         DIRECTIVE("directives.json");
 187 
 188         public final String fileName;
 189 
 190         public CompileCommand createCompileCommand(Command command,
 191                 MethodDescriptor md, Compiler compiler) {
 192             return new CompileCommand(command, md, compiler, this);
 193         }
 194 
 195         private Type(String fileName) {
 196             this.fileName = fileName;
 197         }
 198     }
 199 
 200     public static Builder getBuilder() {
 201         return new Builder();
 202     }
 203 
 204     public static class Builder {
 205         private final Set<String> vmopts = new LinkedHashSet<>();
 206         private final Map<Type, StateBuilder<CompileCommand>> builders
 207                 = new HashMap<>();
 208 
 209         public Builder() {
 210             builders.put(Type.FILE, new CommandFileBuilder(Type.FILE.fileName));
 211             builders.put(Type.OPTION, new CommandOptionsBuilder());
 212             builders.put(Type.DIRECTIVE, new DirectiveBuilder(
 213                     Type.DIRECTIVE.fileName));
 214         }
 215 
 216         public void add(CompileCommand compileCommand) {
 217             String[] vmOptions = compileCommand.command.vmOpts;
 218             Collections.addAll(vmopts, vmOptions);
 219             StateBuilder builder = builders.get(compileCommand.type);
 220             if (builder == null) {
 221                 throw new Error("TESTBUG: Missing builder for the type: "
 222                         + compileCommand.type);
 223             }
 224             builder.add(compileCommand);
 225         }
 226 
 227         public Scenario build() {
 228             boolean isValid = true;
 229 
 230             // Get states from each of the state builders
 231             Map<Executable, State> commandFileStates
 232                     = builders.get(Type.FILE).getStates();
 233             Map<Executable, State> commandOptionStates
 234                     = builders.get(Type.OPTION).getStates();
 235             Map<Executable, State> directiveFileStates
 236                     = builders.get(Type.DIRECTIVE).getStates();
 237 
 238             // Merge states
 239             List<Pair<Executable, Callable<?>>> methods = new PoolHelper()
 240                     .getAllMethods();
 241             Map<Executable, State> finalStates = new HashMap<>();
 242             for (Pair<Executable, Callable<?>> pair : methods) {
 243                 Executable x = pair.first;
 244                 State commandOptionState = commandOptionStates.get(x);
 245                 State commandFileState = commandFileStates.get(x);
 246                 State st = State.merge(commandOptionState, commandFileState);
 247                 State directiveState = directiveFileStates.get(x);
 248                 if (directiveState != null) {
 249                     st = directiveState;
 250                 }
 251                 finalStates.put(x, st);
 252             }
 253 
 254             /*
 255              * Create a list of commands from options and file
 256              * to handle quiet command
 257              */
 258             List<CompileCommand> ccList = new ArrayList<>();
 259             ccList.addAll(builders.get(Type.OPTION).getCompileCommands());
 260             ccList.addAll(builders.get(Type.FILE).getCompileCommands());
 261 
 262             // Get all VM options after we build all states and files
 263             List<String> options = new ArrayList<>();
 264             options.addAll(vmopts);
 265             for (StateBuilder<?> builder : builders.values()) {
 266                 options.addAll(builder.getOptions());
 267                 isValid &= builder.isValid();
 268             }
 269             return new Scenario(isValid, options, finalStates, ccList);
 270         }
 271     }
 272 }