lab.nexedi.com/kirr/go123@v0.0.0-20240207185015-8299741fa871/xfmt/fmt_test.go (about) 1 // Copyright (C) 2017 Nexedi SA and Contributors. 2 // Kirill Smelkov <kirr@nexedi.com> 3 // 4 // This program is free software: you can Use, Study, Modify and Redistribute 5 // it under the terms of the GNU General Public License version 3, or (at your 6 // option) any later version, as published by the Free Software Foundation. 7 // 8 // You can also Link and Combine this program with other software covered by 9 // the terms of any of the Free Software licenses or any of the Open Source 10 // Initiative approved licenses and Convey the resulting work. Corresponding 11 // source of such a combination shall include the source code for all other 12 // software used. 13 // 14 // This program is distributed WITHOUT ANY WARRANTY; without even the implied 15 // warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 16 // 17 // See COPYING file for full licensing terms. 18 // See https://www.nexedi.com/licensing for rationale and options. 19 20 package xfmt 21 22 import ( 23 "fmt" 24 "reflect" 25 "testing" 26 ) 27 28 var testv = []struct {format, xformatMeth string; value interface{}} { 29 {"%c", "Cb", byte('A')}, 30 {"%c", "C", rune(-1)}, 31 {"%c", "C", 'B'}, // 1-byte encoded 32 {"%c", "C", 'и'}, // 2-bytes encoded 33 {"%c", "C", '\u20ac'}, // 3-bytes encoded 34 {"%c", "C", '\U00010001'}, // 4-bytes encoded 35 36 {"%s", "S", "hello"}, 37 {"%s", "Sb", []byte("world")}, 38 {"%q", "Q", "alpha"}, 39 {"%q", "Qb", []byte("beta")}, 40 {"%q", "Qcb", byte('D')}, 41 {"%q", "Qc", 'B'}, // 1-byte encoded 42 {"%q", "Qc", 'и'}, // 2-bytes encoded 43 {"%q", "Qc", '\u20ac'}, // 3-bytes encoded 44 {"%q", "Qc", '\U00010001'}, // 4-bytes encoded 45 46 {"%x", "Xb", []byte("hexstring")}, 47 {"%x", "Xs", "stringhex"}, 48 {"%d", "D", 12765}, 49 {"%d", "D64", int64(12764)}, 50 {"%x", "X", 12789}, 51 {"%016x", "X016", uint64(124)}, 52 53 {"%v", "V", &stringerTest{}}, 54 } 55 56 57 type stringerTest struct { 58 } 59 60 func (s *stringerTest) String() string { 61 return string(s.XFmtString(nil)) 62 } 63 64 func (s *stringerTest) XFmtString(b []byte) []byte { 65 return append(b, `stringer test`...) 66 } 67 68 // verify formatting result is the same in between std fmt and xfmt 69 func TestXFmt(t *testing.T) { 70 buf := &Buffer{} 71 xbuf := reflect.ValueOf(buf) 72 73 for _, tt := range testv { 74 // result via fmt 75 resFmt := fmt.Sprintf(tt.format, tt.value) 76 77 // result via xfmt (via reflect.Call) 78 buf.Reset() 79 80 xmeth := xbuf.MethodByName(tt.xformatMeth) 81 if !xmeth.IsValid() { 82 t.Errorf(".%v: no such method", tt.xformatMeth) 83 continue 84 } 85 86 xargv := []reflect.Value{reflect.ValueOf(tt.value)} 87 xretv := []reflect.Value{} 88 callOk := false 89 func () { 90 defer func() { 91 if r := recover(); r != nil { 92 t.Errorf("%v: panic: %v", tt, r) 93 } 94 }() 95 xretv = xmeth.Call(xargv) 96 callOk = true 97 }() 98 if !callOk { 99 continue 100 } 101 102 // check all formatters return pointer to the same buf 103 // (this way it is handy to do .S("hello ") .X016(123) .V(zzz) ... 104 if !(len(xretv) == 1 && xretv[0].Interface() == buf) { 105 t.Errorf(".%v: returned %#v ; want %#v", tt.xformatMeth, xretv[0].Interface(), buf) 106 continue 107 } 108 109 resXFmt := string(*buf) 110 111 // results must be the same 112 if resFmt != resXFmt { 113 t.Errorf(".%v(%v) -> %q != printf(%q) -> %q", 114 tt.xformatMeth, tt.value, resXFmt, tt.format, resFmt) 115 } 116 } 117 } 118 119 func BenchmarkXFmt(b *testing.B) { 120 buf := &Buffer{} 121 122 for _, tt := range testv { 123 b.Run(fmt.Sprintf("%s(%#v)", tt.format, tt.value), func (b *testing.B) { 124 for i := 0; i < b.N; i++ { 125 fmt.Sprintf(tt.format, tt.value) 126 } 127 }) 128 129 // construct methProxy for natively calling (not via reflect) method associated with tt.xformatMeth 130 // (calling via reflect allocates a lot and is slow) 131 // NOTE because of proxies the call is a bit slower than e.g. directly calling buf.S("...") 132 var methProxy func(buf *Buffer, v interface{}) 133 134 xmeth, ok := reflect.TypeOf(buf).MethodByName(tt.xformatMeth) 135 if !ok { 136 b.Errorf(".%v: no such method", tt.xformatMeth) 137 continue 138 } 139 140 // XXX a bit ugly -> use code generation instead? 141 meth := xmeth.Func.Interface() 142 switch tt.value.(type) { 143 case byte: methProxy = func(buf *Buffer, v interface{}) { meth.(func (*Buffer, byte) *Buffer)(buf, v.(byte)) } 144 case rune: methProxy = func(buf *Buffer, v interface{}) { meth.(func (*Buffer, rune) *Buffer)(buf, v.(rune)) } 145 case string: methProxy = func(buf *Buffer, v interface{}) { meth.(func (*Buffer, string) *Buffer)(buf, v.(string)) } 146 case []byte: methProxy = func(buf *Buffer, v interface{}) { meth.(func (*Buffer, []byte) *Buffer)(buf, v.([]byte)) } 147 case int: methProxy = func(buf *Buffer, v interface{}) { meth.(func (*Buffer, int) *Buffer)(buf, v.(int)) } 148 case int64: methProxy = func(buf *Buffer, v interface{}) { meth.(func (*Buffer, int64) *Buffer)(buf, v.(int64)) } 149 case uint64: methProxy = func(buf *Buffer, v interface{}) { meth.(func (*Buffer, uint64) *Buffer)(buf, v.(uint64)) } 150 151 case *stringerTest: methProxy = func(buf *Buffer, v interface{}) { meth.(func (*Buffer, Stringer) *Buffer)(buf, v.(Stringer)) } 152 153 default: 154 b.Fatalf("TODO add support for %T", tt.value) 155 } 156 157 b.Run(fmt.Sprintf(".%s(%#v)", tt.xformatMeth, tt.value), func (b *testing.B) { 158 for i := 0; i < b.N; i++ { 159 buf.Reset() 160 methProxy(buf, tt.value) 161 } 162 }) 163 } 164 }