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  }