mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2025-09-20 20:45:58 +08:00
533 lines
12 KiB
Go
533 lines
12 KiB
Go
// Copyright 2019 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package maphash
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"hash"
|
|
"math"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"unsafe"
|
|
|
|
rand "github.com/metacubex/randv2"
|
|
)
|
|
|
|
func TestUnseededHash(t *testing.T) {
|
|
m := map[uint64]struct{}{}
|
|
for i := 0; i < 1000; i++ {
|
|
h := new(Hash)
|
|
m[h.Sum64()] = struct{}{}
|
|
}
|
|
if len(m) < 900 {
|
|
t.Errorf("empty hash not sufficiently random: got %d, want 1000", len(m))
|
|
}
|
|
}
|
|
|
|
func TestSeededHash(t *testing.T) {
|
|
s := MakeSeed()
|
|
m := map[uint64]struct{}{}
|
|
for i := 0; i < 1000; i++ {
|
|
h := new(Hash)
|
|
h.SetSeed(s)
|
|
m[h.Sum64()] = struct{}{}
|
|
}
|
|
if len(m) != 1 {
|
|
t.Errorf("seeded hash is random: got %d, want 1", len(m))
|
|
}
|
|
}
|
|
|
|
func TestHashGrouping(t *testing.T) {
|
|
b := bytes.Repeat([]byte("foo"), 100)
|
|
hh := make([]*Hash, 7)
|
|
for i := range hh {
|
|
hh[i] = new(Hash)
|
|
}
|
|
for _, h := range hh[1:] {
|
|
h.SetSeed(hh[0].Seed())
|
|
}
|
|
hh[0].Write(b)
|
|
hh[1].WriteString(string(b))
|
|
|
|
writeByte := func(h *Hash, b byte) {
|
|
err := h.WriteByte(b)
|
|
if err != nil {
|
|
t.Fatalf("WriteByte: %v", err)
|
|
}
|
|
}
|
|
writeSingleByte := func(h *Hash, b byte) {
|
|
_, err := h.Write([]byte{b})
|
|
if err != nil {
|
|
t.Fatalf("Write single byte: %v", err)
|
|
}
|
|
}
|
|
writeStringSingleByte := func(h *Hash, b byte) {
|
|
_, err := h.WriteString(string([]byte{b}))
|
|
if err != nil {
|
|
t.Fatalf("WriteString single byte: %v", err)
|
|
}
|
|
}
|
|
|
|
for i, x := range b {
|
|
writeByte(hh[2], x)
|
|
writeSingleByte(hh[3], x)
|
|
if i == 0 {
|
|
writeByte(hh[4], x)
|
|
} else {
|
|
writeSingleByte(hh[4], x)
|
|
}
|
|
writeStringSingleByte(hh[5], x)
|
|
if i == 0 {
|
|
writeByte(hh[6], x)
|
|
} else {
|
|
writeStringSingleByte(hh[6], x)
|
|
}
|
|
}
|
|
|
|
sum := hh[0].Sum64()
|
|
for i, h := range hh {
|
|
if sum != h.Sum64() {
|
|
t.Errorf("hash %d not identical to a single Write", i)
|
|
}
|
|
}
|
|
|
|
if sum1 := Bytes(hh[0].Seed(), b); sum1 != hh[0].Sum64() {
|
|
t.Errorf("hash using Bytes not identical to a single Write")
|
|
}
|
|
|
|
if sum1 := String(hh[0].Seed(), string(b)); sum1 != hh[0].Sum64() {
|
|
t.Errorf("hash using String not identical to a single Write")
|
|
}
|
|
}
|
|
|
|
func TestHashBytesVsString(t *testing.T) {
|
|
s := "foo"
|
|
b := []byte(s)
|
|
h1 := new(Hash)
|
|
h2 := new(Hash)
|
|
h2.SetSeed(h1.Seed())
|
|
n1, err1 := h1.WriteString(s)
|
|
if n1 != len(s) || err1 != nil {
|
|
t.Fatalf("WriteString(s) = %d, %v, want %d, nil", n1, err1, len(s))
|
|
}
|
|
n2, err2 := h2.Write(b)
|
|
if n2 != len(b) || err2 != nil {
|
|
t.Fatalf("Write(b) = %d, %v, want %d, nil", n2, err2, len(b))
|
|
}
|
|
if h1.Sum64() != h2.Sum64() {
|
|
t.Errorf("hash of string and bytes not identical")
|
|
}
|
|
}
|
|
|
|
func TestHashHighBytes(t *testing.T) {
|
|
// See issue 34925.
|
|
const N = 10
|
|
m := map[uint64]struct{}{}
|
|
for i := 0; i < N; i++ {
|
|
h := new(Hash)
|
|
h.WriteString("foo")
|
|
m[h.Sum64()>>32] = struct{}{}
|
|
}
|
|
if len(m) < N/2 {
|
|
t.Errorf("from %d seeds, wanted at least %d different hashes; got %d", N, N/2, len(m))
|
|
}
|
|
}
|
|
|
|
func TestRepeat(t *testing.T) {
|
|
h1 := new(Hash)
|
|
h1.WriteString("testing")
|
|
sum1 := h1.Sum64()
|
|
|
|
h1.Reset()
|
|
h1.WriteString("testing")
|
|
sum2 := h1.Sum64()
|
|
|
|
if sum1 != sum2 {
|
|
t.Errorf("different sum after resetting: %#x != %#x", sum1, sum2)
|
|
}
|
|
|
|
h2 := new(Hash)
|
|
h2.SetSeed(h1.Seed())
|
|
h2.WriteString("testing")
|
|
sum3 := h2.Sum64()
|
|
|
|
if sum1 != sum3 {
|
|
t.Errorf("different sum on the same seed: %#x != %#x", sum1, sum3)
|
|
}
|
|
}
|
|
|
|
func TestSeedFromSum64(t *testing.T) {
|
|
h1 := new(Hash)
|
|
h1.WriteString("foo")
|
|
x := h1.Sum64() // seed generated here
|
|
h2 := new(Hash)
|
|
h2.SetSeed(h1.Seed())
|
|
h2.WriteString("foo")
|
|
y := h2.Sum64()
|
|
if x != y {
|
|
t.Errorf("hashes don't match: want %x, got %x", x, y)
|
|
}
|
|
}
|
|
|
|
func TestSeedFromSeed(t *testing.T) {
|
|
h1 := new(Hash)
|
|
h1.WriteString("foo")
|
|
_ = h1.Seed() // seed generated here
|
|
x := h1.Sum64()
|
|
h2 := new(Hash)
|
|
h2.SetSeed(h1.Seed())
|
|
h2.WriteString("foo")
|
|
y := h2.Sum64()
|
|
if x != y {
|
|
t.Errorf("hashes don't match: want %x, got %x", x, y)
|
|
}
|
|
}
|
|
|
|
func TestSeedFromFlush(t *testing.T) {
|
|
b := make([]byte, 65)
|
|
h1 := new(Hash)
|
|
h1.Write(b) // seed generated here
|
|
x := h1.Sum64()
|
|
h2 := new(Hash)
|
|
h2.SetSeed(h1.Seed())
|
|
h2.Write(b)
|
|
y := h2.Sum64()
|
|
if x != y {
|
|
t.Errorf("hashes don't match: want %x, got %x", x, y)
|
|
}
|
|
}
|
|
|
|
func TestSeedFromReset(t *testing.T) {
|
|
h1 := new(Hash)
|
|
h1.WriteString("foo")
|
|
h1.Reset() // seed generated here
|
|
h1.WriteString("foo")
|
|
x := h1.Sum64()
|
|
h2 := new(Hash)
|
|
h2.SetSeed(h1.Seed())
|
|
h2.WriteString("foo")
|
|
y := h2.Sum64()
|
|
if x != y {
|
|
t.Errorf("hashes don't match: want %x, got %x", x, y)
|
|
}
|
|
}
|
|
|
|
func negativeZero[T float32 | float64]() T {
|
|
var f T
|
|
f = -f
|
|
return f
|
|
}
|
|
|
|
func TestComparable(t *testing.T) {
|
|
testComparable(t, int64(2))
|
|
testComparable(t, uint64(8))
|
|
testComparable(t, uintptr(12))
|
|
testComparable(t, any("s"))
|
|
testComparable(t, "s")
|
|
testComparable(t, true)
|
|
testComparable(t, new(float64))
|
|
testComparable(t, float64(9))
|
|
testComparable(t, complex128(9i+1))
|
|
testComparable(t, struct{}{})
|
|
testComparable(t, struct {
|
|
i int
|
|
u uint
|
|
b bool
|
|
f float64
|
|
p *int
|
|
a any
|
|
}{i: 9, u: 1, b: true, f: 9.9, p: new(int), a: 1})
|
|
type S struct {
|
|
s string
|
|
}
|
|
s1 := S{s: heapStr(t)}
|
|
s2 := S{s: heapStr(t)}
|
|
if unsafe.StringData(s1.s) == unsafe.StringData(s2.s) {
|
|
t.Fatalf("unexpected two heapStr ptr equal")
|
|
}
|
|
if s1.s != s2.s {
|
|
t.Fatalf("unexpected two heapStr value not equal")
|
|
}
|
|
testComparable(t, s1, s2)
|
|
testComparable(t, s1.s, s2.s)
|
|
testComparable(t, float32(0), negativeZero[float32]())
|
|
testComparable(t, float64(0), negativeZero[float64]())
|
|
testComparableNoEqual(t, math.NaN(), math.NaN())
|
|
testComparableNoEqual(t, [2]string{"a", ""}, [2]string{"", "a"})
|
|
testComparableNoEqual(t, struct{ a, b string }{"foo", ""}, struct{ a, b string }{"", "foo"})
|
|
testComparableNoEqual(t, struct{ a, b any }{int(0), struct{}{}}, struct{ a, b any }{struct{}{}, int(0)})
|
|
}
|
|
|
|
func testComparableNoEqual[T comparable](t *testing.T, v1, v2 T) {
|
|
seed := MakeSeed()
|
|
if Comparable(seed, v1) == Comparable(seed, v2) {
|
|
t.Fatalf("Comparable(seed, %v) == Comparable(seed, %v)", v1, v2)
|
|
}
|
|
}
|
|
|
|
var heapStrValue = []byte("aTestString")
|
|
|
|
func heapStr(t *testing.T) string {
|
|
return string(heapStrValue)
|
|
}
|
|
|
|
func testComparable[T comparable](t *testing.T, v T, v2 ...T) {
|
|
t.Run(TypeFor[T]().String(), func(t *testing.T) {
|
|
var a, b T = v, v
|
|
if len(v2) != 0 {
|
|
b = v2[0]
|
|
}
|
|
var pa *T = &a
|
|
seed := MakeSeed()
|
|
if Comparable(seed, a) != Comparable(seed, b) {
|
|
t.Fatalf("Comparable(seed, %v) != Comparable(seed, %v)", a, b)
|
|
}
|
|
old := Comparable(seed, pa)
|
|
stackGrow(8192)
|
|
new := Comparable(seed, pa)
|
|
if old != new {
|
|
t.Fatal("Comparable(seed, ptr) != Comparable(seed, ptr)")
|
|
}
|
|
})
|
|
}
|
|
|
|
var use byte
|
|
|
|
//go:noinline
|
|
func stackGrow(dep int) {
|
|
if dep == 0 {
|
|
return
|
|
}
|
|
var local [1024]byte
|
|
// make sure local is allocated on the stack.
|
|
local[rand.Uint64()%1024] = byte(rand.Uint64())
|
|
use = local[rand.Uint64()%1024]
|
|
stackGrow(dep - 1)
|
|
}
|
|
|
|
func TestWriteComparable(t *testing.T) {
|
|
testWriteComparable(t, int64(2))
|
|
testWriteComparable(t, uint64(8))
|
|
testWriteComparable(t, uintptr(12))
|
|
testWriteComparable(t, any("s"))
|
|
testWriteComparable(t, "s")
|
|
testComparable(t, true)
|
|
testWriteComparable(t, new(float64))
|
|
testWriteComparable(t, float64(9))
|
|
testWriteComparable(t, complex128(9i+1))
|
|
testWriteComparable(t, struct{}{})
|
|
testWriteComparable(t, struct {
|
|
i int
|
|
u uint
|
|
b bool
|
|
f float64
|
|
p *int
|
|
a any
|
|
}{i: 9, u: 1, b: true, f: 9.9, p: new(int), a: 1})
|
|
type S struct {
|
|
s string
|
|
}
|
|
s1 := S{s: heapStr(t)}
|
|
s2 := S{s: heapStr(t)}
|
|
if unsafe.StringData(s1.s) == unsafe.StringData(s2.s) {
|
|
t.Fatalf("unexpected two heapStr ptr equal")
|
|
}
|
|
if s1.s != s2.s {
|
|
t.Fatalf("unexpected two heapStr value not equal")
|
|
}
|
|
testWriteComparable(t, s1, s2)
|
|
testWriteComparable(t, s1.s, s2.s)
|
|
testWriteComparable(t, float32(0), negativeZero[float32]())
|
|
testWriteComparable(t, float64(0), negativeZero[float64]())
|
|
testWriteComparableNoEqual(t, math.NaN(), math.NaN())
|
|
testWriteComparableNoEqual(t, [2]string{"a", ""}, [2]string{"", "a"})
|
|
testWriteComparableNoEqual(t, struct{ a, b string }{"foo", ""}, struct{ a, b string }{"", "foo"})
|
|
testWriteComparableNoEqual(t, struct{ a, b any }{int(0), struct{}{}}, struct{ a, b any }{struct{}{}, int(0)})
|
|
}
|
|
|
|
func testWriteComparableNoEqual[T comparable](t *testing.T, v1, v2 T) {
|
|
seed := MakeSeed()
|
|
h1 := Hash{}
|
|
h2 := Hash{}
|
|
*(*Seed)(unsafe.Pointer(&h1)), *(*Seed)(unsafe.Pointer(&h2)) = seed, seed
|
|
WriteComparable(&h1, v1)
|
|
WriteComparable(&h2, v2)
|
|
if h1.Sum64() == h2.Sum64() {
|
|
t.Fatalf("WriteComparable(seed, %v) == WriteComparable(seed, %v)", v1, v2)
|
|
}
|
|
|
|
}
|
|
|
|
func testWriteComparable[T comparable](t *testing.T, v T, v2 ...T) {
|
|
t.Run(TypeFor[T]().String(), func(t *testing.T) {
|
|
var a, b T = v, v
|
|
if len(v2) != 0 {
|
|
b = v2[0]
|
|
}
|
|
var pa *T = &a
|
|
h1 := Hash{}
|
|
h2 := Hash{}
|
|
*(*Seed)(unsafe.Pointer(&h1)) = MakeSeed()
|
|
h2 = h1
|
|
WriteComparable(&h1, a)
|
|
WriteComparable(&h2, b)
|
|
if h1.Sum64() != h2.Sum64() {
|
|
t.Fatalf("WriteComparable(h, %v) != WriteComparable(h, %v)", a, b)
|
|
}
|
|
WriteComparable(&h1, pa)
|
|
old := h1.Sum64()
|
|
stackGrow(8192)
|
|
WriteComparable(&h2, pa)
|
|
new := h2.Sum64()
|
|
if old != new {
|
|
t.Fatal("WriteComparable(seed, ptr) != WriteComparable(seed, ptr)")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestComparableShouldPanic(t *testing.T) {
|
|
s := []byte("s")
|
|
a := any(s)
|
|
defer func() {
|
|
e := recover()
|
|
err, ok := e.(error)
|
|
if !ok {
|
|
t.Fatalf("Comaparable(any([]byte)) should panic")
|
|
}
|
|
want := "hash of unhashable type []uint8"
|
|
if s := err.Error(); !strings.Contains(s, want) {
|
|
t.Fatalf("want %s, got %s", want, s)
|
|
}
|
|
}()
|
|
Comparable(MakeSeed(), a)
|
|
}
|
|
|
|
func TestWriteComparableNoncommute(t *testing.T) {
|
|
seed := MakeSeed()
|
|
var h1, h2 Hash
|
|
h1.SetSeed(seed)
|
|
h2.SetSeed(seed)
|
|
|
|
h1.WriteString("abc")
|
|
WriteComparable(&h1, 123)
|
|
WriteComparable(&h2, 123)
|
|
h2.WriteString("abc")
|
|
|
|
if h1.Sum64() == h2.Sum64() {
|
|
t.Errorf("WriteComparable and WriteString unexpectedly commute")
|
|
}
|
|
}
|
|
|
|
func TestComparableAllocations(t *testing.T) {
|
|
t.Skip("test broken in old golang version")
|
|
seed := MakeSeed()
|
|
x := heapStr(t)
|
|
allocs := testing.AllocsPerRun(10, func() {
|
|
s := "s" + x
|
|
Comparable(seed, s)
|
|
})
|
|
if allocs > 0 {
|
|
t.Errorf("got %v allocs, want 0", allocs)
|
|
}
|
|
|
|
type S struct {
|
|
a int
|
|
b string
|
|
}
|
|
allocs = testing.AllocsPerRun(10, func() {
|
|
s := S{123, "s" + x}
|
|
Comparable(seed, s)
|
|
})
|
|
if allocs > 0 {
|
|
t.Errorf("got %v allocs, want 0", allocs)
|
|
}
|
|
}
|
|
|
|
// Make sure a Hash implements the hash.Hash and hash.Hash64 interfaces.
|
|
var _ hash.Hash = &Hash{}
|
|
var _ hash.Hash64 = &Hash{}
|
|
|
|
func benchmarkSize(b *testing.B, size int) {
|
|
h := &Hash{}
|
|
buf := make([]byte, size)
|
|
s := string(buf)
|
|
|
|
b.Run("Write", func(b *testing.B) {
|
|
b.SetBytes(int64(size))
|
|
for i := 0; i < b.N; i++ {
|
|
h.Reset()
|
|
h.Write(buf)
|
|
h.Sum64()
|
|
}
|
|
})
|
|
|
|
b.Run("Bytes", func(b *testing.B) {
|
|
b.SetBytes(int64(size))
|
|
seed := h.Seed()
|
|
for i := 0; i < b.N; i++ {
|
|
Bytes(seed, buf)
|
|
}
|
|
})
|
|
|
|
b.Run("String", func(b *testing.B) {
|
|
b.SetBytes(int64(size))
|
|
seed := h.Seed()
|
|
for i := 0; i < b.N; i++ {
|
|
String(seed, s)
|
|
}
|
|
})
|
|
}
|
|
|
|
func BenchmarkHash(b *testing.B) {
|
|
sizes := []int{4, 8, 16, 32, 64, 256, 320, 1024, 4096, 16384}
|
|
for _, size := range sizes {
|
|
b.Run(fmt.Sprint("n=", size), func(b *testing.B) {
|
|
benchmarkSize(b, size)
|
|
})
|
|
}
|
|
}
|
|
|
|
func benchmarkComparable[T comparable](b *testing.B, v T) {
|
|
b.Run(TypeFor[T]().String(), func(b *testing.B) {
|
|
seed := MakeSeed()
|
|
for i := 0; i < b.N; i++ {
|
|
Comparable(seed, v)
|
|
}
|
|
})
|
|
}
|
|
|
|
func BenchmarkComparable(b *testing.B) {
|
|
type testStruct struct {
|
|
i int
|
|
u uint
|
|
b bool
|
|
f float64
|
|
p *int
|
|
a any
|
|
}
|
|
benchmarkComparable(b, int64(2))
|
|
benchmarkComparable(b, uint64(8))
|
|
benchmarkComparable(b, uintptr(12))
|
|
benchmarkComparable(b, any("s"))
|
|
benchmarkComparable(b, "s")
|
|
benchmarkComparable(b, true)
|
|
benchmarkComparable(b, new(float64))
|
|
benchmarkComparable(b, float64(9))
|
|
benchmarkComparable(b, complex128(9i+1))
|
|
benchmarkComparable(b, struct{}{})
|
|
benchmarkComparable(b, testStruct{i: 9, u: 1, b: true, f: 9.9, p: new(int), a: 1})
|
|
}
|
|
|
|
// TypeFor returns the [Type] that represents the type argument T.
|
|
func TypeFor[T any]() reflect.Type {
|
|
var v T
|
|
if t := reflect.TypeOf(v); t != nil {
|
|
return t // optimize for T being a non-interface kind
|
|
}
|
|
return reflect.TypeOf((*T)(nil)).Elem() // only for an interface kind
|
|
}
|