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:
@@ -61,6 +61,7 @@ func Measure(url string, opts Options) Result {
|
||||
wroteRequest time.Time
|
||||
firstByte time.Time
|
||||
dnsErr error
|
||||
tlsErr error
|
||||
)
|
||||
|
||||
trace := &httptrace.ClientTrace{
|
||||
@@ -74,9 +75,12 @@ func Measure(url string, opts Options) Result {
|
||||
connectStart = time.Now()
|
||||
}
|
||||
},
|
||||
ConnectDone: func(_, _ string, _ error) { connectDone = time.Now() },
|
||||
ConnectDone: func(_, _ string, _ error) { connectDone = time.Now() },
|
||||
TLSHandshakeStart: func() { tlsStart = time.Now() },
|
||||
TLSHandshakeDone: func(_ tls.ConnectionState, _ error) { tlsDone = time.Now() },
|
||||
TLSHandshakeDone: func(_ tls.ConnectionState, err error) {
|
||||
tlsDone = time.Now()
|
||||
tlsErr = err
|
||||
},
|
||||
WroteRequest: func(_ httptrace.WroteRequestInfo) { wroteRequest = time.Now() },
|
||||
GotFirstResponseByte: func() { firstByte = time.Now() },
|
||||
}
|
||||
@@ -100,7 +104,7 @@ func Measure(url string, opts Options) Result {
|
||||
if err != nil {
|
||||
end := time.Now()
|
||||
r.Err = err
|
||||
r.FailPhase = classifyErr(err, dnsErr, tlsStart, tlsDone)
|
||||
r.FailPhase = classifyErr(err, dnsErr, tlsErr, tlsStart)
|
||||
r.Total = Phase{Duration: end.Sub(start), Present: true}
|
||||
r.DNS = makePhase(dnsStart, dnsDone)
|
||||
r.Connect = makePhase(connectStart, connectDone)
|
||||
@@ -137,7 +141,10 @@ func makePhase(start, end time.Time) Phase {
|
||||
return Phase{Duration: end.Sub(start), Present: true}
|
||||
}
|
||||
|
||||
func classifyErr(err, dnsErr error, tlsStart, tlsDone time.Time) string {
|
||||
// classifyErr maps a Do() error to the phase that caused it.
|
||||
// Order: dns → timeout → tls → connect.
|
||||
// Timeout during TLS still classifies as timeout, not tls.
|
||||
func classifyErr(err, dnsErr, tlsErr error, tlsStart time.Time) string {
|
||||
if dnsErr != nil {
|
||||
return "dns"
|
||||
}
|
||||
@@ -152,8 +159,11 @@ func classifyErr(err, dnsErr error, tlsStart, tlsDone time.Time) string {
|
||||
if errors.As(err, &netErr) && netErr.Timeout() {
|
||||
return "timeout"
|
||||
}
|
||||
// TLS started but handshake never completed
|
||||
if !tlsStart.IsZero() && tlsDone.IsZero() {
|
||||
if tlsErr != nil {
|
||||
return "tls"
|
||||
}
|
||||
// TLS started but handshake was interrupted (e.g. context cancelled mid-handshake)
|
||||
if !tlsStart.IsZero() {
|
||||
return "tls"
|
||||
}
|
||||
return "connect"
|
||||
|
||||
Reference in New Issue
Block a user