[python] PY-59322 Don't add gutter icon to pytest fixtures with test_* prefix

Merge-request: IJ-MR-181128
Merged-by: Egor Eliseev <Egor.Eliseev@jetbrains.com>

GitOrigin-RevId: 3f1d486d3bab13647f498ce3f146b43c3638f2ea
This commit is contained in:
chbndrhnns
2025-11-03 15:55:56 +00:00
committed by intellij-monorepo-bot
parent 57dc355ada
commit a5a215495f
3 changed files with 55 additions and 11 deletions
@@ -9,18 +9,35 @@ import com.jetbrains.python.psi.PyClass
import com.jetbrains.python.psi.PyFile
import com.jetbrains.python.psi.PyFunction
import com.jetbrains.python.psi.types.TypeEvalContext
import com.jetbrains.python.testing.pyTestFixtures.TEST_FIXTURE_DECORATOR_NAMES
/**
* Any "test_" function could be used as test by pytest.
* Pytest could use any "test_" function as a test.
* See `PythonUnitTestDetectorsBasedOnSettings.isTestFunction`.
*/
fun isTestFunction(function: PyFunction) = function.name?.startsWith("test") == true
fun isTestFunction(function: PyFunction): Boolean {
// Check if the function name starts with "test"
val isTestNamed = function.name?.startsWith("test") == true
if (!isTestNamed) return false
// Get the list of decorators, if any, otherwise consider this a test function
val decoratorList = function.decoratorList?.decorators ?: return true
// Determine if any decorator matches known pytest fixture names
val isPytestFixture = decoratorList.any { decorator ->
val decoratorName = decorator.callee?.text
decoratorName in TEST_FIXTURE_DECORATOR_NAMES
}
// A test function must not be a pytest fixture
return !isPytestFixture
}
/**
* Inheritor of TestCase class is always test for unittest and could also be launched with pytest.
* See `PythonUnitTestDetectorsBasedOnSettings.isTestFunction`.
*/
fun isUnitTestCaseClass(clazz: PyClass, context: TypeEvalContext) = clazz.inherits(context, "unittest.TestCase", "unittest.case.TestCase")
fun isUnitTestCaseClass(clazz: PyClass, context: TypeEvalContext): Boolean = clazz.inherits(context, "unittest.TestCase", "unittest.case.TestCase")
/**
* Checks if class [isUnitTestCaseClass] or both conditions are true: it's name starts/ends with "Test" and it has at least one
@@ -55,13 +72,13 @@ fun isTestClass(clazz: PyClass, context: TypeEvalContext): Boolean {
fun isTestFile(file: PyFile,
context: TypeEvalContext): Boolean {
return if (file.topLevelClasses.stream().anyMatch { o: PyClass ->
return if (file.topLevelClasses.any { o: PyClass ->
isTestClass(o, context)
}) {
true
}
else file.name.startsWith("test_") ||
file.topLevelFunctions.stream().anyMatch { o: PyFunction ->
file.topLevelFunctions.any { o: PyFunction ->
isTestFunction(o)
}
}
@@ -0,0 +1,8 @@
import pytest
@pytest.fixture
def test_fixture():
return 42
def test_actual():
assert True
@@ -6,12 +6,12 @@ import com.intellij.execution.lineMarker.RunLineMarkerContributor.Info
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.jetbrains.python.fixtures.PyTestCase
import junit.framework.TestCase
open class PyTestRunLineMarkerTest : PyTestCase() {
companion object {
const val TESTS_DIR = "/pyTestLineMarker/"
const val PYTHON_FILE = "pythonFile.py"
const val FIXTURE_FILE = "fixtureTest.py"
}
override fun getTestDataPath(): String = super.getTestDataPath() + TESTS_DIR
@@ -25,9 +25,9 @@ open class PyTestRunLineMarkerTest : PyTestCase() {
protected fun getCaretElement(fileName: String): PsiElement? {
val psiFile = configureByFile(fileName)
TestCase.assertNotNull("Can't find test file", psiFile)
assertNotNull("Can't find test file", psiFile)
val element = psiFile?.findElementAt(myFixture.caretOffset)
TestCase.assertNotNull("Can't find caret element", element)
assertNotNull("Can't find caret element", element)
return element
}
@@ -37,14 +37,14 @@ open class PyTestRunLineMarkerTest : PyTestCase() {
protected fun assertInfoFound(element: PsiElement, lineMarkerContributor: RunLineMarkerContributor) {
val info = getInfo(element, lineMarkerContributor)
TestCase.assertNotNull("Info is not found", info)
assertNotNull("Info is not found", info)
if (info != null) {
TestCase.assertNotNull("Run icon is not found", info.icon)
assertNotNull("Run icon is not found", info.icon)
}
}
protected fun assertInfoNotFound(element: PsiElement, lineMarkerContributor: RunLineMarkerContributor) {
TestCase.assertNull("Info is found", getInfo(element, lineMarkerContributor))
assertNull("Info is found", getInfo(element, lineMarkerContributor))
}
fun testPythonFile() {
@@ -54,4 +54,23 @@ open class PyTestRunLineMarkerTest : PyTestCase() {
assertInfoFound(element, lineMarkerContributor)
}
}
fun testFixtureWithTestPrefix() {
val lineMarkerContributor = PyTestLineMarkerContributor()
myFixture.configureByText(FIXTURE_FILE, """
import pytest
@pytest.fixture
def <caret>test_fixture():
return 42
def test_actual():
assert True
""".trimIndent())
val fixtureElement = myFixture.file.findElementAt(myFixture.caretOffset)
assertNotNull(fixtureElement)
assertInfoNotFound(fixtureElement!!, lineMarkerContributor)
}
}