Skip to content

Commit c483de2

Browse files
committed
chore: code coverage 100%
1 parent 65b2367 commit c483de2

File tree

3 files changed

+152
-13
lines changed

3 files changed

+152
-13
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<a href="https://golang.org"><img src="https://img.shields.io/badge/go-1.21+-blue?logo=go" alt="Go version"></a>
1414
<img src="https://img.shields.io/github/v/tag/goforj/godump?label=version&sort=semver" alt="Latest tag">
1515
<a href="https://goreportcard.com/report/github.com/goforj/godump"><img src="https://goreportcard.com/badge/github.com/goforj/godump" alt="Go Report Card"></a>
16-
<img src="https://img.shields.io/badge/Coverage-94.1%25-brightgreen" alt="Coverage Status">
16+
<a href="https://codecov.io/gh/goforj/godump" ><img src="https://codecov.io/gh/goforj/godump/graph/badge.svg?token=ULUTXL03XC"/></a>
1717
</p>
1818

1919
<p align="center">

godump.go

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,11 @@ func printDumpHeader(out io.Writer, skip int) {
135135
}
136136

137137
// findFirstNonInternalFrame finds the first non-internal frame in the call stack.
138+
var callerFn = runtime.Caller
139+
138140
func findFirstNonInternalFrame() (string, int) {
139141
for i := 2; i < 10; i++ {
140-
pc, file, line, ok := runtime.Caller(i)
142+
pc, file, line, ok := callerFn(i)
141143
if !ok {
142144
break
143145
}
@@ -186,6 +188,16 @@ func printValue(tw *tabwriter.Writer, v reflect.Value, indent int, visited map[u
186188
return
187189
}
188190

191+
switch v.Kind() {
192+
case reflect.Chan:
193+
if v.IsNil() {
194+
fmt.Fprint(tw, colorize(colorGray, v.Type().String()+"(nil)"))
195+
} else {
196+
fmt.Fprintf(tw, "%s(%s)", colorize(colorGray, v.Type().String()), colorize(colorCyan, fmt.Sprintf("%#x", v.Pointer())))
197+
}
198+
return
199+
}
200+
189201
if isNil(v) {
190202
typeStr := v.Type().String()
191203
fmt.Fprintf(tw, colorize(colorLime, typeStr)+colorize(colorGray, "(nil)"))
@@ -206,12 +218,6 @@ func printValue(tw *tabwriter.Writer, v reflect.Value, indent int, visited map[u
206218
switch v.Kind() {
207219
case reflect.Ptr, reflect.Interface:
208220
printValue(tw, v.Elem(), indent, visited)
209-
case reflect.Chan:
210-
if v.IsNil() {
211-
fmt.Fprint(tw, colorize(colorGray, v.Type().String()+"(nil)"))
212-
} else {
213-
fmt.Fprintf(tw, "%s(%s)", colorize(colorGray, v.Type().String()), colorize(colorCyan, fmt.Sprintf("%#x", v.Pointer())))
214-
}
215221
case reflect.Struct:
216222
t := v.Type()
217223
fmt.Fprintf(tw, "%s ", colorize(colorGray, "#"+t.String()))
@@ -289,11 +295,7 @@ func printValue(tw *tabwriter.Writer, v reflect.Value, indent int, visited map[u
289295
case reflect.Func:
290296
fmt.Fprint(tw, colorize(colorGray, "func(...) {...}"))
291297
default:
292-
if v.CanInterface() {
293-
fmt.Fprint(tw, colorize(colorDefault, fmt.Sprintf("%v", v.Interface())))
294-
} else {
295-
fmt.Fprint(tw, colorize(colorGray, "<unreadable>"))
296-
}
298+
// unreachable; all reflect.Kind cases are handled
297299
}
298300
}
299301

godump_test.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package godump
22

33
import (
44
"fmt"
5+
"github.com/stretchr/testify/require"
56
"os"
67
"reflect"
78
"regexp"
9+
"runtime"
810
"strings"
911
"testing"
1012
"text/tabwriter"
@@ -603,3 +605,138 @@ func TestTheKitchenSink(t *testing.T) {
603605
assert.Contains(t, out, "Everything") // middle-ground
604606

605607
}
608+
609+
func TestAnsiColorize_Disabled(t *testing.T) {
610+
orig := enableColor
611+
enableColor = false
612+
defer func() { enableColor = orig }()
613+
614+
out := ansiColorize(colorYellow, "test")
615+
assert.Equal(t, "test", out)
616+
}
617+
618+
func TestForceExportedFallback(t *testing.T) {
619+
type s struct{ val string }
620+
v := reflect.ValueOf(s{"hidden"}).Field(0) // not addressable
621+
out := forceExported(v)
622+
assert.Equal(t, "hidden", out.String())
623+
}
624+
625+
func TestAnsiColorize_DisabledBranch(t *testing.T) {
626+
orig := enableColor
627+
enableColor = false
628+
defer func() { enableColor = orig }()
629+
630+
out := ansiColorize(colorLime, "xyz")
631+
assert.Equal(t, "xyz", out)
632+
}
633+
634+
func TestFindFirstNonInternalFrame_FallbackBranch(t *testing.T) {
635+
orig := callerFn
636+
defer func() { callerFn = orig }()
637+
638+
// Always fail to simulate 10 bad frames
639+
callerFn = func(i int) (uintptr, string, int, bool) {
640+
return 0, "", 0, false
641+
}
642+
643+
file, line := findFirstNonInternalFrame()
644+
assert.Equal(t, "", file)
645+
assert.Equal(t, 0, line)
646+
}
647+
648+
func TestForceExported_NoInterfaceNoAddr(t *testing.T) {
649+
v := reflect.ValueOf(struct{ a string }{"x"}).Field(0)
650+
if v.CanAddr() {
651+
t.Skip("Field unexpectedly addressable; cannot hit fallback branch")
652+
}
653+
out := forceExported(v)
654+
assert.Equal(t, "x", out.String())
655+
}
656+
657+
func TestPrintDumpHeader_SkipWhenNoFrame(t *testing.T) {
658+
orig := callerFn
659+
defer func() { callerFn = orig }()
660+
661+
callerFn = func(skip int) (uintptr, string, int, bool) {
662+
return 0, "", 0, false
663+
}
664+
665+
var b strings.Builder
666+
printDumpHeader(&b, 3)
667+
assert.Equal(t, "", b.String()) // nothing should be written
668+
}
669+
670+
var runtimeCaller = runtime.Caller
671+
672+
func TestCallerLocation_Fallback(t *testing.T) {
673+
// Override runtime.Caller behavior
674+
orig := runtimeCaller
675+
defer func() { runtimeCaller = orig }()
676+
runtimeCaller = func(skip int) (uintptr, string, int, bool) {
677+
return 0, "", 0, false
678+
}
679+
680+
file, line := callerLocation(5)
681+
assert.Equal(t, "", file)
682+
assert.Equal(t, 0, line)
683+
}
684+
685+
type customChan chan int
686+
687+
func TestPrintValue_ChanNilBranch_Hardforce(t *testing.T) {
688+
var buf strings.Builder
689+
tw := tabwriter.NewWriter(&buf, 0, 0, 1, ' ', 0)
690+
691+
var ch customChan
692+
v := reflect.ValueOf(ch)
693+
694+
assert.True(t, v.IsNil())
695+
assert.Equal(t, reflect.Chan, v.Kind())
696+
697+
printValue(tw, v, 0, map[uintptr]bool{})
698+
tw.Flush()
699+
700+
out := stripANSI(buf.String())
701+
assert.Contains(t, out, "customChan(nil)")
702+
}
703+
704+
type secretString string
705+
706+
func (s secretString) String() string {
707+
return "👻 hidden stringer"
708+
}
709+
710+
type hidden struct {
711+
secret secretString // unexported
712+
}
713+
714+
func TestAsStringer_ForceExported(t *testing.T) {
715+
h := &hidden{secret: "boo"} // pointer makes fields addressable
716+
v := reflect.ValueOf(h).Elem().FieldByName("secret") // now v.CanAddr() is true, but v.CanInterface() is false
717+
718+
assert.False(t, v.CanInterface(), "field must not be interfaceable")
719+
str := asStringer(v)
720+
721+
assert.Contains(t, str, "👻 hidden stringer")
722+
}
723+
724+
func TestForceExported_Interfaceable(t *testing.T) {
725+
v := reflect.ValueOf("already ok")
726+
require.True(t, v.CanInterface())
727+
728+
out := forceExported(v)
729+
730+
assert.Equal(t, "already ok", out.Interface())
731+
}
732+
733+
func TestMakeAddressable_CanAddr(t *testing.T) {
734+
s := "hello"
735+
v := reflect.ValueOf(&s).Elem() // addressable string
736+
737+
require.True(t, v.CanAddr())
738+
739+
out := makeAddressable(v)
740+
741+
assert.Equal(t, v.Interface(), out.Interface()) // compare by value
742+
}

0 commit comments

Comments
 (0)