github.com/Aoi-hosizora/ahlib@v1.5.1-0.20230404072829-241b93cf91c7/xerror/xerror_test.go (about) 1 package xerror 2 3 import ( 4 "context" 5 "errors" 6 "github.com/Aoi-hosizora/ahlib/xtesting" 7 "strconv" 8 "testing" 9 "time" 10 ) 11 12 type stringError string 13 14 func (m stringError) Error() string { 15 return string(m) 16 } 17 18 func TestMultiError(t *testing.T) { 19 me := &multiError{errs: []error{}} 20 xtesting.Equal(t, me.Errors(), []error{}) 21 xtesting.Equal(t, me.Error(), "") 22 23 me = &multiError{errs: []error{nil}} 24 xtesting.Equal(t, me.Errors(), []error{nil}) 25 xtesting.Panic(t, func() { _ = me.Error() }) 26 27 test := errors.New("test") 28 me = &multiError{errs: []error{test}} 29 xtesting.Equal(t, me.Errors(), []error{test}) 30 xtesting.Equal(t, me.Error(), "test") 31 xtesting.Equal(t, errors.Is(me, test), true) 32 xtesting.Equal(t, errors.Is(me, errors.New("test")), false) 33 e1 := &multiError{} 34 xtesting.Equal(t, errors.As(me, &e1), true) 35 xtesting.Equal(t, errors.Is(me, e1), true) 36 xtesting.Equal(t, e1, me) // <<< 37 e2 := &strconv.NumError{} 38 xtesting.Equal(t, errors.As(me, &e2), false) 39 me.errs = append(me.errs, &strconv.NumError{Func: "xxx"}) 40 xtesting.Equal(t, errors.As(me, &e2), true) 41 xtesting.Equal(t, errors.Is(me, e2), true) 42 xtesting.Equal(t, e2.Func, "xxx") // <<< 43 44 test1, test2 := errors.New("test1"), stringError("test2") 45 me = &multiError{errs: []error{test1, test2}} 46 xtesting.Equal(t, me.Errors(), []error{test1, test2}) 47 xtesting.Equal(t, me.Error(), "test1; test2") 48 xtesting.Equal(t, errors.Is(me, test1), true) 49 xtesting.Equal(t, errors.Is(me, test2), true) 50 xtesting.Equal(t, errors.Is(me, errors.New("test2")), false) 51 e3 := &multiError{} 52 xtesting.Equal(t, errors.As(me, &e3), true) 53 xtesting.Equal(t, errors.Is(me, e3), true) 54 xtesting.Equal(t, e3, me) // <<< 55 e4 := new(stringError) 56 xtesting.Equal(t, errors.As(me, e4), true) 57 xtesting.Equal(t, *e4, test2) 58 } 59 60 func TestCombine(t *testing.T) { 61 for _, tc := range []struct { 62 give []error 63 want error 64 }{ 65 {[]error{}, nil}, 66 {[]error{nil}, nil}, 67 {[]error{nil, nil}, nil}, 68 {[]error{nil, nil, errors.New("1")}, errors.New("1")}, 69 {[]error{nil, stringError("1"), nil}, stringError("1")}, 70 {[]error{nil, errors.New("1"), nil, errors.New("2")}, &multiError{errs: []error{errors.New("1"), errors.New("2")}}}, 71 {[]error{&multiError{errs: []error{errors.New("1"), stringError("2")}}, nil, errors.New("3")}, 72 &multiError{errs: []error{errors.New("1"), stringError("2"), errors.New("3")}}}, 73 {[]error{nil, &multiError{errs: []error{stringError("1")}}, nil, &multiError{errs: []error{errors.New("2"), errors.New("3")}}}, 74 &multiError{errs: []error{stringError("1"), errors.New("2"), errors.New("3")}}}, 75 {[]error{nil, &multiError{errs: []error{errors.New("1"), nil}}, nil, &multiError{errs: []error{errors.New("2"), nil, stringError("3")}}}, 76 &multiError{errs: []error{errors.New("1"), nil, errors.New("2"), nil, stringError("3")}}}, // <<< nil 77 {[]error{errors.New("1"), nil, &multiError{errs: []error{nil}}, &multiError{errs: []error{nil, nil}}}, 78 &multiError{errs: []error{errors.New("1"), nil, nil, nil}}}, // <<< nil 79 {[]error{&multiError{errs: []error{&multiError{errs: []error{stringError("?")}}}}}, 80 &multiError{errs: []error{&multiError{errs: []error{stringError("?")}}}}}, // <<< 81 } { 82 xtesting.Equal(t, Combine(tc.give...), tc.want) 83 } 84 85 t.Run("Append", func(t *testing.T) { 86 err := errors.New("1") 87 for i := 2; i <= 10; i++ { 88 err = Combine(err, errors.New(strconv.Itoa(i))) // <- Append 89 } 90 me, ok := err.(MultiError) 91 xtesting.True(t, ok) 92 xtesting.Equal(t, len(me.Errors()), 10) 93 for i := 0; i < 10; i++ { 94 xtesting.Equal(t, me.Errors()[i].Error(), strconv.Itoa(i+1)) 95 } 96 }) 97 } 98 99 func TestSeparate(t *testing.T) { 100 for _, tc := range []struct { 101 give error 102 want []error 103 }{ 104 {nil, nil}, 105 {errors.New("test"), []error{errors.New("test")}}, 106 {stringError("test"), []error{stringError("test")}}, 107 {&multiError{}, []error{}}, 108 {&multiError{errs: []error{nil}}, []error{nil}}, // <<< nil 109 {&multiError{errs: []error{nil, nil}}, []error{nil, nil}}, // <<< nil 110 {&multiError{errs: []error{errors.New("test")}}, []error{errors.New("test")}}, // <<< nil 111 {&multiError{errs: []error{errors.New("1"), stringError("2")}}, []error{errors.New("1"), stringError("2")}}, 112 {&multiError{errs: []error{nil, errors.New("test")}}, []error{nil, errors.New("test")}}, // <<< nil 113 {&multiError{errs: []error{nil, stringError("1"), &multiError{errs: []error{stringError("2")}}}}, 114 []error{nil, stringError("1"), &multiError{errs: []error{stringError("2")}}}}, // <<< 115 } { 116 xtesting.Equal(t, Separate(tc.give), tc.want) 117 } 118 } 119 120 func TestErrorGroup(t *testing.T) { 121 t.Run("Zero ErrorGroup", func(t *testing.T) { 122 eg := &ErrorGroup{} 123 eg.SetGoExecutor(defaultExecutor) 124 // 1. test context and panic 125 eg.Go(func(ctx context.Context) error { 126 xtesting.NotEqual(t, ctx, context.Background()) // wrap cancel automatically 127 return nil 128 }) 129 eg.Go(func(ctx context.Context) error { 130 panic("test") // will be recovered 131 }) 132 xtesting.Nil(t, eg.Wait()) 133 // 2. test error and deadlock 134 eg.Go(func(ctx context.Context) error { 135 return errors.New("test") 136 }) 137 eg.Go(func(ctx context.Context) error { 138 select { 139 case <-ctx.Done(): // no deadlock report 140 } 141 return nil 142 }) 143 xtesting.Equal(t, eg.Wait(), errors.New("test")) 144 // 3. test executor 145 eg = &ErrorGroup{} 146 xtesting.Nil(t, eg.goExecutor) 147 eg.Go(func(ctx context.Context) error { return nil }) // set eg.goExecutor 148 xtesting.Nil(t, eg.Wait()) 149 xtesting.NotNil(t, eg.goExecutor) 150 }) 151 152 t.Run("NewErrorGroup", func(t *testing.T) { 153 eg := NewErrorGroup(context.WithValue(context.Background(), "key", "value")) 154 eg.SetGoExecutor(defaultExecutor) 155 // 1. test context and panic 156 xtesting.NotPanic(t, func() { eg.Go(nil) }) 157 eg.Go(func(ctx context.Context) error { 158 xtesting.Equal(t, ctx.Value("key"), "value") 159 return nil 160 }) 161 eg.Go(func(ctx context.Context) error { 162 panic("test") // will be recovered 163 }) 164 xtesting.Nil(t, eg.Wait()) 165 // 2. test error and cancellation 166 eg.Go(func(ctx context.Context) error { 167 time.Sleep(time.Second) // wait for the second eg.Go 168 return errors.New("test") 169 }) 170 var outErr error 171 eg.Go(func(ctx context.Context) error { 172 select { 173 case <-ctx.Done(): 174 outErr = ctx.Err() // canceled by the first eg.Go 175 } 176 return nil 177 }) 178 xtesting.Equal(t, eg.Wait(), errors.New("test")) 179 xtesting.Equal(t, outErr, errors.New("context canceled")) // xxx 180 outVal := 1 181 eg.Go(func(ctx context.Context) error { 182 outVal = 2 // will not be executed after eg.Wait 183 return nil 184 }) 185 xtesting.Equal(t, eg.Wait(), errors.New("test")) 186 xtesting.Equal(t, outVal, 1) 187 // 3. test reset 188 eg.Reset(context.Background()) 189 eg.Go(func(ctx context.Context) error { 190 outVal = 2 // will not be executed after eg.Wait 191 return nil 192 }) 193 xtesting.Equal(t, eg.Wait(), nil) 194 xtesting.Equal(t, outVal, 2) 195 // 4. test timeout 196 f := func(ctx context.Context) error { 197 select { 198 case <-ctx.Done(): 199 return ctx.Err() 200 case <-time.After(time.Millisecond * 200): 201 } 202 return nil 203 } 204 ctx1, cancel1 := context.WithTimeout(context.Background(), time.Millisecond*100) 205 defer cancel1() 206 eg = NewErrorGroup(ctx1) 207 eg.Go(f) 208 xtesting.Equal(t, eg.Wait().Error(), "context deadline exceeded") // ctx.Done 209 ctx2, cancel2 := context.WithTimeout(context.Background(), time.Millisecond*300) 210 defer cancel2() 211 eg = NewErrorGroup(ctx2) 212 eg.Go(f) 213 xtesting.Nil(t, eg.Wait()) // time.After 214 }) 215 }