go-micro.dev/v5@v5.12.0/test/service.go (about) 1 // Package test implements a testing framwork, and provides default tests. 2 package test 3 4 import ( 5 "context" 6 "fmt" 7 "sync" 8 "testing" 9 "time" 10 11 "github.com/pkg/errors" 12 13 "go-micro.dev/v5" 14 "go-micro.dev/v5/client" 15 "go-micro.dev/v5/debug/handler" 16 17 pb "go-micro.dev/v5/debug/proto" 18 ) 19 20 var ( 21 // ErrNoTests returns no test params are set. 22 ErrNoTests = errors.New("No tests to run, all values set to 0") 23 testTopic = "Test-Topic" 24 errorTopic = "Error-Topic" 25 ) 26 27 type parTest func(name string, c client.Client, p, s int, errChan chan error) 28 type testFunc func(name string, c client.Client, errChan chan error) 29 30 // ServiceTestConfig allows you to easily test a service configuration by 31 // running predefined tests against your custom service. You only need to 32 // provide a function to create the service, and how many of which test you 33 // want to run. 34 // 35 // The default tests provided, all running with separate parallel routines are: 36 // - Sequential Call requests 37 // - Bi-directional streaming 38 // - Pub/Sub events brokering 39 // 40 // You can provide an array of parallel routines to run for the request and 41 // stream tests. They will be run as matrix tests, so with each possible combination. 42 // Thus, in total (p * seq) + (p * streams) tests will be run. 43 type ServiceTestConfig struct { 44 // Service name to use for the tests 45 Name string 46 // NewService function will be called to setup the new service. 47 // It takes in a list of options, which by default will Context and an 48 // AfterStart with channel to signal when the service has been started. 49 NewService func(name string, opts ...micro.Option) (micro.Service, error) 50 // Parallel is the number of prallell routines to use for the tests. 51 Parallel []int 52 // Sequential is the number of sequential requests to send per parallel process. 53 Sequential []int 54 // Streams is the nummber of streaming messages to send over the stream per routine. 55 Streams []int 56 // PubSub is the number of times to publish messages to the broker per routine. 57 PubSub []int 58 59 mu sync.Mutex 60 msgCount int 61 } 62 63 // Run will start the benchmark tests. 64 func (stc *ServiceTestConfig) Run(b *testing.B) { 65 if err := stc.validate(); err != nil { 66 b.Fatal("Failed to validate config", err) 67 } 68 69 // Run routines with sequential requests 70 stc.prepBench(b, "req", stc.runParSeqTest, stc.Sequential) 71 72 // Run routines with streams 73 stc.prepBench(b, "streams", stc.runParStreamTest, stc.Streams) 74 75 // Run routines with pub/sub 76 stc.prepBench(b, "pubsub", stc.runBrokerTest, stc.PubSub) 77 } 78 79 // prepBench will prepare the benmark by setting the right parameters, 80 // and invoking the test. 81 func (stc *ServiceTestConfig) prepBench(b *testing.B, tName string, test parTest, seq []int) { 82 par := stc.Parallel 83 84 // No requests needed 85 if len(seq) == 0 || seq[0] == 0 { 86 return 87 } 88 89 for _, parallel := range par { 90 for _, sequential := range seq { 91 // Create the service name for the test 92 name := fmt.Sprintf("%s.%dp-%d%s", stc.Name, parallel, sequential, tName) 93 94 // Run test with parallel routines making each sequential requests 95 test := func(name string, c client.Client, errChan chan error) { 96 test(name, c, parallel, sequential, errChan) 97 } 98 99 benchmark := func(b *testing.B) { 100 b.ReportAllocs() 101 stc.runBench(b, name, test) 102 } 103 104 b.Logf("----------- STARTING TEST %s -----------", name) 105 106 // Run test, return if it fails 107 if !b.Run(name, benchmark) { 108 return 109 } 110 } 111 } 112 } 113 114 // runParSeqTest will make s sequential requests in p parallel routines. 115 func (stc *ServiceTestConfig) runParSeqTest(name string, c client.Client, p, s int, errChan chan error) { 116 testParallel(p, func() { 117 // Make serial requests 118 for z := 0; z < s; z++ { 119 if err := testRequest(context.Background(), c, name); err != nil { 120 errChan <- errors.Wrapf(err, "[%s] Request failed during testRequest", name) 121 return 122 } 123 } 124 }) 125 } 126 127 // Handle is used as a test handler. 128 func (stc *ServiceTestConfig) Handle(ctx context.Context, msg *pb.HealthRequest) error { 129 stc.mu.Lock() 130 stc.msgCount++ 131 stc.mu.Unlock() 132 133 return nil 134 } 135 136 // HandleError is used as a test handler. 137 func (stc *ServiceTestConfig) HandleError(ctx context.Context, msg *pb.HealthRequest) error { 138 return errors.New("dummy error") 139 } 140 141 // runBrokerTest will publish messages to the broker to test pub/sub. 142 func (stc *ServiceTestConfig) runBrokerTest(name string, c client.Client, p, s int, errChan chan error) { 143 stc.msgCount = 0 144 145 testParallel(p, func() { 146 for z := 0; z < s; z++ { 147 msg := pb.BusMsg{Msg: "Hello from broker!"} 148 if err := c.Publish(context.Background(), c.NewMessage(testTopic, &msg)); err != nil { 149 errChan <- errors.Wrap(err, "failed to publish message to broker") 150 return 151 } 152 153 msg = pb.BusMsg{Msg: "Some message that will error"} 154 if err := c.Publish(context.Background(), c.NewMessage(errorTopic, &msg)); err == nil { 155 errChan <- errors.New("Publish is supposed to return an error, but got no error") 156 return 157 } 158 } 159 }) 160 161 if stc.msgCount != s*p { 162 errChan <- fmt.Errorf("pub/sub does not work properly, invalid message count. Expected %d messaged, but received %d", s*p, stc.msgCount) 163 return 164 } 165 } 166 167 // runParStreamTest will start streaming, and send s messages parallel in p routines. 168 func (stc *ServiceTestConfig) runParStreamTest(name string, c client.Client, p, s int, errChan chan error) { 169 testParallel(p, func() { 170 // Create a client service 171 srv := pb.NewDebugService(name, c) 172 173 // Establish a connection to server over which we start streaming 174 bus, err := srv.MessageBus(context.Background()) 175 if err != nil { 176 errChan <- errors.Wrap(err, "failed to connect to message bus") 177 return 178 } 179 180 // Start streaming requests 181 for z := 0; z < s; z++ { 182 if err := bus.Send(&pb.BusMsg{Msg: "Hack the world!"}); err != nil { 183 errChan <- errors.Wrap(err, "failed to send to stream") 184 return 185 } 186 187 msg, err := bus.Recv() 188 if err != nil { 189 errChan <- errors.Wrap(err, "failed to receive message from stream") 190 return 191 } 192 193 expected := "Request received!" 194 if msg.Msg != expected { 195 errChan <- fmt.Errorf("stream returned unexpected mesage. Expected '%s', but got '%s'", expected, msg.Msg) 196 return 197 } 198 } 199 }) 200 } 201 202 // validate will make sure the provided test parameters are a legal combination. 203 func (stc *ServiceTestConfig) validate() error { 204 lp, lseq, lstr := len(stc.Parallel), len(stc.Sequential), len(stc.Streams) 205 206 if lp == 0 || (lseq == 0 && lstr == 0) { 207 return ErrNoTests 208 } 209 210 return nil 211 } 212 213 // runBench will create a service with the provided stc.NewService function, 214 // and run a benchmark on the test function. 215 func (stc *ServiceTestConfig) runBench(b *testing.B, name string, test testFunc) { 216 b.StopTimer() 217 218 // Channel to signal service has started 219 started := make(chan struct{}) 220 221 // Context with cancel to stop the service 222 ctx, cancel := context.WithCancel(context.Background()) 223 224 opts := []micro.Option{ 225 micro.Context(ctx), 226 micro.AfterStart(func() error { 227 started <- struct{}{} 228 return nil 229 }), 230 } 231 232 // Create a new service per test 233 service, err := stc.NewService(name, opts...) 234 if err != nil { 235 b.Fatalf("failed to create service: %v", err) 236 } 237 238 // Register handler 239 if err := pb.RegisterDebugHandler(service.Server(), handler.NewHandler(service.Client())); err != nil { 240 b.Fatalf("failed to register handler during initial service setup: %v", err) 241 } 242 243 o := service.Options() 244 if err := o.Broker.Connect(); err != nil { 245 b.Fatal(err) 246 } 247 248 // a := new(testService) 249 if err := o.Server.Subscribe(o.Server.NewSubscriber(testTopic, stc.Handle)); err != nil { 250 b.Fatalf("[%s] Failed to register subscriber: %v", name, err) 251 } 252 253 if err := o.Server.Subscribe(o.Server.NewSubscriber(errorTopic, stc.HandleError)); err != nil { 254 b.Fatalf("[%s] Failed to register subscriber: %v", name, err) 255 } 256 257 b.Logf("# == [ Service ] ==================") 258 b.Logf("# * Server: %s", o.Server.String()) 259 b.Logf("# * Client: %s", o.Client.String()) 260 b.Logf("# * Transport: %s", o.Transport.String()) 261 b.Logf("# * Broker: %s", o.Broker.String()) 262 b.Logf("# * Registry: %s", o.Registry.String()) 263 b.Logf("# * Auth: %s", o.Auth.String()) 264 b.Logf("# * Cache: %s", o.Cache.String()) 265 b.Logf("# ================================") 266 267 RunBenchmark(b, name, service, test, cancel, started) 268 } 269 270 // RunBenchmark will run benchmarks on a provided service. 271 // 272 // A test function can be provided that will be fun b.N times. 273 func RunBenchmark(b *testing.B, name string, service micro.Service, test testFunc, 274 cancel context.CancelFunc, started chan struct{}) { 275 b.StopTimer() 276 277 // Receive errors from routines on this channel 278 errChan := make(chan error, 1) 279 280 // Receive singal after service has shutdown 281 done := make(chan struct{}) 282 283 // Start the server 284 go func() { 285 b.Logf("[%s] Starting server for benchmark", name) 286 287 if err := service.Run(); err != nil { 288 errChan <- errors.Wrapf(err, "[%s] Error occurred during service.Run", name) 289 } 290 done <- struct{}{} 291 }() 292 293 sigTerm := make(chan struct{}) 294 295 // Benchmark routine 296 go func() { 297 defer func() { 298 b.StopTimer() 299 300 // Shutdown service 301 b.Logf("[%s] Shutting down", name) 302 cancel() 303 304 // Wait for service to be fully stopped 305 <-done 306 sigTerm <- struct{}{} 307 }() 308 309 // Wait for service to start 310 <-started 311 312 // Give the registry more time to setup 313 time.Sleep(time.Second) 314 315 b.Logf("[%s] Server started", name) 316 317 // Make a test call to warm the cache 318 for i := 0; i < 10; i++ { 319 if err := testRequest(context.Background(), service.Client(), name); err != nil { 320 errChan <- errors.Wrapf(err, "[%s] Failure during cache warmup testRequest", name) 321 } 322 } 323 324 // Check registration 325 services, err := service.Options().Registry.GetService(name) 326 if err != nil || len(services) == 0 { 327 errChan <- fmt.Errorf("service registration must have failed (%d services found), unable to get service: %w", len(services), err) 328 return 329 } 330 331 // Start benchmark 332 b.Logf("[%s] Starting benchtest", name) 333 b.ResetTimer() 334 b.StartTimer() 335 336 // Number of iterations 337 for i := 0; i < b.N; i++ { 338 test(name, service.Client(), errChan) 339 } 340 }() 341 342 // Wait for completion or catch any errors 343 select { 344 case err := <-errChan: 345 b.Fatal(err) 346 case <-sigTerm: 347 b.Logf("[%s] Completed benchmark", name) 348 } 349 } 350 351 // testParallel will run the test function in p parallel routines. 352 func testParallel(p int, test func()) { 353 // Waitgroup to wait for requests to finish 354 wg := sync.WaitGroup{} 355 356 // For concurrency 357 for j := 0; j < p; j++ { 358 wg.Add(1) 359 360 go func() { 361 defer wg.Done() 362 363 test() 364 }() 365 } 366 367 // Wait for test completion 368 wg.Wait() 369 } 370 371 // testRequest sends one test request. 372 // It calls the Debug.Health endpoint, and validates if the response returned 373 // contains the expected message. 374 func testRequest(ctx context.Context, c client.Client, name string) error { 375 req := c.NewRequest( 376 name, 377 "Debug.Health", 378 new(pb.HealthRequest), 379 ) 380 381 rsp := new(pb.HealthResponse) 382 383 if err := c.Call(ctx, req, rsp); err != nil { 384 return err 385 } 386 387 if rsp.Status != "ok" { 388 return errors.New("service response: " + rsp.Status) 389 } 390 391 return nil 392 }