TestProbeChatNoReasoningEffortIsRaceFree pins the atomic.Bool guard on Client.noReasoningEffort. The startup probe and the first chat can run on the same *Client concurrently (probe from Init, chat when the user submits early): both read the flag via postChat, and a 400 fallback writes it. A plain b
(t *testing.T)
| 695 | } |
| 696 | if strings.Contains(bodies[0], `"reasoning_effort"`) { |
| 697 | t.Fatalf("second turn must not resend reasoning_effort: %s", bodies[0]) |
| 698 | } |
| 699 | } |
| 700 | |
| 701 | // TestProbeChatNoReasoningEffortIsRaceFree pins the atomic.Bool guard on |
| 702 | // Client.noReasoningEffort. The startup probe and the first chat can run on the |
| 703 | // same *Client concurrently (probe from Init, chat when the user submits early): |
| 704 | // both read the flag via postChat, and a 400 fallback writes it. A plain bool |
| 705 | // would be a data race; this must run clean under -race. |
| 706 | func TestProbeChatNoReasoningEffortIsRaceFree(t *testing.T) { |
| 707 | srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
| 708 | b, _ := io.ReadAll(r.Body) |
| 709 | // Force the 400 → write-the-flag branch on every chat still shipping |
| 710 | // reasoning_effort, so concurrent Chat goroutines hit the write path. |
| 711 | if strings.Contains(string(b), `"reasoning_effort"`) { |
| 712 | w.WriteHeader(400) |
| 713 | fmt.Fprint(w, `{"error":{"message":"reasoning_effort not supported"}}`) |
| 714 | return |
| 715 | } |
| 716 | w.Header().Set("Content-Type", "text/event-stream") |
| 717 | fmt.Fprint(w, "data: "+`{"choices":[{"delta":{"content":"ok"}}]}`+"\n\n") |
| 718 | fmt.Fprint(w, "data: [DONE]\n\n") |
| 719 | })) |
| 720 | defer srv.Close() |
| 721 | |
| 722 | c := New(srv.URL, "m", "") |
| 723 | |
| 724 | var wg sync.WaitGroup |
| 725 | for range 50 { |
| 726 | wg.Add(2) |
| 727 | go func() { |
| 728 | defer wg.Done() |
| 729 | _, _ = c.Probe(context.Background()) |
| 730 | }() |
| 731 | go func() { |
| 732 | defer wg.Done() |