block512_test.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package hashx
  4. import (
  5. "crypto/sha256"
  6. "encoding/binary"
  7. "hash"
  8. "math/rand"
  9. "testing"
  10. qt "github.com/frankban/quicktest"
  11. "tailscale.com/util/must"
  12. )
  13. // naiveHash is an obviously correct implementation of Hash.
  14. type naiveHash struct {
  15. hash.Hash
  16. scratch [256]byte
  17. }
  18. func newNaive() *naiveHash { return &naiveHash{Hash: sha256.New()} }
  19. func (h *naiveHash) HashUint8(n uint8) { h.Write(append(h.scratch[:0], n)) }
  20. func (h *naiveHash) HashUint16(n uint16) { h.Write(binary.LittleEndian.AppendUint16(h.scratch[:0], n)) }
  21. func (h *naiveHash) HashUint32(n uint32) { h.Write(binary.LittleEndian.AppendUint32(h.scratch[:0], n)) }
  22. func (h *naiveHash) HashUint64(n uint64) { h.Write(binary.LittleEndian.AppendUint64(h.scratch[:0], n)) }
  23. func (h *naiveHash) HashBytes(b []byte) { h.Write(b) }
  24. func (h *naiveHash) HashString(s string) { h.Write(append(h.scratch[:0], s...)) }
  25. var bytes = func() (out []byte) {
  26. out = make([]byte, 130)
  27. for i := range out {
  28. out[i] = byte(i)
  29. }
  30. return out
  31. }()
  32. type hasher interface {
  33. HashUint8(uint8)
  34. HashUint16(uint16)
  35. HashUint32(uint32)
  36. HashUint64(uint64)
  37. HashBytes([]byte)
  38. HashString(string)
  39. }
  40. func hashSuite(h hasher) {
  41. for i := range 10 {
  42. for j := 0; j < 10; j++ {
  43. h.HashUint8(0x01)
  44. h.HashUint8(0x23)
  45. h.HashUint32(0x456789ab)
  46. h.HashUint8(0xcd)
  47. h.HashUint8(0xef)
  48. h.HashUint16(0x0123)
  49. h.HashUint32(0x456789ab)
  50. h.HashUint16(0xcdef)
  51. h.HashUint8(0x01)
  52. h.HashUint64(0x23456789abcdef01)
  53. h.HashUint16(0x2345)
  54. h.HashUint8(0x67)
  55. h.HashUint16(0x89ab)
  56. h.HashUint8(0xcd)
  57. }
  58. b := bytes[:(i+1)*13]
  59. if i%2 == 0 {
  60. h.HashBytes(b)
  61. } else {
  62. h.HashString(string(b))
  63. }
  64. }
  65. }
  66. func Test(t *testing.T) {
  67. c := qt.New(t)
  68. h1 := must.Get(New512(sha256.New()))
  69. h2 := newNaive()
  70. hashSuite(h1)
  71. hashSuite(h2)
  72. c.Assert(h1.Sum(nil), qt.DeepEquals, h2.Sum(nil))
  73. }
  74. func TestAllocations(t *testing.T) {
  75. c := qt.New(t)
  76. c.Run("Sum", func(c *qt.C) {
  77. h := must.Get(New512(sha256.New()))
  78. c.Assert(testing.AllocsPerRun(100, func() {
  79. var a [sha256.Size]byte
  80. h.Sum(a[:0])
  81. }), qt.Equals, 0.0)
  82. })
  83. c.Run("HashUint8", func(c *qt.C) {
  84. h := must.Get(New512(sha256.New()))
  85. c.Assert(testing.AllocsPerRun(100, func() {
  86. h.HashUint8(0x01)
  87. }), qt.Equals, 0.0)
  88. })
  89. c.Run("HashUint16", func(c *qt.C) {
  90. h := must.Get(New512(sha256.New()))
  91. c.Assert(testing.AllocsPerRun(100, func() {
  92. h.HashUint16(0x0123)
  93. }), qt.Equals, 0.0)
  94. })
  95. c.Run("HashUint32", func(c *qt.C) {
  96. h := must.Get(New512(sha256.New()))
  97. c.Assert(testing.AllocsPerRun(100, func() {
  98. h.HashUint32(0x01234567)
  99. }), qt.Equals, 0.0)
  100. })
  101. c.Run("HashUint64", func(c *qt.C) {
  102. h := must.Get(New512(sha256.New()))
  103. c.Assert(testing.AllocsPerRun(100, func() {
  104. h.HashUint64(0x0123456789abcdef)
  105. }), qt.Equals, 0.0)
  106. })
  107. c.Run("HashBytes", func(c *qt.C) {
  108. h := must.Get(New512(sha256.New()))
  109. c.Assert(testing.AllocsPerRun(100, func() {
  110. h.HashBytes(bytes)
  111. }), qt.Equals, 0.0)
  112. })
  113. c.Run("HashString", func(c *qt.C) {
  114. h := must.Get(New512(sha256.New()))
  115. c.Assert(testing.AllocsPerRun(100, func() {
  116. h.HashString("abcdefghijklmnopqrstuvwxyz")
  117. }), qt.Equals, 0.0)
  118. })
  119. }
  120. func Fuzz(f *testing.F) {
  121. f.Fuzz(func(t *testing.T, seed int64) {
  122. c := qt.New(t)
  123. execute := func(h hasher, r *rand.Rand) {
  124. for range r.Intn(256) {
  125. switch r.Intn(5) {
  126. case 0:
  127. n := uint8(r.Uint64())
  128. h.HashUint8(n)
  129. case 1:
  130. n := uint16(r.Uint64())
  131. h.HashUint16(n)
  132. case 2:
  133. n := uint32(r.Uint64())
  134. h.HashUint32(n)
  135. case 3:
  136. n := uint64(r.Uint64())
  137. h.HashUint64(n)
  138. case 4:
  139. b := make([]byte, r.Intn(256))
  140. r.Read(b)
  141. h.HashBytes(b)
  142. }
  143. }
  144. }
  145. r1 := rand.New(rand.NewSource(seed))
  146. r2 := rand.New(rand.NewSource(seed))
  147. h1 := must.Get(New512(sha256.New()))
  148. h2 := newNaive()
  149. execute(h1, r1)
  150. execute(h2, r2)
  151. c.Assert(h1.Sum(nil), qt.DeepEquals, h2.Sum(nil))
  152. execute(h1, r1)
  153. execute(h2, r2)
  154. c.Assert(h1.Sum(nil), qt.DeepEquals, h2.Sum(nil))
  155. h1.Reset()
  156. h2.Reset()
  157. execute(h1, r1)
  158. execute(h2, r2)
  159. c.Assert(h1.Sum(nil), qt.DeepEquals, h2.Sum(nil))
  160. })
  161. }
  162. func Benchmark(b *testing.B) {
  163. var sum [sha256.Size]byte
  164. b.Run("Hash", func(b *testing.B) {
  165. b.ReportAllocs()
  166. h := must.Get(New512(sha256.New()))
  167. for range b.N {
  168. h.Reset()
  169. hashSuite(h)
  170. h.Sum(sum[:0])
  171. }
  172. })
  173. b.Run("Naive", func(b *testing.B) {
  174. b.ReportAllocs()
  175. h := newNaive()
  176. for range b.N {
  177. h.Reset()
  178. hashSuite(h)
  179. h.Sum(sum[:0])
  180. }
  181. })
  182. }