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>
This commit is contained in:
105
go/cli_test.go
Normal file
105
go/cli_test.go
Normal file
@@ -0,0 +1,105 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user