blob: 0b2f7dee9d7ce5380325266ea12b862ac106770d [file] [log] [blame]
// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed 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 fountain
import (
"bytes"
"reflect"
"testing"
)
func TestBlockLength(t *testing.T) {
var lengthTests = []struct {
b block
len int
}{
{block{}, 0},
{block{[]byte{1, 0, 1}, 0}, 3},
{block{[]byte{1, 0, 1}, 1}, 4},
}
for _, i := range lengthTests {
if i.b.length() != i.len {
t.Errorf("Length of b is %d, should be %d", i.b.length(), i.len)
}
if (i.len == 0) != i.b.empty() {
t.Errorf("Emptiness check error. Got %v, want %v", i.b.empty(), i.len == 0)
}
}
}
func TestBlockXor(t *testing.T) {
var xorTests = []struct {
a block
b block
out block
}{
{block{[]byte{1, 0, 1}, 0}, block{[]byte{1, 1, 1}, 0}, block{[]byte{0, 1, 0}, 0}},
{block{[]byte{1}, 0}, block{[]byte{0, 14, 6}, 0}, block{[]byte{1, 14, 6}, 0}},
{block{}, block{[]byte{100, 200}, 0}, block{[]byte{100, 200}, 0}},
{block{[]byte{}, 5}, block{[]byte{0, 1, 0}, 0}, block{[]byte{0, 1, 0}, 2}},
{block{[]byte{}, 5}, block{[]byte{0, 1, 0, 2, 3}, 0}, block{[]byte{0, 1, 0, 2, 3}, 0}},
{block{[]byte{}, 5}, block{[]byte{0, 1, 0, 2, 3, 7}, 0}, block{[]byte{0, 1, 0, 2, 3, 7}, 0}},
{block{[]byte{1}, 4}, block{[]byte{0, 1, 0, 2, 3, 7}, 0}, block{[]byte{1, 1, 0, 2, 3, 7}, 0}},
}
for _, i := range xorTests {
t.Logf("...Testing %v XOR %v", i.a, i.b)
originalLength := i.a.length()
i.a.xor(i.b)
if i.a.length() < originalLength {
t.Errorf("Length shrunk. Got %d, want length >= %d", i.a.length(), originalLength)
}
if len(i.a.data) != len(i.b.data) {
t.Errorf("a and b data should be same length after xor. a len=%d, b len=%d", len(i.a.data), len(i.b.data))
}
if !bytes.Equal(i.a.data, i.out.data) {
t.Errorf("XOR value is %v : should be %v", i.a.data, i.out.data)
}
}
}
func TestPartitionBytes(t *testing.T) {
a := make([]byte, 100)
for i := 0; i < len(a); i++ {
a[i] = byte(i)
}
var partitionTests = []struct {
numPartitions int
lenLong, lenShort int
}{
{11, 1, 10},
{3, 1, 2},
}
for _, i := range partitionTests {
t.Logf("Partitioning %v into %d", a, i.numPartitions)
long, short := partitionBytes(a, i.numPartitions)
if len(long) != i.lenLong {
t.Errorf("Got %d long blocks, should have %d", len(long), i.lenLong)
}
if len(short) != i.lenShort {
t.Errorf("Got %d short blocks, should have %d", len(short), i.lenShort)
}
if short[len(short)-1].padding != 0 {
t.Errorf("Should fit blocks exactly, have last padding %d", short[len(short)-1].padding)
}
if long[0].data[0] != 0 {
t.Errorf("Long block should be first. First value is %v", long[0].data)
}
}
}
func TestEqualizeBlockLengths(t *testing.T) {
b := []byte("abcdefghijklmnopq")
var equalizeTests = []struct {
numPartitions int
length int
padding int
}{
{1, 17, 0},
{2, 9, 1},
{3, 6, 1},
{4, 5, 1},
{5, 4, 1},
{6, 3, 1},
{7, 3, 1},
{8, 3, 1},
{9, 2, 1},
{10, 2, 1},
{16, 2, 1},
{17, 1, 0},
}
for _, i := range equalizeTests {
long, short := partitionBytes(b, i.numPartitions)
blocks := equalizeBlockLengths(long, short)
if len(blocks) != i.numPartitions {
t.Errorf("Got %d blocks, should have %d", len(blocks), i.numPartitions)
}
for k := range blocks {
if blocks[k].length() != i.length {
t.Errorf("Got block length %d for block %d, should be %d",
blocks[0].length(), k, i.length)
}
}
if blocks[len(blocks)-1].padding != i.padding {
t.Errorf("Padding of last block is %d, should be %d",
blocks[len(blocks)-1].padding, i.padding)
}
}
}
func printMatrix(m sparseMatrix, t *testing.T) {
t.Log("------- matrix -----------")
for i := range m.coeff {
t.Logf("%v = %v\n", m.coeff[i], m.v[i].data)
}
}
func TestMatrixXorRow(t *testing.T) {
var xorRowTests = []struct {
arow []int
r []int
result []int
}{
{[]int{0, 1}, []int{2, 3}, []int{0, 1, 2, 3}},
{[]int{0, 1}, []int{1, 2, 3}, []int{0, 2, 3}},
{[]int{}, []int{1, 2, 3}, []int{1, 2, 3}},
{[]int{1, 2, 3}, []int{}, []int{1, 2, 3}},
{[]int{1}, []int{2}, []int{1, 2}},
{[]int{1}, []int{1}, []int{}},
{[]int{1, 2}, []int{1, 2, 3, 4}, []int{3, 4}},
{[]int{3, 4}, []int{1, 2, 3, 4}, []int{1, 2}},
{[]int{1, 2, 3, 4}, []int{1, 2}, []int{3, 4}},
{[]int{0, 1, 2, 3, 4}, []int{1, 2}, []int{0, 3, 4}},
{[]int{3, 4}, []int{1, 2, 3, 4, 5}, []int{1, 2, 5}},
{[]int{3, 4, 8}, []int{1, 2, 3, 4, 5}, []int{1, 2, 5, 8}},
}
for _, test := range xorRowTests {
m := sparseMatrix{coeff: [][]int{test.arow}, v: []block{block{[]byte{1}, 0}}}
testb := block{[]byte{2}, 0}
test.r, testb = m.xorRow(0, test.r, testb)
// Needed since under DeepEqual the nil and the empty slice are not equal.
if test.r == nil {
test.r = make([]int, 0)
}
if !reflect.DeepEqual(test.r, test.result) {
t.Errorf("XOR row result got %v, should be %v", test.r, test.result)
}
if !reflect.DeepEqual(testb, block{[]byte{3}, 0}) {
t.Errorf("XOR row block got %v, should be %v", testb, block{[]byte{3}, 0})
}
}
}
func TestMatrixBasic(t *testing.T) {
m := sparseMatrix{coeff: [][]int{{}, {}}, v: []block{block{}, block{}}}
m.addEquation([]int{0}, block{data: []byte{1}})
if m.determined() {
t.Errorf("2-row matrix should not be determined after 1 equation")
printMatrix(m, t)
}
m.addEquation([]int{0, 1}, block{data: []byte{2}})
if !m.determined() {
t.Errorf("2-row matrix should be determined after 2 equations")
printMatrix(m, t)
}
printMatrix(m, t)
if !reflect.DeepEqual(m.coeff[0], []int{0}) ||
!reflect.DeepEqual(m.v[0].data, []byte{1}) {
t.Errorf("Equation 0 got (%v = %v), want ([0] = [1])", m.coeff[0], m.v[0].data)
}
if !reflect.DeepEqual(m.coeff[1], []int{1}) ||
!reflect.DeepEqual(m.v[1].data, []byte{3}) {
t.Errorf("Equation 1 got (%v = %v), want ([1] = [3])", m.coeff[0], m.v[0].data)
}
m.reduce()
if !reflect.DeepEqual(m.coeff[0], []int{0}) ||
!reflect.DeepEqual(m.v[0].data, []byte{1}) {
t.Errorf("Equation 0 got (%v = %v), want ([0] = [1])", m.coeff[0], m.v[0].data)
}
if !reflect.DeepEqual(m.coeff[1], []int{1}) ||
!reflect.DeepEqual(m.v[1].data, []byte{3}) {
t.Errorf("Equation 1 got (%v = %v), want ([1] = [3])", m.coeff[0], m.v[0].data)
}
}
func TestMatrixLarge(t *testing.T) {
m := sparseMatrix{coeff: make([][]int, 4), v: make([]block, 4)}
m.addEquation([]int{2, 3}, block{data: []byte{1}})
m.addEquation([]int{2}, block{data: []byte{2}})
if m.determined() {
t.Errorf("4-row matrix should not be determined after 2 equations")
printMatrix(m, t)
}
printMatrix(m, t)
// Should have triangular entries in {2} and {3} now.
if len(m.coeff[2]) != 1 || m.v[2].data[0] != 2 {
t.Errorf("Equation 2 got %v = %v, should be [2] = [2]", m.coeff[2], m.v[2])
}
if len(m.coeff[3]) != 1 || m.v[3].data[0] != 3 {
t.Errorf("Equation 3 got %v = %v, should be [3] = [3]", m.coeff[3], m.v[3])
}
if len(m.coeff[0]) != 0 || len(m.coeff[1]) != 0 {
t.Errorf("Equations 0 and 1 should be empty")
printMatrix(m, t)
}
m.addEquation([]int{0, 1, 2, 3}, block{data: []byte{4}})
if m.determined() {
t.Errorf("4-row matrix should not be determined after 3 equations")
printMatrix(m, t)
}
m.addEquation([]int{3}, block{data: []byte{3}})
if m.determined() {
t.Errorf("4-row matrix should not be determined after redundant equation")
printMatrix(m, t)
}
m.addEquation([]int{0, 2}, block{data: []byte{8}})
if !m.determined() {
t.Errorf("4-row matrix should be determined after 4 equations")
printMatrix(m, t)
}
// The matrix should now have entries in rows 0 and 1, but not equal to the
// original equations.
printMatrix(m, t)
if !reflect.DeepEqual(m.coeff[0], []int{0, 2}) {
t.Errorf("Got %v for coeff[0], expect [0, 2]", m.coeff[0])
}
if !reflect.DeepEqual(m.coeff[1], []int{1, 3}) {
t.Errorf("Got %v for coeff[1], expect [1, 3]", m.coeff[1])
}
}