| // Copyright 2014 The Bazel Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| package com.google.devtools.build.lib.util.io; |
| |
| import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; |
| import com.google.devtools.build.lib.vfs.Path; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| |
| /** |
| * The FileWatcher dumps the contents of a files into an OutErr. |
| * It then stays active and dumps any content to the OutErr that is |
| * added to the file, until it is told to stop and all output has |
| * been dumped. |
| * |
| * This is useful to emulate streaming test output. |
| */ |
| @ThreadSafe |
| public class FileWatcher extends Thread { |
| |
| // How often we check for updates in the file we watch. (in ms) |
| private static final int WATCH_INTERVAL = 100; |
| |
| private final Path outputFile; |
| private final OutErr output; |
| private volatile boolean finishPumping; |
| private long toSkip = 0; |
| |
| /** |
| * Creates a FileWatcher that will dump any output that is appended to |
| * outputFile onto output. If skipExisting is true, the watcher will not dump |
| * any output that is in outputFile when we construct the watcher. If |
| * skipExisting is false, already existing output will be dumped, too. |
| * |
| * @param outputFile the File to watch |
| * @param output the outErr to dump the files contents to |
| * @param skipExisting whether to dump already existing output or not. |
| */ |
| public FileWatcher(Path outputFile, OutErr output, boolean skipExisting) throws IOException { |
| super("Streaming Test Output Pump"); |
| this.outputFile = outputFile; |
| this.output = output; |
| finishPumping = false; |
| |
| if (outputFile.exists() && skipExisting) { |
| toSkip = outputFile.getFileSize(); |
| } |
| } |
| |
| /** |
| * Tells the FileWatcher to stop pumping output and finish. |
| * The FileWatcher will only finish until there is no data left to display. |
| * This means that it is rarely a good idea to unconditionally wait for the |
| * FileWatcher thread to terminate -- Instead, it is better to have a timeout. |
| */ |
| @ThreadSafe |
| public void stopPumping() { |
| finishPumping = true; |
| } |
| |
| @Override |
| public void run() { |
| |
| try { |
| |
| // Wait until the file exists, or we have to abort. |
| while (!outputFile.exists() && !finishPumping) { |
| Thread.sleep(WATCH_INTERVAL); |
| } |
| |
| // Check that we did not have abort before the file was created. |
| if (outputFile.exists()) { |
| try (InputStream inputStream = outputFile.getInputStream(); |
| OutputStream outputStream = output.getOutputStream();) { |
| byte[] buffer = new byte[1024]; |
| while (!finishPumping || (inputStream.available() != 0)) { |
| if (inputStream.available() != 0) { |
| if (toSkip > 0) { |
| toSkip -= inputStream.skip(toSkip); |
| } else { |
| int read = inputStream.read(buffer); |
| if (read > 0) { |
| outputStream.write(buffer, 0, read); |
| } |
| } |
| } else { |
| Thread.sleep(WATCH_INTERVAL); |
| } |
| } |
| } |
| } |
| } catch (IOException ex) { |
| output.printOutLn("Failure reading or writing: " + ex.getMessage()); |
| } catch (InterruptedException ex) { |
| // Don't do anything. |
| } |
| } |
| } |