1// Copyright 2015 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// Test broken pipes on Unix systems.
6// +build !windows,!plan9,!nacl
7
8package os_test
9
10import (
11	"fmt"
12	"internal/testenv"
13	"os"
14	osexec "os/exec"
15	"os/signal"
16	"syscall"
17	"testing"
18)
19
20func TestEPIPE(t *testing.T) {
21	r, w, err := os.Pipe()
22	if err != nil {
23		t.Fatal(err)
24	}
25	if err := r.Close(); err != nil {
26		t.Fatal(err)
27	}
28
29	// Every time we write to the pipe we should get an EPIPE.
30	for i := 0; i < 20; i++ {
31		_, err = w.Write([]byte("hi"))
32		if err == nil {
33			t.Fatal("unexpected success of Write to broken pipe")
34		}
35		if pe, ok := err.(*os.PathError); ok {
36			err = pe.Err
37		}
38		if se, ok := err.(*os.SyscallError); ok {
39			err = se.Err
40		}
41		if err != syscall.EPIPE {
42			t.Errorf("iteration %d: got %v, expected EPIPE", i, err)
43		}
44	}
45}
46
47func TestStdPipe(t *testing.T) {
48	testenv.MustHaveExec(t)
49	r, w, err := os.Pipe()
50	if err != nil {
51		t.Fatal(err)
52	}
53	if err := r.Close(); err != nil {
54		t.Fatal(err)
55	}
56	// Invoke the test program to run the test and write to a closed pipe.
57	// If sig is false:
58	// writing to stdout or stderr should cause an immediate SIGPIPE;
59	// writing to descriptor 3 should fail with EPIPE and then exit 0.
60	// If sig is true:
61	// all writes should fail with EPIPE and then exit 0.
62	for _, sig := range []bool{false, true} {
63		for dest := 1; dest < 4; dest++ {
64			cmd := osexec.Command(os.Args[0], "-test.run", "TestStdPipeHelper")
65			cmd.Stdout = w
66			cmd.Stderr = w
67			cmd.ExtraFiles = []*os.File{w}
68			cmd.Env = append(os.Environ(), fmt.Sprintf("GO_TEST_STD_PIPE_HELPER=%d", dest))
69			if sig {
70				cmd.Env = append(cmd.Env, "GO_TEST_STD_PIPE_HELPER_SIGNAL=1")
71			}
72			if err := cmd.Run(); err == nil {
73				if !sig && dest < 3 {
74					t.Errorf("unexpected success of write to closed pipe %d sig %t in child", dest, sig)
75				}
76			} else if ee, ok := err.(*osexec.ExitError); !ok {
77				t.Errorf("unexpected exec error type %T: %v", err, err)
78			} else if ws, ok := ee.Sys().(syscall.WaitStatus); !ok {
79				t.Errorf("unexpected wait status type %T: %v", ee.Sys(), ee.Sys())
80			} else if ws.Signaled() && ws.Signal() == syscall.SIGPIPE {
81				if sig || dest > 2 {
82					t.Errorf("unexpected SIGPIPE signal for descriptor %d sig %t", dest, sig)
83				}
84			} else {
85				t.Errorf("unexpected exit status %v for descriptor %ds sig %t", err, dest, sig)
86			}
87		}
88	}
89}
90
91// This is a helper for TestStdPipe.  It's not a test in itself.
92func TestStdPipeHelper(t *testing.T) {
93	if os.Getenv("GO_TEST_STD_PIPE_HELPER_SIGNAL") != "" {
94		signal.Notify(make(chan os.Signal, 1), syscall.SIGPIPE)
95	}
96	switch os.Getenv("GO_TEST_STD_PIPE_HELPER") {
97	case "1":
98		os.Stdout.Write([]byte("stdout"))
99	case "2":
100		os.Stderr.Write([]byte("stderr"))
101	case "3":
102		if _, err := os.NewFile(3, "3").Write([]byte("3")); err == nil {
103			os.Exit(3)
104		}
105	default:
106		t.Skip("skipping test helper")
107	}
108	// For stdout/stderr, we should have crashed with a broken pipe error.
109	// The caller will be looking for that exit status,
110	// so just exit normally here to cause a failure in the caller.
111	// For descriptor 3, a normal exit is expected.
112	os.Exit(0)
113}
114