kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/services/graphstore/proxy/proxy_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 proxy
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"testing"
    25  
    26  	"kythe.io/kythe/go/services/graphstore"
    27  
    28  	"google.golang.org/protobuf/proto"
    29  
    30  	spb "kythe.io/kythe/proto/storage_go_proto"
    31  )
    32  
    33  var ctx = context.Background()
    34  
    35  // Static set of Entry protos to use for testing
    36  var testEntries = []entry{
    37  	{},
    38  	{K: "edge", F: "fact", V: "value"},
    39  	{K: "a"}, {K: "b"}, {K: "c"}, {K: "d"}, {K: "e"},
    40  	{K: "f"}, {K: "g"}, {K: "h"}, {K: "i"},
    41  }
    42  
    43  func te(i int) *spb.Entry { return testEntries[i].proto() }
    44  
    45  func tes(is ...int) (es []*spb.Entry) {
    46  	for _, i := range is {
    47  		es = append(es, te(i))
    48  	}
    49  	return es
    50  }
    51  
    52  func TestMergeOrder(t *testing.T) {
    53  	tests := []struct {
    54  		streams [][]*spb.Entry // Each stream must be in order
    55  		want    []*spb.Entry
    56  	}{
    57  		{nil, nil},
    58  
    59  		{[][]*spb.Entry{}, []*spb.Entry{}},
    60  
    61  		{[][]*spb.Entry{{}}, []*spb.Entry{}},
    62  
    63  		{[][]*spb.Entry{{te(0)}}, tes(0)},
    64  
    65  		{[][]*spb.Entry{tes(0), tes(1), tes(5)}, tes(0, 5, 1)},
    66  
    67  		{[][]*spb.Entry{tes(0), tes(1), tes(0, 1, 1)}, tes(0, 1)},
    68  
    69  		{[][]*spb.Entry{tes(2, 5, 7), tes(3, 8), tes(4, 6)}, tes(2, 3, 4, 5, 6, 7, 8)},
    70  
    71  		{[][]*spb.Entry{tes(8), tes(2, 3, 5), tes(5, 6), tes(7, 9, 10), tes(4), tes(6, 6, 6, 6, 6)},
    72  			tes(2, 3, 4, 5, 6, 7, 8, 9, 10)},
    73  	}
    74  
    75  	for i, test := range tests {
    76  		t.Logf("Begin test %d", i)
    77  		var ss []graphstore.Service
    78  		for _, stream := range test.streams {
    79  			ss = append(ss, &mockGraphStore{Entries: stream})
    80  		}
    81  		result := make(chan *spb.Entry)
    82  		done := checkResults(t, fmt.Sprintf("Merge test %d", i), result, test.want)
    83  
    84  		if err := New(ss...).Read(ctx, new(spb.ReadRequest), func(e *spb.Entry) error {
    85  			result <- e
    86  			return nil
    87  		}); err != nil {
    88  			t.Errorf("Test %d Read failed: %v", i, err)
    89  		}
    90  		close(result)
    91  		<-done
    92  	}
    93  }
    94  
    95  func TestProxy(t *testing.T) {
    96  	testError := errors.New("test")
    97  	mocks := []*mockGraphStore{
    98  		{Entries: []*spb.Entry{te(2), te(3)}},
    99  		{Entries: []*spb.Entry{te(5), te(6)}, Error: testError},
   100  		{Entries: []*spb.Entry{te(2), te(4)}},
   101  	}
   102  	allEntries := []*spb.Entry{
   103  		te(2), te(3), te(4),
   104  		te(5), te(6),
   105  	}
   106  	proxy := New(mocks[0], mocks[1], mocks[2])
   107  
   108  	readReq := new(spb.ReadRequest)
   109  	readRes := make(chan *spb.Entry)
   110  	readDone := checkResults(t, "Read", readRes, allEntries)
   111  
   112  	if err := proxy.Read(ctx, readReq, func(e *spb.Entry) error {
   113  		readRes <- e
   114  		return nil
   115  	}); err != testError {
   116  		t.Errorf("Incorrect Read error: %v", err)
   117  	}
   118  	close(readRes)
   119  	<-readDone
   120  
   121  	for idx, mock := range mocks {
   122  		if mock.LastReq != readReq {
   123  			t.Errorf("Read request was not sent to service %d", idx)
   124  		}
   125  	}
   126  
   127  	scanReq := new(spb.ScanRequest)
   128  	scanRes := make(chan *spb.Entry)
   129  	scanDone := checkResults(t, "Scan", scanRes, allEntries)
   130  
   131  	if err := proxy.Scan(ctx, scanReq, func(e *spb.Entry) error {
   132  		scanRes <- e
   133  		return nil
   134  	}); err != testError {
   135  		t.Errorf("Incorrect Scan error: %v", err)
   136  	}
   137  	close(scanRes)
   138  	<-scanDone
   139  
   140  	for idx, mock := range mocks {
   141  		if mock.LastReq != scanReq {
   142  			t.Errorf("Scan request was not sent to for service %d", idx)
   143  		}
   144  	}
   145  
   146  	writeReq := new(spb.WriteRequest)
   147  	if err := proxy.Write(ctx, writeReq); err != testError {
   148  		t.Errorf("Incorrect Write error: %v", err)
   149  	}
   150  	for idx, mock := range mocks {
   151  		if mock.LastReq != writeReq {
   152  			t.Errorf("Write request was not sent to service %d", idx)
   153  		}
   154  	}
   155  }
   156  
   157  // Verify that a proxy store behaves sensibly if an operation fails.
   158  func TestCancellation(t *testing.T) {
   159  	bomb := entry{K: "bomb", F: "die", V: "horrible catastrophe"}
   160  	stores := []graphstore.Service{
   161  		&mockGraphStore{Entries: tes(8, 3, 2, 4, 5, 9, 2, 0)},
   162  		&mockGraphStore{Entries: []*spb.Entry{bomb.proto()}},
   163  		&mockGraphStore{Entries: tes(1, 1, 1, 0, 1, 6, 5)},
   164  		&mockGraphStore{Entries: tes(3, 10, 7)},
   165  	}
   166  	p := New(stores...)
   167  
   168  	// Check that a callback returning a non-EOF error propagates an error to
   169  	// the caller.
   170  	var numEntries int
   171  	if err := p.Scan(ctx, new(spb.ScanRequest), func(e *spb.Entry) error {
   172  		if e.FactName == bomb.F {
   173  			return errors.New(bomb.V)
   174  		}
   175  		numEntries++
   176  		return nil
   177  	}); err != nil {
   178  		t.Logf("Got expected error: %v", err)
   179  	} else {
   180  		t.Error("Unexpected success! Got nil, wanted an error")
   181  	}
   182  	if numEntries != 3 {
   183  		t.Errorf("Wrong number of entries scanned: got %d, want 3", numEntries)
   184  	}
   185  
   186  	// Check that a callback returning io.EOF ends without error.
   187  	numEntries = 0
   188  	if err := p.Read(ctx, new(spb.ReadRequest), func(e *spb.Entry) error {
   189  		if e.FactName == bomb.F {
   190  			return io.EOF
   191  		}
   192  		numEntries++
   193  		return nil
   194  	}); err != nil {
   195  		t.Errorf("Read: unexpected error: %v", err)
   196  	}
   197  	if numEntries != 3 {
   198  		t.Errorf("Wrong number of entries read: got %d, want 3", numEntries)
   199  	}
   200  }
   201  
   202  type vname struct {
   203  	S, C, R, P, L string
   204  }
   205  
   206  func (v vname) proto() *spb.VName {
   207  	return &spb.VName{
   208  		Signature: v.S,
   209  		Corpus:    v.C,
   210  		Path:      v.P,
   211  		Root:      v.R,
   212  		Language:  v.L,
   213  	}
   214  }
   215  
   216  type entry struct {
   217  	S, T    vname
   218  	K, F, V string
   219  }
   220  
   221  func (e entry) proto() *spb.Entry {
   222  	return &spb.Entry{
   223  		Source:    e.S.proto(),
   224  		EdgeKind:  e.K,
   225  		FactName:  e.F,
   226  		Target:    e.T.proto(),
   227  		FactValue: []byte(e.V),
   228  	}
   229  }
   230  
   231  type mockGraphStore struct {
   232  	Entries []*spb.Entry
   233  	LastReq proto.Message
   234  	Error   error
   235  }
   236  
   237  func (m *mockGraphStore) Read(ctx context.Context, req *spb.ReadRequest, f graphstore.EntryFunc) error {
   238  	m.LastReq = req
   239  	for _, entry := range m.Entries {
   240  		if err := f(entry); err == io.EOF {
   241  			return nil
   242  		} else if err != nil {
   243  			return err
   244  		}
   245  	}
   246  	return m.Error
   247  }
   248  
   249  func (m *mockGraphStore) Scan(ctx context.Context, req *spb.ScanRequest, f graphstore.EntryFunc) error {
   250  	m.LastReq = req
   251  	for _, entry := range m.Entries {
   252  		if err := f(entry); err == io.EOF {
   253  			return nil
   254  		} else if err != nil {
   255  			return err
   256  		}
   257  	}
   258  	return m.Error
   259  }
   260  
   261  func (m *mockGraphStore) Write(ctx context.Context, req *spb.WriteRequest) error {
   262  	m.LastReq = req
   263  	return m.Error
   264  }
   265  
   266  func (m *mockGraphStore) Close(ctx context.Context) error { return m.Error }
   267  
   268  // checkResults starts a goroutine that consumes entries from results and
   269  // compares them to corresponding members of want.  If the corresponding values
   270  // are unequal or if there are more or fewer results than wanted, errors are
   271  // logged to t prefixed with the given tag.
   272  //
   273  // The returned channel is closed when all results have been checked.
   274  func checkResults(t *testing.T, tag string, results <-chan *spb.Entry, want []*spb.Entry) chan struct{} {
   275  	done := make(chan struct{})
   276  	go func() {
   277  		defer close(done)
   278  		i := 0
   279  		for entry := range results {
   280  			if i < len(want) {
   281  				if !proto.Equal(entry, want[i]) {
   282  					t.Errorf("%s result %d: got {%+v}, want {%+v}", tag, i, entry, want[i])
   283  				}
   284  			} else {
   285  				t.Errorf("%s extra result %d: {%+v}", tag, i, entry)
   286  			}
   287  			i++
   288  		}
   289  		for i < len(want) {
   290  			t.Errorf("%s missing result %d: {%+v}", tag, i, want[i])
   291  			i++
   292  		}
   293  	}()
   294  	return done
   295  }