blob: fe9e7d0dc75e5322d40ec35f09296c484776e38b [file] [log] [blame]
/*
* 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.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.getCurrentArguments;
import static org.easymock.EasyMock.replay;
import static org.junit.Assert.assertEquals;
import com.facebook.buck.event.BuckEvent;
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.FakeProcess;
import com.facebook.buck.util.FakeProcessExecutor;
import com.facebook.buck.util.ImmutableProcessExecutorParams;
import com.facebook.buck.util.ProcessExecutorParams;
import com.facebook.buck.util.Verbosity;
import com.google.common.base.Charsets;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import org.easymock.EasyMock;
import org.easymock.IAnswer;
import org.junit.Test;
import java.io.File;
import java.util.Map;
import java.util.logging.Level;
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 File PATH = new File("/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) {
ExecutionContext context = EasyMock.createMock(ExecutionContext.class);
expect(context.getProcessExecutor())
.andReturn(new FakeProcessExecutor(processes, console))
.anyTimes();
expect(context.getConsole()).andReturn(console).anyTimes();
expect(context.getVerbosity()).andReturn(console.getVerbosity()).anyTimes();
context.postEvent(anyObject(BuckEvent.class));
expectLastCall().andStubAnswer(
new IAnswer<Void>() {
@Override
public Void answer() throws Throwable {
ConsoleEvent event = (ConsoleEvent) getCurrentArguments()[0];
if (event.getLevel().equals(Level.WARNING)) {
console.getStdErr().write(event.getMessage().getBytes(Charsets.US_ASCII));
}
return null;
}
});
replay(context);
return context;
}
private static ProcessExecutorParams createParams() {
return ImmutableProcessExecutorParams
.builder()
.setCommand(ImmutableList.of("test"))
.build();
}
private static ShellStep createCommand(
ImmutableMap<String, String> env,
ImmutableList<String> cmd,
File workingDirectory) {
return createCommand(
env,
cmd,
workingDirectory,
/* shouldPrintStdErr */ false,
/* shouldRecordStdOut */ false,
/* stdin */ Optional.<String>absent());
}
private static ShellStep createCommand(boolean shouldPrintStdErr, boolean shouldPrintStdOut) {
return createCommand(
ENV,
ARGS,
null,
shouldPrintStdErr,
shouldPrintStdOut,
/* stdin */ Optional.<String>absent());
}
private static ShellStep createCommand(
final ImmutableMap<String, String> env,
final ImmutableList<String> cmd,
File workingDirectory,
final boolean shouldPrintStdErr,
final boolean shouldPrintStdOut,
final Optional<String> stdin) {
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() {
ShellStep command = createCommand(ENV, ARGS, null);
ExecutionContext context = TestExecutionContext
.newBuilder()
.setProcessExecutor(new FakeProcessExecutor())
.build();
assertEquals("V1='two words' V2='$foo'\\''bar'\\''' bash -c 'echo $V1 $V2'",
command.getDescription(context));
}
@Test
public void testDescriptionWithEnvironmentAndPath() {
ShellStep command = createCommand(ENV, ARGS, PATH);
ExecutionContext context = TestExecutionContext
.newBuilder()
.setProcessExecutor(new FakeProcessExecutor())
.build();
assertEquals(
String.format("(cd '%s' && V1='two words' V2='$foo'\\''bar'\\''' bash -c 'echo $V1 $V2')",
PATH.getPath()),
command.getDescription(context));
}
@Test
public void testDescriptionWithPath() {
ShellStep command = createCommand(ImmutableMap.<String, String>of(), ARGS, PATH);
ExecutionContext context = TestExecutionContext
.newBuilder()
.setProcessExecutor(new FakeProcessExecutor())
.build();
assertEquals(String.format("(cd '%s' && bash -c 'echo $V1 $V2')", PATH.getPath()),
command.getDescription(context));
}
@Test
public void testDescription() {
ShellStep command = createCommand(ImmutableMap.<String, String>of(), ARGS, null);
ExecutionContext context = TestExecutionContext
.newBuilder()
.setProcessExecutor(new FakeProcessExecutor())
.build();
assertEquals("bash -c 'echo $V1 $V2'", 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 testStdErrNotPrintedOnErrorIfSilentAndNotShouldPrintStdErr() 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.SILENT);
ExecutionContext context = createContext(ImmutableMap.of(params, process), console);
command.launchAndInteractWithProcess(context, params);
assertEquals("", 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"))
.build();
Map<String, String> subProcessEnvironment = Maps.newHashMap();
subProcessEnvironment.put("PROCESS_ENVIRONMENT_VARIABLE", "PROCESS_VALUE");
command.setProcessEnvironment(context, subProcessEnvironment);
assertEquals(
"Sub-process environment should be union of client and step environments.",
subProcessEnvironment,
ImmutableMap.<String, String>builder()
.putAll(context.getEnvironment())
.putAll(ENV).build());
}
@Test
public void testStdinGetsToProcessWhenPresent() throws Exception {
final Optional<String> stdin = Optional.of("hello world!");
ShellStep command = createCommand(
ImmutableMap.<String, String>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.absent();
ShellStep command = createCommand(
ImmutableMap.<String, String>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());
}
}