github.com/mitranim/gg@v0.1.17/gtest/gtest_internal.go (about) 1 package gtest 2 3 import ( 4 "fmt" 5 r "reflect" 6 "strings" 7 8 "github.com/mitranim/gg" 9 "github.com/mitranim/gg/grepr" 10 ) 11 12 // Suboptimal, TODO revise. 13 func reindent(src string) string { 14 return gg.JoinLines(gg.Map(gg.SplitLines(src), indent)...) 15 } 16 17 func indent(src string) string { 18 if src == `` { 19 return src 20 } 21 return gg.Indent + src 22 } 23 24 // TODO rename and make public. 25 func goStringIndent[A any](val A) string { return grepr.StringIndent(val, 1) } 26 27 func msgSingle[A any](val A) string { 28 if isSimple(val) { 29 return Msg(`value:`, gg.StringAny(val)) 30 } 31 32 return gg.JoinLinesOpt( 33 Msg(`detailed:`, goStringIndent(val)), 34 Msg(`simple:`, gg.StringAny(val)), 35 ) 36 } 37 38 func msgOpt(opt []any, src string) string { 39 return gg.JoinLinesOpt( 40 src, 41 MsgOpt(`extra:`, gg.SpacedOpt(opt...)), 42 ) 43 } 44 45 func msgPanicNoneWithTest(fun func(), test func(error) bool) string { 46 return gg.JoinLinesOpt(msgNotPanic(), msgErrFunTest(fun, test)) 47 } 48 49 func msgPanicNoneWithStr(fun func(), exp string) string { 50 return gg.JoinLinesOpt(msgNotPanic(), msgFun(fun), msgExp(exp)) 51 } 52 53 func msgPanicNoneWithErr(fun func(), exp error) string { 54 return gg.JoinLinesOpt(msgNotPanic(), msgFun(fun), msgExp(exp)) 55 } 56 57 func msgErrFunTest(fun func(), test func(error) bool) string { 58 return gg.JoinLinesOpt(msgFun(fun), msgErrTest(test)) 59 } 60 61 func msgNotPanic() string { return `unexpected lack of panic` } 62 63 func msgFun(val func()) string { 64 if val == nil { 65 return `` 66 } 67 return Msg(`function:`, gg.FuncName(val)) 68 } 69 70 func msgErrTest(val func(error) bool) string { 71 if val == nil { 72 return `` 73 } 74 return Msg(`error test:`, gg.FuncName(val)) 75 } 76 77 func msgErrMismatch(fun func(), test func(error) bool, err error) string { 78 return gg.JoinLinesOpt( 79 `unexpected error mismatch`, 80 msgErrFunTest(fun, test), 81 msgErrActual(err), 82 ) 83 } 84 85 func msgErrMsgMismatch(fun func(), exp, act string) string { 86 return gg.JoinLinesOpt( 87 `unexpected error message mismatch`, 88 msgFun(fun), 89 Msg(`actual error message:`, act), 90 Msg(`expected error message substring:`, exp), 91 ) 92 } 93 94 func msgErrIsMismatch(err, exp error) string { 95 return gg.JoinLinesOpt( 96 `unexpected error mismatch`, 97 msgErrActual(err), 98 Msg(`expected error via errors.Is:`, gg.StringAny(exp)), 99 ) 100 } 101 102 func msgExp[A any](val A) string { return Msg(`expected:`, gg.StringAny(val)) } 103 104 func msgErrorNone(test func(error) bool) string { 105 return gg.JoinLinesOpt(`unexpected lack of error`, msgErrTest(test)) 106 } 107 108 func msgFunErr(fun func(), err error) string { 109 return gg.JoinLinesOpt(msgFun(fun), msgErr(err)) 110 } 111 112 func msgErr(err error) string { 113 return gg.JoinLinesOpt( 114 Msg(`error trace:`, errTrace(err)), 115 Msg(`error string:`, gg.StringAny(err)), 116 ) 117 } 118 119 func msgErrActual(err error) string { 120 return gg.JoinLinesOpt( 121 Msg(`actual error trace:`, errTrace(err)), 122 Msg(`actual error string:`, gg.StringAny(err)), 123 ) 124 } 125 126 func errTrace(err error) string { 127 return strings.TrimSpace(gg.ErrTrace(err).StringIndent(1)) 128 } 129 130 func msgSliceElemMissing[A ~[]B, B any](src A, val B) string { 131 return gg.JoinLinesOpt(`missing element in slice`, msgSliceElem(src, val)) 132 } 133 134 func msgSliceElemUnexpected[A ~[]B, B any](src A, val B) string { 135 return gg.JoinLinesOpt(`unexpected element in slice`, msgSliceElem(src, val)) 136 } 137 138 func msgSliceElem[A ~[]B, B any](src A, val B) string { 139 // TODO avoid detailed view when it's unnecessary. 140 return gg.JoinLinesOpt( 141 Msg(`slice detailed:`, goStringIndent(src)), 142 Msg(`element detailed:`, goStringIndent(val)), 143 Msg(`slice simple:`, gg.StringAny(src)), 144 Msg(`element simple:`, gg.StringAny(val)), 145 ) 146 } 147 148 func msgLess[A any](one, two A) string { 149 return gg.JoinLinesOpt(`expected A < B`, msgAB(one, two)) 150 } 151 152 func msgLessEq[A any](one, two A) string { 153 return gg.JoinLinesOpt(`expected A <= B`, msgAB(one, two)) 154 } 155 156 func msgAB[A any](one, two A) string { 157 return gg.JoinLinesOpt( 158 // TODO avoid detailed view when it's unnecessary. 159 Msg(`A detailed:`, goStringIndent(one)), 160 Msg(`B detailed:`, goStringIndent(two)), 161 Msg(`A simple:`, gg.StringAny(one)), 162 Msg(`B simple:`, gg.StringAny(two)), 163 ) 164 } 165 166 /* 167 Should return `true` when stringifying the given value via `fmt.Sprint` produces 168 basically the same representation as pretty-printing it via `grepr`, with no 169 significant difference in information. We "discount" the string quotes in this 170 case. TODO rename and move to `grepr`. This test for `fmt.Stringer` but ignores 171 other text-encoding interfaces such as `gg.Appender` or `encoding.TextMarshaler` 172 because `gtest` produces the "simple" representation by calling `fmt.Sprint`, 173 which does not support any of those additional interfaces. 174 */ 175 func isSimple(src any) bool { 176 return src == nil || (!gg.AnyIs[fmt.Stringer](src) && 177 !gg.AnyIs[fmt.GoStringer](src) && 178 isPrim(src)) 179 } 180 181 // TODO should probably move to `gg` and make public. 182 func isPrim(src any) bool { 183 val := r.ValueOf(src) 184 185 switch val.Kind() { 186 case r.Bool, 187 r.Int8, r.Int16, r.Int32, r.Int64, r.Int, 188 r.Uint8, r.Uint16, r.Uint32, r.Uint64, r.Uint, r.Uintptr, 189 r.Float32, r.Float64, 190 r.Complex64, r.Complex128, 191 r.String: 192 return true 193 default: 194 return false 195 } 196 }