github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/erasure-decode_test.go (about)

     1  // Copyright (c) 2015-2021 MinIO, Inc.
     2  //
     3  // This file is part of MinIO Object Storage stack
     4  //
     5  // This program is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Affero General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // This program is distributed in the hope that it will be useful
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  // GNU Affero General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Affero General Public License
    16  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package cmd
    19  
    20  import (
    21  	"bytes"
    22  	"context"
    23  	crand "crypto/rand"
    24  	"io"
    25  	"math/rand"
    26  	"testing"
    27  
    28  	"github.com/dustin/go-humanize"
    29  )
    30  
    31  func (a badDisk) ReadFile(ctx context.Context, volume string, path string, offset int64, buf []byte, verifier *BitrotVerifier) (n int64, err error) {
    32  	return 0, errFaultyDisk
    33  }
    34  
    35  var erasureDecodeTests = []struct {
    36  	dataBlocks                   int
    37  	onDisks, offDisks            int
    38  	blocksize, data              int64
    39  	offset                       int64
    40  	length                       int64
    41  	algorithm                    BitrotAlgorithm
    42  	shouldFail, shouldFailQuorum bool
    43  }{
    44  	{dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false},             // 0
    45  	{dataBlocks: 3, onDisks: 6, offDisks: 0, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: SHA256, shouldFail: false, shouldFailQuorum: false},                 // 1
    46  	{dataBlocks: 4, onDisks: 8, offDisks: 0, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 2
    47  	{dataBlocks: 5, onDisks: 10, offDisks: 0, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 1, length: oneMiByte - 1, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false},        // 3
    48  	{dataBlocks: 6, onDisks: 12, offDisks: 0, blocksize: int64(oneMiByte), data: oneMiByte, offset: oneMiByte, length: 0, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false},
    49  	// 4
    50  	{dataBlocks: 7, onDisks: 14, offDisks: 0, blocksize: int64(oneMiByte), data: oneMiByte, offset: 3, length: 1024, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false},                    // 5
    51  	{dataBlocks: 8, onDisks: 16, offDisks: 0, blocksize: int64(oneMiByte), data: oneMiByte, offset: 4, length: 8 * 1024, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false},                // 6
    52  	{dataBlocks: 7, onDisks: 14, offDisks: 7, blocksize: int64(blockSizeV2), data: oneMiByte, offset: oneMiByte, length: 1, algorithm: DefaultBitrotAlgorithm, shouldFail: true, shouldFailQuorum: false},              // 7
    53  	{dataBlocks: 6, onDisks: 12, offDisks: 6, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false},             // 8
    54  	{dataBlocks: 5, onDisks: 10, offDisks: 5, blocksize: int64(oneMiByte), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false},                           // 9
    55  	{dataBlocks: 4, onDisks: 8, offDisks: 4, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: SHA256, shouldFail: false, shouldFailQuorum: false},                              // 10
    56  	{dataBlocks: 3, onDisks: 6, offDisks: 3, blocksize: int64(oneMiByte), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false},                // 11
    57  	{dataBlocks: 2, onDisks: 4, offDisks: 2, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false},              // 12
    58  	{dataBlocks: 2, onDisks: 4, offDisks: 1, blocksize: int64(oneMiByte), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false},                // 13
    59  	{dataBlocks: 3, onDisks: 6, offDisks: 2, blocksize: int64(oneMiByte), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false},                // 14
    60  	{dataBlocks: 4, onDisks: 8, offDisks: 3, blocksize: int64(2 * oneMiByte), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false},            // 15
    61  	{dataBlocks: 5, onDisks: 10, offDisks: 6, blocksize: int64(oneMiByte), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: true},                // 16
    62  	{dataBlocks: 5, onDisks: 10, offDisks: 2, blocksize: int64(blockSizeV2), data: 2 * oneMiByte, offset: oneMiByte, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 17
    63  	{dataBlocks: 5, onDisks: 10, offDisks: 1, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false},                         // 18
    64  	{dataBlocks: 6, onDisks: 12, offDisks: 3, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: SHA256, shouldFail: false, shouldFailQuorum: false},
    65  	// 19
    66  	{dataBlocks: 6, onDisks: 12, offDisks: 7, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: true},                                             // 20
    67  	{dataBlocks: 8, onDisks: 16, offDisks: 8, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false},                                            // 21
    68  	{dataBlocks: 8, onDisks: 16, offDisks: 9, blocksize: int64(oneMiByte), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: true},                                               // 22
    69  	{dataBlocks: 8, onDisks: 16, offDisks: 7, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false},                                            // 23
    70  	{dataBlocks: 2, onDisks: 4, offDisks: 1, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false},                                             // 24
    71  	{dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(blockSizeV2), data: oneMiByte, offset: 0, length: oneMiByte, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false},                                             // 25
    72  	{dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(blockSizeV2), data: int64(blockSizeV2) + 1, offset: 0, length: int64(blockSizeV2) + 1, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false},                               // 26
    73  	{dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(blockSizeV2), data: int64(2 * blockSizeV2), offset: 12, length: int64(blockSizeV2) + 17, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false},                             // 27
    74  	{dataBlocks: 3, onDisks: 6, offDisks: 0, blocksize: int64(blockSizeV2), data: int64(2 * blockSizeV2), offset: 1023, length: int64(blockSizeV2) + 1024, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false},             // 28
    75  	{dataBlocks: 4, onDisks: 8, offDisks: 0, blocksize: int64(blockSizeV2), data: int64(2 * blockSizeV2), offset: 11, length: int64(blockSizeV2) + 2*1024, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false},             // 29
    76  	{dataBlocks: 6, onDisks: 12, offDisks: 0, blocksize: int64(blockSizeV2), data: int64(2 * blockSizeV2), offset: 512, length: int64(blockSizeV2) + 8*1024, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false},           // 30
    77  	{dataBlocks: 8, onDisks: 16, offDisks: 0, blocksize: int64(blockSizeV2), data: int64(2 * blockSizeV2), offset: int64(blockSizeV2), length: int64(blockSizeV2) - 1, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false}, // 31
    78  	{dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(blockSizeV2), data: int64(oneMiByte), offset: -1, length: 3, algorithm: DefaultBitrotAlgorithm, shouldFail: true, shouldFailQuorum: false},                                              // 32
    79  	{dataBlocks: 2, onDisks: 4, offDisks: 0, blocksize: int64(blockSizeV2), data: int64(oneMiByte), offset: 1024, length: -1, algorithm: DefaultBitrotAlgorithm, shouldFail: true, shouldFailQuorum: false},                                           // 33
    80  	{dataBlocks: 4, onDisks: 6, offDisks: 0, blocksize: int64(blockSizeV2), data: int64(blockSizeV2), offset: 0, length: int64(blockSizeV2), algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false},                                       // 34
    81  	{dataBlocks: 4, onDisks: 6, offDisks: 1, blocksize: int64(blockSizeV2), data: int64(2 * blockSizeV2), offset: 12, length: int64(blockSizeV2) + 17, algorithm: BLAKE2b512, shouldFail: false, shouldFailQuorum: false},                             // 35
    82  	{dataBlocks: 4, onDisks: 6, offDisks: 3, blocksize: int64(blockSizeV2), data: int64(2 * blockSizeV2), offset: 1023, length: int64(blockSizeV2) + 1024, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: true},              // 36
    83  	{dataBlocks: 8, onDisks: 12, offDisks: 4, blocksize: int64(blockSizeV2), data: int64(2 * blockSizeV2), offset: 11, length: int64(blockSizeV2) + 2*1024, algorithm: DefaultBitrotAlgorithm, shouldFail: false, shouldFailQuorum: false},            // 37
    84  }
    85  
    86  func TestErasureDecode(t *testing.T) {
    87  	for i, test := range erasureDecodeTests {
    88  		setup, err := newErasureTestSetup(t, test.dataBlocks, test.onDisks-test.dataBlocks, test.blocksize)
    89  		if err != nil {
    90  			t.Fatalf("Test %d: failed to create test setup: %v", i, err)
    91  		}
    92  		erasure, err := NewErasure(context.Background(), test.dataBlocks, test.onDisks-test.dataBlocks, test.blocksize)
    93  		if err != nil {
    94  			t.Fatalf("Test %d: failed to create ErasureStorage: %v", i, err)
    95  		}
    96  		disks := setup.disks
    97  		data := make([]byte, test.data)
    98  		if _, err = io.ReadFull(crand.Reader, data); err != nil {
    99  			t.Fatalf("Test %d: failed to generate random test data: %v", i, err)
   100  		}
   101  
   102  		writeAlgorithm := test.algorithm
   103  		if !test.algorithm.Available() {
   104  			writeAlgorithm = DefaultBitrotAlgorithm
   105  		}
   106  		buffer := make([]byte, test.blocksize, 2*test.blocksize)
   107  		writers := make([]io.Writer, len(disks))
   108  		for i, disk := range disks {
   109  			writers[i] = newBitrotWriter(disk, "", "testbucket", "object", erasure.ShardFileSize(test.data), writeAlgorithm, erasure.ShardSize())
   110  		}
   111  		n, err := erasure.Encode(context.Background(), bytes.NewReader(data), writers, buffer, erasure.dataBlocks+1)
   112  		closeBitrotWriters(writers)
   113  		if err != nil {
   114  			t.Fatalf("Test %d: failed to create erasure test file: %v", i, err)
   115  		}
   116  		if n != test.data {
   117  			t.Fatalf("Test %d: failed to create erasure test file", i)
   118  		}
   119  		for i, w := range writers {
   120  			if w == nil {
   121  				disks[i] = nil
   122  			}
   123  		}
   124  
   125  		// Get the checksums of the current part.
   126  		bitrotReaders := make([]io.ReaderAt, len(disks))
   127  		for index, disk := range disks {
   128  			if disk == OfflineDisk {
   129  				continue
   130  			}
   131  			tillOffset := erasure.ShardFileOffset(test.offset, test.length, test.data)
   132  
   133  			bitrotReaders[index] = newBitrotReader(disk, nil, "testbucket", "object", tillOffset, writeAlgorithm, bitrotWriterSum(writers[index]), erasure.ShardSize())
   134  		}
   135  
   136  		writer := bytes.NewBuffer(nil)
   137  		_, err = erasure.Decode(context.Background(), writer, bitrotReaders, test.offset, test.length, test.data, nil)
   138  		closeBitrotReaders(bitrotReaders)
   139  		if err != nil && !test.shouldFail {
   140  			t.Errorf("Test %d: should pass but failed with: %v", i, err)
   141  		}
   142  		if err == nil && test.shouldFail {
   143  			t.Errorf("Test %d: should fail but it passed", i)
   144  		}
   145  		if err == nil {
   146  			if content := writer.Bytes(); !bytes.Equal(content, data[test.offset:test.offset+test.length]) {
   147  				t.Errorf("Test %d: read returns wrong file content.", i)
   148  			}
   149  		}
   150  
   151  		for i, r := range bitrotReaders {
   152  			if r == nil {
   153  				disks[i] = OfflineDisk
   154  			}
   155  		}
   156  		if err == nil && !test.shouldFail {
   157  			bitrotReaders = make([]io.ReaderAt, len(disks))
   158  			for index, disk := range disks {
   159  				if disk == OfflineDisk {
   160  					continue
   161  				}
   162  				tillOffset := erasure.ShardFileOffset(test.offset, test.length, test.data)
   163  				bitrotReaders[index] = newBitrotReader(disk, nil, "testbucket", "object", tillOffset, writeAlgorithm, bitrotWriterSum(writers[index]), erasure.ShardSize())
   164  			}
   165  			for j := range disks[:test.offDisks] {
   166  				if bitrotReaders[j] == nil {
   167  					continue
   168  				}
   169  				switch r := bitrotReaders[j].(type) {
   170  				case *wholeBitrotReader:
   171  					r.disk = badDisk{nil}
   172  				case *streamingBitrotReader:
   173  					r.disk = badDisk{nil}
   174  				}
   175  			}
   176  			if test.offDisks > 0 {
   177  				bitrotReaders[0] = nil
   178  			}
   179  			writer.Reset()
   180  			_, err = erasure.Decode(context.Background(), writer, bitrotReaders, test.offset, test.length, test.data, nil)
   181  			closeBitrotReaders(bitrotReaders)
   182  			if err != nil && !test.shouldFailQuorum {
   183  				t.Errorf("Test %d: should pass but failed with: %v", i, err)
   184  			}
   185  			if err == nil && test.shouldFailQuorum {
   186  				t.Errorf("Test %d: should fail but it passed", i)
   187  			}
   188  			if !test.shouldFailQuorum {
   189  				if content := writer.Bytes(); !bytes.Equal(content, data[test.offset:test.offset+test.length]) {
   190  					t.Errorf("Test %d: read returns wrong file content", i)
   191  				}
   192  			}
   193  		}
   194  	}
   195  }
   196  
   197  // Test erasureDecode with random offset and lengths.
   198  // This test is t.Skip()ed as it a long time to run, hence should be run
   199  // explicitly after commenting out t.Skip()
   200  func TestErasureDecodeRandomOffsetLength(t *testing.T) {
   201  	if testing.Short() {
   202  		t.Skip()
   203  	}
   204  	// Initialize environment needed for the test.
   205  	dataBlocks := 7
   206  	parityBlocks := 7
   207  	blockSize := int64(1 * humanize.MiByte)
   208  	setup, err := newErasureTestSetup(t, dataBlocks, parityBlocks, blockSize)
   209  	if err != nil {
   210  		t.Error(err)
   211  		return
   212  	}
   213  	disks := setup.disks
   214  	erasure, err := NewErasure(context.Background(), dataBlocks, parityBlocks, blockSize)
   215  	if err != nil {
   216  		t.Fatalf("failed to create ErasureStorage: %v", err)
   217  	}
   218  	// Prepare a slice of 5MiB with random data.
   219  	data := make([]byte, 5*humanize.MiByte)
   220  	length := int64(len(data))
   221  	_, err = rand.Read(data)
   222  	if err != nil {
   223  		t.Fatal(err)
   224  	}
   225  
   226  	writers := make([]io.Writer, len(disks))
   227  	for i, disk := range disks {
   228  		if disk == nil {
   229  			continue
   230  		}
   231  		writers[i] = newBitrotWriter(disk, "", "testbucket", "object", erasure.ShardFileSize(length), DefaultBitrotAlgorithm, erasure.ShardSize())
   232  	}
   233  
   234  	// 10000 iterations with random offsets and lengths.
   235  	iterations := 10000
   236  
   237  	// Create a test file to read from.
   238  	buffer := make([]byte, blockSize, 2*blockSize)
   239  	n, err := erasure.Encode(context.Background(), bytes.NewReader(data), writers, buffer, erasure.dataBlocks+1)
   240  	closeBitrotWriters(writers)
   241  	if err != nil {
   242  		t.Fatal(err)
   243  	}
   244  	if n != length {
   245  		t.Errorf("erasureCreateFile returned %d, expected %d", n, length)
   246  	}
   247  
   248  	// To generate random offset/length.
   249  	r := rand.New(rand.NewSource(UTCNow().UnixNano()))
   250  
   251  	buf := &bytes.Buffer{}
   252  
   253  	// Verify erasure.Decode() for random offsets and lengths.
   254  	for i := 0; i < iterations; i++ {
   255  		offset := r.Int63n(length)
   256  		readLen := r.Int63n(length - offset)
   257  
   258  		expected := data[offset : offset+readLen]
   259  
   260  		// Get the checksums of the current part.
   261  		bitrotReaders := make([]io.ReaderAt, len(disks))
   262  		for index, disk := range disks {
   263  			if disk == OfflineDisk {
   264  				continue
   265  			}
   266  			tillOffset := erasure.ShardFileOffset(offset, readLen, length)
   267  			bitrotReaders[index] = newStreamingBitrotReader(disk, nil, "testbucket", "object", tillOffset, DefaultBitrotAlgorithm, erasure.ShardSize())
   268  		}
   269  		_, err = erasure.Decode(context.Background(), buf, bitrotReaders, offset, readLen, length, nil)
   270  		closeBitrotReaders(bitrotReaders)
   271  		if err != nil {
   272  			t.Fatal(err, offset, readLen)
   273  		}
   274  		got := buf.Bytes()
   275  		if !bytes.Equal(expected, got) {
   276  			t.Fatalf("read data is different from what was expected, offset=%d length=%d", offset, readLen)
   277  		}
   278  		buf.Reset()
   279  	}
   280  }
   281  
   282  // Benchmarks
   283  
   284  func benchmarkErasureDecode(data, parity, dataDown, parityDown int, size int64, b *testing.B) {
   285  	setup, err := newErasureTestSetup(b, data, parity, blockSizeV2)
   286  	if err != nil {
   287  		b.Fatalf("failed to create test setup: %v", err)
   288  	}
   289  	disks := setup.disks
   290  	erasure, err := NewErasure(context.Background(), data, parity, blockSizeV2)
   291  	if err != nil {
   292  		b.Fatalf("failed to create ErasureStorage: %v", err)
   293  	}
   294  
   295  	writers := make([]io.Writer, len(disks))
   296  	for i, disk := range disks {
   297  		if disk == nil {
   298  			continue
   299  		}
   300  		writers[i] = newBitrotWriter(disk, "", "testbucket", "object", erasure.ShardFileSize(size), DefaultBitrotAlgorithm, erasure.ShardSize())
   301  	}
   302  
   303  	content := make([]byte, size)
   304  	buffer := make([]byte, blockSizeV2, 2*blockSizeV2)
   305  	_, err = erasure.Encode(context.Background(), bytes.NewReader(content), writers, buffer, erasure.dataBlocks+1)
   306  	closeBitrotWriters(writers)
   307  	if err != nil {
   308  		b.Fatalf("failed to create erasure test file: %v", err)
   309  	}
   310  
   311  	for i := 0; i < dataDown; i++ {
   312  		writers[i] = nil
   313  	}
   314  	for i := data; i < data+parityDown; i++ {
   315  		writers[i] = nil
   316  	}
   317  
   318  	b.ResetTimer()
   319  	b.SetBytes(size)
   320  	b.ReportAllocs()
   321  	for i := 0; i < b.N; i++ {
   322  		bitrotReaders := make([]io.ReaderAt, len(disks))
   323  		for index, disk := range disks {
   324  			if writers[index] == nil {
   325  				continue
   326  			}
   327  			tillOffset := erasure.ShardFileOffset(0, size, size)
   328  			bitrotReaders[index] = newStreamingBitrotReader(disk, nil, "testbucket", "object", tillOffset, DefaultBitrotAlgorithm, erasure.ShardSize())
   329  		}
   330  		if _, err = erasure.Decode(context.Background(), bytes.NewBuffer(content[:0]), bitrotReaders, 0, size, size, nil); err != nil {
   331  			panic(err)
   332  		}
   333  		closeBitrotReaders(bitrotReaders)
   334  	}
   335  }
   336  
   337  func BenchmarkErasureDecodeQuick(b *testing.B) {
   338  	const size = 12 * 1024 * 1024
   339  	b.Run(" 00|00 ", func(b *testing.B) { benchmarkErasureDecode(2, 2, 0, 0, size, b) })
   340  	b.Run(" 00|X0 ", func(b *testing.B) { benchmarkErasureDecode(2, 2, 0, 1, size, b) })
   341  	b.Run(" X0|00 ", func(b *testing.B) { benchmarkErasureDecode(2, 2, 1, 0, size, b) })
   342  	b.Run(" X0|X0 ", func(b *testing.B) { benchmarkErasureDecode(2, 2, 1, 1, size, b) })
   343  }
   344  
   345  func BenchmarkErasureDecode_4_64KB(b *testing.B) {
   346  	const size = 64 * 1024
   347  	b.Run(" 00|00 ", func(b *testing.B) { benchmarkErasureDecode(2, 2, 0, 0, size, b) })
   348  	b.Run(" 00|X0 ", func(b *testing.B) { benchmarkErasureDecode(2, 2, 0, 1, size, b) })
   349  	b.Run(" X0|00 ", func(b *testing.B) { benchmarkErasureDecode(2, 2, 1, 0, size, b) })
   350  	b.Run(" X0|X0 ", func(b *testing.B) { benchmarkErasureDecode(2, 2, 1, 1, size, b) })
   351  	b.Run(" 00|XX ", func(b *testing.B) { benchmarkErasureDecode(2, 2, 0, 2, size, b) })
   352  	b.Run(" XX|00 ", func(b *testing.B) { benchmarkErasureDecode(2, 2, 2, 0, size, b) })
   353  }
   354  
   355  func BenchmarkErasureDecode_8_20MB(b *testing.B) {
   356  	const size = 20 * 1024 * 1024
   357  	b.Run(" 0000|0000 ", func(b *testing.B) { benchmarkErasureDecode(4, 4, 0, 0, size, b) })
   358  	b.Run(" 0000|X000 ", func(b *testing.B) { benchmarkErasureDecode(4, 4, 0, 1, size, b) })
   359  	b.Run(" X000|0000 ", func(b *testing.B) { benchmarkErasureDecode(4, 4, 1, 0, size, b) })
   360  	b.Run(" X000|X000 ", func(b *testing.B) { benchmarkErasureDecode(4, 4, 1, 1, size, b) })
   361  	b.Run(" 0000|XXXX ", func(b *testing.B) { benchmarkErasureDecode(4, 4, 0, 4, size, b) })
   362  	b.Run(" XX00|XX00 ", func(b *testing.B) { benchmarkErasureDecode(4, 4, 2, 2, size, b) })
   363  	b.Run(" XXXX|0000 ", func(b *testing.B) { benchmarkErasureDecode(4, 4, 4, 0, size, b) })
   364  }
   365  
   366  func BenchmarkErasureDecode_12_30MB(b *testing.B) {
   367  	const size = 30 * 1024 * 1024
   368  	b.Run(" 000000|000000 ", func(b *testing.B) { benchmarkErasureDecode(6, 6, 0, 0, size, b) })
   369  	b.Run(" 000000|X00000 ", func(b *testing.B) { benchmarkErasureDecode(6, 6, 0, 1, size, b) })
   370  	b.Run(" X00000|000000 ", func(b *testing.B) { benchmarkErasureDecode(6, 6, 1, 0, size, b) })
   371  	b.Run(" X00000|X00000 ", func(b *testing.B) { benchmarkErasureDecode(6, 6, 1, 1, size, b) })
   372  	b.Run(" 000000|XXXXXX ", func(b *testing.B) { benchmarkErasureDecode(6, 6, 0, 6, size, b) })
   373  	b.Run(" XXX000|XXX000 ", func(b *testing.B) { benchmarkErasureDecode(6, 6, 3, 3, size, b) })
   374  	b.Run(" XXXXXX|000000 ", func(b *testing.B) { benchmarkErasureDecode(6, 6, 6, 0, size, b) })
   375  }
   376  
   377  func BenchmarkErasureDecode_16_40MB(b *testing.B) {
   378  	const size = 40 * 1024 * 1024
   379  	b.Run(" 00000000|00000000 ", func(b *testing.B) { benchmarkErasureDecode(8, 8, 0, 0, size, b) })
   380  	b.Run(" 00000000|X0000000 ", func(b *testing.B) { benchmarkErasureDecode(8, 8, 0, 1, size, b) })
   381  	b.Run(" X0000000|00000000 ", func(b *testing.B) { benchmarkErasureDecode(8, 8, 1, 0, size, b) })
   382  	b.Run(" X0000000|X0000000 ", func(b *testing.B) { benchmarkErasureDecode(8, 8, 1, 1, size, b) })
   383  	b.Run(" 00000000|XXXXXXXX ", func(b *testing.B) { benchmarkErasureDecode(8, 8, 0, 8, size, b) })
   384  	b.Run(" XXXX0000|XXXX0000 ", func(b *testing.B) { benchmarkErasureDecode(8, 8, 4, 4, size, b) })
   385  	b.Run(" XXXXXXXX|00000000 ", func(b *testing.B) { benchmarkErasureDecode(8, 8, 8, 0, size, b) })
   386  }