Skip to content

Commit

Permalink
chore: add more tests for new code I wrote
Browse files Browse the repository at this point in the history
  • Loading branch information
bassosimone committed Jun 27, 2024
1 parent 4f183bd commit 50d1909
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 4 deletions.
6 changes: 6 additions & 0 deletions internal/engine/experimentbuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ import (
"github.com/ooni/probe-cli/v3/internal/registry"
)

// TODO(bassosimone,DecFox): we should eventually finish merging the code in
// file with the code inside the ./internal/registry package.
//
// If there's time, this could happen at the end of the current (as of 2024-06-27)
// richer input work, otherwise any time in the future is actually fine.

// experimentBuilder implements [model.ExperimentBuilder].
//
// This type is now just a tiny wrapper around registry.Factory.
Expand Down
118 changes: 118 additions & 0 deletions internal/engine/experimentbuilder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package engine

import (
"context"
"encoding/json"
"errors"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/ooni/probe-cli/v3/internal/model"
)

Expand Down Expand Up @@ -49,3 +51,119 @@ func TestExperimentBuilderEngineWebConnectivity(t *testing.T) {
t.Fatal("expected zero length targets")
}
}

func TestExperimentBuilderBasicOperations(t *testing.T) {
// create a session for testing that does not use the network at all
sess := newSessionForTestingNoLookups(t)

// create an experiment builder for example
builder, err := sess.NewExperimentBuilder("example")
if err != nil {
t.Fatal(err)
}

// example should be interruptible
t.Run("Interruptible", func(t *testing.T) {
if !builder.Interruptible() {
t.Fatal("example should be interruptible")
}
})

// we expect to see the InputNone input policy
t.Run("InputPolicy", func(t *testing.T) {
if builder.InputPolicy() != model.InputNone {
t.Fatal("unexpectyed input policy")
}
})

// get the options and check whether they are what we expect
t.Run("Options", func(t *testing.T) {
options, err := builder.Options()
if err != nil {
t.Fatal(err)
}
expectOptions := map[string]model.ExperimentOptionInfo{
"Message": {Doc: "Message to emit at test completion", Type: "string", Value: "Good day from the example experiment!"},
"ReturnError": {Doc: "Toogle to return a mocked error", Type: "bool", Value: false},
"SleepTime": {Doc: "Amount of time to sleep for in nanosecond", Type: "int64", Value: int64(1000000000)},
}
if diff := cmp.Diff(expectOptions, options); diff != "" {
t.Fatal(diff)
}
})

// we can set a specific existing option
t.Run("SetOptionAny", func(t *testing.T) {
if err := builder.SetOptionAny("Message", "foobar"); err != nil {
t.Fatal(err)
}
options, err := builder.Options()
if err != nil {
t.Fatal(err)
}
expectOptions := map[string]model.ExperimentOptionInfo{
"Message": {Doc: "Message to emit at test completion", Type: "string", Value: "foobar"},
"ReturnError": {Doc: "Toogle to return a mocked error", Type: "bool", Value: false},
"SleepTime": {Doc: "Amount of time to sleep for in nanosecond", Type: "int64", Value: int64(1000000000)},
}
if diff := cmp.Diff(expectOptions, options); diff != "" {
t.Fatal(diff)
}
})

// we can set all options at the same time
t.Run("SetOptions", func(t *testing.T) {
inputs := map[string]any{
"Message": "foobar",
"ReturnError": true,
}
if err := builder.SetOptionsAny(inputs); err != nil {
t.Fatal(err)
}
options, err := builder.Options()
if err != nil {
t.Fatal(err)
}
expectOptions := map[string]model.ExperimentOptionInfo{
"Message": {Doc: "Message to emit at test completion", Type: "string", Value: "foobar"},
"ReturnError": {Doc: "Toogle to return a mocked error", Type: "bool", Value: true},
"SleepTime": {Doc: "Amount of time to sleep for in nanosecond", Type: "int64", Value: int64(1000000000)},
}
if diff := cmp.Diff(expectOptions, options); diff != "" {
t.Fatal(diff)
}
})

// we can set all options using JSON
t.Run("SetOptionsJSON", func(t *testing.T) {
inputs := json.RawMessage(`{
"Message": "foobar",
"ReturnError": true
}`)
if err := builder.SetOptionsJSON(inputs); err != nil {
t.Fatal(err)
}
options, err := builder.Options()
if err != nil {
t.Fatal(err)
}
expectOptions := map[string]model.ExperimentOptionInfo{
"Message": {Doc: "Message to emit at test completion", Type: "string", Value: "foobar"},
"ReturnError": {Doc: "Toogle to return a mocked error", Type: "bool", Value: true},
"SleepTime": {Doc: "Amount of time to sleep for in nanosecond", Type: "int64", Value: int64(1000000000)},
}
if diff := cmp.Diff(expectOptions, options); diff != "" {
t.Fatal(diff)
}
})

// TODO(bassosimone): we could possibly add more checks here. I am not doing this
// right now, because https://github.com/ooni/probe-cli/pull/1629 mostly cares about
// providing input and the rest of the codebase did not change.
//
// Also, it would make sense to eventually merge experimentbuilder.go with the
// ./internal/registry package, which also has coverage.
//
// In conclusion, our main objective for now is to make sure we don't screw the
// pooch when setting options using the experiment builder.
}
3 changes: 3 additions & 0 deletions internal/model/experiment.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,9 @@ type ExperimentOptionInfo struct {

// Type contains the type.
Type string

// Value contains the current option value.
Value any
}

// ExperimentTargetLoader loads targets from local or remote sources.
Expand Down
8 changes: 5 additions & 3 deletions internal/registry/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,17 @@ func (b *Factory) Options() (map[string]model.ExperimentOptionInfo, error) {
if ptrinfo.Kind() != reflect.Ptr {
return nil, ErrConfigIsNotAStructPointer
}
structinfo := ptrinfo.Elem().Type()
valueinfo := ptrinfo.Elem()
structinfo := valueinfo.Type()
if structinfo.Kind() != reflect.Struct {
return nil, ErrConfigIsNotAStructPointer
}
for i := 0; i < structinfo.NumField(); i++ {
field := structinfo.Field(i)
result[field.Name] = model.ExperimentOptionInfo{
Doc: field.Tag.Get("ooni"),
Type: field.Type.String(),
Doc: field.Tag.Get("ooni"),
Type: field.Type.String(),
Value: valueinfo.Field(i).Interface(),
}
}
return result, nil
Expand Down
24 changes: 23 additions & 1 deletion internal/registry/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,20 @@ func TestExperimentBuilderOptions(t *testing.T) {
})

t.Run("when config is a pointer to struct", func(t *testing.T) {
config := &fakeExperimentConfig{}
config := &fakeExperimentConfig{
Chan: make(chan any),
String: "foobar",
Truth: true,
Value: 177114,
}
b := &Factory{
config: config,
}
options, err := b.Options()
if err != nil {
t.Fatal(err)
}

for name, value := range options {
switch name {
case "Chan":
Expand All @@ -72,27 +78,43 @@ func TestExperimentBuilderOptions(t *testing.T) {
if value.Type != "chan interface {}" {
t.Fatal("invalid type", value.Type)
}
if value.Value.(chan any) == nil {
t.Fatal("expected non-nil channel here")
}

case "String":
if value.Doc != "a string" {
t.Fatal("invalid doc")
}
if value.Type != "string" {
t.Fatal("invalid type", value.Type)
}
if v := value.Value.(string); v != "foobar" {
t.Fatal("unexpected string value", v)
}

case "Truth":
if value.Doc != "something that no-one knows" {
t.Fatal("invalid doc")
}
if value.Type != "bool" {
t.Fatal("invalid type", value.Type)
}
if v := value.Value.(bool); !v {
t.Fatal("unexpected bool value", v)
}

case "Value":
if value.Doc != "a number" {
t.Fatal("invalid doc")
}
if value.Type != "int64" {
t.Fatal("invalid type", value.Type)
}
if v := value.Value.(int64); v != 177114 {
t.Fatal("unexpected int64 value", v)
}

default:
t.Fatal("unknown name", name)
}
Expand Down

0 comments on commit 50d1909

Please sign in to comment.