PY-31836: Use pytest parameter as test name, not keyword.

The correct way to use parameters is to add them to the test name.
"-k" is also works in some cases, but it is not official nor stable.

* pytest runner reports "testname[param]" as metainfo
* we fetch param
* add "parameters" field to the pytest
* glue it with test name

GitOrigin-RevId: 56c5d93fd93a12ba5742b21af51ea04823a9d8f7
This commit is contained in:
Ilya.Kazakevich
2019-12-30 13:34:33 +00:00
committed by intellij-monorepo-bot
parent b88d342c46
commit 4508e0de9d
4 changed files with 83 additions and 19 deletions
@@ -213,9 +213,8 @@ class EchoTeamCityMessages(object):
def pytest_runtest_logstart(self, nodeid, location):
# test name fetched from location passed as metainfo to PyCharm
# it will be used to run specific test using "-k"
# See IDEA-176950
# We only need method/function name because only it could be used as -k
# it will be used to run specific test
# See IDEA-176950, PY-31836
test_name = location[2]
if test_name:
test_name = str(test_name).split(".")[-1]
@@ -27,15 +27,22 @@ import com.intellij.psi.search.GlobalSearchScope
import com.jetbrains.python.PyNames
import com.jetbrains.python.PythonHelper
import com.jetbrains.python.run.targetBasedConfiguration.PyRunTargetVariant
import com.jetbrains.python.testing.PyTestSharedForm.*
/**
* Pytest runner
*/
//Fetch param from parametrized test name spam[eggs]
private val PARAM_REGEX = Regex("\\[(.+)]$")
class PyTestSettingsEditor(configuration: PyAbstractTestConfiguration) :
PyAbstractTestSettingsEditor(
PyTestSharedForm.create(configuration, PyTestSharedForm.CustomOption(
PyTestConfiguration::keywords.name, PyRunTargetVariant.PATH, PyRunTargetVariant.PYTHON)))
create(
configuration,
CustomOption(PyTestConfiguration::keywords.name, PyRunTargetVariant.PATH, PyRunTargetVariant.PYTHON),
CustomOption(PyTestConfiguration::parameters.name, PyRunTargetVariant.PATH, PyRunTargetVariant.PYTHON)
))
class PyPyTestExecutionEnvironment(configuration: PyTestConfiguration, environment: ExecutionEnvironment) :
PyTestExecutionEnvironment<PyTestConfiguration>(configuration, environment) {
@@ -52,6 +59,8 @@ class PyTestConfiguration(project: Project, factory: PyTestFactory)
: PyAbstractTestConfiguration(project, factory, PyTestFrameworkService.getSdkReadableNameByFramework(PyNames.PY_TEST)) {
@ConfigField
var keywords: String = ""
@ConfigField
var parameters: String = ""
override fun getState(executor: Executor, environment: ExecutionEnvironment): RunProfileState? =
PyPyTestExecutionEnvironment(this, environment)
@@ -70,14 +79,31 @@ class PyTestConfiguration(project: Project, factory: PyTestFactory)
return target.generateArgumentsLine(this) + listOf(rawArgumentsSeparator, "--last-failed")
}
override fun getTestSpec(): List<String> {
// Parametrized test must add parameter to target.
// So, foo.spam becomes foo.spam[param]
if (parameters.isNotEmpty() && target.targetType == PyRunTargetVariant.PYTHON) {
return super.getTestSpec().toMutableList().apply {
this[size - 1] = last() + "[$parameters]"
}
}
return super.getTestSpec()
}
override fun isFrameworkInstalled(): Boolean = VFSTestFrameworkListener.getInstance().isTestFrameworkInstalled(sdk, PyNames.PY_TEST)
override fun setMetaInfo(metaInfo: String) {
keywords = metaInfo
// Metainfo contains test name along with params.
parameters = getParamFromMetaInfo(metaInfo)
}
/**
* Fetch params from test name
*/
private fun getParamFromMetaInfo(metaInfo: String) = PARAM_REGEX.find(metaInfo)?.groupValues?.getOrNull(1) ?: ""
override fun isSameAsLocation(target: ConfigurationTarget, metainfo: String?): Boolean {
return super.isSameAsLocation(target, metainfo) && metainfo == keywords
return super.isSameAsLocation(target, metainfo) && getParamFromMetaInfo(metainfo ?: "") == parameters
}
}
@@ -63,10 +63,10 @@ import java.util.regex.Matcher
* New configuration factories
*/
internal val pythonFactories get() = arrayOf<PythonConfigurationFactoryBase>(
PyUnitTestFactory(),
PyTestFactory(),
PyNoseTestFactory(),
PyTrialTestFactory())
PyUnitTestFactory(),
PyTestFactory(),
PyNoseTestFactory(),
PyTrialTestFactory())
/**
* Accepts text that may be wrapped in TC message. Unwraps it and removes TC escape code.
@@ -110,7 +110,7 @@ fun isTestElement(element: PsiElement, testCaseClassRequired: ThreeState, typeEv
/**
* Since runners report names of tests as qualified name, no need to convert it to PSI and back to string.
* We just save its name and provide it again to rerun
* @param metainfo additional info provided by test runner, in case of pytest it is test name that could be used as "-k" argument
* @param metainfo additional info provided by test runner, in case of pytest it is test name with parameters (if test is parametrized)
*/
private class PyTargetBasedPsiLocation(val target: ConfigurationTarget,
element: PsiElement,
@@ -483,8 +483,8 @@ abstract class PyAbstractTestConfiguration(project: Project,
return ConfigurationTarget(qualifiedName, PyRunTargetVariant.PYTHON).generateArgumentsLine(this)
}
override fun getTestSpec(location: Location<*>,
failedTest: AbstractTestProxy): String? {
final override fun getTestSpec(location: Location<*>,
failedTest: AbstractTestProxy): String? {
val list = getPythonTestSpecByLocation(location)
if (list.isEmpty()) {
return null
@@ -504,7 +504,7 @@ abstract class PyAbstractTestConfiguration(project: Project,
return result + generateRawArguments(true)
}
fun getTestSpec(): List<String> {
open fun getTestSpec(): List<String> {
return target.generateArgumentsLine(this) + generateRawArguments()
}
@@ -613,7 +613,8 @@ abstract class PyAbstractTestConfiguration(project: Project,
internal open fun shouldSeparateTargetPath(): Boolean = true
/**
* @param metaInfo String "metainfo" field provided by test runner
* @param metaInfo String "metainfo" field provided by test runner.
* Pytest reports test name with parameters here
*/
open fun setMetaInfo(metaInfo: String) {
@@ -178,7 +178,7 @@ public final class PythonPyTestingTest extends PyEnvTestCase {
}
/**
* Test name must be reported as meta info to be used as argument for "-k" for parametrized tests
* Test name must be reported as meta info to be used as parameter for for parametrized tests
*/
@Test
public void testMetaInfoForMethod() {
@@ -234,6 +234,44 @@ public final class PythonPyTestingTest extends PyEnvTestCase {
});
}
/**
* Ensure that testName[param] is only launched for parametrized test if param provided
*/
@Test
public void testParametrizedRunByParameter() {
runPythonTest(
new PyProcessWithConsoleTestTask<PyTestTestProcessRunner>("/testRunner/env/pytest/parametrized", SdkCreationType.EMPTY_SDK) {
@NotNull
@Override
protected PyTestTestProcessRunner createProcessRunner() {
return new PyTestTestProcessRunner("test_pytest_parametrized.py", 1) {
@Override
protected void configurationCreatedAndWillLaunch(@NotNull PyTestConfiguration configuration) throws IOException {
super.configurationCreatedAndWillLaunch(configuration);
configuration.getTarget().setTarget("test_pytest_parametrized.test_eval");
configuration.getTarget().setTargetType(PyRunTargetVariant.PYTHON);
configuration.setMetaInfo("test_eval[three plus file-8]");
}
};
}
@Override
protected void checkTestResults(@NotNull final PyTestTestProcessRunner runner,
@NotNull final String stdout,
@NotNull final String stderr,
@NotNull final String all, int exitCode) {
assertEquals("Only one test should be launched",
"Test tree:\n" +
"[root](-)\n" +
".test_pytest_parametrized(-)\n" +
"..test_eval(-)\n" +
"...(three plus file-8)(-)\n",
runner.getFormattedTestTree());
}
});
}
/**
* See https://github.com/JetBrains/teamcity-messages/issues/131
@@ -497,8 +535,8 @@ public final class PythonPyTestingTest extends PyEnvTestCase {
configuration.setWorkingDirectory(myFixture.getTempDirPath());
ReadAction.run(() -> Assert.assertThat("Failed to resolve qname",
configuration.getTarget().asPsiElement(configuration),
Matchers.instanceOf(PyFile.class)));
configuration.getTarget().asPsiElement(configuration),
Matchers.instanceOf(PyFile.class)));
}
});
}