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  }