/
td_catch.go
133 lines (116 loc) · 3.62 KB
/
td_catch.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// Copyright (c) 2019, Maxime Soulé
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree.
package td
import (
"reflect"
"github.com/maxatome/go-testdeep/internal/ctxerr"
"github.com/maxatome/go-testdeep/internal/types"
"github.com/maxatome/go-testdeep/internal/util"
)
type tdCatch struct {
tdSmugglerBase
target reflect.Value
}
var _ TestDeep = &tdCatch{}
// summary(Catch): catches data on the fly before comparing it
// input(Catch): all
// Catch is a smuggler operator. It allows to copy data in target on
// the fly before comparing it as usual against expectedValue.
//
// target must be a non-nil pointer and data should be assignable to
// its pointed type. If BeLax config flag is true or called under [Lax]
// (and so [JSON]) operator, data should be convertible to its pointer
// type.
//
// var id int64
// if td.Cmp(t, CreateRecord("test"),
// td.JSON(`{"id": $1, "name": "test"}`, td.Catch(&id, td.NotZero()))) {
// t.Logf("Created record ID is %d", id)
// }
//
// It is really useful when used with [JSON] operator and/or [tdhttp] helper.
//
// var id int64
// ta := tdhttp.NewTestAPI(t, api.Handler).
// PostJSON("/item", `{"name":"foo"}`).
// CmpStatus(http.StatusCreated).
// CmpJSONBody(td.JSON(`{"id": $1, "name": "foo"}`, td.Catch(&id, td.Gt(0))))
// if !ta.Failed() {
// t.Logf("Created record ID is %d", id)
// }
//
// If you need to only catch data without comparing it, use [Ignore]
// operator as expectedValue as in:
//
// var id int64
// if td.Cmp(t, CreateRecord("test"),
// td.JSON(`{"id": $1, "name": "test"}`, td.Catch(&id, td.Ignore()))) {
// t.Logf("Created record ID is %d", id)
// }
//
// TypeBehind method returns the [reflect.Type] of expectedValue,
// except if expectedValue is a [TestDeep] operator. In this case, it
// delegates TypeBehind() to the operator, but if nil is returned by
// this call, the dereferenced [reflect.Type] of target is returned.
//
// [tdhttp]: https://pkg.go.dev/github.com/maxatome/go-testdeep/helpers/tdhttp
func Catch(target, expectedValue any) TestDeep {
vt := reflect.ValueOf(target)
c := tdCatch{
tdSmugglerBase: newSmugglerBase(expectedValue),
target: vt,
}
if vt.Kind() != reflect.Ptr || vt.IsNil() || !vt.Elem().CanSet() {
c.err = ctxerr.OpBadUsage("Catch", "(NON_NIL_PTR, EXPECTED_VALUE)", target, 1, true)
return &c
}
if !c.isTestDeeper {
c.expectedValue = reflect.ValueOf(expectedValue)
}
return &c
}
func (c *tdCatch) Match(ctx ctxerr.Context, got reflect.Value) *ctxerr.Error {
if c.err != nil {
return ctx.CollectError(c.err)
}
if targetType := c.target.Elem().Type(); !got.Type().AssignableTo(targetType) {
if !ctx.BeLax || !types.IsConvertible(got, targetType) {
if ctx.BooleanError {
return ctxerr.BooleanError
}
return ctx.CollectError(ctxerr.TypeMismatch(got.Type(), c.target.Elem().Type()))
}
c.target.Elem().Set(got.Convert(targetType))
} else {
c.target.Elem().Set(got)
}
return deepValueEqual(ctx, got, c.expectedValue)
}
func (c *tdCatch) String() string {
if c.err != nil {
return c.stringError()
}
if c.isTestDeeper {
return c.expectedValue.Interface().(TestDeep).String()
}
return util.ToString(c.expectedValue)
}
func (c *tdCatch) TypeBehind() reflect.Type {
if c.err != nil {
return nil
}
if c.isTestDeeper {
if typ := c.expectedValue.Interface().(TestDeep).TypeBehind(); typ != nil {
return typ
}
// Operator unknown type behind, fallback on target dereferenced type
return c.target.Type().Elem()
}
if c.expectedValue.IsValid() {
return c.expectedValue.Type()
}
return nil
}