kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/services/graphstore/proxy/proxy_test.go (about) 1 /* 2 * Copyright 2015 The Kythe Authors. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package proxy 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "io" 24 "testing" 25 26 "kythe.io/kythe/go/services/graphstore" 27 28 "google.golang.org/protobuf/proto" 29 30 spb "kythe.io/kythe/proto/storage_go_proto" 31 ) 32 33 var ctx = context.Background() 34 35 // Static set of Entry protos to use for testing 36 var testEntries = []entry{ 37 {}, 38 {K: "edge", F: "fact", V: "value"}, 39 {K: "a"}, {K: "b"}, {K: "c"}, {K: "d"}, {K: "e"}, 40 {K: "f"}, {K: "g"}, {K: "h"}, {K: "i"}, 41 } 42 43 func te(i int) *spb.Entry { return testEntries[i].proto() } 44 45 func tes(is ...int) (es []*spb.Entry) { 46 for _, i := range is { 47 es = append(es, te(i)) 48 } 49 return es 50 } 51 52 func TestMergeOrder(t *testing.T) { 53 tests := []struct { 54 streams [][]*spb.Entry // Each stream must be in order 55 want []*spb.Entry 56 }{ 57 {nil, nil}, 58 59 {[][]*spb.Entry{}, []*spb.Entry{}}, 60 61 {[][]*spb.Entry{{}}, []*spb.Entry{}}, 62 63 {[][]*spb.Entry{{te(0)}}, tes(0)}, 64 65 {[][]*spb.Entry{tes(0), tes(1), tes(5)}, tes(0, 5, 1)}, 66 67 {[][]*spb.Entry{tes(0), tes(1), tes(0, 1, 1)}, tes(0, 1)}, 68 69 {[][]*spb.Entry{tes(2, 5, 7), tes(3, 8), tes(4, 6)}, tes(2, 3, 4, 5, 6, 7, 8)}, 70 71 {[][]*spb.Entry{tes(8), tes(2, 3, 5), tes(5, 6), tes(7, 9, 10), tes(4), tes(6, 6, 6, 6, 6)}, 72 tes(2, 3, 4, 5, 6, 7, 8, 9, 10)}, 73 } 74 75 for i, test := range tests { 76 t.Logf("Begin test %d", i) 77 var ss []graphstore.Service 78 for _, stream := range test.streams { 79 ss = append(ss, &mockGraphStore{Entries: stream}) 80 } 81 result := make(chan *spb.Entry) 82 done := checkResults(t, fmt.Sprintf("Merge test %d", i), result, test.want) 83 84 if err := New(ss...).Read(ctx, new(spb.ReadRequest), func(e *spb.Entry) error { 85 result <- e 86 return nil 87 }); err != nil { 88 t.Errorf("Test %d Read failed: %v", i, err) 89 } 90 close(result) 91 <-done 92 } 93 } 94 95 func TestProxy(t *testing.T) { 96 testError := errors.New("test") 97 mocks := []*mockGraphStore{ 98 {Entries: []*spb.Entry{te(2), te(3)}}, 99 {Entries: []*spb.Entry{te(5), te(6)}, Error: testError}, 100 {Entries: []*spb.Entry{te(2), te(4)}}, 101 } 102 allEntries := []*spb.Entry{ 103 te(2), te(3), te(4), 104 te(5), te(6), 105 } 106 proxy := New(mocks[0], mocks[1], mocks[2]) 107 108 readReq := new(spb.ReadRequest) 109 readRes := make(chan *spb.Entry) 110 readDone := checkResults(t, "Read", readRes, allEntries) 111 112 if err := proxy.Read(ctx, readReq, func(e *spb.Entry) error { 113 readRes <- e 114 return nil 115 }); err != testError { 116 t.Errorf("Incorrect Read error: %v", err) 117 } 118 close(readRes) 119 <-readDone 120 121 for idx, mock := range mocks { 122 if mock.LastReq != readReq { 123 t.Errorf("Read request was not sent to service %d", idx) 124 } 125 } 126 127 scanReq := new(spb.ScanRequest) 128 scanRes := make(chan *spb.Entry) 129 scanDone := checkResults(t, "Scan", scanRes, allEntries) 130 131 if err := proxy.Scan(ctx, scanReq, func(e *spb.Entry) error { 132 scanRes <- e 133 return nil 134 }); err != testError { 135 t.Errorf("Incorrect Scan error: %v", err) 136 } 137 close(scanRes) 138 <-scanDone 139 140 for idx, mock := range mocks { 141 if mock.LastReq != scanReq { 142 t.Errorf("Scan request was not sent to for service %d", idx) 143 } 144 } 145 146 writeReq := new(spb.WriteRequest) 147 if err := proxy.Write(ctx, writeReq); err != testError { 148 t.Errorf("Incorrect Write error: %v", err) 149 } 150 for idx, mock := range mocks { 151 if mock.LastReq != writeReq { 152 t.Errorf("Write request was not sent to service %d", idx) 153 } 154 } 155 } 156 157 // Verify that a proxy store behaves sensibly if an operation fails. 158 func TestCancellation(t *testing.T) { 159 bomb := entry{K: "bomb", F: "die", V: "horrible catastrophe"} 160 stores := []graphstore.Service{ 161 &mockGraphStore{Entries: tes(8, 3, 2, 4, 5, 9, 2, 0)}, 162 &mockGraphStore{Entries: []*spb.Entry{bomb.proto()}}, 163 &mockGraphStore{Entries: tes(1, 1, 1, 0, 1, 6, 5)}, 164 &mockGraphStore{Entries: tes(3, 10, 7)}, 165 } 166 p := New(stores...) 167 168 // Check that a callback returning a non-EOF error propagates an error to 169 // the caller. 170 var numEntries int 171 if err := p.Scan(ctx, new(spb.ScanRequest), func(e *spb.Entry) error { 172 if e.FactName == bomb.F { 173 return errors.New(bomb.V) 174 } 175 numEntries++ 176 return nil 177 }); err != nil { 178 t.Logf("Got expected error: %v", err) 179 } else { 180 t.Error("Unexpected success! Got nil, wanted an error") 181 } 182 if numEntries != 3 { 183 t.Errorf("Wrong number of entries scanned: got %d, want 3", numEntries) 184 } 185 186 // Check that a callback returning io.EOF ends without error. 187 numEntries = 0 188 if err := p.Read(ctx, new(spb.ReadRequest), func(e *spb.Entry) error { 189 if e.FactName == bomb.F { 190 return io.EOF 191 } 192 numEntries++ 193 return nil 194 }); err != nil { 195 t.Errorf("Read: unexpected error: %v", err) 196 } 197 if numEntries != 3 { 198 t.Errorf("Wrong number of entries read: got %d, want 3", numEntries) 199 } 200 } 201 202 type vname struct { 203 S, C, R, P, L string 204 } 205 206 func (v vname) proto() *spb.VName { 207 return &spb.VName{ 208 Signature: v.S, 209 Corpus: v.C, 210 Path: v.P, 211 Root: v.R, 212 Language: v.L, 213 } 214 } 215 216 type entry struct { 217 S, T vname 218 K, F, V string 219 } 220 221 func (e entry) proto() *spb.Entry { 222 return &spb.Entry{ 223 Source: e.S.proto(), 224 EdgeKind: e.K, 225 FactName: e.F, 226 Target: e.T.proto(), 227 FactValue: []byte(e.V), 228 } 229 } 230 231 type mockGraphStore struct { 232 Entries []*spb.Entry 233 LastReq proto.Message 234 Error error 235 } 236 237 func (m *mockGraphStore) Read(ctx context.Context, req *spb.ReadRequest, f graphstore.EntryFunc) error { 238 m.LastReq = req 239 for _, entry := range m.Entries { 240 if err := f(entry); err == io.EOF { 241 return nil 242 } else if err != nil { 243 return err 244 } 245 } 246 return m.Error 247 } 248 249 func (m *mockGraphStore) Scan(ctx context.Context, req *spb.ScanRequest, f graphstore.EntryFunc) error { 250 m.LastReq = req 251 for _, entry := range m.Entries { 252 if err := f(entry); err == io.EOF { 253 return nil 254 } else if err != nil { 255 return err 256 } 257 } 258 return m.Error 259 } 260 261 func (m *mockGraphStore) Write(ctx context.Context, req *spb.WriteRequest) error { 262 m.LastReq = req 263 return m.Error 264 } 265 266 func (m *mockGraphStore) Close(ctx context.Context) error { return m.Error } 267 268 // checkResults starts a goroutine that consumes entries from results and 269 // compares them to corresponding members of want. If the corresponding values 270 // are unequal or if there are more or fewer results than wanted, errors are 271 // logged to t prefixed with the given tag. 272 // 273 // The returned channel is closed when all results have been checked. 274 func checkResults(t *testing.T, tag string, results <-chan *spb.Entry, want []*spb.Entry) chan struct{} { 275 done := make(chan struct{}) 276 go func() { 277 defer close(done) 278 i := 0 279 for entry := range results { 280 if i < len(want) { 281 if !proto.Equal(entry, want[i]) { 282 t.Errorf("%s result %d: got {%+v}, want {%+v}", tag, i, entry, want[i]) 283 } 284 } else { 285 t.Errorf("%s extra result %d: {%+v}", tag, i, entry) 286 } 287 i++ 288 } 289 for i < len(want) { 290 t.Errorf("%s missing result %d: {%+v}", tag, i, want[i]) 291 i++ 292 } 293 }() 294 return done 295 }