PK*^G+pradish/argexpregistry.py# -*- coding: utf-8 -*- """ This module provides the Argument-Expression Registry """ from singleton import singleton from radish.exceptions import RadishError @singleton() class ArgExpRegistry(object): """ Registry for all custom argument expressions """ def __init__(self): self._expressions = {} def register(self, name, func): """ Registers a custom argument expression """ if name in self._expressions: raise RadishError("Cannot register argument expression with name {} because it already exists".format(name)) self._expressions[name] = func @property def expressions(self): """ Returns all registered expressions """ return self._expressions class ArgumentExpression(object): # pylint: disable=too-few-public-methods """ Represents an object with the advanced regex parsing """ def __init__(self, regex): self.regex = regex def arg_expr(name, expression): """ Decorator for custom argument expressions """ def _decorator(func): """ Actual decorator """ func.pattern = expression ArgExpRegistry().register(name, func) return func return _decorator PK*^GD+  radish/examplescenario.py# -*- coding: utf-8 -*- """ This module provides a class to represent one Scenario Outline Example """ from radish.scenario import Scenario class ExampleScenario(Scenario): """ Represents one example scenario from a ScenarioOutline """ def __init__(self, id, keyword, sentence, path, line, parent, example): super(ExampleScenario, self).__init__(id, keyword, sentence, path, line, parent) self.example = example def has_to_run(self, scenario_choice, feature_tags, scenario_tags): """ Returns wheiter the scenario has to run or not :param list scenario_choice: the scenarios to run. If None all will run """ return self.parent.has_to_run(scenario_choice, feature_tags, scenario_tags) PK*^G_Z5radish/exceptions.py# -*- coding: utf-8 -*- class RadishError(Exception): """ General radish specific error """ pass class LanguageNotSupportedError(RadishError): """ Raised if the language could not be found. """ def __init__(self, language): self.language = language super(LanguageNotSupportedError, self).__init__("Language {} could not be found".format(language)) class FeatureFileNotFoundError(RadishError): """ Raised if a given feature file does not exist """ def __init__(self, featurefile): self.featurefile = featurefile super(FeatureFileNotFoundError, self).__init__("Feature file '{}': No such file".format(featurefile)) class FeatureFileSyntaxError(RadishError, SyntaxError): """ If a a syntax error occured in a feature file """ pass class StepRegexError(RadishError, SyntaxError): """ Raised if the step regex cannot be compiled. """ def __init__(self, regex, step_func_name, re_error): self.regex = regex self.step_func_name = step_func_name self.re_error = re_error super(StepRegexError, self).__init__("Cannot compile regex '{}' from step '{}': {}".format(regex, step_func_name, re_error)) class StepArgumentRegexError(RadishError, SyntaxError): """ Raised if the steps ArgumentExpression cannot be compiled. """ def __init__(self, regex, step_func_name, error): self.regex = regex self.step_func_name = step_func_name self.error = error super(StepArgumentRegexError, self).__init__("Cannot compile ArgumentExpression regex '{}' from step '{}': {}".format(regex, step_func_name, error)) class SameStepError(RadishError): """ Raised if two step regex are exactly the same. """ def __init__(self, regex, func1, func2): self.regex = regex self.func1 = func1 self.func2 = func2 super(SameStepError, self).__init__("Cannot register step {} with regex '{}' because it is already used by step {}".format(func2.__name__, regex, func1.__name__)) class StepDefinitionNotFoundError(RadishError): """ Raised if the Matcher cannot find a step from the feature file in the registered steps. """ def __init__(self, step): self.step = step super(StepDefinitionNotFoundError, self).__init__("Cannot find step definition for step '{}' in {}:{}".format(step.sentence, step.path, step.line)) class RunnerEarlyExit(RadishError): """ Raised if the runner has to exit to run. """ pass class HookError(RadishError): """ Raised if an exception was raised inside a hook """ def __init__(self, hook_function, failure): self.hook_function = hook_function self.failure = failure super(HookError, self).__init__("Hook '{}' from {}:{} raised: '{}: {}'".format(hook_function.__name__, hook_function.__code__.co_filename, hook_function.__code__.co_firstlineno, failure.name, failure.reason)) class ScenarioNotFoundError(RadishError): """ Raised if a scenario cannot be found """ def __init__(self, scenario_id, amount_of_scenarios): self.scenario_id = scenario_id self.amount_of_scenarios = amount_of_scenarios super(ScenarioNotFoundError, self).__init__("No scenario with id {} found. Specify a scenario id between 1 and {}".format(scenario_id, amount_of_scenarios)) class ValidationError(RadishError): """ Raised by the user if a step is somehow not valid """ pass class FeatureTagNotFoundError(RadishError): """ Raised if a feature tag cannot be found """ def __init__(self, tag): self.tag = tag super(FeatureTagNotFoundError, self).__init__("No features found with tag '{}'".format(tag)) class ScenarioTagNotFoundError(RadishError): """ Raised if a scenario tag cannot be found """ def __init__(self, tag): self.tag = tag super(ScenarioTagNotFoundError, self).__init__("No scenarios found with tag '{}'".format(tag)) PK*^Gk radish/feature.py# -*- coding: utf-8 -*- """ This module provides a class to represent a Feature from a parsed feature file. """ from radish.model import Model from radish.scenariooutline import ScenarioOutline from radish.scenarioloop import ScenarioLoop from radish.step import Step class Feature(Model): """ Represent a Feature """ def __init__(self, id, keyword, sentence, path, line, tags=None): super(Feature, self).__init__(id, keyword, sentence, path, line, None, tags) self.description = [] self.scenarios = [] self.context = self.Context() @property def all_scenarios(self): """ Returns all scenarios from the feature The ScenarioOutline scenarios will be extended to the normal scenarios """ scenarios = [] for scenario in self.scenarios: scenarios.append(scenario) if isinstance(scenario, (ScenarioOutline, ScenarioLoop)): scenarios.extend(scenario.scenarios) return scenarios @property def variables(self): """ Returns all variables """ return self.context.variables def __str__(self): return "Feature: {} from {}:{}".format(self.sentence, self.path, self.line) def __repr__(self): return "".format(self.sentence, self.path, self.line) def __iter__(self): """ Returns an iterator for the scenario of this feature """ return iter(self.scenarios) def __contains__(self, sentence): """ Checks if the given scenario sentence is from a scenario of this feature :param str sentence: the scenario sentence to search """ return any(s for s in self.scenarios if s.sentence == sentence) def __getitem__(self, sentence): """ Returns the scenario with the given sentence :param str sentence: the scenario sentence to search """ return next((s for s in self.scenarios if s.sentence == sentence), None) @property def state(self): """ Returns the state of the scenario """ for scenario in self.all_scenarios: if isinstance(scenario, (ScenarioOutline, ScenarioLoop)): # skip scenario outlines continue if scenario.state in [Step.State.UNTESTED, Step.State.SKIPPED, Step.State.FAILED]: return scenario.state return Step.State.PASSED def has_to_run(self, scenario_choice, feature_tags, scenario_tags): """ Returns wheiter the feature has to run or not """ if not scenario_choice and not feature_tags and not scenario_tags: return True in_choice = False if scenario_choice: in_choice = any(s for s in self.scenarios if s.absolute_id in scenario_choice) in_tags = False if feature_tags: in_tags = any(t for t in self.tags if t.name in feature_tags) scenario_to_run = any(s for s in self.scenarios if s.has_to_run(scenario_choice, feature_tags, scenario_tags)) return in_choice or in_tags or scenario_to_run PK*^G'q q radish/hookregistry.py# -*- coding: utf-8 -*- """ This module provides a registry for all hooks """ from singleton import singleton import radish.utils as utils from radish.exceptions import HookError @singleton() class HookRegistry(object): """ Represents an object with all registered hooks """ def __init__(self): self._hooks = {} self.reset() self.build_hooks() @property def hooks(self): """ Returns all registered hooks """ return self._hooks class Hook(object): """ Represents a hook object This object is needed to provide decorators like: * @before.all * @before.each_feature """ def __init__(self, when): self._when = when @classmethod def build_decorator(cls, what): """ Builds the hook decorator """ def _decorator(self, func): """ Actual hook decorator """ HookRegistry().register(self._when, what, func) # pylint: disable=protected-access return func _decorator.__name__ = _decorator.fn_name = what setattr(cls, what, _decorator) def build_hooks(self): """ Builds all hooks """ for hook in self._hooks.keys(): self.Hook.build_decorator(hook) def register(self, when, what, func): """ Registers a function as a hook """ self._hooks[what][when].append(func) def reset(self): """ Resets all registerd hooks """ self._hooks = { "all": {"before": [], "after": []}, "each_feature": {"before": [], "after": []}, "each_scenario": {"before": [], "after": []}, "each_step": {"before": [], "after": []}, } def call(self, when, what, *args, **kwargs): """ Calls a registered hook """ for hook in self._hooks[what][when]: try: hook(*args, **kwargs) except Exception as e: raise HookError(hook, utils.Failure(e)) return None HookRegistry() before = HookRegistry.Hook("before") # pylint: disable=invalid-name after = HookRegistry.Hook("after") # pylint: disable=invalid-name PK*^GWradish/iterationscenario.py# -*- coding: utf-8 -*- """ This module provides a Scenario which represents one iteration in a ScenarioLoop """ from radish.scenario import Scenario class IterationScenario(Scenario): """ Represents one iteration from a ScenarioLoop """ def __init__(self, id, keyword, sentence, path, line, parent, iteration): super(IterationScenario, self).__init__(id, keyword, sentence, path, line, parent) self.iteration = iteration def has_to_run(self, scenario_choice, feature_tags, scenario_tags): """ Returns wheiter the scenario has to run or not :param list scenario_choice: the scenarios to run. If None all will run """ return self.parent.has_to_run(scenario_choice, feature_tags, scenario_tags) PK*^GΠradish/loader.py# -*- coding: utf-8 -*- """ This module contains a class to load the step and terrain files """ import os import sys import fnmatch import radish.utils as utils class Loader(object): """ Class to load a modules in a given folder """ def __init__(self, location): self._location = os.path.expanduser(os.path.expandvars(location)) self._loaded_modules = {} def load_all(self): """ Loads all modules in the `location` folder """ if not os.path.exists(self._location): raise OSError("Location '{}' to load modules does not exist".format(self._location)) for p, d, f in os.walk(self._location): for filename in fnmatch.filter(f, "*.py"): self.load_module(os.path.join(p, filename)) def load_module(self, path): """ Loads a module by the given `path` :param string path: the path to the module to load """ parent = os.path.dirname(utils.expandpath(path)) sys.path.insert(0, parent) module_name = os.path.splitext(os.path.split(path)[1])[0] try: module = __import__(module_name) except Exception as e: #raise ImportError("Unable to import module '{}' from '{}': {}".format(module_name, path, e)) raise e else: self._loaded_modules[module_name] = module finally: sys.path.remove(parent) PK*^G`4P radish/matcher.py# -*- coding: utf-8 -*- """ This module provides a class to match the feature file steps with the registered steps from the registry """ import re import parse from radish.argexpregistry import ArgExpRegistry, ArgumentExpression from radish.exceptions import StepDefinitionNotFoundError, StepArgumentRegexError class Matcher(object): """ Matches steps from the feature files with the registered steps """ @classmethod def merge_steps(cls, features, steps): """ Merges steps from the given features with the given steps :param list features: the features :param dict steps: the steps """ # FIXME: fix cycle-import ... Matcher -> ScenarioOutline -> Step -> Matcher from radish.scenariooutline import ScenarioOutline for feature in features: for scenario in feature.all_scenarios: if isinstance(scenario, ScenarioOutline): continue # ScenarioOutline steps do not have to be merged for step in scenario.steps: cls.merge_step(step, steps) @classmethod def merge_step(cls, step, steps): """ Merges a single step with the registered steps :param Step step: the step from a feature file to merge :param list steps: the registered steps """ arguments, keyword_arguments, func = cls.match(step.expanded_sentence, steps) if not func: raise StepDefinitionNotFoundError(step) step.definition_func = func step.arguments = arguments step.keyword_arguments = keyword_arguments @classmethod def match(cls, sentence, steps): """ Tries to find a match from the given sentence with the given steps :param string sentence: the step sentence to match :param dict steps: the available registered steps :returns: the arguments and the func which were matched :rtype: tuple """ for regex, func in steps.items(): if isinstance(regex, ArgumentExpression): try: compiled = parse.compile(regex.regex, ArgExpRegistry().expressions) except ValueError as e: raise StepArgumentRegexError(regex.regex, func.__name__, e) match = compiled.search(sentence) if match: return match.fixed, match.named, func else: match = re.search(regex, sentence) if match: return match.groups(), match.groupdict(), func return None, None, None PK*^G^radish/model.py# -*- coding: utf-8 -*- """ This model provides a base class for all models: Models: * Feature * Scenario * ScenarioOutline * Step """ from radish.exceptions import RadishError # FIXME: make ABC class Model(object): """ Represents a base model """ class Tag(object): """ Represents a tag for a model """ def __init__(self, name, arg=None): self.name = name self.arg = arg class Context(object): """ Represents a Models context. For every feature/scenario a new Context object is created """ def __init__(self): self.variables = [] def __init__(self, id, keyword, sentence, path, line, parent=None, tags=None): self.id = id self.keyword = keyword self.sentence = sentence self.path = path self.line = line self.parent = parent self.tags = tags or [] self.starttime = None self.endtime = None @property def duration(self): """ Returns the duration of this model """ if not self.starttime or not self.endtime: raise RadishError("Cannot get duration of {} '{}' because either starttime or endtime is not set".format(self.keyword, self.sentence)) return self.endtime - self.starttime PK*^G$$radish/runner.py# -*- coding: utf-8 -*- """ Providing radish core functionality like the feature file Runner. """ from random import shuffle from radish.terrain import world from radish.scenariooutline import ScenarioOutline from radish.scenarioloop import ScenarioLoop from radish.step import Step class Runner(object): """ Represents a class which is able to run features. """ def handle_exit(func): # pylint: disable=no-self-argument """ Handles an runner exit """ def _decorator(self, *args, **kwargs): """ Actual decorator """ if self._required_exit: # pylint: disable=protected-access return None return func(self, *args, **kwargs) # pylint: disable=not-callable return _decorator def call_hooks(model): # pylint: disable=no-self-argument """ Call hooks for a specific model """ def _decorator(func): """ The actual decorator """ def _wrapper(self, model_instance, *args, **kwargs): """ Decorator wrapper """ self._hooks.call("before", model, model_instance, *args, **kwargs) # pylint: disable=protected-access try: return func(self, model_instance, *args, **kwargs) finally: self._hooks.call("after", model, model_instance, *args, **kwargs) # pylint: disable=protected-access return _wrapper return _decorator def __init__(self, hooks, early_exit=False, dry_run=False): self._hooks = hooks self._early_exit = early_exit self._required_exit = False self._dry_run = dry_run @handle_exit @call_hooks("all") def start(self, features, marker): """ Start running features :param list features: the features to run :param string marker: the marker for this run """ if world.config.shuffle: shuffle(features) returncode = 0 # return code is set to 1 if any feature fails for feature in features: if not feature.has_to_run(world.config.scenarios, world.config.feature_tags, world.config.scenario_tags): continue returncode |= self.run_feature(feature) return returncode @handle_exit @call_hooks("each_feature") def run_feature(self, feature): """ Runs the given feature :param Feature feature: the feature to run """ if world.config.shuffle: shuffle(feature.scenarios) returncode = 0 for scenario in feature: if not scenario.has_to_run(world.config.scenarios, world.config.feature_tags, world.config.scenario_tags): continue returncode |= self.run_scenario(scenario) if not isinstance(scenario, (ScenarioOutline, ScenarioLoop)): continue for sub_scenario in scenario.scenarios: returncode |= self.run_scenario(sub_scenario) return returncode @handle_exit @call_hooks("each_scenario") def run_scenario(self, scenario): """ Runs the given scenario :param Scenario scenario: the scnenario to run """ returncode = 0 steps = scenario.all_steps if world.config.expand else scenario.steps for step in steps: if scenario.state == Step.State.FAILED: self.skip_step(step) continue returncode |= self.run_step(step) if step.state == step.State.FAILED and self._early_exit: self.exit() return 1 return returncode @handle_exit @call_hooks("each_step") def run_step(self, step): """ Runs the given step :param Step step: the step to run """ if self._dry_run: return 0 if world.config.debug_steps: state = step.debug() else: state = step.run() return 1 if state == Step.State.FAILED else 0 def skip_step(self, step): """ Skips the given step :param Step step: the step to skip """ self._hooks.call("before", "each_step", step) step.skip() self._hooks.call("after", "each_step", step) def exit(self): """ Exits the runner """ self._required_exit = True PK*^G7 radish/scenario.py# -*- coding: utf-8 -*- """ This module provides a class to represent a Scenario """ from radish.model import Model from radish.step import Step class Scenario(Model): """ Represents a Scenario """ def __init__(self, id, keyword, sentence, path, line, parent, tags=None, preconditions=None): super(Scenario, self).__init__(id, keyword, sentence, path, line, parent, tags) self.absolute_id = None self.preconditions = preconditions or [] self.steps = [] self.context = self.Context() @property def state(self): """ Returns the state of the scenario """ for step in self.steps: if step.state in [Step.State.UNTESTED, Step.State.SKIPPED, Step.State.FAILED]: return step.state return Step.State.PASSED @property def variables(self): """ Returns all variables """ variables = self.context.variables for name, value in variables: for parent_name, parent_value in self.parent.variables: value = value.replace("${%s}" % parent_name, parent_value) variables.extend(self.parent.variables) return variables @property def all_steps(self): """ Returns all steps from all preconditions in the correct order """ steps = [] for precondition in self.preconditions: steps.extend(precondition.all_steps) steps.extend(self.steps) return steps @property def failed_step(self): """ Returns the first failed step """ for step in self.steps: if step.state == Step.State.FAILED: return step return None def has_to_run(self, scenario_choice, feature_tags, scenario_tags): """ Returns wheiter the scenario has to run or not :param list scenario_choice: the scenarios to run. If None all will run """ if not scenario_choice and not feature_tags and not scenario_tags: return True in_choice = self.absolute_id in (scenario_choice or []) in_tags = False if scenario_tags: in_tags = any(t for t in self.tags if t.name in scenario_tags) feature_has_to_run = False if feature_tags: feature_has_to_run = any(t for t in self.parent.tags if t.name in feature_tags) return in_choice or in_tags or feature_has_to_run def after_parse(self): """ This method is called after the scenario is completely parsed. Actions to do: * number steps * fix parent of precondition steps """ for step_id, step in enumerate(self.all_steps, start=1): step.id = step_id if step.parent != self: step.as_precondition = step.parent step.parent = self PK*^G$|radish/scenarioloop.py# -*- coding: utf-8 -*- """ This module provides a Scenario type which represents a Scenario loop """ from radish.scenario import Scenario from radish.iterationscenario import IterationScenario from radish.step import Step class ScenarioLoop(Scenario): """ Represents a scenario loop """ def __init__(self, id, keyword, iterations_keyword, sentence, path, line, parent, tags=None, preconditions=None): super(ScenarioLoop, self).__init__(id, keyword, sentence, path, line, parent, tags, preconditions) self.iterations_keyword = iterations_keyword self.iterations = 0 self.scenarios = [] def build_scenarios(self): """ Builds the scenarios for every iteration Note: This must be done before mering the steps from the feature file with the step definitions """ for i in range(self.iterations): scenario_id = self.id + i + 1 scenario = IterationScenario(scenario_id, self.keyword, "{} - iteration {}".format(self.sentence, i), self.path, self.line, self, i) for step_id, iteration_step in enumerate(self.steps): step = Step(step_id + 1, iteration_step.sentence, iteration_step.path, iteration_step.line, scenario, True) scenario.steps.append(step) self.scenarios.append(scenario) def after_parse(self): Scenario.after_parse(self) self.build_scenarios() PK*^GIi i radish/scenariooutline.py# -*- coding: utf-8 -*- """ This module provides a class to represent a Scenario Outline """ from radish.scenario import Scenario from radish.examplescenario import ExampleScenario from radish.step import Step from radish.exceptions import RadishError class ScenarioOutline(Scenario): """ Represents a Scenario """ class Example(object): """ Represents the ScenarioOutline examples """ def __init__(self, data, path, line): self.data = data self.path = path self.line = line def __init__(self, id, keyword, example_keyword, sentence, path, line, parent, tags=None, preconditions=None): super(ScenarioOutline, self).__init__(id, keyword, sentence, path, line, parent, tags, preconditions) self.example_keyword = example_keyword self.scenarios = [] self.examples_header = [] self.examples = [] def build_scenarios(self): """ Builds the scenarios with the parsed Examples Note: This must be done before mering the steps from the feature file with the step definitions """ for row_id, example in enumerate(self.examples): examples = dict(zip(self.examples_header, example.data)) scenario_id = self.id + row_id + 1 scenario = ExampleScenario(scenario_id, self.keyword, "{} - row {}".format(self.sentence, row_id), self.path, self.line, self, example) for step_id, outlined_step in enumerate(self.steps): sentence = self._replace_examples_in_sentence(outlined_step.sentence, examples) step = Step(step_id + 1, sentence, outlined_step.path, example.line, scenario, True) scenario.steps.append(step) self.scenarios.append(scenario) @classmethod def _replace_examples_in_sentence(cls, sentence, examples): """ Replaces the given examples in the given sentece :param string sentence: the step sentence in which to replace the examples :param dict examples: the examples :returns: the new step sentence :rtype: string """ for key, value in examples.items(): sentence = sentence.replace("<{}>".format(key), value) return sentence def get_column_width(self, column_index): """ Gets the column width from the given column :param int column_index: the column index to get the width from """ try: return max(max([len(x.data[column_index]) for x in self.examples]), len(self.examples_header[column_index])) except IndexError: raise RadishError("Invalid colum_index to get column width for ScenarioOutline '{}'".format(self.sentence)) def after_parse(self): Scenario.after_parse(self) self.build_scenarios() PK*^G*radish/step.py# -*- coding: utf-8 -*- """ This module provides a class to represent a Step """ from radish.model import Model from radish.exceptions import RadishError from radish.terrain import world from radish.stepregistry import StepRegistry from radish.matcher import Matcher import radish.utils as utils class Step(Model): """ Represents a step """ class State(object): """ Represents the step state FIXME: for the python3 version this should be an Enum """ UNTESTED = "untested" SKIPPED = "skipped" PASSED = "passed" FAILED = "failed" def __init__(self, id, sentence, path, line, parent, runable): super(Step, self).__init__(id, None, sentence, path, line, parent) self.table = [] self.raw_text = [] self.definition_func = None self.arguments = None self.keyword_arguments = None self.state = Step.State.UNTESTED self.failure = None self.runable = runable self.as_precondition = None @property def context(self): """ Returns the scenario context belonging to this step """ return self.parent.context @property def expanded_sentence(self): """ Returns the expanded sentence of this step * Expand variables """ sentence = self.sentence for name, value in self.parent.variables: sentence = sentence.replace("${%s}" % name, value) return sentence @property def text(self): """ Returns the additional text of this step as string """ return "\n".join(self.raw_text) def _validate(self): """ Checks if the step is valid to run or not """ if not self.definition_func or not callable(self.definition_func): raise RadishError("The step '{}' does not have a step definition".format(self.sentence)) def run(self): """ Runs the step. """ if not self.runable: self.state = Step.State.UNTESTED return self.state self._validate() try: if self.keyword_arguments: self.definition_func(self, **self.keyword_arguments) # pylint: disable=not-callable else: self.definition_func(self, *self.arguments) # pylint: disable=not-callable except Exception as e: # pylint: disable=broad-except self.state = Step.State.FAILED self.failure = utils.Failure(e) else: self.state = Step.State.PASSED return self.state def debug(self): """ Debugs the step """ if not self.runable: self.state = Step.State.UNTESTED return self.state self._validate() pdb = utils.get_debugger() try: pdb.runcall(self.definition_func, self, *self.arguments, **self.keyword_arguments) except Exception as e: # pylint: disable=broad-except self.state = Step.State.FAILED self.failure = utils.Failure(e) else: self.state = Step.State.PASSED return self.state def skip(self): """ Skips the step """ self.state = Step.State.SKIPPED def behave_like(self, sentence): """ Make step behave like another one :param string sentence: the sentence of the step to behave like """ # check if this step has already failed from a previous behave_like call if self.state is Step.State.FAILED: return # create step according to given sentence new_step = Step(None, sentence, self.path, self.line, self.parent, True) Matcher.merge_step(new_step, StepRegistry().steps) # run or debug step if world.config.debug_steps: new_step.debug() else: new_step.run() # re-raise exception if the failed if new_step.state is Step.State.FAILED: new_step.failure.exception.args = ("Step '{}' failed: '{}'".format(sentence, new_step.failure.exception.message),) raise new_step.failure.exception PK*^G9m9VVradish/stepregistry.py# -*- coding: utf-8 -*- """ This module provides a registry for all custom steps which were decorated with the @step-decorator. """ import re import inspect import parse from singleton import singleton from radish.argexpregistry import ArgumentExpression from radish.exceptions import RadishError, SameStepError, StepRegexError @singleton() class StepRegistry(object): """ Represents the step registry """ def __init__(self): self.clear() def register(self, regex, func): """ Registers a given regex with the given step function. """ if regex in self._steps: raise SameStepError(regex, self._steps[regex], func) self._steps[regex] = func def register_object(self, steps_object): """ Registers all public methods from the given object as steps. The step regex must be in the first line of the docstring :param instance steps_object: the object with step definition methods """ ignore = getattr(steps_object, "ignore", []) for attr in dir(steps_object): if attr in ignore or not inspect.ismethod(getattr(steps_object, attr)): # attribute should be ignored continue step_definition = getattr(steps_object, attr) step_regex = self._extract_regex(step_definition) self.register(step_regex, step_definition) return steps_object def _extract_regex(self, func): """ Extracts a step regex from the docstring of the given func :param function func: the step definition function """ docstr = func.__doc__.strip() if not docstr: raise RadishError("Step definition '{}' from class must have step regex in docstring".format(func.__name__)) regex = docstr.splitlines()[0] try: re.compile(regex) except re.error as e: raise StepRegexError(regex, func.__name__, e) return regex def clear(self): """ Clears all registered steps """ self._steps = {} @property def steps(self): """ Returns all registered steps """ return self._steps def step(regex): """ Step decorator for custom steps :param string regex: this is the regex to match the steps in the feature file :returns: the decorated function :rtype: function """ def _decorator(func): """ Represents the actual decorator """ try: if not isinstance(regex, ArgumentExpression): re.compile(regex) except re.error as e: raise StepRegexError(regex, func.__name__, e) else: StepRegistry().register(regex, func) return func return _decorator def steps(cls): """ Decorator for classes with step definitions inside """ old_cls_init = getattr(cls, "__init__") def new_cls_init(self, *args, **kwargs): """ New __init__ method for the given class which calls the old __init__ method and registers the steps """ old_cls_init(self, *args, **kwargs) # register functions as step StepRegistry().register_object(self) setattr(cls, "__init__", new_cls_init) return cls given = lambda regex: step("Given {}".format(regex)) # pylint: disable=invalid-name when = lambda regex: step("When {}".format(regex)) # pylint: disable=invalid-name then = lambda regex: step("Then {}".format(regex)) # pylint: disable=invalid-name PK*^G]YZZradish/terrain.py# -*- coding: utf-8 -*- """ Terrain module providing step overlapping data containers """ import threading world = threading.local() # pylint: disable=invalid-name def pick(func): """ Picks the given function and add it to the world object """ setattr(world, func.__name__, func) return func world.pick = pick PK hGז{ { radish/errororacle.py# -*- coding: utf-8 -*- """ This module provides some functionality to diagnose thrown exceptions """ import sys from functools import wraps from colorful import colorful from radish.terrain import world from radish.exceptions import RadishError, FeatureFileSyntaxError, StepDefinitionNotFoundError, HookError, SameStepError __RADISH_DOC__ = "https://github.com/radish-bdd/radish" def write(text): """ Writes the given text to the console """ print(text) def write_error(text): """ Writes the given text to the console """ write("{}: {}".format(colorful.bold_red("Error"), colorful.red(text))) def write_failure(failure): """ Writes the failure to the console """ write("\n{}".format(colorful.red(failure.traceback))) def abort(return_code): """ Aborts the program with the given return_code """ sys.exit(return_code) def error_oracle(func): """ Decorator to diagnose thrown exceptions """ @wraps(func) def _decorator(*args, **kwargs): """ The actual decorator """ try: return func(*args, **kwargs) except Exception as e: # pylint: disable=broad-except handle_exception(e) return _decorator def catch_unhandled_exception(exc_type, exc_value, traceback): """ Catch all unhandled exceptions """ handle_exception(exc_value) def handle_exception(exception): """ Handle the given exception This will print more information about the given exception :param Exception exception: the exception to handle """ if isinstance(exception, HookError): # handle exception from hook write_error(exception) write_failure(exception.failure) abort(1) elif isinstance(exception, FeatureFileSyntaxError): write_error(exception) write("\nError Oracle says:") write("""You have a SyntaxError in your feature file! Please have a look into the radish documentation to found out which features radish supports and how you could use them: Link: {} """.format(__RADISH_DOC__)) abort(1) elif isinstance(exception, StepDefinitionNotFoundError): write_error(exception) write("\nError Oracle says:") write("""There is no step defintion for '{}'. All steps should be declared in a module located in {}. For example you could do: @step(r"{}") def my_step(step): raise NotImplementedError("This step is not implemented yet") """.format(exception.step.sentence, world.config.basedir, exception.step.sentence)) abort(1) elif isinstance(exception, SameStepError): write_error(exception) write("\nError Oracle says:") write("""You have defined two step definitions with the same Regular Expression. This is invalid since radish does not know which one is the one to go with. If you have two similar step definition expressions but ones sentence is a subset of the other you may want to add a $ to mark the sentence's end - take care of the code order - first comes first serves. """) abort(1) elif isinstance(exception, RadishError): write_error(exception) abort(1) elif isinstance(exception, KeyboardInterrupt): write("Aborted by the user...") abort(1) else: write_error(str(exception)) abort(2) PKOhG"D7radish/core.py# -*- coding: utf-8 -*- """ Providing radish core functionality. """ from threading import Lock from collections import OrderedDict from radish.parser import FeatureParser # FIXME: rename class Core(object): """ Provide some core functionalities like parsing and storing of the feature files """ def __init__(self): self.features = [] self._features_to_run = OrderedDict() self._feature_id_lock = Lock() self._feature_id = 0 self._scenario_id_lock = Lock() self._scenario_id = 0 @property def features_to_run(self): """ Return all parsed features which are to run """ return [f for f in self._features_to_run.values()] @property def next_feature_id(self): """ Returns the next feature id """ with self._feature_id_lock: self._feature_id += 1 return self._feature_id @property def next_scenario_id(self): """ Returns the next scenario id """ with self._scenario_id_lock: self._scenario_id += 1 return self._scenario_id def parse_features(self, feature_files): """ Parses the given feature files """ for featurefile in feature_files: feature = self.parse_feature(featurefile, self.next_feature_id) for scenario in feature.scenarios: scenario.absolute_id = self.next_scenario_id self._features_to_run[featurefile] = feature def parse_feature(self, featurefile, featureid=0): """ Parses the given feature file If the feature is alreay parsed then it will just return it :returns: the parsed feature :rtype: Feature """ featureparser = FeatureParser(self, featurefile, featureid) featureparser.parse() self.features.append(featureparser.feature) return featureparser.feature PKQhGOhPPradish/main.py# -*- coding: utf-8 -*- import os import sys from docopt import docopt from time import time from radish import __VERSION__ from radish.core import Core from radish.loader import Loader from radish.matcher import Matcher from radish.stepregistry import StepRegistry from radish.hookregistry import HookRegistry from radish.runner import Runner from radish.exceptions import FeatureFileNotFoundError, ScenarioNotFoundError, FeatureTagNotFoundError, ScenarioTagNotFoundError from radish.errororacle import error_oracle, catch_unhandled_exception from radish.terrain import world import radish.utils as utils def setup_config(arguments): """ Parses the docopt arguments and creates a configuration object in terrain.world """ world.config = lambda: None for key, value in arguments.items(): config_key = key.replace("--", "").replace("-", "_").replace("<", "").replace(">", "") setattr(world.config, config_key, value) def show_features(core): """ Show the parsed features """ # FIXME: load dynamically import radish.extensions.console_writer # set needed configuration world.config.write_steps_once = True if not sys.stdout.isatty(): world.config.no_ansi = True runner = Runner(HookRegistry(), dry_run=True) runner.start(core.features_to_run, marker="show") return 0 def run_features(core): """ Run the parsed features :param Core core: the radish core object """ # FIXME: load dynamically import radish.extensions.argumentexpressions import radish.extensions.time_recorder import radish.extensions.syslog_writer import radish.extensions.console_writer import radish.extensions.endreport_writer import radish.extensions.failure_inspector import radish.extensions.failure_debugger import radish.extensions.bdd_xml_writer # set needed configuration world.config.expand = True # load user's custom python files loader = Loader(world.config.basedir) loader.load_all() # match feature file steps with user's step definitions Matcher.merge_steps(core.features, StepRegistry().steps) # run parsed features if world.config.marker == "time.time()": world.config.marker = int(time()) # scenario choice amount_of_scenarios = sum(len(f.scenarios) for f in core.features_to_run) if world.config.scenarios: world.config.scenarios = [int(s) for s in world.config.scenarios.split(",")] for s in world.config.scenarios: if s <= 0 or s > amount_of_scenarios: raise ScenarioNotFoundError(s, amount_of_scenarios) # tags if world.config.feature_tags: world.config.feature_tags = [t for t in world.config.feature_tags.split(",")] for tag in world.config.feature_tags: if not any(f for f in core.features if tag in [t.name for t in f.tags]): raise FeatureTagNotFoundError(tag) if world.config.scenario_tags: world.config.scenario_tags = [t for t in world.config.scenario_tags.split(",")] for tag in world.config.scenario_tags: if not any(s for f in core.features for s in f.scenarios if tag in [t.name for t in s.tags]): raise ScenarioTagNotFoundError(tag) runner = Runner(HookRegistry(), early_exit=world.config.early_exit) return runner.start(core.features_to_run, marker=world.config.marker) @error_oracle def main(): """ Usage: radish show [--expand] [--no-ansi] radish ... [-b= | --basedir=] [-e | --early-exit] [--debug-steps] [--debug-after-failure] [--inspect-after-failure] [--bdd-xml=] [--no-ansi] [--no-line-jump] [--write-steps-once] [--write-ids] [-t | --with-traceback] [-m= | --marker=] [-p= | --profile=] [-d | --dry-run] [-s= | --scenarios=] [--shuffle] [--feature-tags=] [--scenario-tags=] radish (-h | --help) radish (-v | --version) Arguments: features feature files to run Options: -h --help show this screen -v --version show version -e --early-exit stop the run after the first failed step --debug-steps debugs each step --debug-after-failure start python debugger after failure --inspect-after-failure start python shell after failure --bdd-xml= write BDD XML result file after run --no-ansi print features without any ANSI sequences (like colors, line jump) --no-line-jump print features without line jumps (overwriting steps) --write-steps-once does not rewrite the steps (this option only makes sense in combination with the --no-ansi flag) --write-ids write the feature, scenario and step id before the sentences -t --with-traceback show the Exception traceback when a step fails -m= --marker= specify the marker for this run [default: time.time()] -p= --profile= specify the profile which can be used in the step/hook implementation -b= --basedir= set base dir from where the step.py and terrain.py will be loaded [default: $PWD/radish] -d --dry-run make dry run for the given feature files -s= --scenarios= only run the specified scenarios (comma separated list) --shuffle shuttle run order of features and scenarios --feature-tags= only run features with the given tags --scenario-tags= only run scenarios with the given tags --expand expand the feature file (all preconditions) (C) Copyright 2013 by Timo Furrer """ sys.excepthook = catch_unhandled_exception arguments = docopt("radish {}\n{}".format(__VERSION__, main.__doc__), version=__VERSION__) # store all arguments to configuration dict in terrain.world setup_config(arguments) core = Core() feature_files = [] for given_feature in world.config.features: if not os.path.exists(given_feature): raise FeatureFileNotFoundError(given_feature) if os.path.isdir(given_feature): feature_files.extend(utils.recursive_glob(given_feature, "*.feature")) continue feature_files.append(given_feature) core.parse_features(feature_files) if not core.features: print("Error: no features given") return 1 argument_dispatcher = [ ((lambda: world.config.show), show_features), ((lambda: True), run_features) ] # radish command dispatching for to_run, method in argument_dispatcher: if to_run(): return method(core) if __name__ == "__main__": sys.exit(main()) PKhG'C'Cradish/parser.py# -*- coding: utf-8 -*- import os import codecs import re import json from radish.exceptions import RadishError, FeatureFileSyntaxError, LanguageNotSupportedError from radish.feature import Feature from radish.scenario import Scenario from radish.scenariooutline import ScenarioOutline from radish.scenarioloop import ScenarioLoop from radish.step import Step class Keywords(object): """ Represent config object for gherkin keywords. """ def __init__(self, feature, scenario, scenario_outline, examples, scenario_loop, iterations): self.feature = feature self.scenario = scenario self.scenario_outline = scenario_outline self.examples = examples self.scenario_loop = scenario_loop self.iterations = iterations class FeatureParser(object): """ Class to parse a feature file. A feature file contains just one feature. """ LANGUAGE_LOCATION = os.path.join(os.path.dirname(__file__), "languages") DEFAULT_LANGUAGE = "en" class State(object): """ Represents the parser state """ INIT = "init" FEATURE = "feature" SCENARIO = "scenario" SCENARIO_OUTLINE = "scenario_outline" STEP = "step" EXAMPLES = "examples" EXAMPLES_ROW = "examples_row" SCENARIO_LOOP = "scenario_loop" STEP_TEXT = "step_text" def __init__(self, core, featurefile, featureid, language="en"): if not os.path.exists(featurefile): raise OSError("Feature file at '{}' does not exist".format(featurefile)) self._core = core self._featureid = featureid self._featurefile = featurefile self.keywords = {} self._keywords_delimiter = ":" self._current_state = FeatureParser.State.FEATURE self._current_line = 0 self._current_tags = [] self._current_preconditions = [] self._current_variables = [] self._in_step_text = False self.feature = None self._load_language(language) def _load_language(self, language=None): """ Loads all keywords of the given language :param string language: the lanugage to use for the feature files. if None is given `radish` tries to detect the language. :returns: if the language could be loaded or not :rtype: bool :raises LanguageNotSupportedError: if the given language is not supported by radish """ if not language: # try to detect language raise NotImplementedError("Auto detect language is not implemented yet") language_path = os.path.join(self.LANGUAGE_LOCATION, language + ".json") if not os.path.exists(language_path): raise LanguageNotSupportedError(language) with codecs.open(language_path, "rb", "utf-8") as f: language_pkg = json.load(f) self.keywords = Keywords(**language_pkg["keywords"]) def parse(self): """ Parses the feature file of this `FeatureParser` instance :returns: if the parsing was successful or not :rtype: bool """ with codecs.open(self._featurefile, "rb", "utf-8") as f: for line in f.readlines(): self._current_line += 1 line = line.strip() if not line: # line is empty continue if line.startswith("#"): # try to detect feature file language language = self._detect_language(line) if language: self._load_language(language) continue if self.feature and self._detect_feature(line): raise FeatureFileSyntaxError("radish supports only one Feature per feature file") if not self._parse_context(line): raise FeatureFileSyntaxError("Syntax error in feature file {} on line {}".format(self._featurefile, self._current_line)) if not self.feature: raise FeatureFileSyntaxError("No Feature found in file {}".format(self._featurefile)) if self.feature.scenarios: self.feature.scenarios[-1].after_parse() def _parse_context(self, line): """ Parses arbitrary context from a line :param string line: the line to parse from """ parse_context_func = getattr(self, "_parse_" + self._current_state) if not parse_context_func: raise RadishError("FeatureParser state {} is not support".format(self._current_state)) return parse_context_func(line) def _parse_feature(self, line): """ Parses a Feature Sentence The `INIT` state is used as initiale state. :param string line: the line to parse from """ detected_feature = self._detect_feature(line) if not detected_feature: tag = self._detect_tag(line) if tag: self._current_tags.append(Feature.Tag(tag[0], tag[1])) if tag[0] == "variable": name, value = self._parse_variable(tag[1]) self._current_variables.append((name, value)) return True return False self.feature = Feature(self._featureid, self.keywords.feature, detected_feature, self._featurefile, self._current_line, self._current_tags) self.feature.context.variables = self._current_variables self._current_state = FeatureParser.State.SCENARIO self._current_tags = [] self._current_variables = [] return True def _parse_scenario(self, line): """ Parses a Feature context :param string line: the line to parse from """ detected_scenario = self._detect_scenario(line) scenario_type = Scenario keywords = (self.keywords.scenario,) if not detected_scenario: detected_scenario = self._detect_scenario_outline(line) scenario_type = ScenarioOutline keywords = (self.keywords.scenario_outline, self.keywords.examples) if not detected_scenario: detected_scenario = self._detect_scenario_loop(line) if not detected_scenario: tag = self._detect_tag(line) if tag: self._current_tags.append(Scenario.Tag(tag[0], tag[1])) if tag[0] == "precondition": scenario = self._parse_precondition(tag[1]) self._current_preconditions.append(scenario) elif tag[0] == "variable": name, value = self._parse_variable(tag[1]) self._current_variables.append((name, value)) return True self.feature.description.append(line) return True detected_scenario, iterations = detected_scenario # pylint: disable=unpacking-non-sequence scenario_type = ScenarioLoop keywords = (self.keywords.scenario_loop, self.keywords.iterations) if detected_scenario in self.feature: raise FeatureFileSyntaxError("Scenario with name '{}' defined twice in feature '{}'".format(detected_scenario, self.feature.path)) scenario_id = 1 if self.feature.scenarios: previous_scenario = self.feature.scenarios[-1] if hasattr(previous_scenario, "scenarios") and previous_scenario.scenarios: scenario_id = previous_scenario.scenarios[-1].id + 1 else: scenario_id = previous_scenario.id + 1 self.feature.scenarios.append(scenario_type(scenario_id, *keywords, sentence=detected_scenario, path=self._featurefile, line=self._current_line, parent=self.feature, tags=self._current_tags, preconditions=self._current_preconditions)) self.feature.scenarios[-1].context.variables = self._current_variables self._current_tags = [] self._current_preconditions = [] self._current_variables = [] if scenario_type == ScenarioLoop: self.feature.scenarios[-1].iterations = iterations self._current_state = FeatureParser.State.STEP return True def _parse_examples(self, line): """ Parses the Examples header line :param string line: the line to parse from """ if not isinstance(self.feature.scenarios[-1], ScenarioOutline): raise FeatureFileSyntaxError("Scenario does not support Examples. Use 'Scenario Outline'") self.feature.scenarios[-1].examples_header = [x.strip() for x in line.split("|")[1:-1]] self._current_state = FeatureParser.State.EXAMPLES_ROW return True def _parse_examples_row(self, line): """ Parses an Examples row :param string line: the line to parse from """ # detect next keyword if self._detect_scenario(line) or self._detect_scenario_outline(line) or self._detect_scenario_loop(line): self.feature.scenarios[-1].after_parse() return self._parse_scenario(line) example = ScenarioOutline.Example([x.strip() for x in line.split("|")[1:-1]], self._featurefile, self._current_line) self.feature.scenarios[-1].examples.append(example) return True def _parse_step(self, line): """ Parses a single step :param string line: the line to parse from """ # detect next keyword if self._detect_scenario(line) or self._detect_scenario_outline(line) or self._detect_scenario_loop(line) or self._detect_tag(line): self.feature.scenarios[-1].after_parse() return self._parse_scenario(line) if self._detect_step_text(line): self._current_state = self.State.STEP_TEXT return self._parse_step_text(line) if self._detect_table(line): self._parse_table(line) return True if self._detect_examples(line): self._current_state = FeatureParser.State.EXAMPLES return True step_id = len(self.feature.scenarios[-1].all_steps) + 1 not_runable = isinstance(self.feature.scenarios[-1], (ScenarioOutline, ScenarioLoop)) step = Step(step_id, line, self._featurefile, self._current_line, self.feature.scenarios[-1], not not_runable) self.feature.scenarios[-1].steps.append(step) return True def _parse_table(self, line): """ Parses a step table row :param string line: the line to parse from """ if not self.feature.scenarios[-1].steps: raise FeatureFileSyntaxError("Found step table without previous step definition on line {}".format(self._current_line)) self.feature.scenarios[-1].steps[-1].table.append([x.strip() for x in line.split("|")[1:-1]]) return True def _parse_step_text(self, line): """ Parses additional step text :param str line: the line to parse """ if line.startswith('"""') and not self._in_step_text: self._in_step_text = True line = line[3:] if line.endswith('"""') and self._in_step_text: self._current_state = self.State.STEP self._in_step_text = False line = line[:-3] if line: self.feature.scenarios[-1].steps[-1].raw_text.append(line.strip()) return True def _parse_precondition(self, arguments): """ Parses scenario preconditions The arguments must be in format: File.feature: Some scenario :param str arguments: the raw arguments """ match = re.search(r"(.*?\.feature): (.*)", arguments) if not match: raise FeatureFileSyntaxError("Scenario @precondition tag must have argument in format: 'test.feature: Some scenario'") feature_file_name, scenario_sentence = match.groups() feature_file = os.path.join(os.path.dirname(self._featurefile), feature_file_name) try: feature = self._core.parse_feature(feature_file) except RuntimeError as e: if str(e) == "maximum recursion depth exceeded": # precondition cycling raise FeatureFileSyntaxError("Your feature '{}' has cycling preconditions with '{}: {}' starting at line {}".format(self._featurefile, feature_file_name, scenario_sentence, self._current_line)) raise if scenario_sentence not in feature: raise FeatureFileSyntaxError("Cannot import precondition scenario '{}' from feature '{}': No such scenario".format(scenario_sentence, feature_file)) return feature[scenario_sentence] def _parse_variable(self, arguments): """ Parses tag arguments as a variable containing name and value The arguments must be in format: VariableName: SomeValue VariableName: 5 :param str arguments: the raw arguments to parse """ name, value = arguments.split(":", 1) return name.strip(), value.strip() def _detect_feature(self, line): """ Detects a feature on the given line :param string line: the line to detect a feature :returns: if a feature was found on the given line :rtype: bool """ if line.startswith(self.keywords.feature + self._keywords_delimiter): return line[len(self.keywords.feature) + len(self._keywords_delimiter):].strip() return None def _detect_scenario(self, line): """ Detects a scenario on the given line :param string line: the line to detect a scenario :returns: if a scenario was found on the given line :rtype: bool """ if line.startswith(self.keywords.scenario + self._keywords_delimiter): return line[len(self.keywords.scenario) + len(self._keywords_delimiter):].strip() return None def _detect_scenario_outline(self, line): """ Detects a scenario outline on the given line :param string line: the line to detect a scenario outline :returns: if a scenario outline was found on the given line :rtype: bool """ if line.startswith(self.keywords.scenario_outline + self._keywords_delimiter): return line[len(self.keywords.scenario_outline) + len(self._keywords_delimiter):].strip() return None def _detect_examples(self, line): """ Detects an Examples block on the given line :param string line: the line to detect the Examples :returns: if an Examples block was found on the given line :rtype: bool """ if line.startswith(self.keywords.examples + self._keywords_delimiter): return True return None def _detect_scenario_loop(self, line): """ Detects a scenario loop on the given line :param string line: the line to detect a scenario loop :returns: if a scenario loop was found on the given line :rtype: string """ match = re.search(r"^{} (\d+):(.*)".format(self.keywords.scenario_loop), line) if match: return match.group(2).strip(), int(match.group(1)) return None def _detect_table(self, line): """ Detects a step table row on the given line :param string line: the line to detect the table row :returns: if an step table row was found or not :rtype: bool """ return line.startswith("|") def _detect_step_text(self, line): """ Detects the beginning of an additional step text block :param str line: the line to detect the step text block :returns: if a step text block was found or not :rtype: bool """ return line.startswith('"""') def _detect_language(self, line): """ Detects a language on the given line :param string line: the line to detect the language :returns: the language or None :rtype: str or None """ match = re.search("^# language: (.*)", line) if match: return match.group(1) return None def _detect_tag(self, line): """ Detects a tag on the given line :param string line: the line to detect the tag :returns: the tag or None :rtype: str or None """ match = re.search(r"^@([^\s(]+)(?:\((.*?)\))?", line) if match: return match.group(1), match.group(2) return None PK}iG= radish/utils.py# -*- coding: utf-8 -*- """ This module provides several utility functions """ import os import re import sys import fnmatch import traceback from radish.terrain import world class Failure(object): # pylint: disable=too-few-public-methods """ Represents the fail reason for a step """ def __init__(self, exception): """ Initalizes the Step failure with a given Exception :param Exception exception: the exception shrown in the step """ self.exception = exception try: self.reason = unicode(str(exception), "utf-8") self.traceback = unicode(traceback.format_exc(), "utf-8") except NameError: self.reason = str(exception) self.traceback = traceback.format_exc() self.name = exception.__class__.__name__ traceback_info = traceback.extract_tb(sys.exc_info()[2])[-1] self.filename = traceback_info[0] self.line = int(traceback_info[1]) def console_write(text): """ Writes the given text to the console If the --no-colors flag is given all colors are removed from the text """ if world.config.no_ansi: text = re.sub(r"\x1b[^m]*m", "", text) print(text) def expandpath(path): """ Expands a path :param string path: the path to expand """ return os.path.expanduser(os.path.expandvars(path)) def recursive_glob(root, pattern): matches = [] for root, dirnames, filenames in os.walk(root): for filename in fnmatch.filter(filenames, pattern): matches.append(os.path.join(root, filename)) return matches def get_debugger(): """ Returns a debugger instance """ try: from IPython.core.debugger import Pdb pdb = Pdb() except ImportError: try: from IPython.Debugger import Pdb from IPython.Shell import IPShell IPShell(argv=[""]) pdb = Pdb() except ImportError: import pdb return pdb def datetime_to_str(datetime): """ Returns the datetime object in a defined human readable format. :param Datetime datetime: the datetime object """ if not datetime: return "" return datetime.strftime("%Y-%m-%dT%H:%M:%S") def get_width(data): """ Returns the needed width for a data column :param list data: a column with data """ return max(len(x) for x in data) PKiGX+Hradish/__init__.py# -*- coding: utf-8 -*- __DESCRIPTION__ = "Behaviour-Driven-Development tool for python" __LICENSE__ = "MIT" __VERSION__ = "0.2.8" __AUTHOR__ = "Timo Furrer" __AUTHOR_EMAIL__ = "tuxtimo@gmail.com" __URL__ = "http://radish-bdd.io" __DOWNLOAD_URL__ = "https://github.com/radish-bdd/radish" __BUGTRACKER_URL__ = "https://github.com/radish-bdd/radish/issues" # export some functions for users from radish.terrain import world from radish.hookregistry import before, after from radish.stepregistry import step, given, when, then, steps from radish.argexpregistry import arg_expr, ArgumentExpression from radish.exceptions import ValidationError PK*^Guhradish/extensions/__init__.py# -*- coding: utf-8 -*- PK*^G¸WR(radish/extensions/argumentexpressions.py# -*- coding: utf-8 -*- """ This module provides some default ArgumentExpressions """ from radish.argexpregistry import arg_expr, ArgumentExpression @arg_expr("Number", r"(\+|-)?\d+") def arg_expr_number(text): """ Argument Expression which expects a valid number :param str text: the text which was matched as number :returns: integer number :rtype: int """ return int(text) @arg_expr("FloatNumber", r"(\+|-)?\d+(\.\d+)?") def arg_expr_floatnumber(text): """ Argument Expression which expects a valid floating point number :param str text: the text which was matched as floating point number :returns: float number :rtype: float """ return float(text) @arg_expr("MathExpression", r"[0-9 +\-*/%.e]+") def arg_expr_mathexpression(text): """ Argument Expression which expects a valid math expression :param str text: the text which was matched as math expression :returns: calculated float number from the math expression :rtype: float """ return float(eval(text)) @arg_expr("VariableName", r"[A-Za-z_][A-Za-z0-9_]*") def arg_expr_variablename(text): """ Argument Expression which expects a variable name :param str text: the text which was matched as variable name :returns: the variable name :rtype: str """ return text PK*^GPs s %radish/extensions/endreport_writer.py# -*- coding: utf-8 -*- """ This radish extension module provide the functionality to write the end report """ # disable no-member lint error because of dynamic method from colorful # pylint: disable=no-member from datetime import timedelta from colorful import colorful from radish.hookregistry import after from radish.step import Step from radish.utils import console_write as write from radish.scenariooutline import ScenarioOutline from radish.scenarioloop import ScenarioLoop @after.all # pylint: disable=no-member def console_write_after_all(features, marker): """ Writes the endreport for all features :param list features: all features """ stats = { "features": {"amount": 0, "passed": 0, "failed": 0, "skipped": 0, "untested": 0}, "scenarios": {"amount": 0, "passed": 0, "failed": 0, "skipped": 0, "untested": 0}, "steps": {"amount": 0, "passed": 0, "failed": 0, "skipped": 0, "untested": 0}, } duration = timedelta() for feature in features: stats["features"]["amount"] += 1 stats["features"][feature.state] += 1 if feature.state in [Step.State.PASSED, Step.State.FAILED]: duration += feature.duration for scenario in feature.all_scenarios: if isinstance(scenario, ScenarioOutline): # skip ScenarioOutlines continue if isinstance(scenario, ScenarioLoop): # skip ScenarioLoop continue stats["scenarios"]["amount"] += 1 stats["scenarios"][scenario.state] += 1 for step in scenario.steps: stats["steps"]["amount"] += 1 stats["steps"][step.state] += 1 colored_closing_paren = colorful.bold_white(")") colored_comma = colorful.bold_white(", ") passed_word = colorful.bold_green("{} passed") failed_word = colorful.bold_red("{} failed") skipped_word = colorful.cyan("{} skipped") output = colorful.bold_white("{} features (".format(stats["features"]["amount"])) output += passed_word.format(stats["features"]["passed"]) if stats["features"]["failed"]: output += colored_comma + failed_word.format(stats["features"]["failed"]) if stats["features"]["skipped"]: output += colored_comma + skipped_word.format(stats["features"]["skipped"]) output += colored_closing_paren output += "\n" output += colorful.bold_white("{} scenarios (".format(stats["scenarios"]["amount"])) output += passed_word.format(stats["scenarios"]["passed"]) if stats["scenarios"]["failed"]: output += colored_comma + failed_word.format(stats["scenarios"]["failed"]) if stats["scenarios"]["skipped"]: output += colored_comma + skipped_word.format(stats["scenarios"]["skipped"]) output += colored_closing_paren output += "\n" output += colorful.bold_white("{} steps (".format(stats["steps"]["amount"])) output += passed_word.format(stats["steps"]["passed"]) if stats["steps"]["failed"]: output += colored_comma + failed_word.format(stats["steps"]["failed"]) if stats["steps"]["skipped"]: output += colored_comma + skipped_word.format(stats["steps"]["skipped"]) output += colored_closing_paren output += "\n" output += colorful.cyan("Run {} finished within {}:{} minutes".format(marker, int(duration.total_seconds()) / 60, duration.total_seconds() % 60.0)) write(output) PK*^G41ָ++%radish/extensions/failure_debugger.py# -*- coding: utf-8 -*- """ This module provides an extension which starts a debugger when a step fails """ from radish.terrain import world from radish.hookregistry import after from radish.step import Step import radish.utils as utils @after.each_step # pylint: disable=no-member def failure_debugger_after_each_step(step): """ Starts a python debugger if the step failed """ if not world.config.debug_after_failure or step.state is not Step.State.FAILED: return pdb = utils.get_debugger() pdb.set_trace() PK*^G)!&radish/extensions/failure_inspector.py# -*- coding: utf-8 -*- """ This module provides an extension which starts a python shell after a step failed """ from radish.terrain import world from radish.hookregistry import after from radish.step import Step from radish.exceptions import RadishError @after.each_step # pylint: disable=no-member def failure_inspector_after_each_step(step): """ Starts a python shell after a step failed """ if not world.config.inspect_after_failure or step.state is not Step.State.FAILED: return try: from IPython import embed except ImportError as e: raise RadishError("Cannot import IPython embed function: {}".format(e)) embed() PK*^GVXX"radish/extensions/time_recorder.py# -*- coding: utf-8 -*- """ This module is a REQUIRED extension to record the time of Features, Scenarios and Steps """ from datetime import datetime from radish.hookregistry import after, before __REQUIRED__ = True @before.each_feature # pylint: disable=no-member def time_recorder_before_each_feature(feature): """ Sets the starttime of the feature """ feature.starttime = datetime.now() @before.each_scenario # pylint: disable=no-member def time_recorder_before_each_scenario(scenario): """ Sets the starttime of the scenario """ scenario.starttime = datetime.now() @before.each_step # pylint: disable=no-member def time_recorder_before_each_step(step): """ Sets the starttime of the step """ step.starttime = datetime.now() @after.each_feature # pylint: disable=no-member def time_recorder_after_each_feature(feature): """ Sets the endtime of the feature """ feature.endtime = datetime.now() @after.each_scenario # pylint: disable=no-member def time_recorder_after_each_scenario(scenario): """ Sets the endtime of the scenario """ scenario.endtime = datetime.now() @after.each_step # pylint: disable=no-member def time_recorder_after_each_step(step): """ Sets the endtime of the step """ step.endtime = datetime.now() PK*^GRPPradish/extensions/variables.py# -*- coding: utf-8 -*- """ This module brings the benefit of variables to radish """ from radish.step import step from radish.argexpregistry import ArgumentExpression @step(ArgumentExpression(r"I set the variable {variable_name:VariableName} to {value:MathExpression}")) def set_float_variable_step(step, variable_name, value): # pylint: disable=redefined-outer-name """ Sets the variable `variable_name` in the scenario context to the float value `value` :param str variable_name: the name of the variable :param float value: the value for the variable """ setattr(step.context, variable_name, value) @step(ArgumentExpression(r"I set the variable {variable_name:VariableName} to \"{value}\"")) def set_str_variable_step(step, variable_name, value): # pylint: disable=redefined-outer-name """ Sets the variable `variable_name` in the scenario context to the string `value` :param str variable_name: the name of the variable :param str value: the value for the variable """ setattr(step.context, variable_name, value) PK@iG2DW#radish/extensions/bdd_xml_writer.py# -*- coding: utf-8 -*- """ This module provides a hook which generates a BDD XML result file at the end of the run. """ from getpass import getuser from socket import gethostname from lxml import etree from datetime import timedelta import re from radish.terrain import world from radish.hookregistry import after from radish.exceptions import RadishError from radish.scenariooutline import ScenarioOutline from radish.scenarioloop import ScenarioLoop from radish.step import Step import radish.utils as utils def _get_element_from_model(what, model): """ Create a etree.Element from a given model """ duration = str(model.duration.total_seconds()) if model.starttime and model.endtime else "" return etree.Element( what, sentence=model.sentence, id=str(model.id), result=model.state, starttime=utils.datetime_to_str(model.starttime), endtime=utils.datetime_to_str(model.endtime), duration=duration, testfile=model.path ) def _strip_ansi(text): """ Strips ANSI modifiers from the given text """ pattern = re.compile("(\\033\[\d+(?:;\d+)*m)") return pattern.sub("", text) def generate_bdd_xml(features): """ Generates the bdd xml """ if not features: raise RadishError("No features given to generate BDD xml file") duration = timedelta() for feature in features: if feature.state in [Step.State.PASSED, Step.State.FAILED]: duration += feature.duration testrun_element = etree.Element( "testrun", starttime=utils.datetime_to_str(features[0].starttime), endtime=utils.datetime_to_str(features[-1].endtime), duration=str(duration.total_seconds()), agent="{}@{}".format(getuser(), gethostname()) ) for feature in features: if not feature.has_to_run(world.config.scenarios, world.config.feature_tags, world.config.scenario_tags): continue feature_element = _get_element_from_model("feature", feature) description_element = etree.Element("description") description_element.text = etree.CDATA("\n".join(feature.description)) scenarios_element = etree.Element("scenarios") for scenario in (s for s in feature.all_scenarios if not isinstance(s, (ScenarioOutline, ScenarioLoop))): if not scenario.has_to_run(world.config.scenarios, world.config.feature_tags, world.config.scenario_tags): continue scenario_element = _get_element_from_model("scenario", scenario) for step in scenario.all_steps: step_element = _get_element_from_model("step", step) if step.state is Step.State.FAILED: failure_element = etree.Element( "failure", type=step.failure.name, message=step.failure.reason ) failure_element.text = etree.CDATA(_strip_ansi(step.failure.traceback)) step_element.append(failure_element) scenario_element.append(step_element) scenarios_element.append(scenario_element) feature_element.append(description_element) feature_element.append(scenarios_element) testrun_element.append(feature_element) with open(world.config.bdd_xml, "w+") as f: content = etree.tostring(testrun_element, pretty_print=True, xml_declaration=True, encoding="utf-8") try: if not isinstance(content, str): content = content.decode("utf-8") except Exception: pass finally: f.write(content) @after.all # pylint: disable=no-member def bdd_xml_writer_after_all(features, marker): # pylint: disable=unused-argument """ Generates a BDD XML file with the results """ if not world.config.bdd_xml: return generate_bdd_xml(features) PKaiGO1,1,#radish/extensions/console_writer.py# -*- coding: utf-8 -*- """ This radish extension provides the functionality to write the feature file run to the console. """ # disable no-member lint error because of dynamic method from colorful # pylint: disable=no-member import os from colorful import colorful from radish.terrain import world from radish.hookregistry import before, after from radish.feature import Feature from radish.scenariooutline import ScenarioOutline from radish.scenarioloop import ScenarioLoop from radish.step import Step from radish.utils import console_write as write __LAST_PRECONDITION__ = None def get_color_func(state): """ Returns the color func to use """ if state == Step.State.PASSED: return colorful.bold_green elif state == Step.State.FAILED: return colorful.bold_red elif state: return colorful.cyan def get_line_jump_seq(): """ Returns the line jump ANSI sequence """ line_jump_seq = "" if not world.config.no_ansi and not world.config.no_line_jump and not world.config.write_steps_once: line_jump_seq = "\r\033[A\033[K" return line_jump_seq def get_id_sentence_prefix(model, color_func, max_rows=None): """ Returns the id from a model as sentence prefix :param Model model: a model with an id property :param function color_func: a function which gives coloring :param int max_rows: the maximum rows. Used for padding """ padding = len("{}. ".format(max_rows)) if max_rows else 0 return color_func("{1: >{0}}. ".format(padding, model.id)) if world.config.write_ids else "" def get_id_padding(max_rows): """ Returns the id padding """ if not world.config.write_ids: return "" return " " * (len(str(max_rows)) + 2) def get_table_col_widths(table): """ Returns the width for every column of a table (lists in list) """ return [max(len(str(col)) for col in row) for row in zip(*table)] # pylint: disable=star-args @before.each_feature # pylint: disable=no-member def console_writer_before_each_feature(feature): """ Writes feature header to the console :param Feature feature: the feature to write to the console """ output = "" for tag in feature.tags: output += colorful.cyan(u"@{}{}\n".format(tag.name, "({})".format(tag.arg) if tag.arg else "")) leading = "\n " if feature.description else "" output += u"{}{}: {} # {}{}{}".format( get_id_sentence_prefix(feature, colorful.bold_cyan), colorful.bold_white(feature.keyword), colorful.bold_white(feature.sentence), colorful.bold_black(feature.path), leading, colorful.white("\n ".join(feature.description)) ) write(output) @before.each_scenario # pylint: disable=no-member def console_writer_before_each_scenario(scenario): """ Writes the scenario header to the console :param Scenario scenario: the scenario to write to the console """ output = "\n" if isinstance(scenario.parent, ScenarioOutline): if world.config.write_steps_once: return id_prefix = get_id_sentence_prefix(scenario, colorful.bold_brown, len(scenario.parent.scenarios)) colored_pipe = colorful.bold_white("|") output = u" {0}{1} {2} {1}".format( id_prefix, colored_pipe, (u" {} ").format(colored_pipe).join( colorful.bold_brown(u"{1: <{0}}".format(scenario.parent.get_column_width(i), x)) for i, x in enumerate(scenario.example.data) ) ) elif isinstance(scenario.parent, ScenarioLoop): if world.config.write_steps_once: return id_prefix = get_id_sentence_prefix(scenario, colorful.bold_brown, len(scenario.parent.scenarios)) colored_pipe = colorful.bold_white("|") output = u" {0}{1} {2: <18} {1}".format(id_prefix, colored_pipe, colorful.bold_brown(scenario.iteration)) else: id_prefix = get_id_sentence_prefix(scenario, colorful.bold_cyan) for tag in scenario.tags: if tag.name == "precondition" and world.config.expand and world.config.show: # exceptional for show command when scenario steps expand and tag is a precondition -> comment it out output += colorful.white(u" # @{}{}\n".format(tag.name, "({})".format(tag.arg) if tag.arg else "")) else: output += colorful.cyan(u" @{}{}\n".format(tag.name, u"({})".format(tag.arg) if tag.arg else "")) output += u" {}{}: {}".format(id_prefix, colorful.bold_white(scenario.keyword), colorful.bold_white(scenario.sentence)) write(output) @before.each_step # pylint: disable=no-member def console_writer_before_each_step(step): """ Writes the step to the console before it is run :param Step step: the step to write to the console """ global __LAST_PRECONDITION__ if not isinstance(step.parent.parent, Feature): return if world.config.write_steps_once: return output = "" if step.as_precondition and __LAST_PRECONDITION__ != step.as_precondition: output += colorful.white(u" As precondition from {}: {}\n".format(os.path.basename(step.as_precondition.path), step.as_precondition.sentence)) elif not step.as_precondition and __LAST_PRECONDITION__: output += colorful.white(u" From scenario\n") __LAST_PRECONDITION__ = step.as_precondition output += u"\r {}{}".format(get_id_sentence_prefix(step, colorful.bold_brown), colorful.bold_brown(step.sentence)) if step.text: id_padding = get_id_padding(len(step.parent.steps)) output += colorful.bold_white(u'\n {}"""'.format(id_padding)) output += colorful.cyan(u"".join(["\n {}{}".format(id_padding, l) for l in step.raw_text])) output += colorful.bold_white(u'\n {}"""'.format(id_padding)) if step.table: colored_pipe = colorful.bold_white("|") col_widths = get_table_col_widths(step.table) for row in step.table: output += u"\n {0} {1} {0}".format(colored_pipe, (" {} ").format(colored_pipe).join( colorful.bold_brown(u"{1: <{0}}".format(col_widths[i], x)) for i, x in enumerate(row) )) write(output) @after.each_step # pylint: disable=no-member def console_writer_after_each_step(step): """ Writes the step to the console after it was run :param Step step: the step to write to the console """ if not isinstance(step.parent.parent, Feature): return color_func = get_color_func(step.state) line_jump_seq = get_line_jump_seq() * (((len(step.raw_text) + 3) if step.text else 1) + (len(step.table) if step.table else 0)) output = u"{} {}{}".format(line_jump_seq, get_id_sentence_prefix(step, colorful.bold_cyan), color_func(step.sentence)) if step.text: id_padding = get_id_padding(len(step.parent.steps)) output += colorful.bold_white(u'\n {}"""'.format(id_padding)) output += colorful.cyan(u"".join(["\n {}{}".format(id_padding, l) for l in step.raw_text])) output += colorful.bold_white(u'\n {}"""'.format(id_padding)) if step.table: colored_pipe = colorful.bold_white("|") col_widths = get_table_col_widths(step.table) for row in step.table: output += u"\n {0} {1} {0}".format(colored_pipe, (" {} ").format(colored_pipe).join( color_func(u"{1: <{0}}".format(col_widths[i], x)) for i, x in enumerate(row) )) if step.state == step.State.FAILED: if world.config.with_traceback: output += u"\n {}{}".format(get_id_padding(len(step.parent.steps) - 2), "\n ".join([colorful.red(l) for l in step.failure.traceback.split("\n")[:-2]])) output += u"\n {}{}: {}".format(get_id_padding(len(step.parent.steps) - 2), colorful.bold_red(step.failure.name), colorful.red(step.failure.reason)) write(output) @after.each_scenario # pylint: disable=no-member def console_writer_after_each_scenario(scenario): """ If the scenario is a ExampleScenario it will write the Examples header :param Scenario scenario: the scenario which was ran. """ output = "" if isinstance(scenario, ScenarioOutline): output += u"\n {}:\n".format(colorful.bold_white(scenario.example_keyword)) output += colorful.bold_white(u" {}| {} |".format( get_id_padding(len(scenario.scenarios)), u" | ".join("{1: <{0}}".format(scenario.get_column_width(i), x) for i, x in enumerate(scenario.examples_header)) )) elif isinstance(scenario, ScenarioLoop): output += u"\n {}: {}".format(colorful.bold_white(scenario.iterations_keyword), colorful.cyan(scenario.iterations)) elif isinstance(scenario.parent, ScenarioOutline): colored_pipe = colorful.bold_white("|") color_func = get_color_func(scenario.state) output += u"{0} {1}{2} {3} {2}".format( get_line_jump_seq(), get_id_sentence_prefix(scenario, colorful.bold_cyan, len(scenario.parent.scenarios)), colored_pipe, (u" {} ").format(colored_pipe).join( color_func(u"{1: <{0}}".format(scenario.parent.get_column_width(i), x)) for i, x in enumerate(scenario.example.data) ) ) if scenario.state == Step.State.FAILED: failed_step = scenario.failed_step if world.config.with_traceback: output += u"\n {}{}".format(get_id_padding(len(scenario.parent.scenarios)), "\n ".join([colorful.red(l) for l in failed_step.failure.traceback.split("\n")[:-2]])) output += u"\n {}{}: {}".format(get_id_padding(len(scenario.parent.scenarios)), colorful.bold_red(failed_step.failure.name), colorful.red(failed_step.failure.reason)) elif isinstance(scenario.parent, ScenarioLoop): colored_pipe = colorful.bold_white("|") color_func = get_color_func(scenario.state) output += u"{0} {1}{2} {3: <18} {2}".format(get_line_jump_seq(), get_id_sentence_prefix(scenario, colorful.bold_cyan, len(scenario.parent.scenarios)), colored_pipe, color_func(scenario.iteration)) if scenario.state == Step.State.FAILED: failed_step = scenario.failed_step if world.config.with_traceback: output += u"\n {}{}".format(get_id_padding(len(scenario.parent.scenarios)), "\n ".join([colorful.red(l) for l in failed_step.failure.traceback.split("\n")[:-2]])) output += u"\n {}{}: {}".format(get_id_padding(len(scenario.parent.scenarios)), colorful.bold_red(failed_step.failure.name), colorful.red(failed_step.failure.reason)) if output: write(output) @after.each_feature # pylint: disable=no-member def console_writer_after_each_feature(feature): # pylint: disable=unused-argument """ Writes a newline after each feature :param Feature feature: the feature which was ran. """ write("") PKiGV "radish/extensions/syslog_writer.py# -*- coding: utf-8 -*- """ This module provides an extension to write all features, scenarios and steps to the syslog. """ from radish.terrain import world from radish.feature import Feature from radish.hookregistry import before, after import syslog def get_scenario_feature(scenario): """ Gets the scenarios feature """ if not isinstance(scenario.parent, Feature): return scenario.parent.parent return scenario.parent def log(message): """ Logs the given message to the syslog :param string message: the message to log """ try: if isinstance(message, unicode): message = message.encode("utf8") except Exception: # pylint: disable=broad-except pass finally: syslog.syslog(syslog.LOG_INFO, message) @before.all # pylint: disable=no-member def syslog_writer_before_all(features, marker): # pylint: disable=unused-argument """ Opens the syslog """ syslog.openlog("radish") log(u"begin run {}".format(marker)) @after.all # pylint: disable=no-member def syslog_writer_after_all(features, marker): # pylint: disable=unused-argument """ Closes the syslog """ log(u"end run {}".format(marker)) syslog.closelog() @before.each_feature # pylint: disable=no-member def syslog_writer_before_each_feature(feature): """ Writes the feature to the syslog """ log(u"begin feature {}:{} {}".format(world.config.marker, feature.id, feature.sentence)) @after.each_feature # pylint: disable=no-member def syslog_writer_after_each_feature(feature): """ Writes the feature to the syslog """ log(u"end feature {}:{} {}".format(world.config.marker, feature.id, feature.sentence)) @before.each_scenario # pylint: disable=no-member def syslog_writer_before_each_scenario(scenario): """ Writes the scenario to the syslog """ log(u"begin scenario {}:{}.{} {}".format(world.config.marker, get_scenario_feature(scenario).id, scenario.id, scenario.sentence)) @after.each_scenario # pylint: disable=no-member def syslog_writer_after_each_scenario(scenario): """ Writes the scenario to the syslog """ log(u"end scenario {}:{}.{} {}".format(world.config.marker, get_scenario_feature(scenario).id, scenario.id, scenario.sentence)) @before.each_step # pylint: disable=no-member def syslog_writer_before_each_step(step): """ Writes the step to the syslog """ log(u"begin step {}:{}.{}.{} {}".format(world.config.marker, get_scenario_feature(step.parent).id, step.parent.id, step.id, step.sentence)) @after.each_step # pylint: disable=no-member def syslog_writer_after_each_step(step): """ Writes the step to the syslog """ log(u"{} step {}:{}.{}.{} {}".format(step.state, world.config.marker, get_scenario_feature(step.parent).id, step.parent.id, step.id, step.sentence)) PK*^Gradish/languages/de.json{ "keywords": { "feature": "Funktionalität", "scenario": "Szenario", "scenario_outline": "Szenario Auslagerung", "examples": "Beispiele", "scenario_loop": "Szenario Schleife", "iterations": "Durchläufe" } } PK*^G+Yoyradish/languages/en.json{ "keywords": { "feature": "Feature", "scenario": "Scenario", "scenario_outline": "Scenario Outline", "examples": "Examples", "scenario_loop": "Scenario Loop", "iterations": "Iterations" } } PKiG^- *radish_bdd-0.2.8.dist-info/DESCRIPTION.rstUNKNOWN PKiGEG--+radish_bdd-0.2.8.dist-info/entry_points.txt[console_scripts] radish = radish.main:main PKiG4*<(radish_bdd-0.2.8.dist-info/metadata.json{"classifiers": ["Development Status :: 4 - Beta", "Environment :: Console", "Intended Audience :: Developers", "Intended Audience :: Education", "Intended Audience :: Other Audience", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: MacOS :: MacOS X", "Operating System :: OS Independent", "Operating System :: Microsoft :: Windows", "Operating System :: Microsoft :: Windows :: Windows 7", "Operating System :: Microsoft :: Windows :: Windows XP", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: Implementation", "Topic :: Education :: Testing", "Topic :: Software Development", "Topic :: Software Development :: Testing"], "download_url": "https://github.com/radish-bdd/radish", "extensions": {"python.commands": {"wrap_console": {"radish": "radish.main:main"}}, "python.details": {"contacts": [{"email": "tuxtimo@gmail.com", "name": "Timo Furrer", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "http://radish-bdd.io"}}, "python.exports": {"console_scripts": {"radish": "radish.main:main"}}}, "extras": [], "generator": "bdist_wheel (0.26.0)", "license": "MIT", "metadata_version": "2.0", "name": "radish-bdd", "platform": "Linux", "run_requires": [{"requires": ["colorful (>=0.01.03)", "docopt", "ipython", "lxml", "parse", "pysingleton"]}], "summary": "Behaviour-Driven-Development tool for python", "version": "0.2.8"}PKiGz**(radish_bdd-0.2.8.dist-info/top_level.txtradish radish/extensions radish/languages PKiGndnn radish_bdd-0.2.8.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any Tag: py3-none-any PKiGc̝*]]#radish_bdd-0.2.8.dist-info/METADATAMetadata-Version: 2.0 Name: radish-bdd Version: 0.2.8 Summary: Behaviour-Driven-Development tool for python Home-page: http://radish-bdd.io Author: Timo Furrer Author-email: tuxtimo@gmail.com License: MIT Download-URL: https://github.com/radish-bdd/radish Platform: Linux Platform: Windows Platform: MAC OS X Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Education Classifier: Intended Audience :: Other Audience Classifier: License :: OSI Approved :: MIT License Classifier: Natural Language :: English Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: OS Independent Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: Microsoft :: Windows :: Windows 7 Classifier: Operating System :: Microsoft :: Windows :: Windows XP Classifier: Operating System :: POSIX Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: Implementation Classifier: Topic :: Education :: Testing Classifier: Topic :: Software Development Classifier: Topic :: Software Development :: Testing Requires-Dist: colorful (>=0.01.03) Requires-Dist: docopt Requires-Dist: ipython Requires-Dist: lxml Requires-Dist: parse Requires-Dist: pysingleton UNKNOWN PKiG'-: : !radish_bdd-0.2.8.dist-info/RECORDradish/__init__.py,sha256=WNHnrnrlnTXmDJX9I7jDZcRbZB2Xk77QOrvGvwVQI-g,642 radish/argexpregistry.py,sha256=wzmmGPILeqPyK4tJK0EmTCFOFUzEEesRnQQM6Xpv2V4,1307 radish/core.py,sha256=ZxRX45rmiX6HwOehl3u9lP0HYPjosvgDuk5yXpgI438,2028 radish/errororacle.py,sha256=rO7mx6vM_FICFwGbYhJexducaC6ceImMI16ily_31fY,3451 radish/examplescenario.py,sha256=R1ahpd3c926kT9qb_WfWeZ5xNRnKDFqYQ97S8jZxHqE,781 radish/exceptions.py,sha256=K5mZHYMh76C7fMmNoDzyh-RP6lw7li0udt5VqWb5dBk,4118 radish/feature.py,sha256=2kiNw6plPLPQc4ElhyZQAFbq3taTR7ojYvxXTlDo5H0,3241 radish/hookregistry.py,sha256=srdWy7myK0vuVk8UvLYwQ_X8eIUfRoDW9K9HU9lCQro,2417 radish/iterationscenario.py,sha256=WoENuyUKSCOdnI4VNRBFrMEM6YsREecyVm9U2p8gIVw,791 radish/loader.py,sha256=IknBMLaIF7H8bu2gekmvNrlhMYeSf7UScMcOPgsVJjI,1476 radish/main.py,sha256=xVziBVc9sMjN5y7zJkIbQ_Sa9OXh_PrWL5MWcYDM5sw,7504 radish/matcher.py,sha256=q6cWEKOomlQvuSfj636il-lobKPayViAYGyDRy1Qna4,2706 radish/model.py,sha256=FVBn9VZM95YORX-gPR_saGFeraFcip6pJOpuu8VzIyQ,1412 radish/parser.py,sha256=WUGzCR03Ic3UEib1EkhxHPI_rKQwLdBcwKzVnOE5ypk,17191 radish/runner.py,sha256=3Pp4zelkmlj42d5wqfjXiISdWd95PUFn3QBXLeM9Q5g,4644 radish/scenario.py,sha256=8HuSzhCJ1vVj9h7i4hNJmGiZu5e93uW17Hrx0gRyEDc,2982 radish/scenarioloop.py,sha256=zD2HPV-5L_aBGp4laYPZzZmHbUE_TpWlXBFwFn5TePI,1464 radish/scenariooutline.py,sha256=TKUYCjIICeEbUjcclXHsgxcc5zcDK6J7lIDA5tmLNis,2921 radish/step.py,sha256=WOL0zoENJzd0iKaCajnEfipTCMA-vr6bR5FQfb1HEGo,4312 radish/stepregistry.py,sha256=DbhY5iXMcBOtSt00QWf4gfwwV45jNA-x8Z9nkeL7wyg,3670 radish/terrain.py,sha256=YLKKxTpB8fnXbLoVOm8e1YoNkNFLDZ-FbflQ7UthQqk,346 radish/utils.py,sha256=Gd0sgTJL0aR5G1X_xLfYEAjWdVGrWkrjFZoWwaxaZgU,2515 radish/extensions/__init__.py,sha256=iwhKnzeBJLKxpRVjvzwiRE63_zNpIBfaKLITauVph-0,24 radish/extensions/argumentexpressions.py,sha256=PYljXleM82MjG5jRMX2QvA_AvHkRvJihXdMcytYVIzM,1418 radish/extensions/bdd_xml_writer.py,sha256=b8KK0QuzYevxGyrWS7tyegNQ3v0_U-2CXsOtYw1v5Nk,4006 radish/extensions/console_writer.py,sha256=hNYHcIO1l6iJp55z9wmSRrKFAOsKHwPJk3ntqCpS6Lo,11313 radish/extensions/endreport_writer.py,sha256=ZORQY3Z21E5k1wl1oTqnfTm_UTwBLlLdiq7HKoBncGs,3443 radish/extensions/failure_debugger.py,sha256=5jGWX3lG6Q6bznyM_OoAWoLlkCx0qt74CKXAE9tzj6Q,555 radish/extensions/failure_inspector.py,sha256=hFEeQlvw5dwUxXYgh19RbQOHkEe_RrZlh0ipPUXaGic,688 radish/extensions/syslog_writer.py,sha256=IAUnMA6iTu6ApvfKw4fQXko0-KZ18Wrl5vuYYXGQjBs,2948 radish/extensions/time_recorder.py,sha256=vcCgcWXVl8JETO4kEkr5NL2Os-kvtryjN8aqT5Ta-K8,1368 radish/extensions/variables.py,sha256=u8b0SFVmbguTGzTcE6LFJ7XbYifWRnl0mAaRQ8ndwJc,1104 radish/languages/de.json,sha256=o4hJ4ywg8JHirc1kue482c24tN4tUAumNwWeXYYFqSI,237 radish/languages/en.json,sha256=Cpv8AsT4vZpv_R-379A2_aZU4sov2Z5-frZF8H-XGqw,219 radish_bdd-0.2.8.dist-info/DESCRIPTION.rst,sha256=OCTuuN6LcWulhHS3d5rfjdsQtW22n7HENFRh6jC6ego,10 radish_bdd-0.2.8.dist-info/METADATA,sha256=-2Y8FJiXJQ29eSTr-1zB2b_zZFtvTbmdaqdRwQBF3ag,1629 radish_bdd-0.2.8.dist-info/RECORD,, radish_bdd-0.2.8.dist-info/WHEEL,sha256=GrqQvamwgBV4nLoJe0vhYRSWzWsx7xjlt74FT0SWYfE,110 radish_bdd-0.2.8.dist-info/entry_points.txt,sha256=SvhaTdwVgxLwghZnajV6p9k1inA3clmj9y1BomGNfQI,45 radish_bdd-0.2.8.dist-info/metadata.json,sha256=PqE5uAs7UqstOTA3W6jm4A17JT9JaJHdduy4yHF1yHY,1713 radish_bdd-0.2.8.dist-info/top_level.txt,sha256=L0Ud6aN036mzXrHaNIhJp4nhTEKS924y9Zxy4kcPU-U,42 PK*^G+pradish/argexpregistry.pyPK*^GD+  Qradish/examplescenario.pyPK*^G_Z5radish/exceptions.pyPK*^Gk radish/feature.pyPK*^G'q q %radish/hookregistry.pyPK*^GWZ/radish/iterationscenario.pyPK*^GΠ2radish/loader.pyPK*^G`4P 8radish/matcher.pyPK*^G^]Cradish/model.pyPK*^G$$Iradish/runner.pyPK*^G7 `[radish/scenario.pyPK*^G$|6gradish/scenarioloop.pyPK*^GIi i "mradish/scenariooutline.pyPK*^G*xradish/step.pyPK*^G9m9VVƉradish/stepregistry.pyPK*^G]YZZPradish/terrain.pyPK hGז{ { ٙradish/errororacle.pyPKOhG"D7radish/core.pyPKQhGOhPPradish/main.pyPKhG'C'Cradish/parser.pyPK}iG= pradish/utils.pyPKiGX+Hpradish/__init__.pyPK*^Guh"radish/extensions/__init__.pyPK*^G¸WR(uradish/extensions/argumentexpressions.pyPK*^GPs s %E#radish/extensions/endreport_writer.pyPK*^G41ָ++%0radish/extensions/failure_debugger.pyPK*^G)!&i3radish/extensions/failure_inspector.pyPK*^GVXX"]6radish/extensions/time_recorder.pyPK*^GRPP;radish/extensions/variables.pyPK@iG2DW#@radish/extensions/bdd_xml_writer.pyPKaiGO1,1,#hPradish/extensions/console_writer.pyPKiGV "|radish/extensions/syslog_writer.pyPK*^Gradish/languages/de.jsonPK*^G+Yoyradish/languages/en.jsonPKiG^- *Ҋradish_bdd-0.2.8.dist-info/DESCRIPTION.rstPKiGEG--+$radish_bdd-0.2.8.dist-info/entry_points.txtPKiG4*<(radish_bdd-0.2.8.dist-info/metadata.jsonPKiGz**(radish_bdd-0.2.8.dist-info/top_level.txtPKiGndnn radish_bdd-0.2.8.dist-info/WHEELPKiGc̝*]]#radish_bdd-0.2.8.dist-info/METADATAPKiG'-: : !Kradish_bdd-0.2.8.dist-info/RECORDPK)) ħ