From 5a1588251f5f2bf40956138d4bf6a4e327dd8d2e Mon Sep 17 00:00:00 2001 From: Oleg Shpynov Date: Wed, 16 Jun 2010 17:02:54 +0400 Subject: [PATCH 1/8] PY-1086 False positive for "Cannot perform refactoring when execution flow is interrupted" --- .../codeFragment/PyCodeFragmentUtil.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentUtil.java b/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentUtil.java index ca1c502f430b..883a62326091 100644 --- a/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentUtil.java +++ b/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentUtil.java @@ -92,9 +92,23 @@ public class PyCodeFragmentUtil { } // If we see more than 1 outer instruction, controlflow is interrupted - if (outerInstructions.size() > 1){ + if (outerInstructions.size() > 2){ throw new CannotCreateCodeFragmentException(PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.when.execution.flow.is.interrupted")); } + if (outerInstructions.size() == 2){ + boolean errorFound = true; + for (Instruction outerInstruction : outerInstructions) { + // Here we check control flow when for statement content is beeing extracted + if (outerInstruction instanceof ReadWriteInstruction && + PyForStatementNavigator.getPyForStatementByIterable(outerInstruction.getElement())!=null){ + errorFound = false; + break; + } + } + if (errorFound){ + throw new CannotCreateCodeFragmentException(PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.when.execution.flow.is.interrupted")); + } + } // Building code fragment final PyCodeFragmentBuilder builder = new PyCodeFragmentBuilder(owner, start, end); From da1b4990913ebd2e9249449187d62bb935c30e5d Mon Sep 17 00:00:00 2001 From: Oleg Shpynov Date: Wed, 16 Jun 2010 17:49:50 +0400 Subject: [PATCH 2/8] PY-992 Extract Method doesn't handle conditional returns correctly --- .../codeInsight/codeFragment/PyCodeFragmentUtil.java | 9 ++++++--- .../extractmethod/conditionalreturn.before.py | 7 +++++++ .../python/refactoring/PyExtractMethodTest.java | 5 +++++ 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 python/testData/refactoring/extractmethod/conditionalreturn.before.py diff --git a/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentUtil.java b/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentUtil.java index 883a62326091..241703071895 100644 --- a/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentUtil.java +++ b/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentUtil.java @@ -100,9 +100,12 @@ public class PyCodeFragmentUtil { for (Instruction outerInstruction : outerInstructions) { // Here we check control flow when for statement content is beeing extracted if (outerInstruction instanceof ReadWriteInstruction && - PyForStatementNavigator.getPyForStatementByIterable(outerInstruction.getElement())!=null){ - errorFound = false; - break; + PyForStatementNavigator.getPyForStatementByIterable(outerInstruction.getElement())!=null) { + // In case when return instruction is inside + if (!returnInstructionInside){ + errorFound = false; + break; + } } } if (errorFound){ diff --git a/python/testData/refactoring/extractmethod/conditionalreturn.before.py b/python/testData/refactoring/extractmethod/conditionalreturn.before.py new file mode 100644 index 000000000000..ea7119b33e3a --- /dev/null +++ b/python/testData/refactoring/extractmethod/conditionalreturn.before.py @@ -0,0 +1,7 @@ +class Conference(object): + def __init__(self): + self.talks = [] + + def getTalkAt(self, hour): + for start, end, name in self.talks: + if hour >= start and hour < end: return name \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/refactoring/PyExtractMethodTest.java b/python/testSrc/com/jetbrains/python/refactoring/PyExtractMethodTest.java index c04760dd7c97..6a0334228c5a 100644 --- a/python/testSrc/com/jetbrains/python/refactoring/PyExtractMethodTest.java +++ b/python/testSrc/com/jetbrains/python/refactoring/PyExtractMethodTest.java @@ -126,4 +126,9 @@ public class PyExtractMethodTest extends LightMarkedTestCase { public void testClass() throws Throwable { doTest("context/class.before.py", "bar", "context/class.after.py"); } + + public void testConditionalReturn() throws Throwable { + doTest("conditionalreturn.before.py", "bar", "Cannot perform refactoring when execution flow is interrupted"); + } + } From d2f79bb67d00b45ac9590c61f3ee75289b5a076f Mon Sep 17 00:00:00 2001 From: Oleg Shpynov Date: Wed, 16 Jun 2010 18:22:35 +0400 Subject: [PATCH 3/8] cleanup --- .../refactoring/extractmethod/PyExtractMethodUtil.java | 8 ++++---- .../jetbrains/python/refactoring/PyCodeFragmentTest.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/python/src/com/jetbrains/python/refactoring/extractmethod/PyExtractMethodUtil.java b/python/src/com/jetbrains/python/refactoring/extractmethod/PyExtractMethodUtil.java index b50a9fbf9a77..e0580cb844f4 100644 --- a/python/src/com/jetbrains/python/refactoring/extractmethod/PyExtractMethodUtil.java +++ b/python/src/com/jetbrains/python/refactoring/extractmethod/PyExtractMethodUtil.java @@ -47,7 +47,7 @@ public class PyExtractMethodUtil { final CodeFragment fragment, final PsiElement statement1, final PsiElement statement2) { - if (!fragment.getOutputVariables().isEmpty() && fragment.isReturnInstructonInside()) { + if (!fragment.getOutputVariables().isEmpty() && fragment.isReturnInstructionInside()) { CommonRefactoringUtil.showErrorHint(project, editor, "Cannot perform refactoring from expression with local variables modifications and return instructions inside code fragment", RefactoringBundle.message("error.title"), "refactoring.extractMethod"); @@ -79,7 +79,7 @@ public class PyExtractMethodUtil { // Generating call element final StringBuilder builder = new StringBuilder(); - if (fragment.isReturnInstructonInside()) { + if (fragment.isReturnInstructionInside()) { builder.append("return "); } if (isMethod){ @@ -157,7 +157,7 @@ public class PyExtractMethodUtil { return; } - if (fragment.isReturnInstructonInside()){ + if (fragment.isReturnInstructionInside()){ CommonRefactoringUtil.showErrorHint(project, editor, "Cannot extract method with return instructions inside code fragment", RefactoringBundle.message("error.title"), "refactoring.extractMethod"); @@ -186,7 +186,7 @@ public class PyExtractMethodUtil { // Generating call element final StringBuilder builder = new StringBuilder(); - if (fragment.isReturnInstructonInside()) { + if (fragment.isReturnInstructionInside()) { builder.append("return "); } if (isMethod){ diff --git a/python/testSrc/com/jetbrains/python/refactoring/PyCodeFragmentTest.java b/python/testSrc/com/jetbrains/python/refactoring/PyCodeFragmentTest.java index 03fdb9bae802..db7a65fa4a33 100644 --- a/python/testSrc/com/jetbrains/python/refactoring/PyCodeFragmentTest.java +++ b/python/testSrc/com/jetbrains/python/refactoring/PyCodeFragmentTest.java @@ -70,7 +70,7 @@ public class PyCodeFragmentTest extends LightMarkedTestCase { final StringBuffer buffer = new StringBuffer(); try { final CodeFragment fragment = PyCodeFragmentUtil.createCodeFragment((ScopeOwner)context, startElement, endElement); - if (fragment.isReturnInstructonInside()) { + if (fragment.isReturnInstructionInside()) { buffer.append("Return instruction inside found").append("\n"); } buffer.append("In:\n"); From 1a82eec7cb309cd177155be5544f3f8cf755942d Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Wed, 16 Jun 2010 16:25:08 +0400 Subject: [PATCH 4/8] remove dead code for writing inspections in Python --- python/src/META-INF/python-plugin-common.xml | 3 - .../PythonPyInspectionToolProvider.java | 71 ------------------- 2 files changed, 74 deletions(-) delete mode 100644 python/src/com/jetbrains/python/inspections/PythonPyInspectionToolProvider.java diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml index 6c53e1d844d7..8748529963bc 100644 --- a/python/src/META-INF/python-plugin-common.xml +++ b/python/src/META-INF/python-plugin-common.xml @@ -33,9 +33,6 @@ - diff --git a/python/src/com/jetbrains/python/inspections/PythonPyInspectionToolProvider.java b/python/src/com/jetbrains/python/inspections/PythonPyInspectionToolProvider.java deleted file mode 100644 index e04ce9d71647..000000000000 --- a/python/src/com/jetbrains/python/inspections/PythonPyInspectionToolProvider.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.jetbrains.python.inspections; - -import com.intellij.codeInspection.InspectionProfileEntry; -import com.intellij.codeInspection.InspectionToolsFactory; -import com.intellij.codeInspection.LocalInspectionTool; -import com.intellij.codeInspection.ex.InspectionTool; -import com.intellij.codeInspection.ex.InspectionToolRegistrar; -import com.intellij.codeInspection.ex.LocalInspectionToolWrapper; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.util.Function; -import com.jetbrains.python.JythonManager; -import org.jetbrains.annotations.Nullable; -import org.python.core.PyList; -import org.python.core.PyObject; - -import java.util.HashSet; -import java.util.Set; - -/** - * @author yole - */ -public class PythonPyInspectionToolProvider implements InspectionToolsFactory { - private final JythonManager myManager; - - public static PythonPyInspectionToolProvider getInstance() { - return ApplicationManager.getApplication().getComponent(PythonPyInspectionToolProvider.class); - } - - private final InspectionToolRegistrar myRegistrar; - - private final Set myShortNames = new HashSet(); - - public PythonPyInspectionToolProvider(final InspectionToolRegistrar registrar, final JythonManager jythonManager) { - myRegistrar = registrar; - myManager = jythonManager; - } - - @Nullable - public LocalInspectionTool createLocalInspectionTool(final String inspectionToolName) { - try { - final PyObject object = myManager.eval(inspectionToolName + "()"); - final Object o = object.__tojava__(LocalInspectionTool.class); - return (LocalInspectionTool)o; - } - catch (Exception e) { - return null; - } - } - - public InspectionProfileEntry[] createTools() { - myRegistrar.registerInspectionToolProvider(new Function() { - @Nullable - public InspectionTool fun(String shortName) { - final LocalInspectionTool tool = myShortNames.contains(shortName) ? createLocalInspectionTool(shortName) : null; - return tool != null ? new LocalInspectionToolWrapper(tool) : null; - } - }); - - myManager.execScriptFromResource("inspections/inspections.py"); - - final PyList pyList = (PyList) myManager.eval("getAllInspections()"); - int len = pyList.__len__(); - final InspectionProfileEntry[] profileEntries = new InspectionProfileEntry[len]; - for(int i=0; i Date: Wed, 16 Jun 2010 17:24:40 +0400 Subject: [PATCH 5/8] rewrite PyDemorganIntention in Java, get rid of initializing Jython in IDEA process (PY-792) --- python/src/META-INF/python-plugin-common.xml | 22 +--- .../com/jetbrains/python/JythonManager.java | 68 ---------- .../jetbrains/python/PyExtensionFactory.java | 20 --- .../intentions/PyDemorganIntention.java | 123 ++++++++++++++++++ 4 files changed, 128 insertions(+), 105 deletions(-) delete mode 100644 python/src/com/jetbrains/python/JythonManager.java delete mode 100644 python/src/com/jetbrains/python/PyExtensionFactory.java create mode 100644 python/src/com/jetbrains/python/codeInsight/intentions/PyDemorganIntention.java diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml index 8748529963bc..e19d92a37b7f 100644 --- a/python/src/META-INF/python-plugin-common.xml +++ b/python/src/META-INF/python-plugin-common.xml @@ -61,9 +61,6 @@ - - @@ -71,59 +68,50 @@ - - PyDemorganIntention - Python - PyDemorganIntention - com.jetbrains.python.codeInsight.intentions.ImportToImportFromIntention Python - ImportToImportFromIntention com.jetbrains.python.codeInsight.intentions.ImportFromToImportIntention Python - ImportFromToImportIntention com.jetbrains.python.codeInsight.intentions.ImportToggleAliasIntention Python - ImportToggleAliasIntention com.jetbrains.python.codeInsight.intentions.PySplitIfIntention Python - PySplitIfIntention com.jetbrains.python.codeInsight.intentions.PyNegateComparisonIntention Python - PyNegateComparisonIntention com.jetbrains.python.codeInsight.intentions.PyStringConcatenationToFormatIntention Python - PyStringConcatenationToFormatIntention com.jetbrains.python.codeInsight.intentions.ConvertFormatOperatorToMethodIntention Python - ConvertFormatOperatorToMethodIntention com.jetbrains.python.codeInsight.intentions.PyFlipComparisonIntention Python - PyFlipComparisonIntention + + + + com.jetbrains.python.codeInsight.intentions.PyDemorganIntention + Python diff --git a/python/src/com/jetbrains/python/JythonManager.java b/python/src/com/jetbrains/python/JythonManager.java deleted file mode 100644 index a9da91a3817e..000000000000 --- a/python/src/com/jetbrains/python/JythonManager.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.jetbrains.python; - -import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.util.PathUtil; -import org.jetbrains.annotations.NonNls; -import org.python.core.*; -import org.python.util.PythonInterpreter; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.Properties; - -/** - * @author yole - */ -public class JythonManager { - private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.JythonManager"); - - public static JythonManager getInstance() { - return ServiceManager.getService(JythonManager.class); - } - - private final PythonInterpreter myInterpreter; - - public JythonManager() { - @NonNls Properties postProperties = new Properties(); - postProperties.put("python.cachedir.skip", "true"); - String pythonScriptPath = findPythonScriptPath(); - postProperties.put("python.path", pythonScriptPath); - LOG.info("Loading Python scripts from " + pythonScriptPath); - PySystemState.initialize(PySystemState.getBaseProperties(), postProperties, new String[] { "" }); - myInterpreter = new PythonInterpreter(); - } - - private String findPythonScriptPath() { - @NonNls String classRoot = PathUtil.getJarPathForClass(getClass()); - if (classRoot.endsWith(".jar")) { - return classRoot; - } - // compiled classes - File f = new File(new File(classRoot).getParent(), "python-py"); - if (f.exists()) { - return f.getPath(); - } - throw new RuntimeException("Can't figure out Python script path: " + classRoot); - } - - public void execScriptFromResource(@NonNls String resourcePath) { - final InputStream stream = getClass().getClassLoader().getResourceAsStream(resourcePath); - try { - myInterpreter.execfile(stream); - } - finally { - try { - stream.close(); - } - catch (IOException e) { - LOG.error(e); - } - } - } - - public PyObject eval(@NonNls final String code) { - return myInterpreter.eval(code); - } -} diff --git a/python/src/com/jetbrains/python/PyExtensionFactory.java b/python/src/com/jetbrains/python/PyExtensionFactory.java deleted file mode 100644 index 188b1f40cd25..000000000000 --- a/python/src/com/jetbrains/python/PyExtensionFactory.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.jetbrains.python; - -import com.intellij.openapi.extensions.ExtensionFactory; -import com.intellij.openapi.diagnostic.Logger; -import org.python.core.PyObject; - -/** - * @author yole - */ -public class PyExtensionFactory implements ExtensionFactory { - private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.PyExtensionFactory"); - - public Object createInstance(final String factoryArgument, final String implementationClass) { - LOG.info("Instantiating Jython class " + implementationClass + " from script " + factoryArgument); - JythonManager jythonManager = JythonManager.getInstance(); - jythonManager.execScriptFromResource(factoryArgument); - final PyObject object = jythonManager.eval(implementationClass + "()"); - return object.__tojava__(Object.class); - } -} diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/PyDemorganIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/PyDemorganIntention.java new file mode 100644 index 000000000000..da1eb8cc58dd --- /dev/null +++ b/python/src/com/jetbrains/python/codeInsight/intentions/PyDemorganIntention.java @@ -0,0 +1,123 @@ +package com.jetbrains.python.codeInsight.intentions; + +import com.intellij.codeInsight.intention.impl.BaseIntentionAction; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.IncorrectOperationException; +import com.jetbrains.python.PyTokenTypes; +import com.jetbrains.python.psi.*; +import org.jetbrains.annotations.NotNull; + +/** + * @author yole + */ +public class PyDemorganIntention extends BaseIntentionAction { + @NotNull + @Override + public String getText() { + return "DeMorgan Law"; + } + + @NotNull + @Override + public String getFamilyName() { + return "DeMorgan Law"; + } + + @Override + public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { + PyBinaryExpression expression = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyBinaryExpression.class); + if (expression != null) { + PyElementType op = expression.getOperator(); + if (op == PyTokenTypes.AND_KEYWORD || op == PyTokenTypes.OR_KEYWORD) { + return true; + } + } + return false; + } + + @Override + public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { + PyBinaryExpression expression = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), + PyBinaryExpression.class); + PyElementType op = expression.getOperator(); + String converted = convertConjunctionExpression(expression, op); + replaceExpression(converted, expression); + } + + private static void replaceExpression(String newExpression, PyBinaryExpression expression) { + PsiElement expressionToReplace = expression; + String expString = "not(" + newExpression + ')'; + PsiElement parent = expression.getParent().getParent(); + if (isNegation(parent)) { + expressionToReplace = parent; + expString = newExpression; + } + PyElementGenerator generator = PyElementGenerator.getInstance(expression.getProject()); + PyExpression newCall = generator.createExpressionFromText(expString); + PsiElement insertedElement = expressionToReplace.replace(newCall); + // codeStyleManager = expression.getManager().getCodeStyleManager() + // TODO codeStyleManager.reformat(insertedElement) + } + + private static String convertConjunctionExpression(PyBinaryExpression exp, PyElementType tokenType) { + PyExpression lhs = exp.getLeftExpression(); + String lhsText, rhsText; + if (isConjunctionExpression(lhs, tokenType)) { + lhsText = convertConjunctionExpression((PyBinaryExpression)lhs, tokenType); + } + else { + lhsText = convertLeafExpression(lhs); + } + + PyExpression rhs = exp.getRightExpression(); + if (isConjunctionExpression(rhs, tokenType)) { + rhsText = convertConjunctionExpression((PyBinaryExpression)rhs, tokenType); + } + else { + rhsText = convertLeafExpression(rhs); + } + + String flippedConjunction = (tokenType == PyTokenTypes.AND_KEYWORD) ? " or " : " and "; + return lhsText + flippedConjunction + rhsText; + } + + private static String convertLeafExpression(PyExpression condition) { + if (isNegation(condition)) { + PyExpression negated = getNegated(condition); + if (negated == null) { + return ""; + } + return negated.getText(); + } + else { + return "not " + condition.getText(); + } + } + + private static PyExpression getNegated(PyExpression expression) { + PyExpression operand = ((PyPrefixExpression)expression).getOperand(); + return operand; // TODO strip () + } + + private static boolean isConjunctionExpression(PyExpression expression, PyElementType tokenType) { + if (expression instanceof PyBinaryExpression) { + PyElementType operator = ((PyBinaryExpression) expression).getOperator(); + return operator == tokenType; + } + return false; + } + + private static boolean isNegation(PsiElement expression) { + if (!(expression instanceof PyPrefixExpression)) { + return false; + } + PyElementType op = ((PyPrefixExpression)expression).getOperationSign(); + return op == PyTokenTypes.NOT_KEYWORD; + } + + +} From 67fc51d0f1e85960b260a3ae3ed60473613b2c2e Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Wed, 16 Jun 2010 18:25:35 +0400 Subject: [PATCH 6/8] PY-1059 --- python/src/com/jetbrains/python/sdk/PythonSdkType.java | 1 + 1 file changed, 1 insertion(+) diff --git a/python/src/com/jetbrains/python/sdk/PythonSdkType.java b/python/src/com/jetbrains/python/sdk/PythonSdkType.java index 665587bd2081..0697924d592d 100644 --- a/python/src/com/jetbrains/python/sdk/PythonSdkType.java +++ b/python/src/com/jetbrains/python/sdk/PythonSdkType.java @@ -352,6 +352,7 @@ public class PythonSdkType extends SdkType { public void run(@NotNull final ProgressIndicator indicator) { try { final SdkModificator sdkModificator = sdk.getSdkModificator(); + sdkModificator.removeAllRoots(); setupSdkPaths(sdkModificator, indicator); //sdkModificator.commitChanges() must happen outside, in dispatch thread. sdkModificatorRef.set(sdkModificator); From dd5d425614598aa227364f994922d6e84ceea339 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Wed, 16 Jun 2010 18:34:58 +0400 Subject: [PATCH 7/8] enable drag & drop from Variables to Watches in PyCharm (PY-1054) --- .../src/com/jetbrains/python/debugger/PyDebugValue.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/src/com/jetbrains/python/debugger/PyDebugValue.java b/python/src/com/jetbrains/python/debugger/PyDebugValue.java index 63a1c590a426..47aec3a7fec5 100644 --- a/python/src/com/jetbrains/python/debugger/PyDebugValue.java +++ b/python/src/com/jetbrains/python/debugger/PyDebugValue.java @@ -74,18 +74,18 @@ public class PyDebugValue extends XValue { } // todo: pass StringBuilder to recursive calls - public String getQualifiedExpression() { + public String getEvaluationExpression() { if (myParent == null) { return getTempName(); } else if ("list".equals(myParent.getType()) || "tuple".equals(myParent.getType())) { - return new StringBuilder().append(myParent.getQualifiedExpression()).append('[').append(myName).append(']').toString(); + return new StringBuilder().append(myParent.getEvaluationExpression()).append('[').append(myName).append(']').toString(); } else if ("dict".equals(myParent.getType())) { - return new StringBuilder().append(myParent.getQualifiedExpression()).append("['").append(myName).append("']").toString(); + return new StringBuilder().append(myParent.getEvaluationExpression()).append("['").append(myName).append("']").toString(); } else { - return new StringBuilder().append(myParent.getQualifiedExpression()).append('.').append(myName).toString(); + return new StringBuilder().append(myParent.getEvaluationExpression()).append('.').append(myName).toString(); } } From 45296195f851e1be2b41e52e9323b2c237c40857 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Wed, 16 Jun 2010 20:13:52 +0400 Subject: [PATCH 8/8] support unittest2 test skipping functionality (PY-1062) --- python/helpers/pycharm/tcunittest.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/helpers/pycharm/tcunittest.py b/python/helpers/pycharm/tcunittest.py index 5b78c7f4205d..d8b37caad90f 100644 --- a/python/helpers/pycharm/tcunittest.py +++ b/python/helpers/pycharm/tcunittest.py @@ -40,6 +40,9 @@ class TeamcityTestResult(TestResult): self.messages.testFailed(self.getTestName(test), message='Failure', details=err) + def addSkip(self, test, reason): + self.messages.testIgnored(self.getTestName(test), message=reason) + def startTest(self, test): setattr(test, "startTime", datetime.datetime.now()) self.messages.testStarted(self.getTestName(test), location="python_uttestid://" + str(test.id()))