Path: blob/master/test/langtools/tools/lib/toolbox/AbstractTask.java
41149 views
/*1* Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223package toolbox;2425import java.io.BufferedReader;26import java.io.ByteArrayOutputStream;27import java.io.File;28import java.io.IOException;29import java.io.InputStream;30import java.io.InputStreamReader;31import java.io.PrintStream;32import java.io.PrintWriter;33import java.io.StringWriter;34import java.util.EnumMap;35import java.util.HashMap;36import java.util.Map;37import static toolbox.ToolBox.lineSeparator;3839/**40* A utility base class to simplify the implementation of tasks.41* Provides support for running the task in a process and for42* capturing output written by the task to stdout, stderr and43* other writers where applicable.44* @param <T> the implementing subclass45*/46abstract class AbstractTask<T extends AbstractTask<T>> implements Task {47protected final ToolBox toolBox;48protected final Mode mode;49private final Map<OutputKind, String> redirects = new EnumMap<>(OutputKind.class);50private final Map<String, String> envVars = new HashMap<>();51private Expect expect = Expect.SUCCESS;52int expectedExitCode = 0;5354/**55* Create a task that will execute in the specified mode.56* @param mode the mode57*/58protected AbstractTask(ToolBox tb, Mode mode) {59toolBox = tb;60this.mode = mode;61}6263/**64* Sets the expected outcome of the task and calls {@code run()}.65* @param expect the expected outcome66* @return the result of calling {@code run()}67*/68public Result run(Expect expect) {69expect(expect, Integer.MIN_VALUE);70return run();71}7273/**74* Sets the expected outcome of the task and calls {@code run()}.75* @param expect the expected outcome76* @param exitCode the expected exit code if the expected outcome77* is {@code FAIL}78* @return the result of calling {@code run()}79*/80public Result run(Expect expect, int exitCode) {81expect(expect, exitCode);82return run();83}8485/**86* Sets the expected outcome and expected exit code of the task.87* The exit code will not be checked if the outcome is88* {@code Expect.SUCCESS} or if the exit code is set to89* {@code Integer.MIN_VALUE}.90* @param expect the expected outcome91* @param exitCode the expected exit code92*/93protected void expect(Expect expect, int exitCode) {94this.expect = expect;95this.expectedExitCode = exitCode;96}9798/**99* Checks the exit code contained in a {@code Result} against the100* expected outcome and exit value101* @param result the result object102* @return the result object103* @throws TaskError if the exit code stored in the result object104* does not match the expected outcome and exit code.105*/106protected Result checkExit(Result result) throws TaskError {107switch (expect) {108case SUCCESS:109if (result.exitCode != 0) {110result.writeAll();111throw new TaskError("Task " + name() + " failed: rc=" + result.exitCode);112}113break;114115case FAIL:116if (result.exitCode == 0) {117result.writeAll();118throw new TaskError("Task " + name() + " succeeded unexpectedly");119}120121if (expectedExitCode != Integer.MIN_VALUE122&& result.exitCode != expectedExitCode) {123result.writeAll();124throw new TaskError("Task " + name() + "failed with unexpected exit code "125+ result.exitCode + ", expected " + expectedExitCode);126}127break;128}129return result;130}131132/**133* Sets an environment variable to be used by this task.134* @param name the name of the environment variable135* @param value the value for the environment variable136* @return this task object137* @throws IllegalStateException if the task mode is not {@code EXEC}138*/139public T envVar(String name, String value) {140if (mode != Mode.EXEC)141throw new IllegalStateException();142envVars.put(name, value);143return (T) this;144}145146/**147* Redirects output from an output stream to a file.148* @param outputKind the name of the stream to be redirected.149* @param path the file150* @return this task object151* @throws IllegalStateException if the task mode is not {@code EXEC}152*/153public T redirect(OutputKind outputKind, String path) {154if (mode != Mode.EXEC)155throw new IllegalStateException();156redirects.put(outputKind, path);157return (T) this;158}159160/**161* Returns a {@code ProcessBuilder} initialized with any162* redirects and environment variables that have been set.163* @return a {@code ProcessBuilder}164*/165protected ProcessBuilder getProcessBuilder() {166if (mode != Mode.EXEC)167throw new IllegalStateException();168ProcessBuilder pb = new ProcessBuilder();169if (redirects.get(OutputKind.STDOUT) != null)170pb.redirectOutput(new File(redirects.get(OutputKind.STDOUT)));171if (redirects.get(OutputKind.STDERR) != null)172pb.redirectError(new File(redirects.get(OutputKind.STDERR)));173pb.environment().putAll(envVars);174return pb;175}176177/**178* Collects the output from a process and saves it in a {@code Result}.179* @param tb the {@code ToolBox} containing the task {@code t}180* @param t the task initiating the process181* @param p the process182* @return a Result object containing the output from the process and its183* exit value.184* @throws InterruptedException if the thread is interrupted185*/186protected Result runProcess(ToolBox tb, Task t, Process p) throws InterruptedException {187if (mode != Mode.EXEC)188throw new IllegalStateException();189ProcessOutput sysOut = new ProcessOutput(p.getInputStream()).start();190ProcessOutput sysErr = new ProcessOutput(p.getErrorStream()).start();191sysOut.waitUntilDone();192sysErr.waitUntilDone();193int rc = p.waitFor();194Map<OutputKind, String> outputMap = new EnumMap<>(OutputKind.class);195outputMap.put(OutputKind.STDOUT, sysOut.getOutput());196outputMap.put(OutputKind.STDERR, sysErr.getOutput());197return checkExit(new Result(toolBox, t, rc, outputMap));198}199200/**201* Thread-friendly class to read the output from a process until the stream202* is exhausted.203*/204static class ProcessOutput implements Runnable {205ProcessOutput(InputStream from) {206in = new BufferedReader(new InputStreamReader(from));207out = new StringBuilder();208}209210ProcessOutput start() {211new Thread(this).start();212return this;213}214215@Override216public void run() {217try {218String line;219while ((line = in.readLine()) != null) {220out.append(line).append(lineSeparator);221}222} catch (IOException e) {223}224synchronized (this) {225done = true;226notifyAll();227}228}229230synchronized void waitUntilDone() throws InterruptedException {231boolean interrupted = false;232233// poll interrupted flag, while waiting for copy to complete234while (!(interrupted = Thread.interrupted()) && !done)235wait(1000);236237if (interrupted)238throw new InterruptedException();239}240241String getOutput() {242return out.toString();243}244245private final BufferedReader in;246private final StringBuilder out;247private boolean done;248}249250/**251* Utility class to simplify the handling of temporarily setting a252* new stream for System.out or System.err.253*/254static class StreamOutput {255// Functional interface to set a stream.256// Expected use: System::setOut, System::setErr257interface Initializer {258void set(PrintStream s);259}260261private final ByteArrayOutputStream baos = new ByteArrayOutputStream();262private final PrintStream ps = new PrintStream(baos);263private final PrintStream prev;264private final Initializer init;265266StreamOutput(PrintStream s, Initializer init) {267prev = s;268init.set(ps);269this.init = init;270}271272/**273* Closes the stream and returns the contents that were written to it.274* @return the contents that were written to it.275*/276String close() {277init.set(prev);278ps.close();279return baos.toString();280}281}282283/**284* Utility class to simplify the handling of creating an in-memory PrintWriter.285*/286static class WriterOutput {287private final StringWriter sw = new StringWriter();288final PrintWriter pw = new PrintWriter(sw);289290/**291* Closes the stream and returns the contents that were written to it.292* @return the contents that were written to it.293*/294String close() {295pw.close();296return sw.toString();297}298}299}300301302