1package xxhash
2
3import (
4	"bytes"
5	"encoding/binary"
6	"fmt"
7	"strings"
8	"testing"
9)
10
11func TestAll(t *testing.T) {
12	for _, tt := range []struct {
13		name  string
14		input string
15		want  uint64
16	}{
17		{"empty", "", 0xef46db3751d8e999},
18		{"a", "a", 0xd24ec4f1a98c6e5b},
19		{"as", "as", 0x1c330fb2d66be179},
20		{"asd", "asd", 0x631c37ce72a97393},
21		{"asdf", "asdf", 0x415872f599cea71e},
22		{
23			"len=63",
24			// Exactly 63 characters, which exercises all code paths.
25			"Call me Ishmael. Some years ago--never mind how long precisely-",
26			0x02a2e85470d6fd96,
27		},
28	} {
29		for chunkSize := 1; chunkSize <= len(tt.input); chunkSize++ {
30			name := fmt.Sprintf("%s,chunkSize=%d", tt.name, chunkSize)
31			t.Run(name, func(t *testing.T) {
32				testDigest(t, tt.input, chunkSize, tt.want)
33			})
34		}
35		t.Run(tt.name, func(t *testing.T) { testSum(t, tt.input, tt.want) })
36	}
37}
38
39func testDigest(t *testing.T, input string, chunkSize int, want uint64) {
40	d := New()
41	ds := New() // uses WriteString
42	for i := 0; i < len(input); i += chunkSize {
43		chunk := input[i:]
44		if len(chunk) > chunkSize {
45			chunk = chunk[:chunkSize]
46		}
47		n, err := d.Write([]byte(chunk))
48		if err != nil || n != len(chunk) {
49			t.Fatalf("Digest.Write: got (%d, %v); want (%d, nil)", n, err, len(chunk))
50		}
51		n, err = ds.WriteString(chunk)
52		if err != nil || n != len(chunk) {
53			t.Fatalf("Digest.WriteString: got (%d, %v); want (%d, nil)", n, err, len(chunk))
54		}
55	}
56	if got := d.Sum64(); got != want {
57		t.Fatalf("Digest.Sum64: got 0x%x; want 0x%x", got, want)
58	}
59	if got := ds.Sum64(); got != want {
60		t.Fatalf("Digest.Sum64 (WriteString): got 0x%x; want 0x%x", got, want)
61	}
62	var b [8]byte
63	binary.BigEndian.PutUint64(b[:], want)
64	if got := d.Sum(nil); !bytes.Equal(got, b[:]) {
65		t.Fatalf("Sum: got %v; want %v", got, b[:])
66	}
67}
68
69func testSum(t *testing.T, input string, want uint64) {
70	if got := Sum64([]byte(input)); got != want {
71		t.Fatalf("Sum64: got 0x%x; want 0x%x", got, want)
72	}
73	if got := Sum64String(input); got != want {
74		t.Fatalf("Sum64String: got 0x%x; want 0x%x", got, want)
75	}
76}
77
78func TestReset(t *testing.T) {
79	parts := []string{"The quic", "k br", "o", "wn fox jumps", " ov", "er the lazy ", "dog."}
80	d := New()
81	for _, part := range parts {
82		d.Write([]byte(part))
83	}
84	h0 := d.Sum64()
85
86	d.Reset()
87	d.Write([]byte(strings.Join(parts, "")))
88	h1 := d.Sum64()
89
90	if h0 != h1 {
91		t.Errorf("0x%x != 0x%x", h0, h1)
92	}
93}
94
95func TestBinaryMarshaling(t *testing.T) {
96	d := New()
97	d.WriteString("abc")
98	b, err := d.MarshalBinary()
99	if err != nil {
100		t.Fatal(err)
101	}
102	d = New()
103	d.WriteString("junk")
104	if err := d.UnmarshalBinary(b); err != nil {
105		t.Fatal(err)
106	}
107	d.WriteString("def")
108	if got, want := d.Sum64(), Sum64String("abcdef"); got != want {
109		t.Fatalf("after MarshalBinary+UnmarshalBinary, got 0x%x; want 0x%x", got, want)
110	}
111
112	d0 := New()
113	d1 := New()
114	for i := 0; i < 64; i++ {
115		b, err := d0.MarshalBinary()
116		if err != nil {
117			t.Fatal(err)
118		}
119		d0 = new(Digest)
120		if err := d0.UnmarshalBinary(b); err != nil {
121			t.Fatal(err)
122		}
123		if got, want := d0.Sum64(), d1.Sum64(); got != want {
124			t.Fatalf("after %d Writes, unmarshaled Digest gave sum 0x%x; want 0x%x", i, got, want)
125		}
126
127		d0.Write([]byte{'a'})
128		d1.Write([]byte{'a'})
129	}
130}
131
132var sink uint64
133
134func TestAllocs(t *testing.T) {
135	const shortStr = "abcdefghijklmnop"
136	// Sum64([]byte(shortString)) shouldn't allocate because the
137	// intermediate []byte ought not to escape.
138	// (See https://github.com/cespare/xxhash/pull/2.)
139	t.Run("Sum64", func(t *testing.T) {
140		testAllocs(t, func() {
141			sink = Sum64([]byte(shortStr))
142		})
143	})
144	// Creating and using a Digest shouldn't allocate because its methods
145	// shouldn't make it escape. (A previous version of New returned a
146	// hash.Hash64 which forces an allocation.)
147	t.Run("Digest", func(t *testing.T) {
148		b := []byte("asdf")
149		testAllocs(t, func() {
150			d := New()
151			d.Write(b)
152			sink = d.Sum64()
153		})
154	})
155}
156
157func testAllocs(t *testing.T, fn func()) {
158	t.Helper()
159	if allocs := int(testing.AllocsPerRun(10, fn)); allocs > 0 {
160		t.Fatalf("got %d allocation(s) (want zero)", allocs)
161	}
162}
163