kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/serving/graph/graph_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 graph 18 19 import ( 20 "context" 21 "testing" 22 23 "kythe.io/kythe/go/storage/table" 24 "kythe.io/kythe/go/test/testutil" 25 "kythe.io/kythe/go/util/kytheuri" 26 27 "bitbucket.org/creachadair/stringset" 28 "golang.org/x/text/encoding" 29 "golang.org/x/text/encoding/unicode" 30 "golang.org/x/text/transform" 31 "google.golang.org/protobuf/proto" 32 33 cpb "kythe.io/kythe/proto/common_go_proto" 34 gpb "kythe.io/kythe/proto/graph_go_proto" 35 srvpb "kythe.io/kythe/proto/serving_go_proto" 36 ) 37 38 var ( 39 ctx = context.Background() 40 41 nodes = []*srvpb.Node{ 42 { 43 Ticket: "kythe://someCorpus?lang=otpl#signature", 44 Fact: makeFactList("/kythe/node/kind", "testNode"), 45 }, { 46 Ticket: "kythe://someCorpus#aTicketSig", 47 Fact: makeFactList("/kythe/node/kind", "testNode"), 48 }, { 49 Ticket: "kythe://someCorpus?lang=otpl#something", 50 Fact: makeFactList( 51 "/kythe/node/kind", "name", 52 "/some/other/fact", "value", 53 ), 54 }, { 55 Ticket: "kythe://someCorpus?lang=otpl#sig2", 56 Fact: makeFactList("/kythe/node/kind", "name"), 57 }, { 58 Ticket: "kythe://someCorpus?lang=otpl#sig3", 59 Fact: makeFactList("/kythe/node/kind", "name"), 60 }, { 61 Ticket: "kythe://someCorpus?lang=otpl#sig4", 62 Fact: makeFactList("/kythe/node/kind", "name"), 63 }, { 64 Ticket: "kythe://someCorpus?lang=otpl?path=/some/valid/path#a83md71", 65 Fact: makeFactList( 66 "/kythe/node/kind", "file", 67 "/kythe/text", "; some file content here\nfinal line\n", 68 "/kythe/text/encoding", "utf-8", 69 ), 70 }, { 71 Ticket: "kythe://c?lang=otpl?path=/a/path#6-9", 72 Fact: makeFactList( 73 "/kythe/node/kind", "anchor", 74 "/kythe/loc/start", "6", 75 "/kythe/loc/end", "9", 76 ), 77 }, { 78 Ticket: "kythe://c?lang=otpl?path=/a/path#27-33", 79 Fact: makeFactList( 80 "/kythe/node/kind", "anchor", 81 "/kythe/loc/start", "27", 82 "/kythe/loc/end", "33", 83 ), 84 }, { 85 Ticket: "kythe://c?lang=otpl?path=/a/path#map", 86 Fact: makeFactList("/kythe/node/kind", "function"), 87 }, { 88 Ticket: "kythe://core?lang=otpl#empty?", 89 Fact: makeFactList("/kythe/node/kind", "function"), 90 }, { 91 Ticket: "kythe://c?lang=otpl?path=/a/path#51-55", 92 Fact: makeFactList( 93 "/kythe/node/kind", "anchor", 94 "/kythe/loc/start", "51", 95 "/kythe/loc/end", "55", 96 ), 97 }, { 98 Ticket: "kythe://core?lang=otpl#cons", 99 Fact: makeFactList( 100 "/kythe/node/kind", "function", 101 // Canary to ensure we don't patch anchor facts in non-anchor nodes 102 "/kythe/loc/start", "51", 103 ), 104 }, { 105 Ticket: "kythe://c?path=/a/path", 106 Fact: makeFactList( 107 "/kythe/node/kind", "file", 108 "/kythe/text/encoding", "utf-8", 109 "/kythe/text", "some random text\nhere and \n there\nsome random text\nhere and \n there\n", 110 ), 111 }, { 112 Ticket: "kythe:?path=some/utf16/file", 113 Fact: []*cpb.Fact{{ 114 Name: "/kythe/text/encoding", 115 Value: []byte("utf-16le"), 116 }, { 117 Name: "/kythe/node/kind", 118 Value: []byte("file"), 119 }, { 120 Name: "/kythe/text", 121 Value: encodeText(utf16LE, "これはいくつかのテキストです\n"), 122 }}, 123 }, { 124 Ticket: "kythe:?path=some/utf16/file#0-4", 125 Fact: makeFactList( 126 "/kythe/node/kind", "anchor", 127 "/kythe/loc/start", "0", 128 "/kythe/loc/end", "4", 129 ), 130 }, 131 } 132 133 tbl = &testTable{ 134 Nodes: nodes, 135 EdgeSets: []*srvpb.PagedEdgeSet{ 136 { 137 TotalEdges: 3, 138 139 Source: getNode("kythe://someCorpus?lang=otpl#something"), 140 Group: []*srvpb.EdgeGroup{ 141 { 142 Kind: "someEdgeKind", 143 Edge: getEdgeTargets( 144 "kythe://someCorpus#aTicketSig", 145 ), 146 }, 147 { 148 Kind: "anotherEdge", 149 Edge: getEdgeTargets( 150 "kythe://someCorpus#aTicketSig", 151 "kythe://someCorpus?lang=otpl#signature", 152 ), 153 }, 154 }, 155 }, { 156 TotalEdges: 6, 157 158 Source: getNode("kythe://someCorpus?lang=otpl#signature"), 159 Group: []*srvpb.EdgeGroup{{ 160 Kind: "%/kythe/edge/ref", 161 Edge: getEdgeTargets( 162 "kythe://c?lang=otpl?path=/a/path#51-55", 163 "kythe:?path=some/utf16/file#0-4", 164 ), 165 }, { 166 Kind: "%/kythe/edge/defines/binding", 167 Edge: getEdgeTargets("kythe://c?lang=otpl?path=/a/path#27-33"), 168 }}, 169 170 PageIndex: []*srvpb.PageIndex{{ 171 PageKey: "firstPage", 172 EdgeKind: "someEdgeKind", 173 EdgeCount: 2, 174 }, { 175 PageKey: "secondPage", 176 EdgeKind: "anotherEdge", 177 EdgeCount: 1, 178 }}, 179 }, { 180 TotalEdges: 2, 181 182 Source: getNode("kythe:?path=some/utf16/file#0-4"), 183 Group: []*srvpb.EdgeGroup{{ 184 Kind: "/kythe/edge/ref", 185 Edge: getEdgeTargets("kythe://someCorpus?lang=otpl#signature"), 186 }}, 187 }, { 188 TotalEdges: 1, 189 190 Source: getNode("kythe:?path=some/utf16/file"), 191 }, { 192 TotalEdges: 2, 193 194 Source: getNode("kythe://c?lang=otpl?path=/a/path#51-55"), 195 Group: []*srvpb.EdgeGroup{{ 196 Kind: "/kythe/edge/ref", 197 Edge: getEdgeTargets("kythe://someCorpus?lang=otpl#signature"), 198 }}, 199 }, { 200 TotalEdges: 2, 201 202 Source: getNode("kythe://c?lang=otpl?path=/a/path#27-33"), 203 Group: []*srvpb.EdgeGroup{{ 204 Kind: "/kythe/edge/defines/binding", 205 Edge: getEdgeTargets("kythe://someCorpus?lang=otpl#signature"), 206 }}, 207 }, 208 }, 209 EdgePages: []*srvpb.EdgePage{ 210 { 211 PageKey: "orphanedEdgePage", 212 SourceTicket: "kythe://someCorpus?lang=otpl#something", 213 }, { 214 PageKey: "firstPage", 215 SourceTicket: "kythe://someCorpus?lang=otpl#signature", 216 EdgesGroup: &srvpb.EdgeGroup{ 217 Kind: "someEdgeKind", 218 Edge: getEdgeTargets( 219 "kythe://someCorpus?lang=otpl#sig3", 220 "kythe://someCorpus?lang=otpl#sig4", 221 ), 222 }, 223 }, { 224 PageKey: "secondPage", 225 SourceTicket: "kythe://someCorpus?lang=otpl#signature", 226 EdgesGroup: &srvpb.EdgeGroup{ 227 Kind: "anotherEdge", 228 Edge: getEdgeTargets( 229 "kythe://someCorpus?lang=otpl#sig2", 230 ), 231 }, 232 }, 233 }, 234 } 235 ) 236 237 func getEdgeTargets(tickets ...string) []*srvpb.EdgeGroup_Edge { 238 es := make([]*srvpb.EdgeGroup_Edge, len(tickets)) 239 for i, t := range tickets { 240 es[i] = &srvpb.EdgeGroup_Edge{ 241 Target: getNode(t), 242 Ordinal: int32(i), 243 } 244 } 245 return es 246 } 247 248 func getNode(t string) *srvpb.Node { 249 for _, n := range nodes { 250 if n.Ticket == t { 251 return n 252 } 253 } 254 return &srvpb.Node{Ticket: t} 255 } 256 257 var utf16LE = unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM) 258 259 func encodeText(e encoding.Encoding, text string) []byte { 260 res, _, err := transform.Bytes(e.NewEncoder(), []byte(text)) 261 if err != nil { 262 panic(err) 263 } 264 return res 265 } 266 267 func TestNodes(t *testing.T) { 268 st := tbl.Construct(t) 269 270 for _, node := range tbl.Nodes { 271 reply, err := st.Nodes(ctx, &gpb.NodesRequest{ 272 Ticket: []string{node.Ticket}, 273 }) 274 testutil.Fatalf(t, "NodesRequest error: %v", err) 275 276 if len(reply.Nodes) != 1 { 277 t.Fatalf("Expected 1 node for %q; found %d: {%v}", node.Ticket, len(reply.Nodes), reply) 278 } else if err := testutil.DeepEqual(nodeInfo(node), reply.Nodes[node.Ticket]); err != nil { 279 t.Fatal(err) 280 } 281 } 282 283 var tickets []string 284 expected := make(map[string]*cpb.NodeInfo) 285 for _, n := range tbl.Nodes { 286 tickets = append(tickets, n.Ticket) 287 expected[n.Ticket] = nodeInfo(n) 288 } 289 reply, err := st.Nodes(ctx, &gpb.NodesRequest{Ticket: tickets}) 290 testutil.Fatalf(t, "NodesRequest error: %v", err) 291 292 if err := testutil.DeepEqual(expected, reply.Nodes); err != nil { 293 t.Fatal(err) 294 } 295 } 296 297 func TestNodesMissing(t *testing.T) { 298 st := tbl.Construct(t) 299 reply, err := st.Nodes(ctx, &gpb.NodesRequest{ 300 Ticket: []string{"kythe:#someMissingTicket"}, 301 }) 302 testutil.Fatalf(t, "NodesRequest error: %v", err) 303 304 if len(reply.Nodes) > 0 { 305 t.Fatalf("Received unexpected reply for missing node: {%v}", reply) 306 } 307 } 308 309 func TestEdgesSinglePage(t *testing.T) { 310 tests := []struct { 311 Ticket string 312 Kinds []string 313 314 EdgeSet *srvpb.PagedEdgeSet 315 }{{ 316 Ticket: tbl.EdgeSets[0].Source.Ticket, 317 EdgeSet: tbl.EdgeSets[0], 318 }, { 319 Ticket: tbl.EdgeSets[0].Source.Ticket, 320 Kinds: []string{"someEdgeKind", "anotherEdge"}, 321 EdgeSet: tbl.EdgeSets[0], 322 }} 323 324 st := tbl.Construct(t) 325 326 for _, test := range tests { 327 reply, err := st.Edges(ctx, &gpb.EdgesRequest{ 328 Ticket: []string{test.Ticket}, 329 Kind: test.Kinds, 330 }) 331 testutil.Fatalf(t, "EdgesRequest error: %v", err) 332 333 if len(reply.Nodes) > 0 { 334 t.Errorf("Received unexpected nodes in EdgesReply: {%v}", reply) 335 } 336 if reply.NextPageToken != "" { 337 t.Errorf("Received unexpected next_page_token in EdgesReply: {%v}", reply) 338 } 339 if len(reply.EdgeSets) != 1 { 340 t.Errorf("Expected 1 EdgeSet in EdgesReply; found %d: {%v}", len(reply.EdgeSets), reply) 341 } 342 343 expected := edgeSet(test.Kinds, test.EdgeSet, nil) 344 if err := testutil.DeepEqual(expected, reply.EdgeSets[test.Ticket]); err != nil { 345 t.Error(err) 346 } 347 if err := testutil.DeepEqual(map[string]int64{ 348 "someEdgeKind": 1, 349 "anotherEdge": 2, 350 }, reply.TotalEdgesByKind); err != nil { 351 t.Error(err) 352 } 353 } 354 } 355 356 func TestEdgesLastPage(t *testing.T) { 357 tests := [][]string{ 358 nil, // all kinds 359 {"%/kythe/edge/ref"}, 360 {"%/kythe/edge/defines/binding"}, 361 } 362 363 ticket := tbl.EdgeSets[1].Source.Ticket 364 es := tbl.EdgeSets[1] 365 pages := []*srvpb.EdgePage{tbl.EdgePages[1], tbl.EdgePages[2]} 366 367 st := tbl.Construct(t) 368 369 for _, kinds := range tests { 370 reply, err := st.Edges(ctx, &gpb.EdgesRequest{ 371 Ticket: []string{ticket}, 372 Kind: kinds, 373 }) 374 testutil.Fatalf(t, "EdgesRequest error: %v", err) 375 376 if len(reply.Nodes) > 0 { 377 t.Errorf("Received unexpected nodes in EdgesReply: {%v}", reply) 378 } 379 if reply.NextPageToken != "" { 380 t.Errorf("Received unexpected next_page_token in EdgesReply: {%v}", reply) 381 } 382 if len(reply.EdgeSets) != 1 { 383 t.Fatalf("Expected 1 EdgeSet in EdgesReply; found %d: {%v}", len(reply.EdgeSets), reply) 384 } 385 386 expected := edgeSet(kinds, es, pages) 387 if err := testutil.DeepEqual(expected, reply.EdgeSets[ticket]); err != nil { 388 t.Error(err) 389 } 390 } 391 } 392 393 func TestEdgesMultiPage(t *testing.T) { 394 tests := []struct { 395 Ticket string 396 Kinds []string 397 398 EdgeSet *srvpb.PagedEdgeSet 399 Pages []*srvpb.EdgePage 400 }{{ 401 Ticket: tbl.EdgeSets[1].Source.Ticket, 402 EdgeSet: tbl.EdgeSets[1], 403 Pages: []*srvpb.EdgePage{tbl.EdgePages[1], tbl.EdgePages[2]}, 404 }} 405 406 st := tbl.Construct(t) 407 408 for _, test := range tests { 409 reply, err := st.Edges(ctx, &gpb.EdgesRequest{ 410 Ticket: []string{test.Ticket}, 411 Kind: test.Kinds, 412 }) 413 testutil.Fatalf(t, "EdgesRequest error: %v", err) 414 415 if len(reply.Nodes) > 0 { 416 t.Errorf("Received unexpected nodes in EdgesReply: {%v}", reply) 417 } 418 if reply.NextPageToken != "" { 419 t.Errorf("Received unexpected next_page_token in EdgesReply: {%v}", reply) 420 } 421 if len(reply.EdgeSets) != 1 { 422 t.Errorf("Expected 1 EdgeSet in EdgesReply; found %d: {%v}", len(reply.EdgeSets), reply) 423 } 424 425 expected := edgeSet(test.Kinds, test.EdgeSet, test.Pages) 426 if err := testutil.DeepEqual(expected, reply.EdgeSets[test.Ticket]); err != nil { 427 t.Error(err) 428 } 429 if err := testutil.DeepEqual(map[string]int64{ 430 "%/kythe/edge/defines/binding": 1, 431 "%/kythe/edge/ref": 2, 432 "someEdgeKind": 2, 433 "anotherEdge": 1, 434 }, reply.TotalEdgesByKind); err != nil { 435 t.Error(err) 436 } 437 } 438 } 439 440 func TestEdgesMissing(t *testing.T) { 441 st := tbl.Construct(t) 442 reply, err := st.Edges(ctx, &gpb.EdgesRequest{ 443 Ticket: []string{"kythe:#someMissingTicket"}, 444 }) 445 testutil.Fatalf(t, "EdgesRequest error: %v", err) 446 447 if len(reply.EdgeSets) > 0 || len(reply.Nodes) > 0 || reply.NextPageToken != "" { 448 t.Fatalf("Received unexpected reply for missing edges: {%v}", reply) 449 } 450 } 451 452 func nodeInfo(n *srvpb.Node) *cpb.NodeInfo { 453 ni := &cpb.NodeInfo{Facts: make(map[string][]byte, len(n.Fact))} 454 for _, f := range n.Fact { 455 ni.Facts[f.Name] = f.Value 456 } 457 return ni 458 } 459 460 func makeFactList(keyVals ...string) []*cpb.Fact { 461 if len(keyVals)%2 != 0 { 462 panic("makeFactList: odd number of key values") 463 } 464 facts := make([]*cpb.Fact, 0, len(keyVals)/2) 465 for i := 0; i < len(keyVals); i += 2 { 466 facts = append(facts, &cpb.Fact{ 467 Name: keyVals[i], 468 Value: []byte(keyVals[i+1]), 469 }) 470 } 471 return facts 472 } 473 474 func edgeSet(kinds []string, pes *srvpb.PagedEdgeSet, pages []*srvpb.EdgePage) *gpb.EdgeSet { 475 set := stringset.New(kinds...) 476 477 es := &gpb.EdgeSet{Groups: make(map[string]*gpb.EdgeSet_Group, len(pes.Group))} 478 for _, g := range pes.Group { 479 if set.Contains(g.Kind) || len(set) == 0 { 480 es.Groups[g.Kind] = &gpb.EdgeSet_Group{ 481 Edge: e2e(g.Edge), 482 } 483 } 484 } 485 for _, ep := range pages { 486 g := ep.EdgesGroup 487 if set.Contains(g.Kind) || len(set) == 0 { 488 es.Groups[g.Kind] = &gpb.EdgeSet_Group{ 489 Edge: e2e(g.Edge), 490 } 491 } 492 } 493 return es 494 } 495 496 type testTable struct { 497 Nodes []*srvpb.Node 498 EdgePages []*srvpb.EdgePage 499 EdgeSets []*srvpb.PagedEdgeSet 500 } 501 502 func (tbl *testTable) Construct(t *testing.T) *Table { 503 p := make(testProtoTable) 504 var tickets stringset.Set 505 for _, n := range tbl.Nodes { 506 tickets.Add(n.Ticket) 507 } 508 for _, es := range tbl.EdgeSets { 509 tickets.Discard(es.Source.Ticket) 510 testutil.Fatalf(t, "Error writing edge set: %v", p.Put(ctx, EdgeSetKey(mustFix(t, es.Source.Ticket)), es)) 511 } 512 // Fill in EdgeSets for zero-degree nodes 513 for ticket := range tickets { 514 es := &srvpb.PagedEdgeSet{ 515 Source: getNode(ticket), 516 } 517 testutil.Fatalf(t, "Error writing edge set: %v", p.Put(ctx, EdgeSetKey(mustFix(t, es.Source.Ticket)), es)) 518 } 519 for _, ep := range tbl.EdgePages { 520 testutil.Fatalf(t, "Error writing edge page: %v", p.Put(ctx, EdgePageKey(ep.PageKey), ep)) 521 } 522 return NewCombinedTable(p) 523 } 524 525 func mustFix(t *testing.T, ticket string) string { 526 ft, err := kytheuri.Fix(ticket) 527 if err != nil { 528 t.Fatalf("Error fixing ticket %q: %v", ticket, err) 529 } 530 return ft 531 } 532 533 type testProtoTable map[string]proto.Message 534 535 func (t testProtoTable) Put(_ context.Context, key []byte, val proto.Message) error { 536 t[string(key)] = val 537 return nil 538 } 539 540 func (t testProtoTable) Lookup(_ context.Context, key []byte, msg proto.Message) error { 541 m, ok := t[string(key)] 542 if !ok { 543 return table.ErrNoSuchKey 544 } 545 proto.Merge(msg, m) 546 return nil 547 } 548 549 func (t testProtoTable) LookupValues(_ context.Context, key []byte, m proto.Message, f func(proto.Message) error) error { 550 val, ok := t[string(key)] 551 if !ok { 552 return nil 553 } 554 msg := m.ProtoReflect().New().Interface() 555 proto.Merge(msg, val) 556 if err := f(msg); err != nil && err != table.ErrStopLookup { 557 return err 558 } 559 return nil 560 } 561 562 func (t testProtoTable) Buffered() table.BufferedProto { panic("UNIMPLEMENTED") } 563 564 func (t testProtoTable) Close(context.Context) error { return nil }