ooni-probe-cli/internal/engine/experiment/quicping/quicping_test.go
Simone Basso da1c13e312
cleanup: remove UnderlyingNetworkLibrary and TProxy (#874)
* cleanup: remove UnderlyingNetworkLibrary and TProxy

While there, replace mixture of mocking and real connections inside
quicping with pure mocking of network connections.

Closes https://github.com/ooni/probe/issues/2224

* cleanup: we don't need a SimpleResolver now

This type was only used by UnderlyingNetworkLibrary and all the
rest of the code uses Resolver. So, let's avoid complexity by zapping
the SimpleResolver type and merging it inside Resolver.
2022-08-23 11:43:44 +02:00

322 lines
8.9 KiB
Go

package quicping
import (
"context"
"encoding/hex"
"errors"
"net"
"strings"
"testing"
"time"
"github.com/apex/log"
"github.com/ooni/probe-cli/v3/internal/engine/mockable"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/model/mocks"
)
func TestNewExperimentMeasurer(t *testing.T) {
measurer := NewExperimentMeasurer(Config{})
if measurer.ExperimentName() != "quicping" {
t.Fatal("unexpected name")
}
if measurer.ExperimentVersion() != "0.1.0" {
t.Fatal("unexpected version")
}
}
func TestInvalidHost(t *testing.T) {
measurer := NewExperimentMeasurer(Config{
Port: 443,
Repetitions: 1,
})
measurement := new(model.Measurement)
measurement.Input = model.MeasurementTarget("a.a.a.a")
sess := &mockable.Session{MockableLogger: log.Log}
err := measurer.Run(context.Background(), sess, measurement,
model.NewPrinterCallbacks(log.Log))
if err == nil {
t.Fatal("expected an error here")
}
if _, ok := err.(*net.DNSError); !ok {
t.Fatal("unexpected error type")
}
}
func TestURLInput(t *testing.T) {
if testing.Short() {
t.Skip("skip test in short mode")
}
measurer := NewExperimentMeasurer(Config{
Repetitions: 1,
})
measurement := new(model.Measurement)
measurement.Input = model.MeasurementTarget("https://google.com/")
sess := &mockable.Session{MockableLogger: log.Log}
err := measurer.Run(context.Background(), sess, measurement,
model.NewPrinterCallbacks(log.Log))
if err != nil {
t.Fatal("unexpected error")
}
tk := measurement.TestKeys.(*TestKeys)
if tk.Domain != "google.com" {
t.Fatal("unexpected domain")
}
}
func TestSuccess(t *testing.T) {
if testing.Short() {
t.Skip("skip test in short mode")
}
measurer := NewExperimentMeasurer(Config{})
measurement := new(model.Measurement)
measurement.Input = model.MeasurementTarget("google.com")
sess := &mockable.Session{MockableLogger: log.Log}
err := measurer.Run(context.Background(), sess, measurement,
model.NewPrinterCallbacks(log.Log))
if err != nil {
t.Fatal("did not expect an error here")
}
tk := measurement.TestKeys.(*TestKeys)
if tk.Domain != "google.com" {
t.Fatal("unexpected domain")
}
if tk.Repetitions != 10 {
t.Fatal("unexpected number of repetitions, default is 10")
}
if tk.Pings == nil || len(tk.Pings) != 10 {
t.Fatal("unexpected number of pings", len(tk.Pings))
}
for i, ping := range tk.Pings {
if ping.Failure != nil {
t.Fatal("ping failed unexpectedly", i, *ping.Failure)
}
for _, resp := range ping.Responses {
if resp.Failure != nil {
t.Fatal("unexepcted response failure")
}
if resp.SupportedVersions == nil || len(resp.SupportedVersions) == 0 {
t.Fatal("server did not respond with supported versions")
}
}
}
sk, err := measurer.GetSummaryKeys(measurement)
if err != nil {
t.Fatal(err)
}
if _, ok := sk.(SummaryKeys); !ok {
t.Fatal("invalid type for summary keys")
}
}
func TestWithCancelledContext(t *testing.T) {
measurer := NewExperimentMeasurer(Config{})
measurement := new(model.Measurement)
measurement.Input = model.MeasurementTarget("google.com")
sess := &mockable.Session{MockableLogger: log.Log}
ctx, cancel := context.WithCancel(context.Background())
cancel()
err := measurer.Run(ctx, sess, measurement,
model.NewPrinterCallbacks(log.Log))
if err != nil {
t.Fatal("did not expect an error here")
}
tk := measurement.TestKeys.(*TestKeys)
if len(tk.Pings) > 0 {
t.Fatal("there should not be any measurements")
}
}
func TestListenFails(t *testing.T) {
expected := errors.New("expected")
measurer := NewExperimentMeasurer(Config{
netListenUDP: func(network string, laddr *net.UDPAddr) (model.UDPLikeConn, error) {
return nil, expected
},
})
measurement := new(model.Measurement)
measurement.Input = model.MeasurementTarget("google.com")
sess := &mockable.Session{MockableLogger: log.Log}
err := measurer.Run(context.Background(), sess, measurement,
model.NewPrinterCallbacks(log.Log))
if err == nil {
t.Fatal("expected an error here")
}
if err != expected {
t.Fatal("unexpected error type")
}
}
func TestWriteFails(t *testing.T) {
if testing.Short() {
t.Skip("skip test in short mode")
}
expected := errors.New("expected")
setDeadlineCalled := false
closeCalled := false
pconn := &mocks.UDPLikeConn{
MockReadFrom: func(p []byte) (int, net.Addr, error) {
source := make([]byte, len(p))
copy(p, source)
return len(p), &mocks.Addr{}, nil
},
MockSetDeadline: func(t time.Time) error {
setDeadlineCalled = true
return nil
},
MockClose: func() error {
closeCalled = true
return nil
},
MockWriteTo: func(p []byte, addr net.Addr) (int, error) {
return 0, expected
},
}
measurer := NewExperimentMeasurer(Config{
netListenUDP: func(network string, laddr *net.UDPAddr) (model.UDPLikeConn, error) {
return pconn, nil
},
Repetitions: 1,
})
measurement := new(model.Measurement)
measurement.Input = model.MeasurementTarget("google.com")
sess := &mockable.Session{MockableLogger: log.Log}
err := measurer.Run(context.Background(), sess, measurement,
model.NewPrinterCallbacks(log.Log))
if err != nil {
t.Fatal("unexpected error")
}
tk := measurement.TestKeys.(*TestKeys)
if tk.Pings == nil || len(tk.Pings) != 1 {
t.Fatal("unexpected number of pings", len(tk.Pings))
}
for i, ping := range tk.Pings {
if ping.Failure == nil {
t.Fatal("expected an error here, ping", i)
}
if !strings.Contains(*ping.Failure, "expected") {
t.Fatal("ping: unexpected error type", i, *ping.Failure)
}
}
if !setDeadlineCalled {
t.Fatal("did not call set deadline")
}
if !closeCalled {
t.Fatal("did not call close")
}
}
func TestReadFails(t *testing.T) {
if testing.Short() {
t.Skip("skip test in short mode")
}
setDeadlineCalled := false
closeCalled := false
expected := errors.New("expected")
pconn := &mocks.UDPLikeConn{
MockReadFrom: func(p []byte) (int, net.Addr, error) {
return 0, nil, expected
},
MockSetDeadline: func(t time.Time) error {
setDeadlineCalled = true
return nil
},
MockClose: func() error {
closeCalled = true
return nil
},
MockWriteTo: func(p []byte, addr net.Addr) (int, error) {
return len(p), nil
},
}
measurer := NewExperimentMeasurer(Config{
netListenUDP: func(network string, laddr *net.UDPAddr) (model.UDPLikeConn, error) {
return pconn, nil
},
Repetitions: 1,
})
measurement := new(model.Measurement)
measurement.Input = model.MeasurementTarget("google.com")
sess := &mockable.Session{MockableLogger: log.Log}
err := measurer.Run(context.Background(), sess, measurement,
model.NewPrinterCallbacks(log.Log))
if err != nil {
t.Fatal("unexpected error")
}
tk := measurement.TestKeys.(*TestKeys)
if tk.Pings == nil || len(tk.Pings) != 1 {
t.Fatal("unexpected number of pings", len(tk.Pings))
}
for i, ping := range tk.Pings {
if ping.Failure == nil {
t.Fatal("expected an error here, ping", i)
}
}
if !setDeadlineCalled {
t.Fatal("did not call set deadline")
}
if !closeCalled {
t.Fatal("did not call close")
}
}
func TestNoResponse(t *testing.T) {
if testing.Short() {
t.Skip("skip test in short mode")
}
measurer := NewExperimentMeasurer(Config{
Repetitions: 1,
})
measurement := new(model.Measurement)
measurement.Input = model.MeasurementTarget("ooni.org")
sess := &mockable.Session{MockableLogger: log.Log}
err := measurer.Run(context.Background(), sess, measurement,
model.NewPrinterCallbacks(log.Log))
if err != nil {
t.Fatal("did not expect an error here")
}
tk := measurement.TestKeys.(*TestKeys)
if tk.Pings == nil || len(tk.Pings) != 1 {
t.Fatal("unexpected number of pings", len(tk.Pings))
}
if tk.Pings[0].Failure == nil {
t.Fatal("expected an error here")
}
if *tk.Pings[0].Failure != "generic_timeout_error" {
t.Fatal("unexpected error type")
}
}
func TestDissect(t *testing.T) {
// destID--srcID: 040b9649d3fd4c038ab6c073966f3921--44d064031288e97646451f
versionNegotiationResponse, _ := hex.DecodeString("eb0000000010040b9649d3fd4c038ab6c073966f39210b44d064031288e97646451f00000001ff00001dff00001cff00001b")
measurer := NewExperimentMeasurer(Config{})
destID := "040b9649d3fd4c038ab6c073966f3921"
_, dst, err := measurer.(*Measurer).dissectVersionNegotiation(versionNegotiationResponse)
if err != nil {
t.Fatal("unexpected error", err)
}
if hex.EncodeToString(dst) != destID {
t.Fatal("unexpected destination connection ID")
}
versionNegotiationResponse[1] = byte(0xff)
_, _, err = measurer.(*Measurer).dissectVersionNegotiation(versionNegotiationResponse)
if err == nil {
t.Fatal("expected an error here", err)
}
if !strings.HasSuffix(err.Error(), "unexpected Version Negotiation format") {
t.Fatal("unexpected error type", err)
}
versionNegotiationResponse[0] = byte(0x01)
_, _, err = measurer.(*Measurer).dissectVersionNegotiation(versionNegotiationResponse)
if err == nil {
t.Fatal("expected an error here", err)
}
if !strings.HasSuffix(err.Error(), "not a long header packet") {
t.Fatal("unexpected error type", err)
}
}