refactor: split flat internal/ module into modules-per-cmd (#64)
This commit is contained in:
261
internal/cmd/write/buffer_batcher_internal_test.go
Normal file
261
internal/cmd/write/buffer_batcher_internal_test.go
Normal file
@ -0,0 +1,261 @@
|
||||
package write
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// errorReader mocks io.Reader but returns an error.
|
||||
type errorReader struct{}
|
||||
|
||||
func (r *errorReader) Read(p []byte) (n int, err error) {
|
||||
return 0, fmt.Errorf("error")
|
||||
}
|
||||
|
||||
func TestBatcher_read(t *testing.T) {
|
||||
type args struct {
|
||||
cancel bool
|
||||
r io.Reader
|
||||
max int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
expErr error
|
||||
}{
|
||||
{
|
||||
name: "reading two lines produces 2 lines",
|
||||
args: args{
|
||||
r: strings.NewReader("m1,t1=v1 f1=1\nm2,t2=v2 f2=2"),
|
||||
},
|
||||
want: []string{"m1,t1=v1 f1=1\n", "m2,t2=v2 f2=2"},
|
||||
},
|
||||
{
|
||||
name: "canceling returns no lines",
|
||||
args: args{
|
||||
cancel: true,
|
||||
r: strings.NewReader("m1,t1=v1 f1=1"),
|
||||
},
|
||||
want: nil,
|
||||
expErr: context.Canceled,
|
||||
},
|
||||
{
|
||||
name: "error from reader returns error",
|
||||
args: args{
|
||||
r: &errorReader{},
|
||||
},
|
||||
want: nil,
|
||||
expErr: fmt.Errorf("error"),
|
||||
},
|
||||
{
|
||||
name: "error when input exceeds max line length",
|
||||
args: args{
|
||||
r: strings.NewReader("m1,t1=v1 f1=1"),
|
||||
max: 5,
|
||||
},
|
||||
want: nil,
|
||||
expErr: ErrLineTooLong,
|
||||
},
|
||||
{
|
||||
name: "lines greater than MaxScanTokenSize are allowed",
|
||||
args: args{
|
||||
r: strings.NewReader(strings.Repeat("a", bufio.MaxScanTokenSize+1)),
|
||||
max: bufio.MaxScanTokenSize + 2,
|
||||
},
|
||||
want: []string{strings.Repeat("a", bufio.MaxScanTokenSize+1)},
|
||||
},
|
||||
{
|
||||
name: "lines greater than MaxScanTokenSize by default are not allowed",
|
||||
args: args{
|
||||
r: strings.NewReader(strings.Repeat("a", bufio.MaxScanTokenSize+1)),
|
||||
},
|
||||
want: nil,
|
||||
expErr: ErrLineTooLong,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
var cancel context.CancelFunc
|
||||
if tt.args.cancel {
|
||||
ctx, cancel = context.WithCancel(ctx)
|
||||
cancel()
|
||||
}
|
||||
|
||||
b := &BufferBatcher{MaxLineLength: tt.args.max}
|
||||
var got []string
|
||||
|
||||
lines := make(chan []byte)
|
||||
errC := make(chan error, 1)
|
||||
|
||||
go b.read(ctx, tt.args.r, lines, errC)
|
||||
|
||||
if cancel == nil {
|
||||
for line := range lines {
|
||||
got = append(got, string(line))
|
||||
}
|
||||
}
|
||||
|
||||
err := <-errC
|
||||
assert.Equal(t, err, tt.expErr)
|
||||
assert.Equal(t, got, tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatcher_write(t *testing.T) {
|
||||
type fields struct {
|
||||
MaxFlushBytes int
|
||||
MaxFlushInterval time.Duration
|
||||
}
|
||||
type args struct {
|
||||
cancel bool
|
||||
writeError bool
|
||||
line string
|
||||
lines chan []byte
|
||||
errC chan error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
want string
|
||||
wantErr bool
|
||||
wantNoCall bool
|
||||
}{
|
||||
{
|
||||
name: "sending a single line will send a line to the service",
|
||||
fields: fields{
|
||||
MaxFlushBytes: 1,
|
||||
},
|
||||
args: args{
|
||||
line: "m1,t1=v1 f1=1",
|
||||
lines: make(chan []byte),
|
||||
errC: make(chan error),
|
||||
},
|
||||
want: "m1,t1=v1 f1=1",
|
||||
},
|
||||
{
|
||||
name: "sending a single line and waiting for a timeout will send a line to the service",
|
||||
fields: fields{
|
||||
MaxFlushInterval: time.Millisecond,
|
||||
},
|
||||
args: args{
|
||||
line: "m1,t1=v1 f1=1",
|
||||
lines: make(chan []byte),
|
||||
errC: make(chan error),
|
||||
},
|
||||
want: "m1,t1=v1 f1=1",
|
||||
},
|
||||
{
|
||||
name: "batcher service returning error stops the batcher after timeout",
|
||||
fields: fields{
|
||||
MaxFlushInterval: time.Millisecond,
|
||||
},
|
||||
args: args{
|
||||
writeError: true,
|
||||
line: "m1,t1=v1 f1=1",
|
||||
lines: make(chan []byte),
|
||||
errC: make(chan error),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "canceling will batcher no data to service",
|
||||
fields: fields{
|
||||
MaxFlushBytes: 1,
|
||||
},
|
||||
args: args{
|
||||
cancel: true,
|
||||
line: "m1,t1=v1 f1=1",
|
||||
lines: make(chan []byte, 1),
|
||||
errC: make(chan error, 1),
|
||||
},
|
||||
wantErr: true,
|
||||
wantNoCall: true,
|
||||
},
|
||||
{
|
||||
name: "batcher service returning error stops the batcher",
|
||||
fields: fields{
|
||||
MaxFlushBytes: 1,
|
||||
},
|
||||
args: args{
|
||||
writeError: true,
|
||||
line: "m1,t1=v1 f1=1",
|
||||
lines: make(chan []byte),
|
||||
errC: make(chan error),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "blank line is not sent to service",
|
||||
fields: fields{
|
||||
MaxFlushBytes: 1,
|
||||
},
|
||||
args: args{
|
||||
line: "\n",
|
||||
lines: make(chan []byte),
|
||||
errC: make(chan error),
|
||||
},
|
||||
wantNoCall: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
var cancel context.CancelFunc
|
||||
if tt.args.cancel {
|
||||
ctx, cancel = context.WithCancel(ctx)
|
||||
}
|
||||
|
||||
// mocking the batcher service here to either return an error
|
||||
// or get back all the bytes from the reader.
|
||||
writeCalled := false
|
||||
b := &BufferBatcher{
|
||||
MaxFlushBytes: tt.fields.MaxFlushBytes,
|
||||
MaxFlushInterval: tt.fields.MaxFlushInterval,
|
||||
}
|
||||
var got string
|
||||
writeFn := func(batch []byte) error {
|
||||
writeCalled = true
|
||||
if tt.wantErr {
|
||||
return errors.New("I broke")
|
||||
}
|
||||
got = string(batch)
|
||||
return nil
|
||||
}
|
||||
|
||||
go b.write(ctx, writeFn, tt.args.lines, tt.args.errC)
|
||||
|
||||
if cancel != nil {
|
||||
cancel()
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
tt.args.lines <- []byte(tt.args.line)
|
||||
// if the max flush interval is not zero, we are testing to see
|
||||
// if the data is flushed via the timer rather than forced by
|
||||
// closing the channel.
|
||||
if tt.fields.MaxFlushInterval != 0 {
|
||||
time.Sleep(tt.fields.MaxFlushInterval * 100)
|
||||
}
|
||||
close(tt.args.lines)
|
||||
|
||||
err := <-tt.args.errC
|
||||
require.Equal(t, tt.wantErr, err != nil)
|
||||
|
||||
require.Equal(t, tt.wantNoCall, !writeCalled)
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user