testutil.go 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. package testutil
  2. import (
  3. "bytes"
  4. "io"
  5. "log/slog"
  6. "os"
  7. "path/filepath"
  8. "testing"
  9. "time"
  10. )
  11. // LogWriter returns an [io.Writer] that logs each Write using t.Log.
  12. func LogWriter(t *testing.T) io.Writer {
  13. return testWriter{t}
  14. }
  15. type testWriter struct{ t *testing.T }
  16. func (w testWriter) Write(b []byte) (int, error) {
  17. w.t.Logf("%s", b)
  18. return len(b), nil
  19. }
  20. // Slogger returns a [*slog.Logger] that writes each message
  21. // using t.Log.
  22. func Slogger(t *testing.T) *slog.Logger {
  23. return slog.New(slog.NewTextHandler(LogWriter(t), nil))
  24. }
  25. // SlogBuffer returns a [*slog.Logger] that writes each message to out.
  26. func SlogBuffer() (lg *slog.Logger, out *bytes.Buffer) {
  27. var buf bytes.Buffer
  28. lg = slog.New(slog.NewTextHandler(&buf, nil))
  29. return lg, &buf
  30. }
  31. // Check calls t.Fatal(err) if err is not nil.
  32. func Check(t *testing.T, err error) {
  33. if err != nil {
  34. t.Helper()
  35. t.Fatal(err)
  36. }
  37. }
  38. // CheckFunc exists so other packages do not need to invent their own type for
  39. // taking a Check function.
  40. type CheckFunc func(err error)
  41. // Checker returns a check function that
  42. // calls t.Fatal if err is not nil.
  43. func Checker(t *testing.T) (check func(err error)) {
  44. return func(err error) {
  45. if err != nil {
  46. t.Helper()
  47. t.Fatal(err)
  48. }
  49. }
  50. }
  51. // StopPanic runs f but silently recovers from any panic f causes.
  52. // The normal usage is:
  53. //
  54. // testutil.StopPanic(func() {
  55. // callThatShouldPanic()
  56. // t.Errorf("callThatShouldPanic did not panic")
  57. // })
  58. func StopPanic(f func()) {
  59. defer func() { recover() }()
  60. f()
  61. }
  62. // CheckTime calls t.Fatalf if got != want. Included in the error message is
  63. // want.Sub(got) to help diagnose the difference, along with their values in
  64. // UTC.
  65. func CheckTime(t *testing.T, got, want time.Time) {
  66. t.Helper()
  67. if !got.Equal(want) {
  68. t.Fatalf("got %v, want %v (%v)", got.UTC(), want.UTC(), want.Sub(got))
  69. }
  70. }
  71. // WriteFile writes data to a file named name. It makes the directory if it
  72. // doesn't exist and sets the file mode to perm.
  73. //
  74. // The name must be a relative path and must not contain .. or start with a /;
  75. // otherwise WriteFile will panic.
  76. func WriteFile[S []byte | string](t testing.TB, name string, data S) {
  77. t.Helper()
  78. if filepath.IsAbs(name) {
  79. t.Fatalf("WriteFile: name must be a relative path, got %q", name)
  80. }
  81. name = filepath.Clean(name)
  82. dir := filepath.Dir(name)
  83. if err := os.MkdirAll(dir, 0o755); err != nil {
  84. t.Fatal(err)
  85. }
  86. if err := os.WriteFile(name, []byte(data), 0o644); err != nil {
  87. t.Fatal(err)
  88. }
  89. }