-
Notifications
You must be signed in to change notification settings - Fork 2k
extend-select = ["I"] from pyproject.toml not honored for tests in very specific circumstances #12453
Description
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.
- The tests import items from specific module
mod1 - The python code structure starts at
srcinstead of the repo root - A directory
mod1exists at the repo root. This directory can be empty - 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 causeextend-selectfrompyproject.tomlto be respected. Some of these are very insignificant changes, like insertion of a comment between imports, removal of a blank line between imports, etc. - Run
ruff check --no-cacheand observe thattests/test_mod1.pyis not checked forI001as requested inpyproject.toml. Be sure to use--no-cacheduring tests because some actions will not invalidate the cache that otherwise would, like creating/removing the top-levelmod1directory.
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 filesContents 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)