estimator_checks_generator does not return (estimator, check) when hitting an expected failed check

### Describe the bug

Currently running sklearn_check_generator with mark="skip" on our estimators.

https://scikit-learn.org/stable/modules/generated/sklearn.utils.estimator_checks.estimator_checks_generator.html

I would like to start running those checks with "xfail".

But when I do so any test 'marked' via the `expected_failed_checks` dictionary gives a 
`ValueError: too many values to unpack (expected 2)`

### Steps/Code to Reproduce

```python
from sklearn.utils.estimator_checks import estimator_checks_generator
from sklearn.base import BaseEstimator

expected_failed_checks = {"check_complex_data": "some reason"}

estimator = BaseEstimator()

# fine
for e, check in estimator_checks_generator(
                estimator=estimator,
                expected_failed_checks=expected_failed_checks, 
                mark="skip"
            ):
    print(check)

# error
for e, check in estimator_checks_generator(
                estimator=estimator,
                expected_failed_checks=expected_failed_checks, 
                mark="xfail"
            ):
    print(check)
```

### Expected Results

estimator_checks_generator to yield (estimator, check) tuples whether xfail or skip was requested

### Actual Results

```
functools.partial(<function check_estimator_cloneable at 0x7ec1883e5120>, 'BaseEstimator')
functools.partial(<function check_estimator_cloneable at 0x7ec1883e5120>, 'BaseEstimator')
functools.partial(<function check_estimator_tags_renamed at 0x7ec1883e62a0>, 'BaseEstimator')
functools.partial(<function check_valid_tag_types at 0x7ec1883e6200>, 'BaseEstimator')
functools.partial(<function check_estimator_repr at 0x7ec1883e51c0>, 'BaseEstimator')
functools.partial(<function check_no_attributes_set_in_init at 0x7ec1883e4b80>, 'BaseEstimator')
functools.partial(<function check_fit_score_takes_y at 0x7ec1883da0c0>, 'BaseEstimator')
functools.partial(<function check_estimators_overwrite_params at 0x7ec1883e4a40>, 'BaseEstimator')
functools.partial(<function check_dont_overwrite_parameters at 0x7ec1883d8e00>, 'BaseEstimator')
functools.partial(<function check_estimators_fit_returns_self at 0x7ec1883dbb00>, 'BaseEstimator')
functools.partial(<function check_readonly_memmap_input at 0x7ec1883dbc40>, 'BaseEstimator')
functools.partial(<function check_estimators_unfitted at 0x7ec1883dbd80>, 'BaseEstimator')
functools.partial(<function check_do_not_raise_errors_in_init_or_set_params at 0x7ec1883e6d40>, 'BaseEstimator')
functools.partial(<function check_n_features_in_after_fitting at 0x7ec1883e6160>, 'BaseEstimator')
functools.partial(<function check_mixin_order at 0x7ec1883d9f80>, 'BaseEstimator')
functools.partial(<function check_positive_only_tag_during_fit at 0x7ec1883e54e0>, 'BaseEstimator')
functools.partial(<function check_estimators_dtypes at 0x7ec1883da200>, 'BaseEstimator')
<function _maybe_mark.<locals>.wrapped at 0x7ec20dbda2a0>
functools.partial(<function check_dtype_object at 0x7ec1883d8a40>, 'BaseEstimator')
functools.partial(<function check_estimators_empty_data_messages at 0x7ec1883da3e0>, 'BaseEstimator')
functools.partial(<function check_pipeline_consistency at 0x7ec1883d9e40>, 'BaseEstimator')
functools.partial(<function check_estimators_nan_inf at 0x7ec1883da520>, 'BaseEstimator')
functools.partial(<function check_estimator_sparse_tag at 0x7ec1883bbe20>, 'BaseEstimator')
functools.partial(<function check_estimator_sparse_array at 0x7ec1883d8040>, 'BaseEstimator')
functools.partial(<function check_estimator_sparse_matrix at 0x7ec1883bbf60>, 'BaseEstimator')
functools.partial(<function check_estimators_pickle at 0x7ec1883da7a0>, 'BaseEstimator')
functools.partial(<function check_estimators_pickle at 0x7ec1883da7a0>, 'BaseEstimator', readonly_memmap=True)
functools.partial(<function check_f_contiguous_array_estimator at 0x7ec1883d80e0>, 'BaseEstimator')
functools.partial(<function check_parameters_default_constructible at 0x7ec1883e5260>, 'BaseEstimator')
functools.partial(<function check_methods_sample_order_invariance at 0x7ec1883d9260>, 'BaseEstimator')
functools.partial(<function check_methods_subset_invariance at 0x7ec1883d9120>, 'BaseEstimator')
functools.partial(<function check_fit2d_1sample at 0x7ec1883d9440>, 'BaseEstimator')
functools.partial(<function check_fit2d_1feature at 0x7ec1883d9620>, 'BaseEstimator')
functools.partial(<function check_get_params_invariance at 0x7ec1883e58a0>, 'BaseEstimator')
functools.partial(<function check_set_params at 0x7ec1883e59e0>, 'BaseEstimator')
functools.partial(<function check_dict_unchanged at 0x7ec1883d8c20>, 'BaseEstimator')
functools.partial(<function check_fit_idempotent at 0x7ec1883e5e40>, 'BaseEstimator')
functools.partial(<function check_fit_check_is_fitted at 0x7ec1883e5ee0>, 'BaseEstimator')
functools.partial(<function check_n_features_in at 0x7ec1883e5f80>, 'BaseEstimator')
functools.partial(<function check_fit1d at 0x7ec1883d9760>, 'BaseEstimator')
functools.partial(<function check_fit2d_predict1d at 0x7ec1883d8f40>, 'BaseEstimator')
functools.partial(<function check_estimator_cloneable at 0x7ec1883e5120>, 'BaseEstimator')
functools.partial(<function check_estimator_cloneable at 0x7ec1883e5120>, 'BaseEstimator')
functools.partial(<function check_estimator_tags_renamed at 0x7ec1883e62a0>, 'BaseEstimator')
functools.partial(<function check_valid_tag_types at 0x7ec1883e6200>, 'BaseEstimator')
functools.partial(<function check_estimator_repr at 0x7ec1883e51c0>, 'BaseEstimator')
functools.partial(<function check_no_attributes_set_in_init at 0x7ec1883e4b80>, 'BaseEstimator')
functools.partial(<function check_fit_score_takes_y at 0x7ec1883da0c0>, 'BaseEstimator')
functools.partial(<function check_estimators_overwrite_params at 0x7ec1883e4a40>, 'BaseEstimator')
functools.partial(<function check_dont_overwrite_parameters at 0x7ec1883d8e00>, 'BaseEstimator')
functools.partial(<function check_estimators_fit_returns_self at 0x7ec1883dbb00>, 'BaseEstimator')
functools.partial(<function check_readonly_memmap_input at 0x7ec1883dbc40>, 'BaseEstimator')
functools.partial(<function check_estimators_unfitted at 0x7ec1883dbd80>, 'BaseEstimator')
functools.partial(<function check_do_not_raise_errors_in_init_or_set_params at 0x7ec1883e6d40>, 'BaseEstimator')
functools.partial(<function check_n_features_in_after_fitting at 0x7ec1883e6160>, 'BaseEstimator')
functools.partial(<function check_mixin_order at 0x7ec1883d9f80>, 'BaseEstimator')
functools.partial(<function check_positive_only_tag_during_fit at 0x7ec1883e54e0>, 'BaseEstimator')
functools.partial(<function check_estimators_dtypes at 0x7ec1883da200>, 'BaseEstimator')
Traceback (most recent call last):
  File "/home/remi-gau/github/nilearn/nilearn/tmp/sk.py", line 17, in <module>
    for e, check in estimator_checks_generator(
        ^^^^^^^^
ValueError: too many values to unpack (expected 2)
```

### Versions

```shell
checked from 1.6.0 to 1.7.0rc1
```

COMMENT:
Currently this is how I use it:

https://github.com/nilearn/nilearn/blob/c95672634b52f789fec1003142d29fab100c10c6/nilearn/_utils/estimator_checks.py#L258-L317

```python
        if SKLEARN_GT_1_5:
            tags = est.__sklearn_tags__()


            niimg_input = getattr(tags.input_tags, "niimg_like", False)
            surf_img = getattr(tags.input_tags, "surf_img", False)


            if niimg_input or surf_img:
                if expected_failed_checks is None:
                    expected_failed_checks = CHECKS_TO_SKIP_IF_IMG_INPUT
                else:
                    expected_failed_checks |= CHECKS_TO_SKIP_IF_IMG_INPUT


            for e, check in sklearn_check_generator(
                estimator=est,
                expected_failed_checks=expected_failed_checks,
                # TODO use  mark="xfail"
                # once using only expected_failed_checks and no valid_checks
                mark="skip",
            ):
                # DANGER
                # must rely on sklearn private function _check_name
                # to get name of the check:
                # things may break with no deprecation warning
                name = _check_name(check)


                if valid and name in valid_checks:
                    yield e, check, name
                if not valid and name not in valid_checks:
                    yield e, check, name


        else:
            for e, check in sklearn_check_estimator(
                estimator=est, generate_only=True
            ):
                tags = est._more_tags()


                niimg_input = "niimg_like" in tags["X_types"]
                surf_img = "surf_img" in tags["X_types"]


                if niimg_input or surf_img:
                    if expected_failed_checks is None:
                        expected_failed_checks = CHECKS_TO_SKIP_IF_IMG_INPUT
                    else:
                        expected_failed_checks |= CHECKS_TO_SKIP_IF_IMG_INPUT


                if (
                    isinstance(expected_failed_checks, dict)
                    and check.func.__name__ in expected_failed_checks
                ):
                    continue


                if valid and check.func.__name__ in valid_checks:
                    yield e, check, check.func.__name__
                if not valid and check.func.__name__ not in valid_checks:
                    yield e, check, check.func.__name__


    if valid:
        for est in estimator:
            for e, check in nilearn_check_estimator(estimator=est):
                yield e, check, check.__name__
```

Part of the issue is that we are running test with the latest sklearn that supports expected_failed_checks, but also with older versions (>=1.4.0) that do not support this, originally I hacked something to handle this.

I am not using sklearn check_estimator directly or its decorator in my tests, but am just generating the checks and running them "myself".

For example:

https://github.com/nilearn/nilearn/blob/c95672634b52f789fec1003142d29fab100c10c6/nilearn/maskers/tests/test_nifti_masker.py#L26-L48

```python
@pytest.mark.parametrize(
    "estimator, check, name",
    check_estimator(
        estimator=[NiftiMasker()],
        expected_failed_checks=expected_failed_checks_0pt13pt2(),
    ),
)
def test_check_estimator(estimator, check, name):  # noqa: ARG001
    """Check compliance with sklearn estimators."""
    check(estimator)




@pytest.mark.xfail(reason="invalid checks should fail")
@pytest.mark.parametrize(
    "estimator, check, name",
    check_estimator(
        estimator=[NiftiMasker()],
        valid=False,
    ),
)
def test_check_estimator_invalid(estimator, check, name):  # noqa: ARG001
    """Check compliance with sklearn estimators."""
    check(estimator)
```

This setup allowed me to have a single place where I checked the sklearn version rather having to do that for every estimator that I test.

I will try find a workaround, though any suggestion is welcome.

Labels: Bug, Needs Investigation