package web import ( "bytes" "fmt" "image" "image/color" "image/jpeg" "net/http" "net/http/httptest" "testing" "testing/fstest" "github.com/yuin/goldmark" "khosra/internal/content" "khosra/internal/ext/shortcodes" "khosra/internal/render" ) // photo is a real encoded JPEG, so a benchmark measures decoding rather than a stub. func photo(b *testing.B, width int) []byte { b.Helper() img := image.NewRGBA(image.Rect(0, 0, width, width*2/3)) for x := range width { for y := range width * 2 / 3 { img.Set(x, y, color.RGBA{uint8(x % 256), uint8(y % 256), 90, 255}) } } var out bytes.Buffer if err := jpeg.Encode(&out, img, nil); err != nil { b.Fatal(err) } return out.Bytes() } func benchHandler(b *testing.B, pictures int) http.Handler { b.Helper() fsys := fstest.MapFS{ "content/posts/plain.md": {Data: []byte("---\ntitle: Plain\ndate: 2026-01-01\n---\nJust prose, several words of it.\n")}, "content/art/set/index.md": {Data: []byte("---\ntitle: Set\ndate: 2026-01-02\n---\n::gallery\n")}, } shot := photo(b, 1600) for i := range pictures { fsys[fmt.Sprintf("content/art/set/%02d.jpg", i)] = &fstest.MapFile{Data: shot} } bundles, err := content.Scan(fsys) if err != nil { b.Fatal(err) } r, err := render.New(fsys, content.Settings{}, func(p render.Partial) []goldmark.Extender { return []goldmark.Extender{shortcodes.New(p)} }) if err != nil { b.Fatal(err) } return Handler(Fixed(content.NewSite(bundles)), r, fsys, nil, content.Settings{}) } func serveOnce(b *testing.B, h http.Handler, path string) { b.Helper() rec := httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, path, nil)) if rec.Code != http.StatusOK { b.Fatalf("GET %s = %d", path, rec.Code) } } func BenchmarkPlainPage(b *testing.B) { h := benchHandler(b, 0) for b.Loop() { serveOnce(b, h, "/posts/plain/") } } func BenchmarkGalleryPage(b *testing.B) { for _, n := range []int{1, 6, 12} { b.Run(fmt.Sprintf("pictures=%d", n), func(b *testing.B) { h := benchHandler(b, n) for b.Loop() { serveOnce(b, h, "/art/set/") } }) } } func BenchmarkListing(b *testing.B) { h := benchHandler(b, 0) for b.Loop() { serveOnce(b, h, "/posts/") } }