1/*
2 * Copyright (c) 2015, 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.
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
24package compiler.compilercontrol.share.scenario;
25
26import compiler.compilercontrol.share.method.MethodDescriptor;
27import compiler.compilercontrol.share.pool.PoolHelper;
28import compiler.compilercontrol.share.processors.CommandProcessor;
29import compiler.compilercontrol.share.processors.LogProcessor;
30import compiler.compilercontrol.share.processors.PrintDirectivesProcessor;
31import compiler.compilercontrol.share.processors.PrintProcessor;
32import jdk.test.lib.Asserts;
33import jdk.test.lib.process.OutputAnalyzer;
34import jdk.test.lib.util.Pair;
35
36import java.lang.reflect.Executable;
37import java.util.ArrayList;
38import java.util.Collections;
39import java.util.HashMap;
40import java.util.LinkedHashSet;
41import java.util.List;
42import java.util.Map;
43import java.util.Set;
44import java.util.concurrent.Callable;
45import java.util.function.Consumer;
46
47/**
48 * Test scenario
49 */
50public 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}
328