719 lines
15 KiB
Go
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
|
|
} |