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