GithubHelp home page GithubHelp logo

pytest-ruff's Introduction

pytest-ruff

A pytest plugin to run ruff.

Installation

pip install pytest-ruff

Usage

pytest --ruff --ruff-format

The plugin will run one ruff check test per file and fail if code is not ok for ruff.

Format command only checks for format and fails for formatting errors.

Configuration

You can override ruff configuration options by placing a pyproject.toml or ruff.toml file in your project directory, like when using standalone ruff.

License

Distributed under the terms of the MIT license, pytest-ruff is free and open source software.

pytest-ruff's People

Contributors

cclauss avatar hairmare avatar iurisilvio avatar jaraco avatar lexi-k avatar skellys avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

pytest-ruff's Issues

Assertion Error on version > 0.2.1 with pytest option '-p no:cacheprovider'

Since pytest-ruff 0.2.1, I've noticed an issue with an assertion error relating to the get_stash method within the pytest compatibility layer.

https://github.com/businho/pytest-ruff/blob/main/pytest_ruff/_pytest_compat.py#L41

/opt/hostedtoolcache/Python/3.12.4/x64/lib/python3.12/site-packages/pytest_ruff/_pytest_compat.py:41: AssertionError
=========================== short test summary info ============================
ERROR src/hello/__init__.py::ruff - assert <object object at 0x7fd14fd28df0> is not <object object at 0x7fd14fd28df0>
ERROR src/hello/ruff.py::ruff - assert <object object at 0x7fd14fd2be10> is not <object object at 0x7fd14fd2be10>
============================== 2 errors in 0.20s ===============================

I've created a cut down project to demonstrate the issue, based around this pyproject.toml

[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "hello"
version = "0.0.1"
dependencies = ["pytest", "pytest-ruff"]

[project.scripts]
ruff-hello = "hello.ruff:world"

[tool.pytest.ini_options]
pythonpath = "src"
addopts = "-p no:cacheprovider --ruff"

[tool.ruff]
preview = true

[tool.ruff.lint]
select = [
    # flake8
    "A", "ARG", "B", "BLE", "C4", "PIE", "RET", "SIM", "S",
    "F",  # pyflakes
    "N",  # pep8-nameing
    "PL", # pylint
    "E",  # error
    "W",  # warning
    "PTH", # pathlib
    "RUF", # ruff
]

Examples

It appears to be related to config file discovery, as commenting out assert stash is not missing the tests do run, but use the default ruff config.

AttributeError: module 'pytest' has no attribute 'StashKey'

Is it a pytest version incompatibility issue?

/usr/local/lib/python3.10/site-packages/_pytest/config/__init__.py:318: PluggyTeardownRaisedWarning: A plugin raised an exception during an old-style hookwrapper teardown.
Plugin: helpconfig, Hook: pytest_cmdline_parse
AttributeError: module 'pytest' has no attribute 'StashKey'
For more information see https://pluggy.readthedocs.io/en/stable/api_reference.html#pluggy.PluggyTeardownRaisedWarning

python: 3.10
pytest: 6.2.5
pytest-ruff: 0.2.1

pluggy._manager.PluginValidationError: Plugin 'ruff' for hook 'pytest_collect_file'

Hi All,

We have found that new version 0.3 doesn't work with pytest 8.1.0, we are getting the following stack trace with that combination.

Traceback (most recent call last):
  File "/home/runner/actions-runner/_work/_tool/Python/3.8.18/x64/bin/pytest", line 8, in <module>
    sys.exit(console_main())
  File "/home/runner/actions-runner/_work/_tool/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/config/__init__.py", line 19[5](), in console_main
    code = main()
  File "/home/runner/actions-runner/_work/_tool/Python/3.8.18/x[6]()4/lib/python3.8/site-packages/_pytest/config/__init__.py", line 153, in main
    config = _prepareconfig(args, plugins)
  File "/home/runner/actions-runner/_work/_tool/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/config/__init__.py", line 335, in _prepareconfig
    config = pluginmanager.hook.pytest_cmdline_parse(
  File "/home/runner/actions-runner/_work/_tool/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_hooks.py", line 501, in __call__
    return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
  File "/home/runner/actions-runner/_work/_tool/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_manager.py", line 119, in _hookexec
    return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
  File "/home/runner/actions-runner/_work/_tool/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_callers.py", line 138, in _multicall
    raise exception.with_traceback(exception.__traceback__)
  File "/home/runner/actions-runner/_work/_tool/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_callers.py", line 121, in _multicall
    teardown.throw(exception)  # type: ignore[union-attr]
  File "/home/runner/actions-runner/_work/_tool/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/helpconfig.py", line 105, in pytest_cmdline_parse
    config = yield
  File "/home/runner/actions-runner/_work/_tool/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_callers.py", line 102, in _multicall
    res = hook_impl.function(*args)
  File "/home/runner/actions-runner/_work/_tool/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/config/__init__.py", line 1141, in pytest_cmdline_parse
    self.parse(args)
  File "/home/runner/actions-runner/_work/_tool/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/config/__init__.py", line 1490, in parse
    self._preparse(args, addopts=addopts)
  File "/home/runner/actions-runner/_work/_tool/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/config/__init__.py", line 13[7]()7, in _preparse
    self.pluginmanager.load_setuptools_entrypoints("pytest11")
  File "/home/runner/actions-runner/_work/_tool/Python/3.[8]().18/x64/lib/python3.8/site-packages/pluggy/_manager.py", line 415, in load_setuptools_entrypoints
    self.register(plugin, name=ep.name)
  File "/home/runner/actions-runner/_work/_tool/Python/3.8.18/x64/lib/python3.8/site-packages/_pytest/config/__init__.py", line 4[9]()7, in register
    plugin_name = super().register(plugin, name)
  File "/home/runner/actions-runner/_work/_tool/Python/3.8.18/x64/lib/python3.8/site-packages/pluggy/_manager.py", line [16](7)7, in register
    self._verify_hook(hook, hookimpl)
  File "/home/runner/actions-runner/_work/_tool/Python/3.8.[18](9)/x64/lib/python3.8/site-packages/pluggy/_manager.py", line [34](5)2, in _verify_hook
    raise PluginValidationError(
pluggy._manager.PluginValidationError: Plugin 'ruff' for hook 'pytest_collect_file'
hookimpl definition: pytest_collect_file(path, parent, fspath=None)
Argument(s) {'path'} are declared in the hookimpl but can not be found in the hookspec

For the moment we have fallback to pytest-ruff 0.2.1 and pytest 8.0.2, we also found out that 8.1.0 pytest doesn't play either with 0.2.1 different stack trace.

Happy to get more information if you guys need any help,

Not compatible with `ruff` ≥ 0.5.0

Thanks for pytest-ruff!

As of ruff 0.5.0, ruff no longer accepts the --show-source option (https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md#050, astral-sh/ruff#9814, astral-sh/ruff#12005). As a result, when pytest_ruff executes its check_file() routine (https://github.com/businho/pytest-ruff/blob/main/pytest_ruff/__init__.py#L87), the ruff command fails. When I run the ruff command constructed by pytest_ruff, here's what I get --

$ ruff check my_file.py --quiet --show-source --force-exclude
error: unexpected argument '--show-source' found

  tip: a similar argument exists: '--show-files'

Usage: ruff check <blah blah blah>

For more information, try '--help'.

The --show-source option has been superceded by --output-format. It looks like output-format has been part of ruff since at least version 0.1.0 (based on https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md), so it's probably the backwards and forwards compatible.

E902 No such file or directory when module has space in the name

After adopting ruff in jaraco.home, tests started failing with this error:

path = local('/Users/jaraco/code/jaraco/jaraco.home/jaraco/home/mock hdhomerun.py')

    def check_file(path):
        ruff = find_ruff_bin()
        command = f"{ruff} {path} --quiet --show-source"
        child = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)
        stdout, _ = child.communicate()
        if stdout:
>           raise RuffError(stdout.decode())
E           pytest_ruff.RuffError: hdhomerun.py:1:1: E902 No such file or directory (os error 2)
E            |
E            |
E           
E           jaraco/home/mock:1:1: E902 No such file or directory (os error 2)
E            |
E            |

.tox/py/lib/python3.11/site-packages/pytest_ruff.py:88: RuffError
 jaraco.home main @ tox
.pkg: install_requires> python -I -m pip install 'setuptools>=56' 'setuptools_scm[toml]>=3.4.1'
.pkg: _optional_hooks> python /Users/jaraco/.local/pipx/venvs/tox/lib/python3.11/site-packages/pyproject_api/_backend.py True setuptools.build_meta
.pkg: get_requires_for_build_editable> python /Users/jaraco/.local/pipx/venvs/tox/lib/python3.11/site-packages/pyproject_api/_backend.py True setuptools.build_meta
.pkg: install_requires_for_build_editable> python -I -m pip install wheel
.pkg: build_editable> python /Users/jaraco/.local/pipx/venvs/tox/lib/python3.11/site-packages/pyproject_api/_backend.py True setuptools.build_meta
py: install_package_deps> python -I -m pip install dnspython jaraco.collections jaraco.functools jaraco.mongodb keyring 'lxml>=4.2.6' mockproc pymongo 'pytest-black>=0.3.7; platform_python_implementation != "PyPy"' 'pytest-checkdocs>=2.4' pytest-cov 'pytest-enabler>=2.2' 'pytest-mypy>=0.9.1; platform_python_implementation != "PyPy"' pytest-ruff 'pytest>=6' requests types-requests
py: install_package> python -I -m pip install --force-reinstall --no-deps /Users/jaraco/code/jaraco/jaraco.home/.tox/.tmp/package/1/jaraco.home-3.4.1.dev92+gfe890db-0.editable-py3-none-any.whl
py: commands[0]> pytest
====================================================================================== test session starts =======================================================================================
platform darwin -- Python 3.11.4, pytest-7.4.0, pluggy-1.2.0
cachedir: .tox/py/.pytest_cache
rootdir: /Users/jaraco/code/jaraco/jaraco.home
configfile: pytest.ini
plugins: checkdocs-2.10.0, black-0.3.12, mypy-0.10.3, cov-4.1.0, ruff-0.1, enabler-2.3.1, jaraco.mongodb-11.4.0
collected 25 items                                                                                                                                                                               

conftest.py ....                                                                                                                                                                           [ 16%]
. .                                                                                                                                                                                        [ 20%]
docs/conf.py ...                                                                                                                                                                           [ 33%]
jaraco/home/__init__.py ...                                                                                                                                                                [ 45%]
jaraco/home/hdhomerun.py ...ch=none ss=80
...                                                                                                                                                            [ 70%]
jaraco/home/mock hdhomerun.py F..                                                                                                                                                          [ 83%]
jaraco/home/thermostat.py ....                                                                                                                                                             [100%]

============================================================================================ FAILURES ============================================================================================
__________________________________________________________________________________________ test session __________________________________________________________________________________________

cls = <class '_pytest.runner.CallInfo'>, func = <function call_runtest_hook.<locals>.<lambda> at 0x107109440>, when = 'call'
reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)

    @classmethod
    def from_call(
        cls,
        func: "Callable[[], TResult]",
        when: "Literal['collect', 'setup', 'call', 'teardown']",
        reraise: Optional[
            Union[Type[BaseException], Tuple[Type[BaseException], ...]]
        ] = None,
    ) -> "CallInfo[TResult]":
        """Call func, wrapping the result in a CallInfo.
    
        :param func:
            The function to call. Called without arguments.
        :param when:
            The phase in which the function is called.
        :param reraise:
            Exception or exceptions that shall propagate if raised by the
            function, instead of being wrapped in the CallInfo.
        """
        excinfo = None
        start = timing.time()
        precise_start = timing.perf_counter()
        try:
>           result: Optional[TResult] = func()

.tox/py/lib/python3.11/site-packages/_pytest/runner.py:341: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

>       lambda: ihook(item=item, **kwds), when=when, reraise=reraise
    )

.tox/py/lib/python3.11/site-packages/_pytest/runner.py:262: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_HookCaller 'pytest_runtest_call'>, kwargs = {'item': <RuffItem ruff>}, firstresult = False

    def __call__(self, **kwargs: object) -> Any:
        assert (
            not self.is_historic()
        ), "Cannot directly call a historic hook - use call_historic instead."
        self._verify_all_args_are_provided(kwargs)
        firstresult = self.spec.opts.get("firstresult", False) if self.spec else False
>       return self._hookexec(self.name, self._hookimpls, kwargs, firstresult)

.tox/py/lib/python3.11/site-packages/pluggy/_hooks.py:433: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <_pytest.config.PytestPluginManager object at 0x103b80f90>, hook_name = 'pytest_runtest_call'
methods = [<HookImpl plugin_name='runner', plugin=<module '_pytest.runner' from '/Users/jaraco/code/jaraco/jaraco.home/.tox/py/l...rom '/Users/jaraco/code/jaraco/jaraco.home/.tox/py/lib/python3.11/site-packages/_pytest/unraisableexception.py'>>, ...]
kwargs = {'item': <RuffItem ruff>}, firstresult = False

    def _hookexec(
        self,
        hook_name: str,
        methods: Sequence[HookImpl],
        kwargs: Mapping[str, object],
        firstresult: bool,
    ) -> object | list[object]:
        # called from all hookcaller instances.
        # enable_tracing will set its own wrapping function at self._inner_hookexec
>       return self._inner_hookexec(hook_name, methods, kwargs, firstresult)

.tox/py/lib/python3.11/site-packages/pluggy/_manager.py:112: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

hook_name = 'pytest_runtest_call'
hook_impls = [<HookImpl plugin_name='runner', plugin=<module '_pytest.runner' from '/Users/jaraco/code/jaraco/jaraco.home/.tox/py/l...rom '/Users/jaraco/code/jaraco/jaraco.home/.tox/py/lib/python3.11/site-packages/_pytest/unraisableexception.py'>>, ...]
caller_kwargs = {'item': <RuffItem ruff>}, firstresult = False

    def _multicall(
        hook_name: str,
        hook_impls: Sequence[HookImpl],
        caller_kwargs: Mapping[str, object],
        firstresult: bool,
    ) -> object | list[object]:
        """Execute a call into multiple python functions/methods and return the
        result(s).
    
        ``caller_kwargs`` comes from _HookCaller.__call__().
        """
        __tracebackhide__ = True
        results: list[object] = []
        exception = None
        only_new_style_wrappers = True
        try:  # run impl and wrapper setup functions in a loop
            teardowns: list[Teardown] = []
            try:
                for hook_impl in reversed(hook_impls):
                    try:
                        args = [caller_kwargs[argname] for argname in hook_impl.argnames]
                    except KeyError:
                        for argname in hook_impl.argnames:
                            if argname not in caller_kwargs:
                                raise HookCallError(
                                    f"hook call must provide argument {argname!r}"
                                )
    
                    if hook_impl.hookwrapper:
                        only_new_style_wrappers = False
                        try:
                            # If this cast is not valid, a type error is raised below,
                            # which is the desired response.
                            res = hook_impl.function(*args)
                            wrapper_gen = cast(Generator[None, _Result[object], None], res)
                            next(wrapper_gen)  # first yield
                            teardowns.append((wrapper_gen,))
                        except StopIteration:
                            _raise_wrapfail(wrapper_gen, "did not yield")
                    elif hook_impl.wrapper:
                        try:
                            # If this cast is not valid, a type error is raised below,
                            # which is the desired response.
                            res = hook_impl.function(*args)
                            function_gen = cast(Generator[None, object, object], res)
                            next(function_gen)  # first yield
                            teardowns.append(function_gen)
                        except StopIteration:
                            _raise_wrapfail(function_gen, "did not yield")
                    else:
                        res = hook_impl.function(*args)
                        if res is not None:
                            results.append(res)
                            if firstresult:  # halt further impl calls
                                break
            except BaseException as exc:
                exception = exc
        finally:
            # Fast path - only new-style wrappers, no _Result.
            if only_new_style_wrappers:
                if firstresult:  # first result hooks return a single value
                    result = results[0] if results else None
                else:
                    result = results
    
                # run all wrapper post-yield blocks
                for teardown in reversed(teardowns):
                    try:
                        if exception is not None:
                            teardown.throw(exception)  # type: ignore[union-attr]
                        else:
                            teardown.send(result)  # type: ignore[union-attr]
                        # Following is unreachable for a well behaved hook wrapper.
                        # Try to force finalizers otherwise postponed till GC action.
                        # Note: close() may raise if generator handles GeneratorExit.
                        teardown.close()  # type: ignore[union-attr]
                    except StopIteration as si:
                        result = si.value
                        exception = None
                        continue
                    except BaseException as e:
                        exception = e
                        continue
                    _raise_wrapfail(teardown, "has second yield")  # type: ignore[arg-type]
    
                if exception is not None:
                    raise exception.with_traceback(exception.__traceback__)
                else:
                    return result
    
            # Slow path - need to support old-style wrappers.
            else:
                if firstresult:  # first result hooks return a single value
                    outcome: _Result[object | list[object]] = _Result(
                        results[0] if results else None, exception
                    )
                else:
                    outcome = _Result(results, exception)
    
                # run all wrapper post-yield blocks
                for teardown in reversed(teardowns):
                    if isinstance(teardown, tuple):
                        try:
                            teardown[0].send(outcome)
                            _raise_wrapfail(teardown[0], "has second yield")
                        except StopIteration:
                            pass
                    else:
                        try:
                            if outcome._exception is not None:
                                teardown.throw(outcome._exception)
                            else:
                                teardown.send(outcome._result)
                            # Following is unreachable for a well behaved hook wrapper.
                            # Try to force finalizers otherwise postponed till GC action.
                            # Note: close() may raise if generator handles GeneratorExit.
                            teardown.close()
                        except StopIteration as si:
                            outcome.force_result(si.value)
                            continue
                        except BaseException as e:
                            outcome.force_exception(e)
                            continue
                        _raise_wrapfail(teardown, "has second yield")
    
>               return outcome.get_result()

.tox/py/lib/python3.11/site-packages/pluggy/_callers.py:155: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <pluggy._result._Result object at 0x11547fd30>

    def get_result(self) -> _T:
        """Get the result(s) for this hook call.
    
        If the hook was marked as a ``firstresult`` only a single value
        will be returned, otherwise a list of results.
        """
        __tracebackhide__ = True
        exc = self._exception
        if exc is None:
            return cast(_T, self._result)
        else:
>           raise exc.with_traceback(exc.__traceback__)

.tox/py/lib/python3.11/site-packages/pluggy/_result.py:108: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

hook_name = 'pytest_runtest_call'
hook_impls = [<HookImpl plugin_name='runner', plugin=<module '_pytest.runner' from '/Users/jaraco/code/jaraco/jaraco.home/.tox/py/l...rom '/Users/jaraco/code/jaraco/jaraco.home/.tox/py/lib/python3.11/site-packages/_pytest/unraisableexception.py'>>, ...]
caller_kwargs = {'item': <RuffItem ruff>}, firstresult = False

    def _multicall(
        hook_name: str,
        hook_impls: Sequence[HookImpl],
        caller_kwargs: Mapping[str, object],
        firstresult: bool,
    ) -> object | list[object]:
        """Execute a call into multiple python functions/methods and return the
        result(s).
    
        ``caller_kwargs`` comes from _HookCaller.__call__().
        """
        __tracebackhide__ = True
        results: list[object] = []
        exception = None
        only_new_style_wrappers = True
        try:  # run impl and wrapper setup functions in a loop
            teardowns: list[Teardown] = []
            try:
                for hook_impl in reversed(hook_impls):
                    try:
                        args = [caller_kwargs[argname] for argname in hook_impl.argnames]
                    except KeyError:
                        for argname in hook_impl.argnames:
                            if argname not in caller_kwargs:
                                raise HookCallError(
                                    f"hook call must provide argument {argname!r}"
                                )
    
                    if hook_impl.hookwrapper:
                        only_new_style_wrappers = False
                        try:
                            # If this cast is not valid, a type error is raised below,
                            # which is the desired response.
                            res = hook_impl.function(*args)
                            wrapper_gen = cast(Generator[None, _Result[object], None], res)
                            next(wrapper_gen)  # first yield
                            teardowns.append((wrapper_gen,))
                        except StopIteration:
                            _raise_wrapfail(wrapper_gen, "did not yield")
                    elif hook_impl.wrapper:
                        try:
                            # If this cast is not valid, a type error is raised below,
                            # which is the desired response.
                            res = hook_impl.function(*args)
                            function_gen = cast(Generator[None, object, object], res)
                            next(function_gen)  # first yield
                            teardowns.append(function_gen)
                        except StopIteration:
                            _raise_wrapfail(function_gen, "did not yield")
                    else:
>                       res = hook_impl.function(*args)

.tox/py/lib/python3.11/site-packages/pluggy/_callers.py:80: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

item = <RuffItem ruff>

    def pytest_runtest_call(item: Item) -> None:
        _update_current_test_var(item, "call")
        try:
            del sys.last_type
            del sys.last_value
            del sys.last_traceback
        except AttributeError:
            pass
        try:
            item.runtest()
        except Exception as e:
            # Store trace info to allow postmortem debugging
            sys.last_type = type(e)
            sys.last_value = e
            assert e.__traceback__ is not None
            # Skip *this* frame
            sys.last_traceback = e.__traceback__.tb_next
>           raise e

.tox/py/lib/python3.11/site-packages/_pytest/runner.py:177: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

item = <RuffItem ruff>

    def pytest_runtest_call(item: Item) -> None:
        _update_current_test_var(item, "call")
        try:
            del sys.last_type
            del sys.last_value
            del sys.last_traceback
        except AttributeError:
            pass
        try:
>           item.runtest()

.tox/py/lib/python3.11/site-packages/_pytest/runner.py:169: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <RuffItem ruff>

    def runtest(self):
>       check_file(self.fspath)

.tox/py/lib/python3.11/site-packages/pytest_ruff.py:73: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

path = local('/Users/jaraco/code/jaraco/jaraco.home/jaraco/home/mock hdhomerun.py')

    def check_file(path):
        ruff = find_ruff_bin()
        command = f"{ruff} {path} --quiet --show-source"
        child = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)
        stdout, _ = child.communicate()
        if stdout:
>           raise RuffError(stdout.decode())
E           pytest_ruff.RuffError: hdhomerun.py:1:1: E902 No such file or directory (os error 2)
E            |
E            |
E           
E           jaraco/home/mock:1:1: E902 No such file or directory (os error 2)
E            |
E            |

.tox/py/lib/python3.11/site-packages/pytest_ruff.py:88: RuffError
======================================================================================== warnings summary ========================================================================================
jaraco/home/hdhomerun.py::home.hdhomerun.gather_status
  /Users/jaraco/code/jaraco/jaraco.home/conftest.py:24: EncodingWarning: 'encoding' argument not specified
    script = files('jaraco.home').joinpath('mock hdhomerun.py').read_text()

jaraco/home/hdhomerun.py::home.hdhomerun.gather_status
  /Users/jaraco/code/jaraco/jaraco.home/.tox/py/lib/python3.11/site-packages/mockproc/mockprocess.py:53: EncodingWarning: 'encoding' argument not specified
    fh = open( filename, 'w' )

jaraco/home/hdhomerun.py::home.hdhomerun.gather_status
  /Users/jaraco/code/jaraco/jaraco.home/jaraco/home/hdhomerun.py:46: EncodingWarning: 'encoding' argument not specified.
    return parse_devices(subprocess.check_output(cmd, text=True))

jaraco/home/hdhomerun.py::home.hdhomerun.gather_status
jaraco/home/hdhomerun.py::home.hdhomerun.gather_status
jaraco/home/hdhomerun.py::home.hdhomerun.get_status
  /Users/jaraco/code/jaraco/jaraco.home/jaraco/home/hdhomerun.py:56: EncodingWarning: 'encoding' argument not specified.
    line = subprocess.check_output(cmd, text=True)

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html

---------- coverage: platform darwin, python 3.11.4-final-0 ----------
Name                            Stmts   Miss  Cover   Missing
-------------------------------------------------------------
conftest.py                        15      0   100%
docs/conf.py                        9      0   100%
jaraco/home/__init__.py             0      0   100%
jaraco/home/hdhomerun.py           92     28    70%   84, 108, 112-120, 124-127, 132-136, 140, 144-155, 159-163
jaraco/home/mock hdhomerun.py       8      5    38%   7-11
jaraco/home/thermostat.py          56     31    45%   28-29, 33-36, 41, 54-55, 58, 65, 69, 72-80, 86-87, 90-91, 98-101, 106-107
-------------------------------------------------------------
TOTAL                             180     64    64%

============================================================================================== mypy ==============================================================================================
Success: no issues found in 6 source files
==================================================================================== short test summary info =====================================================================================
FAILED jaraco/home/mock hdhomerun.py::ruff - pytest_ruff.RuffError: hdhomerun.py:1:1: E902 No such file or directory (os error 2)
=========================================================================== 1 failed, 23 passed, 6 warnings in 17.43s ============================================================================
py: exit 1 (19.11 seconds) /Users/jaraco/code/jaraco/jaraco.home> pytest pid=86287
.pkg: _exit> python /Users/jaraco/.local/pipx/venvs/tox/lib/python3.11/site-packages/pyproject_api/_backend.py True setuptools.build_meta
  py: FAIL code 1 (30.02=setup[10.91]+cmd[19.11] seconds)
  evaluation failed :( (30.12 seconds)

New release

Hello, would it be please possible to release a new minor version with this commit included? 🙏

Bug: pytest --ruff and --ruff-format don't work at the command line

repro:

pytest --ruff --ruff-format tests/assets/check_broken.py tests/assets/format_broken.py

output:

================================================================================== short test summary info ===================================================================================
FAILED tests/assets/check_broken.py::ruff - TypeError: check_file() takes 1 positional argument but 2 were given
FAILED tests/assets/check_broken.py::ruff::format - TypeError: format_file() takes 1 positional argument but 2 were given
FAILED tests/assets/format_broken.py::ruff - TypeError: check_file() takes 1 positional argument but 2 were given
FAILED tests/assets/format_broken.py::ruff::format - TypeError: format_file() takes 1 positional argument but 2 were given
===================================================================================== 4 failed in 0.25s ======================================================================================

and a typo:
also, was this intended to be --force-exclude instead of force-exclude on L66 of this change?
df22c01

New errors emerge after upgrade to 0.4.0

With the 0.4.0 release of pytest-ruff, the Setuptools test suite has started failing on the TRY003 checks. Downgrading to pytest-ruff 0.3.2 restores the previous behavior. I don't see anything in the changelog indicating why new checks would start failing.

Is it expected that previously passing checks are now failing with pytest-ruff 0.4?

Chongus tracebacks when failed test

Hello, when ruff check fails it also prints out giant tracebacks from pytest (sometimes even ~2.3k lines) just to inform me that I forgot to sort imports, or whatever. I found that there is --tb=short or --tb=line switch for pytest to reduce the clutter, but I would like to see stack traces within other tests if possible. This is an example screenshot of one RuffError (I can't even tell what the error is since it just says pytest_ruff.RuffError) in one file with --tb=short flag.

Would it be please possible to not include the tracebacks without the need to shorten all other tracebacks with the --tb=... flag)?

image

Parse error of ruff.toml silently ignored

When the ruff.toml file contains syntax errors which cannot be parsed, the ruff checks are silently ignored and no error is printed when run via pytest --ruff:

$ cat ruff.toml                                                                                1 ↵
llline-length = 95    # erroneous keyword llline-length

[lint]
select = ["E", "F", "W"]
ignore = ["E241", "E226", "E402"]
preview = true
$ cat pytest.ini 
[pytest]
addopts = --ruff .
$ cat a.py 
#!/usr/bin/env python3
import sys

print("hi")
$ ruff check                                                                                   1 ↵
ruff failed
  Cause: Failed to parse /tmp/ttt/ruff.toml
  Cause: TOML parse error at line 1, column 1
  |
1 | llline-length = 95
  | ^^^^^^^^^^^^^^^^^^
unknown field `llline-length`
$ pytest .                                                                                     2 ↵
======================================================== test session starts ========================================================
platform linux -- Python 3.12.1, pytest-8.2.1, pluggy-1.5.0
rootdir: /tmp/ttt
configfile: pytest.ini
plugins: ruff-0.3.2
collected 1 item                                                                                                                    

a.py .                                                                                                                        [100%]

========================================================= 1 passed in 0.01s ==

I would expect that the user is shown the error message from ruff itself. Otherwise, an erroneous ruff.toml file will disable any ruff checks.

Emergent Access Denied errors on Windows

With the release of pytest-ruff 0.4, when running a sizeable test suite under pytest-xdist on Windows, many failures are emitted. This issue was reported in pypa/setuptools#4467.

It seems that there's a race condition when ruff is invoked in parallel on the same project on Windows. The cache files collide and fail to be saved.

This issue was masked in 0.3.2 because the exit code from ruff was 2 and ignored.

I wonder if pytest-ruff should detect these "errors saving cache" and continue to suppress them. Or maybe ruff should have a separate exit code for "access denied when saving cache" so that "pytest-ruff" can suppress that code.

Thoughts?

ruff binary not found when installed to PATH

pytest-ruff seems to be making some assumptions about how binaries can be found for an environment, assumptions that fail expectations for a pip-run environment:

 ~ @ pip-run pytest-ruff -- -c "import pytest_ruff; print(pytest_ruff.find_ruff_bin())"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/var/folders/f2/2plv6q2n7l932m2x004jlw340000gn/T/pip-run-jrfhl2p1/ruff/__main__.py", line 28, in find_ruff_bin
    raise FileNotFoundError(path)
FileNotFoundError: /Users/jaraco/Library/Python/3.12/bin/ruff

Even though the binary is present and made prominent by the installation of pytest-ruff:

 ~ @ pip-run pytest-ruff -- -c "import shutil; print(shutil.which('ruff'))"
/var/folders/f2/2plv6q2n7l932m2x004jlw340000gn/T/pip-run-740q7ysr/bin/ruff

The same issue can be observed without using pip-run by using pip install --target:

 draft @ pip install --target target pytest-ruff -q
 draft @ env PYTHONPATH=target PATH=target/bin:$PATH py -c "import pytest_ruff; pytest_ruff.find_ruff_bin()"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/Users/jaraco/draft/target/ruff/__main__.py", line 28, in find_ruff_bin
    raise FileNotFoundError(path)
FileNotFoundError: /Users/jaraco/Library/Python/3.12/bin/ruff

I understand that it may be undesirable to simply rely on ruff as found on $PATH (as shutil.which does), but it would be nice if ruff could be picked up if installed using --target.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.