Skip to content

Commit

Permalink
Merge pull request #4 from Azure/haitao/fixReflectErrorCheck
Browse files Browse the repository at this point in the history
fix panic in reflect
  • Loading branch information
haitch committed Apr 14, 2020
2 parents d54bfec + c166c74 commit 9e1efed
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 2 deletions.
19 changes: 18 additions & 1 deletion async_task.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,23 @@ func Start(ctx context.Context, task AsyncFunc) *TaskStatus {
return record
}

// isErrorReallyError do extra error check
// - Nil Pointer to a Type (that implement error)
// - Zero Value of a Type (that implement error)
func isErrorReallyError(err error) bool {
v := reflect.ValueOf(err)
if v.Type().Kind() == reflect.Ptr &&
v.IsNil() {
return false
}

if v.Type().Kind() == reflect.Struct &&
v.IsZero() {
return false
}
return true
}

func runAndTrackTask(record *TaskStatus, task func(ctx context.Context) (interface{}, error)) {
defer record.waitGroup.Done()
defer func() {
Expand All @@ -150,7 +167,7 @@ func runAndTrackTask(record *TaskStatus, task func(ctx context.Context) (interfa
// incase some team use pointer typed error (implement Error() string on a pointer type)
// which can break err check (but nil point assigned to error result to non-nil error)
// check out TestPointerErrorCase in error_test.go
reflect.ValueOf(err).IsNil() {
!isErrorReallyError(err) {
record.finish(StateCompleted, result, nil)
return
}
Expand Down
11 changes: 10 additions & 1 deletion async_task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func TestEasyCase(t *testing.T) {
result = rawResult.(int)
assert.Equal(t, result, 9)

assert.True(t, elapsed.Microseconds() < 2, "Second wait should take more than 2 millisecond")
assert.True(t, elapsed.Microseconds() < 2, "Second wait should return immediately")
}

func TestCancelFunc(t *testing.T) {
Expand Down Expand Up @@ -150,6 +150,15 @@ func TestConsistentResultAfterTimeout(t *testing.T) {
rawResult, err = t1.Wait()
assert.Equal(t, asynctask.ErrTimeout, err, "should return reason of error")
assert.Nil(t, rawResult, "didn't expect resule on canceled task")

// invoke WaitWithTimeout again,
start := time.Now()
rawResult, err = t1.WaitWithTimeout(1 * time.Second)
elapsed := time.Since(start)
assert.Equal(t, asynctask.StateCanceled, t1.State(), "t1 should remain canceled even after routine finish")
assert.Equal(t, asynctask.ErrTimeout, err, "should return reason of error")
assert.Nil(t, rawResult, "didn't expect resule on canceled task")
assert.True(t, elapsed.Microseconds() < 2, "Second wait should return immediately")
}

func TestCompletedTask(t *testing.T) {
Expand Down
28 changes: 28 additions & 0 deletions error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import (
"github.com/stretchr/testify/assert"
)

type structError struct{}

func (pe structError) Error() string {
return "Error from struct type"
}

type pointerError struct{}

func (pe *pointerError) Error() string {
Expand Down Expand Up @@ -77,3 +83,25 @@ func TestPointerErrorCase(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, result, "Done")
}

func TestStructErrorCase(t *testing.T) {
t.Parallel()

// nil point of a type that implement error
var se structError
// pass this nil pointer to error interface
var err error = se
// now you get a non-nil error
assert.False(t, err == nil, "reason this test is needed")

ctx := newTestContext(t)
tsk := asynctask.Start(ctx, func(ctx context.Context) (interface{}, error) {
time.Sleep(100 * time.Millisecond)
var se structError
return "Done", se
})

result, err := tsk.Wait()
assert.NoError(t, err)
assert.Equal(t, result, "Done")
}

0 comments on commit 9e1efed

Please sign in to comment.