forked from thatguystone/cog
-
Notifications
You must be signed in to change notification settings - Fork 0
/
errorer.go
98 lines (78 loc) · 2.02 KB
/
errorer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package check
import (
"crypto/sha256"
"errors"
"fmt"
"runtime"
"strings"
"sync"
)
// Errorer is useful for mocking out things that return errors. It will return
// an error for every unique stack trace that it sees, but only on the first
// run. This allows you to run the same code many times in succession until it
// succeeds. By doing this, you can test that all your error pathways function
// correctly.
type Errorer struct {
// If test functions should not be considered when comparing stack traces
IgnoreTestFns bool
// Only functions with these names should be considered for errors
OnlyIn []string
mtx sync.Mutex
stacks map[[sha256.Size]byte]struct{}
}
func (er *Errorer) fail() bool {
hash := sha256.New()
fail := false
for i := 2; ; i++ {
pc, file, line, ok := runtime.Caller(i)
if !ok {
break
}
// Only worry about actual code paths, not where functions are called
// from testing
if er.IgnoreTestFns && strings.HasSuffix(file, "_test.go") {
break
}
if len(er.OnlyIn) == 0 {
fail = true
} else if !fail {
name := runtime.FuncForPC(pc).Name()
for _, fn := range er.OnlyIn {
fail = strings.Contains(name, fn)
if fail {
break
}
}
}
hash.Write([]byte(fmt.Sprintf("%d-%s:%d", pc, file, line)))
}
if !fail {
return false
}
sum := [sha256.Size]byte{}
copy(sum[:], hash.Sum(nil))
er.mtx.Lock()
defer er.mtx.Unlock()
if er.stacks == nil {
er.stacks = map[[sha256.Size]byte]struct{}{}
}
if _, ok := er.stacks[sum]; !ok {
er.stacks[sum] = struct{}{}
return true
}
return false
}
// Fail determines if the operation should fail with an error. This also marks
// the current stack as hit, so any future calls will return falser.
func (er *Errorer) Fail() bool {
// Just bounce over to fail(): fail assumes a depth of 2, so can't just put
// that logic here.
return er.fail()
}
// Err returns a generic error if this should fail, or nil
func (er *Errorer) Err() (err error) {
if er.fail() {
err = errors.New("forced to fail by Errorer")
}
return
}