Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

huffman_test.gno

5.75 Kb · 181 lines
  1package huffman
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"gno.land/p/nt/uassert/v0"
  8)
  9
 10// "abracadabra" is the worked example: five symbols, frequencies 5/2/2/1/1,
 11// and one optimal length vector (a=1, the other four=3) that no tie-break can
 12// change. The canonical codes below are therefore the only correct answer.
 13func TestCanonicalTableAbracadabra(t *testing.T) {
 14	codes, err := Table("abracadabra")
 15	uassert.NoError(t, err)
 16	uassert.Equal(t, 5, len(codes))
 17
 18	wantSymbols := "abcdr"
 19	wantBits := []string{"0", "100", "101", "110", "111"}
 20	for i, want := range wantBits {
 21		uassert.Equal(t, string(wantSymbols[i]), string(codes[i].Symbol))
 22		uassert.Equal(t, want, codes[i].String())
 23	}
 24}
 25
 26// The table is sorted by (length, symbol), which is what makes it a header a
 27// decoder can rebuild from the lengths alone.
 28func TestTableIsSortedByLengthThenSymbol(t *testing.T) {
 29	codes, err := Table("the quick brown fox jumps over the lazy dog")
 30	uassert.NoError(t, err)
 31	uassert.True(t, len(codes) > 20, "expected one code per distinct byte")
 32	for i := 1; i < len(codes); i++ {
 33		prev, cur := codes[i-1], codes[i]
 34		ok := prev.Len < cur.Len || (prev.Len == cur.Len && prev.Symbol < cur.Symbol)
 35		uassert.True(t, ok, "table out of order")
 36	}
 37}
 38
 39func TestEncodeBitStream(t *testing.T) {
 40	enc, err := Encode("abracadabra")
 41	uassert.NoError(t, err)
 42	// 5*1 + 2*3 + 2*3 + 1*3 + 1*3 = 23 bits, against 88 raw.
 43	uassert.Equal(t, 23, enc.NBits)
 44	uassert.Equal(t, 11, enc.Size)
 45	uassert.Equal(t, 3, len(enc.Data)) // 23 bits -> 3 bytes, 1 bit of padding
 46	uassert.Equal(t, "01001110101011001001110", enc.BitString())
 47}
 48
 49func TestRoundTrip(t *testing.T) {
 50	cases := []string{
 51		"a",
 52		"aa",
 53		"ab",
 54		"abracadabra",
 55		"hello world",
 56		"the quick brown fox jumps over the lazy dog",
 57		strings.Repeat("x", 1000),
 58		strings.Repeat("ab", 500),
 59		"\x00\x01\x02\xff\xfe",
 60	}
 61	for _, in := range cases {
 62		enc, err := Encode(in)
 63		uassert.NoError(t, err)
 64		got, err := enc.Decode()
 65		uassert.NoError(t, err)
 66		uassert.Equal(t, in, got)
 67	}
 68}
 69
 70// Every byte value exactly once: 256 symbols at equal frequency, so every code
 71// is 8 bits long and the encoding saves nothing. The flat case has to work
 72// before the skewed one means anything.
 73func TestAllByteValues(t *testing.T) {
 74	var b strings.Builder
 75	for i := 0; i < 256; i++ {
 76		b.WriteByte(byte(i))
 77	}
 78	in := b.String()
 79	enc, err := Encode(in)
 80	uassert.NoError(t, err)
 81	uassert.Equal(t, 256, len(enc.Codes))
 82	uassert.Equal(t, 2048, enc.NBits)
 83	for _, c := range enc.Codes {
 84		uassert.Equal(t, 8, c.Len)
 85	}
 86	got, err := enc.Decode()
 87	uassert.NoError(t, err)
 88	uassert.Equal(t, in, got)
 89}
 90
 91// A one-symbol alphabet has no sibling to pair its leaf with. The code is one
 92// bit long and the Kraft sum is 1/2, the only incomplete code this package
 93// emits.
 94func TestSingleSymbol(t *testing.T) {
 95	enc, err := Encode("aaaaa")
 96	uassert.NoError(t, err)
 97	uassert.Equal(t, 1, len(enc.Codes))
 98	uassert.Equal(t, 1, enc.Codes[0].Len)
 99	uassert.Equal(t, 5, enc.NBits)
100	uassert.Equal(t, "00000", enc.BitString())
101	got, err := enc.Decode()
102	uassert.NoError(t, err)
103	uassert.Equal(t, "aaaaa", got)
104}
105
106func TestEmptyInput(t *testing.T) {
107	_, err := Encode("")
108	uassert.ErrorIs(t, err, ErrEmpty)
109	_, err = Table("")
110	uassert.ErrorIs(t, err, ErrEmpty)
111}
112
113func TestTooLong(t *testing.T) {
114	_, err := Encode(strings.Repeat("a", MaxLen+1))
115	uassert.ErrorIs(t, err, ErrTooLong)
116}
117
118// Decode rebuilds the canonical codes from the lengths it is handed and
119// refuses a payload whose own Bits disagree, because a decoder that trusts
120// both fields returns a different string than the one that was encoded.
121func TestDecodeRejectsCorruptPayloads(t *testing.T) {
122	good, err := Encode("abracadabra")
123	uassert.NoError(t, err)
124
125	cases := []struct {
126		name   string
127		mutate func(e *Encoded)
128	}{
129		{"no codes", func(e *Encoded) { e.Codes = nil }},
130		{"zero length", func(e *Encoded) { e.Codes[0].Len = 0 }},
131		{"bits disagree with the canonical table", func(e *Encoded) { e.Codes[1].Bits = 7 }},
132		{"table out of order", func(e *Encoded) { e.Codes[0], e.Codes[1] = e.Codes[1], e.Codes[0] }},
133		{"duplicate symbol", func(e *Encoded) { e.Codes[2].Symbol = e.Codes[1].Symbol }},
134		{"duplicate symbol at another length", func(e *Encoded) { e.Codes[1].Symbol = e.Codes[0].Symbol }},
135		{"more bits than data", func(e *Encoded) { e.NBits = len(e.Data)*8 + 1 }},
136		{"truncated stream", func(e *Encoded) { e.NBits = 2 }},
137		{"size disagrees", func(e *Encoded) { e.Size = 3 }},
138		{"incomplete code", func(e *Encoded) { e.Codes = e.Codes[:4] }},
139		{"empty payload", func(e *Encoded) { e.NBits, e.Size = 0, 0 }},
140		{"padding bits are not zero", func(e *Encoded) { e.Data[len(e.Data)-1] |= 1 }},
141	}
142	for _, c := range cases {
143		e := &Encoded{
144			Codes: append([]Code(nil), good.Codes...),
145			Data:  append([]byte(nil), good.Data...),
146			NBits: good.NBits,
147			Size:  good.Size,
148		}
149		c.mutate(e)
150		_, err := e.Decode()
151		uassert.ErrorIs(t, err, ErrCorrupt, c.name)
152	}
153}
154
155// Same input, same table, every time: a Render built on this cannot reshuffle
156// between two identical calls.
157func TestDeterministic(t *testing.T) {
158	a, err := Encode("mississippi")
159	uassert.NoError(t, err)
160	b, err := Encode("mississippi")
161	uassert.NoError(t, err)
162	uassert.Equal(t, a.NBits, b.NBits)
163	uassert.Equal(t, a.BitString(), b.BitString())
164	uassert.Equal(t, len(a.Codes), len(b.Codes))
165	for i := range a.Codes {
166		uassert.Equal(t, a.Codes[i].String(), b.Codes[i].String())
167	}
168}
169
170// The skew is the whole point: a frequency-flat input saves little, a skewed
171// one saves most of its bits.
172func TestSaved(t *testing.T) {
173	flat, err := Encode("abcd")
174	uassert.NoError(t, err)
175	uassert.Equal(t, 8, flat.NBits) // four 2-bit codes
176	uassert.Equal(t, 24, flat.Saved())
177
178	skewed, err := Encode(strings.Repeat("a", 60) + "bcd")
179	uassert.NoError(t, err)
180	uassert.True(t, skewed.Saved() > 400, "a skewed input must save most of its bits")
181}