github.com/neugram/ng@v0.0.0-20180309130942-d472ff93d872/format/debug.go (about) 1 // Copyright 2017 The Neugram Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package format 6 7 import ( 8 "bytes" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "os" 13 "os/exec" 14 "reflect" 15 "strings" 16 ) 17 18 type debugPrinter struct { 19 buf *bytes.Buffer 20 ptrseen map[interface{}]int // ptr -> count seen 21 ptrdone map[interface{}]bool 22 indent int 23 } 24 25 func (p *debugPrinter) collectPtrs(v reflect.Value) { 26 switch v.Kind() { 27 case reflect.Ptr: 28 if !v.CanInterface() { 29 return 30 } 31 ptr := v.Interface() 32 p.ptrseen[ptr]++ 33 if p.ptrseen[ptr] == 1 { 34 p.collectPtrs(v.Elem()) 35 } 36 case reflect.Interface: 37 if repack := reflect.ValueOf(v.Interface()); repack.Kind() == reflect.Ptr { 38 p.collectPtrs(repack) 39 } else { 40 p.collectPtrs(v.Elem()) 41 } 42 case reflect.Map: 43 for _, key := range v.MapKeys() { 44 p.collectPtrs(key) 45 p.collectPtrs(v.MapIndex(key)) 46 } 47 case reflect.Array: 48 case reflect.Slice: 49 for i := 0; i < v.Len(); i++ { 50 p.collectPtrs(v.Index(i)) 51 } 52 case reflect.Struct: 53 for i := 0; i < v.NumField(); i++ { 54 p.collectPtrs(v.Field(i)) 55 } 56 } 57 } 58 59 func (p *debugPrinter) printf(format string, args ...interface{}) { 60 fmt.Fprintf(p.buf, format, args...) 61 } 62 63 func (p *debugPrinter) newline() { 64 p.buf.WriteByte('\n') 65 for i := 0; i < p.indent; i++ { 66 p.buf.WriteByte('\t') 67 } 68 } 69 70 func isZero(v reflect.Value) bool { 71 switch v.Kind() { 72 case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: 73 return v.IsNil() 74 case reflect.Struct: 75 for i := 0; i < v.NumField(); i++ { 76 if !isZero(v.Field(i)) { 77 return false 78 } 79 } 80 return true 81 case reflect.Array: 82 for i := 0; i < v.Len(); i++ { 83 if !isZero(v.Index(i)) { 84 return false 85 } 86 } 87 return true 88 case reflect.Bool: 89 return !v.Bool() 90 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 91 return v.Int() == 0 92 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 93 return v.Uint() == 0 94 case reflect.Float32, reflect.Float64: 95 return v.Float() == 0 96 case reflect.Complex64, reflect.Complex128: 97 return v.Complex() == 0 98 case reflect.String: 99 return v.String() == "" 100 } 101 return false 102 } 103 104 func (p *debugPrinter) printv(v reflect.Value) { 105 switch v.Kind() { 106 case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: 107 if v.IsNil() { 108 p.buf.WriteString("nil") 109 return 110 } 111 } 112 113 switch v.Kind() { 114 case reflect.Ptr: 115 if !v.CanInterface() { 116 p.printf("unexported") 117 return 118 } 119 p.printf("&") 120 ptr := v.Interface() 121 if p.ptrdone[ptr] { 122 p.printf("%p", ptr) 123 } else if p.ptrseen[ptr] > 1 { 124 // TODO: p.printv(v.Elem()) 125 p.printf(" (TODO type %T)", ptr) 126 p.ptrdone[ptr] = true 127 p.printf(" (ptr %p)", ptr) 128 } else { 129 p.printv(v.Elem()) 130 } 131 case reflect.Interface: 132 if repack := reflect.ValueOf(v.Interface()); repack.Kind() == reflect.Ptr { 133 p.printv(repack) 134 return 135 } 136 p.printv(v.Elem()) 137 case reflect.Map: 138 p.printf("%s{", v.Type()) 139 if v.Len() == 1 { 140 key := v.MapKeys()[0] 141 p.printf("%s: ", key) 142 p.printv(v.MapIndex(key)) 143 } else if v.Len() > 0 { 144 p.indent++ 145 for _, key := range v.MapKeys() { 146 p.newline() 147 p.printv(key) 148 p.printf(": ") 149 p.printv(v.MapIndex(key)) 150 p.buf.WriteByte(',') 151 } 152 p.indent-- 153 p.newline() 154 } 155 p.buf.WriteByte('}') 156 case reflect.Slice: 157 if v.Type().Elem().Kind() == reflect.Int8 { 158 s := v.Bytes() 159 p.printf("%#q", s) 160 return 161 } 162 fallthrough 163 case reflect.Array: 164 p.printf("%s{", v.Type()) 165 if v.Len() > 0 { 166 p.indent++ 167 for i := 0; i < v.Len(); i++ { 168 p.newline() 169 p.printv(v.Index(i)) 170 p.buf.WriteByte(',') 171 } 172 p.indent-- 173 p.newline() 174 } 175 p.buf.WriteByte('}') 176 case reflect.Struct: 177 t := v.Type() 178 p.printf("%s{", t) 179 if v.NumField() > 0 { 180 p.indent++ 181 for i := 0; i < v.NumField(); i++ { 182 if isZero(v.Field(i)) { 183 continue 184 } 185 p.newline() 186 p.printf("%s: ", t.Field(i).Name) 187 p.printv(v.Field(i)) 188 p.buf.WriteByte(',') 189 } 190 p.indent-- 191 p.newline() 192 } 193 p.buf.WriteByte('}') 194 case reflect.Bool, 195 reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 196 reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, 197 reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128: 198 str := fmt.Sprintf("%#v", v) 199 p.printf("%s(%s", v.Type(), str) 200 if v.CanInterface() { 201 if stringer, is := v.Interface().(fmt.Stringer); is { 202 str2 := stringer.String() 203 if str2 != str { 204 p.printf(" /* %s */", str2) 205 } 206 } 207 } 208 p.printf(")") 209 default: 210 if !v.IsValid() { 211 p.printf("?") 212 } else if v.Kind() == reflect.String { 213 p.printf("%q", v.String()) 214 } else if v.CanInterface() { 215 p.printf("%#v", v.Interface()) 216 } else { 217 p.printf("?") 218 } 219 } 220 } 221 222 func printToFile(x interface{}) (path string, err error) { 223 f, err := ioutil.TempFile("", "neugram-diff-") 224 if err != nil { 225 return "", err 226 } 227 defer func() { 228 err2 := f.Close() 229 if err == nil { 230 err = err2 231 } 232 if err != nil { 233 os.Remove(f.Name()) 234 } 235 }() 236 237 str := Debug(x) 238 if _, err := io.WriteString(f, str); err != nil { 239 return "", err 240 } 241 return f.Name(), nil 242 } 243 244 func diffVal(x, y interface{}) (string, error) { 245 fx, err := printToFile(x) 246 if err != nil { 247 return "", fmt.Errorf("diff print lhs error: %v", err) 248 } 249 defer os.Remove(fx) 250 fy, err := printToFile(y) 251 if err != nil { 252 return "", fmt.Errorf("diff print rhs error: %v", err) 253 } 254 defer os.Remove(fy) 255 256 b, _ := ioutil.ReadFile(fx) 257 fmt.Printf("fx: %s\n", b) 258 259 data, err := exec.Command("diff", "-U100", "-u", fx, fy).CombinedOutput() 260 if err != nil && len(data) == 0 { 261 // diff exits with a non-zero status when the files don't match. 262 return "", fmt.Errorf("diff error: %v", err) 263 } 264 res := string(data) 265 res = strings.Replace(res, fx, "/x", 1) 266 res = strings.Replace(res, fy, "/y", 1) 267 return res, nil 268 } 269 270 func WriteDebug(buf *bytes.Buffer, e interface{}) { 271 p := debugPrinter{ 272 buf: buf, 273 ptrseen: make(map[interface{}]int), 274 ptrdone: make(map[interface{}]bool), 275 } 276 v := reflect.ValueOf(e) 277 p.collectPtrs(v) 278 p.printv(v) 279 } 280 281 func Debug(e interface{}) string { 282 buf := new(bytes.Buffer) 283 WriteDebug(buf, e) 284 return buf.String() 285 } 286 287 func Diff(x, y interface{}) string { 288 s, err := diffVal(x, y) 289 if err != nil { 290 return fmt.Sprintf("format.Diff: %v", err) 291 } 292 return s 293 }