delve/cmd/dlv/dlv_test.go
Alex Brainman 0cfe539052 proc/native: make sure debugged executable can be deleted on windows (#890)
* proc/native: make sure debugged executable can be deleted on windows

Delve opens debugged executable to read binary info it
contains, but it never closes the file. Windows will not
let you delete file that is opened. So close Process.bi
in Process.postExit, and actually call Process.postExit
from windows Process.Kill.

Also Windows sends some debugging events
(EXIT_PROCESS_DEBUG_EVENT event in particular) after Delve
calls TerminateProcess. The events need to be consumed by
debugger before debugged process will be released by
Windows. So call Process.waitForDebugEvent after
TerminateProcess in Process.Kill.

Fixes #398

* cmd/dlv: make TestIssue398 pass on darwin

* cmd/dlv: add comment for TestIssue398

* proc/native: wait for debuggee to exit before returning from windows Process.Kill

* proc/native: close process handle before returning from windows killProcess

* proc/native: remove not used Process.Process
2017-07-26 12:51:44 -06:00

160 lines
3.9 KiB
Go

package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
protest "github.com/derekparker/delve/pkg/proc/test"
"github.com/derekparker/delve/service/rpc2"
)
var testBackend string
func TestMain(m *testing.M) {
flag.StringVar(&testBackend, "backend", "", "selects backend")
flag.Parse()
if testBackend == "" {
testBackend = os.Getenv("PROCTEST")
if testBackend == "" {
testBackend = "native"
}
}
os.Exit(m.Run())
}
func assertNoError(err error, t testing.TB, s string) {
if err != nil {
_, file, line, _ := runtime.Caller(1)
fname := filepath.Base(file)
t.Fatalf("failed assertion at %s:%d: %s - %s\n", fname, line, s, err)
}
}
func goEnv(name string) string {
if val := os.Getenv(name); val != "" {
return val
}
val, err := exec.Command("go", "env", name).Output()
if err != nil {
panic(err) // the Go tool was tested to work earlier
}
return strings.TrimSpace(string(val))
}
func TestBuild(t *testing.T) {
const listenAddr = "localhost:40573"
var err error
makedir := filepath.Join(goEnv("GOPATH"), "src", "github.com", "derekparker", "delve")
for _, makeProgram := range []string{"make", "mingw32-make"} {
var out []byte
cmd := exec.Command(makeProgram, "build")
cmd.Dir = makedir
out, err = cmd.CombinedOutput()
if err == nil {
break
} else {
t.Logf("makefile error %s (%s): %v", makeProgram, makedir, err)
t.Logf("output %s", string(out))
}
}
assertNoError(err, t, "make")
dlvbin := filepath.Join(makedir, "dlv")
defer os.Remove(dlvbin)
fixtures := protest.FindFixturesDir()
buildtestdir := filepath.Join(fixtures, "buildtest")
cmd := exec.Command(dlvbin, "debug", "--headless=true", "--listen="+listenAddr, "--api-version=2", "--backend="+testBackend)
cmd.Dir = buildtestdir
stdout, err := cmd.StdoutPipe()
assertNoError(err, t, "stdout pipe")
cmd.Start()
scan := bufio.NewScanner(stdout)
// wait for the debugger to start
scan.Scan()
go func() {
for scan.Scan() {
// keep pipe empty
}
}()
client := rpc2.NewClient(listenAddr)
state := <-client.Continue()
if !state.Exited {
t.Fatal("Program did not exit")
}
client.Detach(true)
cmd.Wait()
}
func testIssue398(t *testing.T, dlvbin string, cmds []string) (stdout, stderr []byte) {
var stdoutBuf, stderrBuf bytes.Buffer
buildtestdir := filepath.Join(protest.FindFixturesDir(), "buildtest")
cmd := exec.Command(dlvbin, "debug")
cmd.Dir = buildtestdir
stdin, err := cmd.StdinPipe()
if err != nil {
t.Fatal(err)
}
cmd.Stdout = &stdoutBuf
cmd.Stderr = &stderrBuf
if err := cmd.Start(); err != nil {
t.Fatal(err)
}
for _, c := range cmds {
fmt.Fprintf(stdin, "%s\n", c)
}
// ignore "dlv debug" command error, it returns
// errors even after successful debug session.
cmd.Wait()
stdout, stderr = stdoutBuf.Bytes(), stderrBuf.Bytes()
debugbin := filepath.Join(buildtestdir, "debug")
_, err = os.Stat(debugbin)
if err == nil {
t.Errorf("running %q: file %v was not deleted\nstdout is %q, stderr is %q", cmds, debugbin, stdout, stderr)
return
}
if !os.IsNotExist(err) {
t.Errorf("running %q: %v\nstdout is %q, stderr is %q", cmds, err, stdout, stderr)
return
}
return
}
// TestIssue398 verifies that the debug executable is removed after exit.
func TestIssue398(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "TestIssue398")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmpdir)
dlvbin := filepath.Join(tmpdir, "dlv.exe")
out, err := exec.Command("go", "build", "-o", dlvbin, "github.com/derekparker/delve/cmd/dlv").CombinedOutput()
if err != nil {
t.Fatalf("go build -o %v github.com/derekparker/delve/cmd/dlv: %v\n%s", dlvbin, err, string(out))
}
testIssue398(t, dlvbin, []string{"exit"})
const hello = "hello world!"
stdout, _ := testIssue398(t, dlvbin, []string{"continue", "exit"})
if !strings.Contains(string(stdout), hello) {
t.Errorf("stdout %q should contain %q", stdout, hello)
}
}