441 lines
11 KiB
Go
441 lines
11 KiB
Go
|
package oonimkall_test
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io/ioutil"
|
||
|
"os"
|
||
|
"runtime"
|
||
|
"strings"
|
||
|
"testing"
|
||
|
"time"
|
||
|
|
||
|
engine "github.com/ooni/probe-cli/v3/internal/engine"
|
||
|
"github.com/ooni/probe-cli/v3/internal/engine/geolocate"
|
||
|
"github.com/ooni/probe-cli/v3/internal/engine/model"
|
||
|
"github.com/ooni/probe-cli/v3/internal/engine/oonimkall"
|
||
|
)
|
||
|
|
||
|
func NewSessionWithAssetsDir(assetsDir string) (*oonimkall.Session, error) {
|
||
|
return oonimkall.NewSession(&oonimkall.SessionConfig{
|
||
|
AssetsDir: assetsDir,
|
||
|
ProbeServicesURL: "https://ams-pg-test.ooni.org/",
|
||
|
SoftwareName: "oonimkall-test",
|
||
|
SoftwareVersion: "0.1.0",
|
||
|
StateDir: "../testdata/oonimkall/state",
|
||
|
TempDir: "../testdata/",
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func NewSession() (*oonimkall.Session, error) {
|
||
|
return NewSessionWithAssetsDir("../testdata/oonimkall/assets")
|
||
|
}
|
||
|
|
||
|
func TestNewSessionWithInvalidStateDir(t *testing.T) {
|
||
|
if testing.Short() {
|
||
|
t.Skip("skip test in short mode")
|
||
|
}
|
||
|
sess, err := oonimkall.NewSession(&oonimkall.SessionConfig{
|
||
|
StateDir: "",
|
||
|
})
|
||
|
if err == nil || !strings.HasSuffix(err.Error(), "no such file or directory") {
|
||
|
t.Fatal("not the error we expected")
|
||
|
}
|
||
|
if sess != nil {
|
||
|
t.Fatal("expected a nil Session here")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestNewSessionWithMissingSoftwareName(t *testing.T) {
|
||
|
if testing.Short() {
|
||
|
t.Skip("skip test in short mode")
|
||
|
}
|
||
|
sess, err := oonimkall.NewSession(&oonimkall.SessionConfig{
|
||
|
StateDir: "../testdata/oonimkall/state",
|
||
|
})
|
||
|
if err == nil || err.Error() != "AssetsDir is empty" {
|
||
|
t.Fatal("not the error we expected")
|
||
|
}
|
||
|
if sess != nil {
|
||
|
t.Fatal("expected a nil Session here")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestMaybeUpdateResourcesWithCancelledContext(t *testing.T) {
|
||
|
if testing.Short() {
|
||
|
t.Skip("skip test in short mode")
|
||
|
}
|
||
|
dir, err := ioutil.TempDir("", "xx")
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
defer os.RemoveAll(dir)
|
||
|
sess, err := NewSessionWithAssetsDir(dir)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
ctx := sess.NewContext()
|
||
|
ctx.Cancel() // cause immediate failure
|
||
|
err = sess.MaybeUpdateResources(ctx)
|
||
|
if !errors.Is(err, context.Canceled) {
|
||
|
t.Fatalf("not the error we expected: %+v", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func ReduceErrorForGeolocate(err error) error {
|
||
|
if err == nil {
|
||
|
return errors.New("we expected an error here")
|
||
|
}
|
||
|
if errors.Is(err, context.Canceled) {
|
||
|
return nil // when we have not downloaded the resources yet
|
||
|
}
|
||
|
if !errors.Is(err, geolocate.ErrAllIPLookuppersFailed) {
|
||
|
return nil // otherwise
|
||
|
}
|
||
|
return fmt.Errorf("not the error we expected: %w", err)
|
||
|
}
|
||
|
|
||
|
func TestGeolocateWithCancelledContext(t *testing.T) {
|
||
|
if testing.Short() {
|
||
|
t.Skip("skip test in short mode")
|
||
|
}
|
||
|
sess, err := NewSession()
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
ctx := sess.NewContext()
|
||
|
ctx.Cancel() // cause immediate failure
|
||
|
location, err := sess.Geolocate(ctx)
|
||
|
if err := ReduceErrorForGeolocate(err); err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if location != nil {
|
||
|
t.Fatal("expected nil location here")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestGeolocateGood(t *testing.T) {
|
||
|
if testing.Short() {
|
||
|
t.Skip("skip test in short mode")
|
||
|
}
|
||
|
sess, err := NewSession()
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
ctx := sess.NewContext()
|
||
|
location, err := sess.Geolocate(ctx)
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
if location.ASN == "" {
|
||
|
t.Fatal("location.ASN is empty")
|
||
|
}
|
||
|
if location.Country == "" {
|
||
|
t.Fatal("location.Country is empty")
|
||
|
}
|
||
|
if location.IP == "" {
|
||
|
t.Fatal("location.IP is empty")
|
||
|
}
|
||
|
if location.Org == "" {
|
||
|
t.Fatal("location.Org is empty")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func ReduceErrorForSubmitter(err error) error {
|
||
|
if err == nil {
|
||
|
return errors.New("we expected an error here")
|
||
|
}
|
||
|
if errors.Is(err, context.Canceled) {
|
||
|
return nil // when we have not downloaded the resources yet
|
||
|
}
|
||
|
if err.Error() == "all available probe services failed" {
|
||
|
return nil // otherwise
|
||
|
}
|
||
|
return fmt.Errorf("not the error we expected: %w", err)
|
||
|
}
|
||
|
|
||
|
func TestSubmitWithCancelledContext(t *testing.T) {
|
||
|
if testing.Short() {
|
||
|
t.Skip("skip test in short mode")
|
||
|
}
|
||
|
sess, err := NewSession()
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
ctx := sess.NewContext()
|
||
|
ctx.Cancel() // cause immediate failure
|
||
|
result, err := sess.Submit(ctx, "{}")
|
||
|
if err := ReduceErrorForSubmitter(err); err != nil {
|
||
|
t.Fatalf("not the error we expected: %+v", err)
|
||
|
}
|
||
|
if result != nil {
|
||
|
t.Fatal("expected nil result here")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestSubmitWithInvalidJSON(t *testing.T) {
|
||
|
if testing.Short() {
|
||
|
t.Skip("skip test in short mode")
|
||
|
}
|
||
|
sess, err := NewSession()
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
ctx := sess.NewContext()
|
||
|
result, err := sess.Submit(ctx, "{")
|
||
|
if err == nil || err.Error() != "unexpected end of JSON input" {
|
||
|
t.Fatalf("not the error we expected: %+v", err)
|
||
|
}
|
||
|
if result != nil {
|
||
|
t.Fatal("expected nil result here")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func DoSubmission(ctx *oonimkall.Context, sess *oonimkall.Session) error {
|
||
|
inputm := model.Measurement{
|
||
|
DataFormatVersion: "0.2.0",
|
||
|
MeasurementStartTime: "2019-10-28 12:51:07",
|
||
|
MeasurementRuntime: 1.71,
|
||
|
ProbeASN: "AS30722",
|
||
|
ProbeCC: "IT",
|
||
|
ProbeIP: "127.0.0.1",
|
||
|
ReportID: "",
|
||
|
ResolverIP: "172.217.33.129",
|
||
|
SoftwareName: "miniooni",
|
||
|
SoftwareVersion: "0.1.0-dev",
|
||
|
TestKeys: map[string]bool{"success": true},
|
||
|
TestName: "example",
|
||
|
TestVersion: "0.1.0",
|
||
|
}
|
||
|
inputd, err := json.Marshal(inputm)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
result, err := sess.Submit(ctx, string(inputd))
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("session_test.go: submit failed: %w", err)
|
||
|
}
|
||
|
if result.UpdatedMeasurement == "" {
|
||
|
return errors.New("expected non empty measurement")
|
||
|
}
|
||
|
if result.UpdatedReportID == "" {
|
||
|
return errors.New("expected non empty report ID")
|
||
|
}
|
||
|
var outputm model.Measurement
|
||
|
return json.Unmarshal([]byte(result.UpdatedMeasurement), &outputm)
|
||
|
}
|
||
|
|
||
|
func TestSubmitMeasurementGood(t *testing.T) {
|
||
|
if testing.Short() {
|
||
|
t.Skip("skip test in short mode")
|
||
|
}
|
||
|
sess, err := NewSession()
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
ctx := sess.NewContext()
|
||
|
if err := DoSubmission(ctx, sess); err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestSubmitCancelContextAfterFirstSubmission(t *testing.T) {
|
||
|
if testing.Short() {
|
||
|
t.Skip("skip test in short mode")
|
||
|
}
|
||
|
sess, err := NewSession()
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
ctx := sess.NewContext()
|
||
|
if err := DoSubmission(ctx, sess); err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
ctx.Cancel() // fail second submission
|
||
|
err = DoSubmission(ctx, sess)
|
||
|
if err == nil || !strings.HasPrefix(err.Error(), "session_test.go: submit failed") {
|
||
|
t.Fatalf("not the error we expected: %+v", err)
|
||
|
}
|
||
|
if !errors.Is(err, context.Canceled) {
|
||
|
t.Fatalf("not the error we expected: %+v", err)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCheckInSuccess(t *testing.T) {
|
||
|
sess, err := NewSession()
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
ctx := sess.NewContext()
|
||
|
config := oonimkall.CheckInConfig{
|
||
|
Charging: true,
|
||
|
OnWiFi: true,
|
||
|
Platform: "android",
|
||
|
RunType: "timed",
|
||
|
SoftwareName: "ooniprobe-android",
|
||
|
SoftwareVersion: "2.7.1",
|
||
|
WebConnectivity: &oonimkall.CheckInConfigWebConnectivity{},
|
||
|
}
|
||
|
config.WebConnectivity.Add("NEWS")
|
||
|
config.WebConnectivity.Add("CULTR")
|
||
|
result, err := sess.CheckIn(ctx, &config)
|
||
|
if err != nil {
|
||
|
t.Fatalf("unexpected error: %+v", err)
|
||
|
}
|
||
|
if result == nil || result.WebConnectivity == nil {
|
||
|
t.Fatal("got nil result or WebConnectivity")
|
||
|
}
|
||
|
if len(result.WebConnectivity.URLs) < 1 {
|
||
|
t.Fatal("unexpected number of URLs")
|
||
|
}
|
||
|
if result.WebConnectivity.ReportID == "" {
|
||
|
t.Fatal("got empty report ID")
|
||
|
}
|
||
|
siz := result.WebConnectivity.Size()
|
||
|
if siz <= 0 {
|
||
|
t.Fatal("unexpected number of URLs")
|
||
|
}
|
||
|
for idx := int64(0); idx < siz; idx++ {
|
||
|
entry := result.WebConnectivity.At(idx)
|
||
|
if entry.CategoryCode != "NEWS" && entry.CategoryCode != "CULTR" {
|
||
|
t.Fatalf("unexpected category code: %+v", entry)
|
||
|
}
|
||
|
}
|
||
|
if result.WebConnectivity.At(-1) != nil {
|
||
|
t.Fatal("expected nil here")
|
||
|
}
|
||
|
if result.WebConnectivity.At(siz) != nil {
|
||
|
t.Fatal("expected nil here")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCheckInLookupLocationFailure(t *testing.T) {
|
||
|
sess, err := NewSession()
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
ctx := sess.NewContext()
|
||
|
config := oonimkall.CheckInConfig{
|
||
|
Charging: true,
|
||
|
OnWiFi: true,
|
||
|
Platform: "android",
|
||
|
RunType: "timed",
|
||
|
SoftwareName: "ooniprobe-android",
|
||
|
SoftwareVersion: "2.7.1",
|
||
|
WebConnectivity: &oonimkall.CheckInConfigWebConnectivity{},
|
||
|
}
|
||
|
config.WebConnectivity.Add("NEWS")
|
||
|
config.WebConnectivity.Add("CULTR")
|
||
|
ctx.Cancel() // immediate failure
|
||
|
result, err := sess.CheckIn(ctx, &config)
|
||
|
if !errors.Is(err, geolocate.ErrAllIPLookuppersFailed) {
|
||
|
t.Fatalf("not the error we expected: %+v", err)
|
||
|
}
|
||
|
if result != nil {
|
||
|
t.Fatal("expected nil result here")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCheckInNewProbeServicesFailure(t *testing.T) {
|
||
|
sess, err := NewSession()
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
sess.TestingCheckInBeforeNewProbeServicesClient = func(ctx *oonimkall.Context) {
|
||
|
ctx.Cancel() // cancel execution
|
||
|
}
|
||
|
ctx := sess.NewContext()
|
||
|
config := oonimkall.CheckInConfig{
|
||
|
Charging: true,
|
||
|
OnWiFi: true,
|
||
|
Platform: "android",
|
||
|
RunType: "timed",
|
||
|
SoftwareName: "ooniprobe-android",
|
||
|
SoftwareVersion: "2.7.1",
|
||
|
WebConnectivity: &oonimkall.CheckInConfigWebConnectivity{},
|
||
|
}
|
||
|
config.WebConnectivity.Add("NEWS")
|
||
|
config.WebConnectivity.Add("CULTR")
|
||
|
result, err := sess.CheckIn(ctx, &config)
|
||
|
if !errors.Is(err, engine.ErrAllProbeServicesFailed) {
|
||
|
t.Fatalf("not the error we expected: %+v", err)
|
||
|
}
|
||
|
if result != nil {
|
||
|
t.Fatal("expected nil result here")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCheckInCheckInFailure(t *testing.T) {
|
||
|
sess, err := NewSession()
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
sess.TestingCheckInBeforeCheckIn = func(ctx *oonimkall.Context) {
|
||
|
ctx.Cancel() // cancel execution
|
||
|
}
|
||
|
ctx := sess.NewContext()
|
||
|
config := oonimkall.CheckInConfig{
|
||
|
Charging: true,
|
||
|
OnWiFi: true,
|
||
|
Platform: "android",
|
||
|
RunType: "timed",
|
||
|
SoftwareName: "ooniprobe-android",
|
||
|
SoftwareVersion: "2.7.1",
|
||
|
WebConnectivity: &oonimkall.CheckInConfigWebConnectivity{},
|
||
|
}
|
||
|
config.WebConnectivity.Add("NEWS")
|
||
|
config.WebConnectivity.Add("CULTR")
|
||
|
result, err := sess.CheckIn(ctx, &config)
|
||
|
if !errors.Is(err, context.Canceled) {
|
||
|
t.Fatalf("not the error we expected: %+v", err)
|
||
|
}
|
||
|
if result != nil {
|
||
|
t.Fatal("expected nil result here")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestCheckInNoParams(t *testing.T) {
|
||
|
sess, err := NewSession()
|
||
|
if err != nil {
|
||
|
t.Fatal(err)
|
||
|
}
|
||
|
ctx := sess.NewContext()
|
||
|
config := oonimkall.CheckInConfig{
|
||
|
Charging: true,
|
||
|
OnWiFi: true,
|
||
|
Platform: "android",
|
||
|
RunType: "timed",
|
||
|
SoftwareName: "ooniprobe-android",
|
||
|
SoftwareVersion: "2.7.1",
|
||
|
}
|
||
|
result, err := sess.CheckIn(ctx, &config)
|
||
|
if err == nil || err.Error() != "oonimkall: missing webconnectivity config" {
|
||
|
t.Fatalf("not the error we expected: %+v", err)
|
||
|
}
|
||
|
if result != nil {
|
||
|
t.Fatal("unexpected not nil result here")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func TestMain(m *testing.M) {
|
||
|
// Here we're basically testing whether eventually the finalizers
|
||
|
// will run and the number of active sessions and cancels will become
|
||
|
// balanced. Especially for the number of active cancels, this is an
|
||
|
// indication that we've correctly cleaned them up in the session.
|
||
|
if exitcode := m.Run(); exitcode != 0 {
|
||
|
os.Exit(exitcode)
|
||
|
}
|
||
|
for {
|
||
|
runtime.GC()
|
||
|
m, n := oonimkall.ActiveContexts.Load(), oonimkall.ActiveSessions.Load()
|
||
|
fmt.Printf("./oonimkall: ActiveContexts: %d; ActiveSessions: %d\n", m, n)
|
||
|
if m == 0 && n == 0 {
|
||
|
break
|
||
|
}
|
||
|
time.Sleep(1 * time.Second)
|
||
|
}
|
||
|
os.Exit(0)
|
||
|
}
|