github.com/vmware/transport-go@v1.3.4/plank/pkg/server/test_suite_harness.go (about) 1 // Copyright 2021 VMware, Inc. 2 // SPDX-License-Identifier: BSD-2-Clause 3 4 package server 5 6 import ( 7 "errors" 8 "fmt" 9 "github.com/sirupsen/logrus" 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/suite" 12 "github.com/vmware/transport-go/bus" 13 "github.com/vmware/transport-go/model" 14 "github.com/vmware/transport-go/plank/utils" 15 svc "github.com/vmware/transport-go/service" 16 "io/ioutil" 17 "net" 18 "os" 19 "path/filepath" 20 "testing" 21 "time" 22 ) 23 24 var testSuitePortMap = make(map[string]int) 25 26 // PlankIntegrationTestSuite makes it easy to set up integration tests for services, that use plank and transport 27 // In a realistic manner. This is a convenience mechanism to avoid having to rig this harnessing up yourself. 28 type PlankIntegrationTestSuite struct { 29 30 // suite.Suite is a reference to a testify test suite. 31 suite.Suite 32 33 // server.PlatformServer is a reference to plank 34 PlatformServer 35 36 // os.Signal is the signal channel passed into plan for OS level notifications. 37 Syschan chan os.Signal 38 39 // bus.ChannelManager is a reference to the Transport's Channel Manager. 40 bus.ChannelManager 41 42 // bus.EventBus is a reference to Transport. 43 bus.EventBus 44 } 45 46 // PlankIntegrationTest allows test suites that use PlankIntegrationTestSuite as an embedded struct to set everything 47 // we need on our test. This is the only contract required to use this harness. 48 type PlankIntegrationTest interface { 49 SetPlatformServer(PlatformServer) 50 SetSysChan(chan os.Signal) 51 SetChannelManager(bus.ChannelManager) 52 SetBus(eventBus bus.EventBus) 53 } 54 55 // SetupPlankTestSuiteForTest will copy over everything from the newly set up test suite, to a suite being run. 56 func SetupPlankTestSuiteForTest(suite *PlankIntegrationTestSuite, test PlankIntegrationTest) { 57 test.SetPlatformServer(suite.PlatformServer) 58 test.SetSysChan(suite.Syschan) 59 test.SetChannelManager(suite.ChannelManager) 60 test.SetBus(suite.EventBus) 61 } 62 63 // GetBasicTestServerConfig will generate a simple platform server config, ready to use in a test. 64 func GetBasicTestServerConfig(rootDir, outLog, accessLog, errLog string, port int, noBanner bool) *PlatformServerConfig { 65 cfg := &PlatformServerConfig{ 66 RootDir: rootDir, 67 Host: "localhost", 68 Port: port, 69 RestBridgeTimeout: time.Minute, 70 LogConfig: &utils.LogConfig{ 71 OutputLog: outLog, 72 AccessLog: accessLog, 73 ErrorLog: errLog, 74 FormatOptions: &utils.LogFormatOption{}, 75 }, 76 NoBanner: noBanner, 77 ShutdownTimeout: time.Minute, 78 } 79 return cfg 80 } 81 82 // SetupPlankTestSuite will boot a new instance of plank on your chosen port and will also fire up your service 83 // Ready to be tested. This always runs on localhost. 84 func SetupPlankTestSuite(service svc.FabricService, serviceChannel string, port int, 85 config *PlatformServerConfig) (*PlankIntegrationTestSuite, error) { 86 87 s := &PlankIntegrationTestSuite{} 88 89 customFormatter := new(logrus.TextFormatter) 90 utils.Log.SetFormatter(customFormatter) 91 customFormatter.DisableTimestamp = true 92 93 // check if config has been supplied, if not, generate default one. 94 if config == nil { 95 config = GetBasicTestServerConfig("/", "stdout", "stdout", "stderr", port, true) 96 } 97 98 s.PlatformServer = NewPlatformServer(config) 99 if err := s.PlatformServer.RegisterService(service, serviceChannel); err != nil { 100 return nil, errors.New("cannot create printing press service, test failed") 101 } 102 103 s.Syschan = make(chan os.Signal, 1) 104 go s.PlatformServer.StartServer(s.Syschan) 105 106 s.EventBus = bus.ResetBus() 107 svc.ResetServiceRegistry() 108 109 // get a pointer to the channel manager 110 s.ChannelManager = s.EventBus.GetChannelManager() 111 112 // wait, service may be slow loading, rest mapping happens last. 113 wait := make(chan bool) 114 go func() { 115 time.Sleep(10 * time.Millisecond) 116 wait <- true // Sending something to the channel to let the main thread continue 117 118 }() 119 120 <-wait 121 return s, nil 122 } 123 124 // CreateTestServer consumes *server.PlatformServerConfig and returns a new instance of PlatformServer along with 125 // its base URL and log file name 126 func CreateTestServer(config *PlatformServerConfig) (baseUrl, logFile string, s PlatformServer) { 127 testServer := NewPlatformServer(config) 128 129 protocol := "http" 130 if config.TLSCertConfig != nil { 131 protocol += "s" 132 } 133 134 baseUrl = fmt.Sprintf("%s://%s:%d", protocol, config.Host, config.Port) 135 return baseUrl, config.LogConfig.OutputLog, testServer 136 } 137 138 // RunWhenServerReady runs test function fn after Plank has booted up 139 func RunWhenServerReady(t *testing.T, eventBus bus.EventBus, fn func(*testing.T)) { 140 handler, _ := eventBus.ListenOnce(PLANK_SERVER_ONLINE_CHANNEL) 141 handler.Handle(func(message *model.Message) { 142 fn(t) 143 }, func(err error) { 144 assert.FailNow(t, err.Error()) 145 }) 146 } 147 148 // GetTestPort returns an available port for use by Plank tests 149 func GetTestPort() int { 150 minPort := 9980 151 fr := utils.GetCallerStackFrame() 152 port, exists := testSuitePortMap[fr.File] 153 if exists { 154 return port 155 } 156 157 // try 5 more times with every failure advancing port by one 158 tryPort := minPort 159 for i := 0; i <= 4; i++ { 160 tryPort = minPort + len(testSuitePortMap) + i 161 _, err := net.Dial("tcp", fmt.Sprintf(":%d", tryPort)) 162 if err == nil { // port in use, try next one 163 continue 164 } 165 166 testSuitePortMap[fr.File] = tryPort 167 break 168 } 169 170 if testSuitePortMap[fr.File] == 0 { // port could not be assigned 171 panic(fmt.Errorf("could not assign a port for tests. last tried port is %d", tryPort)) 172 } 173 174 return testSuitePortMap[fr.File] 175 } 176 177 // GetTestTLSCertConfig returns a new &TLSCertConfig for testing. 178 func GetTestTLSCertConfig(testRootPath string) *TLSCertConfig { 179 crtFile := filepath.Join(testRootPath, "test_server.crt") 180 keyFile := filepath.Join(testRootPath, "test_server.key") 181 _ = ioutil.WriteFile(crtFile, []byte(testServerCertTmpl), 0700) 182 _ = ioutil.WriteFile(keyFile, []byte(testServerKeyTmpl), 0700) 183 return &TLSCertConfig{ 184 CertFile: crtFile, 185 KeyFile: keyFile, 186 SkipCertificateValidation: true, 187 } 188 } 189 190 // GetTestFabricBrokerConfig returns a basic fabric broker config. 191 func GetTestFabricBrokerConfig() *FabricBrokerConfig { 192 return &FabricBrokerConfig{ 193 FabricEndpoint: "/ws", 194 UseTCP: false, 195 TCPPort: 61613, 196 EndpointConfig: &bus.EndpointConfig{ 197 TopicPrefix: "/topic", 198 UserQueuePrefix: "/queue", 199 AppRequestPrefix: "/pub", 200 AppRequestQueuePrefix: "/pub/queue", 201 Heartbeat: 30000, 202 }, 203 } 204 } 205 206 // CreateConfigJsonForTest creates and returns the path to a file containing the plank configuration in JSON format 207 func CreateConfigJsonForTest() (string, error) { 208 configJsonContent := `{ 209 "debug": true, 210 "no_banner": true, 211 "root_dir": "./", 212 "host": "localhost", 213 "port": 31234, 214 "log_config": { 215 "root": ".", 216 "access_log": "access.log", 217 "error_log": "errors.log", 218 "output_log": "stdout", 219 "format_options": { 220 "force_colors": false, 221 "disable_colors": true, 222 "force_quote": false, 223 "disable_quote": false, 224 "environment_override_colors": false, 225 "disable_timestamp": false, 226 "full_timestamp": true, 227 "timestamp_format": "", 228 "disable_sorting": false, 229 "disable_level_truncation": false, 230 "pad_level_text": false, 231 "quote_empty_fields": false, 232 "is_terminal": true 233 } 234 }, 235 "spa_config": { 236 "root_folder": "public/", 237 "base_uri": "/", 238 "static_assets": [ 239 "public/assets:/assets" 240 ], 241 "cache_control_rules": { 242 "*.{js,css}": "public, max-age=86400", 243 "*.{ico,jpg,jpeg,svg,png,gif,tiff}": "public, max-age=604800" 244 } 245 }, 246 "shutdown_timeout_in_minutes": -1, 247 "rest_bridge_timeout_in_minutes": 0, 248 "fabric_config": { 249 "fabric_endpoint": "/ws", 250 "endpoint_config": { 251 "TopicPrefix": "/topic", 252 "UserQueuePrefix": "/queue", 253 "AppRequestPrefix": "/pub", 254 "AppRequestQueuePrefix": "/pub/queue", 255 "Heartbeat": 60000 256 } 257 }, 258 "enable_prometheus": true, 259 "tls_config": { 260 "cert_file": "cert/fullchain.pem", 261 "key_file": "cert/server.key" 262 } 263 }` 264 testDir := filepath.Join(os.TempDir(), "plank-tests") 265 testFile := filepath.Join(testDir, "test-config.json") 266 err := os.MkdirAll(testDir, 0744) 267 if err != nil { 268 return "", err 269 } 270 err = ioutil.WriteFile(testFile, []byte(configJsonContent), 0744) 271 if err != nil { 272 return "", err 273 } 274 return testFile, nil 275 }