6ef3febf69
The T0 field is the moment when we started collecting data, while T is the moment when we finished collecting data. The TransactionID field will be repurposed for step-by-step measurements to indicate related observations collected as part of the same flow (e.g., TCP+TLS+HTTP). Note that, for now, this change will only affect measurexlite and we're not planning on changing other libraries for measuring. Part of https://github.com/ooni/probe/issues/2137
457 lines
11 KiB
Go
457 lines
11 KiB
Go
package measurexlite
|
|
|
|
import (
|
|
"net"
|
|
"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 TestMaybeClose(t *testing.T) {
|
|
t.Run("with nil conn", func(t *testing.T) {
|
|
var conn net.Conn = nil
|
|
MaybeClose(conn)
|
|
})
|
|
|
|
t.Run("with nonnil conn", func(t *testing.T) {
|
|
var called bool
|
|
conn := &mocks.Conn{
|
|
MockClose: func() error {
|
|
called = true
|
|
return nil
|
|
},
|
|
}
|
|
if err := MaybeClose(conn); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !called {
|
|
t.Fatal("not called")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestWrapNetConn(t *testing.T) {
|
|
t.Run("WrapNetConn wraps the conn", func(t *testing.T) {
|
|
underlying := &mocks.Conn{}
|
|
zeroTime := time.Now()
|
|
trace := NewTrace(0, zeroTime)
|
|
conn := trace.MaybeWrapNetConn(underlying)
|
|
ct := conn.(*connTrace)
|
|
if ct.Conn != underlying {
|
|
t.Fatal("invalid underlying")
|
|
}
|
|
if ct.tx != trace {
|
|
t.Fatal("invalid trace")
|
|
}
|
|
})
|
|
|
|
t.Run("Read saves a trace", func(t *testing.T) {
|
|
underlying := &mocks.Conn{
|
|
MockRead: func(b []byte) (int, error) {
|
|
return len(b), nil
|
|
},
|
|
MockRemoteAddr: func() net.Addr {
|
|
return &mocks.Addr{
|
|
MockNetwork: func() string {
|
|
return "tcp"
|
|
},
|
|
MockString: func() string {
|
|
return "1.1.1.1:443"
|
|
},
|
|
}
|
|
},
|
|
}
|
|
zeroTime := time.Now()
|
|
td := testingx.NewTimeDeterministic(zeroTime)
|
|
trace := NewTrace(0, zeroTime)
|
|
trace.TimeNowFn = td.Now // deterministic time counting
|
|
conn := trace.MaybeWrapNetConn(underlying)
|
|
const bufsiz = 128
|
|
buffer := make([]byte, bufsiz)
|
|
count, err := conn.Read(buffer)
|
|
if count != bufsiz {
|
|
t.Fatal("invalid count")
|
|
}
|
|
if err != nil {
|
|
t.Fatal("invalid err")
|
|
}
|
|
events := trace.NetworkEvents()
|
|
if len(events) != 1 {
|
|
t.Fatal("did not save network events")
|
|
}
|
|
expect := &model.ArchivalNetworkEvent{
|
|
Address: "1.1.1.1:443",
|
|
Failure: nil,
|
|
NumBytes: bufsiz,
|
|
Operation: netxlite.ReadOperation,
|
|
Proto: "tcp",
|
|
T: 1.0,
|
|
Tags: []string{},
|
|
}
|
|
got := events[0]
|
|
if diff := cmp.Diff(expect, got); diff != "" {
|
|
t.Fatal(diff)
|
|
}
|
|
})
|
|
|
|
t.Run("Read discards the event when the buffer is full", func(t *testing.T) {
|
|
underlying := &mocks.Conn{
|
|
MockRead: func(b []byte) (int, error) {
|
|
return len(b), nil
|
|
},
|
|
MockRemoteAddr: func() net.Addr {
|
|
return &mocks.Addr{
|
|
MockNetwork: func() string {
|
|
return "tcp"
|
|
},
|
|
MockString: func() string {
|
|
return "1.1.1.1:443"
|
|
},
|
|
}
|
|
},
|
|
}
|
|
zeroTime := time.Now()
|
|
trace := NewTrace(0, zeroTime)
|
|
trace.networkEvent = make(chan *model.ArchivalNetworkEvent) // no buffer
|
|
conn := trace.MaybeWrapNetConn(underlying)
|
|
const bufsiz = 128
|
|
buffer := make([]byte, bufsiz)
|
|
count, err := conn.Read(buffer)
|
|
if count != bufsiz {
|
|
t.Fatal("invalid count")
|
|
}
|
|
if err != nil {
|
|
t.Fatal("invalid err")
|
|
}
|
|
events := trace.NetworkEvents()
|
|
if len(events) != 0 {
|
|
t.Fatal("expected no network events")
|
|
}
|
|
})
|
|
|
|
t.Run("Write saves a trace", func(t *testing.T) {
|
|
underlying := &mocks.Conn{
|
|
MockWrite: func(b []byte) (int, error) {
|
|
return len(b), nil
|
|
},
|
|
MockRemoteAddr: func() net.Addr {
|
|
return &mocks.Addr{
|
|
MockNetwork: func() string {
|
|
return "tcp"
|
|
},
|
|
MockString: func() string {
|
|
return "1.1.1.1:443"
|
|
},
|
|
}
|
|
},
|
|
}
|
|
zeroTime := time.Now()
|
|
td := testingx.NewTimeDeterministic(zeroTime)
|
|
trace := NewTrace(0, zeroTime)
|
|
trace.TimeNowFn = td.Now // deterministic time tracking
|
|
conn := trace.MaybeWrapNetConn(underlying)
|
|
const bufsiz = 128
|
|
buffer := make([]byte, bufsiz)
|
|
count, err := conn.Write(buffer)
|
|
if count != bufsiz {
|
|
t.Fatal("invalid count")
|
|
}
|
|
if err != nil {
|
|
t.Fatal("invalid err")
|
|
}
|
|
events := trace.NetworkEvents()
|
|
if len(events) != 1 {
|
|
t.Fatal("did not save network events")
|
|
}
|
|
expect := &model.ArchivalNetworkEvent{
|
|
Address: "1.1.1.1:443",
|
|
Failure: nil,
|
|
NumBytes: bufsiz,
|
|
Operation: netxlite.WriteOperation,
|
|
Proto: "tcp",
|
|
T: 1.0,
|
|
Tags: []string{},
|
|
}
|
|
got := events[0]
|
|
if diff := cmp.Diff(expect, got); diff != "" {
|
|
t.Fatal(diff)
|
|
}
|
|
})
|
|
|
|
t.Run("Write discards the event when the buffer is full", func(t *testing.T) {
|
|
underlying := &mocks.Conn{
|
|
MockWrite: func(b []byte) (int, error) {
|
|
return len(b), nil
|
|
},
|
|
MockRemoteAddr: func() net.Addr {
|
|
return &mocks.Addr{
|
|
MockNetwork: func() string {
|
|
return "tcp"
|
|
},
|
|
MockString: func() string {
|
|
return "1.1.1.1:443"
|
|
},
|
|
}
|
|
},
|
|
}
|
|
zeroTime := time.Now()
|
|
trace := NewTrace(0, zeroTime)
|
|
trace.networkEvent = make(chan *model.ArchivalNetworkEvent) // no buffer
|
|
conn := trace.MaybeWrapNetConn(underlying)
|
|
const bufsiz = 128
|
|
buffer := make([]byte, bufsiz)
|
|
count, err := conn.Write(buffer)
|
|
if count != bufsiz {
|
|
t.Fatal("invalid count")
|
|
}
|
|
if err != nil {
|
|
t.Fatal("invalid err")
|
|
}
|
|
events := trace.NetworkEvents()
|
|
if len(events) != 0 {
|
|
t.Fatal("expected no network events")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestWrapUDPLikeConn(t *testing.T) {
|
|
t.Run("WrapUDPLikeConn wraps the conn", func(t *testing.T) {
|
|
underlying := &mocks.UDPLikeConn{}
|
|
zeroTime := time.Now()
|
|
trace := NewTrace(0, zeroTime)
|
|
conn := trace.MaybeWrapUDPLikeConn(underlying)
|
|
ct := conn.(*udpLikeConnTrace)
|
|
if ct.UDPLikeConn != underlying {
|
|
t.Fatal("invalid underlying")
|
|
}
|
|
if ct.tx != trace {
|
|
t.Fatal("invalid trace")
|
|
}
|
|
})
|
|
|
|
t.Run("ReadFrom saves a trace", func(t *testing.T) {
|
|
underlying := &mocks.UDPLikeConn{
|
|
MockReadFrom: func(b []byte) (int, net.Addr, error) {
|
|
return len(b), &mocks.Addr{
|
|
MockString: func() string {
|
|
return "1.1.1.1:443"
|
|
},
|
|
}, nil
|
|
},
|
|
}
|
|
zeroTime := time.Now()
|
|
td := testingx.NewTimeDeterministic(zeroTime)
|
|
trace := NewTrace(0, zeroTime)
|
|
trace.TimeNowFn = td.Now // deterministic time counting
|
|
conn := trace.MaybeWrapUDPLikeConn(underlying)
|
|
const bufsiz = 128
|
|
buffer := make([]byte, bufsiz)
|
|
count, addr, err := conn.ReadFrom(buffer)
|
|
if count != bufsiz {
|
|
t.Fatal("invalid count")
|
|
}
|
|
if addr.String() != "1.1.1.1:443" {
|
|
t.Fatal("invalid address")
|
|
}
|
|
if err != nil {
|
|
t.Fatal("invalid err")
|
|
}
|
|
events := trace.NetworkEvents()
|
|
if len(events) != 1 {
|
|
t.Fatal("did not save network events")
|
|
}
|
|
expect := &model.ArchivalNetworkEvent{
|
|
Address: "1.1.1.1:443",
|
|
Failure: nil,
|
|
NumBytes: bufsiz,
|
|
Operation: "read_from",
|
|
Proto: "udp",
|
|
T: 1.0,
|
|
Tags: []string{},
|
|
}
|
|
got := events[0]
|
|
if diff := cmp.Diff(expect, got); diff != "" {
|
|
t.Fatal(diff)
|
|
}
|
|
})
|
|
|
|
t.Run("ReadFrom discards the event when the buffer is full", func(t *testing.T) {
|
|
underlying := &mocks.UDPLikeConn{
|
|
MockReadFrom: func(b []byte) (int, net.Addr, error) {
|
|
return len(b), &mocks.Addr{
|
|
MockString: func() string {
|
|
return "1.1.1.1:443"
|
|
},
|
|
}, nil
|
|
},
|
|
}
|
|
zeroTime := time.Now()
|
|
trace := NewTrace(0, zeroTime)
|
|
trace.networkEvent = make(chan *model.ArchivalNetworkEvent) // no buffer
|
|
conn := trace.MaybeWrapUDPLikeConn(underlying)
|
|
const bufsiz = 128
|
|
buffer := make([]byte, bufsiz)
|
|
count, addr, err := conn.ReadFrom(buffer)
|
|
if count != bufsiz {
|
|
t.Fatal("invalid count")
|
|
}
|
|
if addr.String() != "1.1.1.1:443" {
|
|
t.Fatal("invalid address")
|
|
}
|
|
if err != nil {
|
|
t.Fatal("invalid err")
|
|
}
|
|
events := trace.NetworkEvents()
|
|
if len(events) != 0 {
|
|
t.Fatal("expected no network events")
|
|
}
|
|
})
|
|
|
|
t.Run("WriteTo saves a trace", func(t *testing.T) {
|
|
underlying := &mocks.UDPLikeConn{
|
|
MockWriteTo: func(b []byte, addr net.Addr) (int, error) {
|
|
return len(b), nil
|
|
},
|
|
}
|
|
zeroTime := time.Now()
|
|
td := testingx.NewTimeDeterministic(zeroTime)
|
|
trace := NewTrace(0, zeroTime)
|
|
trace.TimeNowFn = td.Now // deterministic time tracking
|
|
conn := trace.MaybeWrapUDPLikeConn(underlying)
|
|
const bufsiz = 128
|
|
buffer := make([]byte, bufsiz)
|
|
addr := &mocks.Addr{
|
|
MockString: func() string {
|
|
return "1.1.1.1:443"
|
|
},
|
|
}
|
|
count, err := conn.WriteTo(buffer, addr)
|
|
if count != bufsiz {
|
|
t.Fatal("invalid count")
|
|
}
|
|
if err != nil {
|
|
t.Fatal("invalid err")
|
|
}
|
|
events := trace.NetworkEvents()
|
|
if len(events) != 1 {
|
|
t.Fatal("did not save network events")
|
|
}
|
|
expect := &model.ArchivalNetworkEvent{
|
|
Address: "1.1.1.1:443",
|
|
Failure: nil,
|
|
NumBytes: bufsiz,
|
|
Operation: "write_to",
|
|
Proto: "udp",
|
|
T: 1.0,
|
|
Tags: []string{},
|
|
}
|
|
got := events[0]
|
|
if diff := cmp.Diff(expect, got); diff != "" {
|
|
t.Fatal(diff)
|
|
}
|
|
})
|
|
|
|
t.Run("Write discards the event when the buffer is full", func(t *testing.T) {
|
|
underlying := &mocks.UDPLikeConn{
|
|
MockWriteTo: func(b []byte, addr net.Addr) (int, error) {
|
|
return len(b), nil
|
|
},
|
|
}
|
|
zeroTime := time.Now()
|
|
trace := NewTrace(0, zeroTime)
|
|
trace.networkEvent = make(chan *model.ArchivalNetworkEvent) // no buffer
|
|
conn := trace.MaybeWrapUDPLikeConn(underlying)
|
|
const bufsiz = 128
|
|
buffer := make([]byte, bufsiz)
|
|
addr := &mocks.Addr{
|
|
MockString: func() string {
|
|
return "1.1.1.1:443"
|
|
},
|
|
}
|
|
count, err := conn.WriteTo(buffer, addr)
|
|
if count != bufsiz {
|
|
t.Fatal("invalid count")
|
|
}
|
|
if err != nil {
|
|
t.Fatal("invalid err")
|
|
}
|
|
events := trace.NetworkEvents()
|
|
if len(events) != 0 {
|
|
t.Fatal("expected no network events")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestFirstNetworkEvent(t *testing.T) {
|
|
t.Run("returns nil when buffer is empty", func(t *testing.T) {
|
|
zeroTime := time.Now()
|
|
trace := NewTrace(0, zeroTime)
|
|
got := trace.FirstNetworkEventOrNil()
|
|
if got != nil {
|
|
t.Fatal("expected nil event")
|
|
}
|
|
})
|
|
|
|
t.Run("return first non-nil network event", func(t *testing.T) {
|
|
filler := func(tx *Trace, events []*model.ArchivalNetworkEvent) {
|
|
for _, ev := range events {
|
|
tx.networkEvent <- ev
|
|
}
|
|
}
|
|
zeroTime := time.Now()
|
|
trace := NewTrace(0, zeroTime)
|
|
expect := []*model.ArchivalNetworkEvent{{
|
|
Address: "1.1.1.1:443",
|
|
Failure: nil,
|
|
NumBytes: 0,
|
|
Operation: "read_from",
|
|
Proto: "udp",
|
|
T: 1.0,
|
|
Tags: []string{},
|
|
}, {
|
|
Address: "1.1.1.1:443",
|
|
Failure: nil,
|
|
NumBytes: 0,
|
|
Operation: "write_to",
|
|
Proto: "udp",
|
|
T: 1.0,
|
|
Tags: []string{},
|
|
}}
|
|
filler(trace, expect)
|
|
got := trace.FirstNetworkEventOrNil()
|
|
if diff := cmp.Diff(got, expect[0]); diff != "" {
|
|
t.Fatal(diff)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestNewAnnotationArchivalNetworkEvent(t *testing.T) {
|
|
var (
|
|
index int64 = 3
|
|
duration = 250 * time.Millisecond
|
|
operation = "tls_handshake_start"
|
|
)
|
|
expect := &model.ArchivalNetworkEvent{
|
|
Address: "",
|
|
Failure: nil,
|
|
NumBytes: 0,
|
|
Operation: operation,
|
|
Proto: "",
|
|
T0: duration.Seconds(),
|
|
T: duration.Seconds(),
|
|
TransactionID: index,
|
|
Tags: []string{},
|
|
}
|
|
got := NewAnnotationArchivalNetworkEvent(
|
|
index, duration, operation,
|
|
)
|
|
if diff := cmp.Diff(expect, got); diff != "" {
|
|
t.Fatal(diff)
|
|
}
|
|
}
|