github.com/spotify/syslog-redirector-golang@v0.0.0-20140320174030-4859f03d829a/src/pkg/os/exec/exec_test.go (about) 1 // Copyright 2009 The Go 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 5 package exec 6 7 import ( 8 "bufio" 9 "bytes" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "net" 14 "net/http" 15 "net/http/httptest" 16 "os" 17 "path/filepath" 18 "runtime" 19 "strconv" 20 "strings" 21 "testing" 22 "time" 23 ) 24 25 func helperCommand(s ...string) *Cmd { 26 cs := []string{"-test.run=TestHelperProcess", "--"} 27 cs = append(cs, s...) 28 cmd := Command(os.Args[0], cs...) 29 cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} 30 return cmd 31 } 32 33 func TestEcho(t *testing.T) { 34 bs, err := helperCommand("echo", "foo bar", "baz").Output() 35 if err != nil { 36 t.Errorf("echo: %v", err) 37 } 38 if g, e := string(bs), "foo bar baz\n"; g != e { 39 t.Errorf("echo: want %q, got %q", e, g) 40 } 41 } 42 43 func TestCatStdin(t *testing.T) { 44 // Cat, testing stdin and stdout. 45 input := "Input string\nLine 2" 46 p := helperCommand("cat") 47 p.Stdin = strings.NewReader(input) 48 bs, err := p.Output() 49 if err != nil { 50 t.Errorf("cat: %v", err) 51 } 52 s := string(bs) 53 if s != input { 54 t.Errorf("cat: want %q, got %q", input, s) 55 } 56 } 57 58 func TestCatGoodAndBadFile(t *testing.T) { 59 // Testing combined output and error values. 60 bs, err := helperCommand("cat", "/bogus/file.foo", "exec_test.go").CombinedOutput() 61 if _, ok := err.(*ExitError); !ok { 62 t.Errorf("expected *ExitError from cat combined; got %T: %v", err, err) 63 } 64 s := string(bs) 65 sp := strings.SplitN(s, "\n", 2) 66 if len(sp) != 2 { 67 t.Fatalf("expected two lines from cat; got %q", s) 68 } 69 errLine, body := sp[0], sp[1] 70 if !strings.HasPrefix(errLine, "Error: open /bogus/file.foo") { 71 t.Errorf("expected stderr to complain about file; got %q", errLine) 72 } 73 if !strings.Contains(body, "func TestHelperProcess(t *testing.T)") { 74 t.Errorf("expected test code; got %q (len %d)", body, len(body)) 75 } 76 } 77 78 func TestNoExistBinary(t *testing.T) { 79 // Can't run a non-existent binary 80 err := Command("/no-exist-binary").Run() 81 if err == nil { 82 t.Error("expected error from /no-exist-binary") 83 } 84 } 85 86 func TestExitStatus(t *testing.T) { 87 // Test that exit values are returned correctly 88 cmd := helperCommand("exit", "42") 89 err := cmd.Run() 90 want := "exit status 42" 91 switch runtime.GOOS { 92 case "plan9": 93 want = fmt.Sprintf("exit status: '%s %d: 42'", filepath.Base(cmd.Path), cmd.ProcessState.Pid()) 94 } 95 if werr, ok := err.(*ExitError); ok { 96 if s := werr.Error(); s != want { 97 t.Errorf("from exit 42 got exit %q, want %q", s, want) 98 } 99 } else { 100 t.Fatalf("expected *ExitError from exit 42; got %T: %v", err, err) 101 } 102 } 103 104 func TestPipes(t *testing.T) { 105 check := func(what string, err error) { 106 if err != nil { 107 t.Fatalf("%s: %v", what, err) 108 } 109 } 110 // Cat, testing stdin and stdout. 111 c := helperCommand("pipetest") 112 stdin, err := c.StdinPipe() 113 check("StdinPipe", err) 114 stdout, err := c.StdoutPipe() 115 check("StdoutPipe", err) 116 stderr, err := c.StderrPipe() 117 check("StderrPipe", err) 118 119 outbr := bufio.NewReader(stdout) 120 errbr := bufio.NewReader(stderr) 121 line := func(what string, br *bufio.Reader) string { 122 line, _, err := br.ReadLine() 123 if err != nil { 124 t.Fatalf("%s: %v", what, err) 125 } 126 return string(line) 127 } 128 129 err = c.Start() 130 check("Start", err) 131 132 _, err = stdin.Write([]byte("O:I am output\n")) 133 check("first stdin Write", err) 134 if g, e := line("first output line", outbr), "O:I am output"; g != e { 135 t.Errorf("got %q, want %q", g, e) 136 } 137 138 _, err = stdin.Write([]byte("E:I am error\n")) 139 check("second stdin Write", err) 140 if g, e := line("first error line", errbr), "E:I am error"; g != e { 141 t.Errorf("got %q, want %q", g, e) 142 } 143 144 _, err = stdin.Write([]byte("O:I am output2\n")) 145 check("third stdin Write 3", err) 146 if g, e := line("second output line", outbr), "O:I am output2"; g != e { 147 t.Errorf("got %q, want %q", g, e) 148 } 149 150 stdin.Close() 151 err = c.Wait() 152 check("Wait", err) 153 } 154 155 const stdinCloseTestString = "Some test string." 156 157 // Issue 6270. 158 func TestStdinClose(t *testing.T) { 159 check := func(what string, err error) { 160 if err != nil { 161 t.Fatalf("%s: %v", what, err) 162 } 163 } 164 cmd := helperCommand("stdinClose") 165 stdin, err := cmd.StdinPipe() 166 check("StdinPipe", err) 167 // Check that we can access methods of the underlying os.File.` 168 if _, ok := stdin.(interface { 169 Fd() uintptr 170 }); !ok { 171 t.Error("can't access methods of underlying *os.File") 172 } 173 check("Start", cmd.Start()) 174 go func() { 175 _, err := io.Copy(stdin, strings.NewReader(stdinCloseTestString)) 176 check("Copy", err) 177 // Before the fix, this next line would race with cmd.Wait. 178 check("Close", stdin.Close()) 179 }() 180 check("Wait", cmd.Wait()) 181 } 182 183 // Issue 5071 184 func TestPipeLookPathLeak(t *testing.T) { 185 fd0 := numOpenFDS(t) 186 for i := 0; i < 4; i++ { 187 cmd := Command("something-that-does-not-exist-binary") 188 cmd.StdoutPipe() 189 cmd.StderrPipe() 190 cmd.StdinPipe() 191 if err := cmd.Run(); err == nil { 192 t.Fatal("unexpected success") 193 } 194 } 195 fdGrowth := numOpenFDS(t) - fd0 196 if fdGrowth > 2 { 197 t.Errorf("leaked %d fds; want ~0", fdGrowth) 198 } 199 } 200 201 func numOpenFDS(t *testing.T) int { 202 lsof, err := Command("lsof", "-n", "-p", strconv.Itoa(os.Getpid())).Output() 203 if err != nil { 204 t.Skip("skipping test; error finding or running lsof") 205 return 0 206 } 207 return bytes.Count(lsof, []byte("\n")) 208 } 209 210 var testedAlreadyLeaked = false 211 212 // basefds returns the number of expected file descriptors 213 // to be present in a process at start. 214 func basefds() uintptr { 215 n := os.Stderr.Fd() + 1 216 217 // Go runtime for 32-bit Plan 9 requires that /dev/bintime 218 // be kept open. 219 // See ../../runtime/time_plan9_386.c:/^runtime·nanotime 220 if runtime.GOOS == "plan9" && runtime.GOARCH == "386" { 221 n++ 222 } 223 return n 224 } 225 226 func closeUnexpectedFds(t *testing.T, m string) { 227 for fd := basefds(); fd <= 101; fd++ { 228 err := os.NewFile(fd, "").Close() 229 if err == nil { 230 t.Logf("%s: Something already leaked - closed fd %d", m, fd) 231 } 232 } 233 } 234 235 func TestExtraFilesFDShuffle(t *testing.T) { 236 t.Skip("flaky test; see http://golang.org/issue/5780") 237 switch runtime.GOOS { 238 case "darwin": 239 // TODO(cnicolaou): http://golang.org/issue/2603 240 // leads to leaked file descriptors in this test when it's 241 // run from a builder. 242 closeUnexpectedFds(t, "TestExtraFilesFDShuffle") 243 case "netbsd": 244 // http://golang.org/issue/3955 245 closeUnexpectedFds(t, "TestExtraFilesFDShuffle") 246 case "windows": 247 t.Skip("no operating system support; skipping") 248 } 249 250 // syscall.StartProcess maps all the FDs passed to it in 251 // ProcAttr.Files (the concatenation of stdin,stdout,stderr and 252 // ExtraFiles) into consecutive FDs in the child, that is: 253 // Files{11, 12, 6, 7, 9, 3} should result in the file 254 // represented by FD 11 in the parent being made available as 0 255 // in the child, 12 as 1, etc. 256 // 257 // We want to test that FDs in the child do not get overwritten 258 // by one another as this shuffle occurs. The original implementation 259 // was buggy in that in some data dependent cases it would ovewrite 260 // stderr in the child with one of the ExtraFile members. 261 // Testing for this case is difficult because it relies on using 262 // the same FD values as that case. In particular, an FD of 3 263 // must be at an index of 4 or higher in ProcAttr.Files and 264 // the FD of the write end of the Stderr pipe (as obtained by 265 // StderrPipe()) must be the same as the size of ProcAttr.Files; 266 // therefore we test that the read end of this pipe (which is what 267 // is returned to the parent by StderrPipe() being one less than 268 // the size of ProcAttr.Files, i.e. 3+len(cmd.ExtraFiles). 269 // 270 // Moving this test case around within the overall tests may 271 // affect the FDs obtained and hence the checks to catch these cases. 272 npipes := 2 273 c := helperCommand("extraFilesAndPipes", strconv.Itoa(npipes+1)) 274 rd, wr, _ := os.Pipe() 275 defer rd.Close() 276 if rd.Fd() != 3 { 277 t.Errorf("bad test value for test pipe: fd %d", rd.Fd()) 278 } 279 stderr, _ := c.StderrPipe() 280 wr.WriteString("_LAST") 281 wr.Close() 282 283 pipes := make([]struct { 284 r, w *os.File 285 }, npipes) 286 data := []string{"a", "b"} 287 288 for i := 0; i < npipes; i++ { 289 r, w, err := os.Pipe() 290 if err != nil { 291 t.Fatalf("unexpected error creating pipe: %s", err) 292 } 293 pipes[i].r = r 294 pipes[i].w = w 295 w.WriteString(data[i]) 296 c.ExtraFiles = append(c.ExtraFiles, pipes[i].r) 297 defer func() { 298 r.Close() 299 w.Close() 300 }() 301 } 302 // Put fd 3 at the end. 303 c.ExtraFiles = append(c.ExtraFiles, rd) 304 305 stderrFd := int(stderr.(*os.File).Fd()) 306 if stderrFd != ((len(c.ExtraFiles) + 3) - 1) { 307 t.Errorf("bad test value for stderr pipe") 308 } 309 310 expected := "child: " + strings.Join(data, "") + "_LAST" 311 312 err := c.Start() 313 if err != nil { 314 t.Fatalf("Run: %v", err) 315 } 316 ch := make(chan string, 1) 317 go func(ch chan string) { 318 buf := make([]byte, 512) 319 n, err := stderr.Read(buf) 320 if err != nil { 321 t.Fatalf("Read: %s", err) 322 ch <- err.Error() 323 } else { 324 ch <- string(buf[:n]) 325 } 326 close(ch) 327 }(ch) 328 select { 329 case m := <-ch: 330 if m != expected { 331 t.Errorf("Read: '%s' not '%s'", m, expected) 332 } 333 case <-time.After(5 * time.Second): 334 t.Errorf("Read timedout") 335 } 336 c.Wait() 337 } 338 339 func TestExtraFiles(t *testing.T) { 340 if runtime.GOOS == "windows" { 341 t.Skip("no operating system support; skipping") 342 } 343 344 // Ensure that file descriptors have not already been leaked into 345 // our environment. 346 if !testedAlreadyLeaked { 347 testedAlreadyLeaked = true 348 closeUnexpectedFds(t, "TestExtraFiles") 349 } 350 351 // Force network usage, to verify the epoll (or whatever) fd 352 // doesn't leak to the child, 353 ln, err := net.Listen("tcp", "127.0.0.1:0") 354 if err != nil { 355 t.Fatal(err) 356 } 357 defer ln.Close() 358 359 // Make sure duplicated fds don't leak to the child. 360 f, err := ln.(*net.TCPListener).File() 361 if err != nil { 362 t.Fatal(err) 363 } 364 defer f.Close() 365 ln2, err := net.FileListener(f) 366 if err != nil { 367 t.Fatal(err) 368 } 369 defer ln2.Close() 370 371 // Force TLS root certs to be loaded (which might involve 372 // cgo), to make sure none of that potential C code leaks fds. 373 ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 374 w.Write([]byte("Hello")) 375 })) 376 defer ts.Close() 377 http.Get(ts.URL) // ignore result; just calling to force root cert loading 378 379 tf, err := ioutil.TempFile("", "") 380 if err != nil { 381 t.Fatalf("TempFile: %v", err) 382 } 383 defer os.Remove(tf.Name()) 384 defer tf.Close() 385 386 const text = "Hello, fd 3!" 387 _, err = tf.Write([]byte(text)) 388 if err != nil { 389 t.Fatalf("Write: %v", err) 390 } 391 _, err = tf.Seek(0, os.SEEK_SET) 392 if err != nil { 393 t.Fatalf("Seek: %v", err) 394 } 395 396 c := helperCommand("read3") 397 var stdout, stderr bytes.Buffer 398 c.Stdout = &stdout 399 c.Stderr = &stderr 400 c.ExtraFiles = []*os.File{tf} 401 err = c.Run() 402 if err != nil { 403 t.Fatalf("Run: %v; stdout %q, stderr %q", err, stdout.Bytes(), stderr.Bytes()) 404 } 405 if stdout.String() != text { 406 t.Errorf("got stdout %q, stderr %q; want %q on stdout", stdout.String(), stderr.String(), text) 407 } 408 } 409 410 func TestExtraFilesRace(t *testing.T) { 411 if runtime.GOOS == "windows" { 412 t.Skip("no operating system support; skipping") 413 } 414 listen := func() net.Listener { 415 ln, err := net.Listen("tcp", "127.0.0.1:0") 416 if err != nil { 417 t.Fatal(err) 418 } 419 return ln 420 } 421 listenerFile := func(ln net.Listener) *os.File { 422 f, err := ln.(*net.TCPListener).File() 423 if err != nil { 424 t.Fatal(err) 425 } 426 return f 427 } 428 runCommand := func(c *Cmd, out chan<- string) { 429 bout, err := c.CombinedOutput() 430 if err != nil { 431 out <- "ERROR:" + err.Error() 432 } else { 433 out <- string(bout) 434 } 435 } 436 437 for i := 0; i < 10; i++ { 438 la := listen() 439 ca := helperCommand("describefiles") 440 ca.ExtraFiles = []*os.File{listenerFile(la)} 441 lb := listen() 442 cb := helperCommand("describefiles") 443 cb.ExtraFiles = []*os.File{listenerFile(lb)} 444 ares := make(chan string) 445 bres := make(chan string) 446 go runCommand(ca, ares) 447 go runCommand(cb, bres) 448 if got, want := <-ares, fmt.Sprintf("fd3: listener %s\n", la.Addr()); got != want { 449 t.Errorf("iteration %d, process A got:\n%s\nwant:\n%s\n", i, got, want) 450 } 451 if got, want := <-bres, fmt.Sprintf("fd3: listener %s\n", lb.Addr()); got != want { 452 t.Errorf("iteration %d, process B got:\n%s\nwant:\n%s\n", i, got, want) 453 } 454 la.Close() 455 lb.Close() 456 for _, f := range ca.ExtraFiles { 457 f.Close() 458 } 459 for _, f := range cb.ExtraFiles { 460 f.Close() 461 } 462 463 } 464 } 465 466 // TestHelperProcess isn't a real test. It's used as a helper process 467 // for TestParameterRun. 468 func TestHelperProcess(*testing.T) { 469 if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { 470 return 471 } 472 defer os.Exit(0) 473 474 // Determine which command to use to display open files. 475 ofcmd := "lsof" 476 switch runtime.GOOS { 477 case "dragonfly", "freebsd", "netbsd", "openbsd": 478 ofcmd = "fstat" 479 } 480 481 args := os.Args 482 for len(args) > 0 { 483 if args[0] == "--" { 484 args = args[1:] 485 break 486 } 487 args = args[1:] 488 } 489 if len(args) == 0 { 490 fmt.Fprintf(os.Stderr, "No command\n") 491 os.Exit(2) 492 } 493 494 cmd, args := args[0], args[1:] 495 switch cmd { 496 case "echo": 497 iargs := []interface{}{} 498 for _, s := range args { 499 iargs = append(iargs, s) 500 } 501 fmt.Println(iargs...) 502 case "cat": 503 if len(args) == 0 { 504 io.Copy(os.Stdout, os.Stdin) 505 return 506 } 507 exit := 0 508 for _, fn := range args { 509 f, err := os.Open(fn) 510 if err != nil { 511 fmt.Fprintf(os.Stderr, "Error: %v\n", err) 512 exit = 2 513 } else { 514 defer f.Close() 515 io.Copy(os.Stdout, f) 516 } 517 } 518 os.Exit(exit) 519 case "pipetest": 520 bufr := bufio.NewReader(os.Stdin) 521 for { 522 line, _, err := bufr.ReadLine() 523 if err == io.EOF { 524 break 525 } else if err != nil { 526 os.Exit(1) 527 } 528 if bytes.HasPrefix(line, []byte("O:")) { 529 os.Stdout.Write(line) 530 os.Stdout.Write([]byte{'\n'}) 531 } else if bytes.HasPrefix(line, []byte("E:")) { 532 os.Stderr.Write(line) 533 os.Stderr.Write([]byte{'\n'}) 534 } else { 535 os.Exit(1) 536 } 537 } 538 case "stdinClose": 539 b, err := ioutil.ReadAll(os.Stdin) 540 if err != nil { 541 fmt.Fprintf(os.Stderr, "Error: %v\n", err) 542 os.Exit(1) 543 } 544 if s := string(b); s != stdinCloseTestString { 545 fmt.Fprintf(os.Stderr, "Error: Read %q, want %q", s, stdinCloseTestString) 546 os.Exit(1) 547 } 548 os.Exit(0) 549 case "read3": // read fd 3 550 fd3 := os.NewFile(3, "fd3") 551 bs, err := ioutil.ReadAll(fd3) 552 if err != nil { 553 fmt.Printf("ReadAll from fd 3: %v", err) 554 os.Exit(1) 555 } 556 switch runtime.GOOS { 557 case "dragonfly": 558 // TODO(jsing): Determine why DragonFly is leaking 559 // file descriptors... 560 case "darwin": 561 // TODO(bradfitz): broken? Sometimes. 562 // http://golang.org/issue/2603 563 // Skip this additional part of the test for now. 564 case "netbsd": 565 // TODO(jsing): This currently fails on NetBSD due to 566 // the cloned file descriptors that result from opening 567 // /dev/urandom. 568 // http://golang.org/issue/3955 569 default: 570 // Now verify that there are no other open fds. 571 var files []*os.File 572 for wantfd := basefds() + 1; wantfd <= 100; wantfd++ { 573 f, err := os.Open(os.Args[0]) 574 if err != nil { 575 fmt.Printf("error opening file with expected fd %d: %v", wantfd, err) 576 os.Exit(1) 577 } 578 if got := f.Fd(); got != wantfd { 579 fmt.Printf("leaked parent file. fd = %d; want %d\n", got, wantfd) 580 out, _ := Command(ofcmd, "-p", fmt.Sprint(os.Getpid())).CombinedOutput() 581 fmt.Print(string(out)) 582 os.Exit(1) 583 } 584 files = append(files, f) 585 } 586 for _, f := range files { 587 f.Close() 588 } 589 } 590 // Referring to fd3 here ensures that it is not 591 // garbage collected, and therefore closed, while 592 // executing the wantfd loop above. It doesn't matter 593 // what we do with fd3 as long as we refer to it; 594 // closing it is the easy choice. 595 fd3.Close() 596 os.Stdout.Write(bs) 597 case "exit": 598 n, _ := strconv.Atoi(args[0]) 599 os.Exit(n) 600 case "describefiles": 601 f := os.NewFile(3, fmt.Sprintf("fd3")) 602 ln, err := net.FileListener(f) 603 if err == nil { 604 fmt.Printf("fd3: listener %s\n", ln.Addr()) 605 ln.Close() 606 } 607 os.Exit(0) 608 case "extraFilesAndPipes": 609 n, _ := strconv.Atoi(args[0]) 610 pipes := make([]*os.File, n) 611 for i := 0; i < n; i++ { 612 pipes[i] = os.NewFile(uintptr(3+i), strconv.Itoa(i)) 613 } 614 response := "" 615 for i, r := range pipes { 616 ch := make(chan string, 1) 617 go func(c chan string) { 618 buf := make([]byte, 10) 619 n, err := r.Read(buf) 620 if err != nil { 621 fmt.Fprintf(os.Stderr, "Child: read error: %v on pipe %d\n", err, i) 622 os.Exit(1) 623 } 624 c <- string(buf[:n]) 625 close(c) 626 }(ch) 627 select { 628 case m := <-ch: 629 response = response + m 630 case <-time.After(5 * time.Second): 631 fmt.Fprintf(os.Stderr, "Child: Timeout reading from pipe: %d\n", i) 632 os.Exit(1) 633 } 634 } 635 fmt.Fprintf(os.Stderr, "child: %s", response) 636 os.Exit(0) 637 default: 638 fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd) 639 os.Exit(2) 640 } 641 }