refactor: start pivoting netx (#396)
What do I mean by pivoting? Netx is currently organized by row:
```
| dialer | quicdialer | resolver | ...
saving | | | | ...
errorwrapping | | | | ...
logging | | | | ...
mocking/sys | | | | ...
```
Every row needs to implement saving, errorwrapping, logging, mocking (or
adapting to the system or to some underlying library).
This causes cross package dependencies and, in turn, complexity. For
example, we need the `trace` package for supporting saving.
And `dialer`, `quickdialer`, et al. need to depend on such a package.
The same goes for errorwrapping.
This arrangement further complicates testing. For example, I am
currently working on https://github.com/ooni/probe/issues/1505 and
I realize it need to repeat integration tests in multiple places.
Let's say instead we pivot the above matrix as follows:
```
| saving | errorwrapping | logging | ...
dialer | | | | ...
quicdialer | | | | ...
logging | | | | ...
mocking/sys | | | | ...
...
```
In this way, now every row contains everything related to a specific
action to perform. We can now share code without relying on extra
support packages. What's more, we can write tests and, judding from
the way in which things are made, it seems we only need integration
testing in `errorwrapping` because it's where data quality matters
whereas, in all other cases, unit testing is fine.
I am going, therefore, to proceed with these changes and "pivot"
`netx`. Hopefully, it won't be too painful.
2021-06-23 15:53:12 +02:00
|
|
|
package netxlite
|
2021-02-02 12:05:47 +01:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"io"
|
|
|
|
"net"
|
refactor: start pivoting netx (#396)
What do I mean by pivoting? Netx is currently organized by row:
```
| dialer | quicdialer | resolver | ...
saving | | | | ...
errorwrapping | | | | ...
logging | | | | ...
mocking/sys | | | | ...
```
Every row needs to implement saving, errorwrapping, logging, mocking (or
adapting to the system or to some underlying library).
This causes cross package dependencies and, in turn, complexity. For
example, we need the `trace` package for supporting saving.
And `dialer`, `quickdialer`, et al. need to depend on such a package.
The same goes for errorwrapping.
This arrangement further complicates testing. For example, I am
currently working on https://github.com/ooni/probe/issues/1505 and
I realize it need to repeat integration tests in multiple places.
Let's say instead we pivot the above matrix as follows:
```
| saving | errorwrapping | logging | ...
dialer | | | | ...
quicdialer | | | | ...
logging | | | | ...
mocking/sys | | | | ...
...
```
In this way, now every row contains everything related to a specific
action to perform. We can now share code without relying on extra
support packages. What's more, we can write tests and, judding from
the way in which things are made, it seems we only need integration
testing in `errorwrapping` because it's where data quality matters
whereas, in all other cases, unit testing is fine.
I am going, therefore, to proceed with these changes and "pivot"
`netx`. Hopefully, it won't be too painful.
2021-06-23 15:53:12 +02:00
|
|
|
"strings"
|
2021-09-08 21:19:51 +02:00
|
|
|
"sync"
|
2021-02-02 12:05:47 +01:00
|
|
|
"testing"
|
refactor: start pivoting netx (#396)
What do I mean by pivoting? Netx is currently organized by row:
```
| dialer | quicdialer | resolver | ...
saving | | | | ...
errorwrapping | | | | ...
logging | | | | ...
mocking/sys | | | | ...
```
Every row needs to implement saving, errorwrapping, logging, mocking (or
adapting to the system or to some underlying library).
This causes cross package dependencies and, in turn, complexity. For
example, we need the `trace` package for supporting saving.
And `dialer`, `quickdialer`, et al. need to depend on such a package.
The same goes for errorwrapping.
This arrangement further complicates testing. For example, I am
currently working on https://github.com/ooni/probe/issues/1505 and
I realize it need to repeat integration tests in multiple places.
Let's say instead we pivot the above matrix as follows:
```
| saving | errorwrapping | logging | ...
dialer | | | | ...
quicdialer | | | | ...
logging | | | | ...
mocking/sys | | | | ...
...
```
In this way, now every row contains everything related to a specific
action to perform. We can now share code without relying on extra
support packages. What's more, we can write tests and, judding from
the way in which things are made, it seems we only need integration
testing in `errorwrapping` because it's where data quality matters
whereas, in all other cases, unit testing is fine.
I am going, therefore, to proceed with these changes and "pivot"
`netx`. Hopefully, it won't be too painful.
2021-06-23 15:53:12 +02:00
|
|
|
"time"
|
2021-02-02 12:05:47 +01:00
|
|
|
|
refactor: start pivoting netx (#396)
What do I mean by pivoting? Netx is currently organized by row:
```
| dialer | quicdialer | resolver | ...
saving | | | | ...
errorwrapping | | | | ...
logging | | | | ...
mocking/sys | | | | ...
```
Every row needs to implement saving, errorwrapping, logging, mocking (or
adapting to the system or to some underlying library).
This causes cross package dependencies and, in turn, complexity. For
example, we need the `trace` package for supporting saving.
And `dialer`, `quickdialer`, et al. need to depend on such a package.
The same goes for errorwrapping.
This arrangement further complicates testing. For example, I am
currently working on https://github.com/ooni/probe/issues/1505 and
I realize it need to repeat integration tests in multiple places.
Let's say instead we pivot the above matrix as follows:
```
| saving | errorwrapping | logging | ...
dialer | | | | ...
quicdialer | | | | ...
logging | | | | ...
mocking/sys | | | | ...
...
```
In this way, now every row contains everything related to a specific
action to perform. We can now share code without relying on extra
support packages. What's more, we can write tests and, judding from
the way in which things are made, it seems we only need integration
testing in `errorwrapping` because it's where data quality matters
whereas, in all other cases, unit testing is fine.
I am going, therefore, to proceed with these changes and "pivot"
`netx`. Hopefully, it won't be too painful.
2021-06-23 15:53:12 +02:00
|
|
|
"github.com/apex/log"
|
2022-05-31 20:02:11 +02:00
|
|
|
"github.com/ooni/probe-cli/v3/internal/model"
|
2022-01-03 13:53:23 +01:00
|
|
|
"github.com/ooni/probe-cli/v3/internal/model/mocks"
|
2022-07-01 12:22:22 +02:00
|
|
|
"github.com/ooni/probe-cli/v3/internal/testingx"
|
2021-02-02 12:05:47 +01:00
|
|
|
)
|
|
|
|
|
2022-06-05 18:44:17 +02:00
|
|
|
func TestNewDialerWithStdlibResolver(t *testing.T) {
|
|
|
|
dialer := NewDialerWithStdlibResolver(model.DiscardLogger)
|
|
|
|
logger := dialer.(*dialerLogger)
|
|
|
|
if logger.DebugLogger != model.DiscardLogger {
|
|
|
|
t.Fatal("invalid logger")
|
|
|
|
}
|
|
|
|
// typecheck the resolver
|
2022-07-01 12:22:22 +02:00
|
|
|
reso := logger.Dialer.(*dialerResolverWithTracing)
|
2022-06-05 18:44:17 +02:00
|
|
|
typecheckForSystemResolver(t, reso.Resolver, model.DiscardLogger)
|
|
|
|
// typecheck the dialer
|
|
|
|
logger = reso.Dialer.(*dialerLogger)
|
|
|
|
if logger.DebugLogger != model.DiscardLogger {
|
|
|
|
t.Fatal("invalid logger")
|
|
|
|
}
|
|
|
|
errWrapper := logger.Dialer.(*dialerErrWrapper)
|
|
|
|
_ = errWrapper.Dialer.(*DialerSystem)
|
|
|
|
}
|
|
|
|
|
2022-05-31 20:02:11 +02:00
|
|
|
type extensionDialerFirst struct {
|
|
|
|
model.Dialer
|
|
|
|
}
|
|
|
|
|
2022-06-01 08:31:20 +02:00
|
|
|
type dialerWrapperFirst struct{}
|
|
|
|
|
|
|
|
func (*dialerWrapperFirst) WrapDialer(d model.Dialer) model.Dialer {
|
|
|
|
return &extensionDialerFirst{d}
|
|
|
|
}
|
|
|
|
|
2022-05-31 20:02:11 +02:00
|
|
|
type extensionDialerSecond struct {
|
|
|
|
model.Dialer
|
|
|
|
}
|
|
|
|
|
2022-06-01 08:31:20 +02:00
|
|
|
type dialerWrapperSecond struct{}
|
|
|
|
|
|
|
|
func (*dialerWrapperSecond) WrapDialer(d model.Dialer) model.Dialer {
|
|
|
|
return &extensionDialerSecond{d}
|
|
|
|
}
|
2021-09-08 00:00:53 +02:00
|
|
|
func TestNewDialer(t *testing.T) {
|
|
|
|
t.Run("produces a chain with the expected types", func(t *testing.T) {
|
2022-06-01 08:31:20 +02:00
|
|
|
modifiers := []model.DialerWrapper{
|
|
|
|
&dialerWrapperFirst{},
|
|
|
|
nil, // explicitly test for this documented case
|
|
|
|
&dialerWrapperSecond{},
|
2022-05-31 20:02:11 +02:00
|
|
|
}
|
|
|
|
d := NewDialerWithoutResolver(log.Log, modifiers...)
|
2021-09-08 21:19:51 +02:00
|
|
|
logger := d.(*dialerLogger)
|
2022-01-03 13:53:23 +01:00
|
|
|
if logger.DebugLogger != log.Log {
|
2021-09-08 00:00:53 +02:00
|
|
|
t.Fatal("invalid logger")
|
|
|
|
}
|
2022-07-01 12:22:22 +02:00
|
|
|
reso := logger.Dialer.(*dialerResolverWithTracing)
|
2022-05-31 20:02:11 +02:00
|
|
|
if _, okay := reso.Resolver.(*NullResolver); !okay {
|
2021-09-08 00:00:53 +02:00
|
|
|
t.Fatal("invalid Resolver type")
|
|
|
|
}
|
|
|
|
logger = reso.Dialer.(*dialerLogger)
|
2022-01-03 13:53:23 +01:00
|
|
|
if logger.DebugLogger != log.Log {
|
2021-09-08 00:00:53 +02:00
|
|
|
t.Fatal("invalid logger")
|
|
|
|
}
|
2022-05-31 20:02:11 +02:00
|
|
|
ext2 := logger.Dialer.(*extensionDialerSecond)
|
|
|
|
ext1 := ext2.Dialer.(*extensionDialerFirst)
|
|
|
|
errWrapper := ext1.Dialer.(*dialerErrWrapper)
|
|
|
|
_ = errWrapper.Dialer.(*DialerSystem)
|
2021-09-08 00:00:53 +02:00
|
|
|
})
|
2021-09-05 19:55:28 +02:00
|
|
|
}
|
|
|
|
|
2021-09-08 00:00:53 +02:00
|
|
|
func TestDialerSystem(t *testing.T) {
|
2021-09-08 21:19:51 +02:00
|
|
|
t.Run("has a default timeout", func(t *testing.T) {
|
2022-05-31 20:02:11 +02:00
|
|
|
d := &DialerSystem{}
|
2022-10-12 17:38:33 +02:00
|
|
|
timeout := d.configuredTimeout()
|
|
|
|
if timeout != dialerDefaultTimeout {
|
2021-09-08 21:19:51 +02:00
|
|
|
t.Fatal("unexpected default timeout")
|
2021-09-08 00:00:53 +02:00
|
|
|
}
|
|
|
|
})
|
2021-02-02 12:05:47 +01:00
|
|
|
|
2021-09-08 21:19:51 +02:00
|
|
|
t.Run("we can change the timeout for testing", func(t *testing.T) {
|
|
|
|
const smaller = 1 * time.Second
|
2022-05-31 20:02:11 +02:00
|
|
|
d := &DialerSystem{timeout: smaller}
|
2022-10-12 17:38:33 +02:00
|
|
|
timeout := d.configuredTimeout()
|
|
|
|
if timeout != smaller {
|
2021-09-08 21:19:51 +02:00
|
|
|
t.Fatal("unexpected timeout")
|
2021-09-08 00:00:53 +02:00
|
|
|
}
|
|
|
|
})
|
2021-02-02 12:05:47 +01:00
|
|
|
|
2021-09-08 00:00:53 +02:00
|
|
|
t.Run("CloseIdleConnections", func(t *testing.T) {
|
2022-05-31 20:02:11 +02:00
|
|
|
d := &DialerSystem{}
|
2021-09-08 21:19:51 +02:00
|
|
|
d.CloseIdleConnections() // to avoid missing coverage
|
2021-09-08 00:00:53 +02:00
|
|
|
})
|
2021-02-02 12:05:47 +01:00
|
|
|
|
2021-09-08 21:19:51 +02:00
|
|
|
t.Run("DialContext", func(t *testing.T) {
|
|
|
|
t.Run("with canceled context", func(t *testing.T) {
|
2022-05-31 20:02:11 +02:00
|
|
|
d := &DialerSystem{}
|
2021-09-08 21:19:51 +02:00
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
cancel() // immediately!
|
2022-04-12 11:43:12 +02:00
|
|
|
conn, err := d.DialContext(ctx, "tcp", "8.8.8.8:443")
|
|
|
|
if err == nil || !strings.HasSuffix(err.Error(), "operation was canceled") {
|
2021-09-08 21:19:51 +02:00
|
|
|
t.Fatal("unexpected err", err)
|
|
|
|
}
|
|
|
|
if conn != nil {
|
|
|
|
t.Fatal("unexpected conn")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("enforces the configured timeout", func(t *testing.T) {
|
2021-09-29 16:04:26 +02:00
|
|
|
const timeout = 1 * time.Nanosecond
|
2022-05-31 20:02:11 +02:00
|
|
|
d := &DialerSystem{timeout: timeout}
|
2021-09-08 21:19:51 +02:00
|
|
|
ctx := context.Background()
|
|
|
|
start := time.Now()
|
|
|
|
conn, err := d.DialContext(ctx, "tcp", "dns.google:443")
|
|
|
|
stop := time.Now()
|
|
|
|
if err == nil || !strings.HasSuffix(err.Error(), "i/o timeout") {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if conn != nil {
|
|
|
|
t.Fatal("unexpected conn")
|
|
|
|
}
|
|
|
|
if stop.Sub(start) > 100*time.Millisecond {
|
|
|
|
t.Fatal("undable to enforce timeout")
|
|
|
|
}
|
|
|
|
})
|
2021-09-08 00:00:53 +02:00
|
|
|
})
|
2021-02-02 12:05:47 +01:00
|
|
|
}
|
|
|
|
|
2022-07-01 12:22:22 +02:00
|
|
|
func TestDialerResolverWithTracing(t *testing.T) {
|
2021-09-08 00:00:53 +02:00
|
|
|
t.Run("DialContext", func(t *testing.T) {
|
2021-09-08 21:19:51 +02:00
|
|
|
t.Run("fails without a port", func(t *testing.T) {
|
2022-07-01 12:22:22 +02:00
|
|
|
d := &dialerResolverWithTracing{
|
2022-05-31 20:02:11 +02:00
|
|
|
Dialer: &DialerSystem{},
|
2022-06-06 14:46:44 +02:00
|
|
|
Resolver: NewUnwrappedStdlibResolver(),
|
2021-09-08 00:00:53 +02:00
|
|
|
}
|
2021-09-08 21:19:51 +02:00
|
|
|
const missingPort = "ooni.nu"
|
|
|
|
conn, err := d.DialContext(context.Background(), "tcp", missingPort)
|
2021-09-08 00:00:53 +02:00
|
|
|
if err == nil || !strings.HasSuffix(err.Error(), "missing port in address") {
|
2021-09-08 21:19:51 +02:00
|
|
|
t.Fatal("unexpected err", err)
|
2021-09-08 00:00:53 +02:00
|
|
|
}
|
|
|
|
if conn != nil {
|
2021-09-08 21:19:51 +02:00
|
|
|
t.Fatal("unexpected conn")
|
2021-09-08 00:00:53 +02:00
|
|
|
}
|
|
|
|
})
|
2021-02-02 12:05:47 +01:00
|
|
|
|
2021-09-08 00:00:53 +02:00
|
|
|
t.Run("handles dialing error correctly for single IP address", func(t *testing.T) {
|
2022-07-01 12:22:22 +02:00
|
|
|
d := &dialerResolverWithTracing{
|
2021-09-08 00:00:53 +02:00
|
|
|
Dialer: &mocks.Dialer{
|
|
|
|
MockDialContext: func(ctx context.Context, network string, address string) (net.Conn, error) {
|
|
|
|
return nil, io.EOF
|
|
|
|
},
|
2021-06-08 23:59:30 +02:00
|
|
|
},
|
2022-05-31 20:02:11 +02:00
|
|
|
Resolver: &NullResolver{},
|
2021-09-08 00:00:53 +02:00
|
|
|
}
|
|
|
|
conn, err := d.DialContext(context.Background(), "tcp", "1.1.1.1:853")
|
|
|
|
if !errors.Is(err, io.EOF) {
|
|
|
|
t.Fatal("not the error we expected")
|
|
|
|
}
|
2022-07-01 12:22:22 +02:00
|
|
|
var errWrapper *ErrWrapper
|
|
|
|
if !errors.As(err, &errWrapper) {
|
|
|
|
t.Fatal("the error has not been wrapped")
|
|
|
|
}
|
|
|
|
if errWrapper.Failure != FailureEOFError {
|
|
|
|
t.Fatal("invalid wrapped error's failure")
|
|
|
|
}
|
|
|
|
if errWrapper.Operation != ConnectOperation {
|
|
|
|
t.Fatal("invalid wrapped error's operation")
|
|
|
|
}
|
|
|
|
if !errors.Is(errWrapper.WrappedErr, io.EOF) {
|
|
|
|
t.Fatal("invalid wrapped error's underlying error")
|
|
|
|
}
|
2021-09-08 00:00:53 +02:00
|
|
|
if conn != nil {
|
|
|
|
t.Fatal("expected nil conn")
|
|
|
|
}
|
|
|
|
})
|
2021-02-02 12:05:47 +01:00
|
|
|
|
2021-09-08 00:00:53 +02:00
|
|
|
t.Run("handles dialing error correctly for many IP addresses", func(t *testing.T) {
|
2022-07-01 12:22:22 +02:00
|
|
|
d := &dialerResolverWithTracing{
|
2021-09-08 00:00:53 +02:00
|
|
|
Dialer: &mocks.Dialer{
|
|
|
|
MockDialContext: func(ctx context.Context, network string, address string) (net.Conn, error) {
|
|
|
|
return nil, io.EOF
|
|
|
|
},
|
|
|
|
},
|
2021-09-08 21:19:51 +02:00
|
|
|
Resolver: &mocks.Resolver{
|
|
|
|
MockLookupHost: func(ctx context.Context, domain string) ([]string, error) {
|
|
|
|
return []string{"1.1.1.1", "8.8.8.8"}, nil
|
|
|
|
},
|
|
|
|
},
|
2021-09-08 00:00:53 +02:00
|
|
|
}
|
2021-09-08 21:19:51 +02:00
|
|
|
conn, err := d.DialContext(context.Background(), "tcp", "dot.dns:853")
|
2021-09-08 00:00:53 +02:00
|
|
|
if !errors.Is(err, io.EOF) {
|
|
|
|
t.Fatal("not the error we expected")
|
|
|
|
}
|
2022-07-01 12:22:22 +02:00
|
|
|
var errWrapper *ErrWrapper
|
|
|
|
if !errors.As(err, &errWrapper) {
|
|
|
|
t.Fatal("the error has not been wrapped")
|
|
|
|
}
|
|
|
|
if errWrapper.Failure != FailureEOFError {
|
|
|
|
t.Fatal("invalid wrapped error's failure")
|
|
|
|
}
|
|
|
|
if errWrapper.Operation != ConnectOperation {
|
|
|
|
t.Fatal("invalid wrapped error's operation")
|
|
|
|
}
|
|
|
|
if !errors.Is(errWrapper.WrappedErr, io.EOF) {
|
|
|
|
t.Fatal("invalid wrapped error's underlying error")
|
|
|
|
}
|
2021-09-08 00:00:53 +02:00
|
|
|
if conn != nil {
|
|
|
|
t.Fatal("expected nil conn")
|
|
|
|
}
|
|
|
|
})
|
2021-09-05 19:55:28 +02:00
|
|
|
|
2021-09-08 00:00:53 +02:00
|
|
|
t.Run("handles dialing success correctly for many IP addresses", func(t *testing.T) {
|
2021-09-08 21:19:51 +02:00
|
|
|
expectedConn := &mocks.Conn{
|
|
|
|
MockClose: func() error {
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
2022-07-01 12:22:22 +02:00
|
|
|
d := &dialerResolverWithTracing{
|
2021-09-08 00:00:53 +02:00
|
|
|
Dialer: &mocks.Dialer{
|
|
|
|
MockDialContext: func(ctx context.Context, network string, address string) (net.Conn, error) {
|
2021-09-08 21:19:51 +02:00
|
|
|
return expectedConn, nil
|
2021-06-23 17:00:44 +02:00
|
|
|
},
|
2021-09-08 21:19:51 +02:00
|
|
|
},
|
|
|
|
Resolver: &mocks.Resolver{
|
2021-09-08 00:00:53 +02:00
|
|
|
MockLookupHost: func(ctx context.Context, domain string) ([]string, error) {
|
|
|
|
return []string{"1.1.1.1", "8.8.8.8"}, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
conn, err := d.DialContext(context.Background(), "tcp", "dot.dns:853")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2022-07-01 12:22:22 +02:00
|
|
|
// Ensure that the dialer returns a connection that is already wrapping errors,
|
|
|
|
// which is a new behavior since https://github.com/ooni/probe-cli/pull/815
|
|
|
|
errWrapperConn := conn.(*dialerErrWrapperConn)
|
|
|
|
if errWrapperConn.Conn != expectedConn {
|
2021-09-08 21:19:51 +02:00
|
|
|
t.Fatal("unexpected conn")
|
2021-09-08 00:00:53 +02:00
|
|
|
}
|
|
|
|
conn.Close()
|
|
|
|
})
|
2021-09-08 21:19:51 +02:00
|
|
|
|
|
|
|
t.Run("calls the underlying dialer sequentially", func(t *testing.T) {
|
|
|
|
// This test is fundamental to the following
|
|
|
|
// TODO(https://github.com/ooni/probe/issues/1779)
|
|
|
|
mu := &sync.Mutex{}
|
2022-07-01 12:22:22 +02:00
|
|
|
d := &dialerResolverWithTracing{
|
2021-09-08 21:19:51 +02:00
|
|
|
Dialer: &mocks.Dialer{
|
|
|
|
MockDialContext: func(ctx context.Context, network string, address string) (net.Conn, error) {
|
|
|
|
// It should not happen to have parallel dials with
|
|
|
|
// this implementation. When we have parallelism greater
|
|
|
|
// than one, this code will lock forever and we'll see
|
|
|
|
// a failed test and see we broke the QUIRK.
|
|
|
|
defer mu.Unlock()
|
|
|
|
mu.Lock()
|
|
|
|
return nil, io.EOF
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Resolver: &mocks.Resolver{
|
|
|
|
MockLookupHost: func(ctx context.Context, domain string) ([]string, error) {
|
|
|
|
return []string{"1.1.1.1", "8.8.8.8"}, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
conn, err := d.DialContext(context.Background(), "tcp", "dot.dns:853")
|
|
|
|
if !errors.Is(err, io.EOF) {
|
|
|
|
t.Fatal("not the error we expected")
|
|
|
|
}
|
|
|
|
if conn != nil {
|
|
|
|
t.Fatal("expected nil conn")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("attempts with IPv4 addresses before IPv6 addresses", func(t *testing.T) {
|
|
|
|
// This test is fundamental to the following
|
|
|
|
// TODO(https://github.com/ooni/probe/issues/1779)
|
|
|
|
mu := &sync.Mutex{}
|
|
|
|
var attempts []string
|
2022-07-01 12:22:22 +02:00
|
|
|
d := &dialerResolverWithTracing{
|
2021-09-08 21:19:51 +02:00
|
|
|
Dialer: &mocks.Dialer{
|
|
|
|
MockDialContext: func(ctx context.Context, network string, address string) (net.Conn, error) {
|
|
|
|
// It should not happen to have parallel dials with
|
|
|
|
// this implementation. When we have parallelism greater
|
|
|
|
// than one, this code will lock forever and we'll see
|
|
|
|
// a failed test and see we broke the QUIRK.
|
|
|
|
defer mu.Unlock()
|
|
|
|
attempts = append(attempts, address)
|
|
|
|
mu.Lock()
|
|
|
|
return nil, io.EOF
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Resolver: &mocks.Resolver{
|
|
|
|
MockLookupHost: func(ctx context.Context, domain string) ([]string, error) {
|
|
|
|
return []string{"2001:4860:4860::8888", "8.8.8.8"}, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
conn, err := d.DialContext(context.Background(), "tcp", "dot.dns:853")
|
|
|
|
if !errors.Is(err, io.EOF) {
|
|
|
|
t.Fatal("not the error we expected")
|
|
|
|
}
|
|
|
|
if conn != nil {
|
|
|
|
t.Fatal("expected nil conn")
|
|
|
|
}
|
|
|
|
mu.Lock()
|
|
|
|
asExpected := (attempts[0] == "8.8.8.8:853" &&
|
|
|
|
attempts[1] == "[2001:4860:4860::8888]:853")
|
|
|
|
mu.Unlock()
|
|
|
|
if !asExpected {
|
|
|
|
t.Fatal("addresses not reordered")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("returns the first meaningful error if there is one", func(t *testing.T) {
|
|
|
|
// This test is fundamental to the following
|
|
|
|
// TODO(https://github.com/ooni/probe/issues/1779)
|
|
|
|
mu := &sync.Mutex{}
|
|
|
|
errorsList := []error{
|
|
|
|
errors.New("a mocked error"),
|
2022-07-01 12:22:22 +02:00
|
|
|
NewErrWrapper(
|
|
|
|
ClassifyGenericError,
|
2021-09-28 12:42:01 +02:00
|
|
|
CloseOperation,
|
2021-09-08 21:19:51 +02:00
|
|
|
io.EOF,
|
|
|
|
),
|
|
|
|
}
|
|
|
|
var errorIdx int
|
2022-07-01 12:22:22 +02:00
|
|
|
d := &dialerResolverWithTracing{
|
2021-09-08 21:19:51 +02:00
|
|
|
Dialer: &mocks.Dialer{
|
|
|
|
MockDialContext: func(ctx context.Context, network string, address string) (net.Conn, error) {
|
|
|
|
// It should not happen to have parallel dials with
|
|
|
|
// this implementation. When we have parallelism greater
|
|
|
|
// than one, this code will lock forever and we'll see
|
|
|
|
// a failed test and see we broke the QUIRK.
|
|
|
|
defer mu.Unlock()
|
|
|
|
err := errorsList[errorIdx]
|
|
|
|
errorIdx++
|
|
|
|
mu.Lock()
|
|
|
|
return nil, err
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Resolver: &mocks.Resolver{
|
|
|
|
MockLookupHost: func(ctx context.Context, domain string) ([]string, error) {
|
|
|
|
return []string{"2001:4860:4860::8888", "8.8.8.8"}, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
conn, err := d.DialContext(context.Background(), "tcp", "dot.dns:853")
|
2021-09-28 12:42:01 +02:00
|
|
|
if err == nil || err.Error() != FailureEOFError {
|
2021-09-08 21:19:51 +02:00
|
|
|
t.Fatal("unexpected err", err)
|
|
|
|
}
|
|
|
|
if conn != nil {
|
|
|
|
t.Fatal("expected nil conn")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("though ignores the unknown failures", func(t *testing.T) {
|
|
|
|
// This test is fundamental to the following
|
|
|
|
// TODO(https://github.com/ooni/probe/issues/1779)
|
2022-07-01 12:22:22 +02:00
|
|
|
expectedErr := errors.New("a mocked error")
|
2021-09-08 21:19:51 +02:00
|
|
|
mu := &sync.Mutex{}
|
|
|
|
errorsList := []error{
|
2022-07-01 12:22:22 +02:00
|
|
|
expectedErr,
|
|
|
|
NewErrWrapper(
|
|
|
|
ClassifyGenericError,
|
2021-09-28 12:42:01 +02:00
|
|
|
CloseOperation,
|
2022-07-01 12:22:22 +02:00
|
|
|
errors.New("antani"), // this is an unknown failure and we should not return it
|
2021-09-08 21:19:51 +02:00
|
|
|
),
|
|
|
|
}
|
|
|
|
var errorIdx int
|
2022-07-01 12:22:22 +02:00
|
|
|
d := &dialerResolverWithTracing{
|
2021-09-08 21:19:51 +02:00
|
|
|
Dialer: &mocks.Dialer{
|
|
|
|
MockDialContext: func(ctx context.Context, network string, address string) (net.Conn, error) {
|
|
|
|
// It should not happen to have parallel dials with
|
|
|
|
// this implementation. When we have parallelism greater
|
|
|
|
// than one, this code will lock forever and we'll see
|
|
|
|
// a failed test and see we broke the QUIRK.
|
|
|
|
defer mu.Unlock()
|
|
|
|
err := errorsList[errorIdx]
|
|
|
|
errorIdx++
|
|
|
|
mu.Lock()
|
|
|
|
return nil, err
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Resolver: &mocks.Resolver{
|
|
|
|
MockLookupHost: func(ctx context.Context, domain string) ([]string, error) {
|
|
|
|
return []string{"2001:4860:4860::8888", "8.8.8.8"}, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
conn, err := d.DialContext(context.Background(), "tcp", "dot.dns:853")
|
2022-07-01 12:22:22 +02:00
|
|
|
if !errors.Is(err, expectedErr) {
|
2021-09-08 21:19:51 +02:00
|
|
|
t.Fatal("unexpected err", err)
|
|
|
|
}
|
2022-07-01 12:22:22 +02:00
|
|
|
var errWrapper *ErrWrapper
|
|
|
|
if !errors.As(err, &errWrapper) {
|
|
|
|
t.Fatal("error has not been wrapped")
|
|
|
|
}
|
|
|
|
if errWrapper.Failure != "unknown_failure: a mocked error" {
|
|
|
|
t.Fatal("unexpected wrapped error's failure")
|
|
|
|
}
|
|
|
|
if errWrapper.Operation != ConnectOperation {
|
|
|
|
t.Fatal("unexpected wrapped error's operation")
|
|
|
|
}
|
|
|
|
if !errors.Is(errWrapper.WrappedErr, expectedErr) {
|
|
|
|
t.Fatal("unexpected wrapped error's underlying error")
|
|
|
|
}
|
2021-09-08 21:19:51 +02:00
|
|
|
if conn != nil {
|
|
|
|
t.Fatal("expected nil conn")
|
|
|
|
}
|
|
|
|
})
|
2022-07-01 12:22:22 +02:00
|
|
|
|
|
|
|
t.Run("uses a context-injected custom trace", func(t *testing.T) {
|
|
|
|
var (
|
|
|
|
called bool
|
|
|
|
domainOK bool
|
|
|
|
networkOK bool
|
|
|
|
remoteAddrOK bool
|
|
|
|
startTimeOK bool
|
|
|
|
finishTimeOK bool
|
|
|
|
wrappedErr bool
|
|
|
|
)
|
|
|
|
zeroTime := time.Now()
|
|
|
|
deterministicTime := testingx.NewTimeDeterministic(zeroTime)
|
|
|
|
tx := &mocks.Trace{
|
|
|
|
MockTimeNow: deterministicTime.Now,
|
|
|
|
MockOnConnectDone: func(started time.Time, network, domain, remoteAddr string, err error, finished time.Time) {
|
|
|
|
var ew *ErrWrapper
|
|
|
|
called = true
|
|
|
|
domainOK = (domain == "1.1.1.1")
|
|
|
|
networkOK = (network == "tcp")
|
|
|
|
remoteAddrOK = (remoteAddr == "1.1.1.1:853")
|
|
|
|
startTimeOK = (started.Sub(zeroTime) == 0)
|
|
|
|
finishTimeOK = (finished.Sub(zeroTime) == time.Second)
|
|
|
|
wrappedErr = errors.As(err, &ew) && ew.Failure == FailureEOFError
|
|
|
|
},
|
|
|
|
}
|
|
|
|
ctx := ContextWithTrace(context.Background(), tx)
|
|
|
|
d := &dialerResolverWithTracing{
|
|
|
|
Dialer: &mocks.Dialer{
|
|
|
|
MockDialContext: func(ctx context.Context, network string, address string) (net.Conn, error) {
|
|
|
|
return nil, io.EOF
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Resolver: &NullResolver{},
|
|
|
|
}
|
|
|
|
conn, err := d.DialContext(ctx, "tcp", "1.1.1.1:853")
|
|
|
|
if !errors.Is(err, io.EOF) {
|
|
|
|
t.Fatal("not the error we expected")
|
|
|
|
}
|
|
|
|
if conn != nil {
|
|
|
|
t.Fatal("expected nil conn")
|
|
|
|
}
|
|
|
|
if !called {
|
|
|
|
t.Fatal("not called")
|
|
|
|
}
|
|
|
|
if !domainOK {
|
|
|
|
t.Fatal("domain was not okay")
|
|
|
|
}
|
|
|
|
if !networkOK {
|
|
|
|
t.Fatal("network was not okay")
|
|
|
|
}
|
|
|
|
if !remoteAddrOK {
|
|
|
|
t.Fatal("remoteAddr was not okay")
|
|
|
|
}
|
|
|
|
if !startTimeOK {
|
|
|
|
t.Fatal("start time was not okay")
|
|
|
|
}
|
|
|
|
if !finishTimeOK {
|
|
|
|
t.Fatal("finish time was not okay")
|
|
|
|
}
|
|
|
|
if !wrappedErr {
|
|
|
|
t.Fatal("not wrapped")
|
|
|
|
}
|
|
|
|
})
|
2021-09-08 00:00:53 +02:00
|
|
|
})
|
2021-02-02 12:05:47 +01:00
|
|
|
|
2021-09-08 00:00:53 +02:00
|
|
|
t.Run("lookupHost", func(t *testing.T) {
|
|
|
|
t.Run("handles addresses correctly", func(t *testing.T) {
|
2022-07-01 12:22:22 +02:00
|
|
|
dialer := &dialerResolverWithTracing{
|
2022-05-31 20:02:11 +02:00
|
|
|
Dialer: &DialerSystem{},
|
|
|
|
Resolver: &NullResolver{},
|
2021-09-08 00:00:53 +02:00
|
|
|
}
|
|
|
|
addrs, err := dialer.lookupHost(context.Background(), "1.1.1.1")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if len(addrs) != 1 || addrs[0] != "1.1.1.1" {
|
|
|
|
t.Fatal("not the result we expected")
|
|
|
|
}
|
|
|
|
})
|
2021-02-02 12:05:47 +01:00
|
|
|
|
2021-09-08 00:00:53 +02:00
|
|
|
t.Run("fails correctly on lookup error", func(t *testing.T) {
|
2022-07-01 12:22:22 +02:00
|
|
|
dialer := &dialerResolverWithTracing{
|
2022-05-31 20:02:11 +02:00
|
|
|
Dialer: &DialerSystem{},
|
|
|
|
Resolver: &NullResolver{},
|
2021-09-08 00:00:53 +02:00
|
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
conn, err := dialer.DialContext(ctx, "tcp", "dns.google.com:853")
|
|
|
|
if !errors.Is(err, ErrNoResolver) {
|
|
|
|
t.Fatal("not the error we expected", err)
|
|
|
|
}
|
|
|
|
if conn != nil {
|
|
|
|
t.Fatal("expected nil conn")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("CloseIdleConnections", func(t *testing.T) {
|
|
|
|
var (
|
|
|
|
calledDialer bool
|
|
|
|
calledResolver bool
|
|
|
|
)
|
2022-07-01 12:22:22 +02:00
|
|
|
d := &dialerResolverWithTracing{
|
2021-09-08 00:00:53 +02:00
|
|
|
Dialer: &mocks.Dialer{
|
|
|
|
MockCloseIdleConnections: func() {
|
|
|
|
calledDialer = true
|
|
|
|
},
|
2021-09-05 19:55:28 +02:00
|
|
|
},
|
2021-09-08 00:00:53 +02:00
|
|
|
Resolver: &mocks.Resolver{
|
|
|
|
MockCloseIdleConnections: func() {
|
|
|
|
calledResolver = true
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
d.CloseIdleConnections()
|
|
|
|
if !calledDialer || !calledResolver {
|
|
|
|
t.Fatal("not called")
|
|
|
|
}
|
|
|
|
})
|
2021-09-05 19:55:28 +02:00
|
|
|
}
|
|
|
|
|
2021-09-08 00:00:53 +02:00
|
|
|
func TestDialerLogger(t *testing.T) {
|
|
|
|
t.Run("DialContext", func(t *testing.T) {
|
|
|
|
t.Run("handles success correctly", func(t *testing.T) {
|
2021-09-08 21:19:51 +02:00
|
|
|
var count int
|
|
|
|
lo := &mocks.Logger{
|
|
|
|
MockDebugf: func(format string, v ...interface{}) {
|
|
|
|
count++
|
|
|
|
},
|
|
|
|
}
|
2021-09-08 00:00:53 +02:00
|
|
|
d := &dialerLogger{
|
|
|
|
Dialer: &mocks.Dialer{
|
|
|
|
MockDialContext: func(ctx context.Context, network string, address string) (net.Conn, error) {
|
|
|
|
return &mocks.Conn{
|
|
|
|
MockClose: func() error {
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
},
|
|
|
|
},
|
2022-01-03 13:53:23 +01:00
|
|
|
DebugLogger: lo,
|
2021-09-08 00:00:53 +02:00
|
|
|
}
|
|
|
|
conn, err := d.DialContext(context.Background(), "tcp", "www.google.com:443")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if conn == nil {
|
|
|
|
t.Fatal("expected non-nil conn here")
|
|
|
|
}
|
|
|
|
conn.Close()
|
2021-09-08 21:19:51 +02:00
|
|
|
if count != 2 {
|
|
|
|
t.Fatal("not enough log calls")
|
|
|
|
}
|
2021-09-08 00:00:53 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("handles failure correctly", func(t *testing.T) {
|
2021-09-08 21:19:51 +02:00
|
|
|
var count int
|
|
|
|
lo := &mocks.Logger{
|
|
|
|
MockDebugf: func(format string, v ...interface{}) {
|
|
|
|
count++
|
|
|
|
},
|
|
|
|
}
|
2021-09-08 00:00:53 +02:00
|
|
|
d := &dialerLogger{
|
|
|
|
Dialer: &mocks.Dialer{
|
|
|
|
MockDialContext: func(ctx context.Context, network string, address string) (net.Conn, error) {
|
|
|
|
return nil, io.EOF
|
|
|
|
},
|
|
|
|
},
|
2022-01-03 13:53:23 +01:00
|
|
|
DebugLogger: lo,
|
2021-09-08 00:00:53 +02:00
|
|
|
}
|
|
|
|
conn, err := d.DialContext(context.Background(), "tcp", "www.google.com:443")
|
|
|
|
if !errors.Is(err, io.EOF) {
|
|
|
|
t.Fatal("not the error we expected")
|
|
|
|
}
|
|
|
|
if conn != nil {
|
|
|
|
t.Fatal("expected nil conn here")
|
|
|
|
}
|
2021-09-08 21:19:51 +02:00
|
|
|
if count != 2 {
|
|
|
|
t.Fatal("not enough log calls")
|
|
|
|
}
|
2021-09-08 00:00:53 +02:00
|
|
|
})
|
|
|
|
})
|
2021-09-05 20:41:46 +02:00
|
|
|
|
2021-09-08 00:00:53 +02:00
|
|
|
t.Run("CloseIdleConnections", func(t *testing.T) {
|
|
|
|
var (
|
|
|
|
calledDialer bool
|
|
|
|
)
|
|
|
|
d := &dialerLogger{
|
|
|
|
Dialer: &mocks.Dialer{
|
|
|
|
MockCloseIdleConnections: func() {
|
|
|
|
calledDialer = true
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
d.CloseIdleConnections()
|
|
|
|
if !calledDialer {
|
|
|
|
t.Fatal("not called")
|
|
|
|
}
|
|
|
|
})
|
2021-09-05 20:41:46 +02:00
|
|
|
}
|
2021-09-06 14:12:30 +02:00
|
|
|
|
2021-09-08 00:00:53 +02:00
|
|
|
func TestDialerSingleUse(t *testing.T) {
|
|
|
|
t.Run("works as intended", func(t *testing.T) {
|
|
|
|
conn := &mocks.Conn{}
|
|
|
|
d := NewSingleUseDialer(conn)
|
|
|
|
outconn, err := d.DialContext(context.Background(), "", "")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
2021-09-06 14:12:30 +02:00
|
|
|
}
|
2021-09-08 00:00:53 +02:00
|
|
|
if conn != outconn {
|
|
|
|
t.Fatal("invalid outconn")
|
2021-09-06 14:12:30 +02:00
|
|
|
}
|
2021-09-08 00:00:53 +02:00
|
|
|
for i := 0; i < 4; i++ {
|
|
|
|
outconn, err = d.DialContext(context.Background(), "", "")
|
|
|
|
if !errors.Is(err, ErrNoConnReuse) {
|
|
|
|
t.Fatal("not the error we expected", err)
|
|
|
|
}
|
|
|
|
if outconn != nil {
|
|
|
|
t.Fatal("expected nil outconn here")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("CloseIdleConnections", func(t *testing.T) {
|
|
|
|
d := &dialerSingleUse{}
|
2021-09-08 21:19:51 +02:00
|
|
|
d.CloseIdleConnections() // to have the coverage
|
2021-09-08 00:00:53 +02:00
|
|
|
})
|
2021-09-06 14:12:30 +02:00
|
|
|
}
|
2021-09-07 19:56:42 +02:00
|
|
|
|
|
|
|
func TestDialerErrWrapper(t *testing.T) {
|
|
|
|
t.Run("DialContext on success", func(t *testing.T) {
|
|
|
|
t.Run("on success", func(t *testing.T) {
|
|
|
|
expectedConn := &mocks.Conn{}
|
|
|
|
d := &dialerErrWrapper{
|
|
|
|
Dialer: &mocks.Dialer{
|
|
|
|
MockDialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
|
|
|
return expectedConn, nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
conn, err := d.DialContext(ctx, "", "")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
errWrapperConn := conn.(*dialerErrWrapperConn)
|
|
|
|
if errWrapperConn.Conn != expectedConn {
|
|
|
|
t.Fatal("unexpected conn")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("on failure", func(t *testing.T) {
|
|
|
|
expectedErr := io.EOF
|
|
|
|
d := &dialerErrWrapper{
|
|
|
|
Dialer: &mocks.Dialer{
|
|
|
|
MockDialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
|
|
|
return nil, expectedErr
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
ctx := context.Background()
|
|
|
|
conn, err := d.DialContext(ctx, "", "")
|
2021-09-28 12:42:01 +02:00
|
|
|
if err == nil || err.Error() != FailureEOFError {
|
2021-09-07 19:56:42 +02:00
|
|
|
t.Fatal("unexpected err", err)
|
|
|
|
}
|
|
|
|
if conn != nil {
|
|
|
|
t.Fatal("expected nil conn")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("CloseIdleConnections", func(t *testing.T) {
|
|
|
|
var called bool
|
|
|
|
d := &dialerErrWrapper{
|
|
|
|
Dialer: &mocks.Dialer{
|
|
|
|
MockCloseIdleConnections: func() {
|
|
|
|
called = true
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
d.CloseIdleConnections()
|
|
|
|
if !called {
|
|
|
|
t.Fatal("not called")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestDialerErrWrapperConn(t *testing.T) {
|
|
|
|
t.Run("Read", func(t *testing.T) {
|
|
|
|
t.Run("on success", func(t *testing.T) {
|
|
|
|
b := make([]byte, 128)
|
|
|
|
conn := &dialerErrWrapperConn{
|
|
|
|
Conn: &mocks.Conn{
|
|
|
|
MockRead: func(b []byte) (int, error) {
|
|
|
|
return len(b), nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
count, err := conn.Read(b)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if count != len(b) {
|
|
|
|
t.Fatal("unexpected count")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("on failure", func(t *testing.T) {
|
|
|
|
b := make([]byte, 128)
|
|
|
|
expectedErr := io.EOF
|
|
|
|
conn := &dialerErrWrapperConn{
|
|
|
|
Conn: &mocks.Conn{
|
|
|
|
MockRead: func(b []byte) (int, error) {
|
|
|
|
return 0, expectedErr
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
count, err := conn.Read(b)
|
2021-09-28 12:42:01 +02:00
|
|
|
if err == nil || err.Error() != FailureEOFError {
|
2021-09-07 19:56:42 +02:00
|
|
|
t.Fatal("unexpected err", err)
|
|
|
|
}
|
|
|
|
if count != 0 {
|
|
|
|
t.Fatal("unexpected count")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Write", func(t *testing.T) {
|
|
|
|
t.Run("on success", func(t *testing.T) {
|
|
|
|
b := make([]byte, 128)
|
|
|
|
conn := &dialerErrWrapperConn{
|
|
|
|
Conn: &mocks.Conn{
|
|
|
|
MockWrite: func(b []byte) (int, error) {
|
|
|
|
return len(b), nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
count, err := conn.Write(b)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if count != len(b) {
|
|
|
|
t.Fatal("unexpected count")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("on failure", func(t *testing.T) {
|
|
|
|
b := make([]byte, 128)
|
|
|
|
expectedErr := io.EOF
|
|
|
|
conn := &dialerErrWrapperConn{
|
|
|
|
Conn: &mocks.Conn{
|
|
|
|
MockWrite: func(b []byte) (int, error) {
|
|
|
|
return 0, expectedErr
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
count, err := conn.Write(b)
|
2021-09-28 12:42:01 +02:00
|
|
|
if err == nil || err.Error() != FailureEOFError {
|
2021-09-07 19:56:42 +02:00
|
|
|
t.Fatal("unexpected err", err)
|
|
|
|
}
|
|
|
|
if count != 0 {
|
|
|
|
t.Fatal("unexpected count")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Close", func(t *testing.T) {
|
|
|
|
t.Run("on success", func(t *testing.T) {
|
|
|
|
conn := &dialerErrWrapperConn{
|
|
|
|
Conn: &mocks.Conn{
|
|
|
|
MockClose: func() error {
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
err := conn.Close()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("on failure", func(t *testing.T) {
|
|
|
|
expectedErr := io.EOF
|
|
|
|
conn := &dialerErrWrapperConn{
|
|
|
|
Conn: &mocks.Conn{
|
|
|
|
MockClose: func() error {
|
|
|
|
return expectedErr
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
err := conn.Close()
|
2021-09-28 12:42:01 +02:00
|
|
|
if err == nil || err.Error() != FailureEOFError {
|
2021-09-07 19:56:42 +02:00
|
|
|
t.Fatal("unexpected err", err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
2021-09-08 14:46:17 +02:00
|
|
|
|
|
|
|
func TestNewNullDialer(t *testing.T) {
|
|
|
|
dialer := NewNullDialer()
|
|
|
|
conn, err := dialer.DialContext(context.Background(), "", "")
|
|
|
|
if !errors.Is(err, ErrNoDialer) {
|
|
|
|
t.Fatal("unexpected err", err)
|
|
|
|
}
|
|
|
|
if conn != nil {
|
|
|
|
t.Fatal("expected nil conn")
|
|
|
|
}
|
2021-09-08 21:19:51 +02:00
|
|
|
dialer.CloseIdleConnections() // to have coverage
|
2021-09-08 14:46:17 +02:00
|
|
|
}
|