kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/serving/explore/explore_test.go (about) 1 /* 2 * Copyright 2018 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 explore 18 19 import ( 20 "context" 21 "fmt" 22 "testing" 23 24 "kythe.io/kythe/go/storage/table" 25 "kythe.io/kythe/go/test/testutil" 26 27 "bitbucket.org/creachadair/stringset" 28 "google.golang.org/protobuf/proto" 29 30 epb "kythe.io/kythe/proto/explore_go_proto" 31 srvpb "kythe.io/kythe/proto/serving_go_proto" 32 ) 33 34 const ( 35 p1 = "kythe:#parent1" 36 p2 = "kythe:#parent2" 37 p1c1 = "kythe:#p1child1" 38 p1c2 = "kythe:#p1child2" 39 badType = "kythe:#baddatatype" 40 dontcare = "kythe:#dontcare" 41 f1 = "kythe:#function1" 42 f2 = "kythe:#function2" 43 f1r1 = "kythe:#f1caller1" 44 fr = "kythe:#fcaller" 45 f2r1 = "kythe:#f2caller1" 46 f3 = "kythe:#function3_recursive" 47 dne = "kythe:#does_not_exist" 48 ) 49 50 var ( 51 ctx = context.Background() 52 parentToChildren = &protoTable{ 53 p1: &srvpb.Relatives{ 54 Tickets: []string{p1c1, p1c2}, 55 Type: srvpb.Relatives_CHILDREN, 56 }, 57 badType: &srvpb.Relatives{ 58 Tickets: []string{dontcare}, 59 Type: srvpb.Relatives_PARENTS, 60 }, 61 } 62 63 childToParents = &protoTable{ 64 p1c1: &srvpb.Relatives{ 65 Tickets: []string{p1}, 66 Type: srvpb.Relatives_PARENTS, 67 }, 68 p1c2: &srvpb.Relatives{ 69 Tickets: []string{p2}, 70 Type: srvpb.Relatives_PARENTS, 71 }, 72 badType: &srvpb.Relatives{ 73 Tickets: []string{dontcare}, 74 Type: srvpb.Relatives_CHILDREN, 75 }, 76 } 77 78 functionToCallers = &protoTable{ 79 f1: &srvpb.Callgraph{ 80 Tickets: []string{f1r1, fr}, 81 Type: srvpb.Callgraph_CALLER, 82 }, 83 f2: &srvpb.Callgraph{ 84 Tickets: []string{f2r1, fr}, 85 Type: srvpb.Callgraph_CALLER, 86 }, 87 f3: &srvpb.Callgraph{ 88 Tickets: []string{f1, fr, f3}, 89 Type: srvpb.Callgraph_CALLER, 90 }, 91 fr: &srvpb.Callgraph{ 92 Tickets: []string{f2}, 93 Type: srvpb.Callgraph_CALLER, 94 }, 95 badType: &srvpb.Callgraph{ 96 Tickets: []string{dontcare}, 97 Type: srvpb.Callgraph_CALLEE, 98 }, 99 } 100 101 functionToCallees = &protoTable{ 102 f1: &srvpb.Callgraph{ 103 Tickets: []string{f3}, 104 Type: srvpb.Callgraph_CALLEE, 105 }, 106 f2: &srvpb.Callgraph{ 107 Tickets: []string{fr}, 108 Type: srvpb.Callgraph_CALLEE, 109 }, 110 f3: &srvpb.Callgraph{ 111 Tickets: []string{f3}, 112 Type: srvpb.Callgraph_CALLEE, 113 }, 114 fr: &srvpb.Callgraph{ 115 Tickets: []string{f1, f2, f3}, 116 Type: srvpb.Callgraph_CALLEE, 117 }, 118 f1r1: &srvpb.Callgraph{ 119 Tickets: []string{f1}, 120 Type: srvpb.Callgraph_CALLEE, 121 }, 122 f2r1: &srvpb.Callgraph{ 123 Tickets: []string{f2}, 124 Type: srvpb.Callgraph_CALLEE, 125 }, 126 badType: &srvpb.Callgraph{ 127 Tickets: []string{dontcare}, 128 Type: srvpb.Callgraph_CALLER, 129 }, 130 } 131 ) 132 133 func TestChildren_badData(t *testing.T) { 134 svc := construct(t) 135 136 reply, err := svc.Children(ctx, &epb.ChildrenRequest{ 137 Tickets: []string{badType}, 138 }) 139 if err == nil { 140 t.Errorf("Expected Children error for bad data, got: %v", reply) 141 } 142 } 143 144 func TestChildren_noData(t *testing.T) { 145 svc := construct(t) 146 147 reply, err := svc.Children(ctx, &epb.ChildrenRequest{ 148 Tickets: []string{dne}, 149 }) 150 testutil.Fatalf(t, "Children error: %v", err) 151 if len(reply.InputToChildren) != 0 { 152 t.Errorf("Expected empty response for missing key, got: %v", reply) 153 } 154 } 155 156 func TestChildren(t *testing.T) { 157 svc := construct(t) 158 request := &epb.ChildrenRequest{ 159 Tickets: []string{p1}, 160 } 161 162 reply, err := svc.Children(ctx, request) 163 testutil.Fatalf(t, "Children error: %v", err) 164 165 expectedInputToChildren := map[string]*epb.Tickets{ 166 p1: { 167 Tickets: []string{p1c1, p1c2}, 168 }, 169 } 170 171 expectedInputKeys := stringset.FromKeys(expectedInputToChildren) 172 actualInputKeys := stringset.FromKeys(reply.InputToChildren) 173 if !expectedInputKeys.Equals(actualInputKeys) { 174 t.Errorf("Expected results and actual results have different ticket key sets:\n"+ 175 "expected: %v\n actual: %v", expectedInputKeys, actualInputKeys) 176 } 177 178 for ticket, expectedChildren := range expectedInputToChildren { 179 children := reply.InputToChildren[ticket] 180 checkEquivalentLists(t, expectedChildren.Tickets, children.Tickets, "children") 181 } 182 } 183 184 func TestParents_badData(t *testing.T) { 185 svc := construct(t) 186 187 reply, err := svc.Parents(ctx, &epb.ParentsRequest{ 188 Tickets: []string{badType}, 189 }) 190 if err == nil { 191 t.Errorf("Expected Parents error for bad data, got: %v", reply) 192 } 193 } 194 195 func TestParents_noData(t *testing.T) { 196 svc := construct(t) 197 198 reply, err := svc.Parents(ctx, &epb.ParentsRequest{ 199 Tickets: []string{dne}, 200 }) 201 testutil.Fatalf(t, "Parents error: %v", err) 202 if len(reply.InputToParents) != 0 { 203 t.Errorf("Expected empty response for missing key, got: %v", reply) 204 } 205 } 206 207 func TestParents(t *testing.T) { 208 svc := construct(t) 209 request := &epb.ParentsRequest{ 210 Tickets: []string{p1c1}, 211 } 212 213 reply, err := svc.Parents(ctx, request) 214 testutil.Fatalf(t, "Parents error: %v", err) 215 216 expectedInputToParents := map[string]*epb.Tickets{ 217 p1c1: { 218 Tickets: []string{p1}, 219 }, 220 } 221 222 expectedInputKeys := stringset.FromKeys(expectedInputToParents) 223 actualInputKeys := stringset.FromKeys(reply.InputToParents) 224 if !expectedInputKeys.Equals(actualInputKeys) { 225 t.Errorf("Expected results and actual results have different ticket key sets:\n"+ 226 "expected: %v\n actual: %v", expectedInputKeys, actualInputKeys) 227 } 228 229 for ticket, expectedParents := range expectedInputToParents { 230 parents := reply.InputToParents[ticket] 231 checkEquivalentLists(t, expectedParents.Tickets, parents.Tickets, 232 fmt.Sprintf("children of %s", ticket)) 233 } 234 } 235 236 func TestCallers_badData(t *testing.T) { 237 svc := construct(t) 238 239 reply, err := svc.Callers(ctx, &epb.CallersRequest{ 240 Tickets: []string{badType}, 241 }) 242 if err == nil { 243 t.Errorf("Expected Callers error for bad data, got: %v", reply) 244 } 245 } 246 247 func TestCallers_noData(t *testing.T) { 248 svc := construct(t) 249 250 reply, err := svc.Callers(ctx, &epb.CallersRequest{ 251 Tickets: []string{dne}, 252 }) 253 testutil.Fatalf(t, "CallersRequest error: %v", err) 254 if len(reply.Graph.Nodes) != 0 { 255 t.Errorf("Expected empty response for missing key, got: %v", reply) 256 } 257 } 258 259 func TestCallers(t *testing.T) { 260 svc := construct(t) 261 request := &epb.CallersRequest{ 262 Tickets: []string{f1}, 263 } 264 265 reply, err := svc.Callers(ctx, request) 266 testutil.Fatalf(t, "Callers error: %v", err) 267 268 expectedGraph := &epb.Graph{ 269 Nodes: map[string]*epb.GraphNode{ 270 f1: { 271 Predecessors: []string{f1r1, fr}, 272 }, 273 f1r1: { 274 Successors: []string{f1}, 275 }, 276 fr: { 277 Successors: []string{f1}, 278 }, 279 }, 280 } 281 282 checkEqualGraphs(t, expectedGraph, reply.Graph) 283 } 284 285 func TestCallers_multipleInputs(t *testing.T) { 286 svc := construct(t) 287 request := &epb.CallersRequest{ 288 Tickets: []string{f1, f2, f3}, 289 } 290 291 reply, err := svc.Callers(ctx, request) 292 testutil.Fatalf(t, "Callers error: %v", err) 293 294 // The edge f2->fr is not in the reply because we're asking for neither 295 // the callers of fr nor the callees of f2. 296 expectedGraph := &epb.Graph{ 297 Nodes: map[string]*epb.GraphNode{ 298 f1: { 299 Predecessors: []string{f1r1, fr}, 300 Successors: []string{f3}, 301 }, 302 f2: { 303 Predecessors: []string{f2r1, fr}, 304 }, 305 f3: { 306 Predecessors: []string{f1, fr, f3}, 307 Successors: []string{f3}, 308 }, 309 f1r1: { 310 Successors: []string{f1}, 311 }, 312 f2r1: { 313 Successors: []string{f2}, 314 }, 315 fr: { 316 Successors: []string{f1, f2, f3}, 317 }, 318 }, 319 } 320 321 checkEqualGraphs(t, expectedGraph, reply.Graph) 322 } 323 324 func TestCallees_badData(t *testing.T) { 325 svc := construct(t) 326 327 reply, err := svc.Callees(ctx, &epb.CalleesRequest{ 328 Tickets: []string{badType}, 329 }) 330 if err == nil { 331 t.Errorf("Expected Callees error for bad data, got: %v", reply) 332 } 333 } 334 335 func TestCallees_noData(t *testing.T) { 336 svc := construct(t) 337 338 reply, err := svc.Callees(ctx, &epb.CalleesRequest{ 339 Tickets: []string{dne}, 340 }) 341 testutil.Fatalf(t, "CalleesRequest error: %v", err) 342 if len(reply.Graph.Nodes) != 0 { 343 t.Errorf("Expected empty response for missing key, got: %v", reply) 344 } 345 } 346 347 func TestCallees(t *testing.T) { 348 svc := construct(t) 349 request := &epb.CalleesRequest{ 350 Tickets: []string{fr}, 351 } 352 353 reply, err := svc.Callees(ctx, request) 354 testutil.Fatalf(t, "Callees error: %v", err) 355 356 expectedGraph := &epb.Graph{ 357 Nodes: map[string]*epb.GraphNode{ 358 fr: { 359 Successors: []string{f1, f2, f3}, 360 }, 361 f1: { 362 Predecessors: []string{fr}, 363 }, 364 f2: { 365 Predecessors: []string{fr}, 366 }, 367 f3: { 368 Predecessors: []string{fr}, 369 }, 370 }, 371 } 372 373 checkEqualGraphs(t, expectedGraph, reply.Graph) 374 } 375 376 func TestCallees_multipleInputs(t *testing.T) { 377 svc := construct(t) 378 request := &epb.CalleesRequest{ 379 Tickets: []string{f1, f2, f3}, 380 } 381 382 reply, err := svc.Callees(ctx, request) 383 testutil.Fatalf(t, "Callees error: %v", err) 384 385 // The following edges are present in the stored callgraph 386 // but intentionally not present in the response: 387 // f1r1->f1, fr->f1; f2r1->f2, fr->f2; fr->f3 388 // (because the request doesn't ask for the callers of f1, f2, f3). 389 expectedGraph := &epb.Graph{ 390 Nodes: map[string]*epb.GraphNode{ 391 f1: { 392 Successors: []string{f3}, 393 }, 394 f2: { 395 Successors: []string{fr}, 396 }, 397 f3: { 398 Predecessors: []string{f1, f3}, 399 Successors: []string{f3}, 400 }, 401 fr: { 402 Predecessors: []string{f2}, 403 }, 404 }, 405 } 406 407 checkEqualGraphs(t, expectedGraph, reply.Graph) 408 } 409 410 func checkEqualGraphs(t *testing.T, expected, actual *epb.Graph) { 411 if len(expected.Nodes) != len(actual.Nodes) { 412 t.Errorf("Mismatch in graph node counts: expected: %d, actual: %d", 413 len(expected.Nodes), len(actual.Nodes)) 414 } 415 416 expectedNodeSet := stringset.FromKeys(expected.Nodes) 417 actualNodeSet := stringset.FromKeys(actual.Nodes) 418 if !expectedNodeSet.Equals(actualNodeSet) { 419 t.Errorf("Unexpected mismatch in graph node sets:\n expected:\n%v\n actual:\n%v\n", 420 expectedNodeSet, actualNodeSet) 421 } 422 423 for ticket, expectedNode := range expected.Nodes { 424 node := actual.Nodes[ticket] 425 checkEquivalentLists(t, expectedNode.Predecessors, node.Predecessors, 426 fmt.Sprintf("predecessors for %s", ticket)) 427 checkEquivalentLists(t, expectedNode.Successors, node.Successors, 428 fmt.Sprintf("successors for %s", ticket)) 429 } 430 } 431 432 // Checks whether the lists are equivalent. 433 func checkEquivalentLists(t *testing.T, expected, actual []string, tag string) { 434 if len(expected) != len(actual) { 435 t.Errorf("Mismatch in counts for %s; expected:\n%v actual:\n%v", 436 tag, expected, actual) 437 } 438 439 if !stringset.FromKeys(expected).Equals(stringset.FromKeys(actual)) { 440 t.Errorf("Mismatch for %s; expected:\n%v actual:\n%v", 441 tag, expected, actual) 442 } 443 } 444 445 type protoTable map[string]proto.Message 446 447 func (t protoTable) Lookup(_ context.Context, key []byte, msg proto.Message) error { 448 m, ok := t[string(key)] 449 if !ok { 450 return table.ErrNoSuchKey 451 } 452 proto.Merge(msg, m) 453 return nil 454 } 455 456 func (t protoTable) LookupValues(_ context.Context, key []byte, m proto.Message, f func(proto.Message) error) error { 457 val, ok := t[string(key)] 458 if !ok { 459 return nil 460 } 461 msg := m.ProtoReflect().New().Interface() 462 proto.Merge(msg, val) 463 if err := f(msg); err != nil && err != table.ErrStopLookup { 464 return err 465 } 466 return nil 467 } 468 469 func construct(t *testing.T) *Tables { 470 return &Tables{ 471 ParentToChildren: parentToChildren, 472 ChildToParents: childToParents, 473 FunctionToCallers: functionToCallers, 474 FunctionToCallees: functionToCallees, 475 } 476 }