blob: f418d5d851ce67d3f8d1e4e3a9a734ad32fb8b33 [file]
// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package encoding_test
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/apache/skywalking-banyandb/pkg/encoding"
)
func TestDecodeBytes(t *testing.T) {
src := [][]byte{
[]byte("Hello, "),
[]byte("world!"),
}
encoded := make([]byte, 0)
for _, d := range src {
encoded = encoding.EncodeBytes(encoded, d)
}
var decoded []byte
var err error
for i := range src {
encoded, decoded, err = encoding.DecodeBytes(encoded)
require.Nil(t, err)
assert.Equal(t, src[i], decoded)
}
}
func TestEncodeBlockAndDecode(t *testing.T) {
slices := [][]byte{
[]byte("Hello, "),
[]byte("world!"),
[]byte("Testing bytes"),
}
encoded := encoding.EncodeBytesBlock(nil, slices)
require.NotNil(t, encoded)
blockDecoder := &encoding.BytesBlockDecoder{}
decoded, err := blockDecoder.Decode(nil, encoded, uint64(len(slices)))
require.Nil(t, err)
for i, slice := range slices {
assert.Equal(t, slice, decoded[i])
}
}
func TestEncodeBytesBlockEdgeCases(t *testing.T) {
tests := []struct {
name string
input [][]byte
expected [][]byte
}{
{
name: "nil slice",
input: [][]byte{nil},
expected: [][]byte{nil},
},
{
name: "empty slice",
input: [][]byte{{}},
expected: [][]byte{{}},
},
{
name: "single byte",
input: [][]byte{[]byte("a")},
expected: [][]byte{[]byte("a")},
},
{
name: "mixed nil and empty",
input: [][]byte{nil, {}, nil},
expected: [][]byte{nil, {}, nil},
},
{
name: "mixed empty and content",
input: [][]byte{{}, []byte("hello"), {}},
expected: [][]byte{{}, []byte("hello"), {}},
},
{
name: "all nil",
input: [][]byte{nil, nil, nil},
expected: [][]byte{nil, nil, nil},
},
{
name: "all empty",
input: [][]byte{{}, {}, {}},
expected: [][]byte{{}, {}, {}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
encoded := encoding.EncodeBytesBlock(nil, tt.input)
require.NotNil(t, encoded)
blockDecoder := &encoding.BytesBlockDecoder{}
decoded, err := blockDecoder.Decode(nil, encoded, uint64(len(tt.input)))
require.Nil(t, err)
require.Len(t, decoded, len(tt.expected))
for i, expected := range tt.expected {
if expected == nil {
assert.Nil(t, decoded[i], "index %d should be nil", i)
} else {
assert.NotNil(t, decoded[i], "index %d should not be nil", i)
assert.Equal(t, expected, decoded[i], "index %d should match expected", i)
}
}
})
}
}
func TestEncodeBytesBlockRoundTripConsistency(t *testing.T) {
testCases := [][][]byte{
// Basic cases
{nil},
{[]byte{}},
{[]byte("a")},
{[]byte("hello world")},
// Mixed scenarios
{nil, []byte{}, []byte("a")},
{[]byte{}, nil, []byte("hello")},
{nil, []byte{}, nil, []byte("test"), []byte{}},
// Edge cases with different lengths
{[]byte(""), []byte("x"), []byte("xx"), []byte("xxx")},
{nil, nil, []byte{}, []byte{}, []byte("a"), []byte("ab")},
// Large content
{[]byte("this is a longer string for testing purposes")},
{nil, []byte("mixed with nil and empty"), []byte{}, []byte("another string")},
}
for i, original := range testCases {
t.Run(fmt.Sprintf("round_trip_%d", i), func(t *testing.T) {
// Encode
encoded := encoding.EncodeBytesBlock(nil, original)
require.NotNil(t, encoded)
// Decode
blockDecoder := &encoding.BytesBlockDecoder{}
decoded, err := blockDecoder.Decode(nil, encoded, uint64(len(original)))
require.Nil(t, err)
require.Len(t, decoded, len(original))
// Verify round-trip consistency
for j, orig := range original {
if orig == nil {
assert.Nil(t, decoded[j], "position %d: nil should remain nil", j)
} else {
assert.NotNil(t, decoded[j], "position %d: non-nil should remain non-nil", j)
assert.Equal(t, orig, decoded[j], "position %d: content should match", j)
}
}
// Test that we can encode the decoded result again and get the same encoded bytes
reEncoded := encoding.EncodeBytesBlock(nil, decoded)
assert.Equal(t, encoded, reEncoded, "re-encoding decoded result should produce identical bytes")
})
}
}
func TestEncodeBytesBlockNilEmptyStringDistinction(t *testing.T) {
t.Run("nil vs empty string distinction", func(t *testing.T) {
// Test that nil and empty string produce different encoded results
nilSlice := [][]byte{nil}
emptySlice := [][]byte{{}}
nilEncoded := encoding.EncodeBytesBlock(nil, nilSlice)
emptyEncoded := encoding.EncodeBytesBlock(nil, emptySlice)
// They should be different (nil uses 0, empty uses 1)
assert.NotEqual(t, nilEncoded, emptyEncoded)
// Decode and verify they decode to different results
blockDecoder := &encoding.BytesBlockDecoder{}
nilDecoded, err := blockDecoder.Decode(nil, nilEncoded, 1)
require.Nil(t, err)
assert.Len(t, nilDecoded, 1)
assert.Nil(t, nilDecoded[0])
blockDecoder.Reset()
emptyDecoded, err := blockDecoder.Decode(nil, emptyEncoded, 1)
require.Nil(t, err)
assert.Len(t, emptyDecoded, 1)
assert.NotNil(t, emptyDecoded[0])
assert.Equal(t, []byte{}, emptyDecoded[0])
})
}