v2 / vlib / os / process_test.v
448 lines · 411 sloc · 12.31 KB · d03153c184f5ec5f2717cbe4d38e3a9ba27e0e31
Raw
1import os
2import time
3
4const vexe = os.getenv('VEXE')
5const vroot = os.dir(vexe)
6const tfolder = os.join_path(os.vtmp_dir(), 'os_process_tests')
7const test_os_process = os.join_path(tfolder, 'test_os_process.exe')
8const test_os_process_source = os.join_path(vroot, 'cmd/tools/test_os_process.v')
9const echo_process_exe_filename = os.join_path(tfolder, 'echo.exe')
10const echo_process_source_filename = os.join_path(tfolder, 'echo.v')
11const delayed_output_exe_filename = os.join_path(tfolder, 'delayed_output.exe')
12const delayed_output_source_filename = os.join_path(tfolder, 'delayed_output.v')
13const utf16le_output_exe_filename = os.join_path(tfolder, 'utf16le_output.exe')
14const utf16le_output_source_filename = os.join_path(tfolder, 'utf16le_output.v')
15const stdin_exit_exe_filename = os.join_path(tfolder, 'stdin_exit.exe')
16const stdin_exit_source_filename = os.join_path(tfolder, 'stdin_exit.v')
17const argv_echo_exe_filename = os.join_path(tfolder, 'argv_echo.exe')
18const argv_echo_source_filename = os.join_path(tfolder, 'argv_echo.v')
19const echo_process_source_code = '
20module main
21import io
22import os
23
24fn main() {
25 unbuffer_stdout()
26 mut reader := io.new_buffered_reader(reader: os.stdin(), cap: 1)
27 for {
28 line := reader.read_line()!
29 println(line)
30 eprintln(line)
31 }
32}
33'
34
35const delayed_output_source_code = '
36module main
37import time
38
39fn main() {
40 unbuffer_stdout()
41 time.sleep(500 * time.millisecond)
42 println("late")
43 time.sleep(300 * time.millisecond)
44}
45'
46
47const utf16le_output_source_code = '
48module main
49import os
50
51fn main() {
52 payload := [u8(`O`), 0, `K`, 0, u8(10), 0]
53 mut out := os.stdout()
54 out.write(payload) or { panic(err) }
55}
56'
57
58const stdin_exit_source_code = '
59module main
60import os
61
62fn main() {
63 _ = os.get_raw_line()
64 exit(7)
65}
66'
67
68const argv_echo_source_code = '
69module main
70import os
71
72fn main() {
73 for arg in os.args[1..] {
74 println(arg)
75 }
76}
77'
78
79const echo_wait_timeout = 5 // seconds
80
81fn testsuite_begin() {
82 os.rmdir_all(tfolder) or {}
83 os.mkdir_all(tfolder)!
84 if os.getenv('WINE_TEST_OS_PROCESS_EXE') != '' {
85 // Make it easier to run the test under wine emulation, by just
86 // prebuilding the executable with:
87 // v -os windows -o x.exe cmd/tools/test_os_process.v
88 // WINE_TEST_OS_PROCESS_EXE=x.exe ./v -os windows vlib/os/process_test.v
89 os.cp(os.getenv('WINE_TEST_OS_PROCESS_EXE'), test_os_process)!
90 } else {
91 os.system('${os.quoted_path(vexe)} -o ${os.quoted_path(test_os_process)} ${os.quoted_path(test_os_process_source)}')
92 }
93 assert os.exists(test_os_process)
94
95 os.write_file(echo_process_source_filename, echo_process_source_code)!
96 os.system('${os.quoted_path(vexe)} -o ${os.quoted_path(echo_process_exe_filename)} ${os.quoted_path(echo_process_source_filename)}')
97 assert os.exists(echo_process_exe_filename)
98
99 os.write_file(delayed_output_source_filename, delayed_output_source_code)!
100 os.system('${os.quoted_path(vexe)} -o ${os.quoted_path(delayed_output_exe_filename)} ${os.quoted_path(delayed_output_source_filename)}')
101 assert os.exists(delayed_output_exe_filename)
102
103 os.write_file(utf16le_output_source_filename, utf16le_output_source_code)!
104 os.system('${os.quoted_path(vexe)} -o ${os.quoted_path(utf16le_output_exe_filename)} ${os.quoted_path(utf16le_output_source_filename)}')
105 assert os.exists(utf16le_output_exe_filename)
106
107 os.write_file(stdin_exit_source_filename, stdin_exit_source_code)!
108 os.system('${os.quoted_path(vexe)} -o ${os.quoted_path(stdin_exit_exe_filename)} ${os.quoted_path(stdin_exit_source_filename)}')
109 assert os.exists(stdin_exit_exe_filename)
110
111 os.write_file(argv_echo_source_filename, argv_echo_source_code)!
112 os.system('${os.quoted_path(vexe)} -o ${os.quoted_path(argv_echo_exe_filename)} ${os.quoted_path(argv_echo_source_filename)}')
113 assert os.exists(argv_echo_exe_filename)
114}
115
116fn testsuite_end() {
117 os.rmdir_all(tfolder) or {}
118}
119
120fn test_getpid() {
121 eprintln(@FN)
122 pid := os.getpid()
123 eprintln('current pid: ${pid}')
124 assert pid != 0
125}
126
127fn test_set_work_folder() {
128 eprintln(@FN)
129 new_work_folder := os.real_path(os.temp_dir())
130 parent_working_folder := os.getwd()
131 dump(new_work_folder)
132 dump(parent_working_folder)
133 if new_work_folder == parent_working_folder {
134 eprintln('... skipping ${@METHOD} because the working folder is the temporary one')
135 return
136 }
137 mut p := os.new_process(test_os_process)
138 p.set_args(['-show_wd', '-target', 'stdout'])
139 p.set_work_folder(new_work_folder)
140 p.set_redirect_stdio()
141 p.wait()
142 assert p.code == 0
143 output := p.stdout_slurp().trim_space()
144 p.close()
145 $if trace_process_output ? {
146 eprintln('p output: "${output}"')
147 }
148 child_work_folder := output.find_between('stdout, WORK_DIR=', '\n').trim_space()
149 dump(child_work_folder)
150 assert child_work_folder == new_work_folder
151 new_parent_work_folder := os.getwd()
152 dump(new_parent_work_folder)
153 assert new_parent_work_folder == parent_working_folder
154 assert new_parent_work_folder != child_work_folder
155}
156
157fn test_set_environment() {
158 eprintln(@FN)
159 mut p := os.new_process(test_os_process)
160 p.set_args(['-show_env', '-target', 'stdout'])
161 p.set_environment({
162 'V_OS_TEST_PORT': '1234567890'
163 })
164 p.set_redirect_stdio()
165 p.wait()
166 assert p.code == 0
167 output := p.stdout_slurp().trim_space()
168 p.close()
169 $if trace_process_output ? {
170 eprintln('p output: "${output}"')
171 }
172 assert output.contains('V_OS_TEST_PORT=1234567890'), output
173}
174
175fn test_new_process_uses_exact_executable_path_when_folder_contains_spaces() {
176 $if !windows {
177 return
178 }
179 eprintln(@FN)
180 spaced_dir := os.join_path(tfolder, 'spawn path with spaces')
181 os.rmdir_all(spaced_dir) or {}
182 os.mkdir_all(spaced_dir)!
183 spaced_exe := os.join_path(spaced_dir, 'test os process.exe')
184 os.cp(test_os_process, spaced_exe)!
185
186 stale_source := os.join_path(tfolder, 'spawn.v')
187 stale_exe := os.join_path(tfolder, 'spawn.exe')
188 os.write_file(stale_source, 'fn main() {\n\tprintln("stale-prefix-exe")\n}\n')!
189 assert os.system('${os.quoted_path(vexe)} -o ${os.quoted_path(stale_exe)} ${os.quoted_path(stale_source)}') == 0
190
191 mut p := os.new_process(spaced_exe)
192 p.set_args(['-show_env', '-target', 'stdout'])
193 p.set_environment({
194 'V_OS_TEST_PORT': 'exact_path'
195 })
196 p.set_redirect_stdio()
197 p.wait()
198 assert p.code == 0
199 output := p.stdout_slurp().trim_space()
200 errors := p.stderr_slurp().trim_space()
201 p.close()
202 assert output.contains('V_OS_TEST_PORT=exact_path'), 'stdout:\n${output}\nstderr:\n${errors}'
203 assert !output.contains('stale-prefix-exe'), output
204}
205
206fn test_new_process_passes_spaced_path_args_on_windows() {
207 $if !windows {
208 return
209 }
210 eprintln(@FN)
211 file_arg := os.join_path(tfolder, 'path with spaces', 'child file.txt')
212 dir_arg := os.join_path(tfolder, 'path with spaces') + '\\'
213 quoted_arg := 'value with "quotes"'
214 mut p := os.new_process(argv_echo_exe_filename)
215 p.set_args([file_arg, dir_arg, quoted_arg, 'marker'])
216 p.set_redirect_stdio()
217 p.wait()
218 assert p.code == 0
219 output := p.stdout_slurp().trim_space()
220 errors := p.stderr_slurp().trim_space()
221 p.close()
222 lines := output.split_into_lines()
223 assert lines.len == 4, 'stdout:\n${output}\nstderr:\n${errors}'
224 assert lines[0] == file_arg
225 assert lines[1] == dir_arg
226 assert lines[2] == quoted_arg
227 assert lines[3] == 'marker'
228}
229
230fn test_new_process_uses_path_for_bare_command_names() {
231 $if windows {
232 return
233 }
234 eprintln(@FN)
235 original_path := os.getenv('PATH')
236 defer {
237 os.setenv('PATH', original_path, true)
238 }
239 path_dir := os.join_path(tfolder, 'path_bin')
240 os.rmdir_all(path_dir) or {}
241 os.mkdir_all(path_dir)!
242 path_exe := os.join_path(path_dir, 'process_from_path.exe')
243 os.cp(test_os_process, path_exe)!
244 os.setenv('PATH', '${path_dir}${os.path_delimiter}${original_path}', true)
245 mut p := os.new_process('process_from_path.exe')
246 p.set_args(['-exitcode', '7'])
247 p.set_work_folder(os.real_path(os.temp_dir()))
248 p.wait()
249 assert p.status == .exited
250 assert p.code == 7
251 p.close()
252}
253
254fn test_run() {
255 eprintln(@FN)
256 mut p := os.new_process(test_os_process)
257 p.set_args(['-timeout_ms', '150', '-period_ms', '50'])
258 p.run()
259 assert p.status == .running
260 assert p.pid > 0
261 assert p.pid != os.getpid()
262 mut i := 0
263 for {
264 if !p.is_alive() {
265 break
266 }
267 $if trace_process_output ? {
268 os.system('ps -opid= -oppid= -ouser= -onice= -of= -ovsz= -orss= -otime= -oargs= -p ${p.pid}')
269 }
270 time.sleep(50 * time.millisecond)
271 i++
272 }
273 p.wait()
274 assert p.code == 0
275 assert p.status == .exited
276
277 eprintln('polling iterations: ${i}')
278 assert i < 50
279 p.close()
280}
281
282fn test_wait() {
283 eprintln(@FN)
284 mut p := os.new_process(test_os_process)
285 assert p.status != .exited
286 p.wait()
287 assert p.status == .exited
288 assert p.code == 0
289 assert p.pid != os.getpid()
290 p.close()
291}
292
293fn test_slurping_output() {
294 eprintln(@FN)
295 mut p := os.new_process(test_os_process)
296 p.set_args(['-timeout_ms', '600', '-period_ms', '50'])
297 p.set_redirect_stdio()
298 assert p.status != .exited
299 p.wait()
300 assert p.status == .exited
301 assert p.code == 0
302 output := p.stdout_slurp().trim_space()
303 errors := p.stderr_slurp().trim_space()
304 p.close()
305 $if trace_process_output ? {
306 eprintln('---------------------------')
307 eprintln('p output: "${output}"')
308 eprintln('p errors: "${errors}"')
309 eprintln('---------------------------')
310 }
311 assert output.contains('stdout, 1'), output
312 assert output.contains('stdout, 2'), output
313 assert output.contains('stdout, 3'), output
314 assert output.contains('stdout, 4'), output
315 assert errors.contains('stderr, 1'), errors
316 assert errors.contains('stderr, 2'), errors
317 assert errors.contains('stderr, 3'), errors
318 assert errors.contains('stderr, 4'), errors
319}
320
321fn echo(mut p os.Process, echo_string string) {
322 // append `\n`, as `echo.exe` use `read_line()`
323 p.stdin_write(echo_string + '\n')
324 mut got_echo_back := false
325 mut echo_back := ''
326 mut timeout := 0
327 for p.is_alive() && timeout < echo_wait_timeout * 1000 / 50 {
328 echo_back = p.stdout_read()
329 if echo_back.len > 0 {
330 got_echo_back = true
331 break
332 }
333 time.sleep(50 * time.millisecond)
334 timeout++
335 }
336 assert got_echo_back
337 assert echo_back.trim_space() == echo_string.trim_space()
338}
339
340fn test_stdin_write() {
341 eprintln(@FN)
342 echo_exe := $if windows { echo_process_exe_filename } $else { os.find_abs_path_of_executable('cat') or {
343 '/bin/cat'} }
344 mut p := os.new_process(echo_exe)
345 p.set_redirect_stdio()
346 assert p.status != .exited
347 p.run()
348 echo(mut p, 'hello')
349 echo(mut p, 'world')
350 p.signal_kill()
351 p.close()
352}
353
354fn test_close_before_wait_preserves_exit_code() {
355 eprintln(@FN)
356 mut p := os.new_process(stdin_exit_exe_filename)
357 p.set_redirect_stdio()
358 p.run()
359 p.stdin_write('hello\n')
360 p.close()
361 p.wait()
362 assert p.status == .exited
363 assert p.code == 7
364 p.close()
365}
366
367fn test_stdout_read_returns_immediately_when_no_data_is_pending() {
368 eprintln(@FN)
369 mut p := os.new_process(delayed_output_exe_filename)
370 p.set_redirect_stdio()
371 p.run()
372 defer {
373 if p.is_alive() {
374 p.signal_kill()
375 }
376 p.close()
377 }
378 mut sw := time.new_stopwatch()
379 output := p.stdout_read()
380 elapsed_ms := sw.elapsed().milliseconds()
381 assert output == ''
382 assert elapsed_ms < 300, 'stdout_read blocked for ${elapsed_ms}ms'
383 p.wait()
384 assert p.stdout_slurp().contains('late')
385}
386
387fn test_pipe_read_while_process_is_alive() {
388 eprintln(@FN)
389 mut p := os.new_process(test_os_process)
390 p.set_args(['-timeout_ms', '600', '-period_ms', '50'])
391 p.set_redirect_stdio()
392 p.run()
393 mut stdout_output := ''
394 mut stderr_output := ''
395 mut timeout := 0
396 for p.is_alive() && timeout < echo_wait_timeout * 1000 / 20 {
397 if out := p.pipe_read(.stdout) {
398 stdout_output += out
399 }
400 if err := p.pipe_read(.stderr) {
401 stderr_output += err
402 }
403 if stdout_output.len > 0 && stderr_output.len > 0 {
404 break
405 }
406 time.sleep(20 * time.millisecond)
407 timeout++
408 }
409 p.wait()
410 p.close()
411 assert stdout_output.contains('stdout, start'), stdout_output
412 assert stderr_output.contains('stderr, start'), stderr_output
413}
414
415fn test_pipe_read_returns_none_after_eof() {
416 eprintln(@FN)
417 mut p := os.new_process(test_os_process)
418 p.set_args(['-timeout_ms', '120', '-period_ms', '50'])
419 p.set_redirect_stdio()
420 p.wait()
421 assert p.code == 0
422 _ = p.stdout_slurp()
423 _ = p.stderr_slurp()
424 assert !p.is_pending(.stdout)
425 assert !p.is_pending(.stderr)
426 if out := p.pipe_read(.stdout) {
427 assert false, 'expected none after stdout EOF, got `${out}`'
428 }
429 if err := p.pipe_read(.stderr) {
430 assert false, 'expected none after stderr EOF, got `${err}`'
431 }
432 p.close()
433}
434
435fn test_slurping_utf16le_output_on_windows() {
436 if os.user_os() != 'windows' {
437 return
438 }
439 mut p := os.new_process(utf16le_output_exe_filename)
440 p.set_redirect_stdio()
441 p.wait()
442 assert p.code == 0
443 output := p.stdout_slurp()
444 errors := p.stderr_slurp()
445 p.close()
446 assert output == 'OK\n', output
447 assert errors == ''
448}
449