1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117 |
- package httpexpect
- import (
- "bytes"
- "encoding/json"
- "flag"
- "fmt"
- "math"
- "net/http/httputil"
- "os"
- "path/filepath"
- "regexp"
- "strconv"
- "strings"
- "sync"
- "testing"
- "text/template"
- "github.com/fatih/color"
- "github.com/mattn/go-isatty"
- "github.com/mitchellh/go-wordwrap"
- "github.com/sanity-io/litter"
- "github.com/yudai/gojsondiff"
- "github.com/yudai/gojsondiff/formatter"
- )
- // Formatter is used to format assertion messages into strings.
- type Formatter interface {
- FormatSuccess(*AssertionContext) string
- FormatFailure(*AssertionContext, *AssertionFailure) string
- }
- // DefaultFormatter is the default Formatter implementation.
- //
- // DefaultFormatter gathers values from AssertionContext and AssertionFailure,
- // converts them to strings, and creates FormatData struct. Then it passes
- // FormatData to the template engine (text/template) to format message.
- //
- // You can control what is included and what is excluded from messages via
- // several public fields.
- //
- // If desired, you can provide custom templates and function map. This may
- // be easier than creating your own formatter from scratch.
- type DefaultFormatter struct {
- // Exclude test name and request name from failure report.
- DisableNames bool
- // Exclude assertion path from failure report.
- DisablePaths bool
- // Exclude aliased assertion path from failure report.
- DisableAliases bool
- // Exclude diff from failure report.
- DisableDiffs bool
- // Exclude HTTP request from failure report.
- DisableRequests bool
- // Exclude HTTP response from failure report.
- DisableResponses bool
- // Thousand separator.
- // Default is DigitSeparatorUnderscore.
- DigitSeparator DigitSeparator
- // Float printing format.
- // Default is FloatFormatAuto.
- FloatFormat FloatFormat
- // Defines whether to print stacktrace on failure and in what format.
- // Default is StacktraceModeDisabled.
- StacktraceMode StacktraceMode
- // Colorization mode.
- // Default is ColorModeAuto.
- ColorMode ColorMode
- // Wrap text to keep lines below given width.
- // Use zero for default width, and negative value to disable wrapping.
- LineWidth int
- // If not empty, used to format success messages.
- // If empty, default template is used.
- SuccessTemplate string
- // If not empty, used to format failure messages.
- // If empty, default template is used.
- FailureTemplate string
- // When SuccessTemplate or FailureTemplate is set, this field
- // defines the function map passed to template engine.
- // May be nil.
- TemplateFuncs template.FuncMap
- }
- // FormatSuccess implements Formatter.FormatSuccess.
- func (f *DefaultFormatter) FormatSuccess(ctx *AssertionContext) string {
- if f.SuccessTemplate != "" {
- return f.applyTemplate("SuccessTemplate",
- f.SuccessTemplate, f.TemplateFuncs, ctx, nil)
- } else {
- return f.applyTemplate("SuccessTemplate",
- defaultSuccessTemplate, defaultTemplateFuncs, ctx, nil)
- }
- }
- // FormatFailure implements Formatter.FormatFailure.
- func (f *DefaultFormatter) FormatFailure(
- ctx *AssertionContext, failure *AssertionFailure,
- ) string {
- if f.FailureTemplate != "" {
- return f.applyTemplate("FailureTemplate",
- f.FailureTemplate, f.TemplateFuncs, ctx, failure)
- } else {
- return f.applyTemplate("FailureTemplate",
- defaultFailureTemplate, defaultTemplateFuncs, ctx, failure)
- }
- }
- // DigitSeparator defines the separator used to format integers and floats.
- type DigitSeparator int
- const (
- // Separate using underscore
- DigitSeparatorUnderscore DigitSeparator = iota
- // Separate using comma
- DigitSeparatorComma
- // Separate using apostrophe
- DigitSeparatorApostrophe
- // Do not separate
- DigitSeparatorNone
- )
- // FloatFormat defines the format in which all floats are printed.
- type FloatFormat int
- const (
- // Print floats in scientific notation for large exponents,
- // otherwise print in decimal notation.
- // Precision is the smallest needed to identify the value uniquely.
- // Similar to %g format.
- FloatFormatAuto FloatFormat = iota
- // Always print floats in decimal notation.
- // Precision is the smallest needed to identify the value uniquely.
- // Similar to %f format.
- FloatFormatDecimal
- // Always print floats in scientific notation.
- // Precision is the smallest needed to identify the value uniquely.
- // Similar to %e format.
- FloatFormatScientific
- )
- // StacktraceMode defines the format of stacktrace.
- type StacktraceMode int
- const (
- // Don't print stacktrace.
- StacktraceModeDisabled StacktraceMode = iota
- // Standard, verbose format.
- StacktraceModeStandard
- // Compact format.
- StacktraceModeCompact
- )
- // ColorMode defines how the text color is enabled.
- type ColorMode int
- const (
- // Automatically enable colors if ALL of the following is true:
- // - stdout is a tty / console
- // - AssertionHandler is known to output to testing.T
- // - testing.Verbose() is true
- //
- // Colors are forcibly enabled if FORCE_COLOR environment variable
- // is set to a positive integer.
- //
- // Colors are forcibly disabled if TERM is "dumb" or NO_COLOR
- // environment variable is set to non-empty string.
- ColorModeAuto ColorMode = iota
- // Unconditionally enable colors.
- ColorModeAlways
- // Unconditionally disable colors.
- ColorModeNever
- )
- // FormatData defines data passed to template engine when DefaultFormatter
- // formats assertion. You can use these fields in your custom templates.
- type FormatData struct {
- TestName string
- RequestName string
- AssertPath []string
- AssertType string
- AssertSeverity string
- Errors []string
- HaveActual bool
- Actual string
- HaveExpected bool
- IsNegation bool
- IsComparison bool
- ExpectedKind string
- Expected []string
- HaveReference bool
- Reference string
- HaveDelta bool
- Delta string
- HaveDiff bool
- Diff string
- HaveRequest bool
- Request string
- HaveResponse bool
- Response string
- HaveStacktrace bool
- Stacktrace []string
- EnableColors bool
- LineWidth int
- }
- const (
- kindRange = "range"
- kindSchema = "schema"
- kindPath = "path"
- kindRegexp = "regexp"
- kindFormat = "format"
- kindFormatList = "formats"
- kindKey = "key"
- kindElement = "element"
- kindSubset = "subset"
- kindValue = "value"
- kindValueList = "values"
- )
- func (f *DefaultFormatter) applyTemplate(
- templateName string,
- templateString string,
- templateFuncs template.FuncMap,
- ctx *AssertionContext,
- failure *AssertionFailure,
- ) string {
- templateData := f.buildFormatData(ctx, failure)
- t, err := template.New(templateName).Funcs(templateFuncs).Parse(templateString)
- if err != nil {
- panic(err)
- }
- var b bytes.Buffer
- err = t.Execute(&b, templateData)
- if err != nil {
- panic(err)
- }
- return b.String()
- }
- func (f *DefaultFormatter) buildFormatData(
- ctx *AssertionContext, failure *AssertionFailure,
- ) *FormatData {
- data := FormatData{}
- f.fillGeneral(&data, ctx)
- if failure != nil {
- data.AssertType = failure.Type.String()
- data.AssertSeverity = failure.Severity.String()
- f.fillErrors(&data, ctx, failure)
- if failure.Actual != nil {
- f.fillActual(&data, ctx, failure)
- }
- if failure.Expected != nil {
- f.fillExpected(&data, ctx, failure)
- f.fillIsNegation(&data, ctx, failure)
- f.fillIsComparison(&data, ctx, failure)
- }
- if failure.Reference != nil {
- f.fillReference(&data, ctx, failure)
- }
- if failure.Delta != nil {
- f.fillDelta(&data, ctx, failure)
- }
- f.fillRequest(&data, ctx, failure)
- f.fillResponse(&data, ctx, failure)
- f.fillStacktrace(&data, ctx, failure)
- }
- return &data
- }
- func (f *DefaultFormatter) fillGeneral(
- data *FormatData, ctx *AssertionContext,
- ) {
- if !f.DisableNames {
- data.TestName = ctx.TestName
- data.RequestName = ctx.RequestName
- }
- if !f.DisablePaths {
- if !f.DisableAliases {
- data.AssertPath = ctx.AliasedPath
- } else {
- data.AssertPath = ctx.Path
- }
- }
- switch f.ColorMode {
- case ColorModeAuto:
- switch colorMode() {
- case colorsUnsupported:
- data.EnableColors = false
- case colorsForced:
- data.EnableColors = true
- case colorsSupported:
- data.EnableColors = ctx.TestingTB && flag.Parsed() && testing.Verbose()
- }
- case ColorModeAlways:
- data.EnableColors = true
- case ColorModeNever:
- data.EnableColors = false
- }
- if f.LineWidth != 0 {
- data.LineWidth = f.LineWidth
- } else {
- data.LineWidth = defaultLineWidth
- }
- }
- func (f *DefaultFormatter) fillErrors(
- data *FormatData, ctx *AssertionContext, failure *AssertionFailure,
- ) {
- data.Errors = []string{}
- for _, err := range failure.Errors {
- if refIsNil(err) {
- continue
- }
- data.Errors = append(data.Errors, err.Error())
- }
- }
- func (f *DefaultFormatter) fillActual(
- data *FormatData, ctx *AssertionContext, failure *AssertionFailure,
- ) {
- switch failure.Type { //nolint
- case AssertUsage, AssertOperation:
- data.HaveActual = false
- case AssertType, AssertNotType:
- data.HaveActual = true
- data.Actual = f.formatTypedValue(failure.Actual.Value)
- default:
- data.HaveActual = true
- data.Actual = f.formatValue(failure.Actual.Value)
- }
- }
- func (f *DefaultFormatter) fillExpected(
- data *FormatData, ctx *AssertionContext, failure *AssertionFailure,
- ) {
- switch failure.Type {
- case AssertUsage, AssertOperation,
- AssertType, AssertNotType,
- AssertValid, AssertNotValid,
- AssertNil, AssertNotNil,
- AssertEmpty, AssertNotEmpty,
- AssertNotEqual:
- data.HaveExpected = false
- case AssertEqual:
- data.HaveExpected = true
- data.ExpectedKind = kindValue
- data.Expected = []string{
- f.formatValue(failure.Expected.Value),
- }
- if !f.DisableDiffs && failure.Actual != nil && failure.Expected != nil {
- data.Diff, data.HaveDiff = f.formatDiff(
- failure.Expected.Value, failure.Actual.Value)
- }
- case AssertLt, AssertLe, AssertGt, AssertGe:
- data.HaveExpected = true
- data.ExpectedKind = kindValue
- data.Expected = []string{
- f.formatValue(failure.Expected.Value),
- }
- case AssertInRange, AssertNotInRange:
- data.HaveExpected = true
- data.ExpectedKind = kindRange
- data.Expected = f.formatRangeValue(failure.Expected.Value)
- case AssertMatchSchema, AssertNotMatchSchema:
- data.HaveExpected = true
- data.ExpectedKind = kindSchema
- data.Expected = []string{
- f.formatMatchValue(failure.Expected.Value),
- }
- case AssertMatchPath, AssertNotMatchPath:
- data.HaveExpected = true
- data.ExpectedKind = kindPath
- data.Expected = []string{
- f.formatMatchValue(failure.Expected.Value),
- }
- case AssertMatchRegexp, AssertNotMatchRegexp:
- data.HaveExpected = true
- data.ExpectedKind = kindRegexp
- data.Expected = []string{
- f.formatMatchValue(failure.Expected.Value),
- }
- case AssertMatchFormat, AssertNotMatchFormat:
- data.HaveExpected = true
- if extractList(failure.Expected.Value) != nil {
- data.ExpectedKind = kindFormatList
- } else {
- data.ExpectedKind = kindFormat
- }
- data.Expected = f.formatListValue(failure.Expected.Value)
- case AssertContainsKey, AssertNotContainsKey:
- data.HaveExpected = true
- data.ExpectedKind = kindKey
- data.Expected = []string{
- f.formatValue(failure.Expected.Value),
- }
- case AssertContainsElement, AssertNotContainsElement:
- data.HaveExpected = true
- data.ExpectedKind = kindElement
- data.Expected = []string{
- f.formatValue(failure.Expected.Value),
- }
- case AssertContainsSubset, AssertNotContainsSubset:
- data.HaveExpected = true
- data.ExpectedKind = kindSubset
- data.Expected = []string{
- f.formatValue(failure.Expected.Value),
- }
- case AssertBelongs, AssertNotBelongs:
- data.HaveExpected = true
- data.ExpectedKind = kindValueList
- data.Expected = f.formatListValue(failure.Expected.Value)
- }
- }
- func (f *DefaultFormatter) fillIsNegation(
- data *FormatData, ctx *AssertionContext, failure *AssertionFailure,
- ) {
- switch failure.Type {
- case AssertUsage, AssertOperation,
- AssertType,
- AssertValid,
- AssertNil,
- AssertEmpty,
- AssertEqual,
- AssertLt, AssertLe, AssertGt, AssertGe,
- AssertInRange,
- AssertMatchSchema,
- AssertMatchPath,
- AssertMatchRegexp,
- AssertMatchFormat,
- AssertContainsKey,
- AssertContainsElement,
- AssertContainsSubset,
- AssertBelongs:
- break
- case AssertNotType,
- AssertNotValid,
- AssertNotNil,
- AssertNotEmpty,
- AssertNotEqual,
- AssertNotInRange,
- AssertNotMatchSchema,
- AssertNotMatchPath,
- AssertNotMatchRegexp,
- AssertNotMatchFormat,
- AssertNotContainsKey,
- AssertNotContainsElement,
- AssertNotContainsSubset,
- AssertNotBelongs:
- data.IsNegation = true
- }
- }
- func (f *DefaultFormatter) fillIsComparison(
- data *FormatData, ctx *AssertionContext, failure *AssertionFailure,
- ) {
- switch failure.Type { //nolint
- case AssertLt, AssertLe, AssertGt, AssertGe:
- data.IsComparison = true
- }
- }
- func (f *DefaultFormatter) fillReference(
- data *FormatData, ctx *AssertionContext, failure *AssertionFailure,
- ) {
- data.HaveReference = true
- data.Reference = f.formatValue(failure.Reference.Value)
- }
- func (f *DefaultFormatter) fillDelta(
- data *FormatData, ctx *AssertionContext, failure *AssertionFailure,
- ) {
- data.HaveDelta = true
- data.Delta = f.formatValue(failure.Delta.Value)
- }
- func (f *DefaultFormatter) fillRequest(
- data *FormatData, ctx *AssertionContext, failure *AssertionFailure,
- ) {
- if !f.DisableRequests && ctx.Request != nil && ctx.Request.httpReq != nil {
- dump, err := httputil.DumpRequest(ctx.Request.httpReq, false)
- if err != nil {
- return
- }
- data.HaveRequest = true
- data.Request = string(dump)
- }
- }
- func (f *DefaultFormatter) fillResponse(
- data *FormatData, ctx *AssertionContext, failure *AssertionFailure,
- ) {
- if !f.DisableResponses && ctx.Response != nil && ctx.Response.httpResp != nil {
- dump, err := httputil.DumpResponse(ctx.Response.httpResp, false)
- if err != nil {
- return
- }
- text := strings.Replace(string(dump), "\r\n", "\n", -1)
- lines := strings.SplitN(text, "\n", 2)
- data.HaveResponse = true
- data.Response = fmt.Sprintf("%s %s\n%s", lines[0], ctx.Response.rtt, lines[1])
- }
- }
- func (f *DefaultFormatter) fillStacktrace(
- data *FormatData, ctx *AssertionContext, failure *AssertionFailure,
- ) {
- data.Stacktrace = []string{}
- switch f.StacktraceMode {
- case StacktraceModeDisabled:
- break
- case StacktraceModeStandard:
- for _, entry := range failure.Stacktrace {
- data.HaveStacktrace = true
- data.Stacktrace = append(data.Stacktrace,
- fmt.Sprintf("%s()\n\t%s:%d +0x%x",
- entry.Func.Name(), entry.File, entry.Line, entry.FuncOffset))
- }
- case StacktraceModeCompact:
- for _, entry := range failure.Stacktrace {
- if entry.IsEntrypoint {
- break
- }
- data.HaveStacktrace = true
- data.Stacktrace = append(data.Stacktrace,
- fmt.Sprintf("%s() at %s:%d (%s)",
- entry.FuncName, filepath.Base(entry.File), entry.Line, entry.FuncPackage))
- }
- }
- }
- func (f *DefaultFormatter) formatValue(value interface{}) string {
- if flt := extractFloat32(value); flt != nil {
- return f.reformatNumber(f.formatFloatValue(*flt, 32))
- }
- if flt := extractFloat64(value); flt != nil {
- return f.reformatNumber(f.formatFloatValue(*flt, 64))
- }
- if refIsNum(value) {
- return f.reformatNumber(fmt.Sprintf("%v", value))
- }
- if !refIsNil(value) && !refIsHTTP(value) {
- if s, _ := value.(fmt.Stringer); s != nil {
- if ss := s.String(); strings.TrimSpace(ss) != "" {
- return ss
- }
- }
- if b, err := json.MarshalIndent(value, "", defaultIndent); err == nil {
- return string(b)
- }
- }
- sq := litter.Options{
- Separator: defaultIndent,
- }
- return sq.Sdump(value)
- }
- func (f *DefaultFormatter) formatFloatValue(value float64, bits int) string {
- switch f.FloatFormat {
- case FloatFormatAuto:
- if _, frac := math.Modf(value); frac != 0 {
- return strconv.FormatFloat(value, 'g', -1, bits)
- } else {
- return strconv.FormatFloat(value, 'f', -1, bits)
- }
- case FloatFormatDecimal:
- return strconv.FormatFloat(value, 'f', -1, bits)
- case FloatFormatScientific:
- return strconv.FormatFloat(value, 'e', -1, bits)
- default:
- return fmt.Sprintf("%v", value)
- }
- }
- func (f *DefaultFormatter) formatTypedValue(value interface{}) string {
- if refIsNum(value) {
- return fmt.Sprintf("%T(%v)", value, f.formatValue(value))
- }
- return fmt.Sprintf("%T(%#v)", value, value)
- }
- func (f *DefaultFormatter) formatMatchValue(value interface{}) string {
- if str := extractString(value); str != nil {
- return *str
- }
- return f.formatValue(value)
- }
- func (f *DefaultFormatter) formatRangeValue(value interface{}) []string {
- if rng := exctractRange(value); rng != nil {
- if refIsNum(rng.Min) && refIsNum(rng.Max) {
- return []string{
- fmt.Sprintf("[%v; %v]", f.formatValue(rng.Min), f.formatValue(rng.Max)),
- }
- } else {
- return []string{
- fmt.Sprintf("%v", rng.Min),
- fmt.Sprintf("%v", rng.Max),
- }
- }
- } else {
- return []string{
- f.formatValue(value),
- }
- }
- }
- func (f *DefaultFormatter) formatListValue(value interface{}) []string {
- if lst := extractList(value); lst != nil {
- s := make([]string, 0, len(*lst))
- for _, e := range *lst {
- s = append(s, f.formatValue(e))
- }
- return s
- } else {
- return []string{
- f.formatValue(value),
- }
- }
- }
- func (f *DefaultFormatter) formatDiff(expected, actual interface{}) (string, bool) {
- differ := gojsondiff.New()
- var diff gojsondiff.Diff
- if ve, ok := expected.(map[string]interface{}); ok {
- if va, ok := actual.(map[string]interface{}); ok {
- diff = differ.CompareObjects(ve, va)
- } else {
- return "", false
- }
- } else if ve, ok := expected.([]interface{}); ok {
- if va, ok := actual.([]interface{}); ok {
- diff = differ.CompareArrays(ve, va)
- } else {
- return "", false
- }
- } else {
- return "", false
- }
- if !diff.Modified() {
- return "", false
- }
- config := formatter.AsciiFormatterConfig{
- ShowArrayIndex: true,
- }
- fa := formatter.NewAsciiFormatter(expected, config)
- str, err := fa.Format(diff)
- if err != nil {
- return "", false
- }
- diffText := "--- expected\n+++ actual\n" + str
- return diffText, true
- }
- func (f *DefaultFormatter) reformatNumber(numStr string) string {
- signPart, intPart, fracPart, expPart := f.decomposeNumber(numStr)
- if intPart == "" {
- return numStr
- }
- var sb strings.Builder
- sb.WriteString(signPart)
- sb.WriteString(f.applySeparator(intPart, -1))
- if fracPart != "" {
- sb.WriteString(".")
- sb.WriteString(f.applySeparator(fracPart, +1))
- }
- if expPart != "" {
- sb.WriteString("e")
- sb.WriteString(expPart)
- }
- return sb.String()
- }
- var (
- decomposeRegexp = regexp.MustCompile(`^([+-])?(\d+)([.](\d+))?([eE]([+-]?\d+))?$`)
- )
- func (f *DefaultFormatter) decomposeNumber(numStr string) (
- signPart, intPart, fracPart, expPart string,
- ) {
- parts := decomposeRegexp.FindStringSubmatch(numStr)
- if len(parts) > 1 {
- signPart = parts[1]
- }
- if len(parts) > 2 {
- intPart = parts[2]
- }
- if len(parts) > 4 {
- fracPart = parts[4]
- }
- if len(parts) > 6 {
- expPart = parts[6]
- }
- return
- }
- func (f *DefaultFormatter) applySeparator(numStr string, dir int) string {
- var separator string
- switch f.DigitSeparator {
- case DigitSeparatorUnderscore:
- separator = "_"
- break
- case DigitSeparatorApostrophe:
- separator = "'"
- break
- case DigitSeparatorComma:
- separator = ","
- break
- case DigitSeparatorNone:
- default:
- return numStr
- }
- var sb strings.Builder
- cnt := 0
- if dir < 0 {
- cnt = len(numStr)
- }
- for i := 0; i != len(numStr); i++ {
- sb.WriteByte(numStr[i])
- cnt += dir
- if cnt%3 == 0 && i != len(numStr)-1 {
- sb.WriteString(separator)
- }
- }
- return sb.String()
- }
- func extractString(value interface{}) *string {
- switch s := value.(type) {
- case string:
- return &s
- default:
- return nil
- }
- }
- func extractFloat32(value interface{}) *float64 {
- switch f := value.(type) {
- case float32:
- ff := float64(f)
- return &ff
- default:
- return nil
- }
- }
- func extractFloat64(value interface{}) *float64 {
- switch f := value.(type) {
- case float64:
- return &f
- default:
- return nil
- }
- }
- func exctractRange(value interface{}) *AssertionRange {
- switch rng := value.(type) {
- case AssertionRange:
- return &rng
- case *AssertionRange: // invalid, but we handle it
- return rng
- default:
- return nil
- }
- }
- func extractList(value interface{}) *AssertionList {
- switch lst := value.(type) {
- case AssertionList:
- return &lst
- case *AssertionList: // invalid, but we handle it
- return lst
- default:
- return nil
- }
- }
- var (
- colorsSupportedOnce sync.Once
- colorsSupportedMode int
- )
- const (
- colorsUnsupported = iota
- colorsSupported
- colorsForced
- )
- func colorMode() int {
- colorsSupportedOnce.Do(func() {
- if s := os.Getenv("FORCE_COLOR"); len(s) != 0 {
- if n, err := strconv.Atoi(s); err == nil && n > 0 {
- colorsSupportedMode = colorsForced
- return
- }
- }
- if (isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())) &&
- len(os.Getenv("NO_COLOR")) == 0 &&
- !strings.HasPrefix(os.Getenv("TERM"), "dumb") {
- colorsSupportedMode = colorsSupported
- return
- }
- colorsSupportedMode = colorsUnsupported
- return
- })
- return colorsSupportedMode
- }
- const (
- defaultIndent = " "
- defaultLineWidth = 60
- )
- var defaultColors = map[string]color.Attribute{
- // regular
- "Black": color.FgBlack,
- "Red": color.FgRed,
- "Green": color.FgGreen,
- "Yellow": color.FgYellow,
- "Magenta": color.FgMagenta,
- "Cyan": color.FgCyan,
- "White": color.FgWhite,
- // bright
- "HiBlack": color.FgHiBlack,
- "HiRed": color.FgHiRed,
- "HiGreen": color.FgHiGreen,
- "HiYellow": color.FgHiYellow,
- "HiMagenta": color.FgHiMagenta,
- "HiCyan": color.FgHiCyan,
- "HiWhite": color.FgHiWhite,
- }
- var defaultTemplateFuncs = template.FuncMap{
- "trim": func(input string) string {
- return strings.TrimSpace(input)
- },
- "indent": func(input string) string {
- var sb strings.Builder
- for _, s := range strings.Split(input, "\n") {
- if sb.Len() != 0 {
- sb.WriteString("\n")
- }
- sb.WriteString(defaultIndent)
- sb.WriteString(s)
- }
- return sb.String()
- },
- "wrap": func(width int, input string) string {
- input = strings.TrimSpace(input)
- width -= len(defaultIndent)
- if width <= 0 {
- return input
- }
- return wordwrap.WrapString(input, uint(width))
- },
- "join": func(width int, tokenList []string) string {
- width -= len(defaultIndent)
- if width <= 0 {
- return strings.Join(tokenList, ".")
- }
- var sb strings.Builder
- lineLen := 0
- lineNum := 0
- write := func(s string) {
- sb.WriteString(s)
- lineLen += len(s)
- }
- for n, token := range tokenList {
- if lineLen+len(token)+1 > width {
- write("\n")
- lineLen = 0
- if lineNum < 2 {
- lineNum++
- }
- }
- if lineLen == 0 {
- for l := 0; l < lineNum; l++ {
- write(defaultIndent)
- }
- }
- write(token)
- if n != len(tokenList)-1 {
- write(".")
- }
- }
- return sb.String()
- },
- "color": func(enable bool, colorName, input string) string {
- if !enable {
- return input
- }
- colorAttr := color.Reset
- if ca, ok := defaultColors[colorName]; ok {
- colorAttr = ca
- }
- return color.New(colorAttr).Sprint(input)
- },
- "colordiff": func(enable bool, input string) string {
- if !enable {
- return input
- }
- prefixColor := []struct {
- prefix string
- color color.Attribute
- }{
- {"---", color.FgWhite},
- {"+++", color.FgWhite},
- {"-", color.FgRed},
- {"+", color.FgGreen},
- }
- lineColor := func(s string) color.Attribute {
- for _, pc := range prefixColor {
- if strings.HasPrefix(s, pc.prefix) {
- return pc.color
- }
- }
- return color.Reset
- }
- var sb strings.Builder
- for _, line := range strings.Split(input, "\n") {
- if sb.Len() != 0 {
- sb.WriteString("\n")
- }
- sb.WriteString(color.New(lineColor(line)).Sprint(line))
- }
- return sb.String()
- },
- }
- var defaultSuccessTemplate = `[OK] {{ join .LineWidth .AssertPath }}`
- var defaultFailureTemplate = `
- {{- range $n, $err := .Errors }}
- {{ if eq $n 0 -}}
- {{ $err | wrap $.LineWidth | color $.EnableColors "Red" }}
- {{- else -}}
- {{ $err | wrap $.LineWidth | indent | color $.EnableColors "Red" }}
- {{- end -}}
- {{- end -}}
- {{- if .TestName }}
- test name: {{ .TestName | color $.EnableColors "Cyan" }}
- {{- end -}}
- {{- if .RequestName }}
- request name: {{ .RequestName | color $.EnableColors "Cyan" }}
- {{- end -}}
- {{- if .HaveRequest }}
- request: {{ .Request | indent | trim | color $.EnableColors "HiMagenta" }}
- {{- end -}}
- {{- if .HaveResponse }}
- response: {{ .Response | indent | trim | color $.EnableColors "HiMagenta" }}
- {{- end -}}
- {{- if .HaveStacktrace }}
- trace:
- {{- range $n, $call := .Stacktrace }}
- {{ $call | indent }}
- {{- end -}}
- {{- end -}}
- {{- if .AssertPath }}
- assertion:
- {{ join .LineWidth .AssertPath | indent | color .EnableColors "Yellow" }}
- {{- end -}}
- {{- if .HaveExpected }}
- {{ if .IsNegation }}denied
- {{- else if .IsComparison }}compared
- {{- else }}expected
- {{- end }} {{ .ExpectedKind }}:
- {{- range $n, $exp := .Expected }}
- {{ $exp | indent | color $.EnableColors "HiMagenta" }}
- {{- end -}}
- {{- end -}}
- {{- if .HaveActual }}
- actual value:
- {{ .Actual | indent | color .EnableColors "HiMagenta" }}
- {{- end -}}
- {{- if .HaveReference }}
- reference value:
- {{ .Reference | indent | color .EnableColors "HiMagenta" }}
- {{- end -}}
- {{- if .HaveDelta }}
- allowed delta:
- {{ .Delta | indent | color .EnableColors "HiMagenta" }}
- {{- end -}}
- {{- if .HaveDiff }}
- diff:
- {{ .Diff | colordiff .EnableColors | indent }}
- {{- end -}}
- `
|