Skip to content

Commit 03cd31e

Browse files
committed
Add implementations of encoding.MarshalText to stack.Call and stack.CallStack.
1 parent cbb7b96 commit 03cd31e

File tree

2 files changed

+95
-3
lines changed

2 files changed

+95
-3
lines changed

stack.go

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
package stack
1111

1212
import (
13+
"bytes"
14+
"errors"
1315
"fmt"
1416
"io"
1517
"path/filepath"
@@ -50,6 +52,21 @@ func (c Call) String() string {
5052
return fmt.Sprint(c)
5153
}
5254

55+
// MarshalText implements encoding.TextMarshaler. It formats the Call the same
56+
// as fmt.Sprintf("%v", c).
57+
func (c Call) MarshalText() ([]byte, error) {
58+
if c.fn == nil {
59+
return nil, ErrNoFunc
60+
}
61+
buf := bytes.Buffer{}
62+
fmt.Fprint(&buf, c)
63+
return buf.Bytes(), nil
64+
}
65+
66+
// ErrNoFunc means that the Call has a nil *runtime.Func. The most likely
67+
// cause is a Call with the zero value.
68+
var ErrNoFunc = errors.New("no call stack information")
69+
5370
// Format implements fmt.Formatter with support for the following verbs.
5471
//
5572
// %s source file
@@ -181,18 +198,42 @@ func (cs CallStack) String() string {
181198
return fmt.Sprint(cs)
182199
}
183200

201+
var (
202+
openBracketBytes = []byte("[")
203+
closeBracketBytes = []byte("]")
204+
spaceBytes = []byte(" ")
205+
)
206+
207+
// MarshalText implements encoding.TextMarshaler. It formats the CallStack the
208+
// same as fmt.Sprintf("%v", cs).
209+
func (cs CallStack) MarshalText() ([]byte, error) {
210+
buf := bytes.Buffer{}
211+
buf.Write(openBracketBytes)
212+
for i, pc := range cs {
213+
if pc.fn == nil {
214+
return nil, ErrNoFunc
215+
}
216+
if i > 0 {
217+
buf.Write(spaceBytes)
218+
}
219+
fmt.Fprint(&buf, pc)
220+
}
221+
buf.Write(closeBracketBytes)
222+
return buf.Bytes(), nil
223+
}
224+
184225
// Format implements fmt.Formatter by printing the CallStack as square brackes
185226
// ([, ]) surrounding a space separated list of Calls each formatted with the
186227
// supplied verb and options.
187228
func (cs CallStack) Format(s fmt.State, verb rune) {
188-
s.Write([]byte("["))
229+
s.Write(openBracketBytes)
189230
for i, pc := range cs {
190231
if i > 0 {
191-
s.Write([]byte(" "))
232+
s.Write(spaceBytes)
192233
}
193234
pc.Format(s, verb)
194235
}
195-
s.Write([]byte("]"))
236+
s.Write(closeBracketBytes)
196237
}
197238

198239
// findSigpanic intentially executes faulting code to generate a stack trace

stack_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"io/ioutil"
66
"path"
77
"path/filepath"
8+
"reflect"
89
"runtime"
910
"strings"
1011
"testing"
@@ -110,6 +111,43 @@ func TestCallString(t *testing.T) {
110111
}
111112
}
112113

114+
func TestCallMarshalText(t *testing.T) {
115+
t.Parallel()
116+
117+
c := stack.Caller(0)
118+
_, file, line, ok := runtime.Caller(0)
119+
line--
120+
if !ok {
121+
t.Fatal("runtime.Caller(0) failed")
122+
}
123+
124+
c2, _, file2, line2, ok2 := testType{}.testMethod()
125+
if !ok2 {
126+
t.Fatal("runtime.Caller(0) failed")
127+
}
128+
129+
data := []struct {
130+
c stack.Call
131+
desc string
132+
out []byte
133+
err error
134+
}{
135+
{stack.Call{}, "error", nil, stack.ErrNoFunc},
136+
{c, "func", []byte(fmt.Sprint(path.Base(file), ":", line)), nil},
137+
{c2, "meth", []byte(fmt.Sprint(path.Base(file2), ":", line2)), nil},
138+
}
139+
140+
for _, d := range data {
141+
text, err := d.c.MarshalText()
142+
if got, want := err, d.err; got != want {
143+
t.Errorf("%s: got err %v, want err %v", d.desc, got, want)
144+
}
145+
if got, want := text, d.out; !reflect.DeepEqual(got, want) {
146+
t.Errorf("%s: got %s, want %s", d.desc, got, want)
147+
}
148+
}
149+
}
150+
113151
func TestCallStackString(t *testing.T) {
114152
cs, line0 := getTrace(t)
115153
_, file, line1, ok := runtime.Caller(0)
@@ -123,6 +161,19 @@ func TestCallStackString(t *testing.T) {
123161
}
124162
}
125163

164+
func TestCallStackMarshalText(t *testing.T) {
165+
cs, line0 := getTrace(t)
166+
_, file, line1, ok := runtime.Caller(0)
167+
line1--
168+
if !ok {
169+
t.Fatal("runtime.Caller(0) failed")
170+
}
171+
file = path.Base(file)
172+
text, _ := cs.MarshalText()
173+
if got, want := text, []byte(fmt.Sprintf("[%s:%d %s:%d]", file, line0, file, line1)); !reflect.DeepEqual(got, want) {
174+
t.Errorf("\n got %v\nwant %v", got, want)
175+
}
176+
}
126177
func getTrace(t *testing.T) (stack.CallStack, int) {
127178
cs := stack.Trace().TrimRuntime()
128179
_, _, line, ok := runtime.Caller(0)

0 commit comments

Comments
 (0)