1#!/usr/local/bin/python3.8 2 3# Copyright 2001 Dave Abrahams 4# Copyright 2011 Steven Watanabe 5# Distributed under the Boost Software License, Version 1.0. 6# (See accompanying file LICENSE_1_0.txt or copy at 7# http://www.boost.org/LICENSE_1_0.txt) 8 9# Tests Windows command line construction. 10# 11# Note that the regular 'echo' is an internal shell command on Windows and 12# therefore can not be called directly as a standalone Windows process. 13 14import BoostBuild 15import os 16import re 17import sys 18 19 20executable = sys.executable.replace("\\", "/") 21if " " in executable: 22 executable = '"%s"' % executable 23 24 25def string_of_length(n): 26 if n <= 0: 27 return "" 28 n -= 1 29 y = ['', '$(1x10-1)', '$(10x10-1)', '$(100x10-1)', '$(1000x10-1)'] 30 result = [] 31 for i in reversed(range(5)): 32 x, n = divmod(n, 10 ** i) 33 result += [y[i]] * x 34 result.append('x') 35 return " ".join(result) 36 37 38# Boost Jam currently does not allow preparing actions with completely empty 39# content and always requires at least a single whitespace after the opening 40# brace in order to satisfy its Boost Jam language grammar rules. 41def test_raw_empty(): 42 whitespace_in = " \n\n\r\r\v\v\t\t \t \r\r \n\n" 43 44 # We tell the testing system to read its child process output as raw 45 # binary data but the bjam process we run will read its input file and 46 # write out its output as text, i.e. convert all of our "\r\n" sequences to 47 # "\n" on input and all of its "\n" characters back to "\r\n" on output. 48 # This means that any lone "\n" input characters not preceded by "\r" will 49 # get an extra "\r" added in front of it on output. 50 whitespace_out = whitespace_in.replace("\r\n", "\n").replace("\n", "\r\n") 51 52 t = BoostBuild.Tester(["-d2", "-d+4"], pass_toolset=0, 53 use_test_config=False) 54 t.write("file.jam", """\ 55actions do_empty {%s} 56JAMSHELL = %% ; 57do_empty all ; 58""" % (whitespace_in)) 59 t.run_build_system(["-ffile.jam"], universal_newlines=False) 60 t.expect_output_lines("do_empty all") 61 t.expect_output_lines("Executing raw command directly", False) 62 if "\r\n%s\r\n" % whitespace_out not in t.stdout(): 63 BoostBuild.annotation("failure", "Whitespace action content not found " 64 "on stdout.") 65 t.fail_test(1, dump_difference=False) 66 t.cleanup() 67 68 69def test_raw_nt(n=None, error=False): 70 t = BoostBuild.Tester(["-d1", "-d+4"], pass_toolset=0, 71 use_test_config=False) 72 73 cmd_prefix = "%s -c \"print('XXX: " % executable 74 cmd_suffix = "')\"" 75 cmd_extra_length = len(cmd_prefix) + len(cmd_suffix) 76 77 if n == None: 78 n = cmd_extra_length 79 80 data_length = n - cmd_extra_length 81 if data_length < 0: 82 BoostBuild.annotation("failure", """\ 83Can not construct Windows command of desired length. Requested command length 84too short for the current test configuration. 85 Requested command length: %d 86 Minimal supported command length: %d 87""" % (n, cmd_extra_length)) 88 t.fail_test(1, dump_difference=False) 89 90 # Each $(Xx10-1) variable contains X words of 9 characters each, which, 91 # including spaces between words, brings the total number of characters in 92 # its string representation to X * 10 - 1 (X * 9 characters + (X - 1) 93 # spaces). 94 t.write("file.jam", """\ 95ten = 0 1 2 3 4 5 6 7 8 9 ; 96 971x10-1 = 123456789 ; 9810x10-1 = $(ten)12345678 ; 99100x10-1 = $(ten)$(ten)1234567 ; 1001000x10-1 = $(ten)$(ten)$(ten)123456 ; 101 102actions do_echo 103{ 104 %s%s%s 105} 106JAMSHELL = %% ; 107do_echo all ; 108""" % (cmd_prefix, string_of_length(data_length), cmd_suffix)) 109 if error: 110 expected_status = 1 111 else: 112 expected_status = 0 113 t.run_build_system(["-ffile.jam"], status=expected_status) 114 if error: 115 t.expect_output_lines("Executing raw command directly", False) 116 t.expect_output_lines("do_echo action is too long (%d, max 32766):" % n 117 ) 118 t.expect_output_lines("XXX: *", False) 119 else: 120 t.expect_output_lines("Executing raw command directly") 121 t.expect_output_lines("do_echo action is too long*", False) 122 123 m = re.search("^XXX: (.*)$", t.stdout(), re.MULTILINE) 124 if not m: 125 BoostBuild.annotation("failure", "Expected output line starting " 126 "with 'XXX: ' not found.") 127 t.fail_test(1, dump_difference=False) 128 if len(m.group(1)) != data_length: 129 BoostBuild.annotation("failure", """Unexpected output data length. 130 Expected: %d 131 Received: %d""" % (n, len(m.group(1)))) 132 t.fail_test(1, dump_difference=False) 133 134 t.cleanup() 135 136 137def test_raw_to_shell_fallback_nt(): 138 t = BoostBuild.Tester(["-d1", "-d+4"], pass_toolset=0, 139 use_test_config=False) 140 141 cmd_prefix = '%s -c print(' % executable 142 cmd_suffix = ')' 143 144 t.write("file_multiline.jam", """\ 145actions do_multiline 146{ 147 echo one 148 149 150 echo two 151} 152JAMSHELL = % ; 153do_multiline all ; 154""") 155 t.run_build_system(["-ffile_multiline.jam"]) 156 t.expect_output_lines("do_multiline all") 157 t.expect_output_lines("one") 158 t.expect_output_lines("two") 159 t.expect_output_lines("Executing raw command directly", False) 160 t.expect_output_lines("Executing using a command file and the shell: " 161 "cmd.exe /Q/C") 162 163 t.write("file_redirect.jam", """\ 164actions do_redirect { echo one > two.txt } 165JAMSHELL = % ; 166do_redirect all ; 167""") 168 t.run_build_system(["-ffile_redirect.jam"]) 169 t.expect_output_lines("do_redirect all") 170 t.expect_output_lines("one", False) 171 t.expect_output_lines("Executing raw command directly", False) 172 t.expect_output_lines("Executing using a command file and the shell: " 173 "cmd.exe /Q/C") 174 t.expect_addition("two.txt") 175 176 t.write("file_pipe.jam", """\ 177actions do_pipe 178{ 179 echo one | echo two 180} 181JAMSHELL = % ; 182do_pipe all ; 183""") 184 t.run_build_system(["-ffile_pipe.jam"]) 185 t.expect_output_lines("do_pipe all") 186 t.expect_output_lines("one*", False) 187 t.expect_output_lines("two") 188 t.expect_output_lines("Executing raw command directly", False) 189 t.expect_output_lines("Executing using a command file and the shell: " 190 "cmd.exe /Q/C") 191 192 t.write("file_single_quoted.jam", """\ 193actions do_single_quoted { %s'5>10'%s } 194JAMSHELL = %% ; 195do_single_quoted all ; 196""" % (cmd_prefix, cmd_suffix)) 197 t.run_build_system(["-ffile_single_quoted.jam"]) 198 t.expect_output_lines("do_single_quoted all") 199 t.expect_output_lines("5>10") 200 t.expect_output_lines("Executing raw command directly") 201 t.expect_output_lines("Executing using a command file and the shell: " 202 "cmd.exe /Q/C", False) 203 t.expect_nothing_more() 204 205 t.write("file_double_quoted.jam", """\ 206actions do_double_quoted { %s"5>10"%s } 207JAMSHELL = %% ; 208do_double_quoted all ; 209""" % (cmd_prefix, cmd_suffix)) 210 t.run_build_system(["-ffile_double_quoted.jam"]) 211 t.expect_output_lines("do_double_quoted all") 212 # The difference between this example and the similar previous one using 213 # single instead of double quotes stems from how the used Python executable 214 # parses the command-line string received from Windows. 215 t.expect_output_lines("False") 216 t.expect_output_lines("Executing raw command directly") 217 t.expect_output_lines("Executing using a command file and the shell: " 218 "cmd.exe /Q/C", False) 219 t.expect_nothing_more() 220 221 t.write("file_escaped_quote.jam", """\ 222actions do_escaped_quote { %s\\"5>10\\"%s } 223JAMSHELL = %% ; 224do_escaped_quote all ; 225""" % (cmd_prefix, cmd_suffix)) 226 t.run_build_system(["-ffile_escaped_quote.jam"]) 227 t.expect_output_lines("do_escaped_quote all") 228 t.expect_output_lines("5>10") 229 t.expect_output_lines("Executing raw command directly", False) 230 t.expect_output_lines("Executing using a command file and the shell: " 231 "cmd.exe /Q/C") 232 t.expect_nothing_more() 233 234 t.cleanup() 235 236 237############################################################################### 238# 239# main() 240# ------ 241# 242############################################################################### 243 244if os.name == 'nt': 245 test_raw_empty() 246 247 # Can not test much shorter lengths as the shortest possible command line 248 # line length constructed in this depends on the runtime environment, e.g. 249 # path to the Panther executable running this test. 250 test_raw_nt() 251 test_raw_nt(255) 252 test_raw_nt(1000) 253 test_raw_nt(8000) 254 test_raw_nt(8191) 255 test_raw_nt(8192) 256 test_raw_nt(10000) 257 test_raw_nt(30000) 258 test_raw_nt(32766) 259 # CreateProcessA() Windows API places a limit of 32768 on the allowed 260 # command-line length, including a trailing Unicode (2-byte) nul-terminator 261 # character. 262 test_raw_nt(32767, error=True) 263 test_raw_nt(40000, error=True) 264 test_raw_nt(100001, error=True) 265 266 test_raw_to_shell_fallback_nt()