github.com/lmb/consul@v1.4.1/connect/proxy/conn_test.go (about) 1 package proxy 2 3 import ( 4 "bufio" 5 "io" 6 "net" 7 "testing" 8 "time" 9 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 ) 13 14 // Assert io.Closer implementation 15 var _ io.Closer = new(Conn) 16 17 // testConnPairSetup creates a TCP connection by listening on a random port, and 18 // returns both ends. Ready to have data sent down them. It also returns a 19 // closer function that will close both conns and the listener. 20 func testConnPairSetup(t *testing.T) (net.Conn, net.Conn, func()) { 21 t.Helper() 22 23 l, err := net.Listen("tcp", "localhost:0") 24 require.Nil(t, err) 25 26 ch := make(chan net.Conn, 1) 27 go func() { 28 src, err := l.Accept() 29 require.Nil(t, err) 30 ch <- src 31 }() 32 33 dst, err := net.Dial("tcp", l.Addr().String()) 34 require.Nil(t, err) 35 36 src := <-ch 37 38 stopper := func() { 39 l.Close() 40 src.Close() 41 dst.Close() 42 } 43 44 return src, dst, stopper 45 } 46 47 // testConnPipelineSetup creates a pipeline consiting of two TCP connection 48 // pairs and a Conn that copies bytes between them. Data flow looks like this: 49 // 50 // src1 <---> dst1 <== Conn.CopyBytes ==> src2 <---> dst2 51 // 52 // The returned values are the src1 and dst2 which should be able to send and 53 // receive to each other via the Conn, the Conn itself (not running), and a 54 // stopper func to close everything. 55 func testConnPipelineSetup(t *testing.T) (net.Conn, net.Conn, *Conn, func()) { 56 src1, dst1, stop1 := testConnPairSetup(t) 57 src2, dst2, stop2 := testConnPairSetup(t) 58 c := NewConn(dst1, src2) 59 return src1, dst2, c, func() { 60 c.Close() 61 stop1() 62 stop2() 63 } 64 } 65 66 func TestConn(t *testing.T) { 67 t.Parallel() 68 69 src, dst, c, stop := testConnPipelineSetup(t) 70 defer stop() 71 72 retCh := make(chan error, 1) 73 go func() { 74 retCh <- c.CopyBytes() 75 }() 76 77 // Now write/read into the other ends of the pipes (src1, dst2) 78 srcR := bufio.NewReader(src) 79 dstR := bufio.NewReader(dst) 80 81 _, err := src.Write([]byte("ping 1\n")) 82 require.Nil(t, err) 83 _, err = dst.Write([]byte("ping 2\n")) 84 require.Nil(t, err) 85 86 got, err := dstR.ReadString('\n') 87 require.Nil(t, err) 88 require.Equal(t, "ping 1\n", got) 89 90 got, err = srcR.ReadString('\n') 91 require.Nil(t, err) 92 require.Equal(t, "ping 2\n", got) 93 94 tx, rx := c.Stats() 95 assert.Equal(t, uint64(7), tx) 96 assert.Equal(t, uint64(7), rx) 97 98 _, err = src.Write([]byte("pong 1\n")) 99 require.Nil(t, err) 100 _, err = dst.Write([]byte("pong 2\n")) 101 require.Nil(t, err) 102 103 got, err = dstR.ReadString('\n') 104 require.Nil(t, err) 105 require.Equal(t, "pong 1\n", got) 106 107 got, err = srcR.ReadString('\n') 108 require.Nil(t, err) 109 require.Equal(t, "pong 2\n", got) 110 111 tx, rx = c.Stats() 112 assert.Equal(t, uint64(14), tx) 113 assert.Equal(t, uint64(14), rx) 114 115 c.Close() 116 117 ret := <-retCh 118 require.Nil(t, ret, "Close() should not cause error return") 119 } 120 121 func TestConnSrcClosing(t *testing.T) { 122 t.Parallel() 123 124 src, dst, c, stop := testConnPipelineSetup(t) 125 defer stop() 126 127 retCh := make(chan error, 1) 128 go func() { 129 retCh <- c.CopyBytes() 130 }() 131 132 // Wait until we can actually get some bytes through both ways so we know that 133 // the copy goroutines are running. 134 srcR := bufio.NewReader(src) 135 dstR := bufio.NewReader(dst) 136 137 _, err := src.Write([]byte("ping 1\n")) 138 require.Nil(t, err) 139 _, err = dst.Write([]byte("ping 2\n")) 140 require.Nil(t, err) 141 142 got, err := dstR.ReadString('\n') 143 require.Nil(t, err) 144 require.Equal(t, "ping 1\n", got) 145 got, err = srcR.ReadString('\n') 146 require.Nil(t, err) 147 require.Equal(t, "ping 2\n", got) 148 149 // If we close the src conn, we expect CopyBytes to return and dst to be 150 // closed too. No good way to assert that the conn is closed really other than 151 // assume the retCh receive will hang unless CopyBytes exits and that 152 // CopyBytes defers Closing both. 153 testTimer := time.AfterFunc(3*time.Second, func() { 154 panic("test timeout") 155 }) 156 src.Close() 157 <-retCh 158 testTimer.Stop() 159 } 160 161 func TestConnDstClosing(t *testing.T) { 162 t.Parallel() 163 164 src, dst, c, stop := testConnPipelineSetup(t) 165 defer stop() 166 167 retCh := make(chan error, 1) 168 go func() { 169 retCh <- c.CopyBytes() 170 }() 171 172 // Wait until we can actually get some bytes through both ways so we know that 173 // the copy goroutines are running. 174 srcR := bufio.NewReader(src) 175 dstR := bufio.NewReader(dst) 176 177 _, err := src.Write([]byte("ping 1\n")) 178 require.Nil(t, err) 179 _, err = dst.Write([]byte("ping 2\n")) 180 require.Nil(t, err) 181 182 got, err := dstR.ReadString('\n') 183 require.Nil(t, err) 184 require.Equal(t, "ping 1\n", got) 185 got, err = srcR.ReadString('\n') 186 require.Nil(t, err) 187 require.Equal(t, "ping 2\n", got) 188 189 // If we close the dst conn, we expect CopyBytes to return and src to be 190 // closed too. No good way to assert that the conn is closed really other than 191 // assume the retCh receive will hang unless CopyBytes exits and that 192 // CopyBytes defers Closing both. i.e. if this test doesn't time out it's 193 // good! 194 testTimer := time.AfterFunc(3*time.Second, func() { 195 panic("test timeout") 196 }) 197 src.Close() 198 <-retCh 199 testTimer.Stop() 200 }