go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/sdk/testutil/suite.go (about) 1 /* 2 3 Copyright (c) 2023 - Present. Will Charczuk. All rights reserved. 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 5 6 */ 7 8 package testutil 9 10 import ( 11 "context" 12 "fmt" 13 "os" 14 "testing" 15 16 "go.charczuk.com/sdk/db" 17 ) 18 19 // FailureCodes 20 const ( 21 SuiteFailureTests = 1 22 SuiteFailureBefore = 2 23 SuiteFailureAfter = 3 24 ) 25 26 // New returns a new test suite. 27 func New(m *testing.M, opts ...Option) *Suite { 28 s := Suite{ 29 M: m, 30 } 31 for _, opt := range opts { 32 opt(&s) 33 } 34 return &s 35 } 36 37 // Option is a mutator for a test suite. 38 type Option func(*Suite) 39 40 // OptAfter appends after run actions. 41 func OptAfter(steps ...SuiteAction) Option { 42 return func(s *Suite) { 43 s.After = append(s.After, steps...) 44 } 45 } 46 47 // OptBefore appends before run actions. 48 func OptBefore(steps ...SuiteAction) Option { 49 return func(s *Suite) { 50 s.Before = append(s.Before, steps...) 51 } 52 } 53 54 // OptWithDefaultDB runs a test suite with a dedicated database connection. 55 func OptWithDefaultDB(opts ...db.Option) Option { 56 return func(s *Suite) { 57 var err error 58 s.Before = append(s.Before, func(ctx context.Context) error { 59 _defaultDB, err = CreateTestDatabase(ctx, opts...) 60 if err != nil { 61 return err 62 } 63 return nil 64 }) 65 s.After = append(s.After, func(ctx context.Context) error { 66 if err := _defaultDB.Close(); err != nil { 67 return err 68 } 69 return DropTestDatabase(ctx, _defaultDB) 70 }) 71 } 72 } 73 74 // SuiteAction is a step that can be run either before or after package tests. 75 type SuiteAction func(context.Context) error 76 77 // Suite is a set of before and after actions for a given package tests. 78 type Suite struct { 79 M *testing.M 80 Before []SuiteAction 81 After []SuiteAction 82 } 83 84 // Run runs tests and calls os.Exit(...) with the exit code. 85 func (s Suite) Run() { 86 os.Exit(s.RunCode()) 87 } 88 89 // RunCode runs the suite and returns an exit code. 90 // 91 // It is used by `.Run()`, which will os.Exit(...) this code. 92 func (s Suite) RunCode() (code int) { 93 ctx := context.Background() 94 var err error 95 for _, before := range s.Before { 96 if err = executeSafe(ctx, before); err != nil { 97 fmt.Fprintf(os.Stderr, "failed to complete before test actions: %+v\n", err) 98 code = SuiteFailureBefore 99 return 100 } 101 } 102 defer func() { 103 for _, after := range s.After { 104 if err = executeSafe(ctx, after); err != nil { 105 fmt.Fprintf(os.Stderr, "failed to complete after test actions: %+v\n", err) 106 code = SuiteFailureAfter 107 return 108 } 109 } 110 }() 111 if s.M != nil { 112 code = s.M.Run() 113 } 114 return 115 } 116 117 func executeSafe(ctx context.Context, action func(context.Context) error) (err error) { 118 defer func() { 119 if r := recover(); r != nil { 120 err = fmt.Errorf("%v", r) 121 } 122 }() 123 err = action(ctx) 124 return 125 }