kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/serving/explore/explore_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 explore
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"testing"
    23  
    24  	"kythe.io/kythe/go/storage/table"
    25  	"kythe.io/kythe/go/test/testutil"
    26  
    27  	"bitbucket.org/creachadair/stringset"
    28  	"google.golang.org/protobuf/proto"
    29  
    30  	epb "kythe.io/kythe/proto/explore_go_proto"
    31  	srvpb "kythe.io/kythe/proto/serving_go_proto"
    32  )
    33  
    34  const (
    35  	p1       = "kythe:#parent1"
    36  	p2       = "kythe:#parent2"
    37  	p1c1     = "kythe:#p1child1"
    38  	p1c2     = "kythe:#p1child2"
    39  	badType  = "kythe:#baddatatype"
    40  	dontcare = "kythe:#dontcare"
    41  	f1       = "kythe:#function1"
    42  	f2       = "kythe:#function2"
    43  	f1r1     = "kythe:#f1caller1"
    44  	fr       = "kythe:#fcaller"
    45  	f2r1     = "kythe:#f2caller1"
    46  	f3       = "kythe:#function3_recursive"
    47  	dne      = "kythe:#does_not_exist"
    48  )
    49  
    50  var (
    51  	ctx              = context.Background()
    52  	parentToChildren = &protoTable{
    53  		p1: &srvpb.Relatives{
    54  			Tickets: []string{p1c1, p1c2},
    55  			Type:    srvpb.Relatives_CHILDREN,
    56  		},
    57  		badType: &srvpb.Relatives{
    58  			Tickets: []string{dontcare},
    59  			Type:    srvpb.Relatives_PARENTS,
    60  		},
    61  	}
    62  
    63  	childToParents = &protoTable{
    64  		p1c1: &srvpb.Relatives{
    65  			Tickets: []string{p1},
    66  			Type:    srvpb.Relatives_PARENTS,
    67  		},
    68  		p1c2: &srvpb.Relatives{
    69  			Tickets: []string{p2},
    70  			Type:    srvpb.Relatives_PARENTS,
    71  		},
    72  		badType: &srvpb.Relatives{
    73  			Tickets: []string{dontcare},
    74  			Type:    srvpb.Relatives_CHILDREN,
    75  		},
    76  	}
    77  
    78  	functionToCallers = &protoTable{
    79  		f1: &srvpb.Callgraph{
    80  			Tickets: []string{f1r1, fr},
    81  			Type:    srvpb.Callgraph_CALLER,
    82  		},
    83  		f2: &srvpb.Callgraph{
    84  			Tickets: []string{f2r1, fr},
    85  			Type:    srvpb.Callgraph_CALLER,
    86  		},
    87  		f3: &srvpb.Callgraph{
    88  			Tickets: []string{f1, fr, f3},
    89  			Type:    srvpb.Callgraph_CALLER,
    90  		},
    91  		fr: &srvpb.Callgraph{
    92  			Tickets: []string{f2},
    93  			Type:    srvpb.Callgraph_CALLER,
    94  		},
    95  		badType: &srvpb.Callgraph{
    96  			Tickets: []string{dontcare},
    97  			Type:    srvpb.Callgraph_CALLEE,
    98  		},
    99  	}
   100  
   101  	functionToCallees = &protoTable{
   102  		f1: &srvpb.Callgraph{
   103  			Tickets: []string{f3},
   104  			Type:    srvpb.Callgraph_CALLEE,
   105  		},
   106  		f2: &srvpb.Callgraph{
   107  			Tickets: []string{fr},
   108  			Type:    srvpb.Callgraph_CALLEE,
   109  		},
   110  		f3: &srvpb.Callgraph{
   111  			Tickets: []string{f3},
   112  			Type:    srvpb.Callgraph_CALLEE,
   113  		},
   114  		fr: &srvpb.Callgraph{
   115  			Tickets: []string{f1, f2, f3},
   116  			Type:    srvpb.Callgraph_CALLEE,
   117  		},
   118  		f1r1: &srvpb.Callgraph{
   119  			Tickets: []string{f1},
   120  			Type:    srvpb.Callgraph_CALLEE,
   121  		},
   122  		f2r1: &srvpb.Callgraph{
   123  			Tickets: []string{f2},
   124  			Type:    srvpb.Callgraph_CALLEE,
   125  		},
   126  		badType: &srvpb.Callgraph{
   127  			Tickets: []string{dontcare},
   128  			Type:    srvpb.Callgraph_CALLER,
   129  		},
   130  	}
   131  )
   132  
   133  func TestChildren_badData(t *testing.T) {
   134  	svc := construct(t)
   135  
   136  	reply, err := svc.Children(ctx, &epb.ChildrenRequest{
   137  		Tickets: []string{badType},
   138  	})
   139  	if err == nil {
   140  		t.Errorf("Expected Children error for bad data, got: %v", reply)
   141  	}
   142  }
   143  
   144  func TestChildren_noData(t *testing.T) {
   145  	svc := construct(t)
   146  
   147  	reply, err := svc.Children(ctx, &epb.ChildrenRequest{
   148  		Tickets: []string{dne},
   149  	})
   150  	testutil.Fatalf(t, "Children error: %v", err)
   151  	if len(reply.InputToChildren) != 0 {
   152  		t.Errorf("Expected empty response for missing key, got: %v", reply)
   153  	}
   154  }
   155  
   156  func TestChildren(t *testing.T) {
   157  	svc := construct(t)
   158  	request := &epb.ChildrenRequest{
   159  		Tickets: []string{p1},
   160  	}
   161  
   162  	reply, err := svc.Children(ctx, request)
   163  	testutil.Fatalf(t, "Children error: %v", err)
   164  
   165  	expectedInputToChildren := map[string]*epb.Tickets{
   166  		p1: {
   167  			Tickets: []string{p1c1, p1c2},
   168  		},
   169  	}
   170  
   171  	expectedInputKeys := stringset.FromKeys(expectedInputToChildren)
   172  	actualInputKeys := stringset.FromKeys(reply.InputToChildren)
   173  	if !expectedInputKeys.Equals(actualInputKeys) {
   174  		t.Errorf("Expected results and actual results have different ticket key sets:\n"+
   175  			"expected: %v\n actual: %v", expectedInputKeys, actualInputKeys)
   176  	}
   177  
   178  	for ticket, expectedChildren := range expectedInputToChildren {
   179  		children := reply.InputToChildren[ticket]
   180  		checkEquivalentLists(t, expectedChildren.Tickets, children.Tickets, "children")
   181  	}
   182  }
   183  
   184  func TestParents_badData(t *testing.T) {
   185  	svc := construct(t)
   186  
   187  	reply, err := svc.Parents(ctx, &epb.ParentsRequest{
   188  		Tickets: []string{badType},
   189  	})
   190  	if err == nil {
   191  		t.Errorf("Expected Parents error for bad data, got: %v", reply)
   192  	}
   193  }
   194  
   195  func TestParents_noData(t *testing.T) {
   196  	svc := construct(t)
   197  
   198  	reply, err := svc.Parents(ctx, &epb.ParentsRequest{
   199  		Tickets: []string{dne},
   200  	})
   201  	testutil.Fatalf(t, "Parents error: %v", err)
   202  	if len(reply.InputToParents) != 0 {
   203  		t.Errorf("Expected empty response for missing key, got: %v", reply)
   204  	}
   205  }
   206  
   207  func TestParents(t *testing.T) {
   208  	svc := construct(t)
   209  	request := &epb.ParentsRequest{
   210  		Tickets: []string{p1c1},
   211  	}
   212  
   213  	reply, err := svc.Parents(ctx, request)
   214  	testutil.Fatalf(t, "Parents error: %v", err)
   215  
   216  	expectedInputToParents := map[string]*epb.Tickets{
   217  		p1c1: {
   218  			Tickets: []string{p1},
   219  		},
   220  	}
   221  
   222  	expectedInputKeys := stringset.FromKeys(expectedInputToParents)
   223  	actualInputKeys := stringset.FromKeys(reply.InputToParents)
   224  	if !expectedInputKeys.Equals(actualInputKeys) {
   225  		t.Errorf("Expected results and actual results have different ticket key sets:\n"+
   226  			"expected: %v\n actual: %v", expectedInputKeys, actualInputKeys)
   227  	}
   228  
   229  	for ticket, expectedParents := range expectedInputToParents {
   230  		parents := reply.InputToParents[ticket]
   231  		checkEquivalentLists(t, expectedParents.Tickets, parents.Tickets,
   232  			fmt.Sprintf("children of %s", ticket))
   233  	}
   234  }
   235  
   236  func TestCallers_badData(t *testing.T) {
   237  	svc := construct(t)
   238  
   239  	reply, err := svc.Callers(ctx, &epb.CallersRequest{
   240  		Tickets: []string{badType},
   241  	})
   242  	if err == nil {
   243  		t.Errorf("Expected Callers error for bad data, got: %v", reply)
   244  	}
   245  }
   246  
   247  func TestCallers_noData(t *testing.T) {
   248  	svc := construct(t)
   249  
   250  	reply, err := svc.Callers(ctx, &epb.CallersRequest{
   251  		Tickets: []string{dne},
   252  	})
   253  	testutil.Fatalf(t, "CallersRequest error: %v", err)
   254  	if len(reply.Graph.Nodes) != 0 {
   255  		t.Errorf("Expected empty response for missing key, got: %v", reply)
   256  	}
   257  }
   258  
   259  func TestCallers(t *testing.T) {
   260  	svc := construct(t)
   261  	request := &epb.CallersRequest{
   262  		Tickets: []string{f1},
   263  	}
   264  
   265  	reply, err := svc.Callers(ctx, request)
   266  	testutil.Fatalf(t, "Callers error: %v", err)
   267  
   268  	expectedGraph := &epb.Graph{
   269  		Nodes: map[string]*epb.GraphNode{
   270  			f1: {
   271  				Predecessors: []string{f1r1, fr},
   272  			},
   273  			f1r1: {
   274  				Successors: []string{f1},
   275  			},
   276  			fr: {
   277  				Successors: []string{f1},
   278  			},
   279  		},
   280  	}
   281  
   282  	checkEqualGraphs(t, expectedGraph, reply.Graph)
   283  }
   284  
   285  func TestCallers_multipleInputs(t *testing.T) {
   286  	svc := construct(t)
   287  	request := &epb.CallersRequest{
   288  		Tickets: []string{f1, f2, f3},
   289  	}
   290  
   291  	reply, err := svc.Callers(ctx, request)
   292  	testutil.Fatalf(t, "Callers error: %v", err)
   293  
   294  	// The edge f2->fr is not in the reply because we're asking for neither
   295  	// the callers of fr nor the callees of f2.
   296  	expectedGraph := &epb.Graph{
   297  		Nodes: map[string]*epb.GraphNode{
   298  			f1: {
   299  				Predecessors: []string{f1r1, fr},
   300  				Successors:   []string{f3},
   301  			},
   302  			f2: {
   303  				Predecessors: []string{f2r1, fr},
   304  			},
   305  			f3: {
   306  				Predecessors: []string{f1, fr, f3},
   307  				Successors:   []string{f3},
   308  			},
   309  			f1r1: {
   310  				Successors: []string{f1},
   311  			},
   312  			f2r1: {
   313  				Successors: []string{f2},
   314  			},
   315  			fr: {
   316  				Successors: []string{f1, f2, f3},
   317  			},
   318  		},
   319  	}
   320  
   321  	checkEqualGraphs(t, expectedGraph, reply.Graph)
   322  }
   323  
   324  func TestCallees_badData(t *testing.T) {
   325  	svc := construct(t)
   326  
   327  	reply, err := svc.Callees(ctx, &epb.CalleesRequest{
   328  		Tickets: []string{badType},
   329  	})
   330  	if err == nil {
   331  		t.Errorf("Expected Callees error for bad data, got: %v", reply)
   332  	}
   333  }
   334  
   335  func TestCallees_noData(t *testing.T) {
   336  	svc := construct(t)
   337  
   338  	reply, err := svc.Callees(ctx, &epb.CalleesRequest{
   339  		Tickets: []string{dne},
   340  	})
   341  	testutil.Fatalf(t, "CalleesRequest error: %v", err)
   342  	if len(reply.Graph.Nodes) != 0 {
   343  		t.Errorf("Expected empty response for missing key, got: %v", reply)
   344  	}
   345  }
   346  
   347  func TestCallees(t *testing.T) {
   348  	svc := construct(t)
   349  	request := &epb.CalleesRequest{
   350  		Tickets: []string{fr},
   351  	}
   352  
   353  	reply, err := svc.Callees(ctx, request)
   354  	testutil.Fatalf(t, "Callees error: %v", err)
   355  
   356  	expectedGraph := &epb.Graph{
   357  		Nodes: map[string]*epb.GraphNode{
   358  			fr: {
   359  				Successors: []string{f1, f2, f3},
   360  			},
   361  			f1: {
   362  				Predecessors: []string{fr},
   363  			},
   364  			f2: {
   365  				Predecessors: []string{fr},
   366  			},
   367  			f3: {
   368  				Predecessors: []string{fr},
   369  			},
   370  		},
   371  	}
   372  
   373  	checkEqualGraphs(t, expectedGraph, reply.Graph)
   374  }
   375  
   376  func TestCallees_multipleInputs(t *testing.T) {
   377  	svc := construct(t)
   378  	request := &epb.CalleesRequest{
   379  		Tickets: []string{f1, f2, f3},
   380  	}
   381  
   382  	reply, err := svc.Callees(ctx, request)
   383  	testutil.Fatalf(t, "Callees error: %v", err)
   384  
   385  	// The following edges are present in the stored callgraph
   386  	// but intentionally not present in the response:
   387  	// f1r1->f1, fr->f1; f2r1->f2, fr->f2; fr->f3
   388  	// (because the request doesn't ask for the callers of f1, f2, f3).
   389  	expectedGraph := &epb.Graph{
   390  		Nodes: map[string]*epb.GraphNode{
   391  			f1: {
   392  				Successors: []string{f3},
   393  			},
   394  			f2: {
   395  				Successors: []string{fr},
   396  			},
   397  			f3: {
   398  				Predecessors: []string{f1, f3},
   399  				Successors:   []string{f3},
   400  			},
   401  			fr: {
   402  				Predecessors: []string{f2},
   403  			},
   404  		},
   405  	}
   406  
   407  	checkEqualGraphs(t, expectedGraph, reply.Graph)
   408  }
   409  
   410  func checkEqualGraphs(t *testing.T, expected, actual *epb.Graph) {
   411  	if len(expected.Nodes) != len(actual.Nodes) {
   412  		t.Errorf("Mismatch in graph node counts: expected: %d, actual: %d",
   413  			len(expected.Nodes), len(actual.Nodes))
   414  	}
   415  
   416  	expectedNodeSet := stringset.FromKeys(expected.Nodes)
   417  	actualNodeSet := stringset.FromKeys(actual.Nodes)
   418  	if !expectedNodeSet.Equals(actualNodeSet) {
   419  		t.Errorf("Unexpected mismatch in graph node sets:\n expected:\n%v\n actual:\n%v\n",
   420  			expectedNodeSet, actualNodeSet)
   421  	}
   422  
   423  	for ticket, expectedNode := range expected.Nodes {
   424  		node := actual.Nodes[ticket]
   425  		checkEquivalentLists(t, expectedNode.Predecessors, node.Predecessors,
   426  			fmt.Sprintf("predecessors for %s", ticket))
   427  		checkEquivalentLists(t, expectedNode.Successors, node.Successors,
   428  			fmt.Sprintf("successors for %s", ticket))
   429  	}
   430  }
   431  
   432  // Checks whether the lists are equivalent.
   433  func checkEquivalentLists(t *testing.T, expected, actual []string, tag string) {
   434  	if len(expected) != len(actual) {
   435  		t.Errorf("Mismatch in counts for %s; expected:\n%v actual:\n%v",
   436  			tag, expected, actual)
   437  	}
   438  
   439  	if !stringset.FromKeys(expected).Equals(stringset.FromKeys(actual)) {
   440  		t.Errorf("Mismatch for %s; expected:\n%v actual:\n%v",
   441  			tag, expected, actual)
   442  	}
   443  }
   444  
   445  type protoTable map[string]proto.Message
   446  
   447  func (t protoTable) Lookup(_ context.Context, key []byte, msg proto.Message) error {
   448  	m, ok := t[string(key)]
   449  	if !ok {
   450  		return table.ErrNoSuchKey
   451  	}
   452  	proto.Merge(msg, m)
   453  	return nil
   454  }
   455  
   456  func (t protoTable) LookupValues(_ context.Context, key []byte, m proto.Message, f func(proto.Message) error) error {
   457  	val, ok := t[string(key)]
   458  	if !ok {
   459  		return nil
   460  	}
   461  	msg := m.ProtoReflect().New().Interface()
   462  	proto.Merge(msg, val)
   463  	if err := f(msg); err != nil && err != table.ErrStopLookup {
   464  		return err
   465  	}
   466  	return nil
   467  }
   468  
   469  func construct(t *testing.T) *Tables {
   470  	return &Tables{
   471  		ParentToChildren:  parentToChildren,
   472  		ChildToParents:    childToParents,
   473  		FunctionToCallers: functionToCallers,
   474  		FunctionToCallees: functionToCallees,
   475  	}
   476  }