kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/serving/xrefs/columnar_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 xrefs
    18  
    19  import (
    20  	"context"
    21  	"testing"
    22  
    23  	"kythe.io/kythe/go/services/xrefs"
    24  	"kythe.io/kythe/go/serving/xrefs/columnar"
    25  	"kythe.io/kythe/go/storage/inmemory"
    26  	"kythe.io/kythe/go/storage/keyvalue"
    27  	"kythe.io/kythe/go/util/compare"
    28  	"kythe.io/kythe/go/util/kytheuri"
    29  	"kythe.io/kythe/go/util/schema/facts"
    30  
    31  	cpb "kythe.io/kythe/proto/common_go_proto"
    32  	scpb "kythe.io/kythe/proto/schema_go_proto"
    33  	srvpb "kythe.io/kythe/proto/serving_go_proto"
    34  	spb "kythe.io/kythe/proto/storage_go_proto"
    35  	xpb "kythe.io/kythe/proto/xref_go_proto"
    36  	xspb "kythe.io/kythe/proto/xref_serving_go_proto"
    37  )
    38  
    39  func mustWriteDecor(t *testing.T, w keyvalue.Writer, fd *xspb.FileDecorations) {
    40  	kv, err := columnar.EncodeDecorationsEntry(columnar.DecorationsKeyPrefix, fd)
    41  	if err != nil {
    42  		t.Fatal(err)
    43  	}
    44  	mustWrite(t, w, kv.Key, kv.Value)
    45  }
    46  
    47  func mustWriteXRef(t *testing.T, w keyvalue.Writer, fd *xspb.CrossReferences) {
    48  	kv, err := columnar.EncodeCrossReferencesEntry(columnar.CrossReferencesKeyPrefix, fd)
    49  	if err != nil {
    50  		t.Fatal(err)
    51  	}
    52  	mustWrite(t, w, kv.Key, kv.Value)
    53  }
    54  
    55  func mustWrite(t *testing.T, w keyvalue.Writer, key, val []byte) {
    56  	if err := w.Write(key, val); err != nil {
    57  		t.Fatal(err)
    58  	}
    59  }
    60  
    61  func TestServingDecorations(t *testing.T) {
    62  	ctx := context.Background()
    63  	db := inmemory.NewKeyValueDB()
    64  	w, err := db.Writer(ctx)
    65  	if err != nil {
    66  		t.Fatal(err)
    67  	}
    68  
    69  	// Mark table as columnar
    70  	mustWrite(t, w, []byte(ColumnarTableKeyMarker), []byte{})
    71  
    72  	file := &spb.VName{Path: "path"}
    73  	const expectedText = "some text\n"
    74  	decor := []*xspb.FileDecorations{{
    75  		File: file,
    76  		Entry: &xspb.FileDecorations_Index_{&xspb.FileDecorations_Index{
    77  			TextEncoding: "ascii",
    78  		}},
    79  	}, {
    80  		File: file,
    81  		Entry: &xspb.FileDecorations_Text_{&xspb.FileDecorations_Text{
    82  			Text: []byte(expectedText),
    83  		}},
    84  	}, {
    85  		File: file,
    86  		Entry: &xspb.FileDecorations_Target_{&xspb.FileDecorations_Target{
    87  			StartOffset: 0,
    88  			EndOffset:   4,
    89  			Kind: &xspb.FileDecorations_Target_KytheKind{
    90  				scpb.EdgeKind_REF,
    91  			},
    92  			Target: &spb.VName{Signature: "simpleDecor"},
    93  		}},
    94  	}, {
    95  		File: file,
    96  		Entry: &xspb.FileDecorations_TargetNode_{&xspb.FileDecorations_TargetNode{
    97  			Node: &scpb.Node{
    98  				Source: &spb.VName{Signature: "simpleDecor"},
    99  				Kind:   &scpb.Node_KytheKind{scpb.NodeKind_RECORD},
   100  			},
   101  		}},
   102  	}, {
   103  		File: file,
   104  		Entry: &xspb.FileDecorations_Target_{&xspb.FileDecorations_Target{
   105  			StartOffset: 5,
   106  			EndOffset:   9,
   107  			Kind: &xspb.FileDecorations_Target_KytheKind{
   108  				scpb.EdgeKind_REF,
   109  			},
   110  			Target: &spb.VName{Signature: "decorWithDef"},
   111  		}},
   112  	}, {
   113  		File: file,
   114  		Entry: &xspb.FileDecorations_TargetDefinition_{&xspb.FileDecorations_TargetDefinition{
   115  			Target:     &spb.VName{Signature: "decorWithDef"},
   116  			Definition: &spb.VName{Signature: "def1"},
   117  		}},
   118  	}, {
   119  		File: file,
   120  		Entry: &xspb.FileDecorations_DefinitionLocation_{&xspb.FileDecorations_DefinitionLocation{
   121  			Location: &srvpb.ExpandedAnchor{
   122  				Ticket: "kythe:#def1",
   123  			},
   124  		}},
   125  	}}
   126  	for _, fd := range decor {
   127  		mustWriteDecor(t, w, fd)
   128  	}
   129  
   130  	if err := w.Close(); err != nil {
   131  		t.Fatal(err)
   132  	}
   133  
   134  	xs := NewService(ctx, db)
   135  
   136  	fileTicket := kytheuri.ToString(file)
   137  	t.Run("file_location", func(t *testing.T) {
   138  		reply, err := xs.Decorations(ctx, &xpb.DecorationsRequest{
   139  			Location:   &xpb.Location{Ticket: fileTicket},
   140  			SourceText: true,
   141  		})
   142  		if err != nil {
   143  			t.Fatalf("Decorations error: %v", err)
   144  		}
   145  
   146  		expectedLoc := &xpb.Location{Ticket: fileTicket}
   147  		if diff := compare.ProtoDiff(expectedLoc, reply.Location); diff != "" {
   148  			t.Fatalf("Location differences: (- expected; + found)\n%s", diff)
   149  		}
   150  	})
   151  
   152  	t.Run("source_text", makeDecorTestCase(ctx, xs, &xpb.DecorationsRequest{
   153  		Location:   &xpb.Location{Ticket: fileTicket},
   154  		SourceText: true,
   155  	}, &xpb.DecorationsReply{
   156  		Location:   &xpb.Location{Ticket: fileTicket},
   157  		SourceText: []byte(expectedText),
   158  		Encoding:   "ascii",
   159  	}))
   160  
   161  	t.Run("references", makeDecorTestCase(ctx, xs, &xpb.DecorationsRequest{
   162  		Location:   &xpb.Location{Ticket: fileTicket},
   163  		References: true,
   164  	}, &xpb.DecorationsReply{
   165  		Location: &xpb.Location{Ticket: fileTicket},
   166  		Reference: []*xpb.DecorationsReply_Reference{{
   167  			Span: &cpb.Span{
   168  				Start: &cpb.Point{
   169  					LineNumber: 1,
   170  				},
   171  				End: &cpb.Point{
   172  					ByteOffset:   4,
   173  					ColumnOffset: 4,
   174  					LineNumber:   1,
   175  				},
   176  			},
   177  			Kind:         "/kythe/edge/ref",
   178  			TargetTicket: "kythe:#simpleDecor",
   179  		}, {
   180  			Span: &cpb.Span{
   181  				Start: &cpb.Point{
   182  					ByteOffset:   5,
   183  					ColumnOffset: 5,
   184  					LineNumber:   1,
   185  				},
   186  				End: &cpb.Point{
   187  					ByteOffset:   9,
   188  					ColumnOffset: 9,
   189  					LineNumber:   1,
   190  				},
   191  			},
   192  			Kind:         "/kythe/edge/ref",
   193  			TargetTicket: "kythe:#decorWithDef",
   194  			// TargetDefinition: explicitly not requested
   195  		}},
   196  		// Nodes: not requested
   197  		// DefinitionLocations: not requested
   198  	}))
   199  
   200  	t.Run("references_dirty", makeDecorTestCase(ctx, xs, &xpb.DecorationsRequest{
   201  		Location:    &xpb.Location{Ticket: fileTicket},
   202  		References:  true,
   203  		DirtyBuffer: []byte("\n " + expectedText),
   204  	}, &xpb.DecorationsReply{
   205  		Location: &xpb.Location{Ticket: fileTicket},
   206  		Reference: []*xpb.DecorationsReply_Reference{{
   207  			Span: &cpb.Span{
   208  				Start: &cpb.Point{
   209  					ByteOffset:   0 + 2,
   210  					ColumnOffset: 0 + 1,
   211  					LineNumber:   1 + 1,
   212  				},
   213  				End: &cpb.Point{
   214  					ByteOffset:   4 + 2,
   215  					ColumnOffset: 4 + 1,
   216  					LineNumber:   1 + 1,
   217  				},
   218  			},
   219  			Kind:         "/kythe/edge/ref",
   220  			TargetTicket: "kythe:#simpleDecor",
   221  		}, {
   222  			Span: &cpb.Span{
   223  				Start: &cpb.Point{
   224  					ByteOffset:   5 + 2,
   225  					ColumnOffset: 5 + 1,
   226  					LineNumber:   1 + 1,
   227  				},
   228  				End: &cpb.Point{
   229  					ByteOffset:   9 + 2,
   230  					ColumnOffset: 9 + 1,
   231  					LineNumber:   1 + 1,
   232  				},
   233  			},
   234  			Kind:         "/kythe/edge/ref",
   235  			TargetTicket: "kythe:#decorWithDef",
   236  			// TargetDefinition: explicitly not requested
   237  		}},
   238  		// Nodes: not requested
   239  		// DefinitionLocations: not requested
   240  	}))
   241  
   242  	t.Run("referenced_nodes", makeDecorTestCase(ctx, xs, &xpb.DecorationsRequest{
   243  		Location:   &xpb.Location{Ticket: fileTicket},
   244  		References: true,
   245  		Filter:     []string{"**"},
   246  	}, &xpb.DecorationsReply{
   247  		Location: &xpb.Location{Ticket: fileTicket},
   248  		Reference: []*xpb.DecorationsReply_Reference{{
   249  			Span: &cpb.Span{
   250  				Start: &cpb.Point{
   251  					LineNumber: 1,
   252  				},
   253  				End: &cpb.Point{
   254  					ByteOffset:   4,
   255  					ColumnOffset: 4,
   256  					LineNumber:   1,
   257  				},
   258  			},
   259  			Kind:         "/kythe/edge/ref",
   260  			TargetTicket: "kythe:#simpleDecor",
   261  		}, {
   262  			Span: &cpb.Span{
   263  				Start: &cpb.Point{
   264  					ByteOffset:   5,
   265  					ColumnOffset: 5,
   266  					LineNumber:   1,
   267  				},
   268  				End: &cpb.Point{
   269  					ByteOffset:   9,
   270  					ColumnOffset: 9,
   271  					LineNumber:   1,
   272  				},
   273  			},
   274  			Kind:         "/kythe/edge/ref",
   275  			TargetTicket: "kythe:#decorWithDef",
   276  			// TargetDefinition: explicitly not requested
   277  		}},
   278  		Nodes: map[string]*cpb.NodeInfo{
   279  			"kythe:#simpleDecor": {
   280  				Facts: map[string][]byte{
   281  					"/kythe/node/kind": []byte("record"),
   282  				},
   283  			},
   284  		},
   285  		// DefinitionLocations: not requested
   286  	}))
   287  
   288  	t.Run("target_definitions", makeDecorTestCase(ctx, xs, &xpb.DecorationsRequest{
   289  		Location:          &xpb.Location{Ticket: fileTicket},
   290  		References:        true,
   291  		TargetDefinitions: true,
   292  	}, &xpb.DecorationsReply{
   293  		Location: &xpb.Location{Ticket: fileTicket},
   294  		Reference: []*xpb.DecorationsReply_Reference{{
   295  			Span: &cpb.Span{
   296  				Start: &cpb.Point{
   297  					LineNumber: 1,
   298  				},
   299  				End: &cpb.Point{
   300  					ByteOffset:   4,
   301  					ColumnOffset: 4,
   302  					LineNumber:   1,
   303  				},
   304  			},
   305  			Kind:         "/kythe/edge/ref",
   306  			TargetTicket: "kythe:#simpleDecor",
   307  		}, {
   308  			Span: &cpb.Span{
   309  				Start: &cpb.Point{
   310  					ByteOffset:   5,
   311  					ColumnOffset: 5,
   312  					LineNumber:   1,
   313  				},
   314  				End: &cpb.Point{
   315  					ByteOffset:   9,
   316  					ColumnOffset: 9,
   317  					LineNumber:   1,
   318  				},
   319  			},
   320  			Kind:             "/kythe/edge/ref",
   321  			TargetTicket:     "kythe:#decorWithDef",
   322  			TargetDefinition: "kythe:#def1", // expected definition
   323  		}},
   324  		// Nodes: not requested
   325  		DefinitionLocations: map[string]*xpb.Anchor{
   326  			"kythe:#def1": {
   327  				Ticket: "kythe:#def1",
   328  				Parent: "kythe:",
   329  			},
   330  		},
   331  	}))
   332  
   333  	// TODO(schroederc): test split file contents
   334  	// TODO(schroederc): test overrides
   335  	// TODO(schroederc): test diagnostics (w/ or w/o span)
   336  }
   337  
   338  func makeDecorTestCase(ctx context.Context, xs xrefs.Service, req *xpb.DecorationsRequest, expected *xpb.DecorationsReply) func(*testing.T) {
   339  	return func(t *testing.T) {
   340  		reply, err := xs.Decorations(ctx, req)
   341  		if err != nil {
   342  			t.Fatalf("Decorations error: %v", err)
   343  		}
   344  		if diff := compare.ProtoDiff(expected, reply); diff != "" {
   345  			t.Fatalf("DecorationsReply differences: (- expected; + found)\n%s", diff)
   346  		}
   347  	}
   348  }
   349  
   350  func TestServingCrossReferences(t *testing.T) {
   351  	ctx := context.Background()
   352  	db := inmemory.NewKeyValueDB()
   353  	w, err := db.Writer(ctx)
   354  	if err != nil {
   355  		t.Fatal(err)
   356  	}
   357  
   358  	// Mark table as columnar
   359  	mustWrite(t, w, []byte(ColumnarTableKeyMarker), []byte{})
   360  
   361  	src := &spb.VName{Path: "path", Signature: "signature"}
   362  	span := &cpb.Span{
   363  		Start: &cpb.Point{
   364  			ByteOffset:   5,
   365  			ColumnOffset: 5,
   366  			LineNumber:   1,
   367  		},
   368  		End: &cpb.Point{
   369  			ByteOffset:   9,
   370  			ColumnOffset: 9,
   371  			LineNumber:   1,
   372  		},
   373  	}
   374  	ms := &cpb.MarkedSource{
   375  		Kind:    cpb.MarkedSource_IDENTIFIER,
   376  		PreText: "identifier",
   377  	}
   378  	xrefs := []*xspb.CrossReferences{{
   379  		Source: src,
   380  		Entry: &xspb.CrossReferences_Index_{&xspb.CrossReferences_Index{
   381  			Node: &scpb.Node{
   382  				Kind: &scpb.Node_KytheKind{scpb.NodeKind_RECORD},
   383  			},
   384  			MarkedSource: ms,
   385  		}},
   386  	}, {
   387  		Source: src,
   388  		Entry: &xspb.CrossReferences_Reference_{&xspb.CrossReferences_Reference{
   389  			Kind: &xspb.CrossReferences_Reference_KytheKind{scpb.EdgeKind_REF},
   390  			Location: &srvpb.ExpandedAnchor{
   391  				Ticket: "kythe:?path=path1#ref1",
   392  				Span:   span,
   393  			},
   394  		}},
   395  	}, {
   396  		Source: src,
   397  		Entry: &xspb.CrossReferences_Reference_{&xspb.CrossReferences_Reference{
   398  			Kind: &xspb.CrossReferences_Reference_KytheKind{scpb.EdgeKind_REF_CALL},
   399  			Location: &srvpb.ExpandedAnchor{
   400  				Ticket: "kythe:?path=path2#ref2",
   401  				Span:   span,
   402  			},
   403  		}},
   404  	}, {
   405  		Source: src,
   406  		Entry: &xspb.CrossReferences_Reference_{&xspb.CrossReferences_Reference{
   407  			Kind: &xspb.CrossReferences_Reference_KytheKind{scpb.EdgeKind_DEFINES},
   408  			Location: &srvpb.ExpandedAnchor{
   409  				Ticket: "kythe:?path=path1#def1",
   410  				Span:   span,
   411  			},
   412  		}},
   413  	}, {
   414  		Source: src,
   415  		Entry: &xspb.CrossReferences_Reference_{&xspb.CrossReferences_Reference{
   416  			Kind: &xspb.CrossReferences_Reference_KytheKind{scpb.EdgeKind_DEFINES_BINDING},
   417  			Location: &srvpb.ExpandedAnchor{
   418  				Ticket: "kythe:?path=path2#def2",
   419  				Span:   span,
   420  			},
   421  		}},
   422  	}, {
   423  		Source: src,
   424  		Entry: &xspb.CrossReferences_Reference_{&xspb.CrossReferences_Reference{
   425  			Kind: &xspb.CrossReferences_Reference_GenericKind{"#internal/ref/declare"},
   426  			Location: &srvpb.ExpandedAnchor{
   427  				Ticket: "kythe:?path=path1#decl1",
   428  				Span:   span,
   429  			},
   430  		}},
   431  	}, {
   432  		Source: src,
   433  		Entry: &xspb.CrossReferences_Reference_{&xspb.CrossReferences_Reference{
   434  			Kind: &xspb.CrossReferences_Reference_GenericKind{"#internal/ref/declare"},
   435  			Location: &srvpb.ExpandedAnchor{
   436  				Ticket: "kythe:?path=path2#decl2",
   437  				Span:   span,
   438  			},
   439  		}},
   440  	}, {
   441  		Source: src,
   442  		Entry: &xspb.CrossReferences_Caller_{&xspb.CrossReferences_Caller{
   443  			Caller: &spb.VName{Signature: "caller"},
   444  			Location: &srvpb.ExpandedAnchor{
   445  				Ticket: "kythe:?path=path#caller",
   446  				Span:   span,
   447  			},
   448  			MarkedSource: &cpb.MarkedSource{Kind: cpb.MarkedSource_IDENTIFIER, PreText: "caller"},
   449  		}},
   450  	}, {
   451  		Source: src,
   452  		Entry: &xspb.CrossReferences_Callsite_{&xspb.CrossReferences_Callsite{
   453  			Caller: &spb.VName{Signature: "caller"},
   454  			Kind:   xspb.CrossReferences_Callsite_DIRECT,
   455  			Location: &srvpb.ExpandedAnchor{
   456  				Ticket: "kythe:?path=path2#callsite",
   457  				Span:   span,
   458  			},
   459  		}},
   460  	}, {
   461  		Source: src,
   462  		Entry: &xspb.CrossReferences_Callsite_{&xspb.CrossReferences_Callsite{
   463  			Caller: &spb.VName{Signature: "caller"},
   464  			Kind:   xspb.CrossReferences_Callsite_OVERRIDE,
   465  			Location: &srvpb.ExpandedAnchor{
   466  				Ticket: "kythe:?path=path3#callsite_override",
   467  				Span:   span,
   468  			},
   469  		}},
   470  	}, {
   471  		Source: src,
   472  		Entry: &xspb.CrossReferences_Relation_{&xspb.CrossReferences_Relation{
   473  			Node:    &spb.VName{Signature: "relatedNode"},
   474  			Kind:    &xspb.CrossReferences_Relation_KytheKind{scpb.EdgeKind_CHILD_OF},
   475  			Reverse: true,
   476  		}},
   477  	}, {
   478  		Source: src,
   479  		Entry: &xspb.CrossReferences_RelatedNode_{&xspb.CrossReferences_RelatedNode{
   480  			Node: &scpb.Node{
   481  				Source: &spb.VName{Signature: "relatedNode"},
   482  				Kind:   &scpb.Node_KytheKind{scpb.NodeKind_FUNCTION},
   483  			},
   484  		}},
   485  	}, {
   486  		Source: src,
   487  		Entry: &xspb.CrossReferences_NodeDefinition_{&xspb.CrossReferences_NodeDefinition{
   488  			Node: &spb.VName{Signature: "relatedNode"},
   489  			Location: &srvpb.ExpandedAnchor{
   490  				Ticket: "kythe:#relatedNodeDef",
   491  				Span:   span,
   492  			},
   493  		}},
   494  	}}
   495  	for _, xr := range xrefs {
   496  		mustWriteXRef(t, w, xr)
   497  	}
   498  	if err := w.Close(); err != nil {
   499  		t.Fatal(err)
   500  	}
   501  	xs := NewService(ctx, db)
   502  
   503  	refs := []*xpb.CrossReferencesReply_RelatedAnchor{{
   504  		Anchor: &xpb.Anchor{
   505  			Parent: "kythe:?path=path1",
   506  			Span:   span,
   507  		},
   508  	}, {
   509  		Anchor: &xpb.Anchor{
   510  			Parent: "kythe:?path=path2",
   511  			Span:   span,
   512  		},
   513  	}}
   514  
   515  	defs := []*xpb.CrossReferencesReply_RelatedAnchor{{
   516  		Anchor: &xpb.Anchor{
   517  			Parent: "kythe:?path=path1",
   518  			Span:   span,
   519  		},
   520  	}, {
   521  		Anchor: &xpb.Anchor{
   522  			Parent: "kythe:?path=path2",
   523  			Span:   span,
   524  		},
   525  	}}
   526  
   527  	decls := []*xpb.CrossReferencesReply_RelatedAnchor{{
   528  		Anchor: &xpb.Anchor{
   529  			Parent: "kythe:?path=path1",
   530  			Span:   span,
   531  		},
   532  	}, {
   533  		Anchor: &xpb.Anchor{
   534  			Parent: "kythe:?path=path2",
   535  			Span:   span,
   536  		},
   537  	}}
   538  
   539  	ticket := kytheuri.ToString(src)
   540  
   541  	t.Run("requested_node", makeXRefTestCase(ctx, xs, &xpb.CrossReferencesRequest{
   542  		Ticket:          []string{ticket},
   543  		Filter:          []string{"**"},
   544  		RelatedNodeKind: []string{"NONE"},
   545  	}, &xpb.CrossReferencesReply{
   546  		CrossReferences: map[string]*xpb.CrossReferencesReply_CrossReferenceSet{
   547  			ticket: {
   548  				Ticket:       ticket,
   549  				MarkedSource: ms,
   550  			},
   551  		},
   552  		Nodes: map[string]*cpb.NodeInfo{
   553  			ticket: {
   554  				Facts: map[string][]byte{
   555  					"/kythe/node/kind": []byte("record"),
   556  				},
   557  			},
   558  		},
   559  	}))
   560  
   561  	t.Run("refs", makeXRefTestCase(ctx, xs, &xpb.CrossReferencesRequest{
   562  		Ticket:        []string{ticket},
   563  		ReferenceKind: xpb.CrossReferencesRequest_ALL_REFERENCES,
   564  	}, &xpb.CrossReferencesReply{
   565  		CrossReferences: map[string]*xpb.CrossReferencesReply_CrossReferenceSet{
   566  			ticket: {
   567  				Ticket:       ticket,
   568  				MarkedSource: ms,
   569  				Reference:    refs,
   570  			},
   571  		},
   572  	}))
   573  
   574  	t.Run("decls", makeXRefTestCase(ctx, xs, &xpb.CrossReferencesRequest{
   575  		Ticket:          []string{ticket},
   576  		DeclarationKind: xpb.CrossReferencesRequest_ALL_DECLARATIONS,
   577  	}, &xpb.CrossReferencesReply{
   578  		CrossReferences: map[string]*xpb.CrossReferencesReply_CrossReferenceSet{
   579  			ticket: {
   580  				Ticket:       ticket,
   581  				MarkedSource: ms,
   582  				Declaration:  decls,
   583  			},
   584  		},
   585  	}))
   586  
   587  	t.Run("defs", makeXRefTestCase(ctx, xs, &xpb.CrossReferencesRequest{
   588  		Ticket:         []string{ticket},
   589  		DefinitionKind: xpb.CrossReferencesRequest_ALL_DEFINITIONS,
   590  	}, &xpb.CrossReferencesReply{
   591  		CrossReferences: map[string]*xpb.CrossReferencesReply_CrossReferenceSet{
   592  			ticket: {
   593  				Ticket:       ticket,
   594  				MarkedSource: ms,
   595  				Definition:   defs,
   596  			},
   597  		},
   598  	}))
   599  
   600  	t.Run("full_defs", makeXRefTestCase(ctx, xs, &xpb.CrossReferencesRequest{
   601  		Ticket:         []string{ticket},
   602  		DefinitionKind: xpb.CrossReferencesRequest_FULL_DEFINITIONS,
   603  	}, &xpb.CrossReferencesReply{
   604  		CrossReferences: map[string]*xpb.CrossReferencesReply_CrossReferenceSet{
   605  			ticket: {
   606  				Ticket:       ticket,
   607  				MarkedSource: ms,
   608  				Definition:   defs[0:1],
   609  			},
   610  		},
   611  	}))
   612  
   613  	t.Run("bindings", makeXRefTestCase(ctx, xs, &xpb.CrossReferencesRequest{
   614  		Ticket:         []string{ticket},
   615  		DefinitionKind: xpb.CrossReferencesRequest_BINDING_DEFINITIONS,
   616  	}, &xpb.CrossReferencesReply{
   617  		CrossReferences: map[string]*xpb.CrossReferencesReply_CrossReferenceSet{
   618  			ticket: {
   619  				Ticket:       ticket,
   620  				MarkedSource: ms,
   621  				Definition:   defs[1:2],
   622  			},
   623  		},
   624  	}))
   625  
   626  	t.Run("related_nodes", makeXRefTestCase(ctx, xs, &xpb.CrossReferencesRequest{
   627  		Ticket:        []string{ticket},
   628  		ReferenceKind: xpb.CrossReferencesRequest_ALL_REFERENCES,
   629  		Filter:        []string{"**"},
   630  	}, &xpb.CrossReferencesReply{
   631  		CrossReferences: map[string]*xpb.CrossReferencesReply_CrossReferenceSet{
   632  			ticket: {
   633  				Ticket:       ticket,
   634  				MarkedSource: ms,
   635  				Reference:    refs,
   636  				RelatedNode: []*xpb.CrossReferencesReply_RelatedNode{{
   637  					Ticket:       "kythe:#relatedNode",
   638  					RelationKind: "%/kythe/edge/childof",
   639  				}},
   640  			},
   641  		},
   642  		Nodes: map[string]*cpb.NodeInfo{
   643  			ticket: {
   644  				Facts: map[string][]byte{
   645  					"/kythe/node/kind": []byte("record"),
   646  				},
   647  			},
   648  			"kythe:#relatedNode": {
   649  				Facts: map[string][]byte{
   650  					"/kythe/node/kind": []byte("function"),
   651  				},
   652  			},
   653  		},
   654  	}))
   655  
   656  	t.Run("node_definitions", makeXRefTestCase(ctx, xs, &xpb.CrossReferencesRequest{
   657  		Ticket:          []string{ticket},
   658  		Filter:          []string{facts.NodeKind},
   659  		NodeDefinitions: true,
   660  	}, &xpb.CrossReferencesReply{
   661  		CrossReferences: map[string]*xpb.CrossReferencesReply_CrossReferenceSet{
   662  			ticket: {
   663  				Ticket:       ticket,
   664  				MarkedSource: ms,
   665  				RelatedNode: []*xpb.CrossReferencesReply_RelatedNode{{
   666  					Ticket:       "kythe:#relatedNode",
   667  					RelationKind: "%/kythe/edge/childof",
   668  				}},
   669  			},
   670  		},
   671  		Nodes: map[string]*cpb.NodeInfo{
   672  			ticket: {Facts: map[string][]byte{"/kythe/node/kind": []byte("record")}},
   673  			"kythe:#relatedNode": {
   674  				Facts:      map[string][]byte{"/kythe/node/kind": []byte("function")},
   675  				Definition: "kythe:#relatedNodeDef",
   676  			},
   677  		},
   678  		DefinitionLocations: map[string]*xpb.Anchor{
   679  			"kythe:#relatedNodeDef": {
   680  				Ticket: "kythe:#relatedNodeDef",
   681  				Parent: "kythe:",
   682  				Span:   span,
   683  			},
   684  		},
   685  	}))
   686  
   687  	t.Run("non_call_refs", makeXRefTestCase(ctx, xs, &xpb.CrossReferencesRequest{
   688  		Ticket:        []string{ticket},
   689  		ReferenceKind: xpb.CrossReferencesRequest_NON_CALL_REFERENCES,
   690  	}, &xpb.CrossReferencesReply{
   691  		CrossReferences: map[string]*xpb.CrossReferencesReply_CrossReferenceSet{
   692  			ticket: {
   693  				Ticket:       ticket,
   694  				MarkedSource: ms,
   695  				Reference:    refs[0:1],
   696  			},
   697  		},
   698  	}))
   699  
   700  	t.Run("callsites", makeXRefTestCase(ctx, xs, &xpb.CrossReferencesRequest{
   701  		Ticket:     []string{ticket},
   702  		CallerKind: xpb.CrossReferencesRequest_OVERRIDE_CALLERS,
   703  	}, &xpb.CrossReferencesReply{
   704  		CrossReferences: map[string]*xpb.CrossReferencesReply_CrossReferenceSet{
   705  			ticket: {
   706  				Ticket:       ticket,
   707  				MarkedSource: ms,
   708  				Caller: []*xpb.CrossReferencesReply_RelatedAnchor{{
   709  					Ticket: "kythe:#caller",
   710  					Anchor: &xpb.Anchor{
   711  						Parent: "kythe:?path=path",
   712  						Span:   span,
   713  					},
   714  					MarkedSource: &cpb.MarkedSource{Kind: cpb.MarkedSource_IDENTIFIER, PreText: "caller"},
   715  					Site: []*xpb.Anchor{{
   716  						Parent: "kythe:?path=path2",
   717  						Span:   span,
   718  					}, {
   719  						Parent: "kythe:?path=path3",
   720  						Span:   span,
   721  					}},
   722  				}},
   723  			},
   724  		},
   725  	}))
   726  
   727  	t.Run("direct_callsites", makeXRefTestCase(ctx, xs, &xpb.CrossReferencesRequest{
   728  		Ticket:     []string{ticket},
   729  		CallerKind: xpb.CrossReferencesRequest_DIRECT_CALLERS,
   730  	}, &xpb.CrossReferencesReply{
   731  		CrossReferences: map[string]*xpb.CrossReferencesReply_CrossReferenceSet{
   732  			ticket: {
   733  				Ticket:       ticket,
   734  				MarkedSource: ms,
   735  				Caller: []*xpb.CrossReferencesReply_RelatedAnchor{{
   736  					Ticket: "kythe:#caller",
   737  					Anchor: &xpb.Anchor{
   738  						Parent: "kythe:?path=path",
   739  						Span:   span,
   740  					},
   741  					MarkedSource: &cpb.MarkedSource{Kind: cpb.MarkedSource_IDENTIFIER, PreText: "caller"},
   742  					Site: []*xpb.Anchor{{
   743  						Parent: "kythe:?path=path2",
   744  						Span:   span,
   745  					}},
   746  				}},
   747  			},
   748  		},
   749  	}))
   750  }
   751  
   752  func makeXRefTestCase(ctx context.Context, xs xrefs.Service, req *xpb.CrossReferencesRequest, expected *xpb.CrossReferencesReply) func(*testing.T) {
   753  	return func(t *testing.T) {
   754  		reply, err := xs.CrossReferences(ctx, req)
   755  		if err != nil {
   756  			t.Fatalf("CrossReferences error: %v", err)
   757  		}
   758  		if diff := compare.ProtoDiff(expected, reply); diff != "" {
   759  			t.Fatalf("CrossReferencesReply differences: (- expected; + found)\n%s", diff)
   760  		}
   761  	}
   762  }