Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

func.gno

4.77 Kb · 219 lines
  1package expect
  2
  3import "gno.land/p/nt/ufmt/v0"
  4
  5type (
  6	// Fn defines a type for generic functions.
  7	Fn = func()
  8
  9	// ErrorFn defines a type for generic functions that return an error.
 10	ErrorFn = func() error
 11
 12	// AnyFn defines a type for generic functions that returns a value.
 13	AnyFn = func() any
 14
 15	// AnyErrorFn defines a type for generic functions that return a value and an error.
 16	AnyErrorFn = func() (any, error)
 17)
 18
 19// Func creates a new checker for functions.
 20func Func(t TestingT, fn any) FuncChecker {
 21	return FuncChecker{
 22		ctx: NewContext(t),
 23		fn:  fn,
 24	}
 25}
 26
 27// FuncChecker asserts function panics, errors and returned value.
 28type FuncChecker struct {
 29	ctx Context
 30	fn  any
 31}
 32
 33// WithFailPrefix assigns a prefix that will be prefixed to testing errors when an assertion fails.
 34func (c FuncChecker) WithFailPrefix(prefix string) FuncChecker {
 35	c.ctx.prefix = prefix
 36	return c
 37}
 38
 39// Not negates the next called expectation.
 40func (c FuncChecker) Not() FuncChecker {
 41	c.ctx.negated = !c.ctx.negated
 42	return c
 43}
 44
 45// ToFail return an error checker to assert if current function returns an error.
 46func (c FuncChecker) ToFail() ErrorChecker {
 47	c.ctx.T().Helper()
 48
 49	var err error
 50	switch fn := c.fn.(type) {
 51	case ErrorFn:
 52		err = fn()
 53	case AnyErrorFn:
 54		_, err = fn()
 55	default:
 56		c.ctx.Fail("Unsupported error func type\nGot: %T", c.fn)
 57		return ErrorChecker{}
 58	}
 59
 60	c.ctx.CheckExpectation(err != nil, func(ctx Context) string {
 61		if !ctx.IsNegated() {
 62			return "Expected func to return an error"
 63		}
 64		return ufmt.Sprintf("Func failed with error\nGot: %s", err.Error())
 65	})
 66
 67	return NewErrorChecker(c.ctx, err)
 68}
 69
 70// ToPanic return an message checker to assert if current function panicked.
 71// This assertion is handled within the same realm, to assert panics when crossing
 72// to another realm use the `ToAbort()` assertion.
 73//
 74// Example usage:
 75//
 76//	func TestFoo(t *testing.T) {
 77//	  expect.Func(t, func() {
 78//	    Foo(cross)
 79//	  }).Not().ToCrossPanic()
 80//	}
 81func (c FuncChecker) ToPanic() MessageChecker {
 82	c.ctx.T().Helper()
 83
 84	var (
 85		msg      string
 86		panicked bool
 87	)
 88
 89	switch fn := c.fn.(type) {
 90	case Fn:
 91		msg, panicked = handlePanic(fn)
 92	case ErrorFn:
 93		msg, panicked = handlePanic(func() { _ = fn() })
 94	case AnyFn:
 95		msg, panicked = handlePanic(func() { _ = fn() })
 96	case AnyErrorFn:
 97		msg, panicked = handlePanic(func() { _, _ = fn() })
 98	default:
 99		c.ctx.Fail("Unsupported func type\nGot: %T", c.fn)
100		return MessageChecker{}
101	}
102
103	c.ctx.CheckExpectation(panicked, func(ctx Context) string {
104		if !ctx.IsNegated() {
105			return "Expected function to panic"
106		}
107		return ufmt.Sprintf("Expected func not to panic\nGot: %s", msg)
108	})
109
110	return NewMessageChecker(c.ctx, msg, MessageTypePanic)
111}
112
113// ToCrossPanic return an message checker to assert if current function panicked when crossing.
114// This assertion is handled only when making a crossing call to another realm, when asserting
115// within the same realm use `ToPanic()`.
116func (c FuncChecker) ToCrossPanic() MessageChecker {
117	c.ctx.T().Helper()
118
119	var (
120		msg      string
121		panicked bool
122	)
123
124	switch fn := c.fn.(type) {
125	case Fn:
126		msg, panicked = handleCrossPanic(fn)
127	case ErrorFn:
128		msg, panicked = handleCrossPanic(func() { _ = fn() })
129	case AnyFn:
130		msg, panicked = handleCrossPanic(func() { _ = fn() })
131	case AnyErrorFn:
132		msg, panicked = handleCrossPanic(func() { _, _ = fn() })
133	default:
134		c.ctx.Fail("Unsupported func type\nGot: %T", c.fn)
135		return MessageChecker{}
136	}
137
138	c.ctx.CheckExpectation(panicked, func(ctx Context) string {
139		if !ctx.IsNegated() {
140			return "Expected function to cross panic"
141		}
142		return ufmt.Sprintf("Expected func not to cross panic\nGot: %s", msg)
143	})
144
145	return NewMessageChecker(c.ctx, msg, MessageTypeCrossPanic)
146}
147
148// ToReturn asserts that current function returned a value equal to an expected value.
149func (c FuncChecker) ToReturn(value any) {
150	c.ctx.T().Helper()
151
152	var (
153		err error
154		v   any
155	)
156
157	if fn, ok := c.fn.(AnyFn); ok {
158		v = fn()
159	} else if fn, ok := c.fn.(AnyErrorFn); ok {
160		v, err = fn()
161	} else {
162		c.ctx.Fail("Unsupported func type\nGot: %T", c.fn)
163		return
164	}
165
166	if err != nil {
167		c.ctx.Fail("Function returned unexpected error\nGot: %s", err.Error())
168		return
169	}
170
171	if c.ctx.negated {
172		Value(c.ctx.T(), v).Not().ToEqual(value)
173	} else {
174		Value(c.ctx.T(), v).ToEqual(value)
175	}
176}
177
178func handlePanic(fn func()) (msg string, panicked bool) {
179	defer func() {
180		r := recover()
181		if r == nil {
182			return
183		}
184
185		panicked = true
186
187		if err, ok := r.(error); ok {
188			msg = err.Error()
189			return
190		}
191
192		if s, ok := r.(string); ok {
193			msg = s
194			return
195		}
196
197		msg = "unsupported panic type"
198	}()
199
200	fn()
201	return
202}
203
204func handleCrossPanic(fn func()) (string, bool) {
205	r := revive(fn)
206	if r == nil {
207		return "", false
208	}
209
210	if err, ok := r.(error); ok {
211		return err.Error(), true
212	}
213
214	if s, ok := r.(string); ok {
215		return s, true
216	}
217
218	return "unsupported panic type", true
219}