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 }