Skip to content

Commit adf84c8

Browse files
Complete test coverage for query package (#20)
1 parent fe2203e commit adf84c8

File tree

3 files changed

+132
-29
lines changed

3 files changed

+132
-29
lines changed

query/bigquery_runner.go

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,54 +4,46 @@
44
package query
55

66
import (
7-
"context"
8-
"log"
97
"math"
108
"sort"
119
"strings"
1210

13-
"github.com/m-lab/prometheus-bigquery-exporter/sql"
14-
1511
"cloud.google.com/go/bigquery"
16-
"google.golang.org/api/iterator"
12+
"github.com/m-lab/prometheus-bigquery-exporter/query/bqiface"
13+
"github.com/m-lab/prometheus-bigquery-exporter/sql"
1714
)
1815

1916
// BQRunner is a concerete implementation of QueryRunner for BigQuery.
2017
type BQRunner struct {
21-
client *bigquery.Client
18+
runner runner
19+
}
20+
21+
// runner interface allows unit testing of the Query function.
22+
type runner interface {
23+
Query(q string, visit func(row map[string]bigquery.Value) error) error
2224
}
2325

2426
// NewBQRunner creates a new QueryRunner instance.
2527
func NewBQRunner(client *bigquery.Client) *BQRunner {
26-
return &BQRunner{client}
28+
return &BQRunner{
29+
runner: &bqiface.BigQueryImpl{
30+
Client: client,
31+
},
32+
}
2733
}
2834

2935
// Query executes the given query. Query only supports standard SQL. The
3036
// query must define a column named "value" for the value, and may define
3137
// additional columns, all of which are used as metric labels.
3238
func (qr *BQRunner) Query(query string) ([]sql.Metric, error) {
3339
metrics := []sql.Metric{}
34-
35-
q := qr.client.Query(query)
36-
// TODO: add context timeout.
37-
it, err := q.Read(context.Background())
40+
err := qr.runner.Query(query, func(row map[string]bigquery.Value) error {
41+
metrics = append(metrics, rowToMetric(row))
42+
return nil
43+
})
3844
if err != nil {
39-
log.Print(err)
4045
return nil, err
4146
}
42-
43-
for {
44-
var row map[string]bigquery.Value
45-
err := it.Next(&row)
46-
if err == iterator.Done {
47-
break
48-
}
49-
if err != nil {
50-
log.Printf("%#v %d", err, len(metrics))
51-
return nil, err
52-
}
53-
metrics = append(metrics, rowToMetric(row))
54-
}
5547
return metrics, nil
5648
}
5749

query/bigquery_runner_test.go

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package query
22

33
import (
4+
"fmt"
5+
"math"
46
"reflect"
57
"testing"
68

@@ -10,9 +12,10 @@ import (
1012

1113
func TestRowToMetric(t *testing.T) {
1214
tests := []struct {
13-
name string
14-
row map[string]bigquery.Value
15-
metric sql.Metric
15+
name string
16+
row map[string]bigquery.Value
17+
metric sql.Metric
18+
wantNaN bool
1619
}{
1720
{
1821
name: "Extract labels",
@@ -72,12 +75,92 @@ func TestRowToMetric(t *testing.T) {
7275
Values: map[string]float64{"": 2.1},
7376
},
7477
},
78+
{
79+
name: "NaN value",
80+
row: map[string]bigquery.Value{
81+
"value": "this-is-NaN",
82+
},
83+
metric: sql.Metric{
84+
Values: map[string]float64{"": math.NaN()},
85+
},
86+
wantNaN: true,
87+
},
7588
}
7689

7790
for _, test := range tests {
7891
m := rowToMetric(test.row)
79-
if !reflect.DeepEqual(m, test.metric) {
92+
if !test.wantNaN && !reflect.DeepEqual(m, test.metric) {
93+
t.Errorf("Failed to convert row to metric. want %#v; got %#v", test.metric, m)
94+
}
95+
if test.wantNaN && !math.IsNaN(m.Values[""]) {
8096
t.Errorf("Failed to convert row to metric. want %#v; got %#v", test.metric, m)
8197
}
8298
}
8399
}
100+
101+
type fakeQuery struct {
102+
err error
103+
rows []map[string]bigquery.Value
104+
}
105+
106+
func (f *fakeQuery) Query(q string, visit func(row map[string]bigquery.Value) error) error {
107+
if f.err != nil {
108+
return f.err
109+
}
110+
for i := range f.rows {
111+
err := visit(f.rows[i])
112+
if err != nil {
113+
return err
114+
}
115+
}
116+
return nil
117+
}
118+
119+
func TestBQRunner_Query(t *testing.T) {
120+
tests := []struct {
121+
name string
122+
runner runner
123+
want []sql.Metric
124+
wantErr bool
125+
}{
126+
{
127+
name: "okay",
128+
runner: &fakeQuery{
129+
rows: []map[string]bigquery.Value{
130+
{
131+
"value_name": 1.23,
132+
},
133+
},
134+
},
135+
want: []sql.Metric{
136+
sql.NewMetric(nil, nil, map[string]float64{"_name": 1.23}),
137+
},
138+
},
139+
{
140+
name: "query-error",
141+
runner: &fakeQuery{
142+
err: fmt.Errorf("Fake query error"),
143+
},
144+
wantErr: true,
145+
},
146+
}
147+
for _, tt := range tests {
148+
t.Run(tt.name, func(t *testing.T) {
149+
qr := &BQRunner{
150+
runner: tt.runner,
151+
}
152+
got, err := qr.Query("select * from `fake-table`")
153+
if (err != nil) != tt.wantErr {
154+
t.Errorf("BQRunner.Query() error = %v, wantErr %v", err, tt.wantErr)
155+
return
156+
}
157+
if !reflect.DeepEqual(got, tt.want) {
158+
t.Errorf("BQRunner.Query() = %#v, want %#v", got, tt.want)
159+
}
160+
})
161+
}
162+
}
163+
164+
func TestNewBQRunner(t *testing.T) {
165+
NewBQRunner(nil)
166+
}

query/bqiface/bqiface.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package bqiface
2+
3+
import (
4+
"context"
5+
6+
"cloud.google.com/go/bigquery"
7+
"google.golang.org/api/iterator"
8+
)
9+
10+
type BigQueryImpl struct {
11+
Client *bigquery.Client
12+
}
13+
14+
func (b *BigQueryImpl) Query(query string, visit func(row map[string]bigquery.Value) error) error {
15+
q := b.Client.Query(query)
16+
it, err := q.Read(context.Background())
17+
if err != nil {
18+
return err
19+
}
20+
var row map[string]bigquery.Value
21+
for err = it.Next(&row); err != iterator.Done; err = it.Next(&row) {
22+
err2 := visit(row)
23+
if err2 != nil {
24+
return err2
25+
}
26+
}
27+
return nil
28+
}

0 commit comments

Comments
 (0)