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