├── .travis.yml ├── LICENSE ├── PRESUBMIT.py ├── README.md ├── WATCHLISTS ├── pre-commit-go.yml └── render ├── render.go └── render_test.go /.travis.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 The Chromium Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style license that can be 3 | # found in the LICENSE file. 4 | 5 | # {sudo: required, dist: trusty} is the magic incantation to pick the trusty 6 | # beta environment, which is the only environment we can get that has >4GB 7 | # memory. Currently the `go test -race` tests that we run will peak at just 8 | # over 4GB, which results in everything getting OOM-killed. 9 | sudo: required 10 | dist: trusty 11 | 12 | language: go 13 | 14 | go: 15 | - 1.4.2 16 | 17 | before_install: 18 | - go get github.com/maruel/pre-commit-go/cmd/pcg 19 | 20 | script: 21 | - pcg 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 The Chromium Authors. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are 5 | // met: 6 | // 7 | // * Redistributions of source code must retain the above copyright 8 | // notice, this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above 10 | // copyright notice, this list of conditions and the following disclaimer 11 | // in the documentation and/or other materials provided with the 12 | // distribution. 13 | // * Neither the name of Google Inc. nor the names of its 14 | // contributors may be used to endorse or promote products derived from 15 | // this software without specific prior written permission. 16 | // 17 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /PRESUBMIT.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 The Chromium Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style license that can be 3 | # found in the LICENSE file. 4 | 5 | """Top-level presubmit script. 6 | 7 | See https://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for 8 | details on the presubmit API built into depot_tools. 9 | """ 10 | 11 | import os 12 | import sys 13 | 14 | 15 | def PreCommitGo(input_api, output_api, pcg_mode): 16 | """Run go-specific checks via pre-commit-go (pcg) if it's in PATH.""" 17 | if input_api.is_committing: 18 | error_type = output_api.PresubmitError 19 | else: 20 | error_type = output_api.PresubmitPromptWarning 21 | 22 | exe = 'pcg.exe' if sys.platform == 'win32' else 'pcg' 23 | pcg = None 24 | for p in os.environ['PATH'].split(os.pathsep): 25 | pcg = os.path.join(p, exe) 26 | if os.access(pcg, os.X_OK): 27 | break 28 | else: 29 | return [ 30 | error_type( 31 | 'pre-commit-go executable (pcg) could not be found in PATH. All Go ' 32 | 'checks are skipped. See https://github.com/maruel/pre-commit-go.') 33 | ] 34 | 35 | cmd = [pcg, 'run', '-m', ','.join(pcg_mode)] 36 | if input_api.verbose: 37 | cmd.append('-v') 38 | # pcg can figure out what files to check on its own based on upstream ref, 39 | # but on PRESUBMIT try builder upsteram isn't set, and it's just 1 commit. 40 | if os.getenv('PRESUBMIT_BUILDER', ''): 41 | cmd.extend(['-r', 'HEAD~1']) 42 | return input_api.RunTests([ 43 | input_api.Command( 44 | name='pre-commit-go: %s' % ', '.join(pcg_mode), 45 | cmd=cmd, 46 | kwargs={}, 47 | message=error_type), 48 | ]) 49 | 50 | 51 | def header(input_api): 52 | """Returns the expected license header regexp for this project.""" 53 | current_year = int(input_api.time.strftime('%Y')) 54 | allowed_years = (str(s) for s in reversed(xrange(2011, current_year + 1))) 55 | years_re = '(' + '|'.join(allowed_years) + ')' 56 | license_header = ( 57 | r'.*? Copyright %(year)s The Chromium Authors\. ' 58 | r'All rights reserved\.\n' 59 | r'.*? Use of this source code is governed by a BSD-style license ' 60 | r'that can be\n' 61 | r'.*? found in the LICENSE file\.(?: \*/)?\n' 62 | ) % { 63 | 'year': years_re, 64 | } 65 | return license_header 66 | 67 | 68 | def source_file_filter(input_api): 69 | """Returns filter that selects source code files only.""" 70 | bl = list(input_api.DEFAULT_BLACK_LIST) + [ 71 | r'.+\.pb\.go$', 72 | r'.+_string\.go$', 73 | ] 74 | wl = list(input_api.DEFAULT_WHITE_LIST) + [ 75 | r'.+\.go$', 76 | ] 77 | return lambda x: input_api.FilterSourceFile(x, white_list=wl, black_list=bl) 78 | 79 | 80 | def CommonChecks(input_api, output_api): 81 | results = [] 82 | results.extend( 83 | input_api.canned_checks.CheckChangeHasNoStrayWhitespace( 84 | input_api, output_api, 85 | source_file_filter=source_file_filter(input_api))) 86 | results.extend( 87 | input_api.canned_checks.CheckLicense( 88 | input_api, output_api, header(input_api), 89 | source_file_filter=source_file_filter(input_api))) 90 | return results 91 | 92 | 93 | def CheckChangeOnUpload(input_api, output_api): 94 | results = CommonChecks(input_api, output_api) 95 | results.extend(PreCommitGo(input_api, output_api, ['lint', 'pre-commit'])) 96 | return results 97 | 98 | 99 | def CheckChangeOnCommit(input_api, output_api): 100 | results = CommonChecks(input_api, output_api) 101 | results.extend(input_api.canned_checks.CheckChangeHasDescription( 102 | input_api, output_api)) 103 | results.extend(input_api.canned_checks.CheckDoNotSubmitInDescription( 104 | input_api, output_api)) 105 | results.extend(input_api.canned_checks.CheckDoNotSubmitInFiles( 106 | input_api, output_api)) 107 | results.extend(PreCommitGo( 108 | input_api, output_api, ['continuous-integration'])) 109 | return results 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go-render: A verbose recursive Go type-to-string conversion library. 2 | ==================================================================== 3 | 4 | [![GoDoc](https://godoc.org/github.com/luci/go-render?status.svg)](https://godoc.org/github.com/luci/go-render) 5 | [![Build Status](https://travis-ci.org/luci/go-render.svg)](https://travis-ci.org/luci/go-render) 6 | 7 | This is not an official Google product. 8 | 9 | ## Overview 10 | 11 | The *render* package implements a more verbose form of the standard Go string 12 | formatter, `fmt.Sprintf("%#v", value)`, adding: 13 | - Pointer recursion. Normally, Go stops at the first pointer and prints its 14 | address. The *render* package will recurse and continue to render pointer 15 | values. 16 | - Recursion loop detection. Recursion is nice, but if a recursion path detects 17 | a loop, *render* will note this and move on. 18 | - Custom type name rendering. 19 | - Deterministic key sorting for `string`- and `int`-keyed maps. 20 | - Testing! 21 | 22 | Call `render.Render` and pass it an `interface{}`. 23 | 24 | For example: 25 | 26 | ```Go 27 | type customType int 28 | type testStruct struct { 29 | S string 30 | V *map[string]int 31 | I interface{} 32 | } 33 | 34 | a := testStruct{ 35 | S: "hello", 36 | V: &map[string]int{"foo": 0, "bar": 1}, 37 | I: customType(42), 38 | } 39 | 40 | fmt.Println("Render test:") 41 | fmt.Printf("fmt.Printf: %#v\n", a))) 42 | fmt.Printf("render.Render: %s\n", Render(a)) 43 | ``` 44 | 45 | Yields: 46 | ``` 47 | fmt.Printf: render.testStruct{S:"hello", V:(*map[string]int)(0x600dd065), I:42} 48 | render.Render: render.testStruct{S:"hello", V:(*map[string]int){"bar":1, "foo":0}, I:render.customType(42)} 49 | ``` 50 | 51 | This is not intended to be a high-performance library, but it's not terrible 52 | either. 53 | 54 | Contributing 55 | ------------ 56 | 57 | * Sign the [Google CLA](https://cla.developers.google.com/clas). 58 | * Make sure your `user.email` and `user.name` are configured in `git config`. 59 | * Install the [pcg](https://github.com/maruel/pre-commit-go) git hook: 60 | `go get -u github.com/maruel/pre-commit-go/cmd/... && pcg` 61 | 62 | Run the following to setup the code review tool and create your first review: 63 | 64 | git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git $HOME/src/depot_tools 65 | export PATH="$PATH:$HOME/src/depot_tools" 66 | cd $GOROOT/github.com/luci/go-render 67 | git checkout -b work origin/master 68 | 69 | # hack hack 70 | 71 | git commit -a -m "This is awesome\nR=joe@example.com" 72 | # This will ask for your Google Account credentials. 73 | git cl upload -s 74 | # Wait for LGTM over email. 75 | # Check the commit queue box in codereview website. 76 | # Wait for the change to be tested and landed automatically. 77 | 78 | Use `git cl help` and `git cl help ` for more details. 79 | -------------------------------------------------------------------------------- /WATCHLISTS: -------------------------------------------------------------------------------- 1 | # Copyright 2015 The Chromium Authors. All rights reserved. 2 | # Use of this source code is governed by a BSD-style license that can be 3 | # found in the LICENSE file. 4 | 5 | # Watchlist Rules 6 | # Refer: http://dev.chromium.org/developers/contributing-code/watchlists 7 | 8 | { 9 | 10 | 'WATCHLIST_DEFINITIONS': { 11 | 'all': { 12 | 'filepath': '.+', 13 | }, 14 | }, 15 | 16 | 'WATCHLISTS': { 17 | 'all': [ 18 | # Add yourself here to get explicitly spammed. 19 | 'maruel@chromium.org', 20 | 'tandrii+luci-go@chromium.org', 21 | 'todd@cloudera.com', 22 | 'andrew.wang@cloudera.com', 23 | ], 24 | }, 25 | 26 | } 27 | -------------------------------------------------------------------------------- /pre-commit-go.yml: -------------------------------------------------------------------------------- 1 | # https://github.com/maruel/pre-commit-go configuration file to run checks 2 | # automatically on commit, on push and on continuous integration service after 3 | # a push or on merge of a pull request. 4 | # 5 | # See https://godoc.org/github.com/maruel/pre-commit-go/checks for more 6 | # information. 7 | 8 | min_version: 0.4.7 9 | modes: 10 | continuous-integration: 11 | checks: 12 | build: 13 | - build_all: false 14 | extra_args: [] 15 | coverage: 16 | - use_global_inference: false 17 | use_coveralls: true 18 | global: 19 | min_coverage: 50 20 | max_coverage: 100 21 | per_dir_default: 22 | min_coverage: 1 23 | max_coverage: 100 24 | per_dir: {} 25 | gofmt: 26 | - {} 27 | goimports: 28 | - {} 29 | test: 30 | - extra_args: 31 | - -v 32 | - -race 33 | max_duration: 600 34 | lint: 35 | checks: 36 | golint: 37 | - blacklist: [] 38 | govet: 39 | - blacklist: 40 | - ' composite literal uses unkeyed fields' 41 | max_duration: 15 42 | pre-commit: 43 | checks: 44 | build: 45 | - build_all: false 46 | extra_args: [] 47 | gofmt: 48 | - {} 49 | test: 50 | - extra_args: 51 | - -short 52 | max_duration: 35 53 | pre-push: 54 | checks: 55 | coverage: 56 | - use_global_inference: false 57 | use_coveralls: false 58 | global: 59 | min_coverage: 50 60 | max_coverage: 100 61 | per_dir_default: 62 | min_coverage: 1 63 | max_coverage: 100 64 | per_dir: {} 65 | goimports: 66 | - {} 67 | test: 68 | - extra_args: 69 | - -v 70 | - -race 71 | max_duration: 35 72 | 73 | ignore_patterns: 74 | - .* 75 | - _* 76 | - '*.pb.go' 77 | - '*_string.go' 78 | - '*-gen.go' 79 | -------------------------------------------------------------------------------- /render/render.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | package render 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "reflect" 11 | "sort" 12 | "strconv" 13 | ) 14 | 15 | var builtinTypeMap = map[reflect.Kind]string{ 16 | reflect.Bool: "bool", 17 | reflect.Complex128: "complex128", 18 | reflect.Complex64: "complex64", 19 | reflect.Float32: "float32", 20 | reflect.Float64: "float64", 21 | reflect.Int16: "int16", 22 | reflect.Int32: "int32", 23 | reflect.Int64: "int64", 24 | reflect.Int8: "int8", 25 | reflect.Int: "int", 26 | reflect.String: "string", 27 | reflect.Uint16: "uint16", 28 | reflect.Uint32: "uint32", 29 | reflect.Uint64: "uint64", 30 | reflect.Uint8: "uint8", 31 | reflect.Uint: "uint", 32 | reflect.Uintptr: "uintptr", 33 | } 34 | 35 | var builtinTypeSet = map[string]struct{}{} 36 | 37 | func init() { 38 | for _, v := range builtinTypeMap { 39 | builtinTypeSet[v] = struct{}{} 40 | } 41 | } 42 | 43 | var typeOfString = reflect.TypeOf("") 44 | var typeOfInt = reflect.TypeOf(int(1)) 45 | var typeOfUint = reflect.TypeOf(uint(1)) 46 | var typeOfFloat = reflect.TypeOf(10.1) 47 | 48 | // Render converts a structure to a string representation. Unline the "%#v" 49 | // format string, this resolves pointer types' contents in structs, maps, and 50 | // slices/arrays and prints their field values. 51 | func Render(v interface{}) string { 52 | buf := bytes.Buffer{} 53 | s := (*traverseState)(nil) 54 | s.render(&buf, 0, reflect.ValueOf(v), false) 55 | return buf.String() 56 | } 57 | 58 | // renderPointer is called to render a pointer value. 59 | // 60 | // This is overridable so that the test suite can have deterministic pointer 61 | // values in its expectations. 62 | var renderPointer = func(buf *bytes.Buffer, p uintptr) { 63 | fmt.Fprintf(buf, "0x%016x", p) 64 | } 65 | 66 | // traverseState is used to note and avoid recursion as struct members are being 67 | // traversed. 68 | // 69 | // traverseState is allowed to be nil. Specifically, the root state is nil. 70 | type traverseState struct { 71 | parent *traverseState 72 | ptr uintptr 73 | } 74 | 75 | func (s *traverseState) forkFor(ptr uintptr) *traverseState { 76 | for cur := s; cur != nil; cur = cur.parent { 77 | if ptr == cur.ptr { 78 | return nil 79 | } 80 | } 81 | 82 | fs := &traverseState{ 83 | parent: s, 84 | ptr: ptr, 85 | } 86 | return fs 87 | } 88 | 89 | func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value, implicit bool) { 90 | if v.Kind() == reflect.Invalid { 91 | buf.WriteString("nil") 92 | return 93 | } 94 | vt := v.Type() 95 | 96 | // If the type being rendered is a potentially recursive type (a type that 97 | // can contain itself as a member), we need to avoid recursion. 98 | // 99 | // If we've already seen this type before, mark that this is the case and 100 | // write a recursion placeholder instead of actually rendering it. 101 | // 102 | // If we haven't seen it before, fork our `seen` tracking so any higher-up 103 | // renderers will also render it at least once, then mark that we've seen it 104 | // to avoid recursing on lower layers. 105 | pe := uintptr(0) 106 | vk := vt.Kind() 107 | switch vk { 108 | case reflect.Ptr: 109 | // Since structs and arrays aren't pointers, they can't directly be 110 | // recursed, but they can contain pointers to themselves. Record their 111 | // pointer to avoid this. 112 | switch v.Elem().Kind() { 113 | case reflect.Struct, reflect.Array: 114 | pe = v.Pointer() 115 | } 116 | 117 | case reflect.Slice, reflect.Map: 118 | pe = v.Pointer() 119 | } 120 | if pe != 0 { 121 | s = s.forkFor(pe) 122 | if s == nil { 123 | buf.WriteString("") 128 | return 129 | } 130 | } 131 | 132 | isAnon := func(t reflect.Type) bool { 133 | if t.Name() != "" { 134 | if _, ok := builtinTypeSet[t.Name()]; !ok { 135 | return false 136 | } 137 | } 138 | return t.Kind() != reflect.Interface 139 | } 140 | 141 | switch vk { 142 | case reflect.Struct: 143 | if !implicit { 144 | writeType(buf, ptrs, vt) 145 | } 146 | structAnon := vt.Name() == "" 147 | buf.WriteRune('{') 148 | for i := 0; i < vt.NumField(); i++ { 149 | if i > 0 { 150 | buf.WriteString(", ") 151 | } 152 | anon := structAnon && isAnon(vt.Field(i).Type) 153 | 154 | if !anon { 155 | buf.WriteString(vt.Field(i).Name) 156 | buf.WriteRune(':') 157 | } 158 | 159 | s.render(buf, 0, v.Field(i), anon) 160 | } 161 | buf.WriteRune('}') 162 | 163 | case reflect.Slice: 164 | if v.IsNil() { 165 | if !implicit { 166 | writeType(buf, ptrs, vt) 167 | buf.WriteString("(nil)") 168 | } else { 169 | buf.WriteString("nil") 170 | } 171 | return 172 | } 173 | fallthrough 174 | 175 | case reflect.Array: 176 | if !implicit { 177 | writeType(buf, ptrs, vt) 178 | } 179 | anon := vt.Name() == "" && isAnon(vt.Elem()) 180 | buf.WriteString("{") 181 | for i := 0; i < v.Len(); i++ { 182 | if i > 0 { 183 | buf.WriteString(", ") 184 | } 185 | 186 | s.render(buf, 0, v.Index(i), anon) 187 | } 188 | buf.WriteRune('}') 189 | 190 | case reflect.Map: 191 | if !implicit { 192 | writeType(buf, ptrs, vt) 193 | } 194 | if v.IsNil() { 195 | buf.WriteString("(nil)") 196 | } else { 197 | buf.WriteString("{") 198 | 199 | mkeys := v.MapKeys() 200 | tryAndSortMapKeys(vt, mkeys) 201 | 202 | kt := vt.Key() 203 | keyAnon := typeOfString.ConvertibleTo(kt) || typeOfInt.ConvertibleTo(kt) || typeOfUint.ConvertibleTo(kt) || typeOfFloat.ConvertibleTo(kt) 204 | valAnon := vt.Name() == "" && isAnon(vt.Elem()) 205 | for i, mk := range mkeys { 206 | if i > 0 { 207 | buf.WriteString(", ") 208 | } 209 | 210 | s.render(buf, 0, mk, keyAnon) 211 | buf.WriteString(":") 212 | s.render(buf, 0, v.MapIndex(mk), valAnon) 213 | } 214 | buf.WriteRune('}') 215 | } 216 | 217 | case reflect.Ptr: 218 | ptrs++ 219 | fallthrough 220 | case reflect.Interface: 221 | if v.IsNil() { 222 | writeType(buf, ptrs, v.Type()) 223 | buf.WriteString("(nil)") 224 | } else { 225 | s.render(buf, ptrs, v.Elem(), false) 226 | } 227 | 228 | case reflect.Chan, reflect.Func, reflect.UnsafePointer: 229 | writeType(buf, ptrs, vt) 230 | buf.WriteRune('(') 231 | renderPointer(buf, v.Pointer()) 232 | buf.WriteRune(')') 233 | 234 | default: 235 | tstr := vt.String() 236 | implicit = implicit || (ptrs == 0 && builtinTypeMap[vk] == tstr) 237 | if !implicit { 238 | writeType(buf, ptrs, vt) 239 | buf.WriteRune('(') 240 | } 241 | 242 | switch vk { 243 | case reflect.String: 244 | fmt.Fprintf(buf, "%q", v.String()) 245 | case reflect.Bool: 246 | fmt.Fprintf(buf, "%v", v.Bool()) 247 | 248 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 249 | fmt.Fprintf(buf, "%d", v.Int()) 250 | 251 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 252 | fmt.Fprintf(buf, "%d", v.Uint()) 253 | 254 | case reflect.Float32, reflect.Float64: 255 | fmt.Fprintf(buf, "%g", v.Float()) 256 | 257 | case reflect.Complex64, reflect.Complex128: 258 | fmt.Fprintf(buf, "%g", v.Complex()) 259 | } 260 | 261 | if !implicit { 262 | buf.WriteRune(')') 263 | } 264 | } 265 | } 266 | 267 | func writeType(buf *bytes.Buffer, ptrs int, t reflect.Type) { 268 | parens := ptrs > 0 269 | switch t.Kind() { 270 | case reflect.Chan, reflect.Func, reflect.UnsafePointer: 271 | parens = true 272 | } 273 | 274 | if parens { 275 | buf.WriteRune('(') 276 | for i := 0; i < ptrs; i++ { 277 | buf.WriteRune('*') 278 | } 279 | } 280 | 281 | switch t.Kind() { 282 | case reflect.Ptr: 283 | if ptrs == 0 { 284 | // This pointer was referenced from within writeType (e.g., as part of 285 | // rendering a list), and so hasn't had its pointer asterisk accounted 286 | // for. 287 | buf.WriteRune('*') 288 | } 289 | writeType(buf, 0, t.Elem()) 290 | 291 | case reflect.Interface: 292 | if n := t.Name(); n != "" { 293 | buf.WriteString(t.String()) 294 | } else { 295 | buf.WriteString("interface{}") 296 | } 297 | 298 | case reflect.Array: 299 | buf.WriteRune('[') 300 | buf.WriteString(strconv.FormatInt(int64(t.Len()), 10)) 301 | buf.WriteRune(']') 302 | writeType(buf, 0, t.Elem()) 303 | 304 | case reflect.Slice: 305 | if t == reflect.SliceOf(t.Elem()) { 306 | buf.WriteString("[]") 307 | writeType(buf, 0, t.Elem()) 308 | } else { 309 | // Custom slice type, use type name. 310 | buf.WriteString(t.String()) 311 | } 312 | 313 | case reflect.Map: 314 | if t == reflect.MapOf(t.Key(), t.Elem()) { 315 | buf.WriteString("map[") 316 | writeType(buf, 0, t.Key()) 317 | buf.WriteRune(']') 318 | writeType(buf, 0, t.Elem()) 319 | } else { 320 | // Custom map type, use type name. 321 | buf.WriteString(t.String()) 322 | } 323 | 324 | default: 325 | buf.WriteString(t.String()) 326 | } 327 | 328 | if parens { 329 | buf.WriteRune(')') 330 | } 331 | } 332 | 333 | type cmpFn func(a, b reflect.Value) int 334 | 335 | type sortableValueSlice struct { 336 | cmp cmpFn 337 | elements []reflect.Value 338 | } 339 | 340 | func (s sortableValueSlice) Len() int { 341 | return len(s.elements) 342 | } 343 | 344 | func (s sortableValueSlice) Less(i, j int) bool { 345 | return s.cmp(s.elements[i], s.elements[j]) < 0 346 | } 347 | 348 | func (s sortableValueSlice) Swap(i, j int) { 349 | s.elements[i], s.elements[j] = s.elements[j], s.elements[i] 350 | } 351 | 352 | // cmpForType returns a cmpFn which sorts the data for some type t in the same 353 | // order that a go-native map key is compared for equality. 354 | func cmpForType(t reflect.Type) cmpFn { 355 | switch t.Kind() { 356 | case reflect.String: 357 | return func(av, bv reflect.Value) int { 358 | a, b := av.String(), bv.String() 359 | if a < b { 360 | return -1 361 | } else if a > b { 362 | return 1 363 | } 364 | return 0 365 | } 366 | 367 | case reflect.Bool: 368 | return func(av, bv reflect.Value) int { 369 | a, b := av.Bool(), bv.Bool() 370 | if !a && b { 371 | return -1 372 | } else if a && !b { 373 | return 1 374 | } 375 | return 0 376 | } 377 | 378 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 379 | return func(av, bv reflect.Value) int { 380 | a, b := av.Int(), bv.Int() 381 | if a < b { 382 | return -1 383 | } else if a > b { 384 | return 1 385 | } 386 | return 0 387 | } 388 | 389 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, 390 | reflect.Uint64, reflect.Uintptr, reflect.UnsafePointer: 391 | return func(av, bv reflect.Value) int { 392 | a, b := av.Uint(), bv.Uint() 393 | if a < b { 394 | return -1 395 | } else if a > b { 396 | return 1 397 | } 398 | return 0 399 | } 400 | 401 | case reflect.Float32, reflect.Float64: 402 | return func(av, bv reflect.Value) int { 403 | a, b := av.Float(), bv.Float() 404 | if a < b { 405 | return -1 406 | } else if a > b { 407 | return 1 408 | } 409 | return 0 410 | } 411 | 412 | case reflect.Interface: 413 | return func(av, bv reflect.Value) int { 414 | a, b := av.InterfaceData(), bv.InterfaceData() 415 | if a[0] < b[0] { 416 | return -1 417 | } else if a[0] > b[0] { 418 | return 1 419 | } 420 | if a[1] < b[1] { 421 | return -1 422 | } else if a[1] > b[1] { 423 | return 1 424 | } 425 | return 0 426 | } 427 | 428 | case reflect.Complex64, reflect.Complex128: 429 | return func(av, bv reflect.Value) int { 430 | a, b := av.Complex(), bv.Complex() 431 | if real(a) < real(b) { 432 | return -1 433 | } else if real(a) > real(b) { 434 | return 1 435 | } 436 | if imag(a) < imag(b) { 437 | return -1 438 | } else if imag(a) > imag(b) { 439 | return 1 440 | } 441 | return 0 442 | } 443 | 444 | case reflect.Ptr, reflect.Chan: 445 | return func(av, bv reflect.Value) int { 446 | a, b := av.Pointer(), bv.Pointer() 447 | if a < b { 448 | return -1 449 | } else if a > b { 450 | return 1 451 | } 452 | return 0 453 | } 454 | 455 | case reflect.Struct: 456 | cmpLst := make([]cmpFn, t.NumField()) 457 | for i := range cmpLst { 458 | cmpLst[i] = cmpForType(t.Field(i).Type) 459 | } 460 | return func(a, b reflect.Value) int { 461 | for i, cmp := range cmpLst { 462 | if rslt := cmp(a.Field(i), b.Field(i)); rslt != 0 { 463 | return rslt 464 | } 465 | } 466 | return 0 467 | } 468 | } 469 | 470 | return nil 471 | } 472 | 473 | func tryAndSortMapKeys(mt reflect.Type, k []reflect.Value) { 474 | if cmp := cmpForType(mt.Key()); cmp != nil { 475 | sort.Sort(sortableValueSlice{cmp, k}) 476 | } 477 | } 478 | -------------------------------------------------------------------------------- /render/render_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Chromium Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be 3 | // found in the LICENSE file. 4 | 5 | package render 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "reflect" 11 | "regexp" 12 | "runtime" 13 | "testing" 14 | ) 15 | 16 | func init() { 17 | // For testing purposes, pointers will render as "PTR" so that they are 18 | // deterministic. 19 | renderPointer = func(buf *bytes.Buffer, p uintptr) { 20 | buf.WriteString("PTR") 21 | } 22 | } 23 | 24 | func assertRendersLike(t *testing.T, name string, v interface{}, exp string) { 25 | act := Render(v) 26 | if act != exp { 27 | _, _, line, _ := runtime.Caller(1) 28 | t.Errorf("On line #%d, [%s] did not match expectations:\nExpected: %s\nActual : %s\n", line, name, exp, act) 29 | } 30 | } 31 | 32 | func TestRenderList(t *testing.T) { 33 | t.Parallel() 34 | 35 | // Note that we make some of the fields exportable. This is to avoid a fun case 36 | // where the first reflect.Value has a read-only bit set, but follow-on values 37 | // do not, so recursion tests are off by one. 38 | type testStruct struct { 39 | Name string 40 | I interface{} 41 | 42 | m string 43 | } 44 | 45 | type myStringSlice []string 46 | type myStringMap map[string]string 47 | type myIntType int 48 | type myStringType string 49 | 50 | s0 := "string0" 51 | s0P := &s0 52 | mit := myIntType(42) 53 | stringer := fmt.Stringer(nil) 54 | 55 | for i, tc := range []struct { 56 | a interface{} 57 | s string 58 | }{ 59 | {nil, `nil`}, 60 | {make(chan int), `(chan int)(PTR)`}, 61 | {&stringer, `(*fmt.Stringer)(nil)`}, 62 | {123, `123`}, 63 | {"hello", `"hello"`}, 64 | {(*testStruct)(nil), `(*render.testStruct)(nil)`}, 65 | {(**testStruct)(nil), `(**render.testStruct)(nil)`}, 66 | {[]***testStruct(nil), `[]***render.testStruct(nil)`}, 67 | {testStruct{Name: "foo", I: &testStruct{Name: "baz"}}, 68 | `render.testStruct{Name:"foo", I:(*render.testStruct){Name:"baz", I:interface{}(nil), m:""}, m:""}`}, 69 | {[]byte(nil), `[]uint8(nil)`}, 70 | {[]byte{}, `[]uint8{}`}, 71 | {map[string]string(nil), `map[string]string(nil)`}, 72 | {[]*testStruct{ 73 | {Name: "foo"}, 74 | {Name: "bar"}, 75 | }, `[]*render.testStruct{(*render.testStruct){Name:"foo", I:interface{}(nil), m:""}, ` + 76 | `(*render.testStruct){Name:"bar", I:interface{}(nil), m:""}}`}, 77 | {myStringSlice{"foo", "bar"}, `render.myStringSlice{"foo", "bar"}`}, 78 | {myStringMap{"foo": "bar"}, `render.myStringMap{"foo":"bar"}`}, 79 | {myIntType(12), `render.myIntType(12)`}, 80 | {&mit, `(*render.myIntType)(42)`}, 81 | {myStringType("foo"), `render.myStringType("foo")`}, 82 | {struct { 83 | a int 84 | b string 85 | }{123, "foo"}, `struct { a int; b string }{123, "foo"}`}, 86 | {[]string{"foo", "foo", "bar", "baz", "qux", "qux"}, 87 | `[]string{"foo", "foo", "bar", "baz", "qux", "qux"}`}, 88 | {[...]int{1, 2, 3}, `[3]int{1, 2, 3}`}, 89 | {map[string]bool{ 90 | "foo": true, 91 | "bar": false, 92 | }, `map[string]bool{"bar":false, "foo":true}`}, 93 | {map[int]string{1: "foo", 2: "bar"}, `map[int]string{1:"foo", 2:"bar"}`}, 94 | {uint32(1337), `1337`}, 95 | {3.14, `3.14`}, 96 | {complex(3, 0.14), `(3+0.14i)`}, 97 | {&s0, `(*string)("string0")`}, 98 | {&s0P, `(**string)("string0")`}, 99 | {[]interface{}{nil, 1, 2, nil}, `[]interface{}{interface{}(nil), 1, 2, interface{}(nil)}`}, 100 | } { 101 | assertRendersLike(t, fmt.Sprintf("Input #%d", i), tc.a, tc.s) 102 | } 103 | } 104 | 105 | func TestRenderRecursiveStruct(t *testing.T) { 106 | type testStruct struct { 107 | Name string 108 | I interface{} 109 | } 110 | 111 | s := &testStruct{ 112 | Name: "recursive", 113 | } 114 | s.I = s 115 | 116 | assertRendersLike(t, "Recursive struct", s, 117 | `(*render.testStruct){Name:"recursive", I:}`) 118 | } 119 | 120 | func TestRenderRecursiveArray(t *testing.T) { 121 | a := [2]interface{}{} 122 | a[0] = &a 123 | a[1] = &a 124 | 125 | assertRendersLike(t, "Recursive array", &a, 126 | `(*[2]interface{}){, }`) 127 | } 128 | 129 | func TestRenderRecursiveMap(t *testing.T) { 130 | m := map[string]interface{}{} 131 | foo := "foo" 132 | m["foo"] = m 133 | m["bar"] = [](*string){&foo, &foo} 134 | v := []map[string]interface{}{m, m} 135 | 136 | assertRendersLike(t, "Recursive map", v, 137 | `[]map[string]interface{}{{`+ 138 | `"bar":[]*string{(*string)("foo"), (*string)("foo")}, `+ 139 | `"foo":}, {`+ 140 | `"bar":[]*string{(*string)("foo"), (*string)("foo")}, `+ 141 | `"foo":}}`) 142 | } 143 | 144 | func TestRenderImplicitType(t *testing.T) { 145 | type namedStruct struct{ a, b int } 146 | type namedInt int 147 | 148 | tcs := []struct { 149 | in interface{} 150 | expect string 151 | }{ 152 | { 153 | []struct{ a, b int }{{1, 2}}, 154 | "[]struct { a int; b int }{{1, 2}}", 155 | }, 156 | { 157 | map[string]struct{ a, b int }{"hi": {1, 2}}, 158 | `map[string]struct { a int; b int }{"hi":{1, 2}}`, 159 | }, 160 | { 161 | map[namedInt]struct{}{10: {}}, 162 | `map[render.namedInt]struct {}{10:{}}`, 163 | }, 164 | { 165 | struct{ a, b int }{1, 2}, 166 | `struct { a int; b int }{1, 2}`, 167 | }, 168 | { 169 | namedStruct{1, 2}, 170 | "render.namedStruct{a:1, b:2}", 171 | }, 172 | } 173 | 174 | for _, tc := range tcs { 175 | assertRendersLike(t, reflect.TypeOf(tc.in).String(), tc.in, tc.expect) 176 | } 177 | } 178 | 179 | func ExampleInReadme() { 180 | type customType int 181 | type testStruct struct { 182 | S string 183 | V *map[string]int 184 | I interface{} 185 | } 186 | 187 | a := testStruct{ 188 | S: "hello", 189 | V: &map[string]int{"foo": 0, "bar": 1}, 190 | I: customType(42), 191 | } 192 | 193 | fmt.Println("Render test:") 194 | fmt.Printf("fmt.Printf: %s\n", sanitizePointer(fmt.Sprintf("%#v", a))) 195 | fmt.Printf("render.Render: %s\n", Render(a)) 196 | // Output: Render test: 197 | // fmt.Printf: render.testStruct{S:"hello", V:(*map[string]int)(0x600dd065), I:42} 198 | // render.Render: render.testStruct{S:"hello", V:(*map[string]int){"bar":1, "foo":0}, I:render.customType(42)} 199 | } 200 | 201 | var pointerRE = regexp.MustCompile(`\(0x[a-f0-9]+\)`) 202 | 203 | func sanitizePointer(s string) string { 204 | return pointerRE.ReplaceAllString(s, "(0x600dd065)") 205 | } 206 | 207 | type chanList []chan int 208 | 209 | func (c chanList) Len() int { return len(c) } 210 | func (c chanList) Swap(i, j int) { c[i], c[j] = c[j], c[i] } 211 | func (c chanList) Less(i, j int) bool { 212 | return reflect.ValueOf(c[i]).Pointer() < reflect.ValueOf(c[j]).Pointer() 213 | } 214 | 215 | func TestMapSortRendering(t *testing.T) { 216 | type namedMapType map[int]struct{ a int } 217 | type mapKey struct{ a, b int } 218 | 219 | chans := make(chanList, 5) 220 | for i := range chans { 221 | chans[i] = make(chan int) 222 | } 223 | 224 | tcs := []struct { 225 | in interface{} 226 | expect string 227 | }{ 228 | { 229 | map[uint32]struct{}{1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {}, 8: {}}, 230 | "map[uint32]struct {}{1:{}, 2:{}, 3:{}, 4:{}, 5:{}, 6:{}, 7:{}, 8:{}}", 231 | }, 232 | { 233 | map[int8]struct{}{1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {}, 8: {}}, 234 | "map[int8]struct {}{1:{}, 2:{}, 3:{}, 4:{}, 5:{}, 6:{}, 7:{}, 8:{}}", 235 | }, 236 | { 237 | map[uintptr]struct{}{1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {}, 8: {}}, 238 | "map[uintptr]struct {}{1:{}, 2:{}, 3:{}, 4:{}, 5:{}, 6:{}, 7:{}, 8:{}}", 239 | }, 240 | { 241 | namedMapType{10: struct{ a int }{20}}, 242 | "render.namedMapType{10:struct { a int }{20}}", 243 | }, 244 | { 245 | map[mapKey]struct{}{mapKey{3, 1}: {}, mapKey{1, 3}: {}, mapKey{1, 2}: {}, mapKey{2, 1}: {}}, 246 | "map[render.mapKey]struct {}{render.mapKey{a:1, b:2}:{}, render.mapKey{a:1, b:3}:{}, render.mapKey{a:2, b:1}:{}, render.mapKey{a:3, b:1}:{}}", 247 | }, 248 | { 249 | map[float64]struct{}{10.5: {}, 10.15: {}, 1203: {}, 1: {}, 2: {}}, 250 | "map[float64]struct {}{1:{}, 2:{}, 10.15:{}, 10.5:{}, 1203:{}}", 251 | }, 252 | { 253 | map[bool]struct{}{true: {}, false: {}}, 254 | "map[bool]struct {}{false:{}, true:{}}", 255 | }, 256 | { 257 | map[interface{}]struct{}{1: {}, 2: {}, 3: {}, "foo": {}}, 258 | `map[interface{}]struct {}{1:{}, 2:{}, 3:{}, "foo":{}}`, 259 | }, 260 | { 261 | map[complex64]struct{}{1 + 2i: {}, 2 + 1i: {}, 3 + 1i: {}, 1 + 3i: {}}, 262 | "map[complex64]struct {}{(1+2i):{}, (1+3i):{}, (2+1i):{}, (3+1i):{}}", 263 | }, 264 | { 265 | map[chan int]string{nil: "a", chans[0]: "b", chans[1]: "c", chans[2]: "d", chans[3]: "e", chans[4]: "f"}, 266 | `map[(chan int)]string{(chan int)(PTR):"a", (chan int)(PTR):"b", (chan int)(PTR):"c", (chan int)(PTR):"d", (chan int)(PTR):"e", (chan int)(PTR):"f"}`, 267 | }, 268 | } 269 | 270 | for _, tc := range tcs { 271 | assertRendersLike(t, reflect.TypeOf(tc.in).Name(), tc.in, tc.expect) 272 | } 273 | } 274 | --------------------------------------------------------------------------------