PY-23279, PY-9963: Use CommandLine language to parse arguments passed as additional

* GeneralCommandLine does not support "as is" command line: it should be splitted to arguments (due to ProcessBuilder interface).
This commit is contained in:
Ilya.Kazakevich
2017-03-31 02:54:43 +03:00
parent eb06387c01
commit 00bcd070b7
6 changed files with 139 additions and 4 deletions
@@ -71,6 +71,9 @@ public class PyElementGeneratorImpl extends PyElementGenerator {
return createDummyFile(langLevel, contents, false);
}
/**
* TODO: Use {@link PsiFileFactory} instead?
*/
public PsiFile createDummyFile(LanguageLevel langLevel, String contents, boolean physical) {
final PsiFileFactory factory = PsiFileFactory.getInstance(myProject);
final String name = getDummyFileName();
@@ -15,14 +15,22 @@
*/
package com.jetbrains.python.testing.universalTests
import com.intellij.execution.ExecutionException
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.module.ModuleUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiErrorElement
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.PsiFileSystemItem
import com.intellij.psi.PsiManager
import com.intellij.psi.util.QualifiedName
import com.jetbrains.commandInterface.commandLine.CommandLineLanguage
import com.jetbrains.commandInterface.commandLine.CommandLinePart
import com.jetbrains.commandInterface.commandLine.psi.CommandLineArgument
import com.jetbrains.commandInterface.commandLine.psi.CommandLineFile
import com.jetbrains.commandInterface.commandLine.psi.CommandLineOption
import com.jetbrains.extensions.getQName
import com.jetbrains.python.PyNames
import com.jetbrains.python.psi.PyFile
@@ -30,6 +38,7 @@ import com.jetbrains.python.psi.PyQualifiedNameOwner
import com.jetbrains.python.psi.PyUtil
import com.jetbrains.python.psi.resolve.fromModule
import com.jetbrains.python.psi.resolve.resolveModuleAt
import java.util.*
/**
* @author Ilya.Kazakevich
@@ -106,3 +115,45 @@ private fun findVFSItemRoot(virtualFile: VirtualFile, project: Project): Virtual
}
/**
* Emulates command line processor by parsing command line to arguments that can be provided as argv.
* Escape chars are not supported but quotes work.
* @throws ExecutionException if can't be parsed
*/
fun getParsedAdditionalArguments(project: Project, additionalArguments: String): List<String> {
val factory = PsiFileFactory.getInstance(project)
val file = factory.createFileFromText(CommandLineLanguage.INSTANCE,
String.format("fake_command %s", additionalArguments)) as CommandLineFile
if (file.children.any { it is PsiErrorElement }) {
throw ExecutionException("Additional arguments can't be parsed. Please check they are valid: $additionalArguments")
}
val additionalArgsList = ArrayList<String>()
var skipArgument = false
file.children.filterIsInstance(CommandLinePart::class.java).forEach {
when (it) {
is CommandLineOption -> {
val optionText = it.text
val possibleArgument = it.findArgument()
if (possibleArgument != null) {
additionalArgsList.add(optionText + possibleArgument.valueNoQuotes)
skipArgument = true
}
else {
additionalArgsList.add(optionText)
}
}
is CommandLineArgument -> {
if (!skipArgument) {
additionalArgsList.add(it.valueNoQuotes)
}
skipArgument = false
}
}
}
return additionalArgsList
}
@@ -385,7 +385,7 @@ abstract class PyUniversalTestConfiguration(project: Project,
private fun generateRawArguments(): List<String> {
val rawArguments = additionalArguments + " " + getCustomRawArgumentsString()
if (rawArguments.isNotBlank()) {
return listOf("--") + rawArguments.trim().split(" ")
return listOf("--") + getParsedAdditionalArguments(project, additionalArguments)
}
return emptyList()
}
@@ -0,0 +1,11 @@
import pytest
@pytest.mark.slow
def test_slow():
pass
def test_fast():
pass
@@ -31,6 +31,39 @@ import static org.junit.Assert.assertEquals;
@EnvTestTagsRequired(tags = "pytest")
public final class PythonPyTestingTest extends PyEnvTestCase {
// Ensure slow test is not run when -m "not slow" is provided
@Test
public void testMarkerWithSpaces() throws Exception {
runPythonTest(
new PyProcessWithConsoleTestTask<PyTestTestProcessRunner>("/testRunner/env/pytest/test_with_markers", SdkCreationType.EMPTY_SDK) {
@NotNull
@Override
protected PyTestTestProcessRunner createProcessRunner() throws Exception {
return new PyTestTestProcessRunner("test_with_markers.py", 0) {
@Override
protected void configurationCreatedAndWillLaunch(@NotNull PyUniversalPyTestConfiguration configuration) throws IOException {
super.configurationCreatedAndWillLaunch(configuration);
configuration.setAdditionalArguments("-m 'not slow'");
}
};
}
@Override
protected void checkTestResults(@NotNull PyTestTestProcessRunner runner,
@NotNull String stdout,
@NotNull String stderr,
@NotNull String all) {
Assert.assertEquals("Marker support broken", "Test tree:\n" +
"[root]\n" +
".test_with_markers\n" +
"..test_fast(+)\n",
runner.getFormattedTestTree());
}
});
}
@Test
public void testConfigurationProducer() throws Exception {
runPythonTest(
@@ -49,7 +82,8 @@ public final class PythonPyTestingTest extends PyEnvTestCase {
@Test
public void testTestsInSubFolderResolvable() throws Exception {
runPythonTest(
new PyUnitTestProcessWithConsoleTestTask.PyTestsInSubFolderRunner<PyTestTestProcessRunner>("test_metheggs", "test_funeggs", "test_first") {
new PyUnitTestProcessWithConsoleTestTask.PyTestsInSubFolderRunner<PyTestTestProcessRunner>("test_metheggs", "test_funeggs",
"test_first") {
@NotNull
@Override
protected PyTestTestProcessRunner createProcessRunner() throws Exception {
@@ -110,7 +144,8 @@ public final class PythonPyTestingTest extends PyEnvTestCase {
@Test
public void testProduceConfigurationOnFile() throws Exception {
runPythonTest(
new CreateConfigurationByFileTask<PyUniversalPyTestConfiguration>(PythonTestConfigurationsModel.PY_TEST_NAME, PyUniversalPyTestConfiguration.class, "spam.py") {
new CreateConfigurationByFileTask<PyUniversalPyTestConfiguration>(PythonTestConfigurationsModel.PY_TEST_NAME,
PyUniversalPyTestConfiguration.class, "spam.py") {
@NotNull
@Override
protected PsiElement getElementToRightClickOnByFile(@NotNull final String fileName) {
@@ -246,7 +281,7 @@ public final class PythonPyTestingTest extends PyEnvTestCase {
if (getLevelForSdk().isPy3K()) {
return new PyTestTestProcessRunner("folder_no_init_py/test_test.py", 2);
}
else {
else {
return new PyTestTestProcessRunner(toFullPath("folder_no_init_py/test_test.py"), 2) {
@Override
protected void configurationCreatedAndWillLaunch(@NotNull PyUniversalPyTestConfiguration configuration) throws IOException {
@@ -0,0 +1,35 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.testing.universalTests
import com.jetbrains.python.fixtures.PyTestCase
import org.junit.Assert
import org.junit.Test
/**
* @author Ilya.Kazakevich
*/
class PyTestRunnerUtilsKtTest : PyTestCase() {
@Test
fun testGetParsedAdditionalArguments() {
var list = getParsedAdditionalArguments(myFixture.project, "-v --color=red -m 'spam and eggs'")
Assert.assertEquals("List parsed incorrectly", listOf("-v", "--color=red", "-m", "spam and eggs"), list)
list = getParsedAdditionalArguments(myFixture.project, "--eggs=spam --foo=\"eggs and spam\"")
Assert.assertEquals("List parsed incorrectly", listOf("--eggs=spam", "--foo=eggs and spam"), list)
}
}