blob: e1b4ec70a80bc0f2d96282cb55e5e6773b280085 [file] [log] [blame]
// Copyright 2016 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.windows;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_16LE;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Joiner;
import com.google.devtools.build.lib.shell.ShellUtils;
import com.google.devtools.build.lib.testutil.TestSpec;
import com.google.devtools.build.lib.util.OS;
import com.google.devtools.build.lib.windows.jni.WindowsProcesses;
import com.google.devtools.build.runfiles.Runfiles;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Unit tests for {@link WindowsProcesses}.
*/
@RunWith(JUnit4.class)
@TestSpec(localOnly = true, supportedOs = OS.WINDOWS)
public class WindowsProcessesTest {
private String mockSubprocess;
private String mockBinary;
private long process;
@Before
public void loadJni() throws Exception {
Runfiles runfiles = Runfiles.create();
mockSubprocess =
runfiles.rlocation(
"io_bazel/src/test/java/com/google/devtools/build/lib/MockSubprocess_deploy.jar");
mockBinary = System.getProperty("java.home") + "\\bin\\java.exe";
process = -1;
}
@After
public void terminateProcess() throws Exception {
if (process != -1) {
WindowsProcesses.terminate(process);
WindowsProcesses.deleteProcess(process);
process = -1;
}
}
private static List<String> quoteArgs(List<String> argv, String... args) {
for (String arg : args) {
argv.add(ShellUtils.windowsEscapeArg(arg));
}
return argv;
}
private static List<String> quoteArgs(String... args) {
List<String> argv = new ArrayList<>();
return quoteArgs(argv, args);
}
private String mockArgs(String... args) {
List<String> argv = new ArrayList<>();
argv.add("-jar");
argv.add(mockSubprocess);
quoteArgs(argv, args);
return Joiner.on(" ").join(argv);
}
private void assertNoProcessError() throws Exception {
assertThat(WindowsProcesses.processGetLastError(process)).isEmpty();
}
private void assertNoStreamError(long stream) throws Exception {
assertThat(WindowsProcesses.streamGetLastError(stream)).isEmpty();
}
@Test
public void testDoesNotQuoteSimpleArg() throws Exception {
assertThat(quoteArgs("x", "a")).containsExactly("x", "a").inOrder();
}
@Test
public void testQuotesEmptyArg() throws Exception {
assertThat(quoteArgs("x", "")).containsExactly("x", "\"\"").inOrder();
}
@Test
public void testQuotesArgWithSpace() throws Exception {
assertThat(quoteArgs("x", "a b")).containsExactly("x", "\"a b\"").inOrder();
}
@Test
public void testDoesNotQuoteArgWithBackslash() throws Exception {
assertThat(quoteArgs("x", "a\\b")).containsExactly("x", "a\\b").inOrder();
}
@Test
public void testDoesNotQuoteArgWithSingleQuote() throws Exception {
assertThat(quoteArgs("x", "a'b")).containsExactly("x", "a'b").inOrder();
}
@Test
public void testQuotesArgWithDoubleQuote() throws Exception {
assertThat(quoteArgs("x", "a\"b", "y")).containsExactly("x", "\"a\\\"b\"", "y").inOrder();
}
@Test
public void testSmoke() throws Exception {
process =
WindowsProcesses.createProcess(
mockBinary, mockArgs("Ia5", "Oa"), null, null, null, null);
assertNoProcessError();
byte[] input = "HELLO".getBytes(UTF_8);
byte[] output = new byte[5];
WindowsProcesses.writeStdin(process, input, 0, 5);
assertNoProcessError();
readStdout(output, 0, 5);
assertNoStreamError(WindowsProcesses.getStdout(process));
assertThat(new String(output, UTF_8)).isEqualTo("HELLO");
}
@Test
public void testPingpong() throws Exception {
List<String> args = new ArrayList<>();
for (int i = 0; i < 100; i++) {
args.add("Ia3");
args.add("Oa");
}
process =
WindowsProcesses.createProcess(
mockBinary, mockArgs(args.toArray(new String[] {})), null, null, null, null);
for (int i = 0; i < 100; i++) {
byte[] input = String.format("%03d", i).getBytes(UTF_8);
assertThat(input.length).isEqualTo(3);
assertThat(WindowsProcesses.writeStdin(process, input, 0, 3)).isEqualTo(3);
byte[] output = new byte[3];
assertThat(readStdout(output, 0, 3)).isEqualTo(3);
assertThat(Integer.parseInt(new String(output, UTF_8))).isEqualTo(i);
}
}
private int readStdout(byte[] output, int offset, int length) {
return WindowsProcesses.readStream(
WindowsProcesses.getStdout(process), output, offset, length);
}
private int readStderr(byte[] output, int offset, int length) {
return WindowsProcesses.readStream(
WindowsProcesses.getStderr(process), output, offset, length);
}
@Test
public void testExitCode() throws Exception {
process =
WindowsProcesses.createProcess(mockBinary, mockArgs("X42"), null, null, null, null);
assertThat(WindowsProcesses.waitFor(process, -1)).isEqualTo(0);
assertThat(WindowsProcesses.getExitCode(process)).isEqualTo(42);
assertNoProcessError();
}
@Test
public void testPartialRead() throws Exception {
process =
WindowsProcesses.createProcess(
mockBinary, mockArgs("O-HELLO"), null, null, null, null);
byte[] one = new byte[2];
byte[] two = new byte[3];
assertThat(readStdout(one, 0, 2)).isEqualTo(2);
assertNoStreamError(WindowsProcesses.getStdout(process));
assertThat(readStdout(two, 0, 3)).isEqualTo(3);
assertNoStreamError(WindowsProcesses.getStdout(process));
assertThat(new String(one, UTF_8)).isEqualTo("HE");
assertThat(new String(two, UTF_8)).isEqualTo("LLO");
}
@Test
public void testArrayOutOfBounds() throws Exception {
process =
WindowsProcesses.createProcess(mockBinary, mockArgs("O-oob"), null, null, null, null);
byte[] buf = new byte[3];
assertThat(readStdout(buf, -1, 3)).isEqualTo(-1);
assertThat(readStdout(buf, 0, 5)).isEqualTo(-1);
assertThat(readStdout(buf, 4, 1)).isEqualTo(-1);
assertThat(readStdout(buf, 2, -1)).isEqualTo(-1);
assertThat(readStdout(buf, Integer.MAX_VALUE, 2)).isEqualTo(-1);
assertThat(readStdout(buf, 2, Integer.MAX_VALUE)).isEqualTo(-1);
assertThat(readStderr(buf, -1, 3)).isEqualTo(-1);
assertThat(readStderr(buf, 0, 5)).isEqualTo(-1);
assertThat(readStderr(buf, 4, 1)).isEqualTo(-1);
assertThat(readStderr(buf, 2, -1)).isEqualTo(-1);
assertThat(readStderr(buf, Integer.MAX_VALUE, 2)).isEqualTo(-1);
assertThat(readStderr(buf, 2, Integer.MAX_VALUE)).isEqualTo(-1);
assertThat(WindowsProcesses.writeStdin(process, buf, -1, 3)).isEqualTo(-1);
assertThat(WindowsProcesses.writeStdin(process, buf, 0, 5)).isEqualTo(-1);
assertThat(WindowsProcesses.writeStdin(process, buf, 4, 1)).isEqualTo(-1);
assertThat(WindowsProcesses.writeStdin(process, buf, 2, -1)).isEqualTo(-1);
assertThat(WindowsProcesses.writeStdin(process, buf, Integer.MAX_VALUE, 2))
.isEqualTo(-1);
assertThat(WindowsProcesses.writeStdin(process, buf, 2, Integer.MAX_VALUE))
.isEqualTo(-1);
assertThat(readStdout(buf, 0, 3)).isEqualTo(3);
assertThat(new String(buf, UTF_8)).isEqualTo("oob");
}
@Test
public void testOffsetedOps() throws Exception {
process =
WindowsProcesses.createProcess(
mockBinary, mockArgs("Ia3", "Oa"), null, null, null, null);
byte[] input = "01234".getBytes(UTF_8);
byte[] output = "abcde".getBytes(UTF_8);
assertThat(WindowsProcesses.writeStdin(process, input, 1, 3)).isEqualTo(3);
assertNoProcessError();
int rv = readStdout(output, 1, 3);
assertNoProcessError();
assertThat(rv).isEqualTo(3);
assertThat(new String(output, UTF_8)).isEqualTo("a123e");
}
@Test
public void testParallelStdoutAndStderr() throws Exception {
process =
WindowsProcesses.createProcess(
mockBinary,
mockArgs(
"O-out1", "E-err1", "O-out2", "E-err2", "E-err3", "O-out3", "E-err4", "O-out4"),
null,
null,
null,
null);
assertNoProcessError();
byte[] buf = new byte[4];
assertThat(readStdout(buf, 0, 4)).isEqualTo(4);
assertThat(new String(buf, UTF_8)).isEqualTo("out1");
assertThat(readStderr(buf, 0, 4)).isEqualTo(4);
assertThat(new String(buf, UTF_8)).isEqualTo("err1");
assertThat(readStderr(buf, 0, 4)).isEqualTo(4);
assertThat(new String(buf, UTF_8)).isEqualTo("err2");
assertThat(readStdout(buf, 0, 4)).isEqualTo(4);
assertThat(new String(buf, UTF_8)).isEqualTo("out2");
assertThat(readStdout(buf, 0, 4)).isEqualTo(4);
assertThat(new String(buf, UTF_8)).isEqualTo("out3");
assertThat(readStderr(buf, 0, 4)).isEqualTo(4);
assertThat(new String(buf, UTF_8)).isEqualTo("err3");
assertThat(readStderr(buf, 0, 4)).isEqualTo(4);
assertThat(new String(buf, UTF_8)).isEqualTo("err4");
assertThat(readStdout(buf, 0, 4)).isEqualTo(4);
assertThat(new String(buf, UTF_8)).isEqualTo("out4");
}
@Test
public void testExecutableNotFound() throws Exception {
process =
WindowsProcesses.createProcess(
"ThisExecutableDoesNotExist", "TheseArgsDontMatter", null, null, null, null);
assertThat(WindowsProcesses.processGetLastError(process))
.contains("The system cannot find the file specified.");
byte[] buf = new byte[1];
assertThat(readStdout(buf, 0, 1)).isEqualTo(0);
}
@Test
public void testReadingAndWritingAfterTermination() throws Exception {
process =
WindowsProcesses.createProcess(mockBinary, mockArgs("X42"), null, null, null, null);
byte[] buf = new byte[1];
assertThat(readStdout(buf, 0, 1)).isEqualTo(0);
assertThat(readStderr(buf, 0, 1)).isEqualTo(0);
assertThat(WindowsProcesses.writeStdin(process, buf, 0, 1)).isEqualTo(-1);
}
@Test
public void testNewEnvironmentVariables() throws Exception {
byte[] data = "ONE=one\0TWO=twotwo\0\0".getBytes(UTF_16LE);
process =
WindowsProcesses.createProcess(
mockBinary, mockArgs("O$ONE", "O$TWO"), data, null, null, null);
assertNoProcessError();
byte[] buf = new byte[3];
assertThat(readStdout(buf, 0, 3)).isEqualTo(3);
assertThat(new String(buf, UTF_8)).isEqualTo("one");
buf = new byte[6];
assertThat(readStdout(buf, 0, 6)).isEqualTo(6);
assertThat(new String(buf, UTF_8)).isEqualTo("twotwo");
}
@Test
public void testNoZeroInEnvBuffer() throws Exception {
byte[] data = "clown".getBytes(UTF_16LE);
process = WindowsProcesses.createProcess(mockBinary, mockArgs(), data, null, null, null);
assertThat(WindowsProcesses.processGetLastError(process)).isNotEmpty();
}
@Test
public void testMissingFinalDoubleZeroInEnvBuffer() throws Exception {
byte[] data = "FOO=bar\0".getBytes(UTF_16LE);
process = WindowsProcesses.createProcess(mockBinary, mockArgs(), data, null, null, null);
assertThat(WindowsProcesses.processGetLastError(process)).isNotEmpty();
}
@Test
public void testOneByteEnvBuffer() throws Exception {
byte[] data = "a".getBytes(UTF_16LE);
process = WindowsProcesses.createProcess(mockBinary, mockArgs(), data, null, null, null);
assertThat(WindowsProcesses.processGetLastError(process)).isNotEmpty();
}
@Test
public void testOneZeroEnvBuffer() throws Exception {
byte[] data = "\0".getBytes(UTF_16LE);
process = WindowsProcesses.createProcess(mockBinary, mockArgs(), data, null, null, null);
assertThat(WindowsProcesses.processGetLastError(process)).isNotEmpty();
}
@Test
public void testTwoZerosInEnvBuffer() throws Exception {
byte[] data = "\0\0".getBytes(UTF_16LE);
process = WindowsProcesses.createProcess(mockBinary, mockArgs(), data, null, null, null);
assertThat(WindowsProcesses.processGetLastError(process)).isEmpty();
}
@Test
public void testRedirect() throws Exception {
String stdoutFile = System.getenv("TEST_TMPDIR") + "\\stdout_redirect";
String stderrFile = System.getenv("TEST_TMPDIR") + "\\stderr_redirect";
process =
WindowsProcesses.createProcess(
mockBinary, mockArgs("O-one", "E-two"), null, null, stdoutFile, stderrFile);
assertThat(process).isGreaterThan(0L);
assertNoProcessError();
assertThat(WindowsProcesses.waitFor(process, -1)).isEqualTo(0);
WindowsProcesses.getExitCode(process);
assertNoProcessError();
byte[] stdout = Files.readAllBytes(Paths.get(stdoutFile));
byte[] stderr = Files.readAllBytes(Paths.get(stderrFile));
assertThat(new String(stdout, UTF_8)).isEqualTo("one");
assertThat(new String(stderr, UTF_8)).isEqualTo("two");
}
@Test
public void testRedirectToSameFile() throws Exception {
String file = System.getenv("TEST_TMPDIR") + "\\captured_";
process =
WindowsProcesses.createProcess(
mockBinary, mockArgs("O-one", "E-two"), null, null, file, file);
assertThat(process).isGreaterThan(0L);
assertNoProcessError();
assertThat(WindowsProcesses.waitFor(process, -1)).isEqualTo(0);
WindowsProcesses.getExitCode(process);
assertNoProcessError();
byte[] bytes = Files.readAllBytes(Paths.get(file));
assertThat(new String(bytes, UTF_8)).isEqualTo("onetwo");
}
@Test
public void testReadingFromRedirectedStreams() throws Exception {
String stdoutFile = System.getenv("TEST_TMPDIR") + "\\captured_stdout";
String stderrFile = System.getenv("TEST_TMPDIR") + "\\captured_stderr";
process =
WindowsProcesses.createProcess(
mockBinary, mockArgs("O-one", "E-two"), null, null, stdoutFile, stderrFile);
assertNoProcessError();
byte[] buf = new byte[1];
assertThat(readStdout(buf, 0, 1)).isEqualTo(0);
assertThat(readStderr(buf, 0, 1)).isEqualTo(0);
WindowsProcesses.waitFor(process, -1);
}
@Test
public void testRedirectedErrorStream() throws Exception {
process =
WindowsProcesses.createProcess(
mockBinary, mockArgs("O-one", "E-two"), null, null, null, null, true);
assertNoProcessError();
byte[] buf = new byte[6];
assertThat(readStdout(buf, 0, 3)).isEqualTo(3);
assertThat(readStdout(buf, 3, 3)).isEqualTo(3);
assertThat(new String(buf, UTF_8)).isEqualTo("onetwo");
assertThat(readStderr(buf, 0, 1)).isEqualTo(0);
WindowsProcesses.waitFor(process, -1);
}
@Test
public void testAppendToExistingFile() throws Exception {
String stdoutFile = System.getenv("TEST_TMPDIR") + "\\stdout_atef";
String stderrFile = System.getenv("TEST_TMPDIR") + "\\stderr_atef";
Path stdout = Paths.get(stdoutFile);
Path stderr = Paths.get(stderrFile);
Files.write(stdout, "out1".getBytes(UTF_8));
Files.write(stderr, "err1".getBytes(UTF_8));
process =
WindowsProcesses.createProcess(
mockBinary, mockArgs("O-out2", "E-err2"), null, null, stdoutFile, stderrFile);
assertNoProcessError();
WindowsProcesses.waitFor(process, -1);
WindowsProcesses.getExitCode(process);
assertNoProcessError();
byte[] stdoutBytes = Files.readAllBytes(Paths.get(stdoutFile));
byte[] stderrBytes = Files.readAllBytes(Paths.get(stderrFile));
assertThat(new String(stdoutBytes, UTF_8)).isEqualTo("out1out2");
assertThat(new String(stderrBytes, UTF_8)).isEqualTo("err1err2");
}
@Test
public void testCwd() throws Exception {
String dir1 = System.getenv("TEST_TMPDIR") + "/dir1";
new File(dir1).mkdir();
process =
WindowsProcesses.createProcess(mockBinary, mockArgs("O."), null, dir1, null, null);
assertNoProcessError();
byte[] buf = new byte[1024]; // Windows MAX_PATH is 260, but whatever
int len = readStdout(buf, 0, 1024);
assertNoProcessError();
assertThat(new String(buf, 0, len, UTF_8).replace("\\", "/")).isEqualTo(dir1);
}
@Test
public void testTimeout() throws Exception {
process =
WindowsProcesses.createProcess(
mockBinary, mockArgs("W5", "X0"), null, null, null, null);
assertThat(WindowsProcesses.waitFor(process, 1000)).isEqualTo(1);
}
}