kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/serving/pipeline/beam_integration_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 pipeline 18 19 import ( 20 "context" 21 "testing" 22 23 "kythe.io/kythe/go/services/graph" 24 "kythe.io/kythe/go/services/xrefs" 25 gsrv "kythe.io/kythe/go/serving/graph" 26 xsrv "kythe.io/kythe/go/serving/xrefs" 27 "kythe.io/kythe/go/storage/inmemory" 28 "kythe.io/kythe/go/storage/keyvalue" 29 "kythe.io/kythe/go/util/compare" 30 "kythe.io/kythe/go/util/kytheuri" 31 "kythe.io/kythe/go/util/schema/edges" 32 "kythe.io/kythe/go/util/schema/facts" 33 "kythe.io/kythe/go/util/schema/nodes" 34 35 "github.com/apache/beam/sdks/go/pkg/beam" 36 "github.com/apache/beam/sdks/go/pkg/beam/testing/ptest" 37 "google.golang.org/protobuf/proto" 38 39 cpb "kythe.io/kythe/proto/common_go_proto" 40 gpb "kythe.io/kythe/proto/graph_go_proto" 41 scpb "kythe.io/kythe/proto/schema_go_proto" 42 spb "kythe.io/kythe/proto/storage_go_proto" 43 xpb "kythe.io/kythe/proto/xref_go_proto" 44 ) 45 46 var ctx = context.Background() 47 48 func encodeMarkedSource(ms *cpb.MarkedSource) []byte { 49 rec, err := proto.Marshal(ms) 50 if err != nil { 51 panic(err) 52 } 53 return rec 54 } 55 56 func TestServingSimpleDecorations(t *testing.T) { 57 file := &spb.VName{Path: "path"} 58 const expectedText = "some text\n" 59 testNodes := []*scpb.Node{{ 60 Source: file, 61 Kind: &scpb.Node_KytheKind{scpb.NodeKind_FILE}, 62 Fact: []*scpb.Fact{{ 63 Name: &scpb.Fact_KytheName{scpb.FactName_TEXT}, 64 Value: []byte(expectedText), 65 }, { 66 Name: &scpb.Fact_KytheName{scpb.FactName_TEXT_ENCODING}, 67 Value: []byte("ascii"), 68 }}, 69 Edge: []*scpb.Edge{{ 70 Kind: &scpb.Edge_KytheKind{scpb.EdgeKind_TAGGED}, 71 Target: &spb.VName{Signature: "diagnostic"}, 72 }}, 73 }, { 74 Source: &spb.VName{Path: "path", Signature: "anchor1"}, 75 Kind: &scpb.Node_KytheKind{scpb.NodeKind_ANCHOR}, 76 Fact: []*scpb.Fact{{ 77 Name: &scpb.Fact_KytheName{scpb.FactName_LOC_START}, 78 Value: []byte("0"), 79 }, { 80 Name: &scpb.Fact_KytheName{scpb.FactName_LOC_END}, 81 Value: []byte("4"), 82 }}, 83 Edge: []*scpb.Edge{{ 84 Kind: &scpb.Edge_KytheKind{scpb.EdgeKind_REF}, 85 Target: &spb.VName{Signature: "simpleDecor"}, 86 }}, 87 }, { 88 Source: &spb.VName{Signature: "simpleDecor"}, 89 Kind: &scpb.Node_KytheKind{scpb.NodeKind_RECORD}, 90 }, { 91 Source: &spb.VName{Path: "path", Signature: "anchor2"}, 92 Kind: &scpb.Node_KytheKind{scpb.NodeKind_ANCHOR}, 93 Fact: []*scpb.Fact{{ 94 Name: &scpb.Fact_KytheName{scpb.FactName_LOC_START}, 95 Value: []byte("5"), 96 }, { 97 Name: &scpb.Fact_KytheName{scpb.FactName_LOC_END}, 98 Value: []byte("9"), 99 }, { 100 Name: &scpb.Fact_KytheName{scpb.FactName_BUILD_CONFIG}, 101 Value: []byte("test-build-config"), 102 }}, 103 Edge: []*scpb.Edge{{ 104 Kind: &scpb.Edge_KytheKind{scpb.EdgeKind_REF}, 105 Target: &spb.VName{Signature: "decorWithDef"}, 106 }}, 107 }, { 108 Source: &spb.VName{Signature: "def1"}, 109 Kind: &scpb.Node_KytheKind{scpb.NodeKind_ANCHOR}, 110 Fact: []*scpb.Fact{{ 111 Name: &scpb.Fact_KytheName{scpb.FactName_LOC_START}, 112 Value: []byte("0"), 113 }, { 114 Name: &scpb.Fact_KytheName{scpb.FactName_LOC_END}, 115 Value: []byte("3"), 116 }}, 117 Edge: []*scpb.Edge{{ 118 Kind: &scpb.Edge_KytheKind{scpb.EdgeKind_DEFINES}, 119 Target: &spb.VName{Signature: "decorWithDef"}, 120 }}, 121 }, { 122 Source: &spb.VName{}, 123 Kind: &scpb.Node_KytheKind{scpb.NodeKind_FILE}, 124 Fact: []*scpb.Fact{{ 125 Name: &scpb.Fact_KytheName{scpb.FactName_TEXT}, 126 Value: []byte("def\n"), 127 }}, 128 }, { 129 Source: &spb.VName{Signature: "diagnostic"}, 130 Kind: &scpb.Node_KytheKind{scpb.NodeKind_DIAGNOSTIC}, 131 Fact: []*scpb.Fact{{ 132 Name: &scpb.Fact_KytheName{scpb.FactName_MESSAGE}, 133 Value: []byte("msg"), 134 }, { 135 Name: &scpb.Fact_KytheName{scpb.FactName_DETAILS}, 136 Value: []byte("dtails"), 137 }, { 138 Name: &scpb.Fact_KytheName{scpb.FactName_CONTEXT_URL}, 139 Value: []byte("https://kythe.io/schema"), 140 }}, 141 }} 142 143 p, s, nodes := ptest.CreateList(testNodes) 144 decor := FromNodes(s, nodes).SplitDecorations() 145 146 db := inmemory.NewKeyValueDB() 147 w, err := db.Writer(ctx) 148 if err != nil { 149 t.Fatal(err) 150 } 151 152 // Mark table as columnar 153 if err := w.Write([]byte(xsrv.ColumnarTableKeyMarker), []byte{}); err != nil { 154 t.Fatal(err) 155 } 156 // Write columnar data to inmemory.KeyValueDB 157 beam.ParDo(s, &writeTo{w}, decor) 158 159 if err := ptest.Run(p); err != nil { 160 t.Fatalf("Pipeline error: %+v", err) 161 } else if err := w.Close(); err != nil { 162 t.Fatal(err) 163 } 164 165 xs := xsrv.NewService(ctx, db) 166 fileTicket := kytheuri.ToString(file) 167 168 t.Run("source_text", makeDecorTestCase(ctx, xs, &xpb.DecorationsRequest{ 169 Location: &xpb.Location{Ticket: fileTicket}, 170 SourceText: true, 171 }, &xpb.DecorationsReply{ 172 Location: &xpb.Location{Ticket: fileTicket}, 173 SourceText: []byte(expectedText), 174 Encoding: "ascii", 175 })) 176 177 t.Run("references", makeDecorTestCase(ctx, xs, &xpb.DecorationsRequest{ 178 Location: &xpb.Location{Ticket: fileTicket}, 179 References: true, 180 }, &xpb.DecorationsReply{ 181 Location: &xpb.Location{Ticket: fileTicket}, 182 Reference: []*xpb.DecorationsReply_Reference{{ 183 Span: &cpb.Span{ 184 Start: &cpb.Point{ 185 LineNumber: 1, 186 }, 187 End: &cpb.Point{ 188 ByteOffset: 4, 189 ColumnOffset: 4, 190 LineNumber: 1, 191 }, 192 }, 193 Kind: "/kythe/edge/ref", 194 TargetTicket: "kythe:#simpleDecor", 195 }, { 196 Span: &cpb.Span{ 197 Start: &cpb.Point{ 198 ByteOffset: 5, 199 ColumnOffset: 5, 200 LineNumber: 1, 201 }, 202 End: &cpb.Point{ 203 ByteOffset: 9, 204 ColumnOffset: 9, 205 LineNumber: 1, 206 }, 207 }, 208 BuildConfig: "test-build-config", 209 Kind: "/kythe/edge/ref", 210 TargetTicket: "kythe:#decorWithDef", 211 // TargetDefinition: explicitly not requested 212 }}, 213 // Nodes: not requested 214 // DefinitionLocations: not requested 215 })) 216 217 t.Run("referenced_nodes", makeDecorTestCase(ctx, xs, &xpb.DecorationsRequest{ 218 Location: &xpb.Location{Ticket: fileTicket}, 219 References: true, 220 Filter: []string{"**"}, 221 }, &xpb.DecorationsReply{ 222 Location: &xpb.Location{Ticket: fileTicket}, 223 Reference: []*xpb.DecorationsReply_Reference{{ 224 Span: &cpb.Span{ 225 Start: &cpb.Point{ 226 LineNumber: 1, 227 }, 228 End: &cpb.Point{ 229 ByteOffset: 4, 230 ColumnOffset: 4, 231 LineNumber: 1, 232 }, 233 }, 234 Kind: "/kythe/edge/ref", 235 TargetTicket: "kythe:#simpleDecor", 236 }, { 237 Span: &cpb.Span{ 238 Start: &cpb.Point{ 239 ByteOffset: 5, 240 ColumnOffset: 5, 241 LineNumber: 1, 242 }, 243 End: &cpb.Point{ 244 ByteOffset: 9, 245 ColumnOffset: 9, 246 LineNumber: 1, 247 }, 248 }, 249 BuildConfig: "test-build-config", 250 Kind: "/kythe/edge/ref", 251 TargetTicket: "kythe:#decorWithDef", 252 // TargetDefinition: explicitly not requested 253 }}, 254 Nodes: map[string]*cpb.NodeInfo{ 255 "kythe:#simpleDecor": { 256 Facts: map[string][]byte{ 257 "/kythe/node/kind": []byte("record"), 258 }, 259 }, 260 }, 261 // DefinitionLocations: not requested 262 })) 263 264 t.Run("target_definitions", makeDecorTestCase(ctx, xs, &xpb.DecorationsRequest{ 265 Location: &xpb.Location{Ticket: fileTicket}, 266 References: true, 267 TargetDefinitions: true, 268 }, &xpb.DecorationsReply{ 269 Location: &xpb.Location{Ticket: fileTicket}, 270 Reference: []*xpb.DecorationsReply_Reference{{ 271 Span: &cpb.Span{ 272 Start: &cpb.Point{ 273 LineNumber: 1, 274 }, 275 End: &cpb.Point{ 276 ByteOffset: 4, 277 ColumnOffset: 4, 278 LineNumber: 1, 279 }, 280 }, 281 Kind: "/kythe/edge/ref", 282 TargetTicket: "kythe:#simpleDecor", 283 }, { 284 Span: &cpb.Span{ 285 Start: &cpb.Point{ 286 ByteOffset: 5, 287 ColumnOffset: 5, 288 LineNumber: 1, 289 }, 290 End: &cpb.Point{ 291 ByteOffset: 9, 292 ColumnOffset: 9, 293 LineNumber: 1, 294 }, 295 }, 296 BuildConfig: "test-build-config", 297 Kind: "/kythe/edge/ref", 298 TargetTicket: "kythe:#decorWithDef", 299 TargetDefinition: "kythe:#def1", // expected definition 300 }}, 301 // Nodes: not requested 302 DefinitionLocations: map[string]*xpb.Anchor{ 303 "kythe:#def1": { 304 Ticket: "kythe:#def1", 305 Parent: "kythe:", 306 Span: &cpb.Span{ 307 Start: &cpb.Point{ 308 LineNumber: 1, 309 }, 310 End: &cpb.Point{ 311 ByteOffset: 3, 312 ColumnOffset: 3, 313 LineNumber: 1, 314 }, 315 }, 316 Snippet: "def", 317 SnippetSpan: &cpb.Span{ 318 Start: &cpb.Point{ 319 LineNumber: 1, 320 }, 321 End: &cpb.Point{ 322 ByteOffset: 3, 323 ColumnOffset: 3, 324 LineNumber: 1, 325 }, 326 }, 327 }, 328 }, 329 })) 330 331 // TODO(schroederc): test diagnostics (w/ span) 332 t.Run("diagnostics", makeDecorTestCase(ctx, xs, &xpb.DecorationsRequest{ 333 Location: &xpb.Location{Ticket: fileTicket}, 334 Diagnostics: true, 335 }, &xpb.DecorationsReply{ 336 Location: &xpb.Location{Ticket: fileTicket}, 337 Diagnostic: []*cpb.Diagnostic{{ 338 Message: "msg", 339 Details: "dtails", 340 ContextUrl: "https://kythe.io/schema", 341 }}, 342 })) 343 344 // TODO(schroederc): test split file contents 345 // TODO(schroederc): test overrides 346 } 347 348 func TestServingSimpleCrossReferences(t *testing.T) { 349 src := &spb.VName{Path: "path", Signature: "signature"} 350 ms := &cpb.MarkedSource{ 351 Kind: cpb.MarkedSource_IDENTIFIER, 352 PreText: "identifier", 353 } 354 testNodes := []*scpb.Node{{ 355 Source: &spb.VName{Path: "path"}, 356 Kind: &scpb.Node_KytheKind{scpb.NodeKind_FILE}, 357 Fact: []*scpb.Fact{{ 358 Name: &scpb.Fact_KytheName{scpb.FactName_TEXT}, 359 Value: []byte("blah blah\n"), 360 }, { 361 Name: &scpb.Fact_KytheName{scpb.FactName_TEXT_ENCODING}, 362 Value: []byte("ascii"), 363 }}, 364 }, { 365 Source: &spb.VName{Path: "path", Signature: "anchor1"}, 366 Kind: &scpb.Node_KytheKind{scpb.NodeKind_ANCHOR}, 367 Fact: []*scpb.Fact{{ 368 Name: &scpb.Fact_KytheName{scpb.FactName_LOC_START}, 369 Value: []byte("5"), 370 }, { 371 Name: &scpb.Fact_KytheName{scpb.FactName_LOC_END}, 372 Value: []byte("9"), 373 }}, 374 Edge: []*scpb.Edge{{ 375 Kind: &scpb.Edge_KytheKind{scpb.EdgeKind_DEFINES_BINDING}, 376 Target: &spb.VName{Signature: "caller"}, 377 }, { 378 Kind: &scpb.Edge_KytheKind{scpb.EdgeKind_DEFINES_BINDING}, 379 Target: &spb.VName{Signature: "interface"}, 380 }, { 381 Kind: &scpb.Edge_KytheKind{scpb.EdgeKind_REF}, 382 Target: src, 383 }}, 384 }, { 385 Source: &spb.VName{Path: "path", Signature: "anchor2"}, 386 Kind: &scpb.Node_KytheKind{scpb.NodeKind_ANCHOR}, 387 Fact: []*scpb.Fact{{ 388 Name: &scpb.Fact_KytheName{scpb.FactName_LOC_START}, 389 Value: []byte("0"), 390 }, { 391 Name: &scpb.Fact_KytheName{scpb.FactName_LOC_END}, 392 Value: []byte("4"), 393 }}, 394 Edge: []*scpb.Edge{{ 395 Kind: &scpb.Edge_KytheKind{scpb.EdgeKind_CHILD_OF}, 396 Target: &spb.VName{Signature: "caller"}, 397 }, { 398 Kind: &scpb.Edge_KytheKind{scpb.EdgeKind_REF_CALL}, 399 Target: src, 400 }}, 401 }, { 402 Source: src, 403 Kind: &scpb.Node_KytheKind{scpb.NodeKind_RECORD}, 404 Fact: []*scpb.Fact{{ 405 Name: &scpb.Fact_KytheName{scpb.FactName_CODE}, 406 Value: encodeMarkedSource(ms), 407 }}, 408 Edge: []*scpb.Edge{{ 409 Kind: &scpb.Edge_KytheKind{scpb.EdgeKind_EXTENDS}, 410 Target: &spb.VName{Signature: "interface"}, 411 }}, 412 }, { 413 Source: &spb.VName{Signature: "interface"}, 414 Kind: &scpb.Node_KytheKind{scpb.NodeKind_INTERFACE}, 415 }} 416 417 p, s, rawNodes := ptest.CreateList(testNodes) 418 xrefs := FromNodes(s, rawNodes).SplitCrossReferences() 419 420 db := inmemory.NewKeyValueDB() 421 w, err := db.Writer(ctx) 422 if err != nil { 423 t.Fatal(err) 424 } 425 426 // Mark table as columnar 427 if err := w.Write([]byte(xsrv.ColumnarTableKeyMarker), []byte{}); err != nil { 428 t.Fatal(err) 429 } 430 // Write columnar data to inmemory.KeyValueDB 431 beam.ParDo(s, &writeTo{w}, xrefs) 432 433 if err := ptest.Run(p); err != nil { 434 t.Fatalf("Pipeline error: %+v", err) 435 } else if err := w.Close(); err != nil { 436 t.Fatal(err) 437 } 438 xs := xsrv.NewService(ctx, db) 439 440 ticket := kytheuri.ToString(src) 441 442 t.Run("requested_node", makeXRefTestCase(ctx, xs, &xpb.CrossReferencesRequest{ 443 Ticket: []string{ticket}, 444 Filter: []string{"**"}, 445 RelatedNodeKind: []string{"NONE"}, 446 }, &xpb.CrossReferencesReply{ 447 CrossReferences: map[string]*xpb.CrossReferencesReply_CrossReferenceSet{ 448 ticket: { 449 Ticket: ticket, 450 MarkedSource: ms, 451 }, 452 }, 453 Nodes: map[string]*cpb.NodeInfo{ 454 ticket: { 455 Facts: map[string][]byte{ 456 "/kythe/node/kind": []byte("record"), 457 458 // TODO(schroederc): ellide; MarkedSource already included 459 "/kythe/code": encodeMarkedSource(ms), 460 }, 461 }, 462 }, 463 })) 464 465 t.Run("non_call_refs", makeXRefTestCase(ctx, xs, &xpb.CrossReferencesRequest{ 466 Ticket: []string{ticket}, 467 ReferenceKind: xpb.CrossReferencesRequest_NON_CALL_REFERENCES, 468 }, &xpb.CrossReferencesReply{ 469 CrossReferences: map[string]*xpb.CrossReferencesReply_CrossReferenceSet{ 470 ticket: { 471 Ticket: ticket, 472 MarkedSource: ms, 473 Reference: []*xpb.CrossReferencesReply_RelatedAnchor{{ 474 Anchor: &xpb.Anchor{ 475 Parent: "kythe:?path=path", 476 Span: &cpb.Span{ 477 Start: &cpb.Point{ 478 ByteOffset: 5, 479 ColumnOffset: 5, 480 LineNumber: 1, 481 }, 482 End: &cpb.Point{ 483 ByteOffset: 9, 484 ColumnOffset: 9, 485 LineNumber: 1, 486 }, 487 }, 488 Snippet: "blah blah", 489 SnippetSpan: &cpb.Span{ 490 Start: &cpb.Point{ 491 LineNumber: 1, 492 }, 493 End: &cpb.Point{ 494 ByteOffset: 9, 495 ColumnOffset: 9, 496 LineNumber: 1, 497 }, 498 }, 499 }, 500 }}, 501 }, 502 }, 503 })) 504 505 t.Run("related_nodes", makeXRefTestCase(ctx, xs, &xpb.CrossReferencesRequest{ 506 Ticket: []string{ticket}, 507 Filter: []string{"**"}, 508 }, &xpb.CrossReferencesReply{ 509 CrossReferences: map[string]*xpb.CrossReferencesReply_CrossReferenceSet{ 510 ticket: { 511 Ticket: ticket, 512 MarkedSource: ms, 513 RelatedNode: []*xpb.CrossReferencesReply_RelatedNode{{ 514 Ticket: "kythe:#interface", 515 RelationKind: edges.Extends, 516 }}, 517 }, 518 }, 519 Nodes: map[string]*cpb.NodeInfo{ 520 ticket: { 521 Facts: map[string][]byte{ 522 facts.NodeKind: []byte(nodes.Record), 523 facts.Code: encodeMarkedSource(ms), 524 }, 525 }, 526 "kythe:#interface": { 527 Facts: map[string][]byte{ 528 facts.NodeKind: []byte(nodes.Interface), 529 }, 530 }, 531 }, 532 })) 533 534 t.Run("node_definitions", makeXRefTestCase(ctx, xs, &xpb.CrossReferencesRequest{ 535 Ticket: []string{ticket}, 536 Filter: []string{facts.NodeKind}, 537 NodeDefinitions: true, 538 }, &xpb.CrossReferencesReply{ 539 CrossReferences: map[string]*xpb.CrossReferencesReply_CrossReferenceSet{ 540 ticket: { 541 Ticket: ticket, 542 MarkedSource: ms, 543 RelatedNode: []*xpb.CrossReferencesReply_RelatedNode{{ 544 Ticket: "kythe:#interface", 545 RelationKind: edges.Extends, 546 }}, 547 }, 548 }, 549 Nodes: map[string]*cpb.NodeInfo{ 550 ticket: {Facts: map[string][]byte{facts.NodeKind: []byte(nodes.Record)}}, 551 "kythe:#interface": { 552 Facts: map[string][]byte{facts.NodeKind: []byte(nodes.Interface)}, 553 Definition: "kythe:?path=path#anchor1", 554 }, 555 }, 556 DefinitionLocations: map[string]*xpb.Anchor{ 557 "kythe:?path=path#anchor1": { 558 Ticket: "kythe:?path=path#anchor1", 559 Parent: "kythe:?path=path", 560 Span: &cpb.Span{ 561 Start: &cpb.Point{ 562 ByteOffset: 5, 563 ColumnOffset: 5, 564 LineNumber: 1, 565 }, 566 End: &cpb.Point{ 567 ByteOffset: 9, 568 ColumnOffset: 9, 569 LineNumber: 1, 570 }, 571 }, 572 Snippet: "blah blah", 573 SnippetSpan: &cpb.Span{ 574 Start: &cpb.Point{ 575 LineNumber: 1, 576 }, 577 End: &cpb.Point{ 578 ByteOffset: 9, 579 ColumnOffset: 9, 580 LineNumber: 1, 581 }, 582 }, 583 }, 584 }, 585 })) 586 587 t.Run("call_refs", makeXRefTestCase(ctx, xs, &xpb.CrossReferencesRequest{ 588 Ticket: []string{ticket}, 589 ReferenceKind: xpb.CrossReferencesRequest_CALL_REFERENCES, 590 }, &xpb.CrossReferencesReply{ 591 CrossReferences: map[string]*xpb.CrossReferencesReply_CrossReferenceSet{ 592 ticket: { 593 Ticket: ticket, 594 MarkedSource: ms, 595 Reference: []*xpb.CrossReferencesReply_RelatedAnchor{{ 596 Anchor: &xpb.Anchor{ 597 Parent: "kythe:?path=path", 598 Span: &cpb.Span{ 599 Start: &cpb.Point{ 600 ByteOffset: 0, 601 ColumnOffset: 0, 602 LineNumber: 1, 603 }, 604 End: &cpb.Point{ 605 ByteOffset: 4, 606 ColumnOffset: 4, 607 LineNumber: 1, 608 }, 609 }, 610 Snippet: "blah blah", 611 SnippetSpan: &cpb.Span{ 612 Start: &cpb.Point{ 613 LineNumber: 1, 614 }, 615 End: &cpb.Point{ 616 ByteOffset: 9, 617 ColumnOffset: 9, 618 LineNumber: 1, 619 }, 620 }, 621 }, 622 }}, 623 }, 624 }, 625 })) 626 627 t.Run("direct_callers", makeXRefTestCase(ctx, xs, &xpb.CrossReferencesRequest{ 628 Ticket: []string{ticket}, 629 CallerKind: xpb.CrossReferencesRequest_DIRECT_CALLERS, 630 }, &xpb.CrossReferencesReply{ 631 CrossReferences: map[string]*xpb.CrossReferencesReply_CrossReferenceSet{ 632 ticket: { 633 Ticket: ticket, 634 MarkedSource: ms, 635 Caller: []*xpb.CrossReferencesReply_RelatedAnchor{{ 636 Ticket: "kythe:#caller", 637 Anchor: &xpb.Anchor{ 638 Parent: "kythe:?path=path", 639 Span: &cpb.Span{ 640 Start: &cpb.Point{ 641 ByteOffset: 5, 642 ColumnOffset: 5, 643 LineNumber: 1, 644 }, 645 End: &cpb.Point{ 646 ByteOffset: 9, 647 ColumnOffset: 9, 648 LineNumber: 1, 649 }, 650 }, 651 Snippet: "blah blah", 652 SnippetSpan: &cpb.Span{ 653 Start: &cpb.Point{ 654 LineNumber: 1, 655 }, 656 End: &cpb.Point{ 657 ByteOffset: 9, 658 ColumnOffset: 9, 659 LineNumber: 1, 660 }, 661 }, 662 }, 663 Site: []*xpb.Anchor{{ 664 Parent: "kythe:?path=path", 665 Span: &cpb.Span{ 666 Start: &cpb.Point{ 667 ByteOffset: 0, 668 ColumnOffset: 0, 669 LineNumber: 1, 670 }, 671 End: &cpb.Point{ 672 ByteOffset: 4, 673 ColumnOffset: 4, 674 LineNumber: 1, 675 }, 676 }, 677 Snippet: "blah blah", 678 SnippetSpan: &cpb.Span{ 679 Start: &cpb.Point{ 680 LineNumber: 1, 681 }, 682 End: &cpb.Point{ 683 ByteOffset: 9, 684 ColumnOffset: 9, 685 LineNumber: 1, 686 }, 687 }, 688 }}, 689 }}, 690 }, 691 }, 692 })) 693 694 // TODO(schroederc): add override caller 695 t.Run("override_callers", makeXRefTestCase(ctx, xs, &xpb.CrossReferencesRequest{ 696 Ticket: []string{ticket}, 697 CallerKind: xpb.CrossReferencesRequest_OVERRIDE_CALLERS, 698 }, &xpb.CrossReferencesReply{ 699 CrossReferences: map[string]*xpb.CrossReferencesReply_CrossReferenceSet{ 700 ticket: { 701 Ticket: ticket, 702 MarkedSource: ms, 703 Caller: []*xpb.CrossReferencesReply_RelatedAnchor{{ 704 Ticket: "kythe:#caller", 705 Anchor: &xpb.Anchor{ 706 Parent: "kythe:?path=path", 707 Span: &cpb.Span{ 708 Start: &cpb.Point{ 709 ByteOffset: 5, 710 ColumnOffset: 5, 711 LineNumber: 1, 712 }, 713 End: &cpb.Point{ 714 ByteOffset: 9, 715 ColumnOffset: 9, 716 LineNumber: 1, 717 }, 718 }, 719 Snippet: "blah blah", 720 SnippetSpan: &cpb.Span{ 721 Start: &cpb.Point{ 722 LineNumber: 1, 723 }, 724 End: &cpb.Point{ 725 ByteOffset: 9, 726 ColumnOffset: 9, 727 LineNumber: 1, 728 }, 729 }, 730 }, 731 Site: []*xpb.Anchor{{ 732 Parent: "kythe:?path=path", 733 Span: &cpb.Span{ 734 Start: &cpb.Point{ 735 ByteOffset: 0, 736 ColumnOffset: 0, 737 LineNumber: 1, 738 }, 739 End: &cpb.Point{ 740 ByteOffset: 4, 741 ColumnOffset: 4, 742 LineNumber: 1, 743 }, 744 }, 745 Snippet: "blah blah", 746 SnippetSpan: &cpb.Span{ 747 Start: &cpb.Point{ 748 LineNumber: 1, 749 }, 750 End: &cpb.Point{ 751 ByteOffset: 9, 752 ColumnOffset: 9, 753 LineNumber: 1, 754 }, 755 }, 756 }}, 757 }}, 758 }, 759 }, 760 })) 761 } 762 763 func TestServingSimpleEdges(t *testing.T) { 764 src := &spb.VName{Path: "path", Signature: "signature"} 765 testNodes := []*scpb.Node{{ 766 Source: &spb.VName{Path: "path"}, 767 Kind: &scpb.Node_KytheKind{scpb.NodeKind_FILE}, 768 Fact: []*scpb.Fact{{ 769 Name: &scpb.Fact_KytheName{scpb.FactName_TEXT}, 770 Value: []byte("blah blah\n"), 771 }, { 772 Name: &scpb.Fact_KytheName{scpb.FactName_TEXT_ENCODING}, 773 Value: []byte("ascii"), 774 }}, 775 }, { 776 Source: &spb.VName{Path: "path", Signature: "anchor1"}, 777 Kind: &scpb.Node_KytheKind{scpb.NodeKind_ANCHOR}, 778 Fact: []*scpb.Fact{{ 779 Name: &scpb.Fact_KytheName{scpb.FactName_LOC_START}, 780 Value: []byte("5"), 781 }, { 782 Name: &scpb.Fact_KytheName{scpb.FactName_LOC_END}, 783 Value: []byte("9"), 784 }}, 785 Edge: []*scpb.Edge{{ 786 Kind: &scpb.Edge_KytheKind{scpb.EdgeKind_REF}, 787 Target: src, 788 }}, 789 }, { 790 Source: src, 791 Kind: &scpb.Node_KytheKind{scpb.NodeKind_RECORD}, 792 Subkind: &scpb.Node_KytheSubkind{scpb.Subkind_CLASS}, 793 Edge: []*scpb.Edge{{ 794 Kind: &scpb.Edge_KytheKind{scpb.EdgeKind_EXTENDS}, 795 Target: &spb.VName{Signature: "interface1"}, 796 }, { 797 Kind: &scpb.Edge_KytheKind{scpb.EdgeKind_EXTENDS}, 798 Target: &spb.VName{Signature: "interface2"}, 799 }}, 800 }, { 801 Source: &spb.VName{Signature: "child"}, 802 Kind: &scpb.Node_KytheKind{scpb.NodeKind_RECORD}, 803 Subkind: &scpb.Node_KytheSubkind{scpb.Subkind_CLASS}, 804 Edge: []*scpb.Edge{{ 805 Kind: &scpb.Edge_KytheKind{scpb.EdgeKind_CHILD_OF}, 806 Target: src, 807 }}, 808 }, { 809 Source: &spb.VName{Signature: "interface"}, 810 Kind: &scpb.Node_KytheKind{scpb.NodeKind_INTERFACE}, 811 }} 812 813 p, s, rawNodes := ptest.CreateList(testNodes) 814 edgeEntries := FromNodes(s, rawNodes).SplitEdges() 815 816 db := inmemory.NewKeyValueDB() 817 w, err := db.Writer(ctx) 818 if err != nil { 819 t.Fatal(err) 820 } 821 822 // Mark table as columnar 823 if err := w.Write([]byte(gsrv.ColumnarTableKeyMarker), []byte{}); err != nil { 824 t.Fatal(err) 825 } 826 // Write columnar data to inmemory.KeyValueDB 827 beam.ParDo(s, &writeTo{w}, edgeEntries) 828 829 if err := ptest.Run(p); err != nil { 830 t.Fatalf("Pipeline error: %+v", err) 831 } else if err := w.Close(); err != nil { 832 t.Fatal(err) 833 } 834 835 gs := gsrv.NewService(ctx, db) 836 ticket := kytheuri.ToString(src) 837 838 t.Run("source_node", makeEdgesTestCase(ctx, gs, &gpb.EdgesRequest{ 839 Ticket: []string{ticket}, 840 Kind: []string{"non_existent_kind"}, 841 Filter: []string{"**"}, 842 }, &gpb.EdgesReply{ 843 Nodes: map[string]*cpb.NodeInfo{ 844 ticket: &cpb.NodeInfo{ 845 Facts: map[string][]byte{ 846 facts.NodeKind: []byte(nodes.Record), 847 facts.Subkind: []byte(nodes.Class), 848 }, 849 }, 850 }, 851 })) 852 853 t.Run("edges", makeEdgesTestCase(ctx, gs, &gpb.EdgesRequest{ 854 Ticket: []string{ticket}, 855 }, &gpb.EdgesReply{ 856 EdgeSets: map[string]*gpb.EdgeSet{ 857 ticket: { 858 Groups: map[string]*gpb.EdgeSet_Group{ 859 edges.Extends: { 860 Edge: []*gpb.EdgeSet_Group_Edge{{ 861 TargetTicket: "kythe:#interface1", 862 }, { 863 TargetTicket: "kythe:#interface2", 864 }}, 865 }, 866 "%" + edges.ChildOf: { 867 Edge: []*gpb.EdgeSet_Group_Edge{{ 868 TargetTicket: "kythe:#child", 869 }}, 870 }, 871 }, 872 }, 873 }, 874 })) 875 876 t.Run("edge_targets", makeEdgesTestCase(ctx, gs, &gpb.EdgesRequest{ 877 Ticket: []string{ticket}, 878 Filter: []string{facts.NodeKind}, 879 }, &gpb.EdgesReply{ 880 EdgeSets: map[string]*gpb.EdgeSet{ 881 ticket: { 882 Groups: map[string]*gpb.EdgeSet_Group{ 883 edges.Extends: { 884 Edge: []*gpb.EdgeSet_Group_Edge{{ 885 TargetTicket: "kythe:#interface1", 886 }, { 887 TargetTicket: "kythe:#interface2", 888 }}, 889 }, 890 "%" + edges.ChildOf: { 891 Edge: []*gpb.EdgeSet_Group_Edge{{ 892 TargetTicket: "kythe:#child", 893 }}, 894 }, 895 }, 896 }, 897 }, 898 Nodes: map[string]*cpb.NodeInfo{ 899 ticket: &cpb.NodeInfo{Facts: map[string][]byte{facts.NodeKind: []byte(nodes.Record)}}, 900 "kythe:#child": &cpb.NodeInfo{Facts: map[string][]byte{facts.NodeKind: []byte(nodes.Record)}}, 901 }, 902 })) 903 } 904 905 type writeTo struct{ w keyvalue.Writer } 906 907 func (p *writeTo) ProcessElement(ctx context.Context, k, v []byte, emit func([]byte)) error { 908 return p.w.Write(k, v) 909 } 910 911 func makeDecorTestCase(ctx context.Context, xs xrefs.Service, req *xpb.DecorationsRequest, expected *xpb.DecorationsReply) func(*testing.T) { 912 return func(t *testing.T) { 913 reply, err := xs.Decorations(ctx, req) 914 if err != nil { 915 t.Fatalf("Decorations error: %v", err) 916 } 917 if diff := compare.ProtoDiff(expected, reply); diff != "" { 918 t.Fatalf("DecorationsReply differences: (- expected; + found)\n%s", diff) 919 } 920 } 921 } 922 923 func makeXRefTestCase(ctx context.Context, xs xrefs.Service, req *xpb.CrossReferencesRequest, expected *xpb.CrossReferencesReply) func(*testing.T) { 924 return func(t *testing.T) { 925 reply, err := xs.CrossReferences(ctx, req) 926 if err != nil { 927 t.Fatalf("CrossReferences error: %v", err) 928 } 929 if diff := compare.ProtoDiff(expected, reply); diff != "" { 930 t.Fatalf("CrossReferencesReply differences: (- expected; + found)\n%s", diff) 931 } 932 } 933 } 934 935 func makeEdgesTestCase(ctx context.Context, gs graph.Service, req *gpb.EdgesRequest, expected *gpb.EdgesReply) func(*testing.T) { 936 return func(t *testing.T) { 937 reply, err := gs.Edges(ctx, req) 938 if err != nil { 939 t.Fatalf("Edges error: %v", err) 940 } 941 if diff := compare.ProtoDiff(expected, reply); diff != "" { 942 t.Fatalf("EdgesReply differences: (- expected; + found)\n%s", diff) 943 } 944 } 945 }