• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kinbiko / rogerr / 16693912839

02 Aug 2025 01:05PM UTC coverage: 85.417% (-5.8%) from 91.228%
16693912839

Pull #11

github

kinbiko
refactor: simplify example code and integration test
Pull Request #11: Implement stacktrace support

71 of 87 new or added lines in 1 file covered. (81.61%)

21 existing lines in 1 file now uncovered.

123 of 144 relevant lines covered (85.42%)

11.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.58
/error.go
1
package rogerr
2

3
import (
4
        "context"
5
        "errors"
6
        "fmt"
7
        "runtime"
8
        "runtime/debug"
9
        "strings"
10
)
11

12
// Frame represents a single frame in a stacktrace.
13
type Frame struct {
14
        File     string // Full file path
15
        Line     int    // Line number
16
        Function string // Function or method name
17
        InApp    bool   // true if application code, false if dependency
18
}
19

20
// ErrorHandler provides configurable error handling with optional stacktrace capture.
21
type ErrorHandler struct {
22
        stacktrace bool
23
}
24

25
// Option is a function that configures an ErrorHandler.
26
type Option func(*ErrorHandler)
27

28
type rError struct {
29
        err        error
30
        ctx        context.Context
31
        msg        string
32
        stacktrace []Frame
33
}
34

35
// Error returns the message of the rError, along with any wrapped error messages.
36
func (e *rError) Error() string {
21✔
37
        if e.err == nil && e.msg == "" {
22✔
38
                return "unknown error"
1✔
39
        }
1✔
40
        if e.err == nil {
27✔
41
                return e.msg
7✔
42
        }
7✔
43
        if e.msg == "" {
15✔
44
                return e.err.Error()
2✔
45
        }
2✔
46
        return fmt.Sprintf("%s: %s", e.msg, e.err)
11✔
47
}
48

49
// Unwrap is the conventional method for getting the underlying error of an error.
UNCOV
50
func (e *rError) Unwrap() error {
×
UNCOV
51
        if e == nil {
×
UNCOV
52
                return nil
×
UNCOV
53
        }
×
UNCOV
54
        return e.err
×
55
}
56

57
// getModulePath returns the main module path for determining if frames are in-app.
58
func getModulePath() string {
5✔
59
        if bi, ok := debug.ReadBuildInfo(); ok {
10✔
60
                return bi.Main.Path
5✔
61
        }
5✔
NEW
UNCOV
62
        return ""
×
63
}
64

65
// captureStacktrace captures the current call stack, excluding rogerr internal frames.
66
func captureStacktrace(modulePath string) []Frame {
5✔
67
        const maxFrames = 64
5✔
68
        ptrs := [maxFrames]uintptr{}
5✔
69

5✔
70
        // Skip 0 frames as we'll filter manually
5✔
71
        pcs := ptrs[0:runtime.Callers(0, ptrs[:])]
5✔
72

5✔
73
        allFrames := make([]Frame, 0, len(pcs))
5✔
74
        iter := runtime.CallersFrames(pcs)
5✔
75

5✔
76
        // First, collect all frames
5✔
77
        for {
37✔
78
                frame, more := iter.Next()
32✔
79

32✔
80
                // Determine if this is application code
32✔
81
                inApp := isInApp(frame.Function, modulePath)
32✔
82

32✔
83
                allFrames = append(allFrames, Frame{
32✔
84
                        File:     frame.File,
32✔
85
                        Line:     frame.Line,
32✔
86
                        Function: frame.Function,
32✔
87
                        InApp:    inApp,
32✔
88
                })
32✔
89

32✔
90
                if !more {
37✔
91
                        break
5✔
92
                }
93
        }
94

95
        // Now filter out rogerr frames, but keep everything after the last rogerr frame
96
        lastRogerrIndex := -1
5✔
97
        for i, frame := range allFrames {
37✔
98
                // Only filter out the main rogerr package, not internal modules
32✔
99
                if strings.HasPrefix(frame.Function, "github.com/kinbiko/rogerr.") {
49✔
100
                        lastRogerrIndex = i
17✔
101
                }
17✔
102
        }
103

104
        // Return frames after the last rogerr frame
105
        if lastRogerrIndex >= 0 && lastRogerrIndex+1 < len(allFrames) {
10✔
106
                return allFrames[lastRogerrIndex+1:]
5✔
107
        }
5✔
108

109
        // If no rogerr frames found, return all frames (shouldn't happen)
NEW
UNCOV
110
        return allFrames
×
111
}
112

113
// isInApp determines if a function belongs to the application or a dependency.
114
func isInApp(function, modulePath string) bool {
32✔
115
        if modulePath == "" {
32✔
NEW
UNCOV
116
                return false
×
NEW
UNCOV
117
        }
×
118

119
        // Special case for main.main
120
        if strings.Contains(function, "main.main") {
32✔
NEW
UNCOV
121
                return true
×
NEW
UNCOV
122
        }
×
123

124
        // Check if function belongs to the main module
125
        if strings.HasPrefix(function, modulePath) {
49✔
126
                return true
17✔
127
        }
17✔
128
        
129
        // Handle case where the binary is built from a module and function names
130
        // start with "main." - check if the module path contains the current module
131
        if strings.HasPrefix(function, "main.") {
15✔
NEW
UNCOV
132
                return true
×
NEW
UNCOV
133
        }
×
134
        
135
        return false
15✔
136
}
137

138
// WithStacktrace configures whether stacktraces should be captured.
139
func WithStacktrace(enabled bool) Option {
3✔
140
        return func(h *ErrorHandler) {
6✔
141
                h.stacktrace = enabled
3✔
142
        }
3✔
143
}
144

145
// NewErrorHandler creates a new ErrorHandler with the given options.
146
// By default, stacktrace capture is enabled.
147
func NewErrorHandler(opts ...Option) *ErrorHandler {
9✔
148
        h := &ErrorHandler{
9✔
149
                stacktrace: true, // stacktrace enabled by default
9✔
150
        }
9✔
151
        for _, opt := range opts {
12✔
152
                opt(h)
3✔
153
        }
3✔
154
        return h
9✔
155
}
156

157
// Stacktrace extracts the stacktrace from an error if it was created with ErrorHandler.
NEW
UNCOV
158
func (h *ErrorHandler) Stacktrace(err error) []Frame {
×
NEW
UNCOV
159
        rErr := &rError{}
×
NEW
UNCOV
160
        if errors.As(err, &rErr) {
×
NEW
UNCOV
161
                return rErr.stacktrace
×
NEW
UNCOV
162
        }
×
NEW
UNCOV
163
        return nil
×
164
}
165

166
// Wrap attaches ctx data and wraps the given error with message, optionally capturing stacktrace.
167
// ctx, err, and msgAndFmtArgs are all optional, but at least one must be given
168
// for this function to return a non-nil error.
169
// Any attached diagnostic data from this ctx will be preserved should you
170
// pass the returned error further up the stack.
171
func (h *ErrorHandler) Wrap(ctx context.Context, err error, msgAndFmtArgs ...interface{}) error {
6✔
172
        if ctx == nil && err == nil && msgAndFmtArgs == nil {
6✔
NEW
UNCOV
173
                return nil
×
NEW
UNCOV
174
        }
×
175

176
        e := &rError{err: err, ctx: ctx}
6✔
177

6✔
178
        if l := len(msgAndFmtArgs); l > 0 {
11✔
179
                if msg, ok := msgAndFmtArgs[0].(string); ok {
10✔
180
                        e.msg = fmt.Sprintf(msg, msgAndFmtArgs[1:]...)
5✔
181
                }
5✔
182
        }
183

184
        // Capture stacktrace if enabled
185
        if h.stacktrace {
11✔
186
                e.stacktrace = captureStacktrace(getModulePath())
5✔
187
        }
5✔
188

189
        return e
6✔
190
}
191

192
// Wrap attaches ctx data and wraps the given error with message.
193
// ctx, err, and msgAndFmtArgs are all optional, but at least one must be given
194
// for this function to return a non-nil error.
195
// Any attached diagnostic data from this ctx will be preserved should you
196
// pass the returned error further up the stack.
197
// Deprecated: Use ErrorHandler.Wrap instead.
198
func Wrap(ctx context.Context, err error, msgAndFmtArgs ...interface{}) error {
26✔
199
        if ctx == nil && err == nil && msgAndFmtArgs == nil {
28✔
200
                return nil
2✔
201
        }
2✔
202
        e := &rError{err: err, ctx: ctx}
24✔
203

24✔
204
        if l := len(msgAndFmtArgs); l > 0 {
41✔
205
                if msg, ok := msgAndFmtArgs[0].(string); ok {
34✔
206
                        e.msg = fmt.Sprintf(msg, msgAndFmtArgs[1:]...)
17✔
207
                }
17✔
208
        }
209
        return e
24✔
210
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc