diff --git a/.travis.yml b/.travis.yml index 7e30f98..6b1cb71 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ go: - 1.x install: -- go get -u github.com/golang/dep/... +- make install-dev-deps - dep ensure - make update-mk-libs diff --git a/Makefile b/Makefile index bf058c6..9b3dd13 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,10 @@ GO ?= go +install-dev-deps: + @$(GO) get -u github.com/golang/dep/... + @$(GO) get golang.org/x/tools/cmd/cover + @$(GO) get github.com/mattn/goveralls + build: @echo "Building dist/ooni" @$(GO) build -i -o dist/ooni cmd/ooni/main.go diff --git a/internal/database/actions.go b/internal/database/actions.go index 4235dd2..6f7dc77 100644 --- a/internal/database/actions.go +++ b/internal/database/actions.go @@ -7,111 +7,54 @@ import ( "github.com/apex/log" "github.com/ooni/probe-cli/utils" "github.com/pkg/errors" + db "upper.io/db.v3" "upper.io/db.v3/lib/sqlbuilder" ) // ListMeasurements given a result ID -func ListMeasurements(db sqlbuilder.Database, resultID int64) ([]*Measurement, error) { - measurements := []*Measurement{} +func ListMeasurements(sess sqlbuilder.Database, resultID int64) ([]MeasurementURLNetwork, error) { + measurements := []MeasurementURLNetwork{} - /* - FIXME - rows, err := db.Query(`SELECT id, name, - start_time, runtime, - country, - asn, - summary, - input - FROM measurements - WHERE result_id = ? - ORDER BY start_time;`, resultID) - if err != nil { - return measurements, errors.Wrap(err, "failed to get measurement list") - } - - for rows.Next() { - msmt := Measurement{} - err = rows.Scan(&msmt.ID, &msmt.Name, - &msmt.StartTime, &msmt.Runtime, - &msmt.CountryCode, - &msmt.ASN, - &msmt.Summary, &msmt.Input, - //&result.DataUsageUp, &result.DataUsageDown) - ) - if err != nil { - log.WithError(err).Error("failed to fetch a row") - continue - } - measurements = append(measurements, &msmt) - } - */ + req := sess.Select( + "networks.id as network_id", + "results.id as result_id", + "urls.id as url_id", + db.Raw("networks.*"), + db.Raw("urls.*"), + db.Raw("measurements.*"), + ).From("results"). + Join("measurements").On("results.id = measurements.result_id"). + Join("networks").On("results.network_id = networks.id"). + LeftJoin("urls").On("urls.id = measurements.url_id"). + OrderBy("measurements.start_time"). + Where("results.id = ?", resultID) + if err := req.All(&measurements); err != nil { + log.Errorf("failed to run query %s: %v", req.String(), err) + return measurements, err + } return measurements, nil } // ListResults return the list of results -func ListResults(db sqlbuilder.Database) ([]*Result, []*Result, error) { - doneResults := []*Result{} - incompleteResults := []*Result{} +func ListResults(sess sqlbuilder.Database) ([]ResultNetwork, []ResultNetwork, error) { + doneResults := []ResultNetwork{} + incompleteResults := []ResultNetwork{} - /* - FIXME - rows, err := db.Query(`SELECT id, name, - start_time, runtime, - network_name, country, - asn, - summary, done - FROM results - WHERE done = 1 - ORDER BY start_time;`) - if err != nil { - return doneResults, incompleteResults, errors.Wrap(err, "failed to get result done list") - } - for rows.Next() { - result := Result{} - err = rows.Scan(&result.ID, &result.Name, - &result.StartTime, &result.Runtime, - &result.NetworkName, &result.Country, - &result.ASN, - &result.Summary, &result.Done, - //&result.DataUsageUp, &result.DataUsageDown) - ) - if err != nil { - log.WithError(err).Error("failed to fetch a row") - continue - } - doneResults = append(doneResults, &result) - } - */ + req := sess.Select( + "networks.id AS network_id", + db.Raw("results.*"), + db.Raw("networks.*"), + ).From("results"). + Join("networks").On("results.network_id = networks.id"). + OrderBy("results.start_time") - /* - FIXME - rows, err := db.Query(`SELECT - id, name, - start_time, - network_name, country, - asn - FROM results - WHERE done != 1 - ORDER BY start_time;`) - if err != nil { - return doneResults, incompleteResults, errors.Wrap(err, "failed to get result done list") - } - */ - - /* - for rows.Next() { - result := Result{Done: false} - err = rows.Scan(&result.ID, &result.Name, &result.StartTime, - &result.NetworkName, &result.Country, - &result.ASN) - if err != nil { - log.WithError(err).Error("failed to fetch a row") - continue - } - incompleteResults = append(incompleteResults, &result) - } - */ + if err := req.Where("is_done = true").All(&doneResults); err != nil { + return doneResults, incompleteResults, errors.Wrap(err, "failed to get result done list") + } + if err := req.Where("is_done = false").All(&incompleteResults); err != nil { + return doneResults, incompleteResults, errors.Wrap(err, "failed to get result done list") + } return doneResults, incompleteResults, nil } diff --git a/internal/database/actions_test.go b/internal/database/actions_test.go index 261e2e9..8c7e770 100644 --- a/internal/database/actions_test.go +++ b/internal/database/actions_test.go @@ -6,6 +6,7 @@ import ( "os" "testing" + "github.com/apex/log" "github.com/ooni/probe-cli/utils" ) @@ -14,7 +15,8 @@ func TestMeasurementWorkflow(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.Remove(tmpfile.Name()) + log.Infof("%s", tmpfile.Name()) + //defer os.Remove(tmpfile.Name()) tmpdir, err := ioutil.TempDir("", "oonitest") if err != nil { @@ -62,4 +64,23 @@ func TestMeasurementWorkflow(t *testing.T) { t.Error("result_id mismatch") } + done, incomplete, err := ListResults(sess) + if err != nil { + t.Fatal(err) + } + + if len(incomplete) != 1 { + t.Error("there should be 1 incomplete measurement") + } + if len(done) != 0 { + t.Error("there should be 0 done measurements") + } + + msmts, err := ListMeasurements(sess, resultID) + if err != nil { + t.Fatal(err) + } + if msmts[0].Network.NetworkType != "wifi" { + t.Error("network_type should be wifi") + } } diff --git a/internal/database/models.go b/internal/database/models.go index 69b679d..1327f63 100644 --- a/internal/database/models.go +++ b/internal/database/models.go @@ -11,6 +11,21 @@ import ( "upper.io/db.v3/lib/sqlbuilder" ) +// ResultNetwork is used to represent the structure made from the JOIN +// between the results and networks tables. +type ResultNetwork struct { + Result `db:",inline"` + Network `db:",inline"` +} + +// MeasurementURLNetwork is used for the JOIN between Measurement and URL +type MeasurementURLNetwork struct { + Measurement `db:",inline"` + Network `db:",inline"` + NetworkID int64 `db:"network_id"` + URL `db:",inline"` +} + // Network represents a network tested by the user type Network struct { ID int64 `db:"id"` @@ -23,10 +38,10 @@ type Network struct { // URL represents URLs from the testing lists type URL struct { - ID int64 `db:"id"` - URL string `db:"url"` - CategoryCode string `db:"category_code"` - CountryCode string `db:"country_code"` + ID sql.NullInt64 `db:"id"` + URL sql.NullString `db:"url"` + CategoryCode sql.NullString `db:"category_code"` + CountryCode sql.NullString `db:"country_code"` } // Measurement model