From 3ff525631a03ee1800d28c1f9d5f36c1c44c17d4 Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Fri, 25 Jul 2014 23:26:24 +0400 Subject: [PATCH] PY-8427 Behave BDD support PY-4186 Lettuce BDD tool support Steps generation fixed --- python/helpers/pycharm/_bdd_utils.py | 23 +++++++++++++- python/helpers/pycharm/behave_runner.py | 25 ++++++--------- .../python/psi/PyElementGenerator.java | 6 ++++ .../python/documentation/EpydocString.java | 7 +++++ .../python/documentation/SphinxDocString.java | 9 +++++- .../StructuredDocStringBase.java | 31 ++++++++++++++----- .../psi/impl/PyElementGeneratorImpl.java | 11 +++++-- .../python/psi/impl/PyFunctionBuilder.java | 30 ++++++++++++++++-- 8 files changed, 112 insertions(+), 30 deletions(-) diff --git a/python/helpers/pycharm/_bdd_utils.py b/python/helpers/pycharm/_bdd_utils.py index 4b11748999f3..26bd107c5288 100644 --- a/python/helpers/pycharm/_bdd_utils.py +++ b/python/helpers/pycharm/_bdd_utils.py @@ -52,6 +52,7 @@ class BddRunner(object): """ self.__base_dir = base_dir self.__last_test_start_time = None # TODO: Doc when use + self.__last_test_name = None def run(self): """" @@ -77,9 +78,26 @@ class BddRunner(object): :param location its location """ - self._test_started(test_name, location) + if test_name != self.__last_test_name: + self._test_started(test_name, location) self._test_failed(test_name, message="Test undefined", details="Please define test") + def _test_skipped(self, test_name, reason, location): + """ + Mark test as skipped + :param test_name: name of test + :param reason: why test was skipped + :type reason str + :type test_name str + :param location its location + + """ + if test_name != self.__last_test_name: + self._test_started(test_name, location) + self.tc_messages.testIgnored(test_name, "Skipped: {}".format(reason)) + self.__last_test_name = None + pass + def _test_failed(self, name, message, details): """ Report test failure @@ -91,6 +109,7 @@ class BddRunner(object): :type details str """ self.tc_messages.testFailed(name, message=message, details=details) + self.__last_test_name = None def _test_passed(self, name, duration=None): """ @@ -106,6 +125,7 @@ class BddRunner(object): duration_to_report = int(time.time() - self.__last_test_start_time) self.tc_messages.testFinished(name, duration=int(duration_to_report)) self.__last_test_start_time = None + self.__last_test_name = None def _test_started(self, name, location): """ @@ -115,6 +135,7 @@ class BddRunner(object): :type name str """ self.__last_test_start_time = time.time() + self.__last_test_name = name self.tc_messages.testStarted(name, self.__gen_location(location)) def _feature_or_scenario(self, is_started, name, location): diff --git a/python/helpers/pycharm/behave_runner.py b/python/helpers/pycharm/behave_runner.py index ca4b673fb794..cf16d875b81c 100644 --- a/python/helpers/pycharm/behave_runner.py +++ b/python/helpers/pycharm/behave_runner.py @@ -120,15 +120,6 @@ class _BehaveRunner(_bdd_utils.BddRunner): BddRunner for behave """ - @staticmethod - def __fix_location(element): - """ - Adds "filename" alias to "file" to preserve _bdd_utils contract - :param element: location - :return: fixed location - """ - element.location.file = element.location.filename - return element def __process_hook(self, is_started, context, element): """ @@ -139,7 +130,7 @@ class _BehaveRunner(_bdd_utils.BddRunner): :type context behave.runner.Context :param element feature/suite/step """ - _BehaveRunner.__fix_location(element) # To preserve _bdd_utils contract + element.location.file = element.location.filename # To preserve _bdd_utils contract if isinstance(element, Step): # Process step if is_started: @@ -148,14 +139,16 @@ class _BehaveRunner(_bdd_utils.BddRunner): self._test_passed(element.name, element.duration) elif element.status == 'failed': self._test_failed(element.name, element.error_message, traceback.format_exc()) + elif element.status == 'undefined': + self._test_undefined(element.name, element.location) else: - self.tc_messages.testIgnored(element.name, "Skipped with status: {}".format(element.status)) + self._test_skipped(element.name, element.status, element.location) elif not is_started and isinstance(element, Scenario) and element.status == 'failed': - # To process scenarios with undefined tests - # TODO: refactor - for step in [s for s in element.steps if s.status == 'undefined']: + # To process scenarios with undefined/skipped tests + for step in element.steps: assert isinstance(step, Step), step - self._test_undefined(step.name, _BehaveRunner.__fix_location(step).location) + if step.status != 'passed': + self.__process_hook(False, context, step) self._feature_or_scenario(is_started, element.name, element.location) elif isinstance(element, ScenarioOutline): self._feature_or_scenario(is_started, str(element.examples), element.location) @@ -210,6 +203,8 @@ if __name__ == "__main__": formatters.register_as(_Null, "com.intellij.python.null") my_config.format = ["com.intellij.python.null"] # To prevent output to stdout my_config.reporters = [] # To prevent summary to stdout + my_config.stdout_capture = False # For test output + my_config.stderr_capture = False # For test output base_dir = _bdd_utils.get_path_by_args(sys.argv) my_config.paths = _get_dirs_to_run(base_dir) _BehaveRunner(my_config, base_dir).run() diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyElementGenerator.java b/python/psi-api/src/com/jetbrains/python/psi/PyElementGenerator.java index 9f6e7c9ff021..197f7536e121 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/PyElementGenerator.java +++ b/python/psi-api/src/com/jetbrains/python/psi/PyElementGenerator.java @@ -126,4 +126,10 @@ public abstract class PyElementGenerator { @NotNull public abstract PyDecoratorList createDecoratorList(@NotNull final String... decoratorTexts); + + /** + * Creates new line whitespace + */ + @NotNull + public abstract PsiElement createNewLine(); } diff --git a/python/src/com/jetbrains/python/documentation/EpydocString.java b/python/src/com/jetbrains/python/documentation/EpydocString.java index 132ef4924b5c..3d0fc3e89d9e 100644 --- a/python/src/com/jetbrains/python/documentation/EpydocString.java +++ b/python/src/com/jetbrains/python/documentation/EpydocString.java @@ -44,6 +44,13 @@ public class EpydocString extends StructuredDocStringBase { "precondition", "postcondition", "invariant", "author", "organization", "copyright", "license", "contact", "summary", "see" }; + /** + * Empty doc (for {@link #createParameterType(String, String)} probably) + */ + public EpydocString() { + this(""); + } + public EpydocString(@NotNull String docstringText) { super(docstringText, "@"); } diff --git a/python/src/com/jetbrains/python/documentation/SphinxDocString.java b/python/src/com/jetbrains/python/documentation/SphinxDocString.java index ae5c7056a90b..c04e1c9471cf 100644 --- a/python/src/com/jetbrains/python/documentation/SphinxDocString.java +++ b/python/src/com/jetbrains/python/documentation/SphinxDocString.java @@ -31,7 +31,14 @@ public class SphinxDocString extends StructuredDocStringBase { ":type", ":raise", ":raises", ":var", ":cvar", ":ivar", ":return", ":returns", ":rtype", ":except", ":exception" }; - public SphinxDocString(@NotNull String docstringText) { + /** + * Empty doc (for {@link #createParameterType(String, String)} probably) + */ + public SphinxDocString() { + this(""); + } + + public SphinxDocString(@NotNull final String docstringText) { super(docstringText, ":"); } diff --git a/python/src/com/jetbrains/python/documentation/StructuredDocStringBase.java b/python/src/com/jetbrains/python/documentation/StructuredDocStringBase.java index 9f6cecba7a84..98f10f7abde1 100644 --- a/python/src/com/jetbrains/python/documentation/StructuredDocStringBase.java +++ b/python/src/com/jetbrains/python/documentation/StructuredDocStringBase.java @@ -43,18 +43,21 @@ public abstract class StructuredDocStringBase implements StructuredDocString { private static final Pattern RE_LOOSE_TAG_LINE = Pattern.compile("([a-z]+) ([a-zA-Z_0-9]*):?([^:]*)"); private static final Pattern RE_ARG_TYPE = Pattern.compile("(.*) ([a-zA-Z_0-9]+)"); - public static String[] PARAM_TAGS = new String[] { "param", "parameter", "arg", "argument" }; - public static String[] PARAM_TYPE_TAGS = new String[] { "type" }; - public static String[] VARIABLE_TAGS = new String[] { "ivar", "cvar", "var" }; + public static String[] PARAM_TAGS = new String[]{"param", "parameter", "arg", "argument"}; + public static String[] PARAM_TYPE_TAGS = new String[]{"type"}; + public static String[] VARIABLE_TAGS = new String[]{"ivar", "cvar", "var"}; - public static String[] RAISES_TAGS = new String[] { "raises", "raise", "except", "exception" }; - public static String[] RETURN_TAGS = new String[] { "return", "returns" }; + public static String[] RAISES_TAGS = new String[]{"raises", "raise", "except", "exception"}; + public static String[] RETURN_TAGS = new String[]{"return", "returns"}; + @NotNull + private final String myTagPrefix; public enum ReferenceType {PARAMETER, PARAMETER_TYPE, KEYWORD, VARIABLE, CLASS_VARIABLE, INSTANCE_VARIABLE} public static String TYPE = "type"; protected StructuredDocStringBase(@NotNull String docStringText, String tagPrefix) { + myTagPrefix = tagPrefix; final Substring docString = new Substring(docStringText); final List lines = docString.splitLines(); final int nlines = lines.size(); @@ -73,6 +76,17 @@ public abstract class StructuredDocStringBase implements StructuredDocString { myDescription = builder.toString(); } + /** + * Creates parameter type documentation specific for certain doct type + * @param name param name + * @param type param type + * @return text to add to docsting + */ + @NotNull + public String createParameterType(@NotNull final String name, @NotNull final String type) { + return myTagPrefix + TYPE + String.format(" %s %s", name, type); + } + @Override public String getDescription() { return myDescription; @@ -82,8 +96,9 @@ public abstract class StructuredDocStringBase implements StructuredDocString { public String getSummary() { final List strings = StringUtil.split(StringUtil.trimLeading(myDescription), "\n", true, false); if (strings.size() > 1) { - if (strings.get(1).isEmpty()) + if (strings.get(1).isEmpty()) { return strings.get(0); + } } return ""; } @@ -216,8 +231,8 @@ public abstract class StructuredDocStringBase implements StructuredDocString { @Override @Nullable - public Substring getParamByNameAndKind(@NotNull String name, String kind) { - for (Substring s: getTagArguments(kind)) { + public Substring getParamByNameAndKind(@NotNull String name, String kind) { + for (Substring s : getTagArguments(kind)) { if (name.equals(s.getValue())) { return s; } diff --git a/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java b/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java index 744dc9731b41..0fa3a4dff6b9 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java @@ -24,6 +24,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFileFactory; +import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.impl.PsiFileFactoryImpl; import com.intellij.psi.impl.source.tree.LeafPsiElement; import com.intellij.psi.tree.TokenSet; @@ -191,7 +192,7 @@ public class PyElementGeneratorImpl extends PyElementGenerator { final LeafPsiElement[] leafs = PsiTreeUtil.getChildrenOfType(list, LeafPsiElement.class); if (leafs != null) { final Deque commas = Queues.newArrayDeque(Collections2.filter(Arrays.asList(leafs), COMMAS_ONLY)); - if (! commas.isEmpty()) { + if (!commas.isEmpty()) { final LeafPsiElement lastComma = commas.getLast(); if (PsiTreeUtil.getNextSiblingOfType(lastComma, PyExpression.class) == null) { //Comma has no expression after it lastComma.delete(); @@ -297,7 +298,7 @@ public class PyElementGeneratorImpl extends PyElementGenerator { AccessDirection accessDirection) { String propertyText; if (accessDirection == AccessDirection.DELETE) { - propertyText = "@" + propertyName +".deleter\ndef " + propertyName + "(self):\n del self." + fieldName; + propertyText = "@" + propertyName + ".deleter\ndef " + propertyName + "(self):\n del self." + fieldName; } else if (accessDirection == AccessDirection.WRITE) { propertyText = "@" + propertyName + ".setter\ndef " + propertyName + "(self, value):\n self." + fieldName + " = value"; @@ -415,6 +416,12 @@ public class PyElementGeneratorImpl extends PyElementGenerator { PyExpressionStatement.class, content + "\n"); } + @NotNull + @Override + public PsiElement createNewLine() { + return createFromText(LanguageLevel.getDefault(), PsiWhiteSpace.class, " \n\n "); + } + private static class CommasOnly extends NotNullPredicate { @Override protected boolean applyNotNull(@NotNull final LeafPsiElement input) { diff --git a/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java b/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java index e0cdf87b2aab..9d2be0f62205 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFunctionBuilder.java @@ -23,6 +23,7 @@ import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.util.ArrayUtil; import com.jetbrains.python.PyNames; import com.jetbrains.python.PythonFileType; +import com.jetbrains.python.documentation.StructuredDocStringBase; import com.jetbrains.python.psi.*; import org.jetbrains.annotations.NotNull; @@ -82,14 +83,21 @@ public class PyFunctionBuilder { /** * Adds docstring to function. Provide doc with out of comment blocks. * + * * @param docString doc */ public void docString(@NotNull final String docString) { - myDocStringLines = StringUtil.splitByLines(removeIndent(docString)); + final String[] stringsToAdd = StringUtil.splitByLines(removeIndent(docString)); + if (myDocStringLines == null) { + myDocStringLines = stringsToAdd; + } + else { + myDocStringLines = ArrayUtil.mergeArrays(myDocStringLines, stringsToAdd); + } } @NotNull - private String removeIndent(@NotNull final String string) { + private static String removeIndent(@NotNull final String string) { return INDENT_REMOVE_PATTERN.matcher(string).replaceAll(""); } @@ -97,6 +105,21 @@ public class PyFunctionBuilder { myName = name; } + /** + * Adds param and its type to doc + * @param name param name + * @param type param type + * @param docStyle what docstyle to use to doc param type + */ + @NotNull + public PyFunctionBuilder parameterWithType(@NotNull final String name, + @NotNull final String type, + @NotNull final StructuredDocStringBase docStyle) { + parameter(name); + docString(docStyle.createParameterType(name, type)); + return this; + } + public PyFunctionBuilder parameter(String baseName) { String name = baseName; int uniqueIndex = 0; @@ -173,8 +196,9 @@ public class PyFunctionBuilder { /** * Adds decorator with argument + * * @param decoratorName decorator name - * @param value its argument + * @param value its argument */ public void decorate(@NotNull final String decoratorName, @NotNull final String value) { decorate(decoratorName);