Files
http-latency-prober/go/cli_test.go
Jan Novak a9534ec2c1 test(go): step 6 — integration tests covering all exit codes and output
Extract run(args, stdout, stderr) int from main() for in-process
testability. Fix TLS failure classification (tlsErr now captured from
TLSHandshakeDone hook). Add run_test.go with 14 table-driven in-process
tests and cli_test.go with TestMain + 3 subprocess smoke tests. All
servers use httptest; .invalid TLD for deterministic DNS failures.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-01 01:00:20 +02:00

106 lines
3.2 KiB
Go

package main
import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
)
// binaryPath is set once by TestMain and used by all CLI smoke tests.
var binaryPath string
// TestMain builds the real binary once, runs all tests, then cleans up.
func TestMain(m *testing.M) {
tmp, err := os.MkdirTemp("", "latprobe-cli-*")
if err != nil {
panic("TestMain: MkdirTemp: " + err.Error())
}
binaryPath = filepath.Join(tmp, "latprobe")
out, err := exec.Command("go", "build", "-o", binaryPath, ".").CombinedOutput()
if err != nil {
panic("TestMain: go build failed:\n" + string(out))
}
code := m.Run()
os.RemoveAll(tmp)
os.Exit(code)
}
// cli runs the compiled binary and returns stdout, stderr, and the exit code.
func cli(args ...string) (stdout, stderr string, code int) {
cmd := exec.Command(binaryPath, args...)
var outBuf, errBuf strings.Builder
cmd.Stdout = &outBuf
cmd.Stderr = &errBuf
err := cmd.Run()
if err != nil {
if ex, ok := err.(*exec.ExitError); ok {
return outBuf.String(), errBuf.String(), ex.ExitCode()
}
}
return outBuf.String(), errBuf.String(), 0
}
// ── smoke tests ───────────────────────────────────────────────────────────────
// TestCLISuccess verifies the happy path against a real local server:
// all phases shown, status 200, exit 0.
func TestCLISuccess(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(200)
}))
defer srv.Close()
stdout, stderr, code := cli(srv.URL)
if code != 0 {
t.Fatalf("exit code = %d, want 0\nstdout: %s\nstderr: %s", code, stdout, stderr)
}
// httptest uses 127.0.0.1 — Go skips DNS for loopback, so no DNS row.
for _, want := range []string{"(200)", "TCP connect", "Total"} {
if !strings.Contains(stdout, want) {
t.Errorf("stdout missing %q\nstdout: %s", want, stdout)
}
}
}
// TestCLIDNSFailure verifies that an NXDOMAIN resolves to exit 2 in the
// real binary (exercises the actual os.Exit path).
func TestCLIDNSFailure(t *testing.T) {
_, _, code := cli("https://no.such.host.for.cli.smoke.invalid")
if code != exitDNS {
t.Errorf("exit code = %d, want %d (dns)", code, exitDNS)
}
}
// TestCLIJSONDNSFailure checks that --json + a DNS failure produces valid JSON
// with a correctly classified error entry — exercising the full output pipeline.
func TestCLIJSONDNSFailure(t *testing.T) {
stdout, _, code := cli("--json", "https://no.such.host.json.cli.invalid")
if code != exitDNS {
t.Fatalf("exit code = %d, want %d\nstdout: %s", code, exitDNS, stdout)
}
var results []struct {
Succeeded int `json:"succeeded"`
Failed int `json:"failed"`
Errors []struct {
Phase string `json:"phase"`
} `json:"errors"`
}
if err := json.Unmarshal([]byte(stdout), &results); err != nil {
t.Fatalf("invalid JSON: %v\nstdout: %s", err, stdout)
}
if len(results) == 0 || len(results[0].Errors) == 0 {
t.Fatalf("expected non-empty errors in JSON\nstdout: %s", stdout)
}
if results[0].Errors[0].Phase != "dns" {
t.Errorf("error phase = %q, want \"dns\"", results[0].Errors[0].Phase)
}
}