Skip to content

extend-select = ["I"] from pyproject.toml not honored for tests in very specific circumstances #12453

@jamesharr

Description

@jamesharr

We've observed an odd behavior where the extend-select = ["I"] is not honored for code in tests/ when a very specific set of circumstances occur. I'm not even sure I have the minimum reproduction steps completely down, but I can very consistently reproduce the situation.

  1. The tests import items from specific module mod1
  2. The python code structure starts at src instead of the repo root
  3. A directory mod1 exists at the repo root. This directory can be empty
  4. This is the most bizarre part: This depends on the contents of the test files tests/test_mod1.py. Modifying the file contents can in some cases cause extend-select from pyproject.toml to be respected. Some of these are very insignificant changes, like insertion of a comment between imports, removal of a blank line between imports, etc.
  5. Run ruff check --no-cache and observe that tests/test_mod1.py is not checked for I001 as requested in pyproject.toml. Be sure to use --no-cache during tests because some actions will not invalidate the cache that otherwise would, like creating/removing the top-level mod1 directory.

The minimum reproduction is below, but is also available as a git repository https://github.com/jamesharr/ruff-issue-12453

This bug was initially spotted due to a dirty working directory, and we eventually narrowed it down to an empty to-level folder that matches one of the modules a test imports. Of course git ignores empty directories, so we were chasing our tails on this one for a few days.

Ruff version: ruff 0.5.4 on macOS 14.5, though we've also seen it with 0.5.2 and 0.5.3

Command: ruff check --no-cache .

Directory structure:

% tree .
.
├── README.md
├── mod1                   # Empty directory required for bug reproduction
├── pyproject.toml         # Sets "extend-select" to "I"
├── src
│   └── mod1
│       └── __init__.py
└── tests
    └── test_mod1.py       # File is not always check

5 directories, 4 files

Contents of pyproject.toml

[tool.poetry]
name = "ruff-bug"
version = "0.0.0"
description = "Ruff bug demo project"
authors = []
readme = "README.md"
packages = [
    { include = "mod1", from = "src" },
]

[tool.ruff.lint]
extend-select = ["I"]

Contents of tests/test_mod1.py

def test_bug1():
    # For the bug to occur:
    # - [repo-root]/mod1 must exist as a directory; contents of directory do not seem to matter
    # - There must be an empty line after the first import import
    # - It cannot be two blank lines
    # - It cannot be line with a comment
    # - It can be a line consisting of only tabs/whitespaces
    # - All 3 imports must exist and in this order
    # - All 3 imports must exist within the function
    #
    # When the above conditions are met, then tests/* are NOT checked for I001 (unsorted imports)
    # as requested in pyproject.toml.
    #
    # Files in src/* are checked for I001 as requested in pyproject.toml, regardless of the contents
    # of this file.

    from external_lib import qux

    from mod1 import foo
    from mod1.bar import baz

    # I think the code below is only important because it uses the above imports
    print(qux)
    print(foo)
    print(baz)

% cat src/mod1/__init__.py
# The contents of this file seem to have no bearing bug reproduction
# - I001 seems to always be checked for this file, as specified in pyproject.toml
#
# You can illustrate this by re-ordering these imports
import os
import sys

# The following code only exists to use the imports and avoid F401 (imported but unused)
print(os)
print(sys)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions