/*
* Copyright 2012-present Facebook, 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.facebook.buck.shell;
import static org.junit.Assert.assertEquals;
import com.facebook.buck.event.ConsoleEvent;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.TestExecutionContext;
import com.facebook.buck.testutil.TestConsole;
import com.facebook.buck.util.Console;
import com.facebook.buck.util.Escaper;
import com.facebook.buck.util.FakeProcess;
import com.facebook.buck.util.FakeProcessExecutor;
import com.facebook.buck.util.ProcessExecutorParams;
import com.facebook.buck.util.Verbosity;
import com.facebook.buck.util.environment.Platform;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.eventbus.Subscribe;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
import org.junit.Test;
public class ShellStepTest {
private static final ImmutableList<String> ARGS = ImmutableList.of("bash", "-c", "echo $V1 $V2");
private static final ImmutableMap<String, String> ENV =
ImmutableMap.of(
"V1", "two words",
"V2", "$foo'bar'");
private static final Path PATH = Paths.get("/tmp/a b");
private static final String ERROR_MSG = "some syntax error\ncompilation failed\n";
private static final String OUTPUT_MSG = "processing data...\n";
private static final int EXIT_FAILURE = 1;
private static final int EXIT_SUCCESS = 0;
private static ExecutionContext createContext(
ImmutableMap<ProcessExecutorParams, FakeProcess> processes, final Console console)
throws IOException {
ExecutionContext context =
TestExecutionContext.newBuilder()
.setConsole(console)
.setProcessExecutor(new FakeProcessExecutor(processes, console))
.build();
context
.getBuckEventBus()
.register(
new Object() {
@Subscribe
public void logEvent(ConsoleEvent event) throws IOException {
if (event.getLevel().equals(Level.WARNING)) {
console.getStdErr().write(event.getMessage().getBytes(Charsets.UTF_8));
}
}
});
return context;
}
private static ProcessExecutorParams createParams() {
return ProcessExecutorParams.builder().setCommand(ImmutableList.of("test")).build();
}
private static ShellStep createCommand(
ImmutableMap<String, String> env, ImmutableList<String> cmd, Path workingDirectory) {
return createCommand(
env,
cmd,
workingDirectory,
/* shouldPrintStdErr */ false,
/* shouldRecordStdOut */ false,
/* stdin */ Optional.empty());
}
private static ShellStep createCommand(boolean shouldPrintStdErr, boolean shouldPrintStdOut) {
return createCommand(
ENV, ARGS, null, shouldPrintStdErr, shouldPrintStdOut, /* stdin */ Optional.empty());
}
private static ShellStep createCommand(
final ImmutableMap<String, String> env,
final ImmutableList<String> cmd,
Path workingDirectory,
final boolean shouldPrintStdErr,
final boolean shouldPrintStdOut,
final Optional<String> stdin) {
workingDirectory =
workingDirectory == null ? Paths.get(".").toAbsolutePath().normalize() : workingDirectory;
return new ShellStep(workingDirectory) {
@Override
public ImmutableMap<String, String> getEnvironmentVariables(ExecutionContext context) {
return env;
}
@Override
public String getShortName() {
return cmd.get(0);
}
@Override
protected Optional<String> getStdin(ExecutionContext context) {
return stdin;
}
@Override
protected ImmutableList<String> getShellCommandInternal(ExecutionContext context) {
return cmd;
}
@Override
protected boolean shouldPrintStderr(Verbosity verbosity) {
return shouldPrintStdErr;
}
@Override
protected boolean shouldPrintStdout(Verbosity verbosity) {
return shouldPrintStdOut;
}
};
}
@Test
public void testDescriptionWithEnvironment() {
Path workingDirectory = Paths.get(".").toAbsolutePath().normalize();
ShellStep command = createCommand(ENV, ARGS, null);
ExecutionContext context =
TestExecutionContext.newBuilder().setProcessExecutor(new FakeProcessExecutor()).build();
String template =
Platform.detect() == Platform.WINDOWS
? "(cd %s && V1=\"two words\" V2=$foo'bar' bash -c \"echo $V1 $V2\")"
: "(cd %s && V1='two words' V2='$foo'\\''bar'\\''' bash -c 'echo $V1 $V2')";
assertEquals(
String.format(template, Escaper.escapeAsBashString(workingDirectory)),
command.getDescription(context));
}
@Test
public void testDescriptionWithEnvironmentAndPath() {
ShellStep command = createCommand(ENV, ARGS, PATH);
ExecutionContext context =
TestExecutionContext.newBuilder().setProcessExecutor(new FakeProcessExecutor()).build();
String template =
Platform.detect() == Platform.WINDOWS
? "(cd %s && V1=\"two words\" V2=$foo'bar' bash -c \"echo $V1 $V2\")"
: "(cd %s && V1='two words' V2='$foo'\\''bar'\\''' bash -c 'echo $V1 $V2')";
assertEquals(
String.format(template, Escaper.escapeAsBashString(PATH)), command.getDescription(context));
}
@Test
public void testDescriptionWithPath() {
ShellStep command = createCommand(ImmutableMap.of(), ARGS, PATH);
ExecutionContext context =
TestExecutionContext.newBuilder().setProcessExecutor(new FakeProcessExecutor()).build();
String template =
Platform.detect() == Platform.WINDOWS
? "(cd %s && bash -c \"echo $V1 $V2\")"
: "(cd %s && bash -c 'echo $V1 $V2')";
assertEquals(
String.format(template, Escaper.escapeAsBashString(PATH)), command.getDescription(context));
}
@Test
public void testDescription() {
Path workingDirectory = Paths.get(".").toAbsolutePath().normalize();
ShellStep command = createCommand(ImmutableMap.of(), ARGS, null);
ExecutionContext context =
TestExecutionContext.newBuilder().setProcessExecutor(new FakeProcessExecutor()).build();
String expectedDescription =
Platform.detect() == Platform.WINDOWS
? "(cd %s && bash -c \"echo $V1 $V2\")"
: "(cd %s && bash -c 'echo $V1 $V2')";
assertEquals(
String.format(expectedDescription, Escaper.escapeAsBashString(workingDirectory)),
command.getDescription(context));
}
@Test
public void testStdErrPrintedOnErrorIfNotSilentEvenIfNotShouldPrintStdErr() throws Exception {
ShellStep command = createCommand(/*shouldPrintStdErr*/ false, /*shouldPrintStdOut*/ false);
ProcessExecutorParams params = createParams();
FakeProcess process = new FakeProcess(EXIT_FAILURE, OUTPUT_MSG, ERROR_MSG);
TestConsole console = new TestConsole(Verbosity.STANDARD_INFORMATION);
ExecutionContext context = createContext(ImmutableMap.of(params, process), console);
command.launchAndInteractWithProcess(context, params);
assertEquals(ERROR_MSG, console.getTextWrittenToStdErr());
}
@Test
public void testStdErrPrintedOnErrorIfShouldPrintStdErrEvenIfSilent() throws Exception {
ShellStep command = createCommand(/*shouldPrintStdErr*/ true, /*shouldPrintStdOut*/ false);
ProcessExecutorParams params = createParams();
FakeProcess process = new FakeProcess(EXIT_FAILURE, OUTPUT_MSG, ERROR_MSG);
TestConsole console = new TestConsole(Verbosity.SILENT);
ExecutionContext context = createContext(ImmutableMap.of(params, process), console);
command.launchAndInteractWithProcess(context, params);
assertEquals(ERROR_MSG, console.getTextWrittenToStdErr());
}
@Test
public void testStdErrNotPrintedOnSuccessIfNotShouldPrintStdErr() throws Exception {
ShellStep command = createCommand(/*shouldPrintStdErr*/ false, /*shouldPrintStdOut*/ false);
ProcessExecutorParams params = createParams();
FakeProcess process = new FakeProcess(EXIT_SUCCESS, OUTPUT_MSG, ERROR_MSG);
TestConsole console = new TestConsole(Verbosity.STANDARD_INFORMATION);
ExecutionContext context = createContext(ImmutableMap.of(params, process), console);
command.launchAndInteractWithProcess(context, params);
assertEquals("", console.getTextWrittenToStdErr());
}
@Test
public void testStdErrPrintedOnSuccessIfShouldPrintStdErrEvenIfSilent() throws Exception {
ShellStep command = createCommand(/*shouldPrintStdErr*/ true, /*shouldPrintStdOut*/ false);
ProcessExecutorParams params = createParams();
FakeProcess process = new FakeProcess(EXIT_SUCCESS, OUTPUT_MSG, ERROR_MSG);
TestConsole console = new TestConsole(Verbosity.SILENT);
ExecutionContext context = createContext(ImmutableMap.of(params, process), console);
command.launchAndInteractWithProcess(context, params);
assertEquals(ERROR_MSG, console.getTextWrittenToStdErr());
}
@Test
public void testStdOutNotPrintedIfNotShouldRecordStdoutEvenIfVerbose() throws Exception {
ShellStep command = createCommand(/*shouldPrintStdErr*/ false, /*shouldPrintStdOut*/ false);
ProcessExecutorParams params = createParams();
FakeProcess process = new FakeProcess(EXIT_SUCCESS, OUTPUT_MSG, ERROR_MSG);
TestConsole console = new TestConsole(Verbosity.ALL);
ExecutionContext context = createContext(ImmutableMap.of(params, process), console);
command.launchAndInteractWithProcess(context, params);
assertEquals("", console.getTextWrittenToStdErr());
}
@Test
public void processEnvironmentIsUnionOfContextAndStepEnvironments() {
ShellStep command = createCommand(/*shouldPrintStdErr*/ false, /*shouldPrintStdOut*/ false);
ExecutionContext context =
TestExecutionContext.newBuilder()
.setEnvironment(
ImmutableMap.of(
"CONTEXT_ENVIRONMENT_VARIABLE", "CONTEXT_VALUE",
"PWD", "/wrong-pwd"))
.build();
Map<String, String> subProcessEnvironment = new HashMap<>();
subProcessEnvironment.put("PROCESS_ENVIRONMENT_VARIABLE", "PROCESS_VALUE");
command.setProcessEnvironment(context, subProcessEnvironment, new File("/right-pwd"));
Map<String, String> actualProcessEnvironment = new HashMap<>();
actualProcessEnvironment.putAll(context.getEnvironment());
actualProcessEnvironment.remove("PWD");
assertEquals(
"Sub-process environment should be union of client and step environments.",
subProcessEnvironment,
ImmutableMap.<String, String>builder()
.putAll(actualProcessEnvironment)
.put("PWD", new File("/right-pwd").getPath())
.putAll(ENV)
.build());
}
@Test
public void testStdinGetsToProcessWhenPresent() throws Exception {
final Optional<String> stdin = Optional.of("hello world!");
ShellStep command =
createCommand(
ImmutableMap.of(),
ImmutableList.of("cat", "-"),
null,
/*shouldPrintStdErr*/ true,
/*shouldPrintStdOut*/ true,
stdin);
ProcessExecutorParams params = createParams();
FakeProcess process = new FakeProcess(EXIT_SUCCESS, OUTPUT_MSG, ERROR_MSG);
TestConsole console = new TestConsole(Verbosity.ALL);
ExecutionContext context = createContext(ImmutableMap.of(params, process), console);
command.launchAndInteractWithProcess(context, params);
assertEquals(stdin.get(), process.getOutput());
}
@Test
public void testStdinDoesNotGetToProcessWhenAbsent() throws Exception {
final Optional<String> stdin = Optional.empty();
ShellStep command =
createCommand(
ImmutableMap.of(),
ImmutableList.of("cat", "-"),
null,
/*shouldPrintStdErr*/ true,
/*shouldPrintStdOut*/ true,
stdin);
ProcessExecutorParams params = createParams();
FakeProcess process = new FakeProcess(EXIT_SUCCESS, OUTPUT_MSG, ERROR_MSG);
TestConsole console = new TestConsole(Verbosity.ALL);
ExecutionContext context = createContext(ImmutableMap.of(params, process), console);
command.launchAndInteractWithProcess(context, params);
assertEquals("", process.getOutput());
}
}