807 lines
28 KiB
Go
807 lines
28 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() model.InputPolicy {
|
|
return model.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() model.InputPolicy {
|
|
return model.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() model.InputPolicy {
|
|
return model.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() model.InputPolicy {
|
|
return model.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() model.InputPolicy {
|
|
return model.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() model.InputPolicy {
|
|
return model.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() model.InputPolicy {
|
|
return model.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 measurement failure and annotations", func(t *testing.T) {
|
|
// See https://github.com/ooni/probe/issues/2173. We want to be sure that
|
|
// we are not crashing when the measurement fails and there are annotations,
|
|
// which is what was happening in the above referenced issue.
|
|
runner, emitter := newRunnerForTesting()
|
|
fake := fakeSuccessfulRun()
|
|
fake.MockableInputPolicy = func() model.InputPolicy {
|
|
return model.InputNone
|
|
}
|
|
fake.MockableMeasureWithContext = func(ctx context.Context, input string) (measurement *model.Measurement, err error) {
|
|
return nil, errors.New("preconditions error")
|
|
}
|
|
runner.sessionBuilder = fake
|
|
runner.settings.Annotations = map[string]string{
|
|
"architecture": "arm64",
|
|
}
|
|
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() model.InputPolicy {
|
|
return model.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() model.InputPolicy {
|
|
return model.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() model.InputPolicy {
|
|
return model.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() model.InputPolicy {
|
|
return model.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() model.InputPolicy {
|
|
return model.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() model.InputPolicy {
|
|
return model.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() model.InputPolicy {
|
|
return model.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)
|
|
})
|
|
}
|