github.com/jxskiss/gopkg/v2@v2.14.9-0.20240514120614-899f3e7952b4/easy/ezdbg/debug_test.go (about) 1 package ezdbg 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "log" 9 "os" 10 "regexp" 11 "sync" 12 "testing" 13 14 "github.com/stretchr/testify/assert" 15 16 "github.com/jxskiss/gopkg/v2/easy" 17 ) 18 19 type simple struct { 20 A string 21 } 22 23 type comptyp struct { 24 I32 int32 25 I32_p *int32 26 27 I64 int64 28 I64_p *int64 29 30 Str string 31 Str_p *string 32 33 Simple simple 34 Simple_p *simple 35 } 36 37 func TestJSON(t *testing.T) { 38 tests := []map[string]any{ 39 { 40 "value": 123, 41 "want": "123", 42 }, 43 { 44 "value": "456", 45 "want": `"456"`, 46 }, 47 { 48 "value": simple{"ABC"}, 49 "want": `{"A":"ABC"}`, 50 }, 51 { 52 "value": "<html></html>", 53 "want": `"<html></html>"`, 54 }, 55 } 56 for _, test := range tests { 57 x := easy.JSON(test["value"]) 58 assert.Equal(t, test["want"], x) 59 } 60 } 61 62 func TestDEBUG_bare_func(t *testing.T) { 63 // test func() 64 configTestLog(true, nil) 65 msg := "test DEBUG_bare_func" 66 got := copyStdLog(func() { 67 DEBUG(func() { 68 log.Println(msg, 1, 2, 3) 69 }) 70 }) 71 assert.Contains(t, string(got), msg) 72 assert.Contains(t, string(got), "1 2 3") 73 } 74 75 func TestDEBUG_logger_interface(t *testing.T) { 76 logbuf := bytes.NewBuffer(nil) 77 logger := &bufLogger{buf: logbuf} 78 79 // test logger interface 80 configTestLog(true, nil) 81 msg := "test DEBUG_logger_interface" 82 DEBUG(logger, msg, 1, 2, 3) 83 got := logbuf.String() 84 assert.Contains(t, got, msg) 85 assert.Contains(t, got, "1 2 3") 86 } 87 88 func TestDEBUG_logger_func(t *testing.T) { 89 // test logger function 90 configTestLog(true, nil) 91 logger := func() stdLogger { 92 return stdLogger{} 93 } 94 msg := "test DEBUG_logger_func" 95 got := copyStdLog(func() { 96 DEBUG(logger, msg, 1, 2, 3) 97 }) 98 assert.Contains(t, string(got), msg) 99 assert.Contains(t, string(got), "1 2 3") 100 } 101 102 func TestDEBUG_print_func(t *testing.T) { 103 // test print function 104 configTestLog(true, nil) 105 msg := "test DEBUG_print_func" 106 prefix := "PREFIX: " 107 logger := func(format string, args ...any) { 108 format = prefix + format 109 log.Printf(format, args...) 110 } 111 got := copyStdLog(func() { 112 DEBUG(logger, msg, 1, 2, 3) 113 }) 114 assert.Contains(t, string(got), prefix) 115 assert.Contains(t, string(got), msg) 116 assert.Contains(t, string(got), "1 2 3") 117 } 118 119 func TestDEBUG_ctx_logger(t *testing.T) { 120 logbuf := bytes.NewBuffer(nil) 121 ctx := context.WithValue(context.Background(), "TEST_LOGGER", &bufLogger{buf: logbuf}) 122 getCtxLogger := func(ctx context.Context) DebugLogger { 123 return ctx.Value("TEST_LOGGER").(*bufLogger) 124 } 125 126 // test ctx logger 127 configTestLog(true, getCtxLogger) 128 msg := "test DEBUG_ctx_logger" 129 DEBUG(ctx, msg, 1, 2, 3) 130 got := logbuf.String() 131 assert.Contains(t, got, msg) 132 assert.Contains(t, got, "1 2 3") 133 } 134 135 func TestDEBUG_simple(t *testing.T) { 136 configTestLog(true, nil) 137 138 // test format 139 got1 := copyStdLog(func() { 140 DEBUG("test DEBUG_simple a=%v b=%v c=%v", 1, 2, 3) 141 }) 142 want1 := "test DEBUG_simple a=1 b=2 c=3" 143 assert.Contains(t, string(got1), want1) 144 145 // raw params 146 got2 := copyStdLog(func() { 147 DEBUG("test DEBUG_simple a=", 1, "b=", 2, "c=", 3) 148 }) 149 want2 := "test DEBUG_simple a= 1 b= 2 c= 3" 150 assert.Contains(t, string(got2), want2) 151 } 152 153 func TestDEBUG_pointers(t *testing.T) { 154 configTestLog(true, nil) 155 156 got := copyStdLog(func() { 157 var x = 1234 158 var p1 = &x 159 var p2 = &p1 160 var p3 *int 161 var p4 **int 162 DEBUG(x, p1, p2, p3, p4) 163 }) 164 assert.Contains(t, string(got), "[DEBUG] [ezdbg.TestDEBUG_pointers.func1] 1234 1234 1234 null null") 165 } 166 167 func TestDEBUG_empty(t *testing.T) { 168 configTestLog(true, nil) 169 got := copyStdLog(func() { DEBUG() }) 170 want := regexp.MustCompile(`ezdbg/debug_test.go#L\d+ - ezdbg.TestDEBUG_empty`) 171 assert.Regexp(t, want, string(got)) 172 } 173 174 func TestDEBUGSkip(t *testing.T) { 175 configTestLog(true, nil) 176 177 got := copyStdLog(func() { DEBUGWrap() }) 178 want := regexp.MustCompile(`ezdbg/debug_test.go#L\d+ - ezdbg.TestDEBUGSkip`) 179 assert.Regexp(t, want, string(got)) 180 181 got = copyStdLog(func() { DEBUGWrapSkip2() }) 182 want = regexp.MustCompile(`ezdbg/debug_test.go#L\d+ - ezdbg.TestDEBUGSkip`) 183 assert.Regexp(t, want, string(got)) 184 } 185 186 func TestConfigLog(t *testing.T) { 187 getCtxLogger := func(ctx context.Context) DebugLogger { 188 return ctx.Value("TEST_LOGGER").(*bufLogger) 189 } 190 configTestLog(true, getCtxLogger) 191 assert.NotNil(t, _logcfg.LoggerFunc) 192 } 193 194 func TestFilterRule(t *testing.T) { 195 msg := "test FilterRule" 196 197 testCases := []struct { 198 name string 199 rule string 200 contains bool 201 }{ 202 {name: "default", rule: "", contains: true}, 203 {name: "allow all", rule: "allow=all", contains: true}, 204 {name: "allow explicitly 1", rule: "allow=ezdbg/*.go", contains: true}, 205 {name: "allow explicitly 2", rule: "allow=easy/**", contains: true}, 206 {name: "not allowed explicitly", rule: "allow=confr/*,easy/*.go,easy/ezhttp/**", contains: false}, 207 {name: "deny all", rule: "deny=all", contains: false}, 208 {name: "deny explicitly", rule: "deny=ezdbg/*", contains: false}, 209 {name: "not denied explicitly", rule: "deny=confr/*,easy/ezhttp/**", contains: true}, 210 } 211 for _, tc := range testCases { 212 t.Run(tc.name, func(t *testing.T) { 213 configTestLogWithFilterRule(true, nil, tc.rule) 214 got := copyStdLog(func() { 215 DEBUG(msg) 216 }) 217 if tc.contains { 218 assert.Contains(t, string(got), msg) 219 } else { 220 assert.NotContains(t, string(got), msg) 221 } 222 }) 223 } 224 } 225 226 // -------- utilities -------- // 227 228 type bufLogger struct { 229 buf *bytes.Buffer 230 } 231 232 func (p *bufLogger) Debugf(format string, args ...any) { 233 if p.buf == nil { 234 p.buf = bytes.NewBuffer(nil) 235 } 236 fmt.Fprintf(p.buf, format, args...) 237 } 238 239 func (p *bufLogger) Errorf(format string, args ...any) { 240 if p.buf == nil { 241 p.buf = bytes.NewBuffer(nil) 242 } 243 fmt.Fprintf(p.buf, format, args...) 244 } 245 246 func DEBUGWrap(args ...any) { 247 DEBUGSkip(1, args...) 248 } 249 250 func DEBUGWrapSkip2(args ...any) { 251 skip2 := func(args ...any) { 252 DEBUGSkip(2, args...) 253 } 254 skip2(args...) 255 } 256 257 func configTestLog( 258 enableDebug bool, 259 ctxLogger func(context.Context) DebugLogger, 260 ) { 261 configTestLogWithFilterRule(enableDebug, ctxLogger, "") 262 } 263 264 func configTestLogWithFilterRule( 265 enableDebug bool, 266 ctxLogger func(context.Context) DebugLogger, 267 filterRule string, 268 ) { 269 Config(Cfg{ 270 EnableDebug: func(_ context.Context) bool { return enableDebug }, 271 LoggerFunc: ctxLogger, 272 FilterRule: filterRule, 273 }) 274 } 275 276 var ( 277 stdoutMu sync.Mutex 278 stdlogMu sync.Mutex 279 ) 280 281 // copyStdout replaces os.Stdout with a file created by `os.Pipe()`, and 282 // copies the content written to os.Stdout. 283 // This is not safe and most likely problematic, it's mainly to help intercepting 284 // output in testing. 285 func copyStdout(f func()) ([]byte, error) { 286 stdoutMu.Lock() 287 defer stdoutMu.Unlock() 288 old := os.Stdout 289 defer func() { os.Stdout = old }() 290 291 r, w, err := os.Pipe() 292 // just to make sure the error didn't happen 293 // in case of unfortunate, we should still do the specified work 294 if err != nil { 295 f() 296 return nil, err 297 } 298 299 // copy the output in a separate goroutine, so printing can't block indefinitely 300 outCh := make(chan []byte) 301 go func() { 302 var buf bytes.Buffer 303 multi := io.MultiWriter(&buf, old) 304 io.Copy(multi, r) 305 outCh <- buf.Bytes() 306 }() 307 308 // do the work, write the stdout to pipe 309 os.Stdout = w 310 f() 311 w.Close() 312 313 out := <-outCh 314 return out, nil 315 } 316 317 // copyStdLog replaces the out Writer of the default logger of `log` package, 318 // and copies the content written to it. 319 // This is unsafe and most likely problematic, it's mainly to help intercepting 320 // log output in testing. 321 // 322 // Also NOTE if the out Writer of the default logger has already been replaced 323 // with another writer, we won't know anything about that writer and will 324 // restore the out Writer to os.Stderr before it returns. 325 // It will be a real mess. 326 func copyStdLog(f func()) []byte { 327 stdlogMu.Lock() 328 defer stdlogMu.Unlock() 329 defer log.SetOutput(os.Stderr) 330 331 var buf bytes.Buffer 332 multi := io.MultiWriter(&buf, os.Stderr) 333 log.SetOutput(multi) 334 f() 335 return buf.Bytes() 336 }