Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ci: get integration tests working with actions #4

Merged
merged 6 commits into from
Dec 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,18 @@ jobs:
build:
strategy:
matrix:
test-type:
- general
# - with-display
go-version: [1.21.x]
platform: [ubuntu-latest]

runs-on: ${{ matrix.platform }}

env:
DISPLAY: ':99.0'
EGL_PLATFORM: 'x11'

steps:
- name: install Go
uses: actions/setup-go@v2
Expand All @@ -29,7 +36,24 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -qq libudev-dev
- name: non-Go display test dependencies
if: matrix.platform == 'ubuntu-latest' && matrix.test-type == 'with-display'
run: |
sudo apt-get install -qq libxss-dev gcc pkg-config libwayland-dev libx11-dev libx11-xcb-dev libxkbcommon-x11-dev libgles2-mesa-dev libegl1-mesa-dev libffi-dev libxcursor-dev libvulkan-dev
# Start a virtual frame buffer.
Xvfb :99 -screen 0 1920x1024x24 &

- name: unit tests
if: matrix.test-type == 'general'
run: |
go test ./rpc ./internal/... ./cmd/*/api ./cmd/worklog/store

- name: integration tests
if: matrix.test-type == 'general'
run: |
go test -p=1 -run TestDaemon ./cmd/{rest,runner,worklog} -verbose_log

- name: dislay integration tests
if: matrix.test-type == 'with-display'
run: |
go test -p=1 -run TestDaemon ./cmd/watcher -verbose_log
1 change: 1 addition & 0 deletions cmd/rest/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ func newDaemon(uid string, log *slog.Logger, level *slog.LevelVar, addSource *at
}

func (d *daemon) dial(ctx context.Context, network, addr string, dialer net.Dialer) error {
d.log.LogAttrs(ctx, slog.LevelDebug, "dial", slog.String("network", network), slog.String("addr", addr))
var err error
d.conn, err = jsonrpc2.Dial(ctx, jsonrpc2.NetDialer(network, addr, dialer), d)
if err != nil {
Expand Down
21 changes: 11 additions & 10 deletions cmd/rest/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,14 @@ import (
"math/big"
"net/http"
"os"
"os/exec"
"path/filepath"
"sync"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/kortschak/jsonrpc2"
"golang.org/x/sys/execabs"
"golang.org/x/tools/godoc/vfs"
"golang.org/x/tools/godoc/vfs/mapfs"
"golang.org/x/tools/txtar"
Expand All @@ -51,7 +51,7 @@ var (
keep = flag.Bool("keep", false, "keep workdir after tests")
)

func Test(t *testing.T) {
func TestDaemon(t *testing.T) {
err := os.Mkdir(workDir, 0o755)
if err != nil && !errors.Is(err, fs.ErrExist) {
t.Fatalf("failed to make dir: %v", err)
Expand All @@ -62,9 +62,10 @@ func Test(t *testing.T) {
})
}

goCmd, err := exec.LookPath("go")
exePath := filepath.Join(t.TempDir(), "rest")
out, err := execabs.Command("go", "build", "-o", exePath, "-race").CombinedOutput()
if err != nil {
t.Fatalf("failed to find go command: %v", err)
t.Fatalf("failed to build daemon: %v\n%s", err, out)
}

// Make certificates and CA.
Expand Down Expand Up @@ -108,12 +109,13 @@ func Test(t *testing.T) {
kernLogBuf locked.BytesBuffer
restLogBuf locked.BytesBuffer
)
level.Set(slog.LevelDebug)
log := slog.New(slogext.NewJSONHandler(&kernLogBuf, &slogext.HandlerOptions{
Level: slog.LevelDebug,
Level: &level,
AddSource: slogext.NewAtomicBool(*lines),
}))

ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
ctx, cancel := context.WithTimeoutCause(context.Background(), 20*time.Second, errors.New("test waited too long"))
defer cancel()

kernel, err := rpc.NewKernel(ctx, network, jsonrpc2.NetListenOptions{}, log)
Expand Down Expand Up @@ -145,18 +147,17 @@ func Test(t *testing.T) {
}
}()

level.Set(slog.LevelDebug)
uid := rpc.UID{Module: "rest"}
err = kernel.Spawn(ctx, os.Stdout, &restLogBuf, uid.Module,
"go", "run", "-race", ".", "-log", level.Level().String(), fmt.Sprintf("-lines=%t", *lines),
exePath, "-log", level.Level().String(), fmt.Sprintf("-lines=%t", *lines),
)
if err != nil {
t.Fatalf("failed to spawn rest: %v", err)
}

conn, _, ok := kernel.Conn(ctx, uid.Module)
if !ok {
t.Fatal("failed to get daemon conn")
t.Fatalf("failed to get daemon conn: %v: %v", ctx.Err(), context.Cause(ctx))
}

storeUID := rpc.UID{Module: "kernel", Service: "store"}
Expand Down Expand Up @@ -323,7 +324,7 @@ func Test(t *testing.T) {
Daemons: map[string]rpc.DaemonState{
"rest": {
UID: "rest",
Command: ptr(goCmd + " run -race . -log " + level.Level().String() + " -lines=false -uid rest -network " + network + " -addr " + addr),
Command: ptr(exePath + " -log " + level.Level().String() + " -lines=false -uid rest -network " + network + " -addr " + addr),
LastHeartbeat: &time.Time{},
Deadline: &time.Time{},
},
Expand Down
2 changes: 1 addition & 1 deletion cmd/runner/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
)

// Params defines the RPC messages passed to start an executable by the runner
// module. Fields correspond to fields in [os/exec.Cmd].
// module. Fields correspond to fields in [os/execabs.Cmd].
type Params struct {
Path string `json:"path"`
Args []string `json:"args,omitempty"`
Expand Down
11 changes: 6 additions & 5 deletions cmd/runner/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ import (
"log/slog"
"net"
"os"
"os/exec"
"strings"
"sync"
"sync/atomic"
"time"

"github.com/kortschak/jsonrpc2"
"golang.org/x/sys/execabs"

runner "github.com/kortschak/dex/cmd/runner/api"
"github.com/kortschak/dex/config"
Expand Down Expand Up @@ -112,11 +112,12 @@ func newDaemon(uid string, log *slog.Logger, level *slog.LevelVar, addSource *at
level: level,
addSource: addSource,
cancel: cancel,
waiting: make(map[*exec.Cmd]context.CancelFunc),
waiting: make(map[*execabs.Cmd]context.CancelFunc),
}
}

func (d *daemon) dial(ctx context.Context, network, addr string, dialer net.Dialer) error {
d.log.LogAttrs(ctx, slog.LevelDebug, "dial", slog.String("network", network), slog.String("addr", addr))
var err error
d.conn, err = jsonrpc2.Dial(ctx, jsonrpc2.NetDialer(network, addr, dialer), d)
if err != nil {
Expand All @@ -142,7 +143,7 @@ type daemon struct {
cancel context.CancelFunc

wMu sync.Mutex
waiting map[*exec.Cmd]context.CancelFunc
waiting map[*execabs.Cmd]context.CancelFunc

hMu sync.Mutex
heartbeat time.Duration
Expand Down Expand Up @@ -201,7 +202,7 @@ func (d *daemon) Handle(ctx context.Context, req *jsonrpc2.Request) (any, error)
cctx, cancel = context.WithCancel(cctx)
}

cmd := exec.CommandContext(cctx, p.Path, p.Args...)
cmd := execabs.CommandContext(cctx, p.Path, p.Args...)
cmd.Env = p.Env
cmd.Dir = p.Dir
cmd.WaitDelay = p.WaitDelay
Expand Down Expand Up @@ -232,7 +233,7 @@ func (d *daemon) Handle(ctx context.Context, req *jsonrpc2.Request) (any, error)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err = cmd.Run()
var exitErr *exec.ExitError
var exitErr *execabs.ExitError
if errors.As(err, &exitErr) {
d.log.LogAttrs(ctx, slog.LevelError, err.Error(), slog.Any("cmd", p))
return nil, err
Expand Down
36 changes: 25 additions & 11 deletions cmd/runner/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ import (
"io/fs"
"log/slog"
"os"
"path/filepath"
"strings"
"testing"
"time"

"github.com/kortschak/jsonrpc2"
"golang.org/x/sys/execabs"

runner "github.com/kortschak/dex/cmd/runner/api"
"github.com/kortschak/dex/internal/locked"
Expand All @@ -28,20 +31,27 @@ var (
lines = flag.Bool("show_lines", false, "log source code position")
)

func TestRunner(t *testing.T) {
func TestDaemon(t *testing.T) {
exePath := filepath.Join(t.TempDir(), "runner")
out, err := execabs.Command("go", "build", "-o", exePath, "-race").CombinedOutput()
if err != nil {
t.Fatalf("failed to build daemon: %v\n%s", err, out)
}

for _, network := range []string{"unix", "tcp"} {
t.Run(network, func(t *testing.T) {
var (
level slog.LevelVar
kernLogBuf locked.BytesBuffer
runnerLogBuf locked.BytesBuffer
)
level.Set(slog.LevelDebug)
log := slog.New(slogext.NewJSONHandler(&kernLogBuf, &slogext.HandlerOptions{
Level: slog.LevelDebug,
Level: &level,
AddSource: slogext.NewAtomicBool(*lines),
}))

ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
ctx, cancel := context.WithTimeoutCause(context.Background(), 20*time.Second, errors.New("test waited too long"))
defer cancel()

kernel, err := rpc.NewKernel(ctx, network, jsonrpc2.NetListenOptions{}, log)
Expand Down Expand Up @@ -75,15 +85,15 @@ func TestRunner(t *testing.T) {

uid := rpc.UID{Module: "runner"}
err = kernel.Spawn(ctx, os.Stdout, &runnerLogBuf, uid.Module,
"go", "run", "-race", ".", "-log", level.Level().String(), fmt.Sprintf("-lines=%t", *lines),
exePath, "-log", level.Level().String(), fmt.Sprintf("-lines=%t", *lines),
)
if err != nil {
t.Fatalf("failed to spawn runner: %v", err)
}

conn, _, ok := kernel.Conn(ctx, uid.Module)
if !ok {
t.Fatal("failed to get daemon conn")
t.Fatalf("failed to get daemon conn: %v: %v", ctx.Err(), context.Cause(ctx))
}

t.Run("configure", func(t *testing.T) {
Expand Down Expand Up @@ -134,13 +144,17 @@ func TestRunner(t *testing.T) {
t.Errorf("failed run call: %v", err)
}

// Get the command output directly to avoid
// system differences causing problems.
var stdout strings.Builder
cmd := execabs.Command("ls", ".")
cmd.Stdout = &stdout
err = cmd.Run()
if err != nil {
t.Errorf("failed run exec: %v", err)
}
want := runner.Return{
// Keep this in sync with the contents of the directory.
Stdout: `api
main.go
main_test.go
README.md
` + targetFile + "\n",
Stdout: stdout.String(),
Stderr: "",
Err: "",
}
Expand Down
1 change: 1 addition & 0 deletions cmd/watcher/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ func newDaemon(uid string, log *slog.Logger, level *slog.LevelVar, addSource *at
}

func (d *daemon) dial(ctx context.Context, network, addr string, dialer net.Dialer) error {
d.log.LogAttrs(ctx, slog.LevelDebug, "dial", slog.String("network", network), slog.String("addr", addr))
var err error
d.conn, err = jsonrpc2.Dial(ctx, jsonrpc2.NetDialer(network, addr, dialer), d)
if err != nil {
Expand Down
32 changes: 18 additions & 14 deletions cmd/watcher/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ package main
import (
"context"
"encoding/json"
"errors"
"flag"
"fmt"
"log/slog"
"os"
"path/filepath"
"sync"
"testing"
"time"
Expand All @@ -29,17 +31,18 @@ var (
lines = flag.Bool("show_lines", false, "log source code position")
)

func TestActive(t *testing.T) {
err := execabs.Command("go", "build", "-o", "./tester/tester", "./tester").Run()
func TestDaemon(t *testing.T) {
tmp := t.TempDir()
exePath := filepath.Join(tmp, "worklog")
out, err := execabs.Command("go", "build", "-o", exePath, "-race").CombinedOutput()
if err != nil {
t.Fatalf("failed to build tester: %v", err)
t.Fatalf("failed to build daemon: %v\n%s", err, out)
}
testerPath := filepath.Join(tmp, "tester")
out, err = execabs.Command("go", "build", "-o", testerPath, "./tester").CombinedOutput()
if err != nil {
t.Fatalf("failed to build tester: %v\n%s", err, out)
}
t.Cleanup(func() {
err := os.Remove("./tester/tester")
if err != nil {
t.Errorf("failed to clean up tester: %v", err)
}
})

for _, network := range []string{"unix", "tcp"} {
t.Run(network, func(t *testing.T) {
Expand All @@ -48,12 +51,13 @@ func TestActive(t *testing.T) {
kernLogBuf locked.BytesBuffer
watcherLogBuf locked.BytesBuffer
)
level.Set(slog.LevelDebug)
log := slog.New(slogext.NewJSONHandler(&kernLogBuf, &slogext.HandlerOptions{
Level: slog.LevelDebug,
Level: &level,
AddSource: slogext.NewAtomicBool(*lines),
}))

ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
ctx, cancel := context.WithTimeoutCause(context.Background(), 20*time.Second, errors.New("test waited too long"))
defer cancel()

kernel, err := rpc.NewKernel(ctx, network, jsonrpc2.NetListenOptions{}, log)
Expand Down Expand Up @@ -87,15 +91,15 @@ func TestActive(t *testing.T) {

uid := rpc.UID{Module: "watcher"}
err = kernel.Spawn(ctx, os.Stdout, &watcherLogBuf, uid.Module,
"go", "run", "-race", ".", "-log", level.Level().String(), fmt.Sprintf("-lines=%t", *lines),
exePath, "-log", level.Level().String(), fmt.Sprintf("-lines=%t", *lines),
)
if err != nil {
t.Fatalf("failed to spawn watcher: %v", err)
}

conn, _, ok := kernel.Conn(ctx, uid.Module)
if !ok {
t.Fatal("failed to get daemon conn")
t.Fatalf("failed to get daemon conn: %v: %v", ctx.Err(), context.Cause(ctx))
}

var (
Expand Down Expand Up @@ -131,7 +135,7 @@ func TestActive(t *testing.T) {
go func() {
for i := 0; i < changes; i++ {
time.Sleep(time.Second)
cmd := execabs.Command("./tester/tester", "-title", fmt.Sprintf("tester:%d", i))
cmd := execabs.Command(testerPath, "-title", fmt.Sprintf("tester:%d", i))
err := cmd.Start()
if err != nil {
t.Errorf("failed to start terminal %d", i)
Expand Down
1 change: 1 addition & 0 deletions cmd/worklog/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ func newDaemon(uid string, log *slog.Logger, level *slog.LevelVar, addSource *at
}

func (d *daemon) dial(ctx context.Context, network, addr string, dialer net.Dialer) error {
d.log.LogAttrs(ctx, slog.LevelDebug, "dial", slog.String("network", network), slog.String("addr", addr))
var err error
d.conn, err = jsonrpc2.Dial(ctx, jsonrpc2.NetDialer(network, addr, dialer), d)
if err != nil {
Expand Down
Loading
Loading