ooni-probe-cli/internal/oonirun/v2_test.go
Simone Basso c420c8bb29
feat(miniooni): run local oonirun v2 descriptor (#966)
We introduce the -f, --input-file FILE option with which we
are able to run an OONI Run v2 descriptor stored locally.

In this running mode, there are no checks related to whether the
descriptor has changed, since we're dealing with a local file.

Closes https://github.com/ooni/probe/issues/2328
2022-09-29 11:43:23 +02:00

353 lines
9.1 KiB
Go

package oonirun
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/ooni/probe-cli/v3/internal/kvstore"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/model/mocks"
"github.com/ooni/probe-cli/v3/internal/runtimex"
)
func TestOONIRunV2LinkCommonCase(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
descriptor := &V2Descriptor{
Name: "",
Description: "",
Author: "",
Nettests: []V2Nettest{{
Inputs: []string{},
Options: map[string]any{
"SleepTime": int64(10 * time.Millisecond),
},
TestName: "example",
}},
}
data, err := json.Marshal(descriptor)
runtimex.PanicOnError(err, "json.Marshal failed")
w.Write(data)
}))
defer server.Close()
ctx := context.Background()
config := &LinkConfig{
AcceptChanges: true, // avoid "oonirun: need to accept changes" error
Annotations: map[string]string{
"platform": "linux",
},
KVStore: &kvstore.Memory{},
MaxRuntime: 0,
NoCollector: true,
NoJSON: true,
Random: false,
ReportFile: "",
Session: newMinimalFakeSession(),
}
r := NewLinkRunner(config, server.URL)
if err := r.Run(ctx); err != nil {
t.Fatal(err)
}
}
func TestOONIRunV2LinkCannotUpdateCache(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
descriptor := &V2Descriptor{
Name: "",
Description: "",
Author: "",
Nettests: []V2Nettest{{
Inputs: []string{},
Options: map[string]any{
"SleepTime": int64(10 * time.Millisecond),
},
TestName: "example",
}},
}
data, err := json.Marshal(descriptor)
runtimex.PanicOnError(err, "json.Marshal failed")
w.Write(data)
}))
defer server.Close()
ctx := context.Background()
expected := errors.New("mocked")
config := &LinkConfig{
AcceptChanges: true, // avoid "oonirun: need to accept changes" error
Annotations: map[string]string{
"platform": "linux",
},
KVStore: &mocks.KeyValueStore{
MockGet: func(key string) ([]byte, error) {
return []byte("{}"), nil
},
MockSet: func(key string, value []byte) error {
return expected
},
},
MaxRuntime: 0,
NoCollector: true,
NoJSON: true,
Random: false,
ReportFile: "",
Session: newMinimalFakeSession(),
}
r := NewLinkRunner(config, server.URL)
err := r.Run(ctx)
if !errors.Is(err, expected) {
t.Fatal("unexpected err", err)
}
}
func TestOONIRunV2LinkWithoutAcceptChanges(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
descriptor := &V2Descriptor{
Name: "",
Description: "",
Author: "",
Nettests: []V2Nettest{{
Inputs: []string{},
Options: map[string]any{
"SleepTime": int64(10 * time.Millisecond),
},
TestName: "example",
}},
}
data, err := json.Marshal(descriptor)
runtimex.PanicOnError(err, "json.Marshal failed")
w.Write(data)
}))
defer server.Close()
ctx := context.Background()
config := &LinkConfig{
AcceptChanges: false, // should see "oonirun: need to accept changes" error
Annotations: map[string]string{
"platform": "linux",
},
KVStore: &kvstore.Memory{},
MaxRuntime: 0,
NoCollector: true,
NoJSON: true,
Random: false,
ReportFile: "",
Session: newMinimalFakeSession(),
}
r := NewLinkRunner(config, server.URL)
err := r.Run(ctx)
if !errors.Is(err, ErrNeedToAcceptChanges) {
t.Fatal("unexpected err", err)
}
}
func TestOONIRunV2LinkNilDescriptor(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("null"))
}))
defer server.Close()
ctx := context.Background()
config := &LinkConfig{
AcceptChanges: true, // avoid "oonirun: need to accept changes" error
Annotations: map[string]string{
"platform": "linux",
},
KVStore: &kvstore.Memory{},
MaxRuntime: 0,
NoCollector: true,
NoJSON: true,
Random: false,
ReportFile: "",
Session: newMinimalFakeSession(),
}
r := NewLinkRunner(config, server.URL)
if err := r.Run(ctx); err != nil {
t.Fatal(err)
}
}
func TestOONIRunV2LinkEmptyTestName(t *testing.T) {
emptyTestNamesPrev := v2CountEmptyNettestNames.Load()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
descriptor := &V2Descriptor{
Name: "",
Description: "",
Author: "",
Nettests: []V2Nettest{{
Inputs: []string{},
Options: map[string]any{
"SleepTime": int64(10 * time.Millisecond),
},
TestName: "", // empty!
}},
}
data, err := json.Marshal(descriptor)
runtimex.PanicOnError(err, "json.Marshal failed")
w.Write(data)
}))
defer server.Close()
ctx := context.Background()
config := &LinkConfig{
AcceptChanges: true, // avoid "oonirun: need to accept changes" error
Annotations: map[string]string{
"platform": "linux",
},
KVStore: &kvstore.Memory{},
MaxRuntime: 0,
NoCollector: true,
NoJSON: true,
Random: false,
ReportFile: "",
Session: newMinimalFakeSession(),
}
r := NewLinkRunner(config, server.URL)
if err := r.Run(ctx); err != nil {
t.Fatal(err)
}
if v2CountEmptyNettestNames.Load() != emptyTestNamesPrev+1 {
t.Fatal("expected to see 1 more instance of empty nettest names")
}
}
func TestV2MeasureDescriptor(t *testing.T) {
t.Run("with nil descriptor", func(t *testing.T) {
ctx := context.Background()
config := &LinkConfig{}
err := V2MeasureDescriptor(ctx, config, nil)
if !errors.Is(err, ErrNilDescriptor) {
t.Fatal("unexpected err", err)
}
})
t.Run("with failing experiment", func(t *testing.T) {
previousFailedExperiments := v2CountFailedExperiments.Load()
expected := errors.New("mocked error")
ctx := context.Background()
sess := newMinimalFakeSession()
sess.MockNewSubmitter = func(ctx context.Context) (model.Submitter, error) {
subm := &mocks.Submitter{
MockSubmit: func(ctx context.Context, m *model.Measurement) error {
panic("should not be called")
},
}
return subm, nil
}
sess.MockNewExperimentBuilder = func(name string) (model.ExperimentBuilder, error) {
eb := &mocks.ExperimentBuilder{
MockInputPolicy: func() model.InputPolicy {
return model.InputNone
},
MockSetOptionsAny: func(options map[string]any) error {
return nil
},
MockNewExperiment: func() model.Experiment {
exp := &mocks.Experiment{
MockMeasureAsync: func(ctx context.Context, input string) (<-chan *model.Measurement, error) {
return nil, expected
},
MockKibiBytesReceived: func() float64 {
return 1.1
},
MockKibiBytesSent: func() float64 {
return 0.1
},
}
return exp
},
}
return eb, nil
}
config := &LinkConfig{
AcceptChanges: false,
Annotations: map[string]string{},
KVStore: nil,
MaxRuntime: 0,
NoCollector: false,
NoJSON: false,
Random: false,
ReportFile: "",
Session: sess,
}
descr := &V2Descriptor{
Name: "",
Description: "",
Author: "",
Nettests: []V2Nettest{{
Inputs: []string{},
Options: map[string]any{},
TestName: "example",
}},
}
err := V2MeasureDescriptor(ctx, config, descr)
if err != nil {
t.Fatal(err)
}
if v2CountFailedExperiments.Load() != previousFailedExperiments+1 {
t.Fatal("expected to see a failed experiment")
}
})
}
func TestV2MeasureHTTPS(t *testing.T) {
t.Run("when we cannot load from cache", func(t *testing.T) {
expected := errors.New("mocked error")
ctx := context.Background()
config := &LinkConfig{
AcceptChanges: false,
Annotations: map[string]string{},
KVStore: &mocks.KeyValueStore{
MockGet: func(key string) (value []byte, err error) {
return nil, expected
},
},
MaxRuntime: 0,
NoCollector: false,
NoJSON: false,
Random: false,
ReportFile: "",
Session: newMinimalFakeSession(),
}
err := v2MeasureHTTPS(ctx, config, "")
if !errors.Is(err, expected) {
t.Fatal("unexpected err", err)
}
})
t.Run("when we cannot pull changes", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
cancel() // fail immediately
config := &LinkConfig{
AcceptChanges: false,
Annotations: map[string]string{},
KVStore: &kvstore.Memory{},
MaxRuntime: 0,
NoCollector: false,
NoJSON: false,
Random: false,
ReportFile: "",
Session: newMinimalFakeSession(),
}
err := v2MeasureHTTPS(ctx, config, "https://example.com") // should not use URL
if !errors.Is(err, context.Canceled) {
t.Fatal("unexpected err", err)
}
})
}
func TestV2DescriptorCacheLoad(t *testing.T) {
t.Run("cannot unmarshal cache content", func(t *testing.T) {
fsstore := &kvstore.Memory{}
if err := fsstore.Set(v2DescriptorCacheKey, []byte("{")); err != nil {
t.Fatal(err)
}
cache, err := v2DescriptorCacheLoad(fsstore)
if err == nil || err.Error() != "unexpected end of JSON input" {
t.Fatal("unexpected err", err)
}
if cache != nil {
t.Fatal("expected nil cache")
}
})
}