Skip to content

Commit

Permalink
issues/65: Dump circular references (#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
komuw authored Feb 22, 2024
1 parent e6f0793 commit f213215
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 2 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Most recent version is listed first.


# v0.0.17
- Add ability to dump items with circular references: https://github.com/komuw/kama/pull/66

# v0.0.16
- Update Go version: https://github.com/komuw/kama/pull/62
- Add ability to dump private struct fields via config option: https://github.com/komuw/kama/pull/64
Expand Down
6 changes: 6 additions & 0 deletions dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ func dump(val reflect.Value, hideZeroValues bool, indentLevel int) string {
if v.IsValid() {
if v.Type().Kind() == reflect.Struct {
fromPtr := true
// TODO: should we pass in `val`, itself instead of `v`
// that way `val.Pointer()` would happen inside `dumpStruct`
return dumpStruct(v, fromPtr, hideZeroValues, indentLevel)
} else {
return dumpNonStructPointer(v, hideZeroValues, indentLevel)
Expand Down Expand Up @@ -179,6 +181,10 @@ func dumpStruct(v reflect.Value, fromPtr, hideZeroValues bool, indentLevel int)
typeName = "&" + typeName
}

if indentLevel > cfg.MaxIndentLevel {
return fmt.Sprintf("%v: kama warning(indentation `%d` exceeds max of `%d`. Possible circular reference)", typeName, indentLevel, cfg.MaxIndentLevel)
}

sep := "\n"
fieldNameSep := strings.Repeat(" ", indentLevel)
lastBracketSep := strings.Repeat(" ", indentLevel-1)
Expand Down
19 changes: 17 additions & 2 deletions kama.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@ import (
)

var (
cfg = Config{MaxLength: 14} //nolint:gochecknoglobals
onceCfg = &sync.Once{} //nolint:gochecknoglobals
cfg = Config{ //nolint:gochecknoglobals
MaxLength: 14,
ShowPrivateFields: false,
MaxIndentLevel: 10,
}

onceCfg = &sync.Once{} //nolint:gochecknoglobals
)

// Config controls how printing is going to be done.
Expand All @@ -28,6 +33,9 @@ type Config struct {
MaxLength int
// ShowPrivateFields dictates whether private struct fields will be dumped.
ShowPrivateFields bool
// MaxIndentLevel is the maximum level of indentation/recursiveness to dump to.
// This is especially important to set if the thing you are dumping has circular references.
MaxIndentLevel int
}

// Dirp prints (to stdout) exported information of types, variables, packages, modules, imports
Expand Down Expand Up @@ -59,6 +67,13 @@ func Dir(i interface{}, c ...Config) string {
// https://github.com/golang/go/issues/38673#issuecomment-643885108
cfg.MaxLength = 10_000
}

if cfg.MaxIndentLevel < 1 {
cfg.MaxIndentLevel = 10 // ie, the default
}
if cfg.MaxIndentLevel > 100 {
cfg.MaxIndentLevel = 100
}
})
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

[
NAME: github.com/komuw/kama.Client
KIND: struct
SIGNATURE: [*kama.Client kama.Client]
FIELDS: [
Public string
]
METHODS: []
SNIPPET: &Client{
Public: "PublicName",
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

[
NAME: github.com/komuw/kama.Client
KIND: struct
SIGNATURE: [*kama.Client kama.Client]
FIELDS: [
Public string
]
METHODS: []
SNIPPET: &Client{
Public: "PublicName",
srv: srvRef{
cli: &Client{
Public: "NewPubName",
srv: srvRef{},
},
},
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

[
NAME: github.com/komuw/kama.Client
KIND: struct
SIGNATURE: [*kama.Client kama.Client]
FIELDS: [
Public string
]
METHODS: []
SNIPPET: &Client{
Public: "PublicName",
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

[
NAME: github.com/komuw/kama.Client
KIND: struct
SIGNATURE: [*kama.Client kama.Client]
FIELDS: [
Public string
]
METHODS: []
SNIPPET: &Client{
Public: "PublicName",
srv: srvRef{
cli: &Client{
Public: "PublicName",
srv: srvRef{
cli: &Client{
Public: "PublicName",
srv: srvRef{
cli: &Client{
Public: "PublicName",
srv: srvRef{
cli: &Client{
Public: "PublicName",
srv: srvRef{
cli: &Client: kama warning(indentation `11` exceeds max of `10`. Possible circular reference),
},
},
},
},
},
},
},
},
},
}
]
86 changes: 86 additions & 0 deletions vars_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -560,3 +560,89 @@ func TestPublicPrivate(t *testing.T) {
})
}
}

type Client struct {
Public string
srv srvRef
}

type srvRef struct {
cli *Client
}

func TestCircularRef(t *testing.T) {
// t.Parallel() // This cannot be ran in Parallel since it mutates a global var.

oldCfg := cfg

{ // Set the new config and schedule to return old config.
onceCfg = &sync.Once{}
t.Cleanup(func() {
cfg = oldCfg
})
}

for _, name := range []string{"ShowPrivateFields", "DoNotShowPrivateFields"} {
name := name
tName := fmt.Sprintf("TestCircularRef-with-cirlce-%s", name)

x := &Client{Public: "PublicName"}
x.srv.cli = x // circular

t.Run(tName, func(t *testing.T) {
// t.Parallel() // This cannot be ran in Parallel since it mutates a global var.

{ // Set the new config and schedule to return old config.
onceCfg = &sync.Once{}
t.Cleanup(func() {
cfg = oldCfg
})
}

var res string
switch name {
default:
t.Fatalf("option `%s` is not expected", name)
case "ShowPrivateFields":
res = Dir(x, Config{ShowPrivateFields: true})
case "DoNotShowPrivateFields":
res = Dir(x, Config{ShowPrivateFields: false})
}

path := getDataPath(t, "vars_test.go", tName)
dealWithTestData(t, path, res)
})
}

for _, name := range []string{"ShowPrivateFields", "DoNotShowPrivateFields"} {
name := name
tName := fmt.Sprintf("TestCircularRef-with-NO-cirlce-%s", name)

x := &Client{Public: "PublicName"}
x.srv.cli = &Client{Public: "NewPubName"} // no circle

t.Run(tName, func(t *testing.T) {
// t.Parallel() // This cannot be ran in Parallel since it mutates a global var.

{ // Set the new config and schedule to return old config.
onceCfg = &sync.Once{}
t.Cleanup(func() {
cfg = oldCfg
})
}

var res string
switch name {
default:
t.Fatalf("option `%s` is not expected", name)
case "ShowPrivateFields":
res = Dir(x, Config{ShowPrivateFields: true})
case "DoNotShowPrivateFields":
res = Dir(x, Config{ShowPrivateFields: false})
}

path := getDataPath(t, "vars_test.go", tName)
dealWithTestData(t, path, res)
})
}
}

0 comments on commit f213215

Please sign in to comment.