go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/logdog/client/butlerlib/streamclient/fake.go (about) 1 // Copyright 2019 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 streamclient 16 17 import ( 18 "fmt" 19 "io" 20 "strconv" 21 "strings" 22 "sync" 23 "sync/atomic" 24 25 "go.chromium.org/luci/common/errors" 26 "go.chromium.org/luci/logdog/client/butlerlib/streamproto" 27 "go.chromium.org/luci/logdog/common/types" 28 ) 29 30 // fakeStream represents a dialed stream, and has methods to retrieve the data 31 // written to it. 32 type fakeStream struct { 33 flags streamproto.Flags 34 35 mu sync.RWMutex 36 closed bool 37 streamData strings.Builder 38 datagrams []string 39 } 40 41 var _ interface { 42 DatagramStream 43 io.Writer 44 FakeStreamData 45 } = (*fakeStream)(nil) 46 47 func (ds *fakeStream) Close() error { 48 ds.mu.Lock() 49 defer ds.mu.Unlock() 50 if ds.closed { 51 return io.ErrClosedPipe 52 } 53 ds.closed = true 54 return nil 55 } 56 57 func (ds *fakeStream) Write(data []byte) (int, error) { 58 ds.mu.Lock() 59 defer ds.mu.Unlock() 60 if ds.closed { 61 return 0, io.ErrClosedPipe 62 } 63 return ds.streamData.Write(data) 64 } 65 66 func (ds *fakeStream) WriteDatagram(dg []byte) error { 67 ds.mu.Lock() 68 defer ds.mu.Unlock() 69 if ds.closed { 70 return io.ErrClosedPipe 71 } 72 ds.datagrams = append(ds.datagrams, string(dg)) 73 return nil 74 } 75 76 func (ds *fakeStream) GetFlags() streamproto.Flags { 77 return ds.flags 78 } 79 80 func (ds *fakeStream) GetStreamData() string { 81 ds.mu.RLock() 82 defer ds.mu.RUnlock() 83 return ds.streamData.String() 84 } 85 86 func (ds *fakeStream) GetDatagrams() []string { 87 ds.mu.RLock() 88 defer ds.mu.RUnlock() 89 return append(make([]string, 0, len(ds.datagrams)), ds.datagrams...) 90 } 91 92 func (ds *fakeStream) IsClosed() bool { 93 ds.mu.RLock() 94 defer ds.mu.RUnlock() 95 return ds.closed 96 } 97 98 type fakeDialer struct { 99 mu sync.RWMutex 100 err error 101 streams map[types.StreamName]*fakeStream 102 } 103 104 var _ dialer = (*fakeDialer)(nil) 105 106 // Makes a shallow interface-based map of all of the fakeStreams. 107 // 108 // Because the FakeStreamData interface is read-only, this should be safe. 109 func (d *fakeDialer) dumpFakeData() map[types.StreamName]FakeStreamData { 110 d.mu.RLock() 111 defer d.mu.RUnlock() 112 ret := make(map[types.StreamName]FakeStreamData, len(d.streams)) 113 for k, v := range d.streams { 114 ret[k] = v 115 } 116 return ret 117 } 118 119 func (d *fakeDialer) setFakeError(err error) { 120 d.mu.Lock() 121 d.err = err 122 d.mu.Unlock() 123 } 124 125 func (d *fakeDialer) getStream(f streamproto.Flags) (*fakeStream, error) { 126 d.mu.Lock() 127 defer d.mu.Unlock() 128 129 if d.err != nil { 130 return nil, d.err 131 } 132 streamName := types.StreamName(f.Name) 133 if _, hasStream := d.streams[streamName]; hasStream { 134 return nil, errors.Reason("stream %q already dialed", streamName).Err() 135 } 136 137 ret := &fakeStream{flags: f} 138 d.streams[streamName] = ret 139 return ret, nil 140 } 141 142 func (d *fakeDialer) DialStream(forProcess bool, f streamproto.Flags) (io.WriteCloser, error) { 143 return d.getStream(f) 144 } 145 146 func (d *fakeDialer) DialDgramStream(f streamproto.Flags) (DatagramStream, error) { 147 return d.getStream(f) 148 } 149 150 // These support NewFake and the "fake" protocol handler. 151 var ( 152 fakeRegistryMu sync.Mutex 153 fakeRegistryID int64 154 fakeRegistry = map[int64]Fake{} 155 ) 156 157 // NewFake makes an in-memory Fake which absorbs all data written via Clients 158 // connected to it, and allows retrieval of this data via extra methods. 159 // 160 // You must call Fake.Unregister to free the collected data. 161 // 162 // You can obtain a Client for this Fake by doing: 163 // 164 // fake := streamclient.NewFake() 165 // defer fake.Unregister() 166 // client, _ := streamclient.New(fake.StreamServerPath(), "whatever") 167 // 168 // This is particularly useful for testing programs which use 169 // bootstrap.GetFromEnv() to obtain the Client: 170 // 171 // env := environ.New(nil) 172 // env.Set(bootstrap.EnvStreamServerPath, client.StreamServerPath()) 173 // 174 // bs := bootstrap.GetFromEnv(env) # returns Bootstrap containing `client`. 175 // 176 // If you JUST want a single client and don't need streamclient.New to work, 177 // consider NewUnregisteredFake. 178 func NewFake() Fake { 179 fakeRegistryMu.Lock() 180 defer fakeRegistryMu.Unlock() 181 182 id := fakeRegistryID + 1 183 fakeRegistryID++ 184 185 ret := Fake{ 186 id, 187 &fakeDialer{streams: map[types.StreamName]*fakeStream{}}, 188 } 189 fakeRegistry[id] = ret 190 return ret 191 } 192 193 // NewUnregisteredFake returns a new Fake as well as a *Client for this Fake. 194 // 195 // Fake.StreamServerPath() will return an empty string and Unregister is 196 // a no-op. 197 // 198 // Aside from the returned *Client there's no way to obtain another *Client 199 // for this Fake. 200 func NewUnregisteredFake(namespace types.StreamName) (Fake, *Client) { 201 f := Fake{ 202 0, 203 &fakeDialer{streams: map[types.StreamName]*fakeStream{}}, 204 } 205 return f, &Client{f.dial, namespace} 206 } 207 208 // Fake implements an in-memory sink for Client connections and data. 209 // 210 // You can retrieve the collected data via the Data method. 211 type Fake struct { 212 id int64 // 0 means "unregistered" 213 dial *fakeDialer 214 } 215 216 // Unregister idempotently removes this Fake from the global registry. 217 // 218 // This has no effect for a Fake created with NewUnregisteredFake. 219 // 220 // After calling Unregister, StreamServerPath will return an empty string. 221 func (f Fake) Unregister() { 222 id := atomic.SwapInt64(&f.id, 0) 223 if id == 0 { 224 return 225 } 226 fakeRegistryMu.Lock() 227 defer fakeRegistryMu.Unlock() 228 delete(fakeRegistry, id) 229 } 230 231 // StreamServerPath returns a value suitable for streamclient.New that will 232 // allow construction of a *Client which points to this Fake. 233 // 234 // If this Fake was generated via NewUnregisteredFake, or you called 235 // Unregister on it, returns an empty string. 236 func (f Fake) StreamServerPath() string { 237 id := atomic.LoadInt64(&f.id) 238 if id == 0 { 239 return "" 240 } 241 return fmt.Sprintf("fake:%d", id) 242 } 243 244 // FakeStreamData is data about a single stream for a client created with the 245 // "fake" protocol. You can retrieve this with Fake.Data(). 246 type FakeStreamData interface { 247 // GetFlags returns the stream's streamproto.Flags from when the stream was 248 // opened. 249 GetFlags() streamproto.Flags 250 251 // GetStreamData returns the written text/binary data as a string. 252 // 253 // If this is a datagram stream, returns "". 254 GetStreamData() string 255 256 // GetDatagrams returns the written datagrams as a list of strings. 257 // 258 // If this is a text/binary stream, returns nil. 259 GetDatagrams() []string 260 261 // Returns true iff the user of the client has closed this stream. 262 IsClosed() bool 263 } 264 265 // Data returns all absorbed data collected by this Fake so far. 266 // 267 // Note that the FakeStreamData objects are `live`, and so calling their methods 268 // will always get you the current value of those streams. You'll need to call 269 // this method again if you expect a new stream to show up, however. 270 func (f Fake) Data() map[types.StreamName]FakeStreamData { 271 return f.dial.dumpFakeData() 272 } 273 274 // SetError causes New* methods for Clients of this Fake to return this error. 275 func (f Fake) SetError(err error) { 276 f.dial.setFakeError(err) 277 } 278 279 func init() { 280 protocolRegistry["fake"] = func(fakeId string) (dialer, error) { 281 id, err := strconv.ParseInt(fakeId, 10, 64) 282 if err != nil { 283 return nil, err 284 } 285 286 fakeRegistryMu.Lock() 287 defer fakeRegistryMu.Unlock() 288 f, ok := fakeRegistry[id] 289 if !ok { 290 return nil, errors.Reason("unknown Fake %d", id).Err() 291 } 292 return f.dial, nil 293 } 294 }