5ebdeb56ca
## Checklist - [x] I have read the [contribution guidelines](https://github.com/ooni/probe-cli/blob/master/CONTRIBUTING.md) - [x] reference issue for this pull request: https://github.com/ooni/probe/issues/2158 - [x] if you changed anything related how experiments work and you need to reflect these changes in the ooni/spec repository, please link to the related ooni/spec pull request: https://github.com/ooni/spec/pull/250 ## Description This diff refactors the codebase to reimplement tlsping and tcpping to use the step-by-step measurements style. See docs/design/dd-003-step-by-step.md for more information on the step-by-step measurement style.
212 lines
6.1 KiB
Go
212 lines
6.1 KiB
Go
package measurexlite
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"math"
|
|
"net"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/ooni/probe-cli/v3/internal/model"
|
|
"github.com/ooni/probe-cli/v3/internal/model/mocks"
|
|
"github.com/ooni/probe-cli/v3/internal/netxlite"
|
|
"github.com/ooni/probe-cli/v3/internal/testingx"
|
|
)
|
|
|
|
func TestNewDialerWithoutResolver(t *testing.T) {
|
|
t.Run("NewDialerWithoutResolver creates a wrapped dialer", func(t *testing.T) {
|
|
underlying := &mocks.Dialer{}
|
|
zeroTime := time.Now()
|
|
trace := NewTrace(0, zeroTime)
|
|
trace.NewDialerWithoutResolverFn = func(dl model.DebugLogger) model.Dialer {
|
|
return underlying
|
|
}
|
|
dialer := trace.NewDialerWithoutResolver(model.DiscardLogger)
|
|
dt := dialer.(*dialerTrace)
|
|
if dt.d != underlying {
|
|
t.Fatal("invalid dialer")
|
|
}
|
|
if dt.tx != trace {
|
|
t.Fatal("invalid trace")
|
|
}
|
|
})
|
|
|
|
t.Run("DialContext calls the underlying dialer with context-based tracing", func(t *testing.T) {
|
|
expectedErr := errors.New("mocked err")
|
|
zeroTime := time.Now()
|
|
trace := NewTrace(0, zeroTime)
|
|
var hasCorrectTrace bool
|
|
underlying := &mocks.Dialer{
|
|
MockDialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
|
gotTrace := netxlite.ContextTraceOrDefault(ctx)
|
|
hasCorrectTrace = (gotTrace == trace)
|
|
return nil, expectedErr
|
|
},
|
|
}
|
|
trace.NewDialerWithoutResolverFn = func(dl model.DebugLogger) model.Dialer {
|
|
return underlying
|
|
}
|
|
dialer := trace.NewDialerWithoutResolver(model.DiscardLogger)
|
|
ctx := context.Background()
|
|
conn, err := dialer.DialContext(ctx, "tcp", "1.1.1.1:443")
|
|
if !errors.Is(err, expectedErr) {
|
|
t.Fatal("unexpected err", err)
|
|
}
|
|
if conn != nil {
|
|
t.Fatal("expected nil conn")
|
|
}
|
|
if !hasCorrectTrace {
|
|
t.Fatal("does not have the correct trace")
|
|
}
|
|
})
|
|
|
|
t.Run("CloseIdleConnection is correctly forwarded", func(t *testing.T) {
|
|
var called bool
|
|
zeroTime := time.Now()
|
|
trace := NewTrace(0, zeroTime)
|
|
underlying := &mocks.Dialer{
|
|
MockCloseIdleConnections: func() {
|
|
called = true
|
|
},
|
|
}
|
|
trace.NewDialerWithoutResolverFn = func(dl model.DebugLogger) model.Dialer {
|
|
return underlying
|
|
}
|
|
dialer := trace.NewDialerWithoutResolver(model.DiscardLogger)
|
|
dialer.CloseIdleConnections()
|
|
if !called {
|
|
t.Fatal("not called")
|
|
}
|
|
})
|
|
|
|
t.Run("DialContext saves into the trace", func(t *testing.T) {
|
|
zeroTime := time.Now()
|
|
td := testingx.NewTimeDeterministic(zeroTime)
|
|
trace := NewTrace(0, zeroTime)
|
|
trace.TimeNowFn = td.Now // deterministic time tracking
|
|
dialer := trace.NewDialerWithoutResolver(model.DiscardLogger)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // we cancel immediately so connect is ~instantaneous
|
|
conn, err := dialer.DialContext(ctx, "tcp", "1.1.1.1:443")
|
|
if err == nil || err.Error() != netxlite.FailureInterrupted {
|
|
t.Fatal("unexpected err", err)
|
|
}
|
|
if conn != nil {
|
|
t.Fatal("expected nil conn")
|
|
}
|
|
events := trace.TCPConnects()
|
|
if len(events) != 1 {
|
|
t.Fatal("expected to see single TCPConnect event")
|
|
}
|
|
expectedFailure := netxlite.FailureInterrupted
|
|
expect := &model.ArchivalTCPConnectResult{
|
|
IP: "1.1.1.1",
|
|
Port: 443,
|
|
Status: model.ArchivalTCPConnectStatus{
|
|
Blocked: nil,
|
|
Failure: &expectedFailure,
|
|
Success: false,
|
|
},
|
|
T: time.Second.Seconds(),
|
|
}
|
|
got := events[0]
|
|
if diff := cmp.Diff(expect, got); diff != "" {
|
|
t.Fatal(diff)
|
|
}
|
|
})
|
|
|
|
t.Run("DialContext discards events when buffer is full", func(t *testing.T) {
|
|
zeroTime := time.Now()
|
|
trace := NewTrace(0, zeroTime)
|
|
trace.TCPConnect = make(chan *model.ArchivalTCPConnectResult) // no buffer
|
|
dialer := trace.NewDialerWithoutResolver(model.DiscardLogger)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // we cancel immediately so connect is ~instantaneous
|
|
conn, err := dialer.DialContext(ctx, "tcp", "1.1.1.1:443")
|
|
if err == nil || err.Error() != netxlite.FailureInterrupted {
|
|
t.Fatal("unexpected err", err)
|
|
}
|
|
if conn != nil {
|
|
t.Fatal("expected nil conn")
|
|
}
|
|
events := trace.TCPConnects()
|
|
if len(events) != 0 {
|
|
t.Fatal("expected to see no TCPConnect events")
|
|
}
|
|
})
|
|
|
|
t.Run("DialContext ignores UDP connect attempts", func(t *testing.T) {
|
|
zeroTime := time.Now()
|
|
trace := NewTrace(0, zeroTime)
|
|
dialer := trace.NewDialerWithoutResolver(model.DiscardLogger)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // we cancel immediately so connect is ~instantaneous
|
|
conn, err := dialer.DialContext(ctx, "udp", "1.1.1.1:443")
|
|
if err == nil || err.Error() != netxlite.FailureInterrupted {
|
|
t.Fatal("unexpected err", err)
|
|
}
|
|
if conn != nil {
|
|
t.Fatal("expected nil conn")
|
|
}
|
|
events := trace.TCPConnects()
|
|
if len(events) != 0 {
|
|
t.Fatal("expected to see no TCPConnect events")
|
|
}
|
|
})
|
|
|
|
t.Run("DialContext uses a dialer without a resolver", func(t *testing.T) {
|
|
zeroTime := time.Now()
|
|
trace := NewTrace(0, zeroTime)
|
|
dialer := trace.NewDialerWithoutResolver(model.DiscardLogger)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // we cancel immediately so connect is ~instantaneous
|
|
conn, err := dialer.DialContext(ctx, "udp", "dns.google:443") // domain
|
|
if !errors.Is(err, netxlite.ErrNoResolver) {
|
|
t.Fatal("unexpected err", err)
|
|
}
|
|
if conn != nil {
|
|
t.Fatal("expected nil conn")
|
|
}
|
|
events := trace.TCPConnects()
|
|
if len(events) != 0 {
|
|
t.Fatal("expected to see no TCPConnect events")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestArchivalSplitHostPort(t *testing.T) {
|
|
addr, port := archivalSplitHostPort("1.1.1.1") // missing port
|
|
if addr != "" {
|
|
t.Fatal("invalid addr", addr)
|
|
}
|
|
if port != "" {
|
|
t.Fatal("invalid port", port)
|
|
}
|
|
}
|
|
|
|
func TestArchivalPortToString(t *testing.T) {
|
|
t.Run("with invalid number", func(t *testing.T) {
|
|
port := archivalPortToString("antani")
|
|
if port != 0 {
|
|
t.Fatal("invalid port")
|
|
}
|
|
})
|
|
|
|
t.Run("with negative number", func(t *testing.T) {
|
|
port := archivalPortToString("-1")
|
|
if port != 0 {
|
|
t.Fatal("invalid port")
|
|
}
|
|
})
|
|
|
|
t.Run("with too-large positive number", func(t *testing.T) {
|
|
port := archivalPortToString(strconv.Itoa(math.MaxUint16 + 1))
|
|
if port != 0 {
|
|
t.Fatal("invalid port")
|
|
}
|
|
})
|
|
}
|