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  }