Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

issues/65: Dump circular references #66

Merged
merged 10 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
})
}
}
Loading