Consolidate test support/setup

Add a test support package which allows shared test functionality
for both the unit and integration tests.

Tests importing the proctl/test package will gain access to a special
test entrypoint which precompiles fixtures and makes them available
to tests.
This commit is contained in:
Dan Mace
2015-05-04 09:31:50 -04:00
committed by Derek Parker
parent 794d5b1e19
commit ecb8e8a42a
4 changed files with 123 additions and 110 deletions

View File

@ -3,29 +3,29 @@ package proctl
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"os"
"os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"testing" "testing"
protest "github.com/derekparker/delve/proctl/test"
) )
func withTestProcess(name string, t *testing.T, fn func(p *DebuggedProcess)) { func TestMain(m *testing.M) {
runtime.LockOSThread() protest.RunTestsWithFixtures(m)
base := filepath.Base(name)
if err := exec.Command("go", "build", "-gcflags=-N -l", "-o", base, name+".go").Run(); err != nil {
t.Fatalf("Could not compile %s due to %s", name, err)
} }
defer os.Remove("./" + base)
p, err := Launch([]string{"./" + base}) func withTestProcess(name string, t *testing.T, fn func(p *DebuggedProcess, fixture protest.Fixture)) {
runtime.LockOSThread()
fixture := protest.Fixtures[name]
p, err := Launch([]string{fixture.Path})
if err != nil { if err != nil {
t.Fatal("Launch():", err) t.Fatal("Launch():", err)
} }
defer p.Process.Kill() defer p.Process.Kill()
fn(p) fn(p, fixture)
} }
func getRegisters(p *DebuggedProcess, t *testing.T) Registers { func getRegisters(p *DebuggedProcess, t *testing.T) Registers {
@ -72,7 +72,7 @@ func currentLineNumber(p *DebuggedProcess, t *testing.T) (string, int) {
} }
func TestExit(t *testing.T) { func TestExit(t *testing.T) {
withTestProcess("../_fixtures/continuetestprog", t, func(p *DebuggedProcess) { withTestProcess("continuetestprog", t, func(p *DebuggedProcess, fixture protest.Fixture) {
err := p.Continue() err := p.Continue()
pe, ok := err.(ProcessExitedError) pe, ok := err.(ProcessExitedError)
if !ok { if !ok {
@ -88,7 +88,7 @@ func TestExit(t *testing.T) {
} }
func TestHalt(t *testing.T) { func TestHalt(t *testing.T) {
withTestProcess("../_fixtures/testprog", t, func(p *DebuggedProcess) { withTestProcess("testprog", t, func(p *DebuggedProcess, fixture protest.Fixture) {
go func() { go func() {
for { for {
if p.Running() { if p.Running() {
@ -117,7 +117,7 @@ func TestHalt(t *testing.T) {
} }
func TestStep(t *testing.T) { func TestStep(t *testing.T) {
withTestProcess("../_fixtures/testprog", t, func(p *DebuggedProcess) { withTestProcess("testprog", t, func(p *DebuggedProcess, fixture protest.Fixture) {
helloworldfunc := p.goSymTable.LookupFunc("main.helloworld") helloworldfunc := p.goSymTable.LookupFunc("main.helloworld")
helloworldaddr := helloworldfunc.Entry helloworldaddr := helloworldfunc.Entry
@ -139,7 +139,7 @@ func TestStep(t *testing.T) {
} }
func TestBreakPoint(t *testing.T) { func TestBreakPoint(t *testing.T) {
withTestProcess("../_fixtures/testprog", t, func(p *DebuggedProcess) { withTestProcess("testprog", t, func(p *DebuggedProcess, fixture protest.Fixture) {
helloworldfunc := p.goSymTable.LookupFunc("main.helloworld") helloworldfunc := p.goSymTable.LookupFunc("main.helloworld")
helloworldaddr := helloworldfunc.Entry helloworldaddr := helloworldfunc.Entry
@ -160,7 +160,7 @@ func TestBreakPoint(t *testing.T) {
} }
func TestBreakPointInSeperateGoRoutine(t *testing.T) { func TestBreakPointInSeperateGoRoutine(t *testing.T) {
withTestProcess("../_fixtures/testthreads", t, func(p *DebuggedProcess) { withTestProcess("testthreads", t, func(p *DebuggedProcess, fixture protest.Fixture) {
fn := p.goSymTable.LookupFunc("main.anotherthread") fn := p.goSymTable.LookupFunc("main.anotherthread")
if fn == nil { if fn == nil {
t.Fatal("No fn exists") t.Fatal("No fn exists")
@ -189,7 +189,7 @@ func TestBreakPointInSeperateGoRoutine(t *testing.T) {
} }
func TestBreakPointWithNonExistantFunction(t *testing.T) { func TestBreakPointWithNonExistantFunction(t *testing.T) {
withTestProcess("../_fixtures/testprog", t, func(p *DebuggedProcess) { withTestProcess("testprog", t, func(p *DebuggedProcess, fixture protest.Fixture) {
_, err := p.Break(0) _, err := p.Break(0)
if err == nil { if err == nil {
t.Fatal("Should not be able to break at non existant function") t.Fatal("Should not be able to break at non existant function")
@ -198,7 +198,7 @@ func TestBreakPointWithNonExistantFunction(t *testing.T) {
} }
func TestClearBreakPoint(t *testing.T) { func TestClearBreakPoint(t *testing.T) {
withTestProcess("../_fixtures/testprog", t, func(p *DebuggedProcess) { withTestProcess("testprog", t, func(p *DebuggedProcess, fixture protest.Fixture) {
fn := p.goSymTable.LookupFunc("main.sleepytime") fn := p.goSymTable.LookupFunc("main.sleepytime")
bp, err := p.Break(fn.Entry) bp, err := p.Break(fn.Entry)
assertNoError(err, t, "Break()") assertNoError(err, t, "Break()")
@ -227,8 +227,7 @@ type nextTest struct {
} }
func testnext(testcases []nextTest, initialLocation string, t *testing.T) { func testnext(testcases []nextTest, initialLocation string, t *testing.T) {
var executablePath = "../_fixtures/testnextprog" withTestProcess("testnextprog", t, func(p *DebuggedProcess, fixture protest.Fixture) {
withTestProcess(executablePath, t, func(p *DebuggedProcess) {
bp, err := p.BreakByLocation(initialLocation) bp, err := p.BreakByLocation(initialLocation)
assertNoError(err, t, "Break()") assertNoError(err, t, "Break()")
assertNoError(p.Continue(), t, "Continue()") assertNoError(p.Continue(), t, "Continue()")
@ -298,8 +297,7 @@ func TestNextFunctionReturn(t *testing.T) {
} }
func TestRuntimeBreakpoint(t *testing.T) { func TestRuntimeBreakpoint(t *testing.T) {
var testfile, _ = filepath.Abs("../_fixtures/testruntimebreakpoint") withTestProcess("testruntimebreakpoint", t, func(p *DebuggedProcess, fixture protest.Fixture) {
withTestProcess(testfile, t, func(p *DebuggedProcess) {
err := p.Continue() err := p.Continue()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -316,16 +314,13 @@ func TestRuntimeBreakpoint(t *testing.T) {
} }
func TestFindReturnAddress(t *testing.T) { func TestFindReturnAddress(t *testing.T) {
var testfile, _ = filepath.Abs("../_fixtures/testnextprog") withTestProcess("testnextprog", t, func(p *DebuggedProcess, fixture protest.Fixture) {
withTestProcess(testfile, t, func(p *DebuggedProcess) {
var ( var (
fdes = p.frameEntries fdes = p.frameEntries
gsd = p.goSymTable gsd = p.goSymTable
) )
testsourcefile := testfile + ".go" start, _, err := gsd.LineToPC(fixture.Source, 24)
start, _, err := gsd.LineToPC(testsourcefile, 24)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -369,9 +364,7 @@ func TestFindReturnAddress(t *testing.T) {
} }
func TestSwitchThread(t *testing.T) { func TestSwitchThread(t *testing.T) {
var testfile, _ = filepath.Abs("../_fixtures/testnextprog") withTestProcess("testnextprog", t, func(p *DebuggedProcess, fixture protest.Fixture) {
withTestProcess(testfile, t, func(p *DebuggedProcess) {
// With invalid thread id // With invalid thread id
err := p.SwitchThread(-1) err := p.SwitchThread(-1)
if err == nil { if err == nil {
@ -412,9 +405,7 @@ func TestSwitchThread(t *testing.T) {
} }
func TestFunctionCall(t *testing.T) { func TestFunctionCall(t *testing.T) {
var testfile, _ = filepath.Abs("../_fixtures/testprog") withTestProcess("testprog", t, func(p *DebuggedProcess, fixture protest.Fixture) {
withTestProcess(testfile, t, func(p *DebuggedProcess) {
pc, err := p.FindLocation("main.main") pc, err := p.FindLocation("main.main")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

87
proctl/test/support.go Normal file
View File

@ -0,0 +1,87 @@
package test
import (
"crypto/rand"
"encoding/hex"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
// Fixture is a test binary.
type Fixture struct {
// Name is the short name of the fixture.
Name string
// Path is the absolute path to the test binary.
Path string
// Source is the absolute path of the test binary source.
Source string
}
// Fixtures is a map of Fixture.Name to Fixture.
var Fixtures map[string]Fixture = make(map[string]Fixture)
// RunTestsWithFixtures will pre-compile test fixtures before running test
// methods. Test binaries are deleted before exiting.
func RunTestsWithFixtures(m *testing.M) {
// Find the fixtures directory; this is necessary because the test file's
// nesting level can vary. Only look up to a maxdepth of 10 (which seems
// like a sane alternative to recursion).
parent := ".."
fixturesDir := filepath.Join(parent, "_fixtures")
for depth := 0; depth < 10; depth++ {
if _, err := os.Stat(fixturesDir); err == nil {
break
}
fixturesDir = filepath.Join("..", fixturesDir)
}
if _, err := os.Stat(fixturesDir); err != nil {
fmt.Println("Couldn't locate fixtures directory")
os.Exit(1)
}
// Collect all files which look like fixture source files.
sources, err := ioutil.ReadDir(fixturesDir)
if err != nil {
fmt.Printf("Couldn't read fixtures dir: %v\n", err)
os.Exit(1)
}
// Compile the fixtures.
for _, src := range sources {
if src.IsDir() {
continue
}
// Make a (good enough) random temporary file name
r := make([]byte, 4)
rand.Read(r)
path := filepath.Join(fixturesDir, src.Name())
name := strings.TrimSuffix(src.Name(), filepath.Ext(src.Name()))
tmpfile := filepath.Join(os.TempDir(), fmt.Sprintf("%s.%s", name, hex.EncodeToString(r)))
// Build the test binary
if err := exec.Command("go", "build", "-gcflags=-N -l", "-o", tmpfile, path).Run(); err != nil {
fmt.Printf("Error compiling %s: %s\n", path, err)
os.Exit(1)
}
fmt.Printf("Compiled test binary %s: %s\n", name, tmpfile)
source, _ := filepath.Abs(path)
Fixtures[name] = Fixture{Name: name, Path: tmpfile, Source: source}
}
status := m.Run()
// Remove the fixtures.
// TODO(danmace): Not sure why yet, but doing these removes in a defer isn't
// working.
for _, f := range Fixtures {
os.Remove(f.Path)
}
os.Exit(status)
}

View File

@ -2,9 +2,10 @@ package proctl
import ( import (
"fmt" "fmt"
"path/filepath"
"sort" "sort"
"testing" "testing"
protest "github.com/derekparker/delve/proctl/test"
) )
type varTest struct { type varTest struct {
@ -29,13 +30,6 @@ func assertVariable(t *testing.T, variable *Variable, expected varTest) {
} }
func TestVariableEvaluation(t *testing.T) { func TestVariableEvaluation(t *testing.T) {
executablePath := "../_fixtures/testvariables"
fp, err := filepath.Abs(executablePath + ".go")
if err != nil {
t.Fatal(err)
}
testcases := []varTest{ testcases := []varTest{
{"a1", "foofoofoofoofoofoo", "struct string", nil}, {"a1", "foofoofoofoofoofoo", "struct string", nil},
{"a10", "ofo", "struct string", nil}, {"a10", "ofo", "struct string", nil},
@ -73,8 +67,8 @@ func TestVariableEvaluation(t *testing.T) {
{"NonExistent", "", "", fmt.Errorf("could not find symbol value for NonExistent")}, {"NonExistent", "", "", fmt.Errorf("could not find symbol value for NonExistent")},
} }
withTestProcess(executablePath, t, func(p *DebuggedProcess) { withTestProcess("testvariables", t, func(p *DebuggedProcess, fixture protest.Fixture) {
pc, _, _ := p.goSymTable.LineToPC(fp, 57) pc, _, _ := p.goSymTable.LineToPC(fixture.Source, 57)
_, err := p.Break(pc) _, err := p.Break(pc)
assertNoError(err, t, "Break() returned an error") assertNoError(err, t, "Break() returned an error")
@ -97,15 +91,8 @@ func TestVariableEvaluation(t *testing.T) {
} }
func TestVariableFunctionScoping(t *testing.T) { func TestVariableFunctionScoping(t *testing.T) {
executablePath := "../_fixtures/testvariables" withTestProcess("testvariables", t, func(p *DebuggedProcess, fixture protest.Fixture) {
pc, _, _ := p.goSymTable.LineToPC(fixture.Source, 57)
fp, err := filepath.Abs(executablePath + ".go")
if err != nil {
t.Fatal(err)
}
withTestProcess(executablePath, t, func(p *DebuggedProcess) {
pc, _, _ := p.goSymTable.LineToPC(fp, 57)
_, err := p.Break(pc) _, err := p.Break(pc)
assertNoError(err, t, "Break() returned an error") assertNoError(err, t, "Break() returned an error")
@ -121,7 +108,7 @@ func TestVariableFunctionScoping(t *testing.T) {
assertNoError(err, t, "Unable to find variable a1") assertNoError(err, t, "Unable to find variable a1")
// Move scopes, a1 exists here by a2 does not // Move scopes, a1 exists here by a2 does not
pc, _, _ = p.goSymTable.LineToPC(fp, 23) pc, _, _ = p.goSymTable.LineToPC(fixture.Source, 23)
_, err = p.Break(pc) _, err = p.Break(pc)
assertNoError(err, t, "Break() returned an error") assertNoError(err, t, "Break() returned an error")
@ -157,13 +144,6 @@ func (s varArray) Less(i, j int) bool {
} }
func TestLocalVariables(t *testing.T) { func TestLocalVariables(t *testing.T) {
executablePath := "../_fixtures/testvariables"
fp, err := filepath.Abs(executablePath + ".go")
if err != nil {
t.Fatal(err)
}
testcases := []struct { testcases := []struct {
fn func(*ThreadContext) ([]*Variable, error) fn func(*ThreadContext) ([]*Variable, error)
output []varTest output []varTest
@ -203,8 +183,8 @@ func TestLocalVariables(t *testing.T) {
{"baz", "bazburzum", "struct string", nil}}}, {"baz", "bazburzum", "struct string", nil}}},
} }
withTestProcess(executablePath, t, func(p *DebuggedProcess) { withTestProcess("testvariables", t, func(p *DebuggedProcess, fixture protest.Fixture) {
pc, _, _ := p.goSymTable.LineToPC(fp, 57) pc, _, _ := p.goSymTable.LineToPC(fixture.Source, 57)
_, err := p.Break(pc) _, err := p.Break(pc)
assertNoError(err, t, "Break() returned an error") assertNoError(err, t, "Break() returned an error")

View File

@ -1,58 +1,16 @@
package rest package rest
import ( import (
"crypto/rand"
"encoding/hex"
"fmt"
"io/ioutil"
"net" "net"
"os"
"os/exec"
"path/filepath"
"strings"
"testing" "testing"
protest "github.com/derekparker/delve/proctl/test"
"github.com/derekparker/delve/service" "github.com/derekparker/delve/service"
"github.com/derekparker/delve/service/api" "github.com/derekparker/delve/service/api"
) )
var fixtures map[string]string = make(map[string]string)
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
fixturesDir := "../../_fixtures" protest.RunTestsWithFixtures(m)
sources, err := ioutil.ReadDir(fixturesDir)
if err != nil {
fmt.Printf("Couldn't read fixtures dir: %v\n", err)
os.Exit(1)
}
for _, src := range sources {
if src.IsDir() {
continue
}
// Make a (good enough) random temporary file name
r := make([]byte, 4)
rand.Read(r)
path := filepath.Join(fixturesDir, src.Name())
name := strings.TrimSuffix(src.Name(), filepath.Ext(src.Name()))
tmpfile := filepath.Join(os.TempDir(), fmt.Sprintf("%s.%s", name, hex.EncodeToString(r)))
// Build the test binary
if err := exec.Command("go", "build", "-gcflags=-N -l", "-o", tmpfile, path).Run(); err != nil {
fmt.Printf("Error compiling %s: %s\n", path, err)
os.Exit(1)
}
fmt.Printf("Compiled test binary %s: %s\n", name, tmpfile)
fixtures[name] = tmpfile
}
status := m.Run()
for _, f := range fixtures {
os.Remove(f)
}
os.Exit(status)
} }
func withTestClient(name string, t *testing.T, fn func(c service.Client)) { func withTestClient(name string, t *testing.T, fn func(c service.Client)) {
@ -60,16 +18,13 @@ func withTestClient(name string, t *testing.T, fn func(c service.Client)) {
if err != nil { if err != nil {
t.Fatalf("couldn't start listener: %s\n", err) t.Fatalf("couldn't start listener: %s\n", err)
} }
server := NewServer(&Config{ server := NewServer(&Config{
Listener: listener, Listener: listener,
ProcessArgs: []string{fixtures[name]}, ProcessArgs: []string{protest.Fixtures[name].Path},
}) })
go server.Run() go server.Run()
client := NewClient(listener.Addr().String()) client := NewClient(listener.Addr().String())
defer client.Detach(true) defer client.Detach(true)
fn(client) fn(client)
} }