Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

assert: accept values with named types in InDelta*, InEpsilon* #1492

Open
RemiMattheyDoret opened this issue Oct 25, 2023 · 2 comments
Open
Assignees
Labels
assert.InEpsilon About assert.InEpsilon and family enhancement pkg-assert Change related to package testify/assert pkg-require Change related to package testify/require
Milestone

Comments

@RemiMattheyDoret
Copy link

RemiMattheyDoret commented Oct 25, 2023

Issue

The function toFloat converts into a float64 all int, uint and float types. However, toFloat does not support conversion from a type whose underlying type is one of those accepted types. Consider for example

type f float64
value, ok := toFloat(f(1.2)) // returns 0.0, false

and therefore some assertions may fail such as

var (
    expected f = 1.1
    actual f = 1.1
)
assert.InEpsilon(t, expected, actual, 1e-12)

who fails with message Parameters must be numerical. The user is then forced to cast the input values

assert.InEpsilon(t, float64(expected), float64(actual), 1e-12)

⁉️ Note that this is particularly problematic that testifylint enforces to use InDelta/InEpsilon for a type derived from float64.

Question

Would it be desirable to allow toFloat to recognise types derived from any of the currently accepted types?

Suggestion

func toFloat(x interface{}) (float64, bool) {
	var xf float64
	xok := true

	switch reflect.TypeOf(x).Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		xf = float64(reflect.ValueOf(x).Int())
	case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		xf = float64(reflect.ValueOf(x).Uint())
	case reflect.Float32, reflect.Float64:
		xf = reflect.ValueOf(x).Float()
	default:
		xok = false
	}

	return xf, xok
}

Consideration

Performance must be considered. Two successive switch-case blocks (the first one not using reflection) may be of interest to retain high performance for cases where the type received is directly numerical (and not just derived from a numerical type).

@RemiMattheyDoret RemiMattheyDoret changed the title feat: toFloat converts types whose underlying type is a float64 feat: toFloat converts types whose underlying type is a int, uint or float Oct 25, 2023
@RemiMattheyDoret RemiMattheyDoret changed the title feat: toFloat converts types whose underlying type is a int, uint or float proposal: toFloat converts types whose underlying type is a int, uint or float Nov 7, 2023
@dolmen
Copy link
Collaborator

dolmen commented Nov 23, 2023

@RemiMattheyDoret This proposal looks good. However the existing type switch must be preserved (for speed) and the switch on reflect.Kind must be added in the existing default case (like mentioned in the "Consideration" section).

Also: because this change will allow us to accept typed numbers in InDelta* and InEpsilon*, I think that we must enforce that if the type of the expected value has a non-anonymous type, the actual value must be exactly of that type. So changes in InDelta and InExpsilon are expected too:

if reflect.TypeOf(expected).PkgPath() != "" {
    IsType(t, expected, actual)
}

Both changes must happen at the same time: if we introduce the toFloat change first, the type check applied later would be then a breaking change.

As this change extends the behavior it will have to happen on a minor (not patch) release.

@dolmen dolmen added enhancement pkg-assert Change related to package testify/assert assert.InEpsilon About assert.InEpsilon and family labels Nov 23, 2023
@dolmen
Copy link
Collaborator

dolmen commented Nov 23, 2023

Test case: https://go.dev/play/p/lGe4Y-2aBfz

func Test(t *testing.T) {
	type F64 float64

	assert.InDelta(t, F64(1.0), F64(1.0), 0.1) // This should succeed
	assert.InDelta(t, 1.0, F64(1.0), 0.1)      // This should succeed
	assert.InDelta(t, F64(1.0), F64(1.0), 0.1) // This should fail: not the expected type

	assert.InEpsilon(t, F64(1.0), F64(1.0), 0.1) // This should succeed
	assert.InEpsilon(t, 1.0, F64(1.0), 0.1)      // This should succeed
	assert.InEpsilon(t, F64(1.0), F64(1.0), 0.1) // This should fail: not the expected type
}

So far all tests fail with "Parameters must be numerical".

@dolmen dolmen changed the title proposal: toFloat converts types whose underlying type is a int, uint or float assert: accept values with named types in InDelta*, InEpsilon* Nov 23, 2023
@dolmen dolmen added the pkg-require Change related to package testify/require label Nov 23, 2023
@brackendawson brackendawson modified the milestones: v1.9.0, v1.10 Feb 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
assert.InEpsilon About assert.InEpsilon and family enhancement pkg-assert Change related to package testify/assert pkg-require Change related to package testify/require
Projects
None yet
Development

No branches or pull requests

3 participants