proc: Fix command-line arguments on Windows (#501)
* proc: add tests for command-line arguments adds tests to make sure command-line arguments are passed to Launch() properly * proc_windows: pass command-line arguments to CreateProcess() build command-line arguments according to how the standard library does it and pass the command line along to the actual syscall on Windows. see discussion in #479 * proc: better testing of cmd-line arguments * proc_windows: fix a possible error-case with passing just 1 argument previously, the command line pointer passed to sys.CreateProcess was empty, if we had 0 parameters (len(cmd) == 1, as cmd[0] is the executable, so no cmdlineGo would be created, while with any argument it would as len(cmd) > 1). This might cause problems down the road, so make sure we include the command line every time, even if it seems to work without. * proc: improve testing of command-line arguments test that arguments with spaces are passed on correctly and DRY failure/success condition checking in the args test
This commit is contained in:
parent
e8cb04303a
commit
60946a759c
20
_fixtures/testargs.go
Normal file
20
_fixtures/testargs.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// this test expects AT LEAST 1 argument, and the first one needs to be "test".
|
||||||
|
// second one is optional but if given, it should be "-passFlag"
|
||||||
|
fmt.Printf("received args %#v\n", os.Args)
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
panic("os.args too short!")
|
||||||
|
} else if os.Args[1] != "test" {
|
||||||
|
panic("os.args[1] is not test!")
|
||||||
|
}
|
||||||
|
if len(os.Args) >= 3 && os.Args[2] != "pass flag" {
|
||||||
|
panic("os.args[2] is not \"pass flag\"!")
|
||||||
|
}
|
||||||
|
}
|
@ -43,6 +43,21 @@ func withTestProcess(name string, t testing.TB, fn func(p *Process, fixture prot
|
|||||||
fn(p, fixture)
|
fn(p, fixture)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func withTestProcessArgs(name string, t testing.TB, fn func(p *Process, fixture protest.Fixture), args []string) {
|
||||||
|
fixture := protest.BuildFixture(name)
|
||||||
|
p, err := Launch(append([]string{fixture.Path}, args...))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Launch():", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
p.Halt()
|
||||||
|
p.Kill()
|
||||||
|
}()
|
||||||
|
|
||||||
|
fn(p, fixture)
|
||||||
|
}
|
||||||
|
|
||||||
func getRegisters(p *Process, t *testing.T) Registers {
|
func getRegisters(p *Process, t *testing.T) Registers {
|
||||||
regs, err := p.Registers()
|
regs, err := p.Registers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -1656,6 +1671,45 @@ func TestPanicBreakpoint(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCmdLineArgs(t *testing.T) {
|
||||||
|
expectSuccess := func(p *Process, fixture protest.Fixture) {
|
||||||
|
err := p.Continue()
|
||||||
|
bp := p.CurrentBreakpoint()
|
||||||
|
if bp != nil && bp.Name == "unrecovered-panic" {
|
||||||
|
t.Fatalf("testing args failed on unrecovered-panic breakpoint: %v", p.CurrentBreakpoint)
|
||||||
|
}
|
||||||
|
exit, exited := err.(ProcessExitedError)
|
||||||
|
if !exited {
|
||||||
|
t.Fatalf("Process did not exit!", err)
|
||||||
|
} else {
|
||||||
|
if exit.Status != 0 {
|
||||||
|
t.Fatalf("process exited with invalid status", exit.Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expectPanic := func(p *Process, fixture protest.Fixture) {
|
||||||
|
p.Continue()
|
||||||
|
bp := p.CurrentBreakpoint()
|
||||||
|
if bp == nil || bp.Name != "unrecovered-panic" {
|
||||||
|
t.Fatalf("not on unrecovered-panic breakpoint: %v", p.CurrentBreakpoint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure multiple arguments (including one with spaces) are passed to the binary correctly
|
||||||
|
withTestProcessArgs("testargs", t, expectSuccess, []string{"test"})
|
||||||
|
withTestProcessArgs("testargs", t, expectSuccess, []string{"test", "pass flag"})
|
||||||
|
// check that arguments with spaces are *only* passed correctly when correctly called
|
||||||
|
withTestProcessArgs("testargs", t, expectPanic, []string{"test pass", "flag"})
|
||||||
|
withTestProcessArgs("testargs", t, expectPanic, []string{"test", "pass", "flag"})
|
||||||
|
withTestProcessArgs("testargs", t, expectPanic, []string{"test pass flag"})
|
||||||
|
// and that invalid cases (wrong arguments or no arguments) panic
|
||||||
|
withTestProcess("testargs", t, expectPanic)
|
||||||
|
withTestProcessArgs("testargs", t, expectPanic, []string{"invalid"})
|
||||||
|
withTestProcessArgs("testargs", t, expectPanic, []string{"test", "invalid"})
|
||||||
|
withTestProcessArgs("testargs", t, expectPanic, []string{"invalid", "pass flag"})
|
||||||
|
}
|
||||||
|
|
||||||
func TestIssue462(t *testing.T) {
|
func TestIssue462(t *testing.T) {
|
||||||
// Stacktrace of Goroutine 0 fails with an error
|
// Stacktrace of Goroutine 0 fails with an error
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
|
@ -47,9 +47,6 @@ func Launch(cmd []string) (*Process, error) {
|
|||||||
if _, err := os.Stat(argv0Go); err != nil {
|
if _, err := os.Stat(argv0Go); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
argv0, _ := syscall.UTF16PtrFromString(argv0Go)
|
|
||||||
|
|
||||||
// Duplicate the stdin/stdout/stderr handles
|
// Duplicate the stdin/stdout/stderr handles
|
||||||
files := []uintptr{uintptr(syscall.Stdin), uintptr(syscall.Stdout), uintptr(syscall.Stderr)}
|
files := []uintptr{uintptr(syscall.Stdin), uintptr(syscall.Stdout), uintptr(syscall.Stderr)}
|
||||||
p, _ := syscall.GetCurrentProcess()
|
p, _ := syscall.GetCurrentProcess()
|
||||||
@ -62,6 +59,32 @@ func Launch(cmd []string) (*Process, error) {
|
|||||||
defer syscall.CloseHandle(syscall.Handle(fd[i]))
|
defer syscall.CloseHandle(syscall.Handle(fd[i]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
argv0, err := syscall.UTF16PtrFromString(argv0Go)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// create suitable command line for CreateProcess
|
||||||
|
// see https://github.com/golang/go/blob/master/src/syscall/exec_windows.go#L326
|
||||||
|
// adapted from standard library makeCmdLine
|
||||||
|
// see https://github.com/golang/go/blob/master/src/syscall/exec_windows.go#L86
|
||||||
|
var cmdLineGo string
|
||||||
|
if len(cmd) >= 1 {
|
||||||
|
for _, v := range cmd {
|
||||||
|
if cmdLineGo != "" {
|
||||||
|
cmdLineGo += " "
|
||||||
|
}
|
||||||
|
cmdLineGo += syscall.EscapeArg(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmdLine *uint16
|
||||||
|
if cmdLineGo != "" {
|
||||||
|
if cmdLine, err = syscall.UTF16PtrFromString(cmdLineGo); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize the startup info and create process
|
// Initialize the startup info and create process
|
||||||
si := new(sys.StartupInfo)
|
si := new(sys.StartupInfo)
|
||||||
si.Cb = uint32(unsafe.Sizeof(*si))
|
si.Cb = uint32(unsafe.Sizeof(*si))
|
||||||
@ -70,7 +93,7 @@ func Launch(cmd []string) (*Process, error) {
|
|||||||
si.StdOutput = sys.Handle(fd[1])
|
si.StdOutput = sys.Handle(fd[1])
|
||||||
si.StdErr = sys.Handle(fd[2])
|
si.StdErr = sys.Handle(fd[2])
|
||||||
pi := new(sys.ProcessInformation)
|
pi := new(sys.ProcessInformation)
|
||||||
err = sys.CreateProcess(argv0, nil, nil, nil, true, DEBUGONLYTHISPROCESS, nil, nil, si, pi)
|
err = sys.CreateProcess(argv0, cmdLine, nil, nil, true, DEBUGONLYTHISPROCESS, nil, nil, si, pi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user