go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/exec/execmock/uses.go (about) 1 // Copyright 2023 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 execmock 16 17 import ( 18 "context" 19 "os" 20 "sync" 21 22 "go.chromium.org/luci/common/errors" 23 "go.chromium.org/luci/common/exec/internal/execmockctx" 24 "go.chromium.org/luci/common/system/environ" 25 ) 26 27 type usage interface { 28 setOutput(any, string, error) 29 getErrorOutput() (string, error) 30 } 31 32 type uses interface { 33 len() int 34 addUsage(*execmockctx.MockCriteria, **os.Process) usage 35 } 36 37 // Uses is used to collect uses of a particular mock (Runner + input data). 38 // 39 // Your test code can interrogate this object after running your code-under-test 40 // to determine how many times the corresponding mock entry was used, what 41 // sub-processes were actually launched (and handles to those processes for your 42 // test to signal/read/etc.), and, if those sub-processes finished, what `Out` 43 // data did they return. 44 type Uses[Out any] struct { 45 mu sync.Mutex 46 47 uses []*Usage[Out] 48 } 49 50 func (u *Uses[Out]) len() int { 51 u.mu.Lock() 52 defer u.mu.Unlock() 53 return len(u.uses) 54 } 55 56 func (u *Uses[Out]) addUsage(mc *execmockctx.MockCriteria, proc **os.Process) usage { 57 u.mu.Lock() 58 defer u.mu.Unlock() 59 ret := &Usage[Out]{ 60 Args: mc.Args, 61 Env: mc.Env, 62 63 proc: proc, 64 outputWritten: make(chan struct{}), 65 } 66 u.uses = append(u.uses, ret) 67 return ret 68 } 69 70 // Snapshot retrieves a snapshot of the current Usages. 71 func (u *Uses[Out]) Snapshot() []*Usage[Out] { 72 u.mu.Lock() 73 defer u.mu.Unlock() 74 ret := make([]*Usage[Out], len(u.uses)) 75 copy(ret, u.uses) 76 return ret 77 } 78 79 var ErrProcessNotStarted = errors.New("sub-process did not start yet") 80 81 // Usage represents a single `hit` for a given mock. 82 type Usage[Out any] struct { 83 // Args and Env are the exact Args and Env of the Cmd which matched this mock. 84 Args []string 85 Env environ.Env 86 87 proc **os.Process 88 89 mu sync.Mutex 90 outputWritten chan struct{} 91 output Out 92 panicStack string 93 err error 94 } 95 96 // GetPID returns the process ID associated with this usage (i.e. the mock 97 // Process) 98 // 99 // NOTE: This is NOT thread-safe; Due to the way that the stdlib exec library 100 // works with regard to populating Cmd.Process, you must ensure that you only 101 // call this from a thread which was sequential with the thread which called 102 // Cmd.Start() (or Run() or CombinedOutput()). 103 // 104 // If this Usage is from a MockError invocation, this will always return nil. 105 // 106 // Returns 0 if the process is not started. 107 func (u *Usage[Out]) GetPID() int { 108 p := *u.proc 109 if p != nil { 110 return p.Pid 111 } 112 return 0 113 } 114 115 // Signal sends a signal to the mock process. 116 // 117 // NOTE: This is NOT thread-safe; Due to the way that the stdlib exec library 118 // works with regard to populating Cmd.Process, you must ensure that you only 119 // call this from a thread which was sequential with the thread which called 120 // Cmd.Start() (or Run() or CombinedOutput()). 121 // 122 // If this Usage is from a MockError invocation, this will always return an 123 // error. 124 func (u *Usage[Out]) Signal(sig os.Signal) error { 125 p := *u.proc 126 if p != nil { 127 return p.Signal(sig) 128 } 129 return ErrProcessNotStarted 130 } 131 132 // Kill kills mock process (usually by sending SIGKILL). 133 // 134 // NOTE: This is NOT thread-safe; Due to the way that the stdlib exec library 135 // works with regard to populating Cmd.Process, you must ensure that you only 136 // call this from a thread which was sequential with the thread which called 137 // Cmd.Start() (or Run() or CombinedOutput()). 138 // 139 // If this Usage is from a MockError invocation, this will always return an 140 // error. 141 func (u *Usage[Out]) Kill() error { 142 p := *u.proc 143 if p != nil { 144 return p.Kill() 145 } 146 return ErrProcessNotStarted 147 } 148 149 func (u *Usage[Out]) setOutput(data any, panicStack string, err error) { 150 u.mu.Lock() 151 defer u.mu.Unlock() 152 153 u.output = data.(Out) 154 u.panicStack = panicStack 155 u.err = err 156 close(u.outputWritten) 157 } 158 159 // GetOutput will block until the process writes output data (or until the 160 // provided context ends), and return the Out value written by the sub-process 161 // (if any). 162 // 163 // If this is Usage[None] then this returns (nil, nil) 164 // 165 // Possible errors: 166 // - ctx.Err() if `ctx` is Done. 167 // - errors which occured when reading the output from the sub-process. 168 // - errors which the mock itself (i.e. the RunnerFunction) returned. 169 func (u *Usage[Out]) GetOutput(ctx context.Context) (value Out, panicStack string, err error) { 170 select { 171 case <-ctx.Done(): 172 err = ctx.Err() 173 return 174 case <-u.outputWritten: 175 } 176 177 u.mu.Lock() 178 defer u.mu.Unlock() 179 return u.output, u.panicStack, u.err 180 } 181 182 func (u *Usage[Out]) getErrorOutput() (string, error) { 183 u.mu.Lock() 184 defer u.mu.Unlock() 185 return u.panicStack, u.err 186 }