github.com/splucs/witchcraft-go-server@v1.7.0/integration/common_test.go (about)

     1  // Copyright (c) 2018 Palantir Technologies. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package integration
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"crypto/tls"
    21  	"encoding/json"
    22  	"fmt"
    23  	"io"
    24  	"io/ioutil"
    25  	"net/http"
    26  	"os"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/nmiyake/pkg/dirs"
    31  	"github.com/palantir/pkg/httpserver"
    32  	"github.com/palantir/witchcraft-go-server/config"
    33  	"github.com/palantir/witchcraft-go-server/rest"
    34  	"github.com/palantir/witchcraft-go-server/witchcraft"
    35  	"github.com/palantir/witchcraft-go-server/witchcraft/refreshable"
    36  	"github.com/stretchr/testify/assert"
    37  	"github.com/stretchr/testify/require"
    38  	"gopkg.in/yaml.v2"
    39  )
    40  
    41  const (
    42  	basePath    = "/example"
    43  	productName = "example"
    44  	installYML  = "var/conf/install.yml"
    45  	runtimeYML  = "var/conf/runtime.yml"
    46  )
    47  
    48  // createAndRunTestServer returns a running witchcraft.Server that is initialized with simple default configuration in a
    49  // temporary directory. Returns the server, the path to the temporary directory, a channel that returns the error
    50  // returned by the server when it stops and a cleanup function that will remove the temporary directory.
    51  func createAndRunTestServer(t *testing.T, initFn witchcraft.InitFunc, logOutputBuffer io.Writer) (server *witchcraft.Server, port int, managementPort int, serverErr <-chan error, cleanup func()) {
    52  	var err error
    53  	port, err = httpserver.AvailablePort()
    54  	require.NoError(t, err)
    55  	managementPort, err = httpserver.AvailablePort()
    56  	require.NoError(t, err)
    57  
    58  	server, serverErr, cleanup = createAndRunCustomTestServer(t, port, managementPort, initFn, logOutputBuffer, createTestServer)
    59  	return server, port, managementPort, serverErr, cleanup
    60  }
    61  
    62  type serverCreatorFn func(t *testing.T, initFn witchcraft.InitFunc, installCfg config.Install, logOutputBuffer io.Writer) *witchcraft.Server
    63  
    64  func createAndRunCustomTestServer(t *testing.T, port, managementPort int, initFn witchcraft.InitFunc, logOutputBuffer io.Writer, createServer serverCreatorFn) (server *witchcraft.Server, serverErr <-chan error, cleanup func()) {
    65  	installCfg := config.Install{
    66  		ProductName:   productName,
    67  		UseConsoleLog: true,
    68  		Server: config.Server{
    69  			Address:        "localhost",
    70  			Port:           port,
    71  			ManagementPort: managementPort,
    72  			ContextPath:    basePath,
    73  		},
    74  	}
    75  
    76  	var err error
    77  	var dir string
    78  	dir, cleanup, err = dirs.TempDir("", "test-server")
    79  	success := false
    80  	defer func() {
    81  		if !success {
    82  			// invoke cleanup function only if this function exits unsuccessfully. Otherwise, the cleanup
    83  			// function is returned and the caller is expected to defer it.
    84  			cleanup()
    85  		}
    86  	}()
    87  	require.NoError(t, err)
    88  
    89  	// restore working directory at the end of the function
    90  	restoreWd, err := dirs.SetwdWithRestorer(dir)
    91  	require.NoError(t, err)
    92  	defer restoreWd()
    93  
    94  	err = os.MkdirAll("var/conf", 0755)
    95  	require.NoError(t, err)
    96  	installCfgYML, err := yaml.Marshal(installCfg)
    97  	require.NoError(t, err)
    98  	err = ioutil.WriteFile(installYML, installCfgYML, 0644)
    99  	require.NoError(t, err)
   100  	err = ioutil.WriteFile(runtimeYML, []byte(`number: 1`), 0644)
   101  	require.NoError(t, err)
   102  
   103  	server = createServer(t, initFn, installCfg, logOutputBuffer)
   104  
   105  	serverChan := make(chan error)
   106  	go func() {
   107  		serverChan <- server.Start()
   108  	}()
   109  	serverErr = serverChan
   110  	success = <-waitForTestServerReady(port, "example/ok", 5*time.Second)
   111  	if !success {
   112  		errMsg := "timed out waiting for server to start"
   113  		select {
   114  		case err := <-serverChan:
   115  			errMsg = fmt.Sprintf("%s: %+v", errMsg, err)
   116  		default:
   117  		}
   118  		require.Fail(t, errMsg)
   119  	}
   120  	return server, serverErr, cleanup
   121  }
   122  
   123  // createTestServer creates a test *witchcraft.Server that has been constructed but not started. The server has the
   124  // context path "/example" and has a handler for an "/ok" method that returns the JSON "ok" on GET calls. Returns the
   125  // server and the port that the server will use when started.
   126  func createTestServer(t *testing.T, initFn witchcraft.InitFunc, installCfg config.Install, logOutputBuffer io.Writer) (server *witchcraft.Server) {
   127  	server = witchcraft.
   128  		NewServer().
   129  		WithInitFunc(func(ctx context.Context, initInfo witchcraft.InitInfo) (func(), error) {
   130  			// register handler that returns "ok"
   131  			err := initInfo.Router.Get("/ok", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   132  				rest.WriteJSONResponse(rw, "ok", http.StatusOK)
   133  			}))
   134  			if err != nil {
   135  				return nil, err
   136  			}
   137  			if initFn != nil {
   138  				return initFn(ctx, initInfo)
   139  			}
   140  			return nil, nil
   141  		}).
   142  		WithInstallConfig(installCfg).
   143  		WithRuntimeConfigProvider(refreshable.NewDefaultRefreshable([]byte{})).
   144  		WithECVKeyProvider(witchcraft.ECVKeyNoOp()).
   145  		WithDisableGoRuntimeMetrics().
   146  		WithSelfSignedCertificate()
   147  	if logOutputBuffer != nil {
   148  		server.WithLoggerStdoutWriter(logOutputBuffer)
   149  	}
   150  	return server
   151  }
   152  
   153  // waitForTestServerReady returns a channel that returns true when a test server is ready on the provided port. Returns
   154  // false if the server is not ready within the provided timeout duration.
   155  func waitForTestServerReady(port int, path string, timeout time.Duration) <-chan bool {
   156  	return httpserver.Ready(func() (*http.Response, error) {
   157  		resp, err := testServerClient().Get(fmt.Sprintf("https://localhost:%d/%s", port, path))
   158  		return resp, err
   159  	},
   160  		httpserver.WaitTimeoutParam(timeout),
   161  	)
   162  }
   163  
   164  func testServerClient() *http.Client {
   165  	return &http.Client{Transport: &http.Transport{
   166  		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
   167  	}}
   168  }
   169  
   170  // getLogFileMessages returns a slice of the content of all of the "msg" fields in the log entries in the given log
   171  // file.
   172  func getLogFileMessages(t *testing.T, logOutput []byte) []string {
   173  	lines := bytes.Split(logOutput, []byte("\n"))
   174  	var messages []string
   175  	for _, line := range lines {
   176  		if len(line) == 0 {
   177  			continue
   178  		}
   179  		var currEntry map[string]interface{}
   180  		assert.NoError(t, json.Unmarshal(line, &currEntry), "failed to parse json line %q", string(line))
   181  		if msg, ok := currEntry["message"]; ok {
   182  			messages = append(messages, msg.(string))
   183  		}
   184  	}
   185  	return messages
   186  }