depot/third_party/copybara/javatests/com/google/copybara/CommandRunnerTest.java
Default email dfee7b6196 Project import generated by Copybara.
GitOrigin-RevId: b578e69f18a543889ded9c57a8f0dffacdb103d8
2020-05-15 16:19:19 -04:00

297 lines
11 KiB
Java

/*
* Copyright (C) 2016 Google Inc.
*
* 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.copybara;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertThrows;
import com.beust.jcommander.internal.Lists;
import com.google.common.base.Strings;
import com.google.copybara.util.CommandOutputWithStatus;
import com.google.copybara.util.CommandRunner;
import com.google.copybara.util.CommandRunner.CommandExecutor;
import com.google.copybara.util.CommandTimeoutException;
import com.google.copybara.shell.AbnormalTerminationException;
import com.google.copybara.shell.Command;
import com.google.copybara.shell.CommandException;
import com.google.copybara.shell.Killable;
import com.google.copybara.shell.KillableObserver;
import com.google.copybara.shell.TerminationStatus;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.PosixFilePermissions;
import java.time.Duration;
import java.util.List;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.StreamHandler;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests that we can execute commands with Bazel shell library.
*/
@RunWith(JUnit4.class)
public class CommandRunnerTest {
private static final int LINES_SIZE = 1000;
private ByteArrayOutputStream outContent;
private ByteArrayOutputStream errContent;
private List<String> logLines;
@Before
public void setUp() throws Exception {
outContent = new ByteArrayOutputStream();
errContent = new ByteArrayOutputStream();
logLines = Lists.newLinkedList();
}
@Test
public void testCommand() throws Exception {
Command command = new Command(new String[]{"echo", "hello", "world"});
CommandOutputWithStatus result = runCommand(new CommandRunner(command));
assertThat(result.getTerminationStatus().success()).isTrue();
assertThat(result.getStdout()).isEqualTo("hello world\n");
assertWithMessage("Not empty: %s", new String(outContent.toByteArray(), UTF_8))
.that(outContent.toByteArray())
.isEmpty();
assertWithMessage("Not empty: %s", new String(errContent.toByteArray(), UTF_8))
.that(errContent.toByteArray())
.isEmpty();
assertLogContains(
"Executing [echo hello world]", "'echo' STDOUT: hello world", "Command 'echo' finished");
}
@Test
public void testTimeout() throws Exception {
Command command = bashCommand(""
+ "echo stdout msg\n"
+ ">&2 echo stderr msg\n"
+ "sleep 10\n");
CommandTimeoutException e =
assertThrows(
CommandTimeoutException.class,
() -> runCommand(new CommandRunner(command, Duration.ofSeconds(1))));
assertThat(e.getOutput().getStdout()).contains("stdout msg");
assertThat(e.getOutput().getStderr()).contains("stderr msg");
assertThat(e.getMessage())
.containsMatch("Command '.*' killed by Copybara after timeout \\(1s\\)");
assertThat(e.getTimeout()).isEquivalentAccordingToCompareTo(Duration.ofSeconds(1));
}
@Test
public void testObserverCanTerminate() throws Exception {
KillableObserver tester = new KillableObserver() {
@Override
public void startObserving(Killable killable) {
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
// ignored
}
killable.kill();
}
@Override
public void stopObserving(Killable killable) {
}
};
Command command = bashCommand(""
+ "echo stdout msg\n"
+ ">&2 echo stderr msg\n"
+ "sleep 10\n");
AbnormalTerminationException e =
assertThrows(
AbnormalTerminationException.class,
() ->
runCommand(
new CommandRunner(command, Duration.ofSeconds(90)).withObserver(tester)));
assertThat(e).hasMessageThat().containsMatch("Process terminated by signal 15");
assertThat(e)
.hasMessageThat()
.doesNotContainMatch("Command '.*' killed by Copybara after timeout \\(1s\\)");
}
@Test
public void testTimeoutCustomExitCode() throws Exception {
Command command = bashCommand(""
+ "trap 'exit 42' SIGTERM SIGINT\n"
+ "echo stdout msg\n"
+ ">&2 echo stderr msg\n"
+ "sleep 10\n");
CommandTimeoutException e =
assertThrows(
CommandTimeoutException.class,
() -> runCommand(new CommandRunner(command, Duration.ofSeconds(1))));
assertThat(e.getTimeout()).isEquivalentAccordingToCompareTo(Duration.ofSeconds(1));
assertThat(e.getOutput().getStderr()).contains("stderr msg");
assertThat(e.getMessage())
.containsMatch("Command '.*' killed by Copybara after timeout \\(1s\\)");
assertThat(e.getOutput().getStdout()).contains("stdout msg");
}
private Command bashCommand(String bashScript) throws IOException {
Path tempFile = Files.createTempFile("test", "file");
Files.write(tempFile, bashScript.getBytes(UTF_8));
Files.setPosixFilePermissions(tempFile, PosixFilePermissions.fromString("rwxr-xr--"));
return new Command(new String[]{tempFile.toAbsolutePath().toString()});
}
@Test
public void testCommandWithVerbose() throws Exception {
Command command = new Command(new String[]{"echo", "hello", "world"});
CommandOutputWithStatus result = runCommand(new CommandRunner(command).withVerbose(true));
assertThat(result.getTerminationStatus().success()).isTrue();
assertThat(result.getStdout()).isEqualTo("hello world\n");
assertThat(outContent.toByteArray()).isEmpty();
String stderr = new String(errContent.toByteArray(), UTF_8);
// Using contains() because stderr also gets all the logs
assertThat(stderr).contains("hello world\n");
// Verify that logging is redirected, even with verbose
assertLogContains(
"Executing [echo hello world]", "'echo' STDOUT: hello world", "Command 'echo' finished");
}
@Test
public void testMaxCommandLength() throws Exception {
String[] args = new String[10000];
args[0] = "echo";
for (int i = 1; i < 10000; i++) {
args[i] = "hello world!";
}
Command command = new Command(args);
CommandOutputWithStatus result = runCommand(new CommandRunner(command).withVerbose(true));
assertThat(result.getTerminationStatus().success()).isTrue();
assertLogContains("...", "'echo' STDOUT: hello world!", "Command 'echo' finished");
}
@Test
public void testCommandWithMaxLogLines() throws Exception {
Command command = new Command(new String[]{"echo", "hello\n", "world"});
CommandOutputWithStatus result =
runCommand(new CommandRunner(command).withMaxStdOutLogLines(1));
assertThat(result.getTerminationStatus().success()).isTrue();
assertThat(result.getStdout()).isEqualTo("hello\n world\n");
assertThat(outContent.toByteArray()).isEmpty();
assertLogContains("Executing [echo 'hello\n' world]",
"'echo' STDOUT: hello",
"'echo' STDOUT: ... truncated after 1 line(s)");
}
@Test
public void testCommandWithVerboseLargeOutput() throws Exception {
Path largeFile = Files.createTempDirectory("test").resolve("large_file.txt");
String singleLine = Strings.repeat("A", 100);
try (BufferedWriter writer = Files.newBufferedWriter(largeFile)) {
for (int i = 0; i < LINES_SIZE; i++) {
writer.write(singleLine + "\n");
}
}
Command command = new Command(new String[]{"cat", largeFile.toAbsolutePath().toString()});
CommandOutputWithStatus result = runCommand(new CommandRunner(command).withVerbose(true));
assertThat(result.getTerminationStatus().success()).isTrue();
for (int i = 1; i < LINES_SIZE - 1; i++) {
assertThat(logLines.get(i)).endsWith(singleLine);
}
}
@Test
public void testCommandWithExtraOutput() throws Exception {
Command command = bashCommand(""
+ "echo hello\n"
+ "echo >&2 world\n");
ByteArrayOutputStream stdoutCollector = new ByteArrayOutputStream();
ByteArrayOutputStream stderrCollector = new ByteArrayOutputStream();
runCommand(new CommandRunner(command)
.withStdErrStream(stderrCollector)
.withStdOutStream(stdoutCollector));
String stderr = new String(stderrCollector.toByteArray(), UTF_8);
String stdout = new String(stdoutCollector.toByteArray(), UTF_8);
assertThat(stderr).contains("world");
assertThat(stdout).contains("hello");
}
@Test
public void testCommandWithCustomRunner() throws Exception {
Command command = bashCommand("");
CommandException e =
assertThrows(
CommandException.class,
() ->
runCommand(
new CommandRunner(command)
.withCommandExecutor(
new CommandExecutor() {
@Override
public TerminationStatus getCommandOutputWithStatus(
Command cmd,
byte[] input,
KillableObserver cmdMonitor,
OutputStream stdoutStream,
OutputStream stderrStream)
throws CommandException {
throw new CommandException(cmd, "OH NOES!");
}
})));
assertThat(e).hasMessageThat().contains("OH NOES!");
}
private CommandOutputWithStatus runCommand(CommandRunner commandRunner) throws CommandException {
Logger logger = Logger.getLogger(CommandRunner.class.getName());
boolean useParentLogger = logger.getUseParentHandlers();
logger.setUseParentHandlers(false);
StreamHandler handler = new StreamHandler() {
@Override
public synchronized void publish(LogRecord record) {
logLines.add(record.getMessage());
}
};
logger.addHandler(handler);
PrintStream outRestore = System.out;
PrintStream errRestore = System.err;
System.setOut(new PrintStream(outContent));
System.setErr(new PrintStream(errContent));
try {
return commandRunner.execute();
} finally {
System.setOut(outRestore);
System.setErr(errRestore);
logger.removeHandler(handler);
logger.setUseParentHandlers(useParentLogger);
}
}
private void assertLogContains(String... messages) {
int i = 0;
for (String message : messages) {
assertThat(logLines.get(i)).contains(message);
i++;
}
}
}