table testing with go routines only really works well when standard

assertions are ued. With suite.Required() there are issues in getting
the test results correct.

Instead, use standard assertions and use suite.T().Failed() to do
early returns.
This commit is contained in:
Erik Brakkee 2024-08-20 00:52:57 +02:00
parent 3f3635b056
commit 3059a16559
3 changed files with 63 additions and 29 deletions

View File

@ -13,7 +13,7 @@ generate:
vet: fmt
go vet ./...
test:
test: build
go test -count=1 -v ./...
build: generate vet

View File

@ -2,6 +2,7 @@ package iowrappers
import (
"context"
"errors"
"io"
"log"
)
@ -28,17 +29,16 @@ func NewChannelReadWriter(ctx context.Context, receiver <-chan []byte,
func (rw *ChannelReadWriter) Read(p []byte) (n int, err error) {
nread := copy(p, rw.readBuf)
if nread > 0 {
log.Printf("Read %v bytes", nread)
rw.readBuf = rw.readBuf[nread:]
return nread, nil
}
select {
case <-rw.ctx.Done():
log.Println("Context was canceled")
return 0, io.EOF
case data, ok := <-rw.receiver:
if !ok {
log.Println("Channel closed")
return 0, io.EOF
}
nread = copy(p, data)
@ -48,13 +48,18 @@ func (rw *ChannelReadWriter) Read(p []byte) (n int, err error) {
}
func (rw *ChannelReadWriter) Write(p []byte) (n int, err error) {
if rw.closed {
return 0, errors.New("Write on closed channel")
}
select {
case <-rw.ctx.Done():
rw.Close()
return 0, io.EOF
case rw.sender <- p:
}
return len(p), nil
}
func (rw *ChannelReadWriter) Close() error {
if !rw.closed {
close(rw.sender)

View File

@ -35,7 +35,6 @@ func (suite *ChannelReadWriterTestSuite) createChannel() {
timeoutCancelFunc()
cancelFunc()
}
suite.cancelFunc = cancelFunc
suite.conn = NewChannelReadWriter(ctx, toChannel, fromChannel)
}
@ -49,47 +48,77 @@ func (suite *ChannelReadWriterTestSuite) TearDownTest() {
type TestFunc func() any
func runAndWait(functions ...TestFunc) []any {
func (suite *ChannelReadWriterTestSuite) runAndWait(functions ...TestFunc) []any {
wg := sync.WaitGroup{}
wg.Add(len(functions))
res := make([]any, len(functions))
for i, function := range functions {
go func() {
defer func() {
wg.Done()
}()
res[i] = function()
wg.Done()
}()
}
wg.Wait()
return res
}
func (suite *ChannelReadWriterTestSuite) Test_SlicesLargeEnough() {
requires := suite.Require()
data := []byte("hello")
runAndWait(
func() any {
suite.toChannel <- data
log.Println("data sent")
return nil
func (suite *ChannelReadWriterTestSuite) Test_SuccessfulCommunication() {
tests := []struct {
name string
data string
chunkSizes []int
chunks []string
}{
{
name: "buffer_large_enough",
data: "hello",
chunkSizes: []int{10},
chunks: []string{"hello"},
},
func() any {
buf := make([]byte, len(data)*2)
n, err := suite.conn.Read(buf)
requires.Nil(err)
requires.Equal(n, len(data))
requires.Equal(data, buf[:n])
return nil
{
name: "two_reads_required",
data: "hello",
chunkSizes: []int{3, 10},
chunks: []string{"hel", "lo"},
},
)
}
{
name: "many_reads_required",
data: "hello",
chunkSizes: []int{1, 1, 1, 1, 1},
chunks: []string{"h", "e", "l", "l", "o"},
},
}
func (suite *ChannelReadWriterTestSuite) Test_SliceTooSmallFullReadInTwoParts() {
suite.FailNow("todo")
}
for _, test := range tests {
suite.Run(test.name, func() {
suite.runAndWait(
func() any {
select {
case <-suite.ctx.Done():
suite.FailNow("deadline reached")
log.Println("Write deadline exceeded")
case suite.toChannel <- []byte(test.data):
}
return nil
},
func() any {
remainder := test.data
for i, chunkSize := range test.chunkSizes {
buf := make([]byte, chunkSize)
n, err := suite.conn.Read(buf)
suite.Nil(err)
suite.Equal(n, len(test.chunks[i]))
suite.Equal([]byte(remainder[:n]), buf[:n])
remainder = remainder[n:]
}
return nil
},
)
})
}
func (suite *ChannelReadWriterTestSuite) Test_SliceTooSmallFullREadInManyParts() {
suite.FailNow("todo")
}
func (suite *ChannelReadWriterTestSuite) Test_Close() {