package astikit import ( "bytes" "fmt" "io" "reflect" "testing" ) func TestBitsWriter(t *testing.T) { // TODO Need to test LittleEndian bw := &bytes.Buffer{} cbBuf := bytes.Buffer{} w := NewBitsWriter(BitsWriterOptions{ Writer: bw, WriteCallback: func(i []byte) { cbBuf.Write(i) }, }) err := w.Write("000000") if err != nil { t.Fatalf("expected no error, got %+v", err) } if e, g := 0, bw.Len(); e != g { t.Fatalf("expected %d, got %d", e, g) } err = w.Write(false) if err != nil { t.Fatalf("expected no error, got %+v", err) } err = w.Write(true) if err != nil { t.Fatalf("expected no error, got %+v", err) } if e, g := []byte{1}, bw.Bytes(); !reflect.DeepEqual(e, g) { t.Fatalf("expected %+v, got %+v", e, g) } err = w.Write([]byte{2, 3}) if err != nil { t.Fatalf("expected no error, got %+v", err) } if e, g := []byte{1, 2, 3}, bw.Bytes(); !reflect.DeepEqual(e, g) { t.Fatalf("expected %+v, got %+v", e, g) } err = w.Write(uint8(4)) if err != nil { t.Fatalf("expected no error, got %+v", err) } if e, g := []byte{1, 2, 3, 4}, bw.Bytes(); !reflect.DeepEqual(e, g) { t.Fatalf("expected %+v, got %+v", e, g) } err = w.Write(uint16(5)) if err != nil { t.Fatalf("expected no error, got %+v", err) } if e, g := []byte{1, 2, 3, 4, 0, 5}, bw.Bytes(); !reflect.DeepEqual(e, g) { t.Fatalf("expected %+v, got %+v", e, g) } err = w.Write(uint32(6)) if err != nil { t.Fatalf("expected no error, got %+v", err) } if e, g := []byte{1, 2, 3, 4, 0, 5, 0, 0, 0, 6}, bw.Bytes(); !reflect.DeepEqual(e, g) { t.Fatalf("expected %+v, got %+v", e, g) } err = w.Write(uint64(7)) if err != nil { t.Fatalf("expected no error, got %+v", err) } if e, g := []byte{1, 2, 3, 4, 0, 5, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7}, bw.Bytes(); !reflect.DeepEqual(e, g) { t.Fatalf("expected %+v, got %+v", e, g) } if e, g := []byte{1, 2, 3, 4, 0, 5, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 7}, cbBuf.Bytes(); !reflect.DeepEqual(e, g) { t.Fatalf("callback buffer: expected %+v, got %+v", e, g) } err = w.Write(1) if err == nil { t.Fatal("expected error") } bw.Reset() err = w.WriteN(uint8(4), 3) if err != nil { t.Fatalf("expected no error, got %+v", err) } err = w.WriteN(uint16(4096), 13) if err != nil { t.Fatalf("expected no error, got %+v", err) } if e, g := []byte{144, 0}, bw.Bytes(); !reflect.DeepEqual(e, g) { t.Fatalf("expected %+v, got %+v", e, g) } } var bitsWriter_WriteBytesN_testCases = []struct { bs []byte n int expected []byte }{ {nil, 0, nil}, {[]byte{0x00}, 0, nil}, {nil, 3, []byte{0xff, 0xff, 0xff}}, {[]byte("en"), 3, []byte{'e', 'n', 0xff}}, {[]byte("eng"), 3, []byte{'e', 'n', 'g'}}, {[]byte("english"), 3, []byte{'e', 'n', 'g'}}, } func TestBitsWriter_WriteBytesN(t *testing.T) { padByte := uint8(0xff) for _, tt := range bitsWriter_WriteBytesN_testCases { t.Run(fmt.Sprintf("%v/%d", tt.bs, tt.n), func(t *testing.T) { buf := bytes.Buffer{} w := NewBitsWriter(BitsWriterOptions{Writer: &buf}) err := w.WriteBytesN(tt.bs, tt.n, padByte) if err != nil { t.Fatalf("expected no error, got %+v", err) } if !reflect.DeepEqual(tt.expected, buf.Bytes()) { t.Fatalf("expected %+v, got %+v", tt.expected, buf.Bytes()) } }) } } // testLimitedWriter is an implementation of io.Writer with max write size limit to test error handling type testLimitedWriter struct { BytesLimit int } func (t *testLimitedWriter) Write(p []byte) (n int, err error) { t.BytesLimit -= len(p) if t.BytesLimit >= 0 { return len(p), nil } return len(p) + t.BytesLimit, io.EOF } func TestNewBitsWriterBatch(t *testing.T) { wr := &testLimitedWriter{BytesLimit: 1} w := NewBitsWriter(BitsWriterOptions{Writer: wr}) b := NewBitsWriterBatch(w) b.Write(uint8(0)) if err := b.Err(); err != nil { t.Fatalf("expected no error, got %+v", err) } b.Write(uint8(1)) if err := b.Err(); err == nil { t.Fatalf("expected error, got %+v", err) } // let's check if the error is persisted b.Write(uint8(2)) if err := b.Err(); err == nil { t.Fatalf("expected error, got %+v", err) } } func BenchmarkBitsWriter_Write(b *testing.B) { benchmarks := []struct { input any }{ {"000000"}, {false}, {true}, {[]byte{2, 3}}, {uint8(4)}, {uint16(5)}, {uint32(6)}, {uint64(7)}, } bw := &bytes.Buffer{} bw.Grow(1024) w := NewBitsWriter(BitsWriterOptions{Writer: bw}) for _, bm := range benchmarks { b.Run(fmt.Sprintf("%#v", bm.input), func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { bw.Reset() w.Write(bm.input) //nolint:errcheck } }) } } func BenchmarkBitsWriter_WriteN(b *testing.B) { type benchData struct { i any n int } benchmarks := make([]benchData, 0, 128) for i := 1; i <= 8; i++ { benchmarks = append(benchmarks, benchData{uint8(0xff), i}) } for i := 1; i <= 16; i++ { benchmarks = append(benchmarks, benchData{uint16(0xffff), i}) } for i := 1; i <= 32; i++ { benchmarks = append(benchmarks, benchData{uint32(0xffffffff), i}) } for i := 1; i <= 64; i++ { benchmarks = append(benchmarks, benchData{uint64(0xffffffffffffffff), i}) } bw := &bytes.Buffer{} bw.Grow(1024) w := NewBitsWriter(BitsWriterOptions{Writer: bw}) for _, bm := range benchmarks { b.Run(fmt.Sprintf("%#v/%d", bm.i, bm.n), func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { bw.Reset() w.WriteN(bm.i, bm.n) //nolint:errcheck } }) } } func BenchmarkBitsWriter_WriteBytesN(b *testing.B) { bw := &bytes.Buffer{} bw.Grow(1024) w := NewBitsWriter(BitsWriterOptions{Writer: bw}) for _, bm := range bitsWriter_WriteBytesN_testCases { b.Run(fmt.Sprintf("%v/%d", bm.bs, bm.n), func(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { bw.Reset() w.WriteBytesN(bm.bs, bm.n, 0xff) //nolint:errcheck } }) } } func testByteHamming84Decode(i uint8) (o uint8, ok bool) { p1, d1, p2, d2, p3, d3, p4, d4 := i>>7&0x1, i>>6&0x1, i>>5&0x1, i>>4&0x1, i>>3&0x1, i>>2&0x1, i>>1&0x1, i&0x1 testA := p1^d1^d3^d4 > 0 testB := d1^p2^d2^d4 > 0 testC := d1^d2^p3^d3 > 0 testD := p1^d1^p2^d2^p3^d3^p4^d4 > 0 if testA && testB && testC { // p4 may be incorrect } else if testD && (!testA || !testB || !testC) { return } else { if !testA && testB && testC { // p1 is incorrect } else if testA && !testB && testC { // p2 is incorrect } else if testA && testB && !testC { // p3 is incorrect } else if !testA && !testB && testC { // d4 is incorrect d4 ^= 1 } else if testA && !testB && !testC { // d2 is incorrect d2 ^= 1 } else if !testA && testB && !testC { // d3 is incorrect d3 ^= 1 } else { // d1 is incorrect d1 ^= 1 } } o = uint8(d4<<3 | d3<<2 | d2<<1 | d1) ok = true return } func TestByteHamming84Decode(t *testing.T) { for i := 0; i < 256; i++ { v, okV := ByteHamming84Decode(uint8(i)) e, okE := testByteHamming84Decode(uint8(i)) if !okE { if okV { t.Fatal("expected false, got true") } } else { if !okV { t.Fatal("expected true, got false") } if !reflect.DeepEqual(e, v) { t.Fatalf("expected %+v, got %+v", e, v) } } } } func testByteParity(i uint8) bool { return (i&0x1)^(i>>1&0x1)^(i>>2&0x1)^(i>>3&0x1)^(i>>4&0x1)^(i>>5&0x1)^(i>>6&0x1)^(i>>7&0x1) > 0 } func TestByteParity(t *testing.T) { for i := 0; i < 256; i++ { v, okV := ByteParity(uint8(i)) okE := testByteParity(uint8(i)) if !okE { if okV { t.Fatal("expected false, got true") } } else { if !okV { t.Fatal("expected true, got false") } if e := uint8(i) & 0x7f; e != v { t.Fatalf("expected %+v, got %+v", e, v) } } } }