1/*
2Copyright 2019 Google LLC
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package bigtable
18
19import (
20	"bytes"
21	"context"
22	"io/ioutil"
23	"path/filepath"
24	"sort"
25	"strings"
26	"testing"
27	"time"
28
29	pb "cloud.google.com/go/bigtable/internal/conformance"
30	"cloud.google.com/go/bigtable/internal/mockserver"
31	"github.com/golang/protobuf/jsonpb"
32	"google.golang.org/api/option"
33	btpb "google.golang.org/genproto/googleapis/bigtable/v2"
34	"google.golang.org/grpc"
35)
36
37func TestConformance(t *testing.T) {
38	ctx := context.Background()
39
40	dir := "internal/conformance/testdata"
41	files, err := filepath.Glob(dir + "/*.json")
42	if err != nil {
43		t.Fatal(err)
44	}
45
46	srv, err := mockserver.NewServer("localhost:0")
47	if err != nil {
48		t.Fatal(err)
49	}
50	defer srv.Close()
51
52	conn, err := grpc.Dial(srv.Addr, grpc.WithInsecure(), grpc.WithBlock())
53	if err != nil {
54		t.Fatal(err)
55	}
56	defer conn.Close()
57	c, err := NewClient(ctx, "some-project", "some-instance", option.WithGRPCConn(conn))
58	if err != nil {
59		t.Fatal(err)
60	}
61
62	for _, f := range files {
63		inBytes, err := ioutil.ReadFile(f)
64		if err != nil {
65			t.Fatalf("%s: %v", f, err)
66		}
67
68		var tf pb.TestFile
69		if err := jsonpb.Unmarshal(bytes.NewReader(inBytes), &tf); err != nil {
70			t.Fatalf("unmarshalling %s: %v", f, err)
71		}
72
73		for _, tc := range tf.GetReadRowsTests() {
74			t.Run(tc.Description, func(t *testing.T) {
75				runReadRowsTest(ctx, t, tc, c, srv)
76			})
77		}
78	}
79}
80
81func runReadRowsTest(ctx context.Context, t *testing.T, tc *pb.ReadRowsTest, c *Client, srv *mockserver.Server) {
82	srv.ReadRowsFn = func(req *btpb.ReadRowsRequest, something btpb.Bigtable_ReadRowsServer) error {
83		something.Send(&btpb.ReadRowsResponse{
84			Chunks: tc.GetChunks(),
85		})
86
87		return nil
88	}
89
90	var resIndex int
91
92	// We perform a SingleRow here, but that arg is basically nonsense since
93	// the server is hard-coded to return a specific response. As in, we could
94	// pass RowRange, ListRows, etc and the result would all be the same.
95	err := c.Open("some-table").ReadRows(ctx, SingleRow("some-row"), func(r Row) bool {
96		type rowElem struct {
97			family    string
98			readItems []ReadItem
99		}
100
101		// Row comes in as a map, which has undefined iteration order. So, we
102		// first stick it in a slice, then sort that slice by family (the
103		// results appear ordered as such), then we're ready to use it.
104		var byFamily []rowElem
105		for family, items := range r {
106			byFamily = append(byFamily, rowElem{family: family, readItems: items})
107		}
108		sort.Slice(byFamily, func(i, j int) bool {
109			return strings.Compare(byFamily[i].family, byFamily[j].family) < 0
110		})
111
112		for _, row := range byFamily {
113			family := row.family
114			items := row.readItems
115			for _, item := range items {
116				want := tc.GetResults()[resIndex]
117
118				if got, want := string(item.Value), want.GetValue(); got != want {
119					t.Fatalf("got %s, want %s", got, want)
120				}
121
122				if got, want := family, want.GetFamilyName(); got != want {
123					t.Fatalf("got %s, want %s", got, want)
124				}
125
126				gotMicros := item.Timestamp.Time().UnixNano() / int64(time.Microsecond)
127				if got, want := gotMicros, want.GetTimestampMicros(); got != want {
128					t.Fatalf("got %d, want %d", got, want)
129				}
130
131				if got, want := item.Column, want.GetFamilyName()+":"+want.GetQualifier(); got != want {
132					t.Fatalf("got %s, want %s", got, want)
133				}
134
135				// TODO: labels do not appear to be accessible. If they ever do become
136				// accessible, we should assert on want.GetLabels().
137
138				resIndex++
139			}
140		}
141		return true
142	})
143
144	wantNumResults := len(tc.GetResults())
145
146	if wantNumResults == 0 {
147		return
148	}
149
150	if tc.GetResults()[wantNumResults-1].GetError() {
151		// Last expected result is an error, which means we wouldn't
152		// count it with gotRowIndex.
153		wantNumResults--
154
155		if err == nil {
156			t.Fatal("expected err, got nil")
157		}
158	} else if err != nil {
159		t.Fatal(err)
160	}
161
162	if got, want := resIndex, wantNumResults; got != want {
163		t.Fatalf("got %d results, want %d", got, want)
164	}
165}
166