| package cmp |
| |
| import ( |
| "bytes" |
| "fmt" |
| "go/ast" |
| "reflect" |
| "text/template" |
| |
| "gotest.tools/v3/internal/source" |
| ) |
| |
| // A Result of a Comparison. |
| type Result interface { |
| Success() bool |
| } |
| |
| // StringResult is an implementation of Result that reports the error message |
| // string verbatim and does not provide any templating or formatting of the |
| // message. |
| type StringResult struct { |
| success bool |
| message string |
| } |
| |
| // Success returns true if the comparison was successful. |
| func (r StringResult) Success() bool { |
| return r.success |
| } |
| |
| // FailureMessage returns the message used to provide additional information |
| // about the failure. |
| func (r StringResult) FailureMessage() string { |
| return r.message |
| } |
| |
| // ResultSuccess is a constant which is returned by a ComparisonWithResult to |
| // indicate success. |
| var ResultSuccess = StringResult{success: true} |
| |
| // ResultFailure returns a failed Result with a failure message. |
| func ResultFailure(message string) StringResult { |
| return StringResult{message: message} |
| } |
| |
| // ResultFromError returns ResultSuccess if err is nil. Otherwise ResultFailure |
| // is returned with the error message as the failure message. |
| func ResultFromError(err error) Result { |
| if err == nil { |
| return ResultSuccess |
| } |
| return ResultFailure(err.Error()) |
| } |
| |
| type templatedResult struct { |
| template string |
| data map[string]interface{} |
| } |
| |
| func (r templatedResult) Success() bool { |
| return false |
| } |
| |
| func (r templatedResult) FailureMessage(args []ast.Expr) string { |
| msg, err := renderMessage(r, args) |
| if err != nil { |
| return fmt.Sprintf("failed to render failure message: %s", err) |
| } |
| return msg |
| } |
| |
| func (r templatedResult) UpdatedExpected(stackIndex int) error { |
| // TODO: would be nice to have structured data instead of a map |
| return source.UpdateExpectedValue(stackIndex+1, r.data["x"], r.data["y"]) |
| } |
| |
| // ResultFailureTemplate returns a Result with a template string and data which |
| // can be used to format a failure message. The template may access data from .Data, |
| // the comparison args with the callArg function, and the formatNode function may |
| // be used to format the call args. |
| func ResultFailureTemplate(template string, data map[string]interface{}) Result { |
| return templatedResult{template: template, data: data} |
| } |
| |
| func renderMessage(result templatedResult, args []ast.Expr) (string, error) { |
| tmpl := template.New("failure").Funcs(template.FuncMap{ |
| "formatNode": source.FormatNode, |
| "callArg": func(index int) ast.Expr { |
| if index >= len(args) { |
| return nil |
| } |
| return args[index] |
| }, |
| // TODO: any way to include this from ErrorIS instead of here? |
| "notStdlibErrorType": func(typ interface{}) bool { |
| r := reflect.TypeOf(typ) |
| return r != stdlibFmtErrorType && r != stdlibErrorNewType |
| }, |
| }) |
| var err error |
| tmpl, err = tmpl.Parse(result.template) |
| if err != nil { |
| return "", err |
| } |
| buf := new(bytes.Buffer) |
| err = tmpl.Execute(buf, map[string]interface{}{ |
| "Data": result.data, |
| }) |
| return buf.String(), err |
| } |