...

Source file src/gotest.tools/v3/assert/cmp/result.go

Documentation: gotest.tools/v3/assert/cmp

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

View as plain text