6a935d5407
1. introduce implementations of HTTPTransport and HTTPClient that apply an error wrapping policy using the constructor for a generic top-level error wrapper 2. make sure we use the implementations in point 1 when we are constructing HTTPTransport and HTTPClient 3. make sure we apply error wrapping using the constructor for a generic top-level error wrapper when reading bodies 4. acknowledge that error wrapping would be broken if we do not return the same classification _and_ operation when we wrap an already wrapped error, so fix the to code to do that 5. acknowledge that the classifiers already deal with preserving the error string and explain why this is a quirk and why we cannot remove it right now and what needs to happen to safely remove this quirk from the codebase Closes https://github.com/ooni/probe/issues/1860
211 lines
5.3 KiB
Go
211 lines
5.3 KiB
Go
package netxlite
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/ooni/probe-cli/v3/internal/netxlite/mocks"
|
|
)
|
|
|
|
func TestReadAllContext(t *testing.T) {
|
|
t.Run("with success and background context", func(t *testing.T) {
|
|
r := strings.NewReader("deadbeef")
|
|
ctx := context.Background()
|
|
out, err := ReadAllContext(ctx, r)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(out) != 8 {
|
|
t.Fatal("not the expected number of bytes")
|
|
}
|
|
})
|
|
|
|
t.Run("with failure and background context", func(t *testing.T) {
|
|
expected := errors.New("mocked error")
|
|
r := &mocks.Reader{
|
|
MockRead: func(b []byte) (int, error) {
|
|
return 0, expected
|
|
},
|
|
}
|
|
ctx := context.Background()
|
|
out, err := ReadAllContext(ctx, r)
|
|
if !errors.Is(err, expected) {
|
|
t.Fatal("not the error we expected", err)
|
|
}
|
|
var errWrapper *ErrWrapper
|
|
if !errors.As(err, &errWrapper) {
|
|
t.Fatal("the returned error is not wrapped")
|
|
}
|
|
if len(out) != 0 {
|
|
t.Fatal("not the expected number of bytes")
|
|
}
|
|
})
|
|
|
|
t.Run("with success and cancelled context", func(t *testing.T) {
|
|
wg := &sync.WaitGroup{}
|
|
wg.Add(1)
|
|
sigch := make(chan interface{})
|
|
r := &mocks.Reader{
|
|
MockRead: func(b []byte) (int, error) {
|
|
defer wg.Done()
|
|
<-sigch
|
|
// "When Read encounters an error or end-of-file condition
|
|
// after successfully reading n > 0 bytes, it returns
|
|
// the number of bytes read. It may return the (non-nil)
|
|
// error from the same call or return the error (and n == 0)
|
|
// from a subsequent call.""
|
|
//
|
|
// See https://pkg.go.dev/io#Reader
|
|
return len(b), io.EOF
|
|
},
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // fail immediately
|
|
out, err := ReadAllContext(ctx, r)
|
|
if !errors.Is(err, context.Canceled) {
|
|
t.Fatal("not the error we expected", err)
|
|
}
|
|
var errWrapper *ErrWrapper
|
|
if !errors.As(err, &errWrapper) {
|
|
t.Fatal("the returned error is not wrapped")
|
|
}
|
|
if len(out) != 0 {
|
|
t.Fatal("not the expected number of bytes")
|
|
}
|
|
close(sigch)
|
|
wg.Wait()
|
|
})
|
|
|
|
t.Run("with failure and cancelled context", func(t *testing.T) {
|
|
wg := &sync.WaitGroup{}
|
|
wg.Add(1)
|
|
sigch := make(chan interface{})
|
|
expected := errors.New("mocked error")
|
|
r := &mocks.Reader{
|
|
MockRead: func(b []byte) (int, error) {
|
|
defer wg.Done()
|
|
<-sigch
|
|
return 0, expected
|
|
},
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // fail immediately
|
|
out, err := ReadAllContext(ctx, r)
|
|
if !errors.Is(err, context.Canceled) {
|
|
t.Fatal("not the error we expected", err)
|
|
}
|
|
var errWrapper *ErrWrapper
|
|
if !errors.As(err, &errWrapper) {
|
|
t.Fatal("the returned error is not wrapped")
|
|
}
|
|
if len(out) != 0 {
|
|
t.Fatal("not the expected number of bytes")
|
|
}
|
|
close(sigch)
|
|
wg.Wait()
|
|
})
|
|
}
|
|
|
|
func TestCopyContext(t *testing.T) {
|
|
t.Run("with success and background context", func(t *testing.T) {
|
|
r := strings.NewReader("deadbeef")
|
|
ctx := context.Background()
|
|
out, err := CopyContext(ctx, io.Discard, r)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if out != 8 {
|
|
t.Fatal("not the expected number of bytes")
|
|
}
|
|
})
|
|
|
|
t.Run("with failure and background context", func(t *testing.T) {
|
|
expected := errors.New("mocked error")
|
|
r := &mocks.Reader{
|
|
MockRead: func(b []byte) (int, error) {
|
|
return 0, expected
|
|
},
|
|
}
|
|
ctx := context.Background()
|
|
out, err := CopyContext(ctx, io.Discard, r)
|
|
if !errors.Is(err, expected) {
|
|
t.Fatal("not the error we expected", err)
|
|
}
|
|
var errWrapper *ErrWrapper
|
|
if !errors.As(err, &errWrapper) {
|
|
t.Fatal("the returned error is not wrapped")
|
|
}
|
|
if out != 0 {
|
|
t.Fatal("not the expected number of bytes")
|
|
}
|
|
})
|
|
|
|
t.Run("with success and cancelled context", func(t *testing.T) {
|
|
wg := &sync.WaitGroup{}
|
|
wg.Add(1)
|
|
sigch := make(chan interface{})
|
|
r := &mocks.Reader{
|
|
MockRead: func(b []byte) (int, error) {
|
|
defer wg.Done()
|
|
<-sigch
|
|
// "When Read encounters an error or end-of-file condition
|
|
// after successfully reading n > 0 bytes, it returns
|
|
// the number of bytes read. It may return the (non-nil)
|
|
// error from the same call or return the error (and n == 0)
|
|
// from a subsequent call.""
|
|
//
|
|
// See https://pkg.go.dev/io#Reader
|
|
return len(b), io.EOF
|
|
},
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // fail immediately
|
|
out, err := CopyContext(ctx, io.Discard, r)
|
|
if !errors.Is(err, context.Canceled) {
|
|
t.Fatal("not the error we expected", err)
|
|
}
|
|
var errWrapper *ErrWrapper
|
|
if !errors.As(err, &errWrapper) {
|
|
t.Fatal("the returned error is not wrapped")
|
|
}
|
|
if out != 0 {
|
|
t.Fatal("not the expected number of bytes")
|
|
}
|
|
close(sigch)
|
|
wg.Wait()
|
|
})
|
|
|
|
t.Run("with failure and cancelled context", func(t *testing.T) {
|
|
wg := &sync.WaitGroup{}
|
|
wg.Add(1)
|
|
sigch := make(chan interface{})
|
|
expected := errors.New("mocked error")
|
|
r := &mocks.Reader{
|
|
MockRead: func(b []byte) (int, error) {
|
|
defer wg.Done()
|
|
<-sigch
|
|
return 0, expected
|
|
},
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // fail immediately
|
|
out, err := CopyContext(ctx, io.Discard, r)
|
|
if !errors.Is(err, context.Canceled) {
|
|
t.Fatal("not the error we expected", err)
|
|
}
|
|
var errWrapper *ErrWrapper
|
|
if !errors.As(err, &errWrapper) {
|
|
t.Fatal("the returned error is not wrapped")
|
|
}
|
|
if out != 0 {
|
|
t.Fatal("not the expected number of bytes")
|
|
}
|
|
close(sigch)
|
|
wg.Wait()
|
|
})
|
|
}
|