| // 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.collect.ImmutableList; |
| 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 String mockArgs(String... args) { |
| List<String> argv = new ArrayList<>(); |
| |
| argv.add("-jar"); |
| argv.add(mockSubprocess); |
| for (String arg : args) { |
| argv.add(arg); |
| } |
| |
| return WindowsProcesses.quoteCommandLine(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(WindowsProcesses.quoteCommandLine(ImmutableList.of("x", "a"))).isEqualTo("x a"); |
| } |
| |
| @Test |
| public void testQuotesEmptyArg() throws Exception { |
| assertThat(WindowsProcesses.quoteCommandLine(ImmutableList.of("x", ""))).isEqualTo("x \"\""); |
| } |
| |
| @Test |
| public void testQuotesArgWithSpace() throws Exception { |
| assertThat(WindowsProcesses.quoteCommandLine(ImmutableList.of("x", "a b"))) |
| .isEqualTo("x \"a b\""); |
| } |
| |
| @Test |
| public void testDoesNotQuoteArgWithBackslash() throws Exception { |
| assertThat(WindowsProcesses.quoteCommandLine(ImmutableList.of("x", "a\\b"))) |
| .isEqualTo("x a\\b"); |
| } |
| |
| @Test |
| public void testDoesNotQuoteArgWithSingleQuote() throws Exception { |
| assertThat(WindowsProcesses.quoteCommandLine(ImmutableList.of("x", "a'b"))).isEqualTo("x a'b"); |
| } |
| |
| @Test |
| public void testDoesNotQuoteArgWithDoubleQuote() throws Exception { |
| assertThat(WindowsProcesses.quoteCommandLine(ImmutableList.of("x", "a\"b"))) |
| .isEqualTo("x a\\\"b"); |
| } |
| |
| @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); |
| } |
| } |