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 }