go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/logdog/common/storage/bigtable/testing.go (about)

     1  // Copyright 2016 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package bigtable
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"fmt"
    21  
    22  	"go.chromium.org/luci/common/data/recordio"
    23  	"go.chromium.org/luci/common/data/treapstore"
    24  	"go.chromium.org/luci/logdog/common/storage"
    25  )
    26  
    27  type storageItem struct {
    28  	key   []byte
    29  	value []byte
    30  }
    31  
    32  // Testing is an extension of storage.Storage with additional testing
    33  // capabilities.
    34  type Testing interface {
    35  	storage.Storage
    36  
    37  	DataMap() map[string][]byte
    38  	SetMaxRowSize(int)
    39  	SetErr(error)
    40  }
    41  
    42  type btTestingStorage struct {
    43  	*Storage
    44  
    45  	maxRowSize int
    46  
    47  	s *treapstore.Store
    48  	c *treapstore.Collection
    49  
    50  	// err, if true, is the error immediately returned by functions.
    51  	err error
    52  }
    53  
    54  func (bts *btTestingStorage) DataMap() map[string][]byte { return bts.dataMap() }
    55  func (bts *btTestingStorage) SetMaxRowSize(v int)        { bts.maxRowSize = v }
    56  func (bts *btTestingStorage) SetErr(err error)           { bts.err = err }
    57  
    58  // NewMemoryInstance returns an in-memory BigTable Storage implementation.
    59  // This can be supplied in the Raw field in Options to simulate a BigTable
    60  // connection.
    61  //
    62  // An optional cache can be supplied to test caching logic.
    63  //
    64  // Close should be called on the resulting value after the user is finished in
    65  // order to free resources.
    66  func NewMemoryInstance(cache storage.Cache) Testing {
    67  	s := Storage{
    68  		LogTable: "test-log-table",
    69  		Cache:    cache,
    70  	}
    71  
    72  	ts := treapstore.New()
    73  	bts := btTestingStorage{
    74  		Storage:    &s,
    75  		maxRowSize: bigTableRowMaxBytes,
    76  		s:          ts,
    77  		c: ts.CreateCollection("", func(a, b any) int {
    78  			return bytes.Compare(a.(*storageItem).key, b.(*storageItem).key)
    79  		}),
    80  	}
    81  
    82  	// Set our BigTable interface to the in-memory testing instance.
    83  	s.testBTInterface = &bts
    84  	return &bts
    85  }
    86  
    87  func (bts *btTestingStorage) putLogData(c context.Context, rk *rowKey, d []byte) error {
    88  	if bts.err != nil {
    89  		return bts.err
    90  	}
    91  
    92  	// Record/count sanity check.
    93  	records, err := recordio.Split(d)
    94  	if err != nil {
    95  		return err
    96  	}
    97  	if int64(len(records)) != rk.count {
    98  		return fmt.Errorf("count mismatch (%d != %d)", len(records), rk.count)
    99  	}
   100  
   101  	enc := []byte(rk.encode())
   102  	if item := bts.c.Get(&storageItem{enc, nil}); item != nil {
   103  		return storage.ErrExists
   104  	}
   105  
   106  	clone := make([]byte, len(d))
   107  	copy(clone, d)
   108  	bts.c.Put(&storageItem{enc, clone})
   109  
   110  	return nil
   111  }
   112  
   113  func (bts *btTestingStorage) dropRowRange(c context.Context, rk *rowKey) error {
   114  	it := bts.c.Iterator(&storageItem{[]byte(rk.pathPrefix()), nil})
   115  	for {
   116  		itm, ok := it.Next()
   117  		if !ok {
   118  			return nil
   119  		}
   120  		drk, err := decodeRowKey(string((itm.(*storageItem)).key))
   121  		if err != nil {
   122  			return err
   123  		}
   124  		if !drk.sharesPathWith(rk) {
   125  			return nil
   126  		}
   127  		bts.c.Delete(itm)
   128  	}
   129  }
   130  
   131  func (bts *btTestingStorage) forEachItem(start []byte, cb func(k, v []byte) bool) {
   132  	it := bts.c.Iterator(&storageItem{start, nil})
   133  	for {
   134  		itm, ok := it.Next()
   135  		if !ok {
   136  			return
   137  		}
   138  		ent := itm.(*storageItem)
   139  		if !cb(ent.key, ent.value) {
   140  			return
   141  		}
   142  	}
   143  }
   144  
   145  func (bts *btTestingStorage) getLogData(c context.Context, rk *rowKey, limit int, keysOnly bool, cb btGetCallback) error {
   146  	if bts.err != nil {
   147  		return bts.err
   148  	}
   149  
   150  	enc := []byte(rk.encode())
   151  	prefix := rk.pathPrefix()
   152  	var ierr error
   153  
   154  	bts.forEachItem(enc, func(k, v []byte) bool {
   155  		var drk *rowKey
   156  		drk, ierr = decodeRowKey(string(k))
   157  		if ierr != nil {
   158  			return false
   159  		}
   160  		if drk.pathPrefix() != prefix {
   161  			return false
   162  		}
   163  
   164  		rowData := v
   165  		if keysOnly {
   166  			rowData = nil
   167  		}
   168  
   169  		if ierr = cb(drk, rowData); ierr != nil {
   170  			if ierr == errStop {
   171  				ierr = nil
   172  			}
   173  			return false
   174  		}
   175  
   176  		if limit > 0 {
   177  			limit--
   178  			if limit == 0 {
   179  				return false
   180  			}
   181  		}
   182  
   183  		return true
   184  	})
   185  	return ierr
   186  }
   187  
   188  func (bts *btTestingStorage) dataMap() map[string][]byte {
   189  	result := map[string][]byte{}
   190  
   191  	bts.forEachItem(nil, func(k, v []byte) bool {
   192  		result[string(k)] = v
   193  		return true
   194  	})
   195  	return result
   196  }
   197  
   198  func (bts *btTestingStorage) getMaxRowSize() int { return bts.maxRowSize }