refactor(engine): more abstract Experiment{,Builder} (#838)
This diff modifies the engine package to make Experiment and
ExperimentBuilder interfaces rather than structs.
The previosuly existing structs are now named experiment{,Builder}.
This diff helps https://github.com/ooni/probe/issues/2184
because it allows us to write unit tests more easily.
There should be no functional change.
While there, I removed a bunch of deprecated functions, which were
unnecessarily complicate the implementation and could be easily
replaced by passing them a context.Context or context.Background().
This commit is contained in:
parent
5b27df1a37
commit
97864b324f
11 changed files with 325 additions and 283 deletions
|
|
@ -1,5 +1,9 @@
|
|||
package engine
|
||||
|
||||
//
|
||||
// ExperimentBuilder definition and implementation
|
||||
//
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
|
@ -42,23 +46,60 @@ const (
|
|||
InputOrStaticDefault = InputPolicy("or_static_default")
|
||||
)
|
||||
|
||||
// ExperimentBuilder is an experiment builder.
|
||||
type ExperimentBuilder struct {
|
||||
build func(interface{}) *Experiment
|
||||
callbacks model.ExperimentCallbacks
|
||||
config interface{}
|
||||
inputPolicy InputPolicy
|
||||
// ExperimentBuilder builds an experiment.
|
||||
type ExperimentBuilder interface {
|
||||
// Interruptible tells you whether this is an interruptible experiment. This kind
|
||||
// of experiments (e.g. ndt7) may be interrupted mid way.
|
||||
Interruptible() bool
|
||||
|
||||
// InputPolicy returns the experiment input policy.
|
||||
InputPolicy() InputPolicy
|
||||
|
||||
// Options returns information about the experiment's options.
|
||||
Options() (map[string]OptionInfo, error)
|
||||
|
||||
// SetOptionAny sets an option whose value is an any value. We will use reasonable
|
||||
// heuristics to convert the any value to the proper type of the field whose name is
|
||||
// contained by the key variable. If we cannot convert the provided any value to
|
||||
// the proper type, then this function returns an error.
|
||||
SetOptionAny(key string, value any) error
|
||||
|
||||
// SetOptionsAny sets options from a map[string]any. See the documentation of
|
||||
// the SetOptionAny method for more information.
|
||||
SetOptionsAny(options map[string]any) error
|
||||
|
||||
// SetCallbacks sets the experiment's interactive callbacks.
|
||||
SetCallbacks(callbacks model.ExperimentCallbacks)
|
||||
|
||||
// NewExperiment creates the experiment instance.
|
||||
NewExperiment() Experiment
|
||||
}
|
||||
|
||||
// experimentBuilder implements ExperimentBuilder.
|
||||
type experimentBuilder struct {
|
||||
// build is the constructor that build an experiment with the given config.
|
||||
build func(config interface{}) *experiment
|
||||
|
||||
// callbacks contains callbacks for the new experiment.
|
||||
callbacks model.ExperimentCallbacks
|
||||
|
||||
// config contains the experiment's config.
|
||||
config interface{}
|
||||
|
||||
// inputPolicy contains the experiment's InputPolicy.
|
||||
inputPolicy InputPolicy
|
||||
|
||||
// interruptible indicates whether the experiment is interruptible.
|
||||
interruptible bool
|
||||
}
|
||||
|
||||
// Interruptible tells you whether this is an interruptible experiment. This kind
|
||||
// of experiments (e.g. ndt7) may be interrupted mid way.
|
||||
func (b *ExperimentBuilder) Interruptible() bool {
|
||||
// Interruptible implements ExperimentBuilder.Interruptible.
|
||||
func (b *experimentBuilder) Interruptible() bool {
|
||||
return b.interruptible
|
||||
}
|
||||
|
||||
// InputPolicy returns the experiment input policy
|
||||
func (b *ExperimentBuilder) InputPolicy() InputPolicy {
|
||||
// InputPolicy implements ExperimentBuilder.InputPolicy.
|
||||
func (b *experimentBuilder) InputPolicy() InputPolicy {
|
||||
return b.inputPolicy
|
||||
}
|
||||
|
||||
|
|
@ -96,8 +137,8 @@ var (
|
|||
ErrUnsupportedOptionType = errors.New("unsupported option type")
|
||||
)
|
||||
|
||||
// Options returns info about all options
|
||||
func (b *ExperimentBuilder) Options() (map[string]OptionInfo, error) {
|
||||
// Options implements ExperimentBuilder.Options.
|
||||
func (b *experimentBuilder) Options() (map[string]OptionInfo, error) {
|
||||
result := make(map[string]OptionInfo)
|
||||
ptrinfo := reflect.ValueOf(b.config)
|
||||
if ptrinfo.Kind() != reflect.Ptr {
|
||||
|
|
@ -118,7 +159,7 @@ func (b *ExperimentBuilder) Options() (map[string]OptionInfo, error) {
|
|||
}
|
||||
|
||||
// setOptionBool sets a bool option.
|
||||
func (b *ExperimentBuilder) setOptionBool(field reflect.Value, value any) error {
|
||||
func (b *experimentBuilder) setOptionBool(field reflect.Value, value any) error {
|
||||
switch v := value.(type) {
|
||||
case bool:
|
||||
field.SetBool(v)
|
||||
|
|
@ -135,7 +176,7 @@ func (b *ExperimentBuilder) setOptionBool(field reflect.Value, value any) error
|
|||
}
|
||||
|
||||
// setOptionInt sets an int option
|
||||
func (b *ExperimentBuilder) setOptionInt(field reflect.Value, value any) error {
|
||||
func (b *experimentBuilder) setOptionInt(field reflect.Value, value any) error {
|
||||
switch v := value.(type) {
|
||||
case int64:
|
||||
field.SetInt(v)
|
||||
|
|
@ -165,7 +206,7 @@ func (b *ExperimentBuilder) setOptionInt(field reflect.Value, value any) error {
|
|||
}
|
||||
|
||||
// setOptionString sets a string option
|
||||
func (b *ExperimentBuilder) setOptionString(field reflect.Value, value any) error {
|
||||
func (b *experimentBuilder) setOptionString(field reflect.Value, value any) error {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
field.SetString(v)
|
||||
|
|
@ -175,11 +216,8 @@ func (b *ExperimentBuilder) setOptionString(field reflect.Value, value any) erro
|
|||
}
|
||||
}
|
||||
|
||||
// SetOptionAny sets an option whose value is an any value. We will use reasonable
|
||||
// heuristics to convert the any value to the proper type of the field whose name is
|
||||
// contained by the key variable. If we cannot convert the provided any value to
|
||||
// the proper type, then this function returns an error.
|
||||
func (b *ExperimentBuilder) SetOptionAny(key string, value any) error {
|
||||
// SetOptionAny implements ExperimentBuilder.SetOptionAny.
|
||||
func (b *experimentBuilder) SetOptionAny(key string, value any) error {
|
||||
field, err := b.fieldbyname(b.config, key)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -196,9 +234,8 @@ func (b *ExperimentBuilder) SetOptionAny(key string, value any) error {
|
|||
}
|
||||
}
|
||||
|
||||
// SetOptionsAny sets options from a map[string]any. See the documentation of
|
||||
// the SetOptionAny function for more information.
|
||||
func (b *ExperimentBuilder) SetOptionsAny(options map[string]any) error {
|
||||
// SetOptionsAny implements ExperimentBuilder.SetOptionsAny.
|
||||
func (b *experimentBuilder) SetOptionsAny(options map[string]any) error {
|
||||
for key, value := range options {
|
||||
if err := b.SetOptionAny(key, value); err != nil {
|
||||
return err
|
||||
|
|
@ -207,12 +244,13 @@ func (b *ExperimentBuilder) SetOptionsAny(options map[string]any) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// SetCallbacks sets the interactive callbacks
|
||||
func (b *ExperimentBuilder) SetCallbacks(callbacks model.ExperimentCallbacks) {
|
||||
// SetCallbacks implements ExperimentBuilder.SetCallbacks.
|
||||
func (b *experimentBuilder) SetCallbacks(callbacks model.ExperimentCallbacks) {
|
||||
b.callbacks = callbacks
|
||||
}
|
||||
|
||||
func (b *ExperimentBuilder) fieldbyname(v interface{}, key string) (reflect.Value, error) {
|
||||
// fieldbyname return v's field whose name is equal to the given key.
|
||||
func (b *experimentBuilder) fieldbyname(v interface{}, key string) (reflect.Value, error) {
|
||||
// See https://stackoverflow.com/a/6396678/4354461
|
||||
ptrinfo := reflect.ValueOf(v)
|
||||
if ptrinfo.Kind() != reflect.Ptr {
|
||||
|
|
@ -230,7 +268,7 @@ func (b *ExperimentBuilder) fieldbyname(v interface{}, key string) (reflect.Valu
|
|||
}
|
||||
|
||||
// NewExperiment creates the experiment
|
||||
func (b *ExperimentBuilder) NewExperiment() *Experiment {
|
||||
func (b *experimentBuilder) NewExperiment() Experiment {
|
||||
experiment := b.build(b.config)
|
||||
experiment.callbacks = b.callbacks
|
||||
return experiment
|
||||
|
|
@ -255,7 +293,8 @@ func canonicalizeExperimentName(name string) string {
|
|||
return name
|
||||
}
|
||||
|
||||
func newExperimentBuilder(session *Session, name string) (*ExperimentBuilder, error) {
|
||||
// newExperimentBuilder creates a new experimentBuilder instance.
|
||||
func newExperimentBuilder(session *Session, name string) (*experimentBuilder, error) {
|
||||
factory := experimentsByName[canonicalizeExperimentName(name)]
|
||||
if factory == nil {
|
||||
return nil, fmt.Errorf("no such experiment: %s", name)
|
||||
|
|
|
|||
Loading…
Reference in a new issue