feat(bsr): create checksum writer

This commit is contained in:
Damian Debkowski authored and Elim Tsiagbey committed 2024-01-11 09:58:32 -05:00
1 parent 4f42dbf59f
commit 450da59686
6 files changed
+827 -10

No files matched your search

+10 -9
View File
@@ -8,13 +8,13 @@ package checksum
import (
"context"
"crypto/sha256"
"errors"
"fmt"
"io"
"io/fs"
"github.com/hashicorp/boundary/internal/bsr/internal/is"
"github.com/hashicorp/go-kms-wrapping/v2/extras/crypto"
)
const (
@@ -45,9 +45,9 @@ type File struct {
// std interfaces that to not take a context as an arg.
ctx context.Context
// Performs the checksuming, provides implementations for Write and
// WriteString
*crypto.Sha256SumWriter
// Performs the checksuming, provides implementations for Write,
// WriteString, and WriteAndClose
*Sha256SumWriter
checksumWriter io.Writer
@@ -66,7 +66,7 @@ func NewFile(ctx context.Context, f writerFile, cs io.Writer) (*File, error) {
return nil, fmt.Errorf("%s: missing checksum writer: %w", op, errInvalidParameter)
}
wrapped, err := crypto.NewSha256SumWriter(ctx, f)
wrapped, err := NewSha256SumWriter(ctx, f, sha256.New())
if err != nil {
return nil, err
}
@@ -107,7 +107,7 @@ func (f *File) Close() error {
closeErrors = errors.Join(closeErrors, fmt.Errorf("%s: %w", op, err))
}
sum, err := f.Sha256SumWriter.Sum(f.ctx, crypto.WithHexEncoding(true))
sum, err := f.Sha256SumWriter.Sum(f.ctx, WithHexEncoding(true))
if err != nil {
closeErrors = errors.Join(closeErrors, fmt.Errorf("%s: %w", op, err))
return closeErrors
@@ -133,12 +133,13 @@ func (f *File) WriteAndClose(b []byte) (int, error) {
return 0, closeErrors
}
n, err := f.underlying.WriteAndClose(b)
// f.Sha256SumWriter will close f.underlying
n, err := f.Sha256SumWriter.WriteAndClose(b)
if err != nil {
return 0, fmt.Errorf("%s: %w", op, err)
closeErrors = errors.Join(closeErrors, fmt.Errorf("%s: %w", op, err))
}
sum, err := f.Sha256SumWriter.Sum(f.ctx, crypto.WithHexEncoding(true))
sum, err := f.Sha256SumWriter.Sum(f.ctx, WithHexEncoding(true))
if err != nil {
closeErrors = errors.Join(closeErrors, fmt.Errorf("%s: %w", op, err))
return 0, closeErrors
@@ -87,7 +87,7 @@ func TestFile(t *testing.T) {
testString,
testSum + " test\n",
nil,
errors.New("checksum.(File).Close: crypto.(Sha256SumWriter).Close: close error"),
errors.New("checksum.(File).Close: checksum.(Sha256SumWriter).Close: close error"),
true,
},
{
+151
View File
@@ -0,0 +1,151 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package checksum
import (
"errors"
)
// Option - a type that wraps an interface for compile-time safety but can
// contain an option for this package or for wrappers implementing this
// interface.
type Option func() interface{}
// getOpts iterates the inbound Options and returns a struct
func getOpts(opt ...Option) (*options, error) {
// First, separate out options into local and global
opts := getDefaultOptions()
for _, o := range opt {
if o == nil {
continue
}
iface := o()
switch to := iface.(type) {
case OptionFunc:
if err := to(&opts); err != nil {
return nil, err
}
default:
return nil, errors.New("option passed into util wrapping options handler" +
" that is not from this package; this is likely due to the wrapper being" +
" invoked as a plugin but options being sent from a specific wrapper package;" +
" use WithConfigMap to send options via the plugin interface")
}
}
return &opts, nil
}
// OptionFunc holds a function with local options
type OptionFunc func(*options) error
// options = how options are represented
type options struct {
withPrefix string
withPrk []byte
withEd25519 bool
withBase64Encoding bool
withBase58Encoding bool
withMarshaledSigInfo bool
withSalt []byte
withInfo []byte
WithHexEncoding bool
}
func getDefaultOptions() options {
return options{}
}
// WithPrefix allows an optional prefix to be specified for the data returned
func WithPrefix(prefix string) Option {
return func() interface{} {
return OptionFunc(func(o *options) error {
o.withPrefix = prefix
return nil
})
}
}
// WithPrk allows an optional PRK (pseudorandom key) to be specified for an
// operation. If you're using this option with HmacSha256, you might consider
// using HmacSha256WithPrk instead.
func WithPrk(prk []byte) Option {
return func() interface{} {
return OptionFunc(func(o *options) error {
o.withPrk = prk
return nil
})
}
}
// WithEd25519 allows an optional request to use ed25519 during the operation
func WithEd25519() Option {
return func() interface{} {
return OptionFunc(func(o *options) error {
o.withEd25519 = true
return nil
})
}
}
// WithBase64Encoding allows an optional request to base64 encode the data returned
func WithBase64Encoding() Option {
return func() interface{} {
return OptionFunc(func(o *options) error {
o.withBase64Encoding = true
return nil
})
}
}
// WithBase58Encoding allows an optional request to base58 encode the data returned
func WithBase58Encoding() Option {
return func() interface{} {
return OptionFunc(func(o *options) error {
o.withBase58Encoding = true
return nil
})
}
}
// WithMarshaledSigInfo allows an optional request to wrap the returned data into
// a marshaled wrapping.SigInfo protobuf
func WithMarshaledSigInfo() Option {
return func() interface{} {
return OptionFunc(func(o *options) error {
o.withMarshaledSigInfo = true
return nil
})
}
}
// WithSalt allows optional salt to be specified for an operation.
func WithSalt(salt []byte) Option {
return func() interface{} {
return OptionFunc(func(o *options) error {
o.withSalt = salt
return nil
})
}
}
// WithInfo allows optional info to be specified for an operation.
func WithInfo(info []byte) Option {
return func() interface{} {
return OptionFunc(func(o *options) error {
o.withInfo = info
return nil
})
}
}
// WithHexEncoding allows an optional request to use hex encoding.
func WithHexEncoding(withHexEncoding bool) Option {
return func() interface{} {
return OptionFunc(func(o *options) error {
o.WithHexEncoding = withHexEncoding
return nil
})
}
}
@@ -0,0 +1,116 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package checksum
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_getOpts(t *testing.T) {
t.Parallel()
assert := assert.New(t)
require := require.New(t)
t.Run("WithPrefix", func(t *testing.T) {
testOpts, err := getOpts()
require.NoError(err)
assert.Empty(testOpts.withPrefix)
opts := getDefaultOptions()
opts.withPrefix = "test"
testOpts, err = getOpts(WithPrefix("test"))
require.NoError(err)
assert.EqualValues(opts, *testOpts)
})
t.Run("WithPrk", func(t *testing.T) {
testOpts, err := getOpts()
require.NoError(err)
assert.Empty(testOpts.withPrk)
opts := getDefaultOptions()
opts.withPrk = []byte("test")
testOpts, err = getOpts(WithPrk([]byte("test")))
require.NoError(err)
assert.EqualValues(opts, *testOpts)
})
t.Run("WithEd25519", func(t *testing.T) {
testOpts, err := getOpts()
require.NoError(err)
assert.False(testOpts.withEd25519)
opts := getDefaultOptions()
opts.withEd25519 = true
testOpts, err = getOpts(WithEd25519())
require.NoError(err)
assert.EqualValues(opts, *testOpts)
})
t.Run("WithBase64Encoding", func(t *testing.T) {
testOpts, err := getOpts()
require.NoError(err)
assert.False(testOpts.withBase64Encoding)
opts := getDefaultOptions()
opts.withBase64Encoding = true
testOpts, err = getOpts(WithBase64Encoding())
require.NoError(err)
assert.EqualValues(opts, *testOpts)
})
t.Run("WithBase58Encoding", func(t *testing.T) {
testOpts, err := getOpts()
require.NoError(err)
assert.False(testOpts.withBase58Encoding)
opts := getDefaultOptions()
opts.withBase58Encoding = true
testOpts, err = getOpts(WithBase58Encoding())
require.NoError(err)
assert.EqualValues(opts, *testOpts)
})
t.Run("WithMarshaledSigInfo", func(t *testing.T) {
testOpts, err := getOpts()
require.NoError(err)
assert.False(testOpts.withMarshaledSigInfo)
opts := getDefaultOptions()
opts.withMarshaledSigInfo = true
testOpts, err = getOpts(WithMarshaledSigInfo())
require.NoError(err)
assert.EqualValues(opts, *testOpts)
})
t.Run("WithSalt", func(t *testing.T) {
testOpts, err := getOpts()
require.NoError(err)
assert.Empty(testOpts.withSalt)
opts := getDefaultOptions()
opts.withSalt = []byte("test")
testOpts, err = getOpts(WithSalt([]byte("test")))
require.NoError(err)
assert.EqualValues(opts, *testOpts)
})
t.Run("WithInfo", func(t *testing.T) {
testOpts, err := getOpts()
require.NoError(err)
assert.Empty(testOpts.withInfo)
opts := getDefaultOptions()
opts.withInfo = []byte("test")
testOpts, err = getOpts(WithInfo([]byte("test")))
require.NoError(err)
assert.EqualValues(opts, *testOpts)
})
t.Run("WithHexEncoding", func(t *testing.T) {
testOpts, err := getOpts()
require.NoError(err)
assert.False(testOpts.WithHexEncoding)
opts := getDefaultOptions()
opts.WithHexEncoding = true
testOpts, err = getOpts(WithHexEncoding(true))
require.NoError(err)
assert.EqualValues(opts, *testOpts)
})
}
@@ -5,11 +5,25 @@ package checksum
import (
"bufio"
"context"
"encoding/hex"
"errors"
"fmt"
"hash"
"io"
"strings"
"sync"
"github.com/hashicorp/boundary/internal/storage"
"github.com/hashicorp/boundary/internal/util"
)
// ErrShortWrite represents an error where the number of written bytes between two writers do not match
var ErrShortWrite = errors.New("short write")
// ErrInvalidParameter represents an error where an invalid parameter value was provided
var ErrInvalidParameter = errors.New("invalid parameter")
const (
sha256sumSep = " "
sha256sumBinarySep = " *"
@@ -70,3 +84,114 @@ func (s Sha256Sums) Sum(f string) ([]byte, error) {
return sum, nil
}
// Sha256SumWriter is used to write to a hash and produce a sum.
// It implements io.WriterCloser, io.StringWriter and a storage.Writer.
type Sha256SumWriter struct {
l sync.Mutex
hash hash.Hash
underlying storage.Writer
}
// NewSha256SumWriter creates a new Sha256SumWriter
func NewSha256SumWriter(ctx context.Context, file storage.Writer, hash hash.Hash) (*Sha256SumWriter, error) {
const op = "checksum.NewSha256SumWriter"
if util.IsNil(file) {
return nil, fmt.Errorf("%s: missing writer: %w", op, ErrInvalidParameter)
}
if util.IsNil(hash) {
return nil, fmt.Errorf("%s: missing hash: %w", op, ErrInvalidParameter)
}
return &Sha256SumWriter{
hash: hash,
underlying: file,
}, nil
}
// Write will write the bytes to the hash. Implements the required io.Writer
// func.
func (w *Sha256SumWriter) Write(b []byte) (int, error) {
const op = "checksum.(Sha256SumWriter).Write"
w.l.Lock()
defer w.l.Unlock()
n, err := w.hash.Write(b)
if err != nil {
return n, fmt.Errorf("%s: %w", op, err)
}
m, err := w.underlying.Write(b)
if err != nil {
return m, fmt.Errorf("%s: %w", op, err)
}
if n != m {
return m, ErrShortWrite
}
return n, nil
}
// WriteString will write the string to hash.
func (w *Sha256SumWriter) WriteString(s string) (int, error) {
const op = "checksum.(Sha256SumWriter).WriteString"
n, err := w.hash.Write([]byte(s))
if err != nil {
return n, fmt.Errorf("%s: %w", op, err)
}
m, err := w.underlying.Write([]byte(s))
if err != nil {
return m, fmt.Errorf("%s: %w", op, err)
}
if n != m {
return m, ErrShortWrite
}
return n, nil
}
// WriteAndClose will write the bytes to the hash and then will the Close
func (w *Sha256SumWriter) WriteAndClose(b []byte) (int, error) {
const op = "checksum.(Sha256SumWriter).WriteAndClose"
w.l.Lock()
defer w.l.Unlock()
n, err := w.hash.Write(b)
if err != nil {
return n, fmt.Errorf("%s: %w", op, err)
}
m, err := w.underlying.WriteAndClose(b)
if err != nil {
return m, fmt.Errorf("%s: %w", op, err)
}
if n != m {
return m, ErrShortWrite
}
return n, nil
}
// Close checks to see if the Sha256SumWriter implements the optional io.Closer
// and if so, then Close() is called; otherwise this is a noop
func (w *Sha256SumWriter) Close() error {
const op = "checksum.(Sha256SumWriter).Close"
var i interface{} = w.underlying
if v, ok := i.(io.Closer); ok {
if err := v.Close(); err != nil {
return fmt.Errorf("%s: %w", op, err)
}
}
return nil
}
// Sum will sum the hash. Options supported: WithHexEncoding
func (w *Sha256SumWriter) Sum(_ context.Context, opt ...Option) ([]byte, error) {
const op = "checksum.(Sha256SumWriter).Sum"
opts, err := getOpts(opt...)
if err != nil {
return nil, fmt.Errorf("%s: %w", op, err)
}
w.l.Lock()
defer w.l.Unlock()
h := w.hash.Sum(nil)
switch {
case opts.WithHexEncoding:
encodedHex := hex.EncodeToString(h[:])
return []byte(encodedHex), nil
default:
return h, nil
}
}
@@ -4,16 +4,440 @@
package checksum_test
import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"hash"
"io"
"strings"
"testing"
"github.com/hashicorp/boundary/internal/bsr/internal/checksum"
"github.com/hashicorp/boundary/internal/storage"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var _ storage.Writer = (*testFile)(nil)
var _ hash.Hash = (*testHash)(nil)
type testFile struct {
b strings.Builder
closed bool
withWrittenBytes int
withWriteError error
}
func (t *testFile) Write(b []byte) (int, error) {
if t.withWriteError != nil || t.withWrittenBytes > 0 {
return t.withWrittenBytes, t.withWriteError
}
return t.b.Write(b)
}
func (t *testFile) WriteString(s string) (int, error) {
if t.withWriteError != nil || t.withWrittenBytes > 0 {
return t.withWrittenBytes, t.withWriteError
}
return t.b.WriteString(s)
}
func (t *testFile) WriteAndClose(b []byte) (int, error) {
if t.withWriteError != nil || t.withWrittenBytes > 0 {
return t.withWrittenBytes, t.withWriteError
}
t.closed = true
return t.b.Write(b)
}
func (t *testFile) Close() error {
t.closed = true
return nil
}
type testHash struct {
withWrittenBytes int
withWriteError error
}
func (t *testHash) Write(b []byte) (int, error) {
return t.withWrittenBytes, t.withWriteError
}
func (t *testHash) Sum(b []byte) []byte {
return nil
}
func (t *testHash) Reset() {}
func (t *testHash) Size() int {
return 0
}
func (t *testHash) BlockSize() int {
return 0
}
func Test_NewSha256SumWriter(t *testing.T) {
t.Parallel()
cases := []struct {
name string
writer storage.Writer
hash hash.Hash
expectedErr string
}{
{
name: "missing writer",
expectedErr: "checksum.NewSha256SumWriter: missing writer: invalid parameter",
},
{
name: "missing hash",
writer: &testFile{},
expectedErr: "checksum.NewSha256SumWriter: missing hash: invalid parameter",
},
{
name: "success",
writer: &testFile{},
hash: sha256.New(),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
w, err := checksum.NewSha256SumWriter(context.Background(), tc.writer, tc.hash)
if tc.expectedErr != "" {
require.Error(err)
assert.Nil(w)
assert.ErrorContains(err, tc.expectedErr)
return
}
require.NoError(err)
assert.NotNil(w)
})
}
}
func TestSha256SumWriter_Write(t *testing.T) {
t.Parallel()
cases := []struct {
name string
writer *checksum.Sha256SumWriter
expectedErr string
data []byte
}{
{
name: "with-hash-error",
writer: func() *checksum.Sha256SumWriter {
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{}, &testHash{
withWriteError: fmt.Errorf("failed hash write"),
})
require.NoError(t, err)
return writer
}(),
data: []byte{},
expectedErr: "failed hash write",
},
{
name: "with-file-error",
writer: func() *checksum.Sha256SumWriter {
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{
withWriteError: fmt.Errorf("failed file write"),
}, &testHash{
withWrittenBytes: 10,
})
require.NoError(t, err)
return writer
}(),
data: []byte{},
expectedErr: "failed file write",
},
{
name: "with-short-write-error",
writer: func() *checksum.Sha256SumWriter {
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{
withWrittenBytes: 9,
}, &testHash{
withWrittenBytes: 10,
})
require.NoError(t, err)
return writer
}(),
data: []byte{},
expectedErr: "short write",
},
{
name: "success",
writer: func() *checksum.Sha256SumWriter {
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{}, sha256.New())
require.NoError(t, err)
return writer
}(),
data: []byte("hello_world"),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
_, err := tc.writer.Write(tc.data)
if tc.expectedErr != "" {
require.Error(err)
assert.ErrorContains(err, tc.expectedErr)
return
}
require.NoError(err)
})
}
}
func TestSha256SumWriter_WriteString(t *testing.T) {
t.Parallel()
cases := []struct {
name string
writer *checksum.Sha256SumWriter
expectedErr string
}{
{
name: "with-hash-error",
writer: func() *checksum.Sha256SumWriter {
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{}, &testHash{
withWriteError: fmt.Errorf("failed hash write"),
})
require.NoError(t, err)
return writer
}(),
expectedErr: "failed hash write",
},
{
name: "with-file-error",
writer: func() *checksum.Sha256SumWriter {
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{
withWriteError: fmt.Errorf("failed file write"),
}, &testHash{
withWrittenBytes: 10,
})
require.NoError(t, err)
return writer
}(),
expectedErr: "failed file write",
},
{
name: "with-short-write-error",
writer: func() *checksum.Sha256SumWriter {
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{
withWrittenBytes: 9,
}, &testHash{
withWrittenBytes: 10,
})
require.NoError(t, err)
return writer
}(),
expectedErr: "short write",
},
{
name: "success",
writer: func() *checksum.Sha256SumWriter {
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{
withWrittenBytes: 10,
}, &testHash{
withWrittenBytes: 10,
})
require.NoError(t, err)
return writer
}(),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
_, err := tc.writer.WriteString("")
if tc.expectedErr != "" {
require.Error(err)
assert.ErrorContains(err, tc.expectedErr)
return
}
require.NoError(err)
})
}
}
func TestSha256SumWriter_WriteAndClose(t *testing.T) {
t.Parallel()
cases := []struct {
name string
writer *checksum.Sha256SumWriter
expectedErr string
}{
{
name: "with-hash-error",
writer: func() *checksum.Sha256SumWriter {
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{}, &testHash{
withWriteError: fmt.Errorf("failed hash write"),
})
require.NoError(t, err)
return writer
}(),
expectedErr: "failed hash write",
},
{
name: "with-file-error",
writer: func() *checksum.Sha256SumWriter {
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{
withWriteError: fmt.Errorf("failed file write"),
}, &testHash{
withWrittenBytes: 10,
})
require.NoError(t, err)
return writer
}(),
expectedErr: "failed file write",
},
{
name: "with-short-write-error",
writer: func() *checksum.Sha256SumWriter {
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{
withWrittenBytes: 9,
}, &testHash{
withWrittenBytes: 10,
})
require.NoError(t, err)
return writer
}(),
expectedErr: "short write",
},
{
name: "success",
writer: func() *checksum.Sha256SumWriter {
writer, err := checksum.NewSha256SumWriter(context.Background(), &testFile{
withWrittenBytes: 10,
}, &testHash{
withWrittenBytes: 10,
})
require.NoError(t, err)
return writer
}(),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
_, err := tc.writer.WriteAndClose([]byte{})
if tc.expectedErr != "" {
require.Error(err)
assert.ErrorContains(err, tc.expectedErr)
return
}
require.NoError(err)
})
}
}
func TestSha256SumWriter_Sum(t *testing.T) {
t.Parallel()
testCtx := context.Background()
testBytes := []byte("test-bytes")
tests := []struct {
name string
data []byte
sumWriter *checksum.Sha256SumWriter
opt []checksum.Option
wantSum []byte
wantErr bool
wantErrIs error
wantErrContains string
}{
{
name: "success",
data: testBytes,
sumWriter: func() *checksum.Sha256SumWriter {
w, err := checksum.NewSha256SumWriter(testCtx, &testFile{}, sha256.New())
require.NoError(t, err)
return w
}(),
wantSum: func() []byte {
hasher := sha256.New()
_, err := hasher.Write(testBytes)
require.NoError(t, err)
_, err = hasher.Write(testBytes)
require.NoError(t, err)
return hasher.Sum(nil)
}(),
},
{
name: "success-with-hex-encoding",
data: testBytes,
sumWriter: func() *checksum.Sha256SumWriter {
w, err := checksum.NewSha256SumWriter(testCtx, &testFile{}, sha256.New())
require.NoError(t, err)
return w
}(),
opt: []checksum.Option{checksum.WithHexEncoding(true)},
wantSum: func() []byte {
hasher := sha256.New()
_, err := hasher.Write(testBytes)
require.NoError(t, err)
_, err = hasher.Write(testBytes)
require.NoError(t, err)
h := hasher.Sum(nil)
return []byte(hex.EncodeToString(h[:]))
}(),
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
_, err := tc.sumWriter.Write(tc.data)
require.NoError(err)
_, err = tc.sumWriter.WriteString(string(tc.data))
require.NoError(err)
sum, err := tc.sumWriter.Sum(testCtx, tc.opt...)
if tc.wantErr {
require.Error(err)
assert.Empty(sum)
if tc.wantErrIs != nil {
assert.ErrorIs(err, tc.wantErrIs)
}
if tc.wantErrContains != "" {
assert.Contains(err.Error(), tc.wantErrContains)
}
return
}
require.NoError(err)
assert.Equal(tc.wantSum, sum)
require.NoError(tc.sumWriter.Close())
})
}
t.Run("success-with-closer", func(t *testing.T) {
c := testFile{
b: strings.Builder{},
closed: false,
}
w, err := checksum.NewSha256SumWriter(testCtx, &c, sha256.New())
require.NoError(t, err)
hasher := sha256.New()
_, err = hasher.Write(testBytes)
require.NoError(t, err)
_, err = hasher.Write(testBytes)
require.NoError(t, err)
wantSum := hasher.Sum(nil)
assert, require := assert.New(t), require.New(t)
_, err = w.Write(testBytes)
require.NoError(err)
_, err = w.WriteString(string(testBytes))
require.NoError(err)
sum, err := w.Sum(testCtx)
require.NoError(err)
assert.Equal(wantSum, sum)
require.NoError(w.Close())
require.True(c.closed)
})
}
func TestLoadSha256Sums(t *testing.T) {
cases := []struct {
name string