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  }