diff --git a/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixture.kt b/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixture.kt index 36735b1672f2..daa67ae4a7ed 100644 --- a/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixture.kt +++ b/python/src/com/jetbrains/python/testing/pyTestFixtures/PyTestFixture.kt @@ -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, */ private fun searchInConftest(fixtureCandidates: List, 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): 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 = 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() + } ?: 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 diff --git a/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_list/conftest.py b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_list/conftest.py new file mode 100644 index 000000000000..388edf6ff19f --- /dev/null +++ b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_list/conftest.py @@ -0,0 +1 @@ +pytest_plugins = ["fixtures.first", "fixtures.second"] \ No newline at end of file diff --git a/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_list/fixtures/first.py b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_list/fixtures/first.py new file mode 100644 index 000000000000..9176ec6c358c --- /dev/null +++ b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_list/fixtures/first.py @@ -0,0 +1,5 @@ +import pytest + +@pytest.fixture +def first(): + return 1 \ No newline at end of file diff --git a/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_list/fixtures/second.py b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_list/fixtures/second.py new file mode 100644 index 000000000000..c6fcaba27afe --- /dev/null +++ b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_list/fixtures/second.py @@ -0,0 +1,5 @@ +import pytest + +@pytest.fixture +def second(): + return 2 \ No newline at end of file diff --git a/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_list/test_pytest_plugins_as_list_first.py b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_list/test_pytest_plugins_as_list_first.py new file mode 100644 index 000000000000..cb8b1df6eb6b --- /dev/null +++ b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_list/test_pytest_plugins_as_list_first.py @@ -0,0 +1,5 @@ +import pytest + + +def test_first(first): + assert first == 1 diff --git a/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_list/test_pytest_plugins_as_list_second.py b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_list/test_pytest_plugins_as_list_second.py new file mode 100644 index 000000000000..0e21864919bd --- /dev/null +++ b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_list/test_pytest_plugins_as_list_second.py @@ -0,0 +1,5 @@ +import pytest + + +def test_second(second): + assert second == 2 diff --git a/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_str/conftest.py b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_str/conftest.py new file mode 100644 index 000000000000..1ea26603ee1d --- /dev/null +++ b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_str/conftest.py @@ -0,0 +1 @@ +pytest_plugins = "fixtures.first" \ No newline at end of file diff --git a/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_str/fixtures/first.py b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_str/fixtures/first.py new file mode 100644 index 000000000000..9176ec6c358c --- /dev/null +++ b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_str/fixtures/first.py @@ -0,0 +1,5 @@ +import pytest + +@pytest.fixture +def first(): + return 1 \ No newline at end of file diff --git a/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_str/test_pytest_plugins_as_str.py b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_str/test_pytest_plugins_as_str.py new file mode 100644 index 000000000000..fc32b6da1dbb --- /dev/null +++ b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_str/test_pytest_plugins_as_str.py @@ -0,0 +1,4 @@ +import pytest + +def test_first(first): + assert first == 1 \ No newline at end of file diff --git a/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_tuple/conftest.py b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_tuple/conftest.py new file mode 100644 index 000000000000..d8c4b747ee0e --- /dev/null +++ b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_tuple/conftest.py @@ -0,0 +1 @@ +pytest_plugins = ("fixtures.first", "fixtures.second") \ No newline at end of file diff --git a/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_tuple/fixtures/first.py b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_tuple/fixtures/first.py new file mode 100644 index 000000000000..9176ec6c358c --- /dev/null +++ b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_tuple/fixtures/first.py @@ -0,0 +1,5 @@ +import pytest + +@pytest.fixture +def first(): + return 1 \ No newline at end of file diff --git a/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_tuple/fixtures/second.py b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_tuple/fixtures/second.py new file mode 100644 index 000000000000..c6fcaba27afe --- /dev/null +++ b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_tuple/fixtures/second.py @@ -0,0 +1,5 @@ +import pytest + +@pytest.fixture +def second(): + return 2 \ No newline at end of file diff --git a/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_tuple/test_pytest_plugins_as_tuple_first.py b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_tuple/test_pytest_plugins_as_tuple_first.py new file mode 100644 index 000000000000..cb8b1df6eb6b --- /dev/null +++ b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_tuple/test_pytest_plugins_as_tuple_first.py @@ -0,0 +1,5 @@ +import pytest + + +def test_first(first): + assert first == 1 diff --git a/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_tuple/test_pytest_plugins_as_tuple_second.py b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_tuple/test_pytest_plugins_as_tuple_second.py new file mode 100644 index 000000000000..0e21864919bd --- /dev/null +++ b/python/testData/testPytestFixtureResolving/testPytestPluginsFixtures/pytest_plugins_as_tuple/test_pytest_plugins_as_tuple_second.py @@ -0,0 +1,5 @@ +import pytest + + +def test_second(second): + assert second == 2 diff --git a/python/testSrc/com/jetbrains/python/testing/PyTestFixtureResolvingTest.kt b/python/testSrc/com/jetbrains/python/testing/PyTestFixtureResolvingTest.kt index e1dafe52fdaa..5a07d180f98a 100644 --- a/python/testSrc/com/jetbrains/python/testing/PyTestFixtureResolvingTest.kt +++ b/python/testSrc/com/jetbrains/python/testing/PyTestFixtureResolvingTest.kt @@ -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) + } } \ No newline at end of file