Add end-to-end pipeline benchmark covering fetch→filter→display

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 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 00:12:02 -05:00
parent 53d65c2148
commit b91c44c4ed

View File

@@ -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)
}
}