pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 1 | // Copyright 2016 The Bazel Authors. All rights reserved. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | package com.google.devtools.build.lib.windows; |
| 16 | |
| 17 | import static com.google.common.truth.Truth.assertThat; |
Laszlo Csomor | d5c1eee | 2019-02-13 04:52:45 -0800 | [diff] [blame] | 18 | import static java.nio.charset.StandardCharsets.UTF_8; |
larsrc | 082d987 | 2021-01-13 06:40:59 -0800 | [diff] [blame] | 19 | import static org.junit.Assert.assertThrows; |
pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 20 | |
laszlocsomor | d2cb96a | 2020-01-14 02:35:04 -0800 | [diff] [blame] | 21 | import com.google.common.collect.ImmutableList; |
pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 22 | import com.google.common.collect.ImmutableMap; |
laszlocsomor | 1da4861 | 2019-02-15 01:41:57 -0800 | [diff] [blame] | 23 | import com.google.devtools.build.lib.shell.ShellUtils; |
pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 24 | import com.google.devtools.build.lib.shell.Subprocess; |
| 25 | import com.google.devtools.build.lib.shell.SubprocessBuilder; |
| 26 | import com.google.devtools.build.lib.testutil.TestSpec; |
| 27 | import com.google.devtools.build.lib.util.OS; |
Laszlo Csomor | 4d05f38 | 2018-11-22 07:53:57 -0800 | [diff] [blame] | 28 | import com.google.devtools.build.runfiles.Runfiles; |
pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 29 | import java.io.File; |
larsrc | 082d987 | 2021-01-13 06:40:59 -0800 | [diff] [blame] | 30 | import java.io.InputStream; |
pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 31 | import org.junit.After; |
| 32 | import org.junit.Before; |
| 33 | import org.junit.Test; |
| 34 | import org.junit.runner.RunWith; |
| 35 | import org.junit.runners.JUnit4; |
| 36 | |
michajlo | f6cc8ac | 2021-06-07 12:04:04 -0700 | [diff] [blame] | 37 | /** Unit tests for {@link WindowsSubprocess}. */ |
pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 38 | @RunWith(JUnit4.class) |
michajlo | f6cc8ac | 2021-06-07 12:04:04 -0700 | [diff] [blame] | 39 | @TestSpec(supportedOs = OS.WINDOWS) |
pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 40 | public class WindowsSubprocessTest { |
pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 41 | private String mockSubprocess; |
| 42 | private String mockBinary; |
| 43 | private Subprocess process; |
Laszlo Csomor | d5c1eee | 2019-02-13 04:52:45 -0800 | [diff] [blame] | 44 | private Runfiles runfiles; |
pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 45 | |
| 46 | @Before |
| 47 | public void loadJni() throws Exception { |
Laszlo Csomor | d5c1eee | 2019-02-13 04:52:45 -0800 | [diff] [blame] | 48 | runfiles = Runfiles.create(); |
Laszlo Csomor | 4d05f38 | 2018-11-22 07:53:57 -0800 | [diff] [blame] | 49 | mockSubprocess = |
| 50 | runfiles.rlocation( |
pcloudy | f8f4332 | 2020-04-08 07:56:38 -0700 | [diff] [blame] | 51 | "io_bazel/src/test/java/com/google/devtools/build/lib/windows/MockSubprocess_deploy.jar"); |
pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 52 | mockBinary = System.getProperty("java.home") + "\\bin\\java.exe"; |
| 53 | |
| 54 | process = null; |
| 55 | } |
| 56 | |
| 57 | @After |
| 58 | public void terminateProcess() throws Exception { |
| 59 | if (process != null) { |
| 60 | process.destroy(); |
| 61 | process.close(); |
| 62 | process = null; |
| 63 | } |
| 64 | } |
| 65 | |
Laszlo Csomor | ecac47d | 2019-07-01 04:29:34 -0700 | [diff] [blame] | 66 | @Test |
| 67 | public void testSystemRootIsSetByDefault() throws Exception { |
| 68 | SubprocessBuilder subprocessBuilder = new SubprocessBuilder(WindowsSubprocessFactory.INSTANCE); |
pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 69 | subprocessBuilder.setWorkingDirectory(new File(".")); |
laszlocsomor | d2cb96a | 2020-01-14 02:35:04 -0800 | [diff] [blame] | 70 | subprocessBuilder.setArgv(ImmutableList.of(mockBinary, "-jar", mockSubprocess, "O$SYSTEMROOT")); |
pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 71 | process = subprocessBuilder.start(); |
| 72 | process.waitFor(); |
| 73 | assertThat(process.exitValue()).isEqualTo(0); |
| 74 | |
| 75 | byte[] buf = new byte[11]; |
| 76 | process.getInputStream().read(buf); |
Laszlo Csomor | d5c1eee | 2019-02-13 04:52:45 -0800 | [diff] [blame] | 77 | assertThat(new String(buf, UTF_8).trim()).isEqualTo(System.getenv("SYSTEMROOT").trim()); |
pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 78 | } |
| 79 | |
| 80 | @Test |
Laszlo Csomor | ecac47d | 2019-07-01 04:29:34 -0700 | [diff] [blame] | 81 | public void testSystemDriveIsSetByDefault() throws Exception { |
| 82 | SubprocessBuilder subprocessBuilder = new SubprocessBuilder(WindowsSubprocessFactory.INSTANCE); |
pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 83 | subprocessBuilder.setWorkingDirectory(new File(".")); |
laszlocsomor | d2cb96a | 2020-01-14 02:35:04 -0800 | [diff] [blame] | 84 | subprocessBuilder.setArgv( |
| 85 | ImmutableList.of(mockBinary, "-jar", mockSubprocess, "O$SYSTEMDRIVE")); |
pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 86 | process = subprocessBuilder.start(); |
| 87 | process.waitFor(); |
| 88 | assertThat(process.exitValue()).isEqualTo(0); |
| 89 | |
| 90 | byte[] buf = new byte[3]; |
| 91 | process.getInputStream().read(buf); |
Laszlo Csomor | d5c1eee | 2019-02-13 04:52:45 -0800 | [diff] [blame] | 92 | assertThat(new String(buf, UTF_8).trim()).isEqualTo(System.getenv("SYSTEMDRIVE").trim()); |
pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 93 | } |
| 94 | |
| 95 | @Test |
Laszlo Csomor | ecac47d | 2019-07-01 04:29:34 -0700 | [diff] [blame] | 96 | public void testSystemRootIsSet() throws Exception { |
| 97 | SubprocessBuilder subprocessBuilder = new SubprocessBuilder(WindowsSubprocessFactory.INSTANCE); |
pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 98 | subprocessBuilder.setWorkingDirectory(new File(".")); |
laszlocsomor | d2cb96a | 2020-01-14 02:35:04 -0800 | [diff] [blame] | 99 | subprocessBuilder.setArgv(ImmutableList.of(mockBinary, "-jar", mockSubprocess, "O$SYSTEMROOT")); |
pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 100 | // Case shouldn't matter on Windows |
| 101 | subprocessBuilder.setEnv(ImmutableMap.of("SystemRoot", "C:\\MySystemRoot")); |
| 102 | process = subprocessBuilder.start(); |
| 103 | process.waitFor(); |
| 104 | assertThat(process.exitValue()).isEqualTo(0); |
| 105 | |
| 106 | byte[] buf = new byte[16]; |
| 107 | process.getInputStream().read(buf); |
Laszlo Csomor | d5c1eee | 2019-02-13 04:52:45 -0800 | [diff] [blame] | 108 | assertThat(new String(buf, UTF_8).trim()).isEqualTo("C:\\MySystemRoot"); |
pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 109 | } |
| 110 | |
| 111 | @Test |
Laszlo Csomor | ecac47d | 2019-07-01 04:29:34 -0700 | [diff] [blame] | 112 | public void testSystemDriveIsSet() throws Exception { |
| 113 | SubprocessBuilder subprocessBuilder = new SubprocessBuilder(WindowsSubprocessFactory.INSTANCE); |
pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 114 | subprocessBuilder.setWorkingDirectory(new File(".")); |
laszlocsomor | d2cb96a | 2020-01-14 02:35:04 -0800 | [diff] [blame] | 115 | subprocessBuilder.setArgv( |
| 116 | ImmutableList.of(mockBinary, "-jar", mockSubprocess, "O$SYSTEMDRIVE")); |
pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 117 | // Case shouldn't matter on Windows |
| 118 | subprocessBuilder.setEnv(ImmutableMap.of("SystemDrive", "X:")); |
| 119 | process = subprocessBuilder.start(); |
| 120 | process.waitFor(); |
| 121 | assertThat(process.exitValue()).isEqualTo(0); |
| 122 | |
| 123 | byte[] buf = new byte[3]; |
| 124 | process.getInputStream().read(buf); |
Laszlo Csomor | d5c1eee | 2019-02-13 04:52:45 -0800 | [diff] [blame] | 125 | assertThat(new String(buf, UTF_8).trim()).isEqualTo("X:"); |
| 126 | } |
| 127 | |
larsrc | 082d987 | 2021-01-13 06:40:59 -0800 | [diff] [blame] | 128 | @Test |
| 129 | public void testStreamAvailable_zeroAfterClose() throws Exception { |
| 130 | SubprocessBuilder subprocessBuilder = new SubprocessBuilder(WindowsSubprocessFactory.INSTANCE); |
| 131 | subprocessBuilder.setWorkingDirectory(new File(".")); |
| 132 | subprocessBuilder.setArgv(ImmutableList.of(mockBinary, "-jar", mockSubprocess, "OHELLO")); |
| 133 | process = subprocessBuilder.start(); |
| 134 | InputStream inputStream = process.getInputStream(); |
| 135 | // We don't know if the process has already written to the pipe |
| 136 | assertThat(inputStream.available()).isAnyOf(0, 5); |
| 137 | process.waitFor(); |
| 138 | // Windows allows streams to be read after the process has died. |
| 139 | assertThat(inputStream.available()).isAnyOf(0, 5); |
| 140 | inputStream.close(); |
Googler | 3aa4fa0 | 2021-02-05 10:17:25 -0800 | [diff] [blame] | 141 | assertThat(assertThrows(IllegalStateException.class, inputStream::available)) |
| 142 | .hasMessageThat() |
larsrc | 082d987 | 2021-01-13 06:40:59 -0800 | [diff] [blame] | 143 | .contains("Stream already closed"); |
| 144 | } |
| 145 | |
Laszlo Csomor | d5c1eee | 2019-02-13 04:52:45 -0800 | [diff] [blame] | 146 | /** |
| 147 | * An argument and its command-line-escaped counterpart. |
| 148 | * |
| 149 | * <p>Such escaping ensures that Bazel correctly forwards arguments to subprocesses. |
| 150 | */ |
| 151 | private static final class ArgPair { |
| 152 | public final String original; |
| 153 | public final String escaped; |
| 154 | |
| 155 | public ArgPair(String original, String escaped) { |
| 156 | this.original = original; |
| 157 | this.escaped = escaped; |
| 158 | } |
| 159 | }; |
| 160 | |
| 161 | /** Asserts that a subprocess correctly receives command line arguments. */ |
Laszlo Csomor | ecac47d | 2019-07-01 04:29:34 -0700 | [diff] [blame] | 162 | private void assertSubprocessReceivesArgsAsIntended(ArgPair... args) throws Exception { |
Laszlo Csomor | d5c1eee | 2019-02-13 04:52:45 -0800 | [diff] [blame] | 163 | // Look up the path of the printarg.exe utility. |
| 164 | String printArgExe = |
pcloudy | f8f4332 | 2020-04-08 07:56:38 -0700 | [diff] [blame] | 165 | runfiles.rlocation( |
| 166 | "io_bazel/src/test/java/com/google/devtools/build/lib/windows/printarg.exe"); |
Laszlo Csomor | d5c1eee | 2019-02-13 04:52:45 -0800 | [diff] [blame] | 167 | assertThat(printArgExe).isNotEmpty(); |
| 168 | |
| 169 | for (ArgPair arg : args) { |
| 170 | // Assert that the command-line encoding logic works as intended. |
Laszlo Csomor | ecac47d | 2019-07-01 04:29:34 -0700 | [diff] [blame] | 171 | assertThat(ShellUtils.windowsEscapeArg(arg.original)).isEqualTo(arg.escaped); |
Laszlo Csomor | d5c1eee | 2019-02-13 04:52:45 -0800 | [diff] [blame] | 172 | |
| 173 | // Create a separate subprocess just for this argument. |
laszlocsomor | 1da4861 | 2019-02-15 01:41:57 -0800 | [diff] [blame] | 174 | SubprocessBuilder subprocessBuilder = |
Laszlo Csomor | ecac47d | 2019-07-01 04:29:34 -0700 | [diff] [blame] | 175 | new SubprocessBuilder(WindowsSubprocessFactory.INSTANCE); |
Laszlo Csomor | d5c1eee | 2019-02-13 04:52:45 -0800 | [diff] [blame] | 176 | subprocessBuilder.setWorkingDirectory(new File(".")); |
laszlocsomor | d2cb96a | 2020-01-14 02:35:04 -0800 | [diff] [blame] | 177 | subprocessBuilder.setArgv(ImmutableList.of(printArgExe, arg.original)); |
Laszlo Csomor | d5c1eee | 2019-02-13 04:52:45 -0800 | [diff] [blame] | 178 | process = subprocessBuilder.start(); |
| 179 | process.waitFor(); |
| 180 | assertThat(process.exitValue()).isEqualTo(0); |
| 181 | |
| 182 | // The subprocess printed its argv[1] in parentheses, e.g. (foo). |
| 183 | // Assert that it printed exactly the *original* argument in parentheses. |
| 184 | byte[] buf = new byte[1000]; |
| 185 | process.getInputStream().read(buf); |
| 186 | String actual = new String(buf, UTF_8).trim(); |
| 187 | assertThat(actual).isEqualTo("(" + arg.original + ")"); |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | @Test |
Laszlo Csomor | ecac47d | 2019-07-01 04:29:34 -0700 | [diff] [blame] | 192 | public void testSubprocessReceivesArgsAsIntended() throws Exception { |
Laszlo Csomor | d5c1eee | 2019-02-13 04:52:45 -0800 | [diff] [blame] | 193 | assertSubprocessReceivesArgsAsIntended( |
laszlocsomor | 1da4861 | 2019-02-15 01:41:57 -0800 | [diff] [blame] | 194 | new ArgPair("", "\"\""), |
| 195 | new ArgPair(" ", "\" \""), |
| 196 | new ArgPair("\"", "\"\\\"\""), |
| 197 | new ArgPair("\"\\", "\"\\\"\\\\\""), |
| 198 | new ArgPair("\\", "\\"), |
| 199 | new ArgPair("\\\"", "\"\\\\\\\"\""), |
| 200 | new ArgPair("with space", "\"with space\""), |
| 201 | new ArgPair("with^caret", "with^caret"), |
| 202 | new ArgPair("space ^caret", "\"space ^caret\""), |
| 203 | new ArgPair("caret^ space", "\"caret^ space\""), |
| 204 | new ArgPair("with\"quote", "\"with\\\"quote\""), |
| 205 | new ArgPair("with\\backslash", "with\\backslash"), |
| 206 | new ArgPair("one\\ backslash and \\space", "\"one\\ backslash and \\space\""), |
| 207 | new ArgPair("two\\\\backslashes", "two\\\\backslashes"), |
| 208 | new ArgPair("two\\\\ backslashes \\\\and space", "\"two\\\\ backslashes \\\\and space\""), |
| 209 | new ArgPair("one\\\"x", "\"one\\\\\\\"x\""), |
| 210 | new ArgPair("two\\\\\"x", "\"two\\\\\\\\\\\"x\""), |
| 211 | new ArgPair("a \\ b", "\"a \\ b\""), |
| 212 | new ArgPair("a \\\" b", "\"a \\\\\\\" b\""), |
| 213 | new ArgPair("A", "A"), |
| 214 | new ArgPair("\"a\"", "\"\\\"a\\\"\""), |
| 215 | new ArgPair("B C", "\"B C\""), |
| 216 | new ArgPair("\"b c\"", "\"\\\"b c\\\"\""), |
| 217 | new ArgPair("D\"E", "\"D\\\"E\""), |
| 218 | new ArgPair("\"d\"e\"", "\"\\\"d\\\"e\\\"\""), |
| 219 | new ArgPair("C:\\F G", "\"C:\\F G\""), |
| 220 | new ArgPair("\"C:\\f g\"", "\"\\\"C:\\f g\\\"\""), |
| 221 | new ArgPair("C:\\H\"I", "\"C:\\H\\\"I\""), |
| 222 | new ArgPair("\"C:\\h\"i\"", "\"\\\"C:\\h\\\"i\\\"\""), |
| 223 | new ArgPair("C:\\J\\\"K", "\"C:\\J\\\\\\\"K\""), |
| 224 | new ArgPair("\"C:\\j\\\"k\"", "\"\\\"C:\\j\\\\\\\"k\\\"\""), |
| 225 | new ArgPair("C:\\L M ", "\"C:\\L M \""), |
| 226 | new ArgPair("\"C:\\l m \"", "\"\\\"C:\\l m \\\"\""), |
| 227 | new ArgPair("C:\\N O\\", "\"C:\\N O\\\\\""), |
| 228 | new ArgPair("\"C:\\n o\\\"", "\"\\\"C:\\n o\\\\\\\"\""), |
| 229 | new ArgPair("C:\\P Q\\ ", "\"C:\\P Q\\ \""), |
| 230 | new ArgPair("\"C:\\p q\\ \"", "\"\\\"C:\\p q\\ \\\"\""), |
| 231 | new ArgPair("C:\\R\\S\\", "C:\\R\\S\\"), |
| 232 | new ArgPair("C:\\R x\\S\\", "\"C:\\R x\\S\\\\\""), |
| 233 | new ArgPair("\"C:\\r\\s\\\"", "\"\\\"C:\\r\\s\\\\\\\"\""), |
| 234 | new ArgPair("\"C:\\r x\\s\\\"", "\"\\\"C:\\r x\\s\\\\\\\"\""), |
| 235 | new ArgPair("C:\\T U\\W\\", "\"C:\\T U\\W\\\\\""), |
| 236 | new ArgPair("\"C:\\t u\\w\\\"", "\"\\\"C:\\t u\\w\\\\\\\"\"")); |
| 237 | } |
pcloudy | 8c21b0b | 2018-09-19 07:19:09 -0700 | [diff] [blame] | 238 | } |