/*
* Copyright (C) 2014 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.tools.subprocess;
import com.facebook.tools.io.MockIO;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class TestSubprocessBuilder {
private Queue<CreateProcessParameters> parameters;
private MockIO io;
@BeforeMethod(alwaysRun = true)
public void setUp() {
parameters = new ArrayDeque<>();
SubprocessBuilder builder = new SubprocessBuilder(
new ProcessBuilderWrapper() {
@Override
public Process createProcess(
RedirectErrorsTo redirectErrorsTo,
Map<String, String> environmentOverrides,
File workingDirectory,
List<String> command
) {
parameters.add(
new CreateProcessParameters(
redirectErrorsTo, environmentOverrides, workingDirectory, command
)
);
return new DummyProcess();
}
}
);
io = new MockIO(builder);
}
@Test(groups = "fast")
public void testStart() {
io.subprocess.forCommand("foo").start();
assertCommand("foo");
}
@Test(groups = "fast")
public void testStream() {
io.subprocess.forCommand("foo").stream();
assertCommand("foo");
}
@Test(groups = "fast")
public void testArguments() {
io.subprocess.forCommand("foo")
.withArguments("bar")
.withArguments("this", "is", "a test")
.withArguments(123)
.withArguments(Arrays.asList(456, "abc"))
.start();
assertCommand("foo", "bar", "this", "is", "a test", "123", "456", "abc");
}
@Test(groups = "fast")
public void testRedirectStdOutToStdErr() {
io.subprocess.forCommand("foo").redirectStderrToStdout().start();
assertCommand(RedirectErrorsTo.STDOUT, Collections.<String, String>emptyMap(), null, "foo");
}
@Test(groups = "fast")
public void testEnvironmentVariables() {
io.subprocess.forCommand("foo")
.withEnvironmentVariable("bar", "baz")
.withoutEnvironmentVariable("bad")
.start();
Map<String, String> expected = new LinkedHashMap<>();
expected.put("bar", "baz");
expected.put("bad", null);
assertCommand(RedirectErrorsTo.STDERR, expected, null, "foo");
}
@Test(groups = "fast")
public void testWorkingDirectory() {
io.subprocess.forCommand("foo")
.withWorkingDirectory(new File("/tmp/test"))
.start();
assertCommand(
RedirectErrorsTo.STDERR, Collections.<String, String>emptyMap(), new File("/tmp/test"), "foo"
);
}
@Test(groups = "fast")
public void testEchoCommand() {
io.subprocess.forCommand("foo")
.withArguments("bar", "baz")
.echoCommand(io)
.start()
.waitFor();
assertCommand("foo", "bar", "baz");
Assert.assertEquals(io.getOut(), "foo bar baz\n");
Assert.assertEquals(io.getErr(), "");
}
@Test(groups = "fast")
public void testEchoOutput() {
io.subprocess.forCommand("foo")
.echoOutput(io)
.start()
.waitFor();
assertCommand("foo");
Assert.assertEquals(io.getOut(), "out: this is a test of stdout\n");
Assert.assertEquals(io.getErr(), "err: this is a test of stderr\n");
}
@Test(groups = "fast")
public void testOutputBytesLimit() {
Subprocess foo = io.subprocess.forCommand("foo")
.echoOutput(io)
.outputBytesLimit(19)
.start();
assertCommand("foo");
Assert.assertEquals(foo.getOutput(), "out: this is a test");
Assert.assertEquals(foo.getError(), "err: this is a test");
}
private void assertCommand(String... command) {
assertCommand(RedirectErrorsTo.STDERR, Collections.<String, String>emptyMap(), null, command);
}
private void assertCommand(
RedirectErrorsTo redirectErrorsTo,
Map<String, String> environmentOverrides,
File workingDirectory,
String... command
) {
CreateProcessParameters actual = parameters.poll();
actual.assertParameters(redirectErrorsTo, environmentOverrides, workingDirectory, command);
}
private static class CreateProcessParameters {
private final RedirectErrorsTo redirectErrorsTo;
private final Map<String, String> environmentOverrides;
private final File workingDirectory;
private final List<String> command;
private CreateProcessParameters(
RedirectErrorsTo redirectErrorsTo,
Map<String, String> environmentOverrides,
File workingDirectory,
List<String> command
) {
this.redirectErrorsTo = redirectErrorsTo;
this.environmentOverrides = environmentOverrides;
this.workingDirectory = workingDirectory;
this.command = command;
}
public void assertParameters(
RedirectErrorsTo redirectErrorsTo,
Map<String, String> environmentOverrides,
File workingDirectory,
String... command
) {
Assert.assertEquals(this.command, Arrays.asList(command));
Assert.assertEquals(this.redirectErrorsTo, redirectErrorsTo);
Assert.assertEquals(this.workingDirectory, workingDirectory);
Assert.assertEquals(this.environmentOverrides, environmentOverrides);
}
}
private static class DummyProcess extends Process {
private final LatchInputStream stdout = new LatchInputStream("out: this is a test of stdout\n");
private final LatchInputStream stderr = new LatchInputStream("err: this is a test of stderr\n");
@Override
public OutputStream getOutputStream() {
return null;
}
@Override
public InputStream getInputStream() {
return stdout;
}
@Override
public InputStream getErrorStream() {
return stderr;
}
@Override
public int waitFor() {
stdout.await();
stderr.await();
return 0;
}
@Override
public int exitValue() {
return 0;
}
@Override
public void destroy() {
}
}
private static class LatchInputStream extends InputStream {
private final CountDownLatch eof = new CountDownLatch(1);
private final InputStream delegate;
private LatchInputStream(String content) {
delegate = new ByteArrayInputStream(content.getBytes());
}
@Override
public int read() throws IOException {
int result = delegate.read();
if (result == -1) {
eof.countDown();
}
return result;
}
public void await() {
try {
eof.await(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
Assert.fail("Interrupted waiting for eof");
}
}
}
}