ooni-probe-cli/pkg/oonimkall/taskrunner_test.go
Simone Basso d922bd9afc
cleanup: mark more integration tests as !short mode (#755)
The objective is to make PR checks run much faster.

See https://github.com/ooni/probe/issues/2113 for context.

Regarding netxlite's tests:

Checking for every commit on master or on a release branch is
good enough and makes pull requests faster than one minute since
netxlite for windows is now 1m slower than coverage.

We're losing some coverage but coverage from integration tests
is not so good anyway, so I'm not super sad about this loss.
2022-05-24 21:01:15 +02:00

774 lines
27 KiB
Go

package oonimkall
import (
"context"
"errors"
"testing"
"time"
"github.com/google/go-cmp/cmp"
engine "github.com/ooni/probe-cli/v3/internal/engine"
"github.com/ooni/probe-cli/v3/internal/model"
)
func TestMeasurementSubmissionEventName(t *testing.T) {
if measurementSubmissionEventName(nil) != eventTypeStatusMeasurementSubmission {
t.Fatal("unexpected submission event name")
}
if measurementSubmissionEventName(errors.New("mocked error")) != eventTypeFailureMeasurementSubmission {
t.Fatal("unexpected submission event name")
}
}
func TestMeasurementSubmissionFailure(t *testing.T) {
if measurementSubmissionFailure(nil) != "" {
t.Fatal("unexpected submission failure")
}
if measurementSubmissionFailure(errors.New("mocked error")) != "mocked error" {
t.Fatal("unexpected submission failure")
}
}
func TestTaskRunnerRun(t *testing.T) {
if testing.Short() {
t.Skip("skip test in short mode")
}
// newRunnerForTesting is a factory for creating a new
// runner that wraps newRunner and also sets a specific
// taskSessionBuilder for testing purposes.
newRunnerForTesting := func() (*runnerForTask, *CollectorTaskEmitter) {
settings := &settings{
Name: "Example",
Options: settingsOptions{
SoftwareName: "oonimkall-test",
SoftwareVersion: "0.1.0",
},
StateDir: "testdata/state",
Version: 1,
}
e := &CollectorTaskEmitter{}
r := newRunner(settings, e)
return r, e
}
// runAndCollectContext runs the task until completion
// and collects the emitted events. Remember that
// it's not race safe to modify the events.
runAndCollectContext := func(ctx context.Context, r taskRunner, e *CollectorTaskEmitter) []*event {
r.Run(ctx)
return e.Collect()
}
// runAndCollect is like runAndCollectContext
// but uses context.Background()
runAndCollect := func(r taskRunner, e *CollectorTaskEmitter) []*event {
return runAndCollectContext(context.Background(), r, e)
}
// countEventsByKey returns the number of events
// with the given key inside of the list.
countEventsByKey := func(events []*event, key string) (count int) {
for _, ev := range events {
if ev.Key == key {
count++
}
}
return
}
// assertCountEventsByKey fails is the number of events
// of the given type is not the expected one.
assertCountEventsByKey := func(events []*event, key string, count int) {
if countEventsByKey(events, key) != count {
t.Fatalf("unexpected number of '%s' events", key)
}
}
t.Run("with unsupported settings", func(t *testing.T) {
runner, emitter := newRunnerForTesting()
runner.settings.Version = 0 // force unsupported version
events := runAndCollect(runner, emitter)
assertCountEventsByKey(events, eventTypeFailureStartup, 1)
})
t.Run("with failure when creating a new kvstore", func(t *testing.T) {
runner, emitter := newRunnerForTesting()
// override the kvstore builder to provoke an error
runner.kvStoreBuilder = &MockableKVStoreFSBuilder{
MockNewFS: func(path string) (model.KeyValueStore, error) {
return nil, errors.New("generic error")
},
}
events := runAndCollect(runner, emitter)
assertCountEventsByKey(events, eventTypeFailureStartup, 1)
})
t.Run("with unparsable proxyURL", func(t *testing.T) {
runner, emitter := newRunnerForTesting()
runner.settings.Proxy = "\t" // invalid proxy URL
events := runAndCollect(runner, emitter)
assertCountEventsByKey(events, eventTypeFailureStartup, 1)
})
t.Run("with a parsable proxyURL", func(t *testing.T) {
runner, emitter := newRunnerForTesting()
// set a valid URL
runner.settings.Proxy = "https://127.0.0.1/"
// set a fake session builder that causes the startup to
// fail but records the config passed to NewSession
saver := &SessionBuilderConfigSaver{}
runner.sessionBuilder = saver
events := runAndCollect(runner, emitter)
assertCountEventsByKey(events, eventTypeFailureStartup, 1)
if saver.Config.ProxyURL.String() != runner.settings.Proxy {
t.Fatal("invalid proxy URL")
}
})
t.Run("with custom probe services URL", func(t *testing.T) {
runner, emitter := newRunnerForTesting()
// set a probe services URL
runner.settings.Options.ProbeServicesBaseURL = "https://127.0.0.1"
// set a fake session builder that causes the startup to
// fail but records the config passed to NewSession
saver := &SessionBuilderConfigSaver{}
runner.sessionBuilder = saver
events := runAndCollect(runner, emitter)
assertCountEventsByKey(events, eventTypeFailureStartup, 1)
psu := saver.Config.AvailableProbeServices
if len(psu) != 1 {
t.Fatal("invalid length")
}
if psu[0].Type != "https" {
t.Fatal("invalid type")
}
if psu[0].Address != runner.settings.Options.ProbeServicesBaseURL {
t.Fatal("invalid address")
}
if psu[0].Front != "" {
t.Fatal("invalid front")
}
})
type eventKeyCount struct {
Key string
Count int
}
// reduceEventsKeysIgnoreLog reduces the list of event keys
// counting equal subsequent keys and ignoring log events
reduceEventsKeysIgnoreLog := func(events []*event) (out []eventKeyCount) {
var current eventKeyCount
for _, ev := range events {
if ev.Key == eventTypeLog {
continue
}
if current.Key == ev.Key {
current.Count++
continue
}
if current.Key != "" {
out = append(out, current)
}
current.Key = ev.Key
current.Count = 1
}
if current.Key != "" {
out = append(out, current)
}
return
}
// fakeSuccessfulRun returns a new set of dependencies that
// will perform a fully successful, but fake, run.
fakeSuccessfulRun := func() *MockableTaskRunnerDependencies {
return &MockableTaskRunnerDependencies{
MockableKibiBytesReceived: func() float64 {
return 10
},
MockableKibiBytesSent: func() float64 {
return 4
},
MockableOpenReportContext: func(ctx context.Context) error {
return nil
},
MockableReportID: func() string {
return "20211202T074907Z_example_IT_30722_n1_axDLHNUfJaV1IbuU"
},
MockableMeasureWithContext: func(ctx context.Context, input string) (*model.Measurement, error) {
return &model.Measurement{}, nil
},
MockableSubmitAndUpdateMeasurementContext: func(ctx context.Context, measurement *model.Measurement) error {
return nil
},
MockableSetCallbacks: func(callbacks model.ExperimentCallbacks) {
},
MockableInputPolicy: func() engine.InputPolicy {
return engine.InputNone
},
MockableInterruptible: func() bool {
return false
},
MockClose: func() error {
return nil
},
MockMaybeLookupBackendsContext: func(ctx context.Context) error {
return nil
},
MockMaybeLookupLocationContext: func(ctx context.Context) error {
return nil
},
MockProbeIP: func() string {
return "130.192.91.211"
},
MockProbeASNString: func() string {
return "AS137"
},
MockProbeCC: func() string {
return "IT"
},
MockProbeNetworkName: func() string {
return "GARR"
},
MockResolverASNString: func() string {
return "AS137"
},
MockResolverIP: func() string {
return "130.192.3.24"
},
MockResolverNetworkName: func() string {
return "GARR"
},
}
}
assertReducedEventsLike := func(t *testing.T, expected, got []eventKeyCount) {
if diff := cmp.Diff(expected, got); diff != "" {
t.Fatal(diff)
}
}
t.Run("with invalid experiment name", func(t *testing.T) {
runner, emitter := newRunnerForTesting()
fake := fakeSuccessfulRun()
fake.MockNewExperimentBuilderByName = func(name string) (taskExperimentBuilder, error) {
return nil, errors.New("invalid experiment name")
}
runner.sessionBuilder = fake
events := runAndCollect(runner, emitter)
reduced := reduceEventsKeysIgnoreLog(events)
expect := []eventKeyCount{
{Key: eventTypeStatusQueued, Count: 1},
{Key: eventTypeStatusStarted, Count: 1},
{Key: eventTypeFailureStartup, Count: 1},
{Key: eventTypeStatusEnd, Count: 1},
}
assertReducedEventsLike(t, expect, reduced)
})
t.Run("with error during backends lookup", func(t *testing.T) {
runner, emitter := newRunnerForTesting()
fake := fakeSuccessfulRun()
fake.MockMaybeLookupBackendsContext = func(ctx context.Context) error {
return errors.New("mocked error")
}
runner.sessionBuilder = fake
events := runAndCollect(runner, emitter)
reduced := reduceEventsKeysIgnoreLog(events)
expect := []eventKeyCount{
{Key: eventTypeStatusQueued, Count: 1},
{Key: eventTypeStatusStarted, Count: 1},
{Key: eventTypeFailureStartup, Count: 1},
{Key: eventTypeStatusEnd, Count: 1},
}
assertReducedEventsLike(t, expect, reduced)
})
t.Run("with error during location lookup", func(t *testing.T) {
runner, emitter := newRunnerForTesting()
fake := fakeSuccessfulRun()
fake.MockMaybeLookupLocationContext = func(ctx context.Context) error {
return errors.New("mocked error")
}
runner.sessionBuilder = fake
events := runAndCollect(runner, emitter)
reduced := reduceEventsKeysIgnoreLog(events)
expect := []eventKeyCount{
{Key: eventTypeStatusQueued, Count: 1},
{Key: eventTypeStatusStarted, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeFailureIPLookup, Count: 1},
{Key: eventTypeFailureASNLookup, Count: 1},
{Key: eventTypeFailureCCLookup, Count: 1},
{Key: eventTypeFailureResolverLookup, Count: 1},
{Key: eventTypeStatusEnd, Count: 1},
}
assertReducedEventsLike(t, expect, reduced)
})
t.Run("with missing input and InputOrQueryBackend policy", func(t *testing.T) {
runner, emitter := newRunnerForTesting()
fake := fakeSuccessfulRun()
fake.MockableInputPolicy = func() engine.InputPolicy {
return engine.InputOrQueryBackend
}
runner.sessionBuilder = fake
events := runAndCollect(runner, emitter)
reduced := reduceEventsKeysIgnoreLog(events)
expect := []eventKeyCount{
{Key: eventTypeStatusQueued, Count: 1},
{Key: eventTypeStatusStarted, Count: 1},
{Key: eventTypeStatusProgress, Count: 3},
{Key: eventTypeStatusGeoIPLookup, Count: 1},
{Key: eventTypeStatusResolverLookup, Count: 1},
{Key: eventTypeFailureStartup, Count: 1},
{Key: eventTypeStatusEnd, Count: 1},
}
assertReducedEventsLike(t, expect, reduced)
})
t.Run("with missing input and InputStrictlyRequired policy", func(t *testing.T) {
runner, emitter := newRunnerForTesting()
fake := fakeSuccessfulRun()
fake.MockableInputPolicy = func() engine.InputPolicy {
return engine.InputStrictlyRequired
}
runner.sessionBuilder = fake
events := runAndCollect(runner, emitter)
reduced := reduceEventsKeysIgnoreLog(events)
expect := []eventKeyCount{
{Key: eventTypeStatusQueued, Count: 1},
{Key: eventTypeStatusStarted, Count: 1},
{Key: eventTypeStatusProgress, Count: 3},
{Key: eventTypeStatusGeoIPLookup, Count: 1},
{Key: eventTypeStatusResolverLookup, Count: 1},
{Key: eventTypeFailureStartup, Count: 1},
{Key: eventTypeStatusEnd, Count: 1},
}
assertReducedEventsLike(t, expect, reduced)
})
t.Run(
"with InputOrStaticDefault policy and experiment with no static input",
func(t *testing.T) {
runner, emitter := newRunnerForTesting()
runner.settings.Name = "Antani" // no input for this experiment
fake := fakeSuccessfulRun()
fake.MockableInputPolicy = func() engine.InputPolicy {
return engine.InputOrStaticDefault
}
runner.sessionBuilder = fake
events := runAndCollect(runner, emitter)
reduced := reduceEventsKeysIgnoreLog(events)
expect := []eventKeyCount{
{Key: eventTypeStatusQueued, Count: 1},
{Key: eventTypeStatusStarted, Count: 1},
{Key: eventTypeStatusProgress, Count: 3},
{Key: eventTypeStatusGeoIPLookup, Count: 1},
{Key: eventTypeStatusResolverLookup, Count: 1},
{Key: eventTypeFailureStartup, Count: 1},
{Key: eventTypeStatusEnd, Count: 1},
}
assertReducedEventsLike(t, expect, reduced)
})
t.Run("with InputNone policy and provided input", func(t *testing.T) {
runner, emitter := newRunnerForTesting()
runner.settings.Inputs = append(runner.settings.Inputs, "https://x.org/")
fake := fakeSuccessfulRun()
fake.MockableInputPolicy = func() engine.InputPolicy {
return engine.InputNone
}
runner.sessionBuilder = fake
events := runAndCollect(runner, emitter)
reduced := reduceEventsKeysIgnoreLog(events)
expect := []eventKeyCount{
{Key: eventTypeStatusQueued, Count: 1},
{Key: eventTypeStatusStarted, Count: 1},
{Key: eventTypeStatusProgress, Count: 3},
{Key: eventTypeStatusGeoIPLookup, Count: 1},
{Key: eventTypeStatusResolverLookup, Count: 1},
{Key: eventTypeFailureStartup, Count: 1},
{Key: eventTypeStatusEnd, Count: 1},
}
assertReducedEventsLike(t, expect, reduced)
})
t.Run("with failure opening report", func(t *testing.T) {
runner, emitter := newRunnerForTesting()
fake := fakeSuccessfulRun()
fake.MockableOpenReportContext = func(ctx context.Context) error {
return errors.New("mocked error")
}
runner.sessionBuilder = fake
events := runAndCollect(runner, emitter)
reduced := reduceEventsKeysIgnoreLog(events)
expect := []eventKeyCount{
{Key: eventTypeStatusQueued, Count: 1},
{Key: eventTypeStatusStarted, Count: 1},
{Key: eventTypeStatusProgress, Count: 3},
{Key: eventTypeStatusGeoIPLookup, Count: 1},
{Key: eventTypeStatusResolverLookup, Count: 1},
{Key: eventTypeFailureReportCreate, Count: 1},
{Key: eventTypeStatusEnd, Count: 1},
}
assertReducedEventsLike(t, expect, reduced)
})
t.Run("with success and InputNone policy", func(t *testing.T) {
runner, emitter := newRunnerForTesting()
fake := fakeSuccessfulRun()
fake.MockableInputPolicy = func() engine.InputPolicy {
return engine.InputNone
}
runner.sessionBuilder = fake
events := runAndCollect(runner, emitter)
reduced := reduceEventsKeysIgnoreLog(events)
expect := []eventKeyCount{
{Key: eventTypeStatusQueued, Count: 1},
{Key: eventTypeStatusStarted, Count: 1},
{Key: eventTypeStatusProgress, Count: 3},
{Key: eventTypeStatusGeoIPLookup, Count: 1},
{Key: eventTypeStatusResolverLookup, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeStatusReportCreate, Count: 1},
{Key: eventTypeStatusMeasurementStart, Count: 1},
{Key: eventTypeMeasurement, Count: 1},
{Key: eventTypeStatusMeasurementSubmission, Count: 1},
{Key: eventTypeStatusMeasurementDone, Count: 1},
{Key: eventTypeStatusEnd, Count: 1},
}
assertReducedEventsLike(t, expect, reduced)
})
t.Run("with measurement failure and InputNone policy", func(t *testing.T) {
runner, emitter := newRunnerForTesting()
fake := fakeSuccessfulRun()
fake.MockableInputPolicy = func() engine.InputPolicy {
return engine.InputNone
}
fake.MockableMeasureWithContext = func(ctx context.Context, input string) (measurement *model.Measurement, err error) {
return nil, errors.New("preconditions error")
}
runner.sessionBuilder = fake
events := runAndCollect(runner, emitter)
reduced := reduceEventsKeysIgnoreLog(events)
expect := []eventKeyCount{
{Key: eventTypeStatusQueued, Count: 1},
{Key: eventTypeStatusStarted, Count: 1},
{Key: eventTypeStatusProgress, Count: 3},
{Key: eventTypeStatusGeoIPLookup, Count: 1},
{Key: eventTypeStatusResolverLookup, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeStatusReportCreate, Count: 1},
{Key: eventTypeStatusMeasurementStart, Count: 1},
{Key: eventTypeFailureMeasurement, Count: 1},
{Key: eventTypeStatusEnd, Count: 1},
}
assertReducedEventsLike(t, expect, reduced)
})
t.Run("with success and InputStrictlyRequired", func(t *testing.T) {
runner, emitter := newRunnerForTesting()
runner.settings.Inputs = []string{"a", "b", "c", "d"}
fake := fakeSuccessfulRun()
fake.MockableInputPolicy = func() engine.InputPolicy {
return engine.InputStrictlyRequired
}
runner.sessionBuilder = fake
events := runAndCollect(runner, emitter)
reduced := reduceEventsKeysIgnoreLog(events)
expect := []eventKeyCount{
{Key: eventTypeStatusQueued, Count: 1},
{Key: eventTypeStatusStarted, Count: 1},
{Key: eventTypeStatusProgress, Count: 3},
{Key: eventTypeStatusGeoIPLookup, Count: 1},
{Key: eventTypeStatusResolverLookup, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeStatusReportCreate, Count: 1},
//
{Key: eventTypeStatusMeasurementStart, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeMeasurement, Count: 1},
{Key: eventTypeStatusMeasurementSubmission, Count: 1},
{Key: eventTypeStatusMeasurementDone, Count: 1},
//
{Key: eventTypeStatusMeasurementStart, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeMeasurement, Count: 1},
{Key: eventTypeStatusMeasurementSubmission, Count: 1},
{Key: eventTypeStatusMeasurementDone, Count: 1},
//
{Key: eventTypeStatusMeasurementStart, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeMeasurement, Count: 1},
{Key: eventTypeStatusMeasurementSubmission, Count: 1},
{Key: eventTypeStatusMeasurementDone, Count: 1},
//
{Key: eventTypeStatusMeasurementStart, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeMeasurement, Count: 1},
{Key: eventTypeStatusMeasurementSubmission, Count: 1},
{Key: eventTypeStatusMeasurementDone, Count: 1},
//
{Key: eventTypeStatusEnd, Count: 1},
}
assertReducedEventsLike(t, expect, reduced)
})
t.Run("with success and InputOptional and input", func(t *testing.T) {
runner, emitter := newRunnerForTesting()
runner.settings.Inputs = []string{"a", "b", "c", "d"}
fake := fakeSuccessfulRun()
fake.MockableInputPolicy = func() engine.InputPolicy {
return engine.InputOptional
}
runner.sessionBuilder = fake
events := runAndCollect(runner, emitter)
reduced := reduceEventsKeysIgnoreLog(events)
expect := []eventKeyCount{
{Key: eventTypeStatusQueued, Count: 1},
{Key: eventTypeStatusStarted, Count: 1},
{Key: eventTypeStatusProgress, Count: 3},
{Key: eventTypeStatusGeoIPLookup, Count: 1},
{Key: eventTypeStatusResolverLookup, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeStatusReportCreate, Count: 1},
//
{Key: eventTypeStatusMeasurementStart, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeMeasurement, Count: 1},
{Key: eventTypeStatusMeasurementSubmission, Count: 1},
{Key: eventTypeStatusMeasurementDone, Count: 1},
//
{Key: eventTypeStatusMeasurementStart, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeMeasurement, Count: 1},
{Key: eventTypeStatusMeasurementSubmission, Count: 1},
{Key: eventTypeStatusMeasurementDone, Count: 1},
//
{Key: eventTypeStatusMeasurementStart, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeMeasurement, Count: 1},
{Key: eventTypeStatusMeasurementSubmission, Count: 1},
{Key: eventTypeStatusMeasurementDone, Count: 1},
//
{Key: eventTypeStatusMeasurementStart, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeMeasurement, Count: 1},
{Key: eventTypeStatusMeasurementSubmission, Count: 1},
{Key: eventTypeStatusMeasurementDone, Count: 1},
//
{Key: eventTypeStatusEnd, Count: 1},
}
assertReducedEventsLike(t, expect, reduced)
})
t.Run("with success and InputOptional and no input", func(t *testing.T) {
runner, emitter := newRunnerForTesting()
fake := fakeSuccessfulRun()
fake.MockableInputPolicy = func() engine.InputPolicy {
return engine.InputOptional
}
runner.sessionBuilder = fake
events := runAndCollect(runner, emitter)
reduced := reduceEventsKeysIgnoreLog(events)
expect := []eventKeyCount{
{Key: eventTypeStatusQueued, Count: 1},
{Key: eventTypeStatusStarted, Count: 1},
{Key: eventTypeStatusProgress, Count: 3},
{Key: eventTypeStatusGeoIPLookup, Count: 1},
{Key: eventTypeStatusResolverLookup, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeStatusReportCreate, Count: 1},
//
{Key: eventTypeStatusMeasurementStart, Count: 1},
{Key: eventTypeMeasurement, Count: 1},
{Key: eventTypeStatusMeasurementSubmission, Count: 1},
{Key: eventTypeStatusMeasurementDone, Count: 1},
//
{Key: eventTypeStatusEnd, Count: 1},
}
assertReducedEventsLike(t, expect, reduced)
})
t.Run("with success and InputOrStaticDefault", func(t *testing.T) {
experimentName := "DNSCheck"
runner, emitter := newRunnerForTesting()
runner.settings.Name = experimentName
fake := fakeSuccessfulRun()
fake.MockableInputPolicy = func() engine.InputPolicy {
return engine.InputOrStaticDefault
}
runner.sessionBuilder = fake
events := runAndCollect(runner, emitter)
reduced := reduceEventsKeysIgnoreLog(events)
expect := []eventKeyCount{
{Key: eventTypeStatusQueued, Count: 1},
{Key: eventTypeStatusStarted, Count: 1},
{Key: eventTypeStatusProgress, Count: 3},
{Key: eventTypeStatusGeoIPLookup, Count: 1},
{Key: eventTypeStatusResolverLookup, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeStatusReportCreate, Count: 1},
}
allEntries, err := engine.StaticBareInputForExperiment(experimentName)
if err != nil {
t.Fatal(err)
}
// write the correct entries for each expected measurement.
for idx := 0; idx < len(allEntries); idx++ {
expect = append(expect, eventKeyCount{Key: eventTypeStatusMeasurementStart, Count: 1})
expect = append(expect, eventKeyCount{Key: eventTypeStatusProgress, Count: 1})
expect = append(expect, eventKeyCount{Key: eventTypeMeasurement, Count: 1})
expect = append(expect, eventKeyCount{Key: eventTypeStatusMeasurementSubmission, Count: 1})
expect = append(expect, eventKeyCount{Key: eventTypeStatusMeasurementDone, Count: 1})
}
expect = append(expect, eventKeyCount{Key: eventTypeStatusEnd, Count: 1})
assertReducedEventsLike(t, expect, reduced)
})
t.Run("with succes and max runtime", func(t *testing.T) {
runner, emitter := newRunnerForTesting()
runner.settings.Inputs = []string{"a", "b", "c", "d"}
runner.settings.Options.MaxRuntime = 2
fake := fakeSuccessfulRun()
fake.MockableInputPolicy = func() engine.InputPolicy {
return engine.InputStrictlyRequired
}
fake.MockableMeasureWithContext = func(ctx context.Context, input string) (measurement *model.Measurement, err error) {
time.Sleep(1 * time.Second)
return &model.Measurement{}, nil
}
runner.sessionBuilder = fake
events := runAndCollect(runner, emitter)
reduced := reduceEventsKeysIgnoreLog(events)
expect := []eventKeyCount{
{Key: eventTypeStatusQueued, Count: 1},
{Key: eventTypeStatusStarted, Count: 1},
{Key: eventTypeStatusProgress, Count: 3},
{Key: eventTypeStatusGeoIPLookup, Count: 1},
{Key: eventTypeStatusResolverLookup, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeStatusReportCreate, Count: 1},
//
{Key: eventTypeStatusMeasurementStart, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeMeasurement, Count: 1},
{Key: eventTypeStatusMeasurementSubmission, Count: 1},
{Key: eventTypeStatusMeasurementDone, Count: 1},
//
{Key: eventTypeStatusMeasurementStart, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeMeasurement, Count: 1},
{Key: eventTypeStatusMeasurementSubmission, Count: 1},
{Key: eventTypeStatusMeasurementDone, Count: 1},
//
{Key: eventTypeStatusEnd, Count: 1},
}
assertReducedEventsLike(t, expect, reduced)
})
t.Run("with interrupted experiment", func(t *testing.T) {
runner, emitter := newRunnerForTesting()
runner.settings.Inputs = []string{"a", "b", "c", "d"}
runner.settings.Options.MaxRuntime = 2
fake := fakeSuccessfulRun()
fake.MockableInputPolicy = func() engine.InputPolicy {
return engine.InputStrictlyRequired
}
fake.MockableInterruptible = func() bool {
return true
}
ctx, cancel := context.WithCancel(context.Background())
fake.MockableMeasureWithContext = func(ctx context.Context, input string) (measurement *model.Measurement, err error) {
cancel()
return &model.Measurement{}, nil
}
runner.sessionBuilder = fake
events := runAndCollectContext(ctx, runner, emitter)
reduced := reduceEventsKeysIgnoreLog(events)
expect := []eventKeyCount{
{Key: eventTypeStatusQueued, Count: 1},
{Key: eventTypeStatusStarted, Count: 1},
{Key: eventTypeStatusProgress, Count: 3},
{Key: eventTypeStatusGeoIPLookup, Count: 1},
{Key: eventTypeStatusResolverLookup, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeStatusReportCreate, Count: 1},
//
{Key: eventTypeStatusMeasurementStart, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
//
{Key: eventTypeStatusEnd, Count: 1},
}
assertReducedEventsLike(t, expect, reduced)
})
t.Run("with measurement submission failure", func(t *testing.T) {
runner, emitter := newRunnerForTesting()
runner.settings.Inputs = []string{"a"}
fake := fakeSuccessfulRun()
fake.MockableInputPolicy = func() engine.InputPolicy {
return engine.InputStrictlyRequired
}
fake.MockableSubmitAndUpdateMeasurementContext = func(ctx context.Context, measurement *model.Measurement) error {
return errors.New("cannot submit")
}
runner.sessionBuilder = fake
events := runAndCollect(runner, emitter)
reduced := reduceEventsKeysIgnoreLog(events)
expect := []eventKeyCount{
{Key: eventTypeStatusQueued, Count: 1},
{Key: eventTypeStatusStarted, Count: 1},
{Key: eventTypeStatusProgress, Count: 3},
{Key: eventTypeStatusGeoIPLookup, Count: 1},
{Key: eventTypeStatusResolverLookup, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeStatusReportCreate, Count: 1},
//
{Key: eventTypeStatusMeasurementStart, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeMeasurement, Count: 1},
{Key: eventTypeFailureMeasurementSubmission, Count: 1},
{Key: eventTypeStatusMeasurementDone, Count: 1},
//
{Key: eventTypeStatusEnd, Count: 1},
}
assertReducedEventsLike(t, expect, reduced)
})
t.Run("with success and progress", func(t *testing.T) {
runner, emitter := newRunnerForTesting()
fake := fakeSuccessfulRun()
var callbacks model.ExperimentCallbacks
fake.MockableSetCallbacks = func(cbs model.ExperimentCallbacks) {
callbacks = cbs
}
fake.MockableMeasureWithContext = func(ctx context.Context, input string) (measurement *model.Measurement, err error) {
callbacks.OnProgress(1, "hello from the fake experiment")
return &model.Measurement{}, nil
}
runner.sessionBuilder = fake
events := runAndCollect(runner, emitter)
reduced := reduceEventsKeysIgnoreLog(events)
expect := []eventKeyCount{
{Key: eventTypeStatusQueued, Count: 1},
{Key: eventTypeStatusStarted, Count: 1},
{Key: eventTypeStatusProgress, Count: 3},
{Key: eventTypeStatusGeoIPLookup, Count: 1},
{Key: eventTypeStatusResolverLookup, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeStatusReportCreate, Count: 1},
{Key: eventTypeStatusMeasurementStart, Count: 1},
{Key: eventTypeStatusProgress, Count: 1},
{Key: eventTypeMeasurement, Count: 1},
{Key: eventTypeStatusMeasurementSubmission, Count: 1},
{Key: eventTypeStatusMeasurementDone, Count: 1},
{Key: eventTypeStatusEnd, Count: 1},
}
assertReducedEventsLike(t, expect, reduced)
})
}