Add URL-level concurrency (-c flag, Step 7)
Probe multiple URLs in parallel with a bounded worker pool; the N samples of each URL remain sequential to preserve accurate min/avg/max statistics. Default auto-concurrency is min(numURLs, 8); -c 1 restores serial mode. Output is always buffered and printed in original input order. Verified clean with go test -race. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -250,6 +250,90 @@ func TestJSONDNSFailure(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── concurrency tests ─────────────────────────────────────────────────────────
|
||||
|
||||
// TestRunConcurrentOrder verifies that with -c > 1 the URL blocks are printed
|
||||
// in the original input order and exit codes are accumulated correctly.
|
||||
func TestRunConcurrentOrder(t *testing.T) {
|
||||
a := statusSrv(200)
|
||||
defer a.Close()
|
||||
b := statusSrv(200)
|
||||
defer b.Close()
|
||||
c := statusSrv(200)
|
||||
defer c.Close()
|
||||
|
||||
// Run with explicit high concurrency — all three URLs measured in parallel.
|
||||
stdout, _, code := invoke("-c", "3", a.URL, b.URL, c.URL)
|
||||
if code != exitOK {
|
||||
t.Fatalf("exit code = %d, want 0\nstdout:\n%s", code, stdout)
|
||||
}
|
||||
|
||||
// Each URL block must appear and in the correct order.
|
||||
posA := strings.Index(stdout, a.URL)
|
||||
posB := strings.Index(stdout, b.URL)
|
||||
posC := strings.Index(stdout, c.URL)
|
||||
if posA < 0 || posB < 0 || posC < 0 {
|
||||
t.Fatalf("one or more URLs missing from stdout:\n%s", stdout)
|
||||
}
|
||||
if !(posA < posB && posB < posC) {
|
||||
t.Errorf("URLs out of order: posA=%d posB=%d posC=%d\nstdout:\n%s",
|
||||
posA, posB, posC, stdout)
|
||||
}
|
||||
}
|
||||
|
||||
// TestConcurrentWorstCode confirms that worstCode aggregation is correct when
|
||||
// both successes and failures run concurrently.
|
||||
func TestConcurrentWorstCode(t *testing.T) {
|
||||
good := statusSrv(200)
|
||||
defer good.Close()
|
||||
|
||||
// DNS failure mixed with a successful URL — highest code (2) must win.
|
||||
stdout, _, code := invoke("-c", "2",
|
||||
good.URL,
|
||||
"https://totally.bogus.domain.for.concurrency.test.invalid",
|
||||
)
|
||||
if code != exitDNS {
|
||||
t.Errorf("exit code = %d, want %d (DNS)\nstdout:\n%s", code, exitDNS, stdout)
|
||||
}
|
||||
// Both URLs must appear in output (good one before the failing one).
|
||||
if !strings.Contains(stdout, good.URL) {
|
||||
t.Errorf("stdout missing good URL\nstdout:\n%s", stdout)
|
||||
}
|
||||
if !strings.Contains(stdout, "✗ dns:") {
|
||||
t.Errorf("stdout missing DNS failure marker\nstdout:\n%s", stdout)
|
||||
}
|
||||
}
|
||||
|
||||
// TestConcurrentJSONOrder verifies that JSON entries preserve input order under
|
||||
// parallel execution.
|
||||
func TestConcurrentJSONOrder(t *testing.T) {
|
||||
a := statusSrv(200)
|
||||
defer a.Close()
|
||||
b := statusSrv(200)
|
||||
defer b.Close()
|
||||
|
||||
stdout, _, code := invoke("--json", "-c", "2", a.URL, b.URL)
|
||||
if code != exitOK {
|
||||
t.Fatalf("exit code = %d, want 0\nstdout:\n%s", code, stdout)
|
||||
}
|
||||
|
||||
var results []struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(stdout), &results); err != nil {
|
||||
t.Fatalf("invalid JSON: %v\nstdout: %s", err, stdout)
|
||||
}
|
||||
if len(results) != 2 {
|
||||
t.Fatalf("expected 2 results, got %d", len(results))
|
||||
}
|
||||
if results[0].URL != a.URL {
|
||||
t.Errorf("results[0].url = %q, want %q", results[0].URL, a.URL)
|
||||
}
|
||||
if results[1].URL != b.URL {
|
||||
t.Errorf("results[1].url = %q, want %q", results[1].URL, b.URL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONSampling(t *testing.T) {
|
||||
srv := statusSrv(200)
|
||||
defer srv.Close()
|
||||
|
||||
Reference in New Issue
Block a user