| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204 |
- // Copyright (c) Tailscale Inc & AUTHORS
- // SPDX-License-Identifier: BSD-3-Clause
- package hashx
- import (
- "crypto/sha256"
- "encoding/binary"
- "hash"
- "math/rand"
- "testing"
- qt "github.com/frankban/quicktest"
- "tailscale.com/util/must"
- )
- // naiveHash is an obviously correct implementation of Hash.
- type naiveHash struct {
- hash.Hash
- scratch [256]byte
- }
- func newNaive() *naiveHash { return &naiveHash{Hash: sha256.New()} }
- func (h *naiveHash) HashUint8(n uint8) { h.Write(append(h.scratch[:0], n)) }
- func (h *naiveHash) HashUint16(n uint16) { h.Write(binary.LittleEndian.AppendUint16(h.scratch[:0], n)) }
- func (h *naiveHash) HashUint32(n uint32) { h.Write(binary.LittleEndian.AppendUint32(h.scratch[:0], n)) }
- func (h *naiveHash) HashUint64(n uint64) { h.Write(binary.LittleEndian.AppendUint64(h.scratch[:0], n)) }
- func (h *naiveHash) HashBytes(b []byte) { h.Write(b) }
- func (h *naiveHash) HashString(s string) { h.Write(append(h.scratch[:0], s...)) }
- var bytes = func() (out []byte) {
- out = make([]byte, 130)
- for i := range out {
- out[i] = byte(i)
- }
- return out
- }()
- type hasher interface {
- HashUint8(uint8)
- HashUint16(uint16)
- HashUint32(uint32)
- HashUint64(uint64)
- HashBytes([]byte)
- HashString(string)
- }
- func hashSuite(h hasher) {
- for i := range 10 {
- for j := 0; j < 10; j++ {
- h.HashUint8(0x01)
- h.HashUint8(0x23)
- h.HashUint32(0x456789ab)
- h.HashUint8(0xcd)
- h.HashUint8(0xef)
- h.HashUint16(0x0123)
- h.HashUint32(0x456789ab)
- h.HashUint16(0xcdef)
- h.HashUint8(0x01)
- h.HashUint64(0x23456789abcdef01)
- h.HashUint16(0x2345)
- h.HashUint8(0x67)
- h.HashUint16(0x89ab)
- h.HashUint8(0xcd)
- }
- b := bytes[:(i+1)*13]
- if i%2 == 0 {
- h.HashBytes(b)
- } else {
- h.HashString(string(b))
- }
- }
- }
- func Test(t *testing.T) {
- c := qt.New(t)
- h1 := must.Get(New512(sha256.New()))
- h2 := newNaive()
- hashSuite(h1)
- hashSuite(h2)
- c.Assert(h1.Sum(nil), qt.DeepEquals, h2.Sum(nil))
- }
- func TestAllocations(t *testing.T) {
- c := qt.New(t)
- c.Run("Sum", func(c *qt.C) {
- h := must.Get(New512(sha256.New()))
- c.Assert(testing.AllocsPerRun(100, func() {
- var a [sha256.Size]byte
- h.Sum(a[:0])
- }), qt.Equals, 0.0)
- })
- c.Run("HashUint8", func(c *qt.C) {
- h := must.Get(New512(sha256.New()))
- c.Assert(testing.AllocsPerRun(100, func() {
- h.HashUint8(0x01)
- }), qt.Equals, 0.0)
- })
- c.Run("HashUint16", func(c *qt.C) {
- h := must.Get(New512(sha256.New()))
- c.Assert(testing.AllocsPerRun(100, func() {
- h.HashUint16(0x0123)
- }), qt.Equals, 0.0)
- })
- c.Run("HashUint32", func(c *qt.C) {
- h := must.Get(New512(sha256.New()))
- c.Assert(testing.AllocsPerRun(100, func() {
- h.HashUint32(0x01234567)
- }), qt.Equals, 0.0)
- })
- c.Run("HashUint64", func(c *qt.C) {
- h := must.Get(New512(sha256.New()))
- c.Assert(testing.AllocsPerRun(100, func() {
- h.HashUint64(0x0123456789abcdef)
- }), qt.Equals, 0.0)
- })
- c.Run("HashBytes", func(c *qt.C) {
- h := must.Get(New512(sha256.New()))
- c.Assert(testing.AllocsPerRun(100, func() {
- h.HashBytes(bytes)
- }), qt.Equals, 0.0)
- })
- c.Run("HashString", func(c *qt.C) {
- h := must.Get(New512(sha256.New()))
- c.Assert(testing.AllocsPerRun(100, func() {
- h.HashString("abcdefghijklmnopqrstuvwxyz")
- }), qt.Equals, 0.0)
- })
- }
- func Fuzz(f *testing.F) {
- f.Fuzz(func(t *testing.T, seed int64) {
- c := qt.New(t)
- execute := func(h hasher, r *rand.Rand) {
- for range r.Intn(256) {
- switch r.Intn(5) {
- case 0:
- n := uint8(r.Uint64())
- h.HashUint8(n)
- case 1:
- n := uint16(r.Uint64())
- h.HashUint16(n)
- case 2:
- n := uint32(r.Uint64())
- h.HashUint32(n)
- case 3:
- n := uint64(r.Uint64())
- h.HashUint64(n)
- case 4:
- b := make([]byte, r.Intn(256))
- r.Read(b)
- h.HashBytes(b)
- }
- }
- }
- r1 := rand.New(rand.NewSource(seed))
- r2 := rand.New(rand.NewSource(seed))
- h1 := must.Get(New512(sha256.New()))
- h2 := newNaive()
- execute(h1, r1)
- execute(h2, r2)
- c.Assert(h1.Sum(nil), qt.DeepEquals, h2.Sum(nil))
- execute(h1, r1)
- execute(h2, r2)
- c.Assert(h1.Sum(nil), qt.DeepEquals, h2.Sum(nil))
- h1.Reset()
- h2.Reset()
- execute(h1, r1)
- execute(h2, r2)
- c.Assert(h1.Sum(nil), qt.DeepEquals, h2.Sum(nil))
- })
- }
- func Benchmark(b *testing.B) {
- var sum [sha256.Size]byte
- b.Run("Hash", func(b *testing.B) {
- b.ReportAllocs()
- h := must.Get(New512(sha256.New()))
- for range b.N {
- h.Reset()
- hashSuite(h)
- h.Sum(sum[:0])
- }
- })
- b.Run("Naive", func(b *testing.B) {
- b.ReportAllocs()
- h := newNaive()
- for range b.N {
- h.Reset()
- hashSuite(h)
- h.Sum(sum[:0])
- }
- })
- }
|