ronantakizawa/codereview-bench huggingface dataset:
Repository: numpy/numpy
File: numpy/tests/test_numpy_config.py (Python)

Code before:
"""
Check the numpy config is valid.
"""
from unittest.mock import patch

import pytest

import numpy as np

pytestmark = pytest.mark.skipif(
    not hasattr(np.__config__, "_built_with_meson"),
    reason="Requires Meson builds",
)


class TestNumPyConfigs:
    REQUIRED_CONFIG_KEYS = [
        "Compilers",
        "Machine Information",
        "Python Information",
    ]

    @patch("numpy.__config__._check_pyyaml")
    @pytest.mark.thread_unsafe(reason="unittest.mock.patch thread-unsafe?")
    def test_pyyaml_not_found(self, mock_yaml_importer):
        mock_yaml_importer.side_effect = ModuleNotFoundError()
        with pytest.warns(UserWarning):
            np.show_config()

    def test_dict_mode(self):
        config = np.show_config(mode="dicts")

        assert isinstance(config, dict)
        assert all(key in config for key in self.REQUIRED_CONFIG_KEYS), (
            "Required key missing,"
            " see index of `False` with `REQUIRED_CONFIG_KEYS`"
        )

    def test_invalid_mode(self):
        with pytest.raises(AttributeError):
            np.show_config(mode="foo")

    def test_warn_to_add_tests(self):
        assert len(np.__config__.DisplayModes) == 2, (
            "New mode detected,"
            " please add UT if applicable and increment this count"
        )


Review comment (suggestion):
```suggestion
    @pytest.mark.thread_unsafe(reason="unittest.mock.patch thread-unsafe")
```

No need to be uncertain, it's unsafe. This is monkeypatching.

Code after:
"""
Check the numpy config is valid.
"""
from unittest.mock import patch

import pytest

import numpy as np

pytestmark = pytest.mark.skipif(
    not hasattr(np.__config__, "_built_with_meson"),
    reason="Requires Meson builds",
)


class TestNumPyConfigs:
    REQUIRED_CONFIG_KEYS = [
        "Compilers",
        "Machine Information",
        "Python Information",
    ]

    @patch("numpy.__config__._check_pyyaml")
    @pytest.mark.thread_unsafe(reason="unittest.mock.patch updates global state")
    def test_pyyaml_not_found(self, mock_yaml_importer):
        mock_yaml_importer.side_effect = ModuleNotFoundError()
        with pytest.warns(UserWarning):
            np.show_config()

    def test_dict_mode(self):
        config = np.show_config(mode="dicts")

        assert isinstance(config, dict)
        assert all(key in config for key in self.REQUIRED_CONFIG_KEYS), (
            "Required key missing,"
            " see index of `False` with `REQUIRED_CONFIG_KEYS`"
        )

    def test_invalid_mode(self):
        with pytest.raises(AttributeError):
            np.show_config(mode="foo")

    def test_warn_to_add_tests(self):
        assert len(np.__config__.DisplayModes) == 2, (
            "New mode detected,"
            " please add UT if applicable and increment this count"
        )
Repository: numpy/numpy
File: numpy/typing/tests/test_isfile.py (Python)

Code before:
import pytest

import numpy as np
from numpy.testing import assert_

ROOT = Path(np.__file__).parents[0]
FILES = [
    ROOT / "py.typed",
    ROOT / "__init__.pyi",
    ROOT / "ctypeslib" / "__init__.pyi",
    ROOT / "_core" / "__init__.pyi",
    ROOT / "f2py" / "__init__.pyi",
    ROOT / "fft" / "__init__.pyi",
    ROOT / "lib" / "__init__.pyi",
    ROOT / "linalg" / "__init__.pyi",
    ROOT / "ma" / "__init__.pyi",
    ROOT / "matrixlib" / "__init__.pyi",
    ROOT / "polynomial" / "__init__.pyi",
    ROOT / "random" / "__init__.pyi",
    ROOT / "testing" / "__init__.pyi",
]
if sys.version_info < (3, 12):
    FILES += [ROOT / "distutils" / "__init__.pyi"]


@pytest.mark.thread_unsafe(reason="os.path has a thread-safety bug")
class TestIsFile:
    def test_isfile(self):
        """Test if all ``.pyi`` files are properly installed."""
        for file in FILES:
            assert_(os.path.isfile(file))


Review comment (suggestion):
This nerd-sniped me pretty good just now. I managed to make a reproducer that doesn't use NumPy at all, see https://github.com/python/cpython/issues/140054. You can link to that issue in the reason here.

Code after:
import pytest

import numpy as np
from numpy.testing import assert_

ROOT = Path(np.__file__).parents[0]
FILES = [
    ROOT / "py.typed",
    ROOT / "__init__.pyi",
    ROOT / "ctypeslib" / "__init__.pyi",
    ROOT / "_core" / "__init__.pyi",
    ROOT / "f2py" / "__init__.pyi",
    ROOT / "fft" / "__init__.pyi",
    ROOT / "lib" / "__init__.pyi",
    ROOT / "linalg" / "__init__.pyi",
    ROOT / "ma" / "__init__.pyi",
    ROOT / "matrixlib" / "__init__.pyi",
    ROOT / "polynomial" / "__init__.pyi",
    ROOT / "random" / "__init__.pyi",
    ROOT / "testing" / "__init__.pyi",
]
if sys.version_info < (3, 12):
    FILES += [ROOT / "distutils" / "__init__.pyi"]


@pytest.mark.thread_unsafe(
    reason="os.path has a thread-safety bug (python/cpython#140054). "
           "Expected to only be a problem in 3.14.0"
)
class TestIsFile:
    def test_isfile(self):
        """Test if all ``.pyi`` files are properly installed."""
        for file in FILES:
            assert_(os.path.isfile(file))
Repository: numpy/numpy
File: numpy/tests/test_numpy_config.py (Python)

Code before:
"""
Check the numpy config is valid.
"""
from unittest.mock import patch

import pytest

import numpy as np

pytestmark = pytest.mark.skipif(
    not hasattr(np.__config__, "_built_with_meson"),
    reason="Requires Meson builds",
)


class TestNumPyConfigs:
    REQUIRED_CONFIG_KEYS = [
        "Compilers",
        "Machine Information",
        "Python Information",
    ]

    @patch("numpy.__config__._check_pyyaml")
    @pytest.mark.thread_unsafe(reason="unittest.mock.patch thread-unsafe")
    def test_pyyaml_not_found(self, mock_yaml_importer):
        mock_yaml_importer.side_effect = ModuleNotFoundError()
        with pytest.warns(UserWarning):
            np.show_config()

    def test_dict_mode(self):
        config = np.show_config(mode="dicts")

        assert isinstance(config, dict)
        assert all(key in config for key in self.REQUIRED_CONFIG_KEYS), (
            "Required key missing,"
            " see index of `False` with `REQUIRED_CONFIG_KEYS`"
        )

    def test_invalid_mode(self):
        with pytest.raises(AttributeError):
            np.show_config(mode="foo")

    def test_warn_to_add_tests(self):
        assert len(np.__config__.DisplayModes) == 2, (
            "New mode detected,"
            " please add UT if applicable and increment this count"
        )


Review comment (suggestion):
```suggestion
    @pytest.mark.thread_unsafe(reason="unittest.mock.patch updates global state")
```

Code after:
"""
Check the numpy config is valid.
"""
from unittest.mock import patch

import pytest

import numpy as np

pytestmark = pytest.mark.skipif(
    not hasattr(np.__config__, "_built_with_meson"),
    reason="Requires Meson builds",
)


class TestNumPyConfigs:
    REQUIRED_CONFIG_KEYS = [
        "Compilers",
        "Machine Information",
        "Python Information",
    ]

    @patch("numpy.__config__._check_pyyaml")
    @pytest.mark.thread_unsafe(reason="unittest.mock.patch updates global state")
    def test_pyyaml_not_found(self, mock_yaml_importer):
        mock_yaml_importer.side_effect = ModuleNotFoundError()
        with pytest.warns(UserWarning):
            np.show_config()

    def test_dict_mode(self):
        config = np.show_config(mode="dicts")

        assert isinstance(config, dict)
        assert all(key in config for key in self.REQUIRED_CONFIG_KEYS), (
            "Required key missing,"
            " see index of `False` with `REQUIRED_CONFIG_KEYS`"
        )

    def test_invalid_mode(self):
        with pytest.raises(AttributeError):
            np.show_config(mode="foo")

    def test_warn_to_add_tests(self):
        assert len(np.__config__.DisplayModes) == 2, (
            "New mode detected,"
            " please add UT if applicable and increment this count"
        )
Repository: numpy/numpy
File: numpy/typing/tests/test_isfile.py (Python)

Code before:

import numpy as np
from numpy.testing import assert_

ROOT = Path(np.__file__).parents[0]
FILES = [
    ROOT / "py.typed",
    ROOT / "__init__.pyi",
    ROOT / "ctypeslib" / "__init__.pyi",
    ROOT / "_core" / "__init__.pyi",
    ROOT / "f2py" / "__init__.pyi",
    ROOT / "fft" / "__init__.pyi",
    ROOT / "lib" / "__init__.pyi",
    ROOT / "linalg" / "__init__.pyi",
    ROOT / "ma" / "__init__.pyi",
    ROOT / "matrixlib" / "__init__.pyi",
    ROOT / "polynomial" / "__init__.pyi",
    ROOT / "random" / "__init__.pyi",
    ROOT / "testing" / "__init__.pyi",
]
if sys.version_info < (3, 12):
    FILES += [ROOT / "distutils" / "__init__.pyi"]


@pytest.mark.thread_unsafe(
    reason="os.path has a thread-safety bug (python/cpython#140054)"
)
class TestIsFile:
    def test_isfile(self):
        """Test if all ``.pyi`` files are properly installed."""
        for file in FILES:
            assert_(os.path.isfile(file))


Review comment (suggestion):
It looks like this will be fixed in 3.14.1, so you could also add a note that we expect this to only be a problem in 3.14.0.

Code after:

import numpy as np
from numpy.testing import assert_

ROOT = Path(np.__file__).parents[0]
FILES = [
    ROOT / "py.typed",
    ROOT / "__init__.pyi",
    ROOT / "ctypeslib" / "__init__.pyi",
    ROOT / "_core" / "__init__.pyi",
    ROOT / "f2py" / "__init__.pyi",
    ROOT / "fft" / "__init__.pyi",
    ROOT / "lib" / "__init__.pyi",
    ROOT / "linalg" / "__init__.pyi",
    ROOT / "ma" / "__init__.pyi",
    ROOT / "matrixlib" / "__init__.pyi",
    ROOT / "polynomial" / "__init__.pyi",
    ROOT / "random" / "__init__.pyi",
    ROOT / "testing" / "__init__.pyi",
]
if sys.version_info < (3, 12):
    FILES += [ROOT / "distutils" / "__init__.pyi"]


@pytest.mark.thread_unsafe(
    reason="os.path has a thread-safety bug (python/cpython#140054). "
           "Expected to only be a problem in 3.14.0"
)
class TestIsFile:
    def test_isfile(self):
        """Test if all ``.pyi`` files are properly installed."""
        for file in FILES:
            assert_(os.path.isfile(file))
Repository: numpy/numpy
File: numpy/_core/src/umath/dispatching.cpp (C++)

Code before:
            nargs, (PyObject **)bmeth->dtypes, 1);
    if (dtypes == NULL) {
        return -1;
    }
    PyObject *info = PyTuple_Pack(2, dtypes, bmeth->method);
    Py_DECREF(bmeth);
    Py_DECREF(dtypes);
    if (info == NULL) {
        return -1;
    }
    return PyUFunc_AddLoop((PyUFuncObject *)ufunc, info, 0);
}


/*UFUNC_API
 * Add multiple loops to ufuncs from ArrayMethod specs. This also
 * handles the registration of sort and argsort methods for dtypes
 * from ArrayMethod specs.
 */
NPY_NO_EXPORT int
PyUFunc_AddLoopsFromSpecs(PyUFunc_LoopSlot *slots)
{
    static PyObject *sort = NULL;
    static PyObject *argsort = NULL;
    if (sort == NULL) {
        sort = npy_import("numpy", "sort");
        if (sort == NULL) {
            return -1;
        }
    }
    if (argsort == NULL) {
        argsort = npy_import("numpy", "argsort");
        if (argsort == NULL) {
            return -1;
        }
    }

    PyUFunc_LoopSlot *slot;
    for (slot = slots; slot->name != NULL; slot++) {
        PyObject *ufunc = npy_import("numpy", slot->name);
        if (ufunc == NULL) {
            return -1;
        }

        if (ufunc == sort) {
            PyArray_DTypeMeta *dtype = slot->spec->dtypes[0];

            PyBoundArrayMethodObject *sort_meth = PyArrayMethod_FromSpec_int(slot->spec, 0);
            if (sort_meth == NULL) {
                PyErr_Format(PyExc_TypeError, "Failed to create sort method for %R", dtype);
                return -1;

Review comment (performance):
If you do it here, please use the `cache_import_runtime`

Code after:
            nargs, (PyObject **)bmeth->dtypes, 1);
    if (dtypes == NULL) {
        return -1;
    }
    PyObject *info = PyTuple_Pack(2, dtypes, bmeth->method);
    Py_DECREF(bmeth);
    Py_DECREF(dtypes);
    if (info == NULL) {
        return -1;
    }
    return PyUFunc_AddLoop((PyUFuncObject *)ufunc, info, 0);
}


/*UFUNC_API
 * Add multiple loops to ufuncs from ArrayMethod specs. This also
 * handles the registration of sort and argsort methods for dtypes
 * from ArrayMethod specs.
 */
NPY_NO_EXPORT int
PyUFunc_AddLoopsFromSpecs(PyUFunc_LoopSlot *slots)
{
    if (npy_cache_import_runtime(
            "numpy", "sort", &npy_runtime_imports.sort) < 0) {
        return -1;
    }
    if (npy_cache_import_runtime(
            "numpy", "argsort", &npy_runtime_imports.argsort) < 0) {
        return -1;
    }

    PyUFunc_LoopSlot *slot;
    for (slot = slots; slot->name != NULL; slot++) {
        PyObject *ufunc = npy_import("numpy", slot->name);
        if (ufunc == NULL) {
            return -1;
        }

        if (ufunc == npy_runtime_imports.sort) {
            Py_DECREF(ufunc);

            PyArray_DTypeMeta *dtype = slot->spec->dtypes[0];
            PyBoundArrayMethodObject *sort_meth = PyArrayMethod_FromSpec_int(slot->spec, 0);
            if (sort_meth == NULL) {
                return -1;
            }

            NPY_DT_SLOTS(dtype)->sort_meth = sort_meth->method;
            Py_INCREF(sort_meth->method);
            Py_DECREF(sort_meth);
        }
Repository: numpy/numpy
File: numpy/_core/_ufunc_config.pyi (Python)

Code before:
from _typeshed import SupportsWrite
from collections.abc import Callable
from typing import Any, Literal, TypeAlias, TypedDict, type_check_only

from numpy import errstate as errstate

_ErrKind: TypeAlias = Literal["ignore", "warn", "raise", "call", "print", "log"]
_ErrFunc: TypeAlias = Callable[[str, int], Any]
_ErrCall: TypeAlias = _ErrFunc | SupportsWrite[str]

@type_check_only
class _ErrDict(TypedDict):
    divide: _ErrKind
    over: _ErrKind
    under: _ErrKind
    invalid: _ErrKind

def seterr(
    all: _ErrKind | None = None,
    divide: _ErrKind | None = None,
    over: _ErrKind | None = None,
    under: _ErrKind | None = None,
    invalid: _ErrKind | None = None,
) -> _ErrDict: ...
def geterr() -> _ErrDict: ...
def setbufsize(size: int) -> int: ...
def getbufsize() -> int: ...
def seterrcall(func: _ErrCall | None) -> _ErrCall | None: ...
def geterrcall() -> _ErrCall | None: ...

# See `numpy/__init__.pyi` for the `errstate` class and `no_nep5_warnings`


Review comment (suggestion):
Weird, I guess I didn't want to clean that up last time around, but we should just use `np._NoValue` it seems to me...
Anyway, doesn't matter for this.

(I don't really think this should be backported -- I can say that once every few months, right? :).)

Code after:
_ErrKind: TypeAlias = Literal["ignore", "warn", "raise", "call", "print", "log"]
_ErrCall: TypeAlias = Callable[[str, int], Any] | SupportsWrite[str]

_CallableT = TypeVar("_CallableT", bound=Callable[..., object])

@type_check_only
class _ErrDict(TypedDict):
    divide: _ErrKind
    over: _ErrKind
    under: _ErrKind
    invalid: _ErrKind

###

class _unspecified: ...

_Unspecified: Final[_unspecified]

class errstate:
    __slots__ = "_all", "_call", "_divide", "_invalid", "_over", "_token", "_under"

    def __init__(
        self,
        /,
        *,
        call: _ErrCall | _unspecified = ...,  # = _Unspecified
        all: _ErrKind | None = None,
        divide: _ErrKind | None = None,
        over: _ErrKind | None = None,
        under: _ErrKind | None = None,
        invalid: _ErrKind | None = None,
    ) -> None: ...
    def __call__(self, /, func: _CallableT) -> _CallableT: ...
    def __enter__(self) -> None: ...
    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_value: BaseException | None,
        traceback: TracebackType | None,
        /,
    ) -> None: ...

def seterr(
    all: _ErrKind | None = None,
    divide: _ErrKind | None = None,
    over: _ErrKind | None = None,
    under: _ErrKind | None = None,
    invalid: _ErrKind | None = None,
) -> _ErrDict: ...
def geterr() -> _ErrDict: ...
def setbufsize(size: int) -> int: ...
Repository: numpy/numpy
File: numpy/_core/_ufunc_config.pyi (Python)

Code before:
from collections.abc import Callable
from types import TracebackType
from typing import Any, Final, Literal, TypeAlias, TypedDict, TypeVar, type_check_only

__all__ = [
    "seterr",
    "geterr",
    "setbufsize",
    "getbufsize",
    "seterrcall",
    "geterrcall",
    "errstate",
]

_ErrKind: TypeAlias = Literal["ignore", "warn", "raise", "call", "print", "log"]
_ErrCall: TypeAlias = Callable[[str, int], Any] | SupportsWrite[str]

_CallableT = TypeVar("_CallableT", bound=Callable[..., object])

@type_check_only
class _ErrDict(TypedDict):
    divide: _ErrKind
    over: _ErrKind
    under: _ErrKind
    invalid: _ErrKind


Review comment (suggestion):
There seems to be [a missing import](https://github.com/numpy/numpy/blob/6bb72644e16e5e7f590533bde670c42b199edb53/numpy/_core/_ufunc_config.pyi#L1):

```pyi
from _typeshed import SupportsWrite
```

Code after:
from collections.abc import Callable
from _typeshed import SupportsWrite
from types import TracebackType
from typing import Any, Final, Literal, TypeAlias, TypedDict, TypeVar, type_check_only

__all__ = [
    "seterr",
    "geterr",
    "setbufsize",
    "getbufsize",
    "seterrcall",
    "geterrcall",
    "errstate",
]

_ErrKind: TypeAlias = Literal["ignore", "warn", "raise", "call", "print", "log"]
_ErrCall: TypeAlias = Callable[[str, int], Any] | SupportsWrite[str]

_CallableT = TypeVar("_CallableT", bound=Callable[..., object])

@type_check_only
class _ErrDict(TypedDict):
    divide: _ErrKind
    over: _ErrKind
    under: _ErrKind
    invalid: _ErrKind
Repository: numpy/numpy
File: numpy/_core/src/common/npy_import.c (C)

Code before:
        return -1;
    }
#endif
    return 0;
}


/*! \brief Import a Python object from its fully qualified name.

 * The name should be of the form "(module ':')? (submodule '.')* name".
 * If no module is present, it is assumed to be "numpy".
 * On error, returns NULL.
 */
NPY_NO_EXPORT PyObject*
npy_import_fqn(const char *name) {
    PyObject *result = NULL;
    char *module = "numpy";  // default

    char *dot = strchr(name, '.');
    char *colon = strchr(name, ':');
    if (dot == NULL && colon == NULL) {  // short-cut
        return npy_import(module, name);
    }

    /* make a copy of name so we can insert 0 as we like. */
    char *copy = malloc(strlen(name) + 1);
    memcpy(copy, name, strlen(name) + 1);
    char *item = copy;
    dot = dot != NULL ? copy + (int)(dot - name) : NULL;
    if (colon) { // there is a module, get first item of name.
        colon = copy + (int)(colon - name);
        if (dot != NULL && dot < colon) {
            *colon = '.';  // Change to dot for later.
        }
        else {
            dot = colon;
        }
        *dot = 0; // replace '.' or ':' with termination marker.
        module = copy;
        item = dot + 1;
        dot = strchr(item, '.');
    }

    result = PyImport_ImportModule(module);
    while (result != NULL && dot != NULL) {
        *dot = 0;  // replace "." with termination marker.
        Py_SETREF(result, PyObject_GetAttrString(result, item));
        item = dot + 1;
        dot = strchr(item, '.');
    }
    if (result != NULL) {

Review comment (suggestion):
I am trying to generally stop using malloc directly, `PyMem_Malloc()` is just malloc, but allows Python the choice (could use the small workspace stuff, but not really worth wit.).

Code after:
        return -1;
    }
#endif
    return 0;
}


/*! \brief Import a Python object from an entry point string.

 * The name should be of the form "(module ':')? (object '.')* attr".
 * If no module is present, it is assumed to be "numpy".
 * On error, returns NULL.
 */
NPY_NO_EXPORT PyObject*
npy_import_entry_point(const char *entry_point) {
    PyObject *result;
    const char *item;

    const char *colon = strchr(entry_point, ':');
    if (colon) { // there is a module.
        result = PyUnicode_FromStringAndSize(entry_point, colon - entry_point);
        if (result != NULL) {
            Py_SETREF(result, PyImport_Import(result));
        }
        item = colon + 1;
    }
    else {
        result = PyImport_ImportModule("numpy");
        item = entry_point;
    }

    const char *dot = item - 1;
    while (result != NULL && dot != NULL) {
        item = dot + 1;
        dot = strchr(item, '.');
        PyObject *string = PyUnicode_FromStringAndSize(
            item, dot ? dot - item : strlen(item));
        if (string == NULL) {
            Py_DECREF(result);
            return NULL;
        }
        Py_SETREF(result, PyObject_GetAttr(result, string));
        Py_DECREF(string);
    }
    return result;
}
Repository: numpy/numpy
File: numpy/lib/tests/test_function_base.py (Python)

Code before:

        a_trim0 = np.zeros((2, 4))
        a_trim0[:, 1:3] = 1

        a_trim1 = np.zeros((4, 2))
        a_trim1[1:3, :] = 1

        a_trim_both = np.ones((2, 2))

        res = trim_zeros(a, axis=())
        assert_array_equal(res, a)

        res = trim_zeros(a, axis=0)
        assert_array_equal(res, a_trim0)

        res = trim_zeros(a, axis=1)
        assert_array_equal(res, a_trim1)

        res = trim_zeros(a, axis=(-2,))
        assert_array_equal(res, a_trim0)

        res = trim_zeros(a, axis=(-1,))
        assert_array_equal(res, a_trim1)

        res = trim_zeros(a, axis=(0, -1))
        assert_array_equal(res, a_trim_both)

        res = trim_zeros(a, axis=None)
        assert_array_equal(res, a_trim_both)


class TestExtins:

    def test_basic(self):
        a = np.array([1, 3, 2, 1, 2, 3, 3])
        b = extract(a > 1, a)
        assert_array_equal(b, [3, 2, 2, 3, 3])

    def test_place(self):
        # Make sure that non-np.ndarray objects
        # raise an error instead of doing nothing
        assert_raises(TypeError, place, [1, 2, 3], [True, False], [0, 1])

        a = np.array([1, 4, 3, 2, 5, 8, 7])
        place(a, [0, 1, 0, 1, 0, 1, 0], [2, 4, 6])
        assert_array_equal(a, [1, 2, 3, 4, 5, 6, 7])

        place(a, np.zeros(7), [])
        assert_array_equal(a, np.arange(1, 8))

        place(a, [1, 0, 1, 0, 1, 0, 1], [8, 9])

Review comment (bug):
All of these currently fail with a TypeError. IMO it makes sense to do something sensible here instead of raising an error.

Code after:
        assert_array_equal(a, res)

    @pytest.mark.parametrize("ndim", (0, 1, 2, 3))
    def test_allzero(self, ndim):
        a = np.zeros((3,) * ndim)
        res = trim_zeros(a, axis=None)
        assert_array_equal(res, np.zeros((0,) * ndim))

    def test_trim_arg(self):
        a = np.array([0, 1, 2, 0])

        res = trim_zeros(a, trim='f')
        assert_array_equal(res, [1, 2, 0])

        res = trim_zeros(a, trim='b')
        assert_array_equal(res, [0, 1, 2])

    @pytest.mark.parametrize("trim", ("front", ""))
    def test_unexpected_trim_value(self, trim):
        arr = self.a
        with pytest.raises(ValueError, match=r"unexpected character\(s\) in `trim`"):
            trim_zeros(arr, trim=trim)

    @pytest.mark.parametrize("shape, axis", [
        [(5,), None],
        [(5,), ()],
        [(5,), 0],
        [(5, 6), None],
        [(5, 6), ()],
        [(5, 6), 0],
        [(5, 6), (-1,)],
        [(5, 6, 7), None],
        [(5, 6, 7), ()],
        [(5, 6, 7), 1],
        [(5, 6, 7), (0, 2)],
        [(5, 6, 7, 8), None],
        [(5, 6, 7, 8), ()],
        [(5, 6, 7, 8), -2],
        [(5, 6, 7, 8), (0, 1, 3)],
    ])
    @pytest.mark.parametrize("trim", ['fb', 'f', 'b'])
    def test_multiple_axes(self, shape, axis, trim):
        rng = np.random.default_rng(4321)
        data, expected = self.construct_input_output(rng, shape, axis, trim)
        assert_array_equal(trim_zeros(data, axis=axis, trim=trim), expected)


class TestExtins:

    def test_basic(self):
        a = np.array([1, 3, 2, 1, 2, 3, 3])