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  }