Skip to content
Open
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
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ license-compatible, no GPL/AGPL; name the one chosen, or why none fit.
never poll the control plane or the LocalAPI in a tight loop.
- Work consciously skipped is said out loud, never left as a TODO comment or
as speculative code.
- Unit tests and e2e tests need to be included with all new functionality.
Test the expected ideal behavior, not the current implementation details.

Being lazy about the solution is the goal. Being lazy about understanding it is
not: trace the flow a change touches before picking an approach, because the
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
.PHONY: build test lint check clean install

BUILD_DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
GIT_HEIGHT := $(shell git rev-list --count HEAD 2>/dev/null || echo 0)
GIT_VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)

GIT_DESC := $(shell git describe --always)
ifneq ($(shell git status --porcelain),)
GIT_DESC := $(GIT_DESC)-dirty
endif

LDFLAGS := -X main.buildVersion=B$(GIT_HEIGHT) -X main.buildCommit=$(GIT_DESC) -X main.buildDate=$(BUILD_DATE)
LDFLAGS := -X main.buildVersion=$(GIT_VERSION) -X main.buildCommit=$(GIT_DESC) -X main.buildDate=$(BUILD_DATE)

build:
go build -ldflags "$(LDFLAGS)" -o .build/aperture ./cmd/aperture
Expand Down
26 changes: 10 additions & 16 deletions cmd/aperture/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ func init() {
}

if buildVersion == "B0-dev" {
if height := gitCommitHeight(); height != "" {
buildVersion = "B" + height
if desc := gitDescribe(); desc != "" {
buildVersion = desc
} else if info.Main.Version != "" && info.Main.Version != "(devel)" {
buildVersion = info.Main.Version
}
Expand Down Expand Up @@ -79,14 +79,17 @@ func init() {
}
}

func gitCommitHeight() string {
// gitDescribe reports the release version of the checkout containing this
// source file: the tag on an exact release commit, or the nearest tag with
// the distance and commit appended. It returns "" outside a checkout.
func gitDescribe() string {
_, file, _, ok := runtime.Caller(0)
if !ok {
return ""
}
for dir := filepath.Dir(file); ; dir = filepath.Dir(dir) {
if _, err := os.Stat(filepath.Join(dir, ".git")); err == nil {
return gitCommitHeightInDir(dir)
return gitDescribeInDir(dir)
}
parent := filepath.Dir(dir)
if parent == dir {
Expand All @@ -95,23 +98,14 @@ func gitCommitHeight() string {
}
}

func gitCommitHeightInDir(dir string) string {
cmd := exec.Command("git", "rev-list", "--count", "HEAD")
func gitDescribeInDir(dir string) string {
cmd := exec.Command("git", "describe", "--tags", "--always", "--dirty")
cmd.Dir = dir
out, err := cmd.Output()
if err != nil {
return ""
}
height := strings.TrimSpace(string(out))
if height == "" {
return ""
}
for _, r := range height {
if r < '0' || r > '9' {
return ""
}
}
return height
return strings.TrimSpace(string(out))
}

// startRunLog points slog at the run log and returns a function that closes
Expand Down
128 changes: 128 additions & 0 deletions e2e/e2e_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package e2e

import (
"io"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)

var apertureBin string

// TestMain builds the binary under test once. The tests exercise what we
// ship, so they run the real build rather than recompiling main's guts
// into the test binary.
func TestMain(m *testing.M) {
dir, err := os.MkdirTemp("", "aperture-e2e")
if err != nil {
panic(err)
}
apertureBin = filepath.Join(dir, "aperture")
build := exec.Command("go", "build", "-o", apertureBin, "../cmd/aperture")
if out, err := build.CombinedOutput(); err != nil {
panic(string(out))
}
code := m.Run()
_ = os.RemoveAll(dir)
os.Exit(code)
}

// modelsJSON is the smallest GET /v1/models payload that walks one provider
// through discovery: one provider, one model, one wire endpoint. With one
// of each, the launch flow asks no follow-up questions — selecting the
// client launches it.
const modelsJSON = `{
"object": "list",
"data": [
{
"id": "test-model",
"supported_endpoints": ["/v1/responses"],
"metadata": {"provider": {"id": "test-provider", "name": "Test Provider", "upstream": "test"}}
}
]
}`

// fakeAperture serves the discovery contract and nothing else.
func fakeAperture(t *testing.T) *httptest.Server {
t.Helper()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/models" {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = io.WriteString(w, modelsJSON)
}))
t.Cleanup(srv.Close)
return srv
}

func TestVersion(t *testing.T) {
stdout, _, err := run(t, apertureBin, hermeticEnv(t, ""), "-version")
if err != nil {
t.Fatalf("-version: %v", err)
}
if stdout == "" || stdout == "B0-dev" {
t.Errorf("version output = %q, want a release version", stdout)
}
}

// A bad endpoint URL has to fail before the TUI takes the terminal: the
// script that passed it reads stderr and the exit code, not a painted error.
func TestBadEndpointExitsBeforeTUI(t *testing.T) {
_, stderr, err := run(t, apertureBin, hermeticEnv(t, ""), "-endpoint", "://nope")
exitErr, ok := err.(*exec.ExitError)
if !ok || exitErr.ExitCode() != 1 {
t.Fatalf("exit = %v, want exit code 1", err)
}
if !strings.Contains(stderr, "aperture:") {
t.Errorf("stderr = %q, want the failure reported", stderr)
}
}

// The happy path: connect to a fake Aperture, pick the only installed
// client, and watch the launch reach the stub binary with the generated
// provider extension pointing back at the fake Aperture.
func TestLaunchPi(t *testing.T) {
srv := fakeAperture(t)
binDir := t.TempDir()
recordDir := installStubPi(t, binDir)
env := append(hermeticEnv(t, binDir), "APERTURE_E2E_RECORD="+recordDir)

term := spawn(t, apertureBin, []string{"-endpoint", srv.URL}, env)
// With the stub as the only installed client, the picker opens with Pi
// as row [1] and no quick-select.
term.waitFor(t, "Which editor do you want to use?")
term.waitFor(t, "[1] Pi")
term.send("1")

// One provider, one backend, one model: no follow-up menus, the
// selection launches straight into the stub.
argv := waitForFile(t, filepath.Join(recordDir, "argv"))
extension := waitForFile(t, filepath.Join(recordDir, "extension"))

// "q" quits only from the root menu: a clean exit here also proves the
// TUI took the terminal back after the child exited.
term.send("q")
term.waitExit(t)

if !strings.Contains(argv, "-e\n") {
t.Errorf("stub argv = %q, want the extension loaded with -e", argv)
}
if !strings.Contains(extension, srv.URL) {
t.Errorf("extension routes to %q, want the Aperture at %s", extension, srv.URL)
}
}

// An unreachable endpoint paints the failure banner rather than hanging or
// exiting; the launcher stays up so the user can pick another endpoint.
func TestUnreachableEndpoint(t *testing.T) {
term := spawn(t, apertureBin, []string{"-endpoint", "http://127.0.0.1:1"}, hermeticEnv(t, ""))
term.waitFor(t, "Could not reach")
term.send("\x03")
term.waitExit(t)
}
Loading
Loading