image/docker/body_reader_test.go

197 lines
5.2 KiB
Go

package docker
import (
"errors"
"math"
"net/http"
"testing"
"time"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestParseDecimalInString(t *testing.T) {
for _, prefix := range []string{"", "text", "0"} {
for _, suffix := range []string{"", "text"} {
for _, c := range []struct {
s string
v int64
}{
{"0", 0},
{"1", 1},
{"0700", 700}, // not octal
} {
input := prefix + c.s + suffix
res, pos, err := parseDecimalInString(input, len(prefix))
require.NoError(t, err, input)
assert.Equal(t, c.v, res, input)
assert.Equal(t, len(prefix)+len(c.s), pos, input)
}
for _, c := range []string{
"-1",
"xA",
"&",
"",
"999999999999999999999999999999999999999999999999999999999999999999",
} {
input := prefix + c + suffix
_, _, err := parseDecimalInString(input, len(prefix))
assert.Error(t, err, c)
}
}
}
}
func TestParseExpectedChar(t *testing.T) {
for _, prefix := range []string{"", "text", "0"} {
for _, suffix := range []string{"", "text"} {
input := prefix + "+" + suffix
pos, err := parseExpectedChar(input, len(prefix), '+')
require.NoError(t, err, input)
assert.Equal(t, len(prefix)+1, pos, input)
_, err = parseExpectedChar(input, len(prefix), '-')
assert.Error(t, err, input)
}
}
}
func TestParseContentRange(t *testing.T) {
for _, c := range []struct {
in string
first, last, completeLength int64
}{
{"bytes 0-0/1", 0, 0, 1},
{"bytes 010-020/030", 10, 20, 30},
{"bytes 1000-1010/*", 1000, 1010, -1},
} {
first, last, completeLength, err := parseContentRange(&http.Response{
Header: http.Header{
http.CanonicalHeaderKey("Content-Range"): []string{c.in},
},
})
require.NoError(t, err, c.in)
assert.Equal(t, c.first, first, c.in)
assert.Equal(t, c.last, last, c.in)
assert.Equal(t, c.completeLength, completeLength, c.in)
}
for _, hdr := range []http.Header{
nil,
{http.CanonicalHeaderKey("Content-Range"): []string{}},
{http.CanonicalHeaderKey("Content-Range"): []string{"bytes 1-2/3", "bytes 1-2/3"}},
} {
_, _, _, err := parseContentRange(&http.Response{
Header: hdr,
})
assert.Error(t, err)
}
for _, c := range []string{
"",
"notbytes 1-2/3",
"bytes ",
"bytes x-2/3",
"bytes 1*2/3",
"bytes 1",
"bytes 1-",
"bytes 1-x/3",
"bytes 1-2",
"bytes 1-2@3",
"bytes 1-2/",
"bytes 1-2/*a",
"bytes 1-2/3a",
} {
_, _, _, err := parseContentRange(&http.Response{
Header: http.Header{
http.CanonicalHeaderKey("Content-Range"): []string{c},
},
})
assert.Error(t, err, c, c)
}
}
func TestMillisecondsSinceOptional(t *testing.T) {
current := time.Date(2023, 2, 9, 8, 7, 6, 5, time.UTC)
res := millisecondsSinceOptional(current, time.Time{})
assert.True(t, math.IsNaN(res))
tm := current.Add(-60 * time.Second) // 60 seconds _before_ current
res = millisecondsSinceOptional(current, tm)
assert.Equal(t, res, 60_000.0)
}
func TestBodyReaderErrorIfNotReconnecting(t *testing.T) {
// Silence logrus.Info logs in the tested method
prevLevel := logrus.StandardLogger().Level
logrus.StandardLogger().SetLevel(logrus.WarnLevel)
t.Cleanup(func() {
logrus.StandardLogger().SetLevel(prevLevel)
})
for _, c := range []struct {
name string
previousRetry bool
currentOffset int64
currentTime int // milliseconds
expectReconnect bool
}{
{
name: "A lot of progress, after a long time, second retry",
previousRetry: true,
currentOffset: 2 * bodyReaderMinimumProgress,
currentTime: 2 * bodyReaderMSSinceLastRetry,
expectReconnect: true,
},
{
name: "A lot of progress, after little time, second retry",
previousRetry: true,
currentOffset: 2 * bodyReaderMinimumProgress,
currentTime: 1,
expectReconnect: true,
},
{
name: "Little progress, after a long time, second retry",
previousRetry: true,
currentOffset: 1,
currentTime: 2 * bodyReaderMSSinceLastRetry,
expectReconnect: true,
},
{
name: "Little progress, after little time, second retry",
previousRetry: true,
currentOffset: 1,
currentTime: 1,
expectReconnect: false,
},
{
name: "Little progress, after little time, first retry",
previousRetry: false,
currentOffset: 1,
currentTime: bodyReaderMSSinceLastRetry / 2,
expectReconnect: true,
},
} {
tm := time.Now()
br := bodyReader{}
if c.previousRetry {
br.lastRetryOffset = 2 * bodyReaderMinimumProgress
br.offset = br.lastRetryOffset + c.currentOffset
br.firstConnectionTime = tm.Add(-time.Duration(c.currentTime+2*bodyReaderMSSinceLastRetry) * time.Millisecond)
br.lastRetryTime = tm.Add(-time.Duration(c.currentTime) * time.Millisecond)
} else {
br.lastRetryOffset = -1
br.lastRetryTime = time.Time{}
br.offset = c.currentOffset
br.firstConnectionTime = tm.Add(-time.Duration(c.currentTime) * time.Millisecond)
}
err := br.errorIfNotReconnecting(errors.New("some error for error text only"), "URL for error text only")
if c.expectReconnect {
assert.NoError(t, err, c.name, br)
} else {
assert.Error(t, err, c.name, br)
}
}
}