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)
 | 
						|
	}
 | 
						|
}
 |