mishmash/mishmash_test.go

174 lines
4.2 KiB
Go
Raw Permalink Normal View History

2025-05-25 16:05:35 -04:00
package mishmash
import (
"bufio"
"bytes"
2025-05-25 16:05:35 -04:00
"errors"
"fmt"
"hash"
"math/big"
2025-05-25 16:05:35 -04:00
"math/rand"
"os"
"strconv"
"strings"
"testing"
)
type HashedString struct {
s string
h32 uint32
h64 uint64
}
var (
TheCppOutputFile = "word_hashes.txt"
TheCppOutput = func() []HashedString {
f, err := os.Open(TheCppOutputFile)
if err != nil {
panic("error opening " + TheCppOutputFile + ": " + err.Error())
}
defer f.Close()
results := make([]HashedString, 0)
scanner := bufio.NewScanner(f)
for scanner.Scan() {
if line := strings.TrimSpace(scanner.Text()); len(line) > 0 {
fields := strings.Fields(line)
if len(fields) == 3 {
n1, err := strconv.ParseUint(fields[1], 16, 32)
if err != nil {
panic("error parsing " + fields[1] + ": " + err.Error())
}
n2, err := strconv.ParseUint(fields[2], 16, 64)
if err != nil {
panic("error parsing " + fields[2] + ": " + err.Error())
}
results = append(results, HashedString{fields[0], uint32(n1), n2})
}
}
}
return results
}()
RandomWordAndHash = func() HashedString {
return TheCppOutput[rand.Intn(len(TheCppOutput))]
}
)
func TestPrimes(t *testing.T) {
for _, kvp := range TheCppOutput {
if word, want, got := kvp.s, kvp.h32, MishmashString(kvp.s); want != got {
t.Fatalf("error: %s; wanted %08x; got %08x\n", word, want, got)
}
}
}
func TestHash32Interface(t *testing.T) {
for _, kvp := range TheCppOutput {
var h hash.Hash32 = &Mishmash32{}
word := kvp.s
h.Write([]byte(word))
if want, got := kvp.h32, h.Sum32(); want != got {
t.Fatalf("error: %s; wanted %08x; got %08x\n", word, want, got)
}
}
}
func TestHash64Interface(t *testing.T) {
for _, kvp := range TheCppOutput {
var h hash.Hash64 = &Mishmash64{}
word := kvp.s
h.Write([]byte(word))
if want, got := kvp.h64, h.Sum64(); want != got {
t.Fatalf("error: %s; wanted %016x; got %016x\n", word, want, got)
}
}
}
func TestHash32Collision(t *testing.T) {
m := make(map[uint32][]string, len(TheCppOutput))
for _, kvp := range TheCppOutput {
m[kvp.h32] = append(m[kvp.h32], kvp.s)
}
errs := make([]error, 0)
for hash, values := range m {
if len(values) > 1 {
errs = append(errs, fmt.Errorf("%08x: %s", hash, strings.Join(values, " ")))
}
}
if err := errors.Join(errs...); err != nil {
fmt.Println(err)
}
}
func BenchmarkLoadEmbededPrimes(b *testing.B) {
const filename = "mishmash_primes.txt"
for _ = range b.N {
if _, err := LoadPrimesSet(filename); err != nil {
panic("error: " + err.Error())
}
}
}
func BenchmarkMishmash32(b *testing.B) {
for _ = range b.N {
kvp := RandomWordAndHash()
var hash hash.Hash32 = &Mishmash32{}
hash.Write([]byte(kvp.s))
if want, got := kvp.h32, hash.Sum32(); want != got {
panic(fmt.Sprintf("error: %s; wanted %08x; got %08x\n", kvp.s, want, got))
}
}
}
func BenchmarkMishmash64(b *testing.B) {
for _ = range b.N {
kvp := RandomWordAndHash()
var hash hash.Hash64 = &Mishmash64{}
hash.Write([]byte(kvp.s))
if want, got := kvp.h64, hash.Sum64(); want != got {
panic(fmt.Sprintf("error: %s; wanted %016x; got %016x\n", kvp.s, want, got))
}
}
}
func mask32(bits int) uint32 {
var t uint32
for range bits {
t <<= 1
t |= 1
}
for range 32 - bits {
t <<= 1
}
return t
}
func TestSum32(t *testing.T) {
hash, ok := New32().(*Mishmash32)
if !ok {
t.Fatalf("error reflecting hash.Hash to *Mishmash32\n")
}
s := []byte("hello world!")
hash.Write(s)
accumulator := hash.accumulator
if want, got := big.NewInt(int64(accumulator&UINT32_RESULTS_MASK)).Bytes(), hash.Bytes(); !bytes.Equal(want, got) {
t.Fatalf("error: wanted %v; got %v\n", want, got)
} else if want, got := append(s, hash.Bytes()...), hash.Sum(s); !bytes.Equal(want, got) {
t.Fatalf("error: wanted %v; got %v\n", want, got)
}
}
func TestSum64(t *testing.T) {
hash, ok := New64().(*Mishmash64)
if !ok {
t.Fatalf("error reflecting hash.Hash to *Mishmash64")
}
s := []byte("hello world!")
hash.Write(s)
accumulator := hash.accumulator
if want, got := big.NewInt(int64(accumulator)).Bytes(), hash.Bytes(); !bytes.Equal(want, got) {
t.Fatalf("error: wanted %v; got %v\n", want, got)
} else if want, got := append(s, hash.Bytes()...), hash.Sum(s); !bytes.Equal(want, got) {
t.Fatalf("error: wanted %v; got %v\n", want, got)
}
}