github.com/mackerelio/mackerel-agent-plugins@v0.89.3/mackerel-plugin-php-fpm/lib/fcgi_test.go (about) 1 //go:build linux 2 3 package mpphpfpm 4 5 import ( 6 "context" 7 "io" 8 "net" 9 "net/http" 10 "net/http/fcgi" 11 "path/filepath" 12 "testing" 13 "time" 14 15 "github.com/stretchr/testify/assert" 16 ) 17 18 // FastCGIServer is a FastCGI server, for use in tests. 19 type FastCGIServer struct { 20 lis net.Listener 21 Address string 22 URL string 23 } 24 25 // NewFastCGIServer returns a server that is listening on address. 26 func NewFastCGIServer(network, address string, handler http.Handler) (*FastCGIServer, error) { 27 l, err := net.Listen(network, address) 28 if err != nil { 29 return nil, err 30 } 31 s := &FastCGIServer{ 32 lis: l, 33 Address: l.Addr().String(), 34 URL: "http://localhost/status?json", 35 } 36 go fcgi.Serve(s.lis, handler) 37 return s, nil 38 } 39 40 // Close shutdown the server. 41 func (s *FastCGIServer) Close() error { 42 return s.lis.Close() 43 } 44 45 func TestFCGITransport(t *testing.T) { 46 dir := t.TempDir() 47 48 tests := []struct { 49 Name string 50 Network string 51 Address string 52 }{ 53 { 54 Name: "listening on TCP-IPv4 address", 55 Network: "tcp", 56 Address: "127.0.0.1:0", 57 }, 58 { 59 Name: "listening on TCP-hostname", 60 Network: "tcp", 61 Address: "localhost:0", 62 }, 63 { 64 Name: "listening Unix socket", 65 Network: "unix", 66 Address: filepath.Join(dir, "php-fpm.sock"), 67 }, 68 } 69 for _, tt := range tests { 70 t.Run(tt.Name, func(t *testing.T) { 71 testFCGITransport(t, tt.Network, tt.Address) 72 }) 73 } 74 } 75 76 func testFCGITransport(t *testing.T, network, address string) { 77 tests := []struct { 78 Status int 79 }{ 80 { 81 Status: http.StatusOK, 82 }, 83 { 84 Status: http.StatusBadRequest, 85 }, 86 { 87 Status: http.StatusInternalServerError, 88 }, 89 } 90 91 for _, tt := range tests { 92 msg := http.StatusText(tt.Status) 93 t.Run(msg, func(t *testing.T) { 94 ts, err := NewFastCGIServer(network, address, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 95 w.WriteHeader(tt.Status) 96 w.Write([]byte(msg)) 97 })) 98 if err != nil { 99 assert.Fail(t, "failed to launch FastCGI server", err) 100 return 101 } 102 defer ts.Close() 103 104 c := http.Client{ 105 Transport: &FastCGITransport{ 106 Network: network, 107 Address: ts.Address, 108 }, 109 } 110 resp, err := c.Get(ts.URL) 111 if err != nil { 112 assert.Fail(t, "failed to request a resource", err) 113 return 114 } 115 defer resp.Body.Close() 116 117 assert.Equal(t, tt.Status, resp.StatusCode) 118 b, err := io.ReadAll(resp.Body) 119 if err != nil { 120 assert.Fail(t, "failed to read response", err) 121 return 122 } 123 assert.Equal(t, msg, string(b)) 124 }) 125 } 126 } 127 128 func TestFCGITransportDialTimeout(t *testing.T) { 129 ts, err := NewFastCGIServer("tcp", "127.0.0.1:0", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 130 assert.Fail(t, "shouldn't reach here") 131 })) 132 if err != nil { 133 assert.FailNow(t, "failed to launch FastCGI server", err) 134 } 135 defer ts.Close() 136 137 c := http.Client{ 138 Transport: &FastCGITransport{ 139 Network: "tcp", 140 Address: ts.Address, 141 }, 142 } 143 req, err := http.NewRequest(http.MethodGet, ts.URL, nil) 144 if err != nil { 145 assert.FailNow(t, "failed to create a request", err) 146 } 147 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Microsecond) 148 defer cancel() 149 req = req.WithContext(ctx) 150 resp, err := c.Do(req) 151 if assert.Error(t, err) { 152 assert.Contains(t, err.Error(), "timeout") 153 } 154 if resp != nil { 155 resp.Body.Close() 156 } 157 }