func ElementsEqualUnderFn(tb testing.TB, expecteds interface{}, actuals interface{}, f func(interface{}) interface{}, msgAndArgs ...interface{}) {
	tb.Helper()
	as := reflect.ValueOf(actuals)
	es := reflect.ValueOf(expecteds)

	// Check if 'actuals' is empty; if so, just pass nil (no need to transform)
	if actuals != nil && !as.IsNil() && as.Kind() != reflect.Slice {
		fatal(tb, msgAndArgs, fmt.Sprintf("\"actuals\" must be a slice, but was %s", as.Type().String()))
	} else if actuals == nil || as.IsNil() || as.Len() == 0 {
		// Just pass 'nil' for 'actuals'
		if err := ElementsEqualOrErr(expecteds, nil); err != nil {
			fatal(tb, msgAndArgs, err.Error())
		}
		return
	}

	// Check if 'expecteds' is empty: if so, return an error (since 'actuals' is
	// not empty)
	if expecteds != nil && !es.IsNil() && es.Kind() != reflect.Slice {
		fatal(tb, msgAndArgs, fmt.Sprintf("\"expecteds\" must be a slice, but was %s", as.Type().String()))
	} else if expecteds == nil || es.IsNil() || es.Len() == 0 {
		fatal(tb, msgAndArgs, fmt.Sprintf("expected 0 distinct elements, but got %d\n elements (before function is applied): %v", as.Len(), actuals))
	}

	// Neither 'expecteds' nor 'actuals' is empty--apply 'f' to 'actuals'
	newActuals := reflect.MakeSlice(reflect.SliceOf(es.Type().Elem()), as.Len(), as.Len())
	for i := 0; i < as.Len(); i++ {
		newActuals.Index(i).Set(reflect.ValueOf(f(as.Index(i).Interface())))
	}
	if err := ElementsEqualOrErr(expecteds, newActuals.Interface()); err != nil {
		fatal(tb, msgAndArgs, err.Error())
	}
}
func ElementsEqualUnderFn(tb testing.TB, expecteds interface{}, actuals interface{}, f func(interface{}) interface{}, msgAndArgs ...interface{}) {
	tb.Helper()
	as := reflect.ValueOf(actuals)
	es := reflect.ValueOf(expecteds)

	// Check if 'actuals' is empty; if so, just pass nil (no need to transform)
	if actuals != nil && !as.IsNil() && as.Kind() != reflect.Slice {
		fatal(tb, msgAndArgs, fmt.Sprintf("\"actuals\" must be a slice, but was %s", as.Type().String()))
	} else if actuals == nil || as.IsNil() || as.Len() == 0 {
		// Just pass 'nil' for 'actuals'
		if err := ElementsEqualOrErr(expecteds, nil); err != nil {
			fatal(tb, msgAndArgs, err.Error())
		}
		return
	}

	// Check if 'expecteds' is empty: if so, return an error (since 'actuals' is
	// not empty)
	if expecteds != nil && !es.IsNil() && es.Kind() != reflect.Slice {
		fatal(tb, msgAndArgs, fmt.Sprintf("\"expecteds\" must be a slice, but was %s", as.Type().String()))
	} else if expecteds == nil || es.IsNil() || es.Len() == 0 {
		fatal(tb, msgAndArgs, fmt.Sprintf("expected 0 distinct elements, but got %d\n elements (before function is applied): %v", as.Len(), actuals))
	}

	// Neither 'expecteds' nor 'actuals' is empty--apply 'f' to 'actuals'
	newActuals := reflect.MakeSlice(reflect.SliceOf(es.Type().Elem()), as.Len(), as.Len())
	for i := 0; i < as.Len(); i++ {
		newActuals.Index(i).Set(reflect.ValueOf(f(as.Index(i).Interface())))
	}
	if err := ElementsEqualOrErr(expecteds, newActuals.Interface()); err != nil {
		fatal(tb, msgAndArgs, err.Error())
	}
}
// ElementsEqualUnderFn checks that the elements of the slice 'expecteds' are
// exactly the images of every element of the slice 'actuals' under 'f',
// ignoring order (i.e.  'expecteds' and 'map(f, actuals)' are setwise-equal,
// but respecting duplicates). This is useful for cases where ElementsEqual
// doesn't quite work, e.g. because the type in 'expecteds'/'actuals' contains a
// pointer, or 'actuals' contains superfluous data which you wish to discard
//
// Like ElementsEqual, treat 'nil' and the empty slice as equivalent (for
// convenience)