2023-09-08 00:46:34 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"io"
|
|
|
|
"testing"
|
|
|
|
)
|
|
|
|
|
|
|
|
func TestReadTrackingBodyRetrySuccess(t *testing.T) {
|
|
|
|
f := func(s string) {
|
|
|
|
t.Helper()
|
|
|
|
rtb := &readTrackingBody{
|
|
|
|
r: io.NopCloser(bytes.NewBufferString(s)),
|
|
|
|
}
|
|
|
|
if !rtb.canRetry() {
|
|
|
|
t.Fatalf("canRetry() must return true")
|
|
|
|
}
|
|
|
|
for i := 0; i < 5; i++ {
|
|
|
|
data, err := io.ReadAll(rtb)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error when reading all the data at iteration %d: %s", i, err)
|
|
|
|
}
|
|
|
|
if string(data) != s {
|
|
|
|
t.Fatalf("unexpected data read at iteration %d\ngot\n%s\nwant\n%s", i, data, s)
|
|
|
|
}
|
|
|
|
if err := rtb.Close(); err != nil {
|
|
|
|
t.Fatalf("unexpected error when closing readTrackingBody at iteration %d: %s", i, err)
|
|
|
|
}
|
|
|
|
if !rtb.canRetry() {
|
|
|
|
t.Fatalf("canRetry() must return true at iteration %d", i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
f("")
|
|
|
|
f("foo")
|
|
|
|
f("foobar")
|
|
|
|
f(newTestString(maxRequestBodySizeToRetry.IntN()))
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestReadTrackingBodyRetryFailure(t *testing.T) {
|
|
|
|
f := func(s string) {
|
|
|
|
t.Helper()
|
|
|
|
rtb := &readTrackingBody{
|
|
|
|
r: io.NopCloser(bytes.NewBufferString(s)),
|
|
|
|
}
|
|
|
|
if !rtb.canRetry() {
|
|
|
|
t.Fatalf("canRetry() must return true")
|
|
|
|
}
|
|
|
|
buf := make([]byte, 1)
|
|
|
|
n, err := rtb.Read(buf)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error when reading a single byte: %s", err)
|
|
|
|
}
|
|
|
|
if n != 1 {
|
|
|
|
t.Fatalf("unexpected number of bytes read; got %d; want 1", n)
|
|
|
|
}
|
|
|
|
data, err := io.ReadAll(rtb)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error when reading all the data: %s", err)
|
|
|
|
}
|
|
|
|
if string(buf)+string(data) != s {
|
|
|
|
t.Fatalf("unexpected data read\ngot\n%s\nwant\n%s", string(buf)+string(data), s)
|
|
|
|
}
|
|
|
|
if err := rtb.Close(); err != nil {
|
|
|
|
t.Fatalf("unexpected error when closing readTrackingBody: %s", err)
|
|
|
|
}
|
|
|
|
if rtb.canRetry() {
|
|
|
|
t.Fatalf("canRetry() must return false")
|
|
|
|
}
|
|
|
|
|
|
|
|
data, err = io.ReadAll(rtb)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("expecting non-nil error")
|
|
|
|
}
|
|
|
|
if len(data) != 0 {
|
|
|
|
t.Fatalf("unexpected non-empty data read: %q", data)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
f(newTestString(maxRequestBodySizeToRetry.IntN() + 1))
|
|
|
|
f(newTestString(2 * maxRequestBodySizeToRetry.IntN()))
|
|
|
|
}
|
|
|
|
|
2024-07-02 14:32:32 +02:00
|
|
|
// request body not over maxRequestBodySizeToRetry
|
|
|
|
// 1. When writing data downstream, buf only caches part of the data because the downstream connection is disconnected.
|
|
|
|
// 2. retry request: because buf caches some data, first read buf and then read stream when retrying
|
|
|
|
// 3. retry request: the data has been read to buf in the second step. if the request fails, retry to read all buf later.
|
|
|
|
func TestRetryReadSuccessAfterPartialRead(t *testing.T) {
|
|
|
|
f := func(s string) {
|
|
|
|
rtb := &readTrackingBody{
|
|
|
|
r: io.NopCloser(bytes.NewBufferString(s)),
|
|
|
|
buf: make([]byte, 0, len(s)),
|
|
|
|
}
|
|
|
|
|
|
|
|
var data []byte
|
|
|
|
var err error
|
|
|
|
halfSize := len(s) / 2
|
|
|
|
if halfSize == 0 {
|
|
|
|
halfSize = 100
|
|
|
|
}
|
|
|
|
buf := make([]byte, halfSize)
|
|
|
|
var n int
|
|
|
|
|
|
|
|
// read part of the data
|
|
|
|
n, err = rtb.Read(buf[:])
|
|
|
|
data = append(data, buf[:n]...)
|
|
|
|
if err != nil && err != io.EOF {
|
|
|
|
t.Fatalf("unexpected error: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// request failed when output stream is closed (eg: server connection reset)
|
|
|
|
// would close the reader
|
|
|
|
if err := rtb.Close(); err != nil {
|
|
|
|
t.Fatalf("unexpected error when closing readTrackingBody: %s", err)
|
|
|
|
}
|
|
|
|
if !rtb.canRetry() {
|
|
|
|
t.Fatalf("canRetry() must return true")
|
|
|
|
}
|
|
|
|
|
|
|
|
// retry read (read buf + remaining data)
|
|
|
|
data = data[:0]
|
|
|
|
err = nil
|
|
|
|
for err == nil {
|
|
|
|
n, err = rtb.Read(buf[:])
|
|
|
|
data = append(data, buf[:n]...)
|
|
|
|
}
|
|
|
|
if err != io.EOF {
|
|
|
|
t.Fatalf("unexpected error: %s", err)
|
|
|
|
}
|
|
|
|
if string(data) != s {
|
|
|
|
t.Fatalf("unexpected data read; got\n%s\nwant\n%s", data, s)
|
|
|
|
}
|
|
|
|
// cannotRetry return false
|
|
|
|
// because the request data is not over maxRequestBodySizeToRetry limit
|
|
|
|
if !rtb.canRetry() {
|
|
|
|
t.Fatalf("canRetry() must return true")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
f("")
|
|
|
|
f("foo")
|
|
|
|
f("foobar")
|
|
|
|
f(newTestString(maxRequestBodySizeToRetry.IntN()))
|
|
|
|
}
|
|
|
|
|
|
|
|
// request body over maxRequestBodySizeToRetry
|
|
|
|
// 1. When writing data downstream, buf only caches part of the data because the downstream connection is disconnected.
|
|
|
|
// 2. retry request: because buf caches some data, first read buf and then read stream when retrying
|
|
|
|
// 3. retry request: the data has been read to buf in the second step. if the request fails, retry to read all buf later.
|
|
|
|
func TestRetryReadSuccessAfterPartialReadAndCannotRetryAgain(t *testing.T) {
|
|
|
|
f := func(s string) {
|
|
|
|
rtb := &readTrackingBody{
|
|
|
|
r: io.NopCloser(bytes.NewBufferString(s)),
|
|
|
|
buf: make([]byte, 0, len(s)),
|
|
|
|
}
|
|
|
|
|
|
|
|
var data []byte
|
|
|
|
var err error
|
|
|
|
halfSize := len(s) / 2
|
|
|
|
if halfSize == 0 {
|
|
|
|
halfSize = 100
|
|
|
|
}
|
|
|
|
buf := make([]byte, halfSize)
|
|
|
|
var n int
|
|
|
|
|
|
|
|
// read part of the data
|
|
|
|
n, err = rtb.Read(buf[:])
|
|
|
|
data = append(data, buf[:n]...)
|
|
|
|
if err != nil && err != io.EOF {
|
|
|
|
t.Fatalf("unexpected error: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// request failed when output stream is closed (eg: server connection reset)
|
|
|
|
if err := rtb.Close(); err != nil {
|
|
|
|
t.Fatalf("unexpected error when closing readTrackingBody: %s", err)
|
|
|
|
}
|
|
|
|
if !rtb.canRetry() {
|
|
|
|
t.Fatalf("canRetry() must return true")
|
|
|
|
}
|
|
|
|
|
|
|
|
// retry read (read buf + remaining data)
|
|
|
|
data = data[:0]
|
|
|
|
err = nil
|
|
|
|
for err == nil {
|
|
|
|
n, err = rtb.Read(buf[:])
|
|
|
|
data = append(data, buf[:n]...)
|
|
|
|
}
|
|
|
|
if err != io.EOF {
|
|
|
|
t.Fatalf("unexpected error: %s", err)
|
|
|
|
}
|
|
|
|
if string(data) != s {
|
|
|
|
t.Fatalf("unexpected data read; got\n%s\nwant\n%s", data, s)
|
|
|
|
}
|
|
|
|
|
|
|
|
// cannotRetry returns true
|
|
|
|
// because the request data is over maxRequestBodySizeToRetry limit
|
|
|
|
if rtb.canRetry() {
|
|
|
|
t.Fatalf("canRetry() must return false")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
f(newTestString(maxRequestBodySizeToRetry.IntN() + 1))
|
|
|
|
f(newTestString(2 * maxRequestBodySizeToRetry.IntN()))
|
|
|
|
}
|
|
|
|
|
2023-09-08 00:46:34 +02:00
|
|
|
func newTestString(sLen int) string {
|
|
|
|
return string(make([]byte, sLen))
|
|
|
|
}
|