blob: c1355ec1cb464590a750e8bfad6a7dc1d330848c [file] [log] [blame]
// Copyright 2018 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.blackbox.framework;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.devtools.build.lib.util.OS;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Test of {@link ProcessRunner} */
@RunWith(JUnit4.class)
public final class ProcessRunnerTest {
private static ExecutorService executorService;
private Path directory;
private Path path;
@BeforeClass
public static void setUpExecutor() {
// we need only two threads to schedule reading from output and error streams
executorService =
MoreExecutors.getExitingExecutorService(
(ThreadPoolExecutor) Executors.newFixedThreadPool(2), 1, TimeUnit.SECONDS);
}
@Before
public void setUp() throws Exception {
directory = Files.createTempDirectory(getClass().getSimpleName());
path = Files.createTempFile(directory, "script", isWindows() ? ".bat" : "");
assertThat(Files.exists(path)).isTrue();
assertThat(path.toFile().setExecutable(true)).isTrue();
path.toFile().deleteOnExit();
directory.toFile().deleteOnExit();
}
@AfterClass
public static void tearDownExecutor() {
MoreExecutors.shutdownAndAwaitTermination(executorService, 5, TimeUnit.SECONDS);
}
@Test
public void testSuccess() throws Exception {
Files.write(path, createScriptText(/* exit code */ 0, /* output */ "Hello!", /* error */ null));
ProcessParameters parameters = createBuilder().build();
ProcessResult result = new ProcessRunner(parameters, executorService).runSynchronously();
assertThat(result.exitCode()).isEqualTo(0);
assertThat(result.outString()).isEqualTo("Hello!");
assertThat(result.errString()).isEmpty();
}
@Test
public void testFailureWithCode() throws Exception {
Files.write(
path, createScriptText(/* exit code */ 124, /* output */ null, /* error */ "Failure"));
ProcessParameters parameters =
createBuilder().setExpectedExitCode(124).setExpectedEmptyError(false).build();
ProcessResult result = new ProcessRunner(parameters, executorService).runSynchronously();
assertThat(result.exitCode()).isEqualTo(124);
assertThat(result.outString()).isEmpty();
assertThat(result.errString()).isEqualTo("Failure");
}
@Test
public void testFailure() throws Exception {
Files.write(
path, createScriptText(/* exit code */ 124, /* output */ null, /* error */ "Failure"));
ProcessParameters parameters =
createBuilder().setExpectedToFail(true).setExpectedEmptyError(false).build();
ProcessResult result = new ProcessRunner(parameters, executorService).runSynchronously();
assertThat(result.exitCode()).isEqualTo(124);
assertThat(result.outString()).isEmpty();
assertThat(result.errString()).isEqualTo("Failure");
}
@Test
public void testTimeout() throws Exception {
// Windows script to sleep 5 seconds, so that we can test timeout.
// This script finds PowerShell using %systemroot% variable, which we assume is always
// defined. It passes some standard parameters like input and output formats,
// important part is the Command parameter, which actually calls Sleep from PowerShell.
String windowsScript =
"%systemroot%\\system32\\cmd.exe /C \"start /I /B powershell"
+ " -Version 3.0 -NoLogo -Sta -NoProfile -InputFormat Text -OutputFormat Text"
+ " -NonInteractive -Command \"\"&PowerShell Sleep 5\"";
Files.write(path, Collections.singleton(isWindows() ? windowsScript : "read smthg"));
ProcessParameters parameters =
createBuilder()
.setExpectedExitCode(-1)
.setExpectedEmptyError(false)
.setTimeoutMillis(100)
.build();
try {
new ProcessRunner(parameters, executorService).runSynchronously();
assertThat(false).isTrue();
} catch (TimeoutException e) {
// ignore
}
}
@Test
public void testRedirect() throws Exception {
Files.write(
path,
createScriptText(
/* exit code */ 12,
/* output */ Lists.newArrayList("Info", "Multi", "line"),
/* error */ Collections.singletonList("Failure")));
Path out = directory.resolve("out.txt");
Path err = directory.resolve("err.txt");
try {
ProcessParameters parameters =
createBuilder()
.setExpectedExitCode(12)
.setExpectedEmptyError(false)
.setRedirectOutput(out)
.setRedirectError(err)
.build();
ProcessResult result = new ProcessRunner(parameters, executorService).runSynchronously();
assertThat(result.exitCode()).isEqualTo(12);
assertThat(result.outString()).isEqualTo("Info\nMulti\nline");
assertThat(result.errString()).isEqualTo("Failure");
} finally {
Files.delete(out);
Files.delete(err);
}
}
private ProcessParameters.Builder createBuilder() {
return ProcessParameters.builder()
.setWorkingDirectory(directory.toFile())
.setName(path.toAbsolutePath().toString());
}
private static List<String> createScriptText(
final int exitCode, @Nullable final String output, @Nullable final String error) {
return createScriptText(
exitCode,
output != null ? Collections.singletonList(output) : null,
error != null ? Collections.singletonList(error) : null);
}
private static List<String> createScriptText(
final int exitCode, @Nullable final List<String> output, @Nullable final List<String> error) {
List<String> text = Lists.newArrayList();
if (isWindows()) {
text.add("@echo off");
}
text.addAll(echoStrings(output, ""));
text.addAll(echoStrings(error, isWindows() ? ">&2" : " 1>&2"));
text.add((isWindows() ? "exit /b " : "exit ") + exitCode);
return text;
}
private static List<String> echoStrings(@Nullable List<String> input, String redirect) {
if (input == null) {
return Collections.emptyList();
}
String quote = isWindows() ? "" : "\"";
return input
.stream()
.map(s -> String.format("echo %s%s%s%s", quote, s, quote, redirect))
.collect(Collectors.toList());
}
private static boolean isWindows() {
return OS.WINDOWS.equals(OS.getCurrent());
}
}