diff options
Diffstat (limited to 'tests/stdlib/tosproc.nim')
-rw-r--r-- | tests/stdlib/tosproc.nim | 316 |
1 files changed, 316 insertions, 0 deletions
diff --git a/tests/stdlib/tosproc.nim b/tests/stdlib/tosproc.nim new file mode 100644 index 000000000..da4f6252d --- /dev/null +++ b/tests/stdlib/tosproc.nim @@ -0,0 +1,316 @@ +discard """ +matrix: "--mm:refc; --mm:orc" +joinable: false +""" + +#[ +joinable: false +because it'd need cleanup up stdout + +see also: tests/osproc/*.nim; consider merging those into a single test here +(easier to factor and test more things as a single self contained test) +]# +import std/[assertions, syncio] + +when defined(case_testfile): # compiled test file for child process + from posix import exitnow + proc c_exit2(code: cint): void {.importc: "_exit", header: "<unistd.h>".} + import os + var a = 0 + proc fun(b = 0) = + a.inc + if a mod 10000000 == 0: # prevents optimizing it away + echo a + fun(b+1) + + proc main() = + let args = commandLineParams() + echo (msg: "child binary", pid: getCurrentProcessId()) + let arg = args[0] + echo (arg: arg) + case arg + of "exit_0": + if true: quit(0) + of "exit_1": + if true: quit(1) + of "exit_2": + if true: quit(2) + of "exit_42": + if true: quit(42) + of "exitnow_139": + if true: exitnow(139) + of "c_exit2_139": + if true: c_exit2(139) + of "quit_139": + # `exitStatusLikeShell` doesn't distinguish between a process that + # exit(139) and a process that gets killed with `SIGSEGV` because + # 139 = 11 + 128 = SIGSEGV + 128. + # However, as #10249 shows, this leads to bad debugging experience + # when a child process dies with SIGSEGV, leaving no trace of why it + # failed. The shell (and lldb debugger) solves that by inserting a + # helpful msg: `segmentation fault` when it detects a signal killed + # the child. + # todo: expose an API that will show more diagnostic, returning + # (exitCode, signal) instead of just `shellExitCode`. + if true: quit(139) + of "exit_recursion": # stack overflow by infinite recursion + fun() + echo a + of "exit_array": # bad array access + echo args[1] + main() + +elif defined(case_testfile2): + import strutils + let x = stdin.readLine() + echo x.parseInt + 5 + +elif defined(case_testfile3): + echo "start ta_out" + stdout.writeLine("to stdout") + stdout.flushFile() + stdout.writeLine("to stdout") + stdout.flushFile() + + stderr.writeLine("to stderr") + stderr.flushFile() + stderr.writeLine("to stderr") + stderr.flushFile() + + stdout.writeLine("to stdout") + stdout.flushFile() + stdout.writeLine("to stdout") + stdout.flushFile() + echo "end ta_out" + +elif defined(case_testfile4): + import system # we could remove that + quit(QuitFailure) + +else: # main driver + import stdtest/[specialpaths, unittest_light] + import os, osproc, strutils + const nim = getCurrentCompilerExe() + const sourcePath = currentSourcePath() + let dir = getCurrentDir() / "tests" / "osproc" + + template deferring(cleanup, body) = + try: body + finally: cleanup + + # we're testing `execShellCmd` so don't rely on it to compile test file + # note: this should be exported in posix.nim + proc c_system(cmd: cstring): cint {.importc: "system", header: "<stdlib.h>".} + + proc compileNimProg(opt: string, name: string): string = + result = buildDir / name.addFileExt(ExeExt) + let cmd = "$# c -o:$# --hints:off $# $#" % [nim.quoteShell, result.quoteShell, opt, sourcePath.quoteShell] + doAssert c_system(cmd) == 0, $cmd + doAssert result.fileExists + + block execShellCmdTest: + let output = compileNimProg("-d:release -d:case_testfile", "D20190111T024543") + + ## use it + template runTest(arg: string, expected: int) = + echo (arg2: arg, expected2: expected) + assertEquals execShellCmd(output & " " & arg), expected + + runTest("exit_0", 0) + runTest("exitnow_139", 139) + runTest("c_exit2_139", 139) + when defined(posix): + runTest("quit_139", 127) # The quit value gets saturated to 127 + else: + runTest("quit_139", 139) + + block execCmdTest: + let output = compileNimProg("-d:release -d:case_testfile", "D20220705T221100") + doAssert execCmd(output & " exit_0") == 0 + doAssert execCmd(output & " exit_1") == 1 + doAssert execCmd(output & " exit_2") == 2 + doAssert execCmd(output & " exit_42") == 42 + + import std/streams + + block execProcessTest: + let dir = sourcePath.parentDir + let (_, err) = execCmdEx(nim & " c " & quoteShell(dir / "osproctest.nim")) + doAssert err == 0 + let exePath = dir / addFileExt("osproctest", ExeExt) + let outStr1 = execProcess(exePath, workingDir = dir, args = ["foo", + "b A r"], options = {}) + doAssert outStr1 == dir & "\nfoo\nb A r\n" + + const testDir = "t e st" + createDir(testDir) + doAssert dirExists(testDir) + let outStr2 = execProcess(exePath, workingDir = testDir, args = ["x yz"], + options = {}) + doAssert outStr2 == absolutePath(testDir) & "\nx yz\n" + + removeDir(testDir) + + # test for PipeOutStream + var + p = startProcess(exePath, args = ["abcdefghi", "foo", "bar", "0123456"]) + outStrm = p.peekableOutputStream + + var tmp: string + doAssert outStrm.readLine(tmp) + doAssert outStrm.readChar == 'a' + doAssert outStrm.peekChar == 'b' + doAssert outStrm.readChar == 'b' + doAssert outStrm.readChar == 'c' + doAssert outStrm.peekChar == 'd' + doAssert outStrm.peekChar == 'd' + doAssert outStrm.readChar == 'd' + doAssert outStrm.readStr(2) == "ef" + doAssert outStrm.peekStr(2) == "gh" + doAssert outStrm.peekStr(2) == "gh" + doAssert outStrm.readStr(1) == "g" + doAssert outStrm.readStr(3) == "hi\n" + + doAssert outStrm.readLine == "foo" + doAssert outStrm.readChar == 'b' + doAssert outStrm.peekChar == 'a' + doAssert outStrm.readLine == "ar" + + tmp.setLen(4) + tmp[0] = 'n' + doAssert outStrm.readDataStr(tmp, 1..3) == 3 + doAssert tmp == "n012" + doAssert outStrm.peekStr(3) == "345" + doAssert outStrm.readDataStr(tmp, 1..2) == 2 + doAssert tmp == "n342" + doAssert outStrm.peekStr(2) == "56" + doAssert outStrm.readDataStr(tmp, 0..3) == 3 + doAssert tmp == "56\n2" + p.close + + p = startProcess(exePath, args = ["123"]) + outStrm = p.peekableOutputStream + let c = outStrm.peekChar + doAssert outStrm.readLine(tmp) + doAssert tmp[0] == c + tmp.setLen(7) + doAssert outStrm.peekData(addr tmp[0], 7) == 4 + doAssert tmp[0..3] == "123\n" + doAssert outStrm.peekData(addr tmp[0], 7) == 4 + doAssert tmp[0..3] == "123\n" + doAssert outStrm.readData(addr tmp[0], 7) == 4 + doAssert tmp[0..3] == "123\n" + p.close + + try: + removeFile(exePath) + except OSError: + discard + + block: # test for startProcess (more tests needed) + # bugfix: windows stdin.close was a noop and led to blocking reads + proc startProcessTest(command: string, options: set[ProcessOption] = { + poStdErrToStdOut, poUsePath}, input = ""): tuple[ + output: string, + exitCode: int] {.tags: + [ExecIOEffect, ReadIOEffect, RootEffect], gcsafe.} = + var p = startProcess(command, options = options + {poEvalCommand}) + var outp = outputStream(p) + if input.len > 0: inputStream(p).write(input) + close inputStream(p) + result = ("", -1) + var line = newStringOfCap(120) + while true: + if outp.readLine(line): + result[0].add(line) + result[0].add("\n") + else: + result[1] = peekExitCode(p) + if result[1] != -1: break + close(p) + + var result = startProcessTest("nim r --hints:off -", options = {}, input = "echo 3*4") + doAssert result == ("12\n", 0) + + block: # startProcess stdin (replaces old test `tstdin` + `ta_in`) + let output = compileNimProg("-d:case_testfile2", "D20200626T215919") + var p = startProcess(output, dir) # dir not needed though + p.inputStream.write("5\n") + p.inputStream.flush() + var line = "" + var s: seq[string] + while p.outputStream.readLine(line): + s.add line + doAssert s == @["10"] + + block: + let output = compileNimProg("-d:case_testfile3", "D20200626T221233") + var x = newStringOfCap(120) + block: # startProcess stdout poStdErrToStdOut (replaces old test `tstdout` + `ta_out`) + var p = startProcess(output, dir, options={poStdErrToStdOut}) + deferring: p.close() + do: + var sout: seq[string] + while p.outputStream.readLine(x): sout.add x + doAssert sout == @["start ta_out", "to stdout", "to stdout", "to stderr", "to stderr", "to stdout", "to stdout", "end ta_out"] + block: # startProcess stderr (replaces old test `tstderr` + `ta_out`) + var p = startProcess(output, dir, options={}) + deferring: p.close() + do: + var serr, sout: seq[string] + while p.errorStream.readLine(x): serr.add x + while p.outputStream.readLine(x): sout.add x + doAssert serr == @["to stderr", "to stderr"] + doAssert sout == @["start ta_out", "to stdout", "to stdout", "to stdout", "to stdout", "end ta_out"] + + block: # startProcess exit code (replaces old test `texitcode` + `tafalse`) + let output = compileNimProg("-d:case_testfile4", "D20200626T224758") + var p = startProcess(output, dir) + doAssert waitForExit(p) == QuitFailure + p = startProcess(output, dir) + var running = true + while running: + # xxx: avoid busyloop? + running = running(p) + doAssert waitForExit(p) == QuitFailure + + # make sure that first call to running() after process exit returns false + p = startProcess(output, dir) + for j in 0..<30: # refs #13449 + os.sleep(50) + if not running(p): break + doAssert not running(p) + doAssert waitForExit(p) == QuitFailure # avoid zombies + + import std/strtabs + block execProcessTest: + var result = execCmdEx("nim r --hints:off -", options = {}, input = "echo 3*4") + stripLineEnd(result[0]) + doAssert result == ("12", 0) + when not defined(windows): + doAssert execCmdEx("ls --nonexistent").exitCode != 0 + when false: + # bug: on windows, this raises; on posix, passes + doAssert execCmdEx("nonexistent").exitCode != 0 + when defined(posix): + doAssert execCmdEx("echo $FO", env = newStringTable({"FO": "B"})) == ("B\n", 0) + doAssert execCmdEx("echo $PWD", workingDir = "/") == ("/\n", 0) + + block: # bug #17749 + let output = compileNimProg("-d:case_testfile4", "D20210417T011153") + var p = startProcess(output, dir) + let inp = p.inputStream + var count = 0 + when defined(windows): + # xxx we should make osproc.hsWriteData raise IOError on windows, consistent + # with posix; we could also (in addition) make IOError a subclass of OSError. + type SIGPIPEError = OSError + else: + type SIGPIPEError = IOError + doAssertRaises(SIGPIPEError): + for i in 0..<100000: + count.inc + inp.writeLine "ok" # was giving SIGPIPE and crashing + doAssert count >= 100 + doAssert waitForExit(p) == QuitFailure + close(p) # xxx isn't that missing in other places? |