result.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. package cmp
  2. import (
  3. "bytes"
  4. "fmt"
  5. "go/ast"
  6. "reflect"
  7. "text/template"
  8. "gotest.tools/v3/internal/source"
  9. )
  10. // A Result of a [Comparison].
  11. type Result interface {
  12. Success() bool
  13. }
  14. // StringResult is an implementation of [Result] that reports the error message
  15. // string verbatim and does not provide any templating or formatting of the
  16. // message.
  17. type StringResult struct {
  18. success bool
  19. message string
  20. }
  21. // Success returns true if the comparison was successful.
  22. func (r StringResult) Success() bool {
  23. return r.success
  24. }
  25. // FailureMessage returns the message used to provide additional information
  26. // about the failure.
  27. func (r StringResult) FailureMessage() string {
  28. return r.message
  29. }
  30. // ResultSuccess is a constant which is returned by a [Comparison] to
  31. // indicate success.
  32. var ResultSuccess = StringResult{success: true}
  33. // ResultFailure returns a failed [Result] with a failure message.
  34. func ResultFailure(message string) StringResult {
  35. return StringResult{message: message}
  36. }
  37. // ResultFromError returns [ResultSuccess] if err is nil. Otherwise [ResultFailure]
  38. // is returned with the error message as the failure message.
  39. func ResultFromError(err error) Result {
  40. if err == nil {
  41. return ResultSuccess
  42. }
  43. return ResultFailure(err.Error())
  44. }
  45. type templatedResult struct {
  46. template string
  47. data map[string]interface{}
  48. }
  49. func (r templatedResult) Success() bool {
  50. return false
  51. }
  52. func (r templatedResult) FailureMessage(args []ast.Expr) string {
  53. msg, err := renderMessage(r, args)
  54. if err != nil {
  55. return fmt.Sprintf("failed to render failure message: %s", err)
  56. }
  57. return msg
  58. }
  59. func (r templatedResult) UpdatedExpected(stackIndex int) error {
  60. // TODO: would be nice to have structured data instead of a map
  61. return source.UpdateExpectedValue(stackIndex+1, r.data["x"], r.data["y"])
  62. }
  63. // ResultFailureTemplate returns a [Result] with a template string and data which
  64. // can be used to format a failure message. The template may access data from .Data,
  65. // the comparison args with the callArg function, and the formatNode function may
  66. // be used to format the call args.
  67. func ResultFailureTemplate(template string, data map[string]interface{}) Result {
  68. return templatedResult{template: template, data: data}
  69. }
  70. func renderMessage(result templatedResult, args []ast.Expr) (string, error) {
  71. tmpl := template.New("failure").Funcs(template.FuncMap{
  72. "formatNode": source.FormatNode,
  73. "callArg": func(index int) ast.Expr {
  74. if index >= len(args) {
  75. return nil
  76. }
  77. return args[index]
  78. },
  79. // TODO: any way to include this from ErrorIS instead of here?
  80. "notStdlibErrorType": func(typ interface{}) bool {
  81. r := reflect.TypeOf(typ)
  82. return r != stdlibFmtErrorType && r != stdlibErrorNewType
  83. },
  84. })
  85. var err error
  86. tmpl, err = tmpl.Parse(result.template)
  87. if err != nil {
  88. return "", err
  89. }
  90. buf := new(bytes.Buffer)
  91. err = tmpl.Execute(buf, map[string]interface{}{
  92. "Data": result.data,
  93. })
  94. return buf.String(), err
  95. }