| // Copyright 2013 Google Inc. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package pretty |
| |
| import ( |
| "fmt" |
| "net" |
| "reflect" |
| "testing" |
| "time" |
| ) |
| |
| func TestVal2nodeDefault(t *testing.T) { |
| err := fmt.Errorf("err") |
| var errNil error |
| |
| tests := []struct { |
| desc string |
| raw interface{} |
| want node |
| }{ |
| { |
| desc: "nil", |
| raw: nil, |
| want: rawVal("nil"), |
| }, |
| { |
| desc: "nil ptr", |
| raw: (*int)(nil), |
| want: rawVal("nil"), |
| }, |
| { |
| desc: "nil slice", |
| raw: []string(nil), |
| want: list{}, |
| }, |
| { |
| desc: "nil map", |
| raw: map[string]string(nil), |
| want: keyvals{}, |
| }, |
| { |
| desc: "string", |
| raw: "zaphod", |
| want: stringVal("zaphod"), |
| }, |
| { |
| desc: "slice", |
| raw: []string{"a", "b"}, |
| want: list{stringVal("a"), stringVal("b")}, |
| }, |
| { |
| desc: "map", |
| raw: map[string]string{ |
| "zaphod": "beeblebrox", |
| "ford": "prefect", |
| }, |
| want: keyvals{ |
| {"ford", stringVal("prefect")}, |
| {"zaphod", stringVal("beeblebrox")}, |
| }, |
| }, |
| { |
| desc: "map of [2]int", |
| raw: map[[2]int]string{ |
| [2]int{-1, 2}: "school", |
| [2]int{0, 0}: "origin", |
| [2]int{1, 3}: "home", |
| }, |
| want: keyvals{ |
| {"[-1,2]", stringVal("school")}, |
| {"[0,0]", stringVal("origin")}, |
| {"[1,3]", stringVal("home")}, |
| }, |
| }, |
| { |
| desc: "struct", |
| raw: struct{ Zaphod, Ford string }{"beeblebrox", "prefect"}, |
| want: keyvals{ |
| {"Zaphod", stringVal("beeblebrox")}, |
| {"Ford", stringVal("prefect")}, |
| }, |
| }, |
| { |
| desc: "int", |
| raw: 3, |
| want: rawVal("3"), |
| }, |
| { |
| desc: "time.Time", |
| raw: time.Unix(1257894000, 0).UTC(), |
| want: rawVal("2009-11-10 23:00:00 +0000 UTC"), |
| }, |
| { |
| desc: "net.IP", |
| raw: net.IPv4(127, 0, 0, 1), |
| want: rawVal("127.0.0.1"), |
| }, |
| { |
| desc: "error", |
| raw: &err, |
| want: rawVal("err"), |
| }, |
| { |
| desc: "nil error", |
| raw: &errNil, |
| want: rawVal("<nil>"), |
| }, |
| } |
| |
| for _, test := range tests { |
| ref := &reflector{ |
| Config: DefaultConfig, |
| } |
| if got, want := ref.val2node(reflect.ValueOf(test.raw)), test.want; !reflect.DeepEqual(got, want) { |
| t.Errorf("%s: got %#v, want %#v", test.desc, got, want) |
| } |
| } |
| } |
| |
| func TestVal2node(t *testing.T) { |
| tests := []struct { |
| desc string |
| raw interface{} |
| cfg *Config |
| want node |
| }{ |
| { |
| desc: "struct default", |
| raw: struct{ Zaphod, Ford, foo string }{"beeblebrox", "prefect", "BAD"}, |
| cfg: DefaultConfig, |
| want: keyvals{ |
| {"Zaphod", stringVal("beeblebrox")}, |
| {"Ford", stringVal("prefect")}, |
| }, |
| }, |
| { |
| desc: "struct w/ IncludeUnexported", |
| raw: struct{ Zaphod, Ford, foo string }{"beeblebrox", "prefect", "GOOD"}, |
| cfg: &Config{ |
| IncludeUnexported: true, |
| }, |
| want: keyvals{ |
| {"Zaphod", stringVal("beeblebrox")}, |
| {"Ford", stringVal("prefect")}, |
| {"foo", stringVal("GOOD")}, |
| }, |
| }, |
| { |
| desc: "time default", |
| raw: struct{ Date time.Time }{time.Unix(1234567890, 0).UTC()}, |
| cfg: DefaultConfig, |
| want: keyvals{ |
| {"Date", rawVal("2009-02-13 23:31:30 +0000 UTC")}, |
| }, |
| }, |
| { |
| desc: "time w/ nil Formatter", |
| raw: struct{ Date time.Time }{time.Unix(1234567890, 0).UTC()}, |
| cfg: &Config{ |
| PrintStringers: true, |
| Formatter: map[reflect.Type]interface{}{ |
| reflect.TypeOf(time.Time{}): nil, |
| }, |
| }, |
| want: keyvals{ |
| {"Date", keyvals{}}, |
| }, |
| }, |
| { |
| desc: "time w/ PrintTextMarshalers", |
| raw: struct{ Date time.Time }{time.Unix(1234567890, 0).UTC()}, |
| cfg: &Config{ |
| PrintTextMarshalers: true, |
| }, |
| want: keyvals{ |
| {"Date", stringVal("2009-02-13T23:31:30Z")}, |
| }, |
| }, |
| { |
| desc: "time w/ PrintStringers", |
| raw: struct{ Date time.Time }{time.Unix(1234567890, 0).UTC()}, |
| cfg: &Config{ |
| PrintStringers: true, |
| }, |
| want: keyvals{ |
| {"Date", stringVal("2009-02-13 23:31:30 +0000 UTC")}, |
| }, |
| }, |
| { |
| desc: "circular list", |
| raw: circular(3), |
| cfg: CycleTracker, |
| want: target{1, keyvals{ |
| {"Value", rawVal("1")}, |
| {"Next", keyvals{ |
| {"Value", rawVal("2")}, |
| {"Next", keyvals{ |
| {"Value", rawVal("3")}, |
| {"Next", ref{1}}, |
| }}, |
| }}, |
| }}, |
| }, |
| { |
| desc: "self referential maps", |
| raw: selfRef(), |
| cfg: CycleTracker, |
| want: target{1, keyvals{ |
| {"ID", rawVal("1")}, |
| {"Child", keyvals{ |
| {"2", target{2, keyvals{ |
| {"ID", rawVal("2")}, |
| {"Child", keyvals{ |
| {"3", target{3, keyvals{ |
| {"ID", rawVal("3")}, |
| {"Child", keyvals{ |
| {"1", ref{1}}, |
| {"2", ref{2}}, |
| {"3", ref{3}}, |
| }}, |
| }}}, |
| }}, |
| }}}, |
| }}, |
| }}, |
| }, |
| { |
| desc: "maps of cycles", |
| raw: map[string]*ListNode{ |
| "1. one": circular(1), |
| "2. two": circular(2), |
| "3. three": circular(1), |
| }, |
| cfg: CycleTracker, |
| want: keyvals{ |
| {"1. one", target{1, keyvals{ |
| {"Value", rawVal("1")}, |
| {"Next", ref{1}}, |
| }}}, |
| {"2. two", target{2, keyvals{ |
| {"Value", rawVal("1")}, |
| {"Next", keyvals{ |
| {"Value", rawVal("2")}, |
| {"Next", ref{2}}, |
| }}, |
| }}}, |
| {"3. three", target{3, keyvals{ |
| {"Value", rawVal("1")}, |
| {"Next", ref{3}}, |
| }}}, |
| }, |
| }, |
| } |
| |
| for _, test := range tests { |
| ref := &reflector{ |
| Config: test.cfg, |
| } |
| if test.cfg.TrackCycles { |
| ref.pointerTracker = new(pointerTracker) |
| } |
| if got, want := ref.val2node(reflect.ValueOf(test.raw)), test.want; !reflect.DeepEqual(got, want) { |
| t.Run(test.desc, func(t *testing.T) { |
| t.Errorf(" got %#v", got) |
| t.Errorf("want %#v", want) |
| t.Errorf("Diff: (-got +want)\n%s", Compare(got, want)) |
| }) |
| } |
| } |
| } |
| |
| type ListNode struct { |
| Value int |
| Next *ListNode |
| } |
| |
| func circular(nodes int) *ListNode { |
| final := &ListNode{ |
| Value: nodes, |
| } |
| final.Next = final |
| |
| recent := final |
| for i := nodes - 1; i > 0; i-- { |
| n := &ListNode{ |
| Value: i, |
| Next: recent, |
| } |
| final.Next = n |
| recent = n |
| } |
| return recent |
| } |
| |
| type SelfReferential struct { |
| ID int |
| Child map[int]*SelfReferential |
| } |
| |
| func selfRef() *SelfReferential { |
| sr1 := &SelfReferential{ |
| ID: 1, |
| Child: make(map[int]*SelfReferential), |
| } |
| sr2 := &SelfReferential{ |
| ID: 2, |
| Child: make(map[int]*SelfReferential), |
| } |
| sr3 := &SelfReferential{ |
| ID: 3, |
| Child: make(map[int]*SelfReferential), |
| } |
| |
| // Build a cycle |
| sr1.Child[2] = sr2 |
| sr2.Child[3] = sr3 |
| sr3.Child[1] = sr1 |
| |
| // Throw in some other stuff for funzies |
| sr3.Child[2] = sr2 |
| sr3.Child[3] = sr3 |
| return sr1 |
| } |
| |
| func BenchmarkVal2node(b *testing.B) { |
| benchmarks := []struct { |
| desc string |
| cfg *Config |
| raw interface{} |
| }{ |
| { |
| desc: "struct", |
| cfg: DefaultConfig, |
| raw: struct{ Zaphod, Ford string }{"beeblebrox", "prefect"}, |
| }, |
| { |
| desc: "map", |
| cfg: DefaultConfig, |
| raw: map[[2]int]string{ |
| [2]int{-1, 2}: "school", |
| [2]int{0, 0}: "origin", |
| [2]int{1, 3}: "home", |
| }, |
| }, |
| { |
| desc: "track/struct", |
| cfg: CycleTracker, |
| raw: struct{ Zaphod, Ford string }{"beeblebrox", "prefect"}, |
| }, |
| { |
| desc: "track/map", |
| cfg: CycleTracker, |
| raw: map[[2]int]string{ |
| [2]int{-1, 2}: "school", |
| [2]int{0, 0}: "origin", |
| [2]int{1, 3}: "home", |
| }, |
| }, |
| { |
| desc: "circlist/small", |
| cfg: CycleTracker, |
| raw: circular(3), |
| }, |
| { |
| desc: "circlist/med", |
| cfg: CycleTracker, |
| raw: circular(300), |
| }, |
| { |
| desc: "circlist/large", |
| cfg: CycleTracker, |
| raw: circular(3000), |
| }, |
| { |
| desc: "mapofcirc/small", |
| cfg: CycleTracker, |
| raw: map[string]*ListNode{ |
| "1. one": circular(1), |
| "2. two": circular(2), |
| "3. three": circular(1), |
| }, |
| }, |
| { |
| desc: "selfrefmap/small", |
| cfg: CycleTracker, |
| raw: selfRef, |
| }, |
| } |
| |
| for _, bench := range benchmarks { |
| b.Run(bench.desc, func(b *testing.B) { |
| b.ReportAllocs() |
| for i := 0; i < b.N; i++ { |
| ref := &reflector{ |
| Config: bench.cfg, |
| } |
| if bench.cfg.TrackCycles { |
| ref.pointerTracker = new(pointerTracker) |
| } |
| ref.val2node(reflect.ValueOf(bench.raw)) |
| } |
| }) |
| } |
| } |