github.com/tsuna/gohbase@v0.0.0-20250731002811-4ffcadfba63e/scanner_test.go (about)

     1  // Copyright (C) 2017  The GoHBase Authors.  All rights reserved.
     2  // This file is part of GoHBase.
     3  // Use of this source code is governed by the Apache License 2.0
     4  // that can be found in the COPYING file.
     5  
     6  package gohbase
     7  
     8  import (
     9  	"context"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"log/slog"
    14  	"reflect"
    15  	"sync"
    16  	"testing"
    17  
    18  	"github.com/tsuna/gohbase/filter"
    19  	"github.com/tsuna/gohbase/hrpc"
    20  	"github.com/tsuna/gohbase/pb"
    21  	"github.com/tsuna/gohbase/region"
    22  	"github.com/tsuna/gohbase/test"
    23  	"github.com/tsuna/gohbase/test/mock"
    24  	"go.uber.org/mock/gomock"
    25  	"google.golang.org/protobuf/proto"
    26  )
    27  
    28  func cp(i uint64) *uint64 {
    29  	j := i
    30  	return &j
    31  }
    32  
    33  type scanMatcher struct {
    34  	scan *hrpc.Scan
    35  }
    36  
    37  func (c *scanMatcher) Matches(x interface{}) bool {
    38  	s, ok := x.(*hrpc.Scan)
    39  	if c.scan.Region() == nil {
    40  		c.scan.SetRegion(region.NewInfo(0, nil, nil, nil, nil, nil))
    41  	}
    42  	if s.Region() == nil {
    43  		s.SetRegion(region.NewInfo(0, nil, nil, nil, nil, nil))
    44  	}
    45  	return ok && proto.Equal(c.scan.ToProto(), s.ToProto())
    46  }
    47  
    48  func (c *scanMatcher) String() string {
    49  	return fmt.Sprintf("is equal to %s", c.scan)
    50  }
    51  
    52  var resultsPB = []*pb.Result{
    53  	// region 1
    54  	&pb.Result{
    55  		Cell: []*pb.Cell{
    56  			&pb.Cell{Row: []byte("a"), Family: []byte("A"), Qualifier: []byte("1")},
    57  			&pb.Cell{Row: []byte("a"), Family: []byte("A"), Qualifier: []byte("2")},
    58  			&pb.Cell{Row: []byte("a"), Family: []byte("B"), Qualifier: []byte("1")},
    59  		},
    60  	},
    61  	&pb.Result{
    62  		Cell: []*pb.Cell{
    63  			&pb.Cell{Row: []byte("b"), Family: []byte("A"), Qualifier: []byte("1")},
    64  			&pb.Cell{Row: []byte("b"), Family: []byte("B"), Qualifier: []byte("2")},
    65  		},
    66  	},
    67  	// region 2
    68  	&pb.Result{
    69  		Cell: []*pb.Cell{
    70  			&pb.Cell{Row: []byte("bar"), Family: []byte("C"), Qualifier: []byte("1")},
    71  			&pb.Cell{Row: []byte("baz"), Family: []byte("C"), Qualifier: []byte("2")},
    72  			&pb.Cell{Row: []byte("baz"), Family: []byte("C"), Qualifier: []byte("2")},
    73  		},
    74  	},
    75  	// region 3
    76  	&pb.Result{
    77  		Cell: []*pb.Cell{
    78  			&pb.Cell{Row: []byte("yolo"), Family: []byte("D"), Qualifier: []byte("1")},
    79  			&pb.Cell{Row: []byte("yolo"), Family: []byte("D"), Qualifier: []byte("2")},
    80  		},
    81  	},
    82  }
    83  
    84  var (
    85  	table   = []byte("test")
    86  	region1 = region.NewInfo(0, nil, table, []byte("table,,bar,whatever"), nil, []byte("bar"))
    87  	region2 = region.NewInfo(0, nil, table,
    88  		[]byte("table,bar,foo,whatever"), []byte("bar"), []byte("foo"))
    89  	region3 = region.NewInfo(0, nil, table, []byte("table,foo,,whatever"), []byte("foo"), nil)
    90  )
    91  
    92  func dup(a []*pb.Result) []*pb.Result {
    93  	b := make([]*pb.Result, len(a))
    94  	copy(b, a)
    95  	return b
    96  }
    97  
    98  func testCallClose(scan *hrpc.Scan, c *mock.MockRPCClient, scannerID uint64,
    99  	group *sync.WaitGroup, t *testing.T) {
   100  	//	t.Helper()
   101  
   102  	s, err := hrpc.NewScanRange(context.Background(), table, nil, nil,
   103  		hrpc.ScannerID(scannerID), hrpc.CloseScanner(), hrpc.NumberOfRows(0))
   104  	if err != nil {
   105  		t.Fatal(err)
   106  	}
   107  
   108  	c.EXPECT().SendRPC(&scanMatcher{scan: s}).Do(func(arg0 interface{}) {
   109  		group.Done()
   110  	}).Return(&pb.ScanResponse{}, nil).Times(1)
   111  }
   112  
   113  func TestScanner(t *testing.T) {
   114  	ctrl := test.NewController(t)
   115  	defer ctrl.Finish()
   116  	c := mock.NewMockRPCClient(ctrl)
   117  
   118  	scan, err := hrpc.NewScan(context.Background(), table, hrpc.NumberOfRows(2))
   119  	if err != nil {
   120  		t.Fatal(err)
   121  	}
   122  
   123  	var scannerID uint64 = 42
   124  	scanner := newScanner(c, scan, slog.Default())
   125  
   126  	s, err := hrpc.NewScanRange(scan.Context(), table, nil, nil,
   127  		hrpc.NumberOfRows(2))
   128  	if err != nil {
   129  		t.Fatal(err)
   130  	}
   131  	c.EXPECT().SendRPC(&scanMatcher{scan: s}).Do(func(rpc hrpc.Call) {
   132  		rpc.SetRegion(region1)
   133  	}).Return(&pb.ScanResponse{
   134  		ScannerId:           cp(scannerID),
   135  		MoreResultsInRegion: proto.Bool(true),
   136  		Results:             dup(resultsPB[:1]),
   137  	}, nil).Times(1)
   138  
   139  	s, err = hrpc.NewScanRange(scan.Context(), table, nil, nil,
   140  		hrpc.ScannerID(scannerID), hrpc.NumberOfRows(2))
   141  	if err != nil {
   142  		t.Fatal(err)
   143  	}
   144  
   145  	c.EXPECT().SendRPC(&scanMatcher{
   146  		scan: s,
   147  	}).Do(func(rpc hrpc.Call) {
   148  		rpc.SetRegion(region1)
   149  	}).Return(&pb.ScanResponse{
   150  		Results: dup(resultsPB[1:2]),
   151  	}, nil).Times(1)
   152  
   153  	scannerID++
   154  
   155  	s, err = hrpc.NewScanRange(scan.Context(), table,
   156  		[]byte("bar"), nil, hrpc.NumberOfRows(2))
   157  	if err != nil {
   158  		t.Fatal(err)
   159  	}
   160  	c.EXPECT().SendRPC(&scanMatcher{scan: s}).Do(func(rpc hrpc.Call) {
   161  		rpc.SetRegion(region2)
   162  	}).Return(&pb.ScanResponse{
   163  		ScannerId: cp(scannerID),
   164  		Results:   dup(resultsPB[2:3]),
   165  	}, nil).Times(1)
   166  
   167  	scannerID++
   168  
   169  	s, err = hrpc.NewScanRange(scan.Context(), table, []byte("foo"), nil,
   170  		hrpc.NumberOfRows(2))
   171  	if err != nil {
   172  		t.Fatal(err)
   173  	}
   174  	c.EXPECT().SendRPC(&scanMatcher{scan: s}).Do(func(rpc hrpc.Call) {
   175  		rpc.SetRegion(region3)
   176  	}).Return(&pb.ScanResponse{
   177  		ScannerId:   cp(scannerID),
   178  		Results:     dup(resultsPB[3:4]),
   179  		MoreResults: proto.Bool(false),
   180  	}, nil).Times(1)
   181  
   182  	var rs []*hrpc.Result
   183  	for {
   184  		r, err := scanner.Next()
   185  		if err == io.EOF {
   186  			break
   187  		}
   188  		if err != nil {
   189  			t.Fatal(err)
   190  		}
   191  		rs = append(rs, r)
   192  	}
   193  
   194  	var expected []*hrpc.Result
   195  	for _, r := range resultsPB {
   196  		expected = append(expected, hrpc.ToLocalResult(r))
   197  	}
   198  
   199  	if !reflect.DeepEqual(expected, rs) {
   200  		t.Fatalf("expected %v, got %v", expected, rs)
   201  	}
   202  }
   203  
   204  var cells = []*pb.Cell{
   205  	&pb.Cell{Row: []byte("a"), Family: []byte("A"), Qualifier: []byte("1")}, // 0
   206  	&pb.Cell{Row: []byte("a"), Family: []byte("A"), Qualifier: []byte("2")},
   207  	&pb.Cell{Row: []byte("a"), Family: []byte("A"), Qualifier: []byte("3")},
   208  	&pb.Cell{Row: []byte("b"), Family: []byte("B"), Qualifier: []byte("1")},   // 3
   209  	&pb.Cell{Row: []byte("b"), Family: []byte("B"), Qualifier: []byte("2")},   // 4
   210  	&pb.Cell{Row: []byte("bar"), Family: []byte("B"), Qualifier: []byte("1")}, // 5
   211  	&pb.Cell{Row: []byte("bar"), Family: []byte("B"), Qualifier: []byte("2")},
   212  	&pb.Cell{Row: []byte("bar"), Family: []byte("B"), Qualifier: []byte("3")}, // 7
   213  	&pb.Cell{Row: []byte("foo"), Family: []byte("F"), Qualifier: []byte("1")}, // 8
   214  	&pb.Cell{Row: []byte("foo"), Family: []byte("F"), Qualifier: []byte("2")}, // 9
   215  }
   216  
   217  func TestPartialResults(t *testing.T) {
   218  	scan, err := hrpc.NewScan(context.Background(), table)
   219  	if err != nil {
   220  		t.Fatal(err)
   221  	}
   222  	expected := []*hrpc.Result{
   223  		hrpc.ToLocalResult(&pb.Result{Cell: cells[:3]}),
   224  		hrpc.ToLocalResult(&pb.Result{Cell: cells[3:5]}),
   225  		hrpc.ToLocalResult(&pb.Result{Cell: cells[5:8]}),
   226  		hrpc.ToLocalResult(&pb.Result{Cell: cells[8:]}),
   227  	}
   228  	testPartialResults(t, scan, expected)
   229  }
   230  
   231  func TestAllowPartialResults(t *testing.T) {
   232  	scan, err := hrpc.NewScan(context.Background(), table, hrpc.AllowPartialResults())
   233  	if err != nil {
   234  		t.Fatal(err)
   235  	}
   236  	expected := []*hrpc.Result{
   237  		hrpc.ToLocalResult(&pb.Result{Cell: cells[:3], Partial: proto.Bool(true)}),
   238  		hrpc.ToLocalResult(&pb.Result{Cell: cells[3:4], Partial: proto.Bool(true)}),
   239  		hrpc.ToLocalResult(&pb.Result{Cell: cells[4:5], Partial: proto.Bool(true)}),
   240  		hrpc.ToLocalResult(&pb.Result{Cell: cells[5:7], Partial: proto.Bool(true)}),
   241  		hrpc.ToLocalResult(&pb.Result{Cell: cells[7:8], Partial: proto.Bool(true)}),
   242  		// empty list
   243  		hrpc.ToLocalResult(&pb.Result{Cell: cells[8:8], Partial: proto.Bool(true)}),
   244  		hrpc.ToLocalResult(&pb.Result{Cell: cells[8:9], Partial: proto.Bool(true)}),
   245  		hrpc.ToLocalResult(&pb.Result{Cell: cells[9:], Partial: proto.Bool(true)}),
   246  	}
   247  	testPartialResults(t, scan, expected)
   248  }
   249  
   250  func TestScanMetrics(t *testing.T) {
   251  	scanned, filtered := rowsScanned, rowsFiltered
   252  	i0, i1, i2, i4 := int64(0), int64(1), int64(2), int64(4)
   253  	tcases := []struct {
   254  		description          string
   255  		trackScanMetrics     func(call hrpc.Call) error
   256  		filter               func(call hrpc.Call) error
   257  		results              []*pb.Result
   258  		scanMetrics          *pb.ScanMetrics
   259  		expectedResults      []*hrpc.Result
   260  		expectedRowsScanned  int64
   261  		expectedRowsFiltered int64
   262  	}{
   263  		{
   264  			description: "ScanMetrics not enabled",
   265  			results: []*pb.Result{
   266  				{Cell: cells[:3]},
   267  			},
   268  			scanMetrics:     nil,
   269  			expectedResults: []*hrpc.Result{hrpc.ToLocalResult(&pb.Result{Cell: cells[:3]})},
   270  		},
   271  		{
   272  			description:          "Empty results",
   273  			trackScanMetrics:     hrpc.TrackScanMetrics(),
   274  			results:              nil,
   275  			scanMetrics:          nil,
   276  			expectedResults:      nil,
   277  			expectedRowsScanned:  0,
   278  			expectedRowsFiltered: 0,
   279  		},
   280  		{
   281  			description:      "ScanMetrics: 1 row scanned",
   282  			trackScanMetrics: hrpc.TrackScanMetrics(),
   283  			results: []*pb.Result{
   284  				{Cell: cells[:3]},
   285  			},
   286  			scanMetrics: &pb.ScanMetrics{
   287  				Metrics: []*pb.NameInt64Pair{
   288  					{
   289  						Name:  &scanned,
   290  						Value: &i1,
   291  					},
   292  					{
   293  						Name:  &filtered,
   294  						Value: &i0,
   295  					},
   296  				},
   297  			},
   298  			expectedResults:      []*hrpc.Result{toLocalResult(&pb.Result{Cell: cells[:3]})},
   299  			expectedRowsScanned:  1,
   300  			expectedRowsFiltered: 0,
   301  		},
   302  		{
   303  			description:      "ScanMetrics: 2 rows scanned",
   304  			trackScanMetrics: hrpc.TrackScanMetrics(),
   305  			results: []*pb.Result{
   306  				{Cell: cells[:5]},
   307  			},
   308  			scanMetrics: &pb.ScanMetrics{
   309  				Metrics: []*pb.NameInt64Pair{
   310  					{
   311  						Name:  &scanned,
   312  						Value: &i2,
   313  					},
   314  					{
   315  						Name:  &filtered,
   316  						Value: &i0,
   317  					},
   318  				},
   319  			},
   320  			expectedResults:      []*hrpc.Result{toLocalResult(&pb.Result{Cell: cells[:5]})},
   321  			expectedRowsScanned:  2,
   322  			expectedRowsFiltered: 0,
   323  		},
   324  		{
   325  			description:      "ScanMetrics: 4 rows scanned, 2 row filtered",
   326  			trackScanMetrics: hrpc.TrackScanMetrics(),
   327  			filter:           hrpc.Filters(filter.NewPrefixFilter([]byte("b"))),
   328  			results: []*pb.Result{
   329  				{Cell: cells},
   330  			},
   331  			scanMetrics: &pb.ScanMetrics{
   332  				Metrics: []*pb.NameInt64Pair{
   333  					{
   334  						Name:  &scanned,
   335  						Value: &i4,
   336  					},
   337  					{
   338  						Name:  &filtered,
   339  						Value: &i2,
   340  					},
   341  				},
   342  			},
   343  			expectedResults:      []*hrpc.Result{toLocalResult(&pb.Result{Cell: cells})},
   344  			expectedRowsScanned:  4,
   345  			expectedRowsFiltered: 2,
   346  		},
   347  		{
   348  			description:      "ScanMetrics: 2 rows scanned, 1 row filtered",
   349  			trackScanMetrics: hrpc.TrackScanMetrics(),
   350  			filter:           hrpc.Filters(filter.NewPrefixFilter([]byte("a"))),
   351  			results: []*pb.Result{
   352  				{Cell: cells[:5]},
   353  			},
   354  			scanMetrics: &pb.ScanMetrics{
   355  				Metrics: []*pb.NameInt64Pair{
   356  					{
   357  						Name:  &scanned,
   358  						Value: &i2,
   359  					},
   360  					{
   361  						Name:  &filtered,
   362  						Value: &i1,
   363  					},
   364  				},
   365  			},
   366  			expectedResults:      []*hrpc.Result{toLocalResult(&pb.Result{Cell: cells[:5]})},
   367  			expectedRowsScanned:  2,
   368  			expectedRowsFiltered: 1,
   369  		},
   370  		{
   371  			description:      "ScanMetrics: 0 rows scanned, 1 row filtered",
   372  			trackScanMetrics: hrpc.TrackScanMetrics(),
   373  			filter:           hrpc.Filters(filter.NewPrefixFilter([]byte("a"))),
   374  			results: []*pb.Result{
   375  				{Cell: cells[:3]},
   376  			},
   377  			scanMetrics: &pb.ScanMetrics{
   378  				Metrics: []*pb.NameInt64Pair{
   379  					{
   380  						Name:  &scanned,
   381  						Value: &i0,
   382  					},
   383  					{
   384  						Name:  &filtered,
   385  						Value: &i1,
   386  					},
   387  				},
   388  			},
   389  			expectedResults:      []*hrpc.Result{toLocalResult(&pb.Result{Cell: cells[:3]})},
   390  			expectedRowsScanned:  0,
   391  			expectedRowsFiltered: 1,
   392  		},
   393  	}
   394  
   395  	ctrl := test.NewController(t)
   396  	defer ctrl.Finish()
   397  	c := mock.NewMockRPCClient(ctrl)
   398  
   399  	for _, tcase := range tcases {
   400  		t.Run(tcase.description, func(t *testing.T) {
   401  			ctx := context.Background()
   402  			var scan *hrpc.Scan
   403  			var err error
   404  			if tcase.trackScanMetrics != nil && tcase.filter != nil {
   405  				scan, err = hrpc.NewScan(ctx, table, tcase.trackScanMetrics, tcase.filter)
   406  			} else if tcase.trackScanMetrics != nil {
   407  				scan, err = hrpc.NewScan(ctx, table, tcase.trackScanMetrics)
   408  			} else {
   409  				scan, err = hrpc.NewScan(ctx, table)
   410  			}
   411  
   412  			if err != nil {
   413  				t.Fatal(err)
   414  			}
   415  
   416  			sc := newScanner(c, scan, slog.Default())
   417  
   418  			c.EXPECT().SendRPC(&scanMatcher{scan: scan}).Return(&pb.ScanResponse{
   419  				Results:     tcase.results,
   420  				ScanMetrics: tcase.scanMetrics,
   421  			}, nil).Times(1)
   422  
   423  			var res []*hrpc.Result
   424  			for {
   425  				var r *hrpc.Result
   426  				r, err = sc.Next()
   427  				if err == io.EOF {
   428  					break
   429  				}
   430  				if err != nil {
   431  					t.Fatal(err)
   432  				}
   433  				res = append(res, r)
   434  			}
   435  
   436  			actualMetrics := sc.GetScanMetrics()
   437  
   438  			if tcase.trackScanMetrics == nil && actualMetrics != nil {
   439  				t.Fatalf("Got non-nil scan metrics when not enabled: %v", actualMetrics)
   440  			}
   441  
   442  			if tcase.expectedRowsScanned != actualMetrics[rowsScanned] {
   443  				t.Errorf("Did not get expected rows scanned - expected: %d, actual %d",
   444  					tcase.expectedRowsScanned, actualMetrics[rowsScanned])
   445  			}
   446  
   447  			if tcase.expectedRowsFiltered != actualMetrics[rowsFiltered] {
   448  				t.Errorf("Did not get expected rows filtered - expected: %d, actual %d",
   449  					tcase.expectedRowsFiltered, actualMetrics[rowsFiltered])
   450  			}
   451  
   452  			if !reflect.DeepEqual(tcase.expectedResults, res) {
   453  				t.Fatalf("expected: %+v\ngot: %+v", tcase.expectedResults, res)
   454  			}
   455  		})
   456  	}
   457  }
   458  
   459  func TestErrorScanFromID(t *testing.T) {
   460  	scan, err := hrpc.NewScan(context.Background(), table)
   461  	if err != nil {
   462  		t.Fatal(err)
   463  	}
   464  	expected := []*hrpc.Result{
   465  		hrpc.ToLocalResult(&pb.Result{Cell: cells[:3]}),
   466  		hrpc.ToLocalResult(&pb.Result{Cell: cells[3:4]}),
   467  	}
   468  	testErrorScanFromID(t, scan, expected)
   469  }
   470  
   471  func TestErrorScanFromIDAllowPartials(t *testing.T) {
   472  	scan, err := hrpc.NewScan(context.Background(), table, hrpc.AllowPartialResults())
   473  	if err != nil {
   474  		t.Fatal(err)
   475  	}
   476  	expected := []*hrpc.Result{
   477  		hrpc.ToLocalResult(&pb.Result{Cell: cells[:3]}),
   478  		hrpc.ToLocalResult(&pb.Result{Cell: cells[3:4]}),
   479  	}
   480  	testErrorScanFromID(t, scan, expected)
   481  }
   482  
   483  func TestErrorFirstFetchNoMetrics(t *testing.T) {
   484  	ctrl := test.NewController(t)
   485  	defer ctrl.Finish()
   486  	c := mock.NewMockRPCClient(ctrl)
   487  
   488  	scan, err := hrpc.NewScan(context.Background(), table)
   489  	if err != nil {
   490  		t.Fatal(err)
   491  	}
   492  	scanner := newScanner(c, scan, slog.Default())
   493  
   494  	srange, err := hrpc.NewScanRange(context.Background(), table, nil, nil)
   495  	if err != nil {
   496  		t.Fatal(err)
   497  	}
   498  
   499  	outErr := errors.New("WTF")
   500  	c.EXPECT().SendRPC(&scanMatcher{scan: srange}).Do(func(rpc hrpc.Call) {
   501  		rpc.SetRegion(region1)
   502  	}).Return(nil, outErr).Times(1)
   503  
   504  	var r *hrpc.Result
   505  	var rs []*hrpc.Result
   506  	for {
   507  		r, err = scanner.Next()
   508  		if r != nil {
   509  			rs = append(rs, r)
   510  		}
   511  		if err != nil {
   512  			break
   513  		}
   514  	}
   515  	if err != outErr {
   516  		t.Errorf("Expected error %v, got error %v", outErr, err)
   517  	}
   518  	if len(rs) != 0 {
   519  		t.Fatalf("expected no results, got %v", rs)
   520  	}
   521  }
   522  
   523  func TestErrorFirstFetchWithMetrics(t *testing.T) {
   524  	ctrl := test.NewController(t)
   525  	defer ctrl.Finish()
   526  	c := mock.NewMockRPCClient(ctrl)
   527  
   528  	scan, err := hrpc.NewScan(context.Background(), table, hrpc.TrackScanMetrics())
   529  	if err != nil {
   530  		t.Fatal(err)
   531  	}
   532  	scanner := newScanner(c, scan, slog.Default())
   533  
   534  	srange, err := hrpc.NewScanRange(context.Background(), table, nil, nil,
   535  		hrpc.TrackScanMetrics())
   536  	if err != nil {
   537  		t.Fatal(err)
   538  	}
   539  
   540  	outErr := errors.New("WTF")
   541  	c.EXPECT().SendRPC(&scanMatcher{scan: srange}).Do(func(rpc hrpc.Call) {
   542  		rpc.SetRegion(region1)
   543  	}).Return(nil, outErr).Times(1)
   544  
   545  	var r *hrpc.Result
   546  	var rs []*hrpc.Result
   547  	for {
   548  		r, err = scanner.Next()
   549  		if r != nil {
   550  			rs = append(rs, r)
   551  		}
   552  		if err != nil {
   553  			break
   554  		}
   555  	}
   556  	if err != outErr {
   557  		t.Errorf("Expected error %v, got error %v", outErr, err)
   558  	}
   559  	if len(rs) != 0 {
   560  		t.Fatalf("expected no results, got %v", rs)
   561  	}
   562  }
   563  
   564  func testErrorScanFromID(t *testing.T, scan *hrpc.Scan, out []*hrpc.Result) {
   565  	ctrl := test.NewController(t)
   566  	defer ctrl.Finish()
   567  	c := mock.NewMockRPCClient(ctrl)
   568  
   569  	var wg sync.WaitGroup
   570  	wg.Add(1)
   571  	defer wg.Wait()
   572  
   573  	var scannerID uint64 = 42
   574  	scanner := newScanner(c, scan, slog.Default())
   575  
   576  	srange, err := hrpc.NewScanRange(scan.Context(), table, nil, nil, scan.Options()...)
   577  	if err != nil {
   578  		t.Fatal(err)
   579  	}
   580  
   581  	c.EXPECT().SendRPC(&scanMatcher{scan: srange}).Do(func(rpc hrpc.Call) {
   582  		rpc.SetRegion(region1)
   583  	}).Return(&pb.ScanResponse{
   584  		ScannerId:           cp(scannerID),
   585  		MoreResultsInRegion: proto.Bool(true),
   586  		Results: []*pb.Result{
   587  			&pb.Result{Cell: cells[:3]},
   588  			&pb.Result{Cell: cells[3:4]},
   589  		},
   590  	}, nil).Times(1)
   591  
   592  	outErr := errors.New("WTF")
   593  
   594  	sid, err := hrpc.NewScanRange(scan.Context(), table, nil, nil,
   595  		hrpc.ScannerID(scannerID))
   596  	if err != nil {
   597  		t.Fatal(err)
   598  	}
   599  	c.EXPECT().SendRPC(&scanMatcher{scan: sid}).Do(func(rpc hrpc.Call) {
   600  		rpc.SetRegion(region1)
   601  	}).Return(nil, outErr).Times(1)
   602  
   603  	// expect scan close rpc to be sent
   604  	testCallClose(sid, c, scannerID, &wg, t)
   605  
   606  	var r *hrpc.Result
   607  	var rs []*hrpc.Result
   608  	for {
   609  		r, err = scanner.Next()
   610  		if r != nil {
   611  			rs = append(rs, r)
   612  		}
   613  		if err != nil {
   614  			break
   615  		}
   616  	}
   617  
   618  	if err != outErr {
   619  		t.Errorf("Expected error %v, got error %v", outErr, err)
   620  	}
   621  	if !reflect.DeepEqual(out, rs) {
   622  		t.Fatalf("expected %v, got %v", out, rs)
   623  	}
   624  }
   625  
   626  func testPartialResults(t *testing.T, scan *hrpc.Scan, expected []*hrpc.Result) {
   627  	t.Helper()
   628  	ctrl := gomock.NewController(t)
   629  	defer ctrl.Finish()
   630  	c := mock.NewMockRPCClient(ctrl)
   631  
   632  	tcase := []struct {
   633  		region              hrpc.RegionInfo
   634  		results             []*pb.Result
   635  		moreResultsInRegion bool
   636  		scanFromID          bool
   637  	}{
   638  		{
   639  			region: region1,
   640  			results: []*pb.Result{
   641  				&pb.Result{Cell: cells[:3], Partial: proto.Bool(true)},
   642  				&pb.Result{Cell: cells[3:4], Partial: proto.Bool(true)},
   643  			},
   644  			moreResultsInRegion: true,
   645  		},
   646  		{ // end of region, should return row b
   647  			region: region1,
   648  			results: []*pb.Result{
   649  				&pb.Result{Cell: cells[4:5], Partial: proto.Bool(true)},
   650  			},
   651  			scanFromID: true,
   652  		},
   653  		{ // half a row in a result in the same response - unlikely, but why not
   654  			region: region2,
   655  			results: []*pb.Result{
   656  				&pb.Result{Cell: cells[5:7], Partial: proto.Bool(true)},
   657  				&pb.Result{Cell: cells[7:8], Partial: proto.Bool(true)},
   658  			},
   659  			moreResultsInRegion: true,
   660  		},
   661  		{ // empty result, last in region
   662  			region:     region2,
   663  			results:    []*pb.Result{&pb.Result{Cell: cells[8:8], Partial: proto.Bool(true)}},
   664  			scanFromID: true,
   665  		},
   666  		{
   667  			region: region3,
   668  			results: []*pb.Result{
   669  				&pb.Result{Cell: cells[8:9], Partial: proto.Bool(true)},
   670  			},
   671  			moreResultsInRegion: true,
   672  		},
   673  		{ // last row
   674  			region: region3,
   675  			results: []*pb.Result{
   676  				&pb.Result{Cell: cells[9:], Partial: proto.Bool(true)},
   677  			},
   678  			scanFromID: true,
   679  		},
   680  	}
   681  
   682  	var scannerID uint64
   683  	scanner := newScanner(c, scan, slog.Default())
   684  	ctx := scan.Context()
   685  	for _, partial := range tcase {
   686  		partial := partial
   687  		var s *hrpc.Scan
   688  		var err error
   689  		if partial.scanFromID {
   690  			s, err = hrpc.NewScanRange(ctx, table, partial.region.StartKey(), nil,
   691  				hrpc.ScannerID(scannerID))
   692  		} else {
   693  			s, err = hrpc.NewScanRange(ctx, table, partial.region.StartKey(), nil,
   694  				scan.Options()...)
   695  			scannerID++
   696  		}
   697  		if err != nil {
   698  			t.Fatal(err)
   699  		}
   700  
   701  		c.EXPECT().SendRPC(&scanMatcher{scan: s}).Do(func(rpc hrpc.Call) {
   702  			rpc.SetRegion(partial.region)
   703  		}).Return(&pb.ScanResponse{
   704  			ScannerId:           cp(scannerID),
   705  			MoreResultsInRegion: &partial.moreResultsInRegion,
   706  			Results:             partial.results,
   707  		}, nil).Times(1)
   708  	}
   709  
   710  	var rs []*hrpc.Result
   711  	for {
   712  		r, err := scanner.Next()
   713  		if err == io.EOF {
   714  			break
   715  		}
   716  		if err != nil {
   717  			t.Fatal(err)
   718  		}
   719  		rs = append(rs, r)
   720  	}
   721  
   722  	if !reflect.DeepEqual(expected, rs) {
   723  		t.Fatalf("expected %v, got %s", expected, rs)
   724  	}
   725  }
   726  
   727  func TestReversedScanner(t *testing.T) {
   728  	ctrl := test.NewController(t)
   729  	defer ctrl.Finish()
   730  	c := mock.NewMockRPCClient(ctrl)
   731  
   732  	ctx := context.Background()
   733  	scan, err := hrpc.NewScan(ctx, table, hrpc.Reversed())
   734  	if err != nil {
   735  		t.Fatal(err)
   736  	}
   737  
   738  	var scannerID uint64 = 42
   739  
   740  	scanner := newScanner(c, scan, slog.Default())
   741  	ctx = scan.Context()
   742  	s, err := hrpc.NewScanRange(ctx, table, nil, nil, hrpc.Reversed())
   743  	if err != nil {
   744  		t.Fatal(err)
   745  	}
   746  	c.EXPECT().SendRPC(&scanMatcher{scan: s}).Do(func(rpc hrpc.Call) {
   747  		rpc.SetRegion(region3)
   748  	}).Return(&pb.ScanResponse{
   749  		ScannerId: cp(scannerID),
   750  		Results:   dup(resultsPB[3:4]),
   751  	}, nil).Times(1)
   752  
   753  	s, err = hrpc.NewScanRange(ctx, table,
   754  		append([]byte("fon"), rowPadding...), nil, hrpc.Reversed())
   755  	if err != nil {
   756  		t.Fatal(err)
   757  	}
   758  	c.EXPECT().SendRPC(&scanMatcher{scan: s}).Do(func(rpc hrpc.Call) {
   759  		rpc.SetRegion(region2)
   760  	}).Return(&pb.ScanResponse{
   761  		ScannerId: cp(scannerID),
   762  		Results:   dup(resultsPB[2:3]),
   763  	}, nil).Times(1)
   764  
   765  	s, err = hrpc.NewScanRange(ctx, table,
   766  		append([]byte("baq"), rowPadding...), nil, hrpc.Reversed())
   767  	if err != nil {
   768  		t.Fatal(err)
   769  	}
   770  	c.EXPECT().SendRPC(&scanMatcher{scan: s}).Do(func(rpc hrpc.Call) {
   771  		rpc.SetRegion(region1)
   772  	}).Return(&pb.ScanResponse{
   773  		MoreResultsInRegion: proto.Bool(true),
   774  		ScannerId:           cp(scannerID),
   775  		Results:             dup(resultsPB[1:2]),
   776  	}, nil).Times(1)
   777  
   778  	s, err = hrpc.NewScanRange(ctx, table, nil, nil, hrpc.ScannerID(scannerID))
   779  	if err != nil {
   780  		t.Fatal(err)
   781  	}
   782  	c.EXPECT().SendRPC(&scanMatcher{
   783  		scan: s,
   784  	}).Do(func(rpc hrpc.Call) {
   785  		rpc.SetRegion(region1)
   786  	}).Return(&pb.ScanResponse{
   787  		Results: dup(resultsPB[:1]),
   788  	}, nil).Times(1)
   789  
   790  	var rs []*hrpc.Result
   791  	for {
   792  		r, err := scanner.Next()
   793  		if err == io.EOF {
   794  			break
   795  		}
   796  		if err != nil {
   797  			t.Fatal(err)
   798  		}
   799  		rs = append(rs, r)
   800  	}
   801  
   802  	var expected []*hrpc.Result
   803  	for i := len(resultsPB) - 1; i >= 0; i-- {
   804  		expected = append(expected, hrpc.ToLocalResult(resultsPB[i]))
   805  	}
   806  
   807  	if !reflect.DeepEqual(expected, rs) {
   808  		t.Fatalf("expected %v, got %v", expected, rs)
   809  	}
   810  }
   811  
   812  func TestScannerWithContextCanceled(t *testing.T) {
   813  	ctrl := test.NewController(t)
   814  	defer ctrl.Finish()
   815  	c := mock.NewMockRPCClient(ctrl)
   816  
   817  	ctx, cancel := context.WithCancel(context.Background())
   818  	scan, err := hrpc.NewScan(ctx, []byte(t.Name()))
   819  	if err != nil {
   820  		t.Fatal(err)
   821  	}
   822  
   823  	scanner := newScanner(c, scan, slog.Default())
   824  
   825  	cancel()
   826  
   827  	_, err = scanner.Next()
   828  	if err != context.Canceled {
   829  		t.Fatalf("unexpected error %v, expected %v", err, context.Canceled)
   830  	}
   831  }
   832  
   833  func TestScannerClosed(t *testing.T) {
   834  	ctrl := test.NewController(t)
   835  	defer ctrl.Finish()
   836  	c := mock.NewMockRPCClient(ctrl)
   837  
   838  	scan, err := hrpc.NewScan(context.Background(), []byte(t.Name()))
   839  	if err != nil {
   840  		t.Fatal(err)
   841  	}
   842  
   843  	scanner := newScanner(c, scan, slog.Default())
   844  	scanner.Close()
   845  
   846  	_, err = scanner.Next()
   847  	if err != io.EOF {
   848  		t.Fatalf("unexpected error %v, expected %v", err, io.EOF)
   849  	}
   850  }