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 }