From d333306575f628e95fb5fb8316685ca8e345fd05 Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Mon, 3 Aug 2020 15:30:47 +0300 Subject: [PATCH] PY-43632: Fix python tests localization Previous solution was kinda messy and buggy. i18n now moved to ConfigField GitOrigin-RevId: 4d1696067bd4c69e1b7c194533085a33652b8d61 --- .../messages/PyBundle.properties | 22 ++++--- .../jetbrains/python/testing/PyNoseTest.kt | 9 +-- .../com/jetbrains/python/testing/PyTest.kt | 19 ++---- .../python/testing/PyTestCustomOption.kt | 26 +++++++++ .../python/testing/PyTestLegacyInterop.kt | 2 +- .../python/testing/PyTestSharedForm.java | 58 +++++-------------- .../jetbrains/python/testing/PyTestsShared.kt | 17 +++--- .../jetbrains/python/testing/PyUnitTest.kt | 9 +-- 8 files changed, 75 insertions(+), 87 deletions(-) create mode 100644 python/src/com/jetbrains/python/testing/PyTestCustomOption.kt diff --git a/python/pluginResources/messages/PyBundle.properties b/python/pluginResources/messages/PyBundle.properties index cb9be74297ff..d2cc06174d28 100644 --- a/python/pluginResources/messages/PyBundle.properties +++ b/python/pluginResources/messages/PyBundle.properties @@ -269,22 +269,28 @@ runcfg.tests.empty_suite=Empty suite runcfg.doctest.display_name=Doctests runcfg.doctest.description=Python's doctests run configuration +runcfg.python_tests.config.additionalArguments=Additional Arguments +runcfg.python_tests.config.target=Target +runcfg.python_tests.config.targetType=Target Type + ### nosetests run conf runcfg.nosetests.display_name=Nosetests runcfg.nosetests.description=Python's nosetests run configuration - -### nosetests run configuration custom options -python.testing.nose.custom.options.regex.pattern=Regex Pattern: -python.testing.nose.custom.options.keywords=Keywords: -python.testing.nose.custom.options.parameters=Parameters: -python.testing.nose.custom.options.additional.arguments=Additional Arguments: +runcfg.nosetests.config.regexPattern=Regex Pattern ### pytest run conf runcfg.pytest.display_name=pytest + +# deprecated start runcfg.pytest.description=pytest run configuration runcfg.pytest.target=&Target: runcfg.pytest.parameters=&Options: runcfg.pytest.keywords=&Keywords: +# deprecated end + +runcfg.pytest.config.keywords=Keywords +runcfg.pytest.config.parameters=Parameters + ### trial run conf runcfg.trial.display_name=Twisted Trial @@ -298,6 +304,8 @@ runcfg.tox.runner=tox runner runcfg.tox=tox ### unittest run configuration +runcfg.unittest.config.pattern=Pattern + runcfg.unittest.display_name=Unittests runcfg.unittest.description=Python's unittest run configuration runcfg.unittest.no_script_name=Please specify script name @@ -1109,4 +1117,4 @@ python.compatibility.inspection.advertiser.version.stale.python3.version.warning python.install.framework.ensure.installed=Ensuring {0} is installed python.install.framework.checking.is.installed=Checking if {0} is installed... python.install.framework.installing=Installing {0}... -py.commandline.configure=Confiures Python SDKs for the project \ No newline at end of file +py.commandline.configure=Confiures Python SDKs for the project diff --git a/python/src/com/jetbrains/python/testing/PyNoseTest.kt b/python/src/com/jetbrains/python/testing/PyNoseTest.kt index f5d69e5bf4c4..6f407f9c6a96 100644 --- a/python/src/com/jetbrains/python/testing/PyNoseTest.kt +++ b/python/src/com/jetbrains/python/testing/PyNoseTest.kt @@ -6,7 +6,6 @@ import com.intellij.execution.configurations.RunProfileState import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.openapi.options.SettingsEditor import com.intellij.openapi.project.Project -import com.jetbrains.python.PyBundle import com.jetbrains.python.PyNames import com.jetbrains.python.PythonHelper import com.jetbrains.python.run.targetBasedConfiguration.PyRunTargetVariant @@ -17,10 +16,8 @@ import com.jetbrains.python.run.targetBasedConfiguration.PyRunTargetVariant class PyNoseTestSettingsEditor(configuration: PyAbstractTestConfiguration) : PyAbstractTestSettingsEditor( - PyTestSharedForm.create(configuration, PyTestSharedForm.CustomOption( - PyNoseTestConfiguration::regexPattern.name, - PyBundle.message("python.testing.nose.custom.options.regex.pattern"), - PyRunTargetVariant.PATH))) + PyTestSharedForm.create(configuration, PyTestCustomOption( + PyNoseTestConfiguration::regexPattern, PyRunTargetVariant.PATH))) class PyNoseTestExecutionEnvironment(configuration: PyNoseTestConfiguration, environment: ExecutionEnvironment) : PyTestExecutionEnvironment(configuration, environment) { @@ -31,7 +28,7 @@ class PyNoseTestExecutionEnvironment(configuration: PyNoseTestConfiguration, env class PyNoseTestConfiguration(project: Project, factory: PyNoseTestFactory) : PyAbstractTestConfiguration(project, factory, PyTestFrameworkService.getSdkReadableNameByFramework(PyNames.NOSE_TEST)), PyTestConfigurationWithCustomSymbol { - @ConfigField + @ConfigField("runcfg.nosetests.config.regexPattern") var regexPattern: String = "" override fun getState(executor: Executor, environment: ExecutionEnvironment): RunProfileState? = diff --git a/python/src/com/jetbrains/python/testing/PyTest.kt b/python/src/com/jetbrains/python/testing/PyTest.kt index 9a1401c939b1..2858894d79f9 100644 --- a/python/src/com/jetbrains/python/testing/PyTest.kt +++ b/python/src/com/jetbrains/python/testing/PyTest.kt @@ -25,7 +25,6 @@ import com.intellij.openapi.options.SettingsEditor import com.intellij.openapi.project.Project import com.intellij.openapi.util.Pair import com.intellij.psi.search.GlobalSearchScope -import com.jetbrains.python.PyBundle import com.jetbrains.python.PyNames import com.jetbrains.python.PythonHelper import com.intellij.execution.target.value.TargetEnvironmentFunction @@ -45,18 +44,8 @@ class PyTestSettingsEditor(configuration: PyAbstractTestConfiguration) : PyAbstractTestSettingsEditor( create( configuration, - CustomOption( - PyTestConfiguration::keywords.name, - PyBundle.message("python.testing.nose.custom.options.keywords"), - PyRunTargetVariant.PATH, - PyRunTargetVariant.PYTHON - ), - CustomOption( - PyTestConfiguration::parameters.name, - PyBundle.message("python.testing.nose.custom.options.parameters"), - PyRunTargetVariant.PATH, - PyRunTargetVariant.PYTHON - ) + PyTestCustomOption(PyTestConfiguration::keywords, PyRunTargetVariant.PATH, PyRunTargetVariant.PYTHON), + PyTestCustomOption(PyTestConfiguration::parameters, PyRunTargetVariant.PATH, PyRunTargetVariant.PYTHON) )) class PyPyTestExecutionEnvironment(configuration: PyTestConfiguration, environment: ExecutionEnvironment) : @@ -80,10 +69,10 @@ class PyPyTestExecutionEnvironment(configuration: PyTestConfiguration, environme class PyTestConfiguration(project: Project, factory: PyTestFactory) : PyAbstractTestConfiguration(project, factory, PyTestFrameworkService.getSdkReadableNameByFramework(PyNames.PY_TEST)), PyTestConfigurationWithCustomSymbol { - @ConfigField + @ConfigField("runcfg.pytest.config.keywords") var keywords: String = "" - @ConfigField + @ConfigField("runcfg.pytest.config.parameters") var parameters: String = "" override fun getState(executor: Executor, environment: ExecutionEnvironment): RunProfileState? = diff --git a/python/src/com/jetbrains/python/testing/PyTestCustomOption.kt b/python/src/com/jetbrains/python/testing/PyTestCustomOption.kt new file mode 100644 index 000000000000..79f0b932ec2e --- /dev/null +++ b/python/src/com/jetbrains/python/testing/PyTestCustomOption.kt @@ -0,0 +1,26 @@ +// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.jetbrains.python.testing + +import com.jetbrains.python.PyBundle +import com.jetbrains.python.run.targetBasedConfiguration.PyRunTargetVariant +import org.jetbrains.annotations.Nls +import java.util.* +import kotlin.reflect.KCallable + +internal class PyTestCustomOption(property: KCallable<*>, vararg supportedTypes: PyRunTargetVariant) { + val name: String = property.name + + @field:Nls + val localizedName: String + + init { + localizedName = property.annotations.filterIsInstance().firstOrNull()?.localizedName?.let { + PyBundle.message(it) + } ?: name + } + + /** + * Types to display this option for + */ + val mySupportedTypes: EnumSet = EnumSet.copyOf(supportedTypes.asList()) +} diff --git a/python/src/com/jetbrains/python/testing/PyTestLegacyInterop.kt b/python/src/com/jetbrains/python/testing/PyTestLegacyInterop.kt index 784c57dd455e..c34261bd0174 100644 --- a/python/src/com/jetbrains/python/testing/PyTestLegacyInterop.kt +++ b/python/src/com/jetbrains/python/testing/PyTestLegacyInterop.kt @@ -84,7 +84,7 @@ class PyTestLegacyConfigurationAdapter(newCo * * Null means "false" and used here to prevent saving useless "false" value in .xml for new configurations. */ - @ConfigField + @ConfigField("runcfg.pytest.config.parameters") // Fake i18n, which will be deleted soon along with outdated code var legacyInformationCopiedToNew: Boolean? = null init { diff --git a/python/src/com/jetbrains/python/testing/PyTestSharedForm.java b/python/src/com/jetbrains/python/testing/PyTestSharedForm.java index 59148bf0e79f..1118f58bd27b 100644 --- a/python/src/com/jetbrains/python/testing/PyTestSharedForm.java +++ b/python/src/com/jetbrains/python/testing/PyTestSharedForm.java @@ -34,7 +34,6 @@ import com.jetbrains.PySymbolFieldWithBrowseButton; import com.jetbrains.extensions.ContextAnchor; import com.jetbrains.extensions.ModuleBasedContextAnchor; import com.jetbrains.extensions.ProjectSdkContextAnchor; -import com.jetbrains.python.PyBundle; import com.jetbrains.python.psi.types.TypeEvalContext; import com.jetbrains.python.run.AbstractPyCommonOptionsForm; import com.jetbrains.python.run.PyBrowseActionListener; @@ -42,7 +41,6 @@ import com.jetbrains.python.run.PyCommonOptionsFormFactory; import com.jetbrains.python.run.targetBasedConfiguration.PyRunTargetVariant; import com.jetbrains.reflection.ReflectionUtilsKt; import com.jetbrains.reflection.SimplePropertiesProvider; -import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -55,17 +53,12 @@ import java.util.regex.Pattern; /** * Form to display run configuration. * It displays target type, target, additional arguments, custom options (if provided) and environment options - * Create with {@link #create(PyAbstractTestConfiguration, CustomOption...)}} + * Create with {@link #create(PyAbstractTestConfiguration, PyTestCustomOption...)}} * * @author Ilya.Kazakevich */ public final class PyTestSharedForm implements SimplePropertiesProvider { - /** - * Regex to convert additionalArgumentNames to "Additional Argument Names" - */ - private static final Pattern CAPITAL_LETTER = Pattern.compile("(?=\\p{Upper})"); - private JPanel myPanel; /** * Panel for test targets @@ -150,7 +143,7 @@ public final class PyTestSharedForm implements SimplePropertiesProvider { */ @NotNull public static PyTestSharedForm create(@NotNull final PyAbstractTestConfiguration configuration, - final CustomOption @NotNull ... customOptions) { + final PyTestCustomOption @NotNull ... customOptions) { final PyTestSharedForm form = new PyTestSharedForm(configuration.getModule(), configuration); for (final PyRunTargetVariant testTargetType : PyRunTargetVariant.values()) { @@ -171,23 +164,22 @@ public final class PyTestSharedForm implements SimplePropertiesProvider { setBorderToPanel(form.myPanel, configuration.getTestFrameworkName()); form.addCustomOptions( - ObjectArrays.concat(customOptions, new CustomOption( - PyTestsSharedKt.getAdditionalArgumentsPropertyName(), - PyBundle.message("python.testing.nose.custom.options.additional.arguments"), + ObjectArrays.concat(customOptions, new PyTestCustomOption( + PyTestsSharedKt.getAdditionalArgumentsProperty(), PyRunTargetVariant.values())) ); configuration.copyTo(ReflectionUtilsKt.getProperties(form, null, true)); return form; } - private void addCustomOptions(final CustomOption @NotNull ... customOptions) { + private void addCustomOptions(final PyTestCustomOption @NotNull ... customOptions) { if (customOptions.length == 0) { return; } final Map optionValueFields = new HashMap<>(); - for (final CustomOption option : customOptions) { + for (final PyTestCustomOption option : customOptions) { final JBTextField textField = new JBTextField(); - optionValueFields.put(option.myName, textField); + optionValueFields.put(option.getName(), textField); } final GridBagConstraints constraints = new GridBagConstraints(); @@ -195,9 +187,9 @@ public final class PyTestSharedForm implements SimplePropertiesProvider { constraints.gridy = 0; constraints.anchor = GridBagConstraints.LINE_START; - for (final CustomOption option : customOptions) { - final JBTextField textField = optionValueFields.get(option.myName); - final JLabel label = new JLabel(option.myLocalizedName); + for (final PyTestCustomOption option : customOptions) { + final JBTextField textField = optionValueFields.get(option.getName()); + final JLabel label = new JLabel(option.getLocalizedName()); label.setHorizontalAlignment(SwingConstants.LEFT); constraints.fill = GridBagConstraints.NONE; @@ -212,7 +204,7 @@ public final class PyTestSharedForm implements SimplePropertiesProvider { constraints.gridy++; - myCustomOptions.put(option.myName, new OptionHolder(option, label, textField)); + myCustomOptions.put(option.getName(), new OptionHolder(option, label, textField)); } } @@ -278,38 +270,16 @@ public final class PyTestSharedForm implements SimplePropertiesProvider { onTargetTypeChanged(); } - static final class CustomOption { - /** - * Option name - */ - @NotNull - private final String myName; - - private final @NotNull @Nls String myLocalizedName; - - /** - * Types to display this option for - */ - private final EnumSet mySupportedTypes; - - CustomOption(@NotNull final String name, - @NotNull @Nls String localizedName, - final PyRunTargetVariant @NotNull ... supportedTypes) { - myName = name; - myLocalizedName = localizedName; - mySupportedTypes = EnumSet.copyOf(Arrays.asList(supportedTypes)); - } - } private static final class OptionHolder { @NotNull - private final CustomOption myOption; + private final PyTestCustomOption myOption; @NotNull private final JLabel myOptionLabel; @NotNull private final JTextField myOptionValue; - private OptionHolder(@NotNull final CustomOption option, + private OptionHolder(@NotNull final PyTestCustomOption option, @NotNull final JLabel optionLabel, @NotNull final JTextField optionValue) { myOption = option; @@ -318,7 +288,7 @@ public final class PyTestSharedForm implements SimplePropertiesProvider { } private void setType(@NotNull final PyRunTargetVariant type) { - final boolean visible = myOption.mySupportedTypes.contains(type); + final boolean visible = myOption.getMySupportedTypes().contains(type); myOptionLabel.setVisible(visible); myOptionValue.setVisible(visible); } diff --git a/python/src/com/jetbrains/python/testing/PyTestsShared.kt b/python/src/com/jetbrains/python/testing/PyTestsShared.kt index 19acfa1db66a..9f519589a43b 100644 --- a/python/src/com/jetbrains/python/testing/PyTestsShared.kt +++ b/python/src/com/jetbrains/python/testing/PyTestsShared.kt @@ -58,6 +58,7 @@ import com.jetbrains.reflection.getProperties import jetbrains.buildServer.messages.serviceMessages.ServiceMessage import jetbrains.buildServer.messages.serviceMessages.TestStdErr import jetbrains.buildServer.messages.serviceMessages.TestStdOut +import org.jetbrains.annotations.PropertyKey import java.util.regex.Matcher /** @@ -83,7 +84,7 @@ fun processTCMessage(text: String): String { } } -internal fun getAdditionalArgumentsPropertyName() = com.jetbrains.python.testing.PyAbstractTestConfiguration::additionalArguments.name +internal fun getAdditionalArgumentsProperty() = PyAbstractTestConfiguration::additionalArguments /** * If runner name is here that means test runner only can run inheritors for TestCase @@ -101,7 +102,7 @@ fun isTestElement(element: PsiElement, testCaseClassRequired: ThreeState, typeEv it is PyFile && PythonUnitTestDetectorsBasedOnSettings.isTestFile(it, testCaseClassRequired, typeEvalContext) } is PyFunction -> PythonUnitTestDetectorsBasedOnSettings.isTestFunction(element, - testCaseClassRequired, typeEvalContext) + testCaseClassRequired, typeEvalContext) is com.jetbrains.python.psi.PyClass -> { PythonUnitTestDetectorsBasedOnSettings.isTestClass(element, testCaseClassRequired, typeEvalContext) } @@ -272,8 +273,8 @@ private const val DEFAULT_PATH = "" /** * Target depends on target type. It could be path to file/folder or python target */ -data class ConfigurationTarget(@ConfigField override var target: String, - @ConfigField override var targetType: PyRunTargetVariant) : TargetWithVariant { +data class ConfigurationTarget(@ConfigField("runcfg.python_tests.config.target") override var target: String, + @ConfigField("runcfg.python_tests.config.targetType") override var targetType: PyRunTargetVariant) : TargetWithVariant { fun copyTo(dst: ConfigurationTarget) { // TODO: do we have such method it in Kotlin? dst.target = target @@ -401,6 +402,7 @@ internal interface PyTestConfigurationWithCustomSymbol { * Separates file part and symbol */ val fileSymbolSeparator: String + /** * Separates parts of symbol name */ @@ -429,7 +431,8 @@ abstract class PyAbstractTestConfiguration(project: Project, @DelegationProperty val target: ConfigurationTarget = ConfigurationTarget(DEFAULT_PATH, PyRunTargetVariant.PATH) - @ConfigField + + @ConfigField("runcfg.python_tests.config.additionalArguments") var additionalArguments: String = "" val testFrameworkName: String = configurationFactory.name @@ -862,6 +865,6 @@ internal class PyTestsConfigurationProducer : AbstractPythonTestConfigurationPro @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.PROPERTY) /** - * Mark run configuration field with it to enable saving, resotring and form iteraction + * Mark run configuration field with it to enable saving, restoring and form iteraction */ -annotation class ConfigField +annotation class ConfigField(@param:PropertyKey(resourceBundle = PyBundle.BUNDLE) val localizedName: String) diff --git a/python/src/com/jetbrains/python/testing/PyUnitTest.kt b/python/src/com/jetbrains/python/testing/PyUnitTest.kt index 82bc2c5f52db..fc52075c5f62 100644 --- a/python/src/com/jetbrains/python/testing/PyUnitTest.kt +++ b/python/src/com/jetbrains/python/testing/PyUnitTest.kt @@ -36,12 +36,7 @@ import com.jetbrains.python.run.targetBasedConfiguration.PyRunTargetVariant class PyUnitTestSettingsEditor(configuration: PyAbstractTestConfiguration) : PyAbstractTestSettingsEditor( PyTestSharedForm.create(configuration, - PyTestSharedForm.CustomOption( - PyUnitTestConfiguration::pattern.name, - PyBundle.message("python.testing.nose.custom.options.additional.arguments"), - PyRunTargetVariant.PATH - ) - )) + PyTestCustomOption(PyUnitTestConfiguration::pattern, PyRunTargetVariant.PATH))) class PyUnitTestExecutionEnvironment(configuration: PyUnitTestConfiguration, environment: ExecutionEnvironment) : PyTestExecutionEnvironment(configuration, environment) { @@ -61,7 +56,7 @@ class PyUnitTestExecutionEnvironment(configuration: PyUnitTestConfiguration, env class PyUnitTestConfiguration(project: Project, factory: PyUnitTestFactory) : PyAbstractTestConfiguration(project, factory, PythonTestConfigurationsModel.getPythonsUnittestName()) { // Bare functions not supported in unittest: classes only - @ConfigField + @ConfigField("runcfg.unittest.config.pattern") var pattern: String? = null override fun getState(executor: Executor, environment: ExecutionEnvironment): RunProfileState? =