delve/cmd/dlv/dlv_test.go
aarzilli 087431f029 Makefile: replace makefile with a script
We were using our makefile not for the intended purpose of makefiles
but to multiplex a series of small scripts.
We can easily achieve the same result in a more reasonable programming
language and as a bonus we don't need to install a version of make on
windows anymore.
Additionally our test script had become fairly complicated and will
become even more complicated when testing of PIE buildmode is
introduced.
Allows the tests to run on Windows without having to install Mingw
(although we still want it installed so that we can run cgo tests on
Windows).

Fixes building when GOPATH isn't set.

Fixes #759
2018-09-18 12:06:25 -07:00

247 lines
6.5 KiB
Go

package main_test
import (
"bufio"
"bytes"
"flag"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"github.com/derekparker/delve/cmd/dlv/cmds"
protest "github.com/derekparker/delve/pkg/proc/test"
"github.com/derekparker/delve/pkg/terminal"
"github.com/derekparker/delve/service/rpc2"
"github.com/spf13/cobra/doc"
)
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 goPath(name string) string {
if val := os.Getenv(name); val != "" {
// Use first GOPATH entry if there are multiple.
return filepath.SplitList(val)[0]
}
val, err := exec.Command("go", "env", name).Output()
if err != nil {
panic(err) // the Go tool was tested to work earlier
}
return filepath.SplitList(strings.TrimSpace(string(val)))[0]
}
func TestBuild(t *testing.T) {
const listenAddr = "localhost:40573"
var err error
cmd := exec.Command("go", "run", "scripts/make.go", "build")
cmd.Dir = filepath.Join(goPath("GOPATH"), "src", "github.com", "derekparker", "delve")
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("makefile error: %v\noutput %s\n", err, string(out))
}
dlvbin := filepath.Join(cmd.Dir, "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, "--log", "--log-output=debugger,rpc")
cmd.Dir = buildtestdir
stderr, err := cmd.StderrPipe()
assertNoError(err, t, "stderr pipe")
cmd.Start()
scan := bufio.NewScanner(stderr)
// 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 testOutput(t *testing.T, dlvbin, output string, delveCmds []string) (stdout, stderr []byte) {
var stdoutBuf, stderrBuf bytes.Buffer
buildtestdir := filepath.Join(protest.FindFixturesDir(), "buildtest")
c := []string{dlvbin, "debug"}
debugbin := filepath.Join(buildtestdir, "debug")
if output != "" {
c = append(c, "--output", output)
if filepath.IsAbs(output) {
debugbin = output
} else {
debugbin = filepath.Join(buildtestdir, output)
}
}
cmd := exec.Command(c[0], c[1:]...)
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)
}
// Give delve some time to compile and write the binary.
foundIt := false
for wait := 0; wait < 30; wait++ {
_, err = os.Stat(debugbin)
if err == nil {
foundIt = true
break
}
time.Sleep(1 * time.Second)
}
if !foundIt {
t.Errorf("running %q: file not created: %v", delveCmds, err)
}
for _, c := range delveCmds {
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()
_, err = os.Stat(debugbin)
if err == nil {
if strings.ToLower(os.Getenv("APPVEYOR")) != "true" {
// Sometimes delve on Appveyor can't remove the built binary before
// exiting and gets an "Access is denied" error when trying.
// See: https://ci.appveyor.com/project/derekparker/delve/build/1527
t.Errorf("running %q: file %v was not deleted\nstdout is %q, stderr is %q", delveCmds, debugbin, stdout, stderr)
}
return
}
if !os.IsNotExist(err) {
t.Errorf("running %q: %v\nstdout is %q, stderr is %q", delveCmds, err, stdout, stderr)
return
}
return
}
func getDlvBin(t *testing.T) (string, string) {
tmpdir, err := ioutil.TempDir("", "TestDlv")
if err != nil {
t.Fatal(err)
}
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))
}
return dlvbin, tmpdir
}
// TestOutput verifies that the debug executable is created in the correct path
// and removed after exit.
func TestOutput(t *testing.T) {
dlvbin, tmpdir := getDlvBin(t)
defer os.RemoveAll(tmpdir)
for _, output := range []string{"", "myownname", filepath.Join(tmpdir, "absolute.path")} {
testOutput(t, dlvbin, output, []string{"exit"})
const hello = "hello world!"
stdout, _ := testOutput(t, dlvbin, output, []string{"continue", "exit"})
if !strings.Contains(string(stdout), hello) {
t.Errorf("stdout %q should contain %q", stdout, hello)
}
}
}
func checkAutogenDoc(t *testing.T, filename, gencommand string, generated []byte) {
saved := slurpFile(t, os.ExpandEnv(fmt.Sprintf("$GOPATH/src/github.com/derekparker/delve/%s", filename)))
if len(saved) != len(generated) {
t.Fatalf("%s: needs to be regenerated; run %s", filename, gencommand)
}
for i := range saved {
if saved[i] != generated[i] {
t.Fatalf("%s: needs to be regenerated; run %s", filename, gencommand)
}
}
}
func slurpFile(t *testing.T, filename string) []byte {
saved, err := ioutil.ReadFile(filename)
if err != nil {
t.Fatalf("Could not read %s: %v", filename, err)
}
return saved
}
// TestGeneratedDoc tests that the autogenerated documentation has been
// updated.
func TestGeneratedDoc(t *testing.T) {
// Checks gen-cli-docs.go
var generatedBuf bytes.Buffer
commands := terminal.DebugCommands(nil)
commands.WriteMarkdown(&generatedBuf)
cliDocFilename := "Documentation/cli/README.md"
checkAutogenDoc(t, cliDocFilename, "scripts/gen-cli-docs.go", generatedBuf.Bytes())
// Checks gen-usage-docs.go
tempDir, err := ioutil.TempDir(os.TempDir(), "test-gen-doc")
assertNoError(err, t, "TempDir")
defer cmds.SafeRemoveAll(tempDir)
doc.GenMarkdownTree(cmds.New(true), tempDir)
entries, err := ioutil.ReadDir(tempDir)
assertNoError(err, t, "ReadDir")
for _, doc := range entries {
docFilename := "Documentation/usage/" + doc.Name()
checkAutogenDoc(t, docFilename, "scripts/gen-usage-docs.go", slurpFile(t, tempDir+"/"+doc.Name()))
}
}