PY-62419 Pytest fixtures from pytest_plugins are not resolved

Add fixtures searching in `pytest_plugins` statement from `conftest.py`
Add tests


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

GitOrigin-RevId: 673ad9a614dba84ffdf87398f908a54af02be81f
This commit is contained in:
Egor.Eliseev
2023-12-08 10:44:56 +00:00
committed by intellij-monorepo-bot
parent d31e9ef795
commit 0aecae551e
15 changed files with 120 additions and 2 deletions
@@ -8,6 +8,7 @@ import com.intellij.psi.PsiFile
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.stubs.StubIndex
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.PathUtil
import com.intellij.util.Processor
import com.intellij.util.ThreeState
import com.jetbrains.extensions.getSdk
@@ -164,9 +165,10 @@ private fun findRightFixture(fixtureCandidates: List<PyTestFixture>,
*/
private fun searchInConftest(fixtureCandidates: List<PyTestFixture>, currentDirectory: PsiDirectory, elementName: String, func: PyFunction?): NamedFixtureLink? {
fixtureCandidates.find { it.isInConftestInDir(currentDirectory) }?.let { return NamedFixtureLink(it, null) }
// search in imports in "conftest.py" file
// search in imports and 'pytest_plugins' in "conftest.py" file
(currentDirectory.findFile(CONFTEST_PY) as? PyFile)?.let { pyFile ->
getFixtureFromImports(pyFile, elementName, func, fixtureCandidates)?.let { return it }
getFixtureFromPytestPlugins(pyFile, fixtureCandidates)?.let { return it }
}
return null
}
@@ -192,6 +194,40 @@ private fun getFixtureFromImports(targetFile: PyFile, elementName: String, func:
return null
}
/**
* Return fixture from pytest_plugins
*/
private fun getFixtureFromPytestPlugins(targetFile: PyFile, fixtureCandidates: List<PyTestFixture>): NamedFixtureLink? {
val pyTestPluginsStatement = targetFile.statements.findLast { it is PyAssignmentStatement && it.isAssignmentTo("pytest_plugins") }
as? PyAssignmentStatement ?: return null
val assignedValue = pyTestPluginsStatement.assignedValue ?: return null // str or Sequence[str]
val fixtures: List<PyExpression> = when (assignedValue) {
is PyListLiteralExpression -> assignedValue.elements.toList()
is PyStringLiteralExpression -> listOf(assignedValue)
is PyParenthesizedExpression -> assignedValue.children.find { it is PyTupleExpression }?.let { tuple ->
(tuple as PyTupleExpression).elements.filterIsInstance<PyStringLiteralExpression>()
} ?: emptyList()
else -> emptyList()
}
if (fixtures.isEmpty()) return null
val fixturesPaths = fixtures.map {
val text = it.text
text.subSequence(1, text.length - 1).toString().replace('.', '/')
}
val candidate = fixtureCandidates.find { fixtureCandidate ->
var fixtureFilePath = fixtureCandidate.function?.containingFile?.virtualFile?.path ?: return@find false
fixtureFilePath = PathUtil.toSystemIndependentName(fixtureFilePath).let { it.subSequence(0, it.length - 3).toString() }
fixturesPaths.any { fixtureFilePath.endsWith(it) }
} ?: return null
return NamedFixtureLink(candidate, null)
}
/**
* @return Boolean If named parameter has fixture or not
*/
@@ -221,7 +257,6 @@ fun findDecoratorsByName(module: Module, vararg names: String): Iterable<PyDecor
}
private fun createFixture(decorator: PyDecorator): PyTestFixture? {
val target = decorator.target ?: return null
return (decorator.getNamedArgument("name") ?: target.name)?.let { name ->
@@ -0,0 +1 @@
pytest_plugins = ["fixtures.first", "fixtures.second"]
@@ -0,0 +1,5 @@
import pytest
@pytest.fixture
def first():
return 1
@@ -0,0 +1,5 @@
import pytest
@pytest.fixture
def second():
return 2
@@ -0,0 +1,5 @@
import pytest
def test_first(fi<caret>rst):
assert first == 1
@@ -0,0 +1,5 @@
import pytest
def test_second(sec<caret>ond):
assert second == 2
@@ -0,0 +1 @@
pytest_plugins = "fixtures.first"
@@ -0,0 +1,5 @@
import pytest
@pytest.fixture
def first():
return 1
@@ -0,0 +1,4 @@
import pytest
def test_first(fi<caret>rst):
assert first == 1
@@ -0,0 +1 @@
pytest_plugins = ("fixtures.first", "fixtures.second")
@@ -0,0 +1,5 @@
import pytest
@pytest.fixture
def first():
return 1
@@ -0,0 +1,5 @@
import pytest
@pytest.fixture
def second():
return 2
@@ -0,0 +1,5 @@
import pytest
def test_first(fi<caret>rst):
assert first == 1
@@ -0,0 +1,5 @@
import pytest
def test_second(sec<caret>ond):
assert second == 2
@@ -58,6 +58,20 @@ class PyTestFixtureResolvingTest : PyTestCase() {
const val TEST_IMPORTED_FIXTURE_IN_CONFTEST = "/test_imported_fixtures_in_conftest.py"
const val IMPORTED_FIXTURE_IN_CONFTEST_FOO_FIXTURES_DIR_NAME = "fixtures"
const val IMPORTED_FIXTURE_IN_CONFTEST_FOO_FIXTURES = "foo_fixtures.py"
const val PYTEST_PLUGINS_FIXTURES_DIR_NAME = "testPytestPluginsFixtures"
const val PYTEST_PLUGINS_FIXTURES_DIR = "/$PYTEST_PLUGINS_FIXTURES_DIR_NAME"
const val PYTEST_PLUGINS_FIXTURES_AS_LIST_DIR = "/pytest_plugins_as_list"
const val PYTEST_PLUGINS_FIXTURES_AS_STR_DIR = "/pytest_plugins_as_str"
const val PYTEST_PLUGINS_FIXTURES_AS_TUPLE_DIR = "/pytest_plugins_as_tuple"
const val PYTEST_PLUGINS_FIXTURES = "fixtures"
const val PYTEST_PLUGINS_FIXTURES_FIRST = "first.py"
const val PYTEST_PLUGINS_FIXTURES_SECOND = "second.py"
const val PYTEST_PLUGINS_FIXTURES_AS_LIST_FIRST_TEST = "/test_pytest_plugins_as_list_first.py"
const val PYTEST_PLUGINS_FIXTURES_AS_LIST_SECOND_TEST = "/test_pytest_plugins_as_list_second.py"
const val PYTEST_PLUGINS_FIXTURES_AS_TUPLE_FIRST_TEST = "/test_pytest_plugins_as_tuple_first.py"
const val PYTEST_PLUGINS_FIXTURES_AS_TUPLE_SECOND_TEST = "/test_pytest_plugins_as_tuple_second.py"
const val PYTEST_PLUGINS_FIXTURES_AS_STR_TEST = "/test_pytest_plugins_as_str.py"
}
override fun getTestDataPath() = super.getTestDataPath() + TESTS_SUBDIR
@@ -223,4 +237,21 @@ class PyTestFixtureResolvingTest : PyTestCase() {
fun testImportedFixtureInConftest() {
assertCorrectFile(IMPORTED_FIXTURE_IN_CONFTEST_DIR, TEST_IMPORTED_FIXTURE_IN_CONFTEST, IMPORTED_FIXTURE_IN_CONFTEST_FOO_FIXTURES, IMPORTED_FIXTURE_IN_CONFTEST_FOO_FIXTURES_DIR_NAME)
}
fun testPytestPluginsFixtureAsList() {
val testDir = PYTEST_PLUGINS_FIXTURES_DIR + PYTEST_PLUGINS_FIXTURES_AS_LIST_DIR
assertCorrectFile(testDir, PYTEST_PLUGINS_FIXTURES_AS_LIST_FIRST_TEST, PYTEST_PLUGINS_FIXTURES_FIRST, PYTEST_PLUGINS_FIXTURES)
assertCorrectFile(testDir, PYTEST_PLUGINS_FIXTURES_AS_LIST_SECOND_TEST, PYTEST_PLUGINS_FIXTURES_SECOND, PYTEST_PLUGINS_FIXTURES)
}
fun testPytestPluginsFixtureAsTuple() {
val testDir = PYTEST_PLUGINS_FIXTURES_DIR + PYTEST_PLUGINS_FIXTURES_AS_TUPLE_DIR
assertCorrectFile(testDir, PYTEST_PLUGINS_FIXTURES_AS_TUPLE_FIRST_TEST, PYTEST_PLUGINS_FIXTURES_FIRST, PYTEST_PLUGINS_FIXTURES)
assertCorrectFile(testDir, PYTEST_PLUGINS_FIXTURES_AS_TUPLE_SECOND_TEST, PYTEST_PLUGINS_FIXTURES_SECOND, PYTEST_PLUGINS_FIXTURES)
}
fun testPytestPluginsFixtureAsStr() {
val testDir = PYTEST_PLUGINS_FIXTURES_DIR + PYTEST_PLUGINS_FIXTURES_AS_STR_DIR
assertCorrectFile(testDir, PYTEST_PLUGINS_FIXTURES_AS_STR_TEST, PYTEST_PLUGINS_FIXTURES_FIRST, PYTEST_PLUGINS_FIXTURES)
}
}