Commit 8bf05dfc authored by Ilya.Kazakevich's avatar Ilya.Kazakevich
Browse files

PY-13191: Support single quotes for ParametersListUtil

parent bbdac0c9
No related merge requests found
Showing with 56 additions and 158 deletions
+56 -158
......@@ -19,7 +19,7 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtilRt;
import com.intellij.util.containers.HashSet;
import gnu.trove.TIntHashSet;
import org.jetbrains.annotations.NotNull;
import java.util.*;
......@@ -141,26 +141,20 @@ public class ParametersListUtil {
final StringBuilder token = new StringBuilder(128);
boolean inQuotes = false;
boolean escapedQuote = false;
final List<Character> possibleQuoteChars = new ArrayList<Character>(Collections.singletonList('"'));
final TIntHashSet possibleQuoteChars = new TIntHashSet();
possibleQuoteChars.add('"');
if (supportSingleQuotes) {
possibleQuoteChars.add('\'');
}
final Collection<Character> quoteChars = new HashSet<Character>(possibleQuoteChars);
char currentQuote = 0;
boolean nonEmpty = false;
for (int i = 0; i < parameterString.length(); i++) {
final char ch = parameterString.charAt(i);
if (quoteChars.contains(ch)) {
if ((inQuotes ? currentQuote == ch : possibleQuoteChars.contains(ch))) {
if (!escapedQuote) {
inQuotes = !inQuotes;
quoteChars.clear();
if (inQuotes) {
quoteChars.add(ch);
}
else {
quoteChars.addAll(possibleQuoteChars);
}
currentQuote = ch;
nonEmpty = true;
if (!keepQuotes) {
continue;
......@@ -178,8 +172,9 @@ public class ParametersListUtil {
continue;
}
}
else if (ch == '\\') {
if (i < parameterString.length() - 1 && quoteChars.contains(parameterString.charAt(i + 1))) {
else if (ch == '\\' && i < parameterString.length() - 1) {
final char nextchar = parameterString.charAt(i + 1);
if (inQuotes ? currentQuote == nextchar : possibleQuoteChars.contains(nextchar)) {
escapedQuote = true;
if (!keepQuotes) {
continue;
......
/*
* 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.intellij.execution.ExecutionException
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiErrorElement
import com.intellij.psi.PsiFileFactory
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 java.util.*
/**
* @author Ilya.Kazakevich
*/
//TODO: Migrate to [ParametersListUtil#parse] but support single quotes
/**
* Emulates command line processor (cmd, bash) 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
}
......@@ -50,15 +50,13 @@ import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.util.QualifiedName
import com.intellij.refactoring.listeners.RefactoringElementListener
import com.intellij.refactoring.listeners.UndoRefactoringElementAdapter
import com.intellij.util.execution.ParametersListUtil
import com.jetbrains.extensions.getQName
import com.jetbrains.extenstions.QNameResolveContext
import com.jetbrains.extenstions.splitNameParts
import com.jetbrains.extenstions.toElement
import com.jetbrains.python.PyBundle
import com.jetbrains.python.psi.PyClass
import com.jetbrains.python.psi.PyFile
import com.jetbrains.python.psi.PyFunction
import com.jetbrains.python.psi.PyQualifiedNameOwner
import com.jetbrains.python.psi.*
import com.jetbrains.python.psi.types.TypeEvalContext
import com.jetbrains.python.run.AbstractPythonRunConfiguration
import com.jetbrains.python.run.CommandLinePatcher
......@@ -486,7 +484,7 @@ abstract class PyUniversalTestConfiguration(project: Project,
private fun generateRawArguments(): List<String> {
val rawArguments = additionalArguments + " " + getCustomRawArgumentsString()
if (rawArguments.isNotBlank()) {
return listOf("--") + getParsedAdditionalArguments(project, rawArguments)
return listOf("--") + ParametersListUtil.parse(rawArguments, false, true)
}
return emptyList()
}
......
......@@ -50,36 +50,16 @@ public final class PythonNoseTestingTest extends PyEnvTestCase {
// Ensure slow test is not run when --attr="!slow" is provided
@Test
public void testMarkerWithSlow() throws Exception {
runPythonTest(
new PyProcessWithConsoleTestTask<PyNoseTestProcessRunner>("/testRunner/env/nose/test_with_slow", SdkCreationType.EMPTY_SDK) {
@NotNull
@Override
protected PyNoseTestProcessRunner createProcessRunner() throws Exception {
return new PyNoseTestProcessRunner("test_with_slow.py", 0) {
@Override
protected void configurationCreatedAndWillLaunch(@NotNull PyUniversalNoseTestConfiguration configuration) throws IOException {
super.configurationCreatedAndWillLaunch(configuration);
configuration.setAdditionalArguments("--attr=\"!slow\"");
}
};
}
@Override
protected void checkTestResults(@NotNull PyNoseTestProcessRunner runner,
@NotNull String stdout,
@NotNull String stderr,
@NotNull String all) {
Assert.assertEquals("--slow runner borken", "Test tree:\n" +
"[root]\n" +
".test_with_slow\n" +
"..test_fast(+)\n",
runner.getFormattedTestTree());
}
});
runPythonTest(new SlowRunnerTask("--attr=\"!slow\" -vvv"));
}
@Test
public void testMarkerWithSlowSingleQuotes() throws Exception {
runPythonTest(new SlowRunnerTask("--attr='!slow' -vvv"));
}
@Test
public void testMarkerWithSlowRegexp() throws Exception {
runPythonTest(new SlowRunnerTask("--attr='!slow' -vvv -m \"(?:^|[\\b_\\./-])[Tt]est\""));
}
@Test
public void testMultipleCases() throws Exception {
......@@ -210,4 +190,39 @@ public final class PythonNoseTestingTest extends PyEnvTestCase {
}
});
}
private static class SlowRunnerTask extends PyProcessWithConsoleTestTask<PyNoseTestProcessRunner> {
@NotNull
private final String myArguments;
SlowRunnerTask(@NotNull final String arguments) {
super("/testRunner/env/nose/test_with_slow", SdkCreationType.EMPTY_SDK);
myArguments = arguments;
}
@NotNull
@Override
protected PyNoseTestProcessRunner createProcessRunner() throws Exception {
return new PyNoseTestProcessRunner("test_with_slow.py", 0) {
@Override
protected void configurationCreatedAndWillLaunch(@NotNull PyUniversalNoseTestConfiguration configuration) throws IOException {
super.configurationCreatedAndWillLaunch(configuration);
configuration.setAdditionalArguments(myArguments);
}
};
}
@Override
protected void checkTestResults(@NotNull PyNoseTestProcessRunner runner,
@NotNull String stdout,
@NotNull String stderr,
@NotNull String all) {
Assert.assertEquals("--slow runner broken on arguments" + myArguments, "Test tree:\n" +
"[root]\n" +
".test_with_slow\n" +
"..test_fast(+)\n",
runner.getFormattedTestTree());
}
}
}
/*
* 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)
}
}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment