tosproc.nim 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. discard """
  2. joinable: false
  3. """
  4. #[
  5. joinable: false
  6. because it'd need cleanup up stdout
  7. see also: tests/osproc/*.nim; consider merging those into a single test here
  8. (easier to factor and test more things as a single self contained test)
  9. ]#
  10. when defined(case_testfile): # compiled test file for child process
  11. from posix import exitnow
  12. proc c_exit2(code: c_int): void {.importc: "_exit", header: "<unistd.h>".}
  13. import os
  14. var a = 0
  15. proc fun(b = 0) =
  16. a.inc
  17. if a mod 10000000 == 0: # prevents optimizing it away
  18. echo a
  19. fun(b+1)
  20. proc main() =
  21. let args = commandLineParams()
  22. echo (msg: "child binary", pid: getCurrentProcessId())
  23. let arg = args[0]
  24. echo (arg: arg)
  25. case arg
  26. of "exit_0":
  27. if true: quit(0)
  28. of "exitnow_139":
  29. if true: exitnow(139)
  30. of "c_exit2_139":
  31. if true: c_exit2(139)
  32. of "quit_139":
  33. # `exitStatusLikeShell` doesn't distinguish between a process that
  34. # exit(139) and a process that gets killed with `SIGSEGV` because
  35. # 139 = 11 + 128 = SIGSEGV + 128.
  36. # However, as #10249 shows, this leads to bad debugging experience
  37. # when a child process dies with SIGSEGV, leaving no trace of why it
  38. # failed. The shell (and lldb debugger) solves that by inserting a
  39. # helpful msg: `segmentation fault` when it detects a signal killed
  40. # the child.
  41. # todo: expose an API that will show more diagnostic, returning
  42. # (exitCode, signal) instead of just `shellExitCode`.
  43. if true: quit(139)
  44. of "exit_recursion": # stack overflow by infinite recursion
  45. fun()
  46. echo a
  47. of "exit_array": # bad array access
  48. echo args[1]
  49. main()
  50. elif defined(case_testfile2):
  51. import strutils
  52. let x = stdin.readLine()
  53. echo x.parseInt + 5
  54. elif defined(case_testfile3):
  55. echo "start ta_out"
  56. stdout.writeLine("to stdout")
  57. stdout.flushFile()
  58. stdout.writeLine("to stdout")
  59. stdout.flushFile()
  60. stderr.writeLine("to stderr")
  61. stderr.flushFile()
  62. stderr.writeLine("to stderr")
  63. stderr.flushFile()
  64. stdout.writeLine("to stdout")
  65. stdout.flushFile()
  66. stdout.writeLine("to stdout")
  67. stdout.flushFile()
  68. echo "end ta_out"
  69. elif defined(case_testfile4):
  70. import system # we could remove that
  71. quit(QuitFailure)
  72. else: # main driver
  73. import stdtest/[specialpaths, unittest_light]
  74. import os, osproc, strutils
  75. const nim = getCurrentCompilerExe()
  76. const sourcePath = currentSourcePath()
  77. let dir = getCurrentDir() / "tests" / "osproc"
  78. template deferScoped(cleanup, body) =
  79. # pending https://github.com/nim-lang/RFCs/issues/236#issuecomment-646855314
  80. # xxx move to std/sugar or (preferably) some low level module
  81. try: body
  82. finally: cleanup
  83. # we're testing `execShellCmd` so don't rely on it to compile test file
  84. # note: this should be exported in posix.nim
  85. proc c_system(cmd: cstring): cint {.importc: "system", header: "<stdlib.h>".}
  86. proc compileNimProg(opt: string, name: string): string =
  87. result = buildDir / name.addFileExt(ExeExt)
  88. let cmd = "$# c -o:$# --hints:off $# $#" % [nim.quoteShell, result.quoteShell, opt, sourcePath.quoteShell]
  89. doAssert c_system(cmd) == 0, $cmd
  90. doAssert result.fileExists
  91. block execShellCmdTest:
  92. let output = compileNimProg("-d:release -d:case_testfile", "D20190111T024543")
  93. ## use it
  94. template runTest(arg: string, expected: int) =
  95. echo (arg2: arg, expected2: expected)
  96. assertEquals execShellCmd(output & " " & arg), expected
  97. runTest("exit_0", 0)
  98. runTest("exitnow_139", 139)
  99. runTest("c_exit2_139", 139)
  100. runTest("quit_139", 139)
  101. block execProcessTest:
  102. let dir = sourcePath.parentDir
  103. let (_, err) = execCmdEx(nim & " c " & quoteShell(dir / "osproctest.nim"))
  104. doAssert err == 0
  105. let exePath = dir / addFileExt("osproctest", ExeExt)
  106. let outStr1 = execProcess(exePath, workingDir = dir, args = ["foo",
  107. "b A r"], options = {})
  108. doAssert outStr1 == dir & "\nfoo\nb A r\n"
  109. const testDir = "t e st"
  110. createDir(testDir)
  111. doAssert dirExists(testDir)
  112. let outStr2 = execProcess(exePath, workingDir = testDir, args = ["x yz"],
  113. options = {})
  114. doAssert outStr2 == absolutePath(testDir) & "\nx yz\n"
  115. removeDir(testDir)
  116. try:
  117. removeFile(exePath)
  118. except OSError:
  119. discard
  120. import std/streams
  121. block: # test for startProcess (more tests needed)
  122. # bugfix: windows stdin.close was a noop and led to blocking reads
  123. proc startProcessTest(command: string, options: set[ProcessOption] = {
  124. poStdErrToStdOut, poUsePath}, input = ""): tuple[
  125. output: TaintedString,
  126. exitCode: int] {.tags:
  127. [ExecIOEffect, ReadIOEffect, RootEffect], gcsafe.} =
  128. var p = startProcess(command, options = options + {poEvalCommand})
  129. var outp = outputStream(p)
  130. if input.len > 0: inputStream(p).write(input)
  131. close inputStream(p)
  132. result = (TaintedString"", -1)
  133. var line = newStringOfCap(120).TaintedString
  134. while true:
  135. if outp.readLine(line):
  136. result[0].string.add(line.string)
  137. result[0].string.add("\n")
  138. else:
  139. result[1] = peekExitCode(p)
  140. if result[1] != -1: break
  141. close(p)
  142. var result = startProcessTest("nim r --hints:off -", options = {}, input = "echo 3*4")
  143. doAssert result == ("12\n", 0)
  144. block: # startProcess stdin (replaces old test `tstdin` + `ta_in`)
  145. let output = compileNimProg("-d:case_testfile2", "D20200626T215919")
  146. var p = startProcess(output, dir) # dir not needed though
  147. p.inputStream.write("5\n")
  148. p.inputStream.flush()
  149. var line = ""
  150. var s: seq[string]
  151. while p.outputStream.readLine(line.TaintedString):
  152. s.add line
  153. doAssert s == @["10"]
  154. block:
  155. let output = compileNimProg("-d:case_testfile3", "D20200626T221233")
  156. var x = newStringOfCap(120)
  157. block: # startProcess stdout poStdErrToStdOut (replaces old test `tstdout` + `ta_out`)
  158. var p = startProcess(output, dir, options={poStdErrToStdOut})
  159. deferScoped: p.close()
  160. do:
  161. var sout: seq[string]
  162. while p.outputStream.readLine(x.TaintedString): sout.add x
  163. doAssert sout == @["start ta_out", "to stdout", "to stdout", "to stderr", "to stderr", "to stdout", "to stdout", "end ta_out"]
  164. block: # startProcess stderr (replaces old test `tstderr` + `ta_out`)
  165. var p = startProcess(output, dir, options={})
  166. deferScoped: p.close()
  167. do:
  168. var serr, sout: seq[string]
  169. while p.errorStream.readLine(x.TaintedString): serr.add x
  170. while p.outputStream.readLine(x.TaintedString): sout.add x
  171. doAssert serr == @["to stderr", "to stderr"]
  172. doAssert sout == @["start ta_out", "to stdout", "to stdout", "to stdout", "to stdout", "end ta_out"]
  173. block: # startProcess exit code (replaces old test `texitcode` + `tafalse`)
  174. let output = compileNimProg("-d:case_testfile4", "D20200626T224758")
  175. var p = startProcess(output, dir)
  176. doAssert waitForExit(p) == QuitFailure
  177. p = startProcess(output, dir)
  178. var running = true
  179. while running:
  180. # xxx: avoid busyloop?
  181. running = running(p)
  182. doAssert waitForExit(p) == QuitFailure
  183. # make sure that first call to running() after process exit returns false
  184. p = startProcess(output, dir)
  185. for j in 0..<30: # refs #13449
  186. os.sleep(50)
  187. if not running(p): break
  188. doAssert not running(p)
  189. doAssert waitForExit(p) == QuitFailure # avoid zombies
  190. import std/strtabs
  191. block execProcessTest:
  192. var result = execCmdEx("nim r --hints:off -", options = {}, input = "echo 3*4")
  193. stripLineEnd(result[0])
  194. doAssert result == ("12", 0)
  195. doAssert execCmdEx("ls --nonexistant").exitCode != 0
  196. when false:
  197. # bug: on windows, this raises; on posix, passes
  198. doAssert execCmdEx("nonexistant").exitCode != 0
  199. when defined(posix):
  200. doAssert execCmdEx("echo $FO", env = newStringTable({"FO": "B"})) == ("B\n", 0)
  201. doAssert execCmdEx("echo $PWD", workingDir = "/") == ("/\n", 0)