Files
go-jdenticon/jdenticon/generate_test.go
Kevin McIntyre f84b511895 init
2025-06-18 01:00:00 -04:00

719 lines
15 KiB
Go

package jdenticon
import (
"bytes"
"fmt"
"strings"
"testing"
)
func TestGenerate(t *testing.T) {
tests := []struct {
name string
value string
size int
}{
{
name: "email address",
value: "test@example.com",
size: 64,
},
{
name: "username",
value: "johndoe",
size: 32,
},
{
name: "large icon",
value: "large-icon-test",
size: 256,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
icon, err := Generate(tt.value, tt.size)
if err != nil {
t.Fatalf("Generate failed: %v", err)
}
if icon == nil {
t.Fatal("Generate returned nil icon")
}
// Test SVG generation
svg, err := icon.ToSVG()
if err != nil {
t.Fatalf("ToSVG failed: %v", err)
}
if svg == "" {
t.Error("ToSVG returned empty string")
}
// Basic SVG validation
if !strings.Contains(svg, "<svg") {
t.Error("SVG output does not contain svg tag")
}
if !strings.Contains(svg, "</svg>") {
t.Error("SVG output does not contain closing svg tag")
}
// Test PNG generation
png, err := icon.ToPNG()
if err != nil {
t.Fatalf("ToPNG failed: %v", err)
}
if len(png) == 0 {
t.Error("ToPNG returned empty data")
}
// Basic PNG validation (check PNG signature)
if len(png) < 8 || string(png[1:4]) != "PNG" {
t.Error("PNG output does not have valid PNG signature")
}
})
}
}
func TestGenerateConsistency(t *testing.T) {
value := "consistency-test"
size := 64
// Generate the same icon multiple times
icon1, err := Generate(value, size)
if err != nil {
t.Fatalf("First generate failed: %v", err)
}
icon2, err := Generate(value, size)
if err != nil {
t.Fatalf("Second generate failed: %v", err)
}
// SVG should be identical
svg1, err := icon1.ToSVG()
if err != nil {
t.Fatalf("First ToSVG failed: %v", err)
}
svg2, err := icon2.ToSVG()
if err != nil {
t.Fatalf("Second ToSVG failed: %v", err)
}
if svg1 != svg2 {
t.Error("SVG outputs are not consistent for same input")
}
// PNG should be identical
png1, err := icon1.ToPNG()
if err != nil {
t.Fatalf("First ToPNG failed: %v", err)
}
png2, err := icon2.ToPNG()
if err != nil {
t.Fatalf("Second ToPNG failed: %v", err)
}
if !bytes.Equal(png1, png2) {
t.Error("PNG outputs are not consistent for same input")
}
}
func TestGenerateInvalidInputs(t *testing.T) {
tests := []struct {
name string
value string
size int
}{
{
name: "zero size",
value: "test",
size: 0,
},
{
name: "negative size",
value: "test",
size: -10,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := Generate(tt.value, tt.size)
if err == nil {
t.Error("Expected error for invalid input")
}
})
}
}
func TestGenerateVariety(t *testing.T) {
// Test that different inputs produce different outputs
values := []string{"value1", "value2", "value3", "test@example.com", "another-test"}
size := 64
svgs := make([]string, len(values))
for i, value := range values {
icon, err := Generate(value, size)
if err != nil {
t.Fatalf("Generate failed for %s: %v", value, err)
}
svg, err := icon.ToSVG()
if err != nil {
t.Fatalf("ToSVG failed for %s: %v", value, err)
}
svgs[i] = svg
}
// Check that all SVGs are different
for i := 0; i < len(svgs); i++ {
for j := i + 1; j < len(svgs); j++ {
if svgs[i] == svgs[j] {
t.Errorf("SVG outputs are identical for different inputs: %s and %s", values[i], values[j])
}
}
}
}
func BenchmarkGenerate(b *testing.B) {
value := "benchmark-test"
size := 64
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := Generate(value, size)
if err != nil {
b.Fatalf("Generate failed: %v", err)
}
}
}
// BenchmarkGenerateVariousSizes tests generation performance across different icon sizes
func BenchmarkGenerateVariousSizes(b *testing.B) {
sizes := []int{64, 128, 256, 512, 1024}
for _, size := range sizes {
b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := Generate("benchmark@test.com", size)
if err != nil {
b.Fatalf("Generate failed for size %d: %v", size, err)
}
}
})
}
}
// BenchmarkGenerateVariousInputs tests generation performance with different input types
func BenchmarkGenerateVariousInputs(b *testing.B) {
inputs := []struct {
name string
value string
}{
{"email", "user@example.com"},
{"username", "john_doe_123"},
{"uuid", "550e8400-e29b-41d4-a716-446655440000"},
{"short", "abc"},
{"long", "this_is_a_very_long_identifier_that_might_be_used_for_generating_identicons_in_some_applications"},
{"special_chars", "user+test@domain.co.uk"},
{"numbers", "12345678901234567890"},
}
for _, input := range inputs {
b.Run(input.name, func(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := Generate(input.value, 128)
if err != nil {
b.Fatalf("Generate failed for input %s: %v", input.name, err)
}
}
})
}
}
func BenchmarkToSVG(b *testing.B) {
icon, err := Generate("benchmark-test", 64)
if err != nil {
b.Fatalf("Generate failed: %v", err)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := icon.ToSVG()
if err != nil {
b.Fatalf("ToSVG failed: %v", err)
}
}
}
func BenchmarkToPNG(b *testing.B) {
icon, err := Generate("benchmark-test", 64)
if err != nil {
b.Fatalf("Generate failed: %v", err)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := icon.ToPNG()
if err != nil {
b.Fatalf("ToPNG failed: %v", err)
}
}
}
// BenchmarkHashGeneration benchmarks just hash computation performance
func BenchmarkHashGeneration(b *testing.B) {
inputs := []string{
"user@example.com",
"john_doe_123",
"550e8400-e29b-41d4-a716-446655440000",
"abc",
"this_is_a_very_long_identifier_that_might_be_used_for_generating_identicons",
}
for _, input := range inputs {
b.Run(fmt.Sprintf("len_%d", len(input)), func(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_ = ComputeHash(input)
}
})
}
}
// BenchmarkSVGRenderingVariousSizes benchmarks SVG rendering across different sizes
func BenchmarkSVGRenderingVariousSizes(b *testing.B) {
sizes := []int{64, 128, 256, 512}
icons := make(map[int]*Icon)
// Pre-generate icons
for _, size := range sizes {
icon, err := Generate("benchmark@test.com", size)
if err != nil {
b.Fatalf("Failed to generate icon for size %d: %v", size, err)
}
icons[size] = icon
}
for _, size := range sizes {
b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) {
icon := icons[size]
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := icon.ToSVG()
if err != nil {
b.Fatalf("ToSVG failed for size %d: %v", size, err)
}
}
})
}
}
// BenchmarkPNGRenderingVariousSizes benchmarks PNG rendering across different sizes
func BenchmarkPNGRenderingVariousSizes(b *testing.B) {
sizes := []int{64, 128, 256} // Smaller range for PNG due to higher memory usage
icons := make(map[int]*Icon)
// Pre-generate icons
for _, size := range sizes {
icon, err := Generate("benchmark@test.com", size)
if err != nil {
b.Fatalf("Failed to generate icon for size %d: %v", size, err)
}
icons[size] = icon
}
for _, size := range sizes {
b.Run(fmt.Sprintf("size_%d", size), func(b *testing.B) {
icon := icons[size]
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := icon.ToPNG()
if err != nil {
b.Fatalf("ToPNG failed for size %d: %v", size, err)
}
}
})
}
}
// BenchmarkWithCustomConfig benchmarks generation with custom configuration
func BenchmarkWithCustomConfig(b *testing.B) {
config, err := Configure(
WithHueRestrictions([]float64{0.0, 0.33, 0.66}),
WithColorSaturation(0.6),
WithBackgroundColor("#ffffff"),
WithPadding(0.1),
)
if err != nil {
b.Fatalf("Configure failed: %v", err)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := ToSVG("benchmark@test.com", 128, config)
if err != nil {
b.Fatalf("ToSVG with config failed: %v", err)
}
}
}
// BenchmarkWithCustomConfigPNG benchmarks PNG generation with custom configuration
func BenchmarkWithCustomConfigPNG(b *testing.B) {
config, err := Configure(
WithColorSaturation(0.6),
WithBackgroundColor("#123456"),
WithPadding(0.1),
)
if err != nil {
b.Fatalf("Configure failed: %v", err)
}
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
_, err := ToPNG("benchmark-png-config@test.com", 128, config)
if err != nil {
b.Fatalf("ToPNG with config failed: %v", err)
}
}
}
// BenchmarkConcurrentGeneration tests performance under concurrent load
func BenchmarkConcurrentGeneration(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
b.RunParallel(func(pb *testing.PB) {
i := 0
for pb.Next() {
value := fmt.Sprintf("concurrent_user_%d@example.com", i)
_, err := Generate(value, 128)
if err != nil {
b.Fatalf("Generate failed: %v", err)
}
i++
}
})
}
// BenchmarkBatchGeneration tests memory allocation patterns for batch generation
func BenchmarkBatchGeneration(b *testing.B) {
const batchSize = 100
b.SetBytes(batchSize) // Report throughput as items/sec
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for j := 0; j < batchSize; j++ {
value := fmt.Sprintf("batch_user_%d@example.com", j)
_, err := Generate(value, 64)
if err != nil {
b.Fatalf("Generate failed: %v", err)
}
}
}
}
// Tests for new public API functions
func TestToSVG(t *testing.T) {
tests := []struct {
name string
value interface{}
size int
valid bool
}{
{"string input", "test@example.com", 64, true},
{"int input", 12345, 64, true},
{"float input", 123.45, 64, true},
{"bool input", true, 64, true},
{"nil input", nil, 64, true},
{"byte slice", []byte("hello"), 64, true},
{"zero size", "test", 0, false},
{"negative size", "test", -10, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
svg, err := ToSVG(tt.value, tt.size)
if tt.valid {
if err != nil {
t.Fatalf("ToSVG failed: %v", err)
}
if svg == "" {
t.Error("ToSVG returned empty string")
}
// Basic SVG validation
if !strings.Contains(svg, "<svg") {
t.Error("SVG output does not contain svg tag")
}
if !strings.Contains(svg, "</svg>") {
t.Error("SVG output does not contain closing svg tag")
}
} else {
if err == nil {
t.Error("Expected error for invalid input")
}
}
})
}
}
func TestToPNG(t *testing.T) {
tests := []struct {
name string
value interface{}
size int
valid bool
}{
{"string input", "test@example.com", 64, true},
{"int input", 12345, 64, true},
{"float input", 123.45, 64, true},
{"bool input", false, 64, true},
{"nil input", nil, 64, true},
{"byte slice", []byte("hello"), 64, true},
{"zero size", "test", 0, false},
{"negative size", "test", -10, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
png, err := ToPNG(tt.value, tt.size)
if tt.valid {
if err != nil {
t.Fatalf("ToPNG failed: %v", err)
}
if len(png) == 0 {
t.Error("ToPNG returned empty data")
}
// Basic PNG validation (check PNG signature)
if len(png) < 8 || string(png[1:4]) != "PNG" {
t.Error("PNG output does not have valid PNG signature")
}
} else {
if err == nil {
t.Error("Expected error for invalid input")
}
}
})
}
}
func TestToHash(t *testing.T) {
tests := []struct {
name string
value interface{}
expected string
}{
{"string", "test", ComputeHash("test")},
{"int", 123, ComputeHash("123")},
{"float", 123.45, ComputeHash("123.45")},
{"bool true", true, ComputeHash("true")},
{"bool false", false, ComputeHash("false")},
{"nil", nil, ComputeHash("")},
{"byte slice", []byte("hello"), ComputeHash("hello")},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hash := ToHash(tt.value)
if hash != tt.expected {
t.Errorf("ToHash(%v) = %s, expected %s", tt.value, hash, tt.expected)
}
// Hash should be non-empty and valid hex
if hash == "" {
t.Error("ToHash returned empty string")
}
// Should be valid hex characters
for _, c := range hash {
if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
t.Errorf("Hash contains invalid character: %c", c)
break
}
}
})
}
}
func TestToSVGWithConfig(t *testing.T) {
value := "config-test"
size := 64
// Test with custom configuration
config, err := Configure(
WithHueRestrictions([]float64{120, 240}), // Blue/green hues
WithColorSaturation(0.8),
WithBackgroundColor("#ffffff"),
WithPadding(0.1),
)
if err != nil {
t.Fatalf("Configure failed: %v", err)
}
svg, err := ToSVG(value, size, config)
if err != nil {
t.Fatalf("ToSVG with config failed: %v", err)
}
if svg == "" {
t.Error("ToSVG with config returned empty string")
}
// Test that it's different from default
defaultSvg, err := ToSVG(value, size)
if err != nil {
t.Fatalf("ToSVG default failed: %v", err)
}
if svg == defaultSvg {
t.Error("SVG with config is identical to default SVG")
}
}
func TestToPNGWithConfig(t *testing.T) {
value := "config-test"
size := 64
// Test with custom configuration
config, err := Configure(
WithHueRestrictions([]float64{60, 180}), // Yellow/cyan hues
WithColorSaturation(0.9),
WithBackgroundColor("#000000"),
WithPadding(0.05),
)
if err != nil {
t.Fatalf("Configure failed: %v", err)
}
png, err := ToPNG(value, size, config)
if err != nil {
t.Fatalf("ToPNG with config failed: %v", err)
}
if len(png) == 0 {
t.Error("ToPNG with config returned empty data")
}
// Test that it's different from default
defaultPng, err := ToPNG(value, size)
if err != nil {
t.Fatalf("ToPNG default failed: %v", err)
}
if bytes.Equal(png, defaultPng) {
t.Error("PNG with config is identical to default PNG")
}
}
func TestPublicAPIConsistency(t *testing.T) {
value := "consistency-test"
size := 64
// Generate with both old and new APIs
icon, err := Generate(value, size)
if err != nil {
t.Fatalf("Generate failed: %v", err)
}
oldSvg, err := icon.ToSVG()
if err != nil {
t.Fatalf("Icon.ToSVG failed: %v", err)
}
oldPng, err := icon.ToPNG()
if err != nil {
t.Fatalf("Icon.ToPNG failed: %v", err)
}
newSvg, err := ToSVG(value, size)
if err != nil {
t.Fatalf("ToSVG failed: %v", err)
}
newPng, err := ToPNG(value, size)
if err != nil {
t.Fatalf("ToPNG failed: %v", err)
}
// Results should be identical
if oldSvg != newSvg {
t.Error("SVG output differs between old and new APIs")
}
if !bytes.Equal(oldPng, newPng) {
t.Error("PNG output differs between old and new APIs")
}
}
func TestTypeConversion(t *testing.T) {
tests := []struct {
name string
input interface{}
expected string
}{
{"string", "hello", "hello"},
{"int", 42, "42"},
{"int64", int64(42), "42"},
{"float64", 3.14, "3.14"},
{"bool true", true, "true"},
{"bool false", false, "false"},
{"nil", nil, ""},
{"byte slice", []byte("test"), "test"},
{"struct", struct{ Name string }{"test"}, "{test}"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := convertToString(tt.input)
if result != tt.expected {
t.Errorf("convertToString(%v) = %s, expected %s", tt.input, result, tt.expected)
}
})
}
}
// Helper function for min
func min(a, b int) int {
if a < b {
return a
}
return b
}