/* * Copyright 2015-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.util; import static org.hamcrest.Matchers.emptyString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertThat; import com.facebook.buck.testutil.integration.TemporaryPaths; import com.facebook.buck.util.ProcessListeners.CapturingListener; import com.facebook.buck.util.ProcessListeners.StdinWritingListener; import com.facebook.buck.util.environment.Platform; import com.google.common.collect.ImmutableMap; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.concurrent.TimeUnit; import org.hamcrest.junit.ExpectedException; import org.junit.Rule; import org.junit.Test; /** Tests for {@link ListeningProcessExecutor}. */ public class ListeningProcessExecutorTest { @Rule public TemporaryPaths tmp = new TemporaryPaths(); @Rule public ExpectedException exception = ExpectedException.none(); @Test public void echoTextReceivedOnStdout() throws Exception { ListeningProcessExecutor executor = new ListeningProcessExecutor(); CapturingListener listener = new CapturingListener(); ProcessExecutorParams params; if (Platform.detect() == Platform.WINDOWS) { params = ProcessExecutorParams.ofCommand("cmd.exe", "/c", "echo", "Hello"); } else { params = ProcessExecutorParams.ofCommand("echo", "Hello"); } ListeningProcessExecutor.LaunchedProcess process = executor.launchProcess(params, listener); int returnCode = executor.waitForProcess(process); assertThat(returnCode, equalTo(0)); assertThat(listener.capturedStdout.toString("UTF-8"), equalTo(String.format("Hello%n"))); assertThat(listener.capturedStderr.toString("UTF-8"), is(emptyString())); } @Test public void processCwdIsRespected() throws Exception { ProcessExecutorParams.Builder paramsBuilder = ProcessExecutorParams.builder(); if (Platform.detect() == Platform.WINDOWS) { paramsBuilder.addCommand("cmd.exe", "/c", "type"); } else { paramsBuilder.addCommand("cat"); } paramsBuilder.addCommand("hello-world.txt"); paramsBuilder.setDirectory(tmp.getRoot()); Path helloWorldPath = tmp.getRoot().resolve("hello-world.txt"); String fileContents = "Hello, world!"; Files.write(helloWorldPath, fileContents.getBytes(StandardCharsets.UTF_8)); ListeningProcessExecutor executor = new ListeningProcessExecutor(); CapturingListener listener = new CapturingListener(); ListeningProcessExecutor.LaunchedProcess process = executor.launchProcess(paramsBuilder.build(), listener); int returnCode = executor.waitForProcess(process); assertThat(returnCode, equalTo(0)); assertThat(listener.capturedStdout.toString("UTF-8"), equalTo(fileContents)); assertThat(listener.capturedStderr.toString("UTF-8"), is(emptyString())); } @Test public void catTextSentToStdinReceivedOnStdout() throws Exception { ProcessExecutorParams params; if (Platform.detect() == Platform.WINDOWS) { params = ProcessExecutorParams.ofCommand( "python", "-c", "import sys, shutil; shutil.copyfileobj(sys.stdin, sys.stdout)"); } else { params = ProcessExecutorParams.ofCommand("cat"); } ListeningProcessExecutor executor = new ListeningProcessExecutor(); StdinWritingListener listener = new StdinWritingListener(String.format("Meow%n")); ListeningProcessExecutor.LaunchedProcess process = executor.launchProcess(params, listener); process.wantWrite(); int returnCode = executor.waitForProcess(process); assertThat(returnCode, equalTo(0)); assertThat(listener.capturedStdout.toString("UTF-8"), equalTo(String.format("Meow%n"))); assertThat(listener.capturedStderr.toString("UTF-8"), is(emptyString())); } @Test public void catMoreTextThanFitsInSingleBufferReceivedOnStdout() throws Exception { ProcessExecutorParams params; if (Platform.detect() == Platform.WINDOWS) { params = ProcessExecutorParams.ofCommand( "python", "-c", "import sys, shutil; shutil.copyfileobj(sys.stdin, sys.stdout)"); } else { params = ProcessExecutorParams.ofCommand("cat"); } ListeningProcessExecutor executor = new ListeningProcessExecutor(); StringBuilder sb = new StringBuilder(); // Use a 3 byte Unicode sequence to ensure writes go across byte buffer // boundaries, and append it as many times as needed to ensure it doesn't // fit in a single I/O buffer. String threeByteUTF8 = "\u2764"; for (int i = 0; i < ListeningProcessExecutor.LaunchedProcess.BUFFER_CAPACITY + 1; i++) { sb.append(threeByteUTF8); } sb.append(String.format("%n")); String longString = sb.toString(); StdinWritingListener listener = new StdinWritingListener(longString); ListeningProcessExecutor.LaunchedProcess process = executor.launchProcess(params, listener); process.wantWrite(); int returnCode = executor.waitForProcess(process); assertThat(returnCode, equalTo(0)); assertThat(listener.capturedStdout.toString("UTF-8"), equalTo(longString)); assertThat(listener.capturedStderr.toString("UTF-8"), is(emptyString())); } @Test public void processFailureExitCodeNotZero() throws Exception { ProcessExecutorParams params; if (Platform.detect() == Platform.WINDOWS) { params = ProcessExecutorParams.ofCommand("cmd.exe", "/c", "exit", "1"); } else { params = ProcessExecutorParams.ofCommand("false"); } ListeningProcessExecutor executor = new ListeningProcessExecutor(); CapturingListener listener = new CapturingListener(); ListeningProcessExecutor.LaunchedProcess process = executor.launchProcess(params, listener); int returnCode = executor.waitForProcess(process); assertThat(returnCode, not(equalTo(0))); assertThat(listener.capturedStdout.toString("UTF-8"), is(emptyString())); assertThat(listener.capturedStderr.toString("UTF-8"), is(emptyString())); } @Test public void nonExistentBinaryExitCodeNotZeroOnLimitedWait() throws Exception { ListeningProcessExecutor executor = new ListeningProcessExecutor(); CapturingListener listener = new CapturingListener(); ListeningProcessExecutor.LaunchedProcess process = executor.launchProcess( ProcessExecutorParams.ofCommand("this-better-not-be-a-process-on-your-system-for-real"), listener); int returnCode = executor.waitForProcess(process, Long.MAX_VALUE, TimeUnit.SECONDS); assertThat(returnCode, not(equalTo(0))); assertThat(listener.capturedStdout.toString("UTF-8"), is(emptyString())); assertThat(listener.capturedStderr.toString("UTF-8"), is(emptyString())); } @Test public void nonExistentBinaryThrowsExceptionOnInfiniteWait() throws Exception { ListeningProcessExecutor executor = new ListeningProcessExecutor(); CapturingListener listener = new CapturingListener(); ListeningProcessExecutor.LaunchedProcess process = executor.launchProcess( ProcessExecutorParams.ofCommand("this-better-not-be-a-process-on-your-system-for-real"), listener); exception.expect(IOException.class); exception.expectMessage( "Failed to start process [this-better-not-be-a-process-on-your-system-for-real]"); executor.waitForProcess(process); } @Test public void waitForProcessReturnsMinIntegerOnTimeout() throws Exception { ListeningProcessExecutor executor = new ListeningProcessExecutor(); CapturingListener listener = new CapturingListener(); ProcessExecutorParams params; if (Platform.detect() == Platform.WINDOWS) { params = ProcessExecutorParams.ofCommand("python", "-c", "import time; time.sleep(50)"); } else { params = ProcessExecutorParams.ofCommand("sleep", "50"); } ListeningProcessExecutor.LaunchedProcess process = executor.launchProcess(params, listener); int returnCode = executor.waitForProcess(process, 100, TimeUnit.MILLISECONDS); assertThat(returnCode, equalTo(Integer.MIN_VALUE)); assertThat(listener.capturedStdout.toString("UTF-8"), is(emptyString())); assertThat(listener.capturedStderr.toString("UTF-8"), is(emptyString())); executor.destroyProcess(process, /* force */ true); executor.waitForProcess(process); } @Test public void clearsEnvWhenExplicitlySet() throws Exception { ListeningProcessExecutor executor = new ListeningProcessExecutor(); CapturingListener listener = new CapturingListener(); ProcessExecutorParams params; if (Platform.detect() == Platform.WINDOWS) { params = ProcessExecutorParams.ofCommand("cmd.exe", "/c", "set"); } else { params = ProcessExecutorParams.ofCommand("env"); } params = params.withEnvironment(ImmutableMap.of()); ListeningProcessExecutor.LaunchedProcess process = executor.launchProcess(params, listener); int returnCode = executor.waitForProcess(process); assertThat(returnCode, equalTo(0)); assertThat(listener.capturedStdout.toString("UTF-8"), is(emptyString())); assertThat(listener.capturedStderr.toString("UTF-8"), is(emptyString())); } }