assertions.go 1.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. // Package assertive provides two boolean assertion functions, both compatible
  2. // with the standard testing.T type. They avoid repetitive boilerplate around
  3. // T.Error and T.Fatal and augment those functions with extracted source code.
  4. // See this package's tests for usage examples.
  5. package assertive
  6. import (
  7. "os"
  8. "runtime"
  9. "strings"
  10. )
  11. // Want marks the test as failed if condition is false. Use this function
  12. // when the test may continue even if the assertion fails.
  13. func Want(t miniT, condition bool) {
  14. t.Helper()
  15. if !condition {
  16. line := getLine(t)
  17. t.Error(line)
  18. }
  19. }
  20. // Need marks the test as failed if condition is false and stops execution
  21. // of the running test. Use this function when the test cannot continue after
  22. // the current assertion fails, due to cascading failures or because it makes
  23. // no sense to continue the execution for any other reason.
  24. func Need(t miniT, condition bool) {
  25. t.Helper()
  26. if !condition {
  27. line := getLine(t)
  28. t.Fatal(line)
  29. }
  30. }
  31. type miniT interface {
  32. Error(...any)
  33. Fatal(...any)
  34. Helper()
  35. }
  36. func getLine(t miniT) string {
  37. t.Helper()
  38. // get the program counter for the caller of Want()/Need()
  39. // and convert the program counter to a runtime.Frame
  40. pc := make([]uintptr, 1)
  41. n := runtime.Callers(3, pc)
  42. if n < 1 {
  43. // this cannot happen under normal circumstances
  44. // at the very least we'd have 4 frames
  45. // main -> Want/Need -> getLine -> runtime.Callers
  46. return "[ERROR OBTAINING CALLER FRAME]"
  47. }
  48. frames := runtime.CallersFrames(pc)
  49. frame, _ := frames.Next()
  50. text, err := os.ReadFile(frame.File)
  51. if err != nil {
  52. // this cannot happen under normal circumstances
  53. // frame.File is where the function corresponding
  54. // to the 4th stack frame is defined
  55. return "[ERROR READING SOURCE FILE]"
  56. }
  57. // isolate the line we want by asking for one extra split
  58. parts := strings.SplitN(string(text), "\n", frame.Line+1)
  59. // file lines are 1-indexed
  60. return parts[frame.Line-1]
  61. }