summaryrefslogtreecommitdiff
path: root/python/helpers/pycharm/_bdd_utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/helpers/pycharm/_bdd_utils.py')
-rw-r--r--python/helpers/pycharm/_bdd_utils.py201
1 files changed, 201 insertions, 0 deletions
diff --git a/python/helpers/pycharm/_bdd_utils.py b/python/helpers/pycharm/_bdd_utils.py
new file mode 100644
index 000000000000..0c92532b516c
--- /dev/null
+++ b/python/helpers/pycharm/_bdd_utils.py
@@ -0,0 +1,201 @@
+# coding=utf-8
+"""
+Tools for running BDD frameworks in python.
+You probably need to extend BddRunner (see its doc).
+
+You may also need "get_path_by_args" that gets folder (current or passed as first argument)
+"""
+import os
+import time
+import abc
+
+import tcmessages
+
+
+__author__ = 'Ilya.Kazakevich'
+
+
+def get_path_by_args(arguments):
+ """
+ :type arguments list
+ :param arguments: arguments (sys.argv)
+ :return: tuple (base_dir, what_to_run) where dir is current or first argument from argv, checking it exists
+ :rtype tuple of str
+ """
+ what_to_run = arguments[1] if len(arguments) > 1 else "."
+ base_dir = what_to_run
+ assert os.path.exists(what_to_run), "{} does not exist".format(what_to_run)
+
+ if os.path.isfile(what_to_run):
+ base_dir = os.path.dirname(what_to_run) # User may point to the file directly
+ return base_dir, what_to_run
+
+
+class BddRunner(object):
+ """
+ Extends this class, implement abstract methods and use its API to implement new BDD frameworks.
+ Call "run()" to launch it.
+ This class does the following:
+ * Gets features to run (using "_get_features_to_run()") and calculates steps in it
+ * Reports steps to Intellij or TC
+ * Calls "_run_tests()" where *you* should install all hooks you need into your BDD and use "self._" functions
+ to report tests and features. It actually wraps tcmessages but adds some stuff like duration count etc
+ :param base_dir:
+ """
+ __metaclass__ = abc.ABCMeta
+
+ def __init__(self, base_dir):
+ """
+ :type base_dir str
+ :param base_dir base directory of your project
+ """
+ super(BddRunner, self).__init__()
+ self.tc_messages = tcmessages.TeamcityServiceMessages()
+ """
+ tcmessages TeamCity/Intellij test API. See TeamcityServiceMessages
+ """
+ self.__base_dir = base_dir
+ self.__last_test_start_time = None # TODO: Doc when use
+ self.__last_test_name = None
+
+ def run(self):
+ """"
+ Runs runner. To be called right after constructor.
+ """
+ self.tc_messages.testCount(self._get_number_of_tests())
+ self.tc_messages.testMatrixEntered()
+ self._run_tests()
+
+ def __gen_location(self, location):
+ """
+ Generates location in format, supported by tcmessages
+ :param location object with "file" (relative to base_dir) and "line" fields.
+ :return: location in format file:line (as supported in tcmessages)
+ """
+ my_file = str(location.file).lstrip("/\\")
+ return "file:///{}:{}".format(os.path.normpath(os.path.join(self.__base_dir, my_file)), location.line)
+
+ def _test_undefined(self, test_name, location):
+ """
+ Mark test as undefined
+ :param test_name: name of test
+ :type test_name str
+ :param location its 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
+ :param name: test name
+ :type name str
+ :param message: failure message
+ :type message str
+ :param details: failure details (probably stacktrace)
+ :type details str
+ """
+ self.tc_messages.testFailed(name, message=message, details=details)
+ self.__last_test_name = None
+
+ def _test_passed(self, name, duration=None):
+ """
+ Reports test passed
+ :param name: test name
+ :type name str
+ :param duration: time (in seconds) test took. Pass None if you do not know (we'll try to calculate it)
+ :type duration int
+ :return:
+ """
+ duration_to_report = duration
+ if self.__last_test_start_time and not duration: # And not provided
+ 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):
+ """
+ Reports test launched
+ :param name: test name
+ :param location object with "file" (relative to base_dir) and "line" fields.
+ :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):
+ """
+ Reports feature or scenario launched or stopped
+ :param is_started: started or finished?
+ :type is_started bool
+ :param name: scenario or feature name
+ :param location object with "file" (relative to base_dir) and "line" fields.
+ """
+ if is_started:
+ self.tc_messages.testSuiteStarted(name, self.__gen_location(location))
+ else:
+ self.tc_messages.testSuiteFinished(name)
+
+ def _background(self, is_started, location):
+ """
+ Reports background or stopped
+ :param is_started: started or finished?
+ :type is_started bool
+ :param location object with "file" (relative to base_dir) and "line" fields.
+ """
+ self._feature_or_scenario(is_started, "Background", location)
+
+ def _get_number_of_tests(self):
+ """"
+ Gets number of tests using "_get_features_to_run()" to obtain number of features to calculate.
+ Supports backgrounds as well.
+ :return number of steps
+ :rtype int
+ """
+ num_of_steps = 0
+ for feature in self._get_features_to_run():
+ if feature.background:
+ num_of_steps += len(feature.background.steps) * len(feature.scenarios)
+ for scenario in feature.scenarios:
+ num_of_steps += len(scenario.steps)
+ return num_of_steps
+
+ @abc.abstractmethod
+ def _get_features_to_run(self):
+ """
+ Implement it! Return list of features to run. Each "feature" should have "scenarios".
+ Each "scenario" should have "steps". Each "feature" may have "background" and each "background" should have
+ "steps". Duck typing.
+ :rtype list
+ :returns list of features
+ """
+ return []
+
+ @abc.abstractmethod
+ def _run_tests(self):
+ """
+ Implement it! It should launch tests using your BDD. Use "self._" functions to report results.
+ """
+ pass
+
+