packages feed

reedsolomon-0.0.1.0: test/ReedSolomon.lhs

> {-# LANGUAGE OverloadedLists #-}
> module ReedSolomon (tests) where
>
> import Control.Exception.Base (catch, fromException, throwIO, try)
> import Control.Monad (void)
>
> import System.Random (mkStdGen, randoms)
>
> import Test.Tasty (TestTree, testGroup)
> import Test.Tasty.HUnit (Assertion, (@?=), assertBool, assertFailure, testCase)
> import Test.Tasty.QuickCheck (testProperty)
>
> import Test.QuickCheck (Gen, Positive(Positive), arbitrary, choose, vectorOf)
>
> import qualified Data.Vector.Generic as V
>
> import qualified Data.ReedSolomon as RS

/**
 * Unit tests for ReedSolomon
 *
 * Copyright 2015, Klaus Post
 * Copyright 2015, Backblaze, Inc.  All rights reserved.
 */

package reedsolomon

import (
	"bytes"
	"fmt"
	"math/rand"
	"runtime"
	"testing"
)

func TestEncoding(t *testing.T) {
	perShard := 50000
	r, err := New(10, 3)
	if err != nil {
		t.Fatal(err)
	}
	shards := make([][]byte, 13)
	for s := range shards {
		shards[s] = make([]byte, perShard)
	}

	rand.Seed(0)
	for s := 0; s < 13; s++ {
		fillRandom(shards[s])
	}

	err = r.Encode(shards)
	if err != nil {
		t.Fatal(err)
	}
	ok, err := r.Verify(shards)
	if err != nil {
		t.Fatal(err)
	}
	if !ok {
		t.Fatal("Verification failed")
	}

	err = r.Encode(make([][]byte, 1))
	if err != ErrTooFewShards {
		t.Errorf("expected %v, got %v", ErrTooFewShards, err)
	}

	badShards := make([][]byte, 13)
	badShards[0] = make([]byte, 1)
	err = r.Encode(badShards)
	if err != ErrShardSize {
		t.Errorf("expected %v, got %v", ErrShardSize, err)
	}
}

> testEncoding :: Assertion
> testEncoding = do
>     let perShard = 50000
>     r <- RS.new 10 3
>     let randoms' = randoms $ mkStdGen 0
>         shards = flip (V.unfoldrN 10) randoms' $ \s ->
>                     let (h, t) = splitAt perShard s in
>                     Just (V.fromListN perShard h, t)
>
>     parities <- RS.encode r shards
>     let allChunks = (V.++) shards parities
>
>     assertBool "Verification failed" =<< RS.verify r allChunks
>
>     catch
>         (void $ RS.encode r [[]])
>         (\(RS.InvalidNumberOfShards RS.DataShard 1) -> return ())
>
>     catch
>         (void $ RS.encode r (V.fromListN 13 ([1] : replicate 12 [])))
>         (\(RS.InvalidNumberOfShards RS.DataShard 13) -> return ())

func TestReconstruct(t *testing.T) {
	perShard := 50000
	r, err := New(10, 3)
	if err != nil {
		t.Fatal(err)
	}
	shards := make([][]byte, 13)
	for s := range shards {
		shards[s] = make([]byte, perShard)
	}

	rand.Seed(0)
	for s := 0; s < 13; s++ {
		fillRandom(shards[s])
	}

	err = r.Encode(shards)
	if err != nil {
		t.Fatal(err)
	}

	// Reconstruct with all shards present
	err = r.Reconstruct(shards)
	if err != nil {
		t.Fatal(err)
	}

	// Reconstruct with 10 shards present
	shards[0] = nil
	shards[7] = nil
	shards[11] = nil

	err = r.Reconstruct(shards)
	if err != nil {
		t.Fatal(err)
	}

	ok, err := r.Verify(shards)
	if err != nil {
		t.Fatal(err)
	}
	if !ok {
		t.Fatal("Verification failed")
	}

	// Reconstruct with 9 shards present (should fail)
	shards[0] = nil
	shards[4] = nil
	shards[7] = nil
	shards[11] = nil

	err = r.Reconstruct(shards)
	if err != ErrTooFewShards {
		t.Errorf("expected %v, got %v", ErrTooFewShards, err)
	}

	err = r.Reconstruct(make([][]byte, 1))
	if err != ErrTooFewShards {
		t.Errorf("expected %v, got %v", ErrTooFewShards, err)
	}
	err = r.Reconstruct(make([][]byte, 13))
	if err != ErrShardNoData {
		t.Errorf("expected %v, got %v", ErrShardNoData, err)
	}
}

> testReconstruct :: Assertion
> testReconstruct = do
>     let perShard = 50000
>     r <- RS.new 10 3
>     let randoms' = randoms $ mkStdGen 0
>         shards = flip (V.unfoldrN 10) randoms' $ \s ->
>                     let (h, t) = splitAt perShard s in
>                     Just (V.fromListN perShard h, t)
>     parities <- RS.encode r shards
>     let toReconstruct = V.map Just ((V.++) shards parities)
>     all' <- RS.reconstruct r toReconstruct
>
>     all' @?= (V.++) shards parities
>
>     shards' <- RS.reconstruct r $ (V.//) toReconstruct [ (0, Nothing)
>                                                        , (7, Nothing)
>                                                        , (11, Nothing)
>                                                        ]
>     verified <- RS.verify r shards'
>     verified @?= True
>
>     catch
>         (do
>             _ <- RS.reconstruct r $ (V.//) toReconstruct [ (0, Nothing)
>                                                          , (4, Nothing)
>                                                          , (7, Nothing)
>                                                          , (11, Nothing)
>                                                          ]
>             assertFailure "Expected 'Too few shards'")
>         (\(RS.InvalidNumberOfShards RS.AnyShard 9) -> return ())
>
>     catch
>         (do
>             _ <- RS.reconstruct r [Just []]
>             assertFailure "Expected 'Too few shards'")
>         (\(RS.InvalidNumberOfShards RS.AnyShard 1) -> return ())
>
>     catch
>         (do
>             _ <- RS.reconstruct r (V.replicate 13 Nothing)
>             assertFailure "Expected 'No shard data'")
>         (\RS.EmptyShards -> return ())

func TestVerify(t *testing.T) {
	perShard := 33333
	r, err := New(10, 4)
	if err != nil {
		t.Fatal(err)
	}
	shards := make([][]byte, 14)
	for s := range shards {
		shards[s] = make([]byte, perShard)
	}

	rand.Seed(0)
	for s := 0; s < 10; s++ {
		fillRandom(shards[s])
	}

	err = r.Encode(shards)
	if err != nil {
		t.Fatal(err)
	}
	ok, err := r.Verify(shards)
	if err != nil {
		t.Fatal(err)
	}
	if !ok {
		t.Fatal("Verification failed")
	}

	// Put in random data. Verification should fail
	fillRandom(shards[10])
	ok, err = r.Verify(shards)
	if err != nil {
		t.Fatal(err)
	}
	if ok {
		t.Fatal("Verification did not fail")
	}
	// Re-encode
	err = r.Encode(shards)
	if err != nil {
		t.Fatal(err)
	}
	// Fill a data segment with random data
	fillRandom(shards[0])
	ok, err = r.Verify(shards)
	if err != nil {
		t.Fatal(err)
	}
	if ok {
		t.Fatal("Verification did not fail")
	}

	_, err = r.Verify(make([][]byte, 1))
	if err != ErrTooFewShards {
		t.Errorf("expected %v, got %v", ErrTooFewShards, err)
	}

	_, err = r.Verify(make([][]byte, 14))
	if err != ErrShardNoData {
		t.Errorf("expected %v, got %v", ErrShardNoData, err)
	}
}

> testVerify :: Assertion
> testVerify = do
>     let perShard = 33333
>     r <- RS.new 10 4
>     let randoms' = randoms $ mkStdGen 0
>         dataShards = flip (V.unfoldrN 10) randoms' $ \s ->
>                         let (h, t) = splitAt perShard s in
>                         Just (V.fromListN perShard h, t)
>
>     parityShards <- RS.encode r dataShards
>     let shards = (V.++) dataShards parityShards
>
>     assertBool "Verification failed" =<< RS.verify r shards
>
>     let shards' = V.update shards [(10, V.replicate perShard 0)]
>
>     assertBool "Verification did not fail" =<< not `fmap` RS.verify r shards'
>
>     let shards'' = V.update shards [(0, V.replicate perShard 0)]
>
>     assertBool "Verification did not fail" =<< not `fmap` RS.verify r shards''
>
>     catch
>         (void $ RS.verify r [[]])
>         (\(RS.InvalidNumberOfShards RS.DataShard 1) -> return ())
>
>     catch
>         (void $ RS.verify r (V.replicate 14 []))
>         (\RS.EmptyShards -> return ())

func TestOneEncode(t *testing.T) {
	codec, err := New(5, 5)
	if err != nil {
		t.Fatal(err)
	}
	shards := [][]byte{
		{0, 1},
		{4, 5},
		{2, 3},
		{6, 7},
		{8, 9},
		{0, 0},
		{0, 0},
		{0, 0},
		{0, 0},
		{0, 0},
	}
	codec.Encode(shards)
	if shards[5][0] != 12 || shards[5][1] != 13 {
		t.Fatal("shard 5 mismatch")
	}
	if shards[6][0] != 10 || shards[6][1] != 11 {
		t.Fatal("shard 6 mismatch")
	}
	if shards[7][0] != 14 || shards[7][1] != 15 {
		t.Fatal("shard 7 mismatch")
	}
	if shards[8][0] != 90 || shards[8][1] != 91 {
		t.Fatal("shard 8 mismatch")
	}
	if shards[9][0] != 94 || shards[9][1] != 95 {
		t.Fatal("shard 9 mismatch")
	}

	ok, err := codec.Verify(shards)
	if err != nil {
		t.Fatal(err)
	}
	if !ok {
		t.Fatal("did not verify")
	}
	shards[8][0]++
	ok, err = codec.Verify(shards)
	if err != nil {
		t.Fatal(err)
	}
	if ok {
		t.Fatal("verify did not fail as expected")
	}

}

> testOneEncode :: Assertion
> testOneEncode = do
>     codec <- RS.new 5 5
>     let shards = [ [0, 1]
>                  , [4, 5]
>                  , [2, 3]
>                  , [6, 7]
>                  , [8, 9]
>                  ]
>     parity <- RS.encode codec shards
>     let expected = [ [12, 13]
>                    , [10, 11]
>                    , [14, 15]
>                    , [90, 91]
>                    , [94, 95]
>                    ]
>     parity @?= expected
>
>     let allShards = (V.++) shards expected
>
>     verified <- RS.verify codec allShards
>     verified @?= True
>
>     let allShards' = (V.//) allShards [(8, [91, 91])]
>
>     verified' <- RS.verify codec allShards'
>     verified' @?= False

func fillRandom(b []byte) {
	for i := range b {
		b[i] = byte(rand.Int() & 0xff)
	}
}

func benchmarkEncode(b *testing.B, dataShards, parityShards, shardSize int) {
	r, err := New(dataShards, parityShards)
	if err != nil {
		b.Fatal(err)
	}
	shards := make([][]byte, dataShards+parityShards)
	for s := range shards {
		shards[s] = make([]byte, shardSize)
	}

	rand.Seed(0)
	for s := 0; s < dataShards; s++ {
		fillRandom(shards[s])
	}

	b.SetBytes(int64(shardSize * dataShards))
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		err = r.Encode(shards)
		if err != nil {
			b.Fatal(err)
		}
	}
}

func BenchmarkEncode10x2x10000(b *testing.B) {
	benchmarkEncode(b, 10, 2, 10000)
}

func BenchmarkEncode100x20x10000(b *testing.B) {
	benchmarkEncode(b, 100, 20, 10000)
}

func BenchmarkEncode17x3x1M(b *testing.B) {
	benchmarkEncode(b, 17, 3, 1024*1024)
}

// Benchmark 10 data shards and 4 parity shards with 16MB each.
func BenchmarkEncode10x4x16M(b *testing.B) {
	benchmarkEncode(b, 10, 4, 16*1024*1024)
}

// Benchmark 5 data shards and 2 parity shards with 1MB each.
func BenchmarkEncode5x2x1M(b *testing.B) {
	benchmarkEncode(b, 5, 2, 1024*1024)
}

// Benchmark 1 data shards and 2 parity shards with 1MB each.
func BenchmarkEncode10x2x1M(b *testing.B) {
	benchmarkEncode(b, 10, 2, 1024*1024)
}

// Benchmark 10 data shards and 4 parity shards with 1MB each.
func BenchmarkEncode10x4x1M(b *testing.B) {
	benchmarkEncode(b, 10, 4, 1024*1024)
}

// Benchmark 50 data shards and 20 parity shards with 1MB each.
func BenchmarkEncode50x20x1M(b *testing.B) {
	benchmarkEncode(b, 50, 20, 1024*1024)
}

// Benchmark 17 data shards and 3 parity shards with 16MB each.
func BenchmarkEncode17x3x16M(b *testing.B) {
	benchmarkEncode(b, 17, 3, 16*1024*1024)
}

func benchmarkVerify(b *testing.B, dataShards, parityShards, shardSize int) {
	r, err := New(dataShards, parityShards)
	if err != nil {
		b.Fatal(err)
	}
	shards := make([][]byte, parityShards+dataShards)
	for s := range shards {
		shards[s] = make([]byte, shardSize)
	}

	rand.Seed(0)
	for s := 0; s < dataShards; s++ {
		fillRandom(shards[s])
	}
	err = r.Encode(shards)
	if err != nil {
		b.Fatal(err)
	}

	b.SetBytes(int64(shardSize * dataShards))
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, err = r.Verify(shards)
		if err != nil {
			b.Fatal(err)
		}
	}
}

// Benchmark 10 data slices with 2 parity slices holding 10000 bytes each
func BenchmarkVerify10x2x10000(b *testing.B) {
	benchmarkVerify(b, 10, 2, 10000)
}

// Benchmark 50 data slices with 5 parity slices holding 100000 bytes each
func BenchmarkVerify50x5x50000(b *testing.B) {
	benchmarkVerify(b, 50, 5, 100000)
}

// Benchmark 10 data slices with 2 parity slices holding 1MB bytes each
func BenchmarkVerify10x2x1M(b *testing.B) {
	benchmarkVerify(b, 10, 2, 1024*1024)
}

// Benchmark 5 data slices with 2 parity slices holding 1MB bytes each
func BenchmarkVerify5x2x1M(b *testing.B) {
	benchmarkVerify(b, 5, 2, 1024*1024)
}

// Benchmark 10 data slices with 4 parity slices holding 1MB bytes each
func BenchmarkVerify10x4x1M(b *testing.B) {
	benchmarkVerify(b, 10, 4, 1024*1024)
}

// Benchmark 5 data slices with 2 parity slices holding 1MB bytes each
func BenchmarkVerify50x20x1M(b *testing.B) {
	benchmarkVerify(b, 50, 20, 1024*1024)
}

// Benchmark 10 data slices with 4 parity slices holding 16MB bytes each
func BenchmarkVerify10x4x16M(b *testing.B) {
	benchmarkVerify(b, 10, 4, 16*1024*1024)
}

// Simple example of how to use all functions of the Encoder.
// Note that all error checks have been removed to keep it short.
func ExampleEncoder() {
	// Create some sample data
	var data = make([]byte, 250000)
	fillRandom(data)

	// Create an encoder with 17 data and 3 parity slices.
	enc, _ := New(17, 3)

	// Split the data into shards
	shards, _ := enc.Split(data)

	// Encode the parity set
	_ = enc.Encode(shards)

	// Verify the parity set
	ok, _ := enc.Verify(shards)
	if ok {
		fmt.Println("ok")
	}

	// Delete two shards
	shards[10], shards[11] = nil, nil

	// Reconstruct the shards
	_ = enc.Reconstruct(shards)

	// Verify the data set
	ok, _ = enc.Verify(shards)
	if ok {
		fmt.Println("ok")
	}
	// Output: ok
	// ok
}

// This demonstrates that shards can be arbitrary sliced and
// merged and still remain valid.
func ExampleEncoder_slicing() {
	// Create some sample data
	var data = make([]byte, 250000)
	fillRandom(data)

	// Create 5 data slices of 50000 elements each
	enc, _ := New(5, 3)
	shards, _ := enc.Split(data)
	err := enc.Encode(shards)
	if err != nil {
		panic(err)
	}

	// Check that it verifies
	ok, err := enc.Verify(shards)
	if ok && err == nil {
		fmt.Println("encode ok")
	}

	// Split the data set of 50000 elements into two of 25000
	splitA := make([][]byte, 8)
	splitB := make([][]byte, 8)

	// Merge into a 100000 element set
	merged := make([][]byte, 8)

	// Split/merge the shards
	for i := range shards {
		splitA[i] = shards[i][:25000]
		splitB[i] = shards[i][25000:]

		// Concencate it to itself
		merged[i] = append(make([]byte, 0, len(shards[i])*2), shards[i]...)
		merged[i] = append(merged[i], shards[i]...)
	}

	// Each part should still verify as ok.
	ok, err = enc.Verify(shards)
	if ok && err == nil {
		fmt.Println("splitA ok")
	}

	ok, err = enc.Verify(splitB)
	if ok && err == nil {
		fmt.Println("splitB ok")
	}

	ok, err = enc.Verify(merged)
	if ok && err == nil {
		fmt.Println("merge ok")
	}
	// Output: encode ok
	// splitA ok
	// splitB ok
	// merge ok
}

// This demonstrates that shards can xor'ed and
// still remain a valid set.
//
// The xor value must be the same for element 'n' in each shard,
// except if you xor with a similar sized encoded shard set.
func ExampleEncoder_xor() {
	// Create some sample data
	var data = make([]byte, 25000)
	fillRandom(data)

	// Create 5 data slices of 5000 elements each
	enc, _ := New(5, 3)
	shards, _ := enc.Split(data)
	err := enc.Encode(shards)
	if err != nil {
		panic(err)
	}

	// Check that it verifies
	ok, err := enc.Verify(shards)
	if !ok || err != nil {
		fmt.Println("falied initial verify", err)
	}

	// Create an xor'ed set
	xored := make([][]byte, 8)

	// We xor by the index, so you can see that the xor can change,
	// It should however be constant vertically through your slices.
	for i := range shards {
		xored[i] = make([]byte, len(shards[i]))
		for j := range xored[i] {
			xored[i][j] = shards[i][j] ^ byte(j&0xff)
		}
	}

	// Each part should still verify as ok.
	ok, err = enc.Verify(xored)
	if ok && err == nil {
		fmt.Println("verified ok after xor")
	}
	// Output: verified ok after xor
}

func TestEncoderReconstruct(t *testing.T) {
	// Create some sample data
	var data = make([]byte, 250000)
	fillRandom(data)

	// Create 5 data slices of 50000 elements each
	enc, _ := New(5, 3)
	shards, _ := enc.Split(data)
	err := enc.Encode(shards)
	if err != nil {
		t.Fatal(err)
	}

	// Check that it verifies
	ok, err := enc.Verify(shards)
	if !ok || err != nil {
		t.Fatal("not ok:", ok, "err:", err)
	}

	// Delete a shard
	shards[0] = nil

	// Should reconstruct
	err = enc.Reconstruct(shards)
	if err != nil {
		t.Fatal(err)
	}

	// Check that it verifies
	ok, err = enc.Verify(shards)
	if !ok || err != nil {
		t.Fatal("not ok:", ok, "err:", err)
	}

	// Recover original bytes
	buf := new(bytes.Buffer)
	err = enc.Join(buf, shards, len(data))
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(buf.Bytes(), data) {
		t.Fatal("recovered bytes do not match")
	}

	// Corrupt a shard
	shards[0] = nil
	shards[1][0], shards[1][500] = 75, 75

	// Should reconstruct (but with corrupted data)
	err = enc.Reconstruct(shards)
	if err != nil {
		t.Fatal(err)
	}

	// Check that it verifies
	ok, err = enc.Verify(shards)
	if ok || err != nil {
		t.Fatal("error or ok:", ok, "err:", err)
	}

	// Recovered data should not match original
	buf.Reset()
	err = enc.Join(buf, shards, len(data))
	if err != nil {
		t.Fatal(err)
	}
	if bytes.Equal(buf.Bytes(), data) {
		t.Fatal("corrupted data matches original")
	}
}

func TestSplitJoin(t *testing.T) {
	var data = make([]byte, 250000)
	rand.Seed(0)
	fillRandom(data)

	enc, _ := New(5, 3)
	shards, err := enc.Split(data)
	if err != nil {
		t.Fatal(err)
	}

	_, err = enc.Split([]byte{})
	if err != ErrShortData {
		t.Errorf("expected %v, got %v", ErrShortData, err)
	}

	buf := new(bytes.Buffer)
	err = enc.Join(buf, shards, 50)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(buf.Bytes(), data[:50]) {
		t.Fatal("recovered data does match original")
	}

	err = enc.Join(buf, [][]byte{}, 0)
	if err != ErrTooFewShards {
		t.Errorf("expected %v, got %v", ErrTooFewShards, err)
	}

	err = enc.Join(buf, shards, len(data)+1)
	if err != ErrShortData {
		t.Errorf("expected %v, got %v", ErrShortData, err)
	}
}

> testSplitJoin :: Assertion
> testSplitJoin = do
>     let dataSize = 250000
>         data_ = V.fromListN dataSize $ randoms $ mkStdGen 0
>
>     let Right enc = RS.new 5 3
>     shards <- RS.split enc data_
>
>     let Left err = RS.split enc V.empty
>     fromException err @?= Just RS.InvalidDataSize
>
>     buf <- RS.join enc shards 50
>     buf @?= V.take 50 data_
>
>     let Left err' = RS.join enc V.empty 0
>     fromException err' @?= Just (RS.InvalidNumberOfShards RS.DataShard 0)
>
>     let Left err'' = RS.join enc shards (dataSize + 1)
>     fromException err'' @?= Just RS.InvalidDataSize

func TestCodeSomeShards(t *testing.T) {
	var data = make([]byte, 250000)
	fillRandom(data)
	enc, _ := New(5, 3)
	r := enc.(*reedSolomon) // need to access private methods
	shards, _ := enc.Split(data)

	old := runtime.GOMAXPROCS(1)
	r.codeSomeShards(r.parity, shards[:r.DataShards], shards[r.DataShards:], r.ParityShards, len(shards[0]))

	// hopefully more than 1 CPU
	runtime.GOMAXPROCS(runtime.NumCPU())
	r.codeSomeShards(r.parity, shards[:r.DataShards], shards[r.DataShards:], r.ParityShards, len(shards[0]))

	// reset MAXPROCS, otherwise testing complains
	runtime.GOMAXPROCS(old)
}

func TestAllMatrices(t *testing.T) {
	t.Skip("Skipping slow matrix check")
	for i := 1; i < 257; i++ {
		_, err := New(i, i)
		if err != nil {
			t.Fatal("creating matrix size", i, i, ":", err)
		}
	}
}

func TestNew(t *testing.T) {
	tests := []struct {
		data, parity int
		err          error
	}{
		{10, 500, nil},
		{256, 256, nil},

		{0, 1, ErrInvShardNum},
		{1, 0, ErrInvShardNum},
		{257, 1, ErrInvShardNum},

		// overflow causes r.Shards to be negative
		{256, int(^uint(0) >> 1), errInvalidRowSize},
	}
	for _, test := range tests {
		_, err := New(test.data, test.parity)
		if err != test.err {
			t.Errorf("New(%v, %v): expected %v, got %v", test.data, test.parity, test.err, err)
		}
	}
}

> testNew :: Assertion
> testNew = do
>     let tests' = [ (10, 500, Nothing)
>                  , (256, 256, Nothing)
>                  , (0, 1, Just $ RS.InvalidNumberOfShards RS.DataShard 0)
>                  , (1, 0, Just $ RS.InvalidNumberOfShards RS.ParityShard 0)
>                  , (257, 1, Just $ RS.InvalidNumberOfShards RS.DataShard 257)
>                  -- , (256, maxBound `shiftR` 1, Just "Invalid number of shards")
>                  ]  :: [(Int, Int, Maybe RS.ValueError)]
>
>     mapM_ (\(data_, parity, err) -> do
>         r <- try $ RS.new data_ parity
>         case err of
>             Nothing ->
>                 case r of
>                     Right _ -> return ()
>                     Left e -> throwIO e
>             Just e ->
>                 case r of
>                     Right _ -> assertFailure "No exception thrown"
>                     Left e' ->
>                         case fromException e' of
>                             Nothing -> throwIO e'
>                             Just m -> m @?= e)
>         tests'

> encodeRecover :: Positive Int -> Gen Bool
> encodeRecover (Positive fragmentSize) = do
>     numDataShards <- choose (1, 10)
>     numParities <- choose (1, 10)
>
>     let Right r = RS.new numDataShards numParities
>
>     dataShards <- V.replicateM numDataShards (V.replicateM fragmentSize arbitrary)
>     let Just parities = RS.encode r dataShards
>         allFragments = (V.++) dataShards parities
>
>     dropped <- do
>         numDrops <- choose (0, numParities)
>         let dropIdx = choose (0, numDataShards + numParities - 1)
>         vectorOf numDrops dropIdx
>
>     let someFragments = (V.//) (V.map Just allFragments) $ map (\idx -> (idx, Nothing)) dropped
>         Right recovered = RS.reconstruct r someFragments
>
>     return $ recovered == allFragments

> splitJoin :: Positive Int -> Gen Bool
> splitJoin (Positive len) = do
>     numDataShards <- choose (1, 10)
>     let numParityShards = 2
>
>     let Right r = RS.new numDataShards numParityShards
>     vec <- V.replicateM len arbitrary
>
>     case RS.split r vec of
>         Left e ->
>             return $
>                 if len < numDataShards
>                 then fromException e == Just RS.InvalidDataSize
>                 else False
>         Right parts ->
>             let Right joined = RS.join r parts len in
>             return $ joined == vec

> tests :: TestTree
> tests = testGroup "ReedSolomon" [
>       testGroup "Unit" [
>             testCase "testEncoding" testEncoding
>           , testCase "testReconstruct" testReconstruct
>           , testCase "testVerify" testVerify
>           , testCase "testOneEncode" testOneEncode
>           , testCase "testNew" testNew
>           , testCase "testSplitJoin" testSplitJoin
>           ]
>     , testGroup "Properties" [
>             testProperty "encodeRecover" encodeRecover
>           , testProperty "splitJoin" splitJoin
>           ]
>     ]