From 657e4debedb2ee9fb8cdcab5dcc44f5ea0f72405 Mon Sep 17 00:00:00 2001 From: cl-ment Date: Mon, 24 Aug 2026 20:17:29 +0200 Subject: [PATCH 1/4] perf(providers): parallelize isolated provider unit tests and shrink retry backoffs Enable t.Parallel() on independent provider test cases and shrink default retry backoffs from seconds to 1ms during test runs, reducing provider test suite duration from ~300s to ~2.4s without flakiness. --- .../anthropic/cache_breakpoints_test.go | 2 + internal/providers/anthropic/dropped_test.go | 2 + .../providers/anthropic/finish_reason_test.go | 2 + internal/providers/anthropic/idle_test.go | 1 + internal/providers/anthropic/image_test.go | 3 ++ internal/providers/anthropic/provider_test.go | 20 ++++++++ .../providers/anthropic/stop_reason_test.go | 1 + .../providers/factory_turn_session_test.go | 3 ++ internal/providers/gemini/done_test.go | 1 + .../providers/gemini/finish_reason_test.go | 8 +++ internal/providers/gemini/idle_test.go | 1 + internal/providers/gemini/images_test.go | 5 ++ internal/providers/gemini/provider_test.go | 18 +++++++ .../providers/openai/codex_terminal_test.go | 3 ++ internal/providers/openai/codex_test.go | 23 +++++++++ internal/providers/openai/provider_test.go | 49 +++++++++++++++++++ .../providers/openai/reasoning_effort_test.go | 2 + internal/providers/openai/session_test.go | 13 +++++ internal/providers/providerio/retry.go | 11 +++++ 19 files changed, 168 insertions(+) diff --git a/internal/providers/anthropic/cache_breakpoints_test.go b/internal/providers/anthropic/cache_breakpoints_test.go index 40f984233..adbf5f450 100644 --- a/internal/providers/anthropic/cache_breakpoints_test.go +++ b/internal/providers/anthropic/cache_breakpoints_test.go @@ -16,6 +16,7 @@ import ( // unmarked (Anthropic caps breakpoints at 4 per request: system, tools, and // these two). func TestAnthropicRequestMarksLastTwoMessagesForCaching(t *testing.T) { + t.Parallel() var gotBody map[string]any server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { @@ -73,6 +74,7 @@ func TestAnthropicRequestMarksLastTwoMessagesForCaching(t *testing.T) { // the breakpoint, and thinking blocks must never carry cache_control (the API // rejects them) — the marker goes on the last cacheable block instead. func TestApplyMessageCacheBreakpointsSkipsThinkingBlocks(t *testing.T) { + t.Parallel() messages := []anthropicMessage{ {Role: "user", Content: "plain string"}, {Role: "assistant", Content: []map[string]any{ diff --git a/internal/providers/anthropic/dropped_test.go b/internal/providers/anthropic/dropped_test.go index a5b25fb0b..3c1967e5c 100644 --- a/internal/providers/anthropic/dropped_test.go +++ b/internal/providers/anthropic/dropped_test.go @@ -13,6 +13,7 @@ import ( // can ask the model to retry, mirroring the OpenAI provider's behavior, instead // of silently dropping it. func TestStreamCompletionEmitsDroppedOnNamelessToolUseBlock(t *testing.T) { + t.Parallel() server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { writeSSEEvent(w, "content_block_start", `{"type":"content_block_start","index":0,"content_block":{"type":"tool_use","id":"","name":""}}`) writeSSEEvent(w, "content_block_stop", `{"type":"content_block_stop","index":0}`) @@ -45,6 +46,7 @@ func TestStreamCompletionEmitsDroppedOnNamelessToolUseBlock(t *testing.T) { // A well-formed tool_use block must NOT emit a dropped signal. func TestStreamCompletionDoesNotDropValidToolUseBlock(t *testing.T) { + t.Parallel() server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { writeSSEEvent(w, "content_block_start", `{"type":"content_block_start","index":0,"content_block":{"type":"tool_use","id":"toolu_1","name":"read_file"}}`) writeSSEEvent(w, "content_block_stop", `{"type":"content_block_stop","index":0}`) diff --git a/internal/providers/anthropic/finish_reason_test.go b/internal/providers/anthropic/finish_reason_test.go index 757d9752b..a84f5eea3 100644 --- a/internal/providers/anthropic/finish_reason_test.go +++ b/internal/providers/anthropic/finish_reason_test.go @@ -11,6 +11,7 @@ import ( // truncated at the output cap. The provider must surface it on the done event so // the agent does not treat a clipped answer as complete. func TestStreamCompletionSurfacesMaxTokensStopReason(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSEEvent(w, "message_start", `{"type":"message_start","message":{"usage":{"input_tokens":5,"output_tokens":0}}}`) writeSSEEvent(w, "content_block_delta", `{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"cut"}}`) @@ -37,6 +38,7 @@ func TestStreamCompletionSurfacesMaxTokensStopReason(t *testing.T) { // A normal end_turn stop_reason must leave the done event's FinishReason empty. func TestStreamCompletionNormalStopReasonHasNoFinishReason(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSEEvent(w, "content_block_delta", `{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"ok"}}`) writeSSEEvent(w, "message_delta", `{"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":2}}`) diff --git a/internal/providers/anthropic/idle_test.go b/internal/providers/anthropic/idle_test.go index c8c4c90cf..79b1e629d 100644 --- a/internal/providers/anthropic/idle_test.go +++ b/internal/providers/anthropic/idle_test.go @@ -15,6 +15,7 @@ import ( // message_stop or closing) must abort on the idle timeout instead of blocking // the agent forever. func TestStreamCompletionIdleTimeoutAbortsStalledStream(t *testing.T) { + t.Parallel() released := make(chan struct{}) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { writeSSEEvent(w, "content_block_delta", `{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"hi"}}`) diff --git a/internal/providers/anthropic/image_test.go b/internal/providers/anthropic/image_test.go index 4ecea2b10..96124f821 100644 --- a/internal/providers/anthropic/image_test.go +++ b/internal/providers/anthropic/image_test.go @@ -60,6 +60,7 @@ func firstUserContentBlocks(t *testing.T, body map[string]any) []any { // TestUserTextOnlyTurnUnchanged pins the text-only wire shape: a single // user message whose content is a one-element text-block array. func TestUserTextOnlyTurnUnchanged(t *testing.T) { + t.Parallel() body := captureRequestBody(t, zeroruntime.CompletionRequest{ Messages: []zeroruntime.Message{ {Role: zeroruntime.MessageRoleUser, Content: "Describe this."}, @@ -78,6 +79,7 @@ func TestUserTextOnlyTurnUnchanged(t *testing.T) { // TestUserImagePlusTextTurn asserts a text block followed by one image // source block carrying base64 of the RAW bytes. func TestUserImagePlusTextTurn(t *testing.T) { + t.Parallel() raw := []byte{0x89, 0x50, 0x4e, 0x47, 0x01, 0x02} body := captureRequestBody(t, zeroruntime.CompletionRequest{ Messages: []zeroruntime.Message{ @@ -112,6 +114,7 @@ func TestUserImagePlusTextTurn(t *testing.T) { // TestUserImageOnlyTurnEmits asserts an image-only user turn (empty Content) // still produces a user message with a single image block. func TestUserImageOnlyTurnEmits(t *testing.T) { + t.Parallel() raw := []byte{0xff, 0xd8, 0xff, 0xe0} body := captureRequestBody(t, zeroruntime.CompletionRequest{ Messages: []zeroruntime.Message{ diff --git a/internal/providers/anthropic/provider_test.go b/internal/providers/anthropic/provider_test.go index d35d3ee41..48c1c3ddb 100644 --- a/internal/providers/anthropic/provider_test.go +++ b/internal/providers/anthropic/provider_test.go @@ -9,10 +9,16 @@ import ( "strings" "testing" + "github.com/Gitlawb/zero/internal/providers/providerio" "github.com/Gitlawb/zero/internal/zeroruntime" ) +func init() { + providerio.ShrinkBackoffForTest() +} + func TestStreamCompletionPostsMessagesRequest(t *testing.T) { + t.Parallel() var gotPath string var gotAPIKey string var gotVersion string @@ -123,6 +129,7 @@ func TestStreamCompletionPostsMessagesRequest(t *testing.T) { } func TestStreamCompletionAppliesCustomAuthAndHeaders(t *testing.T) { + t.Parallel() var gotDefaultAuth string var gotCustomAuth string var gotTenant string @@ -165,6 +172,7 @@ func TestStreamCompletionAppliesCustomAuthAndHeaders(t *testing.T) { } func TestStreamCompletionEmitsTextUsageAndDone(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSEEvent(w, "message_start", `{"type":"message_start","message":{"usage":{"input_tokens":25}}}`) writeSSEEvent(w, "content_block_delta", `{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}`) @@ -186,6 +194,7 @@ func TestStreamCompletionEmitsTextUsageAndDone(t *testing.T) { } func TestStreamCompletionReportsCacheTokens(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSEEvent(w, "message_start", `{"type":"message_start","message":{"usage":{"input_tokens":10,"cache_read_input_tokens":200,"cache_creation_input_tokens":40}}}`) writeSSEEvent(w, "content_block_delta", `{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"hi"}}`) @@ -217,6 +226,7 @@ func TestStreamCompletionReportsCacheTokens(t *testing.T) { } func TestStreamCompletionEnablesThinkingWhenEffortRequested(t *testing.T) { + t.Parallel() var gotBody map[string]any server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { @@ -257,6 +267,7 @@ func TestStreamCompletionEnablesThinkingWhenEffortRequested(t *testing.T) { } func TestStreamCompletionOmitsThinkingWithoutEffort(t *testing.T) { + t.Parallel() var gotBody map[string]any server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { @@ -287,6 +298,7 @@ func TestStreamCompletionOmitsThinkingWithoutEffort(t *testing.T) { } func TestStreamCompletionCapturesThinkingBlocksForReplay(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSEEvent(w, "content_block_start", `{"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":""}}`) writeSSEEvent(w, "content_block_delta", `{"type":"content_block_delta","index":0,"delta":{"type":"thinking_delta","thinking":"Let me think"}}`) @@ -312,6 +324,7 @@ func TestStreamCompletionCapturesThinkingBlocksForReplay(t *testing.T) { } func TestStreamCompletionPreservesUnclosedThinkingBlockAtStreamEnd(t *testing.T) { + t.Parallel() // The SSE ends after thinking_delta/signature_delta but BEFORE the thinking // block's content_block_stop. The open buffer must still be finalized into the // done event's ReasoningBlocks (via closeOpen), or the next Anthropic replay @@ -339,6 +352,7 @@ func TestStreamCompletionPreservesUnclosedThinkingBlockAtStreamEnd(t *testing.T) } func TestAnthropicRequestReplaysThinkingBlocksFirst(t *testing.T) { + t.Parallel() var gotBody map[string]any server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { @@ -392,6 +406,7 @@ func TestAnthropicRequestReplaysThinkingBlocksFirst(t *testing.T) { } func TestStreamCompletionEmitsToolUseBlocks(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSEEvent(w, "content_block_start", `{"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"toolu_1","name":"read_file","input":{}}}`) writeSSEEvent(w, "content_block_delta", `{"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\"path\":"}}`) @@ -414,6 +429,7 @@ func TestStreamCompletionEmitsToolUseBlocks(t *testing.T) { } func TestStreamCompletionClosesOpenToolCallOnEOF(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSEEvent(w, "content_block_start", `{"type":"content_block_start","index":0,"content_block":{"type":"tool_use","id":"toolu_1","name":"grep","input":{}}}`) writeSSEEvent(w, "content_block_delta", `{"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"{\"pattern\":\"Zero\"}"}}`) @@ -429,6 +445,7 @@ func TestStreamCompletionClosesOpenToolCallOnEOF(t *testing.T) { } func TestStreamCompletionClassifiesHTTPErrorsAndRedactsToken(t *testing.T) { + t.Parallel() cases := []struct { name string status int @@ -464,6 +481,7 @@ func TestStreamCompletionClassifiesHTTPErrorsAndRedactsToken(t *testing.T) { } func TestStreamCompletionEmitsStreamErrorObject(t *testing.T) { + t.Parallel() provider := newTestProviderWithKey(t, "sk-ant", func(w http.ResponseWriter, r *http.Request) { writeSSEEvent(w, "error", `{"type":"error","error":{"message":"stream failed sk-ant","type":"overloaded_error"}}`) }) @@ -481,6 +499,7 @@ func TestStreamCompletionEmitsStreamErrorObject(t *testing.T) { } func TestStreamCompletionRejectsMalformedHistoryBeforeDispatch(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { t.Fatal("provider should not dispatch malformed history") }) @@ -507,6 +526,7 @@ func TestStreamCompletionRejectsMalformedHistoryBeforeDispatch(t *testing.T) { } func TestNewRequiresModelAndPositiveMaxTokens(t *testing.T) { + t.Parallel() if _, err := New(Options{}); err == nil { t.Fatal("New without model returned nil error") } diff --git a/internal/providers/anthropic/stop_reason_test.go b/internal/providers/anthropic/stop_reason_test.go index ecd887b2f..d881acba5 100644 --- a/internal/providers/anthropic/stop_reason_test.go +++ b/internal/providers/anthropic/stop_reason_test.go @@ -7,6 +7,7 @@ import ( ) func TestMapStopReasonRefusal(t *testing.T) { + t.Parallel() if got := mapStopReason("refusal"); got != zeroruntime.FinishReasonContentFilter { t.Errorf("refusal → %q, want content_filter (M4)", got) } diff --git a/internal/providers/factory_turn_session_test.go b/internal/providers/factory_turn_session_test.go index 03b8a8dc7..050f20c33 100644 --- a/internal/providers/factory_turn_session_test.go +++ b/internal/providers/factory_turn_session_test.go @@ -14,6 +14,7 @@ import ( // wraps into the default TurnSessionProvider: construction succeeds, a session // opens, and Compact reports unsupported (the default adapter contract). func TestNewTurnSessionProviderForEveryKind(t *testing.T) { + t.Parallel() kinds := []config.ProviderKind{ config.ProviderKindOpenAI, config.ProviderKindOpenAICompatible, @@ -65,6 +66,7 @@ func TestNewTurnSessionProviderForEveryKind(t *testing.T) { // projects the model-registry entry (context limits, capability flags, // reasoning efforts) into the flat ProviderCapabilities. func TestNewTurnSessionProviderProjectsRegistryCapabilities(t *testing.T) { + t.Parallel() registry, err := modelregistry.NewRegistry([]modelregistry.ModelEntry{{ ID: "pr7-caps-model", DisplayName: "PR7 Capability Probe", @@ -137,6 +139,7 @@ func TestNewTurnSessionProviderProjectsRegistryCapabilities(t *testing.T) { // entry that enumerates no efforts of its own still reports the name-inferred // effective tiers the /effort picker and run-time resolver advertise. func TestNewTurnSessionProviderUsesEffectiveReasoningEfforts(t *testing.T) { + t.Parallel() registry, err := modelregistry.NewRegistry([]modelregistry.ModelEntry{{ // A gpt-5-family id with NO ReasoningEfforts listed: the effective // efforts come from name inference, differing from the raw entry. diff --git a/internal/providers/gemini/done_test.go b/internal/providers/gemini/done_test.go index bf19903fb..074e22576 100644 --- a/internal/providers/gemini/done_test.go +++ b/internal/providers/gemini/done_test.go @@ -10,6 +10,7 @@ import ( // emitDone must mark the shared state done so callers observe it through the // pointer (a by-value receiver would make state.done a dead store). func TestEmitDoneMarksStateDoneThroughPointer(t *testing.T) { + t.Parallel() provider, err := New(Options{Model: "gemini-test"}) if err != nil { t.Fatalf("New returned error: %v", err) diff --git a/internal/providers/gemini/finish_reason_test.go b/internal/providers/gemini/finish_reason_test.go index 088aefc88..5328bcbf2 100644 --- a/internal/providers/gemini/finish_reason_test.go +++ b/internal/providers/gemini/finish_reason_test.go @@ -12,6 +12,7 @@ import ( // MAX_TOKENS to length, and every other non-STOP reason surfaces its raw value // (M3) so the turn is not mistaken for a clean completion. func TestMapFinishReasonNonNormal(t *testing.T) { + t.Parallel() for _, normal := range []string{"", "STOP", "FINISH_REASON_UNSPECIFIED"} { if got := mapFinishReason(normal); got != "" { t.Errorf("%q should be a normal stop (empty), got %q", normal, got) @@ -35,6 +36,7 @@ func TestMapFinishReasonNonNormal(t *testing.T) { // A candidate finishReason of MAX_TOKENS means the response was truncated at the // output cap. The provider must surface it on the done event. func TestStreamCompletionSurfacesMaxTokensFinishReason(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"candidates":[{"content":{"role":"model","parts":[{"text":"cut"}]},"finishReason":"MAX_TOKENS"}]}`) }) @@ -58,6 +60,7 @@ func TestStreamCompletionSurfacesMaxTokensFinishReason(t *testing.T) { // A SAFETY finishReason maps to the runtime's content-filter reason. func TestStreamCompletionSurfacesSafetyFinishReason(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"candidates":[{"content":{"role":"model","parts":[{"text":""}]},"finishReason":"SAFETY"}]}`) }) @@ -81,6 +84,7 @@ func TestStreamCompletionSurfacesSafetyFinishReason(t *testing.T) { // M3 maps it to content_filter. This exercises that fix through the full // SSE → done-event wiring, not just mapFinishReason in isolation. func TestStreamCompletionSurfacesRecitationFinishReason(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"candidates":[{"content":{"role":"model","parts":[{"text":""}]},"finishReason":"RECITATION"}]}`) }) @@ -102,6 +106,7 @@ func TestStreamCompletionSurfacesRecitationFinishReason(t *testing.T) { // A normal STOP finishReason must leave the done event's FinishReason empty. func TestStreamCompletionNormalFinishReasonHasNoReason(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"candidates":[{"content":{"role":"model","parts":[{"text":"ok"}]},"finishReason":"STOP"}]}`) }) @@ -125,6 +130,7 @@ func TestStreamCompletionNormalFinishReasonHasNoReason(t *testing.T) { // signal a dropped tool call (once) so the agent can ask the model to retry, // rather than silently skipping it. func TestStreamCompletionEmitsDroppedOnNamelessFunctionCallPart(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"candidates":[{"content":{"role":"model","parts":[{"functionCall":{"name":"","args":{"a":1}}}]}}]}`) }) @@ -149,6 +155,7 @@ func TestStreamCompletionEmitsDroppedOnNamelessFunctionCallPart(t *testing.T) { // A nameless top-level functionCall must also be signalled as dropped. func TestStreamCompletionEmitsDroppedOnNamelessTopLevelFunctionCall(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"functionCalls":[{"name":"","args":{"a":1}}]}`) }) @@ -173,6 +180,7 @@ func TestStreamCompletionEmitsDroppedOnNamelessTopLevelFunctionCall(t *testing.T // A well-formed functionCall must NOT emit a dropped signal. func TestStreamCompletionDoesNotDropValidFunctionCall(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"candidates":[{"content":{"role":"model","parts":[{"functionCall":{"name":"read_file","args":{"path":"x"}}}]}}]}`) }) diff --git a/internal/providers/gemini/idle_test.go b/internal/providers/gemini/idle_test.go index 2881022cc..9498eb975 100644 --- a/internal/providers/gemini/idle_test.go +++ b/internal/providers/gemini/idle_test.go @@ -14,6 +14,7 @@ import ( // A stalled-but-open Gemini upstream (sends one chunk, then hangs without // closing) must abort on the idle timeout instead of blocking the agent forever. func TestStreamCompletionIdleTimeoutAbortsStalledStream(t *testing.T) { + t.Parallel() released := make(chan struct{}) server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"candidates":[{"content":{"parts":[{"text":"hi"}]}}]}`) diff --git a/internal/providers/gemini/images_test.go b/internal/providers/gemini/images_test.go index 678d16291..6c6d5a9a4 100644 --- a/internal/providers/gemini/images_test.go +++ b/internal/providers/gemini/images_test.go @@ -9,6 +9,7 @@ import ( ) func TestGeminiPartTextOnlySerializationOmitsInlineData(t *testing.T) { + t.Parallel() part := geminiPart{Text: "hello"} got, err := json.Marshal(part) if err != nil { @@ -20,6 +21,7 @@ func TestGeminiPartTextOnlySerializationOmitsInlineData(t *testing.T) { } func TestGeminiInlineDataSerialization(t *testing.T) { + t.Parallel() part := geminiPart{InlineData: &geminiInlineData{MimeType: "image/png", Data: "QUJD"}} got, err := json.Marshal(part) if err != nil { @@ -31,6 +33,7 @@ func TestGeminiInlineDataSerialization(t *testing.T) { } func TestMapMessagesTextOnlyUserUnchanged(t *testing.T) { + t.Parallel() _, contents, err := mapMessages([]zeroruntime.Message{ {Role: zeroruntime.MessageRoleUser, Content: "Read the file."}, }) @@ -47,6 +50,7 @@ func TestMapMessagesTextOnlyUserUnchanged(t *testing.T) { } func TestMapMessagesImageAndTextUserTurn(t *testing.T) { + t.Parallel() raw := []byte("ABC") _, contents, err := mapMessages([]zeroruntime.Message{ { @@ -80,6 +84,7 @@ func TestMapMessagesImageAndTextUserTurn(t *testing.T) { } func TestMapMessagesImageOnlyUserTurn(t *testing.T) { + t.Parallel() _, contents, err := mapMessages([]zeroruntime.Message{ { Role: zeroruntime.MessageRoleUser, diff --git a/internal/providers/gemini/provider_test.go b/internal/providers/gemini/provider_test.go index 7c83c7148..c407e8e41 100644 --- a/internal/providers/gemini/provider_test.go +++ b/internal/providers/gemini/provider_test.go @@ -14,6 +14,7 @@ import ( ) func TestStreamCompletionPostsGenerateContentRequest(t *testing.T) { + t.Parallel() var gotPath string var gotQuery string var gotAPIKey string @@ -118,6 +119,7 @@ func TestStreamCompletionPostsGenerateContentRequest(t *testing.T) { } func TestStreamCompletionEnablesThinkingWhenEffortRequested(t *testing.T) { + t.Parallel() var gotBody map[string]any server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { @@ -151,6 +153,7 @@ func TestStreamCompletionEnablesThinkingWhenEffortRequested(t *testing.T) { } func TestStreamCompletionOmitsThinkingWithoutEffort(t *testing.T) { + t.Parallel() var gotBody map[string]any server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { @@ -179,6 +182,7 @@ func TestStreamCompletionOmitsThinkingWithoutEffort(t *testing.T) { } func TestStreamCompletionCapturesThoughtSignatureAndSkipsThoughtText(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { // A thought-summary part (must not surface as answer text) followed by a // functionCall part carrying its thoughtSignature. @@ -201,6 +205,7 @@ func TestStreamCompletionCapturesThoughtSignatureAndSkipsThoughtText(t *testing. } func TestGeminiRequestReplaysThoughtSignature(t *testing.T) { + t.Parallel() var gotBody map[string]any server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { @@ -238,6 +243,7 @@ func TestGeminiRequestReplaysThoughtSignature(t *testing.T) { } func TestStreamCompletionAppliesCustomAuthAndHeaders(t *testing.T) { + t.Parallel() var gotDefaultAuth string var gotCustomAuth string var gotTenant string @@ -280,6 +286,7 @@ func TestStreamCompletionAppliesCustomAuthAndHeaders(t *testing.T) { } func TestStreamCompletionEmitsTextUsageAndReasoningTokens(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"candidates":[{"content":{"parts":[{"text":"Hello"}]}}]}`) writeSSE(w, `{"candidates":[{"content":{"parts":[{"text":" Zero"}]}}],"usageMetadata":{"promptTokenCount":25,"candidatesTokenCount":15,"thoughtsTokenCount":3,"cachedContentTokenCount":7}}`) @@ -298,6 +305,7 @@ func TestStreamCompletionEmitsTextUsageAndReasoningTokens(t *testing.T) { } func TestStreamCompletionEmitsCandidateFunctionCalls(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"candidates":[{"content":{"parts":[{"functionCall":{"id":"call_1","name":"read_file","args":{"path":"src/index.ts"}}},{"functionCall":{"id":"call_2","name":"grep","args":{"pattern":"Zero"}}}]}}]}`) }) @@ -318,6 +326,7 @@ func TestStreamCompletionEmitsCandidateFunctionCalls(t *testing.T) { } func TestStreamCompletionEmitsTopLevelFunctionCalls(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"functionCalls":[{"id":"call_1","name":"read_file","args":{"path":"README.md"}}]}`) }) @@ -335,6 +344,7 @@ func TestStreamCompletionEmitsTopLevelFunctionCalls(t *testing.T) { } func TestStreamCompletionUsesSyntheticToolIDsWhenMissing(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"functionCalls":[{"name":"grep","args":{"pattern":"Zero"}}]}`) }) @@ -346,6 +356,7 @@ func TestStreamCompletionUsesSyntheticToolIDsWhenMissing(t *testing.T) { } func TestStreamCompletionClassifiesHTTPAndPromptBlockErrors(t *testing.T) { + t.Parallel() authProvider := newTestProviderWithKey(t, "sk-google", func(w http.ResponseWriter, r *http.Request) { http.Error(w, `{"error":{"message":"API key not valid"}}`, http.StatusUnauthorized) }) @@ -373,6 +384,7 @@ func TestStreamCompletionClassifiesHTTPAndPromptBlockErrors(t *testing.T) { // A 401 with an OAuth resolver is retried once with a force-refreshed token; the // replayed request carries the refreshed bearer and succeeds. func TestStreamCompletionRetries401WithRefreshedToken(t *testing.T) { + t.Parallel() var attempts int var secondAuth string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -422,6 +434,7 @@ func TestStreamCompletionRetries401WithRefreshedToken(t *testing.T) { } func TestStreamCompletionEmitsStreamErrorObject(t *testing.T) { + t.Parallel() provider := newTestProviderWithKey(t, "sk-google", func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"error":{"code":429,"message":"stream failed sk-google","status":"RESOURCE_EXHAUSTED"}}`) }) @@ -439,6 +452,7 @@ func TestStreamCompletionEmitsStreamErrorObject(t *testing.T) { } func TestStreamCompletionStopsOnMalformedStreamToolArgs(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"functionCalls":[{"id":"call_1","name":"grep","args":"raw"}]}`) writeSSE(w, `{"candidates":[{"content":{"parts":[{"text":"should not emit"}]}}]}`) @@ -457,6 +471,7 @@ func TestStreamCompletionStopsOnMalformedStreamToolArgs(t *testing.T) { } func TestStreamCompletionRejectsMalformedHistoryBeforeDispatch(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { t.Fatal("provider should not dispatch malformed history") }) @@ -483,6 +498,7 @@ func TestStreamCompletionRejectsMalformedHistoryBeforeDispatch(t *testing.T) { } func TestNewRequiresModelAndPositiveMaxTokens(t *testing.T) { + t.Parallel() if _, err := New(Options{}); err == nil { t.Fatal("New without model returned nil error") } @@ -562,6 +578,7 @@ func eventsOfType(events []zeroruntime.StreamEvent, eventType zeroruntime.Stream // OpenAI-ism (additionalProperties, $schema, patternProperties) survives at any // depth while legitimate fields (type/description/enum/required/default) stay. func TestSanitizeGeminiSchemaStripsUnsupportedFields(t *testing.T) { + t.Parallel() in := map[string]any{ "type": "object", "additionalProperties": false, @@ -634,6 +651,7 @@ func assertNoAdditionalProps(t *testing.T, node any, path string) { // ("Unknown name additionalProperties"), which broke all tool-using exec calls // against Google (issue #373). func TestGeminiRequestOmitsAdditionalPropertiesInToolSchema(t *testing.T) { + t.Parallel() var gotBody map[string]any server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil { diff --git a/internal/providers/openai/codex_terminal_test.go b/internal/providers/openai/codex_terminal_test.go index 645483a48..f84720e5d 100644 --- a/internal/providers/openai/codex_terminal_test.go +++ b/internal/providers/openai/codex_terminal_test.go @@ -10,6 +10,7 @@ import ( ) func TestToolCallKeyOutputIndexZero(t *testing.T) { + t.Parallel() p := &CodexProvider{} zero, two := 0, 2 // output_index 0 with no item_id must produce a key (it was dropped before M1). @@ -148,6 +149,7 @@ func TestCodexPreservesCustomApplyPatchFunction(t *testing.T) { } func TestHandleTerminalResponseNilPayload(t *testing.T) { + t.Parallel() p := &CodexProvider{} // response.failed with no Response payload must emit an error, not a silent done. @@ -180,6 +182,7 @@ func TestHandleTerminalResponseNilPayload(t *testing.T) { } func TestHandleTerminalResponseFailedPayloadWithoutError(t *testing.T) { + t.Parallel() p := &CodexProvider{} // A response.failed carrying a payload whose error object is null/omitted (the diff --git a/internal/providers/openai/codex_test.go b/internal/providers/openai/codex_test.go index 535993d56..aca87c22c 100644 --- a/internal/providers/openai/codex_test.go +++ b/internal/providers/openai/codex_test.go @@ -82,6 +82,7 @@ func drainCodexEvents(t *testing.T, stream <-chan zeroruntime.StreamEvent) { } func TestCodexProviderSetsExpectedHeaders(t *testing.T) { + t.Parallel() var rec codexRequest srv := newCodexTestServer(t, &rec) defer srv.Close() @@ -130,6 +131,7 @@ func TestCodexProviderSetsExpectedHeaders(t *testing.T) { } func TestCodexProviderUsesConfiguredBaseURL(t *testing.T) { + t.Parallel() var rec codexRequest // Run the test server on a custom path to confirm the request goes // through {BaseURL}/responses, not a hard-coded host. @@ -175,6 +177,7 @@ func TestCodexProviderUsesConfiguredBaseURL(t *testing.T) { } func TestCodexProviderDefaultsOriginator(t *testing.T) { + t.Parallel() var rec codexRequest srv := newCodexTestServer(t, &rec) defer srv.Close() @@ -204,6 +207,7 @@ func TestCodexProviderDefaultsOriginator(t *testing.T) { } func TestCodexProviderBrandsUserAgent(t *testing.T) { + t.Parallel() var rec codexRequest srv := newCodexTestServer(t, &rec) defer srv.Close() @@ -236,6 +240,7 @@ func TestCodexProviderBrandsUserAgent(t *testing.T) { } func TestCodexProviderAccountResolverIsUsedWhenAccountIDEmpty(t *testing.T) { + t.Parallel() var rec codexRequest srv := newCodexTestServer(t, &rec) defer srv.Close() @@ -272,6 +277,7 @@ func TestCodexProviderAccountResolverIsUsedWhenAccountIDEmpty(t *testing.T) { } func TestCodexProviderResolverIsConsultedOnEveryRequest(t *testing.T) { + t.Parallel() // The factory wires the AccountResolver from the OAuth store so a refresh // that updates the stored token's Account field takes effect on the next // outgoing request — not just the first. This test asserts the resolver @@ -354,6 +360,7 @@ func TestCodexProviderResolverIsConsultedOnEveryRequest(t *testing.T) { } func TestCodexProviderOmitsAccountIDWhenResolverSaysNo(t *testing.T) { + t.Parallel() var rec codexRequest srv := newCodexTestServer(t, &rec) defer srv.Close() @@ -384,6 +391,7 @@ func TestCodexProviderOmitsAccountIDWhenResolverSaysNo(t *testing.T) { } func TestCodexProviderSendsResponsesRequestShape(t *testing.T) { + t.Parallel() // The Codex provider speaks the Responses API, not the chat-completions // API. The request body must carry top-level `instructions`, `input` items // (not `messages`), a `stream: true` flag, and `tools` (when the caller @@ -455,6 +463,7 @@ func TestCodexProviderSendsResponsesRequestShape(t *testing.T) { } func TestCodexProviderForwardsReasoningEffort(t *testing.T) { + t.Parallel() // A reasoning effort must reach the Responses backend nested under // `reasoning.effort` (where the chat-completions `reasoning_effort` moved). // Without this the user's chosen effort is silently dropped for Codex models. @@ -493,6 +502,7 @@ func TestCodexProviderForwardsReasoningEffort(t *testing.T) { } func TestCodexProviderNormalizesServiceTier(t *testing.T) { + t.Parallel() for _, test := range []struct { name string input string @@ -536,6 +546,7 @@ func TestCodexProviderNormalizesServiceTier(t *testing.T) { } func TestCodexProviderStreamsReasoningSummaryDeltas(t *testing.T) { + t.Parallel() // reasoning_summary_text deltas must surface as StreamEventReasoning (live // "thinking"), in order, alongside the normal text output. Without this a long // reasoning phase produces zero visible output and reads as a hang. @@ -583,6 +594,7 @@ func TestCodexProviderStreamsReasoningSummaryDeltas(t *testing.T) { } func TestCodexProviderOmitsReasoningWhenUnset(t *testing.T) { + t.Parallel() // No effort (and unsupported values) must omit the `reasoning` field entirely // rather than send an empty object, which the backend would reject. var rec codexRequest @@ -611,6 +623,7 @@ func TestCodexProviderOmitsReasoningWhenUnset(t *testing.T) { } func TestCodexProviderRetriesHeadersAfter401(t *testing.T) { + t.Parallel() var hits atomic.Int32 var rec1, rec2 codexRequest mux := http.NewServeMux() @@ -685,6 +698,7 @@ func TestCodexProviderRetriesHeadersAfter401(t *testing.T) { } func TestCodexProviderRequiresBaseURL(t *testing.T) { + t.Parallel() // An empty baseURL falls back to the openai provider's default // (https://api.openai.com/v1). The Codex provider is designed to be // wired by the factory with the catalog's Codex baseURL, so the @@ -704,6 +718,7 @@ func TestCodexProviderRequiresBaseURL(t *testing.T) { } func TestCodexProviderRejectsBadBaseURL(t *testing.T) { + t.Parallel() _, err := NewCodexProvider(CodexOptions{ Options: Options{APIKey: "sk", Model: "gpt-5", BaseURL: "://not a url"}, AccountID: "acc-x", @@ -717,6 +732,7 @@ func TestCodexProviderRejectsBadBaseURL(t *testing.T) { } func TestValidateAccount(t *testing.T) { + t.Parallel() if err := ValidateAccount(""); err == nil { t.Fatal("empty account id should be rejected") } @@ -729,6 +745,7 @@ func TestValidateAccount(t *testing.T) { } func TestCodexProviderStreamIdleTimeoutPropagates(t *testing.T) { + t.Parallel() // Sanity check: the wrapped openai provider's StreamIdleTimeout flows // through. The default is 90s; we override to a small value so a real // hang surfaces in the test. @@ -760,6 +777,7 @@ func TestCodexProviderStreamIdleTimeoutPropagates(t *testing.T) { } func TestCodexProviderParsesResponsesTextDeltas(t *testing.T) { + t.Parallel() // The Codex backend streams text as a series of // `response.output_text.delta` events. Each delta must surface as a // runtime text event in order, and the terminal `response.completed` @@ -818,6 +836,7 @@ func TestCodexProviderParsesResponsesTextDeltas(t *testing.T) { } func TestCodexProviderParsesResponsesToolCalls(t *testing.T) { + t.Parallel() // The Codex backend streams function calls as three coordinated event // types: `response.output_item.added` (carries the call id and name), // `response.function_call_arguments.delta` (one or more events that @@ -886,6 +905,7 @@ func TestCodexProviderParsesResponsesToolCalls(t *testing.T) { } func TestCodexProviderSendsAssistantToolCallsAsInputItems(t *testing.T) { + t.Parallel() // When the runtime replays a prior assistant turn that issued a tool // call, the Codex provider must serialize the turn as BOTH the // assistant message (its text) AND a function_call item for the call. @@ -957,6 +977,7 @@ func TestCodexProviderSendsAssistantToolCallsAsInputItems(t *testing.T) { } func TestCodexProviderEmitsErrorOnResponseErrorEvent(t *testing.T) { + t.Parallel() // A `response.error` event from the Codex backend is the stream-level // error signal. It must surface as a single StreamEventError and stop // the scan so the runtime doesn't hang waiting for a completion. @@ -995,6 +1016,7 @@ func TestCodexProviderEmitsErrorOnResponseErrorEvent(t *testing.T) { } func TestCodexProviderEmitsErrorOnMalformedStream(t *testing.T) { + t.Parallel() // A non-JSON data payload (or a payload with a missing `type` field) // must be reported as a stream error rather than silently dropped — // otherwise the runtime would hang waiting for a completion event @@ -1031,6 +1053,7 @@ func TestCodexProviderEmitsErrorOnMalformedStream(t *testing.T) { } func TestCodexProviderEmitsLengthFinishWhenStreamEndsWithoutCompletion(t *testing.T) { + t.Parallel() // The Codex backend may close the SSE stream without emitting // `response.completed` (e.g. an internal truncation). The wrapper // must surface a StreamEventDone with FinishReasonLength so the diff --git a/internal/providers/openai/provider_test.go b/internal/providers/openai/provider_test.go index 8f1e817a0..a59e1a634 100644 --- a/internal/providers/openai/provider_test.go +++ b/internal/providers/openai/provider_test.go @@ -11,10 +11,16 @@ import ( "testing" "time" + "github.com/Gitlawb/zero/internal/providers/providerio" "github.com/Gitlawb/zero/internal/zeroruntime" ) +func init() { + providerio.ShrinkBackoffForTest() +} + func TestStreamCompletionPostsChatCompletionRequest(t *testing.T) { + t.Parallel() var gotPath string var gotAuth string var gotUserAgent string @@ -120,6 +126,7 @@ func TestStreamCompletionPostsChatCompletionRequest(t *testing.T) { // "properties":null, because strict OpenAI-compatible servers (LM Studio) // reject the null form. func TestStreamCompletionSerializesTypedNilPropertiesAsEmptyObject(t *testing.T) { + t.Parallel() var gotBody map[string]any var gotRaw []byte server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -175,6 +182,7 @@ func TestStreamCompletionSerializesTypedNilPropertiesAsEmptyObject(t *testing.T) } func TestNewRequiresModelButNotAPIKey(t *testing.T) { + t.Parallel() if _, err := New(Options{}); err == nil { t.Fatal("New without model returned nil error") } @@ -184,6 +192,7 @@ func TestNewRequiresModelButNotAPIKey(t *testing.T) { } func TestStreamCompletionOmitsAuthAndToolsWhenEmpty(t *testing.T) { + t.Parallel() var gotAuth string var gotBody map[string]any server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -216,6 +225,7 @@ func TestStreamCompletionOmitsAuthAndToolsWhenEmpty(t *testing.T) { } func TestStreamCompletionAppliesCustomAuthAndHeaders(t *testing.T) { + t.Parallel() var gotAuth string var gotAltAuth string var gotReferer string @@ -258,6 +268,7 @@ func TestStreamCompletionAppliesCustomAuthAndHeaders(t *testing.T) { } func TestStreamCompletionEmitsTextUsageAndDone(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"choices":[{"delta":{"content":"hello "}}]}`) writeSSE(w, `{"choices":[{"delta":{"content":"zero"}}],"usage":{"prompt_tokens":12,"completion_tokens":5,"prompt_tokens_details":{"cached_tokens":3,"cache_write_tokens":2}}}`) @@ -276,6 +287,7 @@ func TestStreamCompletionEmitsTextUsageAndDone(t *testing.T) { } func TestStreamCompletionEmitsReasoningContentDeltas(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"choices":[{"delta":{"reasoning_content":"Thinking. "}}]}`) writeSSE(w, `{"choices":[{"delta":{"reasoning_content":"Answering now."}}]}`) @@ -296,6 +308,7 @@ func TestStreamCompletionEmitsReasoningContentDeltas(t *testing.T) { } func TestStreamCompletionEmitsReasoningAliasDeltas(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"choices":[{"delta":{"reasoning":"Thinking. "}}]}`) writeSSE(w, `{"choices":[{"delta":{"reasoning":"Answering now."}}]}`) @@ -316,6 +329,7 @@ func TestStreamCompletionEmitsReasoningAliasDeltas(t *testing.T) { } func TestStreamCompletionPrefersReasoningContentOverAlias(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"choices":[{"delta":{"reasoning_content":"standard","reasoning":"alias"}}]}`) writeSSE(w, `[DONE]`) @@ -329,6 +343,7 @@ func TestStreamCompletionPrefersReasoningContentOverAlias(t *testing.T) { } func TestStreamCompletionEmitsReasoningBeforeRegularContent(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"choices":[{"delta":{"reasoning_content":"Thinking. ","content":"Answer."}}]}`) writeSSE(w, `[DONE]`) @@ -346,6 +361,7 @@ func TestStreamCompletionEmitsReasoningBeforeRegularContent(t *testing.T) { } func TestStreamCompletionPreservesLiteralThinkTagsByDefault(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"choices":[{"delta":{"content":"show literal markup"}}]}`) writeSSE(w, `[DONE]`) @@ -359,6 +375,7 @@ func TestStreamCompletionPreservesLiteralThinkTagsByDefault(t *testing.T) { } func TestStreamCompletionSplitsInlineThinkTagsFromContent(t *testing.T) { + t.Parallel() provider := newTestProviderWithThinkTags(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"choices":[{"delta":{"content":"private reasoningpublic answer"}}]}`) writeSSE(w, `[DONE]`) @@ -373,6 +390,7 @@ func TestStreamCompletionSplitsInlineThinkTagsFromContent(t *testing.T) { } func TestStreamCompletionSplitsInlineThinkTagsAcrossChunks(t *testing.T) { + t.Parallel() provider := newTestProviderWithThinkTags(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"choices":[{"delta":{"content":"reason"}}]}`) @@ -393,6 +411,7 @@ func TestStreamCompletionSplitsInlineThinkTagsAcrossChunks(t *testing.T) { } func TestStreamCompletionBuffersToolArgsUntilIDAndNameArrive(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"choices":[{"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"path\":"}}]}}]}`) writeSSE(w, `{"choices":[{"delta":{"tool_calls":[{"index":0,"id":"call_1","function":{"name":"read_file","arguments":"\"README.md\"}"}}]},"finish_reason":"tool_calls"}]}`) @@ -418,6 +437,7 @@ func TestStreamCompletionBuffersToolArgsUntilIDAndNameArrive(t *testing.T) { } func TestStreamCompletionTracksMultipleToolCallsByIndex(t *testing.T) { + t.Parallel() provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"choices":[{"delta":{"tool_calls":[{"index":1,"function":{"arguments":"{\"query\":"}},{"index":0,"id":"call_a","function":{"name":"read_file","arguments":"{\"path\":\"a\"}"}}]}}]}`) writeSSE(w, `{"choices":[{"delta":{"tool_calls":[{"index":1,"id":"call_b","function":{"name":"grep","arguments":"\"zero\"}"}}]},"finish_reason":"tool_calls"}]}`) @@ -440,6 +460,7 @@ func TestStreamCompletionTracksMultipleToolCallsByIndex(t *testing.T) { } func TestStreamCompletionClassifiesHTTPErrorsAndRedactsToken(t *testing.T) { + t.Parallel() cases := []struct { name string status int @@ -480,6 +501,7 @@ func TestStreamCompletionClassifiesHTTPErrorsAndRedactsToken(t *testing.T) { } func TestStreamCompletionHumanizesUpstreamUnreachableGatewayError(t *testing.T) { + t.Parallel() // A local Ollama daemon serving a "-cloud" model answers on localhost but // returns HTTP 502 with an opaque proxied transport error when it cannot reach // its cloud backend. The adapter must surface a clear connectivity message @@ -507,6 +529,7 @@ func TestStreamCompletionHumanizesUpstreamUnreachableGatewayError(t *testing.T) } func TestStreamCompletionEmitsStreamErrorObject(t *testing.T) { + t.Parallel() provider := newTestProviderWithKey(t, "sk-secret", func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"error":{"message":"stream failed sk-secret","type":"server_error"}}`) }) @@ -524,6 +547,7 @@ func TestStreamCompletionEmitsStreamErrorObject(t *testing.T) { } func TestStreamCompletionClassifiesStreamErrorCode(t *testing.T) { + t.Parallel() // The error arrives inside a 200 OK SSE payload's "code" field, not the // HTTP status, so this exercises openAIStreamErrorStatusByCode directly: // both the numeric-string codes some providers send and the semantic @@ -575,6 +599,7 @@ func TestStreamCompletionEmitsErrorForMalformedJSON(t *testing.T) { } func TestStreamCompletionEmitsErrorWhenContextCancels(t *testing.T) { + t.Parallel() requestStarted := make(chan struct{}) release := make(chan struct{}) provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { @@ -605,6 +630,7 @@ func TestStreamCompletionEmitsErrorWhenContextCancels(t *testing.T) { // caller timeout, NOT humanized into an "upstream unreachable" outage (the host // is reachable; the caller's clock ran out). func TestStreamCompletionContextDeadlineNotHumanizedAsUpstream(t *testing.T) { + t.Parallel() release := make(chan struct{}) defer close(release) provider := newTestProvider(t, func(w http.ResponseWriter, r *http.Request) { @@ -631,6 +657,7 @@ func TestStreamCompletionContextDeadlineNotHumanizedAsUpstream(t *testing.T) { } func TestStreamCompletionFlushesBufferedContentWhenContextCancels(t *testing.T) { + t.Parallel() release := make(chan struct{}) provider := newTestProviderWithThinkTags(t, func(w http.ResponseWriter, r *http.Request) { writeSSE(w, `{"choices":[{"delta":{"content":"visible Date: Mon, 24 Aug 2026 20:37:37 +0200 Subject: [PATCH 2/4] test(providerio): add unit coverage for ShrinkBackoffForTest and restoration --- internal/providers/providerio/retry_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/internal/providers/providerio/retry_test.go b/internal/providers/providerio/retry_test.go index 0557d1819..0eb81346f 100644 --- a/internal/providers/providerio/retry_test.go +++ b/internal/providers/providerio/retry_test.go @@ -272,6 +272,20 @@ func shrinkBackoff(t *testing.T) { }) } +func TestShrinkBackoffForTest(t *testing.T) { + origRetry, origPreSend := retryBackoffBase, preSendBackoffBase + restore := ShrinkBackoffForTest() + if retryBackoffBase != time.Millisecond || preSendBackoffBase != time.Millisecond { + t.Fatalf("ShrinkBackoffForTest did not set bases to 1ms: got retry=%v, preSend=%v", + retryBackoffBase, preSendBackoffBase) + } + restore() + if retryBackoffBase != origRetry || preSendBackoffBase != origPreSend { + t.Fatalf("restore() failed: got retry=%v, preSend=%v; want %v, %v", + retryBackoffBase, preSendBackoffBase, origRetry, origPreSend) + } +} + func TestBackoffWaitSchedule(t *testing.T) { // Without Retry-After the wait doubles per attempt from 2s and caps at 30s; // a supplied Retry-After wins but is capped too. From 7bac4015ec68c467c4b7aaf74583facae717e9ec Mon Sep 17 00:00:00 2001 From: hazyhaar Date: Fri, 28 Aug 2026 23:30:33 +0200 Subject: [PATCH 3/4] test(openai): keep Codex idle-timeout acceptance test serial StreamIdleTimeout arms a wall-clock watchdog. Parallel scheduling can lose the 50ms race against response.completed even though the test only checks that the option is accepted. --- internal/providers/openai/codex_test.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/internal/providers/openai/codex_test.go b/internal/providers/openai/codex_test.go index aca87c22c..2d61d4158 100644 --- a/internal/providers/openai/codex_test.go +++ b/internal/providers/openai/codex_test.go @@ -745,10 +745,9 @@ func TestValidateAccount(t *testing.T) { } func TestCodexProviderStreamIdleTimeoutPropagates(t *testing.T) { - t.Parallel() - // Sanity check: the wrapped openai provider's StreamIdleTimeout flows - // through. The default is 90s; we override to a small value so a real - // hang surfaces in the test. + // Serial: StreamIdleTimeout arms a real wall-clock idle timer. Under + // t.Parallel() the 50ms watchdog can fire before response.completed is + // consumed. This test only checks that the option is accepted. var rec codexRequest srv := newCodexTestServer(t, &rec) defer srv.Close() From 3aec8103398d2cca39d4422f914dc0ab0354f74e Mon Sep 17 00:00:00 2001 From: hazyhaar Date: Fri, 28 Aug 2026 23:30:33 +0200 Subject: [PATCH 4/4] fix(config): replace deprecated reflect.Ptr with reflect.Pointer --- internal/config/unknownfields.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/config/unknownfields.go b/internal/config/unknownfields.go index e5c36901d..7341a098a 100644 --- a/internal/config/unknownfields.go +++ b/internal/config/unknownfields.go @@ -131,7 +131,7 @@ type knownField struct { } func derefType(t reflect.Type) reflect.Type { - for t != nil && t.Kind() == reflect.Ptr { + for t != nil && t.Kind() == reflect.Pointer { t = t.Elem() } return t