Cosmos Nicolaou | ee7abc2 | 2014-05-27 10:50:03 -0700 | [diff] [blame] | 1 | package exec_test |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "io" |
| 6 | "log" |
| 7 | "os" |
| 8 | "os/exec" |
Cosmos Nicolaou | 1c18c1c | 2014-10-08 16:37:10 -0700 | [diff] [blame] | 9 | "strings" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 10 | "sync" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 11 | "testing" |
| 12 | "time" |
| 13 | |
Jiri Simsa | 764efb7 | 2014-12-25 20:57:03 -0800 | [diff] [blame] | 14 | vexec "v.io/core/veyron/lib/exec" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 15 | // Use mock timekeeper to avoid actually sleeping during the test. |
Jiri Simsa | 764efb7 | 2014-12-25 20:57:03 -0800 | [diff] [blame] | 16 | "v.io/core/veyron/lib/testutil/timekeeper" |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 17 | ) |
| 18 | |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 19 | // We always expect there to be exactly three open file descriptors |
| 20 | // when the test starts out: STDIN, STDOUT, and STDERR. This |
| 21 | // assumption is tested in init below, and in the rare cases where it |
| 22 | // is wrong, we bail out. |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 23 | const baselineOpenFiles = 3 |
| 24 | |
| 25 | func init() { |
Cosmos Nicolaou | ee7abc2 | 2014-05-27 10:50:03 -0700 | [diff] [blame] | 26 | if os.Getenv("GO_WANT_HELPER_PROCESS_EXEC") == "1" { |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 27 | return |
| 28 | } |
| 29 | if want, got := baselineOpenFiles, openFiles(); want != got { |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 30 | message := `Test expected to start with %d open files, found %d instead. |
| 31 | This can happen if parent process has any open file descriptors, |
| 32 | e.g. pipes, that are being inherited.` |
| 33 | panic(fmt.Errorf(message, want, got)) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 34 | } |
| 35 | } |
| 36 | |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 37 | // These tests need to run a subprocess and we reuse this same test |
| 38 | // binary to do so. A fake test 'TestHelperProcess' contains the code |
| 39 | // we need to run in the child and we simply run this same binary with |
| 40 | // a test.run= arg that runs just that test. This idea was taken from |
| 41 | // the tests for os/exec. |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 42 | func helperCommand(s ...string) *exec.Cmd { |
| 43 | cs := []string{"-test.run=TestHelperProcess", "--"} |
| 44 | cs = append(cs, s...) |
| 45 | cmd := exec.Command(os.Args[0], cs...) |
Cosmos Nicolaou | ee7abc2 | 2014-05-27 10:50:03 -0700 | [diff] [blame] | 46 | cmd.Env = append([]string{"GO_WANT_HELPER_PROCESS_EXEC=1"}, os.Environ()...) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 47 | return cmd |
| 48 | } |
| 49 | |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 50 | func openFiles() int { |
| 51 | f, err := os.Open("/dev/null") |
| 52 | if err != nil { |
| 53 | panic("Failed to open /dev/null\n") |
| 54 | } |
| 55 | n := f.Fd() |
| 56 | f.Close() |
| 57 | return int(n) |
| 58 | } |
| 59 | |
Cosmos Nicolaou | ee7abc2 | 2014-05-27 10:50:03 -0700 | [diff] [blame] | 60 | func clean(t *testing.T, ph ...*vexec.ParentHandle) { |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 61 | for _, p := range ph { |
Cosmos Nicolaou | ee7abc2 | 2014-05-27 10:50:03 -0700 | [diff] [blame] | 62 | alreadyClean := !p.Exists() |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 63 | p.Clean() |
Cosmos Nicolaou | ee7abc2 | 2014-05-27 10:50:03 -0700 | [diff] [blame] | 64 | if !alreadyClean && p.Exists() { |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 65 | t.Errorf("child process left behind even after calling Clean") |
| 66 | } |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 67 | } |
| 68 | if want, got := baselineOpenFiles, openFiles(); want != got { |
| 69 | t.Errorf("Leaking file descriptors: expect %d, got %d", want, got) |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | func read(ch chan bool, r io.Reader, m string) { |
| 74 | buf := make([]byte, 4096*4) |
| 75 | n, err := r.Read(buf) |
| 76 | if err != nil { |
| 77 | log.Printf("failed to read message: error %s, expecting '%s'\n", |
| 78 | err, m) |
| 79 | ch <- false |
| 80 | return |
| 81 | } |
| 82 | g := string(buf[:n]) |
| 83 | b := g == m |
| 84 | if !b { |
| 85 | log.Printf("read '%s', not '%s'\n", g, m) |
| 86 | } |
| 87 | ch <- b |
| 88 | } |
| 89 | |
| 90 | func expectMessage(r io.Reader, m string) bool { |
| 91 | ch := make(chan bool, 1) |
| 92 | go read(ch, r, m) |
| 93 | select { |
| 94 | case b := <-ch: |
| 95 | return b |
| 96 | case <-time.After(5 * time.Second): |
| 97 | log.Printf("expectMessage: timeout\n") |
| 98 | return false |
| 99 | } |
| 100 | panic("unreachable") |
| 101 | } |
| 102 | |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 103 | func TestConfigExchange(t *testing.T) { |
| 104 | cmd := helperCommand("testConfig") |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 105 | stderr, _ := cmd.StderrPipe() |
Cosmos Nicolaou | 486d349 | 2014-09-30 22:21:20 -0700 | [diff] [blame] | 106 | cfg := vexec.NewConfig() |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 107 | cfg.Set("foo", "bar") |
| 108 | ph := vexec.NewParentHandle(cmd, vexec.ConfigOpt{cfg}) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 109 | err := ph.Start() |
| 110 | if err != nil { |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 111 | t.Fatalf("testConfig: start: %v", err) |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 112 | } |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 113 | serialized, err := cfg.Serialize() |
| 114 | if err != nil { |
| 115 | t.Fatalf("testConfig: failed to serialize config: %v", err) |
| 116 | } |
| 117 | if !expectMessage(stderr, serialized) { |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 118 | t.Errorf("unexpected output from child") |
| 119 | } else { |
| 120 | if err = cmd.Wait(); err != nil { |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 121 | t.Errorf("testConfig: wait: %v", err) |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 122 | } |
| 123 | } |
| 124 | clean(t, ph) |
| 125 | } |
| 126 | |
| 127 | func TestSecretExchange(t *testing.T) { |
| 128 | cmd := helperCommand("testSecret") |
| 129 | stderr, _ := cmd.StderrPipe() |
| 130 | ph := vexec.NewParentHandle(cmd, vexec.SecretOpt("dummy_secret")) |
| 131 | err := ph.Start() |
| 132 | if err != nil { |
| 133 | t.Fatalf("testSecretTest: start: %v", err) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 134 | } |
| 135 | if !expectMessage(stderr, "dummy_secret") { |
| 136 | t.Errorf("unexpected output from child") |
| 137 | } else { |
| 138 | if err = cmd.Wait(); err != nil { |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 139 | t.Errorf("testSecretTest: wait: %v", err) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 140 | } |
| 141 | } |
| 142 | clean(t, ph) |
| 143 | } |
| 144 | |
| 145 | func TestNoVersion(t *testing.T) { |
| 146 | // Make sure that Init correctly tests for the presence of VEXEC_VERSION |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 147 | _, err := vexec.GetChildHandle() |
Cosmos Nicolaou | ee7abc2 | 2014-05-27 10:50:03 -0700 | [diff] [blame] | 148 | if err != vexec.ErrNoVersion { |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 149 | t.Errorf("Should be missing Version") |
| 150 | } |
| 151 | } |
| 152 | |
Cosmos Nicolaou | ee7abc2 | 2014-05-27 10:50:03 -0700 | [diff] [blame] | 153 | func waitForReady(t *testing.T, cmd *exec.Cmd, name string, delay int, ph *vexec.ParentHandle) error { |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 154 | err := ph.Start() |
| 155 | if err != nil { |
| 156 | t.Fatalf("%s: start: %v", name, err) |
| 157 | return err |
| 158 | } |
| 159 | return ph.WaitForReady(time.Duration(delay) * time.Second) |
| 160 | } |
| 161 | |
Cosmos Nicolaou | ee7abc2 | 2014-05-27 10:50:03 -0700 | [diff] [blame] | 162 | func readyHelperCmd(t *testing.T, cmd *exec.Cmd, name, result string) *vexec.ParentHandle { |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 163 | stderr, err := cmd.StderrPipe() |
| 164 | if err != nil { |
| 165 | t.Fatalf("%s: failed to get stderr pipe\n", name) |
| 166 | } |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 167 | ph := vexec.NewParentHandle(cmd) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 168 | if err := waitForReady(t, cmd, name, 4, ph); err != nil { |
| 169 | t.Errorf("%s: WaitForReady: %v (%v)", name, err, ph) |
| 170 | return nil |
| 171 | } |
| 172 | if !expectMessage(stderr, result) { |
| 173 | t.Errorf("%s: failed to read '%s' from child\n", name, result) |
| 174 | } |
| 175 | return ph |
| 176 | } |
| 177 | |
Cosmos Nicolaou | ee7abc2 | 2014-05-27 10:50:03 -0700 | [diff] [blame] | 178 | func readyHelper(t *testing.T, name, test, result string) *vexec.ParentHandle { |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 179 | cmd := helperCommand(test) |
| 180 | return readyHelperCmd(t, cmd, name, result) |
| 181 | } |
| 182 | |
Cosmos Nicolaou | ee7abc2 | 2014-05-27 10:50:03 -0700 | [diff] [blame] | 183 | func testMany(t *testing.T, name, test, result string) []*vexec.ParentHandle { |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 184 | nprocs := 10 |
Cosmos Nicolaou | ee7abc2 | 2014-05-27 10:50:03 -0700 | [diff] [blame] | 185 | ph := make([]*vexec.ParentHandle, nprocs) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 186 | cmd := make([]*exec.Cmd, nprocs) |
| 187 | stderr := make([]io.ReadCloser, nprocs) |
| 188 | controlReaders := make([]io.ReadCloser, nprocs) |
| 189 | var done sync.WaitGroup |
| 190 | for i := 0; i < nprocs; i++ { |
| 191 | cmd[i] = helperCommand(test) |
| 192 | // The control pipe is used to signal the child when to wake up. |
| 193 | controlRead, controlWrite, err := os.Pipe() |
| 194 | if err != nil { |
| 195 | t.Errorf("Failed to create control pipe: %v", err) |
| 196 | return nil |
| 197 | } |
| 198 | controlReaders[i] = controlRead |
| 199 | cmd[i].ExtraFiles = append(cmd[i].ExtraFiles, controlRead) |
| 200 | stderr[i], _ = cmd[i].StderrPipe() |
| 201 | tk := timekeeper.NewManualTime() |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 202 | ph[i] = vexec.NewParentHandle(cmd[i], vexec.TimeKeeperOpt{tk}) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 203 | done.Add(1) |
| 204 | go func() { |
| 205 | // For simulated slow children, wait until the parent |
| 206 | // starts waiting, and then wake up the child. |
| 207 | if test == "testReadySlow" { |
| 208 | <-tk.Requests() |
| 209 | tk.AdvanceTime(3 * time.Second) |
| 210 | if _, err = controlWrite.Write([]byte("wake")); err != nil { |
| 211 | t.Errorf("Failed to write to control pipe: %v", err) |
| 212 | } |
| 213 | } |
| 214 | controlWrite.Close() |
| 215 | done.Done() |
| 216 | }() |
| 217 | if err := ph[i].Start(); err != nil { |
| 218 | t.Errorf("%s: Failed to start child %d: %s\n", name, i, err) |
| 219 | } |
| 220 | } |
| 221 | for i := 0; i < nprocs; i++ { |
| 222 | if err := ph[i].WaitForReady(5 * time.Second); err != nil { |
| 223 | t.Errorf("%s: Failed to wait for child %d: %s\n", name, i, err) |
| 224 | } |
| 225 | controlReaders[i].Close() |
| 226 | } |
| 227 | for i := 0; i < nprocs; i++ { |
| 228 | if !expectMessage(stderr[i], result) { |
| 229 | t.Errorf("%s: Failed to read message from child %d\n", name, i) |
| 230 | } |
| 231 | } |
| 232 | done.Wait() |
| 233 | return ph |
| 234 | } |
| 235 | |
| 236 | func TestToReadyMany(t *testing.T) { |
| 237 | clean(t, testMany(t, "TestToReadyMany", "testReady", ".")...) |
| 238 | } |
| 239 | |
| 240 | func TestToReadySlowMany(t *testing.T) { |
| 241 | clean(t, testMany(t, "TestToReadySlowMany", "testReadySlow", "..")...) |
| 242 | } |
| 243 | |
| 244 | func TestToReady(t *testing.T) { |
| 245 | ph := readyHelper(t, "TestToReady", "testReady", ".") |
| 246 | clean(t, ph) |
| 247 | } |
| 248 | |
Cosmos Nicolaou | 1c18c1c | 2014-10-08 16:37:10 -0700 | [diff] [blame] | 249 | func TestToFail(t *testing.T) { |
| 250 | name := "testFail" |
| 251 | cmd := helperCommand(name, "failed", "to", "start") |
| 252 | ph := vexec.NewParentHandle(cmd) |
| 253 | err := waitForReady(t, cmd, name, 4, ph) |
| 254 | if err == nil || err.Error() != "failed to start" { |
| 255 | t.Errorf("unexpected error: %v", err) |
| 256 | } |
| 257 | } |
| 258 | |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 259 | func TestNeverReady(t *testing.T) { |
| 260 | name := "testNeverReady" |
| 261 | result := "never ready" |
| 262 | cmd := helperCommand(name) |
| 263 | stderr, _ := cmd.StderrPipe() |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 264 | ph := vexec.NewParentHandle(cmd) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 265 | err := waitForReady(t, cmd, name, 1, ph) |
Cosmos Nicolaou | ee7abc2 | 2014-05-27 10:50:03 -0700 | [diff] [blame] | 266 | if err != vexec.ErrTimeout { |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 267 | t.Errorf("Failed to get timeout: got %v\n", err) |
| 268 | } else { |
| 269 | // block waiting for error from child |
| 270 | if !expectMessage(stderr, result) { |
| 271 | t.Errorf("%s: failed to read '%s' from child\n", name, result) |
| 272 | } |
| 273 | } |
| 274 | clean(t, ph) |
| 275 | } |
| 276 | |
| 277 | func TestTooSlowToReady(t *testing.T) { |
| 278 | name := "testTooSlowToReady" |
| 279 | result := "write status_wr: broken pipe" |
| 280 | cmd := helperCommand(name) |
| 281 | // The control pipe is used to signal the child when to wake up. |
| 282 | controlRead, controlWrite, err := os.Pipe() |
| 283 | if err != nil { |
| 284 | t.Errorf("Failed to create control pipe: %v", err) |
| 285 | return |
| 286 | } |
| 287 | cmd.ExtraFiles = append(cmd.ExtraFiles, controlRead) |
| 288 | stderr, _ := cmd.StderrPipe() |
| 289 | tk := timekeeper.NewManualTime() |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 290 | ph := vexec.NewParentHandle(cmd, vexec.TimeKeeperOpt{tk}) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 291 | defer clean(t, ph) |
| 292 | defer controlWrite.Close() |
| 293 | defer controlRead.Close() |
| 294 | // Wait for the parent to start waiting, then simulate a timeout. |
| 295 | go func() { |
| 296 | toWait := <-tk.Requests() |
| 297 | tk.AdvanceTime(toWait) |
| 298 | }() |
| 299 | err = waitForReady(t, cmd, name, 1, ph) |
Cosmos Nicolaou | ee7abc2 | 2014-05-27 10:50:03 -0700 | [diff] [blame] | 300 | if err != vexec.ErrTimeout { |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 301 | t.Errorf("Failed to get timeout: got %v\n", err) |
| 302 | } else { |
| 303 | // After the parent timed out, wake up the child and let it |
| 304 | // proceed. |
| 305 | if _, err = controlWrite.Write([]byte("wake")); err != nil { |
| 306 | t.Errorf("Failed to write to control pipe: %v", err) |
| 307 | } else { |
| 308 | // block waiting for error from child |
| 309 | if !expectMessage(stderr, result) { |
| 310 | t.Errorf("%s: failed to read '%s' from child\n", name, result) |
| 311 | } |
| 312 | } |
| 313 | } |
| 314 | } |
| 315 | |
| 316 | func TestToReadySlow(t *testing.T) { |
| 317 | name := "TestToReadySlow" |
| 318 | cmd := helperCommand("testReadySlow") |
| 319 | // The control pipe is used to signal the child when to wake up. |
| 320 | controlRead, controlWrite, err := os.Pipe() |
| 321 | if err != nil { |
| 322 | t.Errorf("Failed to create control pipe: %v", err) |
| 323 | return |
| 324 | } |
| 325 | cmd.ExtraFiles = append(cmd.ExtraFiles, controlRead) |
| 326 | stderr, err := cmd.StderrPipe() |
| 327 | if err != nil { |
| 328 | t.Fatalf("%s: failed to get stderr pipe", name) |
| 329 | } |
| 330 | tk := timekeeper.NewManualTime() |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 331 | ph := vexec.NewParentHandle(cmd, vexec.TimeKeeperOpt{tk}) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 332 | defer clean(t, ph) |
| 333 | defer controlWrite.Close() |
| 334 | defer controlRead.Close() |
| 335 | // Wait for the parent to start waiting, simulate a short wait (but not |
| 336 | // enough to timeout the parent), then wake up the child. |
| 337 | go func() { |
| 338 | <-tk.Requests() |
| 339 | tk.AdvanceTime(2 * time.Second) |
| 340 | if _, err = controlWrite.Write([]byte("wake")); err != nil { |
| 341 | t.Errorf("Failed to write to control pipe: %v", err) |
| 342 | } |
| 343 | }() |
| 344 | if err := waitForReady(t, cmd, name, 4, ph); err != nil { |
| 345 | t.Errorf("%s: WaitForReady: %v (%v)", name, err, ph) |
| 346 | return |
| 347 | } |
| 348 | // After the child has replied, simulate a timeout on the server by |
| 349 | // advacing the time more; at this point, however, the timeout should no |
| 350 | // longer occur since the child already replied. |
| 351 | tk.AdvanceTime(2 * time.Second) |
| 352 | if result := ".."; !expectMessage(stderr, result) { |
| 353 | t.Errorf("%s: failed to read '%s' from child\n", name, result) |
| 354 | } |
| 355 | } |
| 356 | |
| 357 | func TestToCompletion(t *testing.T) { |
| 358 | ph := readyHelper(t, "TestToCompletion", "testSuccess", "...ok") |
| 359 | e := ph.Wait(time.Second) |
| 360 | if e != nil { |
| 361 | t.Errorf("Wait failed: err %s\n", e) |
| 362 | } |
| 363 | clean(t, ph) |
| 364 | } |
| 365 | |
| 366 | func TestToCompletionError(t *testing.T) { |
| 367 | ph := readyHelper(t, "TestToCompletionError", "testError", "...err") |
| 368 | e := ph.Wait(time.Second) |
| 369 | if e == nil { |
| 370 | t.Errorf("Wait failed: err %s\n", e) |
| 371 | } |
| 372 | clean(t, ph) |
| 373 | } |
| 374 | |
| 375 | func TestExtraFiles(t *testing.T) { |
| 376 | cmd := helperCommand("testExtraFiles") |
| 377 | rd, wr, err := os.Pipe() |
| 378 | if err != nil { |
| 379 | t.Fatalf("Failed to create pipe: %s\n", err) |
| 380 | } |
| 381 | cmd.ExtraFiles = append(cmd.ExtraFiles, rd) |
| 382 | msg := "hello there..." |
| 383 | fmt.Fprintf(wr, msg) |
| 384 | ph := readyHelperCmd(t, cmd, "TestExtraFiles", "child: "+msg) |
| 385 | if ph == nil { |
Cosmos Nicolaou | ee7abc2 | 2014-05-27 10:50:03 -0700 | [diff] [blame] | 386 | t.Fatalf("Failed to get vexec.ParentHandle\n") |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 387 | } |
| 388 | e := ph.Wait(1 * time.Second) |
| 389 | if e != nil { |
| 390 | t.Errorf("Wait failed: err %s\n", e) |
| 391 | } |
| 392 | rd.Close() |
| 393 | wr.Close() |
| 394 | clean(t, ph) |
| 395 | } |
| 396 | |
Cosmos Nicolaou | 4e213d7 | 2014-10-26 22:21:52 -0700 | [diff] [blame] | 397 | func TestExitEarly(t *testing.T) { |
| 398 | name := "exitEarly" |
| 399 | cmd := helperCommand(name) |
| 400 | tk := timekeeper.NewManualTime() |
| 401 | ph := vexec.NewParentHandle(cmd, vexec.TimeKeeperOpt{tk}) |
| 402 | err := ph.Start() |
| 403 | if err != nil { |
| 404 | t.Fatalf("%s: start: %v", name, err) |
| 405 | } |
| 406 | e := ph.Wait(time.Second) |
| 407 | if e == nil || e.Error() != "exit status 1" { |
| 408 | t.Errorf("Unexpected value for error: %v\n", e) |
| 409 | } |
| 410 | clean(t, ph) |
| 411 | } |
| 412 | |
Bogdan Caprita | 650b162 | 2014-11-21 15:11:05 -0800 | [diff] [blame] | 413 | func TestWaitAndCleanRace(t *testing.T) { |
Bogdan Caprita | 9038012 | 2014-12-10 16:43:51 -0800 | [diff] [blame] | 414 | name := "testReadyAndHang" |
| 415 | cmd := helperCommand(name) |
Bogdan Caprita | 650b162 | 2014-11-21 15:11:05 -0800 | [diff] [blame] | 416 | tk := timekeeper.NewManualTime() |
| 417 | ph := vexec.NewParentHandle(cmd, vexec.TimeKeeperOpt{tk}) |
Bogdan Caprita | 9038012 | 2014-12-10 16:43:51 -0800 | [diff] [blame] | 418 | if err := waitForReady(t, cmd, name, 1, ph); err != nil { |
| 419 | t.Errorf("%s: WaitForReady: %v (%v)", name, err, ph) |
Bogdan Caprita | 650b162 | 2014-11-21 15:11:05 -0800 | [diff] [blame] | 420 | } |
| 421 | go func() { |
| 422 | // Wait for the ph.Wait below to block, then advance the time |
| 423 | // s.t. the Wait times out. |
| 424 | <-tk.Requests() |
| 425 | tk.AdvanceTime(2 * time.Second) |
| 426 | }() |
| 427 | if got, want := ph.Wait(time.Second), vexec.ErrTimeout; got == nil || got.Error() != want.Error() { |
Bogdan Caprita | 9038012 | 2014-12-10 16:43:51 -0800 | [diff] [blame] | 428 | t.Errorf("Wait returned %v, wanted %v instead", got, want) |
Bogdan Caprita | 650b162 | 2014-11-21 15:11:05 -0800 | [diff] [blame] | 429 | } |
| 430 | if got, want := ph.Clean(), "signal: killed"; got == nil || got.Error() != want { |
Bogdan Caprita | 9038012 | 2014-12-10 16:43:51 -0800 | [diff] [blame] | 431 | t.Errorf("Wait returned %v, wanted %v instead", got, want) |
Bogdan Caprita | 650b162 | 2014-11-21 15:11:05 -0800 | [diff] [blame] | 432 | } |
| 433 | } |
| 434 | |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 435 | func verifyNoExecVariable() { |
| 436 | version := os.Getenv(vexec.VersionVariable) |
| 437 | if len(version) != 0 { |
Bogdan Caprita | 9038012 | 2014-12-10 16:43:51 -0800 | [diff] [blame] | 438 | log.Fatalf("Version variable %q has a value: %s", vexec.VersionVariable, version) |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 439 | } |
| 440 | } |
| 441 | |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 442 | // TestHelperProcess isn't a real test; it's used as a helper process |
| 443 | // for the other tests. |
| 444 | func TestHelperProcess(*testing.T) { |
| 445 | // Return immediately if this is not run as the child helper |
| 446 | // for the other tests. |
Cosmos Nicolaou | ee7abc2 | 2014-05-27 10:50:03 -0700 | [diff] [blame] | 447 | if os.Getenv("GO_WANT_HELPER_PROCESS_EXEC") != "1" { |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 448 | return |
| 449 | } |
| 450 | defer os.Exit(0) |
| 451 | |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 452 | version := os.Getenv(vexec.VersionVariable) |
| 453 | if len(version) == 0 { |
Bogdan Caprita | 9038012 | 2014-12-10 16:43:51 -0800 | [diff] [blame] | 454 | log.Fatalf("Version variable %q has no value", vexec.VersionVariable) |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 455 | } |
| 456 | |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 457 | // Write errors to stderr or using log. since the parent |
| 458 | // process is reading stderr. |
| 459 | args := os.Args |
| 460 | for len(args) > 0 { |
| 461 | if args[0] == "--" { |
| 462 | args = args[1:] |
| 463 | break |
| 464 | } |
| 465 | args = args[1:] |
| 466 | } |
| 467 | |
| 468 | if len(args) == 0 { |
Bogdan Caprita | 9038012 | 2014-12-10 16:43:51 -0800 | [diff] [blame] | 469 | log.Fatal("No command") |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 470 | } |
| 471 | |
| 472 | cmd, args := args[0], args[1:] |
| 473 | |
| 474 | switch cmd { |
Cosmos Nicolaou | 4e213d7 | 2014-10-26 22:21:52 -0700 | [diff] [blame] | 475 | case "exitEarly": |
| 476 | _, err := vexec.GetChildHandle() |
| 477 | if err != nil { |
Bogdan Caprita | 9038012 | 2014-12-10 16:43:51 -0800 | [diff] [blame] | 478 | log.Fatal(err) |
Cosmos Nicolaou | 4e213d7 | 2014-10-26 22:21:52 -0700 | [diff] [blame] | 479 | } |
| 480 | os.Exit(1) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 481 | case "testNeverReady": |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 482 | _, err := vexec.GetChildHandle() |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 483 | if err != nil { |
Bogdan Caprita | 9038012 | 2014-12-10 16:43:51 -0800 | [diff] [blame] | 484 | log.Fatal(err) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 485 | } |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 486 | verifyNoExecVariable() |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 487 | fmt.Fprintf(os.Stderr, "never ready") |
| 488 | case "testTooSlowToReady": |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 489 | ch, err := vexec.GetChildHandle() |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 490 | if err != nil { |
Bogdan Caprita | 9038012 | 2014-12-10 16:43:51 -0800 | [diff] [blame] | 491 | log.Fatal(err) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 492 | } |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 493 | verifyNoExecVariable() |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 494 | // Wait for the parent to tell us when it's ok to proceed. |
| 495 | controlPipe := ch.NewExtraFile(0, "control_rd") |
| 496 | for { |
| 497 | buf := make([]byte, 100) |
| 498 | n, err := controlPipe.Read(buf) |
| 499 | if err != nil { |
Bogdan Caprita | 9038012 | 2014-12-10 16:43:51 -0800 | [diff] [blame] | 500 | log.Fatal(err) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 501 | } |
| 502 | if n > 0 { |
| 503 | break |
| 504 | } |
| 505 | } |
| 506 | // SetReady should return an error since the parent has |
| 507 | // timed out on us and we'd be writing to a closed pipe. |
| 508 | if err := ch.SetReady(); err != nil { |
| 509 | fmt.Fprintf(os.Stderr, "%s", err) |
| 510 | } else { |
| 511 | fmt.Fprintf(os.Stderr, "didn't get the expected error") |
| 512 | } |
Cosmos Nicolaou | 1c18c1c | 2014-10-08 16:37:10 -0700 | [diff] [blame] | 513 | case "testFail": |
| 514 | ch, err := vexec.GetChildHandle() |
| 515 | if err != nil { |
Bogdan Caprita | 9038012 | 2014-12-10 16:43:51 -0800 | [diff] [blame] | 516 | log.Fatal(err) |
Cosmos Nicolaou | 1c18c1c | 2014-10-08 16:37:10 -0700 | [diff] [blame] | 517 | } |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 518 | verifyNoExecVariable() |
Cosmos Nicolaou | 1c18c1c | 2014-10-08 16:37:10 -0700 | [diff] [blame] | 519 | if err := ch.SetFailed(fmt.Errorf("%s", strings.Join(args, " "))); err != nil { |
| 520 | |
| 521 | fmt.Fprintf(os.Stderr, "%s\n", err) |
| 522 | } |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 523 | case "testReady": |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 524 | ch, err := vexec.GetChildHandle() |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 525 | if err != nil { |
Bogdan Caprita | 9038012 | 2014-12-10 16:43:51 -0800 | [diff] [blame] | 526 | log.Fatal(err) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 527 | } |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 528 | verifyNoExecVariable() |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 529 | ch.SetReady() |
| 530 | fmt.Fprintf(os.Stderr, ".") |
| 531 | case "testReadySlow": |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 532 | ch, err := vexec.GetChildHandle() |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 533 | if err != nil { |
Bogdan Caprita | 9038012 | 2014-12-10 16:43:51 -0800 | [diff] [blame] | 534 | log.Fatal(err) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 535 | } |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 536 | verifyNoExecVariable() |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 537 | // Wait for the parent to tell us when it's ok to proceed. |
| 538 | controlPipe := ch.NewExtraFile(0, "control_rd") |
| 539 | for { |
| 540 | buf := make([]byte, 100) |
| 541 | n, err := controlPipe.Read(buf) |
| 542 | if err != nil { |
Bogdan Caprita | 9038012 | 2014-12-10 16:43:51 -0800 | [diff] [blame] | 543 | log.Fatal(err) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 544 | } |
| 545 | if n > 0 { |
| 546 | break |
| 547 | } |
| 548 | } |
| 549 | ch.SetReady() |
| 550 | fmt.Fprintf(os.Stderr, "..") |
Bogdan Caprita | 9038012 | 2014-12-10 16:43:51 -0800 | [diff] [blame] | 551 | case "testReadyAndHang": |
| 552 | ch, err := vexec.GetChildHandle() |
| 553 | if err != nil { |
| 554 | log.Fatal(err) |
| 555 | } |
| 556 | verifyNoExecVariable() |
| 557 | ch.SetReady() |
| 558 | <-time.After(time.Minute) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 559 | case "testSuccess", "testError": |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 560 | ch, err := vexec.GetChildHandle() |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 561 | if err != nil { |
Bogdan Caprita | 9038012 | 2014-12-10 16:43:51 -0800 | [diff] [blame] | 562 | log.Fatal(err) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 563 | } |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 564 | verifyNoExecVariable() |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 565 | ch.SetReady() |
| 566 | rc := make(chan int) |
| 567 | go func() { |
| 568 | if cmd == "testError" { |
| 569 | fmt.Fprintf(os.Stderr, "...err") |
| 570 | rc <- 1 |
| 571 | } else { |
| 572 | fmt.Fprintf(os.Stderr, "...ok") |
| 573 | rc <- 0 |
| 574 | } |
| 575 | }() |
| 576 | r := <-rc |
| 577 | os.Exit(r) |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 578 | case "testConfig": |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 579 | ch, err := vexec.GetChildHandle() |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 580 | if err != nil { |
| 581 | log.Fatalf("%v", err) |
| 582 | } else { |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 583 | verifyNoExecVariable() |
Bogdan Caprita | a4d9ee4 | 2014-06-20 16:42:53 -0700 | [diff] [blame] | 584 | serialized, err := ch.Config.Serialize() |
| 585 | if err != nil { |
| 586 | log.Fatalf("%v", err) |
| 587 | } |
| 588 | fmt.Fprintf(os.Stderr, "%s", serialized) |
Jiri Simsa | c199bc1 | 2014-05-30 12:52:24 -0700 | [diff] [blame] | 589 | } |
| 590 | case "testSecret": |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 591 | ch, err := vexec.GetChildHandle() |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 592 | if err != nil { |
| 593 | log.Fatalf("%s", err) |
| 594 | } else { |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 595 | verifyNoExecVariable() |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 596 | fmt.Fprintf(os.Stderr, "%s", ch.Secret) |
| 597 | } |
| 598 | case "testExtraFiles": |
Jiri Simsa | 84059da | 2014-06-02 17:22:05 -0700 | [diff] [blame] | 599 | ch, err := vexec.GetChildHandle() |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 600 | if err != nil { |
Bogdan Caprita | 9038012 | 2014-12-10 16:43:51 -0800 | [diff] [blame] | 601 | log.Fatalf("error.... %s\n", err) |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 602 | } |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 603 | verifyNoExecVariable() |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 604 | err = ch.SetReady() |
| 605 | rd := ch.NewExtraFile(0, "read") |
| 606 | buf := make([]byte, 1024) |
| 607 | if n, err := rd.Read(buf); err != nil { |
| 608 | fmt.Fprintf(os.Stderr, "child: error %s\n", err) |
| 609 | } else { |
| 610 | fmt.Fprintf(os.Stderr, "child: %s", string(buf[:n])) |
| 611 | } |
Jiri Simsa | 5293dcb | 2014-05-10 09:56:38 -0700 | [diff] [blame] | 612 | } |
| 613 | } |