package renderer import ( "strconv" "strings" "testing" "gitea.dockr.co/kev/go-jdenticon/internal/engine" ) // ============================================================================ // RENDERER MICRO-BENCHMARKS FOR MEMORY ALLOCATION ANALYSIS // ============================================================================ var ( // Test data for renderer benchmarks benchTestPoints = []engine.Point{ {X: 0.0, Y: 0.0}, {X: 10.5, Y: 0.0}, {X: 10.5, Y: 10.5}, {X: 0.0, Y: 10.5}, } benchTestColors = []string{ "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", } benchTestSizes = []int{32, 64, 128, 256} ) // ============================================================================ // SVG STRING BUILDING MICRO-BENCHMARKS // ============================================================================ // BenchmarkSVGStringBuilding tests different string building patterns in SVG generation func BenchmarkSVGStringBuilding(b *testing.B) { points := benchTestPoints b.Run("svgValue_formatting", func(b *testing.B) { values := []float64{0.0, 10.5, 15.75, 100.0, 256.5} b.ReportAllocs() for i := 0; i < b.N; i++ { value := values[i%len(values)] _ = svgValue(value) } }) b.Run("strconv_FormatFloat", func(b *testing.B) { value := 10.5 b.ReportAllocs() for i := 0; i < b.N; i++ { _ = strconv.FormatFloat(value, 'f', 1, 64) } }) b.Run("strconv_Itoa", func(b *testing.B) { value := 10 b.ReportAllocs() for i := 0; i < b.N; i++ { _ = strconv.Itoa(value) } }) b.Run("polygon_path_building", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { var buf strings.Builder buf.Grow(50) // Estimate capacity // Simulate polygon path building buf.WriteString("M") buf.WriteString(svgValue(points[0].X)) buf.WriteString(" ") buf.WriteString(svgValue(points[0].Y)) for j := 1; j < len(points); j++ { buf.WriteString("L") buf.WriteString(svgValue(points[j].X)) buf.WriteString(" ") buf.WriteString(svgValue(points[j].Y)) } buf.WriteString("Z") _ = buf.String() } }) } // BenchmarkSVGPathOperations tests SVGPath struct operations func BenchmarkSVGPathOperations(b *testing.B) { points := benchTestPoints b.Run("SVGPath_AddPolygon", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { path := &SVGPath{} path.AddPolygon(points) } }) b.Run("SVGPath_AddCircle", func(b *testing.B) { topLeft := engine.Point{X: 5.0, Y: 5.0} size := 10.0 b.ReportAllocs() for i := 0; i < b.N; i++ { path := &SVGPath{} path.AddCircle(topLeft, size, false) } }) b.Run("SVGPath_DataString", func(b *testing.B) { path := &SVGPath{} path.AddPolygon(points) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { _ = path.DataString() } }) } // BenchmarkStringBuilderPooling tests the efficiency of string builder pooling func BenchmarkStringBuilderPooling(b *testing.B) { points := benchTestPoints b.Run("direct_builder", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { // Use direct string builder (pool eliminated for direct writing) var buf strings.Builder // Build polygon path buf.WriteString("M") buf.WriteString(svgValue(points[0].X)) buf.WriteString(" ") buf.WriteString(svgValue(points[0].Y)) for j := 1; j < len(points); j++ { buf.WriteString("L") buf.WriteString(svgValue(points[j].X)) buf.WriteString(" ") buf.WriteString(svgValue(points[j].Y)) } buf.WriteString("Z") _ = buf.String() } }) b.Run("without_pool", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { // Create new buffer each time var buf strings.Builder buf.Grow(50) // Build polygon path buf.WriteString("M") buf.WriteString(svgValue(points[0].X)) buf.WriteString(" ") buf.WriteString(svgValue(points[0].Y)) for j := 1; j < len(points); j++ { buf.WriteString("L") buf.WriteString(svgValue(points[j].X)) buf.WriteString(" ") buf.WriteString(svgValue(points[j].Y)) } buf.WriteString("Z") _ = buf.String() } }) b.Run("reused_builder", func(b *testing.B) { var buf strings.Builder buf.Grow(100) // Pre-allocate larger buffer b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { buf.Reset() // Build polygon path buf.WriteString("M") buf.WriteString(svgValue(points[0].X)) buf.WriteString(" ") buf.WriteString(svgValue(points[0].Y)) for j := 1; j < len(points); j++ { buf.WriteString("L") buf.WriteString(svgValue(points[j].X)) buf.WriteString(" ") buf.WriteString(svgValue(points[j].Y)) } buf.WriteString("Z") _ = buf.String() } }) } // ============================================================================ // SVG RENDERER MICRO-BENCHMARKS // ============================================================================ // BenchmarkSVGRendererOperations tests SVG renderer creation and operations func BenchmarkSVGRendererOperations(b *testing.B) { sizes := benchTestSizes colors := benchTestColors b.Run("NewSVGRenderer", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { size := sizes[i%len(sizes)] _ = NewSVGRenderer(size) } }) b.Run("BeginShape", func(b *testing.B) { renderer := NewSVGRenderer(64) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { color := colors[i%len(colors)] renderer.BeginShape(color) } }) b.Run("AddPolygon", func(b *testing.B) { renderer := NewSVGRenderer(64) renderer.BeginShape(colors[0]) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { renderer.AddPolygon(benchTestPoints) } }) b.Run("AddCircle", func(b *testing.B) { renderer := NewSVGRenderer(64) renderer.BeginShape(colors[0]) topLeft := engine.Point{X: 5.0, Y: 5.0} b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { renderer.AddCircle(topLeft, 10.0, false) } }) } // BenchmarkSVGGeneration tests full SVG generation with different scenarios func BenchmarkSVGGeneration(b *testing.B) { colors := benchTestColors[:3] // Use fewer colors for cleaner tests b.Run("empty_renderer", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { renderer := NewSVGRenderer(64) _ = renderer.ToSVG() } }) b.Run("single_shape", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { renderer := NewSVGRenderer(64) renderer.BeginShape(colors[0]) renderer.AddPolygon(benchTestPoints) renderer.EndShape() _ = renderer.ToSVG() } }) b.Run("multiple_shapes", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { renderer := NewSVGRenderer(64) for j, color := range colors { renderer.BeginShape(color) // Offset points for each shape offsetPoints := make([]engine.Point, len(benchTestPoints)) for k, point := range benchTestPoints { offsetPoints[k] = engine.Point{ X: point.X + float64(j*12), Y: point.Y + float64(j*12), } } renderer.AddPolygon(offsetPoints) renderer.EndShape() } _ = renderer.ToSVG() } }) b.Run("with_background", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { renderer := NewSVGRenderer(64) renderer.SetBackground("#ffffff", 1.0) renderer.BeginShape(colors[0]) renderer.AddPolygon(benchTestPoints) renderer.EndShape() _ = renderer.ToSVG() } }) } // BenchmarkSVGSizeEstimation tests SVG capacity estimation func BenchmarkSVGSizeEstimation(b *testing.B) { colors := benchTestColors b.Run("capacity_estimation", func(b *testing.B) { renderer := NewSVGRenderer(64) for _, color := range colors { renderer.BeginShape(color) renderer.AddPolygon(benchTestPoints) renderer.EndShape() } b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { // Simulate capacity estimation logic from ToSVG capacity := svgBaseOverheadBytes capacity += svgBackgroundRectBytes // Assume background // Estimate path data size for _, color := range renderer.colorOrder { path := renderer.pathsByColor[color] if path != nil { capacity += svgPathOverheadBytes + path.data.Len() } } _ = capacity } }) b.Run("strings_builder_with_estimation", func(b *testing.B) { renderer := NewSVGRenderer(64) for _, color := range colors { renderer.BeginShape(color) renderer.AddPolygon(benchTestPoints) renderer.EndShape() } b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { // Test strings.Builder with capacity estimation capacity := svgBaseOverheadBytes + svgBackgroundRectBytes for _, color := range renderer.colorOrder { path := renderer.pathsByColor[color] if path != nil { capacity += svgPathOverheadBytes + path.data.Len() } } var svg strings.Builder svg.Grow(capacity) svg.WriteString(``) svg.WriteString("") _ = svg.String() } }) } // ============================================================================ // MAP OPERATIONS MICRO-BENCHMARKS // ============================================================================ // BenchmarkMapOperations tests map operations used in renderer func BenchmarkMapOperations(b *testing.B) { colors := benchTestColors b.Run("map_creation", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { m := make(map[string]*SVGPath) _ = m } }) b.Run("map_insertion", func(b *testing.B) { m := make(map[string]*SVGPath) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { color := colors[i%len(colors)] m[color] = &SVGPath{} } }) b.Run("map_lookup", func(b *testing.B) { m := make(map[string]*SVGPath) for _, color := range colors { m[color] = &SVGPath{} } b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { color := colors[i%len(colors)] _ = m[color] } }) b.Run("map_existence_check", func(b *testing.B) { m := make(map[string]*SVGPath) for _, color := range colors { m[color] = &SVGPath{} } b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { color := colors[i%len(colors)] _, exists := m[color] _ = exists } }) } // ============================================================================ // SLICE OPERATIONS MICRO-BENCHMARKS // ============================================================================ // BenchmarkSliceOperations tests slice operations for color ordering func BenchmarkSliceOperations(b *testing.B) { colors := benchTestColors b.Run("slice_append", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { var colorOrder []string //lint:ignore S1011 Intentionally benchmarking individual appends vs batch //nolint:gosimple // Intentionally benchmarking individual appends vs batch for _, color := range colors { colorOrder = append(colorOrder, color) } _ = colorOrder } }) b.Run("slice_with_capacity", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { colorOrder := make([]string, 0, len(colors)) //lint:ignore S1011 Intentionally benchmarking individual appends with pre-allocation //nolint:gosimple // Intentionally benchmarking individual appends with pre-allocation for _, color := range colors { colorOrder = append(colorOrder, color) } _ = colorOrder } }) b.Run("slice_iteration", func(b *testing.B) { colorOrder := make([]string, len(colors)) copy(colorOrder, colors) b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { for _, color := range colorOrder { _ = color } } }) } // ============================================================================ // COORDINATE TRANSFORMATION MICRO-BENCHMARKS // ============================================================================ // BenchmarkCoordinateTransforms tests coordinate transformation patterns func BenchmarkCoordinateTransforms(b *testing.B) { points := benchTestPoints b.Run("point_creation", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { _ = engine.Point{X: 10.5, Y: 20.5} } }) b.Run("point_slice_creation", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { pointsCopy := make([]engine.Point, len(points)) copy(pointsCopy, points) _ = pointsCopy } }) b.Run("point_transformation", func(b *testing.B) { transform := func(x, y float64) (float64, float64) { return x * 2.0, y * 2.0 } b.ResetTimer() b.ReportAllocs() for i := 0; i < b.N; i++ { transformedPoints := make([]engine.Point, len(points)) for j, point := range points { newX, newY := transform(point.X, point.Y) transformedPoints[j] = engine.Point{X: newX, Y: newY} } _ = transformedPoints } }) } // ============================================================================ // MEMORY ALLOCATION PATTERN COMPARISONS // ============================================================================ // BenchmarkAllocationPatterns compares different allocation patterns used in rendering func BenchmarkAllocationPatterns(b *testing.B) { b.Run("string_concatenation", func(b *testing.B) { base := "test" suffix := "value" b.ReportAllocs() for i := 0; i < b.N; i++ { _ = base + suffix } }) b.Run("sprintf_formatting", func(b *testing.B) { value := 10.5 b.ReportAllocs() for i := 0; i < b.N; i++ { _ = strconv.FormatFloat(value, 'f', 1, 64) } }) b.Run("builder_small_capacity", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { var buf strings.Builder buf.Grow(10) buf.WriteString("test") buf.WriteString("value") _ = buf.String() } }) b.Run("builder_large_capacity", func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { var buf strings.Builder buf.Grow(100) buf.WriteString("test") buf.WriteString("value") _ = buf.String() } }) }