From f2132154406e3f2e699e10c1635fb9196f2a9c42 Mon Sep 17 00:00:00 2001 From: Komu Wairagu Date: Thu, 22 Feb 2024 15:29:41 +0300 Subject: [PATCH] issues/65: Dump circular references (#66) - Fixes: https://github.com/komuw/kama/issues/65 - See: https://github.com/sanity-io/litter/blob/v1.5.5/README.md#circular-references --- CHANGELOG.md | 4 + dump.go | 6 ++ kama.go | 19 +++- ...-with-NO-cirlce-DoNotShowPrivateFields.txt | 13 +++ ...arRef-with-NO-cirlce-ShowPrivateFields.txt | 19 ++++ ...Ref-with-cirlce-DoNotShowPrivateFields.txt | 13 +++ ...cularRef-with-cirlce-ShowPrivateFields.txt | 36 ++++++++ vars_test.go | 86 +++++++++++++++++++ 8 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 testdata/vars_test/TestCircularRef-with-NO-cirlce-DoNotShowPrivateFields.txt create mode 100644 testdata/vars_test/TestCircularRef-with-NO-cirlce-ShowPrivateFields.txt create mode 100644 testdata/vars_test/TestCircularRef-with-cirlce-DoNotShowPrivateFields.txt create mode 100644 testdata/vars_test/TestCircularRef-with-cirlce-ShowPrivateFields.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 96e1033..f4c14d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/dump.go b/dump.go index da41548..a2f72ae 100644 --- a/dump.go +++ b/dump.go @@ -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) @@ -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) diff --git a/kama.go b/kama.go index 8bfe238..37e52de 100644 --- a/kama.go +++ b/kama.go @@ -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. @@ -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 @@ -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 + } }) } diff --git a/testdata/vars_test/TestCircularRef-with-NO-cirlce-DoNotShowPrivateFields.txt b/testdata/vars_test/TestCircularRef-with-NO-cirlce-DoNotShowPrivateFields.txt new file mode 100644 index 0000000..0d60341 --- /dev/null +++ b/testdata/vars_test/TestCircularRef-with-NO-cirlce-DoNotShowPrivateFields.txt @@ -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", +} +] diff --git a/testdata/vars_test/TestCircularRef-with-NO-cirlce-ShowPrivateFields.txt b/testdata/vars_test/TestCircularRef-with-NO-cirlce-ShowPrivateFields.txt new file mode 100644 index 0000000..8782d76 --- /dev/null +++ b/testdata/vars_test/TestCircularRef-with-NO-cirlce-ShowPrivateFields.txt @@ -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{}, + }, + }, +} +] diff --git a/testdata/vars_test/TestCircularRef-with-cirlce-DoNotShowPrivateFields.txt b/testdata/vars_test/TestCircularRef-with-cirlce-DoNotShowPrivateFields.txt new file mode 100644 index 0000000..0d60341 --- /dev/null +++ b/testdata/vars_test/TestCircularRef-with-cirlce-DoNotShowPrivateFields.txt @@ -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", +} +] diff --git a/testdata/vars_test/TestCircularRef-with-cirlce-ShowPrivateFields.txt b/testdata/vars_test/TestCircularRef-with-cirlce-ShowPrivateFields.txt new file mode 100644 index 0000000..0e59198 --- /dev/null +++ b/testdata/vars_test/TestCircularRef-with-cirlce-ShowPrivateFields.txt @@ -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), + }, + }, + }, + }, + }, + }, + }, + }, + }, +} +] diff --git a/vars_test.go b/vars_test.go index b9aa707..3366584 100644 --- a/vars_test.go +++ b/vars_test.go @@ -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) + }) + } +}