* s/exitStatus(...)/exitStatusLikeShell(...)/ * fix #10273 execShellCmd now returns nonzero when child exits with signal * test case for #10249 and explanation for the bug * fix test failure * add tests/nim.cfg
This commit is contained in:
parent
f5cc2e2de5
commit
9af85fb69f
8 changed files with 148 additions and 41 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -32,7 +32,7 @@ doc/*.html
|
||||||
doc/*.pdf
|
doc/*.pdf
|
||||||
doc/*.idx
|
doc/*.idx
|
||||||
/web/upload
|
/web/upload
|
||||||
build/*
|
/build/*
|
||||||
bin/*
|
bin/*
|
||||||
|
|
||||||
# iOS specific wildcards.
|
# iOS specific wildcards.
|
||||||
|
|
|
||||||
|
|
@ -1287,6 +1287,17 @@ proc moveFile*(source, dest: string) {.rtl, extern: "nos$1",
|
||||||
discard tryRemoveFile(dest)
|
discard tryRemoveFile(dest)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
proc exitStatusLikeShell*(status: cint): cint =
|
||||||
|
## converts exit code from `c_system` into a shell exit code
|
||||||
|
when defined(posix) and not weirdTarget:
|
||||||
|
if WIFSIGNALED(status):
|
||||||
|
# like the shell!
|
||||||
|
128 + WTERMSIG(status)
|
||||||
|
else:
|
||||||
|
WEXITSTATUS(status)
|
||||||
|
else:
|
||||||
|
status
|
||||||
|
|
||||||
proc execShellCmd*(command: string): int {.rtl, extern: "nos$1",
|
proc execShellCmd*(command: string): int {.rtl, extern: "nos$1",
|
||||||
tags: [ExecIOEffect], noNimScript.} =
|
tags: [ExecIOEffect], noNimScript.} =
|
||||||
## Executes a `shell command`:idx:.
|
## Executes a `shell command`:idx:.
|
||||||
|
|
@ -1295,14 +1306,8 @@ proc execShellCmd*(command: string): int {.rtl, extern: "nos$1",
|
||||||
## line arguments given to program. The proc returns the error code
|
## line arguments given to program. The proc returns the error code
|
||||||
## of the shell when it has finished. The proc does not return until
|
## of the shell when it has finished. The proc does not return until
|
||||||
## the process has finished. To execute a program without having a
|
## the process has finished. To execute a program without having a
|
||||||
## shell involved, use the `execProcess` proc of the `osproc`
|
## shell involved, use `osproc.execProcess`.
|
||||||
## module.
|
result = exitStatusLikeShell(c_system(command))
|
||||||
when defined(macosx):
|
|
||||||
result = c_system(command) shr 8
|
|
||||||
elif defined(posix):
|
|
||||||
result = WEXITSTATUS(c_system(command))
|
|
||||||
else:
|
|
||||||
result = c_system(command)
|
|
||||||
|
|
||||||
# Templates for filtering directories and files
|
# Templates for filtering directories and files
|
||||||
when defined(windows) and not weirdTarget:
|
when defined(windows) and not weirdTarget:
|
||||||
|
|
|
||||||
|
|
@ -724,13 +724,6 @@ elif not defined(useNimRtl):
|
||||||
proc isExitStatus(status: cint): bool =
|
proc isExitStatus(status: cint): bool =
|
||||||
WIFEXITED(status) or WIFSIGNALED(status)
|
WIFEXITED(status) or WIFSIGNALED(status)
|
||||||
|
|
||||||
proc exitStatus(status: cint): cint =
|
|
||||||
if WIFSIGNALED(status):
|
|
||||||
# like the shell!
|
|
||||||
128 + WTERMSIG(status)
|
|
||||||
else:
|
|
||||||
WEXITSTATUS(status)
|
|
||||||
|
|
||||||
proc envToCStringArray(t: StringTableRef): cstringArray =
|
proc envToCStringArray(t: StringTableRef): cstringArray =
|
||||||
result = cast[cstringArray](alloc0((t.len + 1) * sizeof(cstring)))
|
result = cast[cstringArray](alloc0((t.len + 1) * sizeof(cstring)))
|
||||||
var i = 0
|
var i = 0
|
||||||
|
|
@ -1054,7 +1047,7 @@ elif not defined(useNimRtl):
|
||||||
|
|
||||||
proc waitForExit(p: Process, timeout: int = -1): int =
|
proc waitForExit(p: Process, timeout: int = -1): int =
|
||||||
if p.exitFlag:
|
if p.exitFlag:
|
||||||
return exitStatus(p.exitStatus)
|
return exitStatusLikeShell(p.exitStatus)
|
||||||
|
|
||||||
if timeout == -1:
|
if timeout == -1:
|
||||||
var status: cint = 1
|
var status: cint = 1
|
||||||
|
|
@ -1109,7 +1102,7 @@ elif not defined(useNimRtl):
|
||||||
finally:
|
finally:
|
||||||
discard posix.close(kqFD)
|
discard posix.close(kqFD)
|
||||||
|
|
||||||
result = exitStatus(p.exitStatus)
|
result = exitStatusLikeShell(p.exitStatus)
|
||||||
else:
|
else:
|
||||||
import times
|
import times
|
||||||
|
|
||||||
|
|
@ -1142,7 +1135,7 @@ elif not defined(useNimRtl):
|
||||||
s.tv_nsec = b.tv_nsec
|
s.tv_nsec = b.tv_nsec
|
||||||
|
|
||||||
if p.exitFlag:
|
if p.exitFlag:
|
||||||
return exitStatus(p.exitStatus)
|
return exitStatusLikeShell(p.exitStatus)
|
||||||
|
|
||||||
if timeout == -1:
|
if timeout == -1:
|
||||||
var status: cint = 1
|
var status: cint = 1
|
||||||
|
|
@ -1220,20 +1213,20 @@ elif not defined(useNimRtl):
|
||||||
if sigprocmask(SIG_UNBLOCK, nmask, omask) == -1:
|
if sigprocmask(SIG_UNBLOCK, nmask, omask) == -1:
|
||||||
raiseOSError(osLastError())
|
raiseOSError(osLastError())
|
||||||
|
|
||||||
result = exitStatus(p.exitStatus)
|
result = exitStatusLikeShell(p.exitStatus)
|
||||||
|
|
||||||
proc peekExitCode(p: Process): int =
|
proc peekExitCode(p: Process): int =
|
||||||
var status = cint(0)
|
var status = cint(0)
|
||||||
result = -1
|
result = -1
|
||||||
if p.exitFlag:
|
if p.exitFlag:
|
||||||
return exitStatus(p.exitStatus)
|
return exitStatusLikeShell(p.exitStatus)
|
||||||
|
|
||||||
var ret = waitpid(p.id, status, WNOHANG)
|
var ret = waitpid(p.id, status, WNOHANG)
|
||||||
if ret > 0:
|
if ret > 0:
|
||||||
if isExitStatus(status):
|
if isExitStatus(status):
|
||||||
p.exitFlag = true
|
p.exitFlag = true
|
||||||
p.exitStatus = status
|
p.exitStatus = status
|
||||||
result = exitStatus(status)
|
result = exitStatusLikeShell(status)
|
||||||
|
|
||||||
proc createStream(stream: var Stream, handle: var FileHandle,
|
proc createStream(stream: var Stream, handle: var FileHandle,
|
||||||
fileMode: FileMode) =
|
fileMode: FileMode) =
|
||||||
|
|
@ -1265,7 +1258,7 @@ elif not defined(useNimRtl):
|
||||||
proc execCmd(command: string): int =
|
proc execCmd(command: string): int =
|
||||||
when defined(linux):
|
when defined(linux):
|
||||||
let tmp = csystem(command)
|
let tmp = csystem(command)
|
||||||
result = if tmp == -1: tmp else: exitStatus(tmp)
|
result = if tmp == -1: tmp else: exitStatusLikeShell(tmp)
|
||||||
else:
|
else:
|
||||||
result = csystem(command)
|
result = csystem(command)
|
||||||
|
|
||||||
|
|
|
||||||
31
testament/lib/stdtest/specialpaths.nim
Normal file
31
testament/lib/stdtest/specialpaths.nim
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#[
|
||||||
|
todo: move findNimStdLibCompileTime, findNimStdLib here
|
||||||
|
]#
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Note: all the const paths defined here are known at compile time and valid
|
||||||
|
# so long Nim repo isn't relocated after compilation.
|
||||||
|
# This means the binaries they produce will embed hardcoded paths, which
|
||||||
|
# isn't appropriate for some applications that need to be relocatable.
|
||||||
|
|
||||||
|
const sourcePath = currentSourcePath()
|
||||||
|
# robust way to derive other paths here
|
||||||
|
# We don't depend on PATH so this is robust to having multiple nim
|
||||||
|
# binaries
|
||||||
|
|
||||||
|
const nimRootDir* = sourcePath.parentDir.parentDir.parentDir.parentDir
|
||||||
|
## root of Nim repo
|
||||||
|
|
||||||
|
const stdlibDir* = nimRootDir / "lib"
|
||||||
|
# todo: make nimeval.findNimStdLibCompileTime use this
|
||||||
|
|
||||||
|
const systemPath* = stdlibDir / "system.nim"
|
||||||
|
|
||||||
|
const buildDir* = nimRootDir / "build"
|
||||||
|
## refs #10268: all testament generated files should go here to avoid
|
||||||
|
## polluting .gitignore
|
||||||
|
|
||||||
|
static:
|
||||||
|
# sanity check
|
||||||
|
doAssert fileExists(systemPath)
|
||||||
1
tests/nim.cfg
Normal file
1
tests/nim.cfg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
--path:"../testament/lib" # so we can `import stdtest/foo` in this dir
|
||||||
|
|
@ -6,6 +6,8 @@ discard """
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
# consider moving this inside tosproc (taking care that it's for cpp mode)
|
||||||
|
|
||||||
if paramCount() == 0:
|
if paramCount() == 0:
|
||||||
# main process
|
# main process
|
||||||
doAssert execShellCmd(getAppFilename().quoteShell & " test") == 1
|
doAssert execShellCmd(getAppFilename().quoteShell & " test") == 1
|
||||||
|
|
|
||||||
|
|
@ -263,3 +263,5 @@ block splitFile:
|
||||||
doAssert splitFile("abc/.") == ("abc", ".", "")
|
doAssert splitFile("abc/.") == ("abc", ".", "")
|
||||||
doAssert splitFile("..") == ("", "..", "")
|
doAssert splitFile("..") == ("", "..", "")
|
||||||
doAssert splitFile("a/..") == ("a", "..", "")
|
doAssert splitFile("a/..") == ("a", "..", "")
|
||||||
|
|
||||||
|
# execShellCmd is tested in tosproc
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,96 @@
|
||||||
discard """
|
|
||||||
output: ""
|
|
||||||
"""
|
|
||||||
# test the osproc module
|
# test the osproc module
|
||||||
|
|
||||||
import os, osproc
|
import stdtest/specialpaths
|
||||||
|
import "../.." / compiler/unittest_light
|
||||||
|
|
||||||
block execProcessTest:
|
when defined(case_testfile): # compiled test file for child process
|
||||||
let dir = parentDir(currentSourcePath())
|
from posix import exitnow
|
||||||
let (outp, err) = execCmdEx("nim c " & quoteShell(dir / "osproctest.nim"))
|
proc c_exit2(code: c_int): void {.importc: "_exit", header: "<unistd.h>".}
|
||||||
doAssert err == 0
|
import os
|
||||||
let exePath = dir / addFileExt("osproctest", ExeExt)
|
var a = 0
|
||||||
let outStr1 = execProcess(exePath, workingDir=dir, args=["foo", "b A r"], options={})
|
proc fun(b = 0) =
|
||||||
doAssert outStr1 == dir & "\nfoo\nb A r\n"
|
a.inc
|
||||||
|
if a mod 10000000 == 0: # prevents optimizing it away
|
||||||
|
echo a
|
||||||
|
fun(b+1)
|
||||||
|
|
||||||
const testDir = "t e st"
|
proc main() =
|
||||||
createDir(testDir)
|
let args = commandLineParams()
|
||||||
doAssert dirExists(testDir)
|
echo (msg: "child binary", pid: getCurrentProcessId())
|
||||||
let outStr2 = execProcess(exePath, workingDir=testDir, args=["x yz"], options={})
|
let arg = args[0]
|
||||||
doAssert outStr2 == absolutePath(testDir) & "\nx yz\n"
|
echo (arg: arg)
|
||||||
|
case arg
|
||||||
|
of "exit_0":
|
||||||
|
if true: quit(0)
|
||||||
|
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, returing
|
||||||
|
# (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()
|
||||||
|
|
||||||
removeDir(testDir)
|
else:
|
||||||
removeFile(exePath)
|
|
||||||
|
import os, osproc, strutils, posix
|
||||||
|
|
||||||
|
block execShellCmdTest:
|
||||||
|
## first, compile child program
|
||||||
|
const nim = getCurrentCompilerExe()
|
||||||
|
const sourcePath = currentSourcePath()
|
||||||
|
let output = buildDir / "D20190111T024543".addFileExt(ExeExt)
|
||||||
|
let cmd = "$# c -o:$# -d:release -d:case_testfile $#" % [nim, output,
|
||||||
|
sourcePath]
|
||||||
|
# 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>".}
|
||||||
|
assertEquals c_system(cmd), 0
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
runTest("quit_139", 139)
|
||||||
|
runTest("exit_array", 1)
|
||||||
|
when defined(posix): # on windows, -1073741571
|
||||||
|
runTest("exit_recursion", SIGSEGV.int + 128) # bug #10273: was returning 0
|
||||||
|
assertEquals exitStatusLikeShell(SIGSEGV), SIGSEGV + 128.cint
|
||||||
|
|
||||||
|
block execProcessTest:
|
||||||
|
let dir = parentDir(currentSourcePath())
|
||||||
|
let (outp, 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)
|
||||||
|
removeFile(exePath)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue