c420c8bb29
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
353 lines
9.1 KiB
Go
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")
|
|
}
|
|
})
|
|
}
|