From b91c44c4edfd73098775c360c3227448d7901add Mon Sep 17 00:00:00 2001 From: teernisse Date: Mon, 23 Feb 2026 00:12:02 -0500 Subject: [PATCH] =?UTF-8?q?Add=20end-to-end=20pipeline=20benchmark=20cover?= =?UTF-8?q?ing=20fetch=E2=86=92filter=E2=86=92display?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New internal/perf package with BenchmarkZipPipeline_1kDeals that exercises the full hot path: API fetch (stores + savings against an httptest server with pre-marshaled payloads), filter.Apply with all predicates active and a 50-item limit, and display.PrintDealsJSON to io.Discard. This provides a single top-level benchmark to catch regressions across package boundaries — e.g. if a filter optimization shifts allocation pressure into the display layer, this benchmark surfaces it where per-package benchmarks would not. Synthetic dataset: 1000 deals with deterministic category/department distribution to ensure the filter pipeline has meaningful work to do (~1/3 BOGO, mixed departments, keyword matches in titles). Co-Authored-By: Claude Opus 4.6 --- internal/perf/pipeline_bench_test.go | 133 +++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 internal/perf/pipeline_bench_test.go diff --git a/internal/perf/pipeline_bench_test.go b/internal/perf/pipeline_bench_test.go new file mode 100644 index 0000000..9539734 --- /dev/null +++ b/internal/perf/pipeline_bench_test.go @@ -0,0 +1,133 @@ +package perf_test + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/tayloree/publix-deals/internal/api" + "github.com/tayloree/publix-deals/internal/display" + "github.com/tayloree/publix-deals/internal/filter" +) + +func strPtr(v string) *string { return &v } + +func benchmarkDeals(count int) []api.SavingItem { + items := make([]api.SavingItem, 0, count) + for i := range count { + title := fmt.Sprintf("Fresh item %d", i) + desc := fmt.Sprintf("Fresh weekly deal %d with great savings", i) + savings := fmt.Sprintf("$%d.99", (i%9)+1) + dept := "Grocery" + if i%4 == 0 { + dept = "Produce" + } + if i%7 == 0 { + dept = "Meat" + } + cats := []string{"grocery"} + if i%3 == 0 { + cats = append(cats, "bogo") + } + if i%5 == 0 { + cats = append(cats, "produce") + } + if i%7 == 0 { + cats = append(cats, "meat") + } + items = append(items, api.SavingItem{ + ID: fmt.Sprintf("id-%d", i), + Title: strPtr(title), + Description: strPtr(desc), + Savings: strPtr(savings), + Department: strPtr(dept), + Categories: cats, + StartFormatted: "2/18", + EndFormatted: "2/24", + }) + } + return items +} + +func setupPipelineServer(b *testing.B, dealCount int) (*httptest.Server, *api.Client) { + b.Helper() + + storesPayload, err := json.Marshal(api.StoreResponse{ + Stores: []api.Store{ + {Key: "01425", Name: "Peachers Mill", Addr: "1490 Tiny Town Rd", City: "Clarksville", State: "TN", Zip: "37042", Distance: "5"}, + }, + }) + if err != nil { + b.Fatalf("marshal stores payload: %v", err) + } + + savingsPayload, err := json.Marshal(api.SavingsResponse{ + Savings: benchmarkDeals(dealCount), + LanguageID: 1, + }) + if err != nil { + b.Fatalf("marshal savings payload: %v", err) + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + switch r.URL.Path { + case "/stores": + _, _ = w.Write(storesPayload) + case "/savings": + _, _ = w.Write(savingsPayload) + default: + http.NotFound(w, r) + } + })) + b.Cleanup(server.Close) + + client := api.NewClientWithBaseURLs(server.URL+"/savings", server.URL+"/stores") + return server, client +} + +func runPipeline(b *testing.B, client *api.Client) { + b.Helper() + + ctx := context.Background() + stores, err := client.FetchStores(ctx, "33101", 1) + if err != nil { + b.Fatalf("fetch stores: %v", err) + } + if len(stores) == 0 { + b.Fatalf("fetch stores: empty result") + } + + resp, err := client.FetchSavings(ctx, api.StoreNumber(stores[0].Key)) + if err != nil { + b.Fatalf("fetch savings: %v", err) + } + + filtered := filter.Apply(resp.Savings, filter.Options{ + BOGO: true, + Category: "grocery", + Department: "grocery", + Query: "fresh", + Limit: 50, + }) + if len(filtered) == 0 { + b.Fatalf("filter returned no deals") + } + if err := display.PrintDealsJSON(io.Discard, filtered); err != nil { + b.Fatalf("print deals json: %v", err) + } +} + +func BenchmarkZipPipeline_1kDeals(b *testing.B) { + _, client := setupPipelineServer(b, 1000) + + b.ReportAllocs() + b.ResetTimer() + for range b.N { + runPipeline(b, client) + } +}