behave-1.2.6/0000755000076600000240000000000013244564040013037 5ustar jensstaff00000000000000behave-1.2.6/.bumpversion.cfg0000644000076600000240000000040413244561652016153 0ustar jensstaff00000000000000[bumpversion] current_version = 1.2.6 files = behave/__init__.py setup.py VERSION.txt .bumpversion.cfg parse = (?P\d+)\.(?P\d+)\.(?P\d+)(?P\w*) serialize = {major}.{minor}.{patch}{drop} commit = False tag = False allow_dirty = True behave-1.2.6/.ci/0000755000076600000240000000000013244564037013516 5ustar jensstaff00000000000000behave-1.2.6/.ci/appveyor.yml0000644000076600000240000000451013244555737016115 0ustar jensstaff00000000000000# ============================================================================= # CI-SERVER CONFIGURATION: behave # ============================================================================= # OS: Windows # SEE ALSO: # * http://www.appveyor.com/docs/build-configuration # * http://www.appveyor.com/docs/installed-software#python # * http://www.appveyor.com/docs/appveyor-yml # # VALIDATE: appveyor.yml # * https://ci.appveyor.com/tools/validate-yaml # ============================================================================= # https://bootstrap.pypa.io/get-pip.py version: 1.2.6.dev0.{build}-{branch} clone_folder: C:\projects\behave.ci # clone_depth: 2 # shallow_clone: true environment: PYTHONPATH: ".;%CD%" BEHAVE_ROOTDIR_PREFIX: "C:" matrix: - PYTHON_DIR: C:\Python36-x64 PYTHON: C:\Python36-x64\python.exe - PYTHON_DIR: C:\Python27-x64 PYTHON: C:\Python27-x64\python.exe BEHAVE_ROOTDIR_PREFIX: "c:" # -- TEMPORARILY DISABLED: environment matrix: # - PYTHON_DIR: C:\Python35-x64 # PYTHON: C:\Python35-x64\python.exe # - PYTHON_DIR: C:\Python35 # PYTHON: C:\Python35\python.exe # -- TEMPORARILY DISABLED: environment matrix: # - PYTHON_DIR: C:\Python34 # - PYTHON_DIR: C:\Python26 # - PYTHON_DIR: C:\Python33 # - PYTHON_DIR: C:\Python26-x64 # - PYTHON_DIR: C:\Python27-x64 # - PYTHON_DIR: C:\Python33-x64 # - PYTHON_DIR: C:\Python34-x64 # - PYTHON_DIR: C:\Python35-x64 init: - cmd: "echo TESTING-WITH %PYTHON_DIR%, %PYTHON%" - cmd: "%PYTHON_DIR%\\python.exe --version" - cmd: "%PYTHON% --version" - cmd: "echo CD=%CD%" - cmd: "echo PYTHONPATH=%PYTHONPATH%" - cmd: set # -- TEMPORARILY DISABLED: Python variants discovery # - cmd: "@echo AVAILABLE PYTHON VERSIONS" # - cmd: "@dir C:\Python*" # - path install: - cmd: "%PYTHON_DIR%\\python.exe -m pip install pytest mock PyHamcrest nose" - cmd: "%PYTHON_DIR%\\python.exe -m pip install ." - cmd: "%PYTHON_DIR%\\python.exe bin/explore_platform_encoding.py" # NOT-NEEDED: - cmd: "%PYTHON_DIR%\\python.exe -m pip install parse" build: off test_script: - cmd: "%PYTHON_DIR%\\Scripts\\py.test.exe test tests" - cmd: "%PYTHON_DIR%\\Scripts\\behave.exe -f progress3 --junit features" - cmd: "%PYTHON_DIR%\\Scripts\\behave.exe -f progress3 --junit issue.features" behave-1.2.6/.coveragerc0000644000076600000240000000204213244555737015173 0ustar jensstaff00000000000000# ========================================================================= # COVERAGE CONFIGURATION FILE: .coveragerc # ========================================================================= # LANGUAGE: Python # SEE ALSO: # * http://nedbatchelder.com/code/coverage/ # * http://nedbatchelder.com/code/coverage/config.html # ========================================================================= [run] # append = .coverage # include = behave* source = behave branch = True parallel = True [report] # Regexes for lines to exclude from consideration exclude_lines = # Have to re-enable the standard pragma pragma: no cover # Don't complain about missing debug-only code: def __repr__ if self\.debug # Don't complain if tests don't hit defensive assertion code: raise AssertionError raise NotImplementedError # Don't complain if non-runnable code isn't run: if 0: if False: if __name__ == .__main__.: ignore_errors = True [html] directory = build/coverage.html [xml] output = build/coverage.xml behave-1.2.6/.editorconfig0000644000076600000240000000106413244555737015532 0ustar jensstaff00000000000000# ============================================================================= # EDITOR CONFIGURATION: http://editorconfig.org # ============================================================================= root = true # -- DEFAULT: Unix-style newlines with a newline ending every file. [*] charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [*.{py,rst,ini,txt}] indent_style = space indent_size = 4 [*.feature] indent_style = space indent_size = 2 [**/makefile] indent_style = tab [*.{cmd,bat}] end_of_line = crlf behave-1.2.6/.pycheckrc0000644000076600000240000001273113244555737015034 0ustar jensstaff00000000000000# ======================================================================================== # PYCHECKER CONFIGURATION # ======================================================================================== # .pycheckrc file created by PyChecker v0.8.18 # .pycheckrc file created by PyChecker v0.8.19 # # It should be placed in your home directory (value of $HOME). # If $HOME is not set, it will look in the current directory. # # SEE ALSO: # * http://pychecker.sourceforge.net/ # * http://sourceforge.net/projects/pychecker # ======================================================================================== # only warn about files passed on the command line only = 0 # the maximum number of warnings to be displayed limit = 100 # list of evil C extensions that crash the interpreter evil = [] # ignore import errors ignoreImportErrors = 0 # unused imports importUsed = 1 # unused imports from __init__.py packageImportUsed = 1 # module imports itself reimportSelf = 1 # reimporting a module moduleImportErrors = 1 # module does import and from ... import mixImport = 1 # unused local variables, except tuples localVariablesUsed = 1 # all unused local variables, including tuples unusedLocalTuple = 0 # all unused class data members membersUsed = 0 # all unused module variables allVariablesUsed = 0 # unused private module variables privateVariableUsed = 1 # report each occurrence of global warnings reportAllGlobals = 0 # functions called with named arguments (like keywords) namedArgs = 0 # Attributes (members) must be defined in __init__() onlyCheckInitForMembers = 0 # Subclass.__init__() not defined initDefinedInSubclass = 0 # Baseclass.__init__() not called baseClassInitted = 1 # Subclass needs to override methods that only throw exceptions abstractClasses = 1 # Return None from __init__() returnNoneFromInit = 1 # unreachable code unreachableCode = 0 # a constant is used in a conditional statement constantConditions = 1 # 1 is used in a conditional statement (if 1: or while 1:) constant1 = 0 # check if iterating over a string stringIteration = 1 # check improper use of string.find() stringFind = 1 # Calling data members as functions callingAttribute = 0 # class attribute does not exist classAttrExists = 1 # First argument to methods methodArgName = 'self' # First argument to classmethods classmethodArgNames = ['cls', 'klass'] # unused method/function arguments argumentsUsed = 1 # unused method/function variable arguments varArgumentsUsed = 1 # ignore if self is unused in methods ignoreSelfUnused = 0 # check if overridden methods have the same signature checkOverridenMethods = 1 # check if __special__ methods exist and have the correct signature checkSpecialMethods = 1 # check if function/class/method names are reused redefiningFunction = 1 # check if using unary positive (+) which is usually meaningless unaryPositive = 1 # check if modify (call method) on a parameter that has a default value modifyDefaultValue = 1 # check if variables are set to different types inconsistentTypes = 0 # check if unpacking a non-sequence unpackNonSequence = 1 # check if unpacking sequence with the wrong length unpackLength = 1 # check if raising or catching bad exceptions badExceptions = 1 # check if statement appears to have no effect noEffect = 1 # check if using (expr % 1), it has no effect on integers and strings modulo1 = 1 # check if using (expr is const-literal), doesn't always work on integers and strings isLiteral = 1 # check if a constant string is passed to getattr()/setattr() constAttr = 1 # check consistent return values checkReturnValues = 1 # check if using implict and explicit return values checkImplicitReturns = 1 # check that attributes of objects exist checkObjectAttrs = 1 # various warnings about incorrect usage of __slots__ slots = 1 # using properties with classic classes classicProperties = 1 # check if __slots__ is empty emptySlots = 1 # check if using integer division intDivide = 1 # check if local variable shadows a global shadows = 1 # check if a variable shadows a builtin shadowBuiltins = 1 # check if input() is used usesInput = 1 # check if the exec statement is used usesExec = 0 # ignore warnings from files under standard library ignoreStandardLibrary = 1 # ignore warnings from the list of modules blacklist = ['Tkinter', 'wxPython', 'gtk', 'GTK', 'GDK'] # ignore global variables not used if name is one of these values variablesToIgnore = ['__version__', '__warningregistry__', '__all__', '__credits__', '__test__', '__author__', '__email__', '__revision__', '__id__', '__copyright__', '__license__', '__date__'] # ignore unused locals/arguments if name is one of these values unusedNames = ['_', 'empty', 'unused', 'dummy', 'crap'] # ignore missing class attributes if name is one of these values missingAttrs = [] # ignore use of deprecated modules/functions deprecated = 1 # maximum lines in a function maxLines = 200 # maximum branches in a function maxBranches = 50 # maximum returns in a function maxReturns = 10 # maximum # of arguments to a function maxArgs = 10 # maximum # of locals in a function maxLocals = 40 # maximum # of identifier references (Law of Demeter) maxReferences = 5 # no module doc strings noDocModule = 0 # no class doc strings noDocClass = 0 # no function/method doc strings noDocFunc = 0 # print internal checker parse structures printParse = 0 # turn on debugging for checker debug = 0 # print each class object to find one that crashes findEvil = 0 # turn off all output except warnings quiet = 0 behave-1.2.6/.pylintrc0000644000076600000240000003120713244555737014724 0ustar jensstaff00000000000000# ============================================================================= # PYLINT CONFIGURATION # ============================================================================= # PYLINT-VERSION: 1.5.x # SEE ALSO: http://www.pylint.org/ # ============================================================================= [MASTER] # Specify a configuration file. #rcfile=.pylintrc # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= # Add files or directories to the blacklist. They should be base names, not # paths. ignore=.git # Pickle collected data for later comparisons. persistent=yes # List of plugins (as comma separated values of python modules names) to load, # usually to register additional checkers. load-plugins= # Use multiple processes to speed up Pylint. jobs=1 # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code extension-pkg-whitelist= # Allow optimization of some AST trees. This will activate a peephole AST # optimizer, which will apply various small optimizations. For instance, it can # be used to obtain the result of joining multiple strings with the addition # operator. Joining a lot of strings can lead to a maximum recursion error in # Pylint and this flag can prevent that. It has one side effect, the resulting # AST will be different than the one from reality. optimize-ast=no [MESSAGES CONTROL] # Only show warnings with the listed confidence levels. Leave empty to show # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED confidence= # Enable the message, report, category or checker with the given id(s). You can # either give multiple identifier separated by comma (,) or put this option # multiple time. See also the "--disable" option for examples. #enable= # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this # option multiple times (only on the command line, not in the configuration # file where it should appear only once).You can also use "--disable=all" to # disable everything first and then reenable specific checks. For example, if # you want to run only the similarities checker, you can use "--disable=all # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" disable=import-star-module-level,old-octal-literal,oct-method,print-statement,unpacking-in-except,parameter-unpacking,backtick,old-raise-syntax,old-ne-operator,long-suffix,dict-view-method,dict-iter-method,metaclass-assignment,next-method-called,raising-string,indexing-exception,raw_input-builtin,long-builtin,file-builtin,execfile-builtin,coerce-builtin,cmp-builtin,buffer-builtin,basestring-builtin,apply-builtin,filter-builtin-not-iterating,unused-variable,using-cmp-argument,useless-suppression,range-builtin-not-iterating,suppressed-message,missing-docstring,no-absolute-import,old-division,cmp-method,reload-builtin,zip-builtin-not-iterating,intern-builtin,unichr-builtin,reduce-builtin,standarderror-builtin,unicode-builtin,xrange-builtin,coerce-method,delslice-method,getslice-method,setslice-method,input-builtin,too-few-public-methods,round-builtin,locally-disabled,hex-method,nonzero-method,map-builtin-not-iterating [REPORTS] # Set the output format. Available formats are text, parseable, colorized, msvs # (visual studio) and html. You can also give a reporter class, eg # mypackage.mymodule.MyReporterClass. # output-format=text output-format=colorized # Put messages in a separate file for each module / package specified on the # command line instead of printing them on stdout. Reports (if any) will be # written in a file name "pylint_global.[txt|html]". files-output=no # Tells whether to display a full report or only the messages reports=yes # Python expression which should return a note less than 10 (10 is the highest # note). You have access to the variables errors warning, statement which # respectively contain the number of errors / warnings messages and the total # number of statements analyzed. This is used by the global evaluation report # (RP0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Template used to display messages. This is a python new-style format string # used to format the message information. See doc for all details #msg-template= [BASIC] # List of builtins function names that should not be used, separated by a comma bad-functions=map,filter,apply,input # Good variable names which should always be accepted, separated by a comma good-names=c,d,f,h,i,j,k,m,n,o,p,r,s,v,w,x,y,e,ex,kw,up,_ # Bad variable names which should always be refused, separated by a comma bad-names=foo,bar,baz,toto,tutu,tata # Colon-delimited sets of names that determine each other's naming style when # the name regexes allow several styles. name-group= # Include a hint for the correct naming format with invalid-name include-naming-hint=yes # Regular expression matching correct function names function-rgx=[a-z_][a-z0-9_]{2,40}$ # Naming hint for function names function-name-hint=[a-z_][a-z0-9_]{2,40}$ # Regular expression matching correct variable names variable-rgx=[a-z_][a-z0-9_]{2,40}$ # Naming hint for variable names variable-name-hint=[a-z_][a-z0-9_]{2,40}$ # Regular expression matching correct constant names const-rgx=(([a-zA-Z_][a-zA-Z0-9_]*)|(__.*__))$ # Naming hint for constant names const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ # Regular expression matching correct attribute names attr-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming hint for attribute names attr-name-hint=[a-z_][a-z0-9_]{2,30}$ # Regular expression matching correct argument names argument-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming hint for argument names argument-name-hint=[a-z_][a-z0-9_]{2,30}$ # Regular expression matching correct class attribute names class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,40}|(__.*__))$ # Naming hint for class attribute names class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,40}|(__.*__))$ # Regular expression matching correct inline iteration names inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ # Naming hint for inline iteration names inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ # Regular expression matching correct class names class-rgx=[A-Z_][a-zA-Z0-9]+$ # Naming hint for class names class-name-hint=[A-Z_][a-zA-Z0-9]+$ # Regular expression matching correct module names module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Naming hint for module names module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ # Regular expression matching correct method names method-rgx=[a-z_][a-z0-9_]{2,30}$ # Naming hint for method names method-name-hint=[a-z_][a-z0-9_]{2,30}$ # Regular expression which should only match function or class names that do # not require a docstring. no-docstring-rgx=__.*__ # Minimum line length for functions/classes that require docstrings, shorter # ones are exempt. docstring-min-length=-1 [ELIF] # Maximum number of nested blocks for function / method body max-nested-blocks=5 [FORMAT] # Maximum number of characters on a single line. max-line-length=85 # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )??$ # Allow the body of an if to be on the same line as the test if there is no # else. single-line-if-stmt=no # List of optional constructs for which whitespace checking is disabled. `dict- # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. # `trailing-comma` allows a space between comma and closing bracket: (a, ). # `empty-line` allows space-only lines. no-space-check=trailing-comma,dict-separator # Maximum number of lines in a module max-module-lines=1000 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' # Number of spaces of indent required inside a hanging or continued line. indent-after-paren=4 # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. expected-line-ending-format= [LOGGING] # Logging modules to check that the string format arguments are in logging # function parameter format logging-modules=logging [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. notes=FIXME,XXX,TODO [SIMILARITIES] # Minimum lines number of a similarity. min-similarity-lines=4 # Ignore comments when computing similarities. ignore-comments=yes # Ignore docstrings when computing similarities. ignore-docstrings=yes # Ignore imports when computing similarities. ignore-imports=no [SPELLING] # Spelling dictionary name. Available dictionaries: none. To make it working # install python-enchant package. spelling-dict= # List of comma separated words that should not be checked. spelling-ignore-words= # A path to a file that contains private dictionary; one word per line. spelling-private-dict-file= # Tells whether to store unknown words to indicated private dictionary in # --spelling-private-dict-file option instead of raising a message. spelling-store-unknown-words=no [TYPECHECK] # Tells whether missing members accessed in mixin class should be ignored. A # mixin class is detected if its name ends with "mixin" (case insensitive). ignore-mixin-members=yes # List of module names for which member attributes should not be checked # (useful for modules/projects where namespaces are manipulated during runtime # and thus existing member attributes cannot be deduced by static analysis. It # supports qualified module names, as well as Unix pattern matching. ignored-modules= # List of classes names for which member attributes should not be checked # (useful for classes with attributes dynamically set). This supports can work # with qualified names. ignored-classes=SQLObject # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E1101 when accessed. Python regular # expressions are accepted. generated-members=REQUEST,acl_users,aq_parent [VARIABLES] # Tells whether we should check for unused import in __init__ files. init-import=no # A regular expression matching the name of dummy variables (i.e. expectedly # not used). dummy-variables-rgx=_|dummy|kwargs # List of additional names supposed to be defined in builtins. Remember that # you should avoid to define new builtins when possible. additional-builtins= # List of strings which can identify a callback function by name. A callback # name must start or end with one of those strings. callbacks=cb_,_cb [CLASSES] # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__,__new__,setUp # List of valid names for the first argument in a class method. valid-classmethod-first-arg=cls # List of valid names for the first argument in a metaclass class method. valid-metaclass-classmethod-first-arg=mcs # List of member names, which should be excluded from the protected access # warning. exclude-protected=_asdict,_fields,_replace,_source,_make [DESIGN] # Maximum number of arguments for function / method max-args=10 # Argument names that match this expression will be ignored. Default to name # with leading underscore ignored-argument-names=_.* # Maximum number of locals for function / method body max-locals=15 # Maximum number of return / yield for function / method body max-returns=6 # Maximum number of branch for function / method body max-branches=12 # Maximum number of statements in function / method body max-statements=50 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of attributes for a class (see R0902). max-attributes=10 # Minimum number of public methods for a class (see R0903). min-public-methods=2 # Maximum number of public methods for a class (see R0904). max-public-methods=30 # Maximum number of boolean expressions in a if statement max-bool-expr=5 [IMPORTS] # Deprecated modules which should not be used, separated by a comma deprecated-modules=regsub,string,TERMIOS,Bastion,rexec # Create a graph of every (i.e. internal and external) dependencies in the # given file (report RP0402 must not be disabled) import-graph= # Create a graph of external dependencies in the given file (report RP0402 must # not be disabled) ext-import-graph= # Create a graph of internal dependencies in the given file (report RP0402 must # not be disabled) int-import-graph= [EXCEPTIONS] # Exceptions that will emit a warning when being caught. Defaults to # "Exception" overgeneral-exceptions=Exception behave-1.2.6/.travis.yml0000644000076600000240000000240113244555737015162 0ustar jensstaff00000000000000language: python sudo: false python: - "3.6" - "2.7" - "3.5" - "2.6" - "pypy" - "pypy3" - "3.7-dev" # -- DISABLED: # - "nightly" # - "3.4" # - "3.3" # # NOW SUPPORTED: "3.5" => python 3.5.2 (>= 3.5.1) # NOTE: Python 3.5.0 is broken (for some tests). # NOTE: nightly = 3.7-dev # -- TEST-BALLON: Check if Python 3.6 is actually Python 3.5.1 or newer matrix: allow_failures: - python: "3.7-dev" - python: "nightly" cache: directories: - $HOME/.cache/pip install: - travis_retry pip install -q -r py.requirements/ci.travis.txt - python setup.py -q install script: - python --version - pytest test tests - behave -f progress --junit features/ - behave -f progress --junit tools/test-features/ - behave -f progress --junit issue.features/ after_failure: - echo "FAILURE DETAILS (from XML reports):" - bin/behave.junit_filter.py --status=failed reports # -- ALTERNATIVE: # egrep -L 'errors="0"|failures="0"' reports/*.xml | xargs -t cat # -- USE: New container-based infrastructure for faster startup. # http://docs.travis-ci.com/user/workers/container-based-infrastructure/ # # SEE ALSO: # http://lint.travis-ci.org # http://docs.travis-ci.com/user/caching/ # http://docs.travis-ci.com/user/multi-os/ (Linux, MACOSX) behave-1.2.6/behave/0000755000076600000240000000000013244564037014277 5ustar jensstaff00000000000000behave-1.2.6/behave/__init__.py0000644000076600000240000000203013244562020016371 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """behave is behaviour-driven development, Python style Behavior-driven development (or BDD) is an agile software development technique that encourages collaboration between developers, QA and non-technical or business participants in a software project. *behave* uses tests written in a natural language style, backed up by Python code. To get started, we recommend the `tutorial`_ and then the `test language`_ and `api`_ references. .. _`tutorial`: tutorial.html .. _`test language`: gherkin.html .. _`api`: api.html """ from __future__ import absolute_import from behave.step_registry import * # pylint: disable=wildcard-import from behave.matchers import use_step_matcher, step_matcher, register_type from behave.fixture import fixture, use_fixture # pylint: disable=undefined-all-variable __all__ = [ "given", "when", "then", "step", "use_step_matcher", "register_type", "Given", "When", "Then", "Step", "fixture", "use_fixture", # -- DEPRECATING: "step_matcher" ] __version__ = "1.2.6" behave-1.2.6/behave/__main__.py0000644000076600000240000001426013244555737016403 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import, print_function import codecs import sys import six from behave import __version__ from behave.configuration import Configuration, ConfigError from behave.parser import ParserError from behave.runner import Runner from behave.runner_util import print_undefined_step_snippets, reset_runtime, \ InvalidFileLocationError, InvalidFilenameError, FileNotFoundError from behave.textutil import compute_words_maxsize, text as _text TAG_HELP = """ Scenarios inherit tags declared on the Feature level. The simplest TAG_EXPRESSION is simply a tag:: --tags @dev You may even leave off the "@" - behave doesn't mind. When a tag in a tag expression starts with a ~, this represents boolean NOT:: --tags ~@dev A tag expression can have several tags separated by a comma, which represents logical OR:: --tags @dev,@wip The --tags option can be specified several times, and this represents logical AND, for instance this represents the boolean expression "(@foo or not @bar) and @zap":: --tags @foo,~@bar --tags @zap. Beware that if you want to use several negative tags to exclude several tags you have to use logical AND:: --tags ~@fixme --tags ~@buggy. """.strip() # TODO # Positive tags can be given a threshold to limit the number of occurrences. # Which can be practical if you are practicing Kanban or CONWIP. This will fail # if there are more than 3 occurrences of the @qa tag: # # --tags @qa:3 # """.strip() def run_behave(config, runner_class=None): """Run behave with configuration. :param config: Configuration object for behave. :param runner_class: Runner class to use or none (use Runner class). :return: 0, if successful. Non-zero on failure. .. note:: BEST EFFORT, not intended for multi-threaded usage. """ # pylint: disable=too-many-branches, too-many-statements, too-many-return-statements if runner_class is None: runner_class = Runner if config.version: print("behave " + __version__) return 0 if config.tags_help: print(TAG_HELP) return 0 if config.lang_list: from behave.i18n import languages stdout = sys.stdout if six.PY2: # -- PYTHON2: Overcome implicit encode problems (encoding=ASCII). stdout = codecs.getwriter("UTF-8")(sys.stdout) iso_codes = languages.keys() print("Languages available:") for iso_code in sorted(iso_codes): native = languages[iso_code]["native"][0] name = languages[iso_code]["name"][0] print(u"%s: %s / %s" % (iso_code, native, name), file=stdout) return 0 if config.lang_help: from behave.i18n import languages if config.lang_help not in languages: print("%s is not a recognised language: try --lang-list" % \ config.lang_help) return 1 trans = languages[config.lang_help] print(u"Translations for %s / %s" % (trans["name"][0], trans["native"][0])) for kw in trans: if kw in "name native".split(): continue print(u"%16s: %s" % (kw.title().replace("_", " "), u", ".join(w for w in trans[kw] if w != "*"))) return 0 if not config.format: config.format = [config.default_format] elif config.format and "format" in config.defaults: # -- CASE: Formatter are specified in behave configuration file. # Check if formatter are provided on command-line, too. if len(config.format) == len(config.defaults["format"]): # -- NO FORMATTER on command-line: Add default formatter. config.format.append(config.default_format) if "help" in config.format: print_formatters("Available formatters:") return 0 if len(config.outputs) > len(config.format): print("CONFIG-ERROR: More outfiles (%d) than formatters (%d)." % \ (len(config.outputs), len(config.format))) return 1 # -- MAIN PART: failed = True try: reset_runtime() runner = runner_class(config) failed = runner.run() except ParserError as e: print(u"ParserError: %s" % e) except ConfigError as e: print(u"ConfigError: %s" % e) except FileNotFoundError as e: print(u"FileNotFoundError: %s" % e) except InvalidFileLocationError as e: print(u"InvalidFileLocationError: %s" % e) except InvalidFilenameError as e: print(u"InvalidFilenameError: %s" % e) except Exception as e: # -- DIAGNOSTICS: text = _text(e) print(u"Exception %s: %s" % (e.__class__.__name__, text)) raise if config.show_snippets and runner.undefined_steps: print_undefined_step_snippets(runner.undefined_steps, colored=config.color) return_code = 0 if failed: return_code = 1 return return_code def print_formatters(title=None, stream=None): """Prints the list of available formatters and their description. :param title: Optional title (as string). :param stream: Optional, output stream to use (default: sys.stdout). """ from behave.formatter._registry import format_items from operator import itemgetter if stream is None: stream = sys.stdout if title: stream.write(u"%s\n" % title) format_items = sorted(format_items(resolved=True), key=itemgetter(0)) format_names = [item[0] for item in format_items] column_size = compute_words_maxsize(format_names) schema = u" %-"+ _text(column_size) +"s %s\n" for name, formatter_class in format_items: formatter_description = getattr(formatter_class, "description", "") stream.write(schema % (name, formatter_description)) def main(args=None): """Main function to run behave (as program). :param args: Command-line args (or string) to use. :return: 0, if successful. Non-zero, in case of errors/failures. """ config = Configuration(args) return run_behave(config) if __name__ == "__main__": # -- EXAMPLE: main("--version") sys.exit(main()) behave-1.2.6/behave/_stepimport.py0000644000076600000240000001632513244555737017234 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ This module provides low-level helper functionality during step imports. .. warn:: Do not use directly It should not be used directly except in behave Runner classes that need to provide the correct context (step_registry, matchers, etc.) instead of using the global module specific variables. """ from __future__ import absolute_import from contextlib import contextmanager from threading import Lock from types import ModuleType import os.path import sys from behave import step_registry as _step_registry # from behave import matchers as _matchers import six # ----------------------------------------------------------------------------- # UTILITY FUNCTIONS: # ----------------------------------------------------------------------------- def setup_api_with_step_decorators(module, step_registry): _step_registry.setup_step_decorators(module, step_registry) def setup_api_with_matcher_functions(module, matcher_factory): module.use_step_matcher = matcher_factory.use_step_matcher module.step_matcher = matcher_factory.use_step_matcher module.register_type = matcher_factory.register_type # ----------------------------------------------------------------------------- # FAKE MODULE CLASSES: For step imports # ----------------------------------------------------------------------------- # class FakeModule(object): class FakeModule(ModuleType): ensure_fake = True # -- SUPPORT FOR: behave.step_registry.setup_step_decorators() def __setitem__(self, name, value): assert "." not in name setattr(self, name, value) class StepRegistryModule(FakeModule): """Provides a fake :mod:`behave.step_registry` module that can be used during step imports. """ __all__ = [ "given", "when", "then", "step", "Given", "When", "Then", "Step", ] def __init__(self, step_registry): super(StepRegistryModule, self).__init__("behave.step_registry") self.registry = step_registry setup_api_with_step_decorators(self, step_registry) class StepMatchersModule(FakeModule): __all__ = ["use_step_matcher", "register_type", "step_matcher"] def __init__(self, matcher_factory): super(StepMatchersModule, self).__init__("behave.matchers") self.matcher_factory = matcher_factory setup_api_with_matcher_functions(self, matcher_factory) self.use_default_step_matcher = matcher_factory.use_default_step_matcher self.get_matcher = matcher_factory.make_matcher # self.matcher_mapping = matcher_mapping or _matchers.matcher_mapping.copy() # self.current_matcher = current_matcher or _matchers.current_matcher # -- INJECT PYTHON PACKAGE META-DATA: # REQUIRED-FOR: Non-fake submodule imports (__path__). here = os.path.dirname(__file__) self.__file__ = os.path.abspath(os.path.join(here, "matchers.py")) self.__name__ = "behave.matchers" # self.__path__ = [os.path.abspath(here)] # def use_step_matcher(self, name): # self.matcher_factory.use_step_matcher(name) # # self.current_matcher = self.matcher_mapping[name] # # def use_default_step_matcher(self, name=None): # self.matcher_factory.use_default_step_matcher(name=None) # # def get_matcher(self, func, pattern): # # return self.current_matcher # return self.matcher_factory.make_matcher(func, pattern) # # def register_type(self, **kwargs): # # _matchers.register_type(**kwargs) # self.matcher_factory.register_type(**kwargs) # # step_matcher = use_step_matcher class BehaveModule(FakeModule): __all__ = StepRegistryModule.__all__ + StepMatchersModule.__all__ def __init__(self, step_registry, matcher_factory=None): if matcher_factory is None: matcher_factory = step_registry.step_matcher_factory assert matcher_factory is not None super(BehaveModule, self).__init__("behave") setup_api_with_step_decorators(self, step_registry) setup_api_with_matcher_functions(self, matcher_factory) self.use_default_step_matcher = matcher_factory.use_default_step_matcher assert step_registry.matcher_factory == matcher_factory # -- INJECT PYTHON PACKAGE META-DATA: # REQUIRED-FOR: Non-fake submodule imports (__path__). here = os.path.dirname(__file__) self.__file__ = os.path.abspath(os.path.join(here, "__init__.py")) self.__name__ = "behave" self.__path__ = [os.path.abspath(here)] self.__package__ = None class StepImportModuleContext(object): def __init__(self, step_container): self.step_registry = step_container.step_registry self.matcher_factory = step_container.matcher_factory assert self.step_registry.matcher_factory == self.matcher_factory self.step_registry.matcher_factory = self.matcher_factory step_registry_module = StepRegistryModule(self.step_registry) step_matchers_module = StepMatchersModule(self.matcher_factory) behave_module = BehaveModule(self.step_registry, self.matcher_factory) self.modules = { "behave": behave_module, "behave.matchers": step_matchers_module, "behave.step_registry": step_registry_module, } # self.default_matcher = self.step_matchers_module.current_matcher def reset_current_matcher(self): self.matcher_factory.use_default_step_matcher() _step_import_lock = Lock() unknown = object() @contextmanager def use_step_import_modules(step_container): """Redirect any step/type registration to the runner's step-context object during step imports by using fake modules (instead of using module-globals). This allows that multiple runners can be used without polluting the global variables in problematic modules (:mod:`behave.step_registry`, mod:`behave.matchers`). .. sourcecode:: python # -- RUNNER-IMPLEMENTATION: def load_step_definitions(self, ...): step_container = self.step_container with use_step_import_modules(step_container) as import_context: # -- USE: Fake modules during step imports ... import_context.reset_current_matcher() :param step_container: Step context object with step_registry, matcher_factory. """ orig_modules = {} import_context = StepImportModuleContext(step_container) with _step_import_lock: # -- CRITICAL-SECTION (multi-threading protected) try: # -- SCOPE-GUARD SETUP: Replace original modules with fake ones. for module_name, fake_module in six.iteritems(import_context.modules): orig_module = sys.modules.get(module_name, unknown) orig_modules[module_name] = orig_module sys.modules[module_name] = fake_module # -- USE: Fake modules for step imports. yield import_context finally: # -- SCOPE-GUARD CLEANUP: Restore original modules. for module_name, orig_module in six.iteritems(orig_modules): if orig_module is unknown: del sys.modules[module_name] else: sys.modules[module_name] = orig_module behave-1.2.6/behave/_types.py0000644000076600000240000001114613244555737016166 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """Basic types (helper classes).""" import sys import six if six.PY2: # -- USE PYTHON2 BACKPORT: With unicode support import traceback2 as traceback else: import traceback class Unknown(object): """Placeholder for unknown/missing information, distinguishable from None. .. code-block:: python data = {} value = data.get("name", Unknown) if value is Unknown: # -- DO SOMETHING ... """ class ExceptionUtil(object): """Provides a utility class for accessing/modifying exception information. .. seealso:: PEP-3134 Chained excpetions """ # pylint: disable=no-init @staticmethod def get_traceback(exception): # -- ASSUMPTION: assert isinstance(exception, Exception) return getattr(exception, "__traceback__", None) @staticmethod def set_traceback(exception, exc_traceback=Unknown): assert isinstance(exception, Exception) if exc_traceback is Unknown: exc_traceback = sys.exc_info()[2] exception.__traceback__ = exc_traceback @classmethod def has_traceback(cls, exception): """Indicates if traceback information related to this exception is stored with the exception object. :param exception: Exception object to check. :return: True, if traceback info is stored. False, otherwise. """ return cls.get_traceback(exception) is not None @classmethod def describe(cls, exception, use_traceback=False, prefix=""): # -- NORMAL CASE: text = u"{prefix}{0}: {1}\n".format(exception.__class__.__name__, exception, prefix=prefix) if use_traceback: exc_traceback = cls.get_traceback(exception) if exc_traceback: # -- NOTE: Chained-exception cause (see: PEP-3134). text += u"".join(traceback.format_tb(exc_traceback)) return text class ChainedExceptionUtil(ExceptionUtil): """Provides a utility class for accessing/modifying exception information related to chained exceptions. .. seealso:: PEP-3134 Chained excpetions """ # pylint: disable=no-init @staticmethod def get_cause(exception): # -- ASSUMPTION: assert isinstance(exception, Exception) return getattr(exception, "__cause__", None) @staticmethod def set_cause(exception, exc_cause): assert isinstance(exception, Exception) assert isinstance(exc_cause, Exception) or exc_cause is None exception.__cause__ = exc_cause if exc_cause and not hasattr(exc_cause, "__traceback__"): # -- NEEDED-FOR: Python2 # Otherwise, traceback formatting tries to access missing attribute. exc_cause.__traceback__ = None # pylint: disable=arguments-differ @classmethod def describe(cls, exception, use_traceback=False, prefix="", style="reversed"): """Describes an exception, optionally together with its traceback info. Also shows information about exception cause (chained exceptions), if exists. :param exception: Exception object to describe. :param use_traceback: Indicates if traceback info should be shown. :param prefix: Optional prefix for description text. :param style: Optional style indicator ("reversed", "normal") :return: Exception description as text. """ text = ExceptionUtil.describe(exception, use_traceback, prefix) # -- STEP: Collect chained exceptions. causes = [] exc_cause = cls.get_cause(exception) while exc_cause: causes.append(exc_cause) exc_cause = cls.get_cause(exc_cause) # -- STEP: Describe causes for chained exceptions. parts = [] if style == "normal": prefix = "CAUSE: " for exc_cause in reversed(causes): cause_text = ExceptionUtil.describe(exc_cause, use_traceback, prefix) parts.append(cause_text) if len(parts) == 1: prefix = "CAUSES: " parts.append(text) else: parts.append(text) for exc_cause in causes: cause_text = ExceptionUtil.describe(exc_cause, use_traceback, prefix="CAUSED-BY: ") parts.append(cause_text) return u"\n".join(parts) # if exc_cause: # cause_text = # text += u"\n" + cause_text # return text behave-1.2.6/behave/api/0000755000076600000240000000000013244564037015050 5ustar jensstaff00000000000000behave-1.2.6/behave/api/__init__.py0000644000076600000240000000022313244555737017165 0ustar jensstaff00000000000000""" Provides/defines stable APIs for behave users: * step writer(s): features/steps/*.py * environment writers: features/environment.py * ... """ behave-1.2.6/behave/api/async_step.py0000644000076600000240000002523713244555737017612 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- # pylint: disable=line-too-long """ This module provides functionality to support "async steps" (coroutines) in a step-module with behave. This functionality simplifies to test frameworks and protocols that make use of `asyncio.coroutines`_ or provide `asyncio.coroutines`_. EXAMPLE: .. code-block:: python # -- FILE: features/steps/my_async_steps.py # EXAMPLE REQUIRES: Python >= 3.5 from behave import step from behave.api.async_step import async_run_until_complete @step('an async coroutine step waits {duration:f} seconds') @async_run_until_complete async def step_async_step_waits_seconds(context, duration): await asyncio.sleep(duration) .. code-block:: python # -- FILE: features/steps/my_async_steps2.py # EXAMPLE REQUIRES: Python >= 3.4 from behave import step from behave.api.async_step import async_run_until_complete import asyncio @step('a tagged-coroutine async step waits {duration:f} seconds') @async_run_until_complete @asyncio.coroutine def step_async_step_waits_seconds2(context, duration): yield from asyncio.sleep(duration) .. requires:: Python 3.5 (or 3.4) or :mod:`asyncio` backport (like :pypi:`trollius`) .. seealso:: https://docs.python.org/3/library/asyncio.html .. _asyncio.coroutines: https://docs.python.org/3/library/asyncio-task.html#coroutines """ # pylint: enable=line-too-long from __future__ import print_function # -- REQUIRES: Python >= 3.4 # MAYBE BACKPORT: trollius import functools from six import string_types try: import asyncio has_asyncio = True except ImportError: has_asyncio = False # ----------------------------------------------------------------------------- # ASYNC STEP DECORATORS: # ----------------------------------------------------------------------------- def async_run_until_complete(astep_func=None, loop=None, timeout=None, async_context=None, should_close=False): """Provides a function decorator for async-steps (coroutines). Provides an async event loop and runs the async-step until completion (or timeout, if specified). .. code-block:: python from behave import step from behave.api.async_step import async_run_until_complete import asyncio @step("an async step is executed") @async_run_until_complete async def astep_impl(context) await asycio.sleep(0.1) @step("an async step is executed") @async_run_until_complete(timeout=1.2) async def astep_impl2(context) # -- NOTE: Wrapped event loop waits with timeout=1.2 seconds. await asycio.sleep(0.3) Parameters: astep_func: Async step function (coroutine) loop (asyncio.EventLoop): Event loop to use or None. timeout (int, float): Timeout to wait for async-step completion. async_context (name): Async_context name or object to use. should_close (bool): Indicates if event lopp should be closed. .. note:: * If :param:`loop` is None, the default event loop will be used or a new event loop is created. * If :param:`timeout` is provided, the event loop waits only the specified time. * :param:`async_context` is only used, if :param:`loop` is None. * If :param:`async_context` is a name, it will be used to retrieve the real async_context object from the context. """ @functools.wraps(astep_func) def step_decorator(astep_func, context, *args, **kwargs): loop = kwargs.pop("_loop", None) timeout = kwargs.pop("_timeout", None) async_context = kwargs.pop("_async_context", None) should_close = kwargs.pop("_should_close", None) if isinstance(loop, string_types): loop = getattr(context, loop, None) elif async_context: if isinstance(async_context, string_types): name = async_context async_context = use_or_create_async_context(context, name) loop = async_context.loop else: assert isinstance(async_context, AsyncContext) loop = async_context.loop if loop is None: loop = asyncio.get_event_loop() or asyncio.new_event_loop() # -- WORKHORSE: try: if timeout is None: loop.run_until_complete(astep_func(context, *args, **kwargs)) else: # MAYBE: loop = asyncio.new_event_loop() # MAYBE: should_close = True task = loop.create_task(astep_func(context, *args, **kwargs)) done, pending = loop.run_until_complete( asyncio.wait([task], timeout=timeout)) assert not pending, "TIMEOUT-OCCURED: timeout=%s" % timeout finally: if loop and should_close: # -- MAYBE-AVOID: loop.close() if astep_func is None: # -- CASE: @decorator(timeout=1.2, ...) # MAYBE: return functools.partial(step_decorator, def wrapped_decorator1(astep_func): @functools.wraps(astep_func) def wrapped_decorator2(context, *args, **kwargs): return step_decorator(astep_func, context, *args, _loop=loop, _timeout=timeout, _async_context=async_context, _should_close=should_close, **kwargs) assert callable(astep_func) return wrapped_decorator2 return wrapped_decorator1 else: # -- CASE: @decorator ... or astep = decorator(astep) # MAYBE: return functools.partial(step_decorator, astep_func=astep_func) assert callable(astep_func) @functools.wraps(astep_func) def wrapped_decorator(context, *args, **kwargs): return step_decorator(astep_func, context, *args, **kwargs) return wrapped_decorator # -- ALIAS: run_until_complete = async_run_until_complete # ----------------------------------------------------------------------------- # ASYNC STEP UTILITY CLASSES: # ----------------------------------------------------------------------------- class AsyncContext(object): # pylint: disable=line-too-long """Provides a context object for "async steps" to keep track: * which event loop is used * which (asyncio) tasks are used or of interest .. attribute:: loop Event loop object to use. If none is provided, the current event-loop is used (or a new one is created). .. attribute:: tasks List of started :mod:`asyncio` tasks (of interest). .. attribute:: name Optional name of this object (in the behave context). If none is provided, :attr:`AsyncContext.default_name` is used. .. attribute:: should_close Indicates if the :attr:`loop` (event-loop) should be closed or not. EXAMPLE: .. code-block:: python # -- FILE: features/steps/my_async_steps.py # REQUIRES: Python 3.5 from behave import given, when, then, step from behave.api.async_step import AsyncContext @when('I dispatch an async-call with param "{param}"') def step_impl(context, param): async_context = getattr(context, "async_context", None) if async_context is None: async_context = context.async_context = AsyncContext() task = async_context.loop.create_task(my_async_func(param)) async_context.tasks.append(task) @then('I wait at most {duration:f} seconds until all async-calls are completed') def step_impl(context, duration): async_context = context.async_context assert async_context.tasks done, pending = async_context.loop.run_until_complete(asyncio.wait( async_context.tasks, loop=async_context.loop, timeout=duration)) assert len(pending) == 0 # -- COROUTINE: async def my_async_func(param): await asyncio.sleep(0.5) return param.upper() """ # pylint: enable=line-too-long default_name = "async_context" def __init__(self, loop=None, name=None, should_close=False, tasks=None): self.loop = loop or asyncio.get_event_loop() or asyncio.new_event_loop() self.tasks = tasks or [] self.name = name or self.default_name self.should_close = should_close def __del__(self): if self.loop and self.should_close: self.close() def close(self): if self.loop and not self.loop.is_closed(): self.loop.close() self.loop = None # ----------------------------------------------------------------------------- # ASYNC STEP UTILITY FUNCTIONS: # ----------------------------------------------------------------------------- def use_or_create_async_context(context, name=None, loop=None, **kwargs): """Utility function to be used in step implementations to ensure that an :class:`AsyncContext` object is stored in the :param:`context` object. If no such attribute exists (under the given name), a new :class:`AsyncContext` object is created with the provided args. Otherwise, the existing context attribute is used. EXAMPLE: .. code-block:: python # -- FILE: features/steps/my_async_steps.py # EXAMPLE REQUIRES: Python 3.5 from behave import when from behave.api.async_step import use_or_create_async_context @when('I dispatch an async-call with param "{param}"') def step_impl(context, param): async_context = use_or_create_async_context(context, "async_context") task = async_context.loop.create_task(my_async_func(param)) async_context.tasks.append(task) # -- COROUTINE: async def my_async_func(param): await asyncio.sleep(0.5) return param.upper() :param context: Behave context object to use. :param name: Optional name of async-context object (as string or None). :param loop: Optional event_loop object to use for create call. :param kwargs: Optional :class:`AsyncContext` params for create call. :return: :class:`AsyncContext` object from the param:`context`. """ if name is None: name = AsyncContext.default_name async_context = getattr(context, name, None) if async_context is None: async_context = AsyncContext(loop=loop, name=name, **kwargs) setattr(context, async_context.name, async_context) assert isinstance(async_context, AsyncContext) assert getattr(context, async_context.name) is async_context return async_context behave-1.2.6/behave/capture.py0000644000076600000240000001724613244555737016335 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Capture output (stdout, stderr), logs, etc. """ from __future__ import absolute_import from contextlib import contextmanager import sys from six import StringIO, PY2 from behave.log_capture import LoggingCapture from behave.textutil import text as _text def add_text_to(value, more_text, separator="\n"): if more_text: if value: if separator and not value.endswith(separator): value += separator value += more_text else: value = more_text return value class Captured(object): """Stores and aggregates captured output data.""" empty = u"" linesep = u"\n" def __init__(self, stdout=None, stderr=None, log_output=None): self.stdout = stdout or self.empty self.stderr = stderr or self.empty self.log_output = log_output or self.empty def reset(self): self.stdout = self.empty self.stderr = self.empty self.log_output = self.empty # -- PYTHON2: if PY2: def __nonzero__(self): return bool(self.stdout or self.stderr or self.log_output) else: def __bool__(self): return bool(self.stdout or self.stderr or self.log_output) @property def output(self): """Makes a simple report of the captured data by concatenating all parts. """ output_text = self.stdout output_text = add_text_to(output_text, self.stderr) output_text = add_text_to(output_text, self.log_output) return output_text def add(self, captured): """Adds/appends captured output data to this object. :param captured: Captured object whose data should be added. :return: self, to allow daisy-chaining (if needed). """ assert isinstance(captured, Captured) self.stdout = add_text_to(self.stdout, captured.stdout, self.linesep) self.stderr = add_text_to(self.stderr, captured.stderr, self.linesep) self.log_output = add_text_to(self.log_output, captured.log_output, self.linesep) return self def make_report(self): """Makes a detailled report of the captured output data. :returns: Report as string. """ report_parts = [] if self.stdout: parts = ["Captured stdout:", _text(self.stdout).rstrip(), ""] report_parts.extend(parts) if self.stderr: parts = ["Captured stderr:", _text(self.stderr).rstrip(), ""] report_parts.extend(parts) if self.log_output: parts = ["Captured logging:", _text(self.log_output)] report_parts.extend(parts) return self.linesep.join(report_parts).strip() def __add__(self, other): """Supports incremental add:: captured1 = Captured("Hello") captured2 = Captured("World") captured3 = captured1 + captured2 assert captured3.stdout == "Hello\nWorld" """ new_data = Captured(self.stdout, self.stderr, self.log_output) return new_data.add(other) def __iadd__(self, other): """Supports incremental add:: captured1 = Captured("Hello") captured2 = Captured("World") captured1 += captured2 assert captured1.stdout == "Hello\nWorld" """ return self.add(other) class CaptureController(object): """Simplifies the lifecycle to capture output from various sources.""" def __init__(self, config): self.config = config self.stdout_capture = None self.stderr_capture = None self.log_capture = None self.old_stdout = None self.old_stderr = None @property def captured(self): """Provides access of the captured output data. :return: Object that stores the captured output parts (as Captured). """ stdout = None stderr = None log_out = None if self.config.stdout_capture and self.stdout_capture: stdout = _text(self.stdout_capture.getvalue()) if self.config.stderr_capture and self.stderr_capture: stderr = _text(self.stderr_capture.getvalue()) if self.config.log_capture and self.log_capture: log_out = _text(self.log_capture.getvalue()) return Captured(stdout, stderr, log_out) def setup_capture(self, context): assert context is not None if self.config.stdout_capture: self.stdout_capture = StringIO() context.stdout_capture = self.stdout_capture if self.config.stderr_capture: self.stderr_capture = StringIO() context.stderr_capture = self.stderr_capture if self.config.log_capture: self.log_capture = LoggingCapture(self.config) self.log_capture.inveigle() context.log_capture = self.log_capture def start_capture(self): if self.config.stdout_capture: # -- REPLACE ONLY: In non-capturing mode. if not self.old_stdout: self.old_stdout = sys.stdout sys.stdout = self.stdout_capture assert sys.stdout is self.stdout_capture if self.config.stderr_capture: # -- REPLACE ONLY: In non-capturing mode. if not self.old_stderr: self.old_stderr = sys.stderr sys.stderr = self.stderr_capture assert sys.stderr is self.stderr_capture def stop_capture(self): if self.config.stdout_capture: # -- RESTORE ONLY: In capturing mode. if self.old_stdout: sys.stdout = self.old_stdout self.old_stdout = None assert sys.stdout is not self.stdout_capture if self.config.stderr_capture: # -- RESTORE ONLY: In capturing mode. if self.old_stderr: sys.stderr = self.old_stderr self.old_stderr = None assert sys.stderr is not self.stderr_capture def teardown_capture(self): if self.config.log_capture: self.log_capture.abandon() def make_capture_report(self): """Combine collected output and return as string.""" return self.captured.make_report() # report = u"" # if self.config.stdout_capture and self.stdout_capture: # output = self.stdout_capture.getvalue() # if output: # output = _text(output) # report += u"\nCaptured stdout:\n" + output # if self.config.stderr_capture and self.stderr_capture: # output = self.stderr_capture.getvalue() # if output: # output = _text(output) # report += u"\nCaptured stderr:\n" + output # if self.config.log_capture and self.log_capture: # output = self.log_capture.getvalue() # if output: # output = _text(output) # report += u"\nCaptured logging:\n" + output # return report # ----------------------------------------------------------------------------- # UTILITY FUNCTIONS: # ----------------------------------------------------------------------------- @contextmanager def capture_output(controller, enabled=True): """Provides a context manager that starts capturing output .. code-block:: with capture_output(capture_controller): ... # Do something """ if enabled: try: controller.start_capture() yield finally: controller.stop_capture() else: # -- CAPTURING OUTPUT is disabled. # Needed to prevent recursive captures with context.execute_steps() yield behave-1.2.6/behave/compat/0000755000076600000240000000000013244564037015562 5ustar jensstaff00000000000000behave-1.2.6/behave/compat/__init__.py0000644000076600000240000000017613244555737017706 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Used for behave as compatibility layer between different Python versions and implementations. """ behave-1.2.6/behave/compat/collections.py0000644000076600000240000000120713244555737020461 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Compatibility of :module:`collections` between different Python versions. """ from __future__ import absolute_import import warnings # pylint: disable=unused-import try: # -- SINCE: Python2.7 from collections import OrderedDict except ImportError: # pragma: no cover try: # -- BACK-PORTED FOR: Python 2.4 .. 2.6 from ordereddict import OrderedDict except ImportError: message = "collections.OrderedDict is missing: Install 'ordereddict'." warnings.warn(message) # -- BACKWARD-COMPATIBLE: Better than nothing (for behave use case). OrderedDict = dict behave-1.2.6/behave/configuration.py0000644000076600000240000007424413244555737017542 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- from __future__ import print_function import argparse import logging import os import re import sys import shlex import six from six.moves import configparser from behave.model import ScenarioOutline from behave.model_core import FileLocation from behave.reporter.junit import JUnitReporter from behave.reporter.summary import SummaryReporter from behave.tag_expression import TagExpression from behave.formatter.base import StreamOpener from behave.formatter import _registry as _format_registry from behave.userdata import UserData, parse_user_define from behave._types import Unknown from behave.textutil import select_best_encoding, to_texts # -- PYTHON 2/3 COMPATIBILITY: # SINCE Python 3.2: ConfigParser = SafeConfigParser ConfigParser = configparser.ConfigParser if six.PY2: ConfigParser = configparser.SafeConfigParser # ----------------------------------------------------------------------------- # CONFIGURATION DATA TYPES: # ----------------------------------------------------------------------------- class LogLevel(object): names = [ "NOTSET", "CRITICAL", "FATAL", "ERROR", "WARNING", "WARN", "INFO", "DEBUG", ] @staticmethod def parse(levelname, unknown_level=None): """ Convert levelname into a numeric log level. :param levelname: Logging levelname (as string) :param unknown_level: Used if levelname is unknown (optional). :return: Numeric log-level or unknown_level, if levelname is unknown. """ return getattr(logging, levelname.upper(), unknown_level) @classmethod def parse_type(cls, levelname): level = cls.parse(levelname, Unknown) if level is Unknown: message = "%s is unknown, use: %s" % \ (levelname, ", ".join(cls.names[1:])) raise argparse.ArgumentTypeError(message) return level @staticmethod def to_string(level): return logging.getLevelName(level) class ConfigError(Exception): pass # ----------------------------------------------------------------------------- # CONFIGURATION SCHEMA: # ----------------------------------------------------------------------------- options = [ (("-c", "--no-color"), dict(action="store_false", dest="color", help="Disable the use of ANSI color escapes.")), (("--color",), dict(action="store_true", dest="color", help="""Use ANSI color escapes. This is the default behaviour. This switch is used to override a configuration file setting.""")), (("-d", "--dry-run"), dict(action="store_true", help="Invokes formatters without executing the steps.")), (("-D", "--define"), dict(dest="userdata_defines", type=parse_user_define, action="append", metavar="NAME=VALUE", help="""Define user-specific data for the config.userdata dictionary. Example: -D foo=bar to store it in config.userdata["foo"].""")), (("-e", "--exclude"), dict(metavar="PATTERN", dest="exclude_re", help="""Don't run feature files matching regular expression PATTERN.""")), (("-i", "--include"), dict(metavar="PATTERN", dest="include_re", help="Only run feature files matching regular expression PATTERN.")), (("--no-junit",), dict(action="store_false", dest="junit", help="Don't output JUnit-compatible reports.")), (("--junit",), dict(action="store_true", help="""Output JUnit-compatible reports. When junit is enabled, all stdout and stderr will be redirected and dumped to the junit report, regardless of the "--capture" and "--no-capture" options. """)), (("--junit-directory",), dict(metavar="PATH", dest="junit_directory", default="reports", help="""Directory in which to store JUnit reports.""")), ((), # -- CONFIGFILE only dict(dest="default_format", help="Specify default formatter (default: pretty).")), (("-f", "--format"), dict(action="append", help="""Specify a formatter. If none is specified the default formatter is used. Pass "--format help" to get a list of available formatters.""")), (("--steps-catalog",), dict(action="store_true", dest="steps_catalog", help="""Show a catalog of all available step definitions. SAME AS: --format=steps.catalog --dry-run --no-summary -q""")), ((), # -- CONFIGFILE only dict(dest="scenario_outline_annotation_schema", help="""Specify name annotation schema for scenario outline (default="{name} -- @{row.id} {examples.name}").""")), (("-k", "--no-skipped"), dict(action="store_false", dest="show_skipped", help="Don't print skipped steps (due to tags).")), (("--show-skipped",), dict(action="store_true", help="""Print skipped steps. This is the default behaviour. This switch is used to override a configuration file setting.""")), (("--no-snippets",), dict(action="store_false", dest="show_snippets", help="Don't print snippets for unimplemented steps.")), (("--snippets",), dict(action="store_true", dest="show_snippets", help="""Print snippets for unimplemented steps. This is the default behaviour. This switch is used to override a configuration file setting.""")), (("-m", "--no-multiline"), dict(action="store_false", dest="show_multiline", help="""Don't print multiline strings and tables under steps.""")), (("--multiline", ), dict(action="store_true", dest="show_multiline", help="""Print multiline strings and tables under steps. This is the default behaviour. This switch is used to override a configuration file setting.""")), (("-n", "--name"), dict(action="append", help="""Only execute the feature elements which match part of the given name. If this option is given more than once, it will match against all the given names.""")), (("--no-capture",), dict(action="store_false", dest="stdout_capture", help="""Don't capture stdout (any stdout output will be printed immediately.)""")), (("--capture",), dict(action="store_true", dest="stdout_capture", help="""Capture stdout (any stdout output will be printed if there is a failure.) This is the default behaviour. This switch is used to override a configuration file setting.""")), (("--no-capture-stderr",), dict(action="store_false", dest="stderr_capture", help="""Don't capture stderr (any stderr output will be printed immediately.)""")), (("--capture-stderr",), dict(action="store_true", dest="stderr_capture", help="""Capture stderr (any stderr output will be printed if there is a failure.) This is the default behaviour. This switch is used to override a configuration file setting.""")), (("--no-logcapture",), dict(action="store_false", dest="log_capture", help="""Don't capture logging. Logging configuration will be left intact.""")), (("--logcapture",), dict(action="store_true", dest="log_capture", help="""Capture logging. All logging during a step will be captured and displayed in the event of a failure. This is the default behaviour. This switch is used to override a configuration file setting.""")), (("--logging-level",), dict(type=LogLevel.parse_type, help="""Specify a level to capture logging at. The default is INFO - capturing everything.""")), (("--logging-format",), dict(help="""Specify custom format to print statements. Uses the same format as used by standard logging handlers. The default is "%%(levelname)s:%%(name)s:%%(message)s".""")), (("--logging-datefmt",), dict(help="""Specify custom date/time format to print statements. Uses the same format as used by standard logging handlers.""")), (("--logging-filter",), dict(help="""Specify which statements to filter in/out. By default, everything is captured. If the output is too verbose, use this option to filter out needless output. Example: --logging-filter=foo will capture statements issued ONLY to foo or foo.what.ever.sub but not foobar or other logger. Specify multiple loggers with comma: filter=foo,bar,baz. If any logger name is prefixed with a minus, eg filter=-foo, it will be excluded rather than included.""", config_help="""Specify which statements to filter in/out. By default, everything is captured. If the output is too verbose, use this option to filter out needless output. Example: ``logging_filter = foo`` will capture statements issued ONLY to "foo" or "foo.what.ever.sub" but not "foobar" or other logger. Specify multiple loggers with comma: ``logging_filter = foo,bar,baz``. If any logger name is prefixed with a minus, eg ``logging_filter = -foo``, it will be excluded rather than included.""")), (("--logging-clear-handlers",), dict(action="store_true", help="Clear all other logging handlers.")), (("--no-summary",), dict(action="store_false", dest="summary", help="""Don't display the summary at the end of the run.""")), (("--summary",), dict(action="store_true", dest="summary", help="""Display the summary at the end of the run.""")), (("-o", "--outfile"), dict(action="append", dest="outfiles", metavar="FILE", help="Write to specified file instead of stdout.")), ((), # -- CONFIGFILE only dict(action="append", dest="paths", help="Specify default feature paths, used when none are provided.")), (("-q", "--quiet"), dict(action="store_true", help="Alias for --no-snippets --no-source.")), (("-s", "--no-source"), dict(action="store_false", dest="show_source", help="""Don't print the file and line of the step definition with the steps.""")), (("--show-source",), dict(action="store_true", dest="show_source", help="""Print the file and line of the step definition with the steps. This is the default behaviour. This switch is used to override a configuration file setting.""")), (("--stage",), dict(help="""Defines the current test stage. The test stage name is used as name prefix for the environment file and the steps directory (instead of default path names). """)), (("--stop",), dict(action="store_true", help="Stop running tests at the first failure.")), # -- DISABLE-UNUSED-OPTION: Not used anywhere. # (("-S", "--strict"), # dict(action="store_true", # help="Fail if there are any undefined or pending steps.")), ((), # -- CONFIGFILE only dict(dest="default_tags", metavar="TAG_EXPRESSION", help="""Define default tags when non are provided. See --tags for more information.""")), (("-t", "--tags"), dict(action="append", metavar="TAG_EXPRESSION", help="""Only execute features or scenarios with tags matching TAG_EXPRESSION. Pass "--tags-help" for more information.""", config_help="""Only execute certain features or scenarios based on the tag expression given. See below for how to code tag expressions in configuration files.""")), (("-T", "--no-timings"), dict(action="store_false", dest="show_timings", help="""Don't print the time taken for each step.""")), (("--show-timings",), dict(action="store_true", dest="show_timings", help="""Print the time taken, in seconds, of each step after the step has completed. This is the default behaviour. This switch is used to override a configuration file setting.""")), (("-v", "--verbose"), dict(action="store_true", help="Show the files and features loaded.")), (("-w", "--wip"), dict(action="store_true", help="""Only run scenarios tagged with "wip". Additionally: use the "plain" formatter, do not capture stdout or logging output and stop at the first failure.""")), (("-x", "--expand"), dict(action="store_true", help="Expand scenario outline tables in output.")), (("--lang",), dict(metavar="LANG", help="Use keywords for a language other than English.")), (("--lang-list",), dict(action="store_true", help="List the languages available for --lang.")), (("--lang-help",), dict(metavar="LANG", help="List the translations accepted for one language.")), (("--tags-help",), dict(action="store_true", help="Show help for tag expressions.")), (("--version",), dict(action="store_true", help="Show version.")), ] # -- OPTIONS: With raw value access semantics in configuration file. raw_value_options = frozenset([ "logging_format", "logging_datefmt", # -- MAYBE: "scenario_outline_annotation_schema", ]) def read_configuration(path): # pylint: disable=too-many-locals, too-many-branches config = ConfigParser() config.optionxform = str # -- SUPPORT: case-sensitive keys config.read(path) config_dir = os.path.dirname(path) result = {} for fixed, keywords in options: if "dest" in keywords: dest = keywords["dest"] else: for opt in fixed: if opt.startswith("--"): dest = opt[2:].replace("-", "_") else: assert len(opt) == 2 dest = opt[1:] if dest in "tags_help lang_list lang_help version".split(): continue if not config.has_option("behave", dest): continue action = keywords.get("action", "store") if action == "store": use_raw_value = dest in raw_value_options result[dest] = config.get("behave", dest, raw=use_raw_value) elif action in ("store_true", "store_false"): result[dest] = config.getboolean("behave", dest) elif action == "append": if dest == "userdata_defines": continue # -- SKIP-CONFIGFILE: Command-line only option. result[dest] = \ [s.strip() for s in config.get("behave", dest).splitlines()] else: raise ValueError('action "%s" not implemented' % action) # -- STEP: format/outfiles coupling if "format" in result: # -- OPTIONS: format/outfiles are coupled in configuration file. formatters = result["format"] formatter_size = len(formatters) outfiles = result.get("outfiles", []) outfiles_size = len(outfiles) if outfiles_size < formatter_size: for formatter_name in formatters[outfiles_size:]: outfile = "%s.output" % formatter_name outfiles.append(outfile) result["outfiles"] = outfiles elif len(outfiles) > formatter_size: print("CONFIG-ERROR: Too many outfiles (%d) provided." % outfiles_size) result["outfiles"] = outfiles[:formatter_size] for paths_name in ("paths", "outfiles"): if paths_name in result: # -- Evaluate relative paths relative to location. # NOTE: Absolute paths are preserved by os.path.join(). paths = result[paths_name] result[paths_name] = \ [os.path.normpath(os.path.join(config_dir, p)) for p in paths] # -- STEP: Special additional configuration sections. # SCHEMA: config_section: data_name special_config_section_map = { "behave.formatters": "more_formatters", "behave.userdata": "userdata", } for section_name, data_name in special_config_section_map.items(): result[data_name] = {} if config.has_section(section_name): result[data_name].update(config.items(section_name)) return result def config_filenames(): paths = ["./", os.path.expanduser("~")] if sys.platform in ("cygwin", "win32") and "APPDATA" in os.environ: paths.append(os.path.join(os.environ["APPDATA"])) for path in reversed(paths): for filename in reversed( ("behave.ini", ".behaverc", "setup.cfg", "tox.ini")): filename = os.path.join(path, filename) if os.path.isfile(filename): yield filename def load_configuration(defaults, verbose=False): for filename in config_filenames(): if verbose: print('Loading config defaults from "%s"' % filename) defaults.update(read_configuration(filename)) if verbose: print("Using defaults:") for k, v in six.iteritems(defaults): print("%15s %s" % (k, v)) def setup_parser(): # construct the parser # usage = "%(prog)s [options] [ [FILE|DIR|URL][:LINE[:LINE]*] ]+" usage = "%(prog)s [options] [ [DIR|FILE|FILE:LINE] ]+" description = """\ Run a number of feature tests with behave.""" more = """ EXAMPLES: behave features/ behave features/one.feature features/two.feature behave features/one.feature:10 behave @features.txt """ parser = argparse.ArgumentParser(usage=usage, description=description) for fixed, keywords in options: if not fixed: continue # -- CONFIGFILE only. if "config_help" in keywords: keywords = dict(keywords) del keywords["config_help"] parser.add_argument(*fixed, **keywords) parser.add_argument("paths", nargs="*", help="Feature directory, file or file location (FILE:LINE).") return parser class Configuration(object): """Configuration object for behave and behave runners.""" # pylint: disable=too-many-instance-attributes defaults = dict( color=sys.platform != "win32", show_snippets=True, show_skipped=True, dry_run=False, show_source=True, show_timings=True, stdout_capture=True, stderr_capture=True, log_capture=True, logging_format="%(levelname)s:%(name)s:%(message)s", logging_level=logging.INFO, steps_catalog=False, summary=True, junit=False, stage=None, userdata={}, # -- SPECIAL: default_format="pretty", # -- Used when no formatters are configured. default_tags="", # -- Used when no tags are defined. scenario_outline_annotation_schema=u"{name} -- @{row.id} {examples.name}" ) cmdline_only_options = set("userdata_defines") def __init__(self, command_args=None, load_config=True, verbose=None, **kwargs): """ Constructs a behave configuration object. * loads the configuration defaults (if needed). * process the command-line args * store the configuration results :param command_args: Provide command args (as sys.argv). If command_args is None, sys.argv[1:] is used. :type command_args: list, str :param load_config: Indicate if configfile should be loaded (=true) :param verbose: Indicate if diagnostic output is enabled :param kwargs: Used to hand-over/overwrite default values. """ # pylint: disable=too-many-branches, too-many-statements if command_args is None: command_args = sys.argv[1:] elif isinstance(command_args, six.string_types): encoding = select_best_encoding() or "utf-8" if six.PY2 and isinstance(command_args, six.text_type): command_args = command_args.encode(encoding) elif six.PY3 and isinstance(command_args, six.binary_type): command_args = command_args.decode(encoding) command_args = shlex.split(command_args) elif isinstance(command_args, (list, tuple)): command_args = to_texts(command_args) if verbose is None: # -- AUTO-DISCOVER: Verbose mode from command-line args. verbose = ("-v" in command_args) or ("--verbose" in command_args) self.version = None self.tags_help = None self.lang_list = None self.lang_help = None self.default_tags = None self.junit = None self.logging_format = None self.logging_datefmt = None self.name = None self.scope = None self.steps_catalog = None self.userdata = None self.wip = None defaults = self.defaults.copy() for name, value in six.iteritems(kwargs): defaults[name] = value self.defaults = defaults self.formatters = [] self.reporters = [] self.name_re = None self.outputs = [] self.include_re = None self.exclude_re = None self.scenario_outline_annotation_schema = None # pylint: disable=invalid-name self.steps_dir = "steps" self.environment_file = "environment.py" self.userdata_defines = None self.more_formatters = None if load_config: load_configuration(self.defaults, verbose=verbose) parser = setup_parser() parser.set_defaults(**self.defaults) args = parser.parse_args(command_args) for key, value in six.iteritems(args.__dict__): if key.startswith("_") and key not in self.cmdline_only_options: continue setattr(self, key, value) self.paths = [os.path.normpath(path) for path in self.paths] self.setup_outputs(args.outfiles) if self.steps_catalog: # -- SHOW STEP-CATALOG: As step summary. self.default_format = "steps.catalog" self.format = ["steps.catalog"] self.dry_run = True self.summary = False self.show_skipped = False self.quiet = True if self.wip: # Only run scenarios tagged with "wip". # Additionally: # * use the "plain" formatter (per default) # * do not capture stdout or logging output and # * stop at the first failure. self.default_format = "plain" self.tags = ["wip"] + self.default_tags.split() self.color = False self.stop = True self.log_capture = False self.stdout_capture = False self.tags = TagExpression(self.tags or self.default_tags.split()) if self.quiet: self.show_source = False self.show_snippets = False if self.exclude_re: self.exclude_re = re.compile(self.exclude_re) if self.include_re: self.include_re = re.compile(self.include_re) if self.name: # -- SELECT: Scenario-by-name, build regular expression. self.name_re = self.build_name_re(self.name) if self.stage is None: # pylint: disable=access-member-before-definition # -- USE ENVIRONMENT-VARIABLE, if stage is undefined. self.stage = os.environ.get("BEHAVE_STAGE", None) self.setup_stage(self.stage) self.setup_model() self.setup_userdata() # -- FINALLY: Setup Reporters and Formatters # NOTE: Reporters and Formatters can now use userdata information. if self.junit: # Buffer the output (it will be put into Junit report) self.stdout_capture = True self.stderr_capture = True self.log_capture = True self.reporters.append(JUnitReporter(self)) if self.summary: self.reporters.append(SummaryReporter(self)) self.setup_formats() unknown_formats = self.collect_unknown_formats() if unknown_formats: parser.error("format=%s is unknown" % ", ".join(unknown_formats)) def setup_outputs(self, args_outfiles=None): if self.outputs: assert not args_outfiles, "ONLY-ONCE" return # -- NORMAL CASE: Setup only initially (once). if not args_outfiles: self.outputs.append(StreamOpener(stream=sys.stdout)) else: for outfile in args_outfiles: if outfile and outfile != "-": self.outputs.append(StreamOpener(outfile)) else: self.outputs.append(StreamOpener(stream=sys.stdout)) def setup_formats(self): """Register more, user-defined formatters by name.""" if self.more_formatters: for name, scoped_class_name in self.more_formatters.items(): _format_registry.register_as(name, scoped_class_name) def collect_unknown_formats(self): unknown_formats = [] if self.format: for format_name in self.format: if (format_name == "help" or _format_registry.is_formatter_valid(format_name)): continue unknown_formats.append(format_name) return unknown_formats @staticmethod def build_name_re(names): """ Build regular expression for scenario selection by name by using a list of name parts or name regular expressions. :param names: List of name parts or regular expressions (as text). :return: Compiled regular expression to use. """ # -- NOTE: re.LOCALE is removed in Python 3.6 (deprecated in Python 3.5) # flags = (re.UNICODE | re.LOCALE) # -- ENSURE: Names are all unicode/text values (for issue #606). names = to_texts(names) pattern = u"|".join(names) return re.compile(pattern, flags=re.UNICODE) def exclude(self, filename): if isinstance(filename, FileLocation): filename = six.text_type(filename) if self.include_re and self.include_re.search(filename) is None: return True if self.exclude_re and self.exclude_re.search(filename) is not None: return True return False def setup_logging(self, level=None, configfile=None, **kwargs): """ Support simple setup of logging subsystem. Ensures that the logging level is set. But note that the logging setup can only occur once. SETUP MODES: * :func:`logging.config.fileConfig()`, if ``configfile`` is provided. * :func:`logging.basicConfig()`, otherwise. .. code-block: python # -- FILE: features/environment.py def before_all(context): context.config.setup_logging() :param level: Logging level of root logger. If None, use :attr:`logging_level` value. :param configfile: Configuration filename for fileConfig() setup. :param kwargs: Passed to :func:`logging.basicConfig()` """ if level is None: level = self.logging_level # pylint: disable=no-member if configfile: from logging.config import fileConfig fileConfig(configfile) else: # pylint: disable=no-member format_ = kwargs.pop("format", self.logging_format) datefmt = kwargs.pop("datefmt", self.logging_datefmt) logging.basicConfig(format=format_, datefmt=datefmt, **kwargs) # -- ENSURE: Default log level is set # (even if logging subsystem is already configured). logging.getLogger().setLevel(level) def setup_model(self): if self.scenario_outline_annotation_schema: name_schema = six.text_type(self.scenario_outline_annotation_schema) ScenarioOutline.annotation_schema = name_schema.strip() def setup_stage(self, stage=None): """Setup the test stage that selects a different set of steps and environment implementations. :param stage: Name of current test stage (as string or None). EXAMPLE:: # -- SETUP DEFAULT TEST STAGE (unnamed): config = Configuration() config.setup_stage() assert config.steps_dir == "steps" assert config.environment_file == "environment.py" # -- SETUP PRODUCT TEST STAGE: config.setup_stage("product") assert config.steps_dir == "product_steps" assert config.environment_file == "product_environment.py" """ steps_dir = "steps" environment_file = "environment.py" if stage: # -- USE A TEST STAGE: Select different set of implementations. prefix = stage + "_" steps_dir = prefix + steps_dir environment_file = prefix + environment_file self.steps_dir = steps_dir self.environment_file = environment_file def setup_userdata(self): if not isinstance(self.userdata, UserData): self.userdata = UserData(self.userdata) if self.userdata_defines: # -- ENSURE: Cmd-line overrides configuration file parameters. self.userdata.update(self.userdata_defines) def update_userdata(self, data): """Update userdata with data and reapply userdata defines (cmdline). :param data: Provides (partial) userdata (as dict) """ self.userdata.update(data) if self.userdata_defines: # -- REAPPLY: Cmd-line defines (override configuration file data). self.userdata.update(self.userdata_defines) behave-1.2.6/behave/contrib/0000755000076600000240000000000013244564037015737 5ustar jensstaff00000000000000behave-1.2.6/behave/contrib/__init__.py0000644000076600000240000000000013244555737020045 0ustar jensstaff00000000000000behave-1.2.6/behave/contrib/formatter_missing_steps.py0000644000076600000240000000520113244555737023270 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Provides a formatter that writes prototypes for missing step functions into a step module file by using step snippets. NOTE: This is only simplistic, proof-of-concept code. """ from __future__ import absolute_import, print_function from behave.runner_util import make_undefined_step_snippets from .steps import StepsUsageFormatter STEP_MODULE_TEMPLATE = '''\ # -*- coding: {encoding} -*- """ Missing step implementations (proof-of-concept). """ from behave import given, when, then, step {step_snippets} ''' class MissingStepsFormatter(StepsUsageFormatter): """Formatter that writes missing steps snippets into a step module file. Reuses StepsUsageFormatter class because it already contains the logic for discovering missing/undefined steps. .. code-block:: ini # -- FILE: behave.ini # NOTE: Long text value needs indentation on following lines. [behave.userdata] behave.formatter.missing_steps.template = # -*- coding: {encoding} -*- # Missing step implementations. from behave import given, when, then, step {step_snippets} """ name = "missing-steps" description = "Writes implementation for missing step definitions." template = STEP_MODULE_TEMPLATE scope = "behave.formatter.missing_steps" def __init__(self, stream_opener, config): super(MissingStepsFormatter, self).__init__(stream_opener, config) self.template = self.__class__.template self.init_from_userdata(config.userdata) def init_from_userdata(self, userdata): scoped_name = "%s.%s" %(self.scope, "template") template_text = userdata.get(scoped_name, self.template) self.template = template_text def close(self): """Called at end of test run. NOTE: Overwritten to avoid to truncate/overwrite output-file. """ if self.step_registry and self.undefined_steps: # -- ENSURE: Output stream is open. self.stream = self.open() self.report() # -- FINALLY: self.close_stream() # -- REPORT SPECIFIC-API: def report(self): """Writes missing step implementations by using step snippets.""" step_snippets = make_undefined_step_snippets(undefined_steps) encoding = self.stream.encoding or "UTF-8" function_separator = u"\n\n\n" step_snippets_text = function_separator.join(step_snippets) module_text = self.template.format(encoding=encoding, step_snippets=step_snippets_text) self.stream.write(module_text) self.stream.write("\n") behave-1.2.6/behave/contrib/scenario_autoretry.py0000644000076600000240000000527313244555737022250 0ustar jensstaff00000000000000# -*- coding: UTF -*- # pylint: disable=line-too-long """ Provides support functionality to retry scenarios a number of times before their failure is accepted. This functionality can be helpful when you use behave tests in a unreliable server/network infrastructure. EXAMPLE: .. sourcecode:: gherkin # -- FILE: features/alice.feature # TAG: Feature or Scenario/ScenarioOutline with @autoretry # NOTE: If you tag the feature, all its scenarios are retried. @autoretry Feature: Use unreliable Server infrastructure Scenario: ... .. sourcecode:: python # -- FILE: features/environment.py from behave.contrib.scenario_autoretry import patch_scenario_with_autoretry def before_feature(context, feature): for scenario in feature.scenarios: if "autoretry" in scenario.effective_tags: patch_scenario_with_autoretry(scenario, max_attempts=2) .. seealso:: * https://github.com/behave/behave/pull/328 * https://github.com/hypothesis/smokey/blob/sauce-reliability/smokey/features/environment.py """ from __future__ import print_function import functools from behave.model import ScenarioOutline def patch_scenario_with_autoretry(scenario, max_attempts=3): """Monkey-patches :func:`~behave.model.Scenario.run()` to auto-retry a scenario that fails. The scenario is retried a number of times before its failure is accepted. This is helpful when the test infrastructure (server/network environment) is unreliable (which should be a rare case). :param scenario: Scenario or ScenarioOutline to patch. :param max_attempts: How many times the scenario can be run. """ def scenario_run_with_retries(scenario_run, *args, **kwargs): for attempt in range(1, max_attempts+1): if not scenario_run(*args, **kwargs): if attempt > 1: message = u"AUTO-RETRY SCENARIO PASSED (after {0} attempts)" print(message.format(attempt)) return False # -- NOT-FAILED = PASSED # -- SCENARIO FAILED: if attempt < max_attempts: print(u"AUTO-RETRY SCENARIO (attempt {0})".format(attempt)) message = u"AUTO-RETRY SCENARIO FAILED (after {0} attempts)" print(message.format(max_attempts)) return True if isinstance(scenario, ScenarioOutline): scenario_outline = scenario for scenario in scenario_outline.scenarios: scenario_run = scenario.run scenario.run = functools.partial(scenario_run_with_retries, scenario_run) else: scenario_run = scenario.run scenario.run = functools.partial(scenario_run_with_retries, scenario_run) behave-1.2.6/behave/contrib/substep_dirs.py0000644000076600000240000000064713244555737021035 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Recipe for loading additional step definitions from sub-directories in the "features/steps" directory. .. code-block:: # -- FILE: features/steps/use_substep_dirs.py # REQUIRES: path.py from behave.runner_util import load_step_modules from path import Path HERE = Path(__file__).dirname SUBSTEP_DIRS = list(HERE.walkdirs()) load_step_modules(SUBSTEP_DIRS) """ behave-1.2.6/behave/fixture.py0000644000076600000240000003604713244555737016360 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- # STATUS: Basic concept works. """ A **fixture** provides a concept to simplify test support functionality that needs a setup/cleanup cycle per scenario, feature or test-run. A fixture is provided as fixture-function that contains the setup part and cleanup part similar to :func:`contextlib.contextmanager` or `pytest.fixture`_. .. _pytest.fixture: https://docs.pytest.org/en/latest/fixture.html A fixture is used when: * the (registered) fixture tag is used for a scenario or feature * the :func:`.use_fixture()` is called in the environment file (normally) .. sourcecode:: python # -- FILE: behave4my_project/fixtures.py (or: features/environment.py) from behave import fixture from somewhere.browser.firefox import FirefoxBrowser @fixture def browser_firefox(context, timeout=30, **kwargs): # -- SETUP-FIXTURE PART: context.browser = FirefoxBrowser(timeout, *args, **kwargs) yield context.browser # -- CLEANUP-FIXTURE PART: context.browser.shutdown() .. sourcecode:: gherkin # -- FILE: features/use_fixture.feature Feature: Use Fixture in Scenario @fixture.browser.firefox Scenario: Use browser=firefox Given I use the browser ... # -- AFTER-SCENEARIO: Cleanup fixture.browser.firefox .. sourcecode:: python # -- FILE: features/environment.py from behave import use_fixture from behave4my_project.fixtures import browser_firefox def before_tag(context, tag): if tag == "fixture.browser.firefox": # -- Performs fixture setup and registers fixture cleanup use_fixture(browser_firefox, context, timeout=10) .. hidden: BEHAVIORAL DECISIONS: * Should scenario/feature be executed when fixture-setup fails (similar to before-hook failures) ? NO, scope is skipped, but after-hooks and cleanups are executed. * Should remaining fixture-setups be performed after first fixture fails? NO, first setup-error aborts the setup and execution of the scope. * Should remaining fixture-cleanups be performed when first cleanup-error occurs? YES, try to perform all fixture-cleanups and then reraise the first cleanup-error. OPEN ISSUES: * AUTO_CALL_REGISTERED_FIXTURE (planned in future): Run fixture setup before or after before-hooks? IDEAS: * Fixture registers itself in fixture registry (runtime context). * Code in before_tag() will either be replaced w/ fixture processing function or will be automatically be executed (AUTO_CALL_REGISTERED_FIXTURE) * Support fixture tags w/ parameters that are automatically parsed and passed to fixture function, like: @fixture(name="foo", pattern="{name}={browser}") """ import inspect # ------------------------------------------------------------------------------- # LOCAL HELPERS: # ------------------------------------------------------------------------------- def iscoroutinefunction(func): """Checks if a function is a coroutine-function, like: * ``async def f(): ...`` (since Python 3.5) * ``@asyncio.coroutine def f(): ...`` (since Python3) .. note:: Compatibility helper Avoids to import :mod:`asyncio` module directly (since Python3), which in turns initializes the :mod:`logging` module as side-effect. :param func: Function to check. :return: True, if function is a coroutine function. False, otherwise. """ # -- NOTE: inspect.iscoroutinefunction() is available since Python 3.5 # Checks also if @asyncio.coroutine decorator is not used. # pylint: disable=no-member return (getattr(func, "_is_coroutine", False) or (hasattr(inspect, "iscoroutinefunction") and inspect.iscoroutinefunction(func))) def is_context_manager(func): """Checks if a fixture function provides context-manager functionality, similar to :func`contextlib.contextmanager()` function decorator. .. code-block:: python @fixture def foo(context, *args, **kwargs): context.foo = setup_foo() yield context.foo cleanup_foo() @fixture def bar(context, *args, **kwargs): context.bar = setup_bar() return context.bar assert is_context_manager(foo) is True # Generator-function assert is_context_manager(bar) is False # Normal function :param func: Function to check. :return: True, if function is a generator/context-manager function. False, otherwise. """ genfunc = inspect.isgeneratorfunction(func) return genfunc and not iscoroutinefunction(func) # ------------------------------------------------------------------------------- # EXCEPTIONS: # ------------------------------------------------------------------------------- class InvalidFixtureError(RuntimeError): """Raised when a fixture is invalid. This occurs when a generator-function with more than one yield statement is used as fixture-function. """ # ------------------------------------------------------------------------------- # FUNCTIONS: Fixture support # ------------------------------------------------------------------------------- def _setup_fixture(fixture_func, context, *fixture_args, **fixture_kwargs): """Provides core functionality to setup a fixture and registers its cleanup part (if needed). """ if is_context_manager(fixture_func): # -- CASE: Fixture function is a two-step generator (setup, cleanup). def cleanup_fixture(): try: next(func_it) # CLEANUP-FIXTURE PART # -- USE func_it: From outer scope (here). except StopIteration: return False # -- NOT-NEEDED: # except Exception: # raise # -- CLEANUP-FIXTURE PART raised an error. else: message = "Has more than one yield: %r" % fixture_func raise InvalidFixtureError(message) # -- GENERATOR: Get instance via call and register cleanup. # Then perform setup_fixture to ensure that cleanup_fixture() # is called even if setup-error occurs. # NOTE: cleanup_fixture() is called when context layer is removed. func_it = fixture_func(context, *fixture_args, **fixture_kwargs) context.add_cleanup(cleanup_fixture) setup_result = next(func_it) # SETUP-FIXTURE PART (may raise error) else: # -- CASE: Fixture is a simple function (setup-only) # NOTE: No cleanup is registered (not needed by intention of user) setup_result = fixture_func(context, *fixture_args, **fixture_kwargs) return setup_result def use_fixture(fixture_func, context, *fixture_args, **fixture_kwargs): """Use fixture (function) and call it to perform its setup-part. The fixture-function is similar to a :func:`contextlib.contextmanager` (and contains a yield-statement to seperate setup and cleanup part). If it contains a yield-statement, it registers a context-cleanup function to the context object to perform the fixture-cleanup at the end of the current scoped when the context layer is removed (and all context-cleanup functions are called). Therefore, fixture-cleanup is performed after scenario, feature or test-run (depending when its fixture-setup is performed). .. code-block:: python # -- FILE: behave4my_project/fixtures.py (or: features/environment.py) from behave import fixture from somewhere.browser import FirefoxBrowser @fixture(name="fixture.browser.firefox") def browser_firefox(context, *args, **kwargs): # -- SETUP-FIXTURE PART: context.browser = FirefoxBrowser(*args, **kwargs) yield context.browser # -- CLEANUP-FIXTURE PART: context.browser.shutdown() .. code-block:: python # -- FILE: features/environment.py from behave import use_fixture from behave4my_project.fixtures import browser_firefox def before_tag(context, tag): if tag == "fixture.browser.firefox": use_fixture(browser_firefox, context, timeout=10) :param fixture_func: Fixture function to use. :param context: Context object to use :param fixture_kwargs: Positional args, passed to the fixture function. :param fixture_kwargs: Additional kwargs, passed to the fixture function. :return: Setup result object (may be None). """ return _setup_fixture(fixture_func, context, *fixture_args, **fixture_kwargs) def use_fixture_by_tag(tag, context, fixture_registry): """Process any fixture-tag to perform :func:`use_fixture()` for its fixture. If the fixture-tag is known, the fixture data is retrieved from the fixture registry. .. code-block:: python # -- FILE: features/environment.py from behave.fixture import use_fixture_by_tag from behave4my_project.fixtures import browser_firefox, browser_chrome # -- SCHEMA 1: fixture_func fixture_registry1 = { "fixture.browser.firefox": browser_firefox, "fixture.browser.chrome": browser_chrome, } # -- SCHEMA 2: fixture_func, fixture_args, fixture_kwargs fixture_registry2 = { "fixture.browser.firefox": (browser_firefox, (), dict(timeout=10)), "fixture.browser.chrome": (browser_chrome, (), dict(timeout=12)), } def before_tag(context, tag): if tag.startswith("fixture."): return use_fixture_by_tag(tag, context, fixture_registry1): # -- MORE: Tag processing steps ... :param tag: Fixture tag to process. :param context: Runtime context object, used for :func:`use_fixture()`. :param fixture_registry: Registry maps fixture-tag to fixture data. :return: Fixture-setup result (same as: use_fixture()) :raises LookupError: If fixture-tag/fixture is unknown. :raises ValueError: If fixture data type is not supported. """ fixture_data = fixture_registry.get(tag, None) if fixture_data is None: raise LookupError("Unknown fixture-tag: %s" % tag) if callable(fixture_data): fixture_func = fixture_data use_fixture(fixture_func, context) elif isinstance(fixture_data, (tuple, list)): assert len(fixture_data) == 3 fixture_func, fixture_args, fixture_kwargs = fixture_data return use_fixture(fixture_func, context, *fixture_args, **fixture_kwargs) else: message = "fixture_data: Expected tuple or fixture-func, but is: %r" raise ValueError(message % fixture_data) def fixture_call_params(fixture_func, *args, **kwargs): # -- SEE: use_composite_fixture_with() return (fixture_func, args, kwargs) def use_composite_fixture_with(context, fixture_funcs_with_params): """Helper function when complex fixtures should be created and safe-cleanup is needed even if an setup-fixture-error occurs. This function ensures that fixture-cleanup is performed for every fixture that was setup before the setup-error occured. .. code-block:: python # -- BAD-EXAMPLE: Simplistic composite-fixture # NOTE: Created fixtures (fixture1) are not cleaned up. @fixture def foo_and_bad0(context, *args, **kwargs): the_fixture1 = setup_fixture_foo(*args, **kwargs) the_fixture2 = setup_fixture_bar_with_error("OOPS-HERE") yield (the_fixture1, the_fixture2) # NOT_REACHED. # -- NOT_REACHED: Due to fixture2-setup-error. the_fixture1.cleanup() # NOT-CALLED (SAD). the_fixture2.cleanup() # OOPS, the_fixture2 is None (if called). .. code-block:: python # -- GOOD-EXAMPLE: Sane composite-fixture # NOTE: Fixture foo.cleanup() occurs even after fixture2-setup-error. @fixture def foo(context, *args, **kwargs): the_fixture = setup_fixture_foo(*args, **kwargs) yield the_fixture cleanup_fixture_foo(the_fixture) @fixture def bad_with_setup_error(context, *args, **kwargs): raise RuntimeError("BAD-FIXTURE-SETUP") # -- SOLUTION 1: With use_fixture() @fixture def foo_and_bad1(context, *args, **kwargs): the_fixture1 = use_fixture(foo, context, *args, **kwargs) the_fixture2 = use_fixture(bad_with_setup_error, context, "OOPS") return (the_fixture1, the_fixture2) # NOT_REACHED # -- SOLUTION 2: With use_composite_fixture_with() @fixture def foo_and_bad2(context, *args, **kwargs): the_fixture = use_composite_fixture_with(context, [ fixture_call_params(foo, *args, **kwargs), fixture_call_params(bad_with_setup_error, "OOPS") ]) return the_fixture :param context: Runtime context object, used for all fixtures. :param fixture_funcs_with_params: List of fixture functions with params. :return: List of created fixture objects. """ composite_fixture = [] for fixture_func, args, kwargs in fixture_funcs_with_params: the_fixture = use_fixture(fixture_func, context, *args, **kwargs) composite_fixture.append(the_fixture) return composite_fixture # ------------------------------------------------------------------------------- # DECORATORS: # ------------------------------------------------------------------------------- def fixture(func=None, name=None, pattern=None): """Fixture decorator (currently mostly syntactic sugar). .. code-block:: python # -- FILE: features/environment.py # CASE FIXTURE-GENERATOR-FUNCTION (like @contextlib.contextmanager): @fixture def foo(context, *args, **kwargs): the_fixture = setup_fixture_foo(*args, **kwargs) context.foo = the_fixture yield the_fixture cleanup_fixture_foo(the_fixture) # CASE FIXTURE-FUNCTION: No cleanup or cleanup via context-cleanup. @fixture(name="fixture.bar") def bar(context, *args, **kwargs): the_fixture = setup_fixture_bar(*args, **kwargs) context.bar = the_fixture context.add_cleanup(cleanup_fixture_bar, the_fixture.cleanup) return the_fixture :param name: Specifies the fixture tag name (as string). .. seealso:: * :func:`contextlib.contextmanager` decorator * `@pytest.fixture`_ """ def mark_as_fixture(func, name=None, pattern=None): func.name = name func.pattern = pattern func.behave_fixture = True return func if func is None: # CASE: @fixture() # CASE: @fixture(name="foo") def decorator(func): return mark_as_fixture(func, name, pattern=pattern) return decorator elif callable(func): # CASE: @fixture return mark_as_fixture(func, name, pattern=pattern) else: # -- OOPS: Should be callable-object or None message = "Invalid func: func=%r, name=%r" % (func, name) raise TypeError(message) behave-1.2.6/behave/formatter/0000755000076600000240000000000013244564037016302 5ustar jensstaff00000000000000behave-1.2.6/behave/formatter/__init__.py0000644000076600000240000000054313244555737020424 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Root module for all behave formatters. """ from __future__ import absolute_import from behave.formatter import _builtins # ----------------------------------------------------------------------------- # MODULE-INIT: # ----------------------------------------------------------------------------- _builtins.setup_formatters() behave-1.2.6/behave/formatter/_builtins.py0000644000076600000240000000340713244555737020657 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Knowledge base of all built-in formatters. """ from __future__ import absolute_import from behave.formatter import _registry # ----------------------------------------------------------------------------- # DATA: # ----------------------------------------------------------------------------- # SCHEMA: formatter.name, formatter.class(_name) _BUILTIN_FORMATS = [ # pylint: disable=bad-whitespace ("plain", "behave.formatter.plain:PlainFormatter"), ("pretty", "behave.formatter.pretty:PrettyFormatter"), ("json", "behave.formatter.json:JSONFormatter"), ("json.pretty", "behave.formatter.json:PrettyJSONFormatter"), ("null", "behave.formatter.null:NullFormatter"), ("progress", "behave.formatter.progress:ScenarioProgressFormatter"), ("progress2", "behave.formatter.progress:StepProgressFormatter"), ("progress3", "behave.formatter.progress:ScenarioStepProgressFormatter"), ("rerun", "behave.formatter.rerun:RerunFormatter"), ("tags", "behave.formatter.tags:TagsFormatter"), ("tags.location", "behave.formatter.tags:TagsLocationFormatter"), ("steps", "behave.formatter.steps:StepsFormatter"), ("steps.doc", "behave.formatter.steps:StepsDocFormatter"), ("steps.catalog", "behave.formatter.steps:StepsCatalogFormatter"), ("steps.usage", "behave.formatter.steps:StepsUsageFormatter"), ("sphinx.steps", "behave.formatter.sphinx_steps:SphinxStepsFormatter"), ] # ----------------------------------------------------------------------------- # FUNCTIONS: # ----------------------------------------------------------------------------- def setup_formatters(): """Register all built-in formatters (lazy-loaded).""" _registry.register_formats(_BUILTIN_FORMATS) behave-1.2.6/behave/formatter/_registry.py0000644000076600000240000001173513244555737020701 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- import sys import warnings from behave.formatter.base import Formatter, StreamOpener from behave.importer import LazyDict, LazyObject, parse_scoped_name, load_module import six # ----------------------------------------------------------------------------- # FORMATTER REGISTRY: # ----------------------------------------------------------------------------- _formatter_registry = LazyDict() def format_iter(): return iter(_formatter_registry.keys()) def format_items(resolved=False): if resolved: # -- ENSURE: All formatter classes are loaded (and resolved). _formatter_registry.load_all(strict=False) return iter(_formatter_registry.items()) def register_as(name, formatter_class): """ Register formatter class with given name. :param name: Name for this formatter (as identifier). :param formatter_class: Formatter class to register. .. since:: 1.2.5 Parameter ordering starts with name. """ if not isinstance(name, six.string_types): # -- REORDER-PARAMS: Used old ordering before behave-1.2.5 (2015). warnings.warn("Use parameter ordering: name, formatter_class (for: %s)"\ % formatter_class) _formatter_class = name name = formatter_class formatter_class = _formatter_class if isinstance(formatter_class, six.string_types): # -- SPEEDUP-STARTUP: Only import formatter_class when used. scoped_formatter_class_name = formatter_class formatter_class = LazyObject(scoped_formatter_class_name) assert (isinstance(formatter_class, LazyObject) or issubclass(formatter_class, Formatter)) _formatter_registry[name] = formatter_class def register(formatter_class): register_as(formatter_class.name, formatter_class) def register_formats(formats): """Register many format items into the registry. :param formats: List of format items (as: (name, class|class_name)). """ for formatter_name, formatter_class_name in formats: register_as(formatter_name, formatter_class_name) def load_formatter_class(scoped_class_name): """Load a formatter class by using its scoped class name. :param scoped_class_name: Formatter module and class name (as string). :return: Formatter class. :raises: ValueError, if scoped_class_name is invalid. :raises: ImportError, if module cannot be loaded or class is not in module. """ if ":" not in scoped_class_name: message = 'REQUIRE: "module:class", but was: "%s"' % scoped_class_name raise ValueError(message) module_name, class_name = parse_scoped_name(scoped_class_name) formatter_module = load_module(module_name) formatter_class = getattr(formatter_module, class_name, None) if formatter_class is None: raise ImportError("CLASS NOT FOUND: %s" % scoped_class_name) return formatter_class def select_formatter_class(formatter_name): """Resolve the formatter class by: * using one of the registered ones * loading a user-specified formatter class (like: my.module_name:MyClass) :param formatter_name: Name of the formatter or scoped name (as string). :return: Formatter class :raises: LookupError, if not found. :raises: ImportError, if a user-specific formatter class cannot be loaded. :raises: ValueError, if formatter name is invalid. """ try: return _formatter_registry[formatter_name] except KeyError: # -- NOT-FOUND: if ":" not in formatter_name: raise # -- OTHERWISE: SCOPED-NAME, try to load a user-specific formatter. # MAY RAISE: ImportError return load_formatter_class(formatter_name) def is_formatter_valid(formatter_name): """Checks if the formatter is known (registered) or loadable. :param formatter_name: Format(ter) name to check (as string). :return: True, if formatter is known or can be loaded. """ try: formatter_class = select_formatter_class(formatter_name) return issubclass(formatter_class, Formatter) except (LookupError, ImportError, ValueError): return False def make_formatters(config, stream_openers): """Build a list of formatter, used by a behave runner. :param config: Configuration object to use. :param stream_openers: List of stream openers to use (for formatters). :return: List of formatters. :raises: LookupError/KeyError if a formatter class is unknown. :raises: ImportError, if a formatter class cannot be loaded/resolved. """ # -- BUILD: Formatter list default_stream_opener = StreamOpener(stream=sys.stdout) formatter_list = [] for i, name in enumerate(config.format): stream_opener = default_stream_opener if i < len(stream_openers): stream_opener = stream_openers[i] formatter_class = select_formatter_class(name) formatter_object = formatter_class(stream_opener, config) formatter_list.append(formatter_object) return formatter_list behave-1.2.6/behave/formatter/ansi_escapes.py0000644000076600000240000000546613244555737021333 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Provides ANSI escape sequences for coloring/formatting output in ANSI terminals. """ from __future__ import absolute_import import os import re colors = { "black": u"\x1b[30m", "red": u"\x1b[31m", "green": u"\x1b[32m", "yellow": u"\x1b[33m", "blue": u"\x1b[34m", "magenta": u"\x1b[35m", "cyan": u"\x1b[36m", "white": u"\x1b[37m", "grey": u"\x1b[90m", "bold": u"\x1b[1m", } aliases = { "untested": "cyan", # SAME-COLOR AS: skipped "undefined": "yellow", "pending": "yellow", "executing": "grey", "failed": "red", "passed": "green", "outline": "cyan", "skipped": "cyan", "comments": "grey", "tag": "cyan", } escapes = { "reset": u"\x1b[0m", "up": u"\x1b[1A", } if "GHERKIN_COLORS" in os.environ: new_aliases = [p.split("=") for p in os.environ["GHERKIN_COLORS"].split(":")] aliases.update(dict(new_aliases)) for alias in aliases: escapes[alias] = "".join([colors[c] for c in aliases[alias].split(",")]) arg_alias = alias + "_arg" arg_seq = aliases.get(arg_alias, aliases[alias] + ",bold") escapes[arg_alias] = "".join([colors[c] for c in arg_seq.split(",")]) # pylint: disable=anomalous-backslash-in-string def up(n): return u"\x1b[%dA" % n _ANSI_ESCAPE_PATTERN = re.compile(u"\x1b\[\d+[mA]", re.UNICODE) # pylint: enable=anomalous-backslash-in-string def strip_escapes(text): """ Removes ANSI escape sequences from text (if any are contained). :param text: Text that may or may not contain ANSI escape sequences. :return: Text without ANSI escape sequences. """ return _ANSI_ESCAPE_PATTERN.sub("", text) def use_ansi_escape_colorbold_composites(): # pragma: no cover """ Patch for "sphinxcontrib-ansi" to process the following ANSI escapes correctly (set-color set-bold sequences): ESC[{color}mESC[1m => ESC[{color};1m Reapply aliases to ANSI escapes mapping. """ # NOT-NEEDED: global escapes color_codes = {} for color_name, color_escape in colors.items(): color_code = color_escape.replace(u"\x1b[", u"").replace(u"m", u"") color_codes[color_name] = color_code # pylint: disable=redefined-outer-name for alias in aliases: parts = [color_codes[c] for c in aliases[alias].split(",")] composite_escape = u"\x1b[{0}m".format(u";".join(parts)) escapes[alias] = composite_escape arg_alias = alias + "_arg" arg_seq = aliases.get(arg_alias, aliases[alias] + ",bold") parts = [color_codes[c] for c in arg_seq.split(",")] composite_escape = u"\x1b[{0}m".format(u";".join(parts)) escapes[arg_alias] = composite_escape behave-1.2.6/behave/formatter/base.py0000644000076600000240000001416213244555737017601 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- # UNUSED: import sys # UNUSED: import codecs import os.path from behave.textutil import select_best_encoding, \ ensure_stream_with_encoder as _ensure_stream_with_encoder class StreamOpener(object): """Provides a transport vehicle to open the formatter output stream when the formatter needs it. In addition, it provides the formatter with more control: * when a stream is opened * if a stream is opened at all * the name (filename/dirname) of the output stream * let it decide if directory mode is used instead of file mode """ # FORMER: default_encoding = "UTF-8" default_encoding = select_best_encoding() def __init__(self, filename=None, stream=None, encoding=None): if not encoding: encoding = self.default_encoding if stream: stream = self.ensure_stream_with_encoder(stream, encoding) self.name = filename self.stream = stream self.encoding = encoding self.should_close_stream = not stream # Only for not pre-opened ones. @staticmethod def ensure_dir_exists(directory): if directory and not os.path.isdir(directory): os.makedirs(directory) @classmethod def ensure_stream_with_encoder(cls, stream, encoding=None): return _ensure_stream_with_encoder(stream, encoding) def open(self): if not self.stream or self.stream.closed: self.ensure_dir_exists(os.path.dirname(self.name)) stream = open(self.name, "w") # stream = codecs.open(self.name, "w", encoding=self.encoding) stream = self.ensure_stream_with_encoder(stream, self.encoding) self.stream = stream # -- Keep stream for house-keeping. self.should_close_stream = True assert self.should_close_stream return self.stream def close(self): """ Close the stream, if it was opened by this stream_opener. Skip closing for sys.stdout and pre-opened streams. :return: True, if stream was closed. """ closed = False if self.stream and self.should_close_stream: closed = getattr(self.stream, "closed", False) if not closed: self.stream.close() closed = True self.stream = None return closed class Formatter(object): """ Base class for all formatter classes. A formatter is an extension point (variation point) for the runner logic. A formatter is called while processing model elements. Processing Logic (simplified, without ScenarioOutline and skip logic):: for feature in runner.features: formatter = make_formatters(...) formatter.uri(feature.filename) formatter.feature(feature) for scenario in feature.scenarios: formatter.scenario(scenario) for step in scenario.all_steps: formatter.step(step) step_match = step_registry.find_match(step) formatter.match(step_match) if step_match: step_match.run() else: step.status = Status.undefined formatter.result(step.status) formatter.eof() # -- FEATURE-END formatter.close() """ name = None description = None def __init__(self, stream_opener, config): self.stream_opener = stream_opener self.stream = stream_opener.stream self.config = config @property def stdout_mode(self): return not self.stream_opener.name def open(self): """ Ensure that the output stream is open. Triggers the stream opener protocol (if necessary). :return: Output stream to use (just opened or already open). """ if not self.stream: self.stream = self.stream_opener.open() return self.stream def uri(self, uri): """Called before processing a file (normally a feature file). :param uri: URI or filename (as string). """ pass def feature(self, feature): """Called before a feature is executed. :param feature: Feature object (as :class:`behave.model.Feature`) """ pass def background(self, background): """Called when a (Feature) Background is provided. Called after :method:`feature()` is called. Called before processing any scenarios or scenario outlines. :param background: Background object (as :class:`behave.model.Background`) """ pass def scenario(self, scenario): """Called before a scenario is executed (or ScenarioOutline scenarios). :param scenario: Scenario object (as :class:`behave.model.Scenario`) """ pass def step(self, step): """Called before a step is executed (and matched). NOTE: Normally called before scenario is executed for all its steps. :param step: Step object (as :class:`behave.model.Step`) """ pass def match(self, match): """Called when a step was matched against its step implementation. :param match: Registered step (as Match), undefined step (as NoMatch). """ pass def result(self, step): """Called after processing a step (when the step result is known). :param step: Step object with result (after being executed/skipped). """ pass def eof(self): """Called after processing a feature (or a feature file).""" pass def close(self): """Called before the formatter is no longer used (stream/io compatibility). """ self.close_stream() def close_stream(self): """Close the stream, but only if this is needed. This step is skipped if the stream is sys.stdout. """ if self.stream: # -- DELEGATE STREAM-CLOSING: To stream_opener assert self.stream is self.stream_opener.stream self.stream_opener.close() self.stream = None # -- MARK CLOSED. behave-1.2.6/behave/formatter/formatters.py0000644000076600000240000000373713244555737021063 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Deprecated module. Functionality was split-up into: * behave.formatter._registry (generic core functionality) * behave.formatter._builtins (registration of known, builtin formatters) .. since:: 1.2.5a1 Deprecated, use "behave.formatter._registry" or "behave.formatter._builtin". """ from __future__ import absolute_import import warnings from behave.formatter import _registry warnings.simplefilter("once", DeprecationWarning) warnings.warn("Use 'behave.formatter._registry' instead.", DeprecationWarning) # ----------------------------------------------------------------------------- # FORMATTER REGISTRY: # ----------------------------------------------------------------------------- def register_as(formatter_class, name): """ Register formatter class with given name. :param formatter_class: Formatter class to register. :param name: Name for this formatter (as identifier). """ warnings.warn("Use behave.formatter._registry.register_as() instead.", DeprecationWarning, stacklevel=2) _registry.register_as(name, formatter_class) def register(formatter_class): register_as(formatter_class, formatter_class.name) def get_formatter(config, stream_openers): warnings.warn("Use make_formatters() instead", DeprecationWarning, stacklevel=2) return _registry.make_formatters(config, stream_openers) # ----------------------------------------------------------------------------- # SETUP: # ----------------------------------------------------------------------------- def setup_formatters(): warnings.warn("Use behave.formatter._builtins instead", DeprecationWarning, stacklevel=2) from behave.formatter import _builtins _builtins.setup_formatters() # ----------------------------------------------------------------------------- # MODULE-INIT: # ----------------------------------------------------------------------------- # DISABLED: setup_formatters() behave-1.2.6/behave/formatter/json.py0000644000076600000240000002036213244555737017637 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ This module provides `JSON`_ formatters for :mod:`behave`: * json: Generates compact JSON output * json.pretty: Generates readable JSON output .. _JSON: http://json.org """ from __future__ import absolute_import import base64 from behave.formatter.base import Formatter from behave.model_core import Status import six try: import json except ImportError: import simplejson as json # ----------------------------------------------------------------------------- # CLASS: JSONFormatter # ----------------------------------------------------------------------------- class JSONFormatter(Formatter): name = "json" description = "JSON dump of test run" dumps_kwargs = {} split_text_into_lines = True # EXPERIMENT for better readability. json_number_types = six.integer_types + (float,) json_scalar_types = json_number_types + (six.text_type, bool, type(None)) def __init__(self, stream_opener, config): super(JSONFormatter, self).__init__(stream_opener, config) # -- ENSURE: Output stream is open. self.stream = self.open() self.feature_count = 0 self.current_feature = None self.current_feature_data = None self.current_scenario = None self._step_index = 0 def reset(self): self.current_feature = None self.current_feature_data = None self.current_scenario = None self._step_index = 0 # -- FORMATTER API: def uri(self, uri): pass def feature(self, feature): self.reset() self.current_feature = feature self.current_feature_data = { "keyword": feature.keyword, "name": feature.name, "tags": list(feature.tags), "location": six.text_type(feature.location), "status": None, # Not known before feature run. } element = self.current_feature_data if feature.description: element["description"] = feature.description def background(self, background): element = self.add_feature_element({ "type": "background", "keyword": background.keyword, "name": background.name, "location": six.text_type(background.location), "steps": [], }) if background.name: element["name"] = background.name self._step_index = 0 # -- ADD BACKGROUND STEPS: Support *.feature file regeneration. for step_ in background.steps: self.step(step_) def scenario(self, scenario): self.finish_current_scenario() self.current_scenario = scenario element = self.add_feature_element({ "type": "scenario", "keyword": scenario.keyword, "name": scenario.name, "tags": scenario.tags, "location": six.text_type(scenario.location), "steps": [], "status": None, }) if scenario.description: element["description"] = scenario.description self._step_index = 0 @classmethod def make_table(cls, table): table_data = { "headings": table.headings, "rows": [list(row) for row in table.rows] } return table_data def step(self, step): s = { "keyword": step.keyword, "step_type": step.step_type, "name": step.name, "location": six.text_type(step.location), } if step.text: text = step.text if self.split_text_into_lines and "\n" in text: text = text.splitlines() s["text"] = text if step.table: s["table"] = self.make_table(step.table) element = self.current_feature_element element["steps"].append(s) def match(self, match): args = [] for argument in match.arguments: argument_value = argument.value if not isinstance(argument_value, self.json_scalar_types): # -- OOPS: Avoid invalid JSON format w/ custom types. # Use raw string (original) instead. argument_value = argument.original assert isinstance(argument_value, self.json_scalar_types) arg = { "value": argument_value, } if argument.name: arg["name"] = argument.name if argument.original != argument_value: # -- REDUNDANT DATA COMPRESSION: Suppress for strings. arg["original"] = argument.original args.append(arg) match_data = { "location": six.text_type(match.location) or "", "arguments": args, } if match.location: # -- NOTE: match.location=None occurs for undefined steps. steps = self.current_feature_element["steps"] steps[self._step_index]["match"] = match_data def result(self, step): steps = self.current_feature_element["steps"] steps[self._step_index]["result"] = { "status": step.status.name, "duration": step.duration, } if step.error_message and step.status == Status.failed: # -- OPTIONAL: Provided for failed steps. error_message = step.error_message if self.split_text_into_lines and "\n" in error_message: error_message = error_message.splitlines() result_element = steps[self._step_index]["result"] result_element["error_message"] = error_message self._step_index += 1 def embedding(self, mime_type, data): step = self.current_feature_element["steps"][-1] step["embeddings"].append({ "mime_type": mime_type, "data": base64.b64encode(data).replace("\n", ""), }) def eof(self): """ End of feature """ if not self.current_feature_data: return # -- NORMAL CASE: Write collected data of current feature. self.finish_current_scenario() self.update_status_data() if self.feature_count == 0: # -- FIRST FEATURE: self.write_json_header() else: # -- NEXT FEATURE: self.write_json_feature_separator() self.write_json_feature(self.current_feature_data) self.reset() self.feature_count += 1 def close(self): if self.feature_count == 0: # -- FIRST FEATURE: Corner case when no features are provided. self.write_json_header() self.write_json_footer() self.close_stream() # -- JSON-DATA COLLECTION: def add_feature_element(self, element): assert self.current_feature_data is not None if "elements" not in self.current_feature_data: self.current_feature_data["elements"] = [] self.current_feature_data["elements"].append(element) return element @property def current_feature_element(self): assert self.current_feature_data is not None return self.current_feature_data["elements"][-1] def update_status_data(self): assert self.current_feature assert self.current_feature_data self.current_feature_data["status"] = self.current_feature.status.name def finish_current_scenario(self): if self.current_scenario: status_name = self.current_scenario.status.name self.current_feature_element["status"] = status_name # -- JSON-WRITER: def write_json_header(self): self.stream.write("[\n") def write_json_footer(self): self.stream.write("\n]\n") def write_json_feature(self, feature_data): self.stream.write(json.dumps(feature_data, **self.dumps_kwargs)) self.stream.flush() def write_json_feature_separator(self): self.stream.write(",\n\n") # ----------------------------------------------------------------------------- # CLASS: PrettyJSONFormatter # ----------------------------------------------------------------------------- class PrettyJSONFormatter(JSONFormatter): """ Provides readable/comparable textual JSON output. """ name = "json.pretty" description = "JSON dump of test run (human readable)" dumps_kwargs = {"indent": 2, "sort_keys": True} behave-1.2.6/behave/formatter/null.py0000644000076600000240000000056613244555737017644 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import from behave.formatter.base import Formatter class NullFormatter(Formatter): """ Provides formatter that does not output anything. Implements the NULL pattern for a formatter (similar like: /dev/null). """ name = "null" description = "Provides formatter that does not output anything." behave-1.2.6/behave/formatter/plain.py0000644000076600000240000001253213244555737017771 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import from behave.formatter.base import Formatter from behave.model_describe import ModelPrinter from behave.textutil import make_indentation # ----------------------------------------------------------------------------- # CLASS: PlainFormatter # ----------------------------------------------------------------------------- class PlainFormatter(Formatter): """ Provides a simple plain formatter without coloring/formatting. The formatter displays now also: * multi-line text (doc-strings) * table * tags (maybe) """ name = "plain" description = "Very basic formatter with maximum compatibility" SHOW_MULTI_LINE = True SHOW_TAGS = False SHOW_ALIGNED_KEYWORDS = False DEFAULT_INDENT_SIZE = 2 RAISE_OUTPUT_ERRORS = True def __init__(self, stream_opener, config, **kwargs): super(PlainFormatter, self).__init__(stream_opener, config) self.steps = [] self.show_timings = config.show_timings self.show_multiline = config.show_multiline and self.SHOW_MULTI_LINE self.show_aligned_keywords = self.SHOW_ALIGNED_KEYWORDS self.show_tags = self.SHOW_TAGS self.indent_size = self.DEFAULT_INDENT_SIZE # -- ENSURE: Output stream is open. self.stream = self.open() self.printer = ModelPrinter(self.stream) # -- LAZY-EVALUATE: self._multiline_indentation = None @property def multiline_indentation(self): if self._multiline_indentation is None: offset = 0 if self.show_aligned_keywords: offset = 2 indentation = make_indentation(3 * self.indent_size + offset) self._multiline_indentation = indentation return self._multiline_indentation def reset_steps(self): self.steps = [] def write_tags(self, tags, indent=None): if tags and self.show_tags: indent = indent or "" text = " @".join(tags) self.stream.write(u"%s@%s\n" % (indent, text)) # -- IMPLEMENT-INTERFACE FOR: Formatter def feature(self, feature): self.reset_steps() self.write_tags(feature.tags) self.stream.write(u"%s: %s\n" % (feature.keyword, feature.name)) def background(self, background): self.reset_steps() indent = make_indentation(self.indent_size) text = u"%s%s: %s\n" % (indent, background.keyword, background.name) self.stream.write(text) def scenario(self, scenario): self.reset_steps() self.stream.write(u"\n") indent = make_indentation(self.indent_size) text = u"%s%s: %s\n" % (indent, scenario.keyword, scenario.name) self.write_tags(scenario.tags, indent) self.stream.write(text) def step(self, step): self.steps.append(step) def result(self, step): """ Process the result of a step (after step execution). :param step: Step object with result to process. """ step = self.steps.pop(0) indent = make_indentation(2 * self.indent_size) if self.show_aligned_keywords: # -- RIGHT-ALIGN KEYWORDS (max. keyword width: 6): text = u"%s%6s %s ... " % (indent, step.keyword, step.name) else: text = u"%s%s %s ... " % (indent, step.keyword, step.name) self.stream.write(text) status_text = step.status.name if self.show_timings: status_text += " in %0.3fs" % step.duration unicode_errors = 0 if step.error_message: try: self.stream.write(u"%s\n%s\n" % (status_text, step.error_message)) except UnicodeError as e: unicode_errors += 1 self.stream.write(u"%s\n" % status_text) self.stream.write(u"%s while writing error message: %s\n" % \ (e.__class__.__name__, e)) if self.RAISE_OUTPUT_ERRORS: raise else: self.stream.write(u"%s\n" % status_text) if self.show_multiline: if step.text: try: self.doc_string(step.text) except UnicodeError as e: unicode_errors += 1 self.stream.write(u"%s while writing docstring: %s\n" % \ (e.__class__.__name__, e)) if self.RAISE_OUTPUT_ERRORS: raise if step.table: self.table(step.table) def eof(self): self.stream.write("\n") # -- MORE: Formatter helpers def doc_string(self, doc_string): self.printer.print_docstring(doc_string, self.multiline_indentation) def table(self, table): self.printer.print_table(table, self.multiline_indentation) # ----------------------------------------------------------------------------- # CLASS: Plain0Formatter # ----------------------------------------------------------------------------- class Plain0Formatter(PlainFormatter): """ Similar to old plain formatter without support for: * multi-line text * tables * tags """ name = "plain0" description = "Very basic formatter with maximum compatibility" SHOW_MULTI_LINE = False SHOW_TAGS = False SHOW_ALIGNED_KEYWORDS = False behave-1.2.6/behave/formatter/pretty.py0000644000076600000240000003006613244555737020217 0ustar jensstaff00000000000000# -*- coding: utf8 -*- from __future__ import absolute_import, division import sys from behave.formatter.ansi_escapes import escapes, up from behave.formatter.base import Formatter from behave.model_core import Status from behave.model_describe import escape_cell, escape_triple_quotes from behave.textutil import indent, text as _text import six from six.moves import range, zip # ----------------------------------------------------------------------------- # TERMINAL SUPPORT: # ----------------------------------------------------------------------------- DEFAULT_WIDTH = 80 DEFAULT_HEIGHT = 24 def get_terminal_size(): if sys.platform == "windows": # Autodetecting the size of a Windows command window is left as an # exercise for the reader. Prizes may be awarded for the best answer. return (DEFAULT_WIDTH, DEFAULT_HEIGHT) try: import fcntl import termios import struct zero_struct = struct.pack("HHHH", 0, 0, 0, 0) result = fcntl.ioctl(0, termios.TIOCGWINSZ, zero_struct) h, w, hp1, wp1 = struct.unpack("HHHH", result) return w or DEFAULT_WIDTH, h or DEFAULT_HEIGHT except Exception: # pylint: disable=broad-except return (DEFAULT_WIDTH, DEFAULT_HEIGHT) # ----------------------------------------------------------------------------- # COLORING SUPPORT: # ----------------------------------------------------------------------------- class MonochromeFormat(object): def text(self, text): # pylint: disable=no-self-use assert isinstance(text, six.text_type) return text class ColorFormat(object): def __init__(self, status): self.status = status def text(self, text): assert isinstance(text, six.text_type) return escapes[self.status] + text + escapes["reset"] # ----------------------------------------------------------------------------- # CLASS: PrettyFormatter # ----------------------------------------------------------------------------- class PrettyFormatter(Formatter): # pylint: disable=too-many-instance-attributes name = "pretty" description = "Standard colourised pretty formatter" def __init__(self, stream_opener, config): super(PrettyFormatter, self).__init__(stream_opener, config) # -- ENSURE: Output stream is open. self.stream = self.open() isatty = getattr(self.stream, "isatty", lambda: True) stream_supports_colors = isatty() self.monochrome = not config.color or not stream_supports_colors self.show_source = config.show_source self.show_timings = config.show_timings self.show_multiline = config.show_multiline self.formats = None self.display_width = get_terminal_size()[0] # -- UNUSED: self.tag_statement = None self.steps = [] self._uri = None self._match = None self.statement = None self.indentations = [] self.step_lines = 0 def reset(self): # -- UNUSED: self.tag_statement = None self.steps = [] self._uri = None self._match = None self.statement = None self.indentations = [] self.step_lines = 0 def uri(self, uri): self.reset() self._uri = uri def feature(self, feature): #self.print_comments(feature.comments, '') self.print_tags(feature.tags, '') self.stream.write(u"%s: %s" % (feature.keyword, feature.name)) if self.show_source: # pylint: disable=redefined-builtin format = self.format("comments") self.stream.write(format.text(u" # %s" % feature.location)) self.stream.write("\n") self.print_description(feature.description, " ", False) self.stream.flush() def background(self, background): self.replay() self.statement = background def scenario(self, scenario): self.replay() self.statement = scenario def replay(self): self.print_statement() self.print_steps() self.stream.flush() def step(self, step): self.steps.append(step) def match(self, match): self._match = match self.print_statement() self.print_step(Status.executing, self._match.arguments, self._match.location, self.monochrome) self.stream.flush() def result(self, step): if not self.monochrome: lines = self.step_lines + 1 if self.show_multiline: if step.table: lines += len(step.table.rows) + 1 if step.text: lines += len(step.text.splitlines()) + 2 self.stream.write(up(lines)) arguments = [] location = None if self._match: arguments = self._match.arguments location = self._match.location self.print_step(step.status, arguments, location, True) if step.error_message: self.stream.write(indent(step.error_message.strip(), u" ")) self.stream.write("\n\n") self.stream.flush() def arg_format(self, key): return self.format(key + "_arg") def format(self, key): if self.monochrome: if self.formats is None: self.formats = MonochromeFormat() return self.formats # -- OTHERWISE: if self.formats is None: self.formats = {} # pylint: disable=redefined-builtin format = self.formats.get(key, None) if format is not None: return format format = self.formats[key] = ColorFormat(key) return format def eof(self): self.replay() self.stream.write("\n") self.stream.flush() def table(self, table): cell_lengths = [] all_rows = [table.headings] + table.rows for row in all_rows: lengths = [len(escape_cell(c)) for c in row] cell_lengths.append(lengths) max_lengths = [] for col in range(0, len(cell_lengths[0])): max_lengths.append(max([c[col] for c in cell_lengths])) for i, row in enumerate(all_rows): #for comment in row.comments: # self.stream.write(" %s\n" % comment.value) self.stream.write(" |") for j, (cell, max_length) in enumerate(zip(row, max_lengths)): self.stream.write(" ") self.stream.write(self.color(cell, None, j)) self.stream.write(" " * (max_length - cell_lengths[i][j])) self.stream.write(" |") self.stream.write("\n") self.stream.flush() def doc_string(self, doc_string): #self.stream.write(' """' + doc_string.content_type + '\n') doc_string = _text(doc_string) prefix = u" " self.stream.write(u'%s"""\n' % prefix) doc_string = escape_triple_quotes(indent(doc_string, prefix)) self.stream.write(doc_string) self.stream.write(u'\n%s"""\n' % prefix) self.stream.flush() # def doc_string(self, doc_string): # from behave.model_describe import ModelDescriptor # prefix = " " # text = ModelDescriptor.describe_docstring(doc_string, prefix) # self.stream.write(text) # self.stream.flush() # -- UNUSED: # def exception(self, exception): # exception_text = _text(exception) # self.stream.write(self.format("failed").text(exception_text) + "\n") # self.stream.flush() def color(self, cell, statuses, _color): # pylint: disable=no-self-use if statuses: return escapes["color"] + escapes["reset"] # -- OTHERWISE: return escape_cell(cell) def indented_text(self, text, proceed): if not text: return u"" if proceed: indentation = self.indentations.pop(0) else: indentation = self.indentations[0] indentation = u" " * indentation return u"%s # %s" % (indentation, text) def calculate_location_indentations(self): line_widths = [] for s in [self.statement] + self.steps: string = s.keyword + " " + s.name line_widths.append(len(string)) max_line_width = max(line_widths) self.indentations = [max_line_width - width for width in line_widths] def print_statement(self): if self.statement is None: return self.calculate_location_indentations() self.stream.write(u"\n") #self.print_comments(self.statement.comments, " ") if hasattr(self.statement, "tags"): self.print_tags(self.statement.tags, u" ") self.stream.write(u" %s: %s " % (self.statement.keyword, self.statement.name)) location = self.indented_text(six.text_type(self.statement.location), True) if self.show_source: self.stream.write(self.format("comments").text(location)) self.stream.write("\n") #self.print_description(self.statement.description, u" ") self.statement = None def print_steps(self): while self.steps: self.print_step(Status.skipped, [], None, True) def print_step(self, status, arguments, location, proceed): if proceed: step = self.steps.pop(0) else: step = self.steps[0] text_format = self.format(status.name) arg_format = self.arg_format(status.name) #self.print_comments(step.comments, " ") self.stream.write(" ") self.stream.write(text_format.text(step.keyword + " ")) line_length = 5 + len(step.keyword) step_name = six.text_type(step.name) text_start = 0 for arg in arguments: if arg.end <= text_start: # -- SKIP-OVER: Optional and nested regexp args # - Optional regexp args (unmatched: None). # - Nested regexp args that are already processed. continue # -- VALID, MATCHED ARGUMENT: assert arg.original is not None text = step_name[text_start:arg.start] self.stream.write(text_format.text(text)) line_length += len(text) self.stream.write(arg_format.text(arg.original)) line_length += len(arg.original) text_start = arg.end if text_start != len(step_name): text = step_name[text_start:] self.stream.write(text_format.text(text)) line_length += (len(text)) if self.show_source: location = six.text_type(location) if self.show_timings and status in (Status.passed, Status.failed): location += " %0.3fs" % step.duration location = self.indented_text(location, proceed) self.stream.write(self.format("comments").text(location)) line_length += len(location) elif self.show_timings and status in (Status.passed, Status.failed): timing = "%0.3fs" % step.duration timing = self.indented_text(timing, proceed) self.stream.write(self.format("comments").text(timing)) line_length += len(timing) self.stream.write("\n") self.step_lines = int((line_length - 1) / self.display_width) if self.show_multiline: if step.text: self.doc_string(step.text) if step.table: self.table(step.table) def print_tags(self, tags, indentation): if not tags: return line = " ".join("@" + tag for tag in tags) self.stream.write(indentation + line + "\n") def print_comments(self, comments, indentation): if not comments: return self.stream.write(indent([c.value for c in comments], indentation)) self.stream.write("\n") def print_description(self, description, indentation, newline=True): if not description: return self.stream.write(indent(description, indentation)) if newline: self.stream.write("\n") behave-1.2.6/behave/formatter/progress.py0000644000076600000240000002407513244555737020537 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Provides 2 dotted progress formatters: * ScenarioProgressFormatter (scope: scenario) * StepProgressFormatter (scope: step) A "dot" character that represents the result status is printed after executing a scope item. """ from __future__ import absolute_import import six from behave.formatter.base import Formatter from behave.model_core import Status from behave.textutil import text as _text # ----------------------------------------------------------------------------- # CLASS: ProgressFormatterBase # ----------------------------------------------------------------------------- class ProgressFormatterBase(Formatter): """ Provides formatter base class for different variants of progress formatters. A progress formatter show an abbreviated, compact dotted progress bar, similar to unittest output (in terse mode). """ # -- MAP: step.status to short dot_status representation. dot_status = { "passed": ".", "failed": "F", "error": "E", # Caught exception, but not an AssertionError "skipped": "S", "untested": "_", "undefined": "U", } show_timings = False def __init__(self, stream_opener, config): super(ProgressFormatterBase, self).__init__(stream_opener, config) # -- ENSURE: Output stream is open. self.stream = self.open() self.steps = [] self.failures = [] self.current_feature = None self.current_scenario = None self.show_timings = config.show_timings and self.show_timings def reset(self): self.steps = [] self.failures = [] self.current_feature = None self.current_scenario = None # -- FORMATTER API: def feature(self, feature): self.current_feature = feature self.stream.write("%s " % six.text_type(feature.filename)) self.stream.flush() def background(self, background): pass def scenario(self, scenario): """ Process the next scenario. But first allow to report the status on the last scenario. """ self.report_scenario_completed() self.current_scenario = scenario def step(self, step): self.steps.append(step) def result(self, step): self.steps.pop(0) self.report_step_progress(step) def eof(self): """ Called at end of a feature. It would be better to have a hook that is called after all features. """ self.report_scenario_completed() self.report_feature_completed() self.report_failures() self.stream.flush() self.reset() # -- SPECIFIC PART: def report_step_progress(self, step): """Report the progress on the current step. The default implementation is empty. It should be override by a concrete class. """ pass def report_scenario_progress(self): """Report the progress for the current/last scenario. The default implementation is empty. It should be override by a concrete class. """ pass def report_feature_completed(self): """Hook called when a feature is completed to perform the last tasks. """ pass def report_scenario_completed(self): """Hook called when a scenario is completed to perform the last tasks. """ self.report_scenario_progress() def report_feature_duration(self): if self.show_timings and self.current_feature: self.stream.write(u" # %.3fs" % self.current_feature.duration) self.stream.write("\n") def report_scenario_duration(self): if self.show_timings and self.current_scenario: self.stream.write(u" # %.3fs" % self.current_scenario.duration) self.stream.write("\n") def report_failures(self): if self.failures: separator = "-" * 80 self.stream.write(u"%s\n" % separator) for step in self.failures: self.stream.write(u"FAILURE in step '%s':\n" % step.name) self.stream.write(u" Feature: %s\n" % step.feature.name) self.stream.write(u" Scenario: %s\n" % step.scenario.name) self.stream.write(u"%s\n" % step.error_message) if step.exception: self.stream.write(u"exception: %s\n" % step.exception) self.stream.write(u"%s\n" % separator) # ----------------------------------------------------------------------------- # CLASS: ScenarioProgressFormatter # ----------------------------------------------------------------------------- class ScenarioProgressFormatter(ProgressFormatterBase): """ Report dotted progress for each scenario similar to unittest. """ name = "progress" description = "Shows dotted progress for each executed scenario." def report_scenario_progress(self): """ Report the progress for the current/last scenario. """ if not self.current_scenario: return # SKIP: No results to report for first scenario. # -- NORMAL-CASE: status_name = self.current_scenario.status.name dot_status = self.dot_status[status_name] if status_name == "failed": # MAYBE TODO: self.failures.append(result) pass self.stream.write(dot_status) self.stream.flush() def report_feature_completed(self): self.report_feature_duration() # ----------------------------------------------------------------------------- # CLASS: StepProgressFormatter # ----------------------------------------------------------------------------- class StepProgressFormatter(ProgressFormatterBase): """ Report dotted progress for each step similar to unittest. """ name = "progress2" description = "Shows dotted progress for each executed step." def report_step_progress(self, step): """Report the progress for each step.""" dot_status = self.dot_status[step.status.name] if step.status == Status.failed: if (step.exception and not isinstance(step.exception, AssertionError)): # -- ISA-ERROR: Some Exception dot_status = self.dot_status["error"] step.feature = self.current_feature step.scenario = self.current_scenario self.failures.append(step) self.stream.write(dot_status) self.stream.flush() def report_feature_completed(self): self.report_feature_duration() # ----------------------------------------------------------------------------- # CLASS: ScenarioStepProgressFormatter # ----------------------------------------------------------------------------- class ScenarioStepProgressFormatter(StepProgressFormatter): """ Shows detailed dotted progress for both each step of a scenario. Differs from StepProgressFormatter by: * showing scenario names (as prefix scenario step progress) * showing failures after each scenario (if necessary) EXAMPLE: $ behave -f progress3 features Feature with failing scenario # features/failing_scenario.feature Simple scenario with last failing step ....F ----------------------------------------------------------------------- FAILURE in step 'last step fails' (features/failing_scenario.feature:7): Assertion Failed: xxx ----------------------------------------------------------------------- """ name = "progress3" description = "Shows detailed progress for each step of a scenario." indent_size = 2 scenario_prefix = " " * indent_size # -- FORMATTER API: def feature(self, feature): self.current_feature = feature self.stream.write(u"%s # %s" % (feature.name, feature.filename)) def scenario(self, scenario): """Process the next scenario.""" # -- LAST SCENARIO: Report failures (if any). self.report_scenario_completed() # -- NEW SCENARIO: assert not self.failures self.current_scenario = scenario scenario_name = scenario.name if scenario_name: scenario_name += " " self.stream.write(u"%s%s " % (self.scenario_prefix, scenario_name)) self.stream.flush() # -- DISABLED: # def eof(self): # has_scenarios = self.current_feature and self.current_scenario # super(ScenarioStepProgressFormatter, self).eof() # if has_scenarios: # # -- EMPTY-LINE between 2 features. # self.stream.write("\n") # -- PROGRESS FORMATTER DETAILS: # @overriden def report_feature_completed(self): # -- SKIP: self.report_feature_duration() has_scenarios = self.current_feature and self.current_scenario if has_scenarios: # -- EMPTY-LINE between 2 features. self.stream.write("\n") def report_scenario_completed(self): self.report_scenario_progress() self.report_scenario_duration() self.report_failures() self.failures = [] def report_failures(self): if self.failures: separator = "-" * 80 self.stream.write(u"%s\n" % separator) unicode_errors = 0 for step in self.failures: try: self.stream.write(u"FAILURE in step '%s' (%s):\n" % \ (step.name, step.location)) self.stream.write(u"%s\n" % step.error_message) self.stream.write(u"%s\n" % separator) except UnicodeError as e: self.stream.write(u"%s while reporting failure in %s\n" % \ (e.__class__.__name__, step.location)) self.stream.write(u"ERROR: %s\n" % \ _text(e, encoding=self.stream.encoding)) unicode_errors += 1 if unicode_errors: msg = u"HINT: %d unicode errors occured during failure reporting.\n" self.stream.write(msg % unicode_errors) self.stream.flush() behave-1.2.6/behave/formatter/rerun.py0000644000076600000240000000776513244555737020035 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Provides a formatter that simplifies to rerun the failing scenarios of the last test run. It writes a text file with the file locations of the failing scenarios, like: # -- file:rerun.features # RERUN: Failing scenarios during last test run. features/alice.feature:10 features/alice.feature:42 features/bob.feature:67 To rerun the failing scenarios, use: behave @rerun_failing.features Normally, you put the RerunFormatter into the behave configuration file: # -- file:behave.ini [behave] format = rerun outfiles = rerun_failing.features """ from __future__ import absolute_import from datetime import datetime from os.path import relpath import os from behave.formatter.base import Formatter from behave.model_core import Status # ----------------------------------------------------------------------------- # CLASS: RerunFormatter # ----------------------------------------------------------------------------- class RerunFormatter(Formatter): """ Provides formatter class that emits a summary which scenarios failed during the last test run. This output can be used to rerun the tests with the failed scenarios. """ name = "rerun" description = "Emits scenario file locations of failing scenarios" show_timestamp = False show_failed_scenarios_descriptions = False def __init__(self, stream_opener, config): super(RerunFormatter, self).__init__(stream_opener, config) self.failed_scenarios = [] self.current_feature = None def reset(self): self.failed_scenarios = [] self.current_feature = None # -- FORMATTER API: def feature(self, feature): self.current_feature = feature def eof(self): """Called at end of a feature.""" if self.current_feature and self.current_feature.status == Status.failed: # -- COLLECT SCENARIO FAILURES: for scenario in self.current_feature.walk_scenarios(): if scenario.status == Status.failed: self.failed_scenarios.append(scenario) # -- RESET: self.current_feature = None assert self.current_feature is None def close(self): """Called at end of test run.""" stream_name = self.stream_opener.name if self.failed_scenarios: # -- ENSURE: Output stream is open. self.stream = self.open() self.report_scenario_failures() elif stream_name and os.path.exists(stream_name): # -- ON SUCCESS: Remove last rerun file with its failures. os.remove(self.stream_opener.name) # -- FINALLY: self.close_stream() # -- SPECIFIC-API: def report_scenario_failures(self): assert self.failed_scenarios # -- SECTION: Banner message = u"# -- RERUN: %d failing scenarios during last test run.\n" self.stream.write(message % len(self.failed_scenarios)) if self.show_timestamp: now = datetime.now().replace(microsecond=0) self.stream.write("# NOW: %s\n"% now.isoformat(" ")) # -- SECTION: Textual summary in comments. if self.show_failed_scenarios_descriptions: current_feature = None for index, scenario in enumerate(self.failed_scenarios): if current_feature != scenario.filename: if current_feature is not None: self.stream.write(u"#\n") current_feature = scenario.filename short_filename = relpath(scenario.filename, os.getcwd()) self.stream.write(u"# %s\n" % short_filename) self.stream.write(u"# %4d: %s\n" % \ (scenario.line, scenario.name)) self.stream.write("\n") # -- SECTION: Scenario file locations, ala: "alice.feature:10" for scenario in self.failed_scenarios: self.stream.write(u"%s\n" % scenario.location) self.stream.write("\n") behave-1.2.6/behave/formatter/sphinx_steps.py0000644000076600000240000003261213244555737021416 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Provides a formatter that generates Sphinx-based documentation of available step definitions (step implementations). TODO: * Post-processor for step docstrings. * Solution for requires: table, text * i18n keywords .. seealso:: http://sphinx-doc.org/ .. note:: REQUIRES docutils :mod:`docutils` are needed to generate step-label for step references. """ from __future__ import absolute_import, print_function from operator import attrgetter import inspect import os.path import sys from behave.formatter.steps import AbstractStepsFormatter from behave.formatter import sphinx_util from behave.model import Table try: # -- NEEDED FOR: step-labels (and step-refs) from docutils.nodes import fully_normalize_name has_docutils = True except ImportError: has_docutils = False # ----------------------------------------------------------------------------- # HELPER CLASS: # ----------------------------------------------------------------------------- class StepsModule(object): """ Value object to keep track of step definitions that belong to same module. """ def __init__(self, module_name, step_definitions=None): self.module_name = module_name self.step_definitions = step_definitions or [] self._name = None self._filename = None @property def name(self): if self._name is None: # -- DISCOVER ON DEMAND: From step definitions (module). # REQUIRED: To discover complete canonical module name. module = self.module if module: # -- USED-BY: Imported step libraries. module_name = self.module.__name__ else: # -- USED-BY: features/steps/*.py (without __init__.py) module_name = self.module_name self._name = module_name return self._name @property def filename(self): if not self._filename: if self.step_definitions: filename = inspect.getfile(self.step_definitions[0].func) self._filename = os.path.relpath(filename) return self._filename @property def module(self): if self.step_definitions: return inspect.getmodule(self.step_definitions[0].func) return sys.modules.get(self.module_name) @property def module_doc(self): module = self.module if module: return inspect.getdoc(module) return None def append(self, step_definition): self.step_definitions.append(step_definition) # ----------------------------------------------------------------------------- # CLASS: SphinxStepsDocumentGenerator # ----------------------------------------------------------------------------- class SphinxStepsDocumentGenerator(object): """ Provides document generator class that generates Sphinx-based documentation for step definitions. The primary purpose is to: * help the step-library provider/writer * simplify self-documentation of step-libraries EXAMPLE: step_definitions = ... # Collect from step_registry doc_generator = SphinxStepsDocumentGenerator(step_definitions, "output") doc_generator.write_docs() .. seealso:: http://sphinx-doc.org/ """ default_step_definition_doc = """\ .. todo:: Step definition description is missing. """ shows_step_module_info = True shows_step_module_overview = True make_step_index_entries = True make_step_labels = has_docutils document_separator = "# -- DOCUMENT-END " + "-" * 60 step_document_prefix = "step_module." step_heading_prefix = "**Step:** " def __init__(self, step_definitions, destdir=None, stream=None): self.step_definitions = step_definitions self.destdir = destdir self.stream = stream self.document = None @property def stdout_mode(self): """ Indicates that output towards stdout should be used. """ return self.stream is not None @staticmethod def describe_step_definition(step_definition, step_type=None): if not step_type: step_type = step_definition.step_type or "step" if step_type == "step": step_type_text = "Given/When/Then" else: step_type_text = step_type.capitalize() # -- ESCAPE: Some chars required for ReST documents (like backticks) step_text = step_definition.pattern if "`" in step_text: step_text = step_text.replace("`", r"\`") return u"%s %s" % (step_type_text, step_text) def ensure_destdir_exists(self): assert self.destdir if os.path.isfile(self.destdir): print("OOPS: remove %s" % self.destdir) os.remove(self.destdir) if not os.path.exists(self.destdir): os.makedirs(self.destdir) def ensure_document_is_closed(self): if self.document and not self.stdout_mode: self.document.close() self.document = None def discover_step_modules(self): step_modules_map = {} for step_definition in self.step_definitions: assert step_definition.step_type is not None step_filename = step_definition.location.filename step_module = step_modules_map.get(step_filename, None) if not step_module: filename = inspect.getfile(step_definition.func) module_name = inspect.getmodulename(filename) assert module_name, \ "step_definition: %s" % step_definition.location step_module = StepsModule(module_name) step_modules_map[step_filename] = step_module step_module.append(step_definition) step_modules = sorted(step_modules_map.values(), key=attrgetter("name")) for module in step_modules: step_definitions = sorted(module.step_definitions, key=attrgetter("location")) module.step_definitions = step_definitions return step_modules def create_document(self, filename): if not (filename.endswith(".rst") or filename.endswith(".txt")): filename += ".rst" if self.stdout_mode: stream = self.stream document = sphinx_util.DocumentWriter(stream, should_close=False) else: self.ensure_destdir_exists() filename = os.path.join(self.destdir, filename) document = sphinx_util.DocumentWriter.open(filename) return document def write_docs(self): step_modules = self.discover_step_modules() self.write_step_module_index(step_modules) for step_module in step_modules: self.write_step_module(step_module) return len(step_modules) def write_step_module_index(self, step_modules, filename="index.rst"): document = self.create_document(filename) document.write(".. _docid.steps:\n\n") document.write_heading("Step Definitions") document.write("""\ The following step definitions are provided here. ---- """) entries = sorted([self.step_document_prefix + module.name for module in step_modules]) document.write_toctree(entries, maxdepth=1) document.close() if self.stdout_mode: sys.stdout.write("\n%s\n" % self.document_separator) def write_step_module(self, step_module): self.ensure_document_is_closed() document_name = self.step_document_prefix + step_module.name self.document = self.create_document(document_name) self.document.write(".. _docid.steps.%s:\n" % step_module.name) self.document.write_heading(step_module.name, index_id=step_module.name) if self.shows_step_module_info: self.document.write(":Module: %s\n" % step_module.name) self.document.write(":Filename: %s\n" % step_module.filename) self.document.write("\n") if step_module.module_doc: module_doc = step_module.module_doc.strip() self.document.write("%s\n\n" % module_doc) if self.shows_step_module_overview: self.document.write_heading("Step Overview", level=1) self.write_step_module_overview(step_module.step_definitions) self.document.write_heading("Step Definitions", level=1) for step_definition in step_module.step_definitions: self.write_step_definition(step_definition) # -- FINALLY: Clean up resources. self.document.close() self.document = None if self.stdout_mode: sys.stdout.write("\n%s\n" % self.document_separator) def write_step_module_overview(self, step_definitions): assert self.document headings = [u"Step Definition", u"Given", u"When", u"Then", u"Step"] table = Table(headings) step_type_cols = { # -- pylint: disable=bad-whitespace "given": [u" x", u" ", u" ", u" "], "when": [u" ", u" x", u" ", u" "], "then": [u" ", u" ", u" x", u" "], "step": [u" x", u" x", u" x", u" x"], } for step_definition in step_definitions: row = [self.describe_step_definition(step_definition)] row.extend(step_type_cols[step_definition.step_type]) table.add_row(row) self.document.write_table(table) @staticmethod def make_step_definition_index_id(step): if step.step_type == "step": index_kinds = ("Given", "When", "Then", "Step") else: keyword = step.step_type.capitalize() index_kinds = (keyword,) schema = "single: %s%s; %s %s" index_parts = [] for index_kind in index_kinds: keyword = index_kind word = " step" if index_kind == "Step": keyword = "Given/When/Then" word = "" part = schema % (index_kind, word, keyword, step.pattern) index_parts.append(part) joiner = "\n " return joiner + joiner.join(index_parts) def make_step_definition_doc(self, step): doc = inspect.getdoc(step.func) if not doc: doc = self.default_step_definition_doc doc = doc.strip() return doc def write_step_definition(self, step): assert self.document step_text = self.describe_step_definition(step) if step_text.startswith("* "): step_text = step_text[2:] index_id = None if self.make_step_index_entries: index_id = self.make_step_definition_index_id(step) heading = step_text step_label = None if self.step_heading_prefix: heading = self.step_heading_prefix + step_text if has_docutils and self.make_step_labels: # -- ADD STEP-LABEL (supports: step-refs by name) # EXAMPLE: See also :ref:`When my step does "{something}"`. step_label = fully_normalize_name(step_text) # SKIP-HERE: self.document.write(".. _%s:\n\n" % step_label) self.document.write_heading(heading, level=2, index_id=index_id, label=step_label) step_definition_doc = self.make_step_definition_doc(step) self.document.write("%s\n" % step_definition_doc) self.document.write("\n") # ----------------------------------------------------------------------------- # CLASS: SphinxStepsFormatter # ----------------------------------------------------------------------------- class SphinxStepsFormatter(AbstractStepsFormatter): """ Provides formatter class that generates Sphinx-based documentation for all registered step definitions. The primary purpose is to: * help the step-library provider/writer * simplify self-documentation of step-libraries .. note:: Supports dry-run mode. Supports destination directory mode to write multiple documents. """ name = "sphinx.steps" description = "Generate sphinx-based documentation for step definitions." doc_generator_class = SphinxStepsDocumentGenerator def __init__(self, stream_opener, config): super(SphinxStepsFormatter, self).__init__(stream_opener, config) self.destdir = stream_opener.name @property def step_definitions(self): """Derive step definitions from step-registry.""" steps = [] for step_type, step_definitions in self.step_registry.steps.items(): for step in step_definitions: step.step_type = step_type steps.append(step) return steps # -- FORMATTER-API: def close(self): """Called at end of test run.""" if not self.step_registry: self.discover_step_definitions() self.report() # -- SPECIFIC-API: def create_document_generator(self): generator_class = self.doc_generator_class if self.stdout_mode: return generator_class(self.step_definitions, stream=self.stream) # -- OTHERWISE: return generator_class(self.step_definitions, destdir=self.destdir) def report(self): document_generator = self.create_document_generator() document_counts = document_generator.write_docs() if not self.stdout_mode: msg = "%s: Written %s document(s) into directory '%s'.\n" sys.stdout.write(msg % (self.name, document_counts, self.destdir)) behave-1.2.6/behave/formatter/sphinx_util.py0000644000076600000240000001045413244555737021235 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Provides utility function for generating Sphinx-based documentation. """ from __future__ import absolute_import import codecs from behave.textutil import compute_words_maxsize, text as _text import six # ----------------------------------------------------------------------------- # SPHINX OUTPUT GENERATION FUNCTIONS: # ----------------------------------------------------------------------------- class DocumentWriter(object): """ Provides a simple "ReStructured Text Writer" to generate Sphinx-based documentation. """ heading_styles = ["=", "=", "-", "~"] default_encoding = "utf-8" default_toctree_title = "**Contents:**" def __init__(self, stream, filename=None, should_close=True): self.stream = stream self.filename = filename self.should_close = should_close @classmethod def open(cls, filename, encoding=None): encoding = encoding or cls.default_encoding stream = codecs.open(filename, "wb", encoding) return cls(stream, filename) def write(self, *args): return self.stream.write(*args) def close(self): if self.stream and self.should_close: self.stream.close() self.stream = None def write_heading(self, heading, level=0, index_id=None, label=None): assert self.stream assert heading, "Heading should not be empty" assert 0 <= level < len(self.heading_styles) if level >= len(self.heading_styles): level = len(self.heading_styles) - 1 heading_size = len(heading) heading_style = self.heading_styles[level] if level == 0 and heading_size < 70: heading_size = 70 separator = heading_style * heading_size if index_id: if isinstance(index_id, (list, tuple)): index_id = ", ".join(index_id) self.stream.write(".. index:: %s\n\n" % index_id) if label: self.stream.write(".. _%s:\n\n" % label) if level == 0: self.stream.write("%s\n" % separator) self.stream.write("%s\n" % heading) self.stream.write("%s\n" % separator) self.stream.write("\n") def write_toctree(self, entries, title=None, maxdepth=2): if title is None: title = self.default_toctree_title line_prefix = " " * 4 if title: self.stream.write("%s\n\n" % title) self.stream.write(".. toctree::\n") self.stream.write("%s:maxdepth: %d\n\n" % (line_prefix, maxdepth)) for entry in entries: self.stream.write("%s%s\n" % (line_prefix, entry)) self.stream.write("\n") def write_table(self, table): """ Write a ReST simple table. EXAMPLE: ========================================= ===== ===== ===== ===== Step Definition Given When Then Step ========================================= ===== ===== ===== ===== Given a file named "{filename}" contains Then the file "{filename}" should ... ========================================= ===== ===== ===== ===== :param table: Table to render (as `behave.model.Table`) .. todo:: Column alignments """ assert self.stream # -- STEP: Determine table layout dimensions cols_size = [] separator_parts = [] row_schema_parts = [] for col_index, heading in enumerate(table.headings): column = [six.text_type(row[col_index]) for row in table.rows] column.append(heading) column_size = compute_words_maxsize(column) cols_size.append(column_size) separator_parts.append("=" * column_size) row_schema_parts.append("%-" + _text(column_size) + "s") separator = " ".join(separator_parts) + "\n" row_schema = " ".join(row_schema_parts) + "\n" self.stream.write("\n") # -- ENSURE: Empty line before table start. self.stream.write(separator) self.stream.write(row_schema % tuple(table.headings)) self.stream.write(separator) for row in table.rows: self.stream.write(row_schema % tuple(row)) self.stream.write(separator) self.stream.write("\n") # -- ENSURE: Empty line after table end. behave-1.2.6/behave/formatter/steps.py0000644000076600000240000004461013244555737020026 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Provides a formatter that provides an overview of available step definitions (step implementations). """ from __future__ import absolute_import from operator import attrgetter import inspect from six.moves import zip from behave.formatter.base import Formatter from behave.step_registry import StepRegistry, registry from behave.textutil import \ compute_words_maxsize, indent, make_indentation, text as _text from behave import i18n # ----------------------------------------------------------------------------- # CLASS: AbstractStepsFormatter # ----------------------------------------------------------------------------- class AbstractStepsFormatter(Formatter): """ Provides a formatter base class that provides the common functionality for formatter classes that operate on step definitions (implementations). .. note:: Supports behave dry-run mode. """ step_types = ("given", "when", "then", "step") def __init__(self, stream_opener, config): super(AbstractStepsFormatter, self).__init__(stream_opener, config) self.step_registry = None self.current_feature = None self.shows_location = config.show_source def reset(self): self.step_registry = None self.current_feature = None def discover_step_definitions(self): if self.step_registry is None: self.step_registry = StepRegistry() for step_type in registry.steps: step_definitions = tuple(registry.steps[step_type]) for step_definition in step_definitions: step_definition.step_type = step_type self.step_registry.steps[step_type] = step_definitions # -- FORMATTER API: def feature(self, feature): self.current_feature = feature if not self.step_registry: # -- ONLY-ONCE: self.discover_step_definitions() def eof(self): """Called at end of a feature.""" self.current_feature = None def close(self): """Called at end of test run.""" if not self.step_registry: self.discover_step_definitions() if self.step_registry: # -- ENSURE: Output stream is open. self.stream = self.open() self.report() # -- FINALLY: self.close_stream() # -- REPORT SPECIFIC-API: def report(self): raise NotImplementedError() # pylint: disable=no-self-use def describe_step_definition(self, step_definition, step_type=None): if not step_type: step_type = step_definition.step_type assert step_type return u"@%s('%s')" % (step_type, step_definition.pattern) # ----------------------------------------------------------------------------- # CLASS: StepsFormatter # ----------------------------------------------------------------------------- class StepsFormatter(AbstractStepsFormatter): """ Provides formatter class that provides an overview which step definitions are available. EXAMPLE: $ behave --dry-run -f steps features/ GIVEN STEP DEFINITIONS[21]: Given a new working directory Given I use the current directory as working directory Given a file named "{filename}" with ... Given a step passes Given a step fails WHEN STEP DEFINITIONS[14]: When I run "{command}" ... When a step passes When a step fails THEN STEP DEFINITIONS[45]: Then the command should fail with returncode="{result:int}" Then it should pass with Then it should fail with Then the command output should contain "{text}" ... Then a step passes Then a step fails GENERIC STEP DEFINITIONS[13]: * I remove the directory "{directory}" * a file named "{filename}" exists * a file named "{filename}" does not exist ... * a step passes * a step fails .. note:: Supports behave dry-run mode. """ name = "steps" description = "Shows step definitions (step implementations)." shows_location = True min_location_column = 40 # -- REPORT SPECIFIC-API: def report(self): self.report_steps_by_type() def report_steps_by_type(self): """Show an overview of the existing step implementations per step type. """ # pylint: disable=too-many-branches assert set(self.step_types) == set(self.step_registry.steps.keys()) language = self.config.lang or "en" language_keywords = i18n.languages[language] for step_type in self.step_types: steps = list(self.step_registry.steps[step_type]) if step_type != "step": steps.extend(self.step_registry.steps["step"]) if not steps: continue # -- PREPARE REPORT: For a step-type. step_type_name = step_type.upper() if step_type == "step": step_keyword = "*" step_type_name = "GENERIC" else: # step_keyword = step_type.capitalize() keywords = language_keywords[step_type] if keywords[0] == u"*": assert len(keywords) > 1 step_keyword = keywords[1] else: step_keyword = keywords[0] steps_text = [u"%s %s" % (step_keyword, step.pattern) for step in steps] if self.shows_location: max_size = compute_words_maxsize(steps_text) if max_size < self.min_location_column: max_size = self.min_location_column schema = u" %-" + _text(max_size) + "s # %s\n" else: schema = u" %s\n" # -- REPORT: message = "%s STEP DEFINITIONS[%s]:\n" self.stream.write(message % (step_type_name, len(steps))) for step, step_text in zip(steps, steps_text): if self.shows_location: self.stream.write(schema % (step_text, step.location)) else: self.stream.write(schema % step_text) self.stream.write("\n") # ----------------------------------------------------------------------------- # CLASS: StepsDocFormatter # ----------------------------------------------------------------------------- class StepsDocFormatter(AbstractStepsFormatter): """ Provides formatter class that shows the documentation of all registered step definitions. The primary purpose is to provide help for a test writer. EXAMPLE: $ behave --dry-run -f steps.doc features/ @given('a file named "{filename}" with') Function: step_a_file_named_filename_with() Location: behave4cmd0/command_steps.py:50 Creates a textual file with the content provided as docstring. @when('I run "{command}"') Function: step_i_run_command() Location: behave4cmd0/command_steps.py:80 Run a command as subprocess, collect its output and returncode. @step('a file named "{filename}" exists') Function: step_file_named_filename_exists() Location: behave4cmd0/command_steps.py:305 Verifies that a file with this filename exists. .. code-block:: gherkin Given a file named "abc.txt" exists When a file named "abc.txt" exists ... .. note:: Supports behave dry-run mode. """ name = "steps.doc" description = "Shows documentation for step definitions." shows_location = True shows_function_name = True ordered_by_location = True doc_prefix = make_indentation(4) # -- REPORT SPECIFIC-API: def report(self): self.report_step_definition_docs() self.stream.write("\n") def report_step_definition_docs(self): step_definitions = [] for step_type in self.step_types: for step_definition in self.step_registry.steps[step_type]: # step_definition.step_type = step_type assert step_definition.step_type is not None step_definitions.append(step_definition) if self.ordered_by_location: step_definitions = sorted(step_definitions, key=attrgetter("location")) for step_definition in step_definitions: self.write_step_definition(step_definition) def write_step_definition(self, step_definition): step_definition_text = self.describe_step_definition(step_definition) self.stream.write(u"%s\n" % step_definition_text) doc = inspect.getdoc(step_definition.func) func_name = step_definition.func.__name__ if self.shows_function_name and func_name not in ("step", "impl"): self.stream.write(u" Function: %s()\n" % func_name) if self.shows_location: self.stream.write(u" Location: %s\n" % step_definition.location) if doc: doc = doc.strip() self.stream.write(indent(doc, self.doc_prefix)) self.stream.write("\n") self.stream.write("\n") # ----------------------------------------------------------------------------- # CLASS: StepsCatalogFormatter # ----------------------------------------------------------------------------- class StepsCatalogFormatter(StepsDocFormatter): """ Provides formatter class that shows the documentation of all registered step definitions. The primary purpose is to provide help for a test writer. In order to ease work for non-programmer testers, the technical details of the steps (i.e. function name, source location) are ommited and the steps are shown as they would apprear in a feature file (no noisy '@', or '(', etc.). Also, the output is sorted by step type (Given, When, Then) Generic step definitions are listed with all three step types. EXAMPLE: $ behave --dry-run -f steps.catalog features/ Given a file named "{filename}" with Creates a textual file with the content provided as docstring. When I run "{command}" Run a command as subprocess, collect its output and returncode. Given a file named "{filename}" exists When a file named "{filename}" exists Then a file named "{filename}" exists Verifies that a file with this filename exists. .. code-block:: gherkin Given a file named "abc.txt" exists When a file named "abc.txt" exists ... .. note:: Supports behave dry-run mode. """ name = "steps.catalog" description = "Shows non-technical documentation for step definitions." shows_location = False shows_function_name = False ordered_by_location = False doc_prefix = make_indentation(4) def describe_step_definition(self, step_definition, step_type=None): if not step_type: step_type = step_definition.step_type assert step_type desc = [] if step_type == "step": for step_type1 in self.step_types[:-1]: text = u"%5s %s" % (step_type1.title(), step_definition.pattern) desc.append(text) else: desc.append(u"%s %s" % (step_type.title(), step_definition.pattern)) return '\n'.join(desc) # ----------------------------------------------------------------------------- # CLASS: StepsUsageFormatter # ----------------------------------------------------------------------------- class StepsUsageFormatter(AbstractStepsFormatter): """ Provides formatter class that shows how step definitions are used by steps. EXAMPLE: $ behave --dry-run -f steps.usage features/ ... .. note:: Supports behave dry-run mode. """ name = "steps.usage" description = "Shows how step definitions are used by steps." doc_prefix = make_indentation(4) min_location_column = 40 def __init__(self, stream_opener, config): super(StepsUsageFormatter, self).__init__(stream_opener, config) self.step_usage_database = {} self.undefined_steps = [] def reset(self): super(StepsUsageFormatter, self).reset() self.step_usage_database = {} self.undefined_steps = [] # pylint: disable=invalid-name def get_step_type_for_step_definition(self, step_definition): step_type = step_definition.step_type if not step_type: # -- DETERMINE STEP-TYPE FROM STEP-REGISTRY: assert self.step_registry for step_type, values in self.step_registry.steps.items(): if step_definition in values: return step_type # -- OTHERWISE: step_type = "step" return step_type # pylint: enable=invalid-name def select_unused_step_definitions(self): step_definitions = set() for step_type, values in self.step_registry.steps.items(): step_definitions.update(values) used_step_definitions = set(self.step_usage_database.keys()) unused_step_definitions = step_definitions - used_step_definitions return unused_step_definitions def update_usage_database(self, step_definition, step): matching_steps = self.step_usage_database.get(step_definition, None) if matching_steps is None: assert step_definition.step_type is not None matching_steps = self.step_usage_database[step_definition] = [] # -- AVOID DUPLICATES: From Scenario Outlines if not steps_contain(matching_steps, step): matching_steps.append(step) def update_usage_database_for_step(self, step): step_definition = self.step_registry.find_step_definition(step) if step_definition: self.update_usage_database(step_definition, step) # elif step not in self.undefined_steps: elif not steps_contain(self.undefined_steps, step): # -- AVOID DUPLICATES: From Scenario Outlines self.undefined_steps.append(step) # pylint: disable=invalid-name def update_usage_database_for_feature(self, feature): # -- PROCESS BACKGROUND (if exists): Use Background steps only once. if feature.background: for step in feature.background.steps: self.update_usage_database_for_step(step) # -- PROCESS SCENARIOS: Without background steps. for scenario in feature.walk_scenarios(): for step in scenario.steps: self.update_usage_database_for_step(step) # pylint: enable=invalid-name # -- FORMATTER API: def feature(self, feature): super(StepsUsageFormatter, self).feature(feature) self.update_usage_database_for_feature(feature) # -- REPORT API: def report(self): self.report_used_step_definitions() self.report_unused_step_definitions() self.report_undefined_steps() self.stream.write("\n") # -- REPORT SPECIFIC-API: def report_used_step_definitions(self): # -- STEP: Used step definitions. # ORDERING: Sort step definitions by file location. get_location = lambda x: x[0].location step_definition_items = self.step_usage_database.items() step_definition_items = sorted(step_definition_items, key=get_location) for step_definition, steps in step_definition_items: stepdef_text = self.describe_step_definition(step_definition) steps_text = [u" %s %s" % (step.keyword, step.name) for step in steps] steps_text.append(stepdef_text) max_size = compute_words_maxsize(steps_text) if max_size < self.min_location_column: max_size = self.min_location_column schema = u"%-" + _text(max_size) + "s # %s\n" self.stream.write(schema % (stepdef_text, step_definition.location)) schema = u"%-" + _text(max_size) + "s # %s\n" for step, step_text in zip(steps, steps_text): self.stream.write(schema % (step_text, step.location)) self.stream.write("\n") def report_unused_step_definitions(self): unused_step_definitions = self.select_unused_step_definitions() if not unused_step_definitions: return # -- STEP: Prepare report for unused step definitions. # ORDERING: Sort step definitions by file location. get_location = lambda x: x.location step_definitions = sorted(unused_step_definitions, key=get_location) step_texts = [self.describe_step_definition(step_definition) for step_definition in step_definitions] max_size = compute_words_maxsize(step_texts) if max_size < self.min_location_column-2: max_size = self.min_location_column-2 # -- STEP: Write report. schema = u" %-" + _text(max_size) + "s # %s\n" self.stream.write("UNUSED STEP DEFINITIONS[%d]:\n" % len(step_texts)) for step_definition, step_text in zip(step_definitions, step_texts): self.stream.write(schema % (step_text, step_definition.location)) def report_undefined_steps(self): if not self.undefined_steps: return # -- STEP: Undefined steps. undefined_steps = sorted(self.undefined_steps, key=attrgetter("location")) steps_text = [u" %s %s" % (step.keyword, step.name) for step in undefined_steps] max_size = compute_words_maxsize(steps_text) if max_size < self.min_location_column: max_size = self.min_location_column self.stream.write("\nUNDEFINED STEPS[%d]:\n" % len(steps_text)) schema = u"%-" + _text(max_size) + "s # %s\n" for step, step_text in zip(undefined_steps, steps_text): self.stream.write(schema % (step_text, step.location)) # ----------------------------------------------------------------------------- # UTILITY FUNCTIONS: # ----------------------------------------------------------------------------- def steps_contain(steps, step): for other_step in steps: if step == other_step and step.location == other_step.location: # -- NOTE: Step comparison does not take location into account. return True # -- OTHERWISE: Not contained yet (or step in other location). return False behave-1.2.6/behave/formatter/tags.py0000644000076600000240000001356213244555737017630 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Collects data how often a tag count is used and where. EXAMPLE: $ behave --dry-run -f tag_counts features/ """ from __future__ import absolute_import import six from behave.formatter.base import Formatter from behave.textutil import compute_words_maxsize, text as _text # ----------------------------------------------------------------------------- # CLASS: AbstractTagsFormatter # ----------------------------------------------------------------------------- class AbstractTagsFormatter(Formatter): """ Abstract base class for formatter that collect information on tags. .. note:: Supports dry-run mode for faster feedback. """ with_tag_inheritance = False def __init__(self, stream_opener, config): super(AbstractTagsFormatter, self).__init__(stream_opener, config) self.tag_counts = {} self._uri = None self._feature_tags = None self._scenario_outline_tags = None # -- Formatter API: def uri(self, uri): self._uri = uri def feature(self, feature): self._feature_tags = feature.tags self.record_tags(feature.tags, feature) def scenario(self, scenario): tags = set(scenario.tags) if self.with_tag_inheritance: tags.update(self._feature_tags) self.record_tags(tags, scenario) def close(self): """Emit tag count reports.""" # -- ENSURE: Output stream is open. self.stream = self.open() self.report_tags() self.close_stream() # -- SPECIFIC API: def record_tags(self, tags, model_element): for tag in tags: if tag not in self.tag_counts: self.tag_counts[tag] = [] self.tag_counts[tag].append(model_element) def report_tags(self): raise NotImplementedError # ----------------------------------------------------------------------------- # CLASS: TagsFormatter # ----------------------------------------------------------------------------- class TagsFormatter(AbstractTagsFormatter): """ Formatter that collects information: * which tags exist * how often a tag is used (counts) * usage context/category: feature, scenario, ... .. note:: Supports dry-run mode for faster feedback. """ name = "tags" description = "Shows tags (and how often they are used)." with_tag_inheritance = False show_ordered_by_usage = False def report_tags(self): self.report_tag_counts() if self.show_ordered_by_usage: self.report_tag_counts_by_usage() @staticmethod def get_tag_count_details(tag_count): details = {} for element in tag_count: category = element.keyword.lower() if category not in details: details[category] = 0 details[category] += 1 parts = [] if len(details) == 1: parts.append(list(details.keys())[0]) else: for category in sorted(details): text = u"%s: %d" % (category, details[category]) parts.append(text) return ", ".join(parts) def report_tag_counts(self): # -- PREPARE REPORT: ordered_tags = sorted(list(self.tag_counts.keys())) tag_maxsize = compute_words_maxsize(ordered_tags) schema = " @%-" + _text(tag_maxsize) + "s %4d (used for %s)\n" # -- EMIT REPORT: self.stream.write("TAG COUNTS (alphabetically sorted):\n") for tag in ordered_tags: tag_data = self.tag_counts[tag] counts = len(tag_data) details = self.get_tag_count_details(tag_data) self.stream.write(schema % (tag, counts, details)) self.stream.write("\n") def report_tag_counts_by_usage(self): # -- PREPARE REPORT: compare_tag_counts_size = lambda x: len(self.tag_counts[x]) ordered_tags = sorted(list(self.tag_counts.keys()), key=compare_tag_counts_size) tag_maxsize = compute_words_maxsize(ordered_tags) schema = " @%-" + _text(tag_maxsize) + "s %4d (used for %s)\n" # -- EMIT REPORT: self.stream.write("TAG COUNTS (most often used first):\n") for tag in ordered_tags: tag_data = self.tag_counts[tag] counts = len(tag_data) details = self.get_tag_count_details(tag_data) self.stream.write(schema % (tag, counts, details)) self.stream.write("\n") # ----------------------------------------------------------------------------- # CLASS: TagsLocationFormatter # ----------------------------------------------------------------------------- class TagsLocationFormatter(AbstractTagsFormatter): """ Formatter that collects information: * which tags exist * where the tags are used (location) .. note:: Supports dry-run mode for faster feedback. """ name = "tags.location" description = "Shows tags and the location where they are used." with_tag_inheritance = False def report_tags(self): self.report_tags_by_locations() def report_tags_by_locations(self): # -- PREPARE REPORT: locations = set() for tag_elements in self.tag_counts.values(): locations.update([six.text_type(x.location) for x in tag_elements]) location_column_size = compute_words_maxsize(locations) schema = u" %-" + _text(location_column_size) + "s %s\n" # -- EMIT REPORT: self.stream.write("TAG LOCATIONS (alphabetically ordered):\n") for tag in sorted(self.tag_counts): self.stream.write(" @%s:\n" % tag) for element in self.tag_counts[tag]: info = u"%s: %s" % (element.keyword, element.name) self.stream.write(schema % (element.location, info)) self.stream.write("\n") self.stream.write("\n") behave-1.2.6/behave/i18n.py0000644000076600000240000006657113244555737015456 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- # -- FILE GENERATED BY: convert_i18n_yaml.py with i18n.yml # pylint: disable=line-too-long languages = \ {'ar': {'and': [u'*', u'\u0648'], 'background': [u'\u0627\u0644\u062e\u0644\u0641\u064a\u0629'], 'but': [u'*', u'\u0644\u0643\u0646'], 'examples': [u'\u0627\u0645\u062b\u0644\u0629'], 'feature': [u'\u062e\u0627\u0635\u064a\u0629'], 'given': [u'*', u'\u0628\u0641\u0631\u0636'], 'name': [u'Arabic'], 'native': [u'\u0627\u0644\u0639\u0631\u0628\u064a\u0629'], 'scenario': [u'\u0633\u064a\u0646\u0627\u0631\u064a\u0648'], 'scenario_outline': [u'\u0633\u064a\u0646\u0627\u0631\u064a\u0648 \u0645\u062e\u0637\u0637'], 'then': [u'*', u'\u0627\u0630\u0627\u064b', u'\u062b\u0645'], 'when': [u'*', u'\u0645\u062a\u0649', u'\u0639\u0646\u062f\u0645\u0627']}, 'bg': {'and': [u'*', u'\u0418'], 'background': [u'\u041f\u0440\u0435\u0434\u0438\u0441\u0442\u043e\u0440\u0438\u044f'], 'but': [u'*', u'\u041d\u043e'], 'examples': [u'\u041f\u0440\u0438\u043c\u0435\u0440\u0438'], 'feature': [u'\u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u043d\u043e\u0441\u0442'], 'given': [u'*', u'\u0414\u0430\u0434\u0435\u043d\u043e'], 'name': [u'Bulgarian'], 'native': [u'\u0431\u044a\u043b\u0433\u0430\u0440\u0441\u043a\u0438'], 'scenario': [u'\u0421\u0446\u0435\u043d\u0430\u0440\u0438\u0439'], 'scenario_outline': [u'\u0420\u0430\u043c\u043a\u0430 \u043d\u0430 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439'], 'then': [u'*', u'\u0422\u043e'], 'when': [u'*', u'\u041a\u043e\u0433\u0430\u0442\u043e']}, 'ca': {'and': [u'*', u'I'], 'background': [u'Rerefons', u'Antecedents'], 'but': [u'*', u'Per\xf2'], 'examples': [u'Exemples'], 'feature': [u'Caracter\xedstica', u'Funcionalitat'], 'given': [u'*', u'Donat', u'Donada', u'At\xe8s', u'Atesa'], 'name': [u'Catalan'], 'native': [u'catal\xe0'], 'scenario': [u'Escenari'], 'scenario_outline': [u"Esquema de l'escenari"], 'then': [u'*', u'Aleshores', u'Cal'], 'when': [u'*', u'Quan']}, 'cs': {'and': [u'*', u'A', u'A tak\xe9'], 'background': [u'Pozad\xed', u'Kontext'], 'but': [u'*', u'Ale'], 'examples': [u'P\u0159\xedklady'], 'feature': [u'Po\u017eadavek'], 'given': [u'*', u'Pokud', u'Za p\u0159edpokladu'], 'name': [u'Czech'], 'native': [u'\u010cesky'], 'scenario': [u'Sc\xe9n\xe1\u0159'], 'scenario_outline': [u'N\xe1\u010drt Sc\xe9n\xe1\u0159e', u'Osnova sc\xe9n\xe1\u0159e'], 'then': [u'*', u'Pak'], 'when': [u'*', u'Kdy\u017e']}, 'cy-GB': {'and': [u'*', u'A'], 'background': [u'Cefndir'], 'but': [u'*', u'Ond'], 'examples': [u'Enghreifftiau'], 'feature': [u'Arwedd'], 'given': [u'*', u'Anrhegedig a'], 'name': [u'Welsh'], 'native': [u'Cymraeg'], 'scenario': [u'Scenario'], 'scenario_outline': [u'Scenario Amlinellol'], 'then': [u'*', u'Yna'], 'when': [u'*', u'Pryd']}, 'da': {'and': [u'*', u'Og'], 'background': [u'Baggrund'], 'but': [u'*', u'Men'], 'examples': [u'Eksempler'], 'feature': [u'Egenskab'], 'given': [u'*', u'Givet'], 'name': [u'Danish'], 'native': [u'dansk'], 'scenario': [u'Scenarie'], 'scenario_outline': [u'Abstrakt Scenario'], 'then': [u'*', u'S\xe5'], 'when': [u'*', u'N\xe5r']}, 'de': {'and': [u'*', u'Und'], 'background': [u'Grundlage'], 'but': [u'*', u'Aber'], 'examples': [u'Beispiele'], 'feature': [u'Funktionalit\xe4t'], 'given': [u'*', u'Angenommen', u'Gegeben sei'], 'name': [u'German'], 'native': [u'Deutsch'], 'scenario': [u'Szenario'], 'scenario_outline': [u'Szenariogrundriss'], 'then': [u'*', u'Dann'], 'when': [u'*', u'Wenn']}, 'en': {'and': [u'*', u'And'], 'background': [u'Background'], 'but': [u'*', u'But'], 'examples': [u'Examples', u'Scenarios'], 'feature': [u'Feature'], 'given': [u'*', u'Given'], 'name': [u'English'], 'native': [u'English'], 'scenario': [u'Scenario'], 'scenario_outline': [u'Scenario Outline', u'Scenario Template'], 'then': [u'*', u'Then'], 'when': [u'*', u'When']}, 'en-Scouse': {'and': [u'*', u'An'], 'background': [u'Dis is what went down'], 'but': [u'*', u'Buh'], 'examples': [u'Examples'], 'feature': [u'Feature'], 'given': [u'*', u'Givun', u'Youse know when youse got'], 'name': [u'Scouse'], 'native': [u'Scouse'], 'scenario': [u'The thing of it is'], 'scenario_outline': [u'Wharrimean is'], 'then': [u'*', u'Dun', u'Den youse gotta'], 'when': [u'*', u'Wun', u'Youse know like when']}, 'en-au': {'and': [u'*', u'N'], 'background': [u'Background'], 'but': [u'*', u'Cept'], 'examples': [u'Cobber'], 'feature': [u'Crikey'], 'given': [u'*', u'Ya know how'], 'name': [u'Australian'], 'native': [u'Australian'], 'scenario': [u'Mate'], 'scenario_outline': [u'Blokes'], 'then': [u'*', u'Ya gotta'], 'when': [u'*', u'When']}, 'en-lol': {'and': [u'*', u'AN'], 'background': [u'B4'], 'but': [u'*', u'BUT'], 'examples': [u'EXAMPLZ'], 'feature': [u'OH HAI'], 'given': [u'*', u'I CAN HAZ'], 'name': [u'LOLCAT'], 'native': [u'LOLCAT'], 'scenario': [u'MISHUN'], 'scenario_outline': [u'MISHUN SRSLY'], 'then': [u'*', u'DEN'], 'when': [u'*', u'WEN']}, 'en-pirate': {'and': [u'*', u'Aye'], 'background': [u'Yo-ho-ho'], 'but': [u'*', u'Avast!'], 'examples': [u'Dead men tell no tales'], 'feature': [u'Ahoy matey!'], 'given': [u'*', u'Gangway!'], 'name': [u'Pirate'], 'native': [u'Pirate'], 'scenario': [u'Heave to'], 'scenario_outline': [u'Shiver me timbers'], 'then': [u'*', u'Let go and haul'], 'when': [u'*', u'Blimey!']}, 'en-tx': {'and': [u'*', u"And y'all"], 'background': [u'Background'], 'but': [u'*', u"But y'all"], 'examples': [u'Examples'], 'feature': [u'Feature'], 'given': [u'*', u"Given y'all"], 'name': [u'Texan'], 'native': [u'Texan'], 'scenario': [u'Scenario'], 'scenario_outline': [u"All y'all"], 'then': [u'*', u"Then y'all"], 'when': [u'*', u"When y'all"]}, 'eo': {'and': [u'*', u'Kaj'], 'background': [u'Fono'], 'but': [u'*', u'Sed'], 'examples': [u'Ekzemploj'], 'feature': [u'Trajto'], 'given': [u'*', u'Donita\u0135o'], 'name': [u'Esperanto'], 'native': [u'Esperanto'], 'scenario': [u'Scenaro'], 'scenario_outline': [u'Konturo de la scenaro'], 'then': [u'*', u'Do'], 'when': [u'*', u'Se']}, 'es': {'and': [u'*', u'Y'], 'background': [u'Antecedentes'], 'but': [u'*', u'Pero'], 'examples': [u'Ejemplos'], 'feature': [u'Caracter\xedstica'], 'given': [u'*', u'Dado', u'Dada', u'Dados', u'Dadas'], 'name': [u'Spanish'], 'native': [u'espa\xf1ol'], 'scenario': [u'Escenario'], 'scenario_outline': [u'Esquema del escenario'], 'then': [u'*', u'Entonces'], 'when': [u'*', u'Cuando']}, 'et': {'and': [u'*', u'Ja'], 'background': [u'Taust'], 'but': [u'*', u'Kuid'], 'examples': [u'Juhtumid'], 'feature': [u'Omadus'], 'given': [u'*', u'Eeldades'], 'name': [u'Estonian'], 'native': [u'eesti keel'], 'scenario': [u'Stsenaarium'], 'scenario_outline': [u'Raamstsenaarium'], 'then': [u'*', u'Siis'], 'when': [u'*', u'Kui']}, 'fi': {'and': [u'*', u'Ja'], 'background': [u'Tausta'], 'but': [u'*', u'Mutta'], 'examples': [u'Tapaukset'], 'feature': [u'Ominaisuus'], 'given': [u'*', u'Oletetaan'], 'name': [u'Finnish'], 'native': [u'suomi'], 'scenario': [u'Tapaus'], 'scenario_outline': [u'Tapausaihio'], 'then': [u'*', u'Niin'], 'when': [u'*', u'Kun']}, 'fr': {'and': [u'*', u'Et'], 'background': [u'Contexte'], 'but': [u'*', u'Mais'], 'examples': [u'Exemples'], 'feature': [u'Fonctionnalit\xe9'], 'given': [u'*', u'Soit', u'Etant donn\xe9', u'Etant donn\xe9e', u'Etant donn\xe9s', u'Etant donn\xe9es', u'\xc9tant donn\xe9', u'\xc9tant donn\xe9e', u'\xc9tant donn\xe9s', u'\xc9tant donn\xe9es'], 'name': [u'French'], 'native': [u'fran\xe7ais'], 'scenario': [u'Sc\xe9nario'], 'scenario_outline': [u'Plan du sc\xe9nario', u'Plan du Sc\xe9nario'], 'then': [u'*', u'Alors'], 'when': [u'*', u'Quand', u'Lorsque', u"Lorsqu'<"]}, 'gl': {'and': [u'*', u'E'], 'background': [u'Contexto'], 'but': [u'*', u'Mais', u'Pero'], 'examples': [u'Exemplos'], 'feature': [u'Caracter\xedstica'], 'given': [u'*', u'Dado', u'Dada', u'Dados', u'Dadas'], 'name': [u'Galician'], 'native': [u'galego'], 'scenario': [u'Escenario'], 'scenario_outline': [u'Esbozo do escenario'], 'then': [u'*', u'Ent\xf3n', u'Logo'], 'when': [u'*', u'Cando']}, 'he': {'and': [u'*', u'\u05d5\u05d2\u05dd'], 'background': [u'\u05e8\u05e7\u05e2'], 'but': [u'*', u'\u05d0\u05d1\u05dc'], 'examples': [u'\u05d3\u05d5\u05d2\u05de\u05d0\u05d5\u05ea'], 'feature': [u'\u05ea\u05db\u05d5\u05e0\u05d4'], 'given': [u'*', u'\u05d1\u05d4\u05d9\u05e0\u05ea\u05df'], 'name': [u'Hebrew'], 'native': [u'\u05e2\u05d1\u05e8\u05d9\u05ea'], 'scenario': [u'\u05ea\u05e8\u05d7\u05d9\u05e9'], 'scenario_outline': [u'\u05ea\u05d1\u05e0\u05d9\u05ea \u05ea\u05e8\u05d7\u05d9\u05e9'], 'then': [u'*', u'\u05d0\u05d6', u'\u05d0\u05d6\u05d9'], 'when': [u'*', u'\u05db\u05d0\u05e9\u05e8']}, 'hr': {'and': [u'*', u'I'], 'background': [u'Pozadina'], 'but': [u'*', u'Ali'], 'examples': [u'Primjeri', u'Scenariji'], 'feature': [u'Osobina', u'Mogu\u0107nost', u'Mogucnost'], 'given': [u'*', u'Zadan', u'Zadani', u'Zadano'], 'name': [u'Croatian'], 'native': [u'hrvatski'], 'scenario': [u'Scenarij'], 'scenario_outline': [u'Skica', u'Koncept'], 'then': [u'*', u'Onda'], 'when': [u'*', u'Kada', u'Kad']}, 'hu': {'and': [u'*', u'\xc9s'], 'background': [u'H\xe1tt\xe9r'], 'but': [u'*', u'De'], 'examples': [u'P\xe9ld\xe1k'], 'feature': [u'Jellemz\u0151'], 'given': [u'*', u'Amennyiben', u'Adott'], 'name': [u'Hungarian'], 'native': [u'magyar'], 'scenario': [u'Forgat\xf3k\xf6nyv'], 'scenario_outline': [u'Forgat\xf3k\xf6nyv v\xe1zlat'], 'then': [u'*', u'Akkor'], 'when': [u'*', u'Majd', u'Ha', u'Amikor']}, 'id': {'and': [u'*', u'Dan'], 'background': [u'Dasar'], 'but': [u'*', u'Tapi'], 'examples': [u'Contoh'], 'feature': [u'Fitur'], 'given': [u'*', u'Dengan'], 'name': [u'Indonesian'], 'native': [u'Bahasa Indonesia'], 'scenario': [u'Skenario'], 'scenario_outline': [u'Skenario konsep'], 'then': [u'*', u'Maka'], 'when': [u'*', u'Ketika']}, 'is': {'and': [u'*', u'Og'], 'background': [u'Bakgrunnur'], 'but': [u'*', u'En'], 'examples': [u'D\xe6mi', u'Atbur\xf0ar\xe1sir'], 'feature': [u'Eiginleiki'], 'given': [u'*', u'Ef'], 'name': [u'Icelandic'], 'native': [u'\xcdslenska'], 'scenario': [u'Atbur\xf0ar\xe1s'], 'scenario_outline': [u'L\xfdsing Atbur\xf0ar\xe1sar', u'L\xfdsing D\xe6ma'], 'then': [u'*', u'\xde\xe1'], 'when': [u'*', u'\xdeegar']}, 'it': {'and': [u'*', u'E'], 'background': [u'Contesto'], 'but': [u'*', u'Ma'], 'examples': [u'Esempi'], 'feature': [u'Funzionalit\xe0'], 'given': [u'*', u'Dato', u'Data', u'Dati', u'Date'], 'name': [u'Italian'], 'native': [u'italiano'], 'scenario': [u'Scenario'], 'scenario_outline': [u'Schema dello scenario'], 'then': [u'*', u'Allora'], 'when': [u'*', u'Quando']}, 'ja': {'and': [u'*', u'\u304b\u3064<'], 'background': [u'\u80cc\u666f'], 'but': [u'*', u'\u3057\u304b\u3057<', u'\u4f46\u3057<', u'\u305f\u3060\u3057<'], 'examples': [u'\u4f8b', u'\u30b5\u30f3\u30d7\u30eb'], 'feature': [u'\u30d5\u30a3\u30fc\u30c1\u30e3', u'\u6a5f\u80fd'], 'given': [u'*', u'\u524d\u63d0<'], 'name': [u'Japanese'], 'native': [u'\u65e5\u672c\u8a9e'], 'scenario': [u'\u30b7\u30ca\u30ea\u30aa'], 'scenario_outline': [u'\u30b7\u30ca\u30ea\u30aa\u30a2\u30a6\u30c8\u30e9\u30a4\u30f3', u'\u30b7\u30ca\u30ea\u30aa\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8', u'\u30c6\u30f3\u30d7\u30ec', u'\u30b7\u30ca\u30ea\u30aa\u30c6\u30f3\u30d7\u30ec'], 'then': [u'*', u'\u306a\u3089\u3070<'], 'when': [u'*', u'\u3082\u3057<']}, 'ko': {'and': [u'*', u'\uadf8\ub9ac\uace0<'], 'background': [u'\ubc30\uacbd'], 'but': [u'*', u'\ud558\uc9c0\ub9cc<', u'\ub2e8<'], 'examples': [u'\uc608'], 'feature': [u'\uae30\ub2a5'], 'given': [u'*', u'\uc870\uac74<', u'\uba3c\uc800<'], 'name': [u'Korean'], 'native': [u'\ud55c\uad6d\uc5b4'], 'scenario': [u'\uc2dc\ub098\ub9ac\uc624'], 'scenario_outline': [u'\uc2dc\ub098\ub9ac\uc624 \uac1c\uc694'], 'then': [u'*', u'\uadf8\ub7ec\uba74<'], 'when': [u'*', u'\ub9cc\uc77c<', u'\ub9cc\uc57d<']}, 'lt': {'and': [u'*', u'Ir'], 'background': [u'Kontekstas'], 'but': [u'*', u'Bet'], 'examples': [u'Pavyzd\u017eiai', u'Scenarijai', u'Variantai'], 'feature': [u'Savyb\u0117'], 'given': [u'*', u'Duota'], 'name': [u'Lithuanian'], 'native': [u'lietuvi\u0173 kalba'], 'scenario': [u'Scenarijus'], 'scenario_outline': [u'Scenarijaus \u0161ablonas'], 'then': [u'*', u'Tada'], 'when': [u'*', u'Kai']}, 'lu': {'and': [u'*', u'an', u'a'], 'background': [u'Hannergrond'], 'but': [u'*', u'awer', u'm\xe4'], 'examples': [u'Beispiller'], 'feature': [u'Funktionalit\xe9it'], 'given': [u'*', u'ugeholl'], 'name': [u'Luxemburgish'], 'native': [u'L\xebtzebuergesch'], 'scenario': [u'Szenario'], 'scenario_outline': [u'Plang vum Szenario'], 'then': [u'*', u'dann'], 'when': [u'*', u'wann']}, 'lv': {'and': [u'*', u'Un'], 'background': [u'Konteksts', u'Situ\u0101cija'], 'but': [u'*', u'Bet'], 'examples': [u'Piem\u0113ri', u'Paraugs'], 'feature': [u'Funkcionalit\u0101te', u'F\u012b\u010da'], 'given': [u'*', u'Kad'], 'name': [u'Latvian'], 'native': [u'latvie\u0161u'], 'scenario': [u'Scen\u0101rijs'], 'scenario_outline': [u'Scen\u0101rijs p\u0113c parauga'], 'then': [u'*', u'Tad'], 'when': [u'*', u'Ja']}, 'nl': {'and': [u'*', u'En'], 'background': [u'Achtergrond'], 'but': [u'*', u'Maar'], 'examples': [u'Voorbeelden'], 'feature': [u'Functionaliteit'], 'given': [u'*', u'Gegeven', u'Stel'], 'name': [u'Dutch'], 'native': [u'Nederlands'], 'scenario': [u'Scenario'], 'scenario_outline': [u'Abstract Scenario'], 'then': [u'*', u'Dan'], 'when': [u'*', u'Als']}, 'no': {'and': [u'*', u'Og'], 'background': [u'Bakgrunn'], 'but': [u'*', u'Men'], 'examples': [u'Eksempler'], 'feature': [u'Egenskap'], 'given': [u'*', u'Gitt'], 'name': [u'Norwegian'], 'native': [u'norsk'], 'scenario': [u'Scenario'], 'scenario_outline': [u'Scenariomal', u'Abstrakt Scenario'], 'then': [u'*', u'S\xe5'], 'when': [u'*', u'N\xe5r']}, 'pl': {'and': [u'*', u'Oraz', u'I'], 'background': [u'Za\u0142o\u017cenia'], 'but': [u'*', u'Ale'], 'examples': [u'Przyk\u0142ady'], 'feature': [u'W\u0142a\u015bciwo\u015b\u0107'], 'given': [u'*', u'Zak\u0142adaj\u0105c', u'Maj\u0105c'], 'name': [u'Polish'], 'native': [u'polski'], 'scenario': [u'Scenariusz'], 'scenario_outline': [u'Szablon scenariusza'], 'then': [u'*', u'Wtedy'], 'when': [u'*', u'Je\u017celi', u'Je\u015bli']}, 'pt': {'and': [u'*', u'E'], 'background': [u'Contexto'], 'but': [u'*', u'Mas'], 'examples': [u'Exemplos'], 'feature': [u'Funcionalidade'], 'given': [u'*', u'Dado', u'Dada', u'Dados', u'Dadas'], 'name': [u'Portuguese'], 'native': [u'portugu\xeas'], 'scenario': [u'Cen\xe1rio', u'Cenario'], 'scenario_outline': [u'Esquema do Cen\xe1rio', u'Esquema do Cenario'], 'then': [u'*', u'Ent\xe3o', u'Entao'], 'when': [u'*', u'Quando']}, 'ro': {'and': [u'*', u'Si', u'\u0218i', u'\u015ei'], 'background': [u'Context'], 'but': [u'*', u'Dar'], 'examples': [u'Exemple'], 'feature': [u'Functionalitate', u'Func\u021bionalitate', u'Func\u0163ionalitate'], 'given': [u'*', u'Date fiind', u'Dat fiind', u'Dati fiind', u'Da\u021bi fiind', u'Da\u0163i fiind'], 'name': [u'Romanian'], 'native': [u'rom\xe2n\u0103'], 'scenario': [u'Scenariu'], 'scenario_outline': [u'Structura scenariu', u'Structur\u0103 scenariu'], 'then': [u'*', u'Atunci'], 'when': [u'*', u'Cand', u'C\xe2nd']}, 'ru': {'and': [u'*', u'\u0418', u'\u041a \u0442\u043e\u043c\u0443 \u0436\u0435'], 'background': [u'\u041f\u0440\u0435\u0434\u044b\u0441\u0442\u043e\u0440\u0438\u044f', u'\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442'], 'but': [u'*', u'\u041d\u043e', u'\u0410'], 'examples': [u'\u041f\u0440\u0438\u043c\u0435\u0440\u044b'], 'feature': [u'\u0424\u0443\u043d\u043a\u0446\u0438\u044f', u'\u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b', u'\u0421\u0432\u043e\u0439\u0441\u0442\u0432\u043e'], 'given': [u'*', u'\u0414\u043e\u043f\u0443\u0441\u0442\u0438\u043c', u'\u0414\u0430\u043d\u043e', u'\u041f\u0443\u0441\u0442\u044c'], 'name': [u'Russian'], 'native': [u'\u0440\u0443\u0441\u0441\u043a\u0438\u0439'], 'scenario': [u'\u0421\u0446\u0435\u043d\u0430\u0440\u0438\u0439'], 'scenario_outline': [u'\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u044f'], 'then': [u'*', u'\u0422\u043e', u'\u0422\u043e\u0433\u0434\u0430'], 'when': [u'*', u'\u0415\u0441\u043b\u0438', u'\u041a\u043e\u0433\u0434\u0430']}, 'sk': {'and': [u'*', u'A'], 'background': [u'Pozadie'], 'but': [u'*', u'Ale'], 'examples': [u'Pr\xedklady'], 'feature': [u'Po\u017eiadavka'], 'given': [u'*', u'Pokia\u013e'], 'name': [u'Slovak'], 'native': [u'Slovensky'], 'scenario': [u'Scen\xe1r'], 'scenario_outline': [u'N\xe1\u010drt Scen\xe1ru'], 'then': [u'*', u'Tak'], 'when': [u'*', u'Ke\u010f']}, 'sr-Cyrl': {'and': [u'*', u'\u0418'], 'background': [u'\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442', u'\u041e\u0441\u043d\u043e\u0432\u0430', u'\u041f\u043e\u0437\u0430\u0434\u0438\u043d\u0430'], 'but': [u'*', u'\u0410\u043b\u0438'], 'examples': [u'\u041f\u0440\u0438\u043c\u0435\u0440\u0438', u'\u0421\u0446\u0435\u043d\u0430\u0440\u0438\u0458\u0438'], 'feature': [u'\u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u043d\u043e\u0441\u0442', u'\u041c\u043e\u0433\u0443\u045b\u043d\u043e\u0441\u0442', u'\u041e\u0441\u043e\u0431\u0438\u043d\u0430'], 'given': [u'*', u'\u0417\u0430\u0434\u0430\u0442\u043e', u'\u0417\u0430\u0434\u0430\u0442\u0435', u'\u0417\u0430\u0434\u0430\u0442\u0438'], 'name': [u'Serbian'], 'native': [u'\u0421\u0440\u043f\u0441\u043a\u0438'], 'scenario': [u'\u0421\u0446\u0435\u043d\u0430\u0440\u0438\u043e', u'\u041f\u0440\u0438\u043c\u0435\u0440'], 'scenario_outline': [u'\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0458\u0430', u'\u0421\u043a\u0438\u0446\u0430', u'\u041a\u043e\u043d\u0446\u0435\u043f\u0442'], 'then': [u'*', u'\u041e\u043d\u0434\u0430'], 'when': [u'*', u'\u041a\u0430\u0434\u0430', u'\u041a\u0430\u0434']}, 'sr-Latn': {'and': [u'*', u'I'], 'background': [u'Kontekst', u'Osnova', u'Pozadina'], 'but': [u'*', u'Ali'], 'examples': [u'Primeri', u'Scenariji'], 'feature': [u'Funkcionalnost', u'Mogu\u0107nost', u'Mogucnost', u'Osobina'], 'given': [u'*', u'Zadato', u'Zadate', u'Zatati'], 'name': [u'Serbian (Latin)'], 'native': [u'Srpski (Latinica)'], 'scenario': [u'Scenario', u'Primer'], 'scenario_outline': [u'Struktura scenarija', u'Skica', u'Koncept'], 'then': [u'*', u'Onda'], 'when': [u'*', u'Kada', u'Kad']}, 'sv': {'and': [u'*', u'Och'], 'background': [u'Bakgrund'], 'but': [u'*', u'Men'], 'examples': [u'Exempel'], 'feature': [u'Egenskap'], 'given': [u'*', u'Givet'], 'name': [u'Swedish'], 'native': [u'Svenska'], 'scenario': [u'Scenario'], 'scenario_outline': [u'Abstrakt Scenario', u'Scenariomall'], 'then': [u'*', u'S\xe5'], 'when': [u'*', u'N\xe4r']}, 'tr': {'and': [u'*', u'Ve'], 'background': [u'Ge\xe7mi\u015f'], 'but': [u'*', u'Fakat', u'Ama'], 'examples': [u'\xd6rnekler'], 'feature': [u'\xd6zellik'], 'given': [u'*', u'Diyelim ki'], 'name': [u'Turkish'], 'native': [u'T\xfcrk\xe7e'], 'scenario': [u'Senaryo'], 'scenario_outline': [u'Senaryo tasla\u011f\u0131'], 'then': [u'*', u'O zaman'], 'when': [u'*', u'E\u011fer ki']}, 'uk': {'and': [u'*', u'\u0406', u'\u0410 \u0442\u0430\u043a\u043e\u0436', u'\u0422\u0430'], 'background': [u'\u041f\u0435\u0440\u0435\u0434\u0443\u043c\u043e\u0432\u0430'], 'but': [u'*', u'\u0410\u043b\u0435'], 'examples': [u'\u041f\u0440\u0438\u043a\u043b\u0430\u0434\u0438'], 'feature': [u'\u0424\u0443\u043d\u043a\u0446\u0456\u043e\u043d\u0430\u043b'], 'given': [u'*', u'\u041f\u0440\u0438\u043f\u0443\u0441\u0442\u0438\u043c\u043e', u'\u041f\u0440\u0438\u043f\u0443\u0441\u0442\u0438\u043c\u043e, \u0449\u043e', u'\u041d\u0435\u0445\u0430\u0439', u'\u0414\u0430\u043d\u043e'], 'name': [u'Ukrainian'], 'native': [u'\u0423\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430'], 'scenario': [u'\u0421\u0446\u0435\u043d\u0430\u0440\u0456\u0439'], 'scenario_outline': [u'\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u0441\u0446\u0435\u043d\u0430\u0440\u0456\u044e'], 'then': [u'*', u'\u0422\u043e', u'\u0422\u043e\u0434\u0456'], 'when': [u'*', u'\u042f\u043a\u0449\u043e', u'\u041a\u043e\u043b\u0438']}, 'uz': {'and': [u'*', u'\u0412\u0430'], 'background': [u'\u0422\u0430\u0440\u0438\u0445'], 'but': [u'*', u'\u041b\u0435\u043a\u0438\u043d', u'\u0411\u0438\u0440\u043e\u043a', u'\u0410\u043c\u043c\u043e'], 'examples': [u'\u041c\u0438\u0441\u043e\u043b\u043b\u0430\u0440'], 'feature': [u'\u0424\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b'], 'given': [u'*', u'\u0410\u0433\u0430\u0440'], 'name': [u'Uzbek'], 'native': [u'\u0423\u0437\u0431\u0435\u043a\u0447\u0430'], 'scenario': [u'\u0421\u0446\u0435\u043d\u0430\u0440\u0438\u0439'], 'scenario_outline': [u'\u0421\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430\u0441\u0438'], 'then': [u'*', u'\u0423\u043d\u0434\u0430'], 'when': [u'*', u'\u0410\u0433\u0430\u0440']}, 'vi': {'and': [u'*', u'V\xe0'], 'background': [u'B\u1ed1i c\u1ea3nh'], 'but': [u'*', u'Nh\u01b0ng'], 'examples': [u'D\u1eef li\u1ec7u'], 'feature': [u'T\xednh n\u0103ng'], 'given': [u'*', u'Bi\u1ebft', u'Cho'], 'name': [u'Vietnamese'], 'native': [u'Ti\u1ebfng Vi\u1ec7t'], 'scenario': [u'T\xecnh hu\u1ed1ng', u'K\u1ecbch b\u1ea3n'], 'scenario_outline': [u'Khung t\xecnh hu\u1ed1ng', u'Khung k\u1ecbch b\u1ea3n'], 'then': [u'*', u'Th\xec'], 'when': [u'*', u'Khi']}, 'zh-CN': {'and': [u'*', u'\u800c\u4e14<'], 'background': [u'\u80cc\u666f'], 'but': [u'*', u'\u4f46\u662f<'], 'examples': [u'\u4f8b\u5b50'], 'feature': [u'\u529f\u80fd'], 'given': [u'*', u'\u5047\u5982<'], 'name': [u'Chinese simplified'], 'native': [u'\u7b80\u4f53\u4e2d\u6587'], 'scenario': [u'\u573a\u666f'], 'scenario_outline': [u'\u573a\u666f\u5927\u7eb2'], 'then': [u'*', u'\u90a3\u4e48<'], 'when': [u'*', u'\u5f53<']}, 'zh-TW': {'and': [u'*', u'\u800c\u4e14<', u'\u4e26\u4e14<'], 'background': [u'\u80cc\u666f'], 'but': [u'*', u'\u4f46\u662f<'], 'examples': [u'\u4f8b\u5b50'], 'feature': [u'\u529f\u80fd'], 'given': [u'*', u'\u5047\u8a2d<'], 'name': [u'Chinese traditional'], 'native': [u'\u7e41\u9ad4\u4e2d\u6587'], 'scenario': [u'\u5834\u666f', u'\u5287\u672c'], 'scenario_outline': [u'\u5834\u666f\u5927\u7db1', u'\u5287\u672c\u5927\u7db1'], 'then': [u'*', u'\u90a3\u9ebc<'], 'when': [u'*', u'\u7576<']}} behave-1.2.6/behave/importer.py0000644000076600000240000000650013244555737016522 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Importer module for lazy-loading/importing modules and objects. REQUIRES: importlib (provided in Python2.7, Python3.2...) """ from __future__ import absolute_import import importlib from behave._types import Unknown def parse_scoped_name(scoped_name): """ SCHEMA: my.module_name:MyClassName EXAMPLE: behave.formatter.plain:PlainFormatter """ scoped_name = scoped_name.strip() if "::" in scoped_name: # -- ALTERNATIVE: my.module_name::MyClassName scoped_name = scoped_name.replace("::", ":") module_name, object_name = scoped_name.rsplit(":", 1) return module_name, object_name def load_module(module_name): return importlib.import_module(module_name) class LazyObject(object): """ Provides a placeholder for an object that should be loaded lazily. """ def __init__(self, module_name, object_name=None): if ":" in module_name and not object_name: module_name, object_name = parse_scoped_name(module_name) assert ":" not in module_name self.module_name = module_name self.object_name = object_name self.resolved_object = None def __get__(self, obj=None, type=None): # pylint: disable=redefined-builtin """ Implement descriptor protocol, useful if this class is used as attribute. :return: Real object (lazy-loaded if necessary). :raise ImportError: If module or object cannot be imported. """ __pychecker__ = "unusednames=obj,type" resolved_object = None if not self.resolved_object: # -- SETUP-ONCE: Lazy load the real object. module = load_module(self.module_name) resolved_object = getattr(module, self.object_name, Unknown) if resolved_object is Unknown: msg = "%s: %s is Unknown" % (self.module_name, self.object_name) raise ImportError(msg) self.resolved_object = resolved_object return resolved_object def __set__(self, obj, value): """Implement descriptor protocol.""" __pychecker__ = "unusednames=obj" self.resolved_object = value def get(self): return self.__get__() class LazyDict(dict): """ Provides a dict that supports lazy loading of objects. A LazyObject is provided as placeholder for a value that should be loaded lazily. """ def __getitem__(self, key): """ Provides access to stored dict values. Implements lazy loading of item value (if necessary). When lazy object is loaded, its value with the dict is replaced with the real value. :param key: Key to access the value of an item in the dict. :return: value :raises: KeyError if item is not found :raises: ImportError for a LazyObject that cannot be imported. """ value = dict.__getitem__(self, key) if isinstance(value, LazyObject): # -- LAZY-LOADING MECHANISM: Load object and replace with lazy one. value = value.__get__() self[key] = value return value def load_all(self, strict=False): for key in self.keys(): try: self.__getitem__(key) except ImportError: if strict: raise behave-1.2.6/behave/json_parser.py0000644000076600000240000002221013244555737017202 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Read behave's JSON output files and store retrieved information in :mod:`behave.model` elements. Utility to retrieve runtime information from behave's JSON output. REQUIRES: Python >= 2.6 (json module is part of Python standard library) """ from __future__ import absolute_import import codecs from behave import model from behave.model_core import Status try: import json except ImportError: # -- PYTHON 2.5 backward compatible: Use simplejson module. import simplejson as json __author__ = "Jens Engel" # ---------------------------------------------------------------------------- # FUNCTIONS: # ---------------------------------------------------------------------------- def parse(json_filename, encoding="UTF-8"): """ Reads behave JSON output file back in and stores information in behave model elements. :param json_filename: JSON filename to process. :return: List of feature objects. """ with codecs.open(json_filename, "rU", encoding=encoding) as input_file: json_data = json.load(input_file, encoding=encoding) json_processor = JsonParser() features = json_processor.parse_features(json_data) return features # ---------------------------------------------------------------------------- # CLASSES: # ---------------------------------------------------------------------------- class JsonParser(object): def __init__(self): self.current_scenario_outline = None def parse_features(self, json_data): assert isinstance(json_data, list) features = [] json_features = json_data for json_feature in json_features: feature = self.parse_feature(json_feature) features.append(feature) return features def parse_feature(self, json_feature): name = json_feature.get("name", u"") keyword = json_feature.get("keyword", None) tags = json_feature.get("tags", []) description = json_feature.get("description", []) location = json_feature.get("location", u"") filename, line = location.split(":") feature = model.Feature(filename, line, keyword, name, tags, description) json_elements = json_feature.get("elements", []) for json_element in json_elements: self.add_feature_element(feature, json_element) return feature def add_feature_element(self, feature, json_element): datatype = json_element.get("type", u"") category = datatype.lower() if category == "background": background = self.parse_background(json_element) feature.background = background elif category == "scenario": scenario = self.parse_scenario(json_element) feature.add_scenario(scenario) elif category == "scenario_outline": scenario_outline = self.parse_scenario_outline(json_element) feature.add_scenario(scenario_outline) self.current_scenario_outline = scenario_outline # elif category == "examples": # examples = self.parse_examples(json_element) # self.current_scenario_outline.examples = examples else: raise KeyError("Invalid feature-element keyword: %s" % category) def parse_background(self, json_element): """ self.add_feature_element({ 'keyword': background.keyword, 'location': background.location, 'steps': [], }) """ keyword = json_element.get("keyword", u"") name = json_element.get("name", u"") location = json_element.get("location", u"") json_steps = json_element.get("steps", []) steps = self.parse_steps(json_steps) filename, line = location.split(":") background = model.Background(filename, line, keyword, name, steps) return background def parse_scenario(self, json_element): """ self.add_feature_element({ 'keyword': scenario.keyword, 'name': scenario.name, 'tags': scenario.tags, 'location': scenario.location, 'steps': [], }) """ keyword = json_element.get("keyword", u"") name = json_element.get("name", u"") description = json_element.get("description", []) tags = json_element.get("tags", []) location = json_element.get("location", u"") json_steps = json_element.get("steps", []) steps = self.parse_steps(json_steps) filename, line = location.split(":") scenario = model.Scenario(filename, line, keyword, name, tags, steps) scenario.description = description return scenario def parse_scenario_outline(self, json_element): """ self.add_feature_element({ 'keyword': scenario_outline.keyword, 'name': scenario_outline.name, 'tags': scenario_outline.tags, 'location': scenario_outline.location, 'steps': [], 'examples': [], }) """ keyword = json_element.get("keyword", u"") name = json_element.get("name", u"") description = json_element.get("description", []) tags = json_element.get("tags", []) location = json_element.get("location", u"") json_steps = json_element.get("steps", []) json_examples = json_element.get("examples", []) steps = self.parse_steps(json_steps) examples = [] if json_examples: # pylint: disable=redefined-variable-type examples = self.parse_examples(json_examples) filename, line = location.split(":") scenario_outline = model.ScenarioOutline(filename, line, keyword, name, tags=tags, steps=steps, examples=examples) scenario_outline.description = description return scenario_outline def parse_steps(self, json_steps): steps = [] for json_step in json_steps: step = self.parse_step(json_step) steps.append(step) return steps def parse_step(self, json_element): """ s = { 'keyword': step.keyword, 'step_type': step.step_type, 'name': step.name, 'location': step.location, } if step.text: s['text'] = step.text if step.table: s['table'] = self.make_table(step.table) element = self.current_feature_element element['steps'].append(s) """ keyword = json_element.get("keyword", u"") name = json_element.get("name", u"") step_type = json_element.get("step_type", u"") location = json_element.get("location", u"") text = json_element.get("text", None) if isinstance(text, list): text = "\n".join(text) table = None json_table = json_element.get("table", None) if json_table: table = self.parse_table(json_table) filename, line = location.split(":") step = model.Step(filename, line, keyword, step_type, name) step.text = text step.table = table json_result = json_element.get("result", None) if json_result: self.add_step_result(step, json_result) return step @staticmethod def add_step_result(step, json_result): """ steps = self.current_feature_element['steps'] steps[self._step_index]['result'] = { 'status': result.status.name, 'duration': result.duration, } """ status_name = json_result.get("status", u"") duration = json_result.get("duration", 0) error_message = json_result.get("error_message", None) if isinstance(error_message, list): error_message = "\n".join(error_message) step.status = Status.from_name(status_name) step.duration = duration step.error_message = error_message @staticmethod def parse_table(json_table): """ table_data = { 'headings': table.headings, 'rows': [ list(row) for row in table.rows ] } return table_data """ headings = json_table.get("headings", []) rows = json_table.get("rows", []) table = model.Table(headings, rows=rows) return table def parse_examples(self, json_element): """ e = { 'keyword': examples.keyword, 'name': examples.name, 'location': examples.location, } if examples.table: e['table'] = self.make_table(examples.table) element = self.current_feature_element element['examples'].append(e) """ keyword = json_element.get("keyword", u"") name = json_element.get("name", u"") location = json_element.get("location", u"") table = None json_table = json_element.get("table", None) if json_table: table = self.parse_table(json_table) filename, line = location.split(":") examples = model.Examples(filename, line, keyword, name, table) return examples behave-1.2.6/behave/log_capture.py0000644000076600000240000001657013244555737017175 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import, print_function from logging.handlers import BufferingHandler import logging import functools import re class RecordFilter(object): """Implement logging record filtering as per the configuration --logging-filter option. """ def __init__(self, names): self.include = set() self.exclude = set() for name in names.split(','): if name[0] == '-': self.exclude.add(name[1:]) else: self.include.add(name) def filter(self, record): if self.exclude: return record.name not in self.exclude return record.name in self.include # originally from nostetsts logcapture plugin class LoggingCapture(BufferingHandler): """Capture logging events in a memory buffer for later display or query. Captured logging events are stored on the attribute :attr:`~LoggingCapture.buffer`: .. attribute:: buffer This is a list of captured logging events as `logging.LogRecords`_. .. _`logging.LogRecords`: http://docs.python.org/library/logging.html#logrecord-objects By default the format of the messages will be:: '%(levelname)s:%(name)s:%(message)s' This may be overridden using standard logging formatter names in the configuration variable ``logging_format``. The level of logging captured is set to ``logging.NOTSET`` by default. You may override this using the configuration setting ``logging_level`` (which is set to a level name.) Finally there may be `filtering of logging events`__ specified by the configuration variable ``logging_filter``. .. __: behave.html#command-line-arguments """ def __init__(self, config, level=None): BufferingHandler.__init__(self, 1000) self.config = config self.old_handlers = [] self.old_level = None # set my formatter log_format = datefmt = None if config.logging_format: log_format = config.logging_format else: log_format = '%(levelname)s:%(name)s:%(message)s' if config.logging_datefmt: datefmt = config.logging_datefmt formatter = logging.Formatter(log_format, datefmt) self.setFormatter(formatter) # figure the level we're logging at if level is not None: self.level = level elif config.logging_level: self.level = config.logging_level else: self.level = logging.NOTSET # construct my filter if config.logging_filter: self.addFilter(RecordFilter(config.logging_filter)) def __bool__(self): return bool(self.buffer) def flush(self): pass # do nothing def truncate(self): self.buffer = [] def getvalue(self): return '\n'.join(self.formatter.format(r) for r in self.buffer) def find_event(self, pattern): """Search through the buffer for a message that matches the given regular expression. Returns boolean indicating whether a match was found. """ pattern = re.compile(pattern) for record in self.buffer: if pattern.search(record.getMessage()) is not None: return True return False def any_errors(self): """Search through the buffer for any ERROR or CRITICAL events. Returns boolean indicating whether a match was found. """ return any(record for record in self.buffer if record.levelname in ('ERROR', 'CRITICAL')) def inveigle(self): """Turn on logging capture by replacing all existing handlers configured in the logging module. If the config var logging_clear_handlers is set then we also remove all existing handlers. We also set the level of the root logger. The opposite of this is :meth:`~LoggingCapture.abandon`. """ root_logger = logging.getLogger() if self.config.logging_clear_handlers: # kill off all the other log handlers for logger in logging.Logger.manager.loggerDict.values(): if hasattr(logger, "handlers"): for handler in logger.handlers: self.old_handlers.append((logger, handler)) logger.removeHandler(handler) # sanity check: remove any existing LoggingCapture for handler in root_logger.handlers[:]: if isinstance(handler, LoggingCapture): root_logger.handlers.remove(handler) elif self.config.logging_clear_handlers: self.old_handlers.append((root_logger, handler)) root_logger.removeHandler(handler) # right, we're it now root_logger.addHandler(self) # capture the level we're interested in self.old_level = root_logger.level root_logger.setLevel(self.level) def abandon(self): """Turn off logging capture. If other handlers were removed by :meth:`~LoggingCapture.inveigle` then they are reinstated. """ root_logger = logging.getLogger() for handler in root_logger.handlers[:]: if handler is self: root_logger.handlers.remove(handler) if self.config.logging_clear_handlers: for logger, handler in self.old_handlers: logger.addHandler(handler) if self.old_level is not None: # -- RESTORE: Old log.level before inveigle() was used. root_logger.setLevel(self.old_level) self.old_level = None # pre-1.2 backwards compatibility MemoryHandler = LoggingCapture def capture(*args, **kw): """Decorator to wrap an *environment file function* in log file capture. It configures the logging capture using the *behave* context - the first argument to the function being decorated (so don't use this to decorate something that doesn't have *context* as the first argument.) The basic usage is: .. code-block: python @capture def after_scenario(context, scenario): ... The function prints any captured logging (at the level determined by the ``log_level`` configuration setting) directly to stdout, regardless of error conditions. It is mostly useful for debugging in situations where you are seeing a message like:: No handlers could be found for logger "name" The decorator takes an optional "level" keyword argument which limits the level of logging captured, overriding the level in the run's configuration: .. code-block: python @capture(level=logging.ERROR) def after_scenario(context, scenario): ... This would limit the logging captured to just ERROR and above, and thus only display logged events if they are interesting. """ def create_decorator(func, level=None): def f(context, *args): h = LoggingCapture(context.config, level=level) h.inveigle() try: func(context, *args) finally: h.abandon() v = h.getvalue() if v: print("Captured Logging:") print(v) return f if not args: return functools.partial(create_decorator, level=kw.get("level")) else: return create_decorator(args[0]) behave-1.2.6/behave/matchers.py0000644000076600000240000003373413244555737016500 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ This module provides the step matchers functionality that matches a step definition (as text) with step-functions that implement this step. """ from __future__ import absolute_import, print_function, with_statement import copy import re import warnings import parse import six from parse_type import cfparse from behave._types import ChainedExceptionUtil, ExceptionUtil from behave.model_core import Argument, FileLocation, Replayable # ----------------------------------------------------------------------------- # SECTION: Exceptions # ----------------------------------------------------------------------------- class StepParseError(ValueError): """Exception class, used when step matching fails before a step is run. This is normally the case when an error occurs during the type conversion of step parameters. """ def __init__(self, text=None, exc_cause=None): if not text and exc_cause: text = six.text_type(exc_cause) if exc_cause and six.PY2: # -- NOTE: Python2 does not show chained-exception causes. # Therefore, provide some hint (see also: PEP-3134). cause_text = ExceptionUtil.describe(exc_cause, use_traceback=True, prefix="CAUSED-BY: ") text += u"\n" + cause_text ValueError.__init__(self, text) if exc_cause: # -- CHAINED EXCEPTION (see: PEP 3134) ChainedExceptionUtil.set_cause(self, exc_cause) # ----------------------------------------------------------------------------- # SECTION: Model Elements # ----------------------------------------------------------------------------- class Match(Replayable): """An parameter-matched *feature file* step name extracted using step decorator `parameters`_. .. attribute:: func The step function that this match will be applied to. .. attribute:: arguments A list of :class:`~behave.model_core.Argument` instances containing the matched parameters from the step name. """ type = "match" def __init__(self, func, arguments=None): super(Match, self).__init__() self.func = func self.arguments = arguments self.location = None if func: self.location = self.make_location(func) def __repr__(self): if self.func: func_name = self.func.__name__ else: func_name = '' return '' % (func_name, self.location) def __eq__(self, other): if not isinstance(other, Match): return False return (self.func, self.location) == (other.func, other.location) def with_arguments(self, arguments): match = copy.copy(self) match.arguments = arguments return match def run(self, context): args = [] kwargs = {} for arg in self.arguments: if arg.name is not None: kwargs[arg.name] = arg.value else: args.append(arg.value) with context.use_with_user_mode(): self.func(context, *args, **kwargs) @staticmethod def make_location(step_function): """Extracts the location information from the step function and builds a FileLocation object with (filename, line_number) info. :param step_function: Function whose location should be determined. :return: FileLocation object for step function. """ return FileLocation.for_function(step_function) class NoMatch(Match): """Used for an "undefined step" when it can not be matched with a step definition. """ def __init__(self): Match.__init__(self, func=None) self.func = None self.arguments = [] self.location = None class MatchWithError(Match): """Match class when error occur during step-matching REASON: * Type conversion error occured. * ... """ def __init__(self, func, error): if not ExceptionUtil.has_traceback(error): ExceptionUtil.set_traceback(error) Match.__init__(self, func=func) self.stored_error = error def run(self, context): """Raises stored error from step matching phase (type conversion).""" raise StepParseError(exc_cause=self.stored_error) # ----------------------------------------------------------------------------- # SECTION: Matchers # ----------------------------------------------------------------------------- class Matcher(object): """Pull parameters out of step names. .. attribute:: pattern The match pattern attached to the step function. .. attribute:: func The step function the pattern is being attached to. """ schema = u"@%s('%s')" # Schema used to describe step definition (matcher) def __init__(self, func, pattern, step_type=None): self.func = func self.pattern = pattern self.step_type = step_type self._location = None # -- BACKWARD-COMPATIBILITY: @property def string(self): warnings.warn("deprecated: Use 'pattern' instead", DeprecationWarning) return self.pattern @property def location(self): if self._location is None: self._location = Match.make_location(self.func) return self._location @property def regex_pattern(self): """Return the used textual regex pattern.""" # -- ASSUMPTION: pattern attribute provides regex-pattern # NOTE: Method must be overridden if assumption is not met. return self.pattern def describe(self, schema=None): """Provide a textual description of the step function/matcher object. :param schema: Text schema to use. :return: Textual description of this step definition (matcher). """ step_type = self.step_type or "step" if not schema: schema = self.schema return schema % (step_type, self.pattern) def check_match(self, step): """Match me against the "step" name supplied. Return None, if I don't match otherwise return a list of matches as :class:`~behave.model_core.Argument` instances. The return value from this function will be converted into a :class:`~behave.matchers.Match` instance by *behave*. """ raise NotImplementedError def match(self, step): # -- PROTECT AGAINST: Type conversion errors (with ParseMatcher). try: result = self.check_match(step) except Exception as e: # pylint: disable=broad-except return MatchWithError(self.func, e) if result is None: return None # -- NO-MATCH return Match(self.func, result) def __repr__(self): return u"<%s: %r>" % (self.__class__.__name__, self.pattern) class ParseMatcher(Matcher): """Uses :class:`~parse.Parser` class to be able to use simpler parse expressions compared to normal regular expressions. """ custom_types = {} parser_class = parse.Parser def __init__(self, func, pattern, step_type=None): super(ParseMatcher, self).__init__(func, pattern, step_type) self.parser = self.parser_class(pattern, self.custom_types) @property def regex_pattern(self): # -- OVERWRITTEN: Pattern as regex text. return self.parser._expression # pylint: disable=protected-access def check_match(self, step): # -- FAILURE-POINT: Type conversion of parameters may fail here. # NOTE: Type converter should raise ValueError in case of PARSE ERRORS. result = self.parser.parse(step) if not result: return None args = [] for index, value in enumerate(result.fixed): start, end = result.spans[index] args.append(Argument(start, end, step[start:end], value)) for name, value in result.named.items(): start, end = result.spans[name] args.append(Argument(start, end, step[start:end], value, name)) args.sort(key=lambda x: x.start) return args class CFParseMatcher(ParseMatcher): """Uses :class:`~parse_type.cfparse.Parser` instead of "parse.Parser". Provides support for automatic generation of type variants for fields with CardinalityField part. """ parser_class = cfparse.Parser def register_type(**kw): # pylint: disable=anomalous-backslash-in-string # REQUIRED-BY: code example """Registers a custom type that will be available to "parse" for type conversion during step matching. Converters should be supplied as ``name=callable`` arguments (or as dict). A type converter should follow :pypi:`parse` module rules. In general, a type converter is a function that converts text (as string) into a value-type (type converted value). EXAMPLE: .. code-block:: python from behave import register_type, given import parse # -- TYPE CONVERTER: For a simple, positive integer number. @parse.with_pattern(r"\d+") def parse_number(text): return int(text) # -- REGISTER TYPE-CONVERTER: With behave register_type(Number=parse_number) # -- STEP DEFINITIONS: Use type converter. @given('{amount:Number} vehicles') def step_impl(context, amount): assert isinstance(amount, int) """ ParseMatcher.custom_types.update(kw) class RegexMatcher(Matcher): def __init__(self, func, pattern, step_type=None): super(RegexMatcher, self).__init__(func, pattern, step_type) self.regex = re.compile(self.pattern) def check_match(self, step): m = self.regex.match(step) if not m: return None groupindex = dict((y, x) for x, y in self.regex.groupindex.items()) args = [] for index, group in enumerate(m.groups()): index += 1 name = groupindex.get(index, None) args.append(Argument(m.start(index), m.end(index), group, group, name)) return args class SimplifiedRegexMatcher(RegexMatcher): """Simplified regular expression step-matcher that automatically adds start-of-line/end-of-line matcher symbols to string: .. code-block:: python @when(u'a step passes') # re.pattern = "^a step passes$" def step_impl(context): pass """ def __init__(self, func, pattern, step_type=None): assert not (pattern.startswith("^") or pattern.endswith("$")), \ "Regular expression should not use begin/end-markers: "+ pattern expression = "^%s$" % pattern super(SimplifiedRegexMatcher, self).__init__(func, expression, step_type) self.pattern = pattern class CucumberRegexMatcher(RegexMatcher): """Compatible to (old) Cucumber style regular expressions. Text must contain start-of-line/end-of-line matcher symbols to string: .. code-block:: python @when(u'^a step passes$') # re.pattern = "^a step passes$" def step_impl(context): pass """ matcher_mapping = { "parse": ParseMatcher, "cfparse": CFParseMatcher, "re": SimplifiedRegexMatcher, # -- BACKWARD-COMPATIBLE REGEX MATCHER: Old Cucumber compatible style. # To make it the default step-matcher use the following snippet: # # -- FILE: features/environment.py # from behave import use_step_matcher # def before_all(context): # use_step_matcher("re0") "re0": CucumberRegexMatcher, } current_matcher = ParseMatcher # pylint: disable=invalid-name def use_step_matcher(name): """Change the parameter matcher used in parsing step text. The change is immediate and may be performed between step definitions in your step implementation modules - allowing adjacent steps to use different matchers if necessary. There are several parsers available in *behave* (by default): **parse** (the default, based on: :pypi:`parse`) Provides a simple parser that replaces regular expressions for step parameters with a readable syntax like ``{param:Type}``. The syntax is inspired by the Python builtin ``string.format()`` function. Step parameters must use the named fields syntax of :pypi:`parse` in step definitions. The named fields are extracted, optionally type converted and then used as step function arguments. Supports type conversions by using type converters (see :func:`~behave.register_type()`). **cfparse** (extends: :pypi:`parse`, requires: :pypi:`parse_type`) Provides an extended parser with "Cardinality Field" (CF) support. Automatically creates missing type converters for related cardinality as long as a type converter for cardinality=1 is provided. Supports parse expressions like: * ``{values:Type+}`` (cardinality=1..N, many) * ``{values:Type*}`` (cardinality=0..N, many0) * ``{value:Type?}`` (cardinality=0..1, optional) Supports type conversions (as above). **re** This uses full regular expressions to parse the clause text. You will need to use named groups "(?P...)" to define the variables pulled from the text and passed to your ``step()`` function. Type conversion is **not supported**. A step function writer may implement type conversion inside the step function (implementation). You may `define your own matcher`_. .. _`define your own matcher`: api.html#step-parameters """ global current_matcher # pylint: disable=global-statement current_matcher = matcher_mapping[name] def step_matcher(name): """ DEPRECATED, use :func:`use_step_matcher()` instead. """ # -- BACKWARD-COMPATIBLE NAME: Mark as deprecated. warnings.warn("deprecated: Use 'use_step_matcher()' instead", DeprecationWarning, stacklevel=2) use_step_matcher(name) def get_matcher(func, pattern): return current_matcher(func, pattern) behave-1.2.6/behave/model.py0000644000076600000240000017025213244555737015767 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- # pylint: disable=too-many-lines """ This module provides the model element class that represent a behave model: * :class:`Feature` * :class:`Scenario` * :class:`ScenarioOutline` * :class:`Step` * ... """ from __future__ import absolute_import, with_statement import copy import difflib import logging import itertools import time import six from six.moves import zip # pylint: disable=redefined-builtin from behave.model_core import \ Status, BasicStatement, TagAndStatusStatement, TagStatement, Replayable from behave.matchers import NoMatch from behave.textutil import text as _text if six.PY2: # -- USE PYTHON3 BACKPORT: With unicode traceback support. import traceback2 as traceback else: import traceback class Feature(TagAndStatusStatement, Replayable): """A `feature`_ parsed from a *feature file*. The attributes are: .. attribute:: keyword This is the keyword as seen in the *feature file*. In English this will be "Feature". .. attribute:: name The name of the feature (the text after "Feature".) .. attribute:: description The description of the feature as seen in the *feature file*. This is stored as a list of text lines. .. attribute:: background The :class:`~behave.model.Background` for this feature, if any. .. attribute:: scenarios A list of :class:`~behave.model.Scenario` making up this feature. .. attribute:: tags A list of @tags (as :class:`~behave.model.Tag` which are basically glorified strings) attached to the feature. See :ref:`controlling things with tags`. .. attribute:: status Read-Only. A summary status of the feature's run. If read before the feature is fully tested it will return "untested" otherwise it will return one of: Status.untested The feature was has not been completely tested yet. Status.skipped One or more steps of this feature was passed over during testing. Status.passed The feature was tested successfully. Status.failed One or more steps of this feature failed. .. versionchanged:: 1.2.6 Use Status enum class (was: string). .. attribute:: hook_failed Indicates if a hook failure occured while running this feature. .. versionadded:: 1.2.6 .. attribute:: duration The time, in seconds, that it took to test this feature. If read before the feature is tested it will return 0.0. .. attribute:: filename The file name (or "") of the *feature file* where the feature was found. .. attribute:: line The line number of the *feature file* where the feature was found. .. attribute:: language Indicates which spoken language (English, French, German, ..) was used for parsing the feature file and its keywords. The I18N language code indicates which language is used. This corresponds to the language tag at the beginning of the feature file. .. versionadded:: 1.2.6 .. _`feature`: gherkin.html#features """ type = "feature" def __init__(self, filename, line, keyword, name, tags=None, description=None, scenarios=None, background=None, language=None): tags = tags or [] super(Feature, self).__init__(filename, line, keyword, name, tags) self.description = description or [] self.scenarios = [] self.background = background self.language = language self.parser = None self.hook_failed = False if scenarios: for scenario in scenarios: self.add_scenario(scenario) def reset(self): """Reset to clean state before a test run.""" super(Feature, self).reset() self.hook_failed = False for scenario in self.scenarios: scenario.reset() def __repr__(self): return '' % \ (self.name, len(self.scenarios)) def __iter__(self): return iter(self.scenarios) def add_scenario(self, scenario): scenario.feature = self scenario.background = self.background self.scenarios.append(scenario) def compute_status(self): """Compute the status of this feature based on its: * scenarios * scenario outlines * hook failures :return: Computed status (as string-enum). """ if self.hook_failed: return Status.failed skipped = True passed_count = 0 for scenario in self.scenarios: scenario_status = scenario.status if scenario_status == Status.failed: return Status.failed elif scenario_status == Status.untested: if passed_count > 0: return Status.failed # ABORTED: Some passed, now untested. return Status.untested if scenario_status != Status.skipped: skipped = False if scenario_status == Status.passed: passed_count += 1 if skipped: return Status.skipped else: return Status.passed @property def duration(self): # -- NEW: Background is executed N times, now part of scenarios. feature_duration = 0.0 for scenario in self.scenarios: feature_duration += scenario.duration return feature_duration def walk_scenarios(self, with_outlines=False): """ Provides a flat list of all scenarios of this feature. A ScenarioOutline element adds its scenarios to this list. But the ScenarioOutline element itself is only added when specified. A flat scenario list is useful when all scenarios of a features should be processed. :param with_outlines: If ScenarioOutline items should be added, too. :return: List of all scenarios of this feature. """ all_scenarios = [] for scenario in self.scenarios: if isinstance(scenario, ScenarioOutline): scenario_outline = scenario if with_outlines: all_scenarios.append(scenario_outline) all_scenarios.extend(scenario_outline.scenarios) else: all_scenarios.append(scenario) return all_scenarios def should_run(self, config=None): """ Determines if this Feature (and its scenarios) should run. Implements the run decision logic for a feature. The decision depends on: * if the Feature is marked as skipped * if the config.tags (tag expression) enable/disable this feature :param config: Runner configuration to use (optional). :return: True, if scenario should run. False, otherwise. """ answer = not self.should_skip if answer and config: answer = self.should_run_with_tags(config.tags) return answer def should_run_with_tags(self, tag_expression): """Determines if this feature should run when the tag expression is used. A feature should run if: * it should run according to its tags * any of its scenarios should run according to its tags :param tag_expression: Runner/config environment tags to use. :return: True, if feature should run. False, otherwise (skip it). """ run_feature = tag_expression.check(self.tags) if not run_feature: for scenario in self: if scenario.should_run_with_tags(tag_expression): run_feature = True break return run_feature def mark_skipped(self): """Marks this feature (and all its scenarios and steps) as skipped. Note this function may be called before the feature is executed. """ self.skip(require_not_executed=True) assert self.status == Status.skipped or self.hook_failed def skip(self, reason=None, require_not_executed=False): """Skip executing this feature or the remaining parts of it. Note that this feature may be already partly executed when this function is called. :param reason: Optional reason why feature should be skipped (as string). :param require_not_executed: Optional, requires that feature is not executed yet (default: false). """ if reason: logger = logging.getLogger("behave") logger.warning(u"SKIP FEATURE %s: %s", self.name, reason) self.clear_status() self.should_skip = True self.skip_reason = reason for scenario in self.scenarios: scenario.skip(reason, require_not_executed) if not self.scenarios: # -- SPECIAL CASE: Feature without scenarios self.set_status(Status.skipped) assert self.status in self.final_status #< skipped, failed or passed. def run(self, runner): # pylint: disable=too-many-branches # MAYBE: self.reset() self.clear_status() self.hook_failed = False runner.context._push(layer_name="feature") # pylint: disable=protected-access runner.context.feature = self runner.context.tags = set(self.tags) skip_feature_untested = runner.aborted run_feature = self.should_run(runner.config) failed_count = 0 hooks_called = False if not runner.config.dry_run and run_feature: hooks_called = True for tag in self.tags: runner.run_hook("before_tag", runner.context, tag) runner.run_hook("before_feature", runner.context, self) if self.hook_failed: failed_count += 1 # -- RE-EVALUATE SHOULD-RUN STATE: # Hook may call feature.mark_skipped() to exclude it. skip_feature_untested = self.hook_failed or runner.aborted run_feature = self.should_run() # run this feature if the tags say so or any one of its scenarios if run_feature or runner.config.show_skipped: for formatter in runner.formatters: formatter.feature(self) if self.background: for formatter in runner.formatters: formatter.background(self.background) if not skip_feature_untested: for scenario in self.scenarios: # -- OPTIONAL: Select scenario by name (regular expressions). if (runner.config.name and not scenario.should_run_with_name_select(runner.config)): scenario.mark_skipped() continue failed = scenario.run(runner) if failed: failed_count += 1 if runner.config.stop or runner.aborted: # -- FAIL-EARLY: Stop after first failure. break self.clear_status() # -- ENFORCE: compute_status() after run. if not self.scenarios and not run_feature: # -- SPECIAL CASE: Feature without scenarios self.set_status(Status.skipped) if hooks_called: runner.run_hook("after_feature", runner.context, self) for tag in self.tags: runner.run_hook("after_tag", runner.context, tag) if self.hook_failed: failed_count += 1 self.set_status(Status.failed) # -- PERFORM CONTEXT CLEANUP: May raise cleanup errors. try: runner.context._pop() # pylint: disable=protected-access except Exception: # -- CLEANUP-ERROR: self.set_status(Status.failed) if run_feature or runner.config.show_skipped: for formatter in runner.formatters: formatter.eof() failed = (failed_count > 0) return failed class Background(BasicStatement, Replayable): """A `background`_ parsed from a *feature file*. The attributes are: .. attribute:: keyword This is the keyword as seen in the *feature file*. In English this will typically be "Background". .. attribute:: name The name of the background (the text after "Background:".) .. attribute:: steps A list of :class:`~behave.model.Step` making up this background. .. attribute:: duration The time, in seconds, that it took to run this background. If read before the background is run it will return 0.0. .. attribute:: filename The file name (or "") of the *feature file* where the background was found. .. attribute:: line The line number of the *feature file* where the background was found. .. _`background`: gherkin.html#backgrounds """ type = "background" def __init__(self, filename, line, keyword, name, steps=None): super(Background, self).__init__(filename, line, keyword, name) self.steps = steps or [] def __repr__(self): return '' % self.name def __iter__(self): return iter(self.steps) @property def duration(self): duration = 0 for step in self.steps: duration += step.duration return duration class Scenario(TagAndStatusStatement, Replayable): """A `scenario`_ parsed from a *feature file*. The attributes are: .. attribute:: keyword This is the keyword as seen in the *feature file*. In English this will typically be "Scenario". .. attribute:: name The name of the scenario (the text after "Scenario:".) .. attribute:: description The description of the scenario as seen in the *feature file*. This is stored as a list of text lines. .. attribute:: feature The :class:`~behave.model.Feature` this scenario belongs to. .. attribute:: steps A list of :class:`~behave.model.Step` making up this scenario. .. attribute:: tags A list of @tags (as :class:`~behave.model.Tag` which are basically glorified strings) attached to the scenario. See :ref:`controlling things with tags`. .. attribute:: status Read-Only. A summary status of the scenario's run. If read before the scenario is fully tested it will return "untested" otherwise it will return one of: Status.untested The scenario was has not been completely tested yet. Status.skipped One or more steps of this scenario was passed over during testing. Status.passed The scenario was tested successfully. Status.failed One or more steps of this scenario failed. .. versionchanged:: 1.2.6 Use Status enum class (was: string) .. attribute:: hook_failed Indicates if a hook failure occured while running this scenario. .. versionadded:: 1.2.6 .. attribute:: duration The time, in seconds, that it took to test this scenario. If read before the scenario is tested it will return 0.0. .. attribute:: filename The file name (or "") of the *feature file* where the scenario was found. .. attribute:: line The line number of the *feature file* where the scenario was found. .. _`scenario`: gherkin.html#scenarios """ # pylint: disable=too-many-instance-attributes type = "scenario" continue_after_failed_step = False def __init__(self, filename, line, keyword, name, tags=None, steps=None, description=None): tags = tags or [] super(Scenario, self).__init__(filename, line, keyword, name, tags) self.description = description or [] self.steps = steps or [] self.background = None self.feature = None # REFER-TO: owner=Feature self.hook_failed = False self._background_steps = None self._row = None self.was_dry_run = False def reset(self): """Reset the internal data to reintroduce new-born state just after the ctor was called. """ super(Scenario, self).reset() self.hook_failed = False self._row = None self.was_dry_run = False for step in self.all_steps: step.reset() @property def background_steps(self): """Provide background steps if feature has a background. Lazy init that copies the background steps. Note that a copy of the background steps is needed to ensure that the background step status is specific to the scenario. :return: List of background steps or empty list """ if self._background_steps is None: # -- LAZY-INIT (need copy of background.steps): # Each scenario needs own background.steps. # Otherwise, background step status of the last-run scenario is used. steps = [] if self.background: steps = [copy.copy(step) for step in self.background.steps] self._background_steps = steps return self._background_steps @property def all_steps(self): """Returns iterator to all steps, including background steps if any.""" if self.background is not None: return itertools.chain(self.background_steps, self.steps) else: return iter(self.steps) def __repr__(self): return '' % self.name def __iter__(self): return self.all_steps def compute_status(self): """Compute the status of the scenario from its steps (and hook failures). :return: Computed status (as enum value). """ if self.hook_failed: return Status.failed for step in self.all_steps: if step.status == Status.undefined: if self.was_dry_run: # -- SPECIAL CASE: In dry-run with undefined-step discovery # Undefined steps should not cause failed scenario. return Status.untested else: # -- NORMALLY: Undefined steps cause failed scenario. return Status.failed elif step.status != Status.passed: # pylint: disable=line-too-long assert step.status in (Status.failed, Status.skipped, Status.untested) return step.status return Status.passed @property def duration(self): # -- ORIG: for step in self.steps: Background steps were excluded. scenario_duration = 0 for step in self.all_steps: scenario_duration += step.duration return scenario_duration @property def effective_tags(self): """ Effective tags for this scenario: * own tags * tags inherited from its feature """ tags = self.tags if self.feature: tags = self.feature.tags + self.tags return tags def should_run(self, config=None): """ Determines if this Scenario (or ScenarioOutline) should run. Implements the run decision logic for a scenario. The decision depends on: * if the Scenario is marked as skipped * if the config.tags (tag expression) enable/disable this scenario * if the scenario is selected by name :param config: Runner configuration to use (optional). :return: True, if scenario should run. False, otherwise. """ answer = not self.should_skip if answer and config: answer = (self.should_run_with_tags(config.tags) and self.should_run_with_name_select(config)) return answer def should_run_with_tags(self, tag_expression): """ Determines if this scenario should run when the tag expression is used. :param tag_expression: Runner/config environment tags to use. :return: True, if scenario should run. False, otherwise (skip it). """ return tag_expression.check(self.effective_tags) def should_run_with_name_select(self, config): """Determines if this scenario should run when it is selected by name. :param config: Runner/config environment name regexp (if any). :return: True, if scenario should run. False, otherwise (skip it). """ # -- SELECT-ANY: If select by name is not specified (not config.name). return not config.name or config.name_re.search(self.name) def mark_skipped(self): """Marks this scenario (and all its steps) as skipped. Note that this method can be called before the scenario is executed. """ self.skip(require_not_executed=True) assert self.status == Status.skipped or self.hook_failed, \ "OOPS: scenario.status=%s" % self.status.name def skip(self, reason=None, require_not_executed=False): """Skip from executing this scenario or the remaining parts of it. Note that the scenario may be already partly executed when this method is called. :param reason: Optional reason why it should be skipped (as string). """ if reason: scenario_type = self.__class__.__name__ logger = logging.getLogger("behave") logger.warning(u"SKIP %s %s: %s", scenario_type, self.name, reason) self.clear_status() self.should_skip = True self.skip_reason = reason for step in self.all_steps: not_executed = step.status in (Status.untested, Status.skipped) if not_executed: step.status = Status.skipped else: assert not require_not_executed, \ "REQUIRE NOT-EXECUTED, but step is %s" % step.status scenario_without_steps = not self.steps and not self.background_steps if scenario_without_steps: self.set_status(Status.skipped) assert self.status in self.final_status #< skipped, failed or passed def run(self, runner): # pylint: disable=too-many-branches, too-many-statements self.clear_status() self.captured.reset() self.hook_failed = False failed = False skip_scenario_untested = runner.aborted run_scenario = self.should_run(runner.config) run_steps = run_scenario and not runner.config.dry_run dry_run_scenario = run_scenario and runner.config.dry_run self.was_dry_run = dry_run_scenario runner.context._push(layer_name="scenario") # pylint: disable=protected-access runner.context.scenario = self runner.context.tags = set(self.effective_tags) hooks_called = False if not runner.config.dry_run and run_scenario: hooks_called = True for tag in self.tags: runner.run_hook("before_tag", runner.context, tag) runner.run_hook("before_scenario", runner.context, self) if self.hook_failed: # -- SKIP: Scenario steps and behave like dry_run_scenario failed = True # -- RE-EVALUATE SHOULD-RUN STATE: # Hook may call scenario.mark_skipped() to exclude it. skip_scenario_untested = self.hook_failed or runner.aborted run_scenario = self.should_run() run_steps = run_scenario and not runner.config.dry_run if run_scenario or runner.config.show_skipped: for formatter in runner.formatters: formatter.scenario(self) # TODO: Reevaluate location => Move in front of hook-calls runner.setup_capture() if run_scenario or runner.config.show_skipped: for step in self: for formatter in runner.formatters: formatter.step(step) if not skip_scenario_untested: for step in self.all_steps: if run_steps: if not step.run(runner): # -- CASE: Failed or undefined step # Optionally continue_after_failed_step if enabled. # But disable run_steps after undefined-step. run_steps = (self.continue_after_failed_step and step.status == Status.failed) failed = True # pylint: disable=protected-access runner.context._set_root_attribute("failed", True) self.set_status(Status.failed) elif self.should_skip: # -- CASE: Step skipped remaining scenario. # assert self.status == Status.skipped run_steps = False elif failed or dry_run_scenario: # -- SKIP STEPS: After failure/undefined-step occurred. # BUT: Detect all remaining undefined steps. step.status = Status.skipped if dry_run_scenario: # pylint: disable=redefined-variable-type step.status = Status.untested found_step_match = runner.step_registry.find_match(step) if not found_step_match: step.status = Status.undefined runner.undefined_steps.append(step) elif dry_run_scenario: # -- BETTER DIAGNOSTICS: Provide step file location # (when --format=pretty is used). assert step.status == Status.untested for formatter in runner.formatters: # -- EMULATE: Step.run() protocol w/o step execution. formatter.match(found_step_match) formatter.result(step) else: # -- SKIP STEPS: For disabled scenario. # CASES: # * Undefined steps are not detected (by intention). # * Step skipped remaining scenario. step.status = Status.skipped self.clear_status() # -- ENFORCE: compute_status() after run. if not run_scenario and not self.steps: # -- SPECIAL CASE: Scenario without steps. self.set_status(Status.skipped) if hooks_called: runner.run_hook("after_scenario", runner.context, self) for tag in self.tags: runner.run_hook("after_tag", runner.context, tag) if self.hook_failed: self.set_status(Status.failed) failed = True # -- PERFORM CONTEXT-CLEANUP: May raise cleanup errors. try: runner.context._pop() # pylint: disable=protected-access except Exception: self.set_status(Status.failed) failed = True # -- CAPTURED-OUTPUT: store_captured = (runner.config.junit or self.status == Status.failed) if store_captured: self.captured = runner.capture_controller.captured runner.teardown_capture() return failed class ScenarioOutlineBuilder(object): """Helper class to use a ScenarioOutline as a template and build its scenarios (as template instances). """ def __init__(self, annotation_schema): self.annotation_schema = annotation_schema @staticmethod def render_template(text, row=None, params=None): """Render a text template with placeholders, ala "Hello ". :param row: As placeholder provider (dict-like). :param params: As additional placeholder provider (as dict). :return: Rendered text, known placeholders are substituted w/ values. """ if not ("<" in text and ">" in text): return text safe_values = False for placeholders in (row, params): if not placeholders: continue for name, value in placeholders.items(): if safe_values and ("<" in value and ">" in value): continue # -- OOPS, value looks like placeholder. text = text.replace("<%s>" % name, value) return text def make_scenario_name(self, outline_name, example, row, params=None): """Build a scenario name for an example row of this scenario outline. Placeholders for row data are replaced by values. SCHEMA: "{outline_name} -*- {examples.name}@{row.id}" :param outline_name: ScenarioOutline's name (as template). :param example: Examples object. :param row: Row of this example. :param params: Additional placeholders for example/row. :return: Computed name for the scenario representing example/row. """ if params is None: params = {} params["examples.name"] = example.name or "" params.setdefault("examples.index", example.index) params.setdefault("row.index", row.index) params.setdefault("row.id", row.id) # -- STEP: Replace placeholders in scenario/example name (if any). examples_name = self.render_template(example.name, row, params) params["examples.name"] = examples_name scenario_name = self.render_template(outline_name, row, params) class Data(object): def __init__(self, name, index): self.name = name self.index = index self.id = name # pylint: disable=invalid-name example_data = Data(examples_name, example.index) row_data = Data(row.id, row.index) return self.annotation_schema.format(name=scenario_name, examples=example_data, row=row_data) @classmethod def make_row_tags(cls, outline_tags, row, params=None): if not outline_tags: return [] tags = [] for tag in outline_tags: if "<" in tag and ">" in tag: tag = cls.render_template(tag, row, params) if "<" in tag or ">" in tag: # -- OOPS: Unknown placeholder, drop tag. continue new_tag = Tag.make_name(tag, unescape=True) tags.append(new_tag) return tags @classmethod def make_step_for_row(cls, outline_step, row, params=None): # -- BASED-ON: new_step = outline_step.set_values(row) new_step = copy.deepcopy(outline_step) new_step.name = cls.render_template(new_step.name, row, params) if new_step.text: new_step.text = cls.render_template(new_step.text, row) if new_step.table: for name, value in row.items(): for row in new_step.table: for i, cell in enumerate(row.cells): row.cells[i] = cell.replace("<%s>" % name, value) return new_step def build_scenarios(self, scenario_outline): """Build scenarios for a ScenarioOutline from its examples.""" # -- BUILD SCENARIOS (once): For this ScenarioOutline from examples. params = { "examples.name": None, "examples.index": None, "row.index": None, "row.id": None, } scenarios = [] for example_index, example in enumerate(scenario_outline.examples): example.index = example_index+1 params["examples.name"] = example.name params["examples.index"] = _text(example.index) for row_index, row in enumerate(example.table): row.index = row_index+1 row.id = "%d.%d" % (example.index, row.index) params["row.id"] = row.id params["row.index"] = _text(row.index) scenario_name = self.make_scenario_name(scenario_outline.name, example, row, params) row_tags = self.make_row_tags(scenario_outline.tags, row, params) row_tags.extend(example.tags) new_steps = [] for outline_step in scenario_outline.steps: new_step = self.make_step_for_row(outline_step, row, params) new_steps.append(new_step) # -- STEP: Make Scenario name for this row. # scenario_line = example.line + 2 + row_index scenario_line = row.line scenario = Scenario(scenario_outline.filename, scenario_line, scenario_outline.keyword, scenario_name, row_tags, new_steps) scenario.feature = scenario_outline.feature scenario.background = scenario_outline.background scenario._row = row # pylint: disable=protected-access scenarios.append(scenario) return scenarios class ScenarioOutline(Scenario): """A `scenario outline`_ parsed from a *feature file*. A scenario outline extends the existing :class:`~behave.model.Scenario` class with the addition of the :class:`~behave.model.Examples` tables of data from the *feature file*. The attributes are: .. attribute:: keyword This is the keyword as seen in the *feature file*. In English this will typically be "Scenario Outline". .. attribute:: name The name of the scenario (the text after "Scenario Outline:".) .. attribute:: description The description of the `scenario outline`_ as seen in the *feature file*. This is stored as a list of text lines. .. attribute:: feature The :class:`~behave.model.Feature` this scenario outline belongs to. .. attribute:: steps A list of :class:`~behave.model.Step` making up this scenario outline. .. attribute:: examples A list of :class:`~behave.model.Examples` used by this scenario outline. .. attribute:: tags A list of @tags (as :class:`~behave.model.Tag` which are basically glorified strings) attached to the scenario. See :ref:`controlling things with tags`. .. attribute:: status Read-Only. A summary status of the scenario outlines's run. If read before the scenario is fully tested it will return "untested" otherwise it will return one of: Status.untested The scenario was has not been completely tested yet. Status.skipped One or more scenarios of this outline was passed over during testing. Status.passed The scenario was tested successfully. Status.failed One or more scenarios of this outline failed. .. versionchanged:: 1.2.6 Use Status enum class (was: string) .. attribute:: duration The time, in seconds, that it took to test the scenarios of this outline. If read before the scenarios are tested it will return 0.0. .. attribute:: filename The file name (or "") of the *feature file* where the scenario was found. .. attribute:: line The line number of the *feature file* where the scenario was found. .. _`scenario outline`: gherkin.html#scenario-outlines """ type = "scenario_outline" annotation_schema = u"{name} -- @{row.id} {examples.name}" def __init__(self, filename, line, keyword, name, tags=None, steps=None, examples=None, description=None): super(ScenarioOutline, self).__init__(filename, line, keyword, name, tags, steps, description) self.examples = examples or [] self._scenarios = [] def reset(self): """Reset runtime temporary data like before a test run.""" super(ScenarioOutline, self).reset() for scenario in self._scenarios: # -- AVOID: BUILD-SCENARIOS scenario.reset() @property def scenarios(self): """Return the scenarios with the steps altered to take the values from the examples. """ if self._scenarios: return self._scenarios # -- BUILD SCENARIOS (once): For this ScenarioOutline from examples. builder = ScenarioOutlineBuilder(self.annotation_schema) self._scenarios = builder.build_scenarios(self) return self._scenarios def __repr__(self): return '' % self.name def __iter__(self): return iter(self.scenarios) # -- REQUIRE: BUILD-SCENARIOS def compute_status(self): skipped_count = 0 for scenario in self._scenarios: # -- AVOID: BUILD-SCENARIOS scenario_status = scenario.status if scenario_status in (Status.failed, Status.untested): return scenario_status elif scenario_status == Status.skipped: skipped_count += 1 if skipped_count > 0 and skipped_count == len(self._scenarios): # -- ALL SKIPPED: return Status.skipped # -- OTHERWISE: ALL PASSED (some scenarios may have been excluded) return Status.passed @property def duration(self): outline_duration = 0 for scenario in self._scenarios: # -- AVOID: BUILD-SCENARIOS outline_duration += scenario.duration return outline_duration def should_run_with_tags(self, tag_expression): """Determines if this scenario outline (or one of its scenarios) should run when the tag expression is used. :param tag_expression: Runner/config environment tags to use. :return: True, if scenario should run. False, otherwise (skip it). """ if tag_expression.check(self.effective_tags): return True for scenario in self.scenarios: # -- REQUIRE: BUILD-SCENARIOS if scenario.should_run_with_tags(tag_expression): return True # -- NOTHING SELECTED: return False def should_run_with_name_select(self, config): """Determines if this scenario should run when it is selected by name. :param config: Runner/config environment name regexp (if any). :return: True, if scenario should run. False, otherwise (skip it). """ if not config.name: return True # -- SELECT-ALL: Select by name is not specified. for scenario in self.scenarios: # -- REQUIRE: BUILD-SCENARIOS if scenario.should_run_with_name_select(config): return True # -- NOTHING SELECTED: return False def mark_skipped(self): """Marks this scenario outline (and all its scenarios/steps) as skipped. Note that this method may be called before the scenario outline is executed. """ self.skip(require_not_executed=True) assert self.status == Status.skipped def skip(self, reason=None, require_not_executed=False): """Skip from executing this scenario outline or its remaining parts. Note that the scenario outline may be already partly executed when this method is called. :param reason: Optional reason why it should be skipped (as string). """ if reason: logger = logging.getLogger("behave") logger.warning(u"SKIP ScenarioOutline %s: %s", self.name, reason) self.clear_status() self.should_skip = True for scenario in self.scenarios: scenario.skip(reason, require_not_executed) if not self.scenarios: # -- SPECIAL CASE: ScenarioOutline without scenarios/examples self.set_status(Status.skipped) assert self.status in self.final_status #< skipped, failed or passed def run(self, runner): # pylint: disable=protected-access # REASON: context._set_root_attribute(), scenario._row self.clear_status() failed_count = 0 for scenario in self.scenarios: # -- REQUIRE: BUILD-SCENARIOS runner.context._set_root_attribute("active_outline", scenario._row) failed = scenario.run(runner) if failed: failed_count += 1 if runner.config.stop or runner.aborted: # -- FAIL-EARLY: Stop after first failure. break runner.context._set_root_attribute("active_outline", None) return failed_count > 0 class Examples(TagStatement, Replayable): """A table parsed from a `scenario outline`_ in a *feature file*. The attributes are: .. attribute:: keyword This is the keyword as seen in the *feature file*. In English this will typically be "Example". .. attribute:: name The name of the example (the text after "Example:".) .. attribute:: table An instance of :class:`~behave.model.Table` that came with the example in the *feature file*. .. attribute:: filename The file name (or "") of the *feature file* where the example was found. .. attribute:: line The line number of the *feature file* where the example was found. .. _`examples`: gherkin.html#examples """ type = "examples" def __init__(self, filename, line, keyword, name, tags=None, table=None): super(Examples, self).__init__(filename, line, keyword, name, tags) self.table = table self.index = None class Step(BasicStatement, Replayable): """A single `step`_ parsed from a *feature file*. The attributes are: .. attribute:: keyword This is the keyword as seen in the *feature file*. In English this will typically be "Given", "When", "Then" or a number of other words. .. attribute:: name The name of the step (the text after "Given" etc.) .. attribute:: step_type The type of step as determined by the keyword. If the keyword is "and" then the previous keyword in the *feature file* will determine this step's step_type. .. attribute:: text An instance of :class:`~behave.model.Text` that came with the step in the *feature file*. .. attribute:: table An instance of :class:`~behave.model.Table` that came with the step in the *feature file*. .. attribute:: status Read-Only. A summary status of the step's run. If read before the step is tested it will return "untested" otherwise it will return one of: Status.untested This step was not run (yet). Status.skipped This step was skipped during testing. Status.passed The step was tested successfully. Status.failed The step failed. Status.undefined The step has no matching step implementation. .. versionchanged:: Use Status enum class (was: string). .. attribute:: hook_failed Indicates if a hook failure occured while running this step. .. versionadded:: 1.2.6 .. attribute:: duration The time, in seconds, that it took to test this step. If read before the step is tested it will return 0.0. .. attribute:: error_message If the step failed then this will hold any error information, as a single string. It will otherwise be None. .. versionchanged:: 1.2.6 (moved to base class) .. attribute:: filename The file name (or "") of the *feature file* where the step was found. .. attribute:: line The line number of the *feature file* where the step was found. .. _`step`: gherkin.html#steps """ type = "step" def __init__(self, filename, line, keyword, step_type, name, text=None, table=None): super(Step, self).__init__(filename, line, keyword, name) self.step_type = step_type self.text = text self.table = table self.status = Status.untested self.hook_failed = False self.duration = 0 def reset(self): """Reset temporary runtime data to reach clean state again.""" super(Step, self).reset() self.status = Status.untested self.hook_failed = False self.duration = 0 # -- POSTCONDITION: assert self.status == Status.untested def __repr__(self): return '<%s "%s">' % (self.step_type, self.name) def __eq__(self, other): return (self.step_type, self.name) == (other.step_type, other.name) def __hash__(self): return hash(self.step_type) + hash(self.name) def set_values(self, table_row): """Clone a new step from this one, used for ScenarioOutline. Replace ScenarioOutline placeholders w/ values. :param table_row: Placeholder data for example row. :return: Cloned, adapted step object. .. note:: Deprecating Use 'ScenarioOutlineBuilder.make_step_for_row()' instead. """ import warnings warnings.warn("Use 'ScenarioOutline.make_step_for_row()' instead", PendingDeprecationWarning, stacklevel=2) outline_step = self return ScenarioOutlineBuilder.make_step_for_row(outline_step, table_row) def run(self, runner, quiet=False, capture=True): # pylint: disable=too-many-branches, too-many-statements # -- RESET: Run-time information. # self.status = Status.untested # self.hook_failed = False self.reset() match = runner.step_registry.find_match(self) if match is None: runner.undefined_steps.append(self) if not quiet: for formatter in runner.formatters: formatter.match(NoMatch()) self.status = Status.undefined if not quiet: for formatter in runner.formatters: formatter.result(self) return False keep_going = True error = u"" if not quiet: for formatter in runner.formatters: formatter.match(match) if capture: runner.start_capture() skip_step_untested = False runner.run_hook("before_step", runner.context, self) if self.hook_failed: skip_step_untested = True start = time.time() if not skip_step_untested: try: # -- ENSURE: # * runner.context.text/.table attributes are reset (#66). # * Even EMPTY multiline text is available in context. runner.context.text = self.text runner.context.table = self.table match.run(runner.context) if self.status == Status.untested: # -- NOTE: Executed step may have skipped scenario and itself. # pylint: disable=redefined-variable-type self.status = Status.passed except KeyboardInterrupt as e: runner.aborted = True error = u"ABORTED: By user (KeyboardInterrupt)." self.status = Status.failed self.store_exception_context(e) except AssertionError as e: self.status = Status.failed self.store_exception_context(e) if e.args: message = _text(e) error = u"Assertion Failed: "+ message else: # no assertion text; format the exception error = _text(traceback.format_exc()) except Exception as e: # pylint: disable=broad-except self.status = Status.failed error = _text(traceback.format_exc()) self.store_exception_context(e) self.duration = time.time() - start runner.run_hook("after_step", runner.context, self) if self.hook_failed: self.status = Status.failed if capture: runner.stop_capture() # flesh out the failure with details store_captured_always = False # PREPARED store_captured = self.status == Status.failed or store_captured_always if self.status == Status.failed: assert isinstance(error, six.text_type) if capture: # -- CAPTURE-ONLY: Non-nested step failures. self.captured = runner.capture_controller.captured error2 = self.captured.make_report() if error2: error += "\n" + error2 self.error_message = error keep_going = False elif store_captured and capture: self.captured = runner.capture_controller.captured if not quiet: for formatter in runner.formatters: formatter.result(self) return keep_going class Table(Replayable): """A `table`_ extracted from a *feature file*. Table instance data is accessible using a number of methods: **iteration** Iterating over the Table will yield the :class:`~behave.model.Row` instances from the .rows attribute. **indexed access** Individual rows may be accessed directly by index on the Table instance; table[0] gives the first non-heading row and table[-1] gives the last row. The attributes are: .. attribute:: headings The headings of the table as a list of strings. .. attribute:: rows An list of instances of :class:`~behave.model.Row` that make up the body of the table in the *feature file*. Tables are also comparable, for what that's worth. Headings and row data are compared. .. _`table`: gherkin.html#table """ type = "table" def __init__(self, headings, line=None, rows=None): Replayable.__init__(self) self.headings = headings self.line = line self.rows = [] if rows: for row in rows: self.add_row(row, line) def add_row(self, row, line=None): self.rows.append(Row(self.headings, row, line)) def add_column(self, column_name, values=None, default_value=u""): """Adds a new column to this table. Uses :param:`default_value` for new cells (if :param:`values` are not provided). param:`values` are extended with :param:`default_value` if values list is smaller than the number of table rows. :param column_name: Name of new column (as string). :param values: Optional list of cell values in new column. :param default_value: Default value for cell (if values not provided). :returns: Index of new column (as number). """ # assert isinstance(column_name, unicode) assert not self.has_column(column_name) if values is None: values = [default_value] * len(self.rows) elif not isinstance(values, list): values = list(values) if len(values) < len(self.rows): more_size = len(self.rows) - len(values) more_values = [default_value] * more_size values.extend(more_values) new_column_index = len(self.headings) self.headings.append(column_name) for row, value in zip(self.rows, values): assert len(row.cells) == new_column_index row.cells.append(value) return new_column_index def remove_column(self, column_name): if not isinstance(column_name, int): try: column_index = self.get_column_index(column_name) except ValueError: raise KeyError("column=%s is unknown" % column_name) assert isinstance(column_index, int) assert column_index < len(self.headings) del self.headings[column_index] for row in self.rows: assert column_index < len(row.cells) del row.cells[column_index] def remove_columns(self, column_names): for column_name in column_names: self.remove_column(column_name) def has_column(self, column_name): return column_name in self.headings def get_column_index(self, column_name): return self.headings.index(column_name) def require_column(self, column_name): """Require that a column exists in the table. Raise an AssertionError if the column does not exist. :param column_name: Name of new column (as string). :return: Index of column (as number) if it exists. """ if not self.has_column(column_name): columns = ", ".join(self.headings) msg = "REQUIRE COLUMN: %s (columns: %s)" % (column_name, columns) raise AssertionError(msg) return self.get_column_index(column_name) def require_columns(self, column_names): for column_name in column_names: self.require_column(column_name) def ensure_column_exists(self, column_name): """Ensures that a column with the given name exists. If the column does not exist, the column is added. :param column_name: Name of column (as string). :return: Index of column (as number). """ if self.has_column(column_name): return self.get_column_index(column_name) else: return self.add_column(column_name) def __repr__(self): return "" % (len(self.headings), len(self.rows)) def __eq__(self, other): if isinstance(other, Table): if self.headings != other.headings: return False for my_row, their_row in zip(self.rows, other.rows): if my_row != their_row: return False else: # -- ASSUME: table <=> raw data comparison other_rows = other for my_row, their_row in zip(self.rows, other_rows): if my_row != their_row: return False return True def __ne__(self, other): return not self.__eq__(other) def __iter__(self): return iter(self.rows) def __getitem__(self, index): return self.rows[index] def assert_equals(self, data): """Assert that this table's cells are the same as the supplied "data". The data passed in must be a list of lists giving: [ [row 1], [row 2], [row 3], ] If the cells do not match then a useful AssertionError will be raised. """ assert self == data raise NotImplementedError class Row(object): """One row of a `table`_ parsed from a *feature file*. Row data is accessible using a number of methods: **iteration** Iterating over the Row will yield the individual cells as strings. **named access** Individual cells may be accessed by heading name; row["name"] would give the cell value for the column with heading "name". **indexed access** Individual cells may be accessed directly by index on the Row instance; row[0] gives the first cell and row[-1] gives the last cell. The attributes are: .. attribute:: cells The list of strings that form the cells of this row. .. attribute:: headings The headings of the table as a list of strings. Rows are also comparable, for what that's worth. Only the cells are compared. .. _`table`: gherkin.html#table """ def __init__(self, headings, cells, line=None, comments=None): self.headings = headings self.comments = comments for c in cells: assert isinstance(c, six.text_type) self.cells = cells self.line = line def __getitem__(self, name): try: index = self.headings.index(name) except ValueError: if isinstance(name, int): index = name else: raise KeyError('"%s" is not a row heading' % name) return self.cells[index] def __repr__(self): return "" % (self.cells,) def __eq__(self, other): return self.cells == other.cells def __ne__(self, other): return not self.__eq__(other) def __len__(self): return len(self.cells) def __iter__(self): return iter(self.cells) def items(self): return zip(self.headings, self.cells) def get(self, key, default=None): try: return self[key] except KeyError: return default def as_dict(self): """Converts the row and its cell data into a dictionary. :return: Row data as dictionary (without comments, line info). """ from behave.compat.collections import OrderedDict return OrderedDict(self.items()) class Tag(six.text_type): """Tags appear may be associated with Features or Scenarios. They're a subclass of regular strings (unicode pre-Python 3) with an additional ``line`` number attribute (where the tag was seen in the source feature file. See :ref:`controlling things with tags`. """ allowed_chars = u"._-=:" # In addition to aplha-numerical chars. quoting_chars = ("'", '"', "<", ">") def __new__(cls, name, line): o = six.text_type.__new__(cls, name) o.line = line return o @classmethod def make_name(cls, text, unescape=False, allowed_chars=None): """Translate text into a "valid tag" without whitespace, etc. Translation rules are: * alnum chars => same, kept * space chars => "_" * other chars => deleted Preserve following characters (in addition to alnums, like: A-z, 0-9): * dots => "." (support: dotted-names, active-tag name schema) * minus => "-" (support: dashed-names) * underscore => "_" * equal => "=" (support: active-tag name schema) * colon => ":" (support: active-tag name schema or similar) :param text: Unicode text as input for name. :param unescape: Optional flag to unescape some chars (default: false) :param allowed_chars: Optional string with additional preserved chars. :return: Unicode name that can be used as tag. """ assert isinstance(text, six.text_type) if allowed_chars is None: allowed_chars = cls.allowed_chars if unescape: # -- UNESCAPE: Some escaped sequences text = text.replace("\\t", "\t").replace("\\n", "\n") chars = [] for char in text: if char.isalnum() or (allowed_chars and char in allowed_chars): chars.append(char) elif char.isspace(): chars.append(u"_") elif char in cls.quoting_chars: pass # -- NORMALIZE: Remove any quoting chars. # -- MAYBE: # else: # # -- OTHERWISE: Accept gracefully any other character. # chars.append(char) return u"".join(chars) class Text(six.text_type): """Store multiline text from a Step definition. The attributes are: .. attribute:: value The actual text parsed from the *feature file*. .. attribute:: content_type Currently only "text/plain". """ def __new__(cls, value, content_type=u"text/plain", line=0): assert isinstance(value, six.text_type) assert isinstance(content_type, six.text_type) o = six.text_type.__new__(cls, value) o.content_type = content_type o.line = line return o def line_range(self): line_count = len(self.splitlines()) return (self.line, self.line + line_count + 1) def replace(self, old, new, count=-1): return Text(super(Text, self).replace(old, new, count), self.content_type, self.line) def assert_equals(self, expected): """Assert that my text is identical to the "expected" text. A nice context diff will be displayed if they do not match. """ if self == expected: return True diff = [] for line in difflib.unified_diff(self.splitlines(), expected.splitlines()): diff.append(line) # strip unnecessary diff prefix diff = ["Text does not match:"] + diff[3:] raise AssertionError("\n".join(diff)) # ----------------------------------------------------------------------------- # UTILITY FUNCTIONS: # ----------------------------------------------------------------------------- def reset_model(model_elements): """Reset the test run information stored in model elements. :param model_elements: List of model elements (Feature, Scenario, ...) """ for model_element in model_elements: model_element.reset() behave-1.2.6/behave/model_core.py0000644000076600000240000003244413244555737016777 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ This module provides the abstract base classes and core concepts for the model elements in behave. """ import os.path import sys import six from behave.capture import Captured from behave.textutil import text as _text from enum import Enum PLATFORM_WIN = sys.platform.startswith("win") def posixpath_normalize(path): return path.replace("\\", "/") # ----------------------------------------------------------------------------- # GENERIC MODEL CLASSES: # ----------------------------------------------------------------------------- class Status(Enum): """Provides the (test-run) status of a model element. Features and Scenarios use: untested, skipped, passed, failed. Steps may use all enum-values. Enum values: * untested (initial state): Defines the initial state before a test-run. Sometimes used to indicate that the model element was not executed during a test run. * skipped: A model element is skipped because it should not run. This is caused by filtering mechanisms, like tags, active-tags, file-location arg, select-by-name, etc. * passed: A model element was executed and passed (without failures). * failed: Failures occurred while executing it. * undefined: Used for undefined-steps (no step implementation was found). * executing: Marks the steps during execution (used in a formatter) .. versionadded:: 1.2.6 Superceeds string-based status values. """ untested = 0 skipped = 1 passed = 2 failed = 3 undefined = 4 executing = 5 def __eq__(self, other): """Comparison operator equals-to other value. Supports other enum-values and string (for backward compatibility). EXAMPLES:: status = Status.passed assert status == Status.passed assert status == "passed" assert status != "failed" :param other: Other value to compare (enum-value, string). :return: True, if both values are equal. False, otherwise. """ if isinstance(other, six.string_types): # -- CONVENIENCE: Compare with string-name (backward-compatible) return self.name == other return super(Status, self).__eq__(other) @classmethod def from_name(cls, name): """Select enumeration value by using its name. :param name: Name as key to the enum value (as string). :return: Enum value (instance) :raises: LookupError, if status name is unknown. """ # pylint: disable=no-member enum_value = cls.__members__.get(name, None) if enum_value is None: known_names = ", ".join(cls.__members__.keys()) raise LookupError("%s (expected: %s)" % (name, known_names)) return enum_value class Argument(object): """An argument found in a *feature file* step name and extracted using step decorator `parameters`_. The attributes are: .. attribute:: original The actual text matched in the step name. .. attribute:: value The potentially type-converted value of the argument. .. attribute:: name The name of the argument. This will be None if the parameter is anonymous. .. attribute:: start The start index in the step name of the argument. Used for display. .. attribute:: end The end index in the step name of the argument. Used for display. """ def __init__(self, start, end, original, value, name=None): self.start = start self.end = end self.original = original self.value = value self.name = name # @total_ordering # class FileLocation(unicode): class FileLocation(object): """ Provides a value object for file location objects. A file location consists of: * filename * line (number), optional LOCATION SCHEMA: * "{filename}:{line}" or * "{filename}" (if line number is not present) """ __pychecker__ = "missingattrs=line" # -- Ignore warnings for 'line'. def __init__(self, filename, line=None): if PLATFORM_WIN: filename = posixpath_normalize(filename) self.filename = filename self.line = line def get(self): return self.filename def abspath(self): return os.path.abspath(self.filename) def basename(self): return os.path.basename(self.filename) def dirname(self): return os.path.dirname(self.filename) def relpath(self, start=os.curdir): """Compute relative path for start to filename. :param start: Base path or start directory (default=current dir). :return: Relative path from start to filename """ return os.path.relpath(self.filename, start) def exists(self): return os.path.exists(self.filename) def _line_lessthan(self, other_line): if self.line is None: # return not (other_line is None) return other_line is not None elif other_line is None: return False else: return self.line < other_line def __eq__(self, other): if isinstance(other, FileLocation): return self.filename == other.filename and self.line == other.line elif isinstance(other, six.string_types): return self.filename == other else: raise TypeError("Cannot compare FileLocation with %s:%s" % \ (type(other), other)) def __ne__(self, other): # return not self == other # pylint: disable=unneeded-not return not self.__eq__(other) def __lt__(self, other): if isinstance(other, FileLocation): if self.filename < other.filename: return True elif self.filename > other.filename: return False else: assert self.filename == other.filename return self._line_lessthan(other.line) elif isinstance(other, six.string_types): return self.filename < other else: raise TypeError("Cannot compare FileLocation with %s:%s" % \ (type(other), other)) def __le__(self, other): # -- SEE ALSO: python2.7, functools.total_ordering # return not other < self # pylint unneeded-not return other >= self def __gt__(self, other): # -- SEE ALSO: python2.7, functools.total_ordering if isinstance(other, FileLocation): return other < self else: return self.filename > other def __ge__(self, other): # -- SEE ALSO: python2.7, functools.total_ordering # return not self < other return not self.__lt__(other) def __repr__(self): return u'' % \ (self.filename, self.line) def __str__(self): filename = self.filename if isinstance(filename, six.binary_type): filename = _text(filename, "utf-8") if self.line is None: return filename return u"%s:%d" % (filename, self.line) if six.PY2: __unicode__ = __str__ __str__ = lambda self: self.__unicode__().encode("utf-8") @classmethod def for_function(cls, func, curdir=None): """Extracts the location information from the function and builds the location string (schema: "{source_filename}:{line_number}"). :param func: Function whose location should be determined. :return: FileLocation object """ func = unwrap_function(func) function_code = six.get_function_code(func) filename = function_code.co_filename line_number = function_code.co_firstlineno curdir = curdir or os.getcwd() try: filename = os.path.relpath(filename, curdir) except ValueError: # WINDOWS-SPECIFIC (#599): # If a step-function comes from a different disk drive, # a relative path will fail: Keep the absolute path. pass return cls(filename, line_number) # ----------------------------------------------------------------------------- # ABSTRACT MODEL CLASSES (and concepts): # ----------------------------------------------------------------------------- class BasicStatement(object): def __init__(self, filename, line, keyword, name): filename = filename or '' filename = os.path.relpath(filename, os.getcwd()) # -- NEEDS: abspath? self.location = FileLocation(filename, line) assert isinstance(keyword, six.text_type) assert isinstance(name, six.text_type) self.keyword = keyword self.name = name # -- SINCE: 1.2.6 self.captured = Captured() # -- ERROR CONTEXT INFO: self.exception = None self.exc_traceback = None self.error_message = None @property def filename(self): # return os.path.abspath(self.location.filename) return self.location.filename @property def line(self): return self.location.line def reset(self): # -- RESET: Captured output data self.captured.reset() # -- RESET: ERROR CONTEXT INFO self.exception = None self.exc_traceback = None self.error_message = None def store_exception_context(self, exception): self.exception = exception self.exc_traceback = sys.exc_info()[2] def __hash__(self): # -- NEEDED-FOR: PYTHON3 # return id((self.keyword, self.name)) return id(self) def __eq__(self, other): # -- PYTHON3 SUPPORT, ORDERABLE: # NOTE: Ignore potential FileLocation differences. return (self.keyword, self.name) == (other.keyword, other.name) def __lt__(self, other): # -- PYTHON3 SUPPORT, ORDERABLE: # NOTE: Ignore potential FileLocation differences. return (self.keyword, self.name) < (other.keyword, other.name) def __ne__(self, other): return not self.__eq__(other) def __le__(self, other): # -- SEE ALSO: python2.7, functools.total_ordering # return not other < self return other >= self def __gt__(self, other): # -- SEE ALSO: python2.7, functools.total_ordering assert isinstance(other, BasicStatement) return other < self def __ge__(self, other): # -- SEE ALSO: python2.7, functools.total_ordering # OR: return self >= other return not self < other # pylint: disable=unneeded-not # def __cmp__(self, other): # # -- NOTE: Ignore potential FileLocation differences. # return cmp((self.keyword, self.name), (other.keyword, other.name)) class TagStatement(BasicStatement): def __init__(self, filename, line, keyword, name, tags): if tags is None: tags = [] super(TagStatement, self).__init__(filename, line, keyword, name) self.tags = tags def should_run_with_tags(self, tag_expression): """Determines if statement should run when the tag expression is used. :param tag_expression: Runner/config environment tags to use. :return: True, if examples should run. False, otherwise (skip it). """ return tag_expression.check(self.tags) class TagAndStatusStatement(BasicStatement): # final_status = ('passed', 'failed', 'skipped') final_status = (Status.passed, Status.failed, Status.skipped) def __init__(self, filename, line, keyword, name, tags): super(TagAndStatusStatement, self).__init__(filename, line, keyword, name) self.tags = tags self.should_skip = False self.skip_reason = None self._cached_status = Status.untested def should_run_with_tags(self, tag_expression): """Determines if statement should run when the tag expression is used. :param tag_expression: Runner/config environment tags to use. :return: True, if examples should run. False, otherwise (skip it). """ return tag_expression.check(self.tags) @property def status(self): if self._cached_status not in self.final_status: # -- RECOMPUTE: As long as final status is not reached. self._cached_status = self.compute_status() return self._cached_status def set_status(self, value): if isinstance(value, six.string_types): value = Status.from_name(value) self._cached_status = value def clear_status(self): self._cached_status = Status.untested def reset(self): self.should_skip = False self.skip_reason = None self.clear_status() def compute_status(self): raise NotImplementedError class Replayable(object): type = None def replay(self, formatter): getattr(formatter, self.type)(self) # ----------------------------------------------------------------------------- # UTILITY FUNCTIONS: # ----------------------------------------------------------------------------- def unwrap_function(func, max_depth=10): """Unwraps a function that is wrapped with :func:`functools.partial()`""" iteration = 0 wrapped = getattr(func, "__wrapped__", None) while wrapped and iteration < max_depth: func = wrapped wrapped = getattr(func, "__wrapped__", None) iteration += 1 return func behave-1.2.6/behave/model_describe.py0000644000076600000240000000672313244555737017630 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Provides textual descriptions for :mod:`behave.model` elements. """ from __future__ import absolute_import from six.moves import range # pylint: disable=redefined-builtin from six.moves import zip # pylint: disable=redefined-builtin from behave.textutil import indent # ----------------------------------------------------------------------------- # FUNCTIONS: # ----------------------------------------------------------------------------- def escape_cell(cell): """ Escape table cell contents. :param cell: Table cell (as unicode string). :return: Escaped cell (as unicode string). """ cell = cell.replace(u'\\', u'\\\\') cell = cell.replace(u'\n', u'\\n') cell = cell.replace(u'|', u'\\|') return cell def escape_triple_quotes(text): """ Escape triple-quotes, used for multi-line text/doc-strings. """ return text.replace(u'"""', u'\\"\\"\\"') # ----------------------------------------------------------------------------- # CLASS: # ----------------------------------------------------------------------------- class ModelDescriptor(object): @staticmethod def describe_table(table, indentation=None): """ Provide a textual description of the table (as used w/ Gherkin). :param table: Table to use (as :class:`behave.model.Table`) :param indentation: Line prefix to use (as string, if any). :return: Textual table description (as unicode string). """ # -- STEP: Determine output size of all cells. cell_lengths = [] all_rows = [table.headings] + table.rows for row in all_rows: lengths = [len(escape_cell(c)) for c in row] cell_lengths.append(lengths) # -- STEP: Determine max. output size for each column. max_lengths = [] for col in range(0, len(cell_lengths[0])): max_lengths.append(max([c[col] for c in cell_lengths])) # -- STEP: Build textual table description. lines = [] for r, row in enumerate(all_rows): line = u"|" for c, (cell, max_length) in enumerate(zip(row, max_lengths)): pad_size = max_length - cell_lengths[r][c] line += u" %s%s |" % (escape_cell(cell), " " * pad_size) line += u"\n" lines.append(line) if indentation: return indent(lines, indentation) # -- OTHERWISE: return u"".join(lines) @staticmethod def describe_docstring(doc_string, indentation=None): """ Provide a textual description of the multi-line text/triple-quoted doc-string (as used w/ Gherkin). :param doc_string: Multi-line text to use. :param indentation: Line prefix to use (as string, if any). :return: Textual table description (as unicode string). """ text = escape_triple_quotes(doc_string) text = u'"""\n' + text + '\n"""\n' if indentation: text = indent(text, indentation) return text class ModelPrinter(ModelDescriptor): def __init__(self, stream): super(ModelPrinter, self).__init__() self.stream = stream def print_table(self, table, indentation=None): self.stream.write(self.describe_table(table, indentation)) self.stream.flush() def print_docstring(self, text, indentation=None): self.stream.write(self.describe_docstring(text, indentation)) self.stream.flush() behave-1.2.6/behave/parser.py0000644000076600000240000005251613244555737016165 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- from __future__ import absolute_import, with_statement import re import sys import six from behave import model, i18n from behave.textutil import text as _text DEFAULT_LANGUAGE = "en" def parse_file(filename, language=None): with open(filename, "rb") as f: # file encoding is assumed to be utf8. Oh, yes. data = f.read().decode("utf8") return parse_feature(data, language, filename) def parse_feature(data, language=None, filename=None): # ALL data operated on by the parser MUST be unicode assert isinstance(data, six.text_type) try: result = Parser(language).parse(data, filename) except ParserError as e: e.filename = filename raise return result def parse_steps(text, language=None, filename=None): """ Parse a number of steps a multi-line text from a scenario. Scenario line with title and keyword is not provided. :param text: Multi-line text with steps to parse (as unicode). :param language: i18n language identifier (optional). :param filename: Filename (optional). :return: Parsed steps (if successful). """ assert isinstance(text, six.text_type) try: result = Parser(language, variant="steps").parse_steps(text, filename) except ParserError as e: e.filename = filename raise return result def parse_tags(text): """ Parse tags from text (one or more lines, as string). :param text: Multi-line text with tags to parse (as unicode). :return: List of tags (if successful). """ # assert isinstance(text, unicode) if not text: return [] return Parser(variant="tags").parse_tags(text) class ParserError(Exception): def __init__(self, message, line, filename=None, line_text=None): if line: message += u" at line %d" % line if line_text: message += u': "%s"' % line_text.strip() super(ParserError, self).__init__(message) self.line = line self.line_text = line_text self.filename = filename def __str__(self): arg0 = _text(self.args[0]) if self.filename: filename = _text(self.filename, sys.getfilesystemencoding()) return u'Failed to parse "%s": %s' % (filename, arg0) else: return u"Failed to parse : %s" % arg0 if six.PY2: __unicode__ = __str__ __str__ = lambda self: self.__unicode__().encode("utf-8") class Parser(object): """Feature file parser for behave.""" # pylint: disable=too-many-instance-attributes def __init__(self, language=None, variant=None): if not variant: variant = "feature" self.language = language self.variant = variant self.state = "init" self.line = 0 self.last_step = None self.multiline_start = None self.multiline_leading = None self.multiline_terminator = None self.filename = None self.feature = None self.statement = None self.tags = [] self.lines = [] self.table = None self.examples = None self.keywords = None if self.language: self.keywords = i18n.languages[self.language] # NOT-NEEDED: self.reset() def reset(self): # This can probably go away. if self.language: self.keywords = i18n.languages[self.language] else: self.keywords = None self.state = "init" self.line = 0 self.last_step = None self.multiline_start = None self.multiline_leading = None self.multiline_terminator = None self.filename = None self.feature = None self.statement = None self.tags = [] self.lines = [] self.table = None self.examples = None def parse(self, data, filename=None): self.reset() self.filename = filename for line in data.split("\n"): self.line += 1 if not line.strip() and self.state != "multiline": # -- SKIP EMPTY LINES, except in multiline string args. continue self.action(line) if self.table: self.action_table("") feature = self.feature if feature: feature.parser = self self.reset() return feature def _build_feature(self, keyword, line): name = line[len(keyword) + 1:].strip() language = self.language or DEFAULT_LANGUAGE self.feature = model.Feature(self.filename, self.line, keyword, name, tags=self.tags, language=language) # -- RESET STATE: self.tags = [] def _build_background_statement(self, keyword, line): if self.tags: msg = u"Background supports no tags: @%s" % (u" @".join(self.tags)) raise ParserError(msg, self.line, self.filename, line) name = line[len(keyword) + 1:].strip() statement = model.Background(self.filename, self.line, keyword, name) self.statement = statement self.feature.background = self.statement def _build_scenario_statement(self, keyword, line): name = line[len(keyword) + 1:].strip() self.statement = model.Scenario(self.filename, self.line, keyword, name, tags=self.tags) self.feature.add_scenario(self.statement) # -- RESET STATE: self.tags = [] def _build_scenario_outline_statement(self, keyword, line): # pylint: disable=C0103 # C0103 Invalid name "build_scenario_outline_statement", too long. name = line[len(keyword) + 1:].strip() self.statement = model.ScenarioOutline(self.filename, self.line, keyword, name, tags=self.tags) self.feature.add_scenario(self.statement) # -- RESET STATE: self.tags = [] def _build_examples(self, keyword, line): if not isinstance(self.statement, model.ScenarioOutline): message = u"Examples must only appear inside scenario outline" raise ParserError(message, self.line, self.filename, line) name = line[len(keyword) + 1:].strip() self.examples = model.Examples(self.filename, self.line, keyword, name, tags=self.tags) # pylint: disable=E1103 # E1103 Instance of "Background" has no "examples" member # (but some types could not be inferred). self.statement.examples.append(self.examples) # -- RESET STATE: self.tags = [] def diagnose_feature_usage_error(self): if self.feature: return "Multiple features in one file are not supported." else: return "Feature should not be used here." def diagnose_background_usage_error(self): if self.feature and self.feature.scenarios: return "Background may not occur after Scenario/ScenarioOutline." elif self.tags: return "Background does not support tags." else: return "Background should not be used here." def diagnose_scenario_usage_error(self): if not self.feature: return "Scenario may not occur before Feature." else: return "Scenario should not be used here." def diagnose_scenario_outline_usage_error(self): # pylint: disable=invalid-name if not self.feature: return "ScenarioOutline may not occur before Feature." else: return "ScenarioOutline should not be used here." def ask_parse_failure_oracle(self, line): """ Try to find the failure reason when a parse failure occurs: Oracle, oracle, ... what went wrong? Zzzz :param line: Text line where parse failure occured (as string). :return: Reason (as string) if an explanation is found. Otherwise, empty string or None. """ feature_kwd = self.match_keyword("feature", line) if feature_kwd: return self.diagnose_feature_usage_error() background_kwd = self.match_keyword("background", line) if background_kwd: return self.diagnose_background_usage_error() scenario_kwd = self.match_keyword("scenario", line) if scenario_kwd: return self.diagnose_scenario_usage_error() scenario_outline_kwd = self.match_keyword("scenario_outline", line) if scenario_outline_kwd: return self.diagnose_scenario_outline_usage_error() # -- OTHERWISE: if self.variant == "feature" and not self.feature: return "No feature found." # -- FINALLY: No glue what went wrong. return None def action(self, line): if line.strip().startswith("#") and self.state != "multiline": if self.state != "init" or self.tags or self.variant != "feature": return # -- DETECT: language comment (at begin of feature file; state=init) line = line.strip()[1:].strip() if line.lstrip().lower().startswith("language:"): language = line[9:].strip() self.language = language self.keywords = i18n.languages[language] return func = getattr(self, "action_" + self.state, None) if func is None: line = line.strip() msg = u"Parser in unknown state %s;" % self.state raise ParserError(msg, self.line, self.filename, line) if not func(line): line = line.strip() msg = u'\nParser failure in state %s, at line %d: "%s"\n' % \ (self.state, self.line, line) reason = self.ask_parse_failure_oracle(line) if reason: msg += u"REASON: %s" % reason raise ParserError(msg, None, self.filename) def action_init(self, line): line = line.strip() if line.startswith("@"): self.tags.extend(self.parse_tags(line)) return True feature_kwd = self.match_keyword("feature", line) if feature_kwd: self._build_feature(feature_kwd, line) self.state = "feature" return True return False # def subaction_detect_next_scenario(self, line): # if line.startswith("@"): # self.tags.extend(self.parse_tags(line)) # self.state = "next_scenario" # return True # # scenario_kwd = self.match_keyword("scenario", line) # if scenario_kwd: # self._build_scenario_statement(scenario_kwd, line) # self.state = "scenario" # return True # # scenario_outline_kwd = self.match_keyword("scenario_outline", line) # if scenario_outline_kwd: # self._build_scenario_outline_statement(scenario_outline_kwd, line) # self.state = "scenario" # return True # # # -- OTHERWISE: # return False # pylint: disable=invalid-name def subaction_detect_taggable_statement(self, line): """Subaction is used after first tag line is detected. Additional lines with tags or taggable_statement follow. Taggable statements (excluding Feature) are: * Scenario * ScenarioOutline * Examples (within ScenarioOutline) """ if line.startswith("@"): self.tags.extend(self.parse_tags(line)) self.state = "taggable_statement" return True scenario_kwd = self.match_keyword("scenario", line) if scenario_kwd: self._build_scenario_statement(scenario_kwd, line) self.state = "scenario" return True scenario_outline_kwd = self.match_keyword("scenario_outline", line) if scenario_outline_kwd: self._build_scenario_outline_statement(scenario_outline_kwd, line) self.state = "scenario" return True examples_kwd = self.match_keyword("examples", line) if examples_kwd: self._build_examples(examples_kwd, line) self.state = "table" return True # -- OTHERWISE: return False # pylint: enable=invalid-name def action_feature(self, line): line = line.strip() # OLD: if self.subaction_detect_next_scenario(line): if self.subaction_detect_taggable_statement(line): # -- DETECTED: Next Scenario, ScenarioOutline (or tags) return True background_kwd = self.match_keyword("background", line) if background_kwd: self._build_background_statement(background_kwd, line) self.state = "steps" return True self.feature.description.append(line) return True # def action_next_scenario(self, line): # """ # Entered after first tag for Scenario/ScenarioOutline is detected. # """ # line = line.strip() # if self.subaction_detect_next_scenario(line): # return True # # return False def action_taggable_statement(self, line): """Entered after first tag for Scenario/ScenarioOutline or Examples is detected (= taggable_statement except Feature). Taggable statements (excluding Feature) are: * Scenario * ScenarioOutline * Examples (within ScenarioOutline) """ line = line.strip() if self.subaction_detect_taggable_statement(line): # -- DETECTED: Next Scenario, ScenarioOutline or Examples (or tags) return True return False def action_scenario(self, line): """ Entered when Scenario/ScenarioOutline keyword/line is detected. Hunts/collects scenario description lines. DETECT: * first step of Scenario/ScenarioOutline * next Scenario/ScenarioOutline. """ line = line.strip() step = self.parse_step(line) if step: # -- FIRST STEP DETECTED: End collection of scenario descriptions. self.state = "steps" self.statement.steps.append(step) return True # -- CASE: Detect next Scenario/ScenarioOutline # * Scenario with scenario description, but without steps. # * Title-only scenario without scenario description and steps. # OLD: if self.subaction_detect_next_scenario(line): if self.subaction_detect_taggable_statement(line): # -- DETECTED: Next Scenario, ScenarioOutline (or tags) return True # -- OTHERWISE: Add scenario description line. # pylint: disable=E1103 # E1103 Instance of "Background" has no "description" member... self.statement.description.append(line) return True def action_steps(self, line): """ Entered when first step is detected (or nested step parsing). Subcases: * step * multi-line text (doc-string), following a step * table, following a step * examples for a ScenarioOutline, after ScenarioOutline steps DETECT: * next Scenario/ScenarioOutline or Examples (in a ScenarioOutline) """ # pylint: disable=R0911 # R0911 Too many return statements (8/6) stripped = line.lstrip() if stripped.startswith('"""') or stripped.startswith("'''"): self.state = "multiline" self.multiline_start = self.line self.multiline_terminator = stripped[:3] self.multiline_leading = line.index(stripped[0]) return True line = line.strip() step = self.parse_step(line) if step: self.statement.steps.append(step) return True if self.subaction_detect_taggable_statement(line): # -- DETECTED: Next Scenario, ScenarioOutline or Examples (or tags) return True if line.startswith("|"): assert self.statement.steps, "TABLE-START without step detected." self.state = "table" return self.action_table(line) return False def action_multiline(self, line): if line.strip().startswith(self.multiline_terminator): step = self.statement.steps[-1] step.text = model.Text(u"\n".join(self.lines), u"text/plain", self.multiline_start) if step.name.endswith(":"): step.name = step.name[:-1] self.lines = [] self.multiline_terminator = None self.state = "steps" return True self.lines.append(line[self.multiline_leading:]) # -- BETTER DIAGNOSTICS: May remove non-whitespace in execute_steps() removed_line_prefix = line[:self.multiline_leading] if removed_line_prefix.strip(): message = u"BAD-INDENT in multiline text: " message += u"Line '%s' would strip leading '%s'" % \ (line, removed_line_prefix) raise ParserError(message, self.line, self.filename) return True def action_table(self, line): line = line.strip() if not line.startswith("|"): if self.examples: self.examples.table = self.table self.examples = None else: step = self.statement.steps[-1] step.table = self.table if step.name.endswith(":"): step.name = step.name[:-1] self.table = None self.state = "steps" return self.action_steps(line) # -- SUPPORT: Escaped-pipe(s) in Gherkin cell values. # Search for pipe(s) that are not preceeded with an escape char. cells = [cell.replace("\\|", "|").strip() for cell in re.split(r"(? xml_element:testsuite scenario -> xml_element:testcase XML document structure:: # -- XML elements: # CARDINALITY SUFFIX: # ? optional (zero or one) # * many0 (zero or more) # + many (one or more) testsuites := sequence testsuite: properties? : sequence testcase* : error? : text failure? : text system-out : text system-err : text testsuite: @name : TokenString @tests : int @failures : int @errors : int @skipped : int @time : Decimal # Duration in seconds # -- SINCE: behave-1.2.6 @timestamp : IsoDateTime @hostname : string testcase: @name : TokenString @classname : TokenString @status : string # Status enum @time : Decimal # Elapsed seconds error: @message : string @type : string failure: @message : string @type : string # -- HINT: Not used property: @name : TokenString @value : string type Status : Enum("passed", "failed", "skipped", "untested") Note that a spec for JUnit XML output was not clearly defined. Best sources are: * `JUnit XML`_ (for PDF) * JUnit XML (`ant spec 1`_, `ant spec 2`_) .. _`JUnit XML`: http://junitpdfreport.sourceforge.net/managedcontent/PdfTranslation .. _`ant spec 1`: https://github.com/windyroad/JUnit-Schema .. _`ant spec 2`: http://svn.apache.org/repos/asf/ant/core/trunk/src/main/org/apache/tools/ant/taskdefs/optional/junit/XMLJUnitResultFormatter.java """ # pylint: enable=line-too-long from __future__ import absolute_import import os.path import codecs from xml.etree import ElementTree from datetime import datetime from behave.reporter.base import Reporter from behave.model import Scenario, ScenarioOutline, Step from behave.model_core import Status from behave.formatter import ansi_escapes from behave.model_describe import ModelDescriptor from behave.textutil import indent, make_indentation, text as _text from behave.userdata import UserDataNamespace import six if six.PY2: # -- USE: Python3 backport for better unicode compatibility. import traceback2 as traceback else: import traceback def CDATA(text=None): # pylint: disable=invalid-name # -- issue #70: remove_ansi_escapes(text) element = ElementTree.Element('![CDATA[') element.text = ansi_escapes.strip_escapes(text) return element class ElementTreeWithCDATA(ElementTree.ElementTree): # pylint: disable=redefined-builtin, no-member def _write(self, file, node, encoding, namespaces): """This method is for ElementTree <= 1.2.6""" if node.tag == '![CDATA[': text = node.text.encode(encoding) file.write("\n\n" % text) else: ElementTree.ElementTree._write(self, file, node, encoding, namespaces) if hasattr(ElementTree, '_serialize'): # pylint: disable=protected-access def _serialize_xml2(write, elem, encoding, qnames, namespaces, orig=ElementTree._serialize_xml): if elem.tag == '![CDATA[': write("\n<%s%s]]>\n" % \ (elem.tag, elem.text.encode(encoding, "xmlcharrefreplace"))) return return orig(write, elem, encoding, qnames, namespaces) def _serialize_xml3(write, elem, qnames, namespaces, short_empty_elements=None, orig=ElementTree._serialize_xml): if elem.tag == '![CDATA[': write("\n<{tag}{text}]]>\n".format( tag=elem.tag, text=elem.text)) return if short_empty_elements: # python >=3.3 return orig(write, elem, qnames, namespaces, short_empty_elements) else: # python <3.3 return orig(write, elem, qnames, namespaces) if six.PY3: ElementTree._serialize_xml = \ ElementTree._serialize['xml'] = _serialize_xml3 elif six.PY2: ElementTree._serialize_xml = \ ElementTree._serialize['xml'] = _serialize_xml2 class FeatureReportData(object): """ Provides value object to collect JUnit report data from a Feature. """ def __init__(self, feature, filename, classname=None): if not classname and filename: classname = filename.replace('/', '.') self.feature = feature self.filename = filename self.classname = classname self.testcases = [] self.counts_tests = 0 self.counts_errors = 0 self.counts_failed = 0 self.counts_skipped = 0 def reset(self): self.testcases = [] self.counts_tests = 0 self.counts_errors = 0 self.counts_failed = 0 self.counts_skipped = 0 class JUnitReporter(Reporter): """Generates JUnit-like XML test report for behave. """ # -- XML REPORT: userdata_scope = "behave.reporter.junit" show_timings = True # -- Show step timings. show_skipped_always = False show_timestamp = True show_hostname = True # -- XML REPORT PART: Describe scenarios show_scenarios = True # Show scenario descriptions. show_tags = True show_multiline = True def __init__(self, config): super(JUnitReporter, self).__init__(config) self.setup_with_userdata(config.userdata) def setup_with_userdata(self, userdata): """Setup JUnit reporter with userdata information. A user can now tweak the output format of this reporter. EXAMPLE: .. code-block:: ini # -- FILE: behave.ini [behave.userdata] behave.reporter.junit.show_hostname = false """ # -- EXPERIMENTAL: config = UserDataNamespace(self.userdata_scope, userdata) self.show_hostname = config.getbool("show_hostname", self.show_hostname) self.show_multiline = config.getbool("show_multiline", self.show_multiline) self.show_scenarios = config.getbool("show_scenarios", self.show_scenarios) self.show_tags = config.getbool("show_tags", self.show_tags) self.show_timings = config.getbool("show_timings", self.show_timings) self.show_timestamp = config.getbool("show_timestamp", self.show_timestamp) self.show_skipped_always = config.getbool("show_skipped_always", self.show_skipped_always) def make_feature_filename(self, feature): filename = None for path in self.config.paths: if feature.filename.startswith(path): filename = feature.filename[len(path) + 1:] break if not filename: # -- NOTE: Directory path (subdirs) are taken into account. filename = feature.location.relpath(self.config.base_dir) filename = filename.rsplit('.', 1)[0] filename = filename.replace('\\', '/').replace('/', '.') return _text(filename) @property def show_skipped(self): return self.config.show_skipped or self.show_skipped_always # -- REPORTER-API: def feature(self, feature): if feature.status == Status.skipped and not self.show_skipped: # -- SKIP-OUTPUT: If skipped features should not be shown. return feature_filename = self.make_feature_filename(feature) classname = feature_filename report = FeatureReportData(feature, feature_filename) now = datetime.now() suite = ElementTree.Element(u'testsuite') feature_name = feature.name or feature_filename suite.set(u'name', u'%s.%s' % (classname, feature_name)) # -- BUILD-TESTCASES: From scenarios for scenario in feature: if isinstance(scenario, ScenarioOutline): scenario_outline = scenario self._process_scenario_outline(scenario_outline, report) else: self._process_scenario(scenario, report) # -- ADD TESTCASES to testsuite: for testcase in report.testcases: suite.append(testcase) suite.set(u'tests', _text(report.counts_tests)) suite.set(u'errors', _text(report.counts_errors)) suite.set(u'failures', _text(report.counts_failed)) suite.set(u'skipped', _text(report.counts_skipped)) # WAS: skips suite.set(u'time', _text(round(feature.duration, 6))) # -- SINCE: behave-1.2.6.dev0 if self.show_timestamp: suite.set(u'timestamp', _text(now.isoformat())) if self.show_hostname: suite.set(u'hostname', _text(gethostname())) if not os.path.exists(self.config.junit_directory): # -- ENSURE: Create multiple directory levels at once. os.makedirs(self.config.junit_directory) tree = ElementTreeWithCDATA(suite) report_dirname = self.config.junit_directory report_basename = u'TESTS-%s.xml' % feature_filename report_filename = os.path.join(report_dirname, report_basename) tree.write(codecs.open(report_filename, "wb"), "UTF-8") # -- MORE: # pylint: disable=line-too-long @staticmethod def select_step_with_status(status, steps): """Helper function to find the first step that has the given step.status. EXAMPLE: Search for a failing step in a scenario (all steps). >>> scenario = ... >>> failed_step = select_step_with_status(Status.failed, scenario) >>> failed_step = select_step_with_status(Status.failed, scenario.all_steps) >>> assert failed_step.status == Status.failed EXAMPLE: Search only scenario steps, skip background steps. >>> failed_step = select_step_with_status(Status.failed, scenario.steps) :param status: Step status to search for (as enum value). :param steps: List of steps to search in (or scenario). :returns: Step object, if found. :returns: None, otherwise. .. versionchanged:: 1.2.6 status: Use enum value instead of string (or string). """ for step in steps: assert isinstance(step, Step), \ "TYPE-MISMATCH: step.class=%s" % step.__class__.__name__ if step.status == status: return step # -- OTHERWISE: No step with the given status found. # KeyError("Step with status={0} not found".format(status)) return None # pylint: enable=line-too-long def describe_step(self, step): status_text = _text(step.status.name) if self.show_timings: status_text += u" in %0.3fs" % step.duration text = u'%s %s ... ' % (step.keyword, step.name) text += u'%s\n' % status_text if self.show_multiline: prefix = make_indentation(2) if step.text: text += ModelDescriptor.describe_docstring(step.text, prefix) elif step.table: text += ModelDescriptor.describe_table(step.table, prefix) return text @classmethod def describe_tags(cls, tags): text = u'' if tags: text = u'@'+ u' @'.join(tags) return text def describe_scenario(self, scenario): """Describe the scenario and the test status. NOTE: table, multiline text is missing in description. :param scenario: Scenario that was tested. :return: Textual description of the scenario. """ header_line = u'\n@scenario.begin\n' if self.show_tags and scenario.tags: header_line += u'\n %s\n' % self.describe_tags(scenario.tags) header_line += u' %s: %s\n' % (scenario.keyword, scenario.name) footer_line = u'\n@scenario.end\n' + u'-' * 80 + '\n' text = u'' for step in scenario: text += self.describe_step(step) step_indentation = make_indentation(4) return header_line + indent(text, step_indentation) + footer_line def _process_scenario(self, scenario, report): """Process a scenario and append information to JUnit report object. This corresponds to a JUnit testcase: * testcase.@classname = f(filename) +'.'+ feature.name * testcase.@name = scenario.name * testcase.@status = scenario.status * testcase.@time = scenario.duration Distinguishes now between failures and errors. Failures are AssertationErrors: expectation is violated/not met. Errors are unexpected RuntimeErrors (all other exceptions). If a failure/error occurs, the step, that caused the failure, and its location are provided now. :param scenario: Scenario to process. :param report: Context object to store/add info to (outgoing param). """ # pylint: disable=too-many-locals, too-many-branches, too-many-statements assert isinstance(scenario, Scenario) assert not isinstance(scenario, ScenarioOutline) if scenario.status != Status.skipped or self.show_skipped: # -- NOTE: Count only if not-skipped or skipped should be shown. report.counts_tests += 1 classname = report.classname feature = report.feature feature_name = feature.name if not feature_name: feature_name = self.make_feature_filename(feature) case = ElementTree.Element('testcase') case.set(u"classname", u"%s.%s" % (classname, feature_name)) case.set(u"name", scenario.name or "") case.set(u"status", scenario.status.name) case.set(u"time", _text(round(scenario.duration, 6))) step = None failing_step = None if scenario.status == Status.failed: for status in (Status.failed, Status.undefined): step = self.select_step_with_status(status, scenario) if step: break # -- NOTE: Scenario may fail now due to hook-errors. element_name = "failure" if step and isinstance(step.exception, (AssertionError, type(None))): # -- FAILURE: AssertionError assert step.status in (Status.failed, Status.undefined) report.counts_failed += 1 else: # -- UNEXPECTED RUNTIME-ERROR: report.counts_errors += 1 element_name = "error" # -- COMMON-PART: failure = ElementTree.Element(element_name) if step: step_text = self.describe_step(step).rstrip() text = u"\nFailing step: %s\nLocation: %s\n" % \ (step_text, step.location) message = _text(step.exception) failure.set(u'type', step.exception.__class__.__name__) failure.set(u'message', message) text += _text(step.error_message) else: # -- MAYBE: Hook failure before any step is executed. failure_type = "UnknownError" if scenario.exception: failure_type = scenario.exception.__class__.__name__ failure.set(u'type', failure_type) failure.set(u'message', scenario.error_message or "") traceback_lines = traceback.format_tb(scenario.exc_traceback) traceback_lines.insert(0, u"Traceback:\n") text = _text(u"".join(traceback_lines)) failure.append(CDATA(text)) case.append(failure) elif (scenario.status in (Status.skipped, Status.untested) and self.show_skipped): report.counts_skipped += 1 step = self.select_step_with_status(Status.undefined, scenario) if step: # -- UNDEFINED-STEP: report.counts_failed += 1 failure = ElementTree.Element(u"failure") failure.set(u"type", u"undefined") failure.set(u"message", (u"Undefined Step: %s" % step.name)) case.append(failure) else: skip = ElementTree.Element(u'skipped') case.append(skip) # Create stdout section for each test case stdout = ElementTree.Element(u"system-out") text = u"" if self.show_scenarios: text = self.describe_scenario(scenario) # Append the captured standard output if scenario.captured.stdout: output = _text(scenario.captured.stdout) text += u"\nCaptured stdout:\n%s\n" % output stdout.append(CDATA(text)) case.append(stdout) # Create stderr section for each test case if scenario.captured.stderr: stderr = ElementTree.Element(u"system-err") output = _text(scenario.captured.stderr) text = u"\nCaptured stderr:\n%s\n" % output stderr.append(CDATA(text)) case.append(stderr) if scenario.status != Status.skipped or self.show_skipped: report.testcases.append(case) def _process_scenario_outline(self, scenario_outline, report): assert isinstance(scenario_outline, ScenarioOutline) for scenario in scenario_outline: assert isinstance(scenario, Scenario) self._process_scenario(scenario, report) # ----------------------------------------------------------------------------- # SUPPORT: # ----------------------------------------------------------------------------- def gethostname(): """Return hostname of local host (as string)""" import socket return socket.gethostname() behave-1.2.6/behave/reporter/summary.py0000644000076600000240000000716113244555737020224 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Provides a summary after each test run. """ from __future__ import absolute_import, division import sys from behave.model import ScenarioOutline from behave.model_core import Status from behave.reporter.base import Reporter from behave.formatter.base import StreamOpener # -- DISABLED: optional_steps = ('untested', 'undefined') optional_steps = (Status.untested,) # MAYBE: Status.undefined status_order = (Status.passed, Status.failed, Status.skipped, Status.undefined, Status.untested) def format_summary(statement_type, summary): parts = [] for status in status_order: if status.name not in summary: continue counts = summary[status.name] if status in optional_steps and counts == 0: # -- SHOW-ONLY: For relevant counts, suppress: untested items, etc. continue if not parts: # -- FIRST ITEM: Add statement_type to counter. label = statement_type if counts != 1: label += 's' part = u"%d %s %s" % (counts, label, status.name) else: part = u"%d %s" % (counts, status.name) parts.append(part) return ", ".join(parts) + "\n" class SummaryReporter(Reporter): show_failed_scenarios = True output_stream_name = "stdout" def __init__(self, config): super(SummaryReporter, self).__init__(config) stream = getattr(sys, self.output_stream_name, sys.stderr) self.stream = StreamOpener.ensure_stream_with_encoder(stream) self.feature_summary = {Status.passed.name: 0, Status.failed.name: 0, Status.skipped.name: 0, Status.untested.name: 0} self.scenario_summary = {Status.passed.name: 0, Status.failed.name: 0, Status.skipped.name: 0, Status.untested.name: 0} self.step_summary = {Status.passed.name: 0, Status.failed.name: 0, Status.skipped.name: 0, Status.untested.name: 0, Status.undefined.name: 0} self.duration = 0.0 self.failed_scenarios = [] def feature(self, feature): self.feature_summary[feature.status.name] += 1 self.duration += feature.duration for scenario in feature: if isinstance(scenario, ScenarioOutline): self.process_scenario_outline(scenario) else: self.process_scenario(scenario) def end(self): # -- SHOW FAILED SCENARIOS (optional): if self.show_failed_scenarios and self.failed_scenarios: self.stream.write("\nFailing scenarios:\n") for scenario in self.failed_scenarios: self.stream.write(u" %s %s\n" % ( scenario.location, scenario.name)) self.stream.write("\n") # -- SHOW SUMMARY COUNTS: self.stream.write(format_summary("feature", self.feature_summary)) self.stream.write(format_summary("scenario", self.scenario_summary)) self.stream.write(format_summary("step", self.step_summary)) timings = (int(self.duration / 60.0), self.duration % 60) self.stream.write('Took %dm%02.3fs\n' % timings) def process_scenario(self, scenario): if scenario.status == Status.failed: self.failed_scenarios.append(scenario) self.scenario_summary[scenario.status.name] += 1 for step in scenario: self.step_summary[step.status.name] += 1 def process_scenario_outline(self, scenario_outline): for scenario in scenario_outline.scenarios: self.process_scenario(scenario) behave-1.2.6/behave/runner.py0000644000076600000240000007327413244555737016206 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ This module provides Runner class to run behave feature files (or model elements). """ from __future__ import absolute_import, print_function, with_statement import contextlib import os.path import sys import warnings import weakref import six from behave._types import ExceptionUtil from behave.capture import CaptureController from behave.configuration import ConfigError from behave.formatter._registry import make_formatters from behave.runner_util import \ collect_feature_locations, parse_features, \ exec_file, load_step_modules, PathManager from behave.step_registry import registry as the_step_registry if six.PY2: # -- USE PYTHON3 BACKPORT: With unicode traceback support. import traceback2 as traceback else: import traceback class CleanupError(RuntimeError): pass class ContextMaskWarning(UserWarning): """Raised if a context variable is being overwritten in some situations. If the variable was originally set by user code then this will be raised if *behave* overwites the value. If the variable was originally set by *behave* then this will be raised if user code overwites the value. """ pass class Context(object): """Hold contextual information during the running of tests. This object is a place to store information related to the tests you're running. You may add arbitrary attributes to it of whatever value you need. During the running of your tests the object will have additional layers of namespace added and removed automatically. There is a "root" namespace and additional namespaces for features and scenarios. Certain names are used by *behave*; be wary of using them yourself as *behave* may overwrite the value you set. These names are: .. attribute:: feature This is set when we start testing a new feature and holds a :class:`~behave.model.Feature`. It will not be present outside of a feature (i.e. within the scope of the environment before_all and after_all). .. attribute:: scenario This is set when we start testing a new scenario (including the individual scenarios of a scenario outline) and holds a :class:`~behave.model.Scenario`. It will not be present outside of the scope of a scenario. .. attribute:: tags The current set of active tags (as a Python set containing instances of :class:`~behave.model.Tag` which are basically just glorified strings) combined from the feature and scenario. This attribute will not be present outside of a feature scope. .. attribute:: aborted This is set to true in the root namespace when the user aborts a test run (:exc:`KeyboardInterrupt` exception). Initially: False. .. attribute:: failed This is set to true in the root namespace as soon as a step fails. Initially: False. .. attribute:: table This is set at the step level and holds any :class:`~behave.model.Table` associated with the step. .. attribute:: text This is set at the step level and holds any multiline text associated with the step. .. attribute:: config The configuration of *behave* as determined by configuration files and command-line options. The attributes of this object are the same as the `configuration file section names`_. .. attribute:: active_outline This is set for each scenario in a scenario outline and references the :class:`~behave.model.Row` that is active for the current scenario. It is present mostly for debugging, but may be useful otherwise. .. attribute:: log_capture If logging capture is enabled then this attribute contains the captured logging as an instance of :class:`~behave.log_capture.LoggingCapture`. It is not present if logging is not being captured. .. attribute:: stdout_capture If stdout capture is enabled then this attribute contains the captured output as a StringIO instance. It is not present if stdout is not being captured. .. attribute:: stderr_capture If stderr capture is enabled then this attribute contains the captured output as a StringIO instance. It is not present if stderr is not being captured. If an attempt made by user code to overwrite one of these variables, or indeed by *behave* to overwite a user-set variable, then a :class:`behave.runner.ContextMaskWarning` warning will be raised. You may use the "in" operator to test whether a certain value has been set on the context, for example: "feature" in context checks whether there is a "feature" value in the context. Values may be deleted from the context using "del" but only at the level they are set. You can't delete a value set by a feature at a scenario level but you can delete a value set for a scenario in that scenario. .. _`configuration file section names`: behave.html#configuration-files """ # pylint: disable=too-many-instance-attributes BEHAVE = "behave" USER = "user" FAIL_ON_CLEANUP_ERRORS = True def __init__(self, runner): self._runner = weakref.proxy(runner) self._config = runner.config d = self._root = { "aborted": False, "failed": False, "config": self._config, "active_outline": None, "cleanup_errors": 0, "@cleanups": [], # -- REQUIRED-BY: before_all() hook "@layer": "testrun", } self._stack = [d] self._record = {} self._origin = {} self._mode = self.BEHAVE self.feature = None # -- RECHECK: If needed self.text = None self.table = None self.stdout_capture = None self.stderr_capture = None self.log_capture = None self.fail_on_cleanup_errors = self.FAIL_ON_CLEANUP_ERRORS @staticmethod def ignore_cleanup_error(context, cleanup_func, exception): pass @staticmethod def print_cleanup_error(context, cleanup_func, exception): cleanup_func_name = getattr(cleanup_func, "__name__", None) if not cleanup_func_name: cleanup_func_name = "%r" % cleanup_func print(u"CLEANUP-ERROR in %s: %s: %s" % (cleanup_func_name, exception.__class__.__name__, exception)) traceback.print_exc(file=sys.stdout) # MAYBE: context._dump(pretty=True, prefix="Context: ") # -- MARK: testrun as FAILED # context._set_root_attribute("failed", True) def _do_cleanups(self): """Execute optional cleanup functions when stack frame is popped. A user can add a user-specified handler for cleanup errors. .. code-block:: python # -- FILE: features/environment.py def cleanup_database(database): pass def handle_cleanup_error(context, cleanup_func, exception): pass def before_all(context): context.on_cleanup_error = handle_cleanup_error context.add_cleanup(cleanup_database, the_database) """ # -- BEST-EFFORT ALGORITHM: Tries to perform all cleanups. assert self._stack, "REQUIRE: Non-empty stack" current_layer = self._stack[0] cleanup_funcs = current_layer.get("@cleanups", []) on_cleanup_error = getattr(self, "on_cleanup_error", self.print_cleanup_error) context = self cleanup_errors = [] for cleanup_func in reversed(cleanup_funcs): try: cleanup_func() except Exception as e: # pylint: disable=broad-except # pylint: disable=protected-access context._root["cleanup_errors"] += 1 cleanup_errors.append(sys.exc_info()) on_cleanup_error(context, cleanup_func, e) if self.fail_on_cleanup_errors and cleanup_errors: first_cleanup_erro_info = cleanup_errors[0] del cleanup_errors # -- ENSURE: Release other exception frames. six.reraise(*first_cleanup_erro_info) def _push(self, layer_name=None): """Push a new layer on the context stack. HINT: Use layer_name values: "scenario", "feature", "testrun". :param layer_name: Layer name to use (or None). """ initial_data = {"@cleanups": []} if layer_name: initial_data["@layer"] = layer_name self._stack.insert(0, initial_data) def _pop(self): """Pop the current layer from the context stack. Performs any pending cleanups, registered for this layer. """ try: self._do_cleanups() finally: # -- ENSURE: Layer is removed even if cleanup-errors occur. self._stack.pop(0) def _use_with_behave_mode(self): """Provides a context manager for using the context in BEHAVE mode.""" return use_context_with_mode(self, Context.BEHAVE) def use_with_user_mode(self): """Provides a context manager for using the context in USER mode.""" return use_context_with_mode(self, Context.USER) def user_mode(self): warnings.warn("Use 'use_with_user_mode()' instead", PendingDeprecationWarning, stacklevel=2) return self.use_with_user_mode() def _set_root_attribute(self, attr, value): for frame in self.__dict__["_stack"]: if frame is self.__dict__["_root"]: continue if attr in frame: record = self.__dict__["_record"][attr] params = { "attr": attr, "filename": record[0], "line": record[1], "function": record[3], } self._emit_warning(attr, params) self.__dict__["_root"][attr] = value if attr not in self._origin: self._origin[attr] = self._mode def _emit_warning(self, attr, params): msg = "" if self._mode is self.BEHAVE and self._origin[attr] is not self.BEHAVE: msg = "behave runner is masking context attribute '%(attr)s' " \ "originally set in %(function)s (%(filename)s:%(line)s)" elif self._mode is self.USER: if self._origin[attr] is not self.USER: msg = "user code is masking context attribute '%(attr)s' " \ "originally set by behave" elif self._config.verbose: msg = "user code is masking context attribute " \ "'%(attr)s'; see the tutorial for what this means" if msg: msg = msg % params warnings.warn(msg, ContextMaskWarning, stacklevel=3) def _dump(self, pretty=False, prefix=" "): for level, frame in enumerate(self._stack): print("%sLevel %d" % (prefix, level)) if pretty: for name in sorted(frame.keys()): value = frame[name] print("%s %-15s = %r" % (prefix, name, value)) else: print(prefix + repr(frame)) def __getattr__(self, attr): if attr[0] == "_": return self.__dict__[attr] for frame in self._stack: if attr in frame: return frame[attr] msg = "'{0}' object has no attribute '{1}'" msg = msg.format(self.__class__.__name__, attr) raise AttributeError(msg) def __setattr__(self, attr, value): if attr[0] == "_": self.__dict__[attr] = value return for frame in self._stack[1:]: if attr in frame: record = self._record[attr] params = { "attr": attr, "filename": record[0], "line": record[1], "function": record[3], } self._emit_warning(attr, params) stack_limit = 2 if six.PY2: stack_limit += 1 # Due to traceback2 usage. stack_frame = traceback.extract_stack(limit=stack_limit)[0] self._record[attr] = stack_frame frame = self._stack[0] frame[attr] = value if attr not in self._origin: self._origin[attr] = self._mode def __delattr__(self, attr): frame = self._stack[0] if attr in frame: del frame[attr] del self._record[attr] else: msg = "'{0}' object has no attribute '{1}' at the current level" msg = msg.format(self.__class__.__name__, attr) raise AttributeError(msg) def __contains__(self, attr): if attr[0] == "_": return attr in self.__dict__ for frame in self._stack: if attr in frame: return True return False def execute_steps(self, steps_text): """The steps identified in the "steps" text string will be parsed and executed in turn just as though they were defined in a feature file. If the execute_steps call fails (either through error or failure assertion) then the step invoking it will need to catch the resulting exceptions. :param steps_text: Text with the Gherkin steps to execute (as string). :returns: True, if the steps executed successfully. :raises: AssertionError, if a step failure occurs. :raises: ValueError, if invoked without a feature context. """ assert isinstance(steps_text, six.text_type), "Steps must be unicode." if not self.feature: raise ValueError("execute_steps() called outside of feature") # -- PREPARE: Save original context data for current step. # Needed if step definition that called this method uses .table/.text original_table = getattr(self, "table", None) original_text = getattr(self, "text", None) self.feature.parser.variant = "steps" steps = self.feature.parser.parse_steps(steps_text) with self._use_with_behave_mode(): for step in steps: passed = step.run(self._runner, quiet=True, capture=False) if not passed: # -- ISSUE #96: Provide more substep info to diagnose problem. step_line = u"%s %s" % (step.keyword, step.name) message = "%s SUB-STEP: %s" % \ (step.status.name.upper(), step_line) if step.error_message: message += "\nSubstep info: %s\n" % step.error_message message += u"Traceback (of failed substep):\n" message += u"".join(traceback.format_tb(step.exc_traceback)) # message += u"\nTraceback (of context.execute_steps()):" assert False, message # -- FINALLY: Restore original context data for current step. self.table = original_table self.text = original_text return True def add_cleanup(self, cleanup_func, *args, **kwargs): """Adds a cleanup function that is called when :meth:`Context._pop()` is called. This is intended for user-cleanups. :param cleanup_func: Callable function :param args: Args for cleanup_func() call (optional). :param kwargs: Kwargs for cleanup_func() call (optional). """ # MAYBE: assert callable(cleanup_func), "REQUIRES: callable(cleanup_func)" assert self._stack if args or kwargs: def internal_cleanup_func(): cleanup_func(*args, **kwargs) else: internal_cleanup_func = cleanup_func current_frame = self._stack[0] if cleanup_func not in current_frame["@cleanups"]: # -- AVOID DUPLICATES: current_frame["@cleanups"].append(internal_cleanup_func) @contextlib.contextmanager def use_context_with_mode(context, mode): """Switch context to BEHAVE or USER mode. Provides a context manager for switching between the two context modes. .. sourcecode:: python context = Context() with use_context_with_mode(context, Context.BEHAVE): ... # Do something # -- POSTCONDITION: Original context._mode is restored. :param context: Context object to use. :param mode: Mode to apply to context object. """ # pylint: disable=protected-access assert mode in (Context.BEHAVE, Context.USER) current_mode = context._mode try: context._mode = mode yield finally: # -- RESTORE: Initial current_mode # Even if an AssertionError/Exception is raised. context._mode = current_mode @contextlib.contextmanager def scoped_context_layer(context, layer_name=None): """Provides context manager for context layer (push/do-something/pop cycle). .. code-block:: with scoped_context_layer(context): the_fixture = use_fixture(foo, context, name="foo_42") """ # pylint: disable=protected-access try: context._push(layer_name) yield context finally: context._pop() def path_getrootdir(path): """ Extract rootdir from path in a platform independent way. POSIX-PATH EXAMPLE: rootdir = path_getrootdir("/foo/bar/one.feature") assert rootdir == "/" WINDOWS-PATH EXAMPLE: rootdir = path_getrootdir("D:\\foo\\bar\\one.feature") assert rootdir == r"D:\" """ drive, _ = os.path.splitdrive(path) if drive: # -- WINDOWS: return drive + os.path.sep # -- POSIX: return os.path.sep class ModelRunner(object): """ Test runner for a behave model (features). Provides the core functionality of a test runner and the functional API needed by model elements. .. attribute:: aborted This is set to true when the user aborts a test run (:exc:`KeyboardInterrupt` exception). Initially: False. Stored as derived attribute in :attr:`Context.aborted`. """ # pylint: disable=too-many-instance-attributes def __init__(self, config, features=None, step_registry=None): self.config = config self.features = features or [] self.hooks = {} self.formatters = [] self.undefined_steps = [] self.step_registry = step_registry self.capture_controller = CaptureController(config) self.context = None self.feature = None self.hook_failures = 0 # @property def _get_aborted(self): value = False if self.context: value = self.context.aborted return value # @aborted.setter def _set_aborted(self, value): # pylint: disable=protected-access assert self.context, "REQUIRE: context, but context=%r" % self.context self.context._set_root_attribute("aborted", bool(value)) aborted = property(_get_aborted, _set_aborted, doc="Indicates that test run is aborted by the user.") def run_hook(self, name, context, *args): if not self.config.dry_run and (name in self.hooks): try: with context.use_with_user_mode(): self.hooks[name](context, *args) # except KeyboardInterrupt: # self.aborted = True # if name not in ("before_all", "after_all"): # raise except Exception as e: # pylint: disable=broad-except # -- HANDLE HOOK ERRORS: use_traceback = False if self.config.verbose: use_traceback = True ExceptionUtil.set_traceback(e) extra = u"" if "tag" in name: extra = "(tag=%s)" % args[0] error_text = ExceptionUtil.describe(e, use_traceback).rstrip() error_message = u"HOOK-ERROR in %s%s: %s" % (name, extra, error_text) print(error_message) self.hook_failures += 1 if "tag" in name: # -- SCENARIO or FEATURE statement = getattr(context, "scenario", context.feature) elif "all" in name: # -- ABORT EXECUTION: For before_all/after_all self.aborted = True statement = None else: # -- CASE: feature, scenario, step statement = args[0] if statement: # -- CASE: feature, scenario, step statement.hook_failed = True if statement.error_message: # -- NOTE: One exception/failure is already stored. # Append only error message. statement.error_message += u"\n"+ error_message else: # -- FIRST EXCEPTION/FAILURE: statement.store_exception_context(e) statement.error_message = error_message def setup_capture(self): if not self.context: self.context = Context(self) self.capture_controller.setup_capture(self.context) def start_capture(self): self.capture_controller.start_capture() def stop_capture(self): self.capture_controller.stop_capture() def teardown_capture(self): self.capture_controller.teardown_capture() def run_model(self, features=None): # pylint: disable=too-many-branches if not self.context: self.context = Context(self) if self.step_registry is None: self.step_registry = the_step_registry if features is None: features = self.features # -- ENSURE: context.execute_steps() works in weird cases (hooks, ...) context = self.context self.hook_failures = 0 self.setup_capture() self.run_hook("before_all", context) run_feature = not self.aborted failed_count = 0 undefined_steps_initial_size = len(self.undefined_steps) for feature in features: if run_feature: try: self.feature = feature for formatter in self.formatters: formatter.uri(feature.filename) failed = feature.run(self) if failed: failed_count += 1 if self.config.stop or self.aborted: # -- FAIL-EARLY: After first failure. run_feature = False except KeyboardInterrupt: self.aborted = True failed_count += 1 run_feature = False # -- ALWAYS: Report run/not-run feature to reporters. # REQUIRED-FOR: Summary to keep track of untested features. for reporter in self.config.reporters: reporter.feature(feature) # -- AFTER-ALL: # pylint: disable=protected-access, broad-except cleanups_failed = False self.run_hook("after_all", self.context) try: self.context._do_cleanups() # Without dropping the last context layer. except Exception: cleanups_failed = True if self.aborted: print("\nABORTED: By user.") for formatter in self.formatters: formatter.close() for reporter in self.config.reporters: reporter.end() failed = ((failed_count > 0) or self.aborted or (self.hook_failures > 0) or (len(self.undefined_steps) > undefined_steps_initial_size) or cleanups_failed) # XXX-MAYBE: or context.failed) return failed def run(self): """ Implements the run method by running the model. """ self.context = Context(self) return self.run_model() class Runner(ModelRunner): """ Standard test runner for behave: * setup paths * loads environment hooks * loads step definitions * select feature files, parses them and creates model (elements) """ def __init__(self, config): super(Runner, self).__init__(config) self.path_manager = PathManager() self.base_dir = None def setup_paths(self): # pylint: disable=too-many-branches, too-many-statements if self.config.paths: if self.config.verbose: print("Supplied path:", \ ", ".join('"%s"' % path for path in self.config.paths)) first_path = self.config.paths[0] if hasattr(first_path, "filename"): # -- BETTER: isinstance(first_path, FileLocation): first_path = first_path.filename base_dir = first_path if base_dir.startswith("@"): # -- USE: behave @features.txt base_dir = base_dir[1:] file_locations = self.feature_locations() if file_locations: base_dir = os.path.dirname(file_locations[0].filename) base_dir = os.path.abspath(base_dir) # supplied path might be to a feature file if os.path.isfile(base_dir): if self.config.verbose: print("Primary path is to a file so using its directory") base_dir = os.path.dirname(base_dir) else: if self.config.verbose: print('Using default path "./features"') base_dir = os.path.abspath("features") # Get the root. This is not guaranteed to be "/" because Windows. root_dir = path_getrootdir(base_dir) new_base_dir = base_dir steps_dir = self.config.steps_dir environment_file = self.config.environment_file while True: if self.config.verbose: print("Trying base directory:", new_base_dir) if os.path.isdir(os.path.join(new_base_dir, steps_dir)): break if os.path.isfile(os.path.join(new_base_dir, environment_file)): break if new_base_dir == root_dir: break new_base_dir = os.path.dirname(new_base_dir) if new_base_dir == root_dir: if self.config.verbose: if not self.config.paths: print('ERROR: Could not find "%s" directory. '\ 'Please specify where to find your features.' % \ steps_dir) else: print('ERROR: Could not find "%s" directory in your '\ 'specified path "%s"' % (steps_dir, base_dir)) message = 'No %s directory in %r' % (steps_dir, base_dir) raise ConfigError(message) base_dir = new_base_dir self.config.base_dir = base_dir for dirpath, dirnames, filenames in os.walk(base_dir): if [fn for fn in filenames if fn.endswith(".feature")]: break else: if self.config.verbose: if not self.config.paths: print('ERROR: Could not find any ".feature" files. '\ 'Please specify where to find your features.') else: print('ERROR: Could not find any ".feature" files '\ 'in your specified path "%s"' % base_dir) raise ConfigError('No feature files in %r' % base_dir) self.base_dir = base_dir self.path_manager.add(base_dir) if not self.config.paths: self.config.paths = [base_dir] if base_dir != os.getcwd(): self.path_manager.add(os.getcwd()) def before_all_default_hook(self, context): """ Default implementation for :func:`before_all()` hook. Setup the logging subsystem based on the configuration data. """ # pylint: disable=no-self-use context.config.setup_logging() def load_hooks(self, filename=None): filename = filename or self.config.environment_file hooks_path = os.path.join(self.base_dir, filename) if os.path.exists(hooks_path): exec_file(hooks_path, self.hooks) if "before_all" not in self.hooks: self.hooks["before_all"] = self.before_all_default_hook def load_step_definitions(self, extra_step_paths=None): if extra_step_paths is None: extra_step_paths = [] # -- Allow steps to import other stuff from the steps dir # NOTE: Default matcher can be overridden in "environment.py" hook. steps_dir = os.path.join(self.base_dir, self.config.steps_dir) step_paths = [steps_dir] + list(extra_step_paths) load_step_modules(step_paths) def feature_locations(self): return collect_feature_locations(self.config.paths) def run(self): with self.path_manager: self.setup_paths() return self.run_with_paths() def run_with_paths(self): self.context = Context(self) self.load_hooks() self.load_step_definitions() # -- ENSURE: context.execute_steps() works in weird cases (hooks, ...) # self.setup_capture() # self.run_hook("before_all", self.context) # -- STEP: Parse all feature files (by using their file location). feature_locations = [filename for filename in self.feature_locations() if not self.config.exclude(filename)] features = parse_features(feature_locations, language=self.config.lang) self.features.extend(features) # -- STEP: Run all features. stream_openers = self.config.outputs self.formatters = make_formatters(self.config, stream_openers) return self.run_model() behave-1.2.6/behave/runner_util.py0000644000076600000240000004331413244555737017233 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Contains utility functions and classes for Runners. """ from __future__ import absolute_import from bisect import bisect import glob import os.path import re import sys from six import string_types from behave import parser from behave.model_core import FileLocation from behave.textutil import ensure_stream_with_encoder # LAZY: from behave.step_registry import setup_step_decorators # ----------------------------------------------------------------------------- # EXCEPTIONS: # ----------------------------------------------------------------------------- class FileNotFoundError(LookupError): pass class InvalidFileLocationError(LookupError): pass class InvalidFilenameError(ValueError): pass # ----------------------------------------------------------------------------- # CLASS: FileLocationParser # ----------------------------------------------------------------------------- class FileLocationParser(object): pattern = re.compile(r"^\s*(?P.*):(?P\d+)\s*$", re.UNICODE) @classmethod def parse(cls, text): match = cls.pattern.match(text) if match: filename = match.group("filename").strip() line = int(match.group("line")) return FileLocation(filename, line) # -- NORMAL PATH/FILENAME: filename = text.strip() return FileLocation(filename) # @classmethod # def compare(cls, location1, location2): # loc1 = cls.parse(location1) # loc2 = cls.parse(location2) # return cmp(loc1, loc2) # ----------------------------------------------------------------------------- # CLASSES: # ----------------------------------------------------------------------------- class FeatureScenarioLocationCollector(object): """ Collects FileLocation objects for a feature. This is used to select a subset of scenarios in a feature that should run. USE CASE: behave feature/foo.feature:10 behave @selected_features.txt behave @rerun_failed_scenarios.txt With features configuration files, like: # -- file:rerun_failed_scenarios.txt feature/foo.feature:10 feature/foo.feature:25 feature/bar.feature # -- EOF """ def __init__(self, feature=None, location=None, filename=None): if not filename and location: filename = location.filename self.feature = feature self.filename = filename self.use_all_scenarios = False self.scenario_lines = set() self.all_scenarios = set() self.selected_scenarios = set() if location: self.add_location(location) def clear(self): self.feature = None self.filename = None self.use_all_scenarios = False self.scenario_lines = set() self.all_scenarios = set() self.selected_scenarios = set() def add_location(self, location): if not self.filename: self.filename = location.filename # if self.feature and False: # self.filename = self.feature.filename # -- NORMAL CASE: assert self.filename == location.filename, \ "%s <=> %s" % (self.filename, location.filename) if location.line: self.scenario_lines.add(location.line) else: # -- LOCATION WITHOUT LINE NUMBER: # Selects all scenarios in a feature. self.use_all_scenarios = True @staticmethod def select_scenario_line_for(line, scenario_lines): """ Select scenario line for any given line. ALGORITHM: scenario.line <= line < next_scenario.line :param line: A line number in the file (as number). :param scenario_lines: Sorted list of scenario lines. :return: Scenario.line (first line) for the given line. """ if not scenario_lines: return 0 # -- Select all scenarios. pos = bisect(scenario_lines, line) - 1 if pos < 0: pos = 0 return scenario_lines[pos] def discover_selected_scenarios(self, strict=False): """ Discovers selected scenarios based on the provided file locations. In addition: * discover all scenarios * auto-correct BAD LINE-NUMBERS :param strict: If true, raises exception if file location is invalid. :return: List of selected scenarios of this feature (as set). :raises InvalidFileLocationError: If file location is no exactly correct and strict is true. """ assert self.feature if not self.all_scenarios: self.all_scenarios = self.feature.walk_scenarios() # -- STEP: Check if lines are correct. existing_lines = [scenario.line for scenario in self.all_scenarios] selected_lines = list(self.scenario_lines) for line in selected_lines: new_line = self.select_scenario_line_for(line, existing_lines) if new_line != line: # -- AUTO-CORRECT BAD-LINE: self.scenario_lines.remove(line) self.scenario_lines.add(new_line) if strict: msg = "Scenario location '...:%d' should be: '%s:%d'" % \ (line, self.filename, new_line) raise InvalidFileLocationError(msg) # -- STEP: Determine selected scenarios and store them. scenario_lines = set(self.scenario_lines) selected_scenarios = set() for scenario in self.all_scenarios: if scenario.line in scenario_lines: selected_scenarios.add(scenario) scenario_lines.remove(scenario.line) # -- CHECK ALL ARE RESOLVED: assert not scenario_lines return selected_scenarios def build_feature(self): """ Determines which scenarios in the feature are selected and marks the remaining scenarios as skipped. Scenarios with the following tags are excluded from skipped-marking: * @setup * @teardown If no file locations are stored, the unmodified feature is returned. :return: Feature object to use. """ use_all_scenarios = not self.scenario_lines or self.use_all_scenarios if not self.feature or use_all_scenarios: return self.feature # -- CASE: Select subset of all scenarios of this feature. # Mark other scenarios as skipped (except in a few cases). self.all_scenarios = self.feature.walk_scenarios() self.selected_scenarios = self.discover_selected_scenarios() unselected_scenarios = set(self.all_scenarios) - self.selected_scenarios for scenario in unselected_scenarios: if "setup" in scenario.tags or "teardown" in scenario.tags: continue scenario.mark_skipped() return self.feature class FeatureListParser(object): """ Read textual file, ala '@features.txt'. This file contains: * a feature filename or FileLocation on each line * empty lines (skipped) * comment lines (skipped) * wildcards are expanded to select 0..N filenames or directories Relative path names are evaluated relative to the listfile directory. A leading '@' (AT) character is removed from the listfile name. """ @staticmethod def parse(text, here=None): """ Parse contents of a features list file as text. :param text: Contents of a features list(file). :param here: Current working directory to use (optional). :return: List of FileLocation objects """ locations = [] for line in text.splitlines(): filename = line.strip() if not filename: continue # SKIP: Over empty line(s). elif filename.startswith('#'): continue # SKIP: Over comment line(s). if here and not os.path.isabs(filename): filename = os.path.join(here, line) filename = os.path.normpath(filename) if glob.has_magic(filename): # -- WITH WILDCARDS: for filename2 in glob.iglob(filename): location = FileLocationParser.parse(filename2) locations.append(location) else: location = FileLocationParser.parse(filename) locations.append(location) return locations @classmethod def parse_file(cls, filename): """ Read textual file, ala '@features.txt'. :param filename: Name of feature list file. :return: List of feature file locations. """ if filename.startswith('@'): filename = filename[1:] if not os.path.isfile(filename): raise FileNotFoundError(filename) here = os.path.dirname(filename) or "." # -- MAYBE BETTER: # contents = codecs.open(filename, "utf-8").read() contents = open(filename).read() return cls.parse(contents, here) class PathManager(object): """Context manager to add paths to sys.path (python search path) within a scope. """ def __init__(self, paths=None): self.initial_paths = paths or [] self.paths = None def __enter__(self): self.paths = list(self.initial_paths) sys.path = self.paths + sys.path def __exit__(self, *crap): for path in self.paths: sys.path.remove(path) self.paths = None def add(self, path): if self.paths is None: # -- CALLED OUTSIDE OF CONTEXT: self.initial_paths.append(path) else: sys.path.insert(0, path) self.paths.append(path) # ----------------------------------------------------------------------------- # FUNCTIONS: # ----------------------------------------------------------------------------- def parse_features(feature_files, language=None): """ Parse feature files and return list of Feature model objects. Handles: * feature file names, ala "alice.feature" * feature file locations, ala: "alice.feature:10" :param feature_files: List of feature file names to parse. :param language: Default language to use. :return: List of feature objects. """ scenario_collector = FeatureScenarioLocationCollector() features = [] for location in feature_files: if not isinstance(location, FileLocation): assert isinstance(location, string_types) location = FileLocation(os.path.normpath(location)) if location.filename == scenario_collector.filename: scenario_collector.add_location(location) continue elif scenario_collector.feature: # -- ADD CURRENT FEATURE: As collection of scenarios. current_feature = scenario_collector.build_feature() features.append(current_feature) scenario_collector.clear() # -- NEW FEATURE: assert isinstance(location, FileLocation) filename = os.path.abspath(location.filename) feature = parser.parse_file(filename, language=language) if feature: # -- VALID FEATURE: # SKIP CORNER-CASE: Feature file without any feature(s). scenario_collector.feature = feature scenario_collector.add_location(location) # -- FINALLY: if scenario_collector.feature: current_feature = scenario_collector.build_feature() features.append(current_feature) return features def collect_feature_locations(paths, strict=True): """ Collect feature file names by processing list of paths (from command line). A path can be a: * filename (ending with ".feature") * location, ala "{filename}:{line_number}" * features configuration filename, ala "@features.txt" * directory, to discover and collect all "*.feature" files below. :param paths: Paths to process. :return: Feature file locations to use (as list of FileLocations). """ locations = [] for path in paths: if os.path.isdir(path): for dirpath, dirnames, filenames in os.walk(path): dirnames.sort() for filename in sorted(filenames): if filename.endswith(".feature"): location = FileLocation(os.path.join(dirpath, filename)) locations.append(location) elif path.startswith('@'): # -- USE: behave @list_of_features.txt locations.extend(FeatureListParser.parse_file(path[1:])) else: # -- OTHERWISE: Normal filename or location (schema: filename:line) location = FileLocationParser.parse(path) if not location.filename.endswith(".feature"): raise InvalidFilenameError(location.filename) elif location.exists(): locations.append(location) elif strict: raise FileNotFoundError(path) return locations def exec_file(filename, globals_=None, locals_=None): if globals_ is None: globals_ = {} if locals_ is None: locals_ = globals_ locals_["__file__"] = filename with open(filename, "rb") as f: # pylint: disable=exec-used filename2 = os.path.relpath(filename, os.getcwd()) code = compile(f.read(), filename2, "exec", dont_inherit=True) exec(code, globals_, locals_) def load_step_modules(step_paths): """Load step modules with step definitions from step_paths directories.""" from behave import matchers from behave.step_registry import setup_step_decorators step_globals = { "use_step_matcher": matchers.use_step_matcher, "step_matcher": matchers.step_matcher, # -- DEPRECATING } setup_step_decorators(step_globals) # -- Allow steps to import other stuff from the steps dir # NOTE: Default matcher can be overridden in "environment.py" hook. with PathManager(step_paths): default_matcher = matchers.current_matcher for path in step_paths: for name in sorted(os.listdir(path)): if name.endswith(".py"): # -- LOAD STEP DEFINITION: # Reset to default matcher after each step-definition. # A step-definition may change the matcher 0..N times. # ENSURE: Each step definition has clean globals. # try: step_module_globals = step_globals.copy() exec_file(os.path.join(path, name), step_module_globals) matchers.current_matcher = default_matcher def make_undefined_step_snippet(step, language=None): """Helper function to create an undefined-step snippet for a step. :param step: Step to use (as Step object or string). :param language: i18n language, optionally needed for step text parsing. :return: Undefined-step snippet (as string). """ if isinstance(step, string_types): step_text = step steps = parser.parse_steps(step_text, language=language) step = steps[0] assert step, "ParseError: %s" % step_text prefix = u"u" single_quote = "'" if single_quote in step.name: step.name = step.name.replace(single_quote, r"\'") schema = u"@%s(%s'%s')\ndef step_impl(context):\n" schema += u" raise NotImplementedError(%s'STEP: %s %s')\n\n" snippet = schema % (step.step_type, prefix, step.name, prefix, step.step_type.title(), step.name) return snippet def make_undefined_step_snippets(undefined_steps, make_snippet=None): """Creates a list of undefined step snippets. Note that duplicated steps are removed internally. :param undefined_steps: List of undefined steps (as Step object or string). :param make_snippet: Function that generates snippet (optional) :return: List of undefined step snippets (as list of strings) """ if make_snippet is None: make_snippet = make_undefined_step_snippet # -- NOTE: Remove any duplicated undefined steps. step_snippets = [] collected_steps = set() for undefined_step in undefined_steps: if undefined_step in collected_steps: continue collected_steps.add(undefined_step) step_snippet = make_snippet(undefined_step) step_snippets.append(step_snippet) return step_snippets def print_undefined_step_snippets(undefined_steps, stream=None, colored=True): """ Print snippets for the undefined steps that were discovered. :param undefined_steps: List of undefined steps (as list). :param stream: Output stream to use (default: sys.stderr). :param colored: Indicates if coloring should be used (default: True) """ if not undefined_steps: return if not stream: stream = sys.stderr msg = u"\nYou can implement step definitions for undefined steps with " msg += u"these snippets:\n\n" msg += u"\n".join(make_undefined_step_snippets(undefined_steps)) if colored: # -- OOPS: Unclear if stream supports ANSI coloring. from behave.formatter.ansi_escapes import escapes msg = escapes['undefined'] + msg + escapes['reset'] stream = ensure_stream_with_encoder(stream) stream.write(msg) stream.flush() def reset_runtime(): """Reset runtime environment. Best effort to reset module data to initial state. """ from behave import step_registry from behave import matchers # -- RESET 1: behave.step_registry step_registry.registry = step_registry.StepRegistry() step_registry.setup_step_decorators(None, step_registry.registry) # -- RESET 2: behave.matchers matchers.ParseMatcher.custom_types = {} matchers.current_matcher = matchers.ParseMatcher behave-1.2.6/behave/step_registry.py0000644000076600000240000000764713244555737017601 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Provides a step registry and step decorators. The step registry allows to match steps (model elements) with step implementations (step definitions). This is necessary to execute steps. """ from __future__ import absolute_import from behave.matchers import Match, get_matcher from behave.textutil import text as _text # limit import * to just the decorators # pylint: disable=undefined-all-variable # names = "given when then step" # names = names + " " + names.title() # __all__ = names.split() __all__ = [ "given", "when", "then", "step", # PREFERRED. "Given", "When", "Then", "Step" # Also possible. ] class AmbiguousStep(ValueError): pass class StepRegistry(object): def __init__(self): self.steps = { "given": [], "when": [], "then": [], "step": [], } @staticmethod def same_step_definition(step, other_pattern, other_location): return (step.pattern == other_pattern and step.location == other_location and other_location.filename != "") def add_step_definition(self, keyword, step_text, func): step_location = Match.make_location(func) step_type = keyword.lower() step_text = _text(step_text) step_definitions = self.steps[step_type] for existing in step_definitions: if self.same_step_definition(existing, step_text, step_location): # -- EXACT-STEP: Same step function is already registered. # This may occur when a step module imports another one. return elif existing.match(step_text): # -- SIMPLISTIC message = u"%s has already been defined in\n existing step %s" new_step = u"@%s('%s')" % (step_type, step_text) existing.step_type = step_type existing_step = existing.describe() existing_step += u" at %s" % existing.location raise AmbiguousStep(message % (new_step, existing_step)) step_definitions.append(get_matcher(func, step_text)) def find_step_definition(self, step): candidates = self.steps[step.step_type] more_steps = self.steps["step"] if step.step_type != "step" and more_steps: # -- ENSURE: self.step_type lists are not modified/extended. candidates = list(candidates) candidates += more_steps for step_definition in candidates: if step_definition.match(step.name): return step_definition return None def find_match(self, step): candidates = self.steps[step.step_type] more_steps = self.steps["step"] if step.step_type != "step" and more_steps: # -- ENSURE: self.step_type lists are not modified/extended. candidates = list(candidates) candidates += more_steps for step_definition in candidates: result = step_definition.match(step.name) if result: return result return None def make_decorator(self, step_type): def decorator(step_text): def wrapper(func): self.add_step_definition(step_type, step_text, func) return func return wrapper return decorator registry = StepRegistry() # -- Create the decorators # pylint: disable=redefined-outer-name def setup_step_decorators(run_context=None, registry=registry): if run_context is None: run_context = globals() for step_type in ("given", "when", "then", "step"): step_decorator = registry.make_decorator(step_type) run_context[step_type.title()] = run_context[step_type] = step_decorator # ----------------------------------------------------------------------------- # MODULE INIT: # ----------------------------------------------------------------------------- setup_step_decorators() behave-1.2.6/behave/tag_expression.py0000644000076600000240000000675413244555737017726 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- import six class TagExpression(object): """ Tag expression, as logical boolean expression, to select (include or exclude) model elements. BOOLEAN LOGIC := (or_expr1) and (or_expr2) and ... with or_exprN := [not] tag1 or [not] tag2 or ... """ def __init__(self, tag_expressions): self.ands = [] self.limits = {} for expr in tag_expressions: self.store_and_extract_limits(self.normalized_tags_from_or(expr)) @staticmethod def normalize_tag(tag): """ Normalize a tag for a tag expression: * strip whitespace * strip '@' char * convert '~' (tilde) into '-' (minus sign) :param tag: Tag (as string). :return: Normalized tag (as string). """ tag = tag.strip() if tag.startswith('@'): tag = tag[1:] elif tag.startswith('-@') or tag.startswith('~@'): tag = '-' + tag[2:] elif tag.startswith('~'): tag = '-' + tag[1:] return tag @classmethod def normalized_tags_from_or(cls, expr): """ Normalizes all tags in an OR expression (and return it as list). :param expr: OR expression to normalize and split (as string). :return: Generator of normalized tags (as string) """ for tag in expr.strip().split(','): yield cls.normalize_tag(tag) def store_and_extract_limits(self, tags): tags_with_negation = [] for tag in tags: negated = tag.startswith('-') tag = tag.split(':') tag_with_negation = tag.pop(0) tags_with_negation.append(tag_with_negation) if tag: limit = int(tag[0]) if negated: tag_without_negation = tag_with_negation[1:] else: tag_without_negation = tag_with_negation limited = tag_without_negation in self.limits if limited and self.limits[tag_without_negation] != limit: msg = "Inconsistent tag limits for {0}: {1:d} and {2:d}" msg = msg.format(tag_without_negation, self.limits[tag_without_negation], limit) raise Exception(msg) self.limits[tag_without_negation] = limit if tags_with_negation: self.ands.append(tags_with_negation) def check(self, tags): """ Checks if this tag expression matches the tags of a model element. :param tags: List of tags of a model element. :return: True, if tag expression matches. False, otherwise. """ if not self.ands: return True element_tags = set(tags) def test_tag(xtag): if xtag.startswith('-'): # -- or xtag.startswith('~'): return xtag[1:] not in element_tags return xtag in element_tags # -- EVALUATE: (or_expr1) and (or_expr2) and ... return all(any(test_tag(xtag) for xtag in ors) for ors in self.ands) def __len__(self): return len(self.ands) def __str__(self): """Conversion back into string that represents this tag expression.""" and_parts = [] for or_terms in self.ands: and_parts.append(u",".join(or_terms)) return u" ".join(and_parts) if six.PY2: __unicode__ = __str__ __str__ = lambda self: self.__unicode__().encode("utf-8") behave-1.2.6/behave/tag_matcher.py0000644000076600000240000004162213244555737017143 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import import re import operator import warnings import six class TagMatcher(object): """Abstract base class that defines the TagMatcher protocol.""" def should_run_with(self, tags): """Determines if a feature/scenario with these tags should run or not. :param tags: List of scenario/feature tags to check. :return: True, if scenario/feature should run. :return: False, if scenario/feature should be excluded from the run-set. """ return not self.should_exclude_with(tags) def should_exclude_with(self, tags): """Determines if a feature/scenario with these tags should be excluded from the run-set. :param tags: List of scenario/feature tags to check. :return: True, if scenario/feature should be excluded from the run-set. :return: False, if scenario/feature should run. """ raise NotImplementedError class ActiveTagMatcher(TagMatcher): """Provides an active tag matcher for many categories. TAG SCHEMA: * active.with_{category}={value} * not_active.with_{category}={value} * use.with_{category}={value} * not.with_{category}={value} * only.with_{category}={value} (NOTE: For backward compatibility) TAG LOGIC ---------- Determine active-tag groups by grouping active-tags with same category together:: active_group.enabled := enabled(group.tag1) or enabled(group.tag2) or ... active_tags.enabled := enabled(group1) and enabled(group2) and ... All active-tag groups must be turned "on". Otherwise, the model element should be excluded. CONCEPT: ValueProvider ------------------------------ A ValueProvider provides the value of a category, used in active tags. A ValueProvider must provide a mapping-like protocol: .. code-block:: python class MyValueProvider(object): def get(self, category_name, default=None): ... return category_value # OR: default, if category is unknown. EXAMPLE: -------- Run some scenarios only when runtime conditions are met: * Run scenario Alice only on Windows OS * Run scenario Bob with all browsers except Chrome .. code-block:: gherkin # -- FILE: features/alice.feature Feature: @active.with_os=win32 Scenario: Alice (Run only on Windows) Given I do something ... @not_active.with_browser=chrome Scenario: Bob (Excluded with Web-Browser Chrome) Given I do something else ... .. code-block:: python # -- FILE: features/environment.py from behave.tag_matcher import ActiveTagMatcher import sys # -- MATCHES ANY ACTIVE TAGS: @{prefix}.with_{category}={value} # NOTE: active_tag_value_provider provides current category values. active_tag_value_provider = { "browser": os.environ.get("BEHAVE_BROWSER", "chrome"), "os": sys.platform, } active_tag_matcher = ActiveTagMatcher(active_tag_value_provider) def before_feature(context, feature): if active_tag_matcher.should_exclude_with(feature.tags): feature.skip() #< LATE-EXCLUDE from run-set. def before_scenario(context, scenario): if active_tag_matcher.should_exclude_with(scenario.effective_tags): exclude_reason = active_tag_matcher.exclude_reason scenario.skip(exclude_reason) #< LATE-EXCLUDE from run-set. """ value_separator = "=" tag_prefixes = ["active", "not_active", "use", "not", "only"] tag_schema = r"^(?P%s)\.with_(?P\w+(\.\w+)*)%s(?P.*)$" ignore_unknown_categories = True use_exclude_reason = False def __init__(self, value_provider, tag_prefixes=None, value_separator=None, ignore_unknown_categories=None): if value_provider is None: value_provider = {} if tag_prefixes is None: tag_prefixes = self.tag_prefixes if ignore_unknown_categories is None: ignore_unknown_categories = self.ignore_unknown_categories super(ActiveTagMatcher, self).__init__() self.value_provider = value_provider self.tag_pattern = self.make_tag_pattern(tag_prefixes, value_separator) self.tag_prefixes = tag_prefixes self.ignore_unknown_categories = ignore_unknown_categories self.exclude_reason = None @classmethod def make_tag_pattern(cls, tag_prefixes, value_separator=None): if value_separator is None: value_separator = cls.value_separator any_tag_prefix = r"|".join(tag_prefixes) expression = cls.tag_schema % (any_tag_prefix, value_separator) return re.compile(expression) @classmethod def make_category_tag(cls, category, value=None, tag_prefix=None, value_sep=None): """Build category tag (mostly for testing purposes). :return: Category tag as string (without leading AT char). """ if tag_prefix is None: tag_prefix = cls.tag_prefixes[0] # -- USE: First as default. if value_sep is None: value_sep = cls.value_separator value = value or "" return "%s.with_%s%s%s" % (tag_prefix, category, value_sep, value) def is_tag_negated(self, tag): # pylint: disable=no-self-use return tag.startswith("not") def is_tag_group_enabled(self, group_category, group_tag_pairs): """Provides boolean logic to determine if all active-tags which use the same category result in a enabled value. Use LOGICAL-OR expression for active-tags with same category:: category_tag_group.enabled := enabled(tag1) or enabled(tag2) or ... .. code-block:: gherkin @use.with_xxx=alice @use.with_xxx=bob @not.with_xxx=charly Scenario: Given a step passes ... :param group_category: Category for this tag-group (as string). :param category_tag_group: List of active-tag match-pairs. :return: True, if tag-group is enabled. """ if not group_tag_pairs: # -- CASE: Empty group is always enabled (CORNER-CASE). return True current_value = self.value_provider.get(group_category, None) if current_value is None and self.ignore_unknown_categories: # -- CASE: Unknown category, ignore it. return True tags_enabled = [] for category_tag, tag_match in group_tag_pairs: tag_prefix = tag_match.group("prefix") category = tag_match.group("category") tag_value = tag_match.group("value") assert category == group_category is_category_tag_switched_on = operator.eq # equal_to if self.is_tag_negated(tag_prefix): is_category_tag_switched_on = operator.ne # not_equal_to tag_enabled = is_category_tag_switched_on(tag_value, current_value) tags_enabled.append(tag_enabled) return any(tags_enabled) # -- PROVIDES: LOGICAL-OR expression def should_exclude_with(self, tags): group_categories = self.group_active_tags_by_category(tags) for group_category, category_tag_pairs in group_categories: if not self.is_tag_group_enabled(group_category, category_tag_pairs): # -- LOGICAL-AND SHORTCUT: Any false => Makes everything false if self.use_exclude_reason: current_value = self.value_provider.get(group_category, None) reason = "%s (but: %s)" % (group_category, current_value) self.exclude_reason = reason return True # SHOULD-EXCLUDE: not enabled = not False # -- LOGICAL-AND: All parts are True return False # SHOULD-EXCLUDE: not enabled = not True def select_active_tags(self, tags): """Select all active tags that match the tag schema pattern. :param tags: List of tags (as string). :return: List of (tag, match_object) pairs (as generator). """ for tag in tags: match_object = self.tag_pattern.match(tag) if match_object: yield (tag, match_object) def group_active_tags_by_category(self, tags): """Select all active tags that match the tag schema pattern and returns groups of active-tags, each group with tags of the same category. :param tags: List of tags (as string). :return: List of tag-groups (as generator), each tag-group is a list of (tag1, match1) pairs for the same category. """ category_tag_groups = {} for tag in tags: match_object = self.tag_pattern.match(tag) if match_object: category = match_object.group("category") category_tag_pairs = category_tag_groups.get(category, None) if category_tag_pairs is None: category_tag_pairs = category_tag_groups[category] = [] category_tag_pairs.append((tag, match_object)) for category, category_tag_pairs in six.iteritems(category_tag_groups): yield (category, category_tag_pairs) class PredicateTagMatcher(TagMatcher): def __init__(self, exclude_function): assert callable(exclude_function) super(PredicateTagMatcher, self).__init__() self.predicate = exclude_function def should_exclude_with(self, tags): return self.predicate(tags) class CompositeTagMatcher(TagMatcher): """Provides a composite tag matcher.""" def __init__(self, tag_matchers=None): super(CompositeTagMatcher, self).__init__() self.tag_matchers = tag_matchers or [] def should_exclude_with(self, tags): for tag_matcher in self.tag_matchers: if tag_matcher.should_exclude_with(tags): return True # -- OTHERWISE: return False def setup_active_tag_values(active_tag_values, data): """Setup/update active_tag values with dict-like data. Only values for keys that are already present are updated. :param active_tag_values: Data storage for active_tag value (dict-like). :param data: Data that should be used for active_tag values (dict-like). """ for category in list(active_tag_values.keys()): if category in data: active_tag_values[category] = data[category] # ----------------------------------------------------------------------------- # PROTOTYPING CLASSES: # ----------------------------------------------------------------------------- class OnlyWithCategoryTagMatcher(TagMatcher): """ Provides a tag matcher that allows to determine if feature/scenario should run or should be excluded from the run-set (at runtime). .. deprecated:: Use :class:`ActiveTagMatcher` instead. EXAMPLE: -------- Run some scenarios only when runtime conditions are met: * Run scenario Alice only on Windows OS * Run scenario Bob only on MACOSX .. code-block:: gherkin # -- FILE: features/alice.feature # TAG SCHEMA: @only.with_{category}={current_value} Feature: @only.with_os=win32 Scenario: Alice (Run only on Windows) Given I do something ... @only.with_os=darwin Scenario: Bob (Run only on MACOSX) Given I do something else ... .. code-block:: python # -- FILE: features/environment.py from behave.tag_matcher import OnlyWithCategoryTagMatcher import sys # -- MATCHES TAGS: @only.with_{category}=* = @only.with_os=* active_tag_matcher = OnlyWithCategoryTagMatcher("os", sys.platform) def before_scenario(context, scenario): if active_tag_matcher.should_exclude_with(scenario.effective_tags): scenario.skip() #< LATE-EXCLUDE from run-set. """ tag_prefix = "only.with_" value_separator = "=" def __init__(self, category, value, tag_prefix=None, value_sep=None): warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning) super(OnlyWithCategoryTagMatcher, self).__init__() self.active_tag = self.make_category_tag(category, value, tag_prefix, value_sep) self.category_tag_prefix = self.make_category_tag(category, None, tag_prefix, value_sep) def should_exclude_with(self, tags): category_tags = self.select_category_tags(tags) if category_tags and self.active_tag not in category_tags: return True # -- OTHERWISE: feature/scenario with theses tags should run. return False def select_category_tags(self, tags): return [tag for tag in tags if tag.startswith(self.category_tag_prefix)] @classmethod def make_category_tag(cls, category, value=None, tag_prefix=None, value_sep=None): if tag_prefix is None: tag_prefix = cls.tag_prefix if value_sep is None: value_sep = cls.value_separator value = value or "" return "%s%s%s%s" % (tag_prefix, category, value_sep, value) class OnlyWithAnyCategoryTagMatcher(TagMatcher): """ Provides a tag matcher that matches any category that follows the "@only.with_" tag schema and determines if it should run or should be excluded from the run-set (at runtime). TAG SCHEMA: @only.with_{category}={value} .. seealso:: OnlyWithCategoryTagMatcher .. deprecated:: Use :class:`ActiveTagMatcher` instead. EXAMPLE: -------- Run some scenarios only when runtime conditions are met: * Run scenario Alice only on Windows OS * Run scenario Bob only with browser Chrome .. code-block:: gherkin # -- FILE: features/alice.feature # TAG SCHEMA: @only.with_{category}={current_value} Feature: @only.with_os=win32 Scenario: Alice (Run only on Windows) Given I do something ... @only.with_browser=chrome Scenario: Bob (Run only with Web-Browser Chrome) Given I do something else ... .. code-block:: python # -- FILE: features/environment.py from behave.tag_matcher import OnlyWithAnyCategoryTagMatcher import sys # -- MATCHES ANY TAGS: @only.with_{category}={value} # NOTE: active_tag_value_provider provides current category values. active_tag_value_provider = { "browser": os.environ.get("BEHAVE_BROWSER", "chrome"), "os": sys.platform, } active_tag_matcher = OnlyWithAnyCategoryTagMatcher(active_tag_value_provider) def before_scenario(context, scenario): if active_tag_matcher.should_exclude_with(scenario.effective_tags): scenario.skip() #< LATE-EXCLUDE from run-set. """ def __init__(self, value_provider, tag_prefix=None, value_sep=None): warnings.warn("Use ActiveTagMatcher instead.", DeprecationWarning) super(OnlyWithAnyCategoryTagMatcher, self).__init__() if value_sep is None: value_sep = OnlyWithCategoryTagMatcher.value_separator self.value_provider = value_provider self.tag_prefix = tag_prefix or OnlyWithCategoryTagMatcher.tag_prefix self.value_separator = value_sep def should_exclude_with(self, tags): exclude_decision_map = {} for category_tag in self.select_category_tags(tags): category, value = self.parse_category_tag(category_tag) active_value = self.value_provider.get(category, None) if active_value is None: # -- CASE: Unknown category, ignore it. continue elif active_value == value: # -- CASE: Active category value selected, decision should run. exclude_decision_map[category] = False else: # -- CASE: Inactive category value selected, may exclude it. if category not in exclude_decision_map: exclude_decision_map[category] = True return any(exclude_decision_map.values()) def select_category_tags(self, tags): return [tag for tag in tags if tag.startswith(self.tag_prefix)] def parse_category_tag(self, tag): assert tag and tag.startswith(self.tag_prefix) category_value = tag[len(self.tag_prefix):] if self.value_separator in category_value: category, value = category_value.split(self.value_separator, 1) else: # -- OOPS: TAG SCHEMA FORMAT MISMATCH category = category_value value = None return category, value behave-1.2.6/behave/textutil.py0000644000076600000240000001305613244555737016547 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Provides some utility functions related to text processing. """ from __future__ import absolute_import, print_function import codecs import os import sys import six # ----------------------------------------------------------------------------- # CONSTANTS: # ----------------------------------------------------------------------------- # DEFAULT STRATEGY: For error handling when unicode-strings are converted. BEHAVE_UNICODE_ERRORS = os.environ.get("BEHAVE_UNICODE_ERRORS", "replace") # ----------------------------------------------------------------------------- # FUNCTIONS: # ----------------------------------------------------------------------------- def make_indentation(indent_size, part=u" "): """Creates an indentation prefix string of the given size.""" return indent_size * part def indent(text, prefix): # pylint: disable=redefined-outer-name """Indent text or a number of text lines (with newline). :param lines: Text lines to indent (as string or list of strings). :param prefix: Line prefix to use (as string). :return: Indented text (as unicode string). """ lines = text newline = u"" if isinstance(text, six.string_types): lines = text.splitlines(True) elif lines and not lines[0].endswith("\n"): # -- TEXT LINES: Without trailing new-line. newline = u"\n" # MAYBE: return newline.join([prefix + six.text_type(line, errors="replace") return newline.join([prefix + six.text_type(line) for line in lines]) def compute_words_maxsize(words): """Compute the maximum word size from a list of words (or strings). :param words: List of words (or strings) to use. :return: Maximum size of all words. """ max_size = 0 for word in words: if len(word) > max_size: max_size = len(word) return max_size def is_ascii_encoding(encoding): """Checks if a given encoding is ASCII.""" try: return codecs.lookup(encoding).name == "ascii" except LookupError: return False def select_best_encoding(outstream=None): """Select the *best* encoding for an output stream/file. Uses: * ``outstream.encoding`` (if available) * ``sys.getdefaultencoding()`` (otherwise) Note: If encoding=ascii, uses encoding=UTF-8 :param outstream: Output stream to select encoding for (or: stdout) :return: Unicode encoding name (as string) to use (for output stream). """ outstream = outstream or sys.stdout encoding = getattr(outstream, "encoding", None) or sys.getdefaultencoding() if is_ascii_encoding(encoding): # -- REQUIRED-FOR: Python2 # MAYBE: locale.getpreferredencoding() return "utf-8" return encoding def text(value, encoding=None, errors=None): """Convert into a unicode string. :param value: Value to convert into a unicode string (bytes, str, object). :return: Unicode string SYNDROMES: * Convert object to unicode: Has only __str__() method (Python2) * Windows: exception-traceback and encoding=unicode-escape are BAD * exception-traceback w/ weird encoding or bytes ALTERNATIVES: * Use traceback2 for Python2: Provides unicode tracebacks """ if encoding is None: encoding = select_best_encoding() if errors is None: errors = BEHAVE_UNICODE_ERRORS if isinstance(value, six.text_type): # -- CASE: ALREADY UNICODE (pass-through, efficiency): return value elif isinstance(value, six.binary_type): # -- CASE: bytes/binary_type (Python2: str) try: return six.text_type(value, encoding, errors) except UnicodeError: # -- BEST-EFFORT: return six.u(value) # elif isinstance(value, bytes): # # -- MAYBE: filename, path, etc. # try: # return value.decode(sys.getfilesystemencoding()) # except UnicodeError: # return value.decode("utf-8", "replace") # MAYBE: "ignore" else: # -- CASE: CONVERT/CAST OBJECT TO TEXT/STRING try: if six.PY2: try: text2 = six.text_type(value) except UnicodeError as e: # -- NOTE: value has no sane unicode conversion # encoding=unicode-escape helps recover from errors. data = str(value) text2 = six.text_type(data, "unicode-escape", "replace") else: # PY3: Cast to string/unicode text2 = six.text_type(value) except UnicodeError as e: # Python3: multi-arg call supports only string-like object: str, bytes text2 = six.text_type(e) return text2 def to_texts(args, encoding=None, errors=None): """Process a list of string-like objects into list of unicode values. Optionally converts binary text into unicode for each item. :return: List of text/unicode values. """ if encoding is None: encoding = select_best_encoding() return [text(arg, encoding, errors) for arg in args] def ensure_stream_with_encoder(stream, encoding=None): if not encoding: encoding = select_best_encoding(stream) if six.PY3: return stream elif hasattr(stream, "stream"): return stream # Already wrapped with a codecs.StreamWriter else: assert six.PY2 # py2 does, however, sometimes declare an encoding on sys.stdout, # even if it doesn't use it (or it might be explicitly None) stream = codecs.getwriter(encoding)(stream) return stream behave-1.2.6/behave/userdata.py0000644000076600000240000001703213244555737016473 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Functionality to support user-specific configuration data (userdata). """ from __future__ import absolute_import from behave._types import Unknown # ----------------------------------------------------------------------------- # FUNCTIONS: # ----------------------------------------------------------------------------- def parse_bool(text): """Parses a boolean text and converts it into boolean value (if possible). Supported truth string values: * true: "true", "yes", "on", "1" * false: "false", "no", "off", "0" :raises: ValueError, if text is invalid """ from distutils.util import strtobool return bool(strtobool(text)) def parse_user_define(text): """Parse "{name}={value}" text and return parts as tuple. Used for command-line definitions, like "... -D name=value". SUPPORTED SCHEMA: * "{name}={value}" * "{name}" (boolean flag; value="true") * '"{name}={value}"' (double-quoted name-value pair) * "'{name}={value}'" (single-quoted name-value pair) * '{name}="{value}"' (double-quoted value) * "{name}='{value}'" (single-quoted value) * " {name} = {value} " (whitespace padded) .. note:: Leading/trailing Quotes are stripped. :param text: Text to parse (as string). :return: (name, value) pair as tuple. """ text = text.strip() if "=" in text: text = unqote(text) name, value = text.split("=", 1) name = name.strip() value = unqote(value.strip()) else: # -- ASSUMPTION: Boolean definition (as flag) name = text value = "true" return (name, value) def unqote(text): """Strip pair of leading and trailing quotes from text.""" # -- QUOTED: Strip single-quote or double-quote pair. if ((text.startswith('"') and text.endswith('"')) or (text.startswith("'") and text.endswith("'"))): text = text[1:-1] return text # ----------------------------------------------------------------------------- # CLASSES: # ----------------------------------------------------------------------------- class UserData(dict): """Dictionary-like user-data with some additional features: * type-converter methods, similar to configparser.ConfigParser.getint() """ def getas(self, convert, name, default=None, valuetype=None): """Converts the value of user-data parameter from a string into a specific value type. :param convert: Converter function to use (string to value-type). :param name: Variable name to use. :param default: Default value, used if parameter is not found. :param valuetype: Value type(s), needed if convert != valuetype() :return: Converted textual value (type: valuetype) :return: Default value, if parameter is unknown. :raises ValueError: If type conversion fails. """ if valuetype is None: # -- ASSUME: Converter function is the type constructor. valuetype = convert value = self.get(name, Unknown) if value is Unknown: return default elif isinstance(value, valuetype): # -- PRESERVE: Pre-converted value if type matches. return value else: # -- CASE: Textual value (expected) # Raise ValueError if parse/conversion fails. assert callable(convert) return convert(value) def getint(self, name, default=0): """Convert parameter value (as string) into a integer value. :return: Parameter value as integer number (on success). :raises: ValueError, if type conversion fails. """ return self.getas(int, name, default) def getfloat(self, name, default=0.0): """Convert parameter value (as string) into a float value. :return: Parameter value as float number (on success). :raises: ValueError, if type conversion fails. """ return self.getas(float, name, default) def getbool(self, name, default=False): """Converts user-data string-value into boolean value (if possible). Supported truth string values: * true: "true", "yes", "on", "1" * false: "false", "no", "off", "0" :param name: Parameter name (as string). :param default: Default value, if parameter is unknown (=False). :return: Boolean value of parameter :raises: ValueError, if type conversion fails. """ return self.getas(parse_bool, name, default, valuetype=bool) @classmethod def make(cls, data): if data is None: data = cls() elif not isinstance(data, cls): data = cls(data) return data class UserDataNamespace(object): """Provides a light-weight dictview to the user data that allows you to access all params in a namespace, that use "{namespace}.*" names. .. code-block:: python my_config = UserDataNamespace("my.config", userdata) value1 = my_config.getint("value1") # USE: my.config.value1 value2 = my_config.get("value2") # USE: my.config.value2 """ def __init__(self, namespace, data=None): self.namespace = namespace or "" self.data = UserData.make(data) @staticmethod def make_scoped(namespace, name): """Creates a scoped-name from its parts.""" if not namespace: # noqa return name return "%s.%s" % (namespace, name) # -- DICT-LIKE: def get(self, name, default=None): scoped_name = self.make_scoped(self.namespace, name) return self.data.get(scoped_name, default) def getas(self, convert, name, default=None, valuetype=None): scoped_name = self.make_scoped(self.namespace, name) return self.data.getas(convert, scoped_name, default=default, valuetype=valuetype) def getint(self, name, default=0): scoped_name = self.make_scoped(self.namespace, name) return self.data.getint(scoped_name, default=default) def getfloat(self, name, default=0.0): scoped_name = self.make_scoped(self.namespace, name) return self.data.getfloat(scoped_name, default=default) def getbool(self, name, default=False): scoped_name = self.make_scoped(self.namespace, name) return self.data.getbool(scoped_name, default=default) def __contains__(self, name): scoped_name = self.make_scoped(self.namespace, name) return scoped_name in self.data def __getitem__(self, name): scoped_name = self.make_scoped(self.namespace, name) return self.data[scoped_name] def __setitem__(self, name, value): scoped_name = self.make_scoped(self.namespace, name) self.data[scoped_name] = value def __len__(self): return len(self.scoped_keys()) def scoped_keys(self): if not self.namespace: # noqa return self.data.keys() prefix = "%s." % self.namespace return [key for key in self.data.keys() if key.startswith(prefix)] def keys(self): prefix = "%s." % self.namespace for scoped_name in self.scoped_keys(): name = scoped_name.replace(prefix, "", 1) yield name def values(self): for scoped_name in self.scoped_keys(): yield self.data[scoped_name] def items(self): for name in self.keys(): scoped_name = self.make_scoped(self.namespace, name) value = self.data[scoped_name] yield (name, value) behave-1.2.6/behave.egg-info/0000755000076600000240000000000013244564037015771 5ustar jensstaff00000000000000behave-1.2.6/behave.egg-info/dependency_links.txt0000644000076600000240000000000113244564037022037 0ustar jensstaff00000000000000 behave-1.2.6/behave.egg-info/entry_points.txt0000644000076600000240000000016313244564037021267 0ustar jensstaff00000000000000[console_scripts] behave = behave.__main__:main [distutils.commands] behave_test = setuptools_behave:behave_test behave-1.2.6/behave.egg-info/PKG-INFO0000644000076600000240000001433213244564037017071 0ustar jensstaff00000000000000Metadata-Version: 1.2 Name: behave Version: 1.2.6 Summary: behave is behaviour-driven development, Python style Home-page: http://github.com/behave/behave Author: Jens Engel, Benno Rice and Richard Jones Author-email: behave-users@googlegroups.com License: BSD Description-Content-Type: UNKNOWN Description: .. image:: https://img.shields.io/travis/behave/behave/master.svg :target: https://travis-ci.org/behave/behave :alt: Travis CI Build Status .. image:: https://readthedocs.org/projects/behave/badge/?version=latest :target: http://behave.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. image:: https://img.shields.io/pypi/v/behave.svg :target: https://pypi.python.org/pypi/behave :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/behave.svg :target: https://pypi.python.org/pypi/behave :alt: Downloads .. image:: https://img.shields.io/pypi/l/behave.svg :target: https://pypi.python.org/pypi/behave/ :alt: License .. image:: https://badges.gitter.im/Join%20Chat.svg :alt: Join the chat at https://gitter.im/behave/behave :target: https://gitter.im/behave/behave?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge .. |logo| image:: https://raw.github.com/behave/behave/master/docs/_static/behave_logo1.png behave is behavior-driven development, Python style. |logo| Behavior-driven development (or BDD) is an agile software development technique that encourages collaboration between developers, QA and non-technical or business participants in a software project. *behave* uses tests written in a natural language style, backed up by Python code. First, `install *behave*.`_ Now make a directory called "features/". In that directory create a file called "example.feature" containing: .. code-block:: gherkin # -- FILE: features/example.feature Feature: Showing off behave Scenario: Run a simple test Given we have behave installed When we implement 5 tests Then behave will test them for us! Make a new directory called "features/steps/". In that directory create a file called "example_steps.py" containing: .. code-block:: python # -- FILE: features/steps/example_steps.py from behave import given, when, then, step @given('we have behave installed') def step_impl(context): pass @when('we implement {number:d} tests') def step_impl(context, number): # -- NOTE: number is converted into integer assert number > 1 or number == 0 context.tests_count = number @then('behave will test them for us!') def step_impl(context): assert context.failed is False assert context.tests_count >= 0 Run behave: .. code-block:: bash $ behave Feature: Showing off behave # features/example.feature:2 Scenario: Run a simple test # features/example.feature:4 Given we have behave installed # features/steps/example_steps.py:4 When we implement 5 tests # features/steps/example_steps.py:8 Then behave will test them for us! # features/steps/example_steps.py:13 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined Now, continue reading to learn how to get the most out of *behave*. To get started, we recommend the `tutorial`_ and then the `feature testing language`_ and `api`_ references. .. _`Install *behave*.`: http://pythonhosted.org/behave/install.html .. _`tutorial`: http://pythonhosted.org/behave/tutorial.html#features .. _`feature testing language`: http://pythonhosted.org/behave/gherkin.html .. _`api`: http://pythonhosted.org/behave/api.html More Information ------------------------------------------------------------------------------- * `behave documentation`_: `latest edition`_, `stable edition`_, `PDF`_ * `behave.example`_: Behave Examples and Tutorials (docs, executable examples). * `changelog`_ (latest changes) .. _behave documentation: http://behave.readthedocs.io/ .. _changelog: https://github.com/behave/behave/blob/master/CHANGES.rst .. _behave.example: https://github.com/behave/behave.example .. _`latest edition`: http://behave.readthedocs.io/en/latest/ .. _`stable edition`: http://behave.readthedocs.io/en/stable/ .. _PDF: https://media.readthedocs.org/pdf/behave/latest/behave.pdf Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 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 :: 3.6 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: Jython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Testing Classifier: License :: OSI Approved :: BSD License Provides: behave Provides: setuptools_behave Requires-Python: >=2.6, !=3.0.*, !=3.1.*, !=3.2.* behave-1.2.6/behave.egg-info/requires.txt0000644000076600000240000000051413244564037020371 0ustar jensstaff00000000000000parse>=1.8.2 parse_type>=0.4.2 six>=1.11 [:python_version < "2.7"] argparse importlib ordereddict [:python_version < "3.0"] traceback2 [:python_version < "3.4"] enum34 [develop] coverage pytest>=3.0 pytest-cov tox invoke>=0.21.0 path.py>=8.1.2 pycmd pathlib modernize>=0.5 pylint [docs] sphinx>=1.6 sphinx_bootstrap_theme>=0.6 behave-1.2.6/behave.egg-info/SOURCES.txt0000644000076600000240000003174113244564037017663 0ustar jensstaff00000000000000.bumpversion.cfg .coveragerc .editorconfig .pycheckrc .pylintrc .travis.yml CHANGES.rst LICENSE MANIFEST.in PROJECT_INFO.rst README.rst VERSION.txt behave.ini conftest.py invoke.yaml pytest.ini setup.cfg setup.py setuptools_behave.py tox.ini .ci/appveyor.yml behave/__init__.py behave/__main__.py behave/_stepimport.py behave/_types.py behave/capture.py behave/configuration.py behave/fixture.py behave/i18n.py behave/importer.py behave/json_parser.py behave/log_capture.py behave/matchers.py behave/model.py behave/model_core.py behave/model_describe.py behave/parser.py behave/runner.py behave/runner_util.py behave/step_registry.py behave/tag_expression.py behave/tag_matcher.py behave/textutil.py behave/userdata.py behave.egg-info/PKG-INFO behave.egg-info/SOURCES.txt behave.egg-info/dependency_links.txt behave.egg-info/entry_points.txt behave.egg-info/requires.txt behave.egg-info/top_level.txt behave.egg-info/zip-safe behave/api/__init__.py behave/api/async_step.py behave/compat/__init__.py behave/compat/collections.py behave/contrib/__init__.py behave/contrib/formatter_missing_steps.py behave/contrib/scenario_autoretry.py behave/contrib/substep_dirs.py behave/formatter/__init__.py behave/formatter/_builtins.py behave/formatter/_registry.py behave/formatter/ansi_escapes.py behave/formatter/base.py behave/formatter/formatters.py behave/formatter/json.py behave/formatter/null.py behave/formatter/plain.py behave/formatter/pretty.py behave/formatter/progress.py behave/formatter/rerun.py behave/formatter/sphinx_steps.py behave/formatter/sphinx_util.py behave/formatter/steps.py behave/formatter/tags.py behave/reporter/__init__.py behave/reporter/base.py behave/reporter/junit.py behave/reporter/summary.py behave4cmd0/__all_steps__.py behave4cmd0/__init__.py behave4cmd0/__setup.py behave4cmd0/command_shell.py behave4cmd0/command_shell_proc.py behave4cmd0/command_steps.py behave4cmd0/command_util.py behave4cmd0/failing_steps.py behave4cmd0/note_steps.py behave4cmd0/passing_steps.py behave4cmd0/pathutil.py behave4cmd0/setup_command_shell.py behave4cmd0/textutil.py behave4cmd0/log/__init__.py behave4cmd0/log/steps.py bin/behave bin/behave.cmd bin/behave.junit_filter.py bin/behave.step_durations.py bin/behave2cucumber_json.py bin/behave_cmd.py bin/convert_i18n_yaml.py bin/explore_platform_encoding.py bin/gherkin-languages.json bin/i18n.yml bin/invoke bin/invoke.cmd bin/json.format.py bin/jsonschema_validate.py bin/make_localpi.py bin/project_bootstrap.sh bin/toxcmd.py bin/toxcmd3.py docs/Makefile docs/_common_extlinks.rst docs/api.rst docs/appendix.rst docs/behave.rst docs/behave.rst-template docs/behave_ecosystem.rst docs/comparison.rst docs/conf.py docs/context_attributes.rst docs/fixtures.rst docs/formatters.rst docs/gherkin.rst docs/index.rst docs/install.rst docs/more_info.rst docs/new_and_noteworthy.rst docs/new_and_noteworthy_v1.2.4.rst docs/new_and_noteworthy_v1.2.5.rst docs/new_and_noteworthy_v1.2.6.rst docs/parse_builtin_types.rst docs/philosophy.rst docs/practical_tips.rst docs/regular_expressions.rst docs/related.rst docs/test_domains.rst docs/tutorial.rst docs/update_behave_rst.py docs/usecase_django.rst docs/usecase_flask.rst docs/_static/agogo.css docs/_static/behave_logo.png docs/_static/behave_logo1.png docs/_static/behave_logo2.png docs/_static/behave_logo3.png docs/_themes/LICENSE docs/_themes/kr/layout.html docs/_themes/kr/relations.html docs/_themes/kr/theme.conf docs/_themes/kr/static/flasky.css_t docs/_themes/kr/static/small_flask.css docs/_themes/kr_small/layout.html docs/_themes/kr_small/theme.conf docs/_themes/kr_small/static/flasky.css_t etc/json/behave.json-schema etc/junit.xml/behave_junit.xsd etc/junit.xml/junit-4.xsd examples/async_step/README.txt examples/async_step/behave.ini examples/async_step/testrun_example.async_dispatch.txt examples/async_step/testrun_example.async_run.txt examples/async_step/features/async_dispatch.feature examples/async_step/features/async_run.feature examples/async_step/features/environment.py examples/async_step/features/steps/async_dispatch_steps.py examples/async_step/features/steps/async_steps34.py examples/async_step/features/steps/async_steps35.py examples/env_vars/README.rst examples/env_vars/behave.ini examples/env_vars/behave_run.output_example.txt examples/env_vars/features/env_var.feature examples/env_vars/features/steps/env_var_steps.py features/README.txt features/background.feature features/capture_stderr.feature features/capture_stdout.feature features/cmdline.lang_list.feature features/configuration.default_paths.feature features/context.global_params.feature features/context.local_params.feature features/directory_layout.advanced.feature features/directory_layout.basic.feature features/directory_layout.basic2.feature features/environment.py features/exploratory_testing.with_table.feature features/feature.description.feature features/feature.exclude_from_run.feature features/fixture.feature features/formatter.help.feature features/formatter.json.feature features/formatter.progress3.feature features/formatter.rerun.feature features/formatter.steps.feature features/formatter.steps_catalog.feature features/formatter.steps_doc.feature features/formatter.steps_usage.feature features/formatter.tags.feature features/formatter.tags_location.feature features/formatter.user_defined.feature features/i18n.unicode_problems.feature features/logcapture.clear_handlers.feature features/logcapture.feature features/logcapture.filter.feature features/logging.no_capture.feature features/logging.setup_format.feature features/logging.setup_level.feature features/logging.setup_with_configfile.feature features/parser.background.sad_cases.feature features/parser.feature.sad_cases.feature features/runner.abort_by_user.feature features/runner.context_cleanup.feature features/runner.continue_after_failed_step.feature features/runner.default_format.feature features/runner.dry_run.feature features/runner.feature_listfile.feature features/runner.hook_errors.feature features/runner.multiple_formatters.feature features/runner.scenario_autoretry.feature features/runner.select_files_by_regexp.example.feature features/runner.select_files_by_regexp.feature features/runner.select_scenarios_by_file_location.feature features/runner.select_scenarios_by_name.feature features/runner.select_scenarios_by_tag.feature features/runner.stop_after_failure.feature features/runner.tag_logic.feature features/runner.unknown_formatter.feature features/runner.use_stage_implementations.feature features/runner.use_substep_dirs.feature features/scenario.description.feature features/scenario.exclude_from_run.feature features/scenario_outline.basics.feature features/scenario_outline.improved.feature features/scenario_outline.name_annotation.feature features/scenario_outline.parametrized.feature features/scenario_outline.tagged_examples.feature features/step.async_steps.feature features/step.duplicated_step.feature features/step.execute_steps.feature features/step.execute_steps.with_table.feature features/step.import_other_step_module.feature features/step.pending_steps.feature features/step.undefined_steps.feature features/step.use_step_library.feature features/step_dialect.generic_steps.feature features/step_dialect.given_when_then.feature features/step_param.builtin_types.with_float.feature features/step_param.builtin_types.with_integer.feature features/step_param.custom_types.feature features/summary.undefined_steps.feature features/tags.active_tags.feature features/tags.default_tags.feature features/tags.tag_expression.feature features/userdata.feature features/steps/behave_active_tags_steps.py features/steps/behave_context_steps.py features/steps/behave_model_tag_logic_steps.py features/steps/behave_model_util.py features/steps/behave_select_files_steps.py features/steps/behave_tag_expression_steps.py features/steps/behave_undefined_steps.py features/steps/fixture_steps.py features/steps/use_steplib_behave4cmd.py issue.features/README.txt issue.features/environment.py issue.features/issue0030.feature issue.features/issue0031.feature issue.features/issue0032.feature issue.features/issue0035.feature issue.features/issue0040.feature issue.features/issue0041.feature issue.features/issue0042.feature issue.features/issue0044.feature issue.features/issue0046.feature issue.features/issue0052.feature issue.features/issue0059.feature issue.features/issue0063.feature issue.features/issue0064.feature issue.features/issue0065.feature issue.features/issue0066.feature issue.features/issue0067.feature issue.features/issue0069.feature issue.features/issue0072.feature issue.features/issue0073.feature issue.features/issue0075.feature issue.features/issue0077.feature issue.features/issue0080.feature issue.features/issue0081.feature issue.features/issue0083.feature issue.features/issue0084.feature issue.features/issue0085.feature issue.features/issue0092.feature issue.features/issue0096.feature issue.features/issue0099.feature issue.features/issue0109.feature issue.features/issue0111.feature issue.features/issue0112.feature issue.features/issue0114.feature issue.features/issue0116.feature issue.features/issue0125.feature issue.features/issue0127.feature issue.features/issue0139.feature issue.features/issue0142.feature issue.features/issue0143.feature issue.features/issue0145.feature issue.features/issue0148.feature issue.features/issue0152.feature issue.features/issue0159.feature issue.features/issue0162.feature issue.features/issue0171.feature issue.features/issue0172.feature issue.features/issue0175.feature issue.features/issue0177.feature issue.features/issue0181.feature issue.features/issue0184.feature issue.features/issue0186.feature issue.features/issue0188.feature issue.features/issue0191.feature issue.features/issue0194.feature issue.features/issue0197.feature issue.features/issue0216.feature issue.features/issue0226.feature issue.features/issue0228.feature issue.features/issue0230.feature issue.features/issue0231.feature issue.features/issue0238.feature issue.features/issue0251.feature issue.features/issue0280.feature issue.features/issue0288.feature issue.features/issue0300.feature issue.features/issue0302.feature issue.features/issue0309.feature issue.features/issue0330.feature issue.features/issue0349.feature issue.features/issue0361.feature issue.features/issue0383.feature issue.features/issue0384.feature issue.features/issue0385.feature issue.features/issue0424.feature issue.features/issue0446.feature issue.features/issue0449.feature issue.features/issue0453.feature issue.features/issue0457.feature issue.features/issue0462.feature issue.features/issue0476.feature issue.features/issue0487.feature issue.features/issue0506.feature issue.features/issue0510.feature issue.features/issue0547.feature issue.features/issue0573.feature issue.features/issue0597.feature issue.features/issue0606.feature issue.features/requirements.txt issue.features/steps/ansi_steps.py issue.features/steps/behave_hooks_steps.py issue.features/steps/use_steplib_behave4cmd.py more.features/formatter.json.validate_output.feature more.features/tutorial.feature more.features/steps/tutorial_steps.py more.features/steps/use_steplib_behave4cmd.py py.requirements/README.txt py.requirements/all.txt py.requirements/basic.txt py.requirements/ci.travis.txt py.requirements/develop.txt py.requirements/docs.txt py.requirements/json.txt py.requirements/more_py26.txt py.requirements/testing.txt tasks/__behave.py tasks/__init__.py tasks/__main__.py tasks/_compat_shutil.py tasks/_dry_run.py tasks/_setup.py tasks/_tasklet_cleanup.py tasks/clean.py tasks/docs.py tasks/py.requirements.txt tasks/release.py tasks/test.py tasks/_vendor/README.rst tasks/_vendor/invoke.zip tasks/_vendor/path.py tasks/_vendor/pathlib.py tasks/_vendor/six.py test/__init__.py test/_importer_candidate.py test/test_ansi_escapes.py test/test_configuration.py test/test_formatter.py test/test_formatter_progress.py test/test_formatter_rerun.py test/test_formatter_tags.py test/test_importer.py test/test_log_capture.py test/test_matchers.py test/test_model.py test/test_parser.py test/test_runner.py test/test_step_registry.py test/test_tag_expression.py test/test_tag_expression2.py test/test_tag_matcher.py test/reporters/__init__.py test/reporters/test_summary.py tests/README.txt tests/__init__.py tests/api/__ONLY_PY34_or_newer.txt tests/api/__init__.py tests/api/_test_async_step34.py tests/api/_test_async_step35.py tests/api/test_async_step.py tests/api/testing_support.py tests/api/testing_support_async.py tests/issues/test_issue0336.py tests/issues/test_issue0449.py tests/issues/test_issue0453.py tests/issues/test_issue0458.py tests/issues/test_issue0495.py tests/unit/__init__.py tests/unit/test_behave4cmd_command_shell_proc.py tests/unit/test_capture.py tests/unit/test_context_cleanups.py tests/unit/test_explore_generator.py tests/unit/test_fixture.py tests/unit/test_model_core.py tests/unit/test_textutil.py tests/unit/test_userdata.py tools/test-features/background.feature tools/test-features/environment.py tools/test-features/french.feature tools/test-features/outline.feature tools/test-features/parse.feature tools/test-features/step-data.feature tools/test-features/tags.feature tools/test-features/steps/steps.pybehave-1.2.6/behave.egg-info/top_level.txt0000644000076600000240000000003113244564037020515 0ustar jensstaff00000000000000behave setuptools_behave behave-1.2.6/behave.egg-info/zip-safe0000644000076600000240000000000113244563051017414 0ustar jensstaff00000000000000 behave-1.2.6/behave.ini0000644000076600000240000000260213244555737015007 0ustar jensstaff00000000000000# ============================================================================= # BEHAVE CONFIGURATION # ============================================================================= # FILE: .behaverc, behave.ini, setup.cfg, tox.ini # # SEE ALSO: # * http://packages.python.org/behave/behave.html#configuration-files # * https://github.com/behave/behave # * http://pypi.python.org/pypi/behave/ # ============================================================================= [behave] default_tags = -@xfail -@not_implemented show_skipped = false format = rerun progress3 outfiles = rerun.txt reports/report_progress3.txt junit = true logging_level = INFO # logging_format = LOG.%(levelname)-8s %(name)-10s: %(message)s # logging_format = LOG.%(levelname)-8s %(asctime)s %(name)-10s: %(message)s # -- ALLURE-FORMATTER REQUIRES: # brew install allure # pip install allure-behave # ALLURE_REPORTS_DIR=allure.reports # behave -f allure -o $ALLURE_REPORTS_DIR ... # allure serve $ALLURE_REPORTS_DIR # # SEE ALSO: # * https://github.com/allure-framework/allure2 # * https://github.com/allure-framework/allure-python [behave.formatters] allure = allure_behave.formatter:AllureFormatter # PREPARED: # [behave] # format = ... missing_steps ... # output = ... features/steps/missing_steps.py ... # [behave.formatters] # missing_steps = behave.contrib.formatter_missing_steps:MissingStepsFormatter behave-1.2.6/behave4cmd0/0000755000076600000240000000000013244564037015127 5ustar jensstaff00000000000000behave-1.2.6/behave4cmd0/__all_steps__.py0000644000076600000240000000050613244555737020273 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Import all step definitions of this step-library. Step definitions are automatically registered in "behave.step_registry". """ from __future__ import absolute_import # -- IMPORT STEP-LIBRARY: behave4cmd0 import behave4cmd0.command_steps import behave4cmd0.note_steps import behave4cmd0.log.steps behave-1.2.6/behave4cmd0/__init__.py0000644000076600000240000000016413244555737017250 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Predecessor of behave4cmd library. Currently used to provide self-tests for behave. """ behave-1.2.6/behave4cmd0/__setup.py0000644000076600000240000000054313244555737017150 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import import os.path # ----------------------------------------------------------------------------- # CONSTANTS: # ----------------------------------------------------------------------------- HERE = os.path.dirname(__file__) TOP = os.path.join(HERE, "..") TOPA = os.path.abspath(TOP) behave-1.2.6/behave4cmd0/command_shell.py0000755000076600000240000001636213244555737020330 0ustar jensstaff00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Provide a behave shell to simplify creation of feature files and running features, etc. context.command_result = behave_shell.behave(cmdline, cwd=context.workdir) behave_shell.create_scenario(scenario_text, cwd=context.workdir) behave_shell.create_step_definition(context.text, cwd=context.workdir) context.command_result = behave_shell.run_feature_with_formatter( context.features[0], formatter=formatter, cwd=context.workdir) """ from __future__ import absolute_import, print_function, with_statement from behave4cmd0.__setup import TOP from behave.textutil import text as _text import os.path import six import subprocess import sys import shlex if six.PY2: import codecs # HERE = os.path.dirname(__file__) # TOP = os.path.join(HERE, "..") # ----------------------------------------------------------------------------- # CLASSES: # ----------------------------------------------------------------------------- class CommandResult(object): """ ValueObject to store the results of a subprocess command call. """ def __init__(self, **kwargs): self.command = kwargs.pop("command", None) self.returncode = kwargs.pop("returncode", 0) self.stdout = kwargs.pop("stdout", "") self.stderr = kwargs.pop("stderr", "") self._output = None if kwargs: names = ", ".join(kwargs.keys()) raise ValueError("Unexpected: %s" % names) @property def output(self): if self._output is None: output = self.stdout if self.stderr: if self.stdout: output += "\n" output += self.stderr self._output = output return self._output @property def failed(self): return self.returncode != 0 def clear(self): self.command = None self.returncode = 0 self.stdout = "" self.stderr = "" self._output = None class Command(object): """ Helper class to run commands as subprocess, collect their output and subprocess returncode. """ DEBUG = False COMMAND_MAP = { "behave": os.path.normpath("{0}/bin/behave".format(TOP)) } PREPROCESSOR_MAP = {} POSTPROCESSOR_MAP = {} USE_SHELL = sys.platform.startswith("win") @staticmethod def preprocess_command(preprocessors, cmdargs, command=None, cwd="."): if not os.path.isdir(cwd): return cmdargs elif not command: command = " ".join(cmdargs) for preprocess in preprocessors: cmdargs = preprocess(command, cmdargs, cwd) return cmdargs @staticmethod def postprocess_command(postprocessors, command_result): for postprocess in postprocessors: command_result = postprocess(command_result) return command_result @classmethod def run(cls, command, cwd=".", **kwargs): """ Make a subprocess call, collect its output and returncode. Returns CommandResult instance as ValueObject. """ assert isinstance(command, six.string_types) command_result = CommandResult() command_result.command = command use_shell = cls.USE_SHELL if "shell" in kwargs: use_shell = kwargs.pop("shell") # -- BUILD COMMAND ARGS: if six.PY2 and isinstance(command, six.text_type): # -- PREPARE-FOR: shlex.split() # In PY2, shlex.split() requires bytes string (non-unicode). # In PY3, shlex.split() accepts unicode string. command = codecs.encode(command, "utf-8") cmdargs = shlex.split(command) # -- TRANSFORM COMMAND (optional) command0 = cmdargs[0] real_command = cls.COMMAND_MAP.get(command0, None) if real_command: cmdargs0 = real_command.split() cmdargs = cmdargs0 + cmdargs[1:] preprocessors = cls.PREPROCESSOR_MAP.get(command0) if preprocessors: cmdargs = cls.preprocess_command(preprocessors, cmdargs, command, cwd) # -- RUN COMMAND: try: process = subprocess.Popen(cmdargs, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=use_shell, cwd=cwd, **kwargs) out, err = process.communicate() if six.PY2: # py3: we get unicode strings, py2 not # default_encoding = "UTF-8" out = _text(out, process.stdout.encoding) err = _text(err, process.stderr.encoding) process.poll() assert process.returncode is not None command_result.stdout = out command_result.stderr = err command_result.returncode = process.returncode if cls.DEBUG: print("shell.cwd={0}".format(kwargs.get("cwd", None))) print("shell.command: {0}".format(" ".join(cmdargs))) print("shell.command.output:\n{0};".format(command_result.output)) except OSError as e: command_result.stderr = u"OSError: %s" % e command_result.returncode = e.errno assert e.errno != 0 postprocessors = cls.POSTPROCESSOR_MAP.get(command0) if postprocessors: command_result = cls.postprocess_command(postprocessors, command_result) return command_result # ----------------------------------------------------------------------------- # PREPROCESSOR: # ----------------------------------------------------------------------------- def path_glob(command, cmdargs, cwd="."): import glob if not glob.has_magic(command): return cmdargs assert os.path.isdir(cwd) try: current_cwd = os.getcwd() os.chdir(cwd) new_cmdargs = [] for cmdarg in cmdargs: if not glob.has_magic(cmdarg): new_cmdargs.append(cmdarg) continue more_args = glob.glob(cmdarg) if more_args: new_cmdargs.extend(more_args) else: # -- BAD-CASE: Require at least one match. # Otherwise, restore original arg. new_cmdargs.append(cmdarg) cmdargs = new_cmdargs finally: os.chdir(current_cwd) return cmdargs # ----------------------------------------------------------------------------- # FUNCTIONS: # ----------------------------------------------------------------------------- def run(command, cwd=".", **kwargs): return Command.run(command, cwd=cwd, **kwargs) def behave(cmdline, cwd=".", **kwargs): """ Run behave as subprocess command and return process/shell instance with results (collected output, returncode). """ assert isinstance(cmdline, six.string_types) return run("behave " + cmdline, cwd=cwd, **kwargs) # ----------------------------------------------------------------------------- # TEST MAIN: # ----------------------------------------------------------------------------- if __name__ == "__main__": command = " ".join(sys.argv[1:]) output = Command.run(sys.argv[1:]) print("command: {0}\n{1}\n".format(command, output)) behave-1.2.6/behave4cmd0/command_shell_proc.py0000755000076600000240000002225513244555737021351 0ustar jensstaff00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ This module provides pre-/post-processors for the mod:`behave4cmd0.command_shell`. """ from __future__ import absolute_import, print_function import re import sys from six import string_types # ----------------------------------------------------------------------------- # UTILITY: # ----------------------------------------------------------------------------- def posixpath_normpath(filename): if not filename: return filename return filename.replace("\\", "/").replace("//", "/") # ----------------------------------------------------------------------------- # LINE PROCESSORS: # ----------------------------------------------------------------------------- class LineProcessor(object): """Function-like object that may perform text-line transformations.""" def __init__(self, marker=None): self.marker = marker def reset(self): pass def __call__(self, text): return text class TracebackLineNormalizer(LineProcessor): """Line processor that tries to normalize path lines in a traceback dump.""" marker = "Traceback (most recent call last):" file_pattern = re.compile(r'\s\s+File "(?P.*)", line .*') def __init__(self): super(TracebackLineNormalizer, self).__init__(self.marker) self.traceback_section = False def reset(self): self.traceback_section = False def __call__(self, line): """Process a line and optionally transform it. :param line: line to process (as text) :return: Same line or transformed/normalized line (as text). """ marker = self.marker stripped_line = line.strip() if marker == stripped_line: assert not self.traceback_section self.traceback_section = True # print("XXX: TRACEBACK-START") elif self.traceback_section: matched = self.file_pattern.match(line) if matched: # matched_range = matched.regs[1] filename = matched.groups()[0] new_filename = posixpath_normpath(filename) if new_filename != filename: # print("XXX: %r => %r" % (filename, new_filename)) line = line.replace(filename, new_filename) elif not stripped_line or line[0].isalpha(): # -- DETECTED TRCAEBACK-END: exception-description # print("XXX: TRACEBACK-END") self.traceback_section = False return line class ExceptionWithPathNormalizer(LineProcessor): """Normalize filename path in Exception line (for Windows).""" # http://myregexp.com/examples.html # Windows File Name Regexp # (?i) ^ (?! ^ (PRN | AUX | CLOCK\$ | NUL | CON | COM\d | LPT\d |\..* )(\..+)?$) # [ ^\\\./:\ * \?\"<>\|][^\\/:\*\?\"<>\|]{0,254}$ problematic_path_patterns = [ 'ConfigError: No steps directory in "(?P.*)"', 'ParserError: Failed to parse "(?P.*)"', "Error: [Errno 2] No such file or directory: '(?P.*)'", ] def __init__(self, pattern, marker_text=None): super(ExceptionWithPathNormalizer, self).__init__(marker_text) self.pattern = re.compile(pattern, re.UNICODE) self.marker = marker_text def __call__(self, line): matched = self.pattern.search(line) if matched: # -- ONLY: One pattern per line should match. filename = matched.groupdict()["path"] new_filename = posixpath_normpath(filename) if new_filename != filename: line = line.replace(filename, new_filename) return line # ----------------------------------------------------------------------------- # COMMAND OUTPUT PROCESSORS: # ----------------------------------------------------------------------------- class CommandPostProcessor(object): """Syntactic sugar to mark a command post-processor.""" class CommandOutputProcessor(CommandPostProcessor): """Abstract base class functionality for a CommandPostProcessor that post-processes the output of a command. """ enabled = True output_parts = ("stderr", "stdout") def __init__(self, enabled=None, output_parts=None): if enabled is None: # -- AUTO-DETECT: Enabled on Windows platform enabled = self.__class__.enabled if output_parts is None: output_parts = self.__class__.output_parts self.enabled = enabled self.output_parts = output_parts def matches_output(self, text): """Abstract method that should be overwritten.""" # pylint: disable=no-self-use, unused-argument return False def process_output(self, text): # pylint: disable=no-self-use """Abstract method that should be overwritten.""" changed = False return changed, text def __call__(self, command_result): """Core functionality of command output processor. :param command_result: As value object w/ command execution details. :return: Command result """ if not self.enabled: return command_result changes = 0 for output_name in self.output_parts: output = getattr(command_result, output_name) if output and self.matches_output(output): changed, new_output = self.process_output(output) if changed: changes += 1 setattr(command_result, output_name, new_output) if changes: # -- RESET: Composite output # pylint: disable=protected-access command_result._output = None return command_result class LineCommandOutputProcessor(CommandOutputProcessor): """Provides functionality to process text in line-oriented way by using a number of line processors. The line processors perform the actual work for transforming/normalizing the text. """ enabled = True line_processors = [TracebackLineNormalizer()] def __init__(self, line_processors=None): if line_processors is None: line_processors = self.__class__.line_processors super(LineCommandOutputProcessor, self).__init__(self.enabled) self.line_processors = line_processors self.markers = [p.marker for p in self.line_processors if p.marker] def matches_output(self, text): """Indicates it text contains sections of interest. :param text: Text to inspect (as string). :return: True, if text contains Traceback sections. False, otherwise. """ if self.markers: for marker in self.markers: if marker in text: return True # -- OTHERWISE: return False def process_output(self, text): """Normalizes multi-line text by applying the line processors. :param text: Text to process (as string). :return: Tuple (changed : bool, new_text : string) """ new_lines = [] changed = False for line_processor in self.line_processors: line_processor.reset() for line in text.splitlines(): # -- LINE PROCESSING PIPELINE: original_line = line for line_processor in self.line_processors: line = line_processor(line) if line != original_line: changed = True new_lines.append(line) if changed: text = "\n".join(new_lines) + "\n" return changed, text class TextProcessor(CommandOutputProcessor): """Provides an adapter that uses an :class:`CommandOutputProcessor` as text processor (normalizer). """ def __init__(self, command_output_processor): self.command_output_processor = command_output_processor self.enabled = self.command_output_processor.enabled self.output_parts = self.command_output_processor.output_parts def process_output(self, text): return self.command_output_processor.process_output(text) def __call__(self, command_result): if isinstance(command_result, string_types): text = command_result return self.command_output_processor.process_output(text)[1] else: return self.command_output_processor(command_result) class BehaveWinCommandOutputProcessor(LineCommandOutputProcessor): """Command output post-processor for :mod:`behave` on Windows platform. Mostly, normalizes windows paths in output and exceptions to conform to POSIX path conventions. """ enabled = sys.platform.startswith("win") or True line_processors = [ TracebackLineNormalizer(), ExceptionWithPathNormalizer( "ConfigError: No steps directory in '(?P.*)'", "ConfigError: No steps directory in"), ExceptionWithPathNormalizer( 'ParserError: Failed to parse "(?P.*)"', "ParserError: Failed to parse"), ExceptionWithPathNormalizer( "No such file or directory: '(?P.*)'", "[Errno 2] No such file or directory:"), # IOError ExceptionWithPathNormalizer( '^\s*File "(?P.*)", line \d+, in ', 'File "'), ] behave-1.2.6/behave4cmd0/command_steps.py0000644000076600000240000004754013244555737020356 0ustar jensstaff00000000000000# -*- coding -*- """ Provides step definitions to: * run commands, like behave * create textual files within a working directory TODO: matcher that ignores empty lines and whitespace and has contains comparison """ from __future__ import absolute_import, print_function from behave import given, when, then, step, matchers from behave4cmd0 import command_shell, command_util, pathutil, textutil from behave4cmd0.pathutil import posixpath_normpath from behave4cmd0.command_shell_proc import \ TextProcessor, BehaveWinCommandOutputProcessor import contextlib import difflib import os import shutil from hamcrest import assert_that, equal_to, is_not, contains_string # ----------------------------------------------------------------------------- # INIT: # ----------------------------------------------------------------------------- matchers.register_type(int=int) DEBUG = False file_contents_normalizer = None if BehaveWinCommandOutputProcessor.enabled: file_contents_normalizer = TextProcessor(BehaveWinCommandOutputProcessor()) # ----------------------------------------------------------------------------- # UTILITIES: # ----------------------------------------------------------------------------- @contextlib.contextmanager def on_assert_failed_print_details(actual, expected): """ Print text details in case of assertation failed errors. .. sourcecode:: python with on_assert_failed_print_details(actual_text, expected_text): assert actual == expected """ try: yield except AssertionError: # diff = difflib.unified_diff(expected.splitlines(), actual.splitlines(), # "expected", "actual") diff = difflib.ndiff(expected.splitlines(), actual.splitlines()) diff_text = u"\n".join(diff) print(u"DIFF (+ ACTUAL, - EXPECTED):\n{0}\n".format(diff_text)) if DEBUG: print(u"expected:\n{0}\n".format(expected)) print(u"actual:\n{0}\n".format(actual)) raise @contextlib.contextmanager def on_error_print_details(actual, expected): """ Print text details in case of assertation failed errors. .. sourcecode:: python with on_error_print_details(actual_text, expected_text): ... # Do something """ try: yield except Exception: diff = difflib.ndiff(expected.splitlines(), actual.splitlines()) diff_text = u"\n".join(diff) print(u"DIFF (+ ACTUAL, - EXPECTED):\n{0}\n".format(diff_text)) if DEBUG: print(u"expected:\n{0}\n".format(expected)) print(u"actual:\n{0}".format(actual)) raise # ----------------------------------------------------------------------------- # STEPS: WORKING DIR # ----------------------------------------------------------------------------- @given(u'a new working directory') def step_a_new_working_directory(context): """Creates a new, empty working directory.""" command_util.ensure_context_attribute_exists(context, "workdir", None) # MAYBE: command_util.ensure_workdir_not_exists(context) command_util.ensure_workdir_exists(context) # OOPS: shutil.rmtree(context.workdir, ignore_errors=True) command_util.ensure_workdir_exists(context) @given(u'I use the current directory as working directory') def step_use_curdir_as_working_directory(context): """ Uses the current directory as working directory """ context.workdir = os.path.abspath(".") command_util.ensure_workdir_exists(context) # ----------------------------------------------------------------------------- # STEPS: Create files with contents # ----------------------------------------------------------------------------- @given(u'a file named "{filename}" and encoding="{encoding}" with') def step_a_file_named_filename_and_encoding_with(context, filename, encoding): """Creates a textual file with the content provided as docstring.""" __encoding_is_valid = True assert context.text is not None, "ENSURE: multiline text is provided." assert not os.path.isabs(filename) assert __encoding_is_valid command_util.ensure_workdir_exists(context) filename2 = os.path.join(context.workdir, filename) pathutil.create_textfile_with_contents(filename2, context.text, encoding) @given(u'a file named "{filename}" with') def step_a_file_named_filename_with(context, filename): """Creates a textual file with the content provided as docstring.""" step_a_file_named_filename_and_encoding_with(context, filename, "UTF-8") # -- SPECIAL CASE: For usage with behave steps. if filename.endswith(".feature"): command_util.ensure_context_attribute_exists(context, "features", []) context.features.append(filename) @given(u'an empty file named "{filename}"') def step_an_empty_file_named_filename(context, filename): """ Creates an empty file. """ assert not os.path.isabs(filename) command_util.ensure_workdir_exists(context) filename2 = os.path.join(context.workdir, filename) pathutil.create_textfile_with_contents(filename2, "") # ----------------------------------------------------------------------------- # STEPS: Run commands # ----------------------------------------------------------------------------- @when(u'I run "{command}"') @when(u'I run `{command}`') def step_i_run_command(context, command): """ Run a command as subprocess, collect its output and returncode. """ command_util.ensure_workdir_exists(context) context.command_result = command_shell.run(command, cwd=context.workdir) command_util.workdir_save_coverage_files(context.workdir) if False and DEBUG: print(u"run_command: {0}".format(command)) print(u"run_command.output {0}".format(context.command_result.output)) @when(u'I successfully run "{command}"') @when(u'I successfully run `{command}`') def step_i_successfully_run_command(context, command): step_i_run_command(context, command) step_it_should_pass(context) @then(u'it should fail with result "{result:int}"') def step_it_should_fail_with_result(context, result): assert_that(context.command_result.returncode, equal_to(result)) assert_that(result, is_not(equal_to(0))) @then(u'the command should fail with returncode="{result:int}"') def step_it_should_fail_with_returncode(context, result): assert_that(context.command_result.returncode, equal_to(result)) assert_that(result, is_not(equal_to(0))) @then(u'the command returncode is "{result:int}"') def step_the_command_returncode_is(context, result): assert_that(context.command_result.returncode, equal_to(result)) @then(u'the command returncode is non-zero') def step_the_command_returncode_is_nonzero(context): assert_that(context.command_result.returncode, is_not(equal_to(0))) @then(u'it should pass') def step_it_should_pass(context): assert_that(context.command_result.returncode, equal_to(0), context.command_result.output) @then(u'it should fail') def step_it_should_fail(context): assert_that(context.command_result.returncode, is_not(equal_to(0)), context.command_result.output) @then(u'it should pass with') def step_it_should_pass_with(context): ''' EXAMPLE: ... when I run "behave ..." then it should pass with: """ TEXT """ ''' assert context.text is not None, "ENSURE: multiline text is provided." step_command_output_should_contain(context) assert_that(context.command_result.returncode, equal_to(0), context.command_result.output) @then(u'it should fail with') def step_it_should_fail_with(context): ''' EXAMPLE: ... when I run "behave ..." then it should fail with: """ TEXT """ ''' assert context.text is not None, "ENSURE: multiline text is provided." step_command_output_should_contain(context) assert_that(context.command_result.returncode, is_not(equal_to(0))) # ----------------------------------------------------------------------------- # STEPS FOR: Output Comparison # ----------------------------------------------------------------------------- @then(u'the command output should contain "{text}"') def step_command_output_should_contain_text(context, text): ''' EXAMPLE: ... Then the command output should contain "TEXT" ''' expected_text = text if "{__WORKDIR__}" in expected_text or "{__CWD__}" in expected_text: expected_text = textutil.template_substitute(text, __WORKDIR__ = posixpath_normpath(context.workdir), __CWD__ = posixpath_normpath(os.getcwd()) ) actual_output = context.command_result.output with on_assert_failed_print_details(actual_output, expected_text): textutil.assert_normtext_should_contain(actual_output, expected_text) @then(u'the command output should not contain "{text}"') def step_command_output_should_not_contain_text(context, text): ''' EXAMPLE: ... then the command output should not contain "TEXT" ''' expected_text = text if "{__WORKDIR__}" in text or "{__CWD__}" in text: expected_text = textutil.template_substitute(text, __WORKDIR__ = posixpath_normpath(context.workdir), __CWD__ = posixpath_normpath(os.getcwd()) ) actual_output = context.command_result.output with on_assert_failed_print_details(actual_output, expected_text): textutil.assert_normtext_should_not_contain(actual_output, expected_text) @then(u'the command output should contain "{text}" {count:d} times') def step_command_output_should_contain_text_multiple_times(context, text, count): ''' EXAMPLE: ... Then the command output should contain "TEXT" 3 times ''' assert count >= 0 expected_text = text if "{__WORKDIR__}" in expected_text or "{__CWD__}" in expected_text: expected_text = textutil.template_substitute(text, __WORKDIR__ = posixpath_normpath(context.workdir), __CWD__ = posixpath_normpath(os.getcwd()) ) actual_output = context.command_result.output with on_assert_failed_print_details(actual_output, expected_text): textutil.assert_normtext_should_contain_multiple_times(actual_output, expected_text, count) @then(u'the command output should contain exactly "{text}"') def step_command_output_should_contain_exactly_text(context, text): """ Verifies that the command output of the last command contains the expected text. .. code-block:: gherkin When I run "echo Hello" Then the command output should contain "Hello" """ expected_text = text if "{__WORKDIR__}" in text or "{__CWD__}" in text: expected_text = textutil.template_substitute(text, __WORKDIR__ = posixpath_normpath(context.workdir), __CWD__ = posixpath_normpath(os.getcwd()) ) actual_output = context.command_result.output textutil.assert_text_should_contain_exactly(actual_output, expected_text) @then(u'the command output should not contain exactly "{text}"') def step_command_output_should_not_contain_exactly_text(context, text): expected_text = text if "{__WORKDIR__}" in text or "{__CWD__}" in text: expected_text = textutil.template_substitute(text, __WORKDIR__ = posixpath_normpath(context.workdir), __CWD__ = posixpath_normpath(os.getcwd()) ) actual_output = context.command_result.output textutil.assert_text_should_not_contain_exactly(actual_output, expected_text) @then(u'the command output should contain') def step_command_output_should_contain(context): ''' EXAMPLE: ... when I run "behave ..." then it should pass and the command output should contain: """ TEXT """ ''' assert context.text is not None, "REQUIRE: multi-line text" step_command_output_should_contain_text(context, context.text) @then(u'the command output should not contain') def step_command_output_should_not_contain(context): ''' EXAMPLE: ... when I run "behave ..." then it should pass and the command output should not contain: """ TEXT """ ''' assert context.text is not None, "REQUIRE: multi-line text" step_command_output_should_not_contain_text(context, context.text.strip()) @then(u'the command output should contain {count:d} times') def step_command_output_should_contain_multiple_times(context, count): ''' EXAMPLE: ... when I run "behave ..." then it should pass and the command output should contain 2 times: """ TEXT """ ''' assert context.text is not None, "REQUIRE: multi-line text" step_command_output_should_contain_text_multiple_times(context, context.text, count) @then(u'the command output should contain exactly') def step_command_output_should_contain_exactly_with_multiline_text(context): assert context.text is not None, "REQUIRE: multi-line text" step_command_output_should_contain_exactly_text(context, context.text) @then(u'the command output should not contain exactly') def step_command_output_should_contain_not_exactly_with_multiline_text(context): assert context.text is not None, "REQUIRE: multi-line text" step_command_output_should_not_contain_exactly_text(context, context.text) # ----------------------------------------------------------------------------- # STEPS FOR: Directories # ----------------------------------------------------------------------------- @step(u'I remove the directory "{directory}"') def step_remove_directory(context, directory): path_ = directory if not os.path.isabs(directory): path_ = os.path.join(context.workdir, os.path.normpath(directory)) if os.path.isdir(path_): shutil.rmtree(path_, ignore_errors=True) assert_that(not os.path.isdir(path_)) @given(u'I ensure that the directory "{directory}" does not exist') def step_given_the_directory_should_not_exist(context, directory): step_remove_directory(context, directory) @given(u'a directory named "{path}"') def step_directory_named_dirname(context, path): assert context.workdir, "REQUIRE: context.workdir" path_ = os.path.join(context.workdir, os.path.normpath(path)) if not os.path.exists(path_): os.makedirs(path_) assert os.path.isdir(path_) @then(u'the directory "{directory}" should exist') def step_the_directory_should_exist(context, directory): path_ = directory if not os.path.isabs(directory): path_ = os.path.join(context.workdir, os.path.normpath(directory)) assert_that(os.path.isdir(path_)) @then(u'the directory "{directory}" should not exist') def step_the_directory_should_not_exist(context, directory): path_ = directory if not os.path.isabs(directory): path_ = os.path.join(context.workdir, os.path.normpath(directory)) assert_that(not os.path.isdir(path_)) @step(u'the directory "{directory}" exists') def step_directory_exists(context, directory): """ Verifies that a directory exists. .. code-block:: gherkin Given the directory "abc.txt" exists When the directory "abc.txt" exists """ step_the_directory_should_exist(context, directory) @step(u'the directory "{directory}" does not exist') def step_directory_named_does_not_exist(context, directory): """ Verifies that a directory does not exist. .. code-block:: gherkin Given the directory "abc/" does not exist When the directory "abc/" does not exist """ step_the_directory_should_not_exist(context, directory) # ----------------------------------------------------------------------------- # FILE STEPS: # ----------------------------------------------------------------------------- @step(u'a file named "{filename}" exists') def step_file_named_filename_exists(context, filename): """ Verifies that a file with this filename exists. .. code-block:: gherkin Given a file named "abc.txt" exists When a file named "abc.txt" exists """ step_file_named_filename_should_exist(context, filename) @step(u'a file named "{filename}" does not exist') def step_file_named_filename_does_not_exist(context, filename): """ Verifies that a file with this filename does not exist. .. code-block:: gherkin Given a file named "abc.txt" does not exist When a file named "abc.txt" does not exist """ step_file_named_filename_should_not_exist(context, filename) @then(u'a file named "{filename}" should exist') def step_file_named_filename_should_exist(context, filename): command_util.ensure_workdir_exists(context) filename_ = pathutil.realpath_with_context(filename, context) assert_that(os.path.exists(filename_) and os.path.isfile(filename_)) @then(u'a file named "{filename}" should not exist') def step_file_named_filename_should_not_exist(context, filename): command_util.ensure_workdir_exists(context) filename_ = pathutil.realpath_with_context(filename, context) assert_that(not os.path.exists(filename_)) # ----------------------------------------------------------------------------- # STEPS FOR FILE CONTENTS: # ----------------------------------------------------------------------------- @then(u'the file "{filename}" should contain "{text}"') def step_file_should_contain_text(context, filename, text): expected_text = text if "{__WORKDIR__}" in text or "{__CWD__}" in text: expected_text = textutil.template_substitute(text, __WORKDIR__ = posixpath_normpath(context.workdir), __CWD__ = posixpath_normpath(os.getcwd()) ) file_contents = pathutil.read_file_contents(filename, context=context) file_contents = file_contents.rstrip() if file_contents_normalizer: # -- HACK: Inject TextProcessor as text normalizer file_contents = file_contents_normalizer(file_contents) with on_assert_failed_print_details(file_contents, expected_text): textutil.assert_normtext_should_contain(file_contents, expected_text) @then(u'the file "{filename}" should not contain "{text}"') def step_file_should_not_contain_text(context, filename, text): file_contents = pathutil.read_file_contents(filename, context=context) file_contents = file_contents.rstrip() textutil.assert_normtext_should_not_contain(file_contents, text) # XXX assert_that(file_contents, is_not(contains_string(text))) @then(u'the file "{filename}" should contain') def step_file_should_contain_multiline_text(context, filename): assert context.text is not None, "REQUIRE: multiline text" step_file_should_contain_text(context, filename, context.text) @then(u'the file "{filename}" should not contain') def step_file_should_not_contain_multiline_text(context, filename): assert context.text is not None, "REQUIRE: multiline text" step_file_should_not_contain_text(context, filename, context.text) # ----------------------------------------------------------------------------- # ENVIRONMENT VARIABLES # ----------------------------------------------------------------------------- @step(u'I set the environment variable "{env_name}" to "{env_value}"') def step_I_set_the_environment_variable_to(context, env_name, env_value): if not hasattr(context, "environ"): context.environ = {} context.environ[env_name] = env_value os.environ[env_name] = env_value @step(u'I remove the environment variable "{env_name}"') def step_I_remove_the_environment_variable(context, env_name): if not hasattr(context, "environ"): context.environ = {} context.environ[env_name] = "" os.environ[env_name] = "" del context.environ[env_name] del os.environ[env_name] behave-1.2.6/behave4cmd0/command_util.py0000644000076600000240000001210413244555737020161 0ustar jensstaff00000000000000# -*- coding -*- """ Provides some command utility functions. TODO: matcher that ignores empty lines and whitespace and has contains comparison """ from __future__ import absolute_import, print_function from behave4cmd0 import pathutil from behave4cmd0.__setup import TOP, TOPA import os.path import sys import shutil import time import tempfile from fnmatch import fnmatch # ----------------------------------------------------------------------------- # CONSTANTS: # ----------------------------------------------------------------------------- # HERE = os.path.dirname(__file__) # TOP = os.path.join(HERE, "..") # TOPA = os.path.abspath(TOP) WORKDIR = os.path.join(TOP, "__WORKDIR__") # ----------------------------------------------------------------------------- # UTILITY FUNCTIONS: # ----------------------------------------------------------------------------- def workdir_save_coverage_files(workdir, destdir=None): assert os.path.isdir(workdir) if not destdir: destdir = TOPA if os.path.abspath(workdir) == os.path.abspath(destdir): return # -- SKIP: Source directory is destination directory (SAME). for fname in os.listdir(workdir): if fnmatch(fname, ".coverage.*"): # -- MOVE COVERAGE FILES: sourcename = os.path.join(workdir, fname) shutil.move(sourcename, destdir) # def ensure_directory_exists(dirname): # """ # Ensures that a directory exits. # If it does not exist, it is automatically created. # """ # if not os.path.exists(dirname): # os.makedirs(dirname) # assert os.path.exists(dirname) # assert os.path.isdir(dirname) def ensure_context_attribute_exists(context, name, default_value=None): """ Ensure a behave resource exists as attribute in the behave context. If this is not the case, the attribute is created by using the default_value. """ if not hasattr(context, name): setattr(context, name, default_value) def ensure_workdir_exists(context): """ Ensures that the work directory exists. In addition, the location of the workdir is stored as attribute in the context object. """ ensure_context_attribute_exists(context, "workdir", None) if not context.workdir: context.workdir = os.path.abspath(WORKDIR) pathutil.ensure_directory_exists(context.workdir) def ensure_workdir_not_exists(context): """Ensures that the work directory does not exist.""" ensure_context_attribute_exists(context, "workdir", None) if context.workdir: orig_dirname = real_dirname = context.workdir context.workdir = None if os.path.exists(real_dirname): renamed_dirname = tempfile.mktemp(prefix=os.path.basename(real_dirname), suffix="_DEAD", dir=os.path.dirname(real_dirname) or ".") os.rename(real_dirname, renamed_dirname) real_dirname = renamed_dirname max_iterations = 2 if sys.platform.startswith("win"): max_iterations = 15 for iteration in range(max_iterations): if not os.path.exists(real_dirname): if iteration > 1: print("REMOVE-WORKDIR after %s iterations" % (iteration+1)) break shutil.rmtree(real_dirname, ignore_errors=True) time.sleep(0.5) assert not os.path.isdir(real_dirname), "ENSURE not-isa dir: %s" % real_dirname assert not os.path.exists(real_dirname), "ENSURE dir not-exists: %s" % real_dirname assert not os.path.isdir(orig_dirname), "ENSURE not-isa dir: %s" % orig_dirname # def create_textfile_with_contents(filename, contents): # """ # Creates a textual file with the provided contents in the workdir. # Overwrites an existing file. # """ # ensure_directory_exists(os.path.dirname(filename)) # if os.path.exists(filename): # os.remove(filename) # outstream = open(filename, "w") # outstream.write(contents) # if not contents.endswith("\n"): # outstream.write("\n") # outstream.flush() # outstream.close() # assert os.path.exists(filename) # def text_remove_empty_lines(text): # """ # Whitespace normalization: # - Strip empty lines # - Strip trailing whitespace # """ # lines = [ line.rstrip() for line in text.splitlines() if line.strip() ] # return "\n".join(lines) # # def text_normalize(text): # """ # Whitespace normalization: # - Strip empty lines # - Strip leading whitespace in a line # - Strip trailing whitespace in a line # - Normalize line endings # """ # lines = [ line.strip() for line in text.splitlines() if line.strip() ] # return "\n".join(lines) # def posixpath_normpath(pathname): # """ # Convert path into POSIX path: # - Normalize path # - Replace backslash with slash # """ # backslash = '\\' # pathname = os.path.normpath(pathname) # if backslash in pathname: # pathname = pathname.replace(backslash, '/') # return pathname behave-1.2.6/behave4cmd0/failing_steps.py0000644000076600000240000000301513244555737020336 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Generic failing steps. Often needed in examples. EXAMPLES: Given a step fails When another step fails Then a step fails Given ... When ... Then it should fail because "the person is unknown". """ from __future__ import absolute_import from behave import step, then # ----------------------------------------------------------------------------- # STEPS FOR: failing # ----------------------------------------------------------------------------- @step(u'{word:w} step fails') def step_fails(context, word): """Step that always fails, mostly needed in examples.""" assert False, "EXPECT: Failing step" @step(u'{word:w} step fails with "{message}"') def step_fails_with_message(context, word, message): """Step that always fails, mostly needed in examples.""" assert False, "FAILED: %s" % message @step(u'{word:w} step fails with') def step_fails_with_text(context, word): """Step that always fails, mostly needed in examples.""" assert context.text is not None, "REQUIRE: text" step_fails_with_message(context, word, context.text) @then(u'it should fail because "{reason}"') def then_it_should_fail_because(context, reason): """Self documenting step that indicates why this step should fail.""" assert False, "FAILED: %s" % reason # @step(u'an error should fail because "{reason}"') # def then_it_should_fail_because(context, reason): # """ # Self documenting step that indicates why this step should fail. # """ # assert False, reason behave-1.2.6/behave4cmd0/log/0000755000076600000240000000000013244564037015710 5ustar jensstaff00000000000000behave-1.2.6/behave4cmd0/log/__init__.py0000644000076600000240000000002413244555737020024 0ustar jensstaff00000000000000__author__ = 'jens' behave-1.2.6/behave4cmd0/log/steps.py0000644000076600000240000003533413244555737017437 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Provides step definitions to perform tests with the Python logging subsystem. .. code-block: gherkin Given I create log records with: | category | level | message | | foo.bar | WARN | Hello LogRecord | | bar | CURRENT | Hello LogRecord | And I create a log record with: | category | level | message | | foo | ERROR | Hello Foo | Then the command output should contain the following log records: | category | level | message | | bar | CURRENT | xxx | Then the command output should not contain the following log records: | category | level | message | | bar | CURRENT | xxx | Then the file "behave.log" should contain the log records: | category | level | message | | bar | CURRENT | xxx | Then the file "behave.log" should not contain the log records: | category | level | message | | bar | CURRENT | xxx | Given I define the log record schema: | category | level | message | | root | INFO | Hello LogRecord | And I create log records with: | category | level | message | | foo.bar | INFO | Hello LogRecord | | bar | INFO | Hello LogRecord | Then the command output should contain log records from categories | category | | foo.bar | | bar | Given I use the log record configuration: | property | value | | format | LOG.%(levelname)-8s %(name)s %(message)s | | datefmt | | IDEA: .. code-block:: gherkin Given I capture log records When I create log records with: | category | level | message | | foo.bar | WARN | Hello LogRecord | Then the captured log should contain the following log records: | category | level | message | | bar | CURRENT | xxx | And the captured log should not contain the following log records: | category | level | message | | bar | CURRENT | xxx | """ from __future__ import absolute_import from behave import given, when, then, step from behave4cmd0.command_steps import \ step_file_should_contain_multiline_text, \ step_file_should_not_contain_multiline_text from behave.configuration import LogLevel from behave.log_capture import LoggingCapture import logging # ----------------------------------------------------------------------------- # STEP UTILS: # ----------------------------------------------------------------------------- def make_log_record(category, level, message): if category in ("root", "__ROOT__"): category = None logger = logging.getLogger(category) logger.log(level, message) def make_log_record_output(category, level, message, format=None, datefmt=None, **kwargs): """ Create the output for a log record, like performed by :mod:`logging` module. :param category: Name of the logger (as string or None). :param level: Log level (as number). :param message: Log message to use. :returns: Log record output (as string) """ if not category or (category == "__ROOT__"): category = "root" levelname = logging.getLevelName(level) record_data = dict(name=category, levelname=levelname, msg=message) record_data.update(kwargs) record = logging.makeLogRecord(record_data) formatter = logging.Formatter(format, datefmt=datefmt) return formatter.format(record) class LogRecordTable(object): @classmethod def make_output_for_row(cls, row, format=None, datefmt=None, **kwargs): category = row.get("category", None) level = LogLevel.parse_type(row.get("level", "INFO")) message = row.get("message", "__UNDEFINED__") return make_log_record_output(category, level, message, format, datefmt, **kwargs) @staticmethod def annotate_with_row_schema(table, row_schema): """ Annotate/extend a table of log-records with additional columns from the log-record schema if columns are missing. :param table: Table w/ log-records (as :class:`behave.model.Table`) :param row_schema: Log-record row schema (as dict). """ for column, value in row_schema.items(): if column not in table.headings: table.add_column(column, default_value=value) # ----------------------------------------------------------------------------- # STEP DEFINITIONS: # ----------------------------------------------------------------------------- # @step('I create log records for the following categories') # def step_I_create_logrecords_for_categories_with_text(context): # assert context.text is not None, "REQUIRE: context.text" # current_level = context.config.logging_level # categories = context.text.split() # for category_name in categories: # logger = logging.getLogger(category_name) # logger.log(current_level, "__LOG_RECORD__") @step('I create log records with') def step_I_create_logrecords_with_table(context): """ Step definition that creates one more log records by using a table. .. code-block: gherkin When I create log records with: | category | level | message | | foo | ERROR | Hello Foo | | foo.bar | WARN | Hello Foo.Bar | Table description ------------------ | Column | Type | Required | Description | | category | string | yes | Category (or logger) to use. | | level | LogLevel | yes | Log level to use. | | message | string | yes | Log message to use. | .. code-block: python import logging from behave.configuration import LogLevel for row in table.rows: logger = logging.getLogger(row.category) level = LogLevel.parse_type(row.level) logger.log(level, row.message) """ assert context.table, "REQUIRE: context.table" context.table.require_columns(["category", "level", "message"]) for row in context.table.rows: category = row["category"] if category == "__ROOT__": category = None level = LogLevel.parse_type(row["level"]) message = row["message"] make_log_record(category, level, message) @step('I create a log record with') def step_I_create_logrecord_with_table(context): """ Create an log record by using a table to provide the parts. .. seealso: :func:`step_I_create_logrecords_with_table()` """ assert context.table, "REQUIRE: context.table" assert len(context.table.rows) == 1, "REQUIRE: table.row.size == 1" step_I_create_logrecords_with_table(context) @step('I define the log record schema') def step_I_define_logrecord_schema_with_table(context): assert context.table, "REQUIRE: context.table" context.table.require_columns(["category", "level", "message"]) assert len(context.table.rows) == 1, \ "REQUIRE: context.table.rows.size(%s) == 1" % (len(context.table.rows)) row = context.table.rows[0] row_schema = dict(category=row["category"], level=row["level"], message=row["message"]) context.log_record_row_schema = row_schema @then('the command output should contain the following log records') def step_command_output_should_contain_log_records(context): """ Verifies that the command output contains the specified log records (in any order). .. code-block: gherkin Then the command output should contain the following log records: | category | level | message | | bar | CURRENT | xxx | """ assert context.table, "REQUIRE: context.table" context.table.require_columns(["category", "level", "message"]) format = getattr(context, "log_record_format", context.config.logging_format) for row in context.table.rows: output = LogRecordTable.make_output_for_row(row, format) context.execute_steps(u''' Then the command output should contain: """ {expected_output} """ '''.format(expected_output=output)) @then('the command output should not contain the following log records') def step_command_output_should_not_contain_log_records(context): """ Verifies that the command output contains the specified log records (in any order). .. code-block: gherkin Then the command output should contain the following log records: | category | level | message | | bar | CURRENT | xxx | """ assert context.table, "REQUIRE: context.table" context.table.require_columns(["category", "level", "message"]) format = getattr(context, "log_record_format", context.config.logging_format) for row in context.table.rows: output = LogRecordTable.make_output_for_row(row, format) context.execute_steps(u''' Then the command output should not contain: """ {expected_output} """ '''.format(expected_output=output)) @then('the command output should contain the following log record') def step_command_output_should_contain_log_record(context): assert context.table, "REQUIRE: context.table" assert len(context.table.rows) == 1, "REQUIRE: table.row.size == 1" step_command_output_should_contain_log_records(context) @then('the command output should not contain the following log record') def step_command_output_should_not_contain_log_record(context): assert context.table, "REQUIRE: context.table" assert len(context.table.rows) == 1, "REQUIRE: table.row.size == 1" step_command_output_should_not_contain_log_records(context) @then('the command output should contain log records from categories') def step_command_output_should_contain_log_records_from_categories(context): """ Verifies that the command output contains the specified log records (in any order). .. code-block: gherkin Given I define a log record schema: | category | level | message | | root | ERROR | __LOG_MESSAGE__ | Then the command output should contain log records from categories: | category | | bar | """ assert context.table, "REQUIRE: context.table" context.table.require_column("category") record_schema = context.log_record_row_schema LogRecordTable.annotate_with_row_schema(context.table, record_schema) step_command_output_should_contain_log_records(context) context.table.remove_columns(["level", "message"]) @then('the command output should not contain log records from categories') def step_command_output_should_not_contain_log_records_from_categories(context): """ Verifies that the command output contains not log records from the provided log categories (in any order). .. code-block: gherkin Given I define the log record schema: | category | level | message | | root | ERROR | __LOG_MESSAGE__ | Then the command output should not contain log records from categories: | category | | bar | """ assert context.table, "REQUIRE: context.table" context.table.require_column("category") record_schema = context.log_record_row_schema LogRecordTable.annotate_with_row_schema(context.table, record_schema) step_command_output_should_not_contain_log_records(context) context.table.remove_columns(["level", "message"]) @then('the file "{filename}" should contain the log records') def step_file_should_contain_log_records(context, filename): """ Verifies that the command output contains the specified log records (in any order). .. code-block: gherkin Then the file "xxx.log" should contain the log records: | category | level | message | | bar | CURRENT | xxx | """ assert context.table, "REQUIRE: context.table" context.table.require_columns(["category", "level", "message"]) format = getattr(context, "log_record_format", context.config.logging_format) for row in context.table.rows: output = LogRecordTable.make_output_for_row(row, format) context.text = output step_file_should_contain_multiline_text(context, filename) @then('the file "{filename}" should not contain the log records') def step_file_should_not_contain_log_records(context, filename): """ Verifies that the command output contains the specified log records (in any order). .. code-block: gherkin Then the file "xxx.log" should not contain the log records: | category | level | message | | bar | CURRENT | xxx | """ assert context.table, "REQUIRE: context.table" context.table.require_columns(["category", "level", "message"]) format = getattr(context, "log_record_format", context.config.logging_format) for row in context.table.rows: output = LogRecordTable.make_output_for_row(row, format) context.text = output step_file_should_not_contain_multiline_text(context, filename) @step('I use "{log_record_format}" as log record format') def step_use_log_record_format_text(context, log_record_format): context.log_record_format = log_record_format @step('I use the log record configuration') def step_use_log_record_configuration(context): """ Define log record configuration parameters. .. code-block: gherkin Given I use the log record configuration: | property | value | | format | | | datefmt | | """ assert context.table, "REQUIRE: context.table" context.table.require_columns(["property", "value"]) for row in context.table.rows: property_name = row["property"] value = row["value"] if property_name == "format": context.log_record_format = value elif property_name == "datefmt": context.log_record_datefmt = value else: raise KeyError("Unknown property=%s" % property_name) # ----------------------------------------------------------------------------- # TODO: STEP DEFINITIONS: # ----------------------------------------------------------------------------- @step('I capture log records with level "{level}" or above') def step_I_capture_logrecords(context, level): raise NotImplementedError() @step('I capture log records') def step_I_capture_logrecords(context): """ .. code-block: gherkin Given I capture log records When I capture log records :param context: """ raise NotImplementedError() logcapture = getattr(context, "logcapture", None) if not logcapture: context.logcapture = LoggingCapture() behave-1.2.6/behave4cmd0/note_steps.py0000644000076600000240000000156413244555737017701 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Step definitions for providing notes/hints. The note steps explain what was important in the last few steps of this scenario (for a test reader). """ from __future__ import absolute_import from behave import step # ----------------------------------------------------------------------------- # STEPS FOR: remarks/comments # ----------------------------------------------------------------------------- @step(u'note that "{remark}"') def step_note_that(context, remark): """ Used as generic step that provides an additional remark/hint and enhance the readability/understanding without performing any check. .. code-block:: gherkin Given that today is "April 1st" But note that "April 1st is Fools day (and beware)" """ log = getattr(context, "log", None) if log: log.info(u"NOTE: %s;" % remark) behave-1.2.6/behave4cmd0/passing_steps.py0000644000076600000240000000146213244555737020375 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Passing steps. Often needed in examples. EXAMPLES: Given a step passes When another step passes Then a step passes Given ... When ... Then it should pass because "the answer is correct". """ from __future__ import absolute_import from behave import step, then # ----------------------------------------------------------------------------- # STEPS FOR: passing # ----------------------------------------------------------------------------- @step('{word:w} step passes') def step_passes(context, word): """ Step that always fails, mostly needed in examples. """ pass @then('it should pass because "{reason}"') def then_it_should_pass_because(context, reason): """ Self documenting step that indicates some reason. """ pass behave-1.2.6/behave4cmd0/pathutil.py0000644000076600000240000001121113244555737017336 0ustar jensstaff00000000000000# -*- coding -*- """ Provides some command utility functions. TODO: matcher that ignores empty lines and whitespace and has contains comparison """ from __future__ import absolute_import, print_function import os.path import sys import codecs # ----------------------------------------------------------------------------- # CONSTANTS: # ----------------------------------------------------------------------------- # HERE, WORKDIR: see "__setup.py" # ----------------------------------------------------------------------------- # UTILITY FUNCTIONS: # ----------------------------------------------------------------------------- def realpath_with_context(path, context): """ Convert a path into its realpath: * For relative path: use :attr:`context.workdir` as root directory * For absolute path: Pass-through without any changes. :param path: Filepath to convert (as string). :param context: Behave context object (with :attr:`context.workdir`) :return: Converted path. """ if not os.path.isabs(path): # XXX ensure_workdir_exists(context) assert context.workdir path = os.path.join(context.workdir, os.path.normpath(path)) return path def posixpath_normpath(pathname): """ Convert path into POSIX path: * Normalize path * Replace backslash with slash :param pathname: Pathname (as string) :return: Normalized POSIX path. """ backslash = '\\' pathname2 = os.path.normpath(pathname) or "." if backslash in pathname2: pathname2 = pathname2.replace(backslash, '/') return pathname2 def ensure_makedirs(directory, max_iterations=3): # -- SPORADIC-ERRORS: WindowsError: [Error 5] Access denied: '...' iteration = 0 exception_text = None for iteration in range(max_iterations): try: os.makedirs(directory) except OSError as e: if iteration >= max_iterations: # XXX-BAD: Never occurs raise else: exception_text = "%s:%s" % (e.__class__.__name__, e) if os.path.isdir(directory): return assert os.path.isdir(directory), \ "FAILED: ensure_makedirs(%r) (after %s iterations):\n%s" % \ (directory, max_iterations, exception_text) def read_file_contents(filename, context=None, encoding=None): filename_ = realpath_with_context(filename, context) assert os.path.exists(filename_) with open(filename_, "r") as file_: file_contents = file_.read() return file_contents # def create_new_workdir(context): # ensure_attribute_exists(context, "workdir", default=WORKDIR) # if os.path.exists(context.workdir): # shutil.rmtree(context.workdir, ignore_errors=True) # ensure_workdir_exists(context) def create_textfile_with_contents(filename, contents, encoding='utf-8'): """ Creates a textual file with the provided contents in the workdir. Overwrites an existing file. """ ensure_directory_exists(os.path.dirname(filename)) if os.path.exists(filename): os.remove(filename) outstream = codecs.open(filename, "w", encoding) outstream.write(contents) if contents and not contents.endswith("\n"): outstream.write("\n") outstream.flush() outstream.close() assert os.path.exists(filename), "ENSURE file exists: %s" % filename def ensure_file_exists(filename, context=None): real_filename = filename if context: real_filename = realpath_with_context(filename, context) if not os.path.exists(real_filename): create_textfile_with_contents(real_filename, "") assert os.path.exists(real_filename), "ENSURE file exists: %s" % filename def ensure_directory_exists(dirname, context=None): """Ensures that a directory exits. If it does not exist, it is automatically created. """ real_dirname = dirname if context: real_dirname = realpath_with_context(dirname, context) if not os.path.exists(real_dirname): mas_iterations = 2 if sys.platform.startswith("win"): mas_iterations = 10 ensure_makedirs(real_dirname, mas_iterations) assert os.path.exists(real_dirname), "ENSURE dir exists: %s" % dirname assert os.path.isdir(real_dirname), "ENSURE isa dir: %s" % dirname # def ensure_workdir_exists(context): # """ # Ensures that the work directory exists. # In addition, the location of the workdir is stored as attribute in # the context object. # """ # ensure_attribute_exists(context, "workdir", default=WORKDIR) # # if not context.workdir: # # context.workdir = os.path.abspath(WORKDIR) # ensure_directory_exists(context.workdir) behave-1.2.6/behave4cmd0/setup_command_shell.py0000755000076600000240000000141413244555737021540 0ustar jensstaff00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Support functionality to simplify the setup for behave tests. .. sourcecode:: python # -- FILE: features/environment.py from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave def before_all(context): setup_command_shell_processors4behave() """ from __future__ import absolute_import from .command_shell import Command from .command_shell_proc import BehaveWinCommandOutputProcessor def setup_command_shell_processors4behave(): Command.POSTPROCESSOR_MAP["behave"] = [] for processor_class in [BehaveWinCommandOutputProcessor]: if processor_class.enabled: processor = processor_class() Command.POSTPROCESSOR_MAP["behave"].append(processor) behave-1.2.6/behave4cmd0/textutil.py0000644000076600000240000002547213244555737017404 0ustar jensstaff00000000000000# -*- coding -*- """ Provides some command utility functions. TODO: matcher that ignores empty lines and whitespace and has contains comparison """ from __future__ import absolute_import, print_function from hamcrest import assert_that, is_not, equal_to, contains_string # DISABLED: from behave4cmd.hamcrest_text import matches_regexp import codecs DEBUG = False # ----------------------------------------------------------------------------- # CLASS: TextProxy # ----------------------------------------------------------------------------- # class TextProxy(object): # """ # Simplifies conversion between (Unicode) string and its byte representation. # Provides a ValueObject to store a string or its byte representation. # Afterwards you can explicitly access both representations by using: # # EXAMPLE: # # .. testcode:: # # from behave4cmd.textutil import TextProxy # message = TextProxy("Hello world", encoding="UTF-8") # assert message.data == "Hello world" # -- RAW DATA access. # assert isinstance(message.text, basestring) # assert isinstance(message.bytes, bytes) # assert message == "Hello world" # assert len(message) == len(message.data) == 11 # """ # encoding_errors = "strict" # default_encoding = "UTF-8" # # def __init__(self, data=None, encoding=None, errors=None): # self.encoding = encoding or self.default_encoding # self.errors = errors or self.encoding_errors # self.set(data) # # def get(self): # return self.data # # def set(self, data): # self.data = data or "" # self._text = None # self._bytes = None # # def clear(self): # self.set(None) # # @property # def text(self): # """Provide access to string-representation of the data.""" # if self._text is None: # if isinstance(self.data, basestring): # _text = self.data # elif isinstance(self.data, bytes): # _text = codecs.decode(self.data, self.encoding, self.errors) # else: # _text = str(self.data) # self._text = _text # assert isinstance(self._text, basestring) # return self._text # # @property # def bytes(self): # """Provide access to byte-representation of the data.""" # if self._bytes is None: # if isinstance(self.data, bytes) and not isinstance(self.data, str): # self._bytes = self.data # else: # text = self.data # if not isinstance(text, basestring): # text = unicode(text) # assert isinstance(text, basestring) # self._bytes = codecs.encode(text, self.encoding, self.errors) # assert isinstance(self._bytes, bytes) # return self._bytes # # def __repr__(self): # """Textual representation of this object.""" # data = self.data or "" # prefix = "" # if isinstance(data, bytes) and not isinstance(data, basestring): # prefix= u"b" # # str(self.text) # # str(self.encoding) # # str(prefix) # # _ = u"" % len(self) # # _ = u"" % prefix # # _ = u"" % self.text # # _ = u"" % self.encoding # return u"" %\ # (len(self), prefix, self.text, self.encoding) # # def __str__(self): # """Conversion into str() object.""" # return self.text # # def __bytes__(self): # """Conversion into bytes() object.""" # return self.bytes # # def __bool__(self): # """Conversion into a bool value, used for truth testing.""" # return bool(self.data) # # def __iter__(self): # """Conversion into an iterator.""" # return iter(self.data) # # def __contains__(self, item): # """Check if item is contained in raw data.""" # if isinstance(item, basestring): # return item in self.text # elif isinstance(item, bytes): # return item in self.bytes # else: # return item in self.data # # def __len__(self): # if self.data is None: # return 0 # return len(self.data) # # def __nonzero__(self): # return len(self) > 0 # # def __eq__(self, other): # if isinstance(other, basestring): # return self.text == other # elif isinstance(other, bytes): # return self.bytes == other # else: # return self.data == other # # def __ne__(self, other): # return not (self == other) # # ----------------------------------------------------------------------------- # UTILITY FUNCTIONS: # ----------------------------------------------------------------------------- def template_substitute(text, **kwargs): """ Replace placeholders in text by using the data mapping. Other placeholders that is not represented by data is left untouched. :param text: Text to search and replace placeholders. :param data: Data mapping/dict for placeholder key and values. :return: Potentially modified text with replaced placeholders. """ for name, value in kwargs.items(): placeholder_pattern = "{%s}" % name if placeholder_pattern in text: text = text.replace(placeholder_pattern, value) return text def text_remove_empty_lines(text): """ Whitespace normalization: - Strip empty lines - Strip trailing whitespace """ lines = [ line.rstrip() for line in text.splitlines() if line.strip() ] return "\n".join(lines) def text_normalize(text): """ Whitespace normalization: - Strip empty lines - Strip leading whitespace in a line - Strip trailing whitespace in a line - Normalize line endings """ # if not isinstance(text, str): if isinstance(text, bytes): # -- MAYBE: command.ouput => bytes, encoded stream output. text = codecs.decode(text) lines = [ line.strip() for line in text.splitlines() if line.strip() ] return "\n".join(lines) # ----------------------------------------------------------------------------- # ASSERTIONS: # ----------------------------------------------------------------------------- from hamcrest.library.text.substringmatcher import SubstringMatcher from hamcrest.core.helpers.hasmethod import hasmethod class StringContainsMultipleTimes(SubstringMatcher): def __init__(self, substring, expected_count): super(StringContainsMultipleTimes, self).__init__(substring) self.expected_count = expected_count self.actual_count = 0 def describe_to(self, description): description.append_text('a string ') \ .append_text(self.relationship()) \ .append_text(" ") \ .append_description_of(self.substring) \ .append_text(" ") \ .append_description_of(self.expected_count) \ .append_text(" times instead of ") \ .append_description_of(self.actual_count) def _matches(self, item): if not hasmethod(item, "count"): return False self.actual_count = item.count(self.substring) return self.actual_count == self.expected_count def relationship(self): return "containing" def contains_substring_multiple_times(substring, expected_count): return StringContainsMultipleTimes(substring, expected_count) # ----------------------------------------------------------------------------- # ASSERTIONS: # ----------------------------------------------------------------------------- def assert_text_should_equal(actual_text, expected_text): assert_that(actual_text, equal_to(expected_text)) def assert_text_should_not_equal(actual_text, expected_text): assert_that(actual_text, is_not(equal_to(expected_text))) def assert_text_should_contain_exactly(text, expected_part): assert_that(text, contains_string(expected_part)) def assert_text_should_not_contain_exactly(text, expected_part): assert_that(text, is_not(contains_string(expected_part))) def assert_text_should_contain(text, expected_part): assert_that(text, contains_string(expected_part)) def assert_normtext_should_contain_multiple_times(text, expected_text, count): assert_that(text, contains_substring_multiple_times(expected_text, count)) def assert_text_should_not_contain(text, unexpected_part): assert_that(text, is_not(contains_string(unexpected_part))) def assert_normtext_should_equal(actual_text, expected_text): expected_text2 = text_normalize(expected_text.strip()) actual_text2 = text_normalize(actual_text.strip()) assert_that(actual_text2, equal_to(expected_text2)) def assert_normtext_should_not_equal(actual_text, expected_text): expected_text2 = text_normalize(expected_text.strip()) actual_text2 = text_normalize(actual_text.strip()) assert_that(actual_text2, is_not(equal_to(expected_text2))) def assert_normtext_should_contain(text, expected_part): expected_part2 = text_normalize(expected_part) actual_text = text_normalize(text.strip()) if DEBUG: print("expected:\n{0}".format(expected_part2)) print("actual:\n{0}".format(actual_text)) assert_text_should_contain(actual_text, expected_part2) def assert_normtext_should_not_contain(text, unexpected_part): unexpected_part2 = text_normalize(unexpected_part) actual_text = text_normalize(text.strip()) if DEBUG: print("expected:\n{0}".format(unexpected_part2)) print("actual:\n{0}".format(actual_text)) assert_text_should_not_contain(actual_text, unexpected_part2) # def assert_text_should_match_pattern(text, pattern): # """ # Assert that the :attr:`text` matches the regular expression :attr:`pattern`. # # :param text: Multi-line text (as string). # :param pattern: Regular expression pattern (as string, compiled regexp). # :raise: AssertionError, if text matches not the pattern. # """ # assert_that(text, matches_regexp(pattern)) # # def assert_text_should_not_match_pattern(text, pattern): # """ # Assert that the :attr:`text` matches not the regular expression # :attr:`pattern`. # # :param text: Multi-line text (as string). # :param pattern: Regular expression pattern (as string, compiled regexp). # :raise: AssertionError, if text matches the pattern. # """ # assert_that(text, is_not(matches_regexp(pattern))) # # ----------------------------------------------------------------------------- # MAIN: # ----------------------------------------------------------------------------- if __name__ == "__main__": import doctest doctest.testmod() behave-1.2.6/bin/0000755000076600000240000000000013244564037013615 5ustar jensstaff00000000000000behave-1.2.6/bin/behave0000755000076600000240000000277713244555737015021 0ustar jensstaff00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import absolute_import # -- ENSURE: Use local path during development. import sys import os.path # ---------------------------------------------------------------------------- # SETUP PATHS: # ---------------------------------------------------------------------------- NAME = "behave" HERE = os.path.dirname(__file__) TOP = os.path.join(HERE, "..") if os.path.isdir(os.path.join(TOP, NAME)): sys.path.insert(0, os.path.abspath(TOP)) # ---------------------------------------------------------------------------- # BEHAVE-TWEAKS: # ---------------------------------------------------------------------------- def setup_behave(): """ Apply tweaks, extensions and patches to "behave". """ from behave.configuration import Configuration # -- DISABLE: Timings to simplify issue.features/ tests. # Configuration.defaults["format0"] = "pretty" # Configuration.defaults["format0"] = "progress" Configuration.defaults["show_timings"] = False def behave_main0(): # from behave.configuration import Configuration from behave.__main__ import main as behave_main setup_behave() return behave_main() # ---------------------------------------------------------------------------- # MAIN: # ---------------------------------------------------------------------------- if __name__ == "__main__": if "COVERAGE_PROCESS_START" in os.environ: import coverage coverage.process_startup() sys.exit(behave_main0()) behave-1.2.6/bin/behave.cmd0000644000076600000240000000042013244555737015537 0ustar jensstaff00000000000000@echo off REM ========================================================================== REM BEHAVE REM ========================================================================== setlocal set HERE=%~dp0 if not defined PYTHON set PYTHON=python %PYTHON% %HERE%behave %* behave-1.2.6/bin/behave.junit_filter.py0000755000076600000240000000545213244555737020136 0ustar jensstaff00000000000000#!/usr/bin/env python """ Filter JUnit XML reports to show only a subset of all information. JUNIT XML SCHEMA: element:testsuite: failures="..." errors="..." +-- element:testcase: name="..." status="..." """ from __future__ import absolute_import, print_function, with_statement import os.path import sys import argparse from fnmatch import fnmatch from xml.etree import ElementTree as ET from xml.dom import minidom from behave.textutil import indent __author = "Jens Engel" __status__ = "prototype" NAME = os.path.basename(__file__) VERSION = "0.1.0" REPORT_DIR = "reports" def xml_prettify(elem): """Return a pretty-printed XML string for the XML element.""" text = ET.tostring(elem, "utf-8") reparsed = minidom.parseString(text) return reparsed.toprettyxml(indent=" ") def xml_select_testcases_with_status(tree, status): return tree.findall(".//testcase[@status='%s']" % status) def path_select_files(paths, pattern="*.xml"): if not paths: paths = [REPORT_DIR] selected = [] for pathname in paths: if os.path.isdir(pathname): for root, dirs, files in os.walk(pathname): for filename in files: if fnmatch(filename, pattern): filename2 = os.path.join(root, filename) selected.append(os.path.normpath(filename2)) elif os.path.isfile(pathname) and fnmatch(pathname, pattern): selected.append(pathname) return selected def report_testcases(filename, testcases): print(u"REPORT: {0}".format(filename)) for xml_testcase in testcases: print(" TESTCASE: {0}".format(xml_testcase.get("name"))) xml_text = indent(xml_prettify(xml_testcase), " ") print(xml_text) def main(args=None): """Filter JUnit XML reports to show only a subset of all information.""" if args is None: args = sys.argv[1:] parser = argparse.ArgumentParser(prog=NAME, description=main.__doc__) parser.add_argument("-s", "--status", default="failed", required=False, choices=["passed", "failed", "skipped"], help="Status to select (passed, failed, skipped).") parser.add_argument("xml_file", nargs="*", help="XML file(s) or directory with XML files.") parser.add_argument("--version", action="version", version=VERSION) options = parser.parse_args(args) xml_files = options.xml_file xml_reports = path_select_files(xml_files) for xml_filename in xml_reports: tree = ET.parse(xml_filename) testcases = xml_select_testcases_with_status(tree, options.status) if testcases: report_testcases(xml_filename, testcases) return 0 if __name__ == "__main__": sys.exit(main()) behave-1.2.6/bin/behave.step_durations.py0000755000076600000240000001345413244555737020504 0ustar jensstaff00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Utility script to retrieve duration information from behave JSON output. REQUIRES: Python >= 2.6 (json module is part of Python standard library) LICENSE: BSD """ from __future__ import absolute_import __author__ = "Jens Engel" __copyright__ = "(c) 2013 by Jens Engel" __license__ = "BSD" VERSION = "0.1.0" # -- IMPORTS: from behave import json_parser from behave.model import ScenarioOutline from optparse import OptionParser from operator import attrgetter import os.path import sys # ---------------------------------------------------------------------------- # FUNCTIONS: # ---------------------------------------------------------------------------- class StepDurationData(object): def __init__(self, step=None): self.step_name = None self.min_duration = sys.maxint self.max_duration = 0 self.durations = [] self.step = step if step: self.process_step(step) @staticmethod def make_step_name(step): step_name = "%s %s" % (step.step_type.capitalize(), step.name) return step_name def process_step(self, step): step_name = self.make_step_name(step) if not self.step_name: self.step_name = step_name if self.min_duration > step.duration: self.min_duration = step.duration if self.max_duration < step.duration: self.max_duration = step.duration self.durations.append(step.duration) class BehaveDurationData(object): def __init__(self): self.step_registry = {} self.all_steps = [] self.all_scenarios = [] def process_features(self, features): for feature in features: self.process_feature(feature) def process_feature(self, feature): if feature.background: self.process_background(feature.background) for scenario in feature.scenarios: if isinstance(scenario, ScenarioOutline): self.process_scenario_outline(scenario) else: self.process_scenario(scenario) def process_step(self, step): step_name = StepDurationData.make_step_name(step) known_step = self.step_registry.get(step_name, None) if known_step: known_step.process_step(step) else: step_data = StepDurationData(step) self.step_registry[step_name] = step_data self.all_steps.append(step) def process_background(self, scenario): for step in scenario: self.process_step(step) def process_scenario(self, scenario): for step in scenario: self.process_step(step) def process_scenario_outline(self, scenario_outline): for scenario in scenario_outline: self.process_scenario(scenario) def report_step_durations(self, limit=None, min_duration=None, ostream=sys.stdout): step_datas = list(self.step_registry.values()) steps_size = len(step_datas) steps_by_longest_duration_first = sorted(step_datas, key=attrgetter("max_duration"), reverse=True) ostream.write("STEP DURATIONS (longest first, size=%d):\n" % steps_size) ostream.write("-" * 80) ostream.write("\n") for index, step in enumerate(steps_by_longest_duration_first): ostream.write("% 4d. %9.6fs %s" % \ (index+1, step.max_duration, step.step_name)) calls = len(step.durations) if calls > 1: ostream.write(" (%d calls, min: %.6fs)\n" % (calls, step.min_duration)) else: ostream.write("\n") if ((limit and index+1 >= limit) or (step.max_duration < min_duration)): remaining = steps_size - (index+1) ostream.write("...\nSkip remaining %d steps.\n" % remaining) break # ---------------------------------------------------------------------------- # MAIN FUNCTION: # ---------------------------------------------------------------------------- def main(args=None): if args is None: args = sys.argv[1:] usage_ = """%prog [OPTIONS] JsonFile Read behave JSON data file and extract steps with longest duration.""" parser = OptionParser(usage=usage_, version=VERSION) parser.add_option("-e", "--encoding", dest="encoding", default="UTF-8", help="Encoding to use (default: %default).") parser.add_option("-l", "--limit", dest="limit", type="int", help="Max. number of steps (default: %default).") parser.add_option("-m", "--min", dest="min_duration", default="0", help="Min. duration threshold (default: %default).") options, filenames = parser.parse_args(args) if not filenames: parser.error("OOPS, no filenames provided.") elif len(filenames) > 1: parser.error("OOPS: Can only process one JSON file.") min_duration = float(options.min_duration) if min_duration < 0: min_duration = None json_filename = filenames[0] if not os.path.exists(json_filename): parser.error("JSON file '%s' not found" % json_filename) # -- NORMAL PROCESSING: Read JSON, extract step durations and report them. features = json_parser.parse(json_filename) processor = BehaveDurationData() processor.process_features(features) processor.report_step_durations(options.limit, min_duration) sys.stdout.write("Detected %d features.\n" % len(features)) return 0 # ---------------------------------------------------------------------------- # AUTO-MAIN: # ---------------------------------------------------------------------------- if __name__ == "__main__": sys.exit(main()) behave-1.2.6/bin/behave2cucumber_json.py0000755000076600000240000000432213244555737020275 0ustar jensstaff00000000000000#!/usr/bin/env python # ============================================================================= # CONVERT: behave JSON dialect to cucumber JSON dialect # ============================================================================= # STATUS: __PROTOTYPE__ # REQUIRES: Python >= 2.6 # REQUIRES: https://github.com/behalfinc/b2c/ # SEE: https://github.com/behave/behave/issues/267#issuecomment-249607191 # ============================================================================= """ Convert a file with behave JSON data into a file with cucumber JSON data. Note that both JSON dialects differ slightly. """ from __future__ import print_function import json import sys import os.path try: import b2c except ImportError: print("REQUIRE: https://github.com/behalfinc/b2c/ (not installed yet)") print("INSTALL: pip install b2c") sys.exit(2) NAME = os.path.basename(__file__) def convert_behave_to_cucumber_json(behave_filename, cucumber_filename, encoding="UTF-8", pretty=True): """Convert behave JSON dialect into cucumber JSON dialect. .. param behave_filename: Input filename with behave JSON data. .. param cucumber_filename: Output filename with cucumber JSON data. """ dump_kwargs = {"encoding": encoding} if pretty: dump_kwargs.update(indent=2, sort_keys=True) with open(behave_filename, "r") as behave_json: with open(cucumber_filename, "w+") as output_file: cucumber_json = b2c.convert(json.load(behave_json, encoding)) # cucumber_text = json.dumps(cucumber_json, **dump_kwargs) # output_file.write(cucumber_text) json.dump(cucumber_json, output_file, **dump_kwargs) return 0 def main(args=None): """Main function to run the script.""" if args is None: args = sys.argv[1:] if len(args) != 2: print("USAGE: %s BEHAVE_FILE.json CUCUMBER_FILE.json" % NAME) print("Converts behave JSON dialect to cucumber JSON dialect.") return 1 behave_filename = args[0] cucumber_filename = args[1] return convert_behave_to_cucumber_json(behave_filename, cucumber_filename) # -- AUTO-MAIN: if __name__ == "__main__": sys.exit(main()) behave-1.2.6/bin/behave_cmd.py0000755000076600000240000000277713244555737016273 0ustar jensstaff00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import absolute_import # -- ENSURE: Use local path during development. import sys import os.path # ---------------------------------------------------------------------------- # SETUP PATHS: # ---------------------------------------------------------------------------- NAME = "behave" HERE = os.path.dirname(__file__) TOP = os.path.join(HERE, "..") if os.path.isdir(os.path.join(TOP, NAME)): sys.path.insert(0, os.path.abspath(TOP)) # ---------------------------------------------------------------------------- # BEHAVE-TWEAKS: # ---------------------------------------------------------------------------- def setup_behave(): """ Apply tweaks, extensions and patches to "behave". """ from behave.configuration import Configuration # -- DISABLE: Timings to simplify issue.features/ tests. # Configuration.defaults["format0"] = "pretty" # Configuration.defaults["format0"] = "progress" Configuration.defaults["show_timings"] = False def behave_main0(): # from behave.configuration import Configuration from behave.__main__ import main as behave_main setup_behave() return behave_main() # ---------------------------------------------------------------------------- # MAIN: # ---------------------------------------------------------------------------- if __name__ == "__main__": if "COVERAGE_PROCESS_START" in os.environ: import coverage coverage.process_startup() sys.exit(behave_main0()) behave-1.2.6/bin/convert_i18n_yaml.py0000755000076600000240000000436113244555737017546 0ustar jensstaff00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- # USAGE: convert_i18n_yaml.py [--data=i18n.yml] behave/i18n.py """ Generates I18N python module based on YAML description (i18n.yml). REQUIRES: * argparse * six * PyYAML """ from __future__ import absolute_import, print_function import argparse import os.path import six import sys import pprint import yaml HERE = os.path.dirname(__file__) NAME = os.path.basename(__file__) __version__ = "1.0" def yaml_normalize(data): for part in data: keywords = data[part] for k in keywords: v = keywords[k] # bloody YAML parser returns a mixture of unicode and str if not isinstance(v, six.text_type): v = v.decode("UTF-8") keywords[k] = v.split("|") return data def main(args=None): if args is None: args = sys.argv[1:] parser = argparse.ArgumentParser(prog=NAME, description="Generate python module i18n from YAML based data") parser.add_argument("-d", "--data", dest="yaml_file", default=os.path.join(HERE, "i18n.yml"), help="Path to i18n.yml file (YAML file).") parser.add_argument("output_file", default="stdout", help="Filename of Python I18N module (as output).") parser.add_argument("--version", action="version", version=__version__) options = parser.parse_args(args) if not os.path.isfile(options.yaml_file): parser.error("YAML file not found: %s" % options.yaml_file) # -- STEP 1: Load YAML data. languages = yaml.load(open(options.yaml_file)) languages = yaml_normalize(languages) # -- STEP 2: Generate python module with i18n data. contents = u"""# -*- coding: UTF-8 -*- # -- FILE GENERATED BY: convert_i18n_yaml.py with i18n.yml # pylint: disable=line-too-long languages = \\ """ if options.output_file in ("-", "stdout"): i18n_py = sys.stdout should_close = False else: i18n_py = open(options.output_file, "w") should_close = True i18n_py.write(contents.encode("UTF-8")) i18n_py.write(pprint.pformat(languages).encode("UTF-8")) i18n_py.write(u"\n") if should_close: i18n_py.close() return 0 if __name__ == "__main__": sys.exit(main()) behave-1.2.6/bin/explore_platform_encoding.py0000755000076600000240000000151713244555737021435 0ustar jensstaff00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- """ Explore encoding settings on a platform. """ from __future__ import print_function import sys import platform import locale from behave.textutil import select_best_encoding def explore_platform_encoding(): python_version = platform.python_version() print("python %s (platform: %s, %s, %s)" % (python_version, sys.platform, platform.python_implementation(), platform.platform())) print("sys.getfilesystemencoding(): %s" % sys.getfilesystemencoding()) print("locale.getpreferredencoding(): %s" % locale.getpreferredencoding()) print("behave.textutil.select_best_encoding(): %s" % select_best_encoding()) return 0 if __name__ == "__main__": sys.exit(explore_platform_encoding()) behave-1.2.6/bin/gherkin-languages.json0000644000076600000240000013624113244555737020121 0ustar jensstaff00000000000000{ "af": { "and": [ "* ", "En " ], "background": [ "Agtergrond" ], "but": [ "* ", "Maar " ], "examples": [ "Voorbeelde" ], "feature": [ "Funksie", "Besigheid Behoefte", "Vermoë" ], "given": [ "* ", "Gegewe " ], "name": "Afrikaans", "native": "Afrikaans", "scenario": [ "Situasie" ], "scenarioOutline": [ "Situasie Uiteensetting" ], "then": [ "* ", "Dan " ], "when": [ "* ", "Wanneer " ] }, "am": { "and": [ "* ", "Եվ " ], "background": [ "Կոնտեքստ" ], "but": [ "* ", "Բայց " ], "examples": [ "Օրինակներ" ], "feature": [ "Ֆունկցիոնալություն", "Հատկություն" ], "given": [ "* ", "Դիցուք " ], "name": "Armenian", "native": "հայերեն", "scenario": [ "Սցենար" ], "scenarioOutline": [ "Սցենարի կառուցվացքը" ], "then": [ "* ", "Ապա " ], "when": [ "* ", "Եթե ", "Երբ " ] }, "ar": { "and": [ "* ", "و " ], "background": [ "الخلفية" ], "but": [ "* ", "لكن " ], "examples": [ "امثلة" ], "feature": [ "خاصية" ], "given": [ "* ", "بفرض " ], "name": "Arabic", "native": "العربية", "scenario": [ "سيناريو" ], "scenarioOutline": [ "سيناريو مخطط" ], "then": [ "* ", "اذاً ", "ثم " ], "when": [ "* ", "متى ", "عندما " ] }, "ast": { "and": [ "* ", "Y ", "Ya " ], "background": [ "Antecedentes" ], "but": [ "* ", "Peru " ], "examples": [ "Exemplos" ], "feature": [ "Carauterística" ], "given": [ "* ", "Dáu ", "Dada ", "Daos ", "Daes " ], "name": "Asturian", "native": "asturianu", "scenario": [ "Casu" ], "scenarioOutline": [ "Esbozu del casu" ], "then": [ "* ", "Entós " ], "when": [ "* ", "Cuando " ] }, "az": { "and": [ "* ", "Və ", "Həm " ], "background": [ "Keçmiş", "Kontekst" ], "but": [ "* ", "Amma ", "Ancaq " ], "examples": [ "Nümunələr" ], "feature": [ "Özəllik" ], "given": [ "* ", "Tutaq ki ", "Verilir " ], "name": "Azerbaijani", "native": "Azərbaycanca", "scenario": [ "Ssenari" ], "scenarioOutline": [ "Ssenarinin strukturu" ], "then": [ "* ", "O halda " ], "when": [ "* ", "Əgər ", "Nə vaxt ki " ] }, "bg": { "and": [ "* ", "И " ], "background": [ "Предистория" ], "but": [ "* ", "Но " ], "examples": [ "Примери" ], "feature": [ "Функционалност" ], "given": [ "* ", "Дадено " ], "name": "Bulgarian", "native": "български", "scenario": [ "Сценарий" ], "scenarioOutline": [ "Рамка на сценарий" ], "then": [ "* ", "То " ], "when": [ "* ", "Когато " ] }, "bm": { "and": [ "* ", "Dan " ], "background": [ "Latar Belakang" ], "but": [ "* ", "Tetapi ", "Tapi " ], "examples": [ "Contoh" ], "feature": [ "Fungsi" ], "given": [ "* ", "Diberi ", "Bagi " ], "name": "Malay", "native": "Bahasa Melayu", "scenario": [ "Senario", "Situasi", "Keadaan" ], "scenarioOutline": [ "Kerangka Senario", "Kerangka Situasi", "Kerangka Keadaan", "Garis Panduan Senario" ], "then": [ "* ", "Maka ", "Kemudian " ], "when": [ "* ", "Apabila " ] }, "bs": { "and": [ "* ", "I ", "A " ], "background": [ "Pozadina" ], "but": [ "* ", "Ali " ], "examples": [ "Primjeri" ], "feature": [ "Karakteristika" ], "given": [ "* ", "Dato " ], "name": "Bosnian", "native": "Bosanski", "scenario": [ "Scenariju", "Scenario" ], "scenarioOutline": [ "Scenariju-obris", "Scenario-outline" ], "then": [ "* ", "Zatim " ], "when": [ "* ", "Kada " ] }, "ca": { "and": [ "* ", "I " ], "background": [ "Rerefons", "Antecedents" ], "but": [ "* ", "Però " ], "examples": [ "Exemples" ], "feature": [ "Característica", "Funcionalitat" ], "given": [ "* ", "Donat ", "Donada ", "Atès ", "Atesa " ], "name": "Catalan", "native": "català", "scenario": [ "Escenari" ], "scenarioOutline": [ "Esquema de l'escenari" ], "then": [ "* ", "Aleshores ", "Cal " ], "when": [ "* ", "Quan " ] }, "cs": { "and": [ "* ", "A také ", "A " ], "background": [ "Pozadí", "Kontext" ], "but": [ "* ", "Ale " ], "examples": [ "Příklady" ], "feature": [ "Požadavek" ], "given": [ "* ", "Pokud ", "Za předpokladu " ], "name": "Czech", "native": "Česky", "scenario": [ "Scénář" ], "scenarioOutline": [ "Náčrt Scénáře", "Osnova scénáře" ], "then": [ "* ", "Pak " ], "when": [ "* ", "Když " ] }, "cy-GB": { "and": [ "* ", "A " ], "background": [ "Cefndir" ], "but": [ "* ", "Ond " ], "examples": [ "Enghreifftiau" ], "feature": [ "Arwedd" ], "given": [ "* ", "Anrhegedig a " ], "name": "Welsh", "native": "Cymraeg", "scenario": [ "Scenario" ], "scenarioOutline": [ "Scenario Amlinellol" ], "then": [ "* ", "Yna " ], "when": [ "* ", "Pryd " ] }, "da": { "and": [ "* ", "Og " ], "background": [ "Baggrund" ], "but": [ "* ", "Men " ], "examples": [ "Eksempler" ], "feature": [ "Egenskab" ], "given": [ "* ", "Givet " ], "name": "Danish", "native": "dansk", "scenario": [ "Scenarie" ], "scenarioOutline": [ "Abstrakt Scenario" ], "then": [ "* ", "Så " ], "when": [ "* ", "Når " ] }, "de": { "and": [ "* ", "Und " ], "background": [ "Grundlage" ], "but": [ "* ", "Aber " ], "examples": [ "Beispiele" ], "feature": [ "Funktionalität" ], "given": [ "* ", "Angenommen ", "Gegeben sei ", "Gegeben seien " ], "name": "German", "native": "Deutsch", "scenario": [ "Szenario" ], "scenarioOutline": [ "Szenariogrundriss" ], "then": [ "* ", "Dann " ], "when": [ "* ", "Wenn " ] }, "el": { "and": [ "* ", "Και " ], "background": [ "Υπόβαθρο" ], "but": [ "* ", "Αλλά " ], "examples": [ "Παραδείγματα", "Σενάρια" ], "feature": [ "Δυνατότητα", "Λειτουργία" ], "given": [ "* ", "Δεδομένου " ], "name": "Greek", "native": "Ελληνικά", "scenario": [ "Σενάριο" ], "scenarioOutline": [ "Περιγραφή Σεναρίου", "Περίγραμμα Σεναρίου" ], "then": [ "* ", "Τότε " ], "when": [ "* ", "Όταν " ] }, "em": { "and": [ "* ", "😂" ], "background": [ "💤" ], "but": [ "* ", "😔" ], "examples": [ "📓" ], "feature": [ "📚" ], "given": [ "* ", "😐" ], "name": "Emoji", "native": "😀", "scenario": [ "📕" ], "scenarioOutline": [ "📖" ], "then": [ "* ", "🙏" ], "when": [ "* ", "🎬" ] }, "en": { "and": [ "* ", "And " ], "background": [ "Background" ], "but": [ "* ", "But " ], "examples": [ "Examples", "Scenarios" ], "feature": [ "Feature", "Business Need", "Ability" ], "given": [ "* ", "Given " ], "name": "English", "native": "English", "scenario": [ "Scenario" ], "scenarioOutline": [ "Scenario Outline", "Scenario Template" ], "then": [ "* ", "Then " ], "when": [ "* ", "When " ] }, "en-Scouse": { "and": [ "* ", "An " ], "background": [ "Dis is what went down" ], "but": [ "* ", "Buh " ], "examples": [ "Examples" ], "feature": [ "Feature" ], "given": [ "* ", "Givun ", "Youse know when youse got " ], "name": "Scouse", "native": "Scouse", "scenario": [ "The thing of it is" ], "scenarioOutline": [ "Wharrimean is" ], "then": [ "* ", "Dun ", "Den youse gotta " ], "when": [ "* ", "Wun ", "Youse know like when " ] }, "en-au": { "and": [ "* ", "Too right " ], "background": [ "First off" ], "but": [ "* ", "Yeah nah " ], "examples": [ "You'll wanna" ], "feature": [ "Pretty much" ], "given": [ "* ", "Y'know " ], "name": "Australian", "native": "Australian", "scenario": [ "Awww, look mate" ], "scenarioOutline": [ "Reckon it's like" ], "then": [ "* ", "But at the end of the day I reckon " ], "when": [ "* ", "It's just unbelievable " ] }, "en-lol": { "and": [ "* ", "AN " ], "background": [ "B4" ], "but": [ "* ", "BUT " ], "examples": [ "EXAMPLZ" ], "feature": [ "OH HAI" ], "given": [ "* ", "I CAN HAZ " ], "name": "LOLCAT", "native": "LOLCAT", "scenario": [ "MISHUN" ], "scenarioOutline": [ "MISHUN SRSLY" ], "then": [ "* ", "DEN " ], "when": [ "* ", "WEN " ] }, "en-old": { "and": [ "* ", "Ond ", "7 " ], "background": [ "Aer", "Ær" ], "but": [ "* ", "Ac " ], "examples": [ "Se the", "Se þe", "Se ðe" ], "feature": [ "Hwaet", "Hwæt" ], "given": [ "* ", "Thurh ", "Þurh ", "Ðurh " ], "name": "Old English", "native": "Englisc", "scenario": [ "Swa" ], "scenarioOutline": [ "Swa hwaer swa", "Swa hwær swa" ], "then": [ "* ", "Tha ", "Þa ", "Ða ", "Tha the ", "Þa þe ", "Ða ðe " ], "when": [ "* ", "Tha ", "Þa ", "Ða " ] }, "en-pirate": { "and": [ "* ", "Aye " ], "background": [ "Yo-ho-ho" ], "but": [ "* ", "Avast! " ], "examples": [ "Dead men tell no tales" ], "feature": [ "Ahoy matey!" ], "given": [ "* ", "Gangway! " ], "name": "Pirate", "native": "Pirate", "scenario": [ "Heave to" ], "scenarioOutline": [ "Shiver me timbers" ], "then": [ "* ", "Let go and haul " ], "when": [ "* ", "Blimey! " ] }, "eo": { "and": [ "* ", "Kaj " ], "background": [ "Fono" ], "but": [ "* ", "Sed " ], "examples": [ "Ekzemploj" ], "feature": [ "Trajto" ], "given": [ "* ", "Donitaĵo ", "Komence " ], "name": "Esperanto", "native": "Esperanto", "scenario": [ "Scenaro", "Kazo" ], "scenarioOutline": [ "Konturo de la scenaro", "Skizo", "Kazo-skizo" ], "then": [ "* ", "Do " ], "when": [ "* ", "Se " ] }, "es": { "and": [ "* ", "Y ", "E " ], "background": [ "Antecedentes" ], "but": [ "* ", "Pero " ], "examples": [ "Ejemplos" ], "feature": [ "Característica" ], "given": [ "* ", "Dado ", "Dada ", "Dados ", "Dadas " ], "name": "Spanish", "native": "español", "scenario": [ "Escenario" ], "scenarioOutline": [ "Esquema del escenario" ], "then": [ "* ", "Entonces " ], "when": [ "* ", "Cuando " ] }, "et": { "and": [ "* ", "Ja " ], "background": [ "Taust" ], "but": [ "* ", "Kuid " ], "examples": [ "Juhtumid" ], "feature": [ "Omadus" ], "given": [ "* ", "Eeldades " ], "name": "Estonian", "native": "eesti keel", "scenario": [ "Stsenaarium" ], "scenarioOutline": [ "Raamstsenaarium" ], "then": [ "* ", "Siis " ], "when": [ "* ", "Kui " ] }, "fa": { "and": [ "* ", "و " ], "background": [ "زمینه" ], "but": [ "* ", "اما " ], "examples": [ "نمونه ها" ], "feature": [ "وِیژگی" ], "given": [ "* ", "با فرض " ], "name": "Persian", "native": "فارسی", "scenario": [ "سناریو" ], "scenarioOutline": [ "الگوی سناریو" ], "then": [ "* ", "آنگاه " ], "when": [ "* ", "هنگامی " ] }, "fi": { "and": [ "* ", "Ja " ], "background": [ "Tausta" ], "but": [ "* ", "Mutta " ], "examples": [ "Tapaukset" ], "feature": [ "Ominaisuus" ], "given": [ "* ", "Oletetaan " ], "name": "Finnish", "native": "suomi", "scenario": [ "Tapaus" ], "scenarioOutline": [ "Tapausaihio" ], "then": [ "* ", "Niin " ], "when": [ "* ", "Kun " ] }, "fr": { "and": [ "* ", "Et que ", "Et qu'", "Et " ], "background": [ "Contexte" ], "but": [ "* ", "Mais que ", "Mais qu'", "Mais " ], "examples": [ "Exemples" ], "feature": [ "Fonctionnalité" ], "given": [ "* ", "Soit ", "Etant donné que ", "Etant donné qu'", "Etant donné ", "Etant donnée ", "Etant donnés ", "Etant données ", "Étant donné que ", "Étant donné qu'", "Étant donné ", "Étant donnée ", "Étant donnés ", "Étant données " ], "name": "French", "native": "français", "scenario": [ "Scénario" ], "scenarioOutline": [ "Plan du scénario", "Plan du Scénario" ], "then": [ "* ", "Alors " ], "when": [ "* ", "Quand ", "Lorsque ", "Lorsqu'" ] }, "ga": { "and": [ "* ", "Agus" ], "background": [ "Cúlra" ], "but": [ "* ", "Ach" ], "examples": [ "Samplaí" ], "feature": [ "Gné" ], "given": [ "* ", "Cuir i gcás go", "Cuir i gcás nach", "Cuir i gcás gur", "Cuir i gcás nár" ], "name": "Irish", "native": "Gaeilge", "scenario": [ "Cás" ], "scenarioOutline": [ "Cás Achomair" ], "then": [ "* ", "Ansin" ], "when": [ "* ", "Nuair a", "Nuair nach", "Nuair ba", "Nuair nár" ] }, "gj": { "and": [ "* ", "અને " ], "background": [ "બેકગ્રાઉન્ડ" ], "but": [ "* ", "પણ " ], "examples": [ "ઉદાહરણો" ], "feature": [ "લક્ષણ", "વ્યાપાર જરૂર", "ક્ષમતા" ], "given": [ "* ", "આપેલ છે " ], "name": "Gujarati", "native": "ગુજરાતી", "scenario": [ "સ્થિતિ" ], "scenarioOutline": [ "પરિદ્દશ્ય રૂપરેખા", "પરિદ્દશ્ય ઢાંચો" ], "then": [ "* ", "પછી " ], "when": [ "* ", "ક્યારે " ] }, "gl": { "and": [ "* ", "E " ], "background": [ "Contexto" ], "but": [ "* ", "Mais ", "Pero " ], "examples": [ "Exemplos" ], "feature": [ "Característica" ], "given": [ "* ", "Dado ", "Dada ", "Dados ", "Dadas " ], "name": "Galician", "native": "galego", "scenario": [ "Escenario" ], "scenarioOutline": [ "Esbozo do escenario" ], "then": [ "* ", "Entón ", "Logo " ], "when": [ "* ", "Cando " ] }, "he": { "and": [ "* ", "וגם " ], "background": [ "רקע" ], "but": [ "* ", "אבל " ], "examples": [ "דוגמאות" ], "feature": [ "תכונה" ], "given": [ "* ", "בהינתן " ], "name": "Hebrew", "native": "עברית", "scenario": [ "תרחיש" ], "scenarioOutline": [ "תבנית תרחיש" ], "then": [ "* ", "אז ", "אזי " ], "when": [ "* ", "כאשר " ] }, "hi": { "and": [ "* ", "और ", "तथा " ], "background": [ "पृष्ठभूमि" ], "but": [ "* ", "पर ", "परन्तु ", "किन्तु " ], "examples": [ "उदाहरण" ], "feature": [ "रूप लेख" ], "given": [ "* ", "अगर ", "यदि ", "चूंकि " ], "name": "Hindi", "native": "हिंदी", "scenario": [ "परिदृश्य" ], "scenarioOutline": [ "परिदृश्य रूपरेखा" ], "then": [ "* ", "तब ", "तदा " ], "when": [ "* ", "जब ", "कदा " ] }, "hr": { "and": [ "* ", "I " ], "background": [ "Pozadina" ], "but": [ "* ", "Ali " ], "examples": [ "Primjeri", "Scenariji" ], "feature": [ "Osobina", "Mogućnost", "Mogucnost" ], "given": [ "* ", "Zadan ", "Zadani ", "Zadano " ], "name": "Croatian", "native": "hrvatski", "scenario": [ "Scenarij" ], "scenarioOutline": [ "Skica", "Koncept" ], "then": [ "* ", "Onda " ], "when": [ "* ", "Kada ", "Kad " ] }, "ht": { "and": [ "* ", "Ak ", "Epi ", "E " ], "background": [ "Kontèks", "Istorik" ], "but": [ "* ", "Men " ], "examples": [ "Egzanp" ], "feature": [ "Karakteristik", "Mak", "Fonksyonalite" ], "given": [ "* ", "Sipoze ", "Sipoze ke ", "Sipoze Ke " ], "name": "Creole", "native": "kreyòl", "scenario": [ "Senaryo" ], "scenarioOutline": [ "Plan senaryo", "Plan Senaryo", "Senaryo deskripsyon", "Senaryo Deskripsyon", "Dyagram senaryo", "Dyagram Senaryo" ], "then": [ "* ", "Lè sa a ", "Le sa a " ], "when": [ "* ", "Lè ", "Le " ] }, "hu": { "and": [ "* ", "És " ], "background": [ "Háttér" ], "but": [ "* ", "De " ], "examples": [ "Példák" ], "feature": [ "Jellemző" ], "given": [ "* ", "Amennyiben ", "Adott " ], "name": "Hungarian", "native": "magyar", "scenario": [ "Forgatókönyv" ], "scenarioOutline": [ "Forgatókönyv vázlat" ], "then": [ "* ", "Akkor " ], "when": [ "* ", "Majd ", "Ha ", "Amikor " ] }, "id": { "and": [ "* ", "Dan " ], "background": [ "Dasar" ], "but": [ "* ", "Tapi " ], "examples": [ "Contoh" ], "feature": [ "Fitur" ], "given": [ "* ", "Dengan " ], "name": "Indonesian", "native": "Bahasa Indonesia", "scenario": [ "Skenario" ], "scenarioOutline": [ "Skenario konsep" ], "then": [ "* ", "Maka " ], "when": [ "* ", "Ketika " ] }, "is": { "and": [ "* ", "Og " ], "background": [ "Bakgrunnur" ], "but": [ "* ", "En " ], "examples": [ "Dæmi", "Atburðarásir" ], "feature": [ "Eiginleiki" ], "given": [ "* ", "Ef " ], "name": "Icelandic", "native": "Íslenska", "scenario": [ "Atburðarás" ], "scenarioOutline": [ "Lýsing Atburðarásar", "Lýsing Dæma" ], "then": [ "* ", "Þá " ], "when": [ "* ", "Þegar " ] }, "it": { "and": [ "* ", "E " ], "background": [ "Contesto" ], "but": [ "* ", "Ma " ], "examples": [ "Esempi" ], "feature": [ "Funzionalità" ], "given": [ "* ", "Dato ", "Data ", "Dati ", "Date " ], "name": "Italian", "native": "italiano", "scenario": [ "Scenario" ], "scenarioOutline": [ "Schema dello scenario" ], "then": [ "* ", "Allora " ], "when": [ "* ", "Quando " ] }, "ja": { "and": [ "* ", "かつ" ], "background": [ "背景" ], "but": [ "* ", "しかし", "但し", "ただし" ], "examples": [ "例", "サンプル" ], "feature": [ "フィーチャ", "機能" ], "given": [ "* ", "前提" ], "name": "Japanese", "native": "日本語", "scenario": [ "シナリオ" ], "scenarioOutline": [ "シナリオアウトライン", "シナリオテンプレート", "テンプレ", "シナリオテンプレ" ], "then": [ "* ", "ならば" ], "when": [ "* ", "もし" ] }, "jv": { "and": [ "* ", "Lan " ], "background": [ "Dasar" ], "but": [ "* ", "Tapi ", "Nanging ", "Ananging " ], "examples": [ "Conto", "Contone" ], "feature": [ "Fitur" ], "given": [ "* ", "Nalika ", "Nalikaning " ], "name": "Javanese", "native": "Basa Jawa", "scenario": [ "Skenario" ], "scenarioOutline": [ "Konsep skenario" ], "then": [ "* ", "Njuk ", "Banjur " ], "when": [ "* ", "Manawa ", "Menawa " ] }, "ka": { "and": [ "* ", "და" ], "background": [ "კონტექსტი" ], "but": [ "* ", "მაგ­რამ" ], "examples": [ "მაგალითები" ], "feature": [ "თვისება" ], "given": [ "* ", "მოცემული" ], "name": "Georgian", "native": "ქართველი", "scenario": [ "სცენარის" ], "scenarioOutline": [ "სცენარის ნიმუში" ], "then": [ "* ", "მაშინ" ], "when": [ "* ", "როდესაც" ] }, "kn": { "and": [ "* ", "ಮತ್ತು " ], "background": [ "ಹಿನ್ನೆಲೆ" ], "but": [ "* ", "ಆದರೆ " ], "examples": [ "ಉದಾಹರಣೆಗಳು" ], "feature": [ "ಹೆಚ್ಚಳ" ], "given": [ "* ", "ನೀಡಿದ " ], "name": "Kannada", "native": "ಕನ್ನಡ", "scenario": [ "ಕಥಾಸಾರಾಂಶ" ], "scenarioOutline": [ "ವಿವರಣೆ" ], "then": [ "* ", "ನಂತರ " ], "when": [ "* ", "ಸ್ಥಿತಿಯನ್ನು " ] }, "ko": { "and": [ "* ", "그리고" ], "background": [ "배경" ], "but": [ "* ", "하지만", "단" ], "examples": [ "예" ], "feature": [ "기능" ], "given": [ "* ", "조건", "먼저" ], "name": "Korean", "native": "한국어", "scenario": [ "시나리오" ], "scenarioOutline": [ "시나리오 개요" ], "then": [ "* ", "그러면" ], "when": [ "* ", "만일", "만약" ] }, "lt": { "and": [ "* ", "Ir " ], "background": [ "Kontekstas" ], "but": [ "* ", "Bet " ], "examples": [ "Pavyzdžiai", "Scenarijai", "Variantai" ], "feature": [ "Savybė" ], "given": [ "* ", "Duota " ], "name": "Lithuanian", "native": "lietuvių kalba", "scenario": [ "Scenarijus" ], "scenarioOutline": [ "Scenarijaus šablonas" ], "then": [ "* ", "Tada " ], "when": [ "* ", "Kai " ] }, "lu": { "and": [ "* ", "an ", "a " ], "background": [ "Hannergrond" ], "but": [ "* ", "awer ", "mä " ], "examples": [ "Beispiller" ], "feature": [ "Funktionalitéit" ], "given": [ "* ", "ugeholl " ], "name": "Luxemburgish", "native": "Lëtzebuergesch", "scenario": [ "Szenario" ], "scenarioOutline": [ "Plang vum Szenario" ], "then": [ "* ", "dann " ], "when": [ "* ", "wann " ] }, "lv": { "and": [ "* ", "Un " ], "background": [ "Konteksts", "Situācija" ], "but": [ "* ", "Bet " ], "examples": [ "Piemēri", "Paraugs" ], "feature": [ "Funkcionalitāte", "Fīča" ], "given": [ "* ", "Kad " ], "name": "Latvian", "native": "latviešu", "scenario": [ "Scenārijs" ], "scenarioOutline": [ "Scenārijs pēc parauga" ], "then": [ "* ", "Tad " ], "when": [ "* ", "Ja " ] }, "mk-Cyrl": { "and": [ "* ", "И " ], "background": [ "Контекст", "Содржина" ], "but": [ "* ", "Но " ], "examples": [ "Примери", "Сценарија" ], "feature": [ "Функционалност", "Бизнис потреба", "Можност" ], "given": [ "* ", "Дадено ", "Дадена " ], "name": "Macedonian", "native": "Македонски", "scenario": [ "Сценарио", "На пример" ], "scenarioOutline": [ "Преглед на сценарија", "Скица", "Концепт" ], "then": [ "* ", "Тогаш " ], "when": [ "* ", "Кога " ] }, "mk-Latn": { "and": [ "* ", "I " ], "background": [ "Kontekst", "Sodrzhina" ], "but": [ "* ", "No " ], "examples": [ "Primeri", "Scenaria" ], "feature": [ "Funkcionalnost", "Biznis potreba", "Mozhnost" ], "given": [ "* ", "Dadeno ", "Dadena " ], "name": "Macedonian (Latin)", "native": "Makedonski (Latinica)", "scenario": [ "Scenario", "Na primer" ], "scenarioOutline": [ "Pregled na scenarija", "Skica", "Koncept" ], "then": [ "* ", "Togash " ], "when": [ "* ", "Koga " ] }, "mn": { "and": [ "* ", "Мөн ", "Тэгээд " ], "background": [ "Агуулга" ], "but": [ "* ", "Гэхдээ ", "Харин " ], "examples": [ "Тухайлбал" ], "feature": [ "Функц", "Функционал" ], "given": [ "* ", "Өгөгдсөн нь ", "Анх " ], "name": "Mongolian", "native": "монгол", "scenario": [ "Сценар" ], "scenarioOutline": [ "Сценарын төлөвлөгөө" ], "then": [ "* ", "Тэгэхэд ", "Үүний дараа " ], "when": [ "* ", "Хэрэв " ] }, "nl": { "and": [ "* ", "En " ], "background": [ "Achtergrond" ], "but": [ "* ", "Maar " ], "examples": [ "Voorbeelden" ], "feature": [ "Functionaliteit" ], "given": [ "* ", "Gegeven ", "Stel " ], "name": "Dutch", "native": "Nederlands", "scenario": [ "Scenario" ], "scenarioOutline": [ "Abstract Scenario" ], "then": [ "* ", "Dan " ], "when": [ "* ", "Als ", "Wanneer " ] }, "no": { "and": [ "* ", "Og " ], "background": [ "Bakgrunn" ], "but": [ "* ", "Men " ], "examples": [ "Eksempler" ], "feature": [ "Egenskap" ], "given": [ "* ", "Gitt " ], "name": "Norwegian", "native": "norsk", "scenario": [ "Scenario" ], "scenarioOutline": [ "Scenariomal", "Abstrakt Scenario" ], "then": [ "* ", "Så " ], "when": [ "* ", "Når " ] }, "pa": { "and": [ "* ", "ਅਤੇ " ], "background": [ "ਪਿਛੋਕੜ" ], "but": [ "* ", "ਪਰ " ], "examples": [ "ਉਦਾਹਰਨਾਂ" ], "feature": [ "ਖਾਸੀਅਤ", "ਮੁਹਾਂਦਰਾ", "ਨਕਸ਼ ਨੁਹਾਰ" ], "given": [ "* ", "ਜੇਕਰ ", "ਜਿਵੇਂ ਕਿ " ], "name": "Panjabi", "native": "ਪੰਜਾਬੀ", "scenario": [ "ਪਟਕਥਾ" ], "scenarioOutline": [ "ਪਟਕਥਾ ਢਾਂਚਾ", "ਪਟਕਥਾ ਰੂਪ ਰੇਖਾ" ], "then": [ "* ", "ਤਦ " ], "when": [ "* ", "ਜਦੋਂ " ] }, "pl": { "and": [ "* ", "Oraz ", "I " ], "background": [ "Założenia" ], "but": [ "* ", "Ale " ], "examples": [ "Przykłady" ], "feature": [ "Właściwość", "Funkcja", "Aspekt", "Potrzeba biznesowa" ], "given": [ "* ", "Zakładając ", "Mając ", "Zakładając, że " ], "name": "Polish", "native": "polski", "scenario": [ "Scenariusz" ], "scenarioOutline": [ "Szablon scenariusza" ], "then": [ "* ", "Wtedy " ], "when": [ "* ", "Jeżeli ", "Jeśli ", "Gdy ", "Kiedy " ] }, "pt": { "and": [ "* ", "E " ], "background": [ "Contexto", "Cenário de Fundo", "Cenario de Fundo", "Fundo" ], "but": [ "* ", "Mas " ], "examples": [ "Exemplos", "Cenários", "Cenarios" ], "feature": [ "Funcionalidade", "Característica", "Caracteristica" ], "given": [ "* ", "Dado ", "Dada ", "Dados ", "Dadas " ], "name": "Portuguese", "native": "português", "scenario": [ "Cenário", "Cenario" ], "scenarioOutline": [ "Esquema do Cenário", "Esquema do Cenario", "Delineação do Cenário", "Delineacao do Cenario" ], "then": [ "* ", "Então ", "Entao " ], "when": [ "* ", "Quando " ] }, "ro": { "and": [ "* ", "Si ", "Și ", "Şi " ], "background": [ "Context" ], "but": [ "* ", "Dar " ], "examples": [ "Exemple" ], "feature": [ "Functionalitate", "Funcționalitate", "Funcţionalitate" ], "given": [ "* ", "Date fiind ", "Dat fiind ", "Dată fiind", "Dati fiind ", "Dați fiind ", "Daţi fiind " ], "name": "Romanian", "native": "română", "scenario": [ "Scenariu" ], "scenarioOutline": [ "Structura scenariu", "Structură scenariu" ], "then": [ "* ", "Atunci " ], "when": [ "* ", "Cand ", "Când " ] }, "ru": { "and": [ "* ", "И ", "К тому же ", "Также " ], "background": [ "Предыстория", "Контекст" ], "but": [ "* ", "Но ", "А " ], "examples": [ "Примеры" ], "feature": [ "Функция", "Функциональность", "Функционал", "Свойство" ], "given": [ "* ", "Допустим ", "Дано ", "Пусть ", "Если " ], "name": "Russian", "native": "русский", "scenario": [ "Сценарий" ], "scenarioOutline": [ "Структура сценария" ], "then": [ "* ", "То ", "Затем ", "Тогда " ], "when": [ "* ", "Когда " ] }, "sk": { "and": [ "* ", "A ", "A tiež ", "A taktiež ", "A zároveň " ], "background": [ "Pozadie" ], "but": [ "* ", "Ale " ], "examples": [ "Príklady" ], "feature": [ "Požiadavka", "Funkcia", "Vlastnosť" ], "given": [ "* ", "Pokiaľ ", "Za predpokladu " ], "name": "Slovak", "native": "Slovensky", "scenario": [ "Scenár" ], "scenarioOutline": [ "Náčrt Scenáru", "Náčrt Scenára", "Osnova Scenára" ], "then": [ "* ", "Tak ", "Potom " ], "when": [ "* ", "Keď ", "Ak " ] }, "sl": { "and": [ "In ", "Ter " ], "background": [ "Kontekst", "Osnova", "Ozadje" ], "but": [ "Toda ", "Ampak ", "Vendar " ], "examples": [ "Primeri", "Scenariji" ], "feature": [ "Funkcionalnost", "Funkcija", "Možnosti", "Moznosti", "Lastnost", "Značilnost" ], "given": [ "Dano ", "Podano ", "Zaradi ", "Privzeto " ], "name": "Slovenian", "native": "Slovenski", "scenario": [ "Scenarij", "Primer" ], "scenarioOutline": [ "Struktura scenarija", "Skica", "Koncept", "Oris scenarija", "Osnutek" ], "then": [ "Nato ", "Potem ", "Takrat " ], "when": [ "Ko ", "Ce ", "Če ", "Kadar " ] }, "sr-Cyrl": { "and": [ "* ", "И " ], "background": [ "Контекст", "Основа", "Позадина" ], "but": [ "* ", "Али " ], "examples": [ "Примери", "Сценарији" ], "feature": [ "Функционалност", "Могућност", "Особина" ], "given": [ "* ", "За дато ", "За дате ", "За дати " ], "name": "Serbian", "native": "Српски", "scenario": [ "Сценарио", "Пример" ], "scenarioOutline": [ "Структура сценарија", "Скица", "Концепт" ], "then": [ "* ", "Онда " ], "when": [ "* ", "Када ", "Кад " ] }, "sr-Latn": { "and": [ "* ", "I " ], "background": [ "Kontekst", "Osnova", "Pozadina" ], "but": [ "* ", "Ali " ], "examples": [ "Primeri", "Scenariji" ], "feature": [ "Funkcionalnost", "Mogućnost", "Mogucnost", "Osobina" ], "given": [ "* ", "Za dato ", "Za date ", "Za dati " ], "name": "Serbian (Latin)", "native": "Srpski (Latinica)", "scenario": [ "Scenario", "Primer" ], "scenarioOutline": [ "Struktura scenarija", "Skica", "Koncept" ], "then": [ "* ", "Onda " ], "when": [ "* ", "Kada ", "Kad " ] }, "sv": { "and": [ "* ", "Och " ], "background": [ "Bakgrund" ], "but": [ "* ", "Men " ], "examples": [ "Exempel" ], "feature": [ "Egenskap" ], "given": [ "* ", "Givet " ], "name": "Swedish", "native": "Svenska", "scenario": [ "Scenario" ], "scenarioOutline": [ "Abstrakt Scenario", "Scenariomall" ], "then": [ "* ", "Så " ], "when": [ "* ", "När " ] }, "ta": { "and": [ "* ", "மேலும் ", "மற்றும் " ], "background": [ "பின்னணி" ], "but": [ "* ", "ஆனால் " ], "examples": [ "எடுத்துக்காட்டுகள்", "காட்சிகள்", " நிலைமைகளில்" ], "feature": [ "அம்சம்", "வணிக தேவை", "திறன்" ], "given": [ "* ", "கொடுக்கப்பட்ட " ], "name": "Tamil", "native": "தமிழ்", "scenario": [ "காட்சி" ], "scenarioOutline": [ "காட்சி சுருக்கம்", "காட்சி வார்ப்புரு" ], "then": [ "* ", "அப்பொழுது " ], "when": [ "* ", "எப்போது " ] }, "th": { "and": [ "* ", "และ " ], "background": [ "แนวคิด" ], "but": [ "* ", "แต่ " ], "examples": [ "ชุดของตัวอย่าง", "ชุดของเหตุการณ์" ], "feature": [ "โครงหลัก", "ความต้องการทางธุรกิจ", "ความสามารถ" ], "given": [ "* ", "กำหนดให้ " ], "name": "Thai", "native": "ไทย", "scenario": [ "เหตุการณ์" ], "scenarioOutline": [ "สรุปเหตุการณ์", "โครงสร้างของเหตุการณ์" ], "then": [ "* ", "ดังนั้น " ], "when": [ "* ", "เมื่อ " ] }, "tl": { "and": [ "* ", "మరియు " ], "background": [ "నేపథ్యం" ], "but": [ "* ", "కాని " ], "examples": [ "ఉదాహరణలు" ], "feature": [ "గుణము" ], "given": [ "* ", "చెప్పబడినది " ], "name": "Telugu", "native": "తెలుగు", "scenario": [ "సన్నివేశం" ], "scenarioOutline": [ "కథనం" ], "then": [ "* ", "అప్పుడు " ], "when": [ "* ", "ఈ పరిస్థితిలో " ] }, "tlh": { "and": [ "* ", "'ej ", "latlh " ], "background": [ "mo'" ], "but": [ "* ", "'ach ", "'a " ], "examples": [ "ghantoH", "lutmey" ], "feature": [ "Qap", "Qu'meH 'ut", "perbogh", "poQbogh malja'", "laH" ], "given": [ "* ", "ghu' noblu' ", "DaH ghu' bejlu' " ], "name": "Klingon", "native": "tlhIngan", "scenario": [ "lut" ], "scenarioOutline": [ "lut chovnatlh" ], "then": [ "* ", "vaj " ], "when": [ "* ", "qaSDI' " ] }, "tr": { "and": [ "* ", "Ve " ], "background": [ "Geçmiş" ], "but": [ "* ", "Fakat ", "Ama " ], "examples": [ "Örnekler" ], "feature": [ "Özellik" ], "given": [ "* ", "Diyelim ki " ], "name": "Turkish", "native": "Türkçe", "scenario": [ "Senaryo" ], "scenarioOutline": [ "Senaryo taslağı" ], "then": [ "* ", "O zaman " ], "when": [ "* ", "Eğer ki " ] }, "tt": { "and": [ "* ", "Һәм ", "Вә " ], "background": [ "Кереш" ], "but": [ "* ", "Ләкин ", "Әмма " ], "examples": [ "Үрнәкләр", "Мисаллар" ], "feature": [ "Мөмкинлек", "Үзенчәлеклелек" ], "given": [ "* ", "Әйтик " ], "name": "Tatar", "native": "Татарча", "scenario": [ "Сценарий" ], "scenarioOutline": [ "Сценарийның төзелеше" ], "then": [ "* ", "Нәтиҗәдә " ], "when": [ "* ", "Әгәр " ] }, "uk": { "and": [ "* ", "І ", "А також ", "Та " ], "background": [ "Передумова" ], "but": [ "* ", "Але " ], "examples": [ "Приклади" ], "feature": [ "Функціонал" ], "given": [ "* ", "Припустимо ", "Припустимо, що ", "Нехай ", "Дано " ], "name": "Ukrainian", "native": "Українська", "scenario": [ "Сценарій" ], "scenarioOutline": [ "Структура сценарію" ], "then": [ "* ", "То ", "Тоді " ], "when": [ "* ", "Якщо ", "Коли " ] }, "ur": { "and": [ "* ", "اور " ], "background": [ "پس منظر" ], "but": [ "* ", "لیکن " ], "examples": [ "مثالیں" ], "feature": [ "صلاحیت", "کاروبار کی ضرورت", "خصوصیت" ], "given": [ "* ", "اگر ", "بالفرض ", "فرض کیا " ], "name": "Urdu", "native": "اردو", "scenario": [ "منظرنامہ" ], "scenarioOutline": [ "منظر نامے کا خاکہ" ], "then": [ "* ", "پھر ", "تب " ], "when": [ "* ", "جب " ] }, "uz": { "and": [ "* ", "Ва " ], "background": [ "Тарих" ], "but": [ "* ", "Лекин ", "Бирок ", "Аммо " ], "examples": [ "Мисоллар" ], "feature": [ "Функционал" ], "given": [ "* ", "Агар " ], "name": "Uzbek", "native": "Узбекча", "scenario": [ "Сценарий" ], "scenarioOutline": [ "Сценарий структураси" ], "then": [ "* ", "Унда " ], "when": [ "* ", "Агар " ] }, "vi": { "and": [ "* ", "Và " ], "background": [ "Bối cảnh" ], "but": [ "* ", "Nhưng " ], "examples": [ "Dữ liệu" ], "feature": [ "Tính năng" ], "given": [ "* ", "Biết ", "Cho " ], "name": "Vietnamese", "native": "Tiếng Việt", "scenario": [ "Tình huống", "Kịch bản" ], "scenarioOutline": [ "Khung tình huống", "Khung kịch bản" ], "then": [ "* ", "Thì " ], "when": [ "* ", "Khi " ] }, "zh-CN": { "and": [ "* ", "而且", "并且", "同时" ], "background": [ "背景" ], "but": [ "* ", "但是" ], "examples": [ "例子" ], "feature": [ "功能" ], "given": [ "* ", "假如", "假设", "假定" ], "name": "Chinese simplified", "native": "简体中文", "scenario": [ "场景", "剧本" ], "scenarioOutline": [ "场景大纲", "剧本大纲" ], "then": [ "* ", "那么" ], "when": [ "* ", "当" ] }, "zh-TW": { "and": [ "* ", "而且", "並且", "同時" ], "background": [ "背景" ], "but": [ "* ", "但是" ], "examples": [ "例子" ], "feature": [ "功能" ], "given": [ "* ", "假如", "假設", "假定" ], "name": "Chinese traditional", "native": "繁體中文", "scenario": [ "場景", "劇本" ], "scenarioOutline": [ "場景大綱", "劇本大綱" ], "then": [ "* ", "那麼" ], "when": [ "* ", "當" ] } } behave-1.2.6/bin/i18n.yml0000644000076600000240000003611713244555737015136 0ustar jensstaff00000000000000# encoding: UTF-8 # # We use ISO 639-1 (language) and ISO 3166 alpha-2 (region - if applicable): # http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes # http://en.wikipedia.org/wiki/ISO_3166-1 # # If you want several aliases for a keyword, just separate them # with a | character. The * is a step keyword alias for all translations. # # If you do *not* want a trailing space after a keyword, end it with a < character. # (See Chinese for examples). # # This file copyright (c) 2009-2011 Mike Sassak, Gregory Hnatiuk, Aslak Hellesøy # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. "en": name: English native: English feature: Feature background: Background scenario: Scenario scenario_outline: Scenario Outline|Scenario Template examples: Examples|Scenarios given: "*|Given" when: "*|When" then: "*|Then" and: "*|And" but: "*|But" # Please keep the grammars in alphabetical order by name from here and down. "ar": name: Arabic native: العربية feature: خاصية background: الخلفية scenario: سيناريو scenario_outline: سيناريو مخطط examples: امثلة given: "*|بفرض" when: "*|متى|عندما" then: "*|اذاً|ثم" and: "*|و" but: "*|لكن" "bg": name: Bulgarian native: български feature: Функционалност background: Предистория scenario: Сценарий scenario_outline: Рамка на сценарий examples: Примери given: "*|Дадено" when: "*|Когато" then: "*|То" and: "*|И" but: "*|Но" "ca": name: Catalan native: català background: Rerefons|Antecedents feature: Característica|Funcionalitat scenario: Escenari scenario_outline: Esquema de l'escenari examples: Exemples given: "*|Donat|Donada|Atès|Atesa" when: "*|Quan" then: "*|Aleshores|Cal" and: "*|I" but: "*|Però" "cy-GB": name: Welsh native: Cymraeg background: Cefndir feature: Arwedd scenario: Scenario scenario_outline: Scenario Amlinellol examples: Enghreifftiau given: "*|Anrhegedig a" when: "*|Pryd" then: "*|Yna" and: "*|A" but: "*|Ond" "cs": name: Czech native: Česky feature: Požadavek background: Pozadí|Kontext scenario: Scénář scenario_outline: Náčrt Scénáře|Osnova scénáře examples: Příklady given: "*|Pokud|Za předpokladu" when: "*|Když" then: "*|Pak" and: "*|A|A také" but: "*|Ale" "da": name: Danish native: dansk feature: Egenskab background: Baggrund scenario: Scenarie scenario_outline: Abstrakt Scenario examples: Eksempler given: "*|Givet" when: "*|Når" then: "*|Så" and: "*|Og" but: "*|Men" "de": name: German native: Deutsch feature: Funktionalität background: Grundlage scenario: Szenario scenario_outline: Szenariogrundriss examples: Beispiele given: "*|Angenommen|Gegeben sei" when: "*|Wenn" then: "*|Dann" and: "*|Und" but: "*|Aber" "en-au": name: Australian native: Australian feature: Crikey background: Background scenario: Mate scenario_outline: Blokes examples: Cobber given: "*|Ya know how" when: "*|When" then: "*|Ya gotta" and: "*|N" but: "*|Cept" "en-lol": name: LOLCAT native: LOLCAT feature: OH HAI background: B4 scenario: MISHUN scenario_outline: MISHUN SRSLY examples: EXAMPLZ given: "*|I CAN HAZ" when: "*|WEN" then: "*|DEN" and: "*|AN" but: "*|BUT" "en-pirate": name: Pirate native: Pirate feature: Ahoy matey! background: Yo-ho-ho scenario: Heave to scenario_outline: Shiver me timbers examples: Dead men tell no tales given: "*|Gangway!" when: "*|Blimey!" then: "*|Let go and haul" and: "*|Aye" but: "*|Avast!" "en-Scouse": name: Scouse native: Scouse feature: Feature background: "Dis is what went down" scenario: "The thing of it is" scenario_outline: "Wharrimean is" examples: Examples given: "*|Givun|Youse know when youse got" when: "*|Wun|Youse know like when" then: "*|Dun|Den youse gotta" and: "*|An" but: "*|Buh" "en-tx": name: Texan native: Texan feature: Feature background: Background scenario: Scenario scenario_outline: All y'all examples: Examples given: "*|Given y'all" when: "*|When y'all" then: "*|Then y'all" and: "*|And y'all" but: "*|But y'all" "eo": name: Esperanto native: Esperanto feature: Trajto background: Fono scenario: Scenaro scenario_outline: Konturo de la scenaro examples: Ekzemploj given: "*|Donitaĵo" when: "*|Se" then: "*|Do" and: "*|Kaj" but: "*|Sed" "es": name: Spanish native: español background: Antecedentes feature: Característica scenario: Escenario scenario_outline: Esquema del escenario examples: Ejemplos given: "*|Dado|Dada|Dados|Dadas" when: "*|Cuando" then: "*|Entonces" and: "*|Y" but: "*|Pero" "et": name: Estonian native: eesti keel feature: Omadus background: Taust scenario: Stsenaarium scenario_outline: Raamstsenaarium examples: Juhtumid given: "*|Eeldades" when: "*|Kui" then: "*|Siis" and: "*|Ja" but: "*|Kuid" "fi": name: Finnish native: suomi feature: Ominaisuus background: Tausta scenario: Tapaus scenario_outline: Tapausaihio examples: Tapaukset given: "*|Oletetaan" when: "*|Kun" then: "*|Niin" and: "*|Ja" but: "*|Mutta" "fr": name: French native: français feature: Fonctionnalité background: Contexte scenario: Scénario scenario_outline: Plan du scénario|Plan du Scénario examples: Exemples given: "*|Soit|Etant donné|Etant donnée|Etant donnés|Etant données|Étant donné|Étant donnée|Étant donnés|Étant données" when: "*|Quand|Lorsque|Lorsqu'<" then: "*|Alors" and: "*|Et" but: "*|Mais" "gl": name: Galician native: galego feature: Característica background: Contexto scenario: Escenario scenario_outline: "Esbozo do escenario" examples: Exemplos given: "*|Dado|Dada|Dados|Dadas" when: "*|Cando" then: "*|Entón|Logo" and: "*|E" but: "*|Mais|Pero" "he": name: Hebrew native: עברית feature: תכונה background: רקע scenario: תרחיש scenario_outline: תבנית תרחיש examples: דוגמאות given: "*|בהינתן" when: "*|כאשר" then: "*|אז|אזי" and: "*|וגם" but: "*|אבל" "hr": name: Croatian native: hrvatski feature: Osobina|Mogućnost|Mogucnost background: Pozadina scenario: Scenarij scenario_outline: Skica|Koncept examples: Primjeri|Scenariji given: "*|Zadan|Zadani|Zadano" when: "*|Kada|Kad" then: "*|Onda" and: "*|I" but: "*|Ali" "hu": name: Hungarian native: magyar feature: Jellemző background: Háttér scenario: Forgatókönyv scenario_outline: Forgatókönyv vázlat examples: Példák given: "*|Amennyiben|Adott" when: "*|Majd|Ha|Amikor" then: "*|Akkor" and: "*|És" but: "*|De" "id": name: Indonesian native: Bahasa Indonesia feature: Fitur background: Dasar scenario: Skenario scenario_outline: Skenario konsep examples: Contoh given: "*|Dengan" when: "*|Ketika" then: "*|Maka" and: "*|Dan" but: "*|Tapi" "is": name: Icelandic native: Íslenska feature: Eiginleiki background: Bakgrunnur scenario: Atburðarás scenario_outline: Lýsing Atburðarásar|Lýsing Dæma examples: Dæmi|Atburðarásir given: "*|Ef" when: "*|Þegar" then: "*|Þá" and: "*|Og" but: "*|En" "it": name: Italian native: italiano feature: Funzionalità background: Contesto scenario: Scenario scenario_outline: Schema dello scenario examples: Esempi given: "*|Dato|Data|Dati|Date" when: "*|Quando" then: "*|Allora" and: "*|E" but: "*|Ma" "ja": name: Japanese native: 日本語 feature: フィーチャ|機能 background: 背景 scenario: シナリオ scenario_outline: シナリオアウトライン|シナリオテンプレート|テンプレ|シナリオテンプレ examples: 例|サンプル given: "*|前提<" when: "*|もし<" then: "*|ならば<" and: "*|かつ<" but: "*|しかし<|但し<|ただし<" "ko": name: Korean native: 한국어 background: 배경 feature: 기능 scenario: 시나리오 scenario_outline: 시나리오 개요 examples: 예 given: "*|조건<|먼저<" when: "*|만일<|만약<" then: "*|그러면<" and: "*|그리고<" but: "*|하지만<|단<" "lt": name: Lithuanian native: lietuvių kalba feature: Savybė background: Kontekstas scenario: Scenarijus scenario_outline: Scenarijaus šablonas examples: Pavyzdžiai|Scenarijai|Variantai given: "*|Duota" when: "*|Kai" then: "*|Tada" and: "*|Ir" but: "*|Bet" "lu": name: Luxemburgish native: Lëtzebuergesch feature: Funktionalitéit background: Hannergrond scenario: Szenario scenario_outline: Plang vum Szenario examples: Beispiller given: "*|ugeholl" when: "*|wann" then: "*|dann" and: "*|an|a" but: "*|awer|mä" "lv": name: Latvian native: latviešu feature: Funkcionalitāte|Fīča background: Konteksts|Situācija scenario: Scenārijs scenario_outline: Scenārijs pēc parauga examples: Piemēri|Paraugs given: "*|Kad" when: "*|Ja" then: "*|Tad" and: "*|Un" but: "*|Bet" "nl": name: Dutch native: Nederlands feature: Functionaliteit background: Achtergrond scenario: Scenario scenario_outline: Abstract Scenario examples: Voorbeelden given: "*|Gegeven|Stel" when: "*|Als" then: "*|Dan" and: "*|En" but: "*|Maar" "no": name: Norwegian native: norsk feature: Egenskap background: Bakgrunn scenario: Scenario scenario_outline: Scenariomal|Abstrakt Scenario examples: Eksempler given: "*|Gitt" when: "*|Når" then: "*|Så" and: "*|Og" but: "*|Men" "pl": name: Polish native: polski feature: Właściwość background: Założenia scenario: Scenariusz scenario_outline: Szablon scenariusza examples: Przykłady given: "*|Zakładając|Mając" when: "*|Jeżeli|Jeśli" then: "*|Wtedy" and: "*|Oraz|I" but: "*|Ale" "pt": name: Portuguese native: português background: Contexto feature: Funcionalidade scenario: Cenário|Cenario scenario_outline: Esquema do Cenário|Esquema do Cenario examples: Exemplos given: "*|Dado|Dada|Dados|Dadas" when: "*|Quando" then: "*|Então|Entao" and: "*|E" but: "*|Mas" "ro": name: Romanian native: română background: Context feature: Functionalitate|Funcționalitate|Funcţionalitate scenario: Scenariu scenario_outline: Structura scenariu|Structură scenariu examples: Exemple given: "*|Date fiind|Dat fiind|Dati fiind|Dați fiind|Daţi fiind" when: "*|Cand|Când" then: "*|Atunci" and: "*|Si|Și|Şi" but: "*|Dar" "ru": name: Russian native: русский feature: Функция|Функционал|Свойство background: Предыстория|Контекст scenario: Сценарий scenario_outline: Структура сценария examples: Примеры given: "*|Допустим|Дано|Пусть" when: "*|Если|Когда" then: "*|То|Тогда" and: "*|И|К тому же" but: "*|Но|А" "sv": name: Swedish native: Svenska feature: Egenskap background: Bakgrund scenario: Scenario scenario_outline: Abstrakt Scenario|Scenariomall examples: Exempel given: "*|Givet" when: "*|När" then: "*|Så" and: "*|Och" but: "*|Men" "sk": name: Slovak native: Slovensky feature: Požiadavka background: Pozadie scenario: Scenár scenario_outline: Náčrt Scenáru examples: Príklady given: "*|Pokiaľ" when: "*|Keď" then: "*|Tak" and: "*|A" but: "*|Ale" "sr-Latn": name: Serbian (Latin) native: Srpski (Latinica) feature: Funkcionalnost|Mogućnost|Mogucnost|Osobina background: Kontekst|Osnova|Pozadina scenario: Scenario|Primer scenario_outline: Struktura scenarija|Skica|Koncept examples: Primeri|Scenariji given: "*|Zadato|Zadate|Zatati" when: "*|Kada|Kad" then: "*|Onda" and: "*|I" but: "*|Ali" "sr-Cyrl": name: Serbian native: Српски feature: Функционалност|Могућност|Особина background: Контекст|Основа|Позадина scenario: Сценарио|Пример scenario_outline: Структура сценарија|Скица|Концепт examples: Примери|Сценарији given: "*|Задато|Задате|Задати" when: "*|Када|Кад" then: "*|Онда" and: "*|И" but: "*|Али" "tr": name: Turkish native: Türkçe feature: Özellik background: Geçmiş scenario: Senaryo scenario_outline: Senaryo taslağı examples: Örnekler given: "*|Diyelim ki" when: "*|Eğer ki" then: "*|O zaman" and: "*|Ve" but: "*|Fakat|Ama" "uk": name: Ukrainian native: Українська feature: Функціонал background: Передумова scenario: Сценарій scenario_outline: Структура сценарію examples: Приклади given: "*|Припустимо|Припустимо, що|Нехай|Дано" when: "*|Якщо|Коли" then: "*|То|Тоді" and: "*|І|А також|Та" but: "*|Але" "uz": name: Uzbek native: Узбекча feature: Функционал background: Тарих scenario: Сценарий scenario_outline: Сценарий структураси examples: Мисоллар given: "*|Агар" when: "*|Агар" then: "*|Унда" and: "*|Ва" but: "*|Лекин|Бирок|Аммо" "vi": name: Vietnamese native: Tiếng Việt feature: Tính năng background: Bối cảnh scenario: Tình huống|Kịch bản scenario_outline: Khung tình huống|Khung kịch bản examples: Dữ liệu given: "*|Biết|Cho" when: "*|Khi" then: "*|Thì" and: "*|Và" but: "*|Nhưng" "zh-CN": name: Chinese simplified native: 简体中文 feature: 功能 background: 背景 scenario: 场景 scenario_outline: 场景大纲 examples: 例子 given: "*|假如<" when: "*|当<" then: "*|那么<" and: "*|而且<" but: "*|但是<" "zh-TW": name: Chinese traditional native: 繁體中文 feature: 功能 background: 背景 scenario: 場景|劇本 scenario_outline: 場景大綱|劇本大綱 examples: 例子 given: "*|假設<" when: "*|當<" then: "*|那麼<" and: "*|而且<|並且<" but: "*|但是<" behave-1.2.6/bin/invoke0000755000076600000240000000025313244555737015045 0ustar jensstaff00000000000000#!/bin/sh #!/bin/bash # RUN INVOKE: From bundled ZIP file. HERE=$(dirname $0) export INVOKE_TASKS_USE_VENDOR_BUNDLES="yes" python ${HERE}/../tasks/_vendor/invoke.zip $* behave-1.2.6/bin/invoke.cmd0000644000076600000240000000031713244555737015605 0ustar jensstaff00000000000000@echo off REM RUN INVOKE: From bundled ZIP file. setlocal set HERE=%~dp0 set INVOKE_TASKS_USE_VENDOR_BUNDLES="yes" if not defined PYTHON set PYTHON=python %PYTHON% %HERE%../tasks/_vendor/invoke.zip "%*" behave-1.2.6/bin/json.format.py0000755000076600000240000001375213244555737016451 0ustar jensstaff00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Utility script to format/beautify one or more JSON files. REQUIRES: Python >= 2.6 (json module is part of Python standard library) LICENSE: BSD """ from __future__ import absolute_import __author__ = "Jens Engel" __copyright__ = "(c) 2011-2013 by Jens Engel" VERSION = "0.2.2" # -- IMPORTS: import os.path import glob import logging from optparse import OptionParser import sys try: import json except ImportError: import simplejson as json #< BACKWARD-COMPATIBLE: Python <= 2.5 # ---------------------------------------------------------------------------- # CONSTANTS: # ---------------------------------------------------------------------------- DEFAULT_INDENT_SIZE = 2 # ---------------------------------------------------------------------------- # FUNCTIONS: # ---------------------------------------------------------------------------- def json_format(filename, indent=DEFAULT_INDENT_SIZE, **kwargs): """ Format/Beautify a JSON file. :param filename: Filename of a JSON file to process. :param indent: Number of chars to indent per level (default: 4). :returns: >= 0, if successful (written=1, skipped=2). Zero(0), otherwise. :raises: ValueError, if parsing JSON file contents fails. :raises: json.JSONDecodeError, if parsing JSON file contents fails. :raises: IOError (Error 2), if file not found. """ console = kwargs.get("console", logging.getLogger("console")) encoding = kwargs.get("encoding", None) dry_run = kwargs.get("dry_run", False) if indent is None: sort_keys = False else: sort_keys = True message = "%s ..." % filename # if not (os.path.exists(filename) and os.path.isfile(filename)): # console.error("%s ERROR: file not found.", message) # return 0 contents = open(filename, "r").read() data = json.loads(contents, encoding=encoding) contents2 = json.dumps(data, indent=indent, sort_keys=sort_keys) contents2 = contents2.strip() contents2 = "%s\n" % contents2 if contents == contents2: console.info("%s SKIP (already pretty)", message) return 2 #< SKIPPED. elif not dry_run: outfile = open(filename, "w") outfile.write(contents2) outfile.close() console.warn("%s OK", message) return 1 #< OK def json_formatall(filenames, indent=DEFAULT_INDENT_SIZE, dry_run=False): """ Format/Beautify a JSON file. :param filenames: Format one or more JSON files. :param indent: Number of chars to indent per level (default: 4). :returns: 0, if successful. Otherwise, number of errors. """ errors = 0 console = logging.getLogger("console") for filename in filenames: try: result = json_format(filename, indent=indent, console=console, dry_run=dry_run) if not result: errors += 1 # except json.decoder.JSONDecodeError, e: # console.error("ERROR: %s (filename: %s)", e, filename) # errors += 1 except Exception as e: console.error("ERROR %s: %s (filename: %s)", e.__class__.__name__, e, filename) errors += 1 return errors # ---------------------------------------------------------------------------- # MAIN FUNCTION: # ---------------------------------------------------------------------------- def main(args=None): """Boilerplate for this script.""" if args is None: args = sys.argv[1:] usage_ = """%prog [OPTIONS] JsonFile [MoreJsonFiles...] Format/Beautify one or more JSON file(s).""" parser = OptionParser(usage=usage_, version=VERSION) parser.add_option("-i", "--indent", dest="indent_size", default=DEFAULT_INDENT_SIZE, type="int", help="Indent size to use (default: %default).") parser.add_option("-c", "--compact", dest="compact", action="store_true", default=False, help="Use compact format (default: %default).") parser.add_option("-n", "--dry-run", dest="dry_run", action="store_true", default=False, help="Check only if JSON is well-formed (default: %default).") options, filenames = parser.parse_args(args) #< pylint: disable=W0612 if not filenames: parser.error("OOPS, no filenames provided.") if options.compact: options.indent_size = None # -- STEP: Init logging subsystem. format_ = "json.format: %(message)s" logging.basicConfig(level=logging.WARN, format=format_) console = logging.getLogger("console") # -- DOS-SHELL SUPPORT: Perform filename globbing w/ wildcards. skipped = 0 filenames2 = [] for filename in filenames: if "*" in filenames: files = glob.glob(filename) filenames2.extend(files) elif os.path.isdir(filename): # -- CONVENIENCE-SHORTCUT: Use DIR as shortcut for JSON files. files = glob.glob(os.path.join(filename, "*.json")) filenames2.extend(files) if not files: console.info("SKIP %s, no JSON files found in dir.", filename) skipped += 1 elif not os.path.exists(filename): console.warn("SKIP %s, file not found.", filename) skipped += 1 continue else: assert os.path.exists(filename) filenames2.append(filename) filenames = filenames2 # -- NORMAL PROCESSING: errors = json_formatall(filenames, options.indent_size, dry_run=options.dry_run) console.error("Processed %d files (%d with errors, skipped=%d).", len(filenames), errors, skipped) if not filenames: errors += 1 return errors # ---------------------------------------------------------------------------- # AUTO-MAIN: # ---------------------------------------------------------------------------- if __name__ == "__main__": sys.exit(main()) behave-1.2.6/bin/jsonschema_validate.py0000755000076600000240000000717013244555737020211 0ustar jensstaff00000000000000#!/usr/bin/env python # -*- encoding: utf-8 -*- """ Validate a JSON file against its JSON schema. SEE ALSO: * https://python-jsonschema.readthedocs.org/ * https://python-jsonschema.readthedocs.org/en/latest/errors.html REQUIRES: Python >= 2.6 jsonschema >= 1.3.0 argparse """ from __future__ import absolute_import, print_function __author__ = "Jens Engel" __version__ = "0.1.0" from jsonschema import validate import argparse import os.path import sys import textwrap try: import json except ImportError: try: import simplejson as json except ImportError: sys.exit("REQUIRE: simplejson (which is not installed)") # ----------------------------------------------------------------------------- # CONSTANTS: # ----------------------------------------------------------------------------- HERE = os.path.dirname(__file__) TOP = os.path.normpath(os.path.join(HERE, "..")) SCHEMA = os.path.join(TOP, "etc", "json", "behave.json-schema") # ----------------------------------------------------------------------------- # FUNCTIONS: # ----------------------------------------------------------------------------- def jsonschema_validate(filename, schema, encoding=None): f = open(filename, "r") contents = f.read() f.close() data = json.loads(contents, encoding=encoding) return validate(data, schema) def main(args=None): """ Validate JSON files against their JSON schema. NOTE: Behave's JSON-schema is used per default. SEE ALSO: * http://json-schema.org/ * http://tools.ietf.org/html/draft-zyp-json-schema-04 """ if args is None: args = sys.argv[1:] default_schema = None if os.path.exists(SCHEMA): default_schema = SCHEMA parser = argparse.ArgumentParser( description=textwrap.dedent(main.__doc__), formatter_class=argparse.RawDescriptionHelpFormatter ) parser.add_argument("-v", "--version", action="version", version=__version__) parser.add_argument("-s", "--schema", help="JSON schema to use.") parser.add_argument("-e", "--encoding", help="Encoding for JSON/JSON schema.") parser.add_argument("files", nargs="+", metavar="JSON_FILE", help="JSON file to check.") parser.set_defaults( schema=default_schema, encoding="UTF-8" ) options = parser.parse_args(args) if not options.schema: parser.error("REQUIRE: JSON schema") elif not os.path.isfile(options.schema): parser.error("SCHEMA not found: %s" % options.schema) try: f = open(options.schema, "r") contents = f.read() f.close() schema = json.loads(contents, encoding=options.encoding) except Exception as e: msg = "ERROR: %s: %s (while loading schema)" % (e.__class__.__name__, e) sys.exit(msg) error_count = 0 for filename in options.files: validated = True more_info = None try: print("validate:", filename, "...", end=' ') jsonschema_validate(filename, schema, encoding=options.encoding) except Exception as e: more_info = "%s: %s" % (e.__class__.__name__, e) validated = False error_count += 1 if validated: print("OK") else: print("FAILED\n\n%s" % more_info) return error_count # ----------------------------------------------------------------------------- # AUTO-MAIN # ----------------------------------------------------------------------------- if __name__ == "__main__": sys.exit(main()) behave-1.2.6/bin/make_localpi.py0000755000076600000240000002156313244555737016630 0ustar jensstaff00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Utility script to create a pypi-like directory structure (localpi) from a number of Python packages in a directory of the local filesystem. DIRECTORY STRUCTURE (before): +-- downloads/ +-- alice-1.0.zip +-- alice-1.0.tar.gz +-- bob-1.3.0.tar.gz +-- bob-1.4.2.tar.gz +-- charly-1.0.tar.bz2 DIRECTORY STRUCTURE (afterwards): +-- downloads/ +-- simple/ | +-- alice/index.html --> ../../alice-*.* | +-- bob/index.html --> ../../bob-*.* | +-- charly/index.html --> ../../charly-*.* | +-- index.html --> alice/, bob/, ... +-- alice-1.0.zip +-- alice-1.0.tar.gz +-- bob-1.3.0.tar.gz +-- bob-1.4.2.tar.gz +-- charly-1.0.tar.bz2 USAGE EXAMPLE: mkdir -p /tmp/downloads pip install --download=/tmp/downloads argparse Jinja2 make_localpi.py /tmp/downloads pip install --index-url=file:///tmp/downloads/simple argparse Jinja2 ALTERNATIVE: pip install --download=/tmp/downloads argparse Jinja2 pip install --find-links=/tmp/downloads --no-index argparse Jinja2 """ from __future__ import absolute_import, print_function, with_statement from fnmatch import fnmatch import os.path import shutil import sys __author__ = "Jens Engel" __version__ = "0.2" __license__ = "BSD" __copyright__ = "(c) 2013 by Jens Engel" class Package(object): """ Package entity that keeps track of: * one or more versions of this package * one or more archive types """ PATTERNS = [ "*.egg", "*.exe", "*.whl", "*.zip", "*.tar.gz", "*.tar.bz2", "*.7z" ] def __init__(self, filename, name=None): if not name and filename: name = self.get_pkgname(filename) self.name = name self.files = [] if filename: self.files.append(filename) @property def versions(self): versions_info = [ self.get_pkgversion(p) for p in self.files ] return versions_info @classmethod def split_pkgname_parts(cls, filename): basename = cls.splitext(os.path.basename(filename)) if basename.startswith("http") and r"%2F" in basename: # -- PIP DOWNLOAD-CACHE PACKAGE FILE NAME SCHEMA: pos = basename.rfind("%2F") basename = basename[pos+3:] version_part_index = 0 parts = basename.split("-") for index, part in enumerate(parts): if index == 0: continue elif part and part[0].isdigit() and len(part) >= 3: version_part_index = index break name = "-".join(parts[:version_part_index]) version = "0.0" remainder = None if version_part_index > 0: version = parts[version_part_index] if version_part_index+1 < len(parts): remainder = "-".join(parts[version_part_index+1:]) assert name, "OOPS: basename=%s, name='%s'" % (basename, name) return (name, version, remainder) @classmethod def get_pkgname(cls, filename): return cls.split_pkgname_parts(filename)[0] @classmethod def get_pkgversion(cls, filename): return cls.split_pkgname_parts(filename)[1] @classmethod def make_pkgname_with_version(cls, filename): pkg_name = cls.get_pkgname(filename) pkg_version = cls.get_pkgversion(filename) return "%s-%s" % (pkg_name, pkg_version) @staticmethod def splitext(filename): fname = os.path.splitext(filename)[0] if fname.endswith(".tar"): fname = os.path.splitext(fname)[0] return fname @classmethod def isa(cls, filename): basename = os.path.basename(filename) if basename.startswith("."): return False for pattern in cls.PATTERNS: if fnmatch(filename, pattern): return True return False def collect_packages(package_dir, package_map=None): if package_map is None: package_map = {} packages = [] for filename in sorted(os.listdir(package_dir)): if not Package.isa(filename): continue pkg_filepath = os.path.join(package_dir, filename) package_name = Package.get_pkgname(pkg_filepath) package = package_map.get(package_name, None) if not package: # -- NEW PACKAGE DETECTED: Store/register package. package = Package(pkg_filepath) package_map[package.name] = package packages.append(package) else: # -- SAME PACKAGE: Collect other variant/version. package.files.append(pkg_filepath) return packages def make_index_for(package, index_dir, verbose=True): """ Create an 'index.html' for one package. :param package: Package object to use. :param index_dir: Where 'index.html' should be created. """ index_template = """\ {title}

{title}

    {packages}
""" item_template = '
  • {0}
  • ' index_filename = os.path.join(index_dir, "index.html") if not os.path.isdir(index_dir): os.makedirs(index_dir) parts = [] for pkg_filename in package.files: pkg_name = os.path.basename(pkg_filename) if pkg_name == "index.html": # -- ROOT-INDEX: pkg_name = os.path.basename(os.path.dirname(pkg_filename)) else: # pkg_name = package.splitext(pkg_name) pkg_name = package.make_pkgname_with_version(pkg_filename) pkg_relpath_to = os.path.relpath(pkg_filename, index_dir) parts.append(item_template.format(pkg_name, pkg_relpath_to)) if not parts: print("OOPS: Package %s has no files" % package.name) return if verbose: root_index = not Package.isa(package.files[0]) if root_index: info = "with %d package(s)" % len(package.files) else: package_versions = sorted(set(package.versions)) info = ", ".join(reversed(package_versions)) message = "%-30s %s" % (package.name, info) print(message) with open(index_filename, "w") as f: packages = "\n".join(parts) text = index_template.format(title=package.name, packages=packages) f.write(text.strip()) f.close() def make_package_index(download_dir): """ Create a pypi server like file structure below download directory. :param download_dir: Download directory with packages. EXAMPLE BEFORE: +-- downloads/ +-- wheelhouse/bob-1.4.2-*.whl +-- alice-1.0.zip +-- alice-1.0.tar.gz +-- bob-1.3.0.tar.gz +-- bob-1.4.2.tar.gz +-- charly-1.0.tar.bz2 EXAMPLE AFTERWARDS: +-- downloads/ +-- simple/ | +-- alice/index.html --> ../../alice-*.* | +-- bob/index.html --> ../../bob-*.* | +-- charly/index.html --> ../../charly-*.* | +-- index.html --> alice/index.html, bob/index.html, ... +-- wheelhouse/bob-1.4.2-*.whl +-- alice-1.0.zip +-- alice-1.0.tar.gz +-- bob-1.3.0.tar.gz +-- bob-1.4.2.tar.gz +-- charly-1.0.tar.bz2 """ if not os.path.isdir(download_dir): raise ValueError("No such directory: %r" % download_dir) pkg_rootdir = os.path.join(download_dir, "simple") if os.path.isdir(pkg_rootdir): shutil.rmtree(pkg_rootdir, ignore_errors=True) os.mkdir(pkg_rootdir) package_dirs = [download_dir] wheelhouse_dir = os.path.join(download_dir, "wheelhouse") if os.path.isdir(wheelhouse_dir): print("Using wheelhouse: %s" % wheelhouse_dir) package_dirs.append(wheelhouse_dir) # -- STEP: Collect all packages. package_map = {} packages = [] for package_dir in package_dirs: new_packages = collect_packages(package_dir, package_map) packages.extend(new_packages) # -- STEP: Make local PYTHON PACKAGE INDEX. root_package = Package(None, "Python Package Index") root_package.files = [ os.path.join(pkg_rootdir, pkg.name, "index.html") for pkg in packages ] make_index_for(root_package, pkg_rootdir) for package in packages: index_dir = os.path.join(pkg_rootdir, package.name) make_index_for(package, index_dir) # ----------------------------------------------------------------------------- # MAIN: # ----------------------------------------------------------------------------- if __name__ == "__main__": if (len(sys.argv) != 2) or "-h" in sys.argv[1:] or "--help" in sys.argv[1:]: print("USAGE: %s DOWNLOAD_DIR" % os.path.basename(sys.argv[0])) print(__doc__) sys.exit(1) make_package_index(sys.argv[1]) behave-1.2.6/bin/project_bootstrap.sh0000755000076600000240000000153313244555737017730 0ustar jensstaff00000000000000#!/bin/sh # ============================================================================= # BOOTSTRAP PROJECT: Download all requirements # ============================================================================= # : ${PIP_INDEX_URL="https://pypi.python.org/simple"} # test ${PIP_DOWNLOADS_DIR} || mkdir -p ${PIP_DOWNLOADS_DIR} # tox -e init # export PIP_DOWNLOAD_DIR set -e # -- CONFIGURATION: HERE=`dirname $0` TOP="${HERE}/.." : ${PIP_DOWNLOAD_DIR:="${TOP}/downloads"} REQUIREMENTS_FILE=${TOP}/py.requirements/all.txt if [ $# -ge 1 ]; then PIP_DOWNLOAD_DIR="$1" fi if [ $# -ge 2 ]; then REQUIREMENTS_FILE="$2" fi # -- EXECUTE STEPS: echo "USING: PIP_DOWNLOAD_DIR=${PIP_DOWNLOAD_DIR}" ${HERE}/toxcmd.py mkdir ${PIP_DOWNLOAD_DIR} pip install --download=${PIP_DOWNLOAD_DIR} -r ${REQUIREMENTS_FILE} ${HERE}/make_localpi.py ${PIP_DOWNLOAD_DIR} behave-1.2.6/bin/toxcmd.py0000755000076600000240000002246613244555737015511 0ustar jensstaff00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- """ Provides a command container for additional tox commands, used in "tox.ini". COMMANDS: * copytree * copy * py2to3 REQUIRES: * argparse """ from glob import glob import argparse import inspect import os.path import shutil import six import sys __author__ = "Jens Engel" __copyright__ = "(c) 2013 by Jens Engel" __license__ = "BSD" # ----------------------------------------------------------------------------- # CONSTANTS: # ----------------------------------------------------------------------------- VERSION = "0.1.0" FORMATTER_CLASS = argparse.RawDescriptionHelpFormatter # ----------------------------------------------------------------------------- # SUBCOMMAND: copytree # ----------------------------------------------------------------------------- def command_copytree(args): """ Copy one or more source directory(s) below a destination directory. Parts of the destination directory path are created if needed. Similar to the UNIX command: 'cp -R srcdir destdir' """ for srcdir in args.srcdirs: basename = os.path.basename(srcdir) destdir2 = os.path.normpath(os.path.join(args.destdir, basename)) if os.path.exists(destdir2): shutil.rmtree(destdir2) sys.stdout.write("copytree: %s => %s\n" % (srcdir, destdir2)) shutil.copytree(srcdir, destdir2) return 0 def setup_parser_copytree(parser): parser.add_argument("srcdirs", nargs="+", help="Source directory(s)") parser.add_argument("destdir", help="Destination directory") command_copytree.usage = "%(prog)s srcdir... destdir" command_copytree.short = "Copy source dir(s) below a destination directory." command_copytree.setup_parser = setup_parser_copytree # ----------------------------------------------------------------------------- # SUBCOMMAND: copy # ----------------------------------------------------------------------------- def command_copy(args): """ Copy one or more source-files(s) to a destpath (destfile or destdir). Destdir mode is used if: * More than one srcfile is provided * Last parameter ends with a slash ("/"). * Last parameter is an existing directory Destination directory path is created if needed. Similar to the UNIX command: 'cp srcfile... destpath' """ sources = args.sources destpath = args.destpath source_files = [] for file_ in sources: if "*" in file_: selected = glob(file_) source_files.extend(selected) elif os.path.isfile(file_): source_files.append(file_) if destpath.endswith("/") or os.path.isdir(destpath) or len(sources) > 1: # -- DESTDIR-MODE: Last argument is a directory. destdir = destpath else: # -- DESTFILE-MODE: Copy (and rename) one file. assert len(source_files) == 1 destdir = os.path.dirname(destpath) # -- WORK-HORSE: Copy one or more files to destpath. if not os.path.isdir(destdir): sys.stdout.write("copy: Create dir %s\n" % destdir) os.makedirs(destdir) for source in source_files: destname = os.path.join(destdir, os.path.basename(source)) sys.stdout.write("copy: %s => %s\n" % (source, destname)) shutil.copy(source, destname) return 0 def setup_parser_copy(parser): parser.add_argument("sources", nargs="+", help="Source files.") parser.add_argument("destpath", help="Destination path") command_copy.usage = "%(prog)s sources... destpath" command_copy.short = "Copy one or more source files to a destinition." command_copy.setup_parser = setup_parser_copy # ----------------------------------------------------------------------------- # SUBCOMMAND: mkdir # ----------------------------------------------------------------------------- def command_mkdir(args): """ Create a non-existing directory (or more ...). If the directory exists, the step is skipped. Similar to the UNIX command: 'mkdir -p dir' """ errors = 0 for directory in args.dirs: if os.path.exists(directory): if not os.path.isdir(directory): # -- SANITY CHECK: directory exists, but as file... sys.stdout.write("mkdir: %s\n" % directory) sys.stdout.write("ERROR: Exists already, but as file...\n") errors += 1 else: # -- NORMAL CASE: Directory does not exits yet. assert not os.path.isdir(directory) sys.stdout.write("mkdir: %s\n" % directory) os.makedirs(directory) return errors def setup_parser_mkdir(parser): parser.add_argument("dirs", nargs="+", help="Directory(s)") command_mkdir.usage = "%(prog)s dir..." command_mkdir.short = "Create non-existing directory (or more...)." command_mkdir.setup_parser = setup_parser_mkdir # ----------------------------------------------------------------------------- # SUBCOMMAND: py2to3 # ----------------------------------------------------------------------------- command_py2to3_work_around3k = six.PY3 def command_py2to3(args): """ Apply '2to3' tool (Python2 to Python3 conversion tool) to Python sources. """ from lib2to3.main import main args2 = [] if command_py2to3_work_around3k: if args.no_diffs: args2.append("--no-diffs") if args.write: args2.append("-w") if args.nobackups: args2.append("-n") args2.extend(args.sources) sys.exit(main("lib2to3.fixes", args=args2)) def setup_parser4py2to3(parser): if command_py2to3_work_around3k: parser.add_argument("--no-diffs", action="store_true", help="Don't show diffs of the refactoring") parser.add_argument("-w", "--write", action="store_true", help="Write back modified files") parser.add_argument("-n", "--nobackups", action="store_true", default=False, help="Don't write backups for modified files.") parser.add_argument("sources", nargs="+", help="Source files.") command_py2to3.name = "2to3" command_py2to3.usage = "%(prog)s sources..." command_py2to3.short = "Apply python's 2to3 tool to Python sources." command_py2to3.setup_parser = setup_parser4py2to3 # ----------------------------------------------------------------------------- # COMMAND HELPERS/UTILS: # ----------------------------------------------------------------------------- def discover_commands(): commands = [] for name, func in inspect.getmembers(inspect.getmodule(toxcmd_main)): if name.startswith("__"): continue if name.startswith("command_") and callable(func): command_name0 = name.replace("command_", "") command_name = getattr(func, "name", command_name0) commands.append(Command(command_name, func)) return commands class Command(object): def __init__(self, name, func): assert isinstance(name, six.string_types) assert callable(func) self.name = name self.func = func self.parser = None def setup_parser(self, command_parser): setup_parser = getattr(self.func, "setup_parser", None) if setup_parser and callable(setup_parser): setup_parser(command_parser) else: command_parser.add_argument("args", nargs="*") @property def usage(self): usage = getattr(self.func, "usage", None) return usage @property def short_description(self): short_description = getattr(self.func, "short", "") return short_description @property def description(self): return inspect.getdoc(self.func) def __call__(self, args): return self.func(args) # ----------------------------------------------------------------------------- # MAIN-COMMAND: # ----------------------------------------------------------------------------- def toxcmd_main(args=None): """Command util with subcommands for tox environments.""" usage = "USAGE: %(prog)s [OPTIONS] COMMAND args..." if args is None: args = sys.argv[1:] # -- STEP: Build command-line parser. parser = argparse.ArgumentParser(description=inspect.getdoc(toxcmd_main), formatter_class=FORMATTER_CLASS) common_parser = parser.add_argument_group("Common options") common_parser.add_argument("--version", action="version", version=VERSION) subparsers = parser.add_subparsers(help="commands") for command in discover_commands(): command_parser = subparsers.add_parser(command.name, usage=command.usage, description=command.description, help=command.short_description, formatter_class=FORMATTER_CLASS) command_parser.set_defaults(func=command) command.setup_parser(command_parser) command.parser = command_parser # -- STEP: Process command-line and run command. options = parser.parse_args(args) command_function = options.func return command_function(options) # ----------------------------------------------------------------------------- # MAIN: # ----------------------------------------------------------------------------- if __name__ == "__main__": sys.exit(toxcmd_main()) behave-1.2.6/bin/toxcmd3.py0000755000076600000240000002256713244555737015576 0ustar jensstaff00000000000000#!/usr/bin/env python3 # -*- coding: UTF-8 -*- """ Provides a command container for additional tox commands, used in "tox.ini". COMMANDS: * copytree * copy * py2to3 REQUIRES: * argparse """ from glob import glob import argparse import inspect import os.path import shutil import sys import collections __author__ = "Jens Engel" __copyright__ = "(c) 2013 by Jens Engel" __license__ = "BSD" # ----------------------------------------------------------------------------- # CONSTANTS: # ----------------------------------------------------------------------------- VERSION = "0.1.0" FORMATTER_CLASS = argparse.RawDescriptionHelpFormatter # ----------------------------------------------------------------------------- # SUBCOMMAND: copytree # ----------------------------------------------------------------------------- def command_copytree(args): """ Copy one or more source directory(s) below a destination directory. Parts of the destination directory path are created if needed. Similar to the UNIX command: 'cp -R srcdir destdir' """ for srcdir in args.srcdirs: basename = os.path.basename(srcdir) destdir2 = os.path.normpath(os.path.join(args.destdir, basename)) if os.path.exists(destdir2): shutil.rmtree(destdir2) sys.stdout.write("copytree: %s => %s\n" % (srcdir, destdir2)) shutil.copytree(srcdir, destdir2) return 0 def setup_parser_copytree(parser): parser.add_argument("srcdirs", nargs="+", help="Source directory(s)") parser.add_argument("destdir", help="Destination directory") command_copytree.usage = "%(prog)s srcdir... destdir" command_copytree.short = "Copy source dir(s) below a destination directory." command_copytree.setup_parser = setup_parser_copytree # ----------------------------------------------------------------------------- # SUBCOMMAND: copy # ----------------------------------------------------------------------------- def command_copy(args): """ Copy one or more source-files(s) to a destpath (destfile or destdir). Destdir mode is used if: * More than one srcfile is provided * Last parameter ends with a slash ("/"). * Last parameter is an existing directory Destination directory path is created if needed. Similar to the UNIX command: 'cp srcfile... destpath' """ sources = args.sources destpath = args.destpath source_files = [] for file_ in sources: if "*" in file_: selected = glob(file_) source_files.extend(selected) elif os.path.isfile(file_): source_files.append(file_) if destpath.endswith("/") or os.path.isdir(destpath) or len(sources) > 1: # -- DESTDIR-MODE: Last argument is a directory. destdir = destpath else: # -- DESTFILE-MODE: Copy (and rename) one file. assert len(source_files) == 1 destdir = os.path.dirname(destpath) # -- WORK-HORSE: Copy one or more files to destpath. if not os.path.isdir(destdir): sys.stdout.write("copy: Create dir %s\n" % destdir) os.makedirs(destdir) for source in source_files: destname = os.path.join(destdir, os.path.basename(source)) sys.stdout.write("copy: %s => %s\n" % (source, destname)) shutil.copy(source, destname) return 0 def setup_parser_copy(parser): parser.add_argument("sources", nargs="+", help="Source files.") parser.add_argument("destpath", help="Destination path") command_copy.usage = "%(prog)s sources... destpath" command_copy.short = "Copy one or more source files to a destinition." command_copy.setup_parser = setup_parser_copy # ----------------------------------------------------------------------------- # SUBCOMMAND: mkdir # ----------------------------------------------------------------------------- def command_mkdir(args): """ Create a non-existing directory (or more ...). If the directory exists, the step is skipped. Similar to the UNIX command: 'mkdir -p dir' """ errors = 0 for directory in args.dirs: if os.path.exists(directory): if not os.path.isdir(directory): # -- SANITY CHECK: directory exists, but as file... sys.stdout.write("mkdir: %s\n" % directory) sys.stdout.write("ERROR: Exists already, but as file...\n") errors += 1 else: # -- NORMAL CASE: Directory does not exits yet. assert not os.path.isdir(directory) sys.stdout.write("mkdir: %s\n" % directory) os.makedirs(directory) return errors def setup_parser_mkdir(parser): parser.add_argument("dirs", nargs="+", help="Directory(s)") command_mkdir.usage = "%(prog)s dir..." command_mkdir.short = "Create non-existing directory (or more...)." command_mkdir.setup_parser = setup_parser_mkdir # ----------------------------------------------------------------------------- # SUBCOMMAND: py2to3 # ----------------------------------------------------------------------------- command_py2to4_work_around3k = True def command_py2to3(args): """ Apply '2to3' tool (Python2 to Python3 conversion tool) to Python sources. """ from lib2to3.main import main args2 = [] if command_py2to4_work_around3k: if args.no_diffs: args2.append("--no-diffs") if args.write: args2.append("-w") if args.nobackups: args2.append("-n") args2.extend(args.sources) sys.exit(main("lib2to3.fixes", args=args2)) def setup_parser4py2to3(parser): if command_py2to4_work_around3k: parser.add_argument("--no-diffs", action="store_true", help="Don't show diffs of the refactoring") parser.add_argument("-w", "--write", action="store_true", help="Write back modified files") parser.add_argument("-n", "--nobackups", action="store_true", default=False, help="Don't write backups for modified files.") parser.add_argument("sources", nargs="+", help="Source files.") command_py2to3.name = "2to3" command_py2to3.usage = "%(prog)s sources..." command_py2to3.short = "Apply python's 2to3 tool to Python sources." command_py2to3.setup_parser = setup_parser4py2to3 # ----------------------------------------------------------------------------- # COMMAND HELPERS/UTILS: # ----------------------------------------------------------------------------- def discover_commands(): commands = [] for name, func in inspect.getmembers(inspect.getmodule(toxcmd_main)): if name.startswith("__"): continue if name.startswith("command_") and isinstance(func, collections.Callable): command_name0 = name.replace("command_", "") command_name = getattr(func, "name", command_name0) commands.append(Command(command_name, func)) return commands class Command(object): def __init__(self, name, func): assert isinstance(name, str) assert isinstance(func, collections.Callable) self.name = name self.func = func self.parser = None def setup_parser(self, command_parser): setup_parser = getattr(self.func, "setup_parser", None) if setup_parser and isinstance(setup_parser, collections.Callable): setup_parser(command_parser) else: command_parser.add_argument("args", nargs="*") @property def usage(self): usage = getattr(self.func, "usage", None) return usage @property def short_description(self): short_description = getattr(self.func, "short", "") return short_description @property def description(self): return inspect.getdoc(self.func) def __call__(self, args): return self.func(args) # ----------------------------------------------------------------------------- # MAIN-COMMAND: # ----------------------------------------------------------------------------- def toxcmd_main(args=None): """Command util with subcommands for tox environments.""" usage = "USAGE: %(prog)s [OPTIONS] COMMAND args..." if args is None: args = sys.argv[1:] # -- STEP: Build command-line parser. parser = argparse.ArgumentParser(description=inspect.getdoc(toxcmd_main), formatter_class=FORMATTER_CLASS) common_parser = parser.add_argument_group("Common options") common_parser.add_argument("--version", action="version", version=VERSION) subparsers = parser.add_subparsers(help="commands") for command in discover_commands(): command_parser = subparsers.add_parser(command.name, usage=command.usage, description=command.description, help=command.short_description, formatter_class=FORMATTER_CLASS) command_parser.set_defaults(func=command) command.setup_parser(command_parser) command.parser = command_parser # -- STEP: Process command-line and run command. options = parser.parse_args(args) command_function = options.func return command_function(options) # ----------------------------------------------------------------------------- # MAIN: # ----------------------------------------------------------------------------- if __name__ == "__main__": sys.exit(toxcmd_main()) behave-1.2.6/CHANGES.rst0000644000076600000240000006162013244561344014652 0ustar jensstaff00000000000000Version History =============================================================================== Version: 1.2.7 (unreleased) ------------------------------------------------------------------------------- GOALS: - Improve support for Windows (continued) - FIX: Unicode problems on Windows (in behave-1.2.6) - FIX: Regression test problems on Windows (in behave-1.2.6) - Drop support for Python 2.6 Version: 1.2.6 (2018-02-25) ------------------------------------------------------------------------------- GOALS: - Improve support for Windows DOCUMENTATION: * issue #625: Formatter documentation is inaccurate for some methods (reported by: throwable-one) * pull #618: Fix a typo in the background section of gherkin docs (provided by: mrrn) * pull #609: Describe execute_steps() behaviour correctly (provided by: mixxorz) * pull #603: Update typo tutorial.rst (provided by: fnaval) * pull #601: Add Flask integration chapter to documentation (provided by: bittner) * pull #580: Fix some dead ecosystem links (provided by: smadness) * pull #579: Add explanation for step_impl function name (provided by: bittner) * issue #574: flake8 reports F811 redefinition of unused 'step_impl' (fixed by #579). * pull #545: Spell "section" correctly (provided by: chelmertz) * pull #489: Fix link to Selenium docs in Django chapter (provided by: bittner) * pull #469: Fix typo in "formatters.rst" (provided by: ab9-er) * pull #443: Fixing grammar in philosophy.rst (provided by: jamesroutley) * pull #441: Integrate hint on testing more nicely (provided by: bittner) * pull #429: Replace "Manual Integration" by "Automation Libraries" section (provided by: bittner) * pull #379: Correct wording in README.rst (provided by: franklinchou) * pull #362: docs/tutorial.rst: fixed non-monospace font (provided by: spacediver) * pull #359: Update documentation related to Django (behave-django) (provided by: bittner) * pull #326: docs/tutorial.rst: Correct features directory path creation (provided by: memee) * issue #356: docs/api.rst: type in implementation (submitted by: tomxtobin) * pull #335: docs/api.rst: execute_steps() example (provided by: miabbott) * pull #339: Adapt wording in install.rst (provided by: charleswhchan) * pull #338: docs/philosophy.rst: Correct to uppercase in example (provided by: charleswhchan) * issue #323: Update Django Example to work with version >=1.7 (submitted by: mpetyx, provided by: bittner) * pull #327: Fix typo in Django doc (provided by: nikolas) * pull #321: Update Django integration (provided by: bittner, contains: #315, #316) * FIX: cmdline/config-param doc-generator, avoid duplicated param entries (related to: #318) * issue #317: Update comparison: lettuce tags (provided by: ramiabughazaleh) * pull #307: Typo in readme (provided by: dflock) * pull #305: behave.rst related fixes reapplied (provided by: bittner) * pull #292: Use title-cased keywords in tutorial scenario (provided by: neoblackcap) * pull #291: Tiny tweaks in tutorial docs (provided by: bernardpaulus) SITE: * pull #626: Formatting issue in stale-bot config (provided by: teapow) * pull #343: Update/fix badges in README (provided by: mixxorz) ENHANCEMENTS: * fixtures: Add concept to simplify setup/cleanup tasks for scenario/feature/test-run * context-cleanups: Use context.add_cleanup() to perform cleanups after scenario/feature/test-run. * Tagged Examples: Examples in a ScenarioOutline can now have tags. * pull #596: Add missing Czech translation (provided by: hason) * pull #554: Adds galician language (provided by: carlosgoce) * pull #447: behave settings from tox.ini (provided by: bittner) * issue #411: Support multiple active-tags with same category (submitted by: Kani999) * issue #409: Support async/@asyncio.coroutine steps (submitted by: dcarp) * issue #357: Add language attribute to Feature class * pull #328: Auto-retry failed scenarios in unreliable environment (provided by: MihaiBalint, robertknight) * issue #302: Support escaped-pipe in Gherkin table cell value (provided by: connorsml, pull #360) * issue #301: Support default tags in configfile * issue #299: Runner can continue after a failed step (same as: #314) * issue #197: Hooks processing should be more exception safe (provided by: vrutkovs, jenisys, pull #205) FORMATTERS: * pull #446: Remove Formatter scenario_outline(), examples() method (provided by: aisbaa, jenisys) * pull #448: json: Add status to scenarios in JSON report (provided by: remcowesterhoud) * issue #462: json: Invalid JSON output when no features are selected (submitted by: remcowesterhoud) * pull #423: sphinx.steps: Support ref link for each step (provided by: ZivThaller) * pull #460: pretty: Print the step implementation location when dry-run (provided by: unklhe, jenisys) REPORTERS: * junit: Add timestamp and hostname attributes to testsuite XML element. * junit: Support to tweak output with userdata (experimental). * junit: Support scenario hook-errors with JUnitReporter (related to: #466) CHANGES: * status: Use Status enum-class for feature/scenario/step.status (was: string) * hook-processing: Skips now feature/scenario/step if before-hook fails (related to: #454) * parser: language comment in feature file has higher priority than --lang option (related to: #334). * issue #385: before_scenario/before_feature called too late (submitted by: BRevzin) FIXED: * issue #606: Using name option w/ special unicode chars (submitted by: alluir42) * issue #547: Crash when using step definition with optional cfparse parts (provided by: ftartaggia, jenisys) * pull #599: Steps from another Windows drive (provided by: psicopep) * issue #582: behave emitting PendingDeprecationWarning messages (submitted by: adamjcooper) * pull #476: scenario.status when scenario without steps is skipped (provided by: ar45, jenisys) * pull #471: convert an object to unicode (py2) using __unicode__ method first unicode (provided by: ftartaggia) * issue #458: UnicodeEncodeError inside naked except block in __main__.py (submitted by: mseery) * issue #453: Unicode chars are broken in stacktrace (submitted by: throwable-one) * issue #455: Restore backward compatibility to Cucumber style RegexMatcher (submitted by: avabramov) * issue #449: Unicode is processed incorrectly for Py2 in "textutil.text" (submitted by: throwable-one) * issue #446: after_scenario HOOK-ERROR asserts with jUnit reporter (submitted by: lagin) * issue #424: Exception message with unicode characters in nested steps (submitted by: yucer) * issue #416: JUnit report messages cut off (submitted by: remcowesterhoud, provided by: bittner) * issue #414: Support for Jython 2.7 (submitted by: gabtwi...) * issue #384: Active Tags fail with ScenarioOutline (submitted by: BRevzin) * issue #383: Handle (custom) Type parsing errors better (submitted by: zsoldosp) * pull #382: fix typo in tag name (provided by: zsoldosp) * issue #361: utf8 file with BOM (provided by: karulis) * issue #349: ScenarioOutline skipped with --format=json * issue #336: Stacktrace contents getting illegal characters inserted with text function (submited by: fj40bryan) * issue #330: Skipped scenarios are included in junit reports when --no-skipped is specified (provided by: vrutkovs, pull #331) * issue #320: Userdata is case-insensitive when read from config file (provided by: mixxorz) * issue #319: python-version requirements in behave.whl for Python2.6 (submitted by: darkfoxprime) * issue #310: Use setuptools_behave.py with behave module * issue #309: behave --lang-list fails on Python3 (and Python2) * issue #300: UnicodeDecodeError when read steps.py (similar to: #361) * issue #288: Use print function instead print statement in environment/steps files Version: 1.2.5 (2015-01-31) ------------------------------------------------------------------------------- :Same as: Version 1.2.5a1 (unreleased). NEWS and CHANGES: - General: * Improve support for Python3 (py3.3, py3.4; #268) * Various unicode related fixes (Unicode errors with non-ASCII, etc.) * Drop support for Python 2.5 - Running: * ScenarioOutline: Annotates name with row.id, ... to better represent row. * NEW: Active Tags, see docs (`New and Noteworthy`_). * NEW: Test stages, see docs (`New and Noteworthy`_). * NEW: User-specific configuration data, see docs (`New and Noteworthy`_). * CHANGED: Undefined step snippet uses now NotImplementedError (related to: #254) - Model: * ScenarioOutline: Various improvements, see docs (`New and Noteworthy`_). - Formatters: * plain: Can now show tags, but currently disabled per default * NEW: steps.catalog: Readable summary of all steps (similar to: steps.doc, #271) * NEW: User-defined formatters, see docs (`New and Noteworthy`_). ENHANCEMENTS: * pull #285: Travis CI improvements to use container environment, etc. (provided by: thedrow) * pull #272: Use option role to format command line arg docs (provided by: helenst) * pull #271: Provide steps.catalog formatter (provided by: berdroid) * pull #261: Support "setup.cfg" as configuration file, too (provided by: bittner) * pull #260: Documentation tweaks and typo fixes (provided by: bittner) * pull #254: Undefined step raises NotImplementedError instead of assert False (provided by: mhfrantz) * issue #242: JUnitReporter can show scenario tags (provided by: rigomes) * issue #240: Test Stages with different step implementations (provided by: attilammagyar, jenisys) * issue #238: Allow to skip scenario in step function (provided by: hotgloupi, jenisys) * issue #228: Exclude scenario fron run (provided by: jdeppe, jenisys) * issue #227: Add a way to add command line options to behave (provided by: attilammagyar, jenisys) FIXED: * pull #283: Fix "fork me" image in docs (provided by: frodopwns) * issue #280: Fix missing begin/end-markers in RegexMatcher (provided by: tomekwszelaki, jenisys) * pull #268: Fix py3 compatibility with all tests passed (provided by: sunliwen) * pull #252: Related to #251 (provided by: mcepl) * pull #190: UnicodeDecodeError in tracebacks (provided by: b3ni, vrutkovs, related to: #226, #230) * issue #257: Fix JUnitReporter (XML) for Python3 (provided by: actionless) * issue #249: Fix a number of docstring problems (provided by: masak) * issue #253: Various problems in PrettyFormatter.exception() * issue #251: Unicode crash in model.py (provided by: mcepl, jenisys) * issue #236: Command line docs are confusing (solved by: #272) * issue #230: problem with assert message that contains ascii over 128 value (provided by: jenisys) * issue #226: UnicodeDecodeError in tracebacks (provided by: md1023, karulis, jenisys) * issue #221: Fix some PY2/PY3 incompatibilities (provided by: johbo) * pull #219: IDE's unknown modules import issue (provided by: xbx) * issue #216: Using --wip option does not disable ANSI escape sequences (coloring). * issue #119: Python3 support for behave (solved by: #268 and ...) * issue #82: JUnitReporter fails with Python 3.x (fixed with: #257, #268) .. _`New and Noteworthy`: https://github.com/behave/behave/blob/master/docs/new_and_noteworthy.rst Version: 1.2.4 (2014-03-02) ------------------------------------------------------------------------------- :Same as: Version 1.2.4a1 (unreleased). NEWS and CHANGES: - Running: * ABORT-BY-USER: Better handle KeyboardInterrupt to abort a test run. * feature list files (formerly: feature configfiles) support wildcards. * Simplify and improve setup of logging subsystem (related to: #143, #177) - Step matchers: * cfparse: Step matcher with "Cardinality Field" support (was: optional). - Formatters: * steps.usage: Avoid duplicated steps usage due to Scenario Outlines. * json: Ensures now that matched step params (match args) cause valid JSON. IMPROVEMENT: * issue #108: behave.main() can be called with command-line args (provided by: medwards, jenisys) * issue #172: Subfolders in junit XML filenames (provided by: roignac). * issue #203: Integration with pdb (debug on error; basic support) * Simple test runner to run behave tests from "setup.py" FIXED: * issue #143: Logging starts with a StreamHandler way too early (provided by: jtatum, jenisys). * issue #175: Scenario isn't marked as 'failed' when Background step fails * issue #177: Cannot setup logging_format * issue #181: Escape apostrophes in undefined steps snippets * issue #184: TypeError when running behave with --include option (provided by: s1ider). * issue #186: ScenarioOutline uses wrong return value when if fails (provided by: mdavezac) * issue #188: Better diagnostics if nested step is undefined * issue #191: Using context.execute_steps() may change context.table/.text * issue #194: Nested steps prevent that original stdout/stderr is restored * issue #199: behave tag expression bug when or-not logic is used Version: 1.2.3 (2013-07-08) ------------------------------------------------------------------------------- Latest stable version. Same as last development version. Version: 1.2.3a20 (2013-07-08) ------------------------------------------------------------------------------- NEWS and CHANGES: - Install: * Require now parse>=1.6.2 to enforce log-bugfix #14 (was: parse>=1.6) - Running: * load_step_definitions: Are now sorted before loading (due to: Linux, ...). * NEW: Use lazy-loading for formatters if possible (speed up self-tests by 20%). - Model: * location: Now a FileLocation object (was: string), required for ordering. - Formatters: * NEW: progress3 formatter, ScenarioStepProgressFormatter (provided by: roignac). * NEW: sphinx.steps formatter, generate Sphinx-based docs for step definitions (related to #166). * NEW: steps formatter, shows available step definitions. * NEW: steps.doc formatter, shows documentation of step definitions (related to: #55). * NEW: steps.usage formatter, shows where step definitions are used. * RENAMED: json-pretty, tag_count, tag_location => json.pretty, tags, tags.location * help: Shows now a better formatted list (improve readability). IMPROVEMENT: * issue #166: behave should have a tool (or formatter) that generates Sphinx-based documentation (basics provided). FIXED: * issue #172: JUnit report filename sometimes truncated (provided by: roignac). * issue #171: Importing step from other step file fails with AmbiguousStep Error. * issue #165: FIX issue #114: do not print a blank line when the feature is skipped (provided by: florentx). * issue #164: StepRegistry.find_match() extends registered step_type lists. * issue #122: Failing selftest feature: selftest.features/duplicated_step.feature. * issue #110: Normalize paths provided at the command line (provided by: jesper). Version: 1.2.3a19 (2013-05-18) ------------------------------------------------------------------------------- NEWS and CHANGES: - Running (and model): * NEW: Support scenario file locations on command-line, ala: "{filename}:{line}" (related to: #160). * Formatters are now created only once (was: once for each feature). * Scenarios can be now be selected by name or regular expression (#87). * Dry-run mode: Detects now undefined steps. * Dry-run mode: Uses untested counts now (was using: skipped counts). * Run decision logic: Use ModelElement.mark_skipped() to preselect what not to run. * Run decision logic: Use ModelElement.should_run() to decide if element should run. - Parsing (and model): * Parser: Add support for Scenario/ScenarioOutline descriptions (related to: #79). * Parser: Refactor to simplify and avoid code duplications (related to: #79). * Parser: Improve diagnostics when parse errors occur. * Parser: Check that Backgrounds have no tags. * NEW: json_parser, parses JSON output and builds model. * json_parser: Add support for scenario descriptions (related to: #79). - Formatters: * INCOMPATIBLE CHANGE: Formatter Ctor uses now StreamOpener instead of opened Stream. Formatter output streams are now opened late, under control of the formatter. This allows the formatter to support also directory mode (if needed). Needed for RerunFormatter whose file was overwritten before it was read (#160). * NEW: RerunFormatter to simplify to rerun last failing scenarios (related to: #160). * NEW: TagLocationFormatter, shows where tags are used. * NEW: TagCountFormatter, shows which tags are used and how often (reborn). * JSONFormatter: Use JSON array mode now (related to: #161). * JSONFormatter: Added "type" to Background, Scenario, ScenerioOutline (related to: #161). * JSONFormatter: Added "error_message" to result (related to: #161). * JSONFormatter: Use now list instead of string for multi-line text (related to: #161). * JSONFormatter: Add support for scenario descriptions (related to: #79). * JSONFormatter: Generates now valid JSON (well-formed). * PlainFormatter: Shows now multi-line step parts (text, table), too. * PrettyFormatter: Enters now monochrome mode if output is piped/redirected. * ProgressFormatter: Flushes now output to provide better feedback. - Reporters: * JUnitReporter: Show complete scenario w/ text/tables. Improve readability. * SummaryReporter: Summary shows now untested items if one or more exist. - Testing, development: * tox: Use tox now in off-line mode per default (use: "tox -e init"...). * Add utility script to show longest step durations based on JSON data. * JSON: Add basic JSON schema to support JSON output validation (related to: #161). * JSON: Add helper script to validate JSON output against its schema (related to: #161). IMPROVEMENT: * issue #161: JSONFormatter: Should use a slightly different output schema (provided by: jenisys) * issue #160: Support rerun file with failed features/scenarios during the last test run (provided by: jenisys) * issue #154: Support multiple formatters (provided by: roignac, jenisys) * issue #103: sort feature file by name in a given directory (provided by: gurneyalex). * issue #102: Add configuration file setting for specifying default feature paths (provided by: lrowe). * issue #87: Add --name option support (provided by: johbo, jenisys). * issue #79: Provide Support for Scenario Descriptions (provided by: caphrim007, jenisys). * issue #42: Show all undefined steps taking tags into account (provided by: roignac, jenisys) FIXED: * issue #162 Unnecessary ContextMaskWarnings when assert fails or exception is raised (provided by: jenisys). * issue #159: output stream is wrapped twice in the codecs.StreamWriter (provided by: florentx). * issue #153: The runtime should not by-pass the formatter to print line breaks minor. * issue #152: Fix encoding issues (provided by: devainandor) * issue #145: before_feature/after_feature should not be skipped (provided by: florentx). * issue #141: Don't check for full package in issue 112 (provided by: roignac). * issue #125: Duplicate "Captured stdout" if substep has failed (provided by: roignac). * issue #60: JSONFormatter has several problems (last problem fixed). * issue #48: Docs aren't clear on how Background is to be used. * issue #47: Formatter processing chain is broken (solved by: #154). * issue #33: behave 1.1.0: Install fails under Windows (verified, solved already some time ago). * issue #28: Install fails on Windows (verified, solved already some time ago). Version: 1.2.2.18 (2013-03-20) ------------------------------------------------------------------------------- NEWS and CHANGES: * NullFormatter provided * model.Row: Changed Ctor parameter ordering, move seldom used to the end. * model.Row: Add methods .get(), .as_dict() and len operator (related to: #27). * Introduce ``behave.compat`` as compatibility layer for Python versions. IMPROVEMENT: * issue #117: context.execute_steps() should also support steps with multi-line text or table * issue #116: SummaryReporter shows list of failing scenarios (provided by: roignac). * issue #112: Improvement to AmbiguousStep error diagnostics * issue #74: django-behave module now available at pypi (done: 2012-10-04). * issue #27: Row should support .get() to be more dict-like FIXED: * issue #135: Avoid leaking globals between step modules. * issue #114: No blank lines when option --no-skipped is used (provided by: florentx). * issue #111: Comment following @wip tag results in scenario being ignored * issue #83: behave.__main__:main() Various sys.exit issues * issue #80: source file names not properly printed with python 3.3.0 * issue #62: --format=json: Background steps are missing (fixed: some time ago). RESOLVED: * issue #98: Summary should include the names of the first X tests that failed (solved by: #116). Version: 1.2.2.16 (2013-02-10) ------------------------------------------------------------------------------- NEW: * "progress" formatter added (from jenisy-repo). * Add "issue.features/" to simplify verification/validation of issues (from jenisy-repo). FIXED: * issue #107: test/ directory gets installed into site-packages * issue #99: Layout variation "a directory containing your feature files" is broken for running single features * issue #96: Sub-steps failed without any error info to help debug issue * issue #85: AssertionError with nested regex and pretty formatter * issue #84: behave.runner behave does not reliably detected failed test runs * issue #75: behave @list_of_features.txt is broken. * issue #73: current_matcher is not predictable. * issue #72: Using GHERKIN_COLORS caused an TypeError. * issue #70: JUnitReporter: Generates invalid UTF-8 in CDATA sections (stdout/stderr output) when ANSI escapes are used. * issue #69: JUnitReporter: Fault when processing ScenarioOutlines with failing steps * issue #67: JSON formatter cannot serialize tables. * issue #66: context.table and context.text are not cleared. * issue #65: unrecognized --tag-help argument. * issue #64: Exit status not set to 1 even there are failures in certain cases (related to: #52) * issue #63: 'ScenarioOutline' object has no attribute 'stdout'. * issue #35: "behave --format=plain --tags @one" seems to execute right scenario w/ wrong steps * issue #32: "behave ... --junit-directory=xxx" fails for more than 1 level RESOLVED: * issue #81: Allow defining steps in a separate library. * issue #78: Added references to django-behave (pull-request). * issue #77: Does not capture stdout from sub-processes REJECTED: * issue #109: Insists that implemented tests are not implemented (not reproducable) * issue #100: Forked package installs but won't run on RHEL. * issue #88: Python 3 compatibility changes (=> use 2to3 tool instead). DUPLICATED: * issue #106: When path is to a feature file only one folder level usable (same as #99). * issue #105: behave's exit code only depends on the last scenario of the last feature (same as #95). * issue #95: Failed test run still returns exit code 0 (same as #84, #64). * issue #94: JUnit format does not handle ScenarioOutlines (same as #69). * issue #92: Output from --format=plain shows skipped steps in next scenario (same as #35). * issue #34: "behave --version" runs features, but shows no version (same as #30) Version 1.2.2 - August 21, 2012 ------------------------------------------------------------------------------- * Fix for an error when an assertion message contains Unicode characters. * Don't repr() the step text in snippets to avoid turning Unicode text into backslash hell. Version 1.2.1 - August 19, 2012 ------------------------------------------------------------------------------- * Fixes for JSON output. * Move summary reporter and snippet output to stderr. Version 1.2.0 - August 18, 2012 ------------------------------------------------------------------------------- * Changed step name provided in snippets to avoid issues with the @step decorator. * Use setup to create console scripts. * Fixed installation on Windows. * Fix ANSI escape sequences for cursor movement and text colourisation. * Fixes for various command-line argument issues. * Only print snippets once per unique step. * Reworked logging capture. * Fixes for dry-run mode. * General fixes. Version 1.1.0 - January 23, 2012 ------------------------------------------------------------------------------- * Context variable now contains current configuration. * Context values can now be tested for (``name in context``) and deleted. * ``__file__`` now available inside step definition files. * Fixes for various formatting issues. * Add support for configuration files. * Add finer-grained controls for various things like log capture, coloured output, etc. * Fixes for tag handling. * Various documentation enhancements, including an example of full-stack testing with Django thanks to David Eyk. * Split reports into a set of modules, add junit output. * Added work-in-progress ("wip") mode which is useful when developing new code or new tests. See documentation for more details. Version 1.0.0 - December 5, 2011 ------------------------------------------------------------------------------- * Initial release behave-1.2.6/conftest.py0000644000076600000240000000127713244555737015262 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Configure pytest environment. Add project-specific information. .. seealso:: * https://github.com/pytest-dev/pytest-html """ import behave import pytest @pytest.fixture(autouse=True) def _annotate_environment(request): """Add project-specific information to test-run environment: * behave.version NOTE: autouse: Fixture is automatically used when test-module is imported. """ # -- USEFULL FOR: pytest --html=report.html ... environment = getattr(request.config, "_environment", None) if environment: # -- PROVIDED-BY: pytest-html behave_version = behave.__version__ environment.append(("behave", behave_version)) behave-1.2.6/docs/0000755000076600000240000000000013244564040013767 5ustar jensstaff00000000000000behave-1.2.6/docs/_common_extlinks.rst0000644000076600000240000000162213244555737020107 0ustar jensstaff00000000000000 .. _behave: https://github.com/behave/behave .. _behave4cmd: https://github.com/behave/behave4cmd .. _`pytest.fixture`: https://docs.pytest.org/en/latest/fixture.html .. _`@pytest.fixture`: https://docs.pytest.org/en/latest/fixture.html .. _`scope guard`: https://en.wikibooks.org/wiki/More_C++_Idioms/Scope_Guard .. _`C++ scope guard`: https://en.wikibooks.org/wiki/More_C++_Idioms/Scope_Guard .. _Cucumber: https://cucumber.io/ .. _Lettuce: http://lettuce.it/ .. _Selenium: http://docs.seleniumhq.org/ .. _PyCharm: https://www.jetbrains.com/pycharm/ .. _Eclipse: http://www.eclipse.org/ .. _VisualStudio: https://www.visualstudio.com/ .. _`PyCharm BDD`: https://blog.jetbrains.com/pycharm/2014/09/feature-spotlight-behavior-driven-development-in-pycharm/ .. _`Cucumber-Eclipse`: http://cucumber.github.io/cucumber-eclipse/ .. _ctags: http://ctags.sourceforge.net/ behave-1.2.6/docs/_static/0000755000076600000240000000000013244564040015415 5ustar jensstaff00000000000000behave-1.2.6/docs/_static/agogo.css0000644000076600000240000001676213244555737017254 0ustar jensstaff00000000000000/* * agogo.css_t * ~~~~~~~~~~~ * * Sphinx stylesheet -- agogo theme. * * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ * { margin: 0px; padding: 0px; } pre { font-family: "Monaco", "Andale Mono", "Courier", monospace; } body { font-family: "Helvetica", "Arial", sans-serif; /* line-height: 1.1em; */ line-height: 1.4em; color: black; background-color: #eeeeec; } /* Page layout */ div.header, div.content, div.footer { width: 70em; margin-left: auto; margin-right: auto; } div.header-wrapper { background: url(bgtop.png) top left repeat-x; border-bottom: 3px solid #2e3436; } /* Default body styles */ a { color: #ce5c00; } div.bodywrapper a, div.footer a { text-decoration: underline; } .clearer { clear: both; } .left { float: left; } .right { float: right; } .line-block { display: block; margin-top: 1em; margin-bottom: 1em; } .line-block .line-block { margin-top: 0; margin-bottom: 0; margin-left: 1.5em; } h1, h2, h3, h4 { font-family: "Georgia", "Times New Roman", serif; font-weight: normal; color: #3465a4; margin-bottom: .8em; } h1 { color: #204a87; } h2 { padding-bottom: .5em; border-bottom: 1px solid #3465a4; } a.headerlink { visibility: hidden; color: #dddddd; padding-left: .3em; } h1:hover > a.headerlink, h2:hover > a.headerlink, h3:hover > a.headerlink, h4:hover > a.headerlink, h5:hover > a.headerlink, h6:hover > a.headerlink, dt:hover > a.headerlink { visibility: visible; } img { border: 0; } div.admonition { margin-top: 10px; margin-bottom: 10px; padding: 2px 7px 1px 7px; border-left: 0.2em solid black; } p.admonition-title { margin: 0px 10px 5px 0px; font-weight: bold; } dt:target, .highlighted { background-color: #fbe54e; } /* Header */ div.header { padding-top: 10px; padding-bottom: 10px; } div.header .headertitle { font-family: "Georgia", "Times New Roman", serif; /* font-family: "Georgia", "Times New Roman", serif; */ font-weight: normal; font-size: 180%; letter-spacing: .08em; /* margin-bottom: .8em; */ } div.header .headertitle a { color: white; } div.header div.rel { text-align: right; /* margin-top: 1em; */ } div.header div.rel a { color: #fcaf3e; letter-spacing: .1em; text-transform: uppercase; } p.logo { float: right; } img.logo { border: 0; } /* Content */ div.content-wrapper { background-color: white; padding-top: 20px; padding-bottom: 20px; } div.document { width: 50em; float: left; } div.body { padding-right: 2em; /* text-align: justify; */ } div.document h1 { line-height: 120%; } div.document ul { margin: 1.5em; list-style-type: square; } div.document dd { margin-left: 1.2em; margin-top: .4em; margin-bottom: 1em; } div.document .section { margin-top: 1.7em; } div.document .section:first-child { margin-top: 0px; } div.document div.highlight-python, div.document div.highlight { padding: 3px; background-color: #eeeeec; border-top: 2px solid #dddddd; border-bottom: 2px solid #dddddd; margin-top: .8em; margin-bottom: .8em; } div.document div.highlight-python div.highlight { background: none; border: none; margin: 0px; } div.document h2 { margin-top: .7em; } div.document p { margin-bottom: .5em; } div.document dl dd { margin-top: 0px; margin-bottom: 0px; } div.document dl dd p:last-child { margin-bottom: 0px; } div.document dl.class > dt { text-indent: -5em; margin-left: 5em; } div.document li.toctree-l1 { margin-bottom: 1em; } div.document .descname { font-weight: bold; } div.document .docutils.literal { background-color: #eeeeec; padding: 1px; } div.document .docutils.xref.literal { background-color: transparent; padding: 0px; } div.document blockquote { margin: 1em; } div.document ol { margin: 1.5em; } dl.function, dl.class { border-left: thick solid #ddd; padding-left: 1em; margin-bottom: .5em; } /* Sidebar */ div.sidebar { width: 20em; float: right; font-size: .9em; } div.sidebar a, div.header a { text-decoration: none; } div.sidebar a:hover, div.header a:hover { text-decoration: underline; } div.sidebar h3 { color: #2e3436; text-transform: uppercase; font-size: 130%; letter-spacing: .1em; } div.sidebar ul { list-style-type: none; } div.sidebar li.toctree-l1 a { display: block; padding: 1px; border: 1px solid #dddddd; background-color: #eeeeec; margin-bottom: .4em; padding-left: 3px; color: #2e3436; } div.sidebar li.toctree-l2 a { background-color: transparent; border: none; margin-left: 1em; border-bottom: 1px solid #dddddd; } div.sidebar li.toctree-l3 a { background-color: transparent; border: none; margin-left: 2em; border-bottom: 1px solid #dddddd; } div.sidebar li.toctree-l2:last-child a { border-bottom: none; } div.sidebar li.toctree-l1.current a { border-right: 5px solid #fcaf3e; } div.sidebar li.toctree-l1.current li.toctree-l2 a { border-right: none; } div.sidebar input[type="text"] { width: 170px; } div.sidebar input[type="submit"] { width: 30px; } /* Footer */ div.footer-wrapper { background: url(bgfooter.png) top left repeat-x; border-top: 4px solid #babdb6; padding-top: 10px; padding-bottom: 10px; min-height: 80px; } div.footer, div.footer a { color: #888a85; } div.footer .right { text-align: right; } div.footer .left { text-transform: uppercase; } /* Styles copied from basic theme */ img.align-left, .figure.align-left, object.align-left { clear: left; float: left; margin-right: 1em; } img.align-right, .figure.align-right, object.align-right { clear: right; float: right; margin-left: 1em; } img.align-center, .figure.align-center, object.align-center { display: block; margin-left: auto; margin-right: auto; } .align-left { text-align: left; } .align-center { text-align: center; } .align-right { text-align: right; } /* -- search page ----------------------------------------------------------- */ ul.search { margin: 10px 0 0 20px; padding: 0; } ul.search li { padding: 5px 0 5px 20px; background-image: url(file.png); background-repeat: no-repeat; background-position: 0 7px; } ul.search li a { font-weight: bold; } ul.search li div.context { color: #888; margin: 2px 0 0 30px; text-align: left; } ul.keywordmatches li.goodmatch a { font-weight: bold; } /* -- index page ------------------------------------------------------------ */ table.contentstable { width: 90%; } table.contentstable p.biglink { line-height: 150%; } a.biglink { font-size: 1.3em; } span.linkdescr { font-style: italic; padding-top: 5px; font-size: 90%; } /* -- general index --------------------------------------------------------- */ table.indextable td { text-align: left; vertical-align: top; } table.indextable dl, table.indextable dd { margin-top: 0; margin-bottom: 0; } table.indextable tr.pcap { height: 10px; } table.indextable tr.cap { margin-top: 10px; background-color: #f2f2f2; } img.toggler { margin-right: 3px; margin-top: 3px; cursor: pointer; } /* -- viewcode extension ---------------------------------------------------- */ .viewcode-link { float: right; } .viewcode-back { float: right; font-family:: "Ubuntu", sans-serif; } div.viewcode-block:target { margin: -1px -3px; padding: 0 3px; background-color: #f4debf; border-top: 1px solid #ac9; border-bottom: 1px solid #ac9; } behave-1.2.6/docs/_static/behave_logo.png0000644000076600000240000007426213244555737020425 0ustar jensstaff00000000000000PNG  IHDR1H$iCCPICC Profile8UoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+ pHYs  @IDATx}TE,awa9KIbTg g8sDA2sNKelbsߪYwװPmjPfkg7HZM^Xk^'㏬D/??m# l6<9%\U4w ^w 'N=O qTdi.aMG) m ! nWOʕzh^ L6u_:he-K.N 5J&-sl%yvyު?9ODˡ@iVKa4av'[pH "T/:[HuN זɵdsF xskuD8ོ'6r|IzSǀ|9א4LG_|/rW6طwABe~ īQf}?]IlAjdo7gKt{˞T`(eBu1<PZ,#GשBu@tQ:y˲t'4ldk~6կyS=:y]TiP<נ=@d; '^-9@Rߕ(( $L(}) (QP_f )?#!Ȯ=]ZB:CP@f`ڲ J՝0DӨHABElK- b Fi2x*G'o)x$Qп{n"$g)x.-5y5PzLGɵ%cGh.vb";LLM S!DvKWH=t>1@a|QMQCFK ŠggG@!oP@5 B;Ys׌˨݈}^3Q<5`Nst*ڎ)/dKp]0`& = P>`N"~N܅@t1ROF@6 8lJ<\?kPװ? E>3@>f+TXSO=t|PZ  *W NnE;A@EڏF^~*(y-+- IOUv]EգjhX2H;j8o`S"85kx wi{HmqfvLފuO(S71:n e[QD |y?e0A6'g⛒]kw !sW~N'n0 =^>2^z\Y)7]Q9ko8,^B-HlYh,U(:(S/ϦTUr"Lp5:ll >T1)eIbow߻vmߨ#y.t<:KtP csd. s { ̠g9T_ rUlw"GNTqrtH}S//ʌZ?dOs ILXtl>Y5$y %꣠6c\nR#C+--TONLqOQ:9| [E=t OČoD@bmk]#W#{HG.rt2'97oNob͆h }(mu&WKnu&xzN#^#O~gl#*O<{)Jډ-94o$Gs$==O^.&NL ? ğ|T)ا @>,yBR1ycV ~4I*O$nџ(:Ƞġ(D| f6F؈ ϒqymh,} f0@yqi@>C-^V<[mᇑN{3y@-YYJC߁zxnK_ frqya+qHw<J3DdgzTmꪆ Xؑ(yB92QxL,e@椼-۩\|>%%f0{Src^OF^kkmsO:il?|Kfr߆i@VD,Iw"y: 8ԍuͻ{;z#2ͻaJ渂t ~RI~?zBr:ogptZtgH(wϖbkɣwKo}wR]Ue^Gy42rq60rXZǍ' <@dS,a埗_)U%?60bʞE`F9%c*v_oGda'QL{L. Ncc \.(x) P3bto{a^aS!PoYVfL4b88Eьf)-jPM,0bFhA ug ߢe(CB",)Ji%_PQJO[},=լۢ5ǀ@~6g0+[9}?)ghs|3ysW BvtPEU0o>Gj i6N^qSGoMȏ=PArJzns4?{ :}O'p\̗ND5׿\ N!@2ƀxz$@pa?ϹT{grea!P:9±x'P/qqįSʑ)VP jS̊aVK1#:k7 *Υf)T]\Be~7;3Ձ:TcҜw[|';Faz;M<]bߒ , CS` @CY9@73ɼ/^zQǮɋe*ׇR#>r(-7˄|tgSdu^G3C~z Cq54h ty+ ]i݇M<5* Yj }+nlTv@P*Y^UV=+";v즯_^?y1%/?a?7F Wr1P"##E+H|]u.:{"""pkUָ؃J=cPpzU֚ *\Ŧ?k Wiu\ө$SI_xjkzDBmhK!ڒ5 5c%n|ij x:VSʖ~q|oեht*;1܈Zւ >A#C+8Q@LjSЌ[~@G+$C^ 9!ս4%hL >ڊZϷ!o/=ԭG7Zx-ȥ陵]e" Uvf4nAU~ݔSq*J+&:w}Sdl$ :o:~w6R~iiKQtJD1T[]Ko?6]}մsJB.C<.f%:)*5Vy\7 _;W_Cټ兢oy 8$AX[PЛxzש((` GO(Ⴠ\``qvR84VOi{U.G1IIйRXL ZLq E{Ӗ5[.s㟏C1`8ozy|<A 9\gS-x>[A ?Oy ?/|b(x_&eT&4E/L)i)'f?1?Ek٪%նNb rU ?yAԻozSO܎Fӷ YZ8S9E< S[CACN_EkMT{׮-%-c0!X9ZAUd_LBckJ7&WQ/Ei#xi(lPQWTf,XTSgN0 o[ )EHD:s)fW,D.)ӧА1C}D6l7E/2a]~e3 Q&? oO̼TJIݱǚΛְڈ>ID)`!eƂmpG/Fz~iPZ54Bs+fN';OS7yu>*yq_:, 8 4SAI X<-xx&Wu4^Uq ,Y"#"l#o> d1C;ҩpIe{◥@ XHo~>'o|}&]U8dՕՔ̃aH+t3wlB.ݨ҃b4bˉu|qo DP &< 0 hpNPӬɗP/z*Zi;?\hU=0G̜aKeB7P<\a:_קjtܭJ1Ŕ'}% 6y \HeSOt#^E Ơ nveXg,*|p 6"U>WΡG?@S2Q)Ȣ׮!3Aw9\QuWՁ*o~6=>f\{hFWIrc5$jG΢^ SeJ)f4KS`x|*g.):|%m{C,DSmQ ҲD I:+=zu4D@y8 ҍ[Ht_´˛_ˎ yf).Oڃ)$vrJjD A#21#) ^0n+w27F,O,tv=Ua=86,5WW.hX}Dz橬 ߓ0Hљx(gS:IX<0뿌l̃-_GrvdaXgpIr81y16Jq|-)~xHl wO%'jv:|mh&,ѠC]LpK[ 9XZs6r=[֏fQ8SG>_MF>RSN+'0mqoOn@sMgtݷ C"WiN?M8iN!WaT?i!SSMuvo6ӯ%/l7ab R/˩|R'0 BM m߳hRb>fkۻ[z]t~0"_50M16><{0Lƫ &lz1gMFd,2Ա-[Ja1XȯK^dec-{X = 08(Pbٝtݙ4U;s%N0 0 {8TBI9Cة)]OuL‹TL95sb ;{:*=r$w~FlDZEcл "0dlk~ L:.uL>{O$PTP`i= nBmz`#fKȎ_ϢU [@nK۠#LziA>pm_Pt*?C%= ·*o??eH]D O_pQēS0g~s9Gbt`X䅊 V=iȟӣ M @fSWݤ䙈}I -ٌJ*B& U&]%-% Ũa:yr71ƢN4c%,@BOXg PPl92;]XUxv$V7@л/L@*@Ik!㰣vH+ chCrO5ʥ 9{O﯇1 P $0ځ Hk*7bRڸ_bj_tH&dՂ|X Ƭ&8 &X#b|h'fYRt&P@u0[esBE32a* ~I.°G>:u )u2[dl \Uv7 hhI{]y:ckߘVFcㅡ< QTsgD3L4A.[+R1$VfБ[(%1zr!V+8'h#9\|촪2|()Pe{h AL˗.ujy1K )eRysy[0L>V3QC; u_P011)/JࡏvP7~"bk6B0f\>~&rwq6 PYԢKgyb w(?)*$eϾ! R Kla8 QU95$N@t"sLMb8Ap1$-o@ˎa3Pz(.xTtAH_KFA.!Ǝ[MwD)p14K(t2(]7ʘtv$&T>]|n"c0!ox1kD>o ym"R]s'MtP `aPёޢ9WVQe2(~*RӜ)\FN0J.,qe,XLR3K=c0⼩:PDCAHe Dl@=~0- #1='@7:7<(&x<)\e|N #3#2[DY;MM8Db{qtO窠w&B3NRPvὠa(AҳiUr=_Cp8_ZGp Z/ru ^=3eEtSƪPz6>hyT:_dR(ftMfJ#t ߙ8A@!ݨ-y3lWc\Z1d և\viqCΒGVEaVOHk817GDΤXzr(ށ$s3ٜ~A@J⩈tP 0v ijz&)7?B/BA29S; H{X,O jPCΟq[l2, :M Ka/U*y>kH6F¢Ք!͋dň\h+B-EF`?bH̪MfH(QcPT@QG{Y7aŎ#H V8? HF4ڍ 2mA: Tk "P6DF\p`\q`'NOAu5R@`jG`ml ^۟v.#* F(zop7t%`fR#~dm9*ʼw&|*i"!+ގX^'C~ZV r$A*o2W.OZ,udfQxwpX$lD)m}?KLQ#rmE]zU1UHΏ )&-J7Hqï/4(ӅBGE1`;0厥g @fίH_AgOtA/A͆:i;[)(-G@  YTu6 `-\N#Y`hƋQf-(8CbG?_*'hI|z;>u7f?W~''Hظ~S/@ >)Jph{?H%G0Hv  B!аqh+3;C 0)TdI+V{qz!;yxR}$so-e1~=Ptsy'TͳTxtLی&;e1*VSFrùpj6 _o7V~CEK)46\~O Xӆ [eJ++#7z #uD8ኝ6 Ad{f8RJ @ _p8m w- C08O)6_:)1@*ZuD-d˜!QDH0*%@}a:c>9h,fz}4(JYGQ|9xLv\F5j74#!+n}{a†(aOR2 |(CQn0UE*o=+0W:H9oDu0pQ<,MD-'f}_. mƍjze}JZth/yR:1+`M΅hhH 6 kByiq?S%B2-?=A~䨸aBteK˓PEgbS6Z=WIx;DC\@dF~DA!˷p=}U±^4yyoK@BFFG"񤜘cCCPk)%t]~}OS{9݁`ֻr"TWYI-~"U}Bl$͒ 'QCiO1(ݵTJ :OrNUadd~]BCtFC!mJ!*H8i,0c.@8ψǝv9'Ze #O e$4ϽeB=dOJN4r qYKZA@tC1՟k}azAm* ,\>c@W0fXG@#MɺU5j%\6z`FXh6PL,ٔbtN~쯴2e %S0w(Y#)0g.Ji1-Ni3/Ɇ*N< 9X 뷝BXTff:8n4E-o@xAq4:7KCchf?DX-w P%eP<6#W&U7 e ~x-x'*5QQ4th gL!WGRś)P@wVpd?NGFu3Z56yV`O:wɔeьԋM>ɴat"]A#κb[4x "4H>Lml ]m$)P#< UT=i 4c1nXWaÈ+;AH' jda*js~5=t yC(ᛠz\{{o5QE e0 *=#̓9,q_#eGJ )I I20LUVC|p!aOiO ˤ9;vd:yAeC< e`:oz:B-R@cԕ߼X W3-UGa30V*%SZxgm?zP'ryjGem} #} MǖWN/i_|H}]B1toMW;"J\X@aÍwC<33+#.,;P3E$`ӫptM}_N|Th 8|U%2tXӬحx9޼g&Ҫ^С?sR>k[ƻT ._G;| 0&*(}7+9E>S!)h"*SE%p&{E#>9D>F\7vY0I 4@˽/EH~LPa)^y% V$d6:8PGq|AtY)lO^Hր 6 ?gF, Ev6td8ynuɎ2f@5<ݏ3`UbvVY4 aRE2SX؝HuslJj6t$)ܨEHs?IIt'eʂBIɍzi@@HKB$JE$ȁS\P6Y~-1,H(F. :ʨ~pTԂtL$s=XE?UK&ӨXWihW^'wY_bAgK{OƢtw~߇MȀ3j .ʣs.l,L:3[.B,|}MT Zx0}3<^lmמ| 핀-{P"E! T(# Iy'3ףPuTq/+|-j$[BBi:txTL-L(/牱)AҸH: M`emVr`ZB .lloT]T$%{bMns} 8?-~J>Xê? 6ڗw|s6{zNjS< ee y$S-|)"T9LTvTęS輗@ߋ;'_DZkjtĶ|[^ Vπu@E;0",d^?sHwTLJ;9<5X𽒴6vtd 6")uT>:wD}i)l4o 8!PFuF%D͆ oAy[>BւEld! pAņlZ*uk І7ޡ)|NL$az`Cp*^^Oyk{(>F-amZÉ xo oh,@Sa)ҝi[R@S9y{bc`j7fo)jט%1/kR.VK𿶢EobLjpF(ײO8WXiR,A8с~k\}E5EiÆH,`Œyubݪ",\#x꤅zF%f}LlĹF?FDaƶ|aVPLy3Ґ7mS;ig! tjzUOfQ|U0p\L*2c> FȆGW8|uV;n8Nn^NSnP@ %B4"Ez(ǪXJUR;%Xb`|Ҙ(q*Ν:YT:DQ@T3 +Kg2)UhwAFĉlтz^0EpAB1%@ep|nɇJ+3D@UifzO-v)Ԧ;"sd"R-tIטrEz遄X5LzTj@}! $7<$~*DBwNüaSec`?7ŧ܌@?߷Jʪ]=:^gk"J: vé8T_xƕ&"sPieXG cAE5#4? d+gUQ(xf`e)EDE;xEk̷D4k'pFfy|^PhÔ_e< `0}  e;p J*V0L!KQʍ(@*gܾO9>7A0DD8߸#XTywn7(m%ڎo u5J{+H?6IR @vqQꡉok>tqfh28̜q`*/; @!Z3em tEȍ]홡8* L;彏)ZuTy׃κn&u0VTɚ@J8|CG`[]e*&thCRkga^l8a>`e?E6P|C;<"aui6w]"TXesK`NCGpAA%)'lcG+ڎEa^Ãp_ªF>"jI~/g%? J} })ozMφY5ΛHӐf"*L6x-&_Lmuc-~M jiv:U J!\:XBEIyTJ(|vL*%OaxAjw-2.~Jqɨ?A$(w8r# Ӧ7@,rgreC=VKI{ql{\H+λ ޑvXMu9ittއod.*S=Mc?=njNcүqpOÞ\[D(ۂAӳV.|qL3X.6(h2DvvD5yTq%@:g/~Ax= lJJ(S7ӯ[P J\vymX\=_ֈy<2OG1S8dMQZ ϟ:,Rb$p>, J8}j /V;ǖ+rP(M[:b7RxިI AŸ!W]O?`'pVv{D&bRTv'gPj=/Mp Ȓ1X&49byg*ӑOT;S rX9xbU!ymޢT u_FAjF^d|ՠ̀U1`~~Lܥi bxd,8ȩU4#7jOn_~o gS&6`X7$]:\Kj@V."{uIp UqG/Z9 ֲ g,(}f!քQWnXU3oOyWQ}'`JK0l-8|l?VZtg.c3 u6cTJՋQ(%TV=0 cC($#9v*lF %kq\$S`|#8S^s?u 77q,6_8AgxĢ!rc3U ©vtf`9fٝKЪ.TF!iP_vlQ顣' o D):g$$fzKzeQ'jM0PG 84KLQlK vNw-L'r86 Ipc7o91;`(lX Y$Iڅ?TY? B2Q#O F).QaoYޫ+5t1NuM8V,{ka\Lj"xGGHqd,[ǘ Vv4<`v W5(:QgqXN!qLw4Rw?-@*0TL6Rdafa[9B_OlyځcXwk <3m:b큒덦Lԯ z5- #< mpb6&= GG䫆>̎봎 g-ܲBPvh6 (o4pJaBζ$<ta>6=AS|9bL~Z?A5W)aS{Dѡ XO) BRUtŽX @|=w\/*.cURUCo0@6 gݹ'}>efecs gL\KloUD? $4mG'%L]'y@`lFhx ~Hű`CڃOپ8>~~0Ҁ ii:=7#lXo 4𐍺* ]m:N! XPAyA]Kq P\ҳg}@ a1H?J~XCPGuG^hpW5Rvc+ͨǴDž)5O~Wjqר=[P0C'dc/S,c^TRݯ x{>:{zqO夗Q]w@># #VUbA%(N%6kӶp'N_}ذg<<,}qTZUxJÍk)KmS9-)wWu<;׆06H5Y|`h#=6%ӫ+Mf9S4`xyj0fՄDlV+d)pB)[5NȖFW?jٱU:pqjNl|tNt(?٩1kZKF:IQ Xķ; J~ZIR(c*}#/dA"PFΠbt5t|ZD,\z Vg)ԕs 4o1u&4Ò~%ՂeC>Ca<N,TĚS t,Ac-| !N ݏfa~+ NuOqyu؁~!#¨fO&Ur2"@;,&OqEw)qGk޵AqT5Nv*-GKÀ #"Ų!'&b]֋m18$@jf=+Lbn*<|Dbbw}Nk(mhSmhX<«~O/@F (O{_>c~O?IsXw,ȤmswJga-IHAүk?p\# (S;ʡ9ygqUj_r=m_\E@Σ~RYP8R@Z3^~XC*ٰ T_ GKR -F핈94^QLuө a]҈!@;3nv·>Pmd" 0n$tعϽBϤ鋴>7h ͍&, rtZE&6!4H(}S~ 1/|cY_)W\q#oIm[bNM'i Em`je7m|#jʅw2^شV@;ѻu{X ]a}#8 A`7 0()ى!&OB ՝qNg]J=ϽIPHqX RNQ=""Hտ,N+7UӵK15s3 ҁ-* }>>lGɿӌeCyz`7al:!JfƼ'39jכpAI!`7\8W;˴ ^mƟ~HU9-o?>_Y VvJWv:r2Q?Y˴!usSNue7{4t`XHC G#t?]&E*w^ۇۉJU0ty?m3䙵G#!D֟UVda4^r_0:\Ak/>a(tgg{/0N y WRA("MkVOdR*sg<<߀&6|\h#ttE)4&a3o${ނ'ZXe̥X2U-~_~0|2oT{>2xG0)(Xgskh/`[|BA䎕al1Q[#.Av쯨꞊"h WQdS#0e#?/2nIy}I9P{L@P/.>I 0 ;cGr懯 DAY\A?{JmĿ?A;aUn_ufPFN(3<AndxprZkqP䋚xvĀV9 ð_q r"}b-mx{ؚw¿UԀ̧/_!_-O8HY@B˕ Ý|Uh 2y}25dcE7ML+ BփtfR?;IZllƟsԼG׼وgMT5|-lzt>{4#M3˃x7R˫6vL 'q]K Ayv&Q̆!,,Ye;odo0Mǫc ϩ \g4x 92Ϸ8{#}{:ik|:x)]k nLG><` {Asr|Q=BF=pkGG3Z<4ķls-U,XePh5(o|?Ơ[}* i*)+\˯hpTi`qi fva ABjPX A7## >9S"2xFLM rV6x':ry?`[;[T8?JJR5f(# ;sJY;U>bΚpx3'0R=)%@:)Ts0UrU¾W]I!`.;PHQUiܨV btj0U?f}7;CuMű#{.bZ'+‘ 9󯣔Xu!LLHg3 O)0ʦNү~mi .yQַs \r $ jq %WyDZ}XW/}>%/l6;P"\B_Ji(:BpHc 3NɢB oI' qP *KJ7oQe/5;^5H3RO.*/EqSt&X|pP|S-<%yTS,5VPh%pz`s"Qf.))[}FϝGg\JLs*=  4"j#|teD}17;vg3KNi@P '\ 2o)+']_T@ ~W}$ 4`tf{ uEwrm+tqvUc۴#<.Jmׅ#;~\1ImcYf)_xJ%zZcC4o}|qVeʃv/t}S"% Gf>ݯ2ZrxAgyyuqo0*~|tP KB|fqE$"<=>ں^#)]8 ?9 GaM; k|0=N˓*_;S@n|dhK .Obg"7x ak .@@ Ɩw`Z&8d"Se E`mJhQgL,deJx[t/#T2zpjT aFu`ܨq`4 &S[x~ɏa)c|?ҡ':*\8\ _u;:"b&ptN E?P-"č(amS|:0_Wa\`jbQϩ{W7 nSLZE)/k)HJs흭U;{]xwV5ZiT֕iKIjM@ D ?d V]`?^igy*?D=͸K0[SeVh*lPʞXa(oOJag}ơAiȵfi=i8EFVi0^ j :HɡLLdj `SSR-e6)ID-!<# 0iًq8|R 25U31J9Kh7;;HKlA)7شS|S0kDI2[\Jp9:L J)T ~I4KXUݏyI]"TLgt5Db:W6DE,RK%vIRbLƉcuzs&'>IZ7i6MJ%6&c`6c@Yfsf{{-w,f\_wc[y/F1Dwc OpRV+aTۛmW"Bd}%ݱ:TǛꪘW*qmK{,G"coreL!6_VZ/, E+is۷`}X0n̫n#G]i>sьmnglC% ߰%siڶ4>H+UgMLdUp휶ÜPlw{w|^w[zZUpMb|R/kt(NG*Y:Z⊣yo>)(~y>@-0< .dvTJ8IaV=k t4S]lū\ cϜ뿻M[㗒'uQr[\;& )p1T \I@0Ý9*ḥ`^ՍР Wq~Uoj*_/=t7 ~]*/~y\@N%׮eeFBܺ%.+7nGZ(WsiۗOo"_̕t9#ocr29ܴ8$N2|)@g+J N޵bIrmE uvjLb[t?Y*U 1Ne d A!~]D7G>kKXzLL̠{ER-&|BݽU;)ckH)9tn3um\b:)S||G9Cm$ls cssDaƕ0/yu*"mJmV 馆-̵C0 :\SXO\OGq Y'gߍkVaE>1wT}KIjXQ4L1u,u=vgL g"QͻVBnO^'/wt+yG9ڪʍh(, PCN1G6oKDjS$[ha9 GyJφj4o ןdWc g2d|;Ж]2|! 0U802a} Djzլ:No%ZܳNQzVTp t#RZxl~RδHVwڱ,%?ouhqwVe +Al %oE`q,Ϙio,f XQM(>0Ejv2njl6\îTAu/:jkɊO7Z$R;k\ 皳52}D6SZ34rtݖÝCyqG*54}+apw|r&;K=DnI=wdqI?Ŝ/‚ "g=%f+h +b+Wv 4]*a3u;o-ϛz\(2d s@INUnb`"Jɤ)f>A}ptC#qn_I jތ9bٗBU>MACLObN{ڀ/=_t9ڔ$J9i)*gQ92| x,t\9.|t]/$1u/nWB':8lHG } 0\)\„hAh/#r'>^mre=XXz:s ]"Z‡^PTFRt :0yb?~ S7';1Se]_Mt/hLulp c:.p9{4g|?uCd|?.Y{FrsMq>Le Dz@0?BðY,w(޺mtH 3hUX |ԥ:l,9;}8^ΰ^ۊj,5 ڠ2"a<[za(Ov}wT>!s&pÌ *@fVkzik8D?ZIКXTZ<3nJ顪\RZW+KIP !ja0կ|;86Ew z=^u? 5HPA"]>2LKBHsBW6*>çN@R*K}>6٭#e, ɜ8Z2T#56#O>)zEvnKR膗M)im]A vЁ'k)o8 \\ղǛ]@Xְ|{Dlmy2 ҢA\kDaJ k]?<{qz< ,ުUR< }Jrmn' oYC/ ýĶ/5Ewzom*r@uݍ "* tJCh%WIS&{^=ÿWu`#X; Q7//`Ay c߷ZvУz>T&ُv)s ಣj"4 \ɶXq\[D!7EX,F״yҼ@B\|M#ΥBkkPĬ~"@<Fӕ"5DU7Z8h|GI`rV(.HmN Y[piG7vdx x<#KX>f? :ta,LUSMZ*6>up$G{ga!\o&!9qAt[)=xL=kċa"Hj$pWG')XqCVlɘ k9!/=Ϫ3>\>.hٞBC -P82K!+>bi|%$- .e!+MGۜV_]cS͞Ge ˆ~S۽תthu*R_杫HU0<Ȑe8"-_h0 p&1n57z]ޟ03 UM֛Lך1cMq|gRӊٴ-+ `AשgLq^a"3@[H$9&n ltr-sè_}YAor7"|:۹p?X¶ŎF\#,遛9D+`4LSPH/ӆ&X@Q"$%w!p +R^` Nr a%f@oisKKKdBO5{V)"cp+1oBgMC[lkᕿdQNX`:?thv_ۊ6v @dK](ⅉeM7ph)y<5N̹ !IǛj{ܒ,dKSbzw:J:!/ *:>1׻ACk񞕞u63+v @(bi3p#a$:G_^Q)u2QZzE;B+o:@]:%ڂzQӟ"H]K6 ĜעЁHpŏl t׋1/L])"ʮ޷0',?yҁ+ )Y&l= `t=;܎ԙāM'~mߕa'Y.eeW\/q"`si^vHo7q?pUXjw!V1̦6$۹Q6ND/yں!Q5p/rpݝ& %0Rt!5~-T*&L^z!0z\("+gneX@%*Bq%m?1rpBl烹`\u)NX]=dz i $f|pu;TwŴ9~'sK9 t*7\p\{%D:l \OA %Ln&BRc+ƇPRpR W((J*ӥx~ ~M-uqE/p1|Zfb>TodA'@QiLmvuYA:)2wcܕ,BrDQe9K\i@$X.{0\zJk'rl'rå hPIENDB`behave-1.2.6/docs/_static/behave_logo1.png0000644000076600000240000007426213244555737020506 0ustar jensstaff00000000000000PNG  IHDR1H$iCCPICC Profile8UoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+ pHYs  @IDATx}TE,awa9KIbTg g8sDA2sNKelbsߪYwװPmjPfkg7HZM^Xk^'㏬D/??m# l6<9%\U4w ^w 'N=O qTdi.aMG) m ! nWOʕzh^ L6u_:he-K.N 5J&-sl%yvyު?9ODˡ@iVKa4av'[pH "T/:[HuN זɵdsF xskuD8ོ'6r|IzSǀ|9א4LG_|/rW6طwABe~ īQf}?]IlAjdo7gKt{˞T`(eBu1<PZ,#GשBu@tQ:y˲t'4ldk~6կyS=:y]TiP<נ=@d; '^-9@Rߕ(( $L(}) (QP_f )?#!Ȯ=]ZB:CP@f`ڲ J՝0DӨHABElK- b Fi2x*G'o)x$Qп{n"$g)x.-5y5PzLGɵ%cGh.vb";LLM S!DvKWH=t>1@a|QMQCFK ŠggG@!oP@5 B;Ys׌˨݈}^3Q<5`Nst*ڎ)/dKp]0`& = P>`N"~N܅@t1ROF@6 8lJ<\?kPװ? E>3@>f+TXSO=t|PZ  *W NnE;A@EڏF^~*(y-+- IOUv]EգjhX2H;j8o`S"85kx wi{HmqfvLފuO(S71:n e[QD |y?e0A6'g⛒]kw !sW~N'n0 =^>2^z\Y)7]Q9ko8,^B-HlYh,U(:(S/ϦTUr"Lp5:ll >T1)eIbow߻vmߨ#y.t<:KtP csd. s { ̠g9T_ rUlw"GNTqrtH}S//ʌZ?dOs ILXtl>Y5$y %꣠6c\nR#C+--TONLqOQ:9| [E=t OČoD@bmk]#W#{HG.rt2'97oNob͆h }(mu&WKnu&xzN#^#O~gl#*O<{)Jډ-94o$Gs$==O^.&NL ? ğ|T)ا @>,yBR1ycV ~4I*O$nџ(:Ƞġ(D| f6F؈ ϒqymh,} f0@yqi@>C-^V<[mᇑN{3y@-YYJC߁zxnK_ frqya+qHw<J3DdgzTmꪆ Xؑ(yB92QxL,e@椼-۩\|>%%f0{Src^OF^kkmsO:il?|Kfr߆i@VD,Iw"y: 8ԍuͻ{;z#2ͻaJ渂t ~RI~?zBr:ogptZtgH(wϖbkɣwKo}wR]Ue^Gy42rq60rXZǍ' <@dS,a埗_)U%?60bʞE`F9%c*v_oGda'QL{L. Ncc \.(x) P3bto{a^aS!PoYVfL4b88Eьf)-jPM,0bFhA ug ߢe(CB",)Ji%_PQJO[},=լۢ5ǀ@~6g0+[9}?)ghs|3ysW BvtPEU0o>Gj i6N^qSGoMȏ=PArJzns4?{ :}O'p\̗ND5׿\ N!@2ƀxz$@pa?ϹT{grea!P:9±x'P/qqįSʑ)VP jS̊aVK1#:k7 *Υf)T]\Be~7;3Ձ:TcҜw[|';Faz;M<]bߒ , CS` @CY9@73ɼ/^zQǮɋe*ׇR#>r(-7˄|tgSdu^G3C~z Cq54h ty+ ]i݇M<5* Yj }+nlTv@P*Y^UV=+";v즯_^?y1%/?a?7F Wr1P"##E+H|]u.:{"""pkUָ؃J=cPpzU֚ *\Ŧ?k Wiu\ө$SI_xjkzDBmhK!ڒ5 5c%n|ij x:VSʖ~q|oեht*;1܈Zւ >A#C+8Q@LjSЌ[~@G+$C^ 9!ս4%hL >ڊZϷ!o/=ԭG7Zx-ȥ陵]e" Uvf4nAU~ݔSq*J+&:w}Sdl$ :o:~w6R~iiKQtJD1T[]Ko?6]}մsJB.C<.f%:)*5Vy\7 _;W_Cټ兢oy 8$AX[PЛxzש((` GO(Ⴠ\``qvR84VOi{U.G1IIйRXL ZLq E{Ӗ5[.s㟏C1`8ozy|<A 9\gS-x>[A ?Oy ?/|b(x_&eT&4E/L)i)'f?1?Ek٪%նNb rU ?yAԻozSO܎Fӷ YZ8S9E< S[CACN_EkMT{׮-%-c0!X9ZAUd_LBckJ7&WQ/Ei#xi(lPQWTf,XTSgN0 o[ )EHD:s)fW,D.)ӧА1C}D6l7E/2a]~e3 Q&? oO̼TJIݱǚΛְڈ>ID)`!eƂmpG/Fz~iPZ54Bs+fN';OS7yu>*yq_:, 8 4SAI X<-xx&Wu4^Uq ,Y"#"l#o> d1C;ҩpIe{◥@ XHo~>'o|}&]U8dՕՔ̃aH+t3wlB.ݨ҃b4bˉu|qo DP &< 0 hpNPӬɗP/z*Zi;?\hU=0G̜aKeB7P<\a:_קjtܭJ1Ŕ'}% 6y \HeSOt#^E Ơ nveXg,*|p 6"U>WΡG?@S2Q)Ȣ׮!3Aw9\QuWՁ*o~6=>f\{hFWIrc5$jG΢^ SeJ)f4KS`x|*g.):|%m{C,DSmQ ҲD I:+=zu4D@y8 ҍ[Ht_´˛_ˎ yf).Oڃ)$vrJjD A#21#) ^0n+w27F,O,tv=Ua=86,5WW.hX}Dz橬 ߓ0Hљx(gS:IX<0뿌l̃-_GrvdaXgpIr81y16Jq|-)~xHl wO%'jv:|mh&,ѠC]LpK[ 9XZs6r=[֏fQ8SG>_MF>RSN+'0mqoOn@sMgtݷ C"WiN?M8iN!WaT?i!SSMuvo6ӯ%/l7ab R/˩|R'0 BM m߳hRb>fkۻ[z]t~0"_50M16><{0Lƫ &lz1gMFd,2Ա-[Ja1XȯK^dec-{X = 08(Pbٝtݙ4U;s%N0 0 {8TBI9Cة)]OuL‹TL95sb ;{:*=r$w~FlDZEcл "0dlk~ L:.uL>{O$PTP`i= nBmz`#fKȎ_ϢU [@nK۠#LziA>pm_Pt*?C%= ·*o??eH]D O_pQēS0g~s9Gbt`X䅊 V=iȟӣ M @fSWݤ䙈}I -ٌJ*B& U&]%-% Ũa:yr71ƢN4c%,@BOXg PPl92;]XUxv$V7@л/L@*@Ik!㰣vH+ chCrO5ʥ 9{O﯇1 P $0ځ Hk*7bRڸ_bj_tH&dՂ|X Ƭ&8 &X#b|h'fYRt&P@u0[esBE32a* ~I.°G>:u )u2[dl \Uv7 hhI{]y:ckߘVFcㅡ< QTsgD3L4A.[+R1$VfБ[(%1zr!V+8'h#9\|촪2|()Pe{h AL˗.ujy1K )eRysy[0L>V3QC; u_P011)/JࡏvP7~"bk6B0f\>~&rwq6 PYԢKgyb w(?)*$eϾ! R Kla8 QU95$N@t"sLMb8Ap1$-o@ˎa3Pz(.xTtAH_KFA.!Ǝ[MwD)p14K(t2(]7ʘtv$&T>]|n"c0!ox1kD>o ym"R]s'MtP `aPёޢ9WVQe2(~*RӜ)\FN0J.,qe,XLR3K=c0⼩:PDCAHe Dl@=~0- #1='@7:7<(&x<)\e|N #3#2[DY;MM8Db{qtO窠w&B3NRPvὠa(AҳiUr=_Cp8_ZGp Z/ru ^=3eEtSƪPz6>hyT:_dR(ftMfJ#t ߙ8A@!ݨ-y3lWc\Z1d և\viqCΒGVEaVOHk817GDΤXzr(ށ$s3ٜ~A@J⩈tP 0v ijz&)7?B/BA29S; H{X,O jPCΟq[l2, :M Ka/U*y>kH6F¢Ք!͋dň\h+B-EF`?bH̪MfH(QcPT@QG{Y7aŎ#H V8? HF4ڍ 2mA: Tk "P6DF\p`\q`'NOAu5R@`jG`ml ^۟v.#* F(zop7t%`fR#~dm9*ʼw&|*i"!+ގX^'C~ZV r$A*o2W.OZ,udfQxwpX$lD)m}?KLQ#rmE]zU1UHΏ )&-J7Hqï/4(ӅBGE1`;0厥g @fίH_AgOtA/A͆:i;[)(-G@  YTu6 `-\N#Y`hƋQf-(8CbG?_*'hI|z;>u7f?W~''Hظ~S/@ >)Jph{?H%G0Hv  B!аqh+3;C 0)TdI+V{qz!;yxR}$so-e1~=Ptsy'TͳTxtLی&;e1*VSFrùpj6 _o7V~CEK)46\~O Xӆ [eJ++#7z #uD8ኝ6 Ad{f8RJ @ _p8m w- C08O)6_:)1@*ZuD-d˜!QDH0*%@}a:c>9h,fz}4(JYGQ|9xLv\F5j74#!+n}{a†(aOR2 |(CQn0UE*o=+0W:H9oDu0pQ<,MD-'f}_. mƍjze}JZth/yR:1+`M΅hhH 6 kByiq?S%B2-?=A~䨸aBteK˓PEgbS6Z=WIx;DC\@dF~DA!˷p=}U±^4yyoK@BFFG"񤜘cCCPk)%t]~}OS{9݁`ֻr"TWYI-~"U}Bl$͒ 'QCiO1(ݵTJ :OrNUadd~]BCtFC!mJ!*H8i,0c.@8ψǝv9'Ze #O e$4ϽeB=dOJN4r qYKZA@tC1՟k}azAm* ,\>c@W0fXG@#MɺU5j%\6z`FXh6PL,ٔbtN~쯴2e %S0w(Y#)0g.Ji1-Ni3/Ɇ*N< 9X 뷝BXTff:8n4E-o@xAq4:7KCchf?DX-w P%eP<6#W&U7 e ~x-x'*5QQ4th gL!WGRś)P@wVpd?NGFu3Z56yV`O:wɔeьԋM>ɴat"]A#κb[4x "4H>Lml ]m$)P#< UT=i 4c1nXWaÈ+;AH' jda*js~5=t yC(ᛠz\{{o5QE e0 *=#̓9,q_#eGJ )I I20LUVC|p!aOiO ˤ9;vd:yAeC< e`:oz:B-R@cԕ߼X W3-UGa30V*%SZxgm?zP'ryjGem} #} MǖWN/i_|H}]B1toMW;"J\X@aÍwC<33+#.,;P3E$`ӫptM}_N|Th 8|U%2tXӬحx9޼g&Ҫ^С?sR>k[ƻT ._G;| 0&*(}7+9E>S!)h"*SE%p&{E#>9D>F\7vY0I 4@˽/EH~LPa)^y% V$d6:8PGq|AtY)lO^Hր 6 ?gF, Ev6td8ynuɎ2f@5<ݏ3`UbvVY4 aRE2SX؝HuslJj6t$)ܨEHs?IIt'eʂBIɍzi@@HKB$JE$ȁS\P6Y~-1,H(F. :ʨ~pTԂtL$s=XE?UK&ӨXWihW^'wY_bAgK{OƢtw~߇MȀ3j .ʣs.l,L:3[.B,|}MT Zx0}3<^lmמ| 핀-{P"E! T(# Iy'3ףPuTq/+|-j$[BBi:txTL-L(/牱)AҸH: M`emVr`ZB .lloT]T$%{bMns} 8?-~J>Xê? 6ڗw|s6{zNjS< ee y$S-|)"T9LTvTęS輗@ߋ;'_DZkjtĶ|[^ Vπu@E;0",d^?sHwTLJ;9<5X𽒴6vtd 6")uT>:wD}i)l4o 8!PFuF%D͆ oAy[>BւEld! pAņlZ*uk І7ޡ)|NL$az`Cp*^^Oyk{(>F-amZÉ xo oh,@Sa)ҝi[R@S9y{bc`j7fo)jט%1/kR.VK𿶢EobLjpF(ײO8WXiR,A8с~k\}E5EiÆH,`Œyubݪ",\#x꤅zF%f}LlĹF?FDaƶ|aVPLy3Ґ7mS;ig! tjzUOfQ|U0p\L*2c> FȆGW8|uV;n8Nn^NSnP@ %B4"Ez(ǪXJUR;%Xb`|Ҙ(q*Ν:YT:DQ@T3 +Kg2)UhwAFĉlтz^0EpAB1%@ep|nɇJ+3D@UifzO-v)Ԧ;"sd"R-tIטrEz遄X5LzTj@}! $7<$~*DBwNüaSec`?7ŧ܌@?߷Jʪ]=:^gk"J: vé8T_xƕ&"sPieXG cAE5#4? d+gUQ(xf`e)EDE;xEk̷D4k'pFfy|^PhÔ_e< `0}  e;p J*V0L!KQʍ(@*gܾO9>7A0DD8߸#XTywn7(m%ڎo u5J{+H?6IR @vqQꡉok>tqfh28̜q`*/; @!Z3em tEȍ]홡8* L;彏)ZuTy׃κn&u0VTɚ@J8|CG`[]e*&thCRkga^l8a>`e?E6P|C;<"aui6w]"TXesK`NCGpAA%)'lcG+ڎEa^Ãp_ªF>"jI~/g%? J} })ozMφY5ΛHӐf"*L6x-&_Lmuc-~M jiv:U J!\:XBEIyTJ(|vL*%OaxAjw-2.~Jqɨ?A$(w8r# Ӧ7@,rgreC=VKI{ql{\H+λ ޑvXMu9ittއod.*S=Mc?=njNcүqpOÞ\[D(ۂAӳV.|qL3X.6(h2DvvD5yTq%@:g/~Ax= lJJ(S7ӯ[P J\vymX\=_ֈy<2OG1S8dMQZ ϟ:,Rb$p>, J8}j /V;ǖ+rP(M[:b7RxިI AŸ!W]O?`'pVv{D&bRTv'gPj=/Mp Ȓ1X&49byg*ӑOT;S rX9xbU!ymޢT u_FAjF^d|ՠ̀U1`~~Lܥi bxd,8ȩU4#7jOn_~o gS&6`X7$]:\Kj@V."{uIp UqG/Z9 ֲ g,(}f!քQWnXU3oOyWQ}'`JK0l-8|l?VZtg.c3 u6cTJՋQ(%TV=0 cC($#9v*lF %kq\$S`|#8S^s?u 77q,6_8AgxĢ!rc3U ©vtf`9fٝKЪ.TF!iP_vlQ顣' o D):g$$fzKzeQ'jM0PG 84KLQlK vNw-L'r86 Ipc7o91;`(lX Y$Iڅ?TY? B2Q#O F).QaoYޫ+5t1NuM8V,{ka\Lj"xGGHqd,[ǘ Vv4<`v W5(:QgqXN!qLw4Rw?-@*0TL6Rdafa[9B_OlyځcXwk <3m:b큒덦Lԯ z5- #< mpb6&= GG䫆>̎봎 g-ܲBPvh6 (o4pJaBζ$<ta>6=AS|9bL~Z?A5W)aS{Dѡ XO) BRUtŽX @|=w\/*.cURUCo0@6 gݹ'}>efecs gL\KloUD? $4mG'%L]'y@`lFhx ~Hű`CڃOپ8>~~0Ҁ ii:=7#lXo 4𐍺* ]m:N! XPAyA]Kq P\ҳg}@ a1H?J~XCPGuG^hpW5Rvc+ͨǴDž)5O~Wjqר=[P0C'dc/S,c^TRݯ x{>:{zqO夗Q]w@># #VUbA%(N%6kӶp'N_}ذg<<,}qTZUxJÍk)KmS9-)wWu<;׆06H5Y|`h#=6%ӫ+Mf9S4`xyj0fՄDlV+d)pB)[5NȖFW?jٱU:pqjNl|tNt(?٩1kZKF:IQ Xķ; J~ZIR(c*}#/dA"PFΠbt5t|ZD,\z Vg)ԕs 4o1u&4Ò~%ՂeC>Ca<N,TĚS t,Ac-| !N ݏfa~+ NuOqyu؁~!#¨fO&Ur2"@;,&OqEw)qGk޵AqT5Nv*-GKÀ #"Ų!'&b]֋m18$@jf=+Lbn*<|Dbbw}Nk(mhSmhX<«~O/@F (O{_>c~O?IsXw,ȤmswJga-IHAүk?p\# (S;ʡ9ygqUj_r=m_\E@Σ~RYP8R@Z3^~XC*ٰ T_ GKR -F핈94^QLuө a]҈!@;3nv·>Pmd" 0n$tعϽBϤ鋴>7h ͍&, rtZE&6!4H(}S~ 1/|cY_)W\q#oIm[bNM'i Em`je7m|#jʅw2^شV@;ѻu{X ]a}#8 A`7 0()ى!&OB ՝qNg]J=ϽIPHqX RNQ=""Hտ,N+7UӵK15s3 ҁ-* }>>lGɿӌeCyz`7al:!JfƼ'39jכpAI!`7\8W;˴ ^mƟ~HU9-o?>_Y VvJWv:r2Q?Y˴!usSNue7{4t`XHC G#t?]&E*w^ۇۉJU0ty?m3䙵G#!D֟UVda4^r_0:\Ak/>a(tgg{/0N y WRA("MkVOdR*sg<<߀&6|\h#ttE)4&a3o${ނ'ZXe̥X2U-~_~0|2oT{>2xG0)(Xgskh/`[|BA䎕al1Q[#.Av쯨꞊"h WQdS#0e#?/2nIy}I9P{L@P/.>I 0 ;cGr懯 DAY\A?{JmĿ?A;aUn_ufPFN(3<AndxprZkqP䋚xvĀV9 ð_q r"}b-mx{ؚw¿UԀ̧/_!_-O8HY@B˕ Ý|Uh 2y}25dcE7ML+ BփtfR?;IZllƟsԼG׼وgMT5|-lzt>{4#M3˃x7R˫6vL 'q]K Ayv&Q̆!,,Ye;odo0Mǫc ϩ \g4x 92Ϸ8{#}{:ik|:x)]k nLG><` {Asr|Q=BF=pkGG3Z<4ķls-U,XePh5(o|?Ơ[}* i*)+\˯hpTi`qi fva ABjPX A7## >9S"2xFLM rV6x':ry?`[;[T8?JJR5f(# ;sJY;U>bΚpx3'0R=)%@:)Ts0UrU¾W]I!`.;PHQUiܨV btj0U?f}7;CuMű#{.bZ'+‘ 9󯣔Xu!LLHg3 O)0ʦNү~mi .yQַs \r $ jq %WyDZ}XW/}>%/l6;P"\B_Ji(:BpHc 3NɢB oI' qP *KJ7oQe/5;^5H3RO.*/EqSt&X|pP|S-<%yTS,5VPh%pz`s"Qf.))[}FϝGg\JLs*=  4"j#|teD}17;vg3KNi@P '\ 2o)+']_T@ ~W}$ 4`tf{ uEwrm+tqvUc۴#<.Jmׅ#;~\1ImcYf)_xJ%zZcC4o}|qVeʃv/t}S"% Gf>ݯ2ZrxAgyyuqo0*~|tP KB|fqE$"<=>ں^#)]8 ?9 GaM; k|0=N˓*_;S@n|dhK .Obg"7x ak .@@ Ɩw`Z&8d"Se E`mJhQgL,deJx[t/#T2zpjT aFu`ܨq`4 &S[x~ɏa)c|?ҡ':*\8\ _u;:"b&ptN E?P-"č(amS|:0_Wa\`jbQϩ{W7 nSLZE)/k)HJs흭U;{]xwV5ZiT֕iKIjM@ D ?d V]`?^igy*?D=͸K0[SeVh*lPʞXa(oOJag}ơAiȵfi=i8EFVi0^ j :HɡLLdj `SSR-e6)ID-!<# 0iًq8|R 25U31J9Kh7;;HKlA)7شS|S0kDI2[\Jp9:L J)T ~I4KXUݏyI]"TLgt5Db:W6DE,RK%vIRbLƉcuzs&'>IZ7i6MJ%6&c`6c@Yfsf{{-w,f\_wc[y/F1Dwc OpRV+aTۛmW"Bd}%ݱ:TǛꪘW*qmK{,G"coreL!6_VZ/, E+is۷`}X0n̫n#G]i>sьmnglC% ߰%siڶ4>H+UgMLdUp휶ÜPlw{w|^w[zZUpMb|R/kt(NG*Y:Z⊣yo>)(~y>@-0< .dvTJ8IaV=k t4S]lū\ cϜ뿻M[㗒'uQr[\;& )p1T \I@0Ý9*ḥ`^ՍР Wq~Uoj*_/=t7 ~]*/~y\@N%׮eeFBܺ%.+7nGZ(WsiۗOo"_̕t9#ocr29ܴ8$N2|)@g+J N޵bIrmE uvjLb[t?Y*U 1Ne d A!~]D7G>kKXzLL̠{ER-&|BݽU;)ckH)9tn3um\b:)S||G9Cm$ls cssDaƕ0/yu*"mJmV 馆-̵C0 :\SXO\OGq Y'gߍkVaE>1wT}KIjXQ4L1u,u=vgL g"QͻVBnO^'/wt+yG9ڪʍh(, PCN1G6oKDjS$[ha9 GyJφj4o ןdWc g2d|;Ж]2|! 0U802a} Djzլ:No%ZܳNQzVTp t#RZxl~RδHVwڱ,%?ouhqwVe +Al %oE`q,Ϙio,f XQM(>0Ejv2njl6\îTAu/:jkɊO7Z$R;k\ 皳52}D6SZ34rtݖÝCyqG*54}+apw|r&;K=DnI=wdqI?Ŝ/‚ "g=%f+h +b+Wv 4]*a3u;o-ϛz\(2d s@INUnb`"Jɤ)f>A}ptC#qn_I jތ9bٗBU>MACLObN{ڀ/=_t9ڔ$J9i)*gQ92| x,t\9.|t]/$1u/nWB':8lHG } 0\)\„hAh/#r'>^mre=XXz:s ]"Z‡^PTFRt :0yb?~ S7';1Se]_Mt/hLulp c:.p9{4g|?uCd|?.Y{FrsMq>Le Dz@0?BðY,w(޺mtH 3hUX |ԥ:l,9;}8^ΰ^ۊj,5 ڠ2"a<[za(Ov}wT>!s&pÌ *@fVkzik8D?ZIКXTZ<3nJ顪\RZW+KIP !ja0կ|;86Ew z=^u? 5HPA"]>2LKBHsBW6*>çN@R*K}>6٭#e, ɜ8Z2T#56#O>)zEvnKR膗M)im]A vЁ'k)o8 \\ղǛ]@Xְ|{Dlmy2 ҢA\kDaJ k]?<{qz< ,ުUR< }Jrmn' oYC/ ýĶ/5Ewzom*r@uݍ "* tJCh%WIS&{^=ÿWu`#X; Q7//`Ay c߷ZvУz>T&ُv)s ಣj"4 \ɶXq\[D!7EX,F״yҼ@B\|M#ΥBkkPĬ~"@<Fӕ"5DU7Z8h|GI`rV(.HmN Y[piG7vdx x<#KX>f? :ta,LUSMZ*6>up$G{ga!\o&!9qAt[)=xL=kċa"Hj$pWG')XqCVlɘ k9!/=Ϫ3>\>.hٞBC -P82K!+>bi|%$- .e!+MGۜV_]cS͞Ge ˆ~S۽תthu*R_杫HU0<Ȑe8"-_h0 p&1n57z]ޟ03 UM֛Lך1cMq|gRӊٴ-+ `AשgLq^a"3@[H$9&n ltr-sè_}YAor7"|:۹p?X¶ŎF\#,遛9D+`4LSPH/ӆ&X@Q"$%w!p +R^` Nr a%f@oisKKKdBO5{V)"cp+1oBgMC[lkᕿdQNX`:?thv_ۊ6v @dK](ⅉeM7ph)y<5N̹ !IǛj{ܒ,dKSbzw:J:!/ *:>1׻ACk񞕞u63+v @(bi3p#a$:G_^Q)u2QZzE;B+o:@]:%ڂzQӟ"H]K6 ĜעЁHpŏl t׋1/L])"ʮ޷0',?yҁ+ )Y&l= `t=;܎ԙāM'~mߕa'Y.eeW\/q"`si^vHo7q?pUXjw!V1̦6$۹Q6ND/yں!Q5p/rpݝ& %0Rt!5~-T*&L^z!0z\("+gneX@%*Bq%m?1rpBl烹`\u)NX]=dz i $f|pu;TwŴ9~'sK9 t*7\p\{%D:l \OA %Ln&BRc+ƇPRpR W((J*ӥx~ ~M-uqE/p1|Zfb>TodA'@QiLmvuYA:)2wcܕ,BrDQe9K\i@$X.{0\zJk'rl'rå hPIENDB`behave-1.2.6/docs/_static/behave_logo2.png0000644000076600000240000006707013244555737020506 0ustar jensstaff00000000000000PNG  IHDR1H$iCCPICC Profile8UoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+ pHYs  @IDATx'vsqw1 KJK o޺Tix{Nr>4!%}NrVgggggggk_/ |@6؝+>|mW;ujkNY{l8&4+LǬF֨~+сн,}WulUpq~+KǫP!unăi^ !֬I=կM:)dmgvBGmmԱlwQ#׃Nj a;֠^!C5-byjp+\qbֳۤsS$ΛٚoЈ3Y'y29`ieǼ2'ڣj^L4xFu26^a;kܰjZf.]\ mβ<'nɀ<_CqK8K9v .BOYC7C9-|"q1瞋jXuJH8e"C%V|~_oCdx6E6CR}:WMc[vթSyz]y`5$.9 <BU >nL 4T7NBt@6yXɏ1jFoml{A'FG[hsڠLg`UX@G('N//Ƈ6l[m|Poӡ9\N LK6/.OksC~[}W Y,eeo0,vCa@ad ̆c፿vpPV1իS3˻V }'/\#k;Dbx{(X{d.QI7vNf}$f0TeL esm?irub۳k[3f-.5dI<=q%S2ՋD{POyӠ@߹seifZ4VHoc|ds}L!v=Nlp-c8ZhVM@h C@ĸA3,z^QvB jbR'xV5 rYI&Cɛ4{H=aM|ZC2 [*rR!E4ذ}!6HF3v8۲5zrjnzhްJ"u(iM+ 0Exx6l5]wǷƍ u Ъtvj]ŶNz@uM4ޭ]A v=e|Vm,rOumhVN0 8sE%o10(ă슺(l'[us ɥ@_'MMY:Jux5?\|!$}~0@ . 8^C ;!TS_;V>b+lCsҜ"(K!!U 4[3f{綡R+GZwK 1^ tW^eiwiW P03ApXJǠp] =wղ֚m&&JsO8?%  ^~kFv.N^lKykNSQb;ĉ >Bq];Rb[hf[wD%ۤ%HJ o1 | kI GW/Db^1ב ߗSk, u ڎ߁2jBfvedZFn.\Tֲ%˷'wnҩo;v:tij$"(߫ccԏ7Cvl"y3$imZ >.eTJGAb])_)Ĕ6t ӫXG*衠.TF(Xqr͟;O\W DF#uqܼy5m:dθQ)AdldM60.-w:*oa ٞWIJ7r ȑ#sW#6h@7mmQ0L 9>L`B*HFj @P30t6fG5յ^1LojYnٺD7i0g<6Cz [%ŭ6e(َ|p4,˕km]{⯿uͷ췛?uUC%\'@;Xjy }?˯my}+p-mukC}֋^'_}#؂E+ߺMiZ2ײ?w,'+aCz獷瞟f n]Zkt:ǿ>{`|ѭ=4{gvab("Ϸw ]s2!HFkdMxSTQwZyO;,];%6gqYD3tY6_%c!dOUXB\jO*Y2߰qO+Yqg:wӻ %yyG.1ƒ Jv_ydؤK8OKn?{qϒ˯LIFJ,_^{}|7sAvYɟ{Djz:5Zݏ8~Kt3~g_枒GxoKv-y%cђ˯??ۼyk%wђK.]׿/ΒV}e,>tDdg^]J+,[* O&~*p@z:Lv}FEշkXB8^v7Sf9'\q'y 'vno,Ms?j |,{۷P> 1aӪ&4au׻얖 ݁Xf=7|ЕnfVTKOF3lmV[n};F :/ Ӭڬi#k٢]q$ֵ;pXآhW/+u_>v93wpȊ$+ ;`gNP/ﳯG]VvHC8PWTF$^o[IQNd0's&^Zl۷ROpˆ'(R{k%p,1=b56ZnԜ (V8"wD+vщI%9H ycopN,;+# 1Ro}^~mڼ^}m%G^g={tɂ>[nG9Yg.Uډ6ܰMlնlzܱڵͲf+.ڥN1RsP؋}YQOCO_L5~-{Q,z䴴3N v ֒KBĩX=O"!uZHmlrcPJݣ-;٪MEBN-_e:Cn+}+hu1mz؋q?g|b>;~{mNeǿneS\6~2 !lY]mUݿꕎV[%eAQc JװGZ۴鿴 o cF9{q۬U+ ClaZ88ഏ&{twX 0,>X>'*IF1iۖQBٺsT^Čv9`E+?gz2#JH/y@o-{@ƶ`=w}$r" Wߌ ͌Ǟ"4KL&L./¹ X' YgXOCUB=$C^Wʜz}BF}1 =e89BLxa?v -L?qUgGط>? XloZ̦vU9nd*F%o/asM[ U";ҁ.XlO<7߆oab/M[t`zT2$ 8ǨUJ~ nՎ| CołcK,1'`3FYH8jMVX+|z(Vvjs62Gb A 8f {K^_1co F^e S)[$sL%)p7,e;9>kԹhfoΰU^x ru&O\9o)rE ,Ͱ^MV*؞(f>^ӶMCQ$r)_|n&XpP,}R8NfgߞZ`:ِg۷]eD V!Xw~aC 9ulozƛCcam(lv[-k#"d֞j`,eM J>j6h h~TT͉Yk5BNrm#|:tZrܺI{4LRE'$1" MʻM?}A`#\{Az! mTQԿ<';p`yR{vR{-*-%W7w!yf]2.=g0cqw;X͝,՛ڍtK|=Xj"9ryv9]DNi,Ed(]K!{>mhh]yшTTI`465>w0'L缳ήoĝpf` 2p$3Ga7%AR}``@`Ć]ޢ`d!-Wt6=[(qt@Yi= COvb҃U, L!>DTR|npSǾ-"m4,"(,ɾ9Mm"3(sՌ­ݴG8mUFN\'1Nf6 o0&eHHb:'/ Uu:7/lR~{ Z/:mJGs; |@apK|ŀ~WgD-B?5>{0ćm/©mkOn?߉+htV[LcVCU3  EZ5tI\p88V,nMJ·*'_,u ̉/4Cc־F(9c'/XjNC0[27kHe(BOڻK3{MEv"Q!4_,X ϳ4dSy#R׃`vS•w(lpN&dd蘽E"ڰ9-YQwX Zԭ*(%g.P|H.(T2jwn)At*F. xBWI΂)J~x"": +;i  z) =C ^C cL:j$ L1% R_C)fuHMGf픕O)с6n?` 75MP #|'i!UC A$=~ZӗJ;ѫ6@\mV{KAY>)le?) -'^xu͉i@Bm%2𯎭01hש}e OiKוi`F!H'F`,g(/Q!GtH;y;gbʬֵ]#k}A&,RזNv89 #Ȑ+Z]l)IlrUH~{ң^sqӮ\my5.9pvV =M%5؆ bsLM= 0v _NS:VWZǛxTtg H6x8|fa]YU -6Fհu6f3!I=,C4W)ƨ!@?ޥR ȉ!c>,;OT}gM,w NmYc9w𞼘$:w/cbT{|k,Ϡ$ ZY IS$`IEѝX(8o+^=0;_dp6>̂a>u0)VJ~Hbꖿ̳k}UÆZa?,y<}s{/e7܊; LGGR.1\Y= l6/FL|B=&L: ZII4z@ sw:ـ$q(@&;43nq..ݰ GGD dVd SoQ[mi=hY^arwLl XKяxn)Us>ץH,YkO?OOB[ǻ(~Ё1$BXawɀ@i)Z6$% eQ]*j2Ve( 8_ȯ:;j&l^pR w-iuvƚ5t(>6rKd"ylY/3pU *'uy=eĝh$J1_t ΦG ڇؔIYT22U.-[ -ߙ׹!6iDVedL?fDcT(p fi aڣ{ӊ 1~Aޑ*C u#w*`ȥX<3m}PC5#@k{PIAdzth,`~c#$.\yi;4Ц(\.Wr#)^J NP:OAmL[KFܿġ%3믖Nri\ P.~K^}`9A+(͝Ҧ|q0XG{mM=sF(! *Ҙ3-05tՐjHh  Ae(xAvWpW˘Bx`gx4 - Oܓyp?Kk^p$CW3fKIW "{~V{ͮ wgr 92:QȠ2T~C~4cEUB逺!ޞn1rH3v/{UHF'n~2A$BF_7z*Qt=r.Zs`򉜜SW*ɹC׺{쁧, Գ y$L =' es}p@F79֥|~. X3KWlC6FV dNzAPIץR7NԺ/.Kp@Aր5.)ˀP'bRweƮH}#BlDhYBެ1uho־Nj<'Ä]$~<@ nѽlɳ⥛+o?r΅{Y>^SM6}viލC&` *"<^Q8 \SIwPxZFeX:#=*'*1F+h alʢp9Zpҥ4!ROҐ]?f ‡WdB,}cc>)dTEr!+X]P-8 I}B*Hĕ|JB~Zf(jBgAٸ)+C m܈.Y mТ֝iʋ@0V:dı=× 1C:ڶ]{3}zo c ,S`G.Se<̈́}mT)ON8, TUݸw#. ဘG BY頑Q/y+VCȤ*(C  / 8vC JTؐvD(Sq~O8gvS+:#ut;WĴ##" 0ϕރ)&h^I qs6 %"Fui+t-g{k2@]-uvP,]'FFD5:nĠlW'q hpp9 y8Qf|u4Sx)C8sɣ2`ǎ珝Nd:׫!T6Afc(@ 2^Ö/^$]߼r_y'$ɵ|5wM $k.]7ԑZ:\8b>JmISO! ;+ELtWXMO6Evvܠ|gsķJy?]}A̝:)zɌo~ GY42#Zt-s!5 iz/L` Ճ65|D}a qS},IQb:5>zNv bս 3vԵvH )!6.lᲭ Wk#~ EDEr9\C~اkk.ak+?|F !c-_LȪeF{GȜ*2"'%9} M=e}U#M}f>}Kڢ%Wpm؜k-CG.c' 9VRpa8/M$PC"?TE#'Ons$gLdD *$NX<8ɃtαSni;~plpwv揞fo^#"8,ŁiCPXaP'y 'ĩ ɲzo.-m]^8:GŎG+?9K[bw:c6Yȼ;LQT^^!&CG6v??}?/Xp*3|QT JT0;#8mAWsXD*XIOnO, %=]Ǖ덬7%&I< uzb._|XDwƄ}{womDf:oTP{*~ #M6WQliFQց%\:~dט&8+^+p@-05^ԝZf٪^z|Uײ)>b X~R2{qWk5UZJg&Ü},F}B(Jd-Bظs ^0*gfƸգEBDO|#bpHWQW*yU&H)Y[s{ κ6l\ ,מ Bc1h^γlQ@"'A21j*3o?~"c9ײEs@&u 1P)iDLٲQ}Ll.@LpWZ@tX#M|m  J{޺9ۓPc/P#iTLHiط|yt!PxdyvG{? gYrWe IA8X@& E UU*N4F\X>%JnIˡ|J!D2"V"8X7[?> I-42 TR /b?QU ฮ$D xHur}Z\>zi!BE _ bt] Vo2\$ʉ8_2dІh ul*-kVo̓k$.z'ʿdV4lt!@➯\R 5TK U ͥxWY$IHufG1ThNVnIyJۤICEDA̤[Qu6-]Q`q{-W[w="År·XO`laZ9H}̀~]$*_+!뺕tk`qUx@&Bmprߣo[6M%ît~Zo7֩\jBVXKεS֙C-iX[ґqPd_)RCzXGm>9GGnטwnf;HءA)" 7lxkj1gXK5mo`tb㤠QoIHG޶ǟ_n=Kn0K<3 >s82Gy*Awkvߴ6gfYwKh :hzj)uR8#p5L,CN;3)ʺaۮ}#" Iv[n:Etp>~K%N  ͧN8-X8߷,8gvmC #7X¶Hj+7- uC<Z۶im߾="ճ dl6qM[ 잧[Nr "D޺=ߞyE.gAy[ VʶG~k|o'et" 9~ˬkmңOY"l7E=nLPiQ;BWY2Xp6WzMG4W-RwO`6ֈJJV]\dY B7IIT e8 !F-8ڈQ9hO5#zC;°o] KeGRKLX&^ҫmw< ,[ZloXe>lnU7bR;EK{Җn-~]ȍXffoDwcMvl"t]ˤG:r.m<_~υk%9~S#S'Vz,D{?Z:4!"ί^qrsݬCOΖȭT mRC ~K?|ze՗!6Dr02i$7#-ֲWIoZjOؑC8:Hm4:m`=vN L^XIgu39$pA4_dCeETfY:xw9 kӴ0~xͲ[ư\(^d"6vdw/nϽL /,.g/_hڱA& 5{׭Mg_lDPՃFiAm,Æne ky7Z"UTtH?y('>S ;*wG!ԕpFݣ_dRT I N۠P'9{h̤6Ʈv':шEc}a4:Z{<}$h{,!X t<ڛխLEd:x%-фIZRÁʒڈ!byޜ%QȥC7<ԋ2~V_˅.BdtuU}왩 tN V6ɞ,[ac쉽3E™ 4bhp#} F㎯M,CP/4G`#qzY!k.7g u1͢JQvB03ƘV-==^7e i=:4W#E݁ZP SJ~7bHZ s+du8X n|lNǖ:ޡ8룡o:2ޞcΖFN H<*7qTv4>k)ZIr4Q5tо r b>^*~|>ՍF>Y$;BQLvgГdz)<'_9*˼◲oȃM@9uB7յKfuyo,!-[M+ kns[YS ۃx> ȁ:¡E D)0fQQũEB}QżbD%DT?]Vȍ`%VtCZdF, "s{yz }, k/ci;r-?2?tXDJFCXbz(Lnr1,N @>@ʥ[CiMq|i"6n)tdw،Ye p.̑tj)U[w:QAh`6UÁd9ZCD\+Bi*ePR:k:4ƨ_ @'^#NFQӖ΁=9^yl*Xm;)IDAT#H|[2Z,Ur1,EcV0[SwEgOBdk|6\7JyO&L00p3vY+7d,y*[ S!^wkYZkزE c,T$>iw;[;ڙ#oto޹㏯ۨ!\[0fsr tft+%p6N; !ɰZl$qgq4_~F/f%e)sK'.y4O"`Wȓ!,7^:Hm0uci6˼dhK|񣺊ȶZqtA9rDz?5=@4I<sIZaG!I܉zI@YǧmN}@"R i Yz+ ݨ[-=`bTP;&B9w r( =?([7uSdj+eiӽKҭ}r(JT.M1[ΐ)V G!NO'g؁0 ;«¢C#5a*@5(wuTCIi6(mwZ| z@AFe#8 ̴`O>ic-^K#1/S1'^-GT0ȟF72S7 iͺm o{]0 ?ѿ+C![*j,U #-a-]Xpq~}/.n3 QɆʟzIJvF2MY,JoC܄\fBa : ws%ͻm.B ț0 /l&bm yhnYVט VqfUuEkY Ean-ϳi4*$H#GWLmpfoW-Bj|nZҕ Q@^|T:@v1-̀[KZ]а!ĈLd=Y;U3X(E[^!*PWMH[y:ߣc /eA E &+FxG%ugo5(tH =Đefحߝbv97zh=ˬoϰѤ|}pQӀI~x9RI֐#+ r O\^P~70AbQ>:9硷<ѡ_<(\ h*5Le2ϒ+Bu"Wz\G\;t i%;8$ 2zˣU1$kQ %8(#a?tvPZ|[3C.A9n'Gj). dslش[;?{>}$c12qpR?sHY2*}#ŷ[[q?=sZo;yZXY%\K$^_5۫i8 H~U>}(گ^g26H1i;>ْ=L]MVm7?.2!@^Rp#9r!SOHAz`gݘo%-|ImvVj~-&*ttWWBe/~}Og`HɐݒI1cWWdzhnhVifRA isuL|zzB H'Uȟ5vʭO׵o:.1eU@E BJNA|ulS\1T.Pwz"pۺXk]\L`yniA=[|'ϓ9!>YPw߼Ŝ"t#Μ@dcH]X@>fd!Tc̰1~LW荱5-iue%tN4̊!4iyŁб#:'Yz~BT\)ue&st\)BB{P o-kՇLךl>,Iޢ}*U7H29|j<2tڕi$:/#[(KkeK'kB2lP7|74ͼA9Yi mMM5\u 0x7 ws2Nԅ?[WV[Y 3.jϙم~c!<+!Zco0_4$2 S2d1˾t<"MƨXIy1kuGI]*qʧ&+_Yʈ/ƫ[Ucx%q<84c:!Nm*wDX?ud!Je{{{hՄg 3O_%Lᕯ"Ha+4a HҼˡH x>IL@('0^T=bxíh(^9"y:cIEj1,y%5!tI#M\82ļ  )[W^qe!1DJoǫ/e><ӭq3t~_Ɲ\9:$TM>jY61GPy/w 0l/Biw_dٲL7>Ch܇zJ?uL&wTN* ?x46k/ŷ6 3qp{'$7n:v#[b*Y=nmH*1[f/uO%X:{H<h ՛`6U#T8ᗬ̳8qY}giֳڧ|3 g>чZ76I=è[ɰSrs-؁ڐ;?cN皧 N\eCY:ksf]:%qɆS9F!xyMf͡O{'=PyY9 (^υW@ ~_Dsʉw[thK1]KyM5k##mmκ]Hj+ѵ] +g:nvw#SuV\6Bԉ\b.,Ǭol#(uE I d8#S7ɍ`w'FA0Sjd_創"L5 ;jw [?\WVf,%J]ɄKz9\^Ao 0hK[wGXeX%گNgQtyo"*ҬD n߹Wu(C #C L6'K4> jϭӦ2HcqK1{B P!^}ѹ2LBZ ב&)I Z#rYd<St$j 6R.I_δvf~Ue!"̱bs53eE(^LEhr5 _ өCv{4tKu[XyzL ġ5_ek릝lR,cH% i.{ r"LLX\* f|< #34,k @Vw :#7luh(l jo;|b"R6iFlUD4keqh(i0GH)EL㖺,xra.!RFp+]5Xu`O G_Xg_cMڮ:dp2={vG:`-6KSR@"?*?N5V8wӞ r9Ɉcd3'N\':̪oq`~F5CUYw~\zYn-C٠ιx@.Y쵷oH%U++EDWWD\>2,uBw/cOҪtMڈӹKE^Z.K>qt"B> 2UoYFC|֭/ C   7. csq3WÚ$ Sq8DEAk²Q vqb yc e j<$s2ԉC,:Z4ѐ9Nԟ_c?&EL\ VڕgT#M<;^v❶R:oYmҝ,I~"Q-+y2K'pS3QZYOO9QM Dq*%{78:+ZҀ1 AЄH,:KFD1L>,^f)!gsfZ+2qXqI}5(ztW-T\L.sCR`vLmub$6jtbKjmSڵg];s.\ۢ Y:9sR42.:#LJ{ kA{fx[,>iLؕQ>󚩉}F,#E?Q~k,dISĞ ͮYuEpSkbR[1\8 7,z)[ˍe²TTNQZb|WUreÖr7rR4}u^ DJ72w\;Ϳ{t]trݚȷ. Q<(} @h Wņ5`l<^06 qI1!|0f7śgh4qf;sl$Txr=;H?BUu/ 'ެ"~`P]w,ϱ6Y_p(V3e a z kSsx/ H0 /ƭҕ Kt_' xu-V3tvZ;EHrX&T%c}u88ӥQٓ k7OˌmSe/ ZƹӷڵvW쵙[{IǷGzÝvy=-B,7=u6m뮐mcesƵwßhpe{5Vym5 )[F?ѕ}\12Qv 'x^gW-zc nܒ8K]9@|34Z'ILO5iDy:5H@]5L'OTUEBX#`/ZC]rzΘIy y]y{ۃB/RyO̠8 FDZɷa-qnUB2)Q֊rss, 0Z&6;c[u8Tf@xgy~YUnJWo4t6 =[Fcq 8񺉒3p|(+0\iuVt~/xrU><Q{/rߜ~kܭu2fI~_xTI}cGoH5̱i'q5ϕT,q%d: ;Vr:kԙ s& @hСcELwCcGho+3ùHDGG9BG?T=u!">JRAcRUC!8C\ \[|l[ܢ A9, izwIrd>{'vu\ͶW)"GGۈYÜH$cd*?8} Ԥ2H`$l=~/!ϽrZ` TV,74۝0fԫmT4Ĭ_n^KUӖ-y{Pꥣ.~{VWŚ4!*D$DN%C,JɀZ]|6WSD8n#TP <)(ݩƋVn?#T~ 1'@ }k >_%o|L&m|uT-dTp,}}~ւ}!5t+~W1L͢-<Swr6~QLgyCZ+f gq5[O-ʏϼYNVOR["~*{mFeXf{l\ CZ_JD Uh[YL!,SunKSf@a2~vb6HELp5<'T1SIfz&(B"{F"az_ xTuM$txܶ=7m|<"FHW2n@wP^t|V,T" ת!SI( bf_d-NNrDiЈĀp$Y𚾧\~jnirf.6RS!be|ߖifF:*,a2d a\k=|xMgLrG\,q(]9׵f0P}q|5!." YihM98%pC _|@?pX@@qSƆc.p Â3umz jsdduaF'!{_J5qu6ji"nbfRA`8#db C1V!wv2w!n* n0P\^\C+@Vh39 I&@ ؊'jh}{҆PU|X3aN%b3u߉ժ2 (zbv؍0FYo*?3j!<&?XPX#楁 =A x+xR?zһPDV 7eC*&#v~Ilil @Qu-`[b1^q^Q[83Vܫ X{_eSa!H}0 (p窆~Xة DJCy;Rd2Ǚ#uuDZo4I4@E{K<5q2dk+ O||5YGfu0h5.kvJ4PL+ʇpՀl<$E/ 4\_)?:[, A)eQ1˰M>X Ԓ)U1ͯI# \bS5qԮ:zm|85i8ϑj@ۄV2kA"aH̞>E5֪\25afX Q0">##/orl1;o*bĘ'﹮"pڭvju$g8Hup=$ 6]nk3z&kMvmF]O0؄wmud_IENDB`behave-1.2.6/docs/_static/behave_logo3.png0000644000076600000240000004757213244555737020514 0ustar jensstaff00000000000000PNG  IHDR1H$iCCPICC Profile8UoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+ pHYs  @IDATx gGU?$do=-Y@ȢP(.,*7p)) )o jl""AABX&ȞLIOݹ~3%3'{o/O>}}qOn7N;|wqGwEuwy}{Fag@s6?k[M/Y>oQ@Y{o{QIu򔟔?)=m @{vk֬I֒rUysOwwWdr<ϊ{2g7|swַow#<3M6Ustvؘqp^>Z[n鮹n^{r!kkk׮8n}ZؠtW]uUYT (WeυIO\&X/s!V3wEk?|n ]~U0qR"\oZOns%TG/š/{衇]wen"X&җ@~d M@x̢_;E:ޯ!A@+_J:ڎV뮻n۰aCNB57*|-h> "s1nZx-#IWSa{XG;|W5a \}Q݅\؝zʩW\yeo~ۮ(u ~Wӳ_nXE#4͛3L9ui/&>6lіzkDx ׾[iL[|-t5B|-v??=^'wU}$[ H Wz}ܽo?ut(³\g闣mh$@xim8B.f2 wn=Ad_~G]Ŵ3a+ֿjSaְkw?=OGTT,۫ţr BF 01 -;c? oȟ#pf<gpsMyWbhL:'CjƔ*7ubvi\ߞ{9d{Nu{XmN;V"!Ok?Eihe|=4v0S>f%pcZ-j(OӢ-m}.W\e$PJ=2K[ၠ>~˛˿K?w]PZgQ9-[_w\c Feg++ xRg$#i%o]U]Ѝ|y])k/U=7SHH9Ҕ5\3L_*o9W\\ؿtCî:蠺ВAՕ/-hQBk2itO't)/\pAu;L:ȣi闛A]k=:jB=9ϩcⳟȖ}b=k C 7Pcn65\Gx5"XGb  `;xjP N|@4iʲRԮ<|9|DFK2&zH?GHTdɓk+mIׇƦ_~@K#2 aR;'vwX "F[}>Qk_]~gblfDh&|!}"HNqLVCc°UYi~u{ϠMkG5iJJ{ԍ?7z\o}Ն|`};WɅ [j-D§5%G|3?C?T}.T/5hBnY>~zK~kS7wEef Ie׬ٳ>KgbMhWϼ7⵺iέ D%+cO}{c]H3Cr4,a!Tf΀#&r )0웥:y\s\yy/WLiqj̏Yk+ 4s/8G>Ӟ1OR3/0CW#D7|SwY/ @`@OzKP{WaUx__}__(Uf|eɮB)^ W۷ܻA5eu5+9Y%/ה!dbΘri[wVh̉4|+^}Ճm# $i/xtQJiq@N%ʟD! c;UhIڷZZ:?u?c?6{gId!m/BRAhYmy{,_Y1!2pt70w&?LvY`6:n}F&! Ih_ocݧ_4 €z饗 ծfi+kZsҵ-{- 5WkV<*9hf!|لr0"U\2L$Է-u^җִ??N8D@G^w??^ WՄ3Y/zы Mڹ GqBk{O֖I3?3x;b mO}S; E9vN8f7Y?n~~G>}ot"&YgUyX'U xu"U;;W-CJ`"lrǚ_r;n00$WB;κPg?۝qU0 HcS\{^&, nߺ'hxPfBCg>|: e\a%O ;3__ 믿N8s/B?ɧ\'e'x[py{^׼[bf\L>O/'Uv[` yji9)A58H;s@{޺0 *1zU7DSN~'`nmH;p|1i1ٟ*|i/e ցW wN{2RiH&As|ڸݻ-fX4cUIPMA[غ^x}ȘO³5K0m M0Uq2xFr,a2~ W]Fi>hd~#/>&ݽ E L~Odt;vsEcZEA3fLhn'xI'ugV$ j|> !"?JWzE6 &<1MυhP{ Y!|q9JfM'=Iw\ʹ.m0 )uRA&MzQ}$dɘ2-l~cOHsd0ҀC$t{̱U\s5%}mb)A+xg>P6h4iϬoWqL[+?sʹ V4%,N~^#p~Vwita3878hlG~KzֳrOY7-BL\A<#MKNEg,Y=ko@y@xIH<0CT*Dk^>vvnVӸw^h¼F:fv^ZS_ź:wy}2ƍUS)􄷋SvA"%U=ќMz ~cLFԾuE `"@:I"+ֺ)|P[0}uG ` F}CRNCTDʻi56*W[ :#,/M:BØMGi61avn O"8L$`>*S4fFVOV,>b.7 W+)/=IA[ƽ%kn,qTh10D-Rޡvء3/VȄ]>AmڬpmӇm_R+OLb&P&M X>&j?3亅 nl4!f͢a鈤hNT?ʋD=#Ki3hФ?s|R-neӞo)n.>|6/j8)hqu+x!iV'ԞA18+g7^lQ7tz-^}8q`Dy̔V?U3~4mCE~:ł  0JK0!ʋ(+4*J0Fgt1_KޡNm+s0mí|i5o^/0tDX \ f~] FY4BY:0COl@`B3WL qp˹[( ^1:!t#ՎǵeyM2?Oc.ޠG%_f܎E 'ڳDoԇXCm?TNPÏzի^+{\HJL0fh뮯޾#mFKJgrk.ѕ7P~bՆ` 1zJ"-gХR 0 *LǢL&p1i+wn>hiB^oҌ7C=̓jUօ mOgr->:L"| iaQr˕&6> 6nSo@u,hy4aV>A# h$]Zp~kٖq c)B 2W)sW! `ſkJ |i'y>fpG#l Z~-U!&i׊ZXv $/<Wv&mW.c 2*0QG'KB!aFf *>8Z ר8F/q)i$պ ffp!i dx:EؼyAOj?I&ǜB؈51ҏ>@MVyO}' C/a ̊j9>gk9$&&IiE{Xf,Ai|eur3 -1HyM"+O^b*h3Ӏ uH`PϺuwk㢥rJ]!]{h:C@d'BHk{(34q)Ig Sp9}#K_X'_P9.}--v)\`BᷘшX.=+YK!0P:N{m9i)ӦOD;  Bc~~nYr}+r҉' Lt+=o?k }*i ߘP8}d9X !da„AG!h˺7X\Z&YJ$鬫cI)o+oG;𤭃8NX$S%cf;B˜\ [v?lMK2Hi VNDcJMʑ5DZs0}cE 3R04ȟ\w1Ml }1i; a3$>~_C [:iɌϬDLv0Oۿ~er Bx?7)/6y\,<|㾣IK@@,7u_׫Ŀ2#ʌL50 h&Msp@D5ng@3-hS, )}SƜזv7WLgꥃ#6xVy3`hkSC%`/@h7.dp)-~<:yk[uB+z[{c̕[a8fF׼@Lk:Q`ZT7jF fi2ZQvT`^׎P>Z=4G'TomAԆ6*<vX9EST[ʁ-T"f$zM$+8dzp, o<5GZKź! &NUӶm}ǫBm^ׯPiSĈC@Զ ߱o,g1 !!G0#hq7:^X__?2T/hB87m7\a4- Vb~M-Fz/}*3&szݠ ]ҧ|>V$^m8/>>l 1q $XVx>tOyAIolf"D6݇~XJ[|PfPQ[^> |II dyFtlhd5`AKIky!/ueh ])OӖ4~N$,77׾jF}K.hMu;^+S+wp&eh?4xZ[NWf;%j!'SSop8VfqhH$+lObDFA@䖨庴R4/5.5sgctw\.ǒaZJ+1L{ы^TgQ\ edGܤZq36U?K rgI#M= ,@[eNiPTtι1=HC$œޠx}\IJ;5\2|&G<9 /]yd-#/pv~p⩱DX_&.I wNq|ƘL-&?0ƄzN+0פaXJ} `TfG}t͊" .ZHZ`O| LwtlTP-ժm}T+כԷ>0KhDX/mT|=p +f%I h›vk+ ʻGV0 ^UEF[rA&!)"ZWcBle6yC?pxTjd%$1B"cng'6m Svd>f-ܺ;o0^xj6 Z%7bqE(Fdև ϳY$t+zь^7|-*T]3i4DHN;|K^:)GphPE@1PL\>o D#]ÈԎmɑ hE)W8ytXa͚Ĥsv1Q_jzh`Q1֙`|+}+q S_W&7/qfȸ7vZ$e,jbv3+C/!pMyaQGjl]ZeKo¢BGx?+{k˨Y:AUwm>:eAQhڲ `B0x~8 7<O(lX= ;Ce҂7We 39c%<75rU}eJv>]N2#Lh%_F}I uﹴu,Q٘d삞4\š!@Нwn^br'uHYyL Ixm>>_o>TP'm>x4q( Ÿ!mA~yy-m0 uʪLK (;ԱWFYgmqL8 Jx*BNMZ>$~[uťӸ5 74 V& (-hC:@(GɌ-~uW_"o#A9e@NbӖMZi?27R|=*ma14Y0ZXrP۹ke [D<m[N $WX`|u TaGoMKfP'<kώַBvs!Ke ΦeoǧaRQ>mUù6 PL1CX;&@u4+%~@[7Y;bBZP:]N]CP 7+0.fMefKgjƠPA'|h\Lk:+3V| mtO;&2WDt|x0M7W\|i^ߴ= +gP?\_>PW^?6Iz׸_>;s0q0fEDX<}Cp)e[)a`4)B{+{xJ iQj'Y"8x9;?7_q(7W@ weuK;L; 菾zFAcὧ9 xmɳ=[<56 2P΄/m]lEiNFDl_񊗗4Z1~2;O.dՇ?.֒wdnLaN{mWvQBkam!t Gv+Bcn̉}OO߽kq W >aw>~TGF!`è!lFn eiʢNn"{lu {ՀLBgI3X٦ / e"3u)GTSOri~~/_i+ g&`!/e N=Ь.5Gsde"OC`ғ:Ma_ŸࢁiSFg5L  ,a\9/4{4zhhG[.8p$m]Q 8r=hYi4)O}mט_^^xe\r5 x)\/sE*lߴ#-!f2MACH9Na M=ae"RL>m@+fIz8GBDuBc˗>sQcڝB2&M @(B1!_,=k^ ~h:׍Tx2,aE$w2TA"h +r-'B ? c~b^]1H{b>꿾D&m TvR=`ik/!4YmN OZP̕0nVJ  3዆KovBnE۶roM~efD$#@qstBaeejԏ9(4A*_fY_fh&o/ѧ~9pP*4wQ?*$WvwWqGeۅ^r Q$.D |̶k˂ҘD25ϔM>z/}0l[^N|xƺ*7#hM|[iCf;ZL]磌v[ z3̯zxnx,`Q,^Cr_TsG+0p; oF^_2,U3}CLlP雝i OSv[\v43E(R~$Cƥߞ&׻K a( }2ʘbA~ <?£E\@&jBI\Qa}@I ^Z_P"/ jgo !  Ba5kU$,7W~WmGkkw<ڂ -d# mOq?Ҏ񏿏&Q@*u[y4@l%yտZ8ůx+/ak~~ !*W8m 8=ZBC C# HQwuB &NE?bvZ{h=N|O}" wx#7ł74)NpA "ӛ0|RXK-DhP=/!C YV1 ;5gyfG[|mu Mt۾rhۆfb`h~.|rm;V>D6n+F逄> \[bl#KƎ4\UG+h&owwuo݇1.Z@&ZոqrǝwYiw1r?k( J|#\!|*504714'Gmmjv} tm)O0hh2lmB _uu%NKp=!.W>I|&(ݬn ,ruv9!Km?nnuDuXA>{u꺩HNf1hdyi|JHX;_]nA򾋾Ҫ4J{^ZBO)ģiN }} jT RgѬyGma</quSAŅK+lMWWv`(/IءߍD1 뺗Q\?coRZyNHc"X,oSΡ+ hG<'!@^x%h{xߤߛ MP@Gׅ$h8?×_>蹆9A[# |`3é.ͳU2'N +M|M|>'{g7?@ XXrKԝx/P(r{>=iVj}j#k^~kgW1Xf,Ÿ:X!OIcty 14i [4?18g]C &iy.<8vǢ3 ٣Ϗ|zPi3Y[B$} bg-\| "A'X(@]( -7>Nµ'~8g%b 6 ^LM/( iZ02%\ M:YkUhW.z (KA_!;{E!+d:zhE3xN״+qH╣W QLaeX]f0iM 2-`F!Cui 313SJяd`320+ "|h_ (rQ9I"jWMy8  ~04x]iVF 54Қ8h }]@wz5AjZy?mЂOzғjhC;}LZ Lr3?JOcxc, ̲`q▀H7{\~kJG =#q !G>jW;0H ai|ʃ"meܷ/ISwm9>Ѐ豐S>GZzte+@.e3AQiF۩cy #ψ D P2EpLτ,]fF'!\N'iO= pw/o`'( ||wKl;e0j??xp{#|سˎdvpvH͕ 7Rms?#u>3h ?NZH|L L>裪U)d?& vνgҖgOK@};>\/<0y\@zڐ޶ϔ;ΚW\KEPȃd23ZrLW0 [%e-e GvY% rp䝖U_:g>mڍh{xFs3dOzbL?>2SkNLHW3tVD͟ % FdIT;vqYQ)|@&&0C-S. d知|IN:)Dl$!fSOm8EwKx[A{?m^^3*[HGۙ4p9Z Q ӄK]![N>?ҍ˟bO/GZ؄iX ^, Sh & Bª, bJ>6m%eVm6ŧ/ο9)2zm~e7i4F[Peu| Gebv1 &F1;i2/b<[Fڶ7 g4e!-&6yBhWpRgt E+k[sYK\,F;ꆖμsڌ)Ig0)&x00ܔ?mޖڒ}6=O9!t;T 6m}I>IsGvT?Hh?35v%VFFPrIZ*k0 0 2h>ڲ3"جL{%i  8'oOzp۲RP|[ŝ/Ɗ2Sܟ3Հmkc NZLW @ڤMcZxI8!<ڲ >ۯ|dѠ1C oD-3WOi m!RCPU/)he?M;;Հh*rK`V'Ǵ޳0-liςe hZ'Nd!'Mʔ{;9FG!+l? }%a数c]GtRwuŧSO9uۑrygbCآMiY{NeoE>h3ڼIWC5` P!C&ì\h{M*ٿUԧ>i9:̭#N҂>>+ex|Ҕcf *!eIZ}-f90فk6|w ] bw>E ni!{svEШMdfht ba:-`0 PjO|'ơ7)I\|nn#<:aN &'> ) _܄]XL{{Eԋ ^'Za?v{nzAh[0SLf k$ !$7[\ Q:-bج'Lp=[z^ ^mr`тvPpH 04חt^ u( IDAT_ND2lQC}4{c>oڍqMKp&m]g hʎp,kuL*aձ圞hB&M[|.-bA &)vZVr^6fT}Y:Q|?>u7rvCApSzٖ3兮e#e: p 3Հ]W^D7+1eZ0kVȌ<;Ih[Ut|C`р4b^-2=/q;*O jhSe&4!-98v,Lr|];`5kQ _̧?S;.]ђ8q|+@i0u[d~[:!! n_"X4Q_hA F[hhSO~qx 5V-!x;%T΂m`;bLE- T)L_dඵpF %)%B6Bh p Nv,xqZiܬN(}HiBp#xD!C ioZr;D9h&bh<)0FҊEX@O?k_=*_sƦ^0})ʇկn0,9YoBCVV:\CuwV~][: Wu1"vd,Zkw53.(_FaEmdbK#*N[fgG`Ű7Lam/gyET)p}r̖A+kU ]&Q_F;1-!u/My2;J*wڵc ="DPق_1q l lܸv݇ &E}(h ;thЙ;7Yg=wow 8Q ʗF(.q!3k&LMsss q@8U{ ŤΠw6g.(ت3hpw.Mi@ommY%f+-_%|ˤa_|6-8p43[tW` 3 fؤPᙼXf%'Fm Cŀȓg 9']~ξGGhsZ[a ;ITӈz:o30"H9Wf)pLp}qXpED0I|bݯ0=^ L As~ }g0#ah@1@4Nvɀ'͕ǜ;k pHMt%)xv/*e5Bx{ӞV'o?\t)'X&S~e |PŤvjf-@3|4j*Ni|iL5 b ܺu{w7\bVOOge~mDs{6P9vb?ۑ&>}ӷmmc\fNVy1 0r mk]ԃvn3< ۊG֛DۤIa[qv.޵H=:vdnuߐl?ϫ ;L1*Ҋ8@~ʠg|0}{ @8*"p fKOH 7.MO1_unAXwE;COjYռe~>fy95!*d֞OU}H}3*l_ϱv+ >'$ c FVJ UҬTQg58'GtjXpa߷X wEv \ ڲ) EGsr^nص80P9@쌖 kz`~a)zl0 &Efc=[:sݽs`hƳK6Ó,&Vl16 i|"HatO#Ϋ1մJGݳ"ޢ$qPiñq` `IICuvݷ8pMk4Cuw790U{N;EF A~!Y^-ծrs'-P^?(tLuRwsGB]3 DGnz/X4op L' 9cAqzFٍfs`tͿRDQpp`H xr~a5mHy fj#TVx}ތcbe3 bj/4.&y ݝP^;#4 Bw!~%yiQم.3 Fђ){umyS]Ж :v P쳰/|ϻfj싩*Okp۪w p`j!dw]pUjb$djz[WP욄]sWM?~ńIENDB`behave-1.2.6/docs/_themes/0000755000076600000240000000000013244564040015413 5ustar jensstaff00000000000000behave-1.2.6/docs/_themes/kr/0000755000076600000240000000000013244564040016027 5ustar jensstaff00000000000000behave-1.2.6/docs/_themes/kr/layout.html0000644000076600000240000000166213244555737020254 0ustar jensstaff00000000000000{%- extends "basic/layout.html" %} {%- block extrahead %} {{ super() }} {% if theme_touch_icon %} {% endif %} {% endblock %} {%- block relbar2 %}{% endblock %} {%- block footer %} Fork me on GitHub {%- endblock %} behave-1.2.6/docs/_themes/kr/relations.html0000644000076600000240000000111613244555737020731 0ustar jensstaff00000000000000

    Related Topics

    behave-1.2.6/docs/_themes/kr/static/0000755000076600000240000000000013244564040017316 5ustar jensstaff00000000000000behave-1.2.6/docs/_themes/kr/static/flasky.css_t0000644000076600000240000001666013244555737021672 0ustar jensstaff00000000000000/* * flasky.css_t * ~~~~~~~~~~~~ * * :copyright: Copyright 2010 by Armin Ronacher. Modifications by Kenneth Reitz. * :license: Flask Design License, see LICENSE for details. */ {% set page_width = '940px' %} {% set sidebar_width = '220px' %} @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro'; font-size: 17px; background-color: white; color: #000; margin: 0; padding: 0; } div.document { width: {{ page_width }}; margin: 30px auto 0 auto; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 0 0 0 {{ sidebar_width }}; } div.sphinxsidebar { width: {{ sidebar_width }}; } hr { border: 1px solid #B1B4B6; } div.body { background-color: #ffffff; color: #3E4349; padding: 0 30px 0 30px; } img.floatingflask { padding: 0 0 10px 10px; float: right; } div.footer { width: {{ page_width }}; margin: 20px auto 30px auto; font-size: 14px; color: #888; text-align: right; } div.footer a { color: #888; } div.related { display: none; } div.sphinxsidebar a { color: #444; text-decoration: none; border-bottom: 1px dotted #999; } div.sphinxsidebar a:hover { border-bottom: 1px solid #999; } div.sphinxsidebar { font-size: 14px; line-height: 1.5; } div.sphinxsidebarwrapper { padding: 18px 10px; } div.sphinxsidebarwrapper p.logo { padding: 0; margin: -10px 0 0 -20px; text-align: center; } div.sphinxsidebar h3, div.sphinxsidebar h4 { font-family: 'Garamond', 'Georgia', serif; color: #444; font-size: 24px; font-weight: normal; margin: 0 0 5px 0; padding: 0; } div.sphinxsidebar h4 { font-size: 20px; } div.sphinxsidebar h3 a { color: #444; } div.sphinxsidebar p.logo a, div.sphinxsidebar h3 a, div.sphinxsidebar p.logo a:hover, div.sphinxsidebar h3 a:hover { border: none; } div.sphinxsidebar p { color: #555; margin: 10px 0; } div.sphinxsidebar ul { margin: 10px 0; padding: 0; color: #000; } div.sphinxsidebar input { border: 1px solid #ccc; font-family: 'Georgia', serif; font-size: 1em; } /* -- body styles ----------------------------------------------------------- */ a { color: #004B6B; text-decoration: underline; } a:hover { color: #6D4100; text-decoration: underline; } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; margin: 30px 0px 10px 0px; padding: 0; } div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } div.body h2 { font-size: 180%; } div.body h3 { font-size: 150%; } div.body h4 { font-size: 130%; } div.body h5 { font-size: 100%; } div.body h6 { font-size: 100%; } a.headerlink { color: #ddd; padding: 0 4px; text-decoration: none; } a.headerlink:hover { color: #444; background: #eaeaea; } div.body p, div.body dd, div.body li { line-height: 1.4em; } div.admonition { background: #fafafa; margin: 20px -30px; padding: 10px 30px; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; } div.admonition tt.xref, div.admonition a tt { border-bottom: 1px solid #fafafa; } dd div.admonition { margin-left: -60px; padding-left: 60px; } div.admonition p.admonition-title { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; font-size: 24px; margin: 0 0 10px 0; padding: 0; line-height: 1; } div.admonition p.last { margin-bottom: 0; } div.highlight { background-color: white; } dt:target, .highlight { background: #FAF3E8; } div.note { background-color: #eee; border: 1px solid #ccc; } div.seealso { background-color: #ffc; border: 1px solid #ff6; } div.topic { background-color: #eee; } p.admonition-title { display: inline; } p.admonition-title:after { content: ":"; } pre, tt { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.9em; } img.screenshot { } tt.descname, tt.descclassname { font-size: 0.95em; } tt.descname { padding-right: 0.08em; } img.screenshot { -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils { border: 1px solid #888; -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils td, table.docutils th { border: 1px solid #888; padding: 0.25em 0.7em; } table.field-list, table.footnote { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } table.footnote { margin: 15px 0; width: 100%; border: 1px solid #eee; background: #fdfdfd; font-size: 0.9em; } table.footnote + table.footnote { margin-top: -15px; border-top: none; } table.field-list th { padding: 0 0.8em 0 0; } table.field-list td { padding: 0; } table.footnote td.label { width: 0px; padding: 0.3em 0 0.3em 0.5em; } table.footnote td { padding: 0.3em 0.5em; } dl { margin: 0; padding: 0; } dl dd { margin-left: 30px; } blockquote { margin: 0 0 0 30px; padding: 0; } ul, ol { margin: 10px 0 10px 30px; padding: 0; } pre { background: #eee; padding: 7px 30px; margin: 15px -30px; line-height: 1.3em; } dl pre, blockquote pre, li pre { margin-left: -60px; padding-left: 60px; } dl dl pre { margin-left: -90px; padding-left: 90px; } tt { background-color: #ecf0f3; color: #222; /* padding: 1px 2px; */ } tt.xref, a tt { background-color: #FBFBFB; border-bottom: 1px solid white; } a.reference { text-decoration: none; border-bottom: 1px dotted #004B6B; } a.reference:hover { border-bottom: 1px solid #6D4100; } a.footnote-reference { text-decoration: none; font-size: 0.7em; vertical-align: top; border-bottom: 1px dotted #004B6B; } a.footnote-reference:hover { border-bottom: 1px solid #6D4100; } a:hover tt { background: #EEE; } @media screen and (max-width: 600px) { div.sphinxsidebar { display: none; } div.document { width: 100%; } div.documentwrapper { margin-left: 0; margin-top: 0; margin-right: 0; margin-bottom: 0; } div.bodywrapper { margin-top: 0; margin-right: 0; margin-bottom: 0; margin-left: 0; } ul { margin-left: 0; } .document { width: auto; } .footer { width: auto; } .bodywrapper { margin: 0; } .footer { width: auto; } .github { display: none; } } /* scrollbars */ ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-button:start:decrement, ::-webkit-scrollbar-button:end:increment { display: block; height: 10px; } ::-webkit-scrollbar-button:vertical:increment { background-color: #fff; } ::-webkit-scrollbar-track-piece { background-color: #eee; -webkit-border-radius: 3px; } ::-webkit-scrollbar-thumb:vertical { height: 50px; background-color: #ccc; -webkit-border-radius: 3px; } ::-webkit-scrollbar-thumb:horizontal { width: 50px; background-color: #ccc; -webkit-border-radius: 3px; } /* misc. */ .revsys-inline { display: none!important; }behave-1.2.6/docs/_themes/kr/static/small_flask.css0000644000076600000240000000215513244555737022340 0ustar jensstaff00000000000000/* * small_flask.css_t * ~~~~~~~~~~~~~~~~~ * * :copyright: Copyright 2010 by Armin Ronacher. * :license: Flask Design License, see LICENSE for details. */ body { margin: 0; padding: 20px 30px; } div.documentwrapper { float: none; background: white; } div.sphinxsidebar { display: block; float: none; width: 102.5%; margin: 50px -30px -20px -30px; padding: 10px 20px; background: #333; color: white; } div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, div.sphinxsidebar h3 a { color: white; } div.sphinxsidebar a { color: #aaa; } div.sphinxsidebar p.logo { display: none; } div.document { width: 100%; margin: 0; } div.related { display: block; margin: 0; padding: 10px 0 20px 0; } div.related ul, div.related ul li { margin: 0; padding: 0; } div.footer { display: none; } div.bodywrapper { margin: 0; } div.body { min-height: 0; padding: 0; } .rtd_doc_footer { display: none; } .document { width: auto; } .footer { width: auto; } .footer { width: auto; } .github { display: none; }behave-1.2.6/docs/_themes/kr/theme.conf0000644000076600000240000000017213244555737020015 0ustar jensstaff00000000000000[theme] inherit = basic stylesheet = flasky.css pygments_style = flask_theme_support.FlaskyStyle [options] touch_icon = behave-1.2.6/docs/_themes/kr_small/0000755000076600000240000000000013244564040017217 5ustar jensstaff00000000000000behave-1.2.6/docs/_themes/kr_small/layout.html0000644000076600000240000000125313244555737021440 0ustar jensstaff00000000000000{% extends "basic/layout.html" %} {% block header %} {{ super() }} {% if pagename == 'index' %}
    {% endif %} {% endblock %} {% block footer %} {% if pagename == 'index' %}
    {% endif %} {% endblock %} {# do not display relbars #} {% block relbar1 %}{% endblock %} {% block relbar2 %} {% if theme_github_fork %} Fork me on GitHub {% endif %} {% endblock %} {% block sidebar1 %}{% endblock %} {% block sidebar2 %}{% endblock %} behave-1.2.6/docs/_themes/kr_small/static/0000755000076600000240000000000013244564040020506 5ustar jensstaff00000000000000behave-1.2.6/docs/_themes/kr_small/static/flasky.css_t0000644000076600000240000001100113244555737023042 0ustar jensstaff00000000000000/* * flasky.css_t * ~~~~~~~~~~~~ * * Sphinx stylesheet -- flasky theme based on nature theme. * * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. * :license: BSD, see LICENSE for details. * */ @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: 'Georgia', serif; font-size: 17px; color: #000; background: white; margin: 0; padding: 0; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 40px auto 0 auto; width: 700px; } hr { border: 1px solid #B1B4B6; } div.body { background-color: #ffffff; color: #3E4349; padding: 0 30px 30px 30px; } img.floatingflask { padding: 0 0 10px 10px; float: right; } div.footer { text-align: right; color: #888; padding: 10px; font-size: 14px; width: 650px; margin: 0 auto 40px auto; } div.footer a { color: #888; text-decoration: underline; } div.related { line-height: 32px; color: #888; } div.related ul { padding: 0 0 0 10px; } div.related a { color: #444; } /* -- body styles ----------------------------------------------------------- */ a { color: #004B6B; text-decoration: underline; } a:hover { color: #6D4100; text-decoration: underline; } div.body { padding-bottom: 40px; /* saved for footer */ } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; margin: 30px 0px 10px 0px; padding: 0; } {% if theme_index_logo %} div.indexwrapper h1 { text-indent: -999999px; background: url({{ theme_index_logo }}) no-repeat center center; height: {{ theme_index_logo_height }}; } {% endif %} div.body h2 { font-size: 180%; } div.body h3 { font-size: 150%; } div.body h4 { font-size: 130%; } div.body h5 { font-size: 100%; } div.body h6 { font-size: 100%; } a.headerlink { color: white; padding: 0 4px; text-decoration: none; } a.headerlink:hover { color: #444; background: #eaeaea; } div.body p, div.body dd, div.body li { line-height: 1.4em; } div.admonition { background: #fafafa; margin: 20px -30px; padding: 10px 30px; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; } div.admonition p.admonition-title { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; font-size: 24px; margin: 0 0 10px 0; padding: 0; line-height: 1; } div.admonition p.last { margin-bottom: 0; } div.highlight{ background-color: white; } dt:target, .highlight { background: #FAF3E8; } div.note { background-color: #eee; border: 1px solid #ccc; } div.seealso { background-color: #ffc; border: 1px solid #ff6; } div.topic { background-color: #eee; } div.warning { background-color: #ffe4e4; border: 1px solid #f66; } p.admonition-title { display: inline; } p.admonition-title:after { content: ":"; } pre, tt { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.85em; } img.screenshot { } tt.descname, tt.descclassname { font-size: 0.95em; } tt.descname { padding-right: 0.08em; } img.screenshot { -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils { border: 1px solid #888; -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils td, table.docutils th { border: 1px solid #888; padding: 0.25em 0.7em; } table.field-list, table.footnote { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } table.footnote { margin: 15px 0; width: 100%; border: 1px solid #eee; } table.field-list th { padding: 0 0.8em 0 0; } table.field-list td { padding: 0; } table.footnote td { padding: 0.5em; } dl { margin: 0; padding: 0; } dl dd { margin-left: 30px; } pre { padding: 0; margin: 15px -30px; padding: 8px; line-height: 1.3em; padding: 7px 30px; background: #eee; border-radius: 2px; -moz-border-radius: 2px; -webkit-border-radius: 2px; } dl pre { margin-left: -60px; padding-left: 60px; } tt { background-color: #ecf0f3; color: #222; /* padding: 1px 2px; */ } tt.xref, a tt { background-color: #FBFBFB; } a:hover tt { background: #EEE; } behave-1.2.6/docs/_themes/kr_small/theme.conf0000644000076600000240000000027013244555737021204 0ustar jensstaff00000000000000[theme] inherit = basic stylesheet = flasky.css nosidebar = true pygments_style = flask_theme_support.FlaskyStyle [options] index_logo = '' index_logo_height = 120px github_fork = '' behave-1.2.6/docs/_themes/LICENSE0000644000076600000240000000350713244555737016442 0ustar jensstaff00000000000000Modifications: Copyright (c) 2011 Kenneth Reitz. Original Project: Copyright (c) 2010 by Armin Ronacher. Some rights reserved. Redistribution and use in source and binary forms of the theme, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. We kindly ask you to only use these themes in an unmodified manner just for Flask and Flask-related products, not for unrelated projects. If you like the visual style and want to use it for your own projects, please consider making some larger changes to the themes (such as changing font faces, sizes, colors or margins). THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. behave-1.2.6/docs/api.rst0000644000076600000240000003102613244555737015311 0ustar jensstaff00000000000000.. _api: ==================== Behave API Reference ==================== This reference is meant for people actually writing step implementations for feature tests. It contains way more information than a typical step implementation will need: most implementations will only need to look at the basic implementation of `step functions`_ and *maybe* `environment file functions`_. The model stuff is for people getting really *serious* about their step implementations. .. note:: Anywhere this document says "string" it means "unicode string" in Python 2.x *behave* works exclusively with unicode strings internally. Step Functions ============== Step functions are implemented in the Python modules present in your "steps" directory. All Python files (files ending in ".py") in that directory will be imported to find step implementations. They are all loaded before *behave* starts executing your feature tests. Step functions are identified using step decorators. All step implementations **should normally** start with the import line: .. code-block:: python from behave import * This line imports several decorators defined by *behave* to allow you to identify your step functions. These are available in both PEP-8 (all lowercase) and traditional (title case) versions: "given", "when", "then" and the generic "step". See the `full list of variables imported`_ in the above statement. .. _`full list of variables imported`: #from-behave-import-* The decorators all take a single string argument: the string to match against the feature file step text *exactly*. So the following step implementation code: .. code-block:: python @given('some known state') def step_impl(context): set_up(some, state) will match the "Given" step from the following feature: .. code-block:: gherkin Scenario: test something Given some known state then some observed outcome. *You don't need to import the decorators*: they're automatically available to your step implementation modules as `global variables`_. .. _`global variables`: #step-global-variables Steps beginning with "and" or "but" in the feature file are renamed to take the name of their preceding keyword, so given the following feature file: .. code-block:: gherkin Given some known state and some other known state when some action is taken then some outcome is observed but some other outcome is not observed. the first "and" step will be renamed internally to "given" and *behave* will look for a step implementation decorated with either "given" or "step": .. code-block:: python @given('some other known state') def step_impl(context): set_up(some, other, state) and similarly the "but" would be renamed internally to "then". Multiple "and" or "but" steps in a row would inherit the non-"and" or "but" keyword. The function decorated by the step decorator will be passed at least one argument. The first argument is always the :class:`~behave.runner.Context` variable. Additional arguments come from `step parameters`_, if any. Step Parameters --------------- You may additionally use `parameters`_ in your step names. These will be handled by either the default simple parser (:pypi:`parse`), its extension "cfparse" or by regular expressions if you invoke :func:`~behave.use_step_matcher`. .. _`parameters`: tutorial.html#step-parameters .. autofunction:: behave.use_step_matcher You may add new types to the default parser by invoking :func:`~behave.register_type`. .. autofunction:: behave.register_type .. hidden: # -- SUPERCEEDED BY: behave.register_type documentation An example of this in action could be, in steps.py: .. code-block:: python from behave import register_type register_type(custom=lambda s: s.upper()) @given('a string {param:custom} a custom type') def step_impl(context, param): assert param.isupper() You may define a new parameter matcher by subclassing :class:`behave.matchers.Matcher` and registering it with :attr:`behave.matchers.matcher_mapping` which is a dictionary of "matcher name" to :class:`~behave.matchers.Matcher` class. .. autoclass:: behave.matchers.Matcher :members: .. autoclass:: behave.model_core.Argument .. autoclass:: behave.matchers.Match Calling Steps From Other Steps ------------------------------ If you find you'd like your step implementation to invoke another step you may do so with the :class:`~behave.runner.Context` method :func:`~behave.runner.Context.execute_steps`. This function allows you to, for example: .. code-block:: python @when('I do the same thing as before') def step_impl(context): context.execute_steps(u''' when I press the big red button and I duck ''') This will cause the "when I do the same thing as before" step to execute the other two steps as though they had also appeared in the scenario file. from behave import * -------------------- The import statement: .. code-block:: python from behave import * is written to introduce a restricted set of variables into your code: =========================== =========== =========================================== Name Kind Description =========================== =========== =========================================== given, when, then, step Decorator Decorators for step implementations. use_step_matcher(name) Function Selects current step matcher (parser). register_type(Type=func) Function Registers a type converter. =========================== =========== =========================================== See also the description in `step parameters`_. Environment File Functions ========================== The environment.py module may define code to run before and after certain events during your testing: **before_step(context, step), after_step(context, step)** These run before and after every step. The step passed in is an instance of :class:`~behave.model.Step`. **before_scenario(context, scenario), after_scenario(context, scenario)** These run before and after each scenario is run. The scenario passed in is an instance of :class:`~behave.model.Scenario`. **before_feature(context, feature), after_feature(context, feature)** These run before and after each feature file is exercised. The feature passed in is an instance of :class:`~behave.model.Feature`. **before_tag(context, tag), after_tag(context, tag)** These run before and after a section tagged with the given name. They are invoked for each tag encountered in the order they're found in the feature file. See :ref:`controlling things with tags`. The tag passed in is an instance of :class:`~behave.model.Tag` and because it's a subclass of string you can do simple tests like: .. code-block:: python # -- ASSUMING: tags @browser.chrome or @browser.any are used. if tag.startswith("browser."): browser_type = tag.replace("browser.", "", 1) if browser_type == "chrome": context.browser = webdriver.Chrome() else: context.browser = webdriver.PlainVanilla() **before_all(context), after_all(context)** These run before and after the whole shooting match. Some Useful Environment Ideas ----------------------------- Here's some ideas for things you could use the environment for. Logging Setup ~~~~~~~~~~~~~~ The following recipe works in all cases (log-capture on or off). If you want to use/configure logging, you should use the following snippet: .. code-block:: python # -- FILE:features/environment.py def before_all(context): # -- SET LOG LEVEL: behave --logging-level=ERROR ... # on behave command-line or in "behave.ini". context.config.setup_logging() # -- ALTERNATIVE: Setup logging with a configuration file. # context.config.setup_logging(configfile="behave_logging.ini") Capture Logging in Hooks ~~~~~~~~~~~~~~~~~~~~~~~~ If you wish to capture any logging generated during an environment hook function's invocation, you may use the :func:`~behave.log_capture.capture` decorator, like: .. code-block:: python # -- FILE:features/environment.py from behave.log_capture import capture @capture def after_scenario(context): ... This will capture any logging done during the call to *after_scenario* and print it out. Detecting that user code overwrites behave Context attributes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The *context* variable in all cases is an instance of :class:`behave.runner.Context`. .. autoclass:: behave.runner.Context :members: .. autoclass:: behave.runner.ContextMaskWarning Fixtures ================ .. excluded: .. automodule:: behave.fixture Provide a Fixture ------------------ .. autofunction:: behave.fixture.fixture Use Fixtures ------------------ .. autofunction:: behave.fixture.use_fixture .. autofunction:: behave.fixture.use_fixture_by_tag .. autofunction:: behave.fixture.use_composite_fixture_with Runner Operation ================ Given all the code that could be run by *behave*, this is the order in which that code is invoked (if they exist.) .. parsed-literal:: before_all for feature in all_features: before_feature for scenario in feature.scenarios: before_scenario for step in scenario.steps: before_step step.run() after_step after_scenario after_feature after_all If the feature contains scenario outlines then there is an additional loop over all the scenarios in the outline making the running look like this: .. parsed-literal:: before_all for feature in all_features: before_feature for outline in feature.scenarios: for scenario in outline.scenarios: before_scenario for step in scenario.steps: before_step step.run() after_step after_scenario after_feature after_all Model Objects ============= The feature, scenario and step objects represent the information parsed from the feature file. They have a number of common attributes: **keyword** "Feature", "Scenario", "Given", etc. **name** The name of the step (the text after the keyword.) **filename** and **line** The file name (or "") and line number of the statement. The structure of model objects parsed from a *feature file* will typically be: .. parsed-literal:: :class:`~behave.model.Tag` (as :py:attr:`Feature.tags`) :class:`~behave.model.Feature` : TaggableModelElement Description (as :py:attr:`Feature.description`) :class:`~behave.model.Background` :class:`~behave.model.Step` :class:`~behave.model.Table` (as :py:attr:`Step.table`) MultiLineText (as :py:attr:`Step.text`) :class:`~behave.model.Tag` (as :py:attr:`Scenario.tags`) :class:`~behave.model.Scenario` : TaggableModelElement Description (as :py:attr:`Scenario.description`) :class:`~behave.model.Step` :class:`~behave.model.Table` (as :py:attr:`Step.table`) MultiLineText (as :py:attr:`Step.text`) :class:`~behave.model.Tag` (as :py:attr:`ScenarioOutline.tags`) :class:`~behave.model.ScenarioOutline` : TaggableModelElement Description (as :py:attr:`ScenarioOutline.description`) :class:`~behave.model.Step` :class:`~behave.model.Table` (as :py:attr:`Step.table`) MultiLineText (as :py:attr:`Step.text`) :class:`~behave.model.Examples` :class:`~behave.model.Table` .. autoclass:: behave.model.Feature .. autoclass:: behave.model.Background .. autoclass:: behave.model.Scenario .. autoclass:: behave.model.ScenarioOutline .. autoclass:: behave.model.Examples .. autoclass:: behave.model.Tag .. autoclass:: behave.model.Step Tables may be associated with either Examples or Steps: .. autoclass:: behave.model.Table .. autoclass:: behave.model.Row And Text may be associated with Steps: .. autoclass:: behave.model.Text Logging Capture =============== The logging capture *behave* uses by default is implemented by the class :class:`~behave.log_capture.LoggingCapture`. It has methods .. autoclass:: behave.log_capture.LoggingCapture :members: The *log_capture* module also defines a handy logging capture decorator that's intended to be used on your `environment file functions`_. .. autofunction:: behave.log_capture.capture .. include:: _common_extlinks.rst behave-1.2.6/docs/appendix.rst0000644000076600000240000000056113244555737016350 0ustar jensstaff00000000000000.. _id.appendix: ============================================================================== Appendix ============================================================================== **Contents:** .. toctree:: :maxdepth: 1 formatters context_attributes parse_builtin_types regular_expressions test_domains behave_ecosystem related behave-1.2.6/docs/behave.rst0000644000076600000240000004061713244556226015772 0ustar jensstaff00000000000000============== Using *behave* ============== The command-line tool *behave* has a bunch of `command-line arguments`_ and is also configurable using `configuration files`_. Values defined in the configuration files are used as defaults which the command-line arguments may override. Command-Line Arguments ====================== You may see the same information presented below at any time using ``behave -h``. .. option:: -c, --no-color Disable the use of ANSI color escapes. .. option:: --color Use ANSI color escapes. This is the default behaviour. This switch is used to override a configuration file setting. .. option:: -d, --dry-run Invokes formatters without executing the steps. .. option:: -D, --define Define user-specific data for the config.userdata dictionary. Example: -D foo=bar to store it in config.userdata["foo"]. .. option:: -e, --exclude Don't run feature files matching regular expression PATTERN. .. option:: -i, --include Only run feature files matching regular expression PATTERN. .. option:: --no-junit Don't output JUnit-compatible reports. .. option:: --junit Output JUnit-compatible reports. When junit is enabled, all stdout and stderr will be redirected and dumped to the junit report, regardless of the "--capture" and "--no-capture" options. .. option:: --junit-directory Directory in which to store JUnit reports. .. option:: -f, --format Specify a formatter. If none is specified the default formatter is used. Pass "--format help" to get a list of available formatters. .. option:: --steps-catalog Show a catalog of all available step definitions. SAME AS: --format=steps.catalog --dry-run --no-summary -q .. option:: -k, --no-skipped Don't print skipped steps (due to tags). .. option:: --show-skipped Print skipped steps. This is the default behaviour. This switch is used to override a configuration file setting. .. option:: --no-snippets Don't print snippets for unimplemented steps. .. option:: --snippets Print snippets for unimplemented steps. This is the default behaviour. This switch is used to override a configuration file setting. .. option:: -m, --no-multiline Don't print multiline strings and tables under steps. .. option:: --multiline Print multiline strings and tables under steps. This is the default behaviour. This switch is used to override a configuration file setting. .. option:: -n, --name Only execute the feature elements which match part of the given name. If this option is given more than once, it will match against all the given names. .. option:: --no-capture Don't capture stdout (any stdout output will be printed immediately.) .. option:: --capture Capture stdout (any stdout output will be printed if there is a failure.) This is the default behaviour. This switch is used to override a configuration file setting. .. option:: --no-capture-stderr Don't capture stderr (any stderr output will be printed immediately.) .. option:: --capture-stderr Capture stderr (any stderr output will be printed if there is a failure.) This is the default behaviour. This switch is used to override a configuration file setting. .. option:: --no-logcapture Don't capture logging. Logging configuration will be left intact. .. option:: --logcapture Capture logging. All logging during a step will be captured and displayed in the event of a failure. This is the default behaviour. This switch is used to override a configuration file setting. .. option:: --logging-level Specify a level to capture logging at. The default is INFO - capturing everything. .. option:: --logging-format Specify custom format to print statements. Uses the same format as used by standard logging handlers. The default is "%(levelname)s:%(name)s:%(message)s". .. option:: --logging-datefmt Specify custom date/time format to print statements. Uses the same format as used by standard logging handlers. .. option:: --logging-filter Specify which statements to filter in/out. By default, everything is captured. If the output is too verbose, use this option to filter out needless output. Example: --logging-filter=foo will capture statements issued ONLY to foo or foo.what.ever.sub but not foobar or other logger. Specify multiple loggers with comma: filter=foo,bar,baz. If any logger name is prefixed with a minus, eg filter=-foo, it will be excluded rather than included. .. option:: --logging-clear-handlers Clear all other logging handlers. .. option:: --no-summary Don't display the summary at the end of the run. .. option:: --summary Display the summary at the end of the run. .. option:: -o, --outfile Write to specified file instead of stdout. .. option:: -q, --quiet Alias for --no-snippets --no-source. .. option:: -s, --no-source Don't print the file and line of the step definition with the steps. .. option:: --show-source Print the file and line of the step definition with the steps. This is the default behaviour. This switch is used to override a configuration file setting. .. option:: --stage Defines the current test stage. The test stage name is used as name prefix for the environment file and the steps directory (instead of default path names). .. option:: --stop Stop running tests at the first failure. .. option:: -t, --tags Only execute features or scenarios with tags matching TAG_EXPRESSION. Pass "--tags-help" for more information. .. option:: -T, --no-timings Don't print the time taken for each step. .. option:: --show-timings Print the time taken, in seconds, of each step after the step has completed. This is the default behaviour. This switch is used to override a configuration file setting. .. option:: -v, --verbose Show the files and features loaded. .. option:: -w, --wip Only run scenarios tagged with "wip". Additionally: use the "plain" formatter, do not capture stdout or logging output and stop at the first failure. .. option:: -x, --expand Expand scenario outline tables in output. .. option:: --lang Use keywords for a language other than English. .. option:: --lang-list List the languages available for --lang. .. option:: --lang-help List the translations accepted for one language. .. option:: --tags-help Show help for tag expressions. .. option:: --version Show version. Tag Expression -------------- Scenarios inherit tags declared on the Feature level. The simplest TAG_EXPRESSION is simply a tag:: --tags @dev You may even leave off the "@" - behave doesn't mind. When a tag in a tag expression starts with a ~, this represents boolean NOT:: --tags ~@dev A tag expression can have several tags separated by a comma, which represents logical OR:: --tags @dev,@wip The --tags option can be specified several times, and this represents logical AND, for instance this represents the boolean expression "(@foo or not @bar) and @zap":: --tags @foo,~@bar --tags @zap. Beware that if you want to use several negative tags to exclude several tags you have to use logical AND:: --tags ~@fixme --tags ~@buggy. Configuration Files =================== Configuration files for *behave* are called either ".behaverc", "behave.ini", "setup.cfg" or "tox.ini" (your preference) and are located in one of three places: 1. the current working directory (good for per-project settings), 2. your home directory ($HOME), or 3. on Windows, in the %APPDATA% directory. If you are wondering where *behave* is getting its configuration defaults from you can use the "-v" command-line argument and it'll tell you. Configuration files **must** start with the label "[behave]" and are formatted in the Windows INI style, for example: .. code-block:: ini [behave] format=plain logging_clear_handlers=yes logging_filter=-suds Configuration Parameter Types ----------------------------- The following types are supported (and used): **text** This just assigns whatever text you supply to the configuration setting. **bool** This assigns a boolean value to the configuration setting. The text describes the functionality when the value is true. True values are "1", "yes", "true", and "on". False values are "0", "no", "false", and "off". **sequence** These fields accept one or more values on new lines, for example a tag expression might look like: .. code-block:: ini tags=@foo,~@bar @zap which is the equivalent of the command-line usage:: --tags @foo,~@bar --tags @zap Configuration Parameters ----------------------------- .. index:: single: configuration param; color .. describe:: color : bool Use ANSI color escapes. This is the default behaviour. This switch is used to override a configuration file setting. .. index:: single: configuration param; dry_run .. describe:: dry_run : bool Invokes formatters without executing the steps. .. index:: single: configuration param; userdata_defines .. describe:: userdata_defines : sequence Define user-specific data for the config.userdata dictionary. Example: -D foo=bar to store it in config.userdata["foo"]. .. index:: single: configuration param; exclude_re .. describe:: exclude_re : text Don't run feature files matching regular expression PATTERN. .. index:: single: configuration param; include_re .. describe:: include_re : text Only run feature files matching regular expression PATTERN. .. index:: single: configuration param; junit .. describe:: junit : bool Output JUnit-compatible reports. When junit is enabled, all stdout and stderr will be redirected and dumped to the junit report, regardless of the "--capture" and "--no-capture" options. .. index:: single: configuration param; junit_directory .. describe:: junit_directory : text Directory in which to store JUnit reports. .. index:: single: configuration param; default_format .. describe:: default_format : text Specify default formatter (default: pretty). .. index:: single: configuration param; format .. describe:: format : sequence Specify a formatter. If none is specified the default formatter is used. Pass "--format help" to get a list of available formatters. .. index:: single: configuration param; steps_catalog .. describe:: steps_catalog : bool Show a catalog of all available step definitions. SAME AS: --format=steps.catalog --dry-run --no-summary -q .. index:: single: configuration param; scenario_outline_annotation_schema .. describe:: scenario_outline_annotation_schema : text Specify name annotation schema for scenario outline (default="{name} -- @{row.id} {examples.name}"). .. index:: single: configuration param; show_skipped .. describe:: show_skipped : bool Print skipped steps. This is the default behaviour. This switch is used to override a configuration file setting. .. index:: single: configuration param; show_snippets .. describe:: show_snippets : bool Print snippets for unimplemented steps. This is the default behaviour. This switch is used to override a configuration file setting. .. index:: single: configuration param; show_multiline .. describe:: show_multiline : bool Print multiline strings and tables under steps. This is the default behaviour. This switch is used to override a configuration file setting. .. index:: single: configuration param; name .. describe:: name : sequence Only execute the feature elements which match part of the given name. If this option is given more than once, it will match against all the given names. .. index:: single: configuration param; stdout_capture .. describe:: stdout_capture : bool Capture stdout (any stdout output will be printed if there is a failure.) This is the default behaviour. This switch is used to override a configuration file setting. .. index:: single: configuration param; stderr_capture .. describe:: stderr_capture : bool Capture stderr (any stderr output will be printed if there is a failure.) This is the default behaviour. This switch is used to override a configuration file setting. .. index:: single: configuration param; log_capture .. describe:: log_capture : bool Capture logging. All logging during a step will be captured and displayed in the event of a failure. This is the default behaviour. This switch is used to override a configuration file setting. .. index:: single: configuration param; logging_level .. describe:: logging_level : text Specify a level to capture logging at. The default is INFO - capturing everything. .. index:: single: configuration param; logging_format .. describe:: logging_format : text Specify custom format to print statements. Uses the same format as used by standard logging handlers. The default is "%(levelname)s:%(name)s:%(message)s". .. index:: single: configuration param; logging_datefmt .. describe:: logging_datefmt : text Specify custom date/time format to print statements. Uses the same format as used by standard logging handlers. .. index:: single: configuration param; logging_filter .. describe:: logging_filter : text Specify which statements to filter in/out. By default, everything is captured. If the output is too verbose, use this option to filter out needless output. Example: ``logging_filter = foo`` will capture statements issued ONLY to "foo" or "foo.what.ever.sub" but not "foobar" or other logger. Specify multiple loggers with comma: ``logging_filter = foo,bar,baz``. If any logger name is prefixed with a minus, eg ``logging_filter = -foo``, it will be excluded rather than included. .. index:: single: configuration param; logging_clear_handlers .. describe:: logging_clear_handlers : bool Clear all other logging handlers. .. index:: single: configuration param; summary .. describe:: summary : bool Display the summary at the end of the run. .. index:: single: configuration param; outfiles .. describe:: outfiles : sequence Write to specified file instead of stdout. .. index:: single: configuration param; paths .. describe:: paths : sequence Specify default feature paths, used when none are provided. .. index:: single: configuration param; quiet .. describe:: quiet : bool Alias for --no-snippets --no-source. .. index:: single: configuration param; show_source .. describe:: show_source : bool Print the file and line of the step definition with the steps. This is the default behaviour. This switch is used to override a configuration file setting. .. index:: single: configuration param; stage .. describe:: stage : text Defines the current test stage. The test stage name is used as name prefix for the environment file and the steps directory (instead of default path names). .. index:: single: configuration param; stop .. describe:: stop : bool Stop running tests at the first failure. .. index:: single: configuration param; default_tags .. describe:: default_tags : text Define default tags when non are provided. See --tags for more information. .. index:: single: configuration param; tags .. describe:: tags : sequence Only execute certain features or scenarios based on the tag expression given. See below for how to code tag expressions in configuration files. .. index:: single: configuration param; show_timings .. describe:: show_timings : bool Print the time taken, in seconds, of each step after the step has completed. This is the default behaviour. This switch is used to override a configuration file setting. .. index:: single: configuration param; verbose .. describe:: verbose : bool Show the files and features loaded. .. index:: single: configuration param; wip .. describe:: wip : bool Only run scenarios tagged with "wip". Additionally: use the "plain" formatter, do not capture stdout or logging output and stop at the first failure. .. index:: single: configuration param; expand .. describe:: expand : bool Expand scenario outline tables in output. .. index:: single: configuration param; lang .. describe:: lang : text Use keywords for a language other than English. behave-1.2.6/docs/behave.rst-template0000644000076600000240000000370713244555737017610 0ustar jensstaff00000000000000============== Using *behave* ============== The command-line tool *behave* has a bunch of `command-line arguments`_ and is also configurable using `configuration files`_. Values defined in the configuration files are used as defaults which the command-line arguments may override. Command-Line Arguments ====================== You may see the same information presented below at any time using ``behave -h``. {cmdline} Tag Expression -------------- {tag_expression} Configuration Files =================== Configuration files for *behave* are called either ".behaverc", "behave.ini", "setup.cfg" or "tox.ini" (your preference) and are located in one of three places: 1. the current working directory (good for per-project settings), 2. your home directory ($HOME), or 3. on Windows, in the %APPDATA% directory. If you are wondering where *behave* is getting its configuration defaults from you can use the "-v" command-line argument and it'll tell you. Configuration files **must** start with the label "[behave]" and are formatted in the Windows INI style, for example: .. code-block:: ini [behave] format=plain logging_clear_handlers=yes logging_filter=-suds Configuration Parameter Types ----------------------------- The following types are supported (and used): **text** This just assigns whatever text you supply to the configuration setting. **bool** This assigns a boolean value to the configuration setting. The text describes the functionality when the value is true. True values are "1", "yes", "true", and "on". False values are "0", "no", "false", and "off". **sequence** These fields accept one or more values on new lines, for example a tag expression might look like: .. code-block:: ini tags=@foo,~@bar @zap which is the equivalent of the command-line usage:: --tags @foo,~@bar --tags @zap Configuration Parameters ----------------------------- {config} behave-1.2.6/docs/behave_ecosystem.rst0000644000076600000240000001125113244555737020063 0ustar jensstaff00000000000000.. _id.appendix.behave_ecosystem: Behave Ecosystem ============================================================================== The following tools and extensions try to simplify the work with `behave`_. .. _behave: https://github.com/behave/behave .. seealso:: * `Are there any non-developer tools for writing Gherkin files ? `_ (``*.feature`` files) Behave related Projects to Github ------------------------------------------------------------------------------ Use the following URL to find `behave`_ related projects on Github: * https://github.com/topics/behave?l=python IDE Plugins ------------------------------------------------------------------------------ =============== =================== ====================================================================================== IDE Plugin Description =============== =================== ====================================================================================== `PyCharm`_ `PyCharm BDD`_ PyCharm 4 (Professional edition) has **built-in support** for `behave`_. `PyCharm`_ Gherkin PyCharm/IDEA editor support for Gherkin. `Eclipse`_ `Cucumber-Eclipse`_ Plugin contains editor support for Gherkin. `VisualStudio`_ `cuke4vs`_ VisualStudio plugin with editor support for Gherkin. =============== =================== ====================================================================================== .. _PyCharm: https://www.jetbrains.com/pycharm/ .. _Eclipse: http://www.eclipse.org/ .. _VisualStudio: https://www.visualstudio.com/ .. _`PyCharm BDD`: https://blog.jetbrains.com/pycharm/2014/09/feature-spotlight-behavior-driven-development-in-pycharm/ .. _`Cucumber-Eclipse`: http://cucumber.github.io/cucumber-eclipse/ .. _cuke4vs: https://github.com/henritersteeg/cuke4vs .. hidden_BROKEN: https://www.jetbrains.com/pycharm/whatsnew/#BDD .. hidden_NEW: https://blog.jetbrains.com/pycharm/2017/06/upgrade-your-testing-with-behavior-driven-development/ https://anvileight.com/blog/2016/04/12/behavior-driven-development-pycharm-python-django/ https://www.udemy.com/bdd-testing-with-python/ Editors and Editor Plugins ------------------------------------------------------------------------------ =================== ======================= ============================================================================= Editor Plugin Description =================== ======================= ============================================================================= `gedit`_ `gedit_behave`_ `gedit`_ plugin for jumping between feature and step files. `Gherkin editor`_ --- An editor for writing ``*.feature`` files. `Notepad++`_ `NP++ gherkin`_ Notepad++ editor syntax highlighting for Gherkin. `Sublime Text`_ `Cucumber (ST Bundle)`_ Gherkin editor support, table formatting. `Sublime Text`_ `Behave Step Finder`_ Helps to navigate to steps in behave. `vim`_ `vim-behave`_ `vim`_ plugin: Port of `vim-cucumber`_ to Python `behave`_. =================== ======================= ============================================================================= .. _`Notepad++`: https://notepad-plus-plus.org/ .. _gedit: https://wiki.gnome.org/Apps/Gedit .. _vim: http://www.vim.org/ .. _`Sublime Text`: http://www.sublimetext.com .. _`Gherkin editor`: http://gherkineditor.codeplex.com .. _gedit_behave: https://gitlab.com/mcepl/gedit_behave .. _`NP++ gherkin`: http://productive.me/develop/cucumbergherkin-syntax-highlighting-for-notepad .. _vim-behave: https://github.com/rooprob/vim-behave .. _vim-cucumber: https://github.com/tpope/vim-cucumber .. _`Cucumber (ST Bundle)`: https://packagecontrol.io/packages/Cucumber .. _Behave Step Finder: https://packagecontrol.io/packages/Behave%20Step%20Finder Tools ------------------------------------------------------------------------------ =========================== =========================================================================== Tool Description =========================== =========================================================================== :pypi:`cucutags` Generate `ctags`_-like information (cross-reference index) for Gherkin feature files and behave step definitions. =========================== =========================================================================== .. _gitlab_cucutags: https://gitlab.com/mcepl/cucutags .. _ctags: http://ctags.sourceforge.net/ behave-1.2.6/docs/comparison.rst0000644000076600000240000000723113244555737016713 0ustar jensstaff00000000000000=========================== Comparison With Other Tools =========================== There are other options for doing Gherkin-based BDD in Python. We've listed the main ones below and why we feel you're better off using behave. Obviously this comes from our point of view and you may disagree. That's cool. We're not worried whichever way you go. This page may be out of date as the projects mentioned will almost certainly change over time. If anything on this page is out of date, please contact us. Cucumber_ ========= You can actually use Cucumber to run test code written in Python. It uses "rubypython" (dead) to fire up a Python interpreter inside the Ruby process though and this can be somewhat brittle. Obviously we prefer to use something written in Python but if you've got an existing workflow based around Cucumber and you have code in multiple languages, Cucumber may be the one for you. .. _Cucumber: https://cucumber.io/ Lettuce_ ======== :pypi:`lettuce` is similar to behave in that it's a fairly straight port of the basic functionality of `Cucumber`_. The main differences with behave are: * Single decorator for step definitions, ``@step``. * The context variable, ``world``, is simply a shared holder of attributes. It never gets cleaned up during the run. * Hooks are declared using decorators rather than as simple functions. * No support for tags. * Step definition code files can be anywhere in the feature directory hierarchy. The issues we had with Lettuce that stopped us using it were: * Lack of tags (which are supported by now, at least since v0.2.20). * The hooks functionality was patchy. For instance it was very hard to clean up the ``world`` variable between scenario outlines. Behave clears the scenario-level context between outlines automatically. * Lettuce's handling of stdout would occasionally cause it to crash mid-run in such a way that cleanup hooks were never run. * Lettuce uses import hackery so .pyc files are left around and the module namespace is polluted. .. _Lettuce: http://lettuce.it/ Freshen_ ======== :pypi:`freshen` is a plugin for :pypi:`nose` that implements a Gherkin-style language with Python step definitions. The main differences with behave are: * Operates as a plugin for nose, and is thus tied to the nose runner and its output model. * Has some additions to its Gherkin syntax allowing it to specify specific step definition modules for each feature. * Has separate context objects for various levels: ``glc``, ``ftc`` and ``scc``. These relate to global, feature and scenario levels respectively. The issues we had with Freshen that stopped us using it were: * The integration with the nose runner made it quite hard to properly debug how and why tests were failing. Quite often you'd get a rather cryptic message with the actual exception having been swallowed. * The feature-specific step includes could lead to specific sets of step definitions for each feature despite them warning against doing that. * The output being handled by nose meant that you couldn't do cucumber-style output without the addition of more plugins. * The context variable names are cryptic and moving context data from one level to another takes a certain amount of work finding and renaming. The behave `context` variable is much more flexible. * Step functions must have unique names, even though they're decorated to match different strings. * As with Lettuce, Freshen uses import hackery so .pyc files are left around and the module namespace is polluted. * Only Before and no contextual before/after control, thus requiring use of atexit for teardown operations and no fine-grained control. .. include:: _common_extlinks.rst behave-1.2.6/docs/conf.py0000644000076600000240000003065113244555737015310 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- # ============================================================================= # SPHINX CONFIGURATION: behave documentation build configuration file # ============================================================================= import os.path import sys import importlib # -- ENSURE: Local workspace is used (for sphinx apidocs). # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath("..")) # ------------------------------------------------------------------------------ # EXTENSIONS CONFIGURATION # ------------------------------------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. needs_sphinx = "1.3" # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named "sphinx.ext.*") or your custom ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.ifconfig", "sphinx.ext.extlinks", "sphinx.ext.todo", "sphinx.ext.intersphinx", ] optional_extensions = [ # -- DISABLED: "sphinxcontrib.youtube", # http://www.sphinx-doc.org/en/stable/faq.html "rinoh.frontend.sphinx", # ALTERNATIVE FOR: LATEX-PDF "rst2pdf.pdfbuilder", # PDF ] for optional_module_name in optional_extensions: try: importlib.import_module(optional_module_name) extensions.append(optional_module_name) except ImportError: pass extlinks = { "pypi": ("https://pypi.python.org/pypi/%s", ""), "github": ("https://github.com/%s", "github:/"), "issue": ("https://github.com/behave/behave/issue/%s", "issue #"), "youtube": ("https://www.youtube.com/watch?v=%s", "youtube:video="), "behave": ("https://github.com/behave/behave", None), } intersphinx_mapping = { "python": ('https://docs.python.org/3', None) } # -- SUPPORT: Documentation variation-points with sphinx.ext.ifconfig def setup(app): # -- VARIATION-POINT: supports_video # BASED-ON: installed("sphinxcontrib-youtube") and output-mode # TODO: Check for output-mode, too (supported on: HTML, ...) supports_video = "sphinxcontrib.youtube" in extensions app.add_config_value("supports_video", supports_video, "env") # ----------------------------------------------------------------------------- # BASIC CONFIGURATION # ----------------------------------------------------------------------------- # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix of source filenames. source_suffix = ".rst" # The encoding of source files. source_encoding = "utf-8" # The master toctree document. master_doc = "index" # ----------------------------------------------------------------------------- # GENERAL CONFIGURATION # ----------------------------------------------------------------------------- project = u"behave" authors = u"Benno Rice, Richard Jones and Jens Engel" copyright = u"2012-2017, %s" % authors # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. from behave import __version__ version = __version__ # The full version, including alpha/beta/rc tags. release = __version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = "" # Else, today_fmt is used as the format for a strftime call. today_fmt = "%Y-%m-%d" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, "()" will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" # MAYBE STYLES: friendly, vs, xcode, vs, tango # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # ------------------------------------------------------------------------------ # OPTIONS FOR: HTML OUTPUT # ------------------------------------------------------------------------------ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = "kr" html_theme = "bootstrap" on_rtd = os.environ.get("READTHEDOCS", None) == "True" if on_rtd: html_theme = "default" if html_theme == "bootstrap": # See sphinx-bootstrap-theme for documentation of these options # https://github.com/ryan-roemer/sphinx-bootstrap-theme import sphinx_bootstrap_theme html_theme_options = { 'navbar_site_name': 'Document', 'navbar_pagenav': True } # Add any paths that contain custom themes here, relative to this directory. html_theme_path = sphinx_bootstrap_theme.get_html_theme_path() elif html_theme in ("default", "kr"): html_theme_path = ["_themes"] html_logo = "_static/behave_logo1.png" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = "_static/behave_logo1.png" # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] # If not "", a "Last updated on:" timestamp is inserted at every page bottom, # using the given strftime format. html_last_updated_fmt = "%Y-%m-%d" # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = "" # This is the file name suffix for HTML files (e.g. ".xhtml"). html_file_suffix = ".html" # ------------------------------------------------------------------------------ # OPTIONS FOR: HTML HELP # ------------------------------------------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = "behavedoc" # ------------------------------------------------------------------------------ # OPTIONS FOR: LATEX OUTPUT # ------------------------------------------------------------------------------ latex_elements = { # The paper size ("letterpaper" or "a4paper"). "papersize": "a4paper", # The font size ("10pt", "11pt" or "12pt"). # "pointsize": "10pt", # Additional stuff for the LaTeX preamble. # "preamble": "", } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ("index", "behave.tex", u"behave Documentation", authors, "manual"), ] # latex_logo = None # ------------------------------------------------------------------------------ # OPTIONS FOR: MANUAL PAGE (man page) OUTPUT # ------------------------------------------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ("index", "behave", u"behave Documentation", [authors], 1) ] # ------------------------------------------------------------------------------ # OPTIONS FOR: Texinfo output # ------------------------------------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ("index", "behave", u"behave Documentation", authors, "behave", "A test runner for behave (feature tests).", "Miscellaneous"), ] # ----------------------------------------------------------------------------- # RST2PDF OUTPUT CONFIGURATION: builder=pdf (prepared) # ----------------------------------------------------------------------------- # Grouping the document tree into PDF files. List of tuples # (source start file, target name, title, author, options). # # If there is more than one author, separate them with \\. # For example: r'Guido van Rossum\\Fred L. Drake, Jr., editor' # # The options element is a dictionary that lets you override # this config per-document. # For example, # ('index', u'MyProject', u'My Project', u'Author Name', # dict(pdf_compressed = True)) # would mean that specific document would be compressed # regardless of the global pdf_compressed setting. pdf_documents = [ ('index', project, project, authors), ] # A comma-separated list of custom stylesheets. Example: pdf_stylesheets = ['sphinx','kerning','a4'] # Create a compressed PDF # Use True/False or 1/0 # Example: compressed=True pdf_compressed = True # A colon-separated list of folders to search for fonts. Example: # pdf_font_path = ['/usr/share/fonts', '/usr/share/texmf-dist/fonts/'] # Language to be used for hyphenation support pdf_language = "en_US" # Mode for literal blocks wider than the frame. Can be overflow, shrink or truncate pdf_fit_mode = "shrink" # Section level that forces a break page. # For example: 1 means top-level sections start in a new page # 0 means disabled # pdf_break_level = 0 XXX pdf_break_level = 1 # When a section starts in a new page, force it to be 'even', 'odd', # or just use 'any' #pdf_breakside = 'any' # Insert footnotes where they are defined instead of # at the end. #pdf_inline_footnotes = True # verbosity level. 0 1 or 2 #pdf_verbosity = 0 # If false, no index is generated. #pdf_use_index = True # If false, no modindex is generated. #pdf_use_modindex = True pdf_use_modindex = False # If false, no coverpage is generated. #pdf_use_coverpage = True # Name of the cover page template to use #pdf_cover_template = 'sphinxcover.tmpl' # Documents to append as an appendix to all manuals. #pdf_appendices = [] # Enable experimental feature to split table cells. Use it # if you get "DelayedTable too big" errors # pdf_splittables = True XXX pdf_splittables = False # Set the default DPI for images #pdf_default_dpi = 72 # Enable rst2pdf extension modules (default is empty list) # you need vectorpdf for better sphinx's graphviz support #pdf_extensions = ['vectorpdf'] # Page template name for "regular" pages pdf_page_template = 'cutePage' behave-1.2.6/docs/context_attributes.rst0000644000076600000240000000674713244555737020506 0ustar jensstaff00000000000000.. _id.appendix.context_attributes: ============================================================================== Context Attributes ============================================================================== A context object (:class:`~behave.runner.Context`) is handed to * step definitions (step implementations) * behave hooks (:func:`before_all`, :func:`before_feature`, ..., :func:`after_all`) Behave Attributes ------------------------- The :pypi:`behave` runner assigns a number of attributes to the context object during a test run. =============== ========= ============================================= ============================================================== Attribute Name Layer Type Description =============== ========= ============================================= ============================================================== config test run :class:`~behave.configuration.Configuration` Configuration that is used. aborted test run bool Set to true if test run is aborted by the user. failed test run bool Set to true if a step fails. feature feature :class:`~behave.model.Feature` Current feature. tags feature, list<:class:`~behave.model.Tag`> Effective tags of current feature, scenario, scenario outline. scenario active_outline scenario :class:`~behave.model.Row` Current row in a scenario outline (in examples table). outline scenario scenario :class:`~behave.model.Scenario` Current scenario. log_capture scenario :class:`~behave.log_capture.LoggingCapture` If logging capture is enabled. stdout_capture scenario :class:`~StringIO.StringIO` If stdout capture is enabled. stderr_capture scenario :class:`~StringIO.StringIO` If stderr capture is enabled. table step :class:`~behave.model.Table` Contains step's table, otherwise None. text step String Contains step's multi-line text (unicode), otherwise None. =============== ========= ============================================= ============================================================== .. note:: `Behave attributes`_ in the context object should not be modified by a user. See :class:`~behave.runner.Context` class description for more details. User Attributes ------------------------- A user can assign (or modify) own attributes to the context object. But these attributes will be removed again from the context object depending where these attributes are defined. ======= =========================== ======================= Kind Assign Location Lifecycle Layer (Scope) ======= =========================== ======================= Hook :func:`before_all` test run Hook :func:`after_all` test run Hook :func:`before_tags` feature or scenario Hook :func:`after_tags` feature or scenario Hook :func:`before_feature` feature Hook :func:`after_feature` feature Hook :func:`before_scenario` scenario Hook :func:`after_scenario` scenario Hook :func:`before_step` scenario Hook :func:`after_step` scenario Step Step definition scenario ======= =========================== ======================= behave-1.2.6/docs/fixtures.rst0000644000076600000240000002507313244555737016416 0ustar jensstaff00000000000000.. _docid.fixtures: Fixtures ============================================================================== A common task during test execution is to: * setup a functionality when a test-scope is entered * cleanup (or teardown) the functionality at the end of the test-scope **Fixtures** are provided as concept to simplify this setup/cleanup task in `behave`_. Providing a Fixture ------------------- .. code-block:: python # -- FILE: behave4my_project/fixtures.py (or in: features/environment.py) from behave import fixture from somewhere.browser.firefox import FirefoxBrowser # -- FIXTURE-VARIANT 1: Use generator-function @fixture def browser_firefox(context, timeout=30, **kwargs): # -- SETUP-FIXTURE PART: context.browser = FirefoxBrowser(timeout, **kwargs) yield context.browser # -- CLEANUP-FIXTURE PART: context.browser.shutdown() .. code-block:: python # -- FIXTURE-VARIANT 2: Use normal function from somewhere.browser.chrome import ChromeBrowser @fixture def browser_chrome(context, timeout=30, **kwargs): # -- SETUP-FIXTURE PART: And register as context-cleanup task. browser = ChromeBrowser(timeout, **kwargs) context.browser = browser context.add_cleanup(browser.shutdown) return browser # -- CLEANUP-FIXTURE PART: browser.shutdown() # Fixture-cleanup is called when current context-layer is removed. .. seealso:: A *fixture* is similar to: * a :func:`contextlib.contextmanager` * a `pytest.fixture`_ * the `scope guard`_ idiom .. include:: _common_extlinks.rst Using a Fixture --------------- In many cases, the usage of a fixture is triggered by the ``fixture-tag`` in a feature file. The ``fixture-tag`` marks that a fixture should be used in this scenario/feature (as test-scope). .. code-block:: Gherkin # -- FILE: features/use_fixture1.feature Feature: Use Fixture on Scenario Level @fixture.browser.firefox Scenario: Use Web Browser Firefox Given I load web page "https://somewhere.web" ... # -- AFTER-SCENARIO: Cleanup fixture.browser.firefox .. code-block:: Gherkin # -- FILE: features/use_fixture2.feature @fixture.browser.firefox Feature: Use Fixture on Feature Level Scenario: Use Web Browser Firefox Given I load web page "https://somewhere.web" ... Scenario: Another Browser Test ... # -- AFTER-FEATURE: Cleanup fixture.browser.firefox A **fixture** can be used by calling the :func:`~behave.use_fixture()` function. The :func:`~behave.use_fixture()` call performs the ``SETUP-FIXTURE`` part and returns the setup result. In addition, it ensures that ``CLEANUP-FIXTURE`` part is called later-on when the current context-layer is removed. Therefore, any manual cleanup handling in the ``after_tag()`` hook is not necessary. .. code-block:: python # -- FILE: features/environment.py from behave import use_fixture from behave4my_project.fixtures import browser_firefox def before_tag(context, tag): if tag == "fixture.browser.firefox": use_fixture(browser_firefox, context, timeout=10) Realistic Example ~~~~~~~~~~~~~~~~~ A more realistic example by using a fixture registry is shown below: .. code-block:: python # -- FILE: features/environment.py from behave.fixture import use_fixture_by_tag, fixture_call_params from behave4my_project.fixtures import browser_firefox, browser_chrome # -- REGISTRY DATA SCHEMA 1: fixture_func fixture_registry1 = { "fixture.browser.firefox": browser_firefox, "fixture.browser.chrome": browser_chrome, } # -- REGISTRY DATA SCHEMA 2: (fixture_func, fixture_args, fixture_kwargs) fixture_registry2 = { "fixture.browser.firefox": fixture_call_params(browser_firefox), "fixture.browser.chrome": fixture_call_params(browser_chrome, timeout=12), } def before_tag(context, tag): if tag.startswith("fixture."): return use_fixture_by_tag(tag, context, fixture_registry1): # -- MORE: Tag processing steps ... .. code-block:: python # -- FILE: behave/fixture.py # ... def use_fixture_by_tag(tag, context, fixture_registry): fixture_data = fixture_registry.get(tag, None) if fixture_data is None: raise LookupError("Unknown fixture-tag: %s" % tag) # -- FOR DATA SCHEMA 1: fixture_func = fixture_data return use_fixture(fixture_func, context) # -- FOR DATA SCHEMA 2: fixture_func, fixture_args, fixture_kwargs = fixture_data return use_fixture(fixture_func, context, *fixture_args, **fixture_kwargs) .. hint:: **Naming Convention for Fixture Tags** Fixture tags should start with ``"@fixture.*"`` prefix to improve readability and understandibilty in feature files (Gherkin). Tags are used for different purposes. Therefore, it should be clear when a ``fixture-tag`` is used. Fixture Cleanup Points ------------------------------------------------------------------------------ The point when a fixture-cleanup is performed depends on the scope where :func:`~behave.use_fixture()` is called (and the fixture-setup is performed). ============= =========================== ========================================================================================== Context Layer Fixture-Setup Point Fixture-Cleanup Point ============= =========================== ========================================================================================== test run In ``before_all()`` hook After ``after_all()`` at end of test-run. feature In ``before_feature()`` After ``after_feature()``, at end of feature. feature In ``before_tag()`` After ``after_feature()`` for feature tag. scenario In ``before_scenario()`` After ``after_scenario()``, at end of scenario. scenario In ``before_tag()`` After ``after_scenario()`` for scenario tag. scenario In a step After ``after_scenario()``. Fixture is usable until end of scenario. ============= =========================== ========================================================================================== Fixture Setup/Cleanup Semantics ------------------------------------------------------------------------------ If an error occurs during fixture-setup (meaning an exception is raised): * Feature/scenario execution is aborted * Any remaining fixture-setups are skipped * After feature/scenario hooks are processed * All fixture-cleanups and context cleanups are performed * The feature/scenario is marked as failed If an error occurs during fixture-cleanup (meaning an exception is raised): * All remaining fixture-cleanups and context cleanups are performed * First cleanup-error is reraised to pass failure to user (test runner) * The feature/scenario is marked as failed Ensure Fixture Cleanups with Fixture Setup Errors ------------------------------------------------------------------------------ Fixture-setup errors are special because a cleanup of a fixture is in many cases not necessary (or rather difficult because the fixture object is only partly created, etc.). Therefore, if an error occurs during fixture-setup (meaning: an exception is raised), the fixture-cleanup part is normally not called. If you need to ensure that the fixture-cleanup is performed, you need to provide a slightly different fixture implementation: .. code-block:: python # -- FILE: behave4my_project/fixtures.py (or: features/environment.py) from behave import fixture from somewhere.browser.firefox import FirefoxBrowser def setup_fixture_part2_with_error(arg): raise RuntimeError("OOPS-FIXTURE-SETUP-ERROR-HERE) # -- FIXTURE-VARIANT 1: Use generator-function with try/finally. @fixture def browser_firefox(context, timeout=30, **kwargs): try: browser = FirefoxBrowser(timeout, **kwargs) browser.part2 = setup_fixture_part2_with_error("OOPS") context.browser = browser # NOT_REACHED yield browser # -- NORMAL FIXTURE-CLEANUP PART: NOT_REACHED due to setup-error. finally: browser.shutdown() # -- CLEANUP: When generator-function is left. .. code-block:: python # -- FIXTURE-VARIANT 2: Use normal function and register cleanup-task early. from somewhere.browser.chrome import ChromeBrowser @fixture def browser_chrome(context, timeout=30, **kwargs): browser = ChromeBrowser(timeout, **kwargs) context.browser = browser context.add_cleanup(browser.shutdown) # -- ENSURE-CLEANUP EARLY browser.part2 = setup_fixture_part2_with_error("OOPS") return browser # NOT_REACHED # -- CLEANUP: browser.shutdown() when context-layer is removed. .. note:: An fixture-setup-error that occurs when the browser object is created, is not covered by these solutions and not so easy to solve. Composite Fixtures ------------------------------------------------------------------------------ The last section already describes some problems when you use complex or *composite fixtures*. It must be ensured that cleanup of already created fixture parts is performed even when errors occur late in the creation of a *composite fixture*. This is basically a `scope guard`_ problem. Solution 1: ~~~~~~~~~~~ .. code-block:: python # -- FILE: behave4my_project/fixtures.py # SOLUTION 1: Use "use_fixture()" to ensure cleanup even in case of errors. from behave import fixture, use_fixture @fixture def foo(context, *args, **kwargs): pass # -- FIXTURE IMPLEMENTATION: Not of interest here. @fixture def bar(context, *args, **kwargs): pass # -- FIXTURE IMPLEMENTATION: Not of interest here. # -- SOLUTION: With use_fixture() # ENSURES: foo-fixture is cleaned up even when setup-error occurs later. @fixture def composite1(context, *args, **kwargs): the_fixture1 = use_fixture(foo, context) the_fixture2 = use_fixture(bar, context) return [the_fixture1, the_fixture2] Solution 2: ~~~~~~~~~~~ .. code-block:: python # -- ALTERNATIVE SOLUTION: With use_composite_fixture_with() from behave import fixture from behave.fixture import use_composite_fixture_with, fixture_call_params @fixture def composite2(context, *args, **kwargs): the_composite = use_composite_fixture_with(context, [ fixture_call_params(foo, name="foo"), fixture_call_params(bar, name="bar"), ]) return the_composite behave-1.2.6/docs/formatters.rst0000644000076600000240000001125413244555737016727 0ustar jensstaff00000000000000.. _id.appendix.formatters: ============================================================================== Formatters and Reporters ============================================================================== :pypi:`behave` provides 2 different concepts for reporting results of a test run: * formatters * reporters A slightly different interface is provided for each "formatter" concept. The ``Formatter`` is informed about each step that is taken. The ``Reporter`` has a more coarse-grained API. Reporters ------------------------------------------------------------------------------ The following reporters are currently supported: ============== ================================================================ Name Description ============== ================================================================ junit Provides JUnit XML-like output. summary Provides a summary of the test run. ============== ================================================================ Formatters ------------------------------------------------------------------------------ The following formatters are currently supported: ============== ======== ================================================================ Name Mode Description ============== ======== ================================================================ help normal Shows all registered formatters. json normal JSON dump of test run json.pretty normal JSON dump of test run (human readable) plain normal Very basic formatter with maximum compatibility pretty normal Standard colourised pretty formatter progress normal Shows dotted progress for each executed scenario. progress2 normal Shows dotted progress for each executed step. progress3 normal Shows detailed progress for each step of a scenario. rerun normal Emits scenario file locations of failing scenarios sphinx.steps dry-run Generate sphinx-based documentation for step definitions. steps dry-run Shows step definitions (step implementations). steps.doc dry-run Shows documentation for step definitions. steps.usage dry-run Shows how step definitions are used by steps (in feature files). tags dry-run Shows tags (and how often they are used). tags.location dry-run Shows tags and the location where they are used. ============== ======== ================================================================ .. note:: You can use more than one formatter during a test run. But in general you have only one formatter that writes to ``stdout``. The "Mode" column indicates if a formatter is intended to be used in dry-run (``--dry-run`` command-line option) or normal mode. User-Defined Formatters ------------------------------------------------------------------------------ Behave allows you to provide your own formatter (class):: # -- USE: Formatter class "Json2Formatter" in python module "foo.bar" # NOTE: Formatter must be importable from python search path. behave -f foo.bar:Json2Formatter ... The usage of a user-defined formatter can be simplified by providing an alias name for it in the configuration file: .. code-block:: ini # -- FILE: behave.ini # ALIAS SUPPORTS: behave -f json2 ... # NOTE: Formatter aliases may override builtin formatters. [behave.formatters] json2 = foo.bar:Json2Formatter If your formatter can be configured, you should use the userdata concept to provide them. The formatter should use the attribute schema: .. code-block:: ini # -- FILE: behave.ini # SCHEMA: behave.formatter.. [behave.userdata] behave.formatter.json2.use_pretty = true # -- SUPPORTS ALSO: # behave -f json2 -D behave.formatter.json2.use_pretty ... More Formatters ------------------------------------------------------------------------------ The following formatters are currently known: ============== ========================================================================= Name Description ============== ========================================================================= allure :pypi:`allure-behave`, an Allure formatter for behave: ``allure_behave.formatter:AllureFormatter`` teamcity :pypi:`behave-teamcity`, a formatter for Jetbrains TeamCity CI testruns with behave. ============== ========================================================================= .. code-block:: ini # -- FILE: behave.ini # FORMATTER ALIASES: behave -f allure ... [behave.formatters] allure = allure_behave.formatter:AllureFormatter teamcity = behave_teamcity:TeamcityFormatter behave-1.2.6/docs/gherkin.rst0000644000076600000240000005457513244555737016205 0ustar jensstaff00000000000000===================== Feature Testing Setup ===================== .. if you change any headings in here make sure you haven't broken the cross-references in the API documentation or module docstrings! Feature Testing Layout ====================== *behave* works with three types of files: 1. `feature files`_ written by your Business Analyst / Sponsor / whoever with your behaviour scenarios in it, and 2. a "steps" directory with `Python step implementations`_ for the scenarios. 3. optionally some `environmental controls`_ (code to run before and after steps, scenarios, features or the whole shooting match). .. _`feature files`: #gherkin-feature-testing-language .. _`Python step implementations`: tutorial.html#python-step-implementations .. _`environmental controls`: tutorial.html#environmental-controls These files are typically stored in a directory called "features". The minimum requirement for a features directory is:: features/ features/everything.feature features/steps/ features/steps/steps.py A more complex directory might look like:: features/ features/signup.feature features/login.feature features/account_details.feature features/environment.py features/steps/ features/steps/website.py features/steps/utils.py Layout Variations ----------------- *behave* has some flexibility built in. It will actually try quite hard to find feature specifications. When launched you may pass on the command line: **nothing** In the absence of any information *behave* will attempt to load your features from a subdirectory called "features" in the directory you launched *behave*. **a features directory path** This is the path to a features directory laid out as described above. It may be called anything by *must* contain at least one "*name*.feature" file and a directory called "steps". The "environment.py" file, if present, must be in the same directory that contains the "steps" directory (not *in* the "steps" directory). **the path to a "*name*.feature" file** This tells *behave* where to find the feature file. To find the steps directory *behave* will look in the directory containing the feature file. If it is not present, *behave* will look in the parent directory, and then its parent, and so on until it hits the root of the filesystem. The "environment.py" file, if present, must be in the same directory that contains the "steps" directory (not *in* the "steps" directory). **a directory containing your feature files** Similar to the approach above, you're identifying the directory where your "*name*.feature" files are, and if the "steps" directory is not in the same place then *behave* will search for it just like above. This allows you to have a layout like:: tests/ tests/environment.py tests/features/signup.feature tests/features/login.feature tests/features/account_details.feature tests/steps/ tests/steps/website.py tests/steps/utils.py Note that with this approach, if you want to execute *behave* without having to explicitly specify the directory (first option) you can set the ``paths`` setting in your `configuration file`_ (e.g. ``paths=tests``). If you're having trouble setting things up and want to see what *behave* is doing in attempting to find your features use the "-v" (verbose) command-line switch. .. _`configuration file`: behave.html#configuration-files .. _chapter.gherkin: Gherkin: Feature Testing Language ================================= *behave* `features`_ are written using a language called `Gherkin`_ (with `some modifications`_) and are named "*name*.feature". .. _`some modifications`: #modifications-to-the-gherkin-standard These files should be written using natural language - ideally by the non-technical business participants in the software project. Feature files serve two purposes – documentation and automated tests. It is very flexible but has a few simple rules that writers need to adhere to. Line endings terminate statements (eg, steps). Either spaces or tabs may be used for indentation (but spaces are more portable). Indentation is almost always ignored - it's a tool for the feature writer to express some structure in the text. Most lines start with a keyword ("Feature", "Scenario", "Given", ...) Comment lines are allowed anywhere in the file. They begin with zero or more spaces, followed by a sharp sign (#) and some amount of text. .. _`gherkin`: https://github.com/cucumber/cucumber/wiki/Gherkin Features -------- Features are composed of scenarios. They may optionally have a description, a background and a set of tags. In its simplest form a feature looks like: .. code-block:: gherkin Feature: feature name Scenario: some scenario Given some condition Then some result is expected. In all its glory it could look like: .. code-block:: gherkin @tags @tag Feature: feature name description further description Background: some requirement of this test Given some setup condition And some other setup action Scenario: some scenario Given some condition When some action is taken Then some result is expected. Scenario: some other scenario Given some other condition When some action is taken Then some other result is expected. Scenario: ... The feature name should just be some reasonably descriptive title for the feature being tested, like "the message posting interface". The following description is optional and serves to clarify any potential confusion or scope issue in the feature name. The description is for the benefit of humans reading the feature text. .. any other advice we could include here? The Background part and the Scenarios will be discussed in the following sections. Background ---------- A background consists of a series of steps similar to `scenarios`_. It allows you to add some context to the scenarios of a feature. A background is executed before each scenario of this feature but after any of the before hooks. It is useful for performing setup operations like: * logging into a web browser or * setting up a database with test data used by the scenarios. The background description is for the benefit of humans reading the feature text. Again the background name should just be a reasonably descriptive title for the background operation being performed or requirement being met. A background section may exist only once within a feature file. In addition, a background must be defined before any scenario or scenario outline. It contains `steps`_ as described below. **Good practices for using Background** Don’t use "Background" to set up complicated state unless that state is actually something the client needs to know. For example, if the user and site names don’t matter to the client, you should use a high-level step such as "Given that I am logged in as a site owner". Keep your "Background" section short. You’re expecting the user to actually remember this stuff when reading your scenarios. If the background is more than 4 lines long, can you move some of the irrelevant details into high-level steps? See `calling steps from other steps`_. Make your "Background" section vivid. You should use colorful names and try to tell a story, because the human brain can keep track of stories much better than it can keep track of names like "User A", "User B", "Site 1", and so on. Keep your scenarios short, and don’t have too many. If the background section has scrolled off the screen, you should think about using higher-level steps, or splitting the features file in two. .. _`calling steps from other steps`: api.html#calling-steps-from-other-steps .. _`Cucumber Background description`: https://github.com/cucumber/cucumber/wiki/Background Scenarios --------- Scenarios describe the discrete behaviours being tested. They are given a title which should be a reasonably descriptive title for the scenario being tested. The scenario description is for the benefit of humans reading the feature text. Scenarios are composed of a series of `steps`_ as described below. The steps typically take the form of "given some condition" "then we expect some test will pass." In this simplest form, a scenario might be: .. code-block:: gherkin Scenario: we have some stock when we open the store Given that the store has just opened then we should have items for sale. There may be additional conditions imposed on the scenario, and these would take the form of "when" steps following the initial "given" condition. If necessary, additional "and" or "but" steps may also follow the "given", "when" and "then" steps if more needs to be tested. A more complex example of a scenario might be: .. code-block:: gherkin Scenario: Replaced items should be returned to stock Given that a customer buys a blue garment and I have two blue garments in stock but I have no red garments in stock and three black garments in stock. When he returns the garment for a replacement in black, then I should have three blue garments in stock and no red garments in stock, and two black garments in stock. It is good practise to have a scenario test only one behaviour or desired outcome. Scenarios contain `steps`_ as described below. Scenario Outlines ----------------- These may be used when you have a set of expected conditions and outcomes to go along with your scenario `steps`_. An outline includes keywords in the step definitions which are filled in using values from example tables. You may have a number of example tables in each scenario outline. .. code-block:: gherkin Scenario Outline: Blenders Given I put in a blender, when I switch the blender on then it should transform into Examples: Amphibians | thing | other thing | | Red Tree Frog | mush | Examples: Consumer Electronics | thing | other thing | | iPhone | toxic waste | | Galaxy Nexus | toxic waste | *behave* will run the scenario once for each (non-heading) line appearing in the example data tables. The values to replace are determined using the name appearing in the angle brackets "<*name*>" which must match a headings of the example tables. The name may include almost any character, though not the close angle bracket ">". Substitution may also occur in `step data`_ if the "<*name*>" texts appear within the step data text or table cells. Steps ----- Steps take a line each and begin with a *keyword* - one of "given", "when", "then", "and" or "but". In a formal sense the keywords are all Title Case, though some languages allow all-lowercase keywords where that makes sense. Steps should not need to contain significant degree of detail about the mechanics of testing; that is, instead of: .. code-block:: gherkin Given a browser client is used to load the URL "http://website.example/website/home.html" the step could instead simply say: .. code-block:: gherkin Given we are looking at the home page Steps are implemented using Python code which is implemented in the "steps" directory in Python modules (files with Python code which are named "*name*.py".) The naming of the Python modules does not matter. *All* modules in the "steps" directory will be imported by *behave* at startup to discover the step implementations. Given, When, Then (And, But) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *behave* doesn't technically distinguish between the various kinds of steps. However, we strongly recommend that you do! These words have been carefully selected for their purpose, and you should know what the purpose is to get into the BDD mindset. Given """"" The purpose of givens is to **put the system in a known state** before the user (or external system) starts interacting with the system (in the When steps). Avoid talking about user interaction in givens. If you had worked with usecases, you would call this preconditions. Examples: - Create records (model instances) / set up the database state. - It's ok to call directly into your application model here. - Log in a user (An exception to the no-interaction recommendation. Things that "happened earlier" are ok). You might also use Given with a multiline table argument to set up database records instead of fixtures hard-coded in steps. This way you can read the scenario and make sense out of it without having to look elsewhere (at the fixtures). When """" Each of these steps should **describe the key action** the user (or external system) performs. This is the interaction with your system which should (or perhaps should not) cause some state to change. Examples: - Interact with a web page (`Requests`_/`Twill`_/`Selenium`_ *interaction* etc should mostly go into When steps). - Interact with some other user interface element. - Developing a library? Kicking off some kind of action that has an observable effect somewhere else. .. _`requests`: http://docs.python-requests.org/en/latest/ .. _`twill`: http://twill.idyll.org/ .. _`selenium`: http://docs.seleniumhq.org/projects/webdriver/ Then """" Here we **observe outcomes**. The observations should be related to the business value/benefit in your feature description. The observations should also be on some kind of *output* - that is something that comes *out* of the system (report, user interface, message) and not something that is deeply buried inside it (that has no business value). Examples: - Verify that something related to the Given+When is (or is not) in the output - Check that some external system has received the expected message (was an email with specific content sent?) While it might be tempting to implement Then steps to just look in the database - resist the temptation. You should only verify outcome that is observable for the user (or external system) and databases usually are not. And, But """""""" If you have several givens, whens or thens you could write: .. code-block:: gherkin Scenario: Multiple Givens Given one thing Given an other thing Given yet an other thing When I open my eyes Then I see something Then I don't see something else Or you can make it read more fluently by writing: .. code-block:: gherkin Scenario: Multiple Givens Given one thing And an other thing And yet an other thing When I open my eyes Then I see something But I don't see something else The two scenarios are identical to *behave* - steps beginning with "and" or "but" are exactly the same kind of steps as all the others. They simply mimic the step that preceeds them. Step Data ~~~~~~~~~ Steps may have some text or a table of data attached to them. Substitution of scenario outline values will be done in step data text or table data if the "<*name*>" texts appear within the step data text or table cells. Text """" Any text block following a step wrapped in ``"""`` lines will be associated with the step. This is the one case where indentation is actually parsed: the leading whitespace is stripped from the text, and successive lines of the text should have at least the same amount of whitespace as the first line. So for this rather contrived example: .. code-block:: gherkin Scenario: some scenario Given a sample text loaded into the frobulator """ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. """ When we activate the frobulator Then we will find it similar to English The text is available to the Python step code as the ".text" attribute in the :class:`~behave.runner.Context` variable passed into each step function. The text supplied on the first step in a scenario will be available on the context variable for the duration of that scenario. Any further text present on a subsequent step will overwrite previously-set text. Table """"" You may associate a table of data with a step by simply entering it, indented, following the step. This can be useful for loading specific required data into a model. The table formatting doesn't have to be strictly lined up but it does need to have the same number of columns on each line. A column is anything appearing between two vertical bars "|". Any whitespace between the column content and the vertical bar is removed. .. code-block:: gherkin Scenario: some scenario Given a set of specific users | name | department | | Barry | Beer Cans | | Pudey | Silly Walks | | Two-Lumps | Silly Walks | When we count the number of people in each department Then we will find two people in "Silly Walks" But we will find one person in "Beer Cans" The table is available to the Python step code as the ".table" attribute in the :class:`~behave.runner.Context` variable passed into each step function. The table is an instance of :class:`~behave.model.Table` and for the example above could be accessed like so: .. code-block:: python @given('a set of specific users') def step_impl(context): for row in context.table: model.add_user(name=row['name'], department=row['department']) There's a variety of ways to access the table data - see the :class:`~behave.model.Table` API documentation for the full details. Tags ---- You may also "tag" parts of your feature file. At the simplest level this allows *behave* to selectively check parts of your feature set. You may tag features, scenarios or scenario outlines but nothing else. Any tag that exists in a feature will be inherited by its scenarios and scenario outlines. Tags appear on the line preceding the feature or scenario you wish to tag. You may have many space-separated tags on a single line. A tag takes the form of the at symbol "@" followed by a word (which may include underscores "_"). Valid tag lines include:: @slow @wip @needs_database @slow For example: .. code-block:: gherkin @wip @slow Feature: annual reporting Some description of a slow reporting system. or: .. code-block:: gherkin @wip @slow Feature: annual reporting Some description of a slow reporting system. Tags may be used to `control your test run`_ by only including certain features or scenarios based on tag selection. The tag information may also be accessed from the `Python code backing up the tests`_. .. _`control your test run`: #controlling-your-test-run-with-tags .. _`Python code backing up the tests`: #accessing-tag-information-in-python Controlling Your Test Run With Tags ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Given a feature file with: .. code-block:: gherkin Feature: Fight or flight In order to increase the ninja survival rate, As a ninja commander I want my ninjas to decide whether to take on an opponent based on their skill levels @slow Scenario: Weaker opponent Given the ninja has a third level black-belt When attacked by a samurai Then the ninja should engage the opponent Scenario: Stronger opponent Given the ninja has a third level black-belt When attacked by Chuck Norris Then the ninja should run for his life then running ``behave --tags=slow`` will run just the scenarios tagged ``@slow``. If you wish to check everything *except* the slow ones then you may run ``behave --tags=-slow``. Another common use-case is to tag a scenario you're working on with ``@wip`` and then ``behave --tags=wip`` to just test that one case. Tag selection on the command-line may be combined: **--tags=wip,slow** This will select all the cases tagged *either* "wip" or "slow". **--tags=wip --tags=slow** This will select all the cases tagged *both* "wip" and "slow". If a feature or scenario is tagged and then skipped because of a command-line control then the *before_* and *after_* environment functions will not be called for that feature or scenario. Accessing Tag Information In Python ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The tags attached to a feature and scenario are available in the environment functions via the "feature" or "scenario" object passed to them. On those objects there is an attribute called "tags" which is a list of the tag names attached, in the order they're found in the features file. There are also `environmental controls`_ specific to tags, so in the above example *behave* will attempt to invoke an ``environment.py`` function ``before_tag`` and ``after_tag`` before and after the Scenario tagged ``@slow``, passing in the name "slow". If multiple tags are present then the functions will be called multiple times with each tag in the order they're defined in the feature file. Re-visiting the example from above; if only some of the features required a browser and web server then you could tag them ``@browser``: .. code-block:: python def before_feature(context, feature): model.init(environment='test') if 'browser' in feature.tags: context.server = simple_server.WSGIServer(('', 8000)) context.server.set_app(web_app.main(environment='test')) context.thread = threading.Thread(target=context.server.serve_forever) context.thread.start() context.browser = webdriver.Chrome() def after_feature(context, feature): if 'browser' in feature.tags: context.server.shutdown() context.thread.join() context.browser.quit() Languages Other Than English ---------------------------- English is the default language used in parsing feature files. If you wish to use a different language you should check to see whether it is available:: behave --lang-list This command lists all the supported languages. If yours is present then you have two options: 1. add a line to the top of the feature files like (for French): # language: fr 2. use the command-line switch ``--lang``:: behave --lang=fr The feature file keywords will now use the French translations. To see what the language equivalents recognised by *behave* are, use:: behave --lang-help fr Modifications to the Gherkin Standard ------------------------------------- *behave* can parse standard Gherkin files and extends Gherkin to allow lowercase step keywords because these can sometimes allow more readable feature specifications. behave-1.2.6/docs/index.rst0000644000076600000240000000317013244555737015646 0ustar jensstaff00000000000000Welcome to behave! ================== behave is behaviour-driven development, Python style. |behave_logo| Behavior-driven development (or BDD) is an agile software development technique that encourages collaboration between developers, QA and non-technical or business participants in a software project. We have a page further describing this :doc:`philosophy `. :pypi:`behave` uses tests written in a natural language style, backed up by Python code. Once you've :doc:`installed ` *behave*, we recommend reading the * :doc:`tutorial ` first and then * :doc:`feature test setup `, * :doc:`behave API ` and * :doc:`related software ` (things that you can combine with :pypi:`behave`) * finally: :doc:`how to use and configure ` the :pypi:`behave` tool. There is also a :doc:`comparison ` with the other tools available. Contents -------- .. toctree:: :maxdepth: 2 install tutorial philosophy gherkin behave api fixtures usecase_django usecase_flask practical_tips comparison new_and_noteworthy more_info appendix .. seealso:: * `behave.example`_: `Behave Examples and Tutorials`_ (HTML) * Peter Parente: `BDD and Behave `_ (tutorial) .. _behave.example: https://github.com/behave/behave.example .. _`Behave Examples and Tutorials`: http://behave.github.io/behave.example/ Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` .. |behave_logo| image:: _static/behave_logo1.png .. include:: _common_extlinks.rst behave-1.2.6/docs/install.rst0000644000076600000240000000274713244555737016216 0ustar jensstaff00000000000000Installation ============ Using pip (or ...) ------------------ :Category: Stable version :Precondition: :pypi:`pip` (or :pypi:`setuptools`) is installed Execute the following command to install :pypi:`behave` with :pypi:`pip`: pip install behave To update an already installed :pypi:`behave` version, use: pip install -U behave As an alternative, you can also use :pypi:`easy_install ` to install :pypi:`behave`:: easy_install behave # CASE: New installation. easy_install -U behave # CASE: Upgrade existing installation. .. hint:: See also `pip related information`_ for installing Python packages. .. _`pip related information`: https://pip.pypa.io/en/latest/installing/ Using a Source Distribution --------------------------- After unpacking the :pypi:`behave` source distribution, enter the newly created directory "behave-" and run:: python setup.py install Using the Github Repository --------------------------- :Category: Bleading edge :Precondition: :pypi:`pip` is installed Run the following command to install the newest version from the `Github repository`_:: pip install git+https://github.com/behave/behave To install a tagged version from the `Github repository`_, use:: pip install git+https://github.com/behave/behave@ where is the placeholder for an `existing tag`_. .. _`Github repository`: https://github.com/behave/behave .. _`existing tag`: https://github.com/behave/behave/tags behave-1.2.6/docs/Makefile0000644000076600000240000001276713244555737015461 0ustar jensstaff00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = UPDATE = ./update_behave_rst.py SPHINXBUILD = $(UPDATE) && sphinx-build PAPER = BUILDDIR = ../build/docs # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/behave.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/behave.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/behave" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/behave" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." behave-1.2.6/docs/more_info.rst0000644000076600000240000001464213244555737016522 0ustar jensstaff00000000000000.. _id.appendix.more_info: More Information about Behave ============================================================================== Tutorials ------------------------------------------------------------------------------ For new users, that want to read, understand and explore the concepts in Gherkin and `behave`_ (after reading the behave documentation): * "`Behave by Example `_" (on `github `_) The following small tutorials provide an introduction how you use `behave`_ in a specific testing domain: * Phillip Johnson, `Getting Started with Behavior Testing in Python with Behave`_ * `Bdd with Python, Behave and WebDriver`_ * Wayne Witzel III, `Using Behave with Pyramid`_, 2014-01-10. .. _`Getting Started with Behavior Testing in Python with Behave`: https://semaphoreci.com/community/tutorials/getting-started-with-behavior-testing-in-python-with-behave .. _`Bdd with Python, Behave and WebDriver`: https://testingbot.com/support/getting-started/behave.html .. _`Using Behave with Pyramid`: https://www.safaribooksonline.com/blog/2014/01/10/using-behave-with-pyramid/ .. warning:: A word of caution if you are new to **"behaviour-driven development" (BDD)**. In general, you want to avoid "user interface" (UI) details in your scenarios, because they describe **how something is implemented** (in this case the UI itself), like: * ``press this button`` * then ``enter this text into the text field`` * ... In **BDD** (or testing in general), you should describe **what should be done** (meaning the intention). This will make your scenarios much more robust and stable because you can change the underlying implementation of: * the "system under test" (SUT) or * the test automation layer, that interacts with the SUT. without changing the scenarios. Books ------------------------------------------------------------------------------ `Behave`_ is covered in the following books: .. [TDD-Python] Harry Percival, `Test-Driven Web Development with Python`_, O'Reilly, June 2014, `Appendix E: BDD `_ (covers behave) .. _`Test-Driven Web Development with Python`: http://chimera.labs.oreilly.com/books/1234000000754 Presentation Videos ------------------------------------------------------------------------------ * Benno Rice: `Making Your Application Behave`_ (30min), 2012-08-12, PyCon Australia. * Selenium: `First behave python tuorial with selenium`_ (8min), 2015-01-28, http://www.seleniumframework.com/python-basic/first-behave-gherkin/ * Jessica Ingrasselino: `Automation with Python and Behave`_ (67min), 2015-12-16 * `Selenium Python Webdriver Tutorial - Behave (BDD)`_ (14min), 2016-01-21 .. hidden: PREPARED: --------------------- .. ifconfig:: not supports_video * Benno Rice: `Making Your Application Behave`_ (30min), PyCon Australia, 2012-08-12 * Selenium: `First behave python tuorial with selenium`_ (8min), 2015-01-28, http://www.seleniumframework.com/python-basic/first-behave-gherkin/ * Jessica Ingrasselino: `Automation with Python and Behave`_ (67min), 2015-12-16 * `Selenium Python Webdriver Tutorial - Behave (BDD)`_ (14min), 2016-01-21 .. hint:: Manually install `sphinxcontrib-youtube`_ (from "youtube" subdirectory in sphinx-extensions bundle) to have embedded videos on this page (when this page is build). .. ifconfig:: supports_video Benno Rice: `Making Your Application Behave`_ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :Conference: PyCon Australia :Date: 2012-08-12 :Duration: 30min .. youtube:: u8BOKuNkmhg :width: 600 :height: 400 Selenium: `First behave python tuorial with selenium`_ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :Date: 2015-01-28 :Duration: 8min .. youtube:: D24_QrGUCFk :width: 600 :height: 400 RELATED: http://www.seleniumframework.com/python-basic/what-is-python/ Jessica Ingrasselino: `Automation with Python and Behave`_ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :Date: 2015-12-16 :Duration: 67min .. youtube:: e78c7h6DRDQ :width: 600 :height: 400 `Selenium Python Webdriver Tutorial - Behave (BDD)`_ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :Date: 2016-01-21 :Duration: 14min .. youtube:: mextSo0UExc :width: 600 :height: 400 .. _`Making Your Application Behave`: https://www.youtube.com/watch?v=u8BOKuNkmhg .. _`First behave python tuorial with selenium`: https://www.youtube.com/watch?v=D24_QrGUCFk .. _`Automation with Python and Behave`: https://www.youtube.com/watch?v=e78c7h6DRDQ .. _`Selenium Python Webdriver Tutorial - Behave (BDD)`: https://www.youtube.com/watch?v=mextSo0UExc .. _sphinxcontrib-youtube: https://bitbucket.org/birkenfeld/sphinx-contrib Tool-oriented Tutorials ------------------------------------------------------------------------------ JetBrains PyCharm: * Blog: `In-Depth Screencast on Testing`_ (2016-04-11; video offset=2:10min) * Docs: `BDD Testing Framework Support in PyCharm 2016.1 `_ .. _`Getting Started with PyCharm`: https://www.youtube.com/playlist?list=PLQ176FUIyIUZ1mwB-uImQE-gmkwzjNLjP .. _`PyCharm In-Depth: Testing`: https://youtu.be/nmBbR97Vsv8?list=PLQ176FUIyIUZ1mwB-uImQE-gmkwzjNLjP .. _`In-Depth Screencast on Testing`: https://blog.jetbrains.com/pycharm/2016/04/in-depth-screencast-on-testing/ Find more Information ------------------------------------------------------------------------------ .. seealso:: * google:`python-behave examples `_ * google:`python-behave tutorials `_ * google:`python-behave videos `_ .. _Behave: https://github.com/behave/behave .. _behave: https://github.com/behave/behave .. _Selenium: http://docs.seleniumhq.org/ .. _behave4cmd: https://github.com/behave/behave4cmd .. _behave-django: https://github.com/behave/behave-django behave-1.2.6/docs/new_and_noteworthy.rst0000644000076600000240000000101013244555737020443 0ustar jensstaff00000000000000New and Noteworthy ============================================================================== In the good tradition of the `Eclipse IDE`_, a number of news, changes and improvements are described here to provide better background information about what has changed and how to make use of it. This page orders the information by newest version first. .. _`Eclipse IDE`: http://www.eclipse.org/ .. toctree:: :maxdepth: 2 new_and_noteworthy_v1.2.6 new_and_noteworthy_v1.2.5 new_and_noteworthy_v1.2.4 behave-1.2.6/docs/new_and_noteworthy_v1.2.4.rst0000644000076600000240000000043513244555737021365 0ustar jensstaff00000000000000Noteworthy in Version 1.2.4 ============================================================================== Diagnostics: Start Debugger on Error ------------------------------------------------------------------------------- :Since: behave 1.2.4a1 See also :ref:`debug-on-error` . behave-1.2.6/docs/new_and_noteworthy_v1.2.5.rst0000644000076600000240000006004013244555737021364 0ustar jensstaff00000000000000Noteworthy in Version 1.2.5 ============================================================================== Scenario Outline Improvements ------------------------------------------------------------------------------- .. index:: single: ScenarioOutline; name annotation pair: ScenarioOutline; file location Better represent Example/Row ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :Since: behave 1.2.5a1 :Covers: Name annotation, file location A scenario outline basically a parametrized scenario template. It represents a macro/script that is executed for a data-driven set of examples (parametrized data). Therefore, a scenario outline generates several scenarios, each representing one example/row combination. .. code-block:: gherkin # -- file:features/xxx.feature Feature: Scenario Outline: Wow # line 2 Given an employee "" Examples: Araxas | name | birthyear | | Alice | 1985 | # line 7 | Bob | 1975 | # line 8 Examples: | name | birthyear | | Charly | 1995 | # line 12 Up to now, the following scenarios were generated from the scenario outline: .. code-block:: gherkin Scenario Outline: Wow # features/xxx.feature:2 Given an employee "Alice" Scenario Outline: Wow # features/xxx.feature:2 Given an employee "Bob" Scenario Outline: Wow # features/xxx.feature:2 Given an employee "Charly" Note that all generated scenarios had the: * same name (scenario_outline.name) * same file location (scenario_outline.file_location) From now on, the generated scenarios better represent the example/row combination within a scenario outline: .. code-block:: gherkin Scenario Outline: Wow -- @1.1 Araxas # features/xxx.feature:7 Given an employee "Alice" Scenario Outline: Wow -- @1.2 Araxas # features/xxx.feature:8 Given an employee "Bob" Scenario Outline: Wow -- @2.1 # features/xxx.feature:12 Given an employee "Charly" Note that: * scenario name is now unique for any examples/row combination * scenario name optionally contains the examples (group) name (if one exists) * each scenario has a unique file location, based on the row's file location Therefore, each generated scenario from a scenario outline can be selected via its file location (and run on its own). In addition, if one fails, it is now possible to rerun only the failing example/row combination(s). The name annoations schema for the generated scenarios from above provides the new default name annotation schema. It can be adapted/overwritten in "behave.ini": .. code-block:: ini # -- file:behave.ini [behave] scenario_outline_annotation_schema = {name} -- @{row.id} {examples.name} # -- REVERT TO: Old naming schema: # scenario_outline_annotation_schema = {name} The following additional placeholders are provided within a scenario outline to support this functionality. They can be used anywhere within a scenario outline. =============== =============================================================== Placeholder Description =============== =============================================================== examples.name Refers name of the example group, may be an empty string. examples.index Index of the example group (range=1..N). row.index Index of the current row within an example group (range=1..R). row.id Shortcut for schema: "." =============== =============================================================== .. index:: single: ScenarioOutline; name with placeholders Name may contain Placeholders ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :Since: behave 1.2.5a1 A scenario outline can now use placeholders from example/rows in its name or its examples name. When the scenarios a generated, these placeholders will be replaced with the values of the example/row. Up to now this behavior did only apply to steps of a scenario outline. EXAMPLE: .. code-block:: gherkin # -- file:features/xxx.feature Feature: Scenario Outline: Wow - # line 2 Given an employee "" Examples: | name | birthyear | | Alice | 1985 | # line 7 | Bob | 1975 | # line 8 Examples: Benares- | name | birthyear | ID | | Charly | 1995 | 42 | # line 12 This leads to the following generated scenarios, one for each examples/row combination: .. code-block:: gherkin Scenario Outline: Wow Alice-1985 -- @1.1 # features/xxx.feature:7 Given an employee "Alice" Scenario Outline: Wow Bob-1975 -- @1.2 # features/xxx.feature:8 Given an employee "Bob" Scenario Outline: Wow Charly-1885 -- @2.1 Benares-42 # features/xxx.feature:12 Given an employee "Charly" .. index:: pair: ScenarioOutline; tags with placeholders Tags may contain Placeholders ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :Since: behave 1.2.5a1 Tags from a Scenario Outline are also part of the parametrized template. Therefore, you may also use placeholders in the tags of a Scenario Outline. .. note:: * Placeholder names, that are used in tags, should not contain whitespace. * Placeholder values, that are used in tags, are transformed to contain no whitespace characters. EXAMPLE: .. code-block:: gherkin # -- file:features/xxx.feature Feature: @foo.group @foo.row @foo.name. Scenario Outline: Wow # line 6 Given an employee "" Examples: Araxas | name | birthyear | | Alice | 1985 | # line 11 | Bob | 1975 | # line 12 Examples: Benares | name | birthyear | ID | | Charly | 1995 | 42 | # line 16 This leads to the following generated scenarios, one for each examples/row combination: .. code-block:: gherkin @foo.group1 @foo.row1.1 @foo.name.Alice Scenario Outline: Wow -- @1.1 Araxas # features/xxx.feature:11 Given an employee "Alice" @foo.group1 @foo.row1.2 @foo.name.Bob Scenario Outline: Wow -- @1.2 Araxas # features/xxx.feature:12 Given an employee "Bob" @foo.group2 @foo.row2.1 @foo.name.Charly Scenario Outline: Wow -- @2.1 Benares # features/xxx.feature:16 Given an employee "Charly" .. index:: single: ScenarioOutline; select-group-by-tag It is now possible to run only the examples group "Araxas" (examples group 1) by using the select-by-tag mechanism: .. code-block:: sh $ behave --tags=@foo.group1 -f progress3 features/xxx.feature ... # features/xxx.feature Wow -- @1.1 Araxas . Wow -- @1.2 Araxas . .. index:: single: ScenarioOutline; select-group-by-name Run examples group via select-by-name ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :Since: behave 1.2.5a1 The improvements on unique generated scenario names for a scenario outline (with name annotation) can now be used to run all rows of one examples group. EXAMPLE: .. code-block:: gherkin # -- file:features/xxx.feature Feature: Scenario Outline: Wow # line 2 Given an employee "" Examples: Araxas | name | birthyear | | Alice | 1985 | # line 7 | Bob | 1975 | # line 8 Examples: Benares | name | birthyear | | Charly | 1995 | # line 12 This leads to the following generated scenarios (when the feature is executed): .. code-block:: gherkin Scenario Outline: Wow -- @1.1 Araxas # features/xxx.feature:7 Given an employee "Alice" Scenario Outline: Wow -- @1.2 Araxas # features/xxx.feature:8 Given an employee "Bob" Scenario Outline: Wow -- @2.1 Benares # features/xxx.feature:12 Given an employee "Charly" You can now run all rows of the "Araxas" examples (group) by selecting it by name (name part or regular expression): .. code-block:: sh $ behave --name=Araxas -f progress3 features/xxx.feature ... # features/xxx.feature Wow -- @1.1 Araxas . Wow -- @1.2 Araxas . $ behave --name='-- @.* Araxas' -f progress3 features/xxx.feature ... # features/xxx.feature Wow -- @1.1 Araxas . Wow -- @1.2 Araxas . .. index:: single: Scenario; exclude from test run pair: Scenario; exclude from test run single: Feature; exclude from test run pair: Feature; exclude from test run Exclude Feature/Scenario at Runtime ------------------------------------------------------------------------------- :Since: behave 1.2.5a1 A test writer can now provide a runtime decision logic to exclude a feature, scenario or scenario outline from a test run within the following hooks: * ``before_feature()`` for a feature * ``before_scenario()`` for a scenario * step implementation (normally only: given step) by using the ``skip()`` method before a feature or scenario is run. .. code-block:: python # -- FILE: features/environment.py # EXAMPLE 1: Exclude scenario from run-set at runtime. import sys def should_exclude_scenario(scenario): # -- RUNTIME DECISION LOGIC: Will exclude # * Scenario: Alice # * Scenario: Alice in Wonderland # * Scenario: Bob and Alice2 return "Alice" in scenario.name def before_scenario(context, scenario): if should_exclude_scenario(scenario): scenario.skip() #< EXCLUDE FROM RUN-SET. # -- OR WITH REASON: # reason = "RUNTIME-EXCLUDED" # scenario.skip(reason) .. code-block:: python # -- FILE: features/steps/my_steps.py # EXAMPLE 2: Skip remaining steps in step implementation. from behave import given @given('the assumption "{assumption}" is met') def step_check_assumption(context, assumption): if not is_assumption_valid(assumption): # -- SKIP: Remaining steps in current scenario. context.scenario.skip("OOPS: Assumption not met") return # -- NORMAL CASE: ... .. index:: single: Stage pair: Stage; Test Stage Test Stages ------------------------------------------------------------------------------- :Since: behave 1.2.5a1 :Intention: Use different Step Implementations for Each Stage A test stage allows the user to provide different step and environment implementation for each stage. Examples for test stages are: * develop (example: development environment with simple database) * product (example: use the real product and its database) * systemint (system integration) * ... Each test stage may have a different test environment and needs to fulfill different testing constraints. EXAMPLE DIRECTORY LAYOUT (with ``stage=testlab`` and default stage):: features/ +-- steps/ # -- Step implementations for default stage. | +-- foo_steps.py +-- testlab_steps/ # -- Step implementations for stage=testlab. | +-- foo_steps.py +-- environment.py # -- Environment for default stage. +-- testlab_environment.py # -- Environment for stage=testlab. +-- *.feature To use the ``stage=testlab``, you run behave with:: behave --stage=testlab ... or define the environment variable ``BEHAVE_STAGE=testlab``. .. _userdata: .. index:: single: userdata pair: userdata; user-specific configuration data Userdata ------------------------------------------------------------------------------- :Since: behave 1.2.5a1 :Intention: User-specific Configuration Data The userdata functionality allows a user to provide its own configuration data: * as command-line option ``-D name=value`` or ``--define name=value`` * with the behave configuration file in section ``behave.userdata`` * load more configuration data in ``before_all()`` hook .. code-block:: ini # -- FILE: behave.ini [behave.userdata] browser = firefox server = asterix .. note:: Command-line definitions override userdata definitions in the configuration file. If the command-line contains no value part, like in ``-D NEEDS_CLEANUP``, its value is ``"true"``. The userdata settings can be accessed as dictionary in hooks and steps by using the ``context.config.userdata`` dictionary. .. code-block:: python # -- FILE: features/environment.py def before_all(context): browser = context.config.userdata.get("browser", "chrome") setup_browser(browser) .. code-block:: python # -- FILE: features/steps/userdata_example_steps.py @given('I setup the system with the user-specified server"') def step_setup_system_with_userdata_server(context): server_host = context.config.userdata.get("server", "beatrix") context.xxx_client = xxx_protocol.connect(server_host) .. code-block:: sh # -- ADAPT TEST-RUN: With user-specific data settings. # SHELL: behave -D server=obelix features/ behave --define server=obelix features/ Other examples for user-specific data are: * Passing a URL to an external resource that should be used in the tests * Turning off cleanup mechanisms implemented in environment hooks, for debugging purposes. Type Converters ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The userdata object provides basic support for "type conversion on demand", similar to the :mod:`configparser` module. The following type conversion methods are provided: * ``Userdata.getint(name, default=0)`` * ``Userdata.getfloat(name, default=0.0)`` * ``Userdata.getbool(name, default=False)`` * ``Userdata.getas(convert_func, name, default=None, ...)`` Type conversion may raise a ``ValueError`` exception if the conversion fails. The following example shows how the type converter functions for integers are used: .. code-block:: python # -- FILE: features/environment.py def before_all(context): userdata = context.config.userdata server_name = userdata.get("server", "beatrix") int_number = userdata.getint("port", 80) bool_answer = userdata.getbool("are_you_sure", True) float_number = userdata.getfloat("temperature_threshold", 50.0) ... .. hidden: * :py:meth:`behave.configuration.Userdata.getint()` * :py:meth:`behave.configuration.Userdata.getfloat()` * :py:meth:`behave.configuration.Userdata.getbool()` * :py:meth:`behave.configuration.Userdata.getas()` Advanced Cases ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The last section described the basic use cases of userdata. For more complicated cases, it is better to provide your own configuration setup in the ``before_all()`` hook. This section describes how to load a JSON configuration file and store its data in the ``userdata`` dictionary. .. code-block:: py # -- FILE: features/environment.py import json import os.path def before_all(context): """Load and update userdata from JSON configuration file.""" userdata = context.config.userdata configfile = userdata.get("configfile", "userconfig.json") if os.path.exists(configfile): assert configfile.endswith(".json") more_userdata = json.load(open(configfile)) context.config.update_userdata(more_userdata) # -- NOTE: Reapplies userdata_defines from command-line, too. Provide the file "userconfig.json" with: .. code-block:: json { "browser": "firefox", "server": "asterix", "count": 42, "cleanup": true } Other advanced use cases: * support configuration profiles via cmdline "... -D PROFILE=xxx ..." (uses profile-specific configuration file or profile-specific config section) * provide test stage specific configuration data .. index:: single: Active Tags Active Tags ------------------------------------------------------------------------------- :Since: behave 1.2.5a1 **Active tags** are used when it is necessary to decide at runtime which features or scenarios should run (and which should be skipped). The runtime decision is based on which: * platform the tests run (like: Windows, Linux, MACOSX, ...) * runtime environment resources are available (by querying the "testbed") * runtime environment resources should be used (via `userdata`_ or ...) Therefore, for *active tags* it is decided at runtime if a tag is enabled or disabled. The runtime decision logic excludes features/scenarios with disabled active tags before they are run. .. note:: The active tag mechanism is applied after the normal tag filtering that is configured on the command-line. The active tag mechanism uses the :class:`~behave.tag_matcher.ActiveTagMatcher` for its core functionality. .. index:: single: Active Tag Logic Active Tag Logic ~~~~~~~~~~~~~~~~~ * A (positive) active tag is enabled, if its value matches the current value of its category. * A negated active tag (starting with "not") is enabled, if its value does not match the current value of its category. * A sequence of active tags is enabled, if all its active tags are enabled (logical-and operation). .. index:: single: Active Tag Schema pair: @active.with_{category}={value}; active tag schema (dialect 1) pair: @not_active.with_{category}={value}; active tag schema (dialect 1) pair: @use.with_{category}={value}; active tag schema (dialect 2) pair: @not.with_{category}={value}; active tag schema (dialect 2) pair: @only.with_{category}={value}; active tag schema (dialect 2) Active Tag Schema ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The following two tag schemas are supported for active tags (by default). **Dialect 1:** * @active.with_{category}={value} * @not_active.with_{category}={value} **Dialect 2:** * @use.with_{category}={value} * @not.with_{category}={value} * @only.with_{category}={value} Example 1 ~~~~~~~~~~ Assuming you have the feature file where: * scenario "Alice" should only run when browser "Chrome" is used * scenario "Bob" should only run when browser "Safari" is used .. code-block:: gherkin # -- FILE: features/alice.feature Feature: @use.with_browser=chrome Scenario: Alice (Run only with Browser Chrome) Given I do something ... @use.with_browser=safari Scenario: Bob (Run only with Browser Safari) Given I do something else ... .. code-block:: python # -- FILE: features/environment.py # EXAMPLE: ACTIVE TAGS, exclude scenario from run-set at runtime. # NOTE: ActiveTagMatcher implements the runtime decision logic. from behave.tag_matcher import ActiveTagMatcher import os import sys active_tag_value_provider = { "browser": "chrome" } active_tag_matcher = ActiveTagMatcher(active_tag_value_provider) def before_all(context): # -- SETUP ACTIVE-TAG MATCHER VALUE(s): active_tag_value_provider["browser"] = os.environ.get("BROWSER", "chrome") def before_scenario(context, scenario): # -- NOTE: scenario.effective_tags := scenario.tags + feature.tags if active_tag_matcher.should_exclude_with(scenario.effective_tags): # -- NOTE: Exclude any with @use.with_browser= scenario.skip(reason="DISABLED ACTIVE-TAG") .. note:: By using this mechanism, the ``@use.with_browser=*`` tags become **active tags**. The runtime decision logic decides when these tags are enabled or disabled (and uses them to exclude their scenario/feature). Example 2 ~~~~~~~~~~ Assuming you have scenarios with the following runtime conditions: * Run scenario Alice only on Windows OS * Run scenario Bob only with browser Chrome .. code-block:: gherkin # -- FILE: features/alice.feature # TAG SCHEMA: @use.with_{category}={value}, ... Feature: @use.with_os=win32 Scenario: Alice (Run only on Windows) Given I do something ... @use.with_browser=chrome Scenario: Bob (Run only with Web-Browser Chrome) Given I do something else ... .. code-block:: python # -- FILE: features/environment.py from behave.tag_matcher import ActiveTagMatcher import sys # -- MATCHES ANY TAGS: @use.with_{category}={value} # NOTE: active_tag_value_provider provides category values for active tags. active_tag_value_provider = { "browser": os.environ.get("BEHAVE_BROWSER", "chrome"), "os": sys.platform, } active_tag_matcher = ActiveTagMatcher(active_tag_value_provider) # -- BETTER USE: from behave.tag_matcher import setup_active_tag_values def setup_active_tag_values(active_tag_values, data): for category in active_tag_values.keys(): if category in data: active_tag_values[category] = data[category] def before_all(context): # -- SETUP ACTIVE-TAG MATCHER (with userdata): # USE: behave -D browser=safari ... setup_active_tag_values(active_tag_value_provider, context.config.userdata) def before_feature(context, feature): if active_tag_matcher.should_exclude_with(feature.tags): feature.skip(reason="DISABLED ACTIVE-TAG") def before_scenario(context, scenario): if active_tag_matcher.should_exclude_with(scenario.effective_tags): scenario.skip("DISABLED ACTIVE-TAG") By using the `userdata`_ mechanism, you can now define on command-line which browser should be used when you run behave. .. code-block:: sh # -- SHELL: Run behave with browser=safari, ... by using userdata. # TEST VARIANT 1: Run tests with browser=safari behave -D browser=safari features/ # TEST VARIANT 2: Run tests with browser=chrome behave -D browser=chrome features/ .. note:: Unknown categories, missing in the ``active_tag_value_provider`` are ignored. User-defined Formatters ------------------------------------------------------------------------------- :Since: behave 1.2.5a1 Behave formatters are a typical candidate for an extension point. You often need another formatter that provides the desired output format for a test-run. Therefore, behave supports now formatters as extension point (or plugin). It is now possible to use own, user-defined formatters in two ways: * Use formatter class (as "scoped class name") as ``--format`` option value * Register own formatters by name in behave's configuration file .. note:: Scoped class name (schema): * ``my.module:MyClass`` (preferred) * ``my.module::MyClass`` (alternative; with double colon as separator) User-defined Formatter on Command-line ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Just use the formatter class (as "scoped class name") on the command-line as value for the ``-format`` option (short option: ``-f``): .. code-block:: sh behave -f my.own_module:SimpleFormatter ... behave -f behave.formatter.plain:PlainFormatter ... .. code-block:: python # -- FILE: my/own_module.py # (or installed as Python module: my.own_module) from behave.formatter.base import Formatter class SimpleFormatter(Formatter): description = "A very simple NULL formatter" Register User-defined Formatter by Name ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It is also possible to extend behave's built-in formatters by registering one or more user-defined formatters by name in the configuration file: .. code-block:: ini # -- FILE: behave.ini [behave.formatters] foo = behave_contrib.formatter.foo:FooFormatter bar = behave_contrib.formatter.bar:BarFormatter .. code-block:: python # -- FILE: behave_contrib/formatter/foo.py from behave.formatter.base import Formatter class FooFormatter(Formatter): description = "A FOO formatter" ... Now you can use the name for any registered, user-defined formatter: .. code-block:: sh # -- NOTE: Use FooFormatter that was registered by name "foo". behave -f foo ... behave-1.2.6/docs/new_and_noteworthy_v1.2.6.rst0000644000076600000240000003650113244555737021372 0ustar jensstaff00000000000000Noteworthy in Version 1.2.6 ============================================================================== Summary: * Tagged Examples: Examples in a ScenarioOutline can now have tags. * Feature model elements have now language attribute based on language tag in feature file (or the default language tag that was used by the parser). * Gherkin parser: Supports escaped-pipe in Gherkin table cell value * Configuration: Supports now to define default tags in configfile * Configuration: language data is now used as default-language that should be used by the Gherkin parser. Language tags in the Feature file override this setting. * Runner: Can continue after a failed step in a scenario * Runner: Hooks processing handles now exceptions. Hook errors (exception in hook processing) lead now to scenario failures (even if no step fails). * Testing support for asynchronuous frameworks or protocols (:mod:`asyncio` based) * Context-cleanups: Register cleanup functions that are executed at the end of the test-scope (scenario, feature or test-run) via :func:`~behave.runner.Context.add_cleanup()`. * :ref:`docid.fixtures`: Simplify setup/cleanup in scenario, feature or test-run Scenario Outline Improvements ------------------------------------------------------------------------------- .. _tagged examples: .. index:: pair: ScenarioOutline; tagged examples pair: Gherkin parser; tagged examples Tagged Examples ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :Since: behave 1.2.6.dev0 The Gherkin parser (and the model) supports now to use tags with the ``Examples`` section in a ``Scenario Outline``. This functionality can be used to provide multiple ``Examples`` sections, for example one section per testing stage (development, integration testing, system testing, ...) or one section per test team. The following feature file provides a simple example of this functionality: .. code-block:: gherkin # -- FILE: features/tagged_examples.feature Feature: Scenario Outline: Wow Given an employee "" @develop Examples: Araxas | name | birthyear | | Alice | 1985 | | Bob | 1975 | @integration Examples: | name | birthyear | | Charly | 1995 | .. note:: The generated scenarios from a ScenarioOutline inherit the tags from the ScenarioOutline and its Examples section:: # -- FOR scenario in scenario_outline.scenarios: scenario.tags = scenario_outline.tags + examples.tags To run only the first ``Examples`` section, you use: .. code-block:: shell behave --tags=@develop features/tagged_examples.feature .. code-block:: gherkin Scenario Outline: Wow -- @1.1 Araxas # features/tagged_examples.feature:7 Given an employee "Alice" Scenario Outline: Wow -- @1.2 Araxas # features/tagged_examples.feature:8 Given an employee "Bob" Tagged Examples with Active Tags and Userdata ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ An even more natural fit is to use ``tagged examples`` together with ``active tags`` and ``userdata``: .. code-block:: gherkin # -- FILE: features/tagged_examples2.feature # VARIANT 2: With active tags and userdata. Feature: Scenario Outline: Wow Given an employee "" @use.with_stage=develop Examples: Araxas | name | birthyear | | Alice | 1985 | | Bob | 1975 | @use.with_stage=integration Examples: | name | birthyear | | Charly | 1995 | Select the ``Examples`` section now by using: .. code-block:: shell # -- VARIANT 1: Use userdata behave -D stage=integration features/tagged_examples2.feature # -- VARIANT 2: Use stage mechanism behave --stage=integration features/tagged_examples2.feature .. code-block:: python # -- FILE: features/environment.py from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values import sys # -- ACTIVE TAG SUPPORT: @use.with_{category}={value}, ... active_tag_value_provider = { "stage": "develop", } active_tag_matcher = ActiveTagMatcher(active_tag_value_provider) # -- BEHAVE HOOKS: def before_all(context): userdata = context.config.userdata stage = context.config.stage or userdata.get("stage", "develop") userdata["stage"] = stage setup_active_tag_values(active_tag_value_provider, userdata) def before_scenario(context, scenario): if active_tag_matcher.should_exclude_with(scenario.effective_tags): sys.stdout.write("ACTIVE-TAG DISABLED: Scenario %s\n" % scenario.name) scenario.skip(active_tag_matcher.exclude_reason) Gherkin Parser Improvements ------------------------------------------------------------------------------- Escaped-Pipe Support in Tables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It is now possible to use the "|" (pipe) symbol in Gherkin tables by escaping it. The pipe symbol is normally used as column separator in tables. EXAMPLE: .. code-block:: Gherkin Scenario: Use escaped-pipe symbol Given I use table data with: | name | value | | alice | one\|two\|three\|four | Then table data for "alice" is "one|two|three|four" .. seealso:: * `issue.features/issue0302.feature`_ for details .. _`issue.features/issue0302.feature`: https://github.com/behave/behave/blob/master/issue.features/issue0302.feature Configuration Improvements ------------------------------------------------------------------------------- Language Option ~~~~~~~~~~~~~~~ The interpretation of the ``language-tag`` comment in feature files (Gherkin) and the configuration ``lang`` option on command-line and in the configuration file changed slightly. If a ``language-tag`` is used in a feature file, it is now prefered over the command-line/configuration file settings. This is especially useful when your feature files use multiple spoken languages (in different files). EXAMPLE: .. code-block:: Gherkin # -- FILE: features/french_1.feature # language: fr Fonctionnalité: Alice ... .. code-block:: ini # -- FILE: behave.ini [behave] lang = de # Default (spoken) language to use: German ... .. note:: The feature object contains now a ``language`` attribute that contains the information which language was used during Gherkin parsing. Default Tags ~~~~~~~~~~~~ It is now possible to define ``default tags`` in the configuration file. ``Default tags`` are used when you do not specify tags on the command-line. EXAMPLE: .. code-block:: ini # -- FILE: behave.ini # Exclude/skip any feature/scenario with @xfail or @not_implemented tags [behave] default_tags = -@xfail -@not_implemented ... Runner Improvements ------------------------------------------------------------------------------- Hook Errors cause Failures ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The behaviour of hook errors, meaning uncaught exceptions while processing hooks, is changed in this release. The new behaviour causes the entity (test-run, feature, scenario), for which the hook is executed, to fail. In addition, a hook error in a ``before_all()``, ``before_feature()``, ``before_scenario()``, and ``before_tag()`` hook causes its corresponding entity to be skipped. .. seealso:: * `features/runner.hook_errors.feature`_ for the detailled specification .. _`features/runner.hook_errors.feature`: https://github.com/behave/behave/blob/master/features/runner.hook_errors.feature Option: Continue after Failed Step in a Scenario ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This behaviour is sometimes desired, when you want to see what happens in the remaining steps of a scenario. EXAMPLE: .. code-block:: python # -- FILE: features/environment.py from behave.model import Scenario def before_all(context): userdata = context.config.userdata continue_after_failed = userdata.getbool("runner.continue_after_failed_step", False) Scenario.continue_after_failed_step = continue_after_failed .. code-block:: shell # -- ENABLE OPTION: Use userdata on command-line behave -D runner.continue_after_failed_step=true features/ .. note:: A failing step normally causes correlated failures in most of the following steps. Therefore, this behaviour is normally not desired. .. seealso:: * `features/runner.continue_after_failed_step.feature`_ for the detailled specification .. _`features/runner.continue_after_failed_step.feature`: https://github.com/behave/behave/blob/master/features/runner.continue_after_failed_step.feature Testing asyncio Frameworks ------------------------------------------------------------------------------- :Since: behave 1.2.6.dev0 The following support was added to simplify testing asynchronuous framework and protocols that are based on :mod:`asyncio` module (since Python 3.4). There are basically two use cases: * async-steps (with event_loop.run_until_complete() semantics) * async-dispatch step(s) with async-collect step(s) later on Async-steps ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It is now possible to use ``async-steps`` in ``behave``. An async-step is basically a coroutine as step-implementation for behave. The async-step is wrapped into an ``event_loop.run_until_complete()`` call by using the ``@async_run_until_complete`` step-decorator. This avoids another layer of indirection that would otherwise be necessary, to use the coroutine. A simple example for the implementation of the async-steps is shown for: * Python 3.5 with new ``async``/``await`` keywords * Python 3.4 with ``@asyncio.coroutine`` decorator and ``yield from`` keyword .. literalinclude:: ../examples/async_step/features/steps/async_steps35.py :language: python :prepend: # -- FILE: features/steps/async_steps35.py .. literalinclude:: ../examples/async_step/features/steps/async_steps34.py :language: python :prepend: # -- FILE: features/steps/async_steps34.py When you use the async-step from above in a feature file and run it with behave: .. literalinclude:: ../examples/async_step/testrun_example.async_run.txt :language: gherkin :prepend: # -- TEST-RUN OUTPUT: $ behave -f plain features/async_run.feature .. hidden: .. literalinclude:: ../examples/async_step/features/async_run.feature :language: gherkin :prepend: # -- FILE: features/async_run.feature .. note:: The async-step is wrapped with an ``event_loop.run_until_complete()`` call. As the timings show, it actually needs approximatly 0.3 seconds to run. Async-dispatch and async-collect ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The other use case with testing async frameworks is that * you dispatch one or more async-calls * you collect (and verify) the results of the async-calls later-on A simple example of this approach is shown in the following feature file: .. literalinclude:: ../examples/async_step/features/async_dispatch.feature :language: gherkin :prepend: # -- FILE: features/async_dispatch.feature When you run this feature file: .. literalinclude:: ../examples/async_step/testrun_example.async_dispatch.txt :language: gherkin :prepend: # -- TEST-RUN OUTPUT: $ behave -f plain features/async_dispatch.feature .. note:: The final async-collect step needs approx. 0.2 seconds until the two dispatched async-tasks have finished. In contrast, the async-dispatch steps basically need no time at all. An :class:`AsyncContext` object is used on the context, to hold the event loop information and the async-tasks that are of interest. The implementation of the steps from above: .. literalinclude:: ../examples/async_step/features/steps/async_dispatch_steps.py :language: gherkin :prepend: # -- FILE: features/steps/async_dispatch_steps.py # REQUIRES: Python 3.4 or newer Context-based Cleanups ------------------------------------------------------------------------------- It is now possible to register cleanup functions with the context object. This functionality is normally used in: * hooks (:func:`before_all()`, :func:`before_feature()`, :func:`before_scenario()`, ...) * step implementations * ... .. code-block:: python # -- SIGNATURE: Context.add_cleanup(cleanup_func, *args, **kwargs) # CLEANUP CALL EXAMPLES: context.add_cleanup(cleanup0) # CALLS LATER: cleanup0() context.add_cleanup(cleanup1, 1, 2) # CALLS LATER: cleanup1(1, 2) context.add_cleanup(cleanup2, name="Alice") # CALLS LATER: cleanup2(name="Alice") context.add_cleanup(cleanup3, 1, 2, name="Bob") # CALLS LATER: cleanup3(1, 2, name="Bob") The registered cleanup will be performed when the context layer is removed. This depends on the the context layer when the cleanup function was registered (test-run, feature, scenario). Example: .. code-block:: python # -- FILE: features/environment.py def before_all(context): context.add_cleanup(cleanup_me) # -- ON CLEANUP: Calls cleanup_me() # Called after test-run. def before_tag(context, tag): if tag == "foo": context.foo = setup_foo() context.add_cleanup(cleanup_foo, context.foo) # -- ON CLEANUP: Calls cleanup_foo(context.foo) # CASE scenario tag: cleanup_foo() will be called after this scenario. # CASE feature tag: cleanup_foo() will be called after this feature. .. seealso:: For more details, see `features/runner.context_cleanup.feature`_ . .. _`features/runner.context_cleanup.feature`: https://github.com/behave/behave/blob/master/features/runner.context_cleanup.feature Fixtures ------------------------------------------------------------------------------- Fixtures simplify setup/cleanup tasks that are often needed for testing. Providing a Fixture ~~~~~~~~~~~~~~~~~~~ .. code-block:: python # -- FILE: behave4my_project/fixtures.py (or in: features/environment.py) from behave import fixture from somewhere.browser.firefox import FirefoxBrowser # -- FIXTURE-VARIANT 1: Use generator-function @fixture def browser_firefox(context, timeout=30, **kwargs): # -- SETUP-FIXTURE PART: context.browser = FirefoxBrowser(timeout, **kwargs) yield context.browser # -- CLEANUP-FIXTURE PART: context.browser.shutdown() Using a Fixture ~~~~~~~~~~~~~~~ .. code-block:: Gherkin # -- FILE: features/use_fixture1.feature Feature: Use Fixture on Scenario Level @fixture.browser.firefox Scenario: Use Web Browser Firefox Given I load web page "https://somewhere.web" ... # -- AFTER-SCENARIO: Cleanup fixture.browser.firefox .. code-block:: python # -- FILE: features/environment.py from behave import use_fixture from behave4my_project.fixtures import browser_firefox def before_tag(context, tag): if tag == "fixture.browser.firefox": use_fixture(browser_firefox, context, timeout=10) .. seealso:: * :ref:`docid.fixtures` description for details * `features/fixture.feature`_ .. _`features/fixture.feature`: https://github.com/behave/behave/blob/master/features/fixture.feature behave-1.2.6/docs/parse_builtin_types.rst0000644000076600000240000000506513244555737020630 0ustar jensstaff00000000000000.. _id.appendix.parse_builtin_types: Predefined Data Types in ``parse`` ============================================================================== :pypi:`behave` uses the :pypi:`parse` module (inverse of Python `string.format`_) under the hoods to parse parameters in step definitions. This leads to rather simple and readable parse expressions for step parameters. .. code-block:: python # -- FILE: features/steps/type_transform_example_steps.py from behave import given @given('I have {number:d} friends') #< Convert 'number' into int type. def step_given_i_have_number_friends(context, number): assert number > 0 ... Therefore, the following ``parse types`` are already supported in step definitions without registration of any *user-defined type*: ===== =========================================== ============ Type Characters Matched Output Type ===== =========================================== ============ w Letters and underscore str W Non-letter and underscore str s Whitespace str S Non-whitespace str d Digits (effectively integer numbers) int D Non-digit str n Numbers with thousands separators (, or .) int % Percentage (converted to value/100.0) float f Fixed-point numbers float e Floating-point numbers with exponent float e.g. 1.1e-10, NAN (all case insensitive) g General number format (either d, f or e) float b Binary numbers int o Octal numbers int x Hexadecimal numbers (lower and upper case) int ti ISO 8601 format date/time datetime e.g. 1972-01-20T10:21:36Z te RFC2822 e-mail format date/time datetime e.g. Mon, 20 Jan 1972 10:21:36 +1000 tg Global (day/month) format date/time datetime e.g. 20/1/1972 10:21:36 AM +1:00 ta US (month/day) format date/time datetime e.g. 1/20/1972 10:21:36 PM +10:30 tc ctime() format date/time datetime e.g. Sun Sep 16 01:03:52 1973 th HTTP log format date/time datetime e.g. 21/Nov/2011:00:07:11 +0000 tt Time time e.g. 10:21:36 PM -5:30 ===== =========================================== ============ .. _string.format: https://docs.python.org/2/library/string.html#format-string-syntax behave-1.2.6/docs/philosophy.rst0000644000076600000240000002301513244555737016735 0ustar jensstaff00000000000000=========================== Behavior Driven Development =========================== Behavior-driven development (or BDD) is an agile software development technique that encourages collaboration between developers, QA and non-technical or business participants in a software project. It was originally named in 2003 by `Dan North`__ as a response to test-driven development (TDD), including acceptance test or customer test driven development practices as found in extreme programming. It has `evolved over the last few years`__. __ https://dannorth.net/introducing-bdd __ https://forums.pragprog.com/forums/95/topics/3035 On the "Agile specifications, BDD and Testing eXchange" in November 2009 in London, Dan North `gave the following definition of BDD`__: BDD is a second-generation, outside–in, pull-based, multiple-stakeholder, multiple-scale, high-automation, agile methodology. It describes a cycle of interactions with well-defined outputs, resulting in the delivery of working, tested software that matters. __ https://skillsmatter.com/skillscasts/923-how-to-sell-bdd-to-the-business BDD focuses on obtaining a clear understanding of desired software behavior through discussion with stakeholders. It extends TDD by writing test cases in a natural language that non-programmers can read. Behavior-driven developers use their native language in combination with the ubiquitous language of domain-driven design to describe the purpose and benefit of their code. This allows the developers to focus on why the code should be created, rather than the technical details, and minimizes translation between the technical language in which the code is written and the domain language spoken by the business, users, stakeholders, project management, etc. BDD practices ------------- The practices of BDD include: - Establishing the goals of different stakeholders required for a vision to be implemented - Drawing out features which will achieve those goals using feature injection - Involving stakeholders in the implementation process through outside–in software development - Using examples to describe the behavior of the application, or of units of code - Automating those examples to provide quick feedback and regression testing - Using 'should' when describing the behavior of software to help clarify responsibility and allow the software's functionality to be questioned - Using 'ensure' when describing responsibilities of software to differentiate outcomes in the scope of the code in question from side-effects of other elements of code. - Using mocks to stand-in for collaborating modules of code which have not yet been written Outside–in ---------- BDD is driven by `business value`__; that is, the benefit to the business which accrues once the application is in production. The only way in which this benefit can be realized is through the user interface(s) to the application, usually (but not always) a GUI. __ https://lizkeogh.com/2007/06/13/bdd-tdd-done-well/ In the same way, each piece of code, starting with the UI, can be considered a stakeholder of the other modules of code which it uses. Each element of code provides some aspect of behavior which, in collaboration with the other elements, provides the application behavior. The first piece of production code that BDD developers implement is the UI. Developers can then benefit from quick feedback as to whether the UI looks and behaves appropriately. Through code, and using principles of good design and refactoring, developers discover collaborators of the UI, and of every unit of code thereafter. This helps them adhere to the principle of YAGNI, since each piece of production code is required either by the business, or by another piece of code already written. The Gherkin language -------------------- The requirements of a retail application might be, "Refunded or exchanged items should be returned to stock." In BDD, a developer or QA engineer might clarify the requirements by breaking this down into specific examples. The language of the examples below is called Gherkin and is used by *behave* as well as many other tools. .. code-block:: gherkin Scenario: Refunded items should be returned to stock Given a customer previously bought a black sweater from me and I currently have three black sweaters left in stock. When he returns the sweater for a refund then I should have four black sweaters in stock., Scenario: Replaced items should be returned to stock Given that a customer buys a blue garment and I have two blue garments in stock and three black garments in stock. When he returns the garment for a replacement in black, then I should have three blue garments in stock and two black garments in stock. Each scenario is an exemplar, designed to illustrate a specific aspect of behavior of the application. When discussing the scenarios, participants question whether the outcomes described always result from those events occurring in the given context. This can `help to uncover further scenarios which clarify the requirements`__. For instance, a domain expert noticing that refunded items are not always returned to stock might reword the requirements as "Refunded or replaced items should be returned to stock, unless faulty.". __ https://dannorth.net/whats-in-a-story This in turn helps participants to pin down the scope of requirements, which leads to better estimates of how long those requirements will take to implement. The words Given, When and Then are often used to help drive out the scenarios, but are not mandated. These scenarios can also be automated, if an appropriate tool exists to allow automation at the UI level. If no such tool exists then it may be possible to automate at the next level in, i.e.: if an MVC design pattern has been used, the level of the Controller. Programmer-domain examples and behavior --------------------------------------- The same principles of examples, using contexts, events and outcomes are used to drive development at the level of abstraction of the programmer, as opposed to the business level. For instance, the following examples describe an aspect of behavior of a list: .. code-block:: gherkin Scenario: New lists are empty Given a new list then the list should be empty. Scenario: Lists with things in them are not empty. Given a new list when we add an object then the list should not be empty. Both these examples are required to describe the boolean nature of a list in Python and to derive the benefit of the nature. These examples are usually automated using TDD frameworks. In BDD these examples are often encapsulated in a single method, with the name of the method being a complete description of the behavior. Both examples are required for the code to be valuable, and encapsulating them in this way makes it easy to question, remove or change the behavior. For instance as unit tests, the above examples might become: .. code-block:: python class TestList(object): def test_empty_list_is_false(self): list = [] assertEqual(bool(list), False) def test_populated_list_is_true(self): list = [] list.append('item') assertEqual(bool(list), True) .. Other practitioners[who?], particularly in the Ruby community, prefer to split these into two separate examples, based on separate contexts for when the list is empty or has items in. This technique is based on Dave Astels' practice, "One assertion per test"[12]. .. 12. http://techblog.daveastels.com/tag/bdd/ Sometimes the difference between the context, events and outcomes is made more explicit. For instance: .. code-block:: python class TestWindow(object): def test_window_close(self): # Given window = gui.Window("My Window") frame = gui.Frame(window) # When window.close() # Then assert_(not frame.isVisible()) However the example is phrased, the effect describes the behavior of the code in question. For instance, from the examples above one can derive: - lists should know when they are empty - window.close() should cause contents to stop being visible The description is intended to be useful if the test fails, and to provide documentation of the code's behavior. Once the examples have been written they are then run and the code implemented to make them work in the same way as TDD. The examples then become part of the suite of regression tests. Using mocks ----------- BDD proponents claim that the use of "should" and "ensureThat" in BDD examples encourages developers to question whether the responsibilities they're assigning to their classes are appropriate, or whether they can be delegated or moved to another class entirely. Practitioners use an object which is simpler than the collaborating code, and provides the same interface but more predictable behavior. This is injected into the code which needs it, and examples of that code's behavior are written using this object instead of the production version. These objects can either be created by hand, or created using a mocking framework such as :pypi:`mock`. Questioning responsibilities in this way, and using mocks to fulfill the required roles of collaborating classes, encourages the use of Role-based Interfaces. It also helps to keep the classes small and loosely coupled. Acknowledgement --------------- This text is partially taken from the wikipedia text on `Behavior Driven Development`_ with modifications where appropriate to be more specific to *behave* and Python. .. _`Behavior Driven Development`: https://en.wikipedia.org/wiki/Behavior_Driven_Development behave-1.2.6/docs/practical_tips.rst0000644000076600000240000001164713244555737017550 0ustar jensstaff00000000000000.. _id.practicaltips: ========================= Practical Tips on Testing ========================= This chapter contains a collection of tips on test strategies and tools, such as test automation libraries, that help you make BDD a successful experience. Seriously, Don't Test the User Interface ======================================== .. warning:: While you can use :pypi:`behave` to drive the "user interface" (UI) or front-end, interacting with the model layer or the business logic, e.g. by using a REST API, is often the better choice. And keep in mind, BDD advises your to test **WHAT** your application should do and not **HOW** it is done. If you want to test/exercise also the "user interface", it may be a good idea to reuse the feature files, that test the model layer, by just replacing the test automation layer (meaning mostly the step implementations). This approach ensures that your feature files are technology-agnostic, meaning they are independent how you interact with "system under test" (SUT) or "application under test" (AUT). For example, if you want to use the feature files in the same directory for testing the model layer and the UI layer, this can be done by using the ``--stage`` option, like with: .. code-block:: bash $ behave --stage=model features/ $ behave --stage=ui features/ # NOTE: Normally used on a subset of features. See the :ref:`id.appendix.more_info` chapter for additional hints. Automation Libraries ==================== With *behave* you can test anything on your application stack: front-end behavior, RESTful APIs, you can even drive your unit tests using Gherkin language. Any library that helps you with that you usually integrate by adding start-up code in ``before_all()`` and tear-down code in ``after_all()``. The following examples show you how to interact with your Python application by using the web interface (see `Seriously, Don't Test the User Interface`_ above to learn about entry points for test automation that may be better suited for your use case). Selenium (Example) ------------------ To start a web browser for interaction with the front-end using :pypi:`selenium` your ``environment.py`` may look like this: .. code-block:: python # -- FILE: features/environment.py # CONTAINS: Browser fixture setup and teardown from behave import fixture, use_fixture from selenium.webdriver import Firefox @fixture def browser_firefox(context): # -- BEHAVE-FIXTURE: Similar to @contextlib.contextmanager context.browser = Firefox() yield context.browser # -- CLEANUP-FIXTURE PART: context.browser.quit() def before_all(context): use_fixture(browser_firefox, context) # -- NOTE: CLEANUP-FIXTURE is called after after_all() hook. In your step implementations you can use the ``context.browser`` object to access Selenium features. See the `Selenium docs`_ (``remote.webdriver``) for details. Example using :pypi:`behave-django`: .. code-block:: python # -- FILE: features/steps/browser_steps.py from behave import given, when, then @when(u'I visit "{url}"') def step_impl(context, url): context.browser.get(context.get_url(url)) .. _Selenium docs: https://seleniumhq.github.io/selenium/docs/api/py/api.html Splinter (Example) ------------------ To start a web browser for interaction with the front-end using :pypi:`splinter` your ``environment.py`` may look like this: .. code-block:: python # -- FILE: features/environment.py # CONTAINS: Browser fixture setup and teardown from behave import fixture, use_fixture from splinter.browser import Browser @fixture def splinter_browser(context): context.browser = Browser() yield context.browser context.browser.quit() def before_all(context): use_fixture(splinter_browser, context) In your step implementations you can use the ``context.browser`` object to access Splinter features. See the `Splinter docs`_ for details. Example using *behave-django*: .. code-block:: python # -- FILE: features/steps/browser_steps.py from behave import given, when, then @when(u'I visit "{url}"') def step_impl(context, url): context.browser.visit(context.get_url(url)) .. _Splinter docs: http://splinter.readthedocs.io/en/latest/ Visual Testing -------------- Visually checking your front-end on regression is integrated into *behave* in a straight-forward manner, too. Basically, what you do is drive your application using the front-end automation library of your choice (such as Selenium, Splinter, etc.) to the test location, take a screenshot and compare it with an earlier, approved screenshot (your "baseline"). A list of visual testing tools and services is available from Dave Haeffner's `How to Do Visual Testing`_ blog post. .. _How to Do Visual Testing: http://testautomation.applitools.com/post/105435804567/how-to-do-visual-testing-with-selenium behave-1.2.6/docs/regular_expressions.rst0000644000076600000240000000743713244555737020654 0ustar jensstaff00000000000000.. _id.appendix.regular_expressions: ============================================================================== Regular Expressions ============================================================================== .. index:: regular expressions, regexp The following tables provide a overview of the `regular expressions`_ syntax. See also `Python regular expressions`_ description in the Python `re module`_. ===================== ========================================================= Special Characters Description ===================== ========================================================= ``.`` Matches any character (dot). ``^`` "^...", matches start-of-string (caret). ``$`` "...$", matches end-of-string (dollar sign). ``|`` "A|B", matches "A" or "B". ``\`` Escape character. ``\.`` EXAMPLE: Matches character '.' (dot). ``\\`` EXAMPLE: Matches character '``\``' (backslash). ===================== ========================================================= To select or match characters from a special set of characters, a character set must be defined. ===================== ========================================================= Character sets Description ===================== ========================================================= ``[...]`` Define a character set, like ``[A-Za-z]``. ``\d`` Matches digit character: [0-9] ``\D`` Matches non-digit character. ``\s`` Matches whitespace character: ``[ \t\n\r\f\v]`` ``\S`` Matches non-whitespace character ``\w`` Matches alphanumeric character: ``[a-zA-Z0-9_]`` ``\W`` Matches non-alphanumeric character. ===================== ========================================================= A text part must be group to extract it as part (parameter). ===================== ========================================================= Grouping Description ===================== ========================================================= ``(...)`` Group a regular expression pattern (anonymous group). ``\number`` Matches text of earlier group by index, like: "``\1``". ``(?P...)`` Matches pattern and stores it in parameter "name". ``(?P=name)`` Match whatever text was matched by earlier group "name". ``(?:...)`` Matches pattern, but does non capture any text. ``(?#...)`` Comment (is ignored), describes pattern details. ===================== ========================================================= If a *group*, *character* or *character set* should be repeated several times, it is necessary to specify the cardinality of the regular expression pattern. ===================== ============================================================== Cardinality Description ===================== ============================================================== ``?`` Pattern with cardinality 0..1: optional part (question mark). ``*`` Pattern with cardinality zero or more, 0.. (asterisk). ``+`` Pattern with cardinality one or more, 1.. (plus sign). ``{m}`` Matches ``m`` repetitions of a pattern. ``{m,n}`` Matches from ``m`` to ``n`` repetitions of a pattern. ``[A-Za-z]+`` EXAMPLE: Matches one or more alphabetical characters. ===================== ============================================================== .. _`regular expressions`: https://en.wikipedia.org/wiki/Regular_expression .. _Python regular expressions: https://docs.python.org/2/library/re.html#module-re .. _re module: https://docs.python.org/2/library/re.html#module-re behave-1.2.6/docs/related.rst0000644000076600000240000000060013244555737016152 0ustar jensstaff00000000000000.. _id.appendix.related: =============================== Software that Enhances *behave* =============================== * Mock * nose.tools and nose.twistedtools * mechanize for pretending to be a browser * selenium webdriver for actually driving a browser * wsgi_intercept for providing more easily testable WSGI servers * BeautifulSoup, lxml and html5lib for parsing HTML * ... behave-1.2.6/docs/test_domains.rst0000644000076600000240000000577213244555737017242 0ustar jensstaff00000000000000.. _id.appendix.test_domain: Testing Domains ============================================================================== Behave and other BDD frameworks allow you to provide **step libraries** to reuse step definitions in similar projects that address the same problem domain. .. _behave: https://github.com/behave/behave .. _Selenium: http://docs.seleniumhq.org/ Step Libraries ------------------------------------------------------------------------------ Support of the following testing domains is currently known: =============== ================= =========================================================== Testing Domain Name Description =============== ================= =========================================================== Command-line `behave4cmd`_ Test command-line tools, like behave, etc. (coming soon). Web Apps `behave-django`_ Test Django Web apps with behave (solution 1). Web Apps `django-behave`_ Test Django Web apps with behave (solution 2). Web, SMS, ... `behaving`_ Test Web Apps, Email, SMS, Personas (step library). =============== ================= =========================================================== .. _behave4cmd: https://github.com/behave/behave4cmd .. _behave-django: https://github.com/behave/behave-django .. _behaving: https://github.com/ggozad/behaving .. _django-behave: https://github.com/django-behave/django-behave Step Usage Examples ------------------------------------------------------------------------------ This examples show how you can use `behave`_ for testing a specific problem domain. This examples are normally not a full-blown step library (that can be reused), but give you an example (or prototype), how the problem can be solved. =============== ==================== =========================================================== Testing Domain Name Description =============== ==================== =========================================================== GUI `Squish test`_ Use `Squish and Behave`_ for GUI testing (cross-platform). Robot Control `behave4poppy`_ Use behave to control a robot via `pypot`_. Web `pyramid_behave`_ Use `behave to test pyramid`_. Web `pycabara-tutorial`_ Use pycabara (with `behave`_ and `Selenium`_). =============== ==================== =========================================================== .. seealso:: * google-search: `behave python example `_ .. _behave4poppy: https://github.com/chbrun/behave4poppy .. _`Squish test`: https://www.froglogic.com/squish/ .. _`Squish and Behave`: https://kb.froglogic.com/display/KB/BDD+with+Squish+and+Behave .. _pycabara-tutorial: https://github.com/excellaco/pycabara-tutorial .. _pypot: https://github.com/poppy-project/pypot .. _pyramid_behave: https://github.com/wwitzel3/pyramid_behave .. _`behave to test pyramid`: https://blog.safaribooksonline.com/2014/01/10/using-behave-with-pyramid/ behave-1.2.6/docs/tutorial.rst0000644000076600000240000005505713244555737016415 0ustar jensstaff00000000000000.. _tutorial: ======== Tutorial ======== First, :doc:`install behave `. Now make a directory called "features". In that directory create a file called "tutorial.feature" containing: .. code-block:: gherkin Feature: showing off behave Scenario: run a simple test Given we have behave installed When we implement a test Then behave will test it for us! Make a new directory called "features/steps". In that directory create a file called "tutorial.py" containing: .. code-block:: python from behave import * @given('we have behave installed') def step_impl(context): pass @when('we implement a test') def step_impl(context): assert True is not False @then('behave will test it for us!') def step_impl(context): assert context.failed is False Run behave:: % behave Feature: showing off behave # features/tutorial.feature:1 Scenario: run a simple test # features/tutorial.feature:3 Given we have behave installed # features/steps/tutorial.py:3 When we implement a test # features/steps/tutorial.py:7 Then behave will test it for us! # features/steps/tutorial.py:11 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined Now, continue reading to learn how to make the most of *behave*. Features ======== *behave* operates on directories containing: 1. `feature files`_ written by your Business Analyst / Sponsor / whoever with your behaviour scenarios in it, and 2. a "steps" directory with `Python step implementations`_ for the scenarios. You may optionally include some `environmental controls`_ (code to run before and after steps, scenarios, features or the whole shooting match). The minimum requirement for a features directory is:: features/ features/everything.feature features/steps/ features/steps/steps.py A more complex directory might look like:: features/ features/signup.feature features/login.feature features/account_details.feature features/environment.py features/steps/ features/steps/website.py features/steps/utils.py If you're having trouble setting things up and want to see what *behave* is doing in attempting to find your features use the "-v" (verbose) command-line switch. Feature Files ============= A feature file has a :ref:`natural language format ` describing a feature or part of a feature with representative examples of expected outcomes. They're plain-text (encoded in UTF-8) and look something like: .. code-block:: gherkin Feature: Fight or flight In order to increase the ninja survival rate, As a ninja commander I want my ninjas to decide whether to take on an opponent based on their skill levels Scenario: Weaker opponent Given the ninja has a third level black-belt When attacked by a samurai Then the ninja should engage the opponent Scenario: Stronger opponent Given the ninja has a third level black-belt When attacked by Chuck Norris Then the ninja should run for his life The "Given", "When" and "Then" parts of this prose form the actual steps that will be taken by *behave* in testing your system. These map to `Python step implementations`_. As a general guide: **Given** we *put the system in a known state* before the user (or external system) starts interacting with the system (in the When steps). Avoid talking about user interaction in givens. **When** we *take key actions* the user (or external system) performs. This is the interaction with your system which should (or perhaps should not) cause some state to change. **Then** we *observe outcomes*. You may also include "And" or "But" as a step - these are renamed by *behave* to take the name of their preceding step, so: .. code-block:: gherkin Scenario: Stronger opponent Given the ninja has a third level black-belt When attacked by Chuck Norris Then the ninja should run for his life And fall off a cliff In this case *behave* will look for a step definition for ``"Then fall off a cliff"``. Scenario Outlines ----------------- Sometimes a scenario should be run with a number of variables giving a set of known states, actions to take and expected outcomes, all using the same basic actions. You may use a Scenario Outline to achieve this: .. code-block:: gherkin Scenario Outline: Blenders Given I put in a blender, when I switch the blender on then it should transform into Examples: Amphibians | thing | other thing | | Red Tree Frog | mush | Examples: Consumer Electronics | thing | other thing | | iPhone | toxic waste | | Galaxy Nexus | toxic waste | *behave* will run the scenario once for each (non-heading) line appearing in the example data tables. Step Data --------- Sometimes it's useful to associate a table of data with your step. Any text block following a step wrapped in ``"""`` lines will be associated with the step. For example: .. code-block:: gherkin Scenario: some scenario Given a sample text loaded into the frobulator """ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. """ When we activate the frobulator Then we will find it similar to English The text is available to the Python step code as the ".text" attribute in the :class:`~behave.runner.Context` variable passed into each step function. You may also associate a table of data with a step by simply entering it, indented, following the step. This can be useful for loading specific required data into a model. .. code-block:: gherkin Scenario: some scenario Given a set of specific users | name | department | | Barry | Beer Cans | | Pudey | Silly Walks | | Two-Lumps | Silly Walks | When we count the number of people in each department Then we will find two people in "Silly Walks" But we will find one person in "Beer Cans" The table is available to the Python step code as the ".table" attribute in the :class:`~behave.runner.Context` variable passed into each step function. The table for the example above could be accessed like so: .. code-block:: python @given('a set of specific users') def step_impl(context): for row in context.table: model.add_user(name=row['name'], department=row['department']) There's a variety of ways to access the table data - see the :class:`~behave.model.Table` API documentation for the full details. Python Step Implementations =========================== Steps used in the scenarios are implemented in Python files in the "steps" directory. You can call these whatever you like as long as they use the python ``*.py`` file extension. You don't need to tell *behave* which ones to use - it'll use all of them. The full detail of the Python side of *behave* is in the :doc:`API documentation `. Steps are identified using decorators which match the predicate from the feature file: **given**, **when**, **then** and **step** (variants with Title case are also available if that's your preference.) The decorator accepts a string containing the rest of the phrase used in the scenario step it belongs to. Given a Scenario: .. code-block:: gherkin Scenario: Search for an account Given I search for a valid account Then I will see the account details Step code implementing the two steps here might look like (using selenium webdriver and some other helpers): .. code-block:: python @given('I search for a valid account') def step_impl(context): context.browser.get('http://localhost:8000/index') form = get_element(context.browser, tag='form') get_element(form, name="msisdn").send_keys('61415551234') form.submit() @then('I will see the account details') def step_impl(context): elements = find_elements(context.browser, id='no-account') eq_(elements, [], 'account not found') h = get_element(context.browser, id='account-head') ok_(h.text.startswith("Account 61415551234"), 'Heading %r has wrong text' % h.text) The ``step`` decorator matches the step to *any* step type, "given", "when" or "then". The "and" and "but" step types are renamed internally to take the preceding step's keyword (so an "and" following a "given" will become a "given" internally and use a **given** decorated step). .. note:: Step function names do not need to have a unique symbol name, because the text matching selects the step function from the step registry before it is called as anonymous function. Hence, when *behave* prints out the missing step implementations in a test run, it uses "step_impl" for all functions by default. If you find you'd like your step implementation to invoke another step you may do so with the :class:`~behave.runner.Context` method :func:`~behave.runner.Context.execute_steps`. This function allows you to, for example: .. code-block:: python @when('I do the same thing as before') def step_impl(context): context.execute_steps(''' when I press the big red button and I duck ''') This will cause the "when I do the same thing as before" step to execute the other two steps as though they had also appeared in the scenario file. Step Parameters --------------- You may find that your feature steps sometimes include very common phrases with only some variation. For example: .. code-block:: gherkin Scenario: look up a book Given I search for a valid book Then the result page will include "success" Scenario: look up an invalid book Given I search for a invalid book Then the result page will include "failure" You may define a single Python step that handles both of those Then clauses (with a Given step that puts some text into ``context.response``): .. code-block:: python @then('the result page will include "{text}"') def step_impl(context, text): if text not in context.response: fail('%r not in %r' % (text, context.response)) There are several parsers available in *behave* (by default): **parse** (the default, based on: :pypi:`parse`) Provides a simple parser that replaces regular expressions for step parameters with a readable syntax like ``{param:Type}``. The syntax is inspired by the Python builtin ``string.format()`` function. Step parameters must use the named fields syntax of :pypi:`parse` in step definitions. The named fields are extracted, optionally type converted and then used as step function arguments. Supports type conversions by using type converters (see :func:`~behave.register_type()`). **cfparse** (extends: :pypi:`parse`, requires: :pypi:`parse_type`) Provides an extended parser with "Cardinality Field" (CF) support. Automatically creates missing type converters for related cardinality as long as a type converter for cardinality=1 is provided. Supports parse expressions like: * ``{values:Type+}`` (cardinality=1..N, many) * ``{values:Type*}`` (cardinality=0..N, many0) * ``{value:Type?}`` (cardinality=0..1, optional). Supports type conversions (as above). **re** This uses full regular expressions to parse the clause text. You will need to use named groups "(?P...)" to define the variables pulled from the text and passed to your ``step()`` function. Type conversion is **not supported**. A step function writer may implement type conversion inside the step function (implementation). To specify which parser to use invoke :func:`~behave.use_step_matcher` with the name of the matcher to use. You may change matcher to suit specific step functions - the last call to ``use_step_matcher`` before a step function declaration will be the one it uses. .. note:: The function :func:`~behave.matchers.step_matcher()` is becoming deprecated. Use :func:`~behave.use_step_matcher()` instead. Context ------- You'll have noticed the "context" variable that's passed around. It's a clever place where you and *behave* can store information to share around. It runs at three levels, automatically managed by *behave*. When *behave* launches into a new feature or scenario it adds a new layer to the context, allowing the new activity level to add new values, or overwrite ones previously defined, for the duration of that activity. These can be thought of as scopes. You can define values in your `environmental controls`_ file which may be set at the feature level and then overridden for some scenarios. Changes made at the scenario level won't permanently affect the value set at the feature level. You may also use it to share values between steps. For example, in some steps you define you might have: .. code-block:: python @given('I request a new widget for an account via SOAP') def step_impl(context): client = Client("http://127.0.0.1:8000/soap/") context.response = client.Allocate(customer_first='Firstname', customer_last='Lastname', colour='red') @then('I should receive an OK SOAP response') def step_impl(context): eq_(context.response['ok'], 1) There's also some values added to the context by *behave* itself: **table** This holds any table data associated with a step. **text** This holds any multi-line text associated with a step. **failed** This is set at the root of the context when any step fails. It is sometimes useful to use this combined with the ``--stop`` command-line option to prevent some mis-behaving resource from being cleaned up in an ``after_feature()`` or similar (for example, a web browser being driven by Selenium.) The *context* variable in all cases is an instance of :class:`behave.runner.Context`. Environmental Controls ====================== The environment.py module may define code to run before and after certain events during your testing: **before_step(context, step), after_step(context, step)** These run before and after every step. **before_scenario(context, scenario), after_scenario(context, scenario)** These run before and after each scenario is run. **before_feature(context, feature), after_feature(context, feature)** These run before and after each feature file is exercised. **before_tag(context, tag), after_tag(context, tag)** These run before and after a section tagged with the given name. They are invoked for each tag encountered in the order they're found in the feature file. See `controlling things with tags`_. **before_all(context), after_all(context)** These run before and after the whole shooting match. The feature, scenario and step objects represent the information parsed from the feature file. They have a number of attributes: **keyword** "Feature", "Scenario", "Given", etc. **name** The name of the step (the text after the keyword.) **tags** A list of the tags attached to the section or step. See `controlling things with tags`_. **filename** and **line** The file name (or "") and line number of the statement. A common use-case for environmental controls might be to set up a web server and browser to run all your tests in. For example: .. code-block:: python # -- FILE: features/environment.py from behave import fixture, use_fixture from behave4my_project.fixtures import wsgi_server from selenium import webdriver @fixture def selenium_browser_chrome(context): # -- HINT: @behave.fixture is similar to @contextlib.contextmanager context.browser = webdriver.Chrome() yield context.browser # -- CLEANUP-FIXTURE PART: context.browser.quit() def before_all(context): use_fixture(wsgi_server, context, port=8000) use_fixture(selenium_browser_chrome, context) # -- HINT: CLEANUP-FIXTURE is performed after after_all() hook is called. def before_feature(context, feature): model.init(environment='test') .. code-block:: python # -- FILE: behave4my_project/fixtures.py # ALTERNATIVE: Place fixture in "features/environment.py" (but reuse is harder) from behave import fixture import threading from wsgiref import simple_server from my_application import model from my_application import web_app @fixture def wsgi_server(context, port=8000): context.server = simple_server.WSGIServer(('', port)) context.server.set_app(web_app.main(environment='test')) context.thread = threading.Thread(target=context.server.serve_forever) context.thread.start() yield context.server # -- CLEANUP-FIXTURE PART: context.server.shutdown() context.thread.join() Of course, if you wish, you could have a new browser for each feature, or to retain the database state between features or even initialise the database for each scenario. .. _`controlling things with tags`: Controlling Things With Tags ============================ You may also "tag" parts of your feature file. At the simplest level this allows *behave* to selectively check parts of your feature set. Given a feature file with: .. code-block:: gherkin Feature: Fight or flight In order to increase the ninja survival rate, As a ninja commander I want my ninjas to decide whether to take on an opponent based on their skill levels @slow Scenario: Weaker opponent Given the ninja has a third level black-belt When attacked by a samurai Then the ninja should engage the opponent Scenario: Stronger opponent Given the ninja has a third level black-belt When attacked by Chuck Norris Then the ninja should run for his life then running ``behave --tags=slow`` will run just the scenarios tagged ``@slow``. If you wish to check everything *except* the slow ones then you may run ``behave --tags=-slow``. Another common use-case is to tag a scenario you're working on with ``@wip`` and then ``behave --tags=wip`` to just test that one case. Tag selection on the command-line may be combined: * ``--tags=wip,slow`` This will select all the cases tagged *either* "wip" or "slow". * ``--tags=wip --tags=slow`` This will select all the cases tagged *both* "wip" and "slow". If a feature or scenario is tagged and then skipped because of a command-line control then the *before_* and *after_* environment functions will not be called for that feature or scenario. Note that *behave* has additional support specifically for testing `works in progress`_. The tags attached to a feature and scenario are available in the environment functions via the "feature" or "scenario" object passed to them. On those objects there is an attribute called "tags" which is a list of the tag names attached, in the order they're found in the features file. There are also `environmental controls`_ specific to tags, so in the above example *behave* will attempt to invoke an ``environment.py`` function ``before_tag`` and ``after_tag`` before and after the Scenario tagged ``@slow``, passing in the name "slow". If multiple tags are present then the functions will be called multiple times with each tag in the order they're defined in the feature file. Re-visiting the example from above; if only some of the features required a browser and web server then you could tag them ``@browser``: .. code-block:: python # -- FILE: features/environment.py # HINT: Reusing some code parts from above. ... def before_feature(context, feature): model.init(environment='test') if 'browser' in feature.tags: use_fixture(wsgi_server, context) use_fixture(selenium_browser_chrome, context) Works In Progress ================= *behave* supports the concept of a highly-unstable "work in progress" scenario that you're actively developing. This scenario may produce strange logging, or odd output to stdout or just plain interact in unexpected ways with *behave*'s scenario runner. To make testing such scenarios simpler we've implemented a "-w" command-line flag. This flag: 1. turns off stdout capture 2. turns off logging capture; you will still need to configure your own logging handlers - we recommend a ``before_all()`` with: .. code-block:: python if not context.config.log_capture: logging.basicConfig(level=logging.DEBUG) 3. turns off pretty output - no ANSI escape sequences to confuse your scenario's output 4. only runs scenarios tagged with "@wip" 5. stops at the first error Fixtures =================================== Fixtures simplify the setup/cleanup tasks that are often needed during test execution. .. code-block:: python # -- FILE: behave4my_project/fixtures.py (or in: features/environment.py) from behave import fixture from somewhere.browser.firefox import FirefoxBrowser # -- FIXTURE: Use generator-function @fixture def browser_firefox(context, timeout=30, **kwargs): # -- SETUP-FIXTURE PART: context.browser = FirefoxBrowser(timeout, **kwargs) yield context.browser # -- CLEANUP-FIXTURE PART: context.browser.shutdown() See :ref:`docid.fixtures` for more information. .. index:: single: debug-on-error .. _debug-on-error: Debug-on-Error (in Case of Step Failures) ========================================= A "debug on error/failure" functionality can easily be provided, by using the ``after_step()`` hook. The debugger is started when a step fails. It is in general a good idea to enable this functionality only when needed (in interactive mode). The functionality is enabled (in this example) by using the user-specific configuration data. A user can: * provide a userdata define on command-line * store a value in the "behave.userdata" section of behave's configuration file .. code-block:: python # -- FILE: features/environment.py # USE: behave -D BEHAVE_DEBUG_ON_ERROR (to enable debug-on-error) # USE: behave -D BEHAVE_DEBUG_ON_ERROR=yes (to enable debug-on-error) # USE: behave -D BEHAVE_DEBUG_ON_ERROR=no (to disable debug-on-error) BEHAVE_DEBUG_ON_ERROR = False def setup_debug_on_error(userdata): global BEHAVE_DEBUG_ON_ERROR BEHAVE_DEBUG_ON_ERROR = userdata.getbool("BEHAVE_DEBUG_ON_ERROR") def before_all(context): setup_debug_on_error(context.config.userdata) def after_step(context, step): if BEHAVE_DEBUG_ON_ERROR and step.status == "failed": # -- ENTER DEBUGGER: Zoom in on failure location. # NOTE: Use IPython debugger, same for pdb (basic python debugger). import ipdb ipdb.post_mortem(step.exc_traceback) behave-1.2.6/docs/update_behave_rst.py0000755000076600000240000000532413244555737020051 0ustar jensstaff00000000000000#!/usr/bin/env python # -*- coding: UTF-8 -*- """ Generates documentation of behave's * command-line options * configuration-file parameters REQUIRES: Python >= 2.6 """ from __future__ import absolute_import, print_function import re import sys import conf import textwrap from behave import configuration from behave.__main__ import TAG_HELP cmdline = [] config = [] indent = " " cmdline_option_schema = """\ .. option:: {cmdline_option} {text} """ config_param_schema = """\ .. index:: single: configuration param; {param} .. describe:: {param} : {type} {text} """ # -- STEP: Collect information and preprocess it. for fixed, keywords in configuration.options: skip = False if "dest" in keywords: dest = keywords["dest"] else: for opt in fixed: if opt.startswith("--no"): option_case = False skip = True if opt.startswith("--"): dest = opt[2:].replace("-", "_") break else: assert len(opt) == 2 dest = opt[1:] # -- CASE: command-line option text = re.sub(r"\s+", " ", keywords["help"]).strip() text = text.replace("%%", "%") text = textwrap.fill(text, 70, initial_indent="", subsequent_indent=indent) if fixed: # -- COMMAND-LINE OPTIONS (CONFIGFILE only have empty fixed): # cmdline.append(".. option:: %s\n\n%s\n" % (", ".join(fixed), text)) cmdline_option = ", ".join(fixed) cmdline.append(cmdline_option_schema.format( cmdline_option=cmdline_option, text=text)) if skip or dest in "tags_help lang_list lang_help version".split(): continue # -- CASE: configuration-file parameter action = keywords.get("action", "store") if action == "store": type = "text" elif action in ("store_true","store_false"): type = "bool" elif action == "append": type = "sequence" else: raise ValueError("unknown action %s" % action) if action == "store_false": # -- AVOID: Duplicated descriptions, use only case:true. continue text = re.sub(r"\s+", " ", keywords.get("config_help", keywords["help"])).strip() text = text.replace("%%", "%") text = textwrap.fill(text, 70, initial_indent="", subsequent_indent=indent) config.append(config_param_schema.format(param=dest, type=type, text=text)) # -- STEP: Generate documentation. print("Writing behave.rst ...") with open("behave.rst-template") as f: template = f.read() values = dict( cmdline="\n".join(cmdline), tag_expression=TAG_HELP, config="\n".join(config), ) with open("behave.rst", "w") as f: f.write(template.format(**values)) behave-1.2.6/docs/usecase_django.rst0000644000076600000240000000615513244555737017517 0ustar jensstaff00000000000000======================= Django Test Integration ======================= There are now at least 2 projects that integrate `Django`_ and :pypi:`behave`. Both use a `LiveServerTestCase`_ to spin up a runserver for the tests automatically, and shut it down when done with the test run. The approach used for integrating Django, though, varies slightly. :pypi:`behave-django` Provides a dedicated management command. Easy, automatic integration (thanks to monkey patching). Behave tests are run with ``python manage.py behave``. Allows running tests against an existing database as a special feature. See `setup behave-django `_ and `usage `_ instructions. :pypi:`django-behave` Provides a Django-specific TestRunner for Behave, which is set with the `TEST_RUNNER`_ property in your settings. Behave tests are run with the usual ``python manage.py test `` by default. See `setup django-behave `_ instructions. .. _Django: https://www.djangoproject.com .. _LiveServerTestCase: https://docs.djangoproject.com/en/1.8/topics/testing/tools/#liveservertestcase .. _TEST_RUNNER: https://docs.djangoproject.com/en/1.8/topics/testing/advanced/#using-different-testing-frameworks Manual Integration ================== Alternatively, you can integrate Django using the following boilerplate code in your ``environment.py`` file: .. code-block:: python # -- FILE: my_django/behave_fixtures.py from behave import fixture import django from django.test.runner import DiscoverRunner from django.test.testcases import LiveServerTestCase @fixture def django_test_runner(context): django.setup() context.test_runner = DiscoverRunner() context.test_runner.setup_test_environment() context.old_db_config = context.test_runner.setup_databases() yield context.test_runner.teardown_databases(context.old_db_config) context.test_runner.teardown_test_environment() @fixture def django_test_case(context): context.test_case = LiveServerTestCase context.test_case.setUpClass() yield context.test_case.tearDownClass() del context.test_case .. code-block:: python # -- FILE: features/environment.py from behave import use_fixture from my_django.behave_fixtures import django_test_runner, django_test_case import os os.environ["DJANGO_SETTINGS_MODULE"] = "test_project.settings" def before_all(context): use_fixture(django_test_runner, context) def before_scenario(context, scenario): use_fixture(django_test_case, context) Taken from Andrey Zarubin's blog post "`BDD. PyCharm + Python & Django`_". .. _`BDD. PyCharm + Python & Django`: https://anvileight.com/blog/2016/04/12/behavior-driven-development-pycharm-python-django/ Strategies and Tooling ====================== See :ref:`id.practicaltips` for automation libraries and implementation tips on your BDD tests. behave-1.2.6/docs/usecase_flask.rst0000644000076600000240000000336613244555737017356 0ustar jensstaff00000000000000====================== Flask Test Integration ====================== Integrating your `Flask`_ application with :pypi:`behave` is done via boilerplate code in your ``environment.py`` file. The `Flask documentation on testing`_ explains how to use the Werkzeug test client for running tests in general. .. _Flask: http://flask.pocoo.org/ .. _Flask documentation on testing: http://flask.pocoo.org/docs/latest/testing/ Integration Example =================== The example below is an integration boilerplate derived from the official Flask documentation, featuring the `Flaskr sample application`_ from the Flask tutorial. .. code-block:: python # -- FILE: features/environment.py import os import tempfile from behave import fixture, use_fixture # flaskr is the sample application we want to test from flaskr import app, init_db @fixture def flaskr_client(context, *args, **kwargs): context.db, app.config['DATABASE'] = tempfile.mkstemp() app.testing = True context.client = app.test_client() with app.app_context(): init_db() yield context.client # -- CLEANUP: os.close(context.db) os.unlink(app.config['DATABASE']) def before_feature(context, feature): # -- HINT: Recreate a new flaskr client before each feature is executed. use_fixture(flaskr_client, context) Taken and adapted from Ismail Dhorat's `BDD testing example on Flaskr`_. .. _Flaskr sample application: http://flask.pocoo.org/docs/latest/tutorial/introduction/ .. _BDD testing example on Flaskr: https://github.com/ismaild/flaskr-bdd Strategies and Tooling ====================== See :ref:`id.practicaltips` for automation libraries and implementation tips on your BDD tests. behave-1.2.6/etc/0000755000076600000240000000000013244564037013620 5ustar jensstaff00000000000000behave-1.2.6/etc/json/0000755000076600000240000000000013244564040014563 5ustar jensstaff00000000000000behave-1.2.6/etc/json/behave.json-schema0000644000076600000240000001444613244555737020174 0ustar jensstaff00000000000000{ "$schema": "http://json-schema.org/draft-04/schema#", "title": "behave JSON schema", "description": "behave schema for JSON formatter output", "type": "array", "items": { "$ref": "#/definitions/Feature" }, "definitions": { "Feature": { "type": "object", "description": "Represents a Feature object.", "properties": { "name": { "type": "string" }, "keyword": { "type": "string" }, "location": { "type": "string" }, "status": { "type": "string" }, "tags": { "$ref": "#/definitions/Tags" }, "description": { "$ref": "#/definitions/MultiLineText" }, "elements": { "type": "array", "items": { "$ref": "#/definitions/FeatureElement" } } }, "required": [ "name", "keyword", "location" ] }, "FeatureElement": { "type": "object", "anyOf": [ { "$ref": "#/definitions/Background" }, { "$ref": "#/definitions/Scenario" }, { "$ref": "#/definitions/ScenarioOutline" } ] }, "Background": { "type": "object", "properties": { "type": { "enum": [ "background" ] }, "name": { "type": "string" }, "keyword": { "type": "string" }, "location": { "type": "string" }, "steps": { "type": "array", "items": { "$ref": "#/definitions/Step" } } }, "required": [ "name", "keyword", "location", "steps" ] }, "Scenario": { "type": "object", "properties": { "type": { "enum": [ "scenario" ] }, "name": { "type": "string" }, "keyword": { "type": "string" }, "location": { "type": "string" }, "tags": { "$ref": "#/definitions/Tags" }, "description": { "$ref": "#/definitions/MultiLineText" }, "status": { "type": "string" }, "steps": { "type": "array", "items": { "$ref": "#/definitions/Step" } } }, "required": [ "name", "keyword", "location", "steps" ] }, "ScenarioOutline": { "type": "object", "properties": { "type": { "enum": [ "scenario_outline" ] }, "name": { "type": "string" }, "keyword": { "type": "string" }, "location": { "type": "string" }, "tags": { "$ref": "#/definitions/Tags" }, "description": { "$ref": "#/definitions/MultiLineText" }, "status": { "type": "string" }, "steps": { "type": "array", "items": { "$ref": "#/definitions/Step" } }, "examples": { "type": "array", "items": { "$ref": "#/definitions/Examples" } } }, "required": [ "name", "keyword", "location", "steps", "examples" ] }, "Examples": { "type": "object", "description": "@wip: Must be defined." }, "Step": { "properties": { "name": { "type": "string" }, "keyword": { "type": "string" }, "step_type":{ "type": "string" }, "location": { "type": "string" }, "text": { "$ref": "#/definitions/MultiLineText" }, "table": { "$ref": "#/definitions/Table" }, "match": { "$ref": "#/definitions/Match" }, "result": { "$ref": "#/definitions/TestResult" } }, "required": [ "name", "keyword", "step_type", "location" ] }, "MultiLineText": { "anyOf": [ { "type": "string" }, { "type": "array", "items": { "type": "string" }} ], "description": "Type used for multi-line text in steps." }, "Text": { "type": "string", "description": "Type used for multi-line text in steps." }, "Table": { "type": "object", "description": "@wip: Must be defined." }, "TestResult": { "type": "object", "properties": { "status": { "type": "string", "description": "Enum: untested, undefined, skipped, passed, failed" }, "duration": { "type": "number", "description": "Duration in seconds (as float)" }, "error_message": { "$ref": "#/definitions/MultiLineText" } }, "required": [ "status", "duration" ] }, "MatchArgument": { "type": "object", "properties": { "name": { "type": "string" }, "original": { "type": "string" }, "value": { "anyOf": [ { "type": "string" }, { "type": "integer" }, { "type": "number" }, { "type": "boolean" }, { "type": "object" }, { "type": "array" }, { "type": "null" } ] } }, "required": [ "value" ] }, "Match": { "type": "object", "description": "Step definition (implementation), associated to a step.", "properties": { "location": { "type": "string" }, "arguments": { "type": "array", "items": { "$ref": "#/definitions/MatchArgument" } } } }, "Tags": { "type": "array", "items": { "type": "string" } }, "SimpleTypes": { "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] } } }behave-1.2.6/etc/junit.xml/0000755000076600000240000000000013244564040015542 5ustar jensstaff00000000000000behave-1.2.6/etc/junit.xml/behave_junit.xsd0000644000076600000240000001067113244555737020747 0ustar jensstaff00000000000000 behave-1.2.6/etc/junit.xml/junit-4.xsd0000644000076600000240000001032613244555737017573 0ustar jensstaff00000000000000 behave-1.2.6/examples/0000755000076600000240000000000013244564037014663 5ustar jensstaff00000000000000behave-1.2.6/examples/async_step/0000755000076600000240000000000013244564040017025 5ustar jensstaff00000000000000behave-1.2.6/examples/async_step/behave.ini0000644000076600000240000000075113244555737021000 0ustar jensstaff00000000000000# ============================================================================= # BEHAVE CONFIGURATION # ============================================================================= # FILE: .behaverc, behave.ini # # SEE ALSO: # * http://packages.python.org/behave/behave.html#configuration-files # * https://github.com/behave/behave # * http://pypi.python.org/pypi/behave/ # ============================================================================= [behave] show_timings = true behave-1.2.6/examples/async_step/features/0000755000076600000240000000000013244564040020643 5ustar jensstaff00000000000000behave-1.2.6/examples/async_step/features/async_dispatch.feature0000644000076600000240000000042713244555737025234 0ustar jensstaff00000000000000@use.with_python.version=3.4 @use.with_python.version=3.5 @use.with_python.version=3.6 Feature: Scenario: Given I dispatch an async-call with param "Alice" And I dispatch an async-call with param "Bob" Then the collected result of the async-calls is "ALICE, BOB" behave-1.2.6/examples/async_step/features/async_run.feature0000644000076600000240000000022613244555737024236 0ustar jensstaff00000000000000@use.with_python.version=3.4 @use.with_python.version=3.5 @use.with_python.version=3.6 Feature: Scenario: Given an async-step waits 0.3 seconds behave-1.2.6/examples/async_step/features/environment.py0000644000076600000240000000210013244555737023567 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values import sys # -- MATCHES ANY TAGS: @use.with_{category}={value} # NOTE: active_tag_value_provider provides category values for active tags. python_version = "%s.%s" % sys.version_info[:2] active_tag_value_provider = { "python.version": python_version, } active_tag_matcher = ActiveTagMatcher(active_tag_value_provider) # ----------------------------------------------------------------------------- # HOOKS: # ----------------------------------------------------------------------------- def before_all(context): # -- SETUP ACTIVE-TAG MATCHER (with userdata): setup_active_tag_values(active_tag_value_provider, context.config.userdata) def before_feature(context, feature): if active_tag_matcher.should_exclude_with(feature.tags): feature.skip(reason=active_tag_matcher.exclude_reason) def before_scenario(context, scenario): if active_tag_matcher.should_exclude_with(scenario.effective_tags): scenario.skip(reason=active_tag_matcher.exclude_reason) behave-1.2.6/examples/async_step/features/steps/0000755000076600000240000000000013244564040022001 5ustar jensstaff00000000000000behave-1.2.6/examples/async_step/features/steps/async_dispatch_steps.py0000644000076600000240000000207313244555737026604 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- # REQUIRES: Python >= 3.5 from behave import given, then, step from behave.api.async_step import use_or_create_async_context, AsyncContext from hamcrest import assert_that, equal_to, empty import asyncio @asyncio.coroutine def async_func(param): yield from asyncio.sleep(0.2) return str(param).upper() @given('I dispatch an async-call with param "{param}"') def step_dispatch_async_call(context, param): async_context = use_or_create_async_context(context, "async_context1") task = async_context.loop.create_task(async_func(param)) async_context.tasks.append(task) @then('the collected result of the async-calls is "{expected}"') def step_collected_async_call_result_is(context, expected): async_context = context.async_context1 done, pending = async_context.loop.run_until_complete( asyncio.wait(async_context.tasks, loop=async_context.loop)) parts = [task.result() for task in done] joined_result = ", ".join(sorted(parts)) assert_that(joined_result, equal_to(expected)) assert_that(pending, empty()) behave-1.2.6/examples/async_step/features/steps/async_steps34.py0000644000076600000240000000050113244555737025066 0ustar jensstaff00000000000000# -- REQUIRES: Python >= 3.4 from behave import step from behave.api.async_step import async_run_until_complete import asyncio @step('an async-step waits {duration:f} seconds') @async_run_until_complete @asyncio.coroutine def step_async_step_waits_seconds_py34(context, duration): yield from asyncio.sleep(duration) behave-1.2.6/examples/async_step/features/steps/async_steps35.py0000644000076600000240000000056513244555737025101 0ustar jensstaff00000000000000# -- REQUIRES: Python >= 3.5 from behave import step from behave.api.async_step import async_run_until_complete import asyncio @step('an async-step waits {duration:f} seconds') @async_run_until_complete async def step_async_step_waits_seconds_py35(context, duration): """Simple example of a coroutine as async-step (in Python 3.5)""" await asyncio.sleep(duration) behave-1.2.6/examples/async_step/README.txt0000644000076600000240000000043113244555737020536 0ustar jensstaff00000000000000EXAMPLE: Async-steps and async-dispatch/async-collect ================================================================================ BASED-ON: features/step.async_step.feature REQUIRES: Python 3.5 (or Python 3.4) Examples for testing asyncio frameworks with async-steps, etc. behave-1.2.6/examples/async_step/testrun_example.async_dispatch.txt0000644000076600000240000000061513244555737026017 0ustar jensstaff00000000000000Feature: Scenario: Given I dispatch an async-call with param "Alice" ... passed in 0.001s And I dispatch an async-call with param "Bob" ... passed in 0.000s Then the collected result of the async-calls is "ALICE, BOB" ... passed in 0.206s 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined Took 0m0.208s behave-1.2.6/examples/async_step/testrun_example.async_run.txt0000644000076600000240000000034313244555737025022 0ustar jensstaff00000000000000Feature: Scenario: Given an async-step waits 0.3 seconds ... passed in 0.307s 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined Took 0m0.307s behave-1.2.6/examples/env_vars/0000755000076600000240000000000013244564040016500 5ustar jensstaff00000000000000behave-1.2.6/examples/env_vars/behave.ini0000644000076600000240000000102413244555737020445 0ustar jensstaff00000000000000# ============================================================================= # BEHAVE CONFIGURATION # ============================================================================= # FILE: .behaverc, behave.ini # # SEE ALSO: # * http://packages.python.org/behave/behave.html#configuration-files # * https://github.com/behave/behave # * http://pypi.python.org/pypi/behave/ # ============================================================================= [behave] default_format = plain stdout_capture = false show_source = true behave-1.2.6/examples/env_vars/behave_run.output_example.txt0000644000076600000240000000061213244555737024445 0ustar jensstaff00000000000000Feature: Test Environment variable concept Scenario: USE ENVIRONMENT-VAR: LOGNAME = xxx (variant 1) When I click on $LOGNAME ... passed USE ENVIRONMENT-VAR: LOGNAME = xxx (variant 2) When I use the environment variable $LOGNAME ... passed 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined Took 0m0.000s behave-1.2.6/examples/env_vars/features/0000755000076600000240000000000013244564040020316 5ustar jensstaff00000000000000behave-1.2.6/examples/env_vars/features/env_var.feature0000644000076600000240000000022113244555737023343 0ustar jensstaff00000000000000Feature: Test Environment variable concept Scenario: When I click on $LOGNAME When I use the environment variable $LOGNAME behave-1.2.6/examples/env_vars/features/steps/0000755000076600000240000000000013244564040021454 5ustar jensstaff00000000000000behave-1.2.6/examples/env_vars/features/steps/env_var_steps.py0000644000076600000240000000236713244555737024731 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- # -- FILE: features/steps/my_steps.py from __future__ import print_function from behave import when import os import sys # -- VARIANT 1: @when(u'I click on ${environment_variable:w}') def step_impl(context, environment_variable): env_value = os.environ.get(environment_variable, None) if env_value is None: raise LookupError("Environment variable '%s' is undefined" % environment_variable) print("USE ENVIRONMENT-VAR: %s = %s (variant 1)" % (environment_variable, env_value)) # -- VARIANT 2: Use type converter from behave import register_type import parse @parse.with_pattern(r"\$\w+") # -- ONLY FOR: $WORD def parse_environment_var(text): assert text.startswith("$") env_name = text[1:] env_value = os.environ.get(env_name, None) return (env_name, env_value) register_type(EnvironmentVar=parse_environment_var) @when(u'I use the environment variable {environment_variable:EnvironmentVar}') def step_impl(context, environment_variable): env_name, env_value = environment_variable if env_value is None: raise LookupError("Environment variable '%s' is undefined" % env_name) print("USE ENVIRONMENT-VAR: %s = %s (variant 2)" \ % (env_name, env_value)) behave-1.2.6/examples/env_vars/README.rst0000644000076600000240000000150313244555737020203 0ustar jensstaff00000000000000EXAMPLE: Use Environment Variables in Steps ============================================================================= :RELATED TO: `issue #497`_ This directory provides a simple example how you can use environment variables in step implementations. :: # -- USE: -f plain --no-capture (via "behave.ini" defaults) $ behave Feature: Test Environment variable concept Scenario: USE ENVIRONMENT-VAR: LOGNAME = xxx (variant 1) When I click on $LOGNAME ... passed USE ENVIRONMENT-VAR: LOGNAME = xxx (variant 2) When I use the environment variable $LOGNAME ... passed 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined Took 0m0.000s .. _`issue #497`: https://github.com/behave/behave/issues/497 behave-1.2.6/features/0000755000076600000240000000000013244564040014655 5ustar jensstaff00000000000000behave-1.2.6/features/background.feature0000644000076600000240000003151113244555737020367 0ustar jensstaff00000000000000Feature: Background As a test writer I want to run a number of steps in each scenario And I want to avoid duplicating these steps for each scenario So that I write these steps only once (DRY principle). . SPECIFICATION: . * A feature can have an optional "Background" section . * The Background must be specified before any Scenario/ScenarioOutline . * The Background may occur at most once . * The Background steps are executed in each Scenario/ScenarioOutline . * The Background steps are executed before any Scenario steps . * If a Background step fails then the is marked as scenario failed . * If a Background fails in a scenario then other scenarios are still executed. . . RELATED: . * parser.background.sad_cases.feature . . NOTE: . Cucumber has a slightly different runtime behaviour. . When a background step fails the first scenario is marked as failed. . But the remaining scenarios are marked as skipped. . . This can lead to problems when you have sporadic background step failures. . For this reason, behave retries the background steps for each scenario. . But this may lead to an increased test duration if a systematic failure . occurs in the failing background step. . . SEE ALSO: . * https://github.com/cucumber/cucumber/blob/master/features/docs/gherkin/background.feature @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/background_steps.py" with: """ from behave import step @step('{word} background step passes') def step_background_step_passes(context, word): pass @step('{word} background step fails') def step_background_step_fails(context, word): assert False, "XFAIL: background step" @step('{word} background step fails sometimes') def step_background_step_fails_sometimes(context, word): should_fail = (context.scenarios_count % 2) == 0 if should_fail: step_background_step_fails(context, word) """ And a file named "features/steps/passing_steps.py" with: """ from behave import step @step('{word} step passes') def step_passes(context, word): pass @step('{word} step fails') def step_fails(context, word): assert False, "XFAIL" """ Scenario: Feature with a Background and Scenarios Given a file named "features/background_example1.feature" with: """ Feature: Background: Given a background step passes And another background step passes Scenario: S1 When a step passes Scenario: S2 Then a step passes And another step passes """ When I run "behave -f plain -T features/background_example1.feature" Then it should pass with: """ 2 scenarios passed, 0 failed, 0 skipped 7 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: Background: Scenario: S1 Given a background step passes ... passed And another background step passes ... passed When a step passes ... passed Scenario: S2 Given a background step passes ... passed And another background step passes ... passed Then a step passes ... passed And another step passes ... passed """ But note that "the Background steps are injected into each Scenario" And note that "the Background steps are executed before any Scenario steps" Scenario: Failing Background Step causes all Scenarios to fail Given a file named "features/background_fail_example.feature" with: """ Feature: Background: B1 Given a background step passes And a background step fails And another background step passes Scenario: S1 When a step passes Scenario: S2 Then a step passes And another step passes """ When I run "behave -f plain -T features/background_fail_example.feature" Then it should fail with: """ 0 scenarios passed, 2 failed, 0 skipped 2 steps passed, 2 failed, 5 skipped, 0 undefined """ And the command output should contain: """ Feature: Background: B1 Scenario: S1 Given a background step passes ... passed And a background step fails ... failed Assertion Failed: XFAIL: background step Scenario: S2 Given a background step passes ... passed And a background step fails ... failed Assertion Failed: XFAIL: background step """ And note that "the failing Background step causes all Scenarios to fail" Scenario: Failing Background Step does not prevent that other Scenarios are executed If a Background step fails sometimes it should be retried in the remaining Scenarios where it might pass. Given a file named "features/background_fails_sometimes_example.feature" with: """ Feature: Background: B2 Given a background step fails sometimes Scenario: S1 Given a step passes Scenario: S2 When another step passes Scenario: S3 Then another step passes """ And a file named "features/environment.py" with: """ scenarios_count = 0 def before_scenario(context, scenario): global scenarios_count context.scenarios_count = scenarios_count scenarios_count += 1 """ When I run "behave -f plain -T features/background_fails_sometimes_example.feature" Then it should fail with: """ 1 scenario passed, 2 failed, 0 skipped 2 steps passed, 2 failed, 2 skipped, 0 undefined """ And the command output should contain: """ Feature: Background: B2 Scenario: S1 Given a background step fails sometimes ... failed Assertion Failed: XFAIL: background step Scenario: S2 Given a background step fails sometimes ... passed When another step passes ... passed Scenario: S3 Given a background step fails sometimes ... failed Assertion Failed: XFAIL: background step """ Scenario: Feature with a Background and ScenarioOutlines Given a file named "features/background_outline_example.feature" with: """ Feature: Background: Given a background step passes And another background step passes Scenario Outline: SO1 When a step Then another step Examples: Alpha | outcome1 | outcome2 | | passes | passes | | passes | passes | Scenario Outline: SO2 Given step passes Then step passes Examples: | word1 | word2 | | a | a | | a | another | | another | a | """ When I run "behave -f plain -T features/background_outline_example.feature" Then it should pass with: """ 5 scenarios passed, 0 failed, 0 skipped 20 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: Background: Scenario Outline: SO1 -- @1.1 Alpha Given a background step passes ... passed And another background step passes ... passed When a step passes ... passed Then another step passes ... passed Scenario Outline: SO1 -- @1.2 Alpha Given a background step passes ... passed And another background step passes ... passed When a step passes ... passed Then another step passes ... passed Scenario Outline: SO2 -- @1.1 Given a background step passes ... passed And another background step passes ... passed Given a step passes ... passed Then a step passes ... passed Scenario Outline: SO2 -- @1.2 Given a background step passes ... passed And another background step passes ... passed Given a step passes ... passed Then another step passes ... passed Scenario Outline: SO2 -- @1.3 Given a background step passes ... passed And another background step passes ... passed Given another step passes ... passed Then a step passes ... passed """ But note that "the Background steps are injected into each ScenarioOutline" And note that "the Background steps are executed before any ScenarioOutline steps" Scenario: Failing Background Step causes all ScenarioOutlines to fail Given a file named "features/background_fail_outline_example.feature" with: """ Feature: Background: Given a background step passes And a background step fails But another background step passes Scenario Outline: SO1 When a step Then another step Examples: Alpha | outcome1 | outcome2 | | passes | passes | | passes | fails | | fails | passes | | fails | fails | Scenario Outline: SO2 When step passes Examples: Beta | word1 | | a | | another | """ When I run "behave -f plain -T features/background_fail_outline_example.feature" Then it should fail with: """ 0 scenarios passed, 6 failed, 0 skipped 6 steps passed, 6 failed, 16 skipped, 0 undefined """ And the command output should contain: """ Feature: Background: Scenario Outline: SO1 -- @1.1 Alpha Given a background step passes ... passed And a background step fails ... failed Assertion Failed: XFAIL: background step Scenario Outline: SO1 -- @1.2 Alpha Given a background step passes ... passed And a background step fails ... failed Assertion Failed: XFAIL: background step Scenario Outline: SO1 -- @1.3 Alpha Given a background step passes ... passed And a background step fails ... failed Assertion Failed: XFAIL: background step Scenario Outline: SO1 -- @1.4 Alpha Given a background step passes ... passed And a background step fails ... failed Assertion Failed: XFAIL: background step Scenario Outline: SO2 -- @1.1 Beta Given a background step passes ... passed And a background step fails ... failed Assertion Failed: XFAIL: background step Scenario Outline: SO2 -- @1.2 Beta Given a background step passes ... passed And a background step fails ... failed Assertion Failed: XFAIL: background step """ But note that "the failing Background step causes each ScenarioOutline to be marked as skipped" Scenario: Feature with Background after first Scenario should fail (SAD CASE) Given a file named "features/background_sad_example1.feature" with: """ Feature: Scenario: S1 When a step passes Background: B1 Given a background step passes Scenario: S2 Then a step passes And another step passes """ When I run "behave -f plain -T features/background_sad_example1.feature" Then it should fail with: """ Parser failure in state steps, at line 5: "Background: B1" REASON: Background may not occur after Scenario/ScenarioOutline. """ Scenario: Feature with two Backgrounds should fail (SAD CASE) Given a file named "features/background_sad_example2.feature" with: """ Feature: Background: B1 Given a background step passes Background: B2 (XFAIL) Given another background step passes Scenario: S1 When a step passes Scenario: S2 Then a step passes And another step passes """ When I run "behave -f plain -T features/background_sad_example2.feature" Then it should fail with: """ Parser failure in state steps, at line 5: "Background: B2 (XFAIL)" REASON: Background should not be used here. """ behave-1.2.6/features/capture_stderr.feature0000644000076600000240000001476613244555737021313 0ustar jensstaff00000000000000@sequential Feature: Capture stderr output and show it in case of failures/errors As a tester To simplify failure diagnostics I want that: - captured output is displayed only when failures/errors occur - all output is displayed when capture is disabled (but may clutter up formatter output) @setup Scenario: Test Setup Given a new working directory And a file named "features/steps/stderr_steps.py" with: """ from behave import step import sys @step('a step writes "{text}" to stderr and passes') def step_writes_to_stderr_and_passes(context, text): sys.stderr.write("stderr:%s;\n" % text) @step('a step writes "{text}" to stderr and fails') def step_writes_to_stderr_and_fails(context, text): sys.stderr.write("stderr:%s;\n" % text) assert False, "XFAIL, step with: %s;" % text """ And a file named "features/capture_stderr.example1.feature" with: """ Feature: Scenario: Given a step writes "Alice" to stderr and passes When a step writes "Bob" to stderr and passes Then a step writes "Charly" to stderr and passes """ And a file named "features/capture_stderr.example2.feature" with: """ Feature: Scenario: Given a step writes "Alice" to stderr and passes When a step writes "Bob" to stderr and fails Then a step writes "Charly" to stderr and fails """ @capture Scenario: Captured output is suppressed if scenario passes (CASE 1: --capture) When I run "behave -f plain -T --capture features/capture_stderr.example1.feature" Then it should pass And the command output should contain: """ Feature: Scenario: Given a step writes "Alice" to stderr and passes ... passed When a step writes "Bob" to stderr and passes ... passed Then a step writes "Charly" to stderr and passes ... passed """ But the command output should not contain: """ stderr:Alice; """ @capture Scenario: Captured output is suppressed if scenario passes (CASE 2: --capture-stderr) When I run "behave -f plain -T --capture-stderr features/capture_stderr.example1.feature" Then it should pass And the command output should contain: """ Feature: Scenario: Given a step writes "Alice" to stderr and passes ... passed When a step writes "Bob" to stderr and passes ... passed Then a step writes "Charly" to stderr and passes ... passed """ But the command output should not contain: """ stderr:Alice; """ @capture Scenario: Captured output is shown up to first failure if scenario fails (CASE 1: --capture) When I run "behave -f plain -T --capture features/capture_stderr.example2.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 1 failed, 1 skipped, 0 undefined """ And the command output should contain: """ Feature: Scenario: Given a step writes "Alice" to stderr and passes ... passed When a step writes "Bob" to stderr and fails ... failed Assertion Failed: XFAIL, step with: Bob; Captured stderr: stderr:Alice; stderr:Bob; """ But the command output should not contain: """ stderr:Charly; """ @capture Scenario: Captured output is shown if scenario fails up to first failure (CASE 2: --capture-stderr) When I run "behave -f plain -T --capture-stderr features/capture_stderr.example2.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 1 failed, 1 skipped, 0 undefined """ And the command output should contain: """ Feature: Scenario: Given a step writes "Alice" to stderr and passes ... passed When a step writes "Bob" to stderr and fails ... failed Assertion Failed: XFAIL, step with: Bob; Captured stderr: stderr:Alice; stderr:Bob; """ But the command output should not contain: """ stderr:Charly; """ @no_capture Scenario: All output is shown when --no-capture-stderr is used and all steps pass (CASE 1) When I run "behave -f plain -T --no-capture-stderr features/capture_stderr.example1.feature" Then it should pass And the command output should contain: """ stderr:Alice; stderr:Bob; stderr:Charly; """ And the command output should contain: """ Feature: Scenario: Given a step writes "Alice" to stderr and passes ... passed When a step writes "Bob" to stderr and passes ... passed Then a step writes "Charly" to stderr and passes ... passed """ @no_capture Scenario: All output is shown up to first failing step when --no-capture-stderr is used (CASE 2) When I run "behave -f plain -T --no-capture-stderr features/capture_stderr.example2.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 1 failed, 1 skipped, 0 undefined """ And the command output should contain: """ Feature: Scenario: Given a step writes "Alice" to stderr and passes ... passed When a step writes "Bob" to stderr and fails ... failed Assertion Failed: XFAIL, step with: Bob; """ And the command output should contain: """ stderr:Alice; stderr:Bob; """ But the command output should not contain: """ stderr:Charly; """ behave-1.2.6/features/capture_stdout.feature0000644000076600000240000001126213244555737021316 0ustar jensstaff00000000000000@sequential Feature: Capture stdout output and show it in case of failures/errors As a tester To simplify failure diagnostics I want that: - captured output is displayed only when failures/errors occur - all output is displayed when capture is disabled (but may clutter up formatter output) @setup Scenario: Test Setup Given a new working directory And a file named "features/steps/stdout_steps.py" with: """ from behave import step import sys @step('a step writes "{text}" to stdout and passes') def step_writes_to_stdout_and_passes(context, text): sys.stdout.write("stdout:%s;\n" % text) @step('a step writes "{text}" to stdout and fails') def step_writes_to_stdout_and_fails(context, text): sys.stdout.write("stdout:%s;\n" % text) assert False, "XFAIL, step with: %s;" % text """ And a file named "features/capture_stdout.example1.feature" with: """ Feature: Scenario: Given a step writes "Alice" to stdout and passes When a step writes "Bob" to stdout and passes Then a step writes "Charly" to stdout and passes """ And a file named "features/capture_stdout.example2.feature" with: """ Feature: Scenario: Given a step writes "Alice" to stdout and passes When a step writes "Bob" to stdout and fails Then a step writes "Charly" to stdout and fails """ @capture Scenario: Captured output is suppressed if scenario passes When I run "behave -f plain -T --capture features/capture_stdout.example1.feature" Then it should pass And the command output should contain: """ Feature: Scenario: Given a step writes "Alice" to stdout and passes ... passed When a step writes "Bob" to stdout and passes ... passed Then a step writes "Charly" to stdout and passes ... passed """ But the command output should not contain: """ stdout:Alice; """ @capture Scenario: Captured output is shown up to first failure if scenario fails When I run "behave -f plain -T --capture features/capture_stdout.example2.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 1 failed, 1 skipped, 0 undefined """ And the command output should contain: """ Feature: Scenario: Given a step writes "Alice" to stdout and passes ... passed When a step writes "Bob" to stdout and fails ... failed Assertion Failed: XFAIL, step with: Bob; Captured stdout: stdout:Alice; stdout:Bob; """ But the command output should not contain: """ stdout:Charly; """ @no_capture Scenario: All output is shown when --no-capture is used and all steps pass (CASE 1) When I run "behave -f plain -T --no-capture features/capture_stdout.example1.feature" Then it should pass And the command output should contain: """ Feature: Scenario: stdout:Alice; Given a step writes "Alice" to stdout and passes ... passed stdout:Bob; When a step writes "Bob" to stdout and passes ... passed stdout:Charly; Then a step writes "Charly" to stdout and passes ... passed """ @no_capture Scenario: All output is shown up to first failing step when --no-capture is used (CASE 2) When I run "behave -f plain -T --no-capture features/capture_stdout.example2.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 1 failed, 1 skipped, 0 undefined """ And the command output should contain: """ Feature: Scenario: stdout:Alice; Given a step writes "Alice" to stdout and passes ... passed stdout:Bob; When a step writes "Bob" to stdout and fails ... failed """ But the command output should not contain: """ stdout:Charly; """ behave-1.2.6/features/cmdline.lang_list.feature0000644000076600000240000000204213244555737021633 0ustar jensstaff00000000000000Feature: Command-line options: Use behave --lang-list As a user I want to determine which languages are supported by behave So that I can use the language code in feature files or command lines @problematic @not.with_os=win32 Scenario: Use behave --lang-list When I run "behave --lang-list" Then it should pass with: """ Languages available: ar: العربية / Arabic bg: български / Bulgarian ca: català / Catalan cs: Česky / Czech cy-GB: Cymraeg / Welsh da: dansk / Danish de: Deutsch / German en: English / English """ And the command output should contain: """ sv: Svenska / Swedish tr: Türkçe / Turkish uk: Українська / Ukrainian uz: Узбекча / Uzbek vi: Tiếng Việt / Vietnamese zh-CN: 简体中文 / Chinese simplified zh-TW: 繁體中文 / Chinese traditional """ But the command output should not contain "Traceback" behave-1.2.6/features/configuration.default_paths.feature0000644000076600000240000000707613244555737023752 0ustar jensstaff00000000000000@sequential Feature: Default paths for features in behave configfile As a tester I want to specify the default paths for features in the configuration file That should be used if none are provided as command-line args To support pre-pared/canned test environments (and to support my laziness while using behave in a shell). @setup Scenario: Test Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step('a step passes') def step_passes(context): pass """ And a file named "features/alice.feature" with: """ Feature: Alice Scenario: A1 Given a step passes When a step passes Then a step passes """ And a file named "features/bob.feature" with: """ Feature: Bob Scenario: B1 When a step passes """ And a file named "more.features/charly.feature" with: """ Feature: Charly Scenario: C1 Then a step passes """ And a file named "behave.ini" with: """ [behave] show_timings = false paths = features/bob.feature more.features/charly.feature """ Scenario: Used default paths from behave configfile When I run "behave -f plain" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped """ And the command output should contain: """ Feature: Bob Scenario: B1 When a step passes ... passed Feature: Charly Scenario: C1 Then a step passes ... passed """ But the command output should not contain: """ Feature: Alice """ Scenario: Command-line args can override default paths in configfile When I run "behave -f plain features/alice.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: Alice Scenario: A1 """ But the command output should not contain: """ Feature: Bob """ And the command output should not contain: """ Feature: Charly """ Scenario: Command-line args are provided (CASE 2) When I run "behave -f plain features" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped """ And the command output should contain: """ Feature: Alice Scenario: A1 Given a step passes ... passed When a step passes ... passed Then a step passes ... passed Feature: Bob Scenario: B1 When a step passes ... passed """ But the command output should not contain: """ Feature: Charly """ behave-1.2.6/features/context.global_params.feature0000644000076600000240000000262513244555737022542 0ustar jensstaff00000000000000Feature: Global Context Parameters defined in environment file . SPECIFICATION: . * When a Context parameter is defined in "environment.py", . its value is provided to all scenarios (steps). . * Each scenario has the same global parameters (and values). . * A scenario (step) may modify global parameters (values). . * After a scenario is executed all changes to Context parameters are reverted. Scenario: Test Setup Description (Example) Given a file named "features/environment.py" with: """ def before_all(context): context.global_name = "env:Alice" context.global_age = 12 """ Scenario: Modify global Context parameter in Scenario Step Given the behave context contains: | Parameter | Value | | global_name | "env:Alice" | | global_age | 12 | When I set the context parameter "global_name" to "step:Bob" Then the behave context should contain: | Parameter | Value | | global_name | "step:Bob" | | global_age | 12 | Scenario: Ensure that Global Context parameter is reset for next Scenario Then the behave context should have a parameter "global_name" And the behave context should contain: | Parameter | Value | | global_name | "env:Alice" | | global_age | 12 | behave-1.2.6/features/context.local_params.feature0000644000076600000240000000150713244555737022372 0ustar jensstaff00000000000000Feature: Local Context Parameters defined in Scenarios (Steps) . SPECIFICATION: . * When a step adds/modifies an attribute in the Context object, . then its value is only available to other steps in this scenario. . * After a scenario is executed all Context object changes are undone. Scenario: Add Local Context parameter in Scenario/Step Given the behave context does not have a parameter "local_name" When I set the context parameter "local_name" to "Alice" Then the behave context should have a parameter "local_name" And the behave context should contain: | Parameter | Value | | local_name | "Alice" | Scenario: Ensure that Local Context parameter is not available to next Scenario Then the behave context should not have a parameter "local_name" behave-1.2.6/features/directory_layout.advanced.feature0000644000076600000240000001212513244555737023415 0ustar jensstaff00000000000000Feature: Advanced, more complex directory layout (Variant 2) As a story/test writer I want a deeper, more structured directory structure when many feature files exist So that I have the parts better under control (more managable) . ADVANCED, MORE COMPLEX DIRECTORY LAYOUT STRUCTURE: . features/ . +-- group1.features/ . | +-- *.feature . +-- group2.features/ . | +-- *.feature . +-- steps/*.py # Step definitions or step-library imports. . +-- environment.py # OPTIONAL: environment setup/hooks. . . SEE ALSO: . * http://pythonhosted.org/behave/gherkin.html#layout-variations . . RELATED: . * issue #99: Layout variation "a directory containing your feature files" ... @setup Scenario: Setup directory structure Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass @step('{word:w} step fails') def step_fails(context, word): assert False, "XFAIL-STEP" """ And a file named "features/steps/environment_steps.py" with: """ from behave import step @step('environment setup was done') def step_ensure_environment_setup(context): assert context.setup_magic == 42 """ And a file named "features/environment.py" with: """ def before_all(context): context.setup_magic = 42 """ And a file named "features/group1/alice.feature" with: """ Feature: Alice Scenario: A1 Given a step passes When another step passes Then a step passes Scenario: A2 Then environment setup was done """ And a file named "features/group1/bob.feature" with: """ Feature: Bob Scenario: B1 When a step passes Then another step passes """ And a file named "features/group2/charly.feature" with: """ Feature: Charly Scenario: C1 Given another step passes Then a step passes """ Scenario: Run behave with feature directory When I run "behave -f progress features/" Then it should pass with: """ 3 features passed, 0 failed, 0 skipped 4 scenarios passed, 0 failed, 0 skipped 8 steps passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Run behave with feature subdirectory (CASE 1) When I run "behave -f progress features/group1/" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 3 scenarios passed, 0 failed, 0 skipped 6 steps passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Run behave with feature subdirectory (CASE 2) When I run "behave -f progress features/group2/" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Run behave with one feature file When I run "behave -f progress features/group1/alice.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped 4 steps passed, 0 failed, 0 skipped, 0 undefined """ When I run "behave -f progress features/group2/charly.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Run behave with two feature files (CASE 1) Given a file named "one.featureset" with: """ features/group1/alice.feature features/group2/charly.feature """ When I run "behave -f progress @one.featureset" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 3 scenarios passed, 0 failed, 0 skipped 6 steps passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Run behave with two feature files (CASE 2: different ordering) Given a file named "two.featureset" with: """ features/group2/charly.feature features/group1/alice.feature """ When I run "behave -f progress @two.featureset" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 3 scenarios passed, 0 failed, 0 skipped 6 steps passed, 0 failed, 0 skipped, 0 undefined """ behave-1.2.6/features/directory_layout.basic.feature0000644000076600000240000000467713244555737022746 0ustar jensstaff00000000000000Feature: Basic directory layout (Variant 1) As a story/test writer I want a simple, non-deep directory structure So that I can easily get an overview which stories/tests exist . BASIC DIRECTORY LAYOUT STRUCTURE: . features/ . +-- steps/*.py # Step definitions or step-library imports. . +-- *.feature # Feature files. . +-- environment.py # OPTIONAL: environment setup/hooks. . . SEE ALSO: . * http://pythonhosted.org/behave/gherkin.html#layout-variations @setup Scenario: Setup directory structure Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass @step('{word:w} step fails') def step_fails(context, word): assert False, "XFAIL-STEP" """ And a file named "features/alice.feature" with: """ Feature: Alice Scenario: A1 Given a step passes When another step passes Then a step passes """ And a file named "features/bob.feature" with: """ Feature: Bob Scenario: B1 When a step passes Then another step passes """ Scenario: Run behave with feature directory When I run "behave -f progress features/" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped 5 steps passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Run behave with one feature file When I run "behave -f progress features/alice.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Run behave with two feature files When I run "behave -f progress features/alice.feature features/bob.feature" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped 5 steps passed, 0 failed, 0 skipped, 0 undefined """ behave-1.2.6/features/directory_layout.basic2.feature0000644000076600000240000000555013244555737023017 0ustar jensstaff00000000000000Feature: Basic directory layout (Variant 1B) As a story/test writer I want a simple, non-deep directory structure So that I can easily get an overview which stories/tests exist . BASIC DIRECTORY LAYOUT STRUCTURE: . testing/features/ . +-- steps/*.py # Step definitions or step-library imports. . +-- *.feature # Feature files. . +-- environment.py # OPTIONAL: environment setup/hooks. . . SEE ALSO: . * http://pythonhosted.org/behave/gherkin.html#layout-variations @setup Scenario: Setup directory structure Given a new working directory And a file named "testing/features/steps/steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass @step('{word:w} step fails') def step_fails(context, word): assert False, "XFAIL-STEP" """ And a file named "testing/features/alice.feature" with: """ Feature: Alice Scenario: A1 Given a step passes When another step passes Then a step passes """ And a file named "testing/features/bob.feature" with: """ Feature: Bob Scenario: B1 When a step passes Then another step passes """ Scenario: Run behave with testing directory When I run "behave -f progress testing/" Then it should fail with: """ ConfigError: No steps directory in '{__WORKDIR__}/testing' """ Scenario: Run behave with feature subdirectory When I run "behave -f progress testing/features/" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped 5 steps passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Run behave with one feature file When I run "behave -f progress testing/features/alice.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Run behave with two feature files Given a file named "one.featureset" with: """ testing/features/alice.feature testing/features/bob.feature """ When I run "behave -f progress @one.featureset" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped 5 steps passed, 0 failed, 0 skipped, 0 undefined """ behave-1.2.6/features/environment.py0000644000076600000240000000415613244555737017616 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave import platform import sys import six # -- MATCHES ANY TAGS: @use.with_{category}={value} # NOTE: active_tag_value_provider provides category values for active tags. python_version = "%s.%s" % sys.version_info[:2] active_tag_value_provider = { "python2": str(six.PY2).lower(), "python3": str(six.PY3).lower(), "python.version": python_version, # -- python.implementation: cpython, pypy, jython, ironpython "python.implementation": platform.python_implementation().lower(), "pypy": str("__pypy__" in sys.modules).lower(), "os": sys.platform, } active_tag_matcher = ActiveTagMatcher(active_tag_value_provider) # ----------------------------------------------------------------------------- # HOOKS: # ----------------------------------------------------------------------------- def before_all(context): # -- SETUP ACTIVE-TAG MATCHER (with userdata): # USE: behave -D browser=safari ... setup_active_tag_values(active_tag_value_provider, context.config.userdata) setup_python_path() setup_context_with_global_params_test(context) setup_command_shell_processors4behave() def before_feature(context, feature): if active_tag_matcher.should_exclude_with(feature.tags): feature.skip(reason=active_tag_matcher.exclude_reason) def before_scenario(context, scenario): if active_tag_matcher.should_exclude_with(scenario.effective_tags): scenario.skip(reason=active_tag_matcher.exclude_reason) # ----------------------------------------------------------------------------- # SPECIFIC FUNCTIONALITY: # ----------------------------------------------------------------------------- def setup_context_with_global_params_test(context): context.global_name = "env:Alice" context.global_age = 12 def setup_python_path(): # -- NEEDED-FOR: formatter.user_defined.feature import os PYTHONPATH = os.environ.get("PYTHONPATH", "") os.environ["PYTHONPATH"] = "."+ os.pathsep + PYTHONPATH behave-1.2.6/features/exploratory_testing.with_table.feature0000644000076600000240000001137313244555737024522 0ustar jensstaff00000000000000Feature: Exploratory Testing with Tables and Table Annotations As a tester I want sometimes to explore a problem domain And see not only the expected results But also the actual results in a table. . HINT: Does not work with monochrome format in pretty formatter: . behave -f pretty --no-color ... . behave -c ... @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/explore_with_table_steps.py" with: """ from behave import given, when, then, step database = { "Alice": { "age": 10 }, "Bob": { "age": 11 }, } @when('I query the database with') def step_query_database_and_update_table(context): assert context.table, "REQUIRE: table" context.table.require_column("Name") age_index = context.table.ensure_column_exists("Age") for index, row in enumerate(context.table.rows): name = row["Name"] person = database.get(name, None) if person: row.cells[age_index] = str(person["age"]) context.current_table = context.table @when('I add {number:n} to column "{column}"') def step_query_and_annotate_database(context, number, column): assert context.current_table, "REQUIRE: current_table" age_index = context.current_table.ensure_column_exists("Age") for row in context.current_table.rows: value = int(row.cells[age_index]) or 0 row.cells[age_index] = str(value + number) @step('note that the "{name}" column was added to the table') def step_note_that_column_was_added(context, name): assert context.current_table.has_column(name) @then('note that the "{name}" column was modified in the table') def step_note_that_column_was_modified(context, name): pass @then('I inspect the table') def step_inspect_table(context): assert context.current_table context.table = context.current_table @then('the table contains') def step_inspect_table(context): assert context.table, "REQUIRE: table" assert context.current_table, "REQUIRE: current_table" assert context.table == context.current_table """ Scenario: Add table column with new data in a step and ensure changes are shown Given a file named "features/table.set_column_data.feature" with: """ Feature: Scenario: When I query the database with: | Name | | Alice | | Bob | Then note that the "Age" column was added to the table """ When I run "behave -f plain features/table.set_column_data.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ | Name | Age | | Alice | 10 | | Bob | 11 | """ But note that "the Age table column was added by the step" Scenario: Modify table cells in a column and ensure changes are shown Given a file named "features/table.modify_column.feature" with: """ Feature: Scenario: When I query the database with: | Name | Age | | Alice | 222 | | Bob | 333 | Then note that the "Age" column was modified in the table Then the table contains: | Name | Age | | Alice | 10 | | Bob | 11 | """ When I run "behave -f plain features/table.modify_column.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ | Name | Age | | Alice | 10 | | Bob | 11 | """ But note that "the Age column was modified in the table" And the command output should not contain: """ | Name | Age | | Alice | 222 | | Bob | 333 | """ Scenario: Modify table cells in a column (formatter=pretty with colors) When I run "behave -f pretty features/table.modify_column.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ | Name | Age | | Alice | 10 | | Bob | 11 | """ But note that "the Age column was modified in the table" behave-1.2.6/features/feature.description.feature0000644000076600000240000000000013244555737022212 0ustar jensstaff00000000000000behave-1.2.6/features/feature.exclude_from_run.feature0000644000076600000240000000634713244555737023253 0ustar jensstaff00000000000000Feature: Exclude Feature from Test Run As a test writer I want sometimes to decide at runtime that a feature is excluded from a test run So that the command-line configuration becomes simpler (and auto-configuration is supported). . MECHANISM: . The "before_feature()" hook can decide just before a feature should run . that the feature should be excluded from the test-run. . NOTE: Hooks are not called in dry-run mode. . . RATIONALE: . There are certain situations where it is better to skip a feature . than to run and fail the feature. . . Reasons for these cases are of often test environment related: . * test environment does not fulfill the desired criteria . * used testbed does not fulfill test requirements . . Instead of providing the exclude-feature selection on the command-line, . the test (environment) and configuration logic should determine . if a test should be excluded (as auto-configuration functionality). . . EXAMPLE: . Certain features should not run on Windows (or Linux, ...). . . EVALUATION ORDER: . Before the user can exclude a feature from a test-run, . additional mechanisms decide, if the feature is part of the selected run-set. . These are: . * tags . * ... . . RELATED: . * features/scenario.exclude_from_run.feature @setup Scenario: Given a new working directory And a file named "features/alice.feature" with: """ Feature: Alice Scenario: Alice in Wonderland Given a step passes When another step passes Then some step passes Scenario: Alice and Bob Given another step passes """ And a file named "features/bob.feature" with: """ Feature: Bob Scenario: Bob in Berlin Given some step passes When another step passes """ And a file named "features/steps/steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass """ Scenario: Exclude a feature from the test run (using: before_feature() hook) Given a file named "features/environment.py" with: """ import sys def should_exclude_feature(feature): if "Alice" in feature.name: return True return False def before_feature(context, feature): if should_exclude_feature(feature): sys.stdout.write("EXCLUDED-BY-USER: Feature %s\n" % feature.name) feature.skip() """ When I run "behave -f plain -T features/" Then it should pass with: """ 1 feature passed, 0 failed, 1 skipped 1 scenario passed, 0 failed, 2 skipped 2 steps passed, 0 failed, 4 skipped, 0 undefined """ And the command output should contain: """ EXCLUDED-BY-USER: Feature Alice """ behave-1.2.6/features/fixture.feature0000644000076600000240000004160213244555737017740 0ustar jensstaff00000000000000Feature: Fixture As a BDD writer I want to be able to use fixtures (scope guard) So that it is easy to provide setup/cleanup functionality. . SPECIFICATION: . A fixture provides common test-support functionality. . It contains the setup functionality and in many cases a teardown/cleanup part. . To simplify writing fixtures, setup and cleanup part are in the same function . (same as: @contextlib.contextmanager, @pytest.fixture). . . In most case, you want to use a fixture-tag in a "*.feature" file (in Gherkin) . to mark that a scenario or feature should use a specific fixture. . . EXAMPLE: . . # -- FILE: features/environment.py . @fixture . def fixture_foo(context, *args, **kwargs): # -- CASE: generator-function . the_fixture = setup_fixture_foo(*args, **kwargs) . context.foo = the_fixture . yield the_fixture . cleanup_fixture_foo(the_fixture) # -- SKIPPED: On fixture-setup-error . . @fixture(name="fixture.bar") . def fixture_bar(context, *args, **kwargs): # -- CASE: function . the_fixture = setup_fixture_bar(*args, **kwargs) . context.bar = the_fixture . context.add_cleanup(the_fixture.cleanup) . return the_fixture . . # -- ENVIRONMENT-HOOKS: . def before_tag(context, tag): . if tag == "fixture.foo": . # -- NOTE: Calls setup_fixture part (until yield statement) and . # registers cleanup_fixture which will be called when . # context scope is left (after scenario, feature or test-run). . the_fixture = use_fixture(fixture_foo, context) @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step(u'the browser is "{browser_name}"') def step_browser_is(context, browser_name): assert context.browser == browser_name @step(u'no browser info exists') def step_no_browser_info(context): assert not hasattr(context, "browser") @step(u'{word:w} step passes') def step_passes(context, word): pass """ And a file named "behave.ini" with: """ [behave] show_timings = false """ And an empty file named "features/environment.py" Scenario: Use fixture with generator-function (setup/cleanup) Given a file named "features/environment.py" with: """ from __future__ import print_function from behave.fixture import fixture, use_fixture @fixture(name="browser.firefox") def browser_firefox(context): print("FIXTURE-SETUP: browser.firefox") context.browser = "firefox" yield print("FIXTURE-CLEANUP: browser.firefox") # -- SCOPE-CLEANUP OR EXPLICIT: del context.browser def before_tag(context, tag): if tag == "fixture.browser.firefox": use_fixture(browser_firefox, context) """ And a file named "features/alice.feature" with: """ Feature: Fixture setup/teardown @fixture.browser.firefox Scenario: Fixture with browser=firefox Given the browser is "firefox" Scenario: Fixture Cleanup check Then no browser info exists """ When I run "behave -f plain features/alice.feature" Then it should pass with: """ 2 scenarios passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ FIXTURE-SETUP: browser.firefox Scenario: Fixture with browser=firefox Given the browser is "firefox" ... passed FIXTURE-CLEANUP: browser.firefox """ Scenario: Use fixture with function (setup-only) Given a file named "features/environment.py" with: """ from __future__ import print_function from behave.fixture import fixture, use_fixture @fixture(name="browser.chrome") def browser_chrome(context): # -- CASE: Setup-only print("FIXTURE-SETUP: browser.chrome") context.browser = "chrome" def before_tag(context, tag): if tag == "fixture.browser.chrome": use_fixture(browser_chrome, context) """ And a file named "features/bob.feature" with: """ Feature: Fixture setup only @fixture.browser.chrome Scenario: Fixture with browser=chrome Given the browser is "chrome" Scenario: Fixture Cleanup check Then no browser info exists """ When I run "behave -f plain features/bob.feature" Then it should pass with: """ 2 scenarios passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ FIXTURE-SETUP: browser.chrome Scenario: Fixture with browser=chrome Given the browser is "chrome" ... passed Scenario: Fixture Cleanup check Then no browser info exists ... passed """ Scenario: Use fixture (case: feature) Given a file named "features/environment.py" with: """ from __future__ import print_function from behave.fixture import fixture, use_fixture @fixture def foo(context): print("FIXTURE-SETUP: foo") yield print("FIXTURE-CLEANUP: foo") def before_tag(context, tag): if tag == "fixture.foo": use_fixture(foo, context) def after_feature(context, feature): print("HOOK-CALLED: after_feature: %s" % feature.name) """ And a file named "features/use2.feature" with: """ @fixture.foo Feature: Use Fixture for Feature Scenario: Given a step passes Scenario: Then another step passes """ When I run "behave -f plain features/use2.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ FIXTURE-SETUP: foo Feature: Use Fixture for Feature Scenario: Given a step passes ... passed Scenario: Then another step passes ... passed HOOK-CALLED: after_feature: Use Fixture for Feature FIXTURE-CLEANUP: foo """ But note that "the fixture-cleanup after the feature" Scenario: Use fixture (case: step) Given a file named "features/environment.py" with: """ from __future__ import print_function def after_scenario(context, scenario): print("HOOK-CALLED: after_scenario: %s" % scenario.name) """ And an empty file named "behave4me/__init__.py" And a file named "behave4me/fixtures.py" with: """ from __future__ import print_function from behave import fixture @fixture def foo(context, name): print("FIXTURE-SETUP: foo%s" % name) yield 42 print("FIXTURE-CLEANUP: foo%s" % name) """ And a file named "features/steps/fixture_steps2.py" with: """ from __future__ import print_function from behave import step, use_fixture from behave4me.fixtures import foo @step(u'I use fixture "{fixture_name}"') def step_use_fixture(context, fixture_name): if fixture_name.startswith("foo"): name = fixture_name.replace("foo", "") the_fixture = use_fixture(foo, context, name) setattr(context, fixture_name, the_fixture) else: raise LookupError("Unknown fixture: %s" % fixture_name) """ And a file named "features/use3.feature" with: """ @fixture.foo Feature: Scenario: Use Fixture Given I use fixture "foo_1" Then a step passes Scenario: Then another step passes """ When I run "behave -f plain features/use3.feature" Then it should pass with: """ 2 scenarios passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: Scenario: Use Fixture Given I use fixture "foo_1" ... passed Then a step passes ... passed HOOK-CALLED: after_scenario: Use Fixture FIXTURE-CLEANUP: foo_1 """ But note that "the fixture-cleanup occurs after the scenario" Scenario: Use multiple fixtures (with setup/cleanup) Given a file named "features/environment.py" with: """ from __future__ import print_function from behave.fixture import fixture, use_fixture @fixture def foo(context): print("FIXTURE-SETUP: foo") yield print("FIXTURE-CLEANUP: foo") @fixture def bar(context): def cleanup_bar(): print("FIXTURE-CLEANUP: bar") print("FIXTURE-SETUP: bar") context.add_cleanup(cleanup_bar) def before_tag(context, tag): if tag == "fixture.foo": use_fixture(foo, context) elif tag == "fixture.bar": use_fixture(bar, context) """ And a file named "features/two.feature" with: """ Feature: Use multiple Fixtures @fixture.foo @fixture.bar Scenario: Two Fixtures Given a step passes Scenario: Then another step passes """ When I run "behave -f plain features/two.feature" Then it should pass with: """ 2 scenarios passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ FIXTURE-SETUP: foo FIXTURE-SETUP: bar Scenario: Two Fixtures Given a step passes ... passed FIXTURE-CLEANUP: bar FIXTURE-CLEANUP: foo """ But note that "the fixture-cleanup occurs in reverse order (LIFO)" Scenario: Use same fixture twice with different args Given a file named "features/environment.py" with: """ from __future__ import print_function from behave.fixture import fixture, use_fixture @fixture def foo(context, name): name2 = "foo%s" % name print("FIXTURE-SETUP: %s" % name2) setattr(context, name2, 1) yield print("FIXTURE-CLEANUP: %s" % name2) def before_tag(context, tag): if tag.startswith("fixture.foo"): # -- FIXTURE-TAG SCHEMA: fixture.foo* name = tag.replace("fixture.foo", "") use_fixture(foo, context, name) """ And a file named "features/two.feature" with: """ Feature: Use same Fixture twice @fixture.foo_1 @fixture.foo_2 Scenario: Use Fixtures Given a step passes Scenario: Then another step passes """ When I run "behave -f plain features/two.feature" Then it should pass with: """ 2 scenarios passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ FIXTURE-SETUP: foo_1 FIXTURE-SETUP: foo_2 Scenario: Use Fixtures Given a step passes ... passed FIXTURE-CLEANUP: foo_2 FIXTURE-CLEANUP: foo_1 """ But note that "the fixture-cleanup occurs in reverse order (LIFO)" Scenario: Use invalid fixture (with two yields or more) Given a file named "features/environment.py" with: """ from __future__ import print_function from behave.fixture import fixture, use_fixture @fixture def invalid_fixture(context): # -- CASE: Setup-only print("FIXTURE-SETUP: invalid") yield print("FIXTURE-CLEANUP: invalid") yield print("OOPS: Too many yields used") def before_tag(context, tag): if tag == "fixture.invalid": use_fixture(invalid_fixture, context) """ And a file named "features/invalid_fixture.feature" with: """ Feature: Fixture with more than one yield @fixture.invalid Scenario: Use invalid fixture Given a step passes Scenario: Then another step passes """ When I run "behave -f plain features/invalid_fixture.feature" Then it should fail with: """ 1 scenario passed, 1 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ FIXTURE-SETUP: invalid Scenario: Use invalid fixture Given a step passes ... passed FIXTURE-CLEANUP: invalid CLEANUP-ERROR in cleanup_fixture: InvalidFixtureError: Has more than one yield: When another step Examples: | outcome1 | outcome2 | | passes | passes | | passes | fails | """ When I run "behave --dry-run -f steps.usage features/use_scenario_outline.feature" Then it should pass with: """ 0 scenarios passed, 0 failed, 0 skipped, 2 untested 0 steps passed, 0 failed, 0 skipped, 0 undefined, 4 untested """ And the command output should contain: """ @step('{word:w} step passes') # features/steps/passing_steps.py:3 Given a step passes # features/use_scenario_outline.feature:3 When another step passes # features/use_scenario_outline.feature:4 @step('{word:w} step fails') # features/steps/passing_steps.py:7 When another step fails # features/use_scenario_outline.feature:4 """ But the command output should not contain: """ @step('{word:w} step passes') # features/steps/passing_steps.py:3 Given a step passes # features/use_scenario_outline.feature:3 When another step passes # features/use_scenario_outline.feature:4 Given a step passes # features/use_scenario_outline.feature:3 """ @use_outline Scenario: Scenario Outlines should not cause duplicated entries for undefined steps Scenario Outlines generate Scenarios that use the same step multiple times. This duplication should not be listed. Given a file named "features/scenario_outline_with_undefined.feature" with: """ Feature: Scenario Outline: Given a step is When another step is Examples: | status1 | status2 | | undefined | undefined | | undefined | undefined | """ When I run "behave --dry-run -f steps.usage features/scenario_outline_with_undefined.feature" Then it should fail with: """ 0 scenarios passed, 0 failed, 0 skipped, 2 untested 0 steps passed, 0 failed, 0 skipped, 4 undefined """ And the command output should contain: """ UNDEFINED STEPS[2]: Given a step is undefined # features/scenario_outline_with_undefined.feature:3 When another step is undefined # features/scenario_outline_with_undefined.feature:4 """ But the command output should not contain: """ UNDEFINED STEPS[2]: Given a step is undefined # features/scenario_outline_with_undefined.feature:3 Given a step is undefined # features/scenario_outline_with_undefined.feature:3 When another step is undefined # features/scenario_outline_with_undefined.feature:4 When another step is undefined # features/scenario_outline_with_undefined.feature:4 """ behave-1.2.6/features/formatter.tags.feature0000644000076600000240000001073413244555737021214 0ustar jensstaff00000000000000@sequential Feature: Tags Formatter (Tag Counts) As a tester I want to obtain a quick overview which tags are used (and how often) So that I can better use tags on command-line NOTE: Primarily intended for dry-run mode. @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step('a step passes') def step_passes(context): pass """ And a file named "features/alice.feature" with: """ @one Feature: Alice @setup Scenario: Setup Feature Given a step passes @wip Scenario: A1 with tags: @wip (inherited: @one) Given a step passes @foo @wip Scenario: A2 with tags: @foo, @wip (inherited: @one) When a step passes @foo @bar Scenario: A3 with tags: @foo, @bar (inherited: @one) Then a step passes """ And a file named "features/bob.feature" with: """ @two Feature: Bob Scenario: B1 without tags (inherited: @two) Given a step passes @foo @one Scenario: B2 with tags: @foo, @one (inherited: @two) When a step passes """ Scenario: Use Tags formatter to get an overview of used tags When I run "behave -f tags --dry-run features/" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 2 untested 0 scenarios passed, 0 failed, 0 skipped, 6 untested """ And the command output should contain: """ TAG COUNTS (alphabetically sorted): @bar 1 (used for scenario) @foo 3 (used for scenario) @one 2 (used for feature: 1, scenario: 1) @setup 1 (used for scenario) @two 1 (used for feature) @wip 2 (used for scenario) """ But note that "tags inherited from its feature are (normally) not counted." Scenario: Use Tags formatter together with another formatter When I run "behave -f tags -f plain -T features/" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 6 scenarios passed, 0 failed, 0 skipped """ And the command output should contain: """ Feature: Alice Scenario: Setup Feature Given a step passes ... passed Scenario: A1 with tags: @wip (inherited: @one) Given a step passes ... passed Scenario: A2 with tags: @foo, @wip (inherited: @one) When a step passes ... passed Scenario: A3 with tags: @foo, @bar (inherited: @one) Then a step passes ... passed Feature: Bob Scenario: B1 without tags (inherited: @two) Given a step passes ... passed Scenario: B2 with tags: @foo, @one (inherited: @two) When a step passes ... passed TAG COUNTS (alphabetically sorted): @bar 1 (used for scenario) @foo 3 (used for scenario) @one 2 (used for feature: 1, scenario: 1) @setup 1 (used for scenario) @two 1 (used for feature) @wip 2 (used for scenario) """ Scenario: Use Tags formatter when tags are selected When I run "behave -f tags --tags=@setup,@wip features/" Then it should pass with: """ 1 feature passed, 0 failed, 1 skipped 3 scenarios passed, 0 failed, 3 skipped """ And the command output should contain: """ TAG COUNTS (alphabetically sorted): @bar 1 (used for scenario) @foo 3 (used for scenario) @one 2 (used for feature: 1, scenario: 1) @setup 1 (used for scenario) @two 1 (used for feature) @wip 2 (used for scenario) """ And note that "all tags are detected even from skipped features and scenarios" behave-1.2.6/features/formatter.tags_location.feature0000644000076600000240000001470413244555737023105 0ustar jensstaff00000000000000@sequential Feature: TagsLocation Formatter As a tester I want to know where and in which context tags are used So that I better understand how to use tags in feature files and on command-line NOTE: Primarily intended for dry-run mode. @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step('a step passes') def step_passes(context): pass """ And a file named "features/alice.feature" with: """ @one Feature: Alice @setup Scenario: Setup Feature Given a step passes @wip Scenario: A1 with tags: @wip (inherited: @one) Given a step passes @foo @wip Scenario: A2 with tags: @foo, @wip (inherited: @one) When a step passes @foo @bar Scenario: A3 with tags: @foo, @bar (inherited: @one) Then a step passes """ And a file named "features/bob.feature" with: """ @two Feature: Bob Scenario: B1 without tags (inherited: @two) Given a step passes @foo @one Scenario: B2 with tags: @foo, @one (inherited: @two) When a step passes """ Scenario: Use TagsLocation formatter to get an overview where tags are used When I run "behave -f tags.location --dry-run features/" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 2 untested 0 scenarios passed, 0 failed, 0 skipped, 6 untested """ And the command output should contain: """ TAG LOCATIONS (alphabetically ordered): @bar: features/alice.feature:18 Scenario: A3 with tags: @foo, @bar (inherited: @one) @foo: features/alice.feature:14 Scenario: A2 with tags: @foo, @wip (inherited: @one) features/alice.feature:18 Scenario: A3 with tags: @foo, @bar (inherited: @one) features/bob.feature:8 Scenario: B2 with tags: @foo, @one (inherited: @two) @one: features/alice.feature:2 Feature: Alice features/bob.feature:8 Scenario: B2 with tags: @foo, @one (inherited: @two) @setup: features/alice.feature:5 Scenario: Setup Feature @two: features/bob.feature:2 Feature: Bob @wip: features/alice.feature:9 Scenario: A1 with tags: @wip (inherited: @one) features/alice.feature:14 Scenario: A2 with tags: @foo, @wip (inherited: @one) """ But note that "tags inherited from its feature are (normally) not counted." Scenario: Use TagsLocation formatter together with another formatter When I run "behave -f tags.location -f plain -T features/" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 6 scenarios passed, 0 failed, 0 skipped """ And the command output should contain: """ Feature: Alice Scenario: Setup Feature Given a step passes ... passed Scenario: A1 with tags: @wip (inherited: @one) Given a step passes ... passed Scenario: A2 with tags: @foo, @wip (inherited: @one) When a step passes ... passed Scenario: A3 with tags: @foo, @bar (inherited: @one) Then a step passes ... passed Feature: Bob Scenario: B1 without tags (inherited: @two) Given a step passes ... passed Scenario: B2 with tags: @foo, @one (inherited: @two) When a step passes ... passed """ And the command output should contain: """ TAG LOCATIONS (alphabetically ordered): @bar: features/alice.feature:18 Scenario: A3 with tags: @foo, @bar (inherited: @one) @foo: features/alice.feature:14 Scenario: A2 with tags: @foo, @wip (inherited: @one) features/alice.feature:18 Scenario: A3 with tags: @foo, @bar (inherited: @one) features/bob.feature:8 Scenario: B2 with tags: @foo, @one (inherited: @two) @one: features/alice.feature:2 Feature: Alice features/bob.feature:8 Scenario: B2 with tags: @foo, @one (inherited: @two) @setup: features/alice.feature:5 Scenario: Setup Feature @two: features/bob.feature:2 Feature: Bob @wip: features/alice.feature:9 Scenario: A1 with tags: @wip (inherited: @one) features/alice.feature:14 Scenario: A2 with tags: @foo, @wip (inherited: @one) """ Scenario: Use TagsLocation formatter when tags are selected When I run "behave -f tags.location --tags=@setup,@wip features/" Then it should pass with: """ 1 feature passed, 0 failed, 1 skipped 3 scenarios passed, 0 failed, 3 skipped """ And the command output should contain: """ TAG LOCATIONS (alphabetically ordered): @bar: features/alice.feature:18 Scenario: A3 with tags: @foo, @bar (inherited: @one) @foo: features/alice.feature:14 Scenario: A2 with tags: @foo, @wip (inherited: @one) features/alice.feature:18 Scenario: A3 with tags: @foo, @bar (inherited: @one) features/bob.feature:8 Scenario: B2 with tags: @foo, @one (inherited: @two) @one: features/alice.feature:2 Feature: Alice features/bob.feature:8 Scenario: B2 with tags: @foo, @one (inherited: @two) @setup: features/alice.feature:5 Scenario: Setup Feature @two: features/bob.feature:2 Feature: Bob @wip: features/alice.feature:9 Scenario: A1 with tags: @wip (inherited: @one) features/alice.feature:14 Scenario: A2 with tags: @foo, @wip (inherited: @one) """ And note that "all tags are detected even from skipped features and scenarios" behave-1.2.6/features/formatter.user_defined.feature0000644000076600000240000001460713244555737022715 0ustar jensstaff00000000000000Feature: Use a user-defined Formatter As a behave user I want to be able to provide own, user-defined formatters So that I am able to add a new, user-defined formatters when I need other output formats (or override existing ones). . SPECIFICATION: . * A user-defined formatter must inherit from the "Formatter" class. . * A user-defined formatter can be specified on command-line . by using a scoped class name as value for the '--format' option. . * A user-defined formatter can be registered by name . by using the "behave.formatters" section in the behave config file. . . SCOPED CLASS NAME (for formatter.class): . * my.module_name:ClassName (preferred: single colon separator) . * my.module_name::ClassName (alternative: double colon separator) @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/passing_steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass @step('{word:w} step fails') def step_fails(context, word): assert False, "XFAIL-STEP" """ And a file named "features/passing.feature" with: """ Feature: Scenario: Alice Given a step passes When another step passes Scenario: Bob Then some step passes """ And an empty file named "behave_ext/__init__.py" And a file named "behave_ext/formatter_one.py" with: """ from behave.formatter.base import Formatter class NotAFormatter(object): pass class SuperFormatter(Formatter): name = "super" description = "Super-duper formatter." """ And a file named "behave_ext/formatter_foo.py" with: """ from behave.formatter.base import Formatter class FooFormatter(Formatter): name = "foo" description = "User-specific FOO formatter." class FooFormatter2(Formatter): description = "User-specific FOO2 formatter." """ And a file named "behave_ext/formatter_bar.py" with: """ from behave.formatter.base import Formatter class BarFormatter(Formatter): description = "User-specific BAR formatter." """ @use_formatter.class Scenario: Use a known, valid user-defined formatter with scoped class name When I run "behave -f behave_ext.formatter_one:SuperFormatter features/passing.feature" Then it should pass with: """ 2 scenarios passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ @use_formatter.class Scenario: Use a known, valid user-defined formatter (with double colon) ALTERNATIVE: Can currently use a double colon as separator, too. When I run "behave -f behave_ext.formatter_one::SuperFormatter features/passing.feature" Then it should pass with: """ 2 scenarios passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ @use_formatter.class Scenario Outline: Use built-in formatter "" like a user-defined formatter When I run "behave -f features/passing.feature" Then it should pass with: """ 2 scenarios passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ Examples: | formatter.name | formatter.class | | plain | behave.formatter.plain:PlainFormatter | | pretty | behave.formatter.pretty:PrettyFormatter | @problematic @use_formatter.class Scenario Outline: Use a problematic user-defined formatter () When I run "behave -f features/passing.feature" Then it should fail with: """ error: format= is unknown """ Examples: | formatter.class | case | | my.unknown_module:SomeFormatter | Unknown module | | behave_ext.formatter_one:UnknownClass | Unknown class | | behave_ext.formatter_one:NotAFormatter | Invalid Formatter class | @formatter.registered_by_name Scenario Outline: Register user-defined formatter by name: Given a file named "behave.ini" with: """ [behave.formatters] foo = behave_ext.formatter_foo:FooFormatter foo2 = behave_ext.formatter_foo:FooFormatter2 bar = behave_ext.formatter_bar:BarFormatter """ And note that "the schema: 'formatter.name = formatter.class' is used" When I run "behave -f features/passing.feature" Then it should pass with: """ 2 scenarios passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ But the command output should not contain: """ error: format= is unknown """ Examples: | formatter.name | Comment | | foo | First user-defined, registered formatter. | | bar | Last user-defined, registered formatter. | @formatter.registered_by_name Scenario: Help-Format shows description of registered-by-name formatters Given a file named "behave.ini" with: """ [behave.formatters] foo = behave_ext.formatter_foo:FooFormatter bar = behave_ext.formatter_bar:BarFormatter """ When I run "behave -f help" Then it should pass with: """ Available formatters: """ And the command output should contain: """ bar User-specific BAR formatter. """ And the command output should contain: """ foo User-specific FOO formatter. """ @problematic @formatter.registered_by_name Scenario Outline: Use problematic, registered-by-name formatter: Given a file named "behave.ini" with: """ [behave.formatters] = """ When I run "behave -f features/passing.feature" Then it should fail with: """ error: format= is unknown """ Examples: | formatter.name | formatter.class | case | | unknown1 | my.unknown_module:SomeFormatter | Unknown module | | unknown2 | behave_ext.formatter_one:UnknownClass | Unknown class | | invalid1 | behave_ext.formatter_one:NotAFormatter | Invalid Formatter class | behave-1.2.6/features/i18n.unicode_problems.feature0000644000076600000240000003340413244555737022362 0ustar jensstaff00000000000000Feature: Internationalization (i18n) and Problems with Unicode Strings . POTENTIAL PROBLEM AREAS: . * Feature, scenario, step names with problematic chars . * Tags with problematic chars . * step raises exception with problematic text (output capture) . * step generates output with problematic and some step fails (stdout capture) . * filenames with problematic chars: feature files, steps files . . CHECKED FORMATTERS and REPORTERS: . * plain . * pretty . * junit (used via "behave.ini" defaults) @setup Scenario: Feature Setup Given a new working directory And a file named "behave.ini" with: """ [behave] show_timings = false show_skipped = false show_source = true junit = true """ And a file named "features/steps/passing_steps.py" with: """ # -*- coding: UTF-8 -*- from behave import step @step(u'{word:w} step passes') def step_passes(context, word): pass @step(u'{word:w} step passes with "{text}"') def step_passes_with_text(context, word, text): pass @step(u'{word:w} step fails') def step_fails(context, word): assert False, "XFAIL" @step(u'{word:w} step fails with "{text}"') def step_fails_with_text(context, word, text): assert False, u"XFAIL: "+ text """ And a file named "features/steps/step_write_output.py" with: """ # -*- coding: UTF-8 -*- from __future__ import print_function from behave import step import six @step(u'I write text "{text}" to stdout') def step_write_text(context, text): if six.PY2 and isinstance(text, six.text_type): text = text.encode("utf-8", "replace") print(text) @step(u'I write bytes "{data}" to stdout') def step_write_bytes(context, data): if isinstance(data, six.text_type): data = data.encode("unicode-escape", "replace") print(data) """ Scenario Outline: Problematic scenario name: (case: passed, ) Given a file named "features/scenario_name_problematic_and_pass.feature" with: """ Feature: Scenario: Given a step passes """ When I run "behave -f features/scenario_name_problematic_and_pass.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped """ And the command output should contain: """ Scenario: """ Examples: | format | scenario.name | | plain | Café | | pretty | Ärgernis ist überall | Scenario Outline: Problematic scenario name: (case: failed, ) Given a file named "features/scenario_name_problematic_and_fail.feature" with: """ Feature: Scenario: Given a step fails """ When I run "behave -f features/scenario_name_problematic_and_fail.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped """ And the command output should contain: """ Scenario: """ Examples: | format | scenario.name | | plain | Café | | pretty | Ärgernis ist überall | Scenario Outline: Problematic step: (case: passed, ) Given a file named "features/step_problematic_and_pass.feature" with: """ Feature: Scenario: Given a step passes with "" """ When I run "behave -f features/step_problematic_and_pass.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ Examples: | format | step.text | | plain | Café | | pretty | Ärgernis ist überall | Scenario Outline: Problematic step: (case: fail, ) Given a file named "features/step_problematic_and_fail.feature" with: """ Feature: Scenario: Given a step fails with "" """ When I run "behave -f features/step_problematic_and_fail.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped 0 steps passed, 1 failed, 0 skipped, 0 undefined """ Examples: | format | step.text | | plain | Café | | pretty | Ärgernis ist überall | @problematic.feature_filename @not.with_os=win32 Scenario Outline: Problematic feature filename: (case: pass, ) Given a file named "features/_and_pass.feature" with: """ Feature: Scenario: Given a step passes """ When I run "behave -f features/_and_pass.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped """ Examples: | format | name | | plain | Café | | pretty | Ärgernis_ist_überall | @problematic.feature_filename @not.with_os=win32 Scenario Outline: Problematic feature filename: (case: fail, ) Given a file named "features/_and_fail.feature" with: """ Feature: Scenario: Given a step fails """ When I run "behave -f features/_and_fail.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped """ Examples: | format | name | | plain | Café | | pretty | Ärgernis_ist_überall | @problematic.step_filename Scenario Outline: Problematic step filename: (case: pass, ) TEST-CONSTRAINT: Only one step file is used (= 1 name only). Otherwise, duplicated steps occur (without cleanup in step directory). Given a file named "features/problematic_stepfile_and_pass.feature" with: """ Feature: Scenario: Given I use a weird step and pass """ And a file named "features/steps/step_pass_.py" with: """ from behave import step @step(u'I use a weird step and pass') def step_weird_pass(context): pass """ When I run "behave -f features/problematic_stepfile_and_pass.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped """ But note that "you should normally use only ASCII/latin-1 python filenames" Examples: | format | name | | plain | Ärgernis_ist_überall | | pretty | Ärgernis_ist_überall | @problematic.step_filename Scenario Outline: Problematic step filename: (case: fail, ) TEST-CONSTRAINT: Only one step file is used (= 1 name only). Otherwise, duplicated steps occur (without cleanup in step directory). Given a file named "features/problematic_stepfile_and_fail.feature" with: """ Feature: Scenario: Given I use a weird step and fail """ And a file named "features/steps/step_fail_.py" with: """ from behave import step @step(u'I use a weird step and fail') def step_weird_fails(context): assert False, "XFAIL-WEIRD" """ When I run "behave -f features/problematic_stepfile_and_fail.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped """ But note that "you should normally use only ASCII/latin-1 python filenames" Examples: | format | name | | plain | Ärgernis_ist_überall | | pretty | Ärgernis_ist_überall | @problematic.output Scenario Outline: Problematic output: (case: pass, ) Given a file named "features/problematic_output_and_pass.feature" with: """ Feature: Scenario: Given I write text "" to stdout Then I write bytes "" to stdout And a step passes """ When I run "behave -f --no-capture features/problematic_output_and_pass.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped """ Examples: | format | text | | plain | Café | | pretty | Ärgernis ist überall | @problematic.output Scenario Outline: Problematic output: (case: fail, ) Given a file named "features/problematic_output_and_fail.feature" with: """ Feature: Scenario: Given I write text "" to stdout Then I write bytes "" to stdout And a step fails """ When I run "behave -f features/problematic_output_and_fail.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped """ And the command output should contain: """ """ Examples: | format | text | | plain | Café | | pretty | Ärgernis ist überall | @problematic.tags Scenario Outline: Problematic tag: (case: pass, ) Given a file named "features/problematic_tag_and_pass.feature" with: """ Feature: @ Scenario: Given a step passes """ When I run "behave -f features/problematic_tag_and_pass.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped """ Examples: | format | tag | | plain | tag.Café | | pretty | tag.Ärgernis_ist_überall | @problematic.tags Scenario Outline: Problematic tag: (case: fail, ) Given a file named "features/problematic_tag_and_fail.feature" with: """ Feature: @ Scenario: Given a step fails """ When I run "behave -f features/problematic_tag_and_fail.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped """ Examples: | format | tag | | plain | tag.Café | | pretty | tag.Ärgernis_ist_überall | @issue_0230 Scenario Outline: Step assert fails with problematic chars (case: ) NOTE: Python2 fails silently without showing the failed step. HINT: Use unicode string when you use, special non-ASCII characters. HINT: Use encoding-hint in python file header. Given a file named "features/steps/problematic_steps.py" with: """ # -*- coding: UTF-8 -*- from behave import step @step(u'{word:w} step fails with assert and non-ASCII text') def step_fails_with_assert_and_problematic_text(context, word): assert False, u"XFAIL:¾;" """ And a file named "features/assert_with_ptext.feature" with: """ Feature: Scenario: Given a step passes When a step fails with assert and non-ASCII text """ When I run "behave -f features/assert_with_ptext.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 1 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Assertion Failed: XFAIL:¾; """ Examples: | format | | plain | | pretty | @issue_0226 Scenario Outline: Step raises exception with problematic chars (case: ) In Python2: When an exception is raised with unicode argument, (and special non-ASCII chars) the conversion of the exception into a unicode string causes implicit conversion into a normal string by using the default encoding (normally: ASCII). Therefore, the implicit encoding into a normal string often fails. SEE ALSO: http://bugs.python.org/issue2517 NOTE: Better if encoding hint is provided in python file header. Given a file named "features/steps/problematic_steps.py" with: """ # -*- coding: UTF-8 -*- from behave import step @step(u'{word:w} step fails with exception and non-ASCII text') def step_fails_with_exception_and_problematic_text(context, word): # -- REQUIRE: UNICODE STRING, when special, non-ASCII chars are used. raise RuntimeError(u"FAIL:¾;") """ And a file named "features/exception_with_ptext.feature" with: """ Feature: Scenario: Given a step passes When a step fails with exception and non-ASCII text """ When I run "behave -f features/exception_with_ptext.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 1 failed, 0 skipped, 0 undefined """ And the command output should contain: """ RuntimeError: FAIL:¾; """ Examples: | format | | plain | | pretty | behave-1.2.6/features/logcapture.clear_handlers.feature0000644000076600000240000000704013244555737023362 0ustar jensstaff00000000000000@logging @capture Feature: Use --logging-clear-handlers configuration option PRECONDITION: log_capture mode is enabled (config.log_capture = true). As a tester In log-capture mode I want sometimes to remove any logging handler before capture starts So that I have the log-records output under control. @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/use_behave4cmd_steps.py" with: """ import behave4cmd0.log.steps import behave4cmd0.failing_steps import behave4cmd0.passing_steps """ And a file named "features/environment.py" with: """ def before_all(context): # -- SAME-AS: context.config.setup_logging() import logging logging.basicConfig(level=context.config.logging_level) # -- ADDITIONAL LOG-HANDLER: Which will be cleared. format = "LOG-HANDLER2: %(name)s %(levelname)s: %(message)s;" handler = logging.StreamHandler() handler.setFormatter(logging.Formatter(format)) root_logger = logging.getLogger() root_logger.addHandler(handler) """ And a file named "features/example.log_with_failure.feature" with: """ Feature: Scenario: Failing Given I create log records with: | category | level | message | | root | ERROR | Hello Alice | | root | WARN | Hello Bob | When a step fails """ Scenario: Use logcapture mode without clearing existing log handlers Given a file named "behave.ini" with: """ [behave] log_capture = true logging_level = WARN """ When I run "behave -f plain features/example.log_with_failure.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 1 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Captured logging: ERROR:root:Hello Alice WARNING:root:Hello Bob """ And the command output should contain: """ LOG-HANDLER2: root ERROR: Hello Alice; LOG-HANDLER2: root WARNING: Hello Bob; """ Scenario: Use logcapture mode with clearing additional log handlers (case: command-line) Given a file named "behave.ini" with: """ [behave] log_capture = true logging_level = WARN """ When I run "behave -f plain --logging-clear-handlers features/example.log_with_failure.feature" Then it should fail with: """ Captured logging: ERROR:root:Hello Alice WARNING:root:Hello Bob """ But the command output should not contain: """ LOG-HANDLER2: root ERROR: Hello Alice; LOG-HANDLER2: root WARNING: Hello Bob; """ Scenario: Use Logcapture mode with clearing additional log handlers (case: configfile) Given a file named "behave.ini" with: """ [behave] log_capture = true logging_level = WARN logging_clear_handlers = true """ When I run "behave -f plain features/example.log_with_failure.feature" Then it should fail with: """ Captured logging: ERROR:root:Hello Alice WARNING:root:Hello Bob """ But the command output should not contain: """ LOG-HANDLER2: root ERROR: Hello Alice; LOG-HANDLER2: root WARNING: Hello Bob; """ behave-1.2.6/features/logcapture.feature0000644000076600000240000001634213244555737020422 0ustar jensstaff00000000000000@logging @capture Feature: Capture log output As a tester I want that log output is captured But log-records are only shown when failures/errors occur So that failure diagnostics are simplified . SPECIFICATION: . * log_capture mode is enabled per default . * log_capture mode can be defined on command-line . * log_capture mode can be defined in behave configuration file . * In log_capture mode: Captured log-records are only shown if a scenario fails . . RELATED: . * logcapture.*.feature . * logging.*.feature @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/use_behave4cmd_steps.py" with: """ import behave4cmd0.log.steps import behave4cmd0.failing_steps import behave4cmd0.passing_steps """ And a file named "features/environment.py" with: """ def before_all(context): context.config.setup_logging() # -- SAME-AS: # import logging # logging.basicConfig(level=context.config.logging_level) """ And a file named "features/example.log_and_pass.feature" with: """ Feature: Scenario: Passing Given I create log records with: | category | level | message | | root | FATAL | Hello Alice | | foo | ERROR | Hello Bob | | foo.bar | WARN | Hello Charly | | bar | INFO | Hello Dora | | baz | DEBUG | Hello Emily | When another step passes """ And a file named "features/example.log_and_fail.feature" with: """ Feature: Scenario: Failing Given I create log records with: | category | level | message | | root | FATAL | Hello Alice | | foo | ERROR | Hello Bob | | foo.bar | WARN | Hello Charly | | bar | INFO | Hello Dora | | baz | DEBUG | Hello Emily | When another step fails """ Scenario: Captured log is suppressed if scenario passes When I run "behave -f plain -T --logcapture features/example.log_and_pass.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Scenario: Passing Given I create log records with ... passed | category | level | message | | root | FATAL | Hello Alice | | foo | ERROR | Hello Bob | | foo.bar | WARN | Hello Charly | | bar | INFO | Hello Dora | | baz | DEBUG | Hello Emily | When another step passes ... passed """ And the command output should not contain: """ Captured logging: """ But the command output should not contain the following log records: | category | level | message | | root | FATAL | Hello Alice | | foo | ERROR | Hello Bob | | foo.bar | WARN | Hello Charly | | bar | INFO | Hello Dora | | baz | DEBUG | Hello Emily | Scenario: Captured log is shown up to first failure if scenario fails When I run "behave -f plain -T --logcapture features/example.log_and_fail.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 1 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: Scenario: Failing Given I create log records with ... passed | category | level | message | | root | FATAL | Hello Alice | | foo | ERROR | Hello Bob | | foo.bar | WARN | Hello Charly | | bar | INFO | Hello Dora | | baz | DEBUG | Hello Emily | When another step fails ... failed Assertion Failed: EXPECT: Failing step Captured logging: CRITICAL:root:Hello Alice ERROR:foo:Hello Bob WARNING:foo.bar:Hello Charly INFO:bar:Hello Dora """ And the command output should contain the following log records: | category | level | message | | root | FATAL | Hello Alice | | foo | ERROR | Hello Bob | | foo.bar | WARN | Hello Charly | | bar | INFO | Hello Dora | But the command output should not contain the following log records: | category | level | message | Comment | | baz | DEBUG | Hello Emily | Log-level too low: filtered-out | Scenario: Captured log is shown up to first failure if log_record.level is not too low Ensure that only log-records are shown that exceed the logging-level. When I run "behave -f plain --logcapture --logging-level=ERROR features/example.log_and_fail.feature" Then it should fail And the command output should contain the following log records: | category | level | message | | root | FATAL | Hello Alice | | foo | ERROR | Hello Bob | But the command output should not contain the following log records: | category | level | message | | foo.bar | WARN | Hello Charly | | bar | INFO | Hello Dora | | baz | DEBUG | Hello Emily | Scenario: Logcapture mode is enabled per default When I run "behave -f plain features/example.log_and_fail.feature" Then it should fail And the command output should contain "Captured logging:" Scenario: Logcapture mode can be enabled on command-line When I run "behave -f plain --logcapture features/example.log_and_fail.feature" Then it should fail And the command output should contain "Captured logging:" Scenario: Logcapture mode can be disabled on command-line When I run "behave -f plain --no-logcapture features/example.log_and_fail.feature" Then it should fail And the command output should not contain "Captured logging:" Scenario: Logcapture mode can be enabled in configfile Given a file named "behave.ini" with: """ [behave] log_capture = true """ When I run "behave -f plain features/example.log_and_fail.feature" Then it should fail And the command output should contain "Captured logging:" Scenario: Logcapture mode can be disabled in configfile Given a file named "behave.ini" with: """ [behave] log_capture = false """ When I run "behave -f plain features/example.log_and_fail.feature" Then it should fail And the command output should not contain "Captured logging:" behave-1.2.6/features/logcapture.filter.feature0000644000076600000240000001211613244555737021701 0ustar jensstaff00000000000000Feature: Use logging_filter with logcapture PRECONDITION: log_capture mode is enabled (config.log_capture = true). As a tester In log-capture mode I want to include/exclude log-records from some logging categories So that the output is not cluttered with unneeded information in case of failures. Background: Given I define the log record schema: | category | level | message | | root | ERROR | __LOG_MESSAGE__ | @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/use_behave4cmd_steps.py" with: """ import behave4cmd0.log.steps import behave4cmd0.failing_steps import behave4cmd0.passing_steps """ And a file named "features/logging.failing_example.feature" with: """ Feature: Scenario: Failing Given I create log records with: | category | level | message | | root | ERROR | __LOG_MESSAGE__ | | foo | ERROR | __LOG_MESSAGE__ | | foo.bar | ERROR | __LOG_MESSAGE__ | | bar | ERROR | __LOG_MESSAGE__ | When another step fails """ And a file named "behave.ini" with: """ [behave] log_capture = true logging_level = WARN """ Scenario: Include only a logging category When I run "behave --logcapture --logging-filter=foo features/logging.failing_example.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 1 failed, 0 skipped, 0 undefined """ And the command output should contain log records from categories: | category | | foo | But the command output should not contain log records from categories: | category | Comment | | root | Not included: filtered-out | | foo.bar | Not included: filtered-out | | bar | Not included: filtered-out | Scenario: Include only a logging sub-category When I run "behave --logcapture --logging-filter=foo.bar features/logging.failing_example.feature" Then it should fail And the command output should contain log records from categories: | category | Comment | | foo.bar | Included | But the command output should not contain log records from categories: | category | Comment | | root | Not included: filtered-out | | foo | Not included: filtered-out | | bar | Not included: filtered-out | Scenario: Exclude a logging category When I run "behave --logcapture --logging-filter=-foo features/logging.failing_example.feature" Then it should fail And the command output should contain log records from categories: | category | Comment | | root | Not excluded: foo | | foo.bar | Not excluded: foo | | bar | Not excluded: foo | But the command output should not contain log records from categories: | category | Comment | | foo | Excluded | Scenario: Include several logging categories When I run "behave --logcapture --logging-filter=foo,bar features/logging.failing_example.feature" Then it should fail And the command output should contain log records from categories: | category | Comment | | foo | Included: foo | | bar | Included: bar | But the command output should not contain log records from categories: | category | Comment | | root | Not included: filtered-out | | foo.bar | Not included (sub-category) | Scenario: Include/exclude several logging categories When I run "behave --logcapture --logging-filter=foo.bar,-bar features/logging.failing_example.feature" Then it should fail And the command output should contain log records from categories: | category | Comment | | root | Not excluded: bar | | foo | Not excluded: bar | | foo.bar | Included | But the command output should not contain log records from categories: | category | Comment | | bar | Excluded: filtered-out | Scenario: Include/exclude several logging categories with configfile Given a file named "behave.ini" with: """ [behave] log_capture = true logging_level = WARN logging_filter = foo.bar,-bar """ When I run "behave --logcapture features/logging.failing_example.feature" Then it should fail And the command output should contain log records from categories: | category | Comment | | root | Not excluded: bar | | foo | Not excluded: bar | | foo.bar | Included | But the command output should not contain log records from categories: | category | Comment | | bar | Excluded: filtered-out | behave-1.2.6/features/logging.no_capture.feature0000644000076600000240000001016213244555737022033 0ustar jensstaff00000000000000@logging @no_capture Feature: No-logcapture mode (normal mode) shows log-records As a tester I want that sometimes that log output is not captured So that I can see progress even in case of success (or failures) @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/use_behave4cmd_steps.py" with: """ import behave4cmd0.log.steps import behave4cmd0.failing_steps import behave4cmd0.passing_steps """ Scenario: Log records are shown when scenario passes (CASE 1) Ensure that log-records are filtered out if log_record.level is too low. Given a file named "features/environment.py" with: """ def before_all(context): import logging context.config.setup_logging(logging.WARN) # -- SAME AS: # logging.basicConfig() # logging.getLogger().setLevel(logging.WARN) """ And a file named "features/logging.passing_example.feature" with: """ Feature: Scenario: Passing Given I create log records with: | category | level | message | | root | ERROR | Hello Alice | | root | WARN | Hello Bob | | root | INFO | Hello Charly | | root | DEBUG | Hello Doro | When another step passes """ When I run "behave -f plain -T --no-logcapture features/logging.passing_example.feature" Then it should pass And the command output should contain the following log records: | category | level | message | | root | ERROR | Hello Alice | | root | WARN | Hello Bob | But the command output should not contain the following log records: | category | level | message | Comment | | root | INFO | Hello Charly | Filtered-out | | root | DEBUG | Hello Doro | Filtered-out | And note that "log_records with level below WARN are filtered out" @no_capture Scenario: Log records are shown up to first failing step (CASE 2) Ensure that log-records are filtered out if log_record.level is too low. Given a file named "features/environment.py" with: """ def before_all(context): context.config.setup_logging() """ And a file named "features/logging.failing_example.feature" with: """ Feature: Scenario: Failing Given I create log records with: | category | level | message | | root | ERROR | Hello Alice | | root | WARN | Hello Bob | | root | INFO | Hello Charly | | root | DEBUG | Hello Doro | When another step fails Then I create log records with: | category | level | message | | root | ERROR | Hello2 Zerberus | """ When I run "behave -f plain -T --no-logcapture --logging-level=ERROR features/logging.failing_example.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 1 failed, 1 skipped, 0 undefined """ And the command output should contain the following log records: | category | level | message | | root | ERROR | Hello Alice | But the command output should not contain the following log records: | category | level | message | Comment | | root | WARN | Hello Bob | Filtered-out | | root | INFO | Hello Charly | Filtered-out | | root | DEBUG | Hello Doro | Filtered-out | | root | ERROR | Hello2 Zerberus | Skipped | And note that "log_records with level below ERROR are filtered out" behave-1.2.6/features/logging.setup_format.feature0000644000076600000240000001234213244555737022406 0ustar jensstaff00000000000000Feature: Setup logging_format As a tester I want to configure the logging_format for log_capture mode So that log-records are shown in my preferred format. As a tester I want to configure the logging_format for logging mode (no-log_capture) So that log-records are shown in my preferred format. . SPECIFICATION: . * logging_format can be defined on command-line . * logging_format can be defined in behave configuration file . . NOTE: . The log record format can also be defined in a logging configuration file. @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/use_behave4cmd_steps.py" with: """ import behave4cmd0.log.steps import behave4cmd0.failing_steps import behave4cmd0.passing_steps """ And a file named "features/environment.py" with: """ def before_all(context): context.config.setup_logging() """ And a file named "features/example.log_and_fail.feature" with: """ Feature: Scenario: Failing Given I create log records with: | category | level | message | | root | ERROR | Hello Alice | | root | WARN | Hello Bob | When a step fails """ @capture Scenario: Use logging_format on command-line (case: log_capture mode) Given a file named "behave.ini" with: """ [behave] log_capture = true logging_level = WARN """ When I run "behave -f plain -T --logging-format='LOG.%(levelname)-8s %(name)-10s: %(message)s' features/" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 1 failed, 0 skipped, 0 undefined """ And the command output should contain "Captured logging:" And the command output should contain: """ Captured logging: LOG.ERROR root : Hello Alice LOG.WARNING root : Hello Bob """ When I use the log record configuration: | property | value | | format | LOG.%(levelname)-8s %(name)-10s: %(message)s | Then the command output should contain the following log records: | category | level | message | | root | ERROR | Hello Alice | | root | WARN | Hello Bob | @capture Scenario: Use logging_format in config-file (case: log_capture mode) Given a file named "behave.ini" with: """ [behave] log_capture = true logging_level = WARN logging_format = LOG.%(levelname)-8s %(name)-10s: %(message)s """ When I run "behave -f plain features/" Then it should fail And the command output should contain "Captured logging:" And the command output should contain: """ Captured logging: LOG.ERROR root : Hello Alice LOG.WARNING root : Hello Bob """ @no_capture Scenario: Use logging_format on command-line (case: logging mode) Given a file named "behave.ini" with: """ [behave] log_capture = false logging_level = WARN """ When I run "behave -f plain -T --logging-format='LOG.%(levelname)-8s %(name)-10s: %(message)s' features/" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 1 failed, 0 skipped, 0 undefined """ And the command output should not contain "Captured logging:" And the command output should contain: """ LOG.ERROR root : Hello Alice LOG.WARNING root : Hello Bob """ When I use the log record configuration: | property | value | | format | LOG.%(levelname)-8s %(name)-10s: %(message)s | Then the command output should contain the following log records: | category | level | message | | root | ERROR | Hello Alice | | root | WARN | Hello Bob | @no_capture Scenario: Use logging_format in config-file (case: logging mode) Given a file named "behave.ini" with: """ [behave] log_capture = false logging_level = WARN logging_format = LOG.%(levelname)-8s %(name)-10s: %(message)s """ When I run "behave -f plain features/" Then it should fail And the command output should not contain "Captured logging:" And the command output should contain: """ LOG.ERROR root : Hello Alice LOG.WARNING root : Hello Bob """ @capture Scenario: Use logging_datefmt in config-file Ensure that "logging_datefmt" option can be used. Given a file named "behave.ini" with: """ [behave] logging_format = %(asctime)s LOG.%(levelname)-8s %(name)s: %(message)s logging_datefmt = %Y-%m-%dT%H:%M:%S """ When I run "behave -f plain features/" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 1 failed, 0 skipped, 0 undefined """ And the command output should contain "LOG.ERROR root: Hello Alice" And the command output should contain "LOG.WARNING root: Hello Bob" behave-1.2.6/features/logging.setup_level.feature0000644000076600000240000001554713244555737022237 0ustar jensstaff00000000000000Feature: Setup logging_level As a tester I want to configure the logging_level for --logcapture mode So that I see only the important log-records when a scenario fails. As a tester I want to configure the logging_level for --nologcapture mode So that I see only the important log-records up to this level. . SPECIFICATION: . * logging_level can be defined on command-line . * logging_level can be defined in behave configuration file . * logging_level should be applied in before_all() hook in --nologcapture mode @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/use_behave4cmd_steps.py" with: """ import behave4cmd0.log.steps import behave4cmd0.failing_steps import behave4cmd0.passing_steps """ And a file named "features/environment.py" with: """ def before_all(context): context.config.setup_logging() """ And a file named "features/example.log_with_failure.feature" with: """ Feature: Scenario: S1 Given I create log records with: | category | level | message | | root | ERROR | Hello1 log-error-record | | root | WARN | Hello1 log-warn-record | | root | INFO | Hello1 log-info-record | | root | DEBUG | Hello1 log-debug-record | When a step fails """ And a file named "features/example.log_with_pass.feature" with: """ Feature: Scenario: S2 Given I create log records with: | category | level | message | | root | ERROR | Hello2 log-error-record | | root | WARN | Hello2 log-warn-record | | root | INFO | Hello2 log-info-record | | root | DEBUG | Hello2 log-debug-record | When a step passes """ @capture Scenario: Logcapture mode: Use logging_level on command-line Also ensure that command-line option can override configuration file info. Given a file named "behave.ini" with: """ [behave] logging_level = INFO """ When I run "behave -f plain -T --logging-level=WARN features/" Then it should fail with: """ 1 scenario passed, 1 failed, 0 skipped 3 steps passed, 1 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Captured logging: ERROR:root:Hello1 log-error-record WARNING:root:Hello1 log-warn-record """ But the command output should not contain the following log records: | category | level | message | Comment | | root | INFO | Hello1 log-info-record | Log-level too low | | root | DEBUG | Hello1 log-debug-record | Log-level too low | | root | ERROR | Hello2 log-error-record | Scenario passes, capture log is suppressed | | root | WARN | Hello2 log-warn-record | Scenario passes, capture log is suppressed | | root | INFO | Hello2 log-info-record | Scenario passes, capture log is suppressed | | root | DEBUG | Hello2 log-debug-record | Scenario passes, capture log is suppressed | @capture Scenario: Logcapture mode: Use logging_level in configuration file Given a file named "behave.ini" with: """ [behave] logging_level = ERROR """ When I run "behave -f plain -T features/" Then it should fail with: """ 1 scenario passed, 1 failed, 0 skipped 3 steps passed, 1 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Captured logging: ERROR:root:Hello1 log-error-record """ But the command output should not contain the following log records: | category | level | message | Comment | | root | WARN | Hello1 log-warn-record | Log-level too low | | root | INFO | Hello1 log-info-record | Log-level too low | | root | DEBUG | Hello1 log-debug-record | Log-level too low | | root | ERROR | Hello2 log-error-record | Scenario passes | | root | WARN | Hello2 log-warn-record | Scenario passes | | root | INFO | Hello2 log-info-record | Scenario passes | | root | DEBUG | Hello2 log-debug-record | Scenario passes | @no_capture Scenario: Normal mode: Use logging_level on command-line Given a file named "behave.ini" with: """ [behave] logging_level = INFO """ When I run "behave -f plain -T --logging-level=WARN --no-logcapture features/" Then it should fail with: """ 1 scenario passed, 1 failed, 0 skipped 3 steps passed, 1 failed, 0 skipped, 0 undefined """ And the command output should contain the following log records: | category | level | message | | root | ERROR | Hello1 log-error-record | | root | WARN | Hello1 log-warn-record | | root | ERROR | Hello2 log-error-record | | root | WARN | Hello2 log-warn-record | But the command output should not contain the following log records: | category | level | message | Comment | | root | INFO | Hello1 log-info-record | Log-level too low | | root | DEBUG | Hello1 log-debug-record | Same reason | | root | INFO | Hello2 log-info-record | Same reason | | root | DEBUG | Hello2 log-debug-record | Same reason | @no_capture Scenario: Normal mode: Use logging_level in configuration file Given a file named "behave.ini" with: """ [behave] log_capture = false logging_level = ERROR """ When I run "behave -f plain -T features/" Then it should fail with: """ 1 scenario passed, 1 failed, 0 skipped 3 steps passed, 1 failed, 0 skipped, 0 undefined """ And the command output should contain the following log records: | category | level | message | | root | ERROR | Hello1 log-error-record | | root | ERROR | Hello2 log-error-record | But the command output should not contain the following log records: | category | level | message | Comment | | root | WARN | Hello1 log-warn-record | Log-level too low | | root | INFO | Hello1 log-info-record | Same reason | | root | DEBUG | Hello1 log-debug-record | Same reason | | root | WARN | Hello2 log-warn-record | Same reason | | root | INFO | Hello2 log-info-record | Same reason | | root | DEBUG | Hello2 log-debug-record | Same reason | behave-1.2.6/features/logging.setup_with_configfile.feature0000644000076600000240000001131313244555737024253 0ustar jensstaff00000000000000Feature: Setup logging subsystem by using a logging configfile As a tester I want to setup the logging subsystem by using a configfile To be more flexible even in complex situations @setup Scenario: Feature setup Given a new working directory And a file named "features/steps/use_behave4cmd_steps.py" with: """ import behave4cmd0.log.steps import behave4cmd0.failing_steps import behave4cmd0.passing_steps """ And a file named "features/example.log_and_pass.feature" with: """ Feature: Scenario: Passing Given I create log records with: | category | level | message | | root | FATAL | Hello Alice | | foo | ERROR | Hello Bob | | foo.bar | WARN | Hello Charly | | bar | INFO | Hello Dora | | baz | DEBUG | Hello Emily | And another step passes """ And a file named "features/example.log_and_fail.feature" with: """ Feature: Scenario: Failing Given I create log records with: | category | level | message | | root | FATAL | Hello Alice | | foo | ERROR | Hello Bob | | foo.bar | WARN | Hello Charly | | bar | INFO | Hello Dora | | baz | DEBUG | Hello Emily | And another step fails """ And a file named "behave.ini" with: """ [behave] log_capture = false logging_level = DEBUG logging_format = LOG.%(levelname)-8s %(name)-10s: %(message)s """ And a file named "behave_logging.ini" with: """ [loggers] keys=root [handlers] keys=Console,File [formatters] keys=Brief [logger_root] level = NOTSET handlers = File # handlers = Console,File [handler_File] class=FileHandler args=("behave.log", 'w') level=NOTSET formatter=Brief [handler_Console] class=StreamHandler args=(sys.stderr,) level=NOTSET formatter=Brief [formatter_Brief] format= LOG.%(levelname)-8s %(name)-10s: %(message)s datefmt= """ Scenario: Setup logging subsystem via environment (case: logging mode) Given a file named "features/environment.py" with: """ def before_all(context): context.config.setup_logging(configfile="behave_logging.ini") """ And I use the log record configuration: | property | value | | format | LOG.%(levelname)-8s %(name)-10s: %(message)s | When I run "behave -f plain features/example.log_and_pass.feature" Then it should pass And the file "behave.log" should contain the log records: | category | level | message | | root | FATAL | Hello Alice | | foo | ERROR | Hello Bob | | foo.bar | WARN | Hello Charly | | bar | INFO | Hello Dora | | baz | DEBUG | Hello Emily | And the command output should not contain the following log records: | category | level | message | | root | FATAL | Hello Alice | | foo | ERROR | Hello Bob | | foo.bar | WARN | Hello Charly | | bar | INFO | Hello Dora | | baz | DEBUG | Hello Emily | Scenario: Setup logging subsystem via environment (case: log-capture mode) Given a file named "features/environment.py" with: """ def before_all(context): context.config.setup_logging(configfile="behave_logging.ini") """ And I use the log record configuration: | property | value | | format | LOG.%(levelname)-8s %(name)-10s: %(message)s | When I run "behave -f plain --logcapture features/example.log_and_fail.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 1 failed, 0 skipped, 0 undefined """ And the file "behave.log" should contain the log records: | category | level | message | | root | FATAL | Hello Alice | | foo | ERROR | Hello Bob | | foo.bar | WARN | Hello Charly | | bar | INFO | Hello Dora | | baz | DEBUG | Hello Emily | And the command output should contain the following log records: | category | level | message | | root | FATAL | Hello Alice | | foo | ERROR | Hello Bob | | foo.bar | WARN | Hello Charly | | bar | INFO | Hello Dora | | baz | DEBUG | Hello Emily | behave-1.2.6/features/parser.background.sad_cases.feature0000644000076600000240000001053713244555737023613 0ustar jensstaff00000000000000Feature: Ensure that BAD/SAD Use cases of Background are detected To improve diagnostics when parser failures occur As a test writer I expect reasonable explanations what went wrong. @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/passing_steps.py" with """ from behave import step @step('a step passes') def step_passes(context): pass @step('a step passes with "{text}"') def step_passes(context, text): pass """ Scenario: Background with tags is not supported Given a file named "features/syndrome.background_with_tags.feature" with """ Feature: Ensure this fails @tags_are @not_supported @here Background: Oops... Given a step passes Scenario: More... Given a step passes """ When I run "behave -f plain -T features/syndrome.background_with_tags.feature" Then it should fail with """ Failed to parse "{__WORKDIR__}/features/syndrome.background_with_tags.feature": Parser failure in state taggable_statement, at line 4: "Background: Oops..." REASON: Background does not support tags. """ Scenario: Background should not occur after a Scenario Given a file named "features/syndrome.background_after_scenario.feature" with """ Feature: Ensure this fails1 Scenario: One... Given a step passes Background: Oops, too late (after Scenario) When a step passes """ When I run "behave -f plain -T features/syndrome.background_after_scenario.feature" Then it should fail with """ Failed to parse "{__WORKDIR__}/features/syndrome.background_after_scenario.feature": Parser failure in state steps, at line 6: "Background: Oops, too late (after Scenario)" REASON: Background may not occur after Scenario/ScenarioOutline. """ Scenario: Tagged Background should not occur after a Scenario Given a file named "features/syndrome.tagged_background_after_scenario.feature" with """ Feature: Ensure this fails1 Scenario: One... Given a step passes @tags_are @not_supported @here Background: Oops, too late (after Scenario) When a step passes """ When I run "behave -f plain -T features/syndrome.tagged_background_after_scenario.feature" Then it should fail with """ Parser failure in state taggable_statement, at line 7: "Background: Oops, too late (after Scenario)" REASON: Background may not occur after Scenario/ScenarioOutline. """ Scenario: Background should not occur after a Scenario Outline Given a file named "features/syndrome.background_after_scenario_outline.feature" with """ Feature: Ensure this fails3 Scenario Outline: Two... Given a step passes with "" Examples: | name | | Alice | Background: Oops, too late (after Scenario Outline) When a step passes """ When I run "behave -f plain -T features/syndrome.background_after_scenario_outline.feature" Then it should fail with """ Parser failure in state steps, at line 10: "Background: Oops, too late (after Scenario Outline)" REASON: Background may not occur after Scenario/ScenarioOutline. """ Scenario: Tagged Background should not occur after a Scenario Outline Given a file named "features/syndrome.background_after_scenario_outline.feature" with """ Feature: Ensure this fails4 Scenario Outline: Two... Given a step passes with "" Examples: | name | | Alice | @tags_are @not_supported @here Background: Oops, too late (after Scenario Outline) When a step passes """ When I run "behave -f plain -T features/syndrome.background_after_scenario_outline.feature" Then it should fail with """ Parser failure in state taggable_statement, at line 11: "Background: Oops, too late (after Scenario Outline)" REASON: Background may not occur after Scenario/ScenarioOutline. """ behave-1.2.6/features/parser.feature.sad_cases.feature0000644000076600000240000001100713244555737023120 0ustar jensstaff00000000000000Feature: Parsing a Feature File without a Feature or with several Features @setup Scenario: Feature Setup Given a new working directory And an empty file named "features/steps/empty_steps.py" And a file named "features/steps/passing_steps.py" with: """ from behave import step @step('a step passes') def step_passes(context): pass """ @no_feature Scenario: Empty Feature File Given an empty file named "features/empty.feature" When I run "behave -f plain features/empty.feature" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped 0 scenarios passed, 0 failed, 0 skipped 0 steps passed, 0 failed, 0 skipped, 0 undefined """ @no_feature Scenario: Feature File without Feature, only with Comments Given a file named "features/only_comments.feature" with: """ # COMMENT: Comment starts at begin of line. # INDENTED-COMMENT: Comment starts after some whitespace. """ When I run "behave -f plain features/only_comments.feature" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped 0 scenarios passed, 0 failed, 0 skipped 0 steps passed, 0 failed, 0 skipped, 0 undefined """ @no_feature Scenario: Feature File without Feature, only with Empty Lines Given a file named "features/only_empty_lines.feature" with: """ """ When I run "behave -f plain features/only_empty_lines.feature" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped 0 scenarios passed, 0 failed, 0 skipped 0 steps passed, 0 failed, 0 skipped, 0 undefined """ @no_feature Scenario: Feature File without Feature, only with Tags Given a file named "features/only_tags.feature" with: """ @weird @no_feature """ When I run "behave -f plain features/only_tags.feature" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped 0 scenarios passed, 0 failed, 0 skipped 0 steps passed, 0 failed, 0 skipped, 0 undefined """ @no_feature @parser.with_parse_error Scenario: Feature File with Text Given a file named "features/only_text.feature" with: """ This File: Contains only text without keywords. OOPS. """ When I run "behave -f plain features/only_text.feature" Then it should fail with: """ Failed to parse "{__WORKDIR__}/features/only_text.feature": Parser failure in state init, at line 1: "This File: Contains only text without keywords." REASON: No feature found. """ @no_feature @parser.with_parse_error Scenario: Feature File with Scenario, but without Feature keyword Given a file named "features/naked_scenario_only.feature" with: """ Scenario: Given a step passes When a step passes Then a step passes """ When I run "behave -f plain features/naked_scenario_only.feature" Then it should fail with: """ Failed to parse "{__WORKDIR__}/features/naked_scenario_only.feature": Parser failure in state init, at line 1: "Scenario:" REASON: Scenario may not occur before Feature. """ @many_features @parser.with_parse_error Scenario: Feature file with 2 features NOTE: Gherkin parser supports only one feature per feature file. Given a file named "features/steps/passing_steps.py" with: """ from behave import step @step('a step passes') def step_passes(context): pass """ And a file named "features/two_features.feature" with: """ Feature: F1 Scenario: F1.1 Given a step passes When a step passes Then a step passes Feature: F2 Scenario: F2.1 Given a step passes Then a step passes """ When I run "behave -f plain features/two_features.feature" Then it should fail with: """ Failed to parse "{__WORKDIR__}/features/two_features.feature": Parser failure in state steps, at line 7: "Feature: F2" REASON: Multiple features in one file are not supported. """ behave-1.2.6/features/README.txt0000644000076600000240000000070313244555737016370 0ustar jensstaff00000000000000behave features: Self-Tests =============================================================================== This directory contains feature tests that are executed with behave. These self-tests are used to: * ensure that ``behave`` reacts as expected * define the common expected behaviour for behave * support "Acceptance Test Driven-Design" (ATDD) RELATED: * Cucumber Technology Compatibility Kit: https://github.com/cucumber/cucumber-tckbehave-1.2.6/features/runner.abort_by_user.feature0000644000076600000240000002336013244555737022422 0ustar jensstaff00000000000000Feature: Test run can be aborted by the user As a tester I want sometimes to abort a test run (because it is anyway failing, etc.) So that I am more productive. . NOTES: . * The test runner should fail gracefully (most of the times) . * At least some cleanup hooks should be called (in general) @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/aborting_steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass @step('the user aborts the test run') def step_user_aborts_testrun(context): raise KeyboardInterrupt() """ And a file named "features/scenarios_pass3.feature" with: """ Feature: Scenario: Given a step passes When another step passes Scenario: Given first step passes When second step passes Then third step passes Scenario: Then last step passes """ Scenario: Abort test run in step definition Given a file named "features/aborting_in_step.feature" with: """ Feature: User aborts test run in a step definition Scenario: Given a step passes When another step passes Scenario: User aborts here Given first step passes When the user aborts the test run Then third step passes Scenario: Then last step passes """ And an empty file named "features/environment.py" When I run "behave -f plain -T features/aborting_in_step.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 1 scenario passed, 1 failed, 0 skipped, 1 untested 3 steps passed, 1 failed, 1 skipped, 0 undefined, 1 untested """ And the command output should contain: """ Feature: User aborts test run in a step definition Scenario: Given a step passes ... passed When another step passes ... passed Scenario: User aborts here Given first step passes ... passed When the user aborts the test run ... failed ABORTED: By user (KeyboardInterrupt). ABORTED: By user. Failing scenarios: features/aborting_in_step.feature:6 User aborts here """ But note that "the last scenario is untested (not-run) due to the user abort" Scenario: Abort test run in before_scenario hook Given a file named "features/aborting_in_before_scenario_hook.feature" with: """ Feature: User aborts test run in before_scenario hook of S2 Scenario: S1 Given a step passes When another step passes @user.aborts.before_scenario Scenario: S2 -- User aborts here Given first step passes When second step passes Then third step passes Scenario: S3 Then last step passes """ And a file named "features/environment.py" with: """ def before_scenario(context, scenario): if "user.aborts.before_scenario" in scenario.tags: user_aborts_testrun_here() def user_aborts_testrun_here(): raise KeyboardInterrupt() """ When I run "behave -f plain -T features/aborting_in_before_scenario_hook.feature" Then it should fail with: """ ABORTED: By user. 0 features passed, 1 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped, 2 untested 2 steps passed, 0 failed, 0 skipped, 0 undefined, 4 untested """ And the command output should contain: """ Feature: User aborts test run in before_scenario hook of S2 Scenario: S1 Given a step passes ... passed When another step passes ... passed """ But the command output should not contain "Scenario: S2 -- User aborts here" But the command output should not contain "Scenario: S3" And note that "the second and third scenario is not run" Scenario: Abort test run in after_scenario hook Given a file named "features/aborting_in_after_scenario_hook.feature" with: """ Feature: User aborts test run in after_scenario hook Scenario: Given a step passes When another step passes @user.aborts.after_scenario Scenario: User aborts here Given first step passes When second step passes Then third step passes Scenario: Then last step passes """ And a file named "features/environment.py" with: """ def after_scenario(context, scenario): if "user.aborts.after_scenario" in scenario.tags: raise KeyboardInterrupt() """ When I run "behave -f plain -T features/aborting_in_after_scenario_hook.feature" Then it should fail with: """ ABORTED: By user. 0 features passed, 1 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped, 1 untested 5 steps passed, 0 failed, 0 skipped, 0 undefined, 1 untested """ And the command output should contain: """ Feature: User aborts test run in after_scenario hook Scenario: Given a step passes ... passed When another step passes ... passed Scenario: User aborts here Given first step passes ... passed When second step passes ... passed Then third step passes ... passed """ But the command output should not contain: """ Scenario: Then last step passes ... passed """ And note that "the last scenario is not run" Scenario: Abort test run in before_feature hook Given a file named "features/aborting_in_before_feature_hook.feature" with: """ @user.aborts.before_feature Feature: User aborts test HERE Scenario: Given a step passes When another step passes Scenario: Given first step passes When second step passes Then third step passes Scenario: Then last step passes """ And a file named "features/environment.py" with: """ from __future__ import print_function def before_feature(context, feature): if "user.aborts.before_feature" in feature.tags: print("ABORTED in before_feature: %s" % feature.location) raise KeyboardInterrupt() """ When I run "behave -f plain -T features/aborting_in_before_feature_hook.feature" Then it should fail with: """ ABORTED in before_feature: features/aborting_in_before_feature_hook.feature:2 ABORTED: By user. 0 features passed, 0 failed, 0 skipped, 1 untested 0 scenarios passed, 0 failed, 0 skipped, 3 untested 0 steps passed, 0 failed, 0 skipped, 0 undefined, 6 untested """ But note that "the feature is not run" And note that "the formatters are not informed of this feature" Scenario: Abort test run in after_feature hook Given a file named "features/aborting_in_after_feature_hook.feature" with: """ @user.aborts.after_feature Feature: User aborts test after HERE Scenario: Given a step passes When another step passes Scenario: Given first step passes When second step passes Then third step passes Scenario: Then last step passes """ And a file named "features/environment.py" with: """ def after_feature(context, feature): if "user.aborts.after_feature" in feature.tags: raise KeyboardInterrupt() """ When I run "behave -f plain -T features/aborting_in_after_feature_hook.feature" Then it should fail with: """ ABORTED: By user. 1 feature passed, 0 failed, 0 skipped 3 scenarios passed, 0 failed, 0 skipped 6 steps passed, 0 failed, 0 skipped, 0 undefined """ But note that "the behave command fails, but all features/scenarios passed" Scenario: Abort test run in before_all hook Note that this situation is not handled very gracefully (yet). Given a file named "features/scenarios_pass3.feature" exists And a file named "features/environment.py" with: """ def before_all(context): raise KeyboardInterrupt() #< ABORT-HERE """ When I run "behave -f plain -T features/scenarios_pass3.feature" Then it should fail with: """ Traceback (most recent call last): """ And the command output should contain: """ File "features/environment.py", line 2, in before_all raise KeyboardInterrupt() #< ABORT-HERE """ And note that "no feature is not run" Scenario: Abort test run in after_all hook Note that this situation is not handled very gracefully (yet). Given a file named "features/scenarios_pass3.feature" exists And a file named "features/environment.py" with: """ def after_all(context): raise KeyboardInterrupt() #< ABORT-HERE """ When I run "behave -f plain -T features/scenarios_pass3.feature" Then it should fail with: """ Traceback (most recent call last): """ And the command output should contain: """ File "features/environment.py", line 2, in after_all raise KeyboardInterrupt() #< ABORT-HERE """ And note that "all features are run" behave-1.2.6/features/runner.context_cleanup.feature0000644000076600000240000002706413244555737022763 0ustar jensstaff00000000000000Feature: Perform Context.cleanups at the end of a test-run, feature or scenario (scope guards) As a test writer I want to perform cleanups (tear-downs) of functionality at the end of a test-run, feature or scenario So that I can easily implement the "scope guard" design pattern (Python: contextmanager) . SPECIFICATION: . * Context.add_cleanup(func) is provided to register cleanup functions . * Cleanup functions are registered in the current layer of the context object stack. . * Cleanup functions are executed before the current layer of the context object is removed/popped. . * Cleanup functions are executed in reverse order of registration . (LIFO: first registered cleanup function will be executed last, etc.) . * Cleanup functions are executed in best-effort mode: If one fails, the others are still executed . * Behaviour when the execution of cleanup function fails should be adjustable . . | Scope/Layer | Cleanup Registration Point | Cleanup Execution Point | . | test-run | In before_all() hook | After after_all() hook is executed. | . | feature | In before_feature() hook | After after_feature() hook is executed. | . | feature | In before_tag() in Feature | After after_feature() hook is executed. | . | scenario | In before_scenario() hook | After after_scenario() hook is executed. | . | scenario | In before_tag() in Scenario | After after_scenario() hook is executed. | . | scenario | In step implementation | After after_scenario() hook is executed. | . | scenario | In step hooks | After after_scenario() hook is executed. | @setup Scenario: Test Setup Given a new working directory And a file named "features/environment.py" with: """ from __future__ import print_function # -- CLEANUP FUNCTIONS: class CleanupFuntion(object): def __init__(self, name=None): self.name = name or "" def __call__(self): print("CALLED: CleanupFunction:%s" % self.name) def cleanup_after_testrun(): print("CALLED: cleanup_after_testrun") def cleanup_foo(): print("CALLED: cleanup_foo") def cleanup_bar(): print("CALLED: cleanup_bar") # -- HOOKS: def before_all(context): print("CALLED-HOOK: before_all") userdata = context.config.userdata use_cleanup = userdata.getbool("use_cleanup_after_testrun") if use_cleanup: print("REGISTER-CLEANUP: cleanup_after_testrun") context.add_cleanup(cleanup_after_testrun) def before_feature(context, feature): print("CALLED-HOOK: before_feature:%s" % feature.name) userdata = context.config.userdata use_cleanup = userdata.getbool("use_cleanup_after_feature") if use_cleanup and "cleanup.after_feature" in feature.tags: print("REGISTER-CLEANUP: cleanup_foo") context.add_cleanup(cleanup_foo) def after_feature(context, feature): print("CALLED-HOOK: after_feature: %s" % feature.name) def after_all(context): print("CALLED-HOOK: after_all") """ And a file named "features/steps/use_steps.py" with: """ import behave4cmd0.passing_steps """ And a file named "features/alice.feature" with: """ Feature: Alice Scenario: A1 Given a step passes Scenario: A2 When another step passes """ And a file named "behave.ini" with: """ [behave] show_timings = false stdout_capture = true """ @cleanup.after_testrun Scenario: Cleanup registered in before_all hook When I run "behave -D use_cleanup_after_testrun -f plain features/alice.feature" Then it should pass with: """ CALLED-HOOK: before_all REGISTER-CLEANUP: cleanup_after_testrun CALLED-HOOK: before_feature:Alice Feature: Alice """ And the command output should contain: """ Scenario: A2 When another step passes ... passed CALLED-HOOK: after_feature: Alice CALLED-HOOK: after_all CALLED: cleanup_after_testrun """ @cleanup.after_feature Scenario: Cleanup registered in before_feature hook Given a file named "features/environment.py" with: """ from __future__ import print_function # -- CLEANUP FUNCTIONS: def cleanup_foo(): print("CALLED: cleanup_foo") # -- HOOKS: def before_feature(context, feature): print("CALLED-HOOK: before_feature:%s" % feature.name) if "cleanup.after_feature" in feature.tags: print("REGISTER-CLEANUP: cleanup_foo") context.add_cleanup(cleanup_foo) def after_feature(context, feature): print("CALLED-HOOK: after_feature:%s" % feature.name) def after_all(context): print("CALLED-HOOK: after_all") """ And a file named "features/bob.feature" with: """ @cleanup.after_feature Feature: Bob Scenario: B1 Given a step passes """ When I run "behave -f plain features/bob.feature" Then it should pass with: """ CALLED-HOOK: before_feature:Bob REGISTER-CLEANUP: cleanup_foo Feature: Bob """ And the command output should contain: """ Scenario: B1 Given a step passes ... passed CALLED-HOOK: after_feature:Bob CALLED: cleanup_foo CALLED-HOOK: after_all """ @cleanup.after_scenario Scenario: Cleanup registered in before_scenario hook Given a file named "features/environment.py" with: """ from __future__ import print_function # -- CLEANUP FUNCTIONS: def cleanup_foo(): print("CALLED: cleanup_foo") # -- HOOKS: def before_scenario(context, scenario): print("CALLED-HOOK: before_scenario:%s" % scenario.name) if "cleanup_foo" in scenario.tags: print("REGISTER-CLEANUP: cleanup_foo") context.add_cleanup(cleanup_foo) def after_scenario(context, scenario): print("CALLED-HOOK: after_scenario:%s" % scenario.name) """ And a file named "features/charly.feature" with: """ Feature: Charly @cleanup_foo Scenario: C1 Given a step passes Scenario: C2 When a step passes """ When I run "behave -f plain features/charly.feature" Then it should pass with: """ CALLED-HOOK: before_scenario:C1 REGISTER-CLEANUP: cleanup_foo Scenario: C1 """ And the command output should contain: """ Scenario: C1 Given a step passes ... passed CALLED-HOOK: after_scenario:C1 CALLED: cleanup_foo CALLED-HOOK: before_scenario:C2 """ @cleanup.after_scenario Scenario: Cleanups are executed in reverse registration order Given a file named "features/environment.py" with: """ from __future__ import print_function # -- CLEANUP FUNCTIONS: def cleanup_foo(): print("CALLED: cleanup_foo") def cleanup_bar(): print("CALLED: cleanup_bar") # -- HOOKS: def before_scenario(context, scenario): print("CALLED-HOOK: before_scenario:%s" % scenario.name) if "cleanup_foo" in scenario.tags: print("REGISTER-CLEANUP: cleanup_foo") context.add_cleanup(cleanup_foo) if "cleanup_bar" in scenario.tags: print("REGISTER-CLEANUP: cleanup_bar") context.add_cleanup(cleanup_bar) def after_scenario(context, scenario): print("CALLED-HOOK: after_scenario:%s" % scenario.name) """ And a file named "features/dodo.feature" with: """ Feature: Dodo @cleanup_foo @cleanup_bar Scenario: D1 Given a step passes Scenario: D2 When a step passes """ When I run "behave -f plain features/dodo.feature" Then it should pass with: """ CALLED-HOOK: before_scenario:D1 REGISTER-CLEANUP: cleanup_foo REGISTER-CLEANUP: cleanup_bar Scenario: D1 """ And the command output should contain: """ Scenario: D1 Given a step passes ... passed CALLED-HOOK: after_scenario:D1 CALLED: cleanup_bar CALLED: cleanup_foo CALLED-HOOK: before_scenario:D2 """ And the command output should contain 1 times: """ CALLED: cleanup_bar CALLED: cleanup_foo """ @cleanup.after_scenario Scenario: Cleanup registered in step implementation Given a file named "features/environment.py" with: """ from __future__ import print_function # -- HOOKS: def before_scenario(context, scenario): print("CALLED-HOOK: before_scenario:%s" % scenario.name) def after_scenario(context, scenario): print("CALLED-HOOK: after_scenario:%s" % scenario.name) """ And a file named "features/steps/cleanup_steps.py" with: """ from behave import given # -- CLEANUP FUNCTIONS: def cleanup_foo(): print("CALLED: cleanup_foo") # -- STEPS: @given(u'I register a cleanup "{cleanup_name}"') def step_register_cleanup(context, cleanup_name): if cleanup_name == "cleanup_foo": context.add_cleanup(cleanup_foo) else: raise KeyError("Unknown_cleanup:%s" % cleanup_name) """ And a file named "features/emily.feature" with: """ Feature: Emily Scenario: E1 Given I register a cleanup "cleanup_foo" Scenario: E2 When a step passes """ When I run "behave -f plain features/emily.feature" Then it should pass with: """ Scenario: E1 Given I register a cleanup "cleanup_foo" ... passed CALLED-HOOK: after_scenario:E1 CALLED: cleanup_foo CALLED-HOOK: before_scenario:E2 """ And the command output should contain 1 times: """ CALLED: cleanup_foo """ @cleanup.after_scenario Scenario: Registered cleanup function args are passed to cleanup Given a file named "features/environment.py" with: """ from __future__ import print_function # -- CLEANUP FUNCTIONS: def cleanup_foo(text): print('CALLED: cleanup_foo("%s")' % text) # -- HOOKS: def before_scenario(context, scenario): print("CALLED-HOOK: before_scenario:%s" % scenario.name) if "cleanup_foo" in scenario.tags: print('REGISTER-CLEANUP: cleanup_foo("Alice")') context.add_cleanup(cleanup_foo, "Alice") print('REGISTER-CLEANUP: cleanup_foo("Bob")') context.add_cleanup(cleanup_foo, "Bob") def after_scenario(context, scenario): print("CALLED-HOOK: after_scenario:%s" % scenario.name) """ And a file named "features/frank.feature" with: """ Feature: Frank @cleanup_foo Scenario: F1 Given a step passes Scenario: F2 When a step passes """ When I run "behave -f plain features/frank.feature" Then it should pass with: """ CALLED-HOOK: before_scenario:F1 REGISTER-CLEANUP: cleanup_foo("Alice") REGISTER-CLEANUP: cleanup_foo("Bob") Scenario: F1 """ And the command output should contain: """ Scenario: F1 Given a step passes ... passed CALLED-HOOK: after_scenario:F1 CALLED: cleanup_foo("Bob") CALLED: cleanup_foo("Alice") CALLED-HOOK: before_scenario:F2 """ behave-1.2.6/features/runner.continue_after_failed_step.feature0000644000076600000240000001157313244555737025132 0ustar jensstaff00000000000000Feature: Runner should continue after a failed step As a tester or test writer I want that the remaining scenario steps are executed even when a step fails So that I see all remaining failures. . NOTES: . This may cause a number of correlated failures in the remaining steps. . Therefore the default behavior of the runner is to skip the . remaining scenario steps after a failure has occurred. . . RELATED TO: CR #299 (duplicated: #314) @setup Scenario: Test Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step import sys @step('{word:w} step passes') def step_passes(context, word): pass @step('{word:w} step fails') def step_fails(context, word): assert False, "XFAIL (in: %s step)" % word """ And a file named "features/alice.feature" with: """ Feature: Alice Scenario: Passing Given first step passes When second step passes Then third step passes Scenario: Fails in second step Given a step passes When second step fails Then another step passes @runner.continue_after_failed_step Scenario: Fails in first and third step Given first step fails When second step passes Then third step fails """ Scenario: Runner continues after failed step in all scenarios Given a file named "features/environment.py" with: """ from behave.model import Scenario def before_all(context): userdata = context.config.userdata continue_after_failed = userdata.getbool("runner.continue_after_failed_step", False) Scenario.continue_after_failed_step = continue_after_failed """ And a file named "behave.ini" with: """ [behave] show_timings = false [behave.userdata] runner.continue_after_failed_step = true """ When I run "behave -f plain features/alice.feature" Then it should fail with: """ Failing scenarios: features/alice.feature:7 Fails in second step features/alice.feature:13 Fails in first and third step 0 features passed, 1 failed, 0 skipped 1 scenario passed, 2 failed, 0 skipped 6 steps passed, 3 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Scenario: Fails in second step Given a step passes ... passed When second step fails ... failed Assertion Failed: XFAIL (in: second step) Then another step passes ... passed Scenario: Fails in first and third step Given first step fails ... failed Assertion Failed: XFAIL (in: first step) When second step passes ... passed Then third step fails ... failed Assertion Failed: XFAIL (in: third step) """ But note that "step execution continues after failed step(s)" And note that "no steps are skipped" Scenario: Runner continues after failed step in some scenarios Enable this feature only on tagged scenarios (or features). Given a file named "features/environment.py" with: """ def before_scenario(context, scenario): if "runner.continue_after_failed_step" in scenario.effective_tags: scenario.continue_after_failed_step = True """ When I run "behave -f plain -T features/alice.feature" Then it should fail with: """ Failing scenarios: features/alice.feature:7 Fails in second step features/alice.feature:13 Fails in first and third step 0 features passed, 1 failed, 0 skipped 1 scenario passed, 2 failed, 0 skipped 5 steps passed, 3 failed, 1 skipped, 0 undefined """ And the command output should contain: """ Scenario: Fails in second step Given a step passes ... passed When second step fails ... failed Assertion Failed: XFAIL (in: second step) Scenario: Fails in first and third step Given first step fails ... failed Assertion Failed: XFAIL (in: first step) When second step passes ... passed Then third step fails ... failed Assertion Failed: XFAIL (in: third step) """ But note that "step execution continues after failed step in tagged scenario only" And note that "some steps are skipped (in 2nd, untagged scenario)" behave-1.2.6/features/runner.default_format.feature0000644000076600000240000001221713244555737022556 0ustar jensstaff00000000000000@sequential Feature: Default Formatter . SPECIFICATION: . * Default formatter is used when no other formatter is specified/provided. . * Default formatter uses stdout as default output/outfile. . * Pretty formatter is the default formatter. . * Behave configfile can specify the default formatter. @setup Scenario: Test Setup Given a new working directory And a file named "features/steps/passing_steps.py" with: """ from behave import step @step('a step passes') def step_passes(context): pass """ And a file named "features/alice.feature" with: """ Feature: Alice Scenario: A1 Given a step passes When a step passes Then a step passes """ And a file named "features/bob.feature" with: """ Feature: Bob Scenario: B1 When a step passes Then a step passes """ @no_configfile Scenario: Pretty formatter is used as default formatter if no other is defined Given a file named "behave.ini" does not exist When I run "behave -c features/" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped """ And the command output should contain: """ Feature: Alice # features/alice.feature:1 Scenario: A1 # features/alice.feature:2 Given a step passes # features/steps/passing_steps.py:3 When a step passes # features/steps/passing_steps.py:3 Then a step passes # features/steps/passing_steps.py:3 Feature: Bob # features/bob.feature:1 Scenario: B1 # features/bob.feature:2 When a step passes # features/steps/passing_steps.py:3 Then a step passes # features/steps/passing_steps.py:3 """ @with_configfile Scenario: Configfile can define own default formatter Given a file named "behave.ini" with: """ [behave] default_format = plain show_timings = false """ When I run "behave features/" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped """ And the command output should contain: """ Feature: Alice Scenario: A1 Given a step passes ... passed When a step passes ... passed Then a step passes ... passed Feature: Bob Scenario: B1 When a step passes ... passed Then a step passes ... passed """ @with_configfile Scenario: Use default formatter with own outfile instead of stdout Given a file named "behave.ini" with: """ [behave] default_format = plain show_timings = false """ When I run "behave --outfile=output/default.out features/" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped """ And the file "output/default.out" should contain: """ Feature: Alice Scenario: A1 Given a step passes ... passed When a step passes ... passed Then a step passes ... passed Feature: Bob Scenario: B1 When a step passes ... passed Then a step passes ... passed """ @with_configfile Scenario: Can override default formatter from configfile on command-line Given a file named "behave.ini" with: """ [behave] default_format = plain show_timings = false """ When I run "behave -f progress features/" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped """ And the command output should contain: """ features/alice.feature . features/bob.feature . """ # -- Some formatter are specified in configfile. @with_configfile Scenario: Default formatter is used when non is provided on command-line Given a file named "behave.ini" with: """ [behave] default_format = plain format = progress outfiles = output/progress.out show_timings = false """ And I remove the directory "output" When I run "behave features/" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped """ And the command output should contain: """ Feature: Alice Scenario: A1 Given a step passes ... passed When a step passes ... passed Then a step passes ... passed Feature: Bob Scenario: B1 When a step passes ... passed Then a step passes ... passed """ And the file "output/progress.out" should contain: """ features/alice.feature . features/bob.feature . """ behave-1.2.6/features/runner.dry_run.feature0000644000076600000240000001463313244555737021250 0ustar jensstaff00000000000000@sequential Feature: Runner should support a --dry-run option As a tester I want to check if behave tests are syntactically correct And all step definitions exist Before I actually run the tests (by executing steps). . SPECIFICATION: Dry-run mode . * Undefined steps are detected . * Marks steps as "untested" or "undefined" . * Marks scenarios as "untested" . * Marks features as "untested" . * Causes no failed scenarios, features . * Causes failed test-run when undefined steps are found. @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step('a step passes') def step_passes(context): pass @step('a step fails') def step_fails(context): assert False, "XFAIL" """ And a file named "features/alice.feature" with: """ Feature: Alice @selected Scenario: A1 Given a step passes When a step passes Then a step passes @other_selected Scenario: A2 Given a step passes When a step fails Then a step passes @selected Scenario: A3 Given a step passes @selected Scenario: A4 Given a step fails """ And a file named "features/bob.feature" with: """ Feature: Bob Scenario: B1 Given a step passes When a step passes Then a step passes Scenario: B2 Given a step passes When a step fails Then a step passes """ And a file named "features/undefined_steps.feature" with: """ Feature: Undefined Steps @selected Scenario: U1 Given a step passes When a step is undefined Then a step fails @other_selected Scenario: U2 fails Given a step is undefined When a step passes And a step fails Then a step is undefined """ Scenario: Dry-run one feature should mark feature/scenarios/steps as untested When I run "behave -f plain --dry-run features/alice.feature" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 1 untested 0 scenarios passed, 0 failed, 0 skipped, 4 untested 0 steps passed, 0 failed, 0 skipped, 0 undefined, 8 untested """ And the command output should contain """ Scenario: A1 """ And the command output should contain: """ Scenario: A2 """ And the command output should contain: """ Scenario: A3 """ And the command output should contain: """ Scenario: A4 """ And note that "all scenarios of this feature are contained" Scenario: Dry-run one feature with tags should mark skipped scenario/steps as skipped When I run "behave -f plain --dry-run --tags=@selected --no-skipped features/alice.feature" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 1 untested 0 scenarios passed, 0 failed, 1 skipped, 3 untested 0 steps passed, 0 failed, 3 skipped, 0 undefined, 5 untested """ And the command output should contain: """ Scenario: A1 """ And the command output should contain: """ Scenario: A3 """ And the command output should contain: """ Scenario: A4 """ But the command output should not contain: """ Scenario: A2 """ And note that "only tagged scenarios of this feature are contained (3 of 4)" Scenario: Dry-run two features When I run "behave --dry-run features/alice.feature features/bob.feature" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 2 untested 0 scenarios passed, 0 failed, 0 skipped, 6 untested 0 steps passed, 0 failed, 0 skipped, 0 undefined, 14 untested """ Scenario: Dry-run one feature with undefined steps When I run "behave --dry-run features/undefined_steps.feature" Then it should fail with: """ 0 features passed, 0 failed, 0 skipped, 1 untested 0 scenarios passed, 0 failed, 0 skipped, 2 untested 0 steps passed, 0 failed, 0 skipped, 3 undefined, 4 untested """ Scenario: Dry-run two features, one with undefined steps When I run "behave --dry-run features/alice.feature features/undefined_steps.feature" Then it should fail with: """ 0 features passed, 0 failed, 0 skipped, 2 untested 0 scenarios passed, 0 failed, 0 skipped, 6 untested 0 steps passed, 0 failed, 0 skipped, 3 undefined, 12 untested """ Scenario: Dry-run two features, one with undefined steps and use tags When I run "behave --dry-run --tags=@selected features/alice.feature features/undefined_steps.feature" Then it should fail with: """ 0 features passed, 0 failed, 0 skipped, 2 untested 0 scenarios passed, 0 failed, 2 skipped, 4 untested 0 steps passed, 0 failed, 7 skipped, 1 undefined, 7 untested """ Scenario: Dry-run two features, one with undefined steps and use other tags When I run "behave --dry-run --tags=@other_selected features/alice.feature features/undefined_steps.feature" Then it should fail with: """ 0 features passed, 0 failed, 0 skipped, 2 untested 0 scenarios passed, 0 failed, 4 skipped, 2 untested 0 steps passed, 0 failed, 8 skipped, 2 undefined, 5 untested """ behave-1.2.6/features/runner.feature_listfile.feature0000644000076600000240000001476613244555737023123 0ustar jensstaff00000000000000@sequential Feature: Feature Listfile (List of feature filenames/directories) As a tester I want to run a list of features together And I do not want to provide the list each time But provide a text file that contains the list of features. . SPECIFICATION: behave file args . * Prepend an '@' char (AT) to the feature configfile name to classify it. . . SPECIFICATION: Feature listfile (text file) . * Each line contains a feature filename or directory with features . * Feature filenames/dirnames are relative to the feature configfile . * Empty lines are removed while reading . * Comment lines are removed while reading (start with '#' char) . * Wildcards are expanded to select 0..N files or directories. . * Wildcards for file locations are not supported (only filenames or dirs). . . SPECIFICATION: Runner . * Feature configfile with unknown/not found feature files . cause runner to fail. . . NOTE: Also supported by Cucumber. . RELATED: issue #75 @setup Scenario: Test Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step('a step passes') def step_passes(context): pass """ And a file named "features/alice.feature" with: """ Feature: Alice Scenario: Given a step passes """ And a file named "features/bob.feature" with: """ Feature: Bob Scenario: Given a step passes """ Scenario: Use @feature_listfile in WORKDIR directory (above features/) Given a file named "alice_and_bob2.txt" with: """ features/alice.feature features/bob.feature """ When I run "behave @alice_and_bob2.txt" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Use @feature_listfile in features/ subdirectory (Case 2) Given a file named "features/alice_and_bob.txt" with: """ alice.feature bob.feature """ When I run "behave @features/alice_and_bob.txt" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Use @feature_listfile with some empty lines Given a file named "features/alice_and_bob_with_empty_lines.txt" with: """ alice.feature bob.feature """ When I run "behave @features/alice_and_bob_with_empty_lines.txt" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Use @feature_listfile with some comment lines Given a file named "features/alice_and_bob_with_comment_lines.txt" with: """ alice.feature # -- USE: bob (comment line) bob.feature """ When I run "behave @features/alice_and_bob_with_comment_lines.txt" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Use @feature_listfile with wildcards (Case 1) Use feature list-file with wildcard pattern to select features. Given a file named "with_wildcard_feature.txt" with: """ features/a*.feature """ When I run "behave -f plain --no-timings @with_wildcard_feature.txt" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped """ And the command output should contain: """ Feature: Alice Scenario: Given a step passes ... passed """ Scenario: Use @feature_listfile with wildcards (Case 2) Use feature list-file with: - normal filename - wildcard pattern to select features Given a file named "with_wildcard_feature2.txt" with: """ features/alice.feature features/b*.feature """ When I run "behave -f plain --no-timings @with_wildcard_feature2.txt" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped """ And the command output should contain: """ Feature: Alice Scenario: Given a step passes ... passed Feature: Bob Scenario: Given a step passes ... passed """ Scenario: Use @feature_listfile with wildcards for file location (not supported) Note that wildcards are not supported when file locations are used. Given a file named "with_wildcard_location.txt" with: """ features/a*.feature:3 """ When I run "behave -f plain --no-timings @with_wildcard_location.txt" Then it should fail with: """ ConfigError: No steps directory in '{__WORKDIR__}' """ Scenario: Use empty @feature_listfile (Case 1) Given an empty file named "empty.txt" When I run "behave @empty.txt" Then it should fail with: """ No steps directory in '{__WORKDIR__}' """ Scenario: Use empty @feature_listfile in features subdirectory (Case 2) Given an empty file named "features/empty.txt" When I run "behave @features/empty.txt" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped """ Scenario: Use @feature_listfile with unknown feature file (Case 1) Given a file named "with_unknown_feature.txt" with: """ features/alice.feature features/UNKNOWN.feature """ When I run "behave @with_unknown_feature.txt" Then it should fail with: """ Error: [Errno 2] No such file or directory: '{__WORKDIR__}/features/UNKNOWN.feature' """ Scenario: Use @feature_listfile with unknown feature file (Case 2) Given a file named "features/with_unknown_feature2.txt" with: """ UNKNOWN.feature """ When I run "behave @features/with_unknown_feature2.txt" Then it should fail with: """ Error: [Errno 2] No such file or directory: '{__WORKDIR__}/features/UNKNOWN.feature' """ Scenario: Use unknown @feature_listfile When I run "behave @unknown_feature_configfile.txt" Then it should fail with: """ FileNotFoundError: unknown_feature_configfile.txt """ behave-1.2.6/features/runner.hook_errors.feature0000644000076600000240000003300413244555737022113 0ustar jensstaff00000000000000Feature: Hooks processing in case of errors (exceptions) . SPECIFICATION: . * Hook errors in before_all/after_all hook aborts the test run (and fail). . * Hook errors in before_feature/after_feature causes the feature to fail. . * Hook errors in before_feature causes feature scenarios to be skipped (untested). . * Hook errors in before_scenario/after_scenario causes the scenario to fail. . * Hook errors in before_scenario causes scenario steps to be skipped (untested). . * Hook errors in before_step/after_step causes step to fail. . * Hook errors in before_tag/after_tag of a feature causes feature to fail. . * Hook errors in before_tag/after_tag of a scenario causes scenario to fail. . * If a hook error occurs in a "before_xxx" hook, . then the "after_xxx" hook will also be called (to cleanup some stuff). . . NOTE: . The --verbose flag can be used to show the exception traceback. @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step(u'{word:w} step passes') def step(context, word): pass """ And a file named "features/passing.feature" with: """ @foo Feature: Alice @soo Scenario: A1 Given a step passes """ And a file named "features/environment.py" with: """ from __future__ import print_function def cause_hook_to_fail(): raise RuntimeError("FAIL") def before_all(context): userdata = context.config.userdata if userdata.get("HOOK_ERROR_LOC") == "before_all": cause_hook_to_fail() def after_all(context): userdata = context.config.userdata if userdata.get("HOOK_ERROR_LOC") == "before_all": print("called_hook:after_all") if userdata.get("HOOK_ERROR_LOC") == "after_all": cause_hook_to_fail() def before_feature(context, feature): userdata = context.config.userdata if userdata.get("HOOK_ERROR_LOC") == "before_feature": cause_hook_to_fail() def after_feature(context, feature): userdata = context.config.userdata if userdata.get("HOOK_ERROR_LOC") == "before_feature": print("called_hook:after_feature") if userdata.get("HOOK_ERROR_LOC") == "after_feature": cause_hook_to_fail() def before_scenario(context, scenario): userdata = context.config.userdata if userdata.get("HOOK_ERROR_LOC") == "before_scenario": cause_hook_to_fail() def after_scenario(context, scenario): userdata = context.config.userdata if userdata.get("HOOK_ERROR_LOC") == "before_scenario": print("called_hook:after_scenario") if userdata.get("HOOK_ERROR_LOC") == "after_scenario": cause_hook_to_fail() def before_step(context, step): userdata = context.config.userdata if userdata.get("HOOK_ERROR_LOC") == "before_step": cause_hook_to_fail() def after_step(context, step): userdata = context.config.userdata if userdata.get("HOOK_ERROR_LOC") == "before_step": print("called_hook:after_step") if userdata.get("HOOK_ERROR_LOC") == "after_step": cause_hook_to_fail() def before_tag(context, tag): userdata = context.config.userdata if userdata.get("HOOK_ERROR_LOC") == "before_tag": error_tag = userdata.get("HOOK_ERROR_TAG") if not error_tag or tag == error_tag: cause_hook_to_fail() def after_tag(context, tag): userdata = context.config.userdata if userdata.get("HOOK_ERROR_LOC") == "before_tag": error_tag = userdata.get("HOOK_ERROR_TAG") if tag == error_tag: print("called_hook:after_tag: tag=%s" % tag) if userdata.get("HOOK_ERROR_LOC") == "after_tag": error_tag = userdata.get("HOOK_ERROR_TAG") if not error_tag or tag == error_tag: cause_hook_to_fail() """ And a file named "behave.ini" with: """ [behave] show_skipped = false show_timings = false """ @hook.before_all Scenario: Hook error in before_all When I run "behave -f plain -D HOOK_ERROR_LOC=before_all features/passing.feature" Then it should fail with """ HOOK-ERROR in before_all: RuntimeError: FAIL called_hook:after_all ABORTED: By user. 0 features passed, 0 failed, 0 skipped, 1 untested 0 scenarios passed, 0 failed, 0 skipped, 1 untested 0 steps passed, 0 failed, 0 skipped, 0 undefined, 1 untested """ But note that "the after_all hook is called, too" @hook.after_all Scenario: Hook error in after_all When I run "behave -f plain -D HOOK_ERROR_LOC=after_all features/passing.feature" Then it should fail with: """ Feature: Alice Scenario: A1 Given a step passes ... passed HOOK-ERROR in after_all: RuntimeError: FAIL ABORTED: By user. 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ @hook.before_feature Scenario: Hook error in before_feature When I run "behave -f plain -D HOOK_ERROR_LOC=before_feature features/passing.feature" Then it should fail with: """ HOOK-ERROR in before_feature: RuntimeError: FAIL Feature: Alice called_hook:after_feature 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 0 failed, 0 skipped, 1 untested 0 steps passed, 0 failed, 0 skipped, 0 undefined, 1 untested """ But note that "the after_feature hook is called, too." @hook.after_feature Scenario: Hook error in after_feature When I run "behave -f plain -D HOOK_ERROR_LOC=after_feature features/passing.feature" Then it should fail with: """ Feature: Alice Scenario: A1 Given a step passes ... passed HOOK-ERROR in after_feature: RuntimeError: FAIL 0 features passed, 1 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ @hook.before_scenario Scenario: Hook error in before_scenario When I run "behave -f plain -D HOOK_ERROR_LOC=before_scenario features/passing.feature" Then it should fail with: """ Feature: Alice HOOK-ERROR in before_scenario: RuntimeError: FAIL Scenario: A1 called_hook:after_scenario Failing scenarios: features/passing.feature:4 A1 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped 0 steps passed, 0 failed, 0 skipped, 0 undefined, 1 untested """ But note that "the after_scenario hook is called, too." @hook.after_scenario Scenario: Hook error in after_scenario When I run "behave -f plain -D HOOK_ERROR_LOC=after_scenario features/passing.feature" Then it should fail with: """ Feature: Alice Scenario: A1 Given a step passes ... passed HOOK-ERROR in after_scenario: RuntimeError: FAIL Failing scenarios: features/passing.feature:4 A1 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ @hook.before_step Scenario: Hook error in before_step When I run "behave -f plain -D HOOK_ERROR_LOC=before_step features/passing.feature" Then it should fail with: """ Feature: Alice Scenario: A1 Given a step passes ... failed Captured stdout: HOOK-ERROR in before_step: RuntimeError: FAIL called_hook:after_step Failing scenarios: features/passing.feature:4 A1 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped 0 steps passed, 1 failed, 0 skipped, 0 undefined """ But note that "the after_step hook is called, too." @hook.after_step Scenario: Hook error in after_step When I run "behave -f plain -D HOOK_ERROR_LOC=after_step features/passing.feature" Then it should fail with: """ Feature: Alice Scenario: A1 Given a step passes ... failed Captured stdout: HOOK-ERROR in after_step: RuntimeError: FAIL Failing scenarios: features/passing.feature:4 A1 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped 0 steps passed, 1 failed, 0 skipped, 0 undefined """ @hook.before_tag Scenario: Hook error in before_tag for feature When I run "behave -f plain -D HOOK_ERROR_LOC=before_tag -D HOOK_ERROR_TAG=foo features/passing.feature" Then it should fail with: """ HOOK-ERROR in before_tag(tag=foo): RuntimeError: FAIL Feature: Alice called_hook:after_tag: tag=foo 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 0 failed, 0 skipped, 1 untested 0 steps passed, 0 failed, 0 skipped, 0 undefined, 1 untested """ And the command output should contain: """ HOOK-ERROR in before_tag(tag=foo): RuntimeError: FAIL Feature: Alice """ But note that "the hook-error in before_tag of the feature causes it to fail and be skipped (untested)" And note that "the after_tag hook is still called" @hook.after_tag Scenario: Hook error in after_tag for feature When I run "behave -f plain -D HOOK_ERROR_LOC=after_tag -D HOOK_ERROR_TAG=foo features/passing.feature" Then it should fail with: """ Feature: Alice Scenario: A1 Given a step passes ... passed HOOK-ERROR in after_tag(tag=foo): RuntimeError: FAIL 0 features passed, 1 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ But note that "the hook-error in after_tag of the scenario causes it to fail" @hook.before_tag Scenario: Hook error in before_tag for scenario When I run "behave -f plain -D HOOK_ERROR_LOC=before_tag -D HOOK_ERROR_TAG=soo features/passing.feature" Then it should fail with: """ Feature: Alice HOOK-ERROR in before_tag(tag=soo): RuntimeError: FAIL Scenario: A1 called_hook:after_tag: tag=soo Failing scenarios: features/passing.feature:4 A1 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped 0 steps passed, 0 failed, 0 skipped, 0 undefined, 1 untested """ But note that "the hook-error in before_tag of the scenario causes it to fail and be skipped (untested)" And note that "the after_tag hook is still called" @hook.after_tag Scenario: Hook error in after_tag for scenario When I run "behave -f plain -D HOOK_ERROR_LOC=after_tag -D HOOK_ERROR_TAG=soo features/passing.feature" Then it should fail with: """ Feature: Alice Scenario: A1 Given a step passes ... passed HOOK-ERROR in after_tag(tag=soo): RuntimeError: FAIL Failing scenarios: features/passing.feature:4 A1 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ But note that "the hook-error in after_tag of the scenario causes it to fail" @skipped.hook.after_feature Scenario: Skipped feature with potential hook error (hooks are not run) This goes unnoticed because hooks are not run for a skipped feature/scenario. NOTE: Except if before_feature(), before_scenario() hook skips the feature/scenario. When I run "behave -f plain -D HOOK_ERROR_LOC=after_feature -t ~@foo features/passing.feature" Then it should pass with: """ 0 features passed, 0 failed, 1 skipped 0 scenarios passed, 0 failed, 1 skipped 0 steps passed, 0 failed, 1 skipped, 0 undefined """ But note that "hooks are not executed for skipped features/scenarios" Scenario: Show hook error details (traceback) When I run "behave -f plain -D HOOK_ERROR_LOC=before_feature --verbose features/passing.feature" Then it should fail with: """ HOOK-ERROR in before_feature: RuntimeError: FAIL """ And the command output should contain: """ self.hooks[name](context, *args) File "features/environment.py", line 21, in before_feature cause_hook_to_fail() File "features/environment.py", line 4, in cause_hook_to_fail raise RuntimeError("FAIL") """ But note that "the traceback caused by the hook-error is shown" behave-1.2.6/features/runner.multiple_formatters.feature0000644000076600000240000002175413244555737023671 0ustar jensstaff00000000000000Feature: Multiple Formatter with different outputs . SPECIFICATION: Command-line option --format . * Each --format option specifies one formatter to use. . . SPECIFICATION: Command-line option --outfile . * Multiple --outfile options can be provided . * The nth --outfile option is used for the nth formatter . * If less --outfile options are provided than formatter, . the remaining formatter use stdout as output stream. . Therefore, the last formatter should in general use stdout. . . SPECIFICATION: Configuration file . * Option format with one or more formatters can be used (optional). . * Formatters specified in the configuration file are always executed. . * If not enough outfiles are specified, the outfiles list is extended . by using an outfile "${format}.output" for each missing outfile. . . RELATED TO: . * issue #47 Formatter chain is broken @setup Scenario: Test Setup Given a new working directory And a file named "features/steps/passing_steps.py" with: """ from behave import step @step('a step passes') def step_passes(context): pass """ And a file named "features/alice.feature" with: """ Feature: Alice Scenario: A1 Given a step passes When a step passes Then a step passes """ And a file named "features/bob.feature" with: """ Feature: Bob Scenario: B1 When a step passes Then a step passes """ @no_configfile Scenario: One formatter, no outfile (use stdout instead) Given a file named "behave.ini" does not exist When I run "behave -f plain -T features/" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped """ And the command output should contain: """ Feature: Alice Scenario: A1 Given a step passes ... passed When a step passes ... passed Then a step passes ... passed Feature: Bob Scenario: B1 When a step passes ... passed Then a step passes ... passed """ @no_configfile Scenario: One formatter, one outfile Given a file named "behave.ini" does not exist When I run "behave -f plain --outfile=output/plain.out -T features/" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped """ And the command output should not contain: """ Feature: Alice Scenario: A1 """ But a file named "output/plain.out" should exist And the file "output/plain.out" should contain: """ Feature: Alice Scenario: A1 Given a step passes ... passed When a step passes ... passed Then a step passes ... passed Feature: Bob Scenario: B1 When a step passes ... passed Then a step passes ... passed """ @no_configfile Scenario: Two formatter, one outfile Given a file named "behave.ini" does not exist When I run "behave -f plain -o output/plain.out -f progress -T features/" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped """ And the command output should contain: """ features/alice.feature . features/bob.feature . """ But a file named "output/plain.out" should exist And the file "output/plain.out" should contain: """ Feature: Alice Scenario: A1 Given a step passes ... passed When a step passes ... passed Then a step passes ... passed Feature: Bob Scenario: B1 When a step passes ... passed Then a step passes ... passed """ @no_configfile Scenario: More outfiles than formatter should fail with CONFIG-ERROR Given a file named "behave.ini" does not exist When I run "behave -f plain -o plain.output -o xxx.output features/" Then it should fail with: """ CONFIG-ERROR: More outfiles (2) than formatters (1). """ @with_configfile Scenario: Use default formatter and outfile from behave configuration file Given a file named "behave.ini" with: """ [behave] format = plain outfiles = output/plain.out show_timings = false """ And I remove the directory "output" When I run "behave features/" Then it should pass And the command output should not contain: """ Feature: Alice Scenario: A1 """ But a file named "output/plain.out" should exist And the file "output/plain.out" should contain: """ Feature: Alice Scenario: A1 Given a step passes ... passed When a step passes ... passed Then a step passes ... passed Feature: Bob Scenario: B1 When a step passes ... passed Then a step passes ... passed """ @with_configfile Scenario: Use default formatter and another without outfile from behave configuration file Given a file named "behave.ini" with: """ [behave] default_format = plain format = progress # -- OOPS: No outfile specified => Use "${format}.output" as outfile show_timings = false """ And I remove the directory "output" When I run "behave features/" Then it should pass And the command output should contain: """ Feature: Alice Scenario: A1 Given a step passes ... passed When a step passes ... passed Then a step passes ... passed Feature: Bob Scenario: B1 When a step passes ... passed Then a step passes ... passed """ And a file named "progress.output" should exist And the file "progress.output" should contain: """ features/alice.feature . features/bob.feature . """ @with_configfile Scenario: Command-line formatter/outfile extend behave configuration file args Given a file named "behave.ini" with: """ [behave] show_timings = false format = plain outfiles = output/plain.out """ And I remove the directory "output" When I run "behave -c -f pretty -o output/pretty.out -f progress -o output/progress.out features/" Then it should pass And the file "output/progress.out" should contain: """ features/alice.feature . features/bob.feature . """ And the file "output/pretty.out" should contain: """ Feature: Alice # features/alice.feature:1 Scenario: A1 # features/alice.feature:2 Given a step passes # features/steps/passing_steps.py:3 When a step passes # features/steps/passing_steps.py:3 Then a step passes # features/steps/passing_steps.py:3 Feature: Bob # features/bob.feature:1 Scenario: B1 # features/bob.feature:2 When a step passes # features/steps/passing_steps.py:3 Then a step passes # features/steps/passing_steps.py:3 """ And a file named "output/plain.out" should exist And the file "output/plain.out" should contain: """ Feature: Alice Scenario: A1 Given a step passes ... passed When a step passes ... passed Then a step passes ... passed Feature: Bob Scenario: B1 When a step passes ... passed Then a step passes ... passed """ @with_configfile Scenario: Combination of formatter from configfile and command-line cannot cause outfile offsets Given a file named "behave.ini" with: """ [behave] show_timings = false format = plain # -- OOPS: No outfiles defined => Use "${format}.output" as outfile. """ And I remove the directory "output" When I run "behave -f progress -o output/progress.out features/" Then it should pass And the command output should not contain: """ Feature: Alice Scenario: A1 Given a step passes ... passed When a step passes ... passed Then a step passes ... passed """ But the file "plain.output" should contain: """ Feature: Alice Scenario: A1 Given a step passes ... passed When a step passes ... passed Then a step passes ... passed """ And the file "output/progress.out" should contain: """ features/alice.feature . features/bob.feature . """ behave-1.2.6/features/runner.scenario_autoretry.feature0000644000076600000240000001100113244555737023471 0ustar jensstaff00000000000000Feature: Auto-retry failed scenarios (a number of times) As a tester in an unreliable environment I want that failed scenarios are automatically retried a number of times So that these scenarios are passing NOTE: This problem should occur rather seldom, but may occur when the server or network infrastructure, etc. is unreliable and causes random failures. @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/unreliable_steps.py" with: """ from behave import given, step unreliable_step_passed_calls = 0 @step(u'{word:w} unreliable step fails sometimes') def step_unreliable_step_fails_sometimes(context, word): global unreliable_step_passed_calls userdata = context.config.userdata fault_pos = userdata.getint("UNRELIABLE_FAULTPOS", 0) if fault_pos <= 0: return # -- NORMAL CASE: Unreliable step should every N calls. unreliable_step_passed_calls += 1 if unreliable_step_passed_calls >= fault_pos: unreliable_step_passed_calls = 0 assert False, "UNRELIABLE-STEP FAILURE" """ And a file named "features/steps/reuse_steps.py" with: """ import behave4cmd0.passing_steps """ And a file named "features/environment.py" with: """ from behave.contrib.scenario_autoretry import patch_scenario_with_autoretry def before_feature(context, feature): for scenario in feature.scenarios: if "autoretry" in scenario.effective_tags: patch_scenario_with_autoretry(scenario, max_attempts=2) """ And a file named "features/unreliable.feature" with: """ @autoretry Feature: Alice Scenario: A1 Given a step passes When an unreliable step fails sometimes Then another unreliable step fails sometimes Scenario: A2 Given an unreliable step fails sometimes Then another step passes """ And a file named "behave.ini" with: """ [behave] show_timings = false """ Scenario: Retry failed scenarios When I run "behave -f plain -D UNRELIABLE_FAULTPOS=3 features/unreliable.feature" Then it should pass with: """ 2 scenarios passed, 0 failed, 0 skipped 5 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ AUTO-RETRY SCENARIO (attempt 1) """ But the command output should not contain: """ AUTO-RETRY SCENARIO (attempt 2) """ And the command output should contain: """ Scenario: A1 Given a step passes ... passed When an unreliable step fails sometimes ... passed Then another unreliable step fails sometimes ... passed Scenario: A2 Given an unreliable step fails sometimes ... failed Assertion Failed: UNRELIABLE-STEP FAILURE AUTO-RETRY SCENARIO (attempt 1) Scenario: A2 Given an unreliable step fails sometimes ... passed Then another step passes ... passed """ Scenario: Scenarios failures are accepted after N=2 attempts When I run "behave -f plain -D UNRELIABLE_FAULTPOS=2 features/unreliable.feature" Then it should fail with: """ 1 scenario passed, 1 failed, 0 skipped 4 steps passed, 1 failed, 0 skipped, 0 undefined """ And the command output should not contain: """ AUTO-RETRY SCENARIO (attempt 2) SCENARIO-FAILURE ACCEPTED (after 2 attempts) """ And the command output should contain: """ Scenario: A1 Given a step passes ... passed When an unreliable step fails sometimes ... passed Then another unreliable step fails sometimes ... failed Assertion Failed: UNRELIABLE-STEP FAILURE AUTO-RETRY SCENARIO (attempt 1) Scenario: A1 Given a step passes ... passed When an unreliable step fails sometimes ... passed Then another unreliable step fails sometimes ... failed Assertion Failed: UNRELIABLE-STEP FAILURE AUTO-RETRY SCENARIO FAILED (after 2 attempts) Scenario: A2 Given an unreliable step fails sometimes ... passed Then another step passes ... passed """ behave-1.2.6/features/runner.select_files_by_regexp.example.feature0000644000076600000240000000353313244555737025722 0ustar jensstaff00000000000000Feature: Select feature files by using regular expressions (self-test) Use behave self-tests to ensure that --incude/--exclude options work. RELATED: runner.select_files_by_regexp.feature @setup Scenario: Feature Setup Given a new working directory And an empty file named "features/steps/steps.py" And a file named "features/alice.feature" with: """ Feature: Alice Scenario: A1 """ And a file named "features/barbi.feature" with: """ Feature: Barbi Scenario: B1 """ And a file named "features/bob.feature" with: """ Feature: Bob Scenario: B2 """ Scenario: Include only feature files Select the following feature files: barbi.feature, bob.feature When I run "behave --include='features/b.*' -f plain features/" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped """ And the command output should contain: """ Feature: Barbi Scenario: B1 Feature: Bob Scenario: B2 """ Scenario: Exclude only feature files Select the following feature files: alice.feature When I run "behave --exclude='features/b.*' -f plain features/" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped """ And the command output should contain: """ Feature: Alice """ Scenario: Include and exclude feature files Select the following feature files: alice.feature When I run "behave --include='features/.*a.*\.feature' --exclude='.*/barbi.*' -f plain features/" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped """ And the command output should contain: """ Feature: Alice """ behave-1.2.6/features/runner.select_files_by_regexp.feature0000644000076600000240000000533713244555737024274 0ustar jensstaff00000000000000@sequential Feature: Select feature files by using regular expressions As a tester I want to include/exclude feature files into/from a test run by using wildcards To be more flexible and avoid to specify all feature files . SPECIFICATION: . * behave provides --include and --exclude command line options . * --include option selects a subset of all files that should be included . * --exclude option is applied after include option is applied . . EXAMPLE: . behave --include="features/ali.*\.feature" ... . behave --exclude="features/ali.*" ... Background: Given behave has the following feature fileset: """ features/alice.feature features/bob.feature features/barbi.feature """ Scenario: Include only feature files When behave includes feature files with "features/a.*" And behave excludes no feature files Then the following feature files are selected: """ features/alice.feature """ Scenario: Include more than one feature file When behave includes feature files with "features/b.*" Then the following feature files are selected: """ features/bob.feature features/barbi.feature """ Scenario: Exclude only feature files When behave excludes feature files with "features/a.*" And behave includes all feature files Then the following feature files are selected: """ features/bob.feature features/barbi.feature """ Scenario: Exclude more than one feature file When behave excludes feature files with "features/b.*" Then the following feature files are selected: """ features/alice.feature """ Scenario: Include and exclude feature files Ensure that exclude file pattern is applied after include file pattern. When behave includes feature files with "features/.*a.*\.feature" And behave excludes feature files with ".*/barbi.*" Then the following feature files are selected: """ features/alice.feature """ Scenario: Include and exclude feature files (in 2 steps) Show how file inclusion/exclusion works by emulating the two steps. When behave includes feature files with "features/.*a.*\.feature" Then the following feature files are selected: """ features/alice.feature features/barbi.feature """ When behave excludes feature files with ".*/barbi.*" Then the following feature files are selected: """ features/alice.feature """ behave-1.2.6/features/runner.select_scenarios_by_file_location.feature0000644000076600000240000003320113244555737026464 0ustar jensstaff00000000000000@sequential Feature: Select Scenarios by File Location To simplify running only one scenario in a feature (or some scenarios) As a tester I want to select a scenario by using its file location, like: "alice.feature:10" (schema: {filename}:{line}) . CONCEPT: File Location . * A file location consists of file name and a positive line number . * A file location is represented as "{filename}:{line}" (or "{filename}") . * A file location with filename but without line number . refers to the complete file . * A file location with line number 0 (zero) refers to the complete file . . SPECIFICATION: Scenario selection by file locations . * scenario.line == file_location.line selects scenario (preferred method). . * Any line number in the following range is acceptable: . scenario.line <= file_location.line < next_scenario.line . * The first scenario is selected, . if the file location line number is less than first scenario.line. . * The last scenario is selected, . if the file location line number is greater than the lines in the file. . . SPECIFICATION: Runner with scenario locations (file locations) . * Adjacent file locations are merged if they refer to the same file, like: . . alice.feature:10 . alice.feature:20 . . => MERGED: Selects/runs "alice.feature" with 2 scenarios. . . alice.feature . alice.feature:20 . . => MERGED: Selects "alice.feature" with all scenarios. . . alice.feature:10 . bob.feature:20 . alice.feature:20 . . => NOT MERGED: Selects/runs "alice.feature" twice. . . * If file locations (scenario locations) are used, . scenarios with @setup or @teardown tags are selected, too. . . REASON: Simplifies to use a Setup Scenario instead of a Background. . . * Additional scenario selection mechanisms, like tags, names, . are applied afterwards. @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step('a step passes') def step_passes(context): pass """ And a file named "features/alice.feature" with: """ Feature: Alice Scenario: Alice First When a step passes Scenario: Alice Last Then a step passes """ And a file named "features/bob.feature" with: """ Feature: Bob @setup Scenario: Setup Bob Given a step passes Scenario: Bob in Berlin When a step passes Scenario: Bob in Paris Then a step passes @teardown Scenario: Teardown Bob Then a step passes """ And a file named "behave.ini" with: """ [behave] show_skipped = false show_timings = false """ @file_location.select Scenario: Select one scenario with its exact file location CASE: scenario.line == file_location.line When I run "behave -f plain --dry-run --no-skipped features/alice.feature:3" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 1 untested 0 scenarios passed, 0 failed, 1 skipped, 1 untested """ And the command output should contain: """ Feature: Alice Scenario: Alice First """ But the command output should not contain: """ Scenario: Alice Last """ @file_location.select Scenario: Select one scenario with a larger file location CASE: scenario.line <= file_location.line < next_scenario.line When I run "behave -f plain --dry-run --no-skipped features/alice.feature:4" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 1 untested 0 scenarios passed, 0 failed, 1 skipped, 1 untested """ And the command output should contain: """ Feature: Alice Scenario: Alice First """ But the command output should not contain: """ Scenario: Alice Last """ @file_location.select Scenario: Select next scenario with its exact location CASE: scenario.line < file_location.line == next_scenario.line When I run "behave -f plain --dry-run --no-skipped features/alice.feature:6" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 1 untested 0 scenarios passed, 0 failed, 1 skipped, 1 untested """ And the command output should contain: """ Feature: Alice Scenario: Alice Last """ But the command output should not contain: """ Scenario: Alice First """ @file_location.select_first Scenario: Select first scenario if line number is smaller than first scenario line CASE: 0 < file_location.line < first_scenario.line When I run "behave -f plain --dry-run --no-skipped features/alice.feature:1" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 1 untested 0 scenarios passed, 0 failed, 1 skipped, 1 untested """ And the command output should contain: """ Feature: Alice Scenario: Alice First """ But the command output should not contain: """ Scenario: Alice Last """ @file_location.select_last Scenario: Select last scenario when line number is too large CASE: last_scenario.line < file.last_line < file_location.line When I run "behave -f plain --dry-run --no-skipped features/alice.feature:100" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 1 untested 0 scenarios passed, 0 failed, 1 skipped, 1 untested """ And the command output should contain: """ Feature: Alice Scenario: Alice Last """ But the command output should not contain: """ Scenario: Alice First """ @file_location.select_all Scenario: Select all scenarios with line number 0 (zero) When I run "behave -f plain --dry-run --no-skipped features/alice.feature:0" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 1 untested 0 scenarios passed, 0 failed, 0 skipped, 2 untested """ And the command output should contain "Scenario: Alice First" And the command output should contain "Scenario: Alice Last" But note that "all scenarios of this features are selected" @file_location.select @with.feature_configfile Scenario: Select a scenario by using file locations from a features configfile Given a file named "alice1.txt" with: """ # -- FEATURES CONFIGFILE: # Selects Alice First features/alice.feature:3 """ When I run "behave -f plain --dry-run --no-skipped @alice1.txt" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 1 untested 0 scenarios passed, 0 failed, 1 skipped, 1 untested """ And the command output should contain: """ Feature: Alice Scenario: Alice First """ But the command output should not contain: """ Scenario: Alice Last """ @file_location.autoselect_setup_teardown Scenario: Auto-select scenarios tagged with @setup or @teardown if file location is used When I run "behave -f plain --dry-run --no-skipped features/bob.feature:7" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 1 untested 0 scenarios passed, 0 failed, 1 skipped, 3 untested """ And the command output should contain "Scenario: Setup Bob" And the command output should contain "Scenario: Bob in Berlin" And the command output should contain "Scenario: Teardown Bob" But the command output should not contain "Scenario: Bob in Paris" @merge.file_locations Scenario: Merge 2 adjacent file locations that refer to the same file When I run "behave -f plain --dry-run --no-skipped features/alice.feature:3 features/alice.feature:6" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 1 untested 0 scenarios passed, 0 failed, 0 skipped, 2 untested """ And the command output should contain: """ Feature: Alice Scenario: Alice First When a step passes ... untested Scenario: Alice Last Then a step passes ... untested """ But the command output should not contain: """ Feature: Alice Scenario: Alice First When a step passes ... untested Feature: Alice Scenario: Alice Last """ And note that "both file locations are merged" @merge.file_locations @file_location.select_all Scenario: Merge filename and adjacent file location that refer to the same file When I run "behave -f plain --dry-run --no-skipped features/alice.feature:3 features/alice.feature" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 1 untested 0 scenarios passed, 0 failed, 0 skipped, 2 untested """ And the command output should contain: """ Feature: Alice Scenario: Alice First When a step passes ... untested Scenario: Alice Last Then a step passes ... untested """ But the command output should not contain: """ Feature: Alice Scenario: Alice First When a step passes ... untested Feature: Alice Scenario: Alice First When a step passes ... untested Scenario: Alice Last Then a step passes ... untested """ And note that "duplicated file locations are removed in the merge" @merge.file_locations @with.feature_listfile Scenario: Merge 2 adjacent file locations to same file from features-listfile Given a file named "alice1_and_alice2.txt" with: """ # -- FEATURES CONFIGFILE: # Selects Alice First, Alice Last features/alice.feature:3 features/alice.feature:6 """ When I run "behave -f plain --dry-run --no-skipped @alice1_and_alice2.txt" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 1 untested 0 scenarios passed, 0 failed, 0 skipped, 2 untested """ And the command output should contain: """ Feature: Alice Scenario: Alice First When a step passes ... untested Scenario: Alice Last Then a step passes ... untested """ But the command output should not contain: """ Feature: Alice Scenario: Alice First When a step passes ... untested Feature: Alice Scenario: Alice Last Then a step passes ... untested """ @no_merge.file_locations @with.feature_listfile Scenario: No merge occurs if file locations to same file are not adjacent Given a file named "alice1_bob2_and_alice2.txt" with: """ # -- FEATURES CONFIGFILE: # Selects Alice First, Bob in Paris (Setup, Teardown), Alice Last features/alice.feature:3 features/bob.feature:10 features/alice.feature:6 """ When I run "behave -f plain --dry-run --no-skipped @alice1_bob2_and_alice2.txt" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 3 untested 0 scenarios passed, 0 failed, 3 skipped, 5 untested """ And the command output should contain: """ Feature: Alice Scenario: Alice First When a step passes ... untested Feature: Bob Scenario: Setup Bob Given a step passes ... untested Scenario: Bob in Paris Then a step passes ... untested Scenario: Teardown Bob Then a step passes ... untested Feature: Alice Scenario: Alice Last Then a step passes ... untested """ But the command output should not contain: """ Feature: Alice Scenario: Alice First When a step passes ... untested Scenario: Alice Last Then a step passes ... untested """ And note that "non-adjacent file locations to the same file are not merged" behave-1.2.6/features/runner.select_scenarios_by_name.feature0000644000076600000240000002617013244555737024604 0ustar jensstaff00000000000000@sequential Feature: Select named scenarios to run As a tester I want to select a subset of all scenarios By using their name or parts of the scenario name. . SPECIFICATION: When --name option is provided . * Name selection is applied only to scenarios (currently) . * A scenario is selected when scenario name part matches one of the provided names . * Regular expressions can be used to match parts . * If a scenario is not selected, it should be marked as skipped @setup Scenario: Test Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step('a step passes') def step_passes(context): pass """ And a file named "features/alice.feature" with: """ Feature: Alice Scenario: Alice in Wonderland Given a step passes Scenario: Alice in Florida When a step passes Scenario: Alice in Antarctica Then a step passes """ And a file named "features/bob.feature" with: """ Feature: Bob Scenario: Bob in Berlin Given a step passes Scenario: Bob in Florida When a step passes Scenario: Alice and Bob Then a step passes """ And a file named "behave.ini" with: """ [behave] show_skipped = false show_timings = false """ Scenario: Select scenarios with name="Alice" and inspect list When I run "behave -f plain --name="Alice" --dry-run features/" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 2 untested 0 scenarios passed, 0 failed, 2 skipped, 4 untested 0 steps passed, 0 failed, 2 skipped, 0 undefined, 4 untested """ And the command output should contain: """ Feature: Alice Scenario: Alice in Wonderland Given a step passes ... untested Scenario: Alice in Florida When a step passes ... untested Scenario: Alice in Antarctica Then a step passes ... untested Feature: Bob Scenario: Alice and Bob Then a step passes ... untested """ Scenario: Select scenarios with name="Alice" and run them When I run "behave -f plain -T --name="Alice" features/" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 4 scenarios passed, 0 failed, 2 skipped 4 steps passed, 0 failed, 2 skipped, 0 undefined """ And the command output should contain: """ Feature: Alice Scenario: Alice in Wonderland Given a step passes ... passed Scenario: Alice in Florida When a step passes ... passed Scenario: Alice in Antarctica Then a step passes ... passed Feature: Bob Scenario: Alice and Bob Then a step passes ... passed """ Scenario: Select scenarios with name="Bob" When I run "behave -f plain --name="Bob" --dry-run features/" Then it should pass with: """ 0 features passed, 0 failed, 1 skipped, 1 untested 0 scenarios passed, 0 failed, 3 skipped, 3 untested 0 steps passed, 0 failed, 3 skipped, 0 undefined, 3 untested """ And the command output should contain: """ Feature: Bob Scenario: Bob in Berlin Given a step passes ... untested Scenario: Bob in Florida When a step passes ... untested Scenario: Alice and Bob Then a step passes ... untested """ Scenario: Select scenarios with name="Florida" When I run "behave -f plain --name="Florida" --dry-run features/" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 2 untested 0 scenarios passed, 0 failed, 4 skipped, 2 untested 0 steps passed, 0 failed, 4 skipped, 0 undefined, 2 untested """ And the command output should contain: """ Feature: Alice Scenario: Alice in Florida When a step passes ... untested Feature: Bob Scenario: Bob in Florida When a step passes ... untested """ Scenario: Select scenarios with name that consists of multiple words When I run "behave -f plain --name="Alice and Bob" --dry-run features/" Then it should pass with: """ 0 features passed, 0 failed, 1 skipped, 1 untested 0 scenarios passed, 0 failed, 5 skipped, 1 untested """ And the command output should contain: """ Feature: Bob Scenario: Alice and Bob """ Scenario: Select scenarios by using two names When I run "behave -f plain --name="Alice" --name="Florida" --dry-run features/" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 2 untested 0 scenarios passed, 0 failed, 1 skipped, 5 untested 0 steps passed, 0 failed, 1 skipped, 0 undefined, 5 untested """ And the command output should contain: """ Feature: Alice Scenario: Alice in Wonderland Given a step passes ... untested Scenario: Alice in Florida When a step passes ... untested Scenario: Alice in Antarctica Then a step passes ... untested Feature: Bob Scenario: Bob in Florida When a step passes ... untested Scenario: Alice and Bob Then a step passes ... untested """ Scenario: Select scenarios by using a regular expression When I run "behave -f plain --name="Alice in .*" --dry-run features/" Then it should pass with: """ 0 features passed, 0 failed, 1 skipped, 1 untested 0 scenarios passed, 0 failed, 3 skipped, 3 untested """ And the command output should contain: """ Feature: Alice Scenario: Alice in Wonderland Given a step passes ... untested Scenario: Alice in Florida When a step passes ... untested Scenario: Alice in Antarctica Then a step passes ... untested """ But the command output should not contain: """ Scenario: Bob in """ @not.with_os=win32 Scenario: Exclude scenarios by using a regular expression (not-pattern) Select all scenarios that do not start with "Alice" in its name. Exclude all scenarios that start with "Alice" in its name. NOTE: Seems to work only for not-start-with idiom. When I run "behave -f plain --name='^(?!Alice)' --dry-run features/" Then it should pass with: """ 0 features passed, 0 failed, 1 skipped, 1 untested 0 scenarios passed, 0 failed, 4 skipped, 2 untested """ And the command output should contain: """ Feature: Bob Scenario: Bob in Berlin Given a step passes ... untested Scenario: Bob in Florida When a step passes ... untested """ But the command output should not contain: """ Scenario: Alice """ Scenario: Select scenarios by using another regular expression When I run "behave -f plain --name=".* in .*" --dry-run features/" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 2 untested 0 scenarios passed, 0 failed, 1 skipped, 5 untested """ And the command output should contain: """ Feature: Alice Scenario: Alice in Wonderland Given a step passes ... untested Scenario: Alice in Florida When a step passes ... untested Scenario: Alice in Antarctica Then a step passes ... untested Feature: Bob Scenario: Bob in Berlin Given a step passes ... untested Scenario: Bob in Florida When a step passes ... untested """ But the command output should not contain: """ Scenario: Alice and Bob """ Scenario: Select scenarios by using two regular expressions When I run "behave -f plain --name="Alice in .*" --name="Bob in .*" --dry-run features/" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 2 untested 0 scenarios passed, 0 failed, 1 skipped, 5 untested """ And the command output should contain: """ Feature: Alice Scenario: Alice in Wonderland Given a step passes ... untested Scenario: Alice in Florida When a step passes ... untested Scenario: Alice in Antarctica Then a step passes ... untested Feature: Bob Scenario: Bob in Berlin Given a step passes ... untested Scenario: Bob in Florida When a step passes ... untested """ But the command output should not contain: """ Scenario: Alice and Bob """ Scenario: Select scenarios by using an unknown/unused name When I run "behave -f plain --name="UNKNOWN" --dry-run features/" Then it should pass with: """ 0 features passed, 0 failed, 2 skipped 0 scenarios passed, 0 failed, 6 skipped """ Scenario: Select scenarios with special unicode names Given a file named "features/xantippe.feature" with: """ Feature: Use special unicode names Scenario: Ärgernis is everywhere Given a step passes Scenario: Second Ärgernis Then a step passes Scenario: Something else When a step passes """ When I run "behave -f plain --name="Ärgernis" --dry-run features/xantippe.feature" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 1 untested 0 scenarios passed, 0 failed, 1 skipped, 2 untested """ But the command output should not contain: """ UnicodeDecodeError: 'ascii' codec can't decode byte """ behave-1.2.6/features/runner.select_scenarios_by_tag.feature0000644000076600000240000001517113244555737024436 0ustar jensstaff00000000000000@sequential Feature: Select scenarios by using tags As a tester I want to select a subset of all scenarios by using tags (for selecting/including them or excluding them) So that I run only a subset of scenarios. . RELATED: . * runner.tag_logic.feature @setup Scenario: Feature Setup Given a new working directory And a file named "behave.ini" with: """ [behave] default_format = plain show_skipped = false show_timings = false """ And a file named "features/steps/steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass """ And a file named "features/alice.feature" with: """ Feature: Alice @foo Scenario: Alice in Wonderland Given a step passes @foo @bar Scenario: Alice in Florida When hotter step passes @bar Scenario: Alice in Antarctica Then colder step passes """ And a file named "features/bob.feature" with: """ Feature: Bob @bar Scenario: Bob in Berlin Given beautiful step passes @foo Scenario: Bob in Florida When freaky step passes Scenario: Alice and Bob Then another step passes """ Scenario: Select scenarios with tag=@foo (inclusive) TAG-LOGIC: @foo When I run "behave -f plain --tags=foo --no-skipped --no-timings features/" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 3 scenarios passed, 0 failed, 3 skipped 3 steps passed, 0 failed, 3 skipped, 0 undefined """ And the command output should contain: """ Feature: Alice Scenario: Alice in Wonderland Given a step passes ... passed Scenario: Alice in Florida When hotter step passes ... passed Feature: Bob Scenario: Bob in Florida When freaky step passes ... passed """ Scenario: Select scenarios without tag=@foo (exclusive) TAG-LOGIC: not @foo Use '-' (minus-sign) or '~' (tilde) in front of the tag-name to negate the tag-selection (excluding tags mode). When I run "behave --tags=~@foo features/" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 3 scenarios passed, 0 failed, 3 skipped 3 steps passed, 0 failed, 3 skipped, 0 undefined """ And the command output should contain: """ Feature: Alice Scenario: Alice in Antarctica Then colder step passes ... passed Feature: Bob Scenario: Bob in Berlin Given beautiful step passes ... passed Scenario: Alice and Bob Then another step passes ... passed """ Scenario: Select scenarios with @foo and @bar tags (both) TAG-LOGIC: @foo and @bar When I run "behave --tags=@foo --tags=@bar features/" Then it should pass with: """ 1 feature passed, 0 failed, 1 skipped 1 scenario passed, 0 failed, 5 skipped 1 step passed, 0 failed, 5 skipped, 0 undefined """ And the command output should contain: """ Feature: Alice Scenario: Alice in Florida When hotter step passes """ And note that "only scenario 'Alice in Florida' has both tags" Scenario: Select scenarios without @foo tag and without @bar tag TAG-LOGIC: not @foo and not @bar When I run "behave --tags=-@foo --tags=-@bar features/" Then it should pass with: """ 1 feature passed, 0 failed, 1 skipped 1 scenario passed, 0 failed, 5 skipped 1 step passed, 0 failed, 5 skipped, 0 undefined """ And the command output should contain: """ Feature: Bob Scenario: Alice and Bob Then another step passes """ And note that "only scenario 'Alice and Bob' has neither tag" Scenario: Select scenarios with @foo tag, but exclude with @bar tag TAG-LOGIC: @foo and not @bar When I run "behave --tags=@foo --tags=-@bar features/" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 4 skipped 2 steps passed, 0 failed, 4 skipped, 0 undefined """ And the command output should contain: """ Feature: Alice Scenario: Alice in Wonderland Given a step passes ... passed Feature: Bob Scenario: Bob in Florida When freaky step passes ... passed """ But note that "'Alice in Florida' is excluded because it has also @bar" Scenario: Select scenarios with @bar tag, but exclude with @foo tag TAG-LOGIC: not @foo and @bar When I run "behave --tags=-@foo --tags=@bar features/" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 4 skipped 2 steps passed, 0 failed, 4 skipped, 0 undefined """ And the command output should contain: """ Feature: Alice Scenario: Alice in Antarctica Then colder step passes ... passed Feature: Bob Scenario: Bob in Berlin Given beautiful step passes ... passed """ But note that "'Alice in Florida' is excluded because it has also @bar" Scenario: Select scenarios with tag=@foo in dry-run mode (inclusive) Ensure that tag-selection works also in dry-run mode. When I run "behave --tags=@foo --dry-run features/" Then it should pass with: """ 0 features passed, 0 failed, 0 skipped, 2 untested 0 scenarios passed, 0 failed, 3 skipped, 3 untested 0 steps passed, 0 failed, 3 skipped, 0 undefined, 3 untested """ And the command output should contain: """ Feature: Alice Scenario: Alice in Wonderland Given a step passes ... untested Scenario: Alice in Florida When hotter step passes ... untested Feature: Bob Scenario: Bob in Florida When freaky step passes ... untested """ behave-1.2.6/features/runner.stop_after_failure.feature0000644000076600000240000001020213244555737023427 0ustar jensstaff00000000000000@sequential Feature: Runner should stop after first failure if --stop option is used As a tester To abort testing early (sometimes) When the first failure occurs. @setup Scenario: Test Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step import sys @step('a step passes') def step_passes(context): pass @step('a step fails') def step_fails(context): assert False, "XFAIL" """ And a file named "features/alice_fails.feature" with: """ Feature: Alice Scenario: A1 Given a step passes When a step passes Then a step passes Scenario: A2 fails Given a step passes When a step fails Then a step passes Scenario: A3 Given a step passes Scenario: A4 fails Given a step fails """ And a file named "features/bob_passes.feature" with: """ Feature: Bob Scenario: B1 Given a step passes When a step passes Then a step passes Scenario: B2 fails Given a step passes When a step passes Then a step passes """ Scenario: Stop running after first failure with one feature When I run "behave -f plain -T --stop features/alice_fails.feature" Then it should fail with: """ Failing scenarios: features/alice_fails.feature:7 A2 fails 0 features passed, 1 failed, 0 skipped 1 scenario passed, 1 failed, 0 skipped, 2 untested 4 steps passed, 1 failed, 1 skipped, 0 undefined, 2 untested """ And the command output should contain: """ Feature: Alice Scenario: A1 Given a step passes ... passed When a step passes ... passed Then a step passes ... passed Scenario: A2 fails Given a step passes ... passed When a step fails ... failed Assertion Failed: XFAIL """ But the command output should not contain: """ Scenario: A3 """ Scenario: Stop running after first failure with several features (CASE 1) When I run "behave -f plain -T --stop features/alice_fails.feature features/bob_passes.feature " Then it should fail with: """ Failing scenarios: features/alice_fails.feature:7 A2 fails 0 features passed, 1 failed, 0 skipped, 1 untested 1 scenario passed, 1 failed, 0 skipped, 4 untested 4 steps passed, 1 failed, 1 skipped, 0 undefined, 8 untested """ Scenario: Stop running after first failure with several features (CASE 2: Different order) When I run "behave -f plain -T --stop features/bob_passes.feature features/alice_fails.feature" Then it should fail with: """ Failing scenarios: features/alice_fails.feature:7 A2 fails 1 feature passed, 1 failed, 0 skipped 3 scenarios passed, 1 failed, 0 skipped, 2 untested 10 steps passed, 1 failed, 1 skipped, 0 undefined, 2 untested """ Scenario: Stop running after first failure with several features (CASE 3: Use directory) When I run "behave -f plain -T --stop features/" Then it should fail with: """ Failing scenarios: features/alice_fails.feature:7 A2 fails 0 features passed, 1 failed, 0 skipped, 1 untested 1 scenario passed, 1 failed, 0 skipped, 4 untested 4 steps passed, 1 failed, 1 skipped, 0 undefined, 8 untested """ behave-1.2.6/features/runner.tag_logic.feature0000644000076600000240000000672613244555737021522 0ustar jensstaff00000000000000Feature: Runner Tag logic As a tester I want to select scenarios using logical AND/OR of tags In order to conveniently run subsets of these scenarios . SPECIFICATION: Tag logic . See "tag_expression.feature" description. . . RELATED: . * tag_expression.feature Scenario: Select scenarios with 1 tag (@foo) Given a behave model with: | statement | name | tags | Comment | | Scenario | S0 | | Untagged | | Scenario | S1 | @foo | With 1 tag | | Scenario | S2 | @other | | | Scenario | S3 | @foo @other | With 2 tags | And note that "are all combinations of 0..2 tags" When I run the behave model with "tags" Then the following scenarios are selected with cmdline: | cmdline | selected? | Logic comment | | | S0, S1, S2, S3 | ALL, no selector | | --tags=@foo | S1, S3 | @foo | | --tags=-@foo | S0, S2 | not @foo | Scenario: Use other tag expression variants Given a behave model with: | statement | name | tags | Comment | | Scenario | S0 | | Untagged | | Scenario | S1 | @foo | With 1 tag | | Scenario | S2 | @other | | | Scenario | S3 | @foo @other | With 2 tags | Then the following scenarios are selected with cmdline: | cmdline | selected? | Logic comment | | --tags=foo | S1, S3 | @foo, without optional @ | | --tags=-foo | S0, S2 | not @foo, without optional @ | | --tags=~foo | S0, S2 | not @foo, with tilde as minus | | --tags=~@foo | S0, S2 | not @foo, with tilde and @ | And note that "these tag expression variants can also be used" Scenario: Select scenarios with 2 tags (@foo, @bar) Given a behave model with: | statement | name | tags | Comment | | Scenario | S0 | | Untagged | | Scenario | S1 | @foo | With a tag | | Scenario | S2 | @bar | | | Scenario | S3 | @other | | | Scenario | S4 | @foo @bar | With 2 tags | | Scenario | S5 | @foo @other | | | Scenario | S6 | @bar @other | | | Scenario | S7 | @foo @bar @other | With 3 tags | And note that "are all combinations of 0..3 tags" When I run the behave model with "tags" Then the following scenarios are selected with cmdline: | cmdline | selected? | Logic comment | | | S0, S1, S2, S3, S4, S5, S6, S7 | ALL, no selector | | --tags=@foo,@bar | S1, S2, S4, S5, S6, S7 | @foo or @bar | | --tags=@foo,-@bar | S0, S1, S3, S4, S5, S7 | @foo or not @bar | | --tags=-@foo,-@bar | S0, S1, S2, S3, S5, S6 | not @foo or not @bar | | --tags=@foo --tags=@bar | S4, S7 | @foo and @bar | | --tags=@foo --tags=-@bar | S1, S5 | @foo and not @bar | | --tags=-@foo --tags=-@bar | S0, S3 | not @foo and not @bar | behave-1.2.6/features/runner.unknown_formatter.feature0000644000076600000240000000121113244555737023334 0ustar jensstaff00000000000000Feature: When an unknown formatter is used Scenario: Unknown formatter is used When I run "behave -f unknown1" Then it should fail with: """ behave: error: format=unknown1 is unknown """ Scenario: Unknown formatter is used together with another formatter When I run "behave -f plain -f unknown1" Then it should fail with: """ behave: error: format=unknown1 is unknown """ Scenario: Two unknown formatters are used When I run "behave -f plain -f unknown1 -f tags -f unknown2" Then it should fail with: """ behave: error: format=unknown1, unknown2 is unknown """ behave-1.2.6/features/runner.use_stage_implementations.feature0000644000076600000240000001104313244555737025025 0ustar jensstaff00000000000000Feature: Use Alternate Step Implementations for Each Test Stage As I tester and test writer I want to run the same feature with other step implementations during different testing stages So I can run quick checks in a development environment and detailed checks in a testlab. . CONCEPT: TEST STAGE . A test stage allows you to use different step/environment implementations . compared to other test stage or the default, unnamed test stage. . . Examples for test stages are: . * develop (using the development environment with more diagnostics) . * product (using the product database, ...) . * systemtest . * systemint (system integration) . * ... . . NOTE: . Test stages can be used to adapt to different test environments . while using these test stages. . . . EXAMPLE DIRECTORY LAYOUT (with default stage and stage=testlab): . . features/ . +-- steps/ # -- Step implementations for default stage. . | +-- foo_steps.py . +-- testlab_steps/ # -- Step implementations for stage=testlab. . | +-- foo_steps.py . +-- *.feature . +-- environment.py # -- Environment for default stage. . +-- testlab_environment.py # -- Environment for stage=testlab. @setup Scenario: Feature Setup Given a new working directory And a file named "features/example1.feature" with: """ Feature: Scenario: Given I do something in a test stage """ And a file named "features/environment.py" with: """ def before_all(context): context.use_develop_environment = False """ And a file named "features/steps/foo_steps.py" with: """ from behave import step @step('I do something in a test stage') def step_do_something(context): assert not context.config.stage assert not context.use_develop_environment """ And I remove the environment variable "BEHAVE_STAGE" Scenario: Use stage=develop Given a file named "features/develop_environment.py" with: """ def before_all(context): context.use_develop_environment = True """ And a file named "features/develop_steps/foo_steps.py" with: """ from behave import step @step('I do something in a test stage') def step_do_something(context): assert context.config.stage == "develop" assert context.use_develop_environment """ When I run "behave -c --stage=develop features/example1.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Scenario: # features/example1.feature:2 Given I do something in a test stage # features/develop_steps/foo_steps.py:3 """ Scenario: Use default stage Given I remove the environment variable "BEHAVE_STAGE" When I run "behave -c features/example1.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Scenario: # features/example1.feature:2 Given I do something in a test stage # features/steps/foo_steps.py:3 """ Scenario: Use the BEHAVE_STAGE environment variable to define the test stage Given I set the environment variable "BEHAVE_STAGE" to "develop" When I run "behave -c features/example1.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Scenario: # features/example1.feature:2 Given I do something in a test stage # features/develop_steps/foo_steps.py:3 """ But note that "I should better remove it again (TEARDOWN PHASE)" And I remove the environment variable "BEHAVE_STAGE" Scenario: Using an unknown stage When I run "behave -c --stage=unknown features/example1.feature" Then it should fail with: """ ConfigError: No unknown_steps directory """ behave-1.2.6/features/runner.use_substep_dirs.feature0000644000076600000240000001104113244555737023136 0ustar jensstaff00000000000000Feature: Use substep directories As a behave user I want to use a deep, hierarchical steps dir, meaning steps from subdirs of the steps directory, So that the steps directory structure is more cleaned up. . HINT: . If you really have so many step modules, . better use step-libraries, instead (in many cases). . . REQUIRES: path.py Background: Given a new working directory And a file named "features/steps/use_substep_dirs.py" with: """ # -- REQUIRES: path.py # from __future__ import absolute_import from behave.runner_util import load_step_modules from path import Path HERE = Path(__file__).dirname() SUBSTEP_DIRS = list(HERE.walkdirs()) load_step_modules(SUBSTEP_DIRS) """ And a file named "behave.ini" with: """ [behave] default_format = pretty color = False """ Scenario: Use one substep directory Given a file named "features/steps/alice_steps.py" with: """ from behave import step @step('I know Alice') def step_i_know_alice(context): pass """ And a file named "features/steps/foo/bob_steps.py" with: """ from behave import step @step('I know Bob') def step_i_know_bob(context): pass """ And a file named "features/use_substeps1.feature" with: """ Feature: Scenario: Given I know Alice And I know Bob """ When I run "behave features/use_substeps1.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: # features/use_substeps1.feature:1 Scenario: # features/use_substeps1.feature:2 Given I know Alice # features/steps/alice_steps.py:3 And I know Bob # features/steps/foo/bob_steps.py:3 """ Scenario: Use two substep directories Given a file named "features/steps/alice_steps.py" with: """ from behave import step @step('I know Alice') def step_i_know_alice(context): pass """ And a file named "features/steps/foo/bob_steps.py" with: """ from behave import step @step('I know Bob') def step_i_know_bob(context): pass """ And a file named "features/steps/bar/baz/charly_steps.py" with: """ from behave import step @step('I know Charly2') def step_i_know_charly2(context): pass """ And a file named "features/use_substeps2.feature" with: """ Feature: Scenario: Given I know Alice And I know Bob And I know Charly2 """ When I run "behave features/use_substeps2.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: # features/use_substeps2.feature:1 Scenario: # features/use_substeps2.feature:2 Given I know Alice # features/steps/alice_steps.py:3 And I know Bob # features/steps/foo/bob_steps.py:3 And I know Charly2 # features/steps/bar/baz/charly_steps.py:3 """ Scenario: Use one substep package directory Ensure that a dummy "__init__.py" does not cause any problems. Given a file named "features/steps/alice_steps.py" with: """ from behave import step @step('I know Alice') def step_i_know_alice(context): pass """ And an empty file named "features/steps/foo/__init__.py" And a file named "features/steps/foo/bob_steps.py" with: """ from behave import step @step('I know Bobby') def step_i_know_bob(context): pass """ And a file named "features/use_substeps3.feature" with: """ Feature: Scenario: Given I know Alice And I know Bobby """ When I run "behave features/use_substeps3.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: # features/use_substeps3.feature:1 Scenario: # features/use_substeps3.feature:2 Given I know Alice # features/steps/alice_steps.py:3 And I know Bobby # features/steps/foo/bob_steps.py:3 """ behave-1.2.6/features/scenario.description.feature0000644000076600000240000001356713244555737022410 0ustar jensstaff00000000000000Feature: Scenario Description As a tester I want to explain the rationale of a test scenario or scenario outline Before I actually execute the steps. . SPECIFICATION: Scenario Description . * Scenario descriptions are in optional section between . Scenario line and the first step. . * All description lines are added to the scenario description. . * Empty lines are not part of the scenario description (are removed). . * Comment lines are not part of the scenario description (are removed). . * A Scenario/ScenarioOutline with a scenario description, . but without steps is valid (to support preparation of scenarios). . . SPECIFICATION: A scenario description line... . * must not start with step keywords, like: . . Given, When, Then, And, But, etc. . (including lower-case versions) . . * must not start with '*' (ASTERISK) due to generic step keyword ambiguity . * must not start with '@' (AT) due to tag ambiguity . (supporting: scenario without steps but with step description). . * may start with '|' (table-cell-separator). . * does not contain only whitespace chars (empty line, filtered-out). . * does not start with '#' (HASH) after whitespace chars (comment line). . . GRAMMAR STRUCTURE: . Scenario-Line : 1 . Scenario-Description-Line : 0 .. N . Step-Line : 0 .. N . . Scenario-Line := Scenario-Keyword ':' Scenario-Name . Scenario-Description-Line := Line does not start with Step-Keyword . Step-Line := Step-Keyword Words+ @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step import sys @step('a step passes') def step_passes(context): pass @step('a step passes with "{comment}"') def step_passes(context, comment): sys.stdout.write("PASSING-STEP: %s;\n" % comment) @step('a step fails') def step_fails(context): assert False, "XFAIL-STEP" """ Scenario: First Example for a Scenario Description Given a file named "features/example_description1.feature" with: """ Feature: Scenario: E1 This is a simple scenario description before the steps start. It explains why this scenario is important. Here another scenario description line after an empty line. Given a step passes with "Alice" When a step passes with "Bob" Then a step passes with "Charly" """ When I run "behave -f plain -T features/example_description1.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: Scenario: E1 Given a step passes with "Alice" ... passed When a step passes with "Bob" ... passed Then a step passes with "Charly" ... passed """ Scenario: Inspect the Scenario Description by using JSON Given a file named "features/example_description1.feature" exists When I run "behave -f json.pretty -o example1.json -f plain -T features/example_description1.feature" Then it should pass And the file "example1.json" should contain: """ "description": [ "This is a simple scenario description before the steps start.", "It explains why this scenario is important.", "Here another scenario description line after an empty line." ], "keyword": "Scenario", "location": "features/example_description1.feature:2", "name": "E1", """ Scenario: Second Example with 2 scenario with scenario descriptions Given a file named "features/example_description2.feature" with: """ @one Feature: F2 Feature description line 1. Feature description line 2. @foo Scenario: S2.1 Scenario description line S2.1-1. Scenario description line S2.1-2 (indentation is removed). Given a step passes with "Alice" Then a step passes with "Charly" @foo @bar @baz Scenario: S2.2 Scenario description line S2.2-1. When a step passes with "Bob" """ When I run "behave -f json.pretty -o example2.json -f plain -T features/example_description2.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: F2 Scenario: S2.1 Given a step passes with "Alice" ... passed Then a step passes with "Charly" ... passed Scenario: S2.2 When a step passes with "Bob" ... passed """ And the file "example2.json" should contain: """ "description": [ "Scenario description line S2.1-1.", "Scenario description line S2.1-2 (indentation is removed)." ], "keyword": "Scenario", "location": "features/example_description2.feature:8", "name": "S2.1", """ And the file "example2.json" should contain: """ "description": [ "Scenario description line S2.2-1." ], "keyword": "Scenario", "location": "features/example_description2.feature:18", "name": "S2.2", """ behave-1.2.6/features/scenario.exclude_from_run.feature0000644000076600000240000001711313244555737023414 0ustar jensstaff00000000000000Feature: Exclude Scenario from Test Run As a test writer I want sometimes to decide at runtime that a scenario is excluded from a test run So that the command-line configuration becomes simpler (and auto-configuration is supported). . MECHANISM: . The "before_scenario()" hook can decide just before a scenario should run . that the scenario should be excluded from the test-run. . NOTE: Hooks are not called in dry-run mode. . . RATIONALE: . There are certain situations where it is better to skip a scenario . than to run and fail the scenario. . . Reasons for these cases are of often test environment related: . * test environment does not fulfill the desired criteria . * used testbed does not fulfill test requirements . . Instead of providing the exclude-scenario selection on the command-line, . the test (environment) and configuration logic should determine . if a test should be excluded (as auto-configuration functionality). . . EXAMPLE: . Certain scenarios should not run on Windows (or Linux, ...). . . EVALUATION ORDER: . Before the user can exclude a scenario from a test-run, . additional mechanisms decide, if the scenario is part of the selected run-set. . These are: . * tags . * ... . . RELATED: . * features/feature.exclude_from_run.feature @setup Scenario: Given a new working directory And a file named "features/example.feature" with: """ Feature: Scenario: Alice Given a step passes When another step passes Then some step passes Scenario: Bob and Alice Given some step passes Scenario: Bob Given another step passes """ And a file named "features/steps/steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass @step('{word:w} step fails') def step_fails(context, word): assert False, "XFAIL-STEP" """ @use_hook.before_scenario Scenario: Exclude a scenario from the test run (using: before_scenario() hook) Given a file named "features/environment.py" with: """ import sys def should_exclude_scenario(context, scenario): if scenario.name.startswith("Alice"): return True return False def before_scenario(context, scenario): if should_exclude_scenario(context, scenario): sys.stdout.write("EXCLUDED-BY-USER: Scenario %s\n" % scenario.name) scenario.skip() """ When I run "behave -f plain -T features/example.feature" Then it should pass with: """ 2 scenarios passed, 0 failed, 1 skipped 2 steps passed, 0 failed, 3 skipped, 0 undefined """ And the command output should contain: """ EXCLUDED-BY-USER: Scenario Alice """ @use_hook.before_feature Scenario: Exclude a scenario from the test run (using: before_feature() hook) Given a file named "features/environment.py" with: """ import sys def should_exclude_scenario(scenario): if "Alice" in scenario.name: # MATCHES: Alice, Bob and Alice return True return False def before_feature(context, feature): # -- NOTE: walk_scenarios() flattens ScenarioOutline.scenarios for scenario in feature.walk_scenarios(): if should_exclude_scenario(scenario): sys.stdout.write("EXCLUDED-BEFORE-FEATURE: Scenario %s\n" % scenario.name) scenario.skip() """ When I run "behave -f plain -T features/example.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 2 skipped 1 step passed, 0 failed, 4 skipped, 0 undefined """ And the command output should contain: """ EXCLUDED-BEFORE-FEATURE: Scenario Alice EXCLUDED-BEFORE-FEATURE: Scenario Bob and Alice """ Scenario: Skip scenario in a step Expect that remaining steps of the scenario are skipped. The skipping step is also marked as skipped to better detect scenarios that are partly executed and then skipped (otherwise a passed step would hide that the remaining steps are skipped). Given a file named "features/skip_scenario.feature" with: """ Feature: Scenario: Alice2 Given a step passes And the assumption "location:Wonderland" is not met When another step passes Then some step passes Scenario: Bob and Alice2 Given some step passes When I skip the remaining scenario Then another step passes """ And a file named "features/steps/skip_scenario_steps.py" with: """ from behave import given, step @given('the assumption "{name}" is not met') def step_assumption_not_met(context, name): context.scenario.skip("Assumption %s not met" % name) @step('I skip the remaining scenario') def step_skip_scenario(context): context.scenario.skip() """ And a file named "features/environment.py" with: """ # -- OVERRIDE WITH EMPTY-ENVIRONMENT. """ When I run "behave -f plain -T features/skip_scenario.feature" Then it should pass with: """ 0 scenarios passed, 0 failed, 2 skipped 2 steps passed, 0 failed, 5 skipped, 0 undefined """ And the command output should contain: """ Scenario: Alice2 Given a step passes ... passed And the assumption "location:Wonderland" is not met ... skipped Scenario: Bob and Alice2 Given some step passes ... passed When I skip the remaining scenario ... skipped """ But note that "the step that skipped the scenario is also marked as skipped" Scenario: Skip scenario in after_scenario hook Expect that scenario is not marked as skipped because it was already executed (with status: passed, failed, ...). Given a file named "features/pass_and_fail.feature" with: """ Feature: Scenario: Passing Given a step passes Scenario: Failing Given some step passes When a step fails """ And a file named "features/environment.py" with: """ def before_all(context): context.config.setup_logging() def after_scenario(context, scenario): scenario.skip("AFTER-SCENARIO") """ When I run "behave -f plain -T features/pass_and_fail.feature" Then it should fail with: """ 1 scenario passed, 1 failed, 0 skipped 2 steps passed, 1 failed, 0 skipped, 0 undefined """ But note that "the scenarios are not marked as skipped (SKIP-TOO-LATE)" behave-1.2.6/features/scenario_outline.basics.feature0000644000076600000240000000602313244555737023055 0ustar jensstaff00000000000000@issue Feature: Issue #187 ScenarioOutline uses wrong return value when if fails Ensure that ScenarioOutline run-logic behaves as expected. @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step('a step passes') def step_passes(context): pass @step('a step fails') def step_fails(context): assert False, "XFAIL-STEP" """ Scenario: All examples pass Given a file named "features/example.scenario_outline_pass.feature" with: """ Feature: All Examples pass Scenario Outline: Given a step Examples: | outcome | Comment | | passes | First example passes | | passes | Last example passes | """ When I run "behave -f plain features/example.scenario_outline_pass.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped """ @scenario_outline.fails Scenario: First example fails Given a file named "features/example.scenario_outline_fail_first.feature" with: """ Feature: First Example in Scenario Outline fails Scenario Outline: Given a step Examples: | outcome | Comment | | fails | First example fails | | passes | Last example passes | """ When I run "behave -f plain features/example.scenario_outline_fail_first.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 1 scenario passed, 1 failed, 0 skipped """ @scenario_outline.fails Scenario: Last example fails Given a file named "features/example.scenario_outline_fail_last.feature" with: """ Feature: Last Example in Scenario Outline fails Scenario Outline: Given a step Examples: | outcome | Comment | | passes | First example passes | | fails | Last example fails | """ When I run "behave -f plain features/example.scenario_outline_fail_last.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 1 scenario passed, 1 failed, 0 skipped """ @scenario_outline.fails Scenario: Middle example fails Given a file named "features/example.scenario_outline_fail_middle.feature" with: """ Feature: Middle Example in Scenario Outline fails Scenario Outline: Given a step Examples: | outcome | Comment | | passes | First example passes | | fails | Middle example fails | | passes | Last example passes | """ When I run "behave -f plain features/example.scenario_outline_fail_middle.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 2 scenarios passed, 1 failed, 0 skipped """ behave-1.2.6/features/scenario_outline.improved.feature0000644000076600000240000001407313244555737023442 0ustar jensstaff00000000000000Feature: Scenario Outline -- Improvements As a behave user / test writer I want that Scenario Outline (as paramtrized Scenario) is improved So that I know better which example/row combination is run. . REQUIREMENTS: . * Generated scenario name should better indicate row/example combination. . * Naming schema for generated scenario names should be configurable. . * File location of generated scenario should represent row/example. . * It should be possible select all scenarios of an examples group. . . IMPROVEMENTS: . * annotate Scenario Outline name (with row.id, examples.name, ...) . * use placeholders (from row/example) in Scenario Outline tags. . * use placeholders (from row/example) in Scenario Outline name. . * use placeholders (from row/example) in Examples (group) name. . * file location for generated scenario is unique (selectable in rerun) . . SPECIFICATION: Special placeholders . . | Placeholder | Description | . | name | Name of the Scenario Outline. | . | examples.name | Name of the examples group (or empty string). | . | examples.index | Index of examples group (range: 1..N). | . | row.index | Index of row in examples group (range: 1..R). | . | row.id | Same as: "{example.index}.{row.index}" | . . RELATED: . * scenario_outline.name_annotation.feature . * scenario_outline.parametrized.feature @setup Scenario: Test Setup Given a new working directory And a file named "behave.ini" with: """ [behave] scenario_outline_annotation_schema = {name} -- @{row.id} {examples.name} show_timings = false show_skipped = false """ And a file named "features/named_examples.feature" with: """ Feature: Scenario Outline: Named Examples Given a param Examples: Alice | param1 | | 10 | | 42 | Examples: Bob | param1 | | 43 | """ And a file named "features/unnamed_examples.feature" with: """ Feature: Scenario Outline: Unnamed Examples Given a param Examples: | param1 | | 100 | | 101 | """ And a file named "features/steps/param_steps.py" with: """ from behave import step @step('a param {value:w}') def step_impl_with_param(context, value): context.param = value @step('a param {name}={value}') def step_impl_with_param_value(context, name, value): context.param_name = name context.param_value = value """ Scenario: Unique File Locations in generated scenarios When I run "behave -f pretty -c features/named_examples.feature" Then it should pass with: """ Scenario Outline: Named Examples -- @1.1 Alice # features/named_examples.feature:7 Given a param 10 # features/steps/param_steps.py:3 Scenario Outline: Named Examples -- @1.2 Alice # features/named_examples.feature:8 Given a param 42 # features/steps/param_steps.py:3 Scenario Outline: Named Examples -- @2.1 Bob # features/named_examples.feature:12 Given a param 43 # features/steps/param_steps.py:3 """ But note that "each generated scenario has unique file location (related to row.line)" Scenario: Select generated scenario by unique File Location When I run "behave -f plain features/named_examples.feature:8" Then it should pass with: """ 1 scenario passed, 0 failed, 2 skipped 1 step passed, 0 failed, 2 skipped, 0 undefined """ And the command output should contain: """ Scenario Outline: Named Examples -- @1.2 Alice Given a param 42 ... passed """ @xfail Scenario: Select all generated scenarios of a Scenario Outline by File Location Given a file named "behave.ini" with: """ [behave] scenario_outline_annotation_schema = {name} -- @{row.id} {examples.name} """ When I run "behave -f plain features/named_examples.feature:2" Then it should pass with: """ 3 scenarios passed, 0 failed, 0 skipped 3 step passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Scenario Outline: Named Examples -- @1.2 Alice Given a param 42 ... passed """ @select.examples.by_name Scenario: Select Examples (Group) by Name (Case: name part) When I run "behave --name=Alice -f plain features/named_examples.feature" Then it should pass with: """ 2 scenarios passed, 0 failed, 1 skipped 2 steps passed, 0 failed, 1 skipped, 0 undefined """ And the command output should contain: """ Scenario Outline: Named Examples -- @1.1 Alice Given a param 10 ... passed Scenario Outline: Named Examples -- @1.2 Alice Given a param 42 ... passed """ @select.examples.by_name Scenario: Select Examples (Group) by Name (Case: regular expression) When I run "behave --name='-- @.* Alice' -f plain features/named_examples.feature" Then it should pass with: """ 2 scenarios passed, 0 failed, 1 skipped 2 steps passed, 0 failed, 1 skipped, 0 undefined """ And the command output should contain: """ Scenario Outline: Named Examples -- @1.1 Alice Given a param 10 ... passed Scenario Outline: Named Examples -- @1.2 Alice Given a param 42 ... passed """ @select.examples.by_name Scenario: Select one Example by Name When I run "behave --name='-- @1.2 Alice' -f plain features/named_examples.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 2 skipped 1 step passed, 0 failed, 2 skipped, 0 undefined """ And the command output should contain: """ Scenario Outline: Named Examples -- @1.2 Alice Given a param 42 ... passed """ behave-1.2.6/features/scenario_outline.name_annotation.feature0000644000076600000240000001171713244555737024771 0ustar jensstaff00000000000000Feature: Scenario Outline -- Scenario Name Annotations As a behave user / test writer I want to know in the current example/row combination So that I know the context of success/failure within a test run (without details). . REQUIREMENTS: . * generated scenario name should better indicate row/example combination. . * name annotation schema for generated scenario names should be configurable. . . IMPROVEMENTS: . * annotate Scenario Outline name (with row.id, examples.name, ...) . . . SCENARIO OUTLINE NAME ANNOTATION SCHEMA: . . scenario_outline_annotation_schema = "{name} -- @{row.id} {examples.name}" . . | Placeholder | Description | . | name | Name of the Scenario Outline. | . | examples.name | Name of the examples group (or empty string). | . | examples.index | Index of examples group (range: 1..N). | . | row.index | Index of row in examples group (range: 1..R). | . | row.id | Same as: "{example.index}.{row.index}" | @setup Scenario: Test Setup Given a new working directory And a file named "features/named_examples.feature" with: """ Feature: Scenario Outline: Named Examples Given a param Examples: Alice | param1 | | 10 | | 42 | Examples: Bob | param1 | | 43 | """ And a file named "features/unnamed_examples.feature" with: """ Feature: Scenario Outline: Unnamed Examples Given a param Examples: | param1 | | 100 | | 101 | """ And a file named "features/steps/param_steps.py" with: """ from behave import step @step('a param {value:w}') def step_impl_with_param(context, value): context.param = value @step('a param {name}={value}') def step_impl_with_param_value(context, name, value): context.param_name = name context.param_value = value """ Scenario: Use default annotation schema for generated scenarios (name annotations) Each example/row combination should be easy to spot. Given a file named "behave.ini" does not exist When I run "behave -f plain --no-timings features/named_examples.feature" Then it should pass with: """ Scenario Outline: Named Examples -- @1.1 Alice Given a param 10 ... passed Scenario Outline: Named Examples -- @1.2 Alice Given a param 42 ... passed Scenario Outline: Named Examples -- @2.1 Bob Given a param 43 ... passed """ And note that "the default annotation schema is: {name} -- @{row.id} {examples.name}" When I run "behave -f plain --no-timings features/unnamed_examples.feature" Then it should pass with: """ Scenario Outline: Unnamed Examples -- @1.1 Given a param 100 ... passed Scenario Outline: Unnamed Examples -- @1.2 Given a param 101 ... passed """ But note that "each generated scenario name has a unique annotation" And note that "each generated scenario name contains has its as annotation" Scenario: Use own annotation schema for generated scenarios (name annotations) Given a file named "behave.ini" with: """ [behave] scenario_outline_annotation_schema = {name} -*- {examples.name} @{row.id} """ When I run "behave -f plain --no-timings features/named_examples.feature" Then it should pass with: """ Scenario Outline: Named Examples -*- Alice @1.1 Given a param 10 ... passed Scenario Outline: Named Examples -*- Alice @1.2 Given a param 42 ... passed Scenario Outline: Named Examples -*- Bob @2.1 Given a param 43 ... passed """ When I run "behave -f plain --no-timings features/unnamed_examples.feature" Then it should pass with: """ Scenario Outline: Unnamed Examples -*- @1.1 Given a param 100 ... passed Scenario Outline: Unnamed Examples -*- @1.2 Given a param 101 ... passed """ Scenario: Disable name annotations (use: old naming scheme) Given a file named "behave.ini" with: """ [behave] scenario_outline_annotation_schema = {name} """ When I run "behave -f plain --no-timings features/named_examples.feature" Then it should pass with: """ Scenario Outline: Named Examples Given a param 10 ... passed Scenario Outline: Named Examples Given a param 42 ... passed Scenario Outline: Named Examples Given a param 43 ... passed """ When I run "behave -f plain --no-timings features/unnamed_examples.feature" Then it should pass with: """ Scenario Outline: Unnamed Examples Given a param 100 ... passed Scenario Outline: Unnamed Examples Given a param 101 ... passed """ behave-1.2.6/features/scenario_outline.parametrized.feature0000644000076600000240000003464713244555737024315 0ustar jensstaff00000000000000Feature: Scenario Outline -- Parametrized Scenarios As a test writer I want to use the DRY principle when writing scenarios So that I am more productive and my work is less error-prone. . COMMENT: . A Scenario Outline is basically a parametrized Scenario template. . It is instantiated for each examples row with the corresponding data. . . SCENARIO GENERICS BASICS: What can be parametrized? . * Scenario name: Based on Scenario Outline with placeholders . * Scenario steps: Step name with placeholders (including: step.text, step.table) . * Scenario tags: Based on Scenario Outline tags with placeholders . . CONSTRAINTS: . * placeholders (names) for tags should not contain any whitespace. . * a row data placeholder may override/hide a special placeholder (see below). . . SPECIAL PLACEHOLDERS: . | Placeholder | Description | . | examples.name | Name of the examples group (or empty string). | . | examples.index | Index of examples group (range: 1..N). | . | row.index | Index of row in examples group (range: 1..R). | . | row.id | Same as: "{example.index}.{row.index}" | @setup Scenario: Test Setup Given a new working directory And a file named "behave.ini" with: """ [behave] scenario_outline_annotation_schema = {name} -*- {examples.name} """ And a file named "features/steps/param_steps.py" with: """ from behave import step @step('a param {value:w}') def step_with_param(context, value): context.param = value @step('a param {name}={value}') def step_with_param_value(context, name, value): context.param_name = name context.param_value = value @step('a step with text') def step_with_param_value(context): assert context.text is not None, "REQUIRE: text" @step('a step with table') def step_with_param_value(context): assert context.table is not None, "REQUIRE: table" @step('an unknown param {name}={value}') def step_with_unknown_param_value(context, name, value): pass """ @parametrize.name Scenario: Parametrized name in generated Scenarios (Case: Row Placeholders) Given a file named "features/use_name_with_row_params.feature" with: """ Feature: SOP1 Scenario Outline: Use placeholder - Given a param param1= And a param name= Examples: E- | ID | name | param1 | | 001 | Alice | 100 | | 002 | Bob | 101 | """ When I run "behave -f progress3 features/use_name_with_row_params.feature" Then it should pass with: """ Use placeholder Alice-100 -*- E-001 .. Use placeholder Bob-101 -*- E-002 .. """ @parametrize.name Scenario: Parametrized name in generated Scenarios (Case: Special Placeholders) Given a file named "features/use_name_with_special_params.feature" with: """ Feature: SOP2 Scenario Outline: Use placeholder S- Given a param name= And a param row.id= Examples: E-/@ | ID | name | param1 | | 001 | Alice | 100 | | 002 | Bob | 101 | """ When I run "behave -f progress3 features/use_name_with_special_params.feature" Then it should pass with: """ Use placeholder S1-1 E-001/@1.1 -*- E-001/@1.1 .. Use placeholder S1-2 E-002/@1.2 -*- E-002/@1.2 .. """ @parametrize.steps Scenario: Use placeholders in generated scenario steps Given a file named "features/use_steps_with_params.feature" with: """ Feature: Scenario Outline: Use row placeholders Given a param ID= And a param name= And a param param1= Examples: | ID | name | param1 | | 001 | Alice | 101 | | 002 | Bob | 102 | """ When I run "behave -f plain --no-timings features/use_steps_with_params.feature" Then it should pass with: """ Scenario Outline: Use row placeholders -*- Given a param ID=001 ... passed And a param name=Alice ... passed And a param param1=101 ... passed Scenario Outline: Use row placeholders -*- Given a param ID=002 ... passed And a param name=Bob ... passed And a param param1=102 ... passed """ @parametrize.steps Scenario: Use an unknown placeholder in generated scenario steps Given a file named "features/use_steps_with_unknown_params.feature" with: """ Feature: Scenario Outline: Use unknown placeholders Given an unknown param unknown= Examples: | ID | name | param1 | | 001 | Alice | 100 | """ When I run "behave -f plain --no-timings features/use_steps_with_unknown_params.feature" Then it should pass with: """ Scenario Outline: Use unknown placeholders -*- Given an unknown param unknown= ... passed """ But note that "unknown placeholders are not replaced" @parametrize.steps Scenario: Use placeholders in generated scenario step.text Given a file named "features/use_steps_with_param_text.feature" with: ''' Feature: Scenario Outline: Use parametrized step with text Given a step with text: """ ; Travel agency: """ Examples: | ID | name | greeting | travel agency | | 001 | Alice | Hello | Pony express | ''' When I run "behave -f plain --no-timings features/use_steps_with_param_text.feature" Then it should pass with: ''' Scenario Outline: Use parametrized step with text -*- Given a step with text ... passed """ Hello Alice; Travel agency: Pony express """ ''' And note that "placeholders in step.text are replaced with row data" @parametrize.steps Scenario: Use placeholders in generated scenario step.table Given a file named "features/use_steps_with_param_table.feature" with: """ Feature: Scenario Outline: Use parametrized step with table Given a step with table: | Id | Name | Travel Agency | row id | | | | | | Examples: | ID | name | greeting | travel agency | | 001 | Alice | Hello | Pony express | """ When I run "behave -f plain --no-timings features/use_steps_with_param_table.feature" Then it should pass with: """ Scenario Outline: Use parametrized step with table -*- Given a step with table ... passed | Id | Name | Travel Agency | row id | | 001 | Alice | Pony express | | """ And note that "placeholders in step.table cells are replaced with row data" But note that " is currently not supported in table cells (like other special placeholders)" @parametrize.steps Scenario: Use special placeholders in generated scenario steps Given a file named "features/use_steps_with_special_params.feature" with: """ Feature: Scenario Outline: Use special placeholders @ Given a param name= And a param examples.name= And a param examples.index= And a param row.index= And a param row.id= Examples: E-/@ | ID | name | param1 | | 001 | Alice | 100 | | 002 | Bob | 101 | """ When I run "behave -f plain --no-timings features/use_steps_with_special_params.feature" Then it should pass with: """ Scenario Outline: Use special placeholders @1.1 -*- E-001/@1.1 Given a param name=Alice ... passed And a param examples.name=E-001/@1.1 ... passed And a param examples.index=1 ... passed And a param row.index=1 ... passed And a param row.id=1.1 ... passed Scenario Outline: Use special placeholders @1.2 -*- E-002/@1.2 Given a param name=Bob ... passed And a param examples.name=E-002/@1.2 ... passed And a param examples.index=1 ... passed And a param row.index=2 ... passed And a param row.id=1.2 ... passed """ @parametrize.name @parametrize.steps Scenario: When special placeholder name is used for row data (Case: Override) Ensure that row data (placeholder) can override special placeholder (if needed). AFFECTED: scenario.name, examples.name, step.name Given a file named "behave.ini" with: """ [behave] scenario_outline_annotation_schema = {name} -*- special:row.id=@{row.id} {examples.name} """ And a file named "features/use_name_with_overwritten_params.feature" with: """ Feature: SOP3 Scenario Outline: Use placeholder data:row.id= Given a param name= And a param row.id= Examples: E-/ | ID | name | row.id | | 001 | Alice | 100 | | 002 | Bob | 101 | """ When I run "behave -f progress3 features/use_name_with_overwritten_params.feature" Then it should pass with: """ Use placeholder data:row.id=100 -*- special:row.id=@1.1 E-001/100 .. Use placeholder data:row.id=101 -*- special:row.id=@1.2 E-002/101 .. """ When I run "behave -f plain --no-timings features/use_name_with_overwritten_params.feature" Then it should pass with: """ Scenario Outline: Use placeholder data:row.id=100 -*- special:row.id=@1.1 E-001/100 Given a param name=Alice ... passed And a param row.id=100 ... passed Scenario Outline: Use placeholder data:row.id=101 -*- special:row.id=@1.2 E-002/101 Given a param name=Bob ... passed And a param row.id=101 ... passed """ But note that "a row data placeholder can override a special placeholder" And note that "the name annotation {row.id} still allows to refer to the special one" @parametrize.steps Scenario: Placeholder value has placeholder syntax (Case: recursion-check) Ensure that weird placeholder value cases are handled correctly. Ensure that no recursion occurs. Given a file named "behave.ini" with: """ [behave] scenario_outline_annotation_schema = {name} -*- @{row.id} {examples.name} """ And a file named "features/use_value_with_placeholder_syntax.feature" with: """ Feature: Scenario Outline: Use weird placeholder values Given a param name= Examples: | name | Expected | ID | Case: | | | 001 | 001 | Value refers to other, known placeholder.| | | 002 | 002 | Check if row specific value is used. | | | | 003 | Value refers to unknown placeholder. | | | | 004 | Value refers to itself (recursion?). | """ When I run "behave -f plain --no-timings features/use_value_with_placeholder_syntax.feature" Then it should pass with: """ Scenario Outline: Use weird placeholder values -*- @1.1 Given a param name=001 ... passed Scenario Outline: Use weird placeholder values -*- @1.2 Given a param name=002 ... passed Scenario Outline: Use weird placeholder values -*- @1.3 Given a param name= ... passed Scenario Outline: Use weird placeholder values -*- @1.4 Given a param name= ... passed """ @simple.case @parametrize.tags Scenario: Parametrized tags in a Scenario Outline Given a file named "behave.ini" with: """ [behave] scenario_outline_annotation_schema = {name} -*- @{row.id} {examples.name} """ And a file named "features/parametrized_tags.feature" with: """ Feature: @foo @outline.e @outline.row. @outline.ID. Scenario Outline: Use parametrized tags Given a param name= Examples: | ID | name | | 001 | Alice | | 002 | Bob | """ When I run "behave -f pretty -c --no-timings features/parametrized_tags.feature" Then it should pass with: """ @foo @outline.e1 @outline.row.1.1 @outline.ID.001 Scenario Outline: Use parametrized tags -*- @1.1 # features/parametrized_tags.feature:8 Given a param name=Alice # features/steps/param_steps.py:7 @foo @outline.e1 @outline.row.1.2 @outline.ID.002 Scenario Outline: Use parametrized tags -*- @1.2 # features/parametrized_tags.feature:9 Given a param name=Bob # features/steps/param_steps.py:7 """ But note that "special and row data placeholders can be used in tags" @parametrize.tags Scenario: Parametrized tags in a Scenario Outline (Case: Whitespace... in value) Given a file named "behave.ini" with: """ [behave] scenario_outline_annotation_schema = {name} -*- @{row.id} {examples.name} """ And a file named "features/parametrized_tags2.feature" with: """ Feature: @outline.name. Scenario Outline: Use parametrized tags Given a param name= Examples: | ID | name | Case: | | 001 | Alice Cooper | Placeholder value w/ whitespace | | 002 | Bob\tMarley | Placeholder value w/ tab | | 003 | Joe\nCocker | Placeholder value w/ newline | """ When I run "behave -f pretty -c --no-source features/parametrized_tags2.feature" Then it should pass with: """ @outline.name.Alice_Cooper Scenario Outline: Use parametrized tags -*- @1.1 Given a param name=Alice Cooper @outline.name.Bob_Marley Scenario Outline: Use parametrized tags -*- @1.2 Given a param name=Bob\tMarley @outline.name.Joe_Cocker Scenario Outline: Use parametrized tags -*- @1.3 Given a param name=Joe\nCocker """ But note that "placeholder values with special chars (whitespace, ...) are transformed" behave-1.2.6/features/scenario_outline.tagged_examples.feature0000644000076600000240000000656313244555737024753 0ustar jensstaff00000000000000Feature: ScenarioOutline with tagged Examples As a tester I want to select (or exclude) a specific Examples table of a ScenarioOutline To run only the scenarios from the Examples table of interest. | SPECIFICATION: | Scenarios from a Examples table inherit the tags from | its ScenarioOutline tags and its Examples tags: | | scenario.tags = scenario_outline.tags + examples.tags | | Therefore, examples.tags can easily be used to select all scenarios | of this Examples table. | | NOTE: This allows to provide multiple Examples sections, | one for each stage of testing and/or one for each test team | (development testing, integration testing, system tests, ...). @setup Scenario: Feature Setup Given a new working directory And a file named "behave.ini" with: """ [behave] show_timestamps = false show_skipped = false """ And a file named "features/steps/steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass """ And a file named "features/tagged_examples.feature" with: """ Feature: @zap Scenario Outline: Given step passes @foo Examples: Alice | variant | Comment | | a | First case | | another | Second case | @bar Examples: Bob | variant | Comment | | weird | First case | """ And a file named "behave.ini" with: """ [behave] show_skipped = false show_timings = false """ Scenario: Use all Examples (and Scenarios) When I run "behave -f plain features/tagged_examples.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 3 scenarios passed, 0 failed, 0 skipped """ Scenario: Select all Examples (and Scenarios) When I run "behave -f plain --tags=@zap features/tagged_examples.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 3 scenarios passed, 0 failed, 0 skipped """ Scenario: Select only first Examples table When I run "behave -f plain --tags=@foo features/tagged_examples.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 1 skipped """ And the command output should contain: """ Scenario Outline: -- @1.1 Alice Given a step passes ... passed Scenario Outline: -- @1.2 Alice Given another step passes ... passed """ But the command output should not contain: """ Scenario Outline: -- @2.1 Bob """ Scenario: Select only second Examples table When I run "behave -f plain --tags=@bar features/tagged_examples.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 2 skipped """ And the command output should contain: """ Scenario Outline: -- @2.1 Bob Given weird step passes ... passed """ But the command output should not contain: """ Scenario Outline: -- @1.1 Alice Given a step passes ... passed Scenario Outline: -- @1.2 Alice Given another step passes ... passed """ behave-1.2.6/features/step.async_steps.feature0000644000076600000240000002054713244555737021564 0ustar jensstaff00000000000000@not.with_python2=true Feature: Async-Test Support (async-step, ...) As a test writer and step provider I want to test async frameworks or protocols (that use asyncio) . USE CASES: . * async-step with run-to-complete semantics (like synchronous step) . * async-dispatch-and-collect-results-later: . one or more steps dispatch async-calls (tasks) . and final step(s) that waits until tasks have been completed . (collects the results of the async-calls and verifies them) . . TERMINOLOGY: async-step . An async-step is either . * an async-function as coroutine using async/await keywords (Python 3.5) . * an async-function tagged with @asyncio.coroutine and using "yield from" . . # -- EXAMPLE CASE 1 (since Python 3.5): . async def coroutine1(duration): . await asyncio.sleep(duration) . . # -- EXAMPLE CASE 2 (since Python 3.4): . @asyncio.coroutine . def coroutine2(duration): . yield from asyncio.sleep(duration) . . RATIONALE: . By using async-steps, an additional layer of indirection is avoided. . The async-step can directly interact with other async-functions. @use.with_python.version=3.5 @use.with_python.version=3.6 Scenario: Use async-step with @async_run_until_complete (async) Given a new working directory And a file named "features/steps/async_steps35.py" with: """ from behave import step from behave.api.async_step import async_run_until_complete import asyncio @step('an async-step waits {duration:f} seconds') @async_run_until_complete async def step_async_step_waits_seconds(context, duration): await asyncio.sleep(duration) """ And a file named "features/async_run.feature" with: """ Feature: Scenario: Given an async-step waits 0.2 seconds """ When I run "behave -f plain --show-timings features/async_run.feature" Then it should pass with: """ Feature: Scenario: Given an async-step waits 0.2 seconds ... passed in 0.2 """ @use.with_python.version=3.4 @use.with_python.version=3.5 @use.with_python.version=3.6 Scenario: Use async-step with @async_run_until_complete (@coroutine) Given a new working directory And a file named "features/steps/async_steps34.py" with: """ from behave import step from behave.api.async_step import async_run_until_complete import asyncio @step('an async-step waits {duration:f} seconds') @async_run_until_complete @asyncio.coroutine def step_async_step_waits_seconds2(context, duration): yield from asyncio.sleep(duration) """ And a file named "features/async_run.feature" with: """ Feature: Scenario: Given an async-step waits 0.3 seconds """ When I run "behave -f plain --show-timings features/async_run.feature" Then it should pass with: """ Feature: Scenario: Given an async-step waits 0.3 seconds ... passed in 0.3 """ @use.with_python.version=3.5 @use.with_python.version=3.6 Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (async) Given a new working directory And a file named "features/steps/async_steps_timeout35.py" with: """ from behave import step from behave.api.async_step import async_run_until_complete import asyncio @step('an async-step waits {duration:f} seconds with timeout') @async_run_until_complete(timeout=0.1) # BAD-TIMEOUT-BY-DESIGN async def step_async_step_waits_seconds_with_timeout35(context, duration): await asyncio.sleep(duration) """ And a file named "features/async_timeout35.feature" with: """ Feature: Scenario: Given an async-step waits 1.0 seconds with timeout """ When I run "behave -f plain --show-timings features/async_timeout35.feature" Then it should fail with: """ 0 steps passed, 1 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Given an async-step waits 1.0 seconds with timeout ... failed in 0.1 """ And the command output should contain: """ Assertion Failed: TIMEOUT-OCCURED: timeout=0.1 """ @use.with_python.version=3.4 @use.with_python.version=3.5 @use.with_python.version=3.6 Scenario: Use @async_run_until_complete(timeout=...) and TIMEOUT occurs (@coroutine) Given a new working directory And a file named "features/steps/async_steps_timeout34.py" with: """ from behave import step from behave.api.async_step import async_run_until_complete import asyncio @step('an async-step waits {duration:f} seconds with timeout') @async_run_until_complete(timeout=0.2) # BAD-TIMEOUT-BY-DESIGN @asyncio.coroutine def step_async_step_waits_seconds_with_timeout34(context, duration): yield from asyncio.sleep(duration) """ And a file named "features/async_timeout34.feature" with: """ Feature: Scenario: Given an async-step waits 1.0 seconds with timeout """ When I run "behave -f plain --show-timings features/async_timeout34.feature" Then it should fail with: """ 0 steps passed, 1 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Given an async-step waits 1.0 seconds with timeout ... failed in 0.2 """ And the command output should contain: """ Assertion Failed: TIMEOUT-OCCURED: timeout=0.2 """ @use.with_python.version=3.4 @use.with_python.version=3.5 @use.with_python.version=3.6 Scenario: Use async-dispatch and async-collect concepts Given a new working directory And a file named "features/steps/async_dispatch_steps.py" with: """ from behave import given, then, step from behave.api.async_step import use_or_create_async_context, AsyncContext from hamcrest import assert_that, equal_to, empty import asyncio @asyncio.coroutine def async_func(param): yield from asyncio.sleep(0.2) return str(param).upper() @given('I dispatch an async-call with param "{param}"') def step_dispatch_async_call(context, param): async_context = use_or_create_async_context(context, "async_context1") task = async_context.loop.create_task(async_func(param)) async_context.tasks.append(task) @then('the collected result of the async-calls is "{expected}"') def step_collected_async_call_result_is(context, expected): async_context = context.async_context1 done, pending = async_context.loop.run_until_complete( asyncio.wait(async_context.tasks, loop=async_context.loop)) parts = [task.result() for task in done] joined_result = ", ".join(sorted(parts)) assert_that(joined_result, equal_to(expected)) assert_that(pending, empty()) """ And a file named "features/async_dispatch.feature" with: """ Feature: Scenario: Given I dispatch an async-call with param "Alice" And I dispatch an async-call with param "Bob" Then the collected result of the async-calls is "ALICE, BOB" """ When I run "behave -f plain --show-timings features/async_dispatch.feature" Then it should pass with: """ 3 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Given I dispatch an async-call with param "Alice" ... passed in 0.00 """ And the command output should contain: """ And I dispatch an async-call with param "Bob" ... passed in 0.00 """ And the command output should contain: """ Then the collected result of the async-calls is "ALICE, BOB" ... passed in 0.2 """ But note that "the async-collect step waits 0.2 seconds for both async-tasks to finish" And note that "the async-dispatch steps do not wait at all" behave-1.2.6/features/step.duplicated_step.feature0000644000076600000240000000616413244555737022401 0ustar jensstaff00000000000000Feature: Duplicated Step Definitions As I tester and test writer I want to know when step definitions are duplicated So that I can fix these problems. Scenario: Duplicated Step in same File Given a new working directory And a file named "features/steps/alice_steps.py" with: """ from behave import given, when, then @given(u'I call Alice') def step(context): pass @given(u'I call Alice') def step(context): pass """ And a file named "features/duplicated_step_alice.feature" with: """ Feature: Scenario: Duplicated Step Given I call Alice """ When I run "behave -f plain features/duplicated_step_alice.feature" Then it should fail And the command output should contain: """ AmbiguousStep: @given('I call Alice') has already been defined in existing step @given('I call Alice') at features/steps/alice_steps.py:3 """ And the command output should contain: """ File "features/steps/alice_steps.py", line 7, in @given(u'I call Alice') """ Scenario: Duplicated Step Definition in another File Given a new working directory And a file named "features/steps/bob1_steps.py" with: """ from behave import given @given('I call Bob') def step_call_bob1(context): pass """ And a file named "features/steps/bob2_steps.py" with: """ from behave import given @given('I call Bob') def step_call_bob2(context): pass """ And a file named "features/duplicated_step_bob.feature" with: """ Feature: Scenario: Duplicated Step Given I call Bob """ When I run "behave -f plain features/duplicated_step_bob.feature" Then it should fail And the command output should contain: """ AmbiguousStep: @given('I call Bob') has already been defined in existing step @given('I call Bob') at features/steps/bob1_steps.py:3 """ And the command output should contain: """ File "features/steps/bob2_steps.py", line 3, in @given('I call Bob') """ @xfail Scenario: Duplicated Same Step Definition via import from another File Given a new working directory And a file named "features/steps/charly1_steps.py" with: """ from behave import given @given('I call Charly') def step_call_charly1(context): pass """ And a file named "features/steps/charly2_steps.py" with: """ import charly1_steps """ And a file named "features/duplicated_step_via_import.feature" with: """ Feature: Scenario: Duplicated same step via import Given I call Charly """ When I run "behave -f plain features/duplicated_step_via_import.feature" Then it should pass And the command output should not contain: """ AmbiguousStep: @given('I call Charly') has already been defined in existing step @given('I call Charly') at features/steps/charly1_steps.py:3 """ behave-1.2.6/features/step.execute_steps.feature0000644000076600000240000000357713244555737022115 0ustar jensstaff00000000000000Feature: Execute Steps within a Step Function (Nested Steps) As a tester I want to reuse existing steps and call several ones within another step So that I can comply with the the DRY principle. Scenario: Execute a number of simple steps (GOOD CASE) Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import given, when, then @given(u'I go to the supermarket') def step_given_I_go_to_the_supermarket(context): context.shopping_cart = {} @when(u'I buy {amount:n} {item:w}') def step_when_I_buy(context, amount, item): assert amount >= 0 if not item in context.shopping_cart: context.shopping_cart[item] = 0 context.shopping_cart[item] += amount # -- HERE: Is the interesting functionality. @when(u'I buy the usual things') def step_when_I_buy_the_usual_things(context): context.execute_steps(u''' When I buy 2 apples And I buy 3 bananas ''') @then(u'I have {amount:n} {item:w}') def step_then_I_have(context, amount, item): actual = context.shopping_cart.get(item, 0) assert amount == actual """ And a file named "features/use_nested_steps.feature" with: """ Feature: Scenario: Given I go to the supermarket When I buy the usual things Then I have 2 apples And I have 3 bananas """ When I run "behave -f plain features/use_nested_steps.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 4 steps passed, 0 failed, 0 skipped, 0 undefined """ @not_implemented Scenario: A Nested Step Fails with Assert @not_implemented Scenario: A Nested Step Fails with Exception behave-1.2.6/features/step.execute_steps.with_table.feature0000644000076600000240000000504613244555737024227 0ustar jensstaff00000000000000Feature: Execute nested steps that use a table Scenario: Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import given, when, then, step import six @given('the following nested steps') def step_given_following_nested_steps(context): assert context.text, "ENSURE: multi-line text is provided." context.nested_steps = six.text_type(context.text) @step('I execute the nested steps {comment}') def step_execute_nested_steps_with_table(context, comment): assert context.nested_steps, "ENSURE: nested steps are provided." context.execute_steps(context.nested_steps) @then('the step "{expected_step}" was called') def then_step_was_called(context, expected_step): assert context.steps_called, "ENSURE: steps_called is provided." assert expected_step in context.steps_called @then('the table should be equal to') def then_table_should_be_equal_to(context): assert context.table, "ENSURE: table is provided." expected_table = context.table actual_table = context.the_table assert actual_table == expected_table # -- SPECIAL-STEP: @step('I setup an address book with') def step_setup_address_book_with_friends(context): assert context.table, "ENSURE: table is provided." if not hasattr(context, "steps_called"): context.steps_called = [] context.steps_called.append("I setup an address book with") context.the_table = context.table """ And a file named "features/execute_nested_steps_with_table.feature" with: ''' Feature: Scenario: Given the following nested steps: """ When I setup an address book with: | Name | Telephone Number | | Alice | 555 1111 | | Bob | 555 2222 | """ When I execute the nested steps with a table Then the step "I setup an address book with" was called And the table should be equal to: | Name | Telephone Number | | Alice | 555 1111 | | Bob | 555 2222 | ''' When I run "behave -f plain features/execute_nested_steps_with_table.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 4 steps passed, 0 failed, 0 skipped, 0 undefined """ behave-1.2.6/features/step.import_other_step_module.feature0000644000076600000240000000620413244555737024336 0ustar jensstaff00000000000000Feature: Ensure that a step module can import another step module As a test writer I want to import step definitions from another module in a step module So that I can reuse other steps and call them directly. . When a step module imports another step module . this should not cause AmbiguousStep errors . due to duplicated registration of the same step functions. . . NOTES: . * In general you should avoid this case (provided as example here). . * Use "context.execute_steps(...)" to avoid importing other step modules . * Use step-libraries; this will in general use sane imports of other step modules Scenario: Step module that imports another step module Given a new working directory And a file named "features/steps/alice1_steps.py" with: """ from behave import given @given(u'I call Alice') def step_call_alice(context): pass """ And a file named "features/steps/bob1_steps.py" with: """ from behave import given from alice1_steps import step_call_alice @given(u'I call Bob') def step_call_bob(context): pass @given(u'I call Bob and Alice') def step_call_bob_and_alice(context): step_call_bob(context) step_call_alice(context) """ And a file named "features/example.import_step_module.feature" with: """ Feature: Scenario: Given I call Bob and Alice """ When I run "behave -f plain --no-timings features/example.import_step_module.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: Scenario: Given I call Bob and Alice ... passed """ Scenario: Step module that imports another step module (cross-wise) Given a new working directory And a file named "features/steps/alice2_steps.py" with: """ from behave import given import bob2_steps # -- BAD: Import other step module, cross-wise. @given(u'I call Alice') def step_call_alice(context): pass """ And a file named "features/steps/bob2_steps.py" with: """ from behave import given import alice2_steps # -- BAD: Import other step module, cross-wise. @given(u'I call Bob') def step_call_bob(context): pass """ And a file named "features/example.cross_imported_step_modules.feature" with: """ Feature: Scenario: Given I call Alice And I call Bob """ When I run "behave -f plain --no-timings features/example.cross_imported_step_modules.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: Scenario: Given I call Alice ... passed And I call Bob ... passed """ behave-1.2.6/features/step.pending_steps.feature0000644000076600000240000001011413244555737022060 0ustar jensstaff00000000000000Feature: Pending Step (Exists with NotImplementedError Marker) . TERMINOLOGY: . * An undefined step is a step without matching step implementation. . * A pending step exists, . but contains only the undefined step snippet as implementation, . that marks it as NotImplemented. . . RELATED TO: . * step.undefined_steps.feature @setup Scenario: Feature Setup Given a new working directory And a file named "behave.ini" with: """ [behave] show_skipped = false show_timings = false """ And a file named "features/steps/passing_steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass @step('{word:w} step fails') def step_fails(context, word): assert False, "XFAIL" """ And a file named "features/steps/pending_steps.py" with: """ from behave import given, when, then @given('a pending step is used') def step_pending_given(context): raise NotImplementedError('STEP: Given a pending step is used') @when('a pending step is used') def step_pending_when(context): raise NotImplementedError('STEP: When a pending step is used') @then('a pending step is used') def step_pending_then(context): raise NotImplementedError('STEP: Then a pending step is used') """ And a file named "features/use_pending_steps.feature" with: """ Feature: Scenario: 1 Given a step passes And a pending step is used When another step passes Scenario: 2 Given a step passes When a pending step is used Then some step passes Scenario: 3 Given a step passes When another step passes Then a pending step is used """ Scenario: Pending given step (not implemented) When I run "behave -f plain features/use_pending_steps.feature:2" Then it should fail And the command output should contain: """ Feature: Scenario: 1 Given a step passes ... passed And a pending step is used ... failed """ But the command output should contain: """ NotImplementedError: STEP: Given a pending step is used """ And the command output should contain: """ File "features/steps/pending_steps.py", line 5, in step_pending_given raise NotImplementedError('STEP: Given a pending step is used') """ Scenario: Pending when step (not implemented) When I run "behave -f plain features/use_pending_steps.feature:7" Then it should fail And the command output should contain: """ Feature: Scenario: 2 Given a step passes ... passed When a pending step is used ... failed """ But the command output should contain: """ NotImplementedError: STEP: When a pending step is used """ And the command output should contain: """ File "features/steps/pending_steps.py", line 9, in step_pending_when raise NotImplementedError('STEP: When a pending step is used') """ Scenario: Pending then step (not implemented) When I run "behave -f plain features/use_pending_steps.feature:12" Then it should fail And the command output should contain: """ Feature: Scenario: 3 Given a step passes ... passed When another step passes ... passed Then a pending step is used ... failed """ But the command output should contain: """ NotImplementedError: STEP: Then a pending step is used """ And the command output should contain: """ File "features/steps/pending_steps.py", line 13, in step_pending_then raise NotImplementedError('STEP: Then a pending step is used') """ behave-1.2.6/features/step.undefined_steps.feature0000644000076600000240000002427513244555737022412 0ustar jensstaff00000000000000Feature: Undefined Step . TERMINOLOGY: . * An undefined step is a step without matching step implementation. . . SPECIFICATION: . * An undefined step should be reported after the run. . * An undefined step should cause its scenario to fail. . * If an undefined step is detected the remaining scenario steps are skipped. . * All undefined steps in a scenario should be reported (issue #42). . * Undefined steps should be detected even after a step fails in a scenario. . * Each undefined step should be reported only once. . * If a scenario is disabled (by tag expression, etc.), . the undefined step discovery should not occur. . This allows to prepare scenarios that are not intended to run (yet). . * Option --dry-run should discover undefined steps, too. . . RELATED TO: . * issue #42 Multiple undefined steps in same scenario are detected. @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/passing_steps.py" with: """ from behave import step @step('a step passes') def step_passes(context): pass @step('a step fails') def step_fails(context): assert False, "XFAIL" """ And a file named "features/undefined_last_step.feature" with: """ Feature: Scenario: Given a step passes When an undefined step is used """ Scenario: An undefined step should be reported When I run "behave -f plain -T features/undefined_last_step.feature" Then it should fail And the command output should contain: """ Feature: Scenario: Given a step passes ... passed When an undefined step is used ... undefined """ And the command output should contain: """ You can implement step definitions for undefined steps with these snippets: @when(u'an undefined step is used') def step_impl(context): raise NotImplementedError(u'STEP: When an undefined step is used') """ And an undefined-step snippet should exist for "When an undefined step is used" Scenario: An undefined step should cause its scenario to fail When I run "behave -f plain features/undefined_last_step.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 1 undefined """ Scenario: Additional scenario steps after an undefined step are skipped Given a file named "features/undefined_step_and_more.feature" with: """ Feature: Scenario: Given a step passes When an undefined step is used Then a step passes And a step fails """ When I run "behave -f plain -T features/undefined_step_and_more.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 0 failed, 2 skipped, 1 undefined """ And the command output should contain: """ Feature: Scenario: Given a step passes ... passed When an undefined step is used ... undefined """ Scenario: Two undefined steps in same scenario should be detected Given a file named "features/two_undefined_steps1.feature" with: """ Feature: Scenario: Given a step passes When an undefined step is used And a step fails Then another undefined step is used """ When I run "behave -f plain -T features/two_undefined_steps1.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 0 failed, 1 skipped, 2 undefined """ And the command output should contain: """ Feature: Scenario: Given a step passes ... passed When an undefined step is used ... undefined """ And undefined-step snippets should exist for: | Step | | When an undefined step is used | | Then another undefined step is used | But the command output should not contain: """ And a step fails ... skipped Then another undefined step is used ... undefined """ Scenario: Two undefined steps in different scenarios Given a file named "features/two_undefined_steps2.feature" with: """ Feature: Scenario: Given a step passes When an undefined step is used Scenario: Given another undefined step is used When a step passes """ When I run "behave -f plain -T features/two_undefined_steps2.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 2 failed, 0 skipped 1 step passed, 0 failed, 1 skipped, 2 undefined """ And the command output should contain: """ Feature: Scenario: Given a step passes ... passed When an undefined step is used ... undefined Scenario: Given another undefined step is used ... undefined """ And undefined-step snippets should exist for: | Step | | When an undefined step is used | | Given another undefined step is used | Scenario: Undefined step in Scenario Outline Given a file named "features/undefined_step_in_scenario_outline.feature" with: """ Feature: Scenario Outline: Given a step When an undefined step is used Then a step Examples: | outcome1 | outcome2 | | passes | passes | | passes | fails | | fails | passes | | fails | fails | """ When I run "behave -f plain -T features/undefined_step_in_scenario_outline.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 4 failed, 0 skipped 2 steps passed, 2 failed, 4 skipped, 4 undefined """ And an undefined-step snippet should exist for "When an undefined step is used" And the command output should contain: """ Feature: Scenario Outline: -- @1.1 Given a step passes ... passed When an undefined step is used ... undefined Scenario Outline: -- @1.2 Given a step passes ... passed When an undefined step is used ... undefined Scenario Outline: -- @1.3 Given a step fails ... failed Assertion Failed: XFAIL Scenario Outline: -- @1.4 Given a step fails ... failed Assertion Failed: XFAIL """ Scenario: Two undefined step in Scenario Outline Given a file named "features/two_undefined_step_in_scenario_outline.feature" with: """ Feature: Scenario Outline: Given a step When an undefined step is used Then a step And another undefined step is used Examples: | outcome1 | outcome2 | | passes | passes | | passes | fails | | fails | passes | | fails | fails | """ When I run "behave -f plain features/two_undefined_step_in_scenario_outline.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 4 failed, 0 skipped 2 steps passed, 2 failed, 4 skipped, 8 undefined """ And undefined-step snippets should exist for: | Step | | When an undefined step is used | | Then another undefined step is used | Scenario: Undefined steps are detected if scenario is selected via tag Given a file named "features/undefined_steps_with_tagged_scenario.feature" with: """ Feature: @selected Scenario: S1 Given a step passes And an undefined step Alice When a step fails Then an undefined step Bob @selected Scenario: S2 Given a step passes When an undefined step Charly @not_selected Scenario: S3 Given an undefined step Delta Then a step fails """ When I run "behave -f plain --tags=@selected features/undefined_steps_with_tagged_scenario.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 2 failed, 1 skipped 2 steps passed, 0 failed, 3 skipped, 3 undefined """ And undefined-step snippets should exist for: | Step | | Given an undefined step Alice | | Then an undefined step Bob | | When an undefined step Charly | But undefined-step snippets should not exist for: | Step | | Given a step passes | | Given an undefined step Delta | | When a step fails | | Then a step fails | Scenario: Undefined steps are detected if --dry-run option is used When I run "behave -f plain --dry-run features/undefined_steps_with_tagged_scenario.feature" Then it should fail with: """ 0 features passed, 0 failed, 0 skipped, 1 untested 0 scenarios passed, 0 failed, 0 skipped, 3 untested 0 steps passed, 0 failed, 0 skipped, 4 undefined, 4 untested """ And undefined-step snippets should exist for: | Step | | Given an undefined step Alice | | Then an undefined step Bob | | When an undefined step Charly | | Given an undefined step Delta | But undefined-step snippets should not exist for: | Step | | Given a step passes | | When a step fails | | Then a step fails | behave-1.2.6/features/step.use_step_library.feature0000644000076600000240000000237213244555737022600 0ustar jensstaff00000000000000Feature: Use a step library As a tester I want to use steps from one or more step libraries To simplify the reuse of problem domain specific steps in multiple projects. Scenario: Use a simple step library Given a new working directory And an empty file named "step_library1/__init__.py" And a file named "step_library1/alice_steps.py" with: """ from behave import step @step('I meet Alice') def step_meet_alice(context): pass """ And a file named "step_library1/bob_steps.py" with: """ from behave import step @step('I meet Bob') def step_meet_bob(context): pass """ And a file named "features/steps/use_step_library.py" with: """ from step_library1 import alice_steps, bob_steps """ And a file named "features/example_use_step_library.feature" with: """ Feature: Scenario: Given I meet Alice And I meet Bob """ When I run "behave -f plain features/example_use_step_library.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ behave-1.2.6/features/step_dialect.generic_steps.feature0000644000076600000240000001301513244555737023540 0ustar jensstaff00000000000000Feature: Step dialect for generic steps As a test/story writer I want to have a possibility to use generic steps So that I can execute a sequence of steps without BDD keywords (Given/When/Then) . SPECIFICATION: Generic step . * Prefix each step with a '*' (asterisk/star) character to mark it as step . * Provide step-functions/step definition with "@step" decorator @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass @step('{word:w} step passes with "{param}"') def step_passes_with_param(context, word, param): pass @step('a multi-line text step with') def step_with_multiline_text(context): assert context.text is not None, "REQUIRE: multi-line text" @step('a step fails with "{param}"') def step_fails_with_param(context, param=None): assert False, "XFAIL-STEP: %s" % param @step('a step fails') def step_fails(context): assert False, "XFAIL-STEP" @step('a table step with') def step_with_table(context): assert context.table, "REQUIRES: table" context.table.require_columns(["name", "age"]) """ Scenario: Simple step-by-step example * a step passes * another step passes Scenario: Simple step-by-step example 2 Given a file named "features/generic_steps.feature" with: """ Feature: Scenario: * a step passes * another step passes * a step passes with "Alice" * another step passes with "Bob" """ When I run "behave -f plain -T features/generic_steps.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 4 steps passed, 0 failed, 0 skipped """ And the command output should contain: """ Feature: Scenario: * a step passes ... passed * another step passes ... passed * a step passes with "Alice" ... passed * another step passes with "Bob" ... passed """ Scenario: Simple step-by-step example with failing steps Given a file named "features/generic_steps.failing.feature" with: """ Feature: Scenario: * a step passes * a step fails with "Alice" * a step fails with "Bob" * another step passes """ When I run "behave -f plain -T features/generic_steps.failing.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 1 failed, 2 skipped """ And the command output should contain: """ Feature: Scenario: * a step passes ... passed * a step fails with "Alice" ... failed """ Scenario: Simple step-by-step example with scenario description CASE: Ensure that first step is discovered after last description line. Given a file named "features/generic_steps.with_description.feature" with: ''' Feature: Scenario: First scenario description line. Second scenario description line. * a step passes * another step passes ''' When I run "behave -f plain -T features/generic_steps.with_description.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped """ Scenario: Simple step-by-step example with multi-line text Given a file named "features/generic_steps.with_text.feature" with: ''' Feature: Scenario: * a step passes * a multi-line text step with: """ First line of multi-line text. Second-line of multi-line text. """ * another step passes with "Charly" ''' When I run "behave -f plain -T features/generic_steps.with_text.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped """ And the command output should contain: ''' Feature: Scenario: * a step passes ... passed * a multi-line text step with ... passed """ First line of multi-line text. Second-line of multi-line text. """ * another step passes with "Charly" ... passed ''' Scenario: Simple step-by-step example with table Given a file named "features/generic_steps.with_table.feature" with: ''' Feature: Scenario: * a step passes * a table step with: | name | age | | Alice | 10 | | Bob | 12 | * another step passes with "Dodo" ''' When I run "behave -f plain -T features/generic_steps.with_table.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped """ And the command output should contain: ''' Feature: Scenario: * a step passes ... passed * a table step with ... passed | name | age | | Alice | 10 | | Bob | 12 | * another step passes with "Dodo" ... passed ''' behave-1.2.6/features/step_dialect.given_when_then.feature0000644000076600000240000000461113244555737024057 0ustar jensstaff00000000000000Feature: Step Dialect for BDD Steps with Given/When/Then Keywords In order to execute a sequence of steps with BDD keywords (Given/When/Then) As a test/story writer I want to have the possibility to express myself. . NOTE: . * More details are provided in other features. Scenario: Simple example Normally preferred style with BDD keywords. Note that BDD keywords are dependent on language settings. Given a step passes When a step passes And a step passes Then a step passes And a step passes But a step passes Scenario: Simple example (with lower-case keywords) Alternative style with lower-case BDD keywords. given a step passes when a step passes and a step passes then a step passes and a step passes but a step passes Scenario: Step usage example with details by running behave Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import given, when, then, step @given('a step passes') def given_step_passes(context): pass @when('a step passes') def when_step_passes(context): pass @then('a step passes') def then_step_passes(context): pass @step('a step passes with "{param}"') def step_passes_with_param(context, param): pass @step('another step passes') def step_passes(context): pass @step('another step passes with "{param}"') def step_passes(context, param): pass """ And a file named "features/basic_steps.feature" with: """ Feature: Scenario: Given a step passes And another step passes When a step passes with "Alice" Then another step passes with "Bob" """ When I run "behave -f plain -T features/basic_steps.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 4 steps passed, 0 failed, 0 skipped """ And the command output should contain: """ Feature: Scenario: Given a step passes ... passed And another step passes ... passed When a step passes with "Alice" ... passed Then another step passes with "Bob" ... passed """ behave-1.2.6/features/step_param.builtin_types.with_float.feature0000644000076600000240000002412613244555737025437 0ustar jensstaff00000000000000@sequential Feature: Parse data types in step parameters (type transformation) As a test writer I want write steps with parameter that should have floating-point data types So that I can just use the step parameter with the correct type behave uses the parse module (inverse of Python string.format). Therefore, the following ``parse types`` for floats are already supported: ===== =========================================== ============= ================================ Type Characters Matched (regex class) Output Type Example(s) ===== =========================================== ============= ================================ % Percentage (converted to value/100.0) float 51.2% f Fixed-point numbers float 1.23 -1.45 e Floating-point numbers with exponent float 1.1e-10 -12.3E+5 g General number format (either d, f or e) float 123 1.23 -1.45E+12 ===== =========================================== ============= ================================ SEE ALSO: * http://pypi.python.org/pypi/parse * string.format: http://docs.python.org/library/string.html#format-string-syntax @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/float_param_steps.py" with: """ from behave import then, step class NotMatched(object): pass @step('a float param with "{value:g}"') def step_float_param_with(context, value): assert type(value) is float context.value = value @step('a float param with "{value}"') def step_float_param_otherwise(context, value): context.value = NotMatched @step('a generic float param with "{value:g}"') def step_generic_float_param_with(context, value): step_float_param_with(context, value) @step('a generic float param with "{value}"') def step_generic_float_param_otherwise(context, value): step_float_param_otherwise(context, value) @step('a float with exponent param with "{value:e}"') def step_float_with_exponent_param_with(context, value): step_float_param_with(context, value) @step('a float with exponent param with "{value}"') def step_float_with_exponent_param_otherwise(context, value): step_float_param_otherwise(context, value) @step('a percentage param with "{value:%}"') def step_percentage_param_with(context, value): step_float_param_with(context, value) @step('a percentage param with "{value}"') def step_percentage_param_otherwise(context, value): step_float_param_otherwise(context, value) @step('a fixed-point number param with "{value:f}"') def step_fixed_point_number_param_with(context, value): step_float_param_with(context, value) @step('a fixed-point number param with "{value}"') def step_fixed_point_number_param_otherwise(context, value): step_float_param_otherwise(context, value) @then('the value should be {outcome} as float number') def step_value_should_be_matched_as_float_number(context, outcome): expected_type = float if outcome == "matched": assert type(context.value) is expected_type, \ "Unexpected type: %s" % type(context.value) else: assert context.value is NotMatched @then('the value should be {outcome} as float number with "{expected:g}"') def step_value_should_be_matched_as_float_number_with_expected(context, outcome, expected): step_value_should_be_matched_as_float_number(context, outcome) assert context.value == expected, \ "FAILED: value(%s) == expected(%s)" % (context.value, expected) """ Scenario: Float parameter values with type "%" (percentage) Given a file named "features/example.float_param.with_percent.feature" with: """ Feature: Float parameter values with type "%" (percentage) Scenario Outline: Good cases Given a percentage param with "" Then the value should be matched as float number with "" Examples: | Value | Expected Value | Case | | 0% | 0 | Zero | | 20% | 0.2 | Positive number | | 120% | 1.2 | Larger than 100% | | 10.5% | 0.105 | Float number | | -10% | -0.1 | Negative number | | +10% | 0.1 | With plus sign | Scenario Outline: Bad cases (not matched) Given a percentage param with "" Then the value should be as float number Examples: | Value | Outcome | Reason | | 123 | not-matched | Percentage sign is missing | | 1.23 | not-matched | Float number without percentage sign | | Alice | not-matched | Not a number | """ When I run "behave -f plain features/example.float_param.with_percent.feature" Then it should pass with: """ 9 scenarios passed, 0 failed, 0 skipped """ Scenario: Fixed-point parameter values with type "f" Given a file named "features/example.float_param.with_type_f.feature" with: """ Feature: Float parameter values with type "f" (fixed-point number) Scenario Outline: Good cases Given a fixed-point number param with "" Then the value should be matched as float number with "" Examples: | Value | Expected Value | Case | | 0.23 | 0.23 | | | 1.23 | 1.23 | | | 123.45 | 123.45 | | | +1.23 | 1.23 | With plus sign | | -1.23 | -1.23 | Negative float | Scenario Outline: Bad cases (not matched) Given a fixed-point number param with "" Then the value should be as float number Examples: | Value | Outcome | Reason | | 123 | not-matched | Integer number | | 1.23E-7 | not-matched | Float number with exponent | | Alice | not-matched | Not a number | """ When I run "behave -f plain features/example.float_param.with_type_f.feature" Then it should pass with: """ 8 scenarios passed, 0 failed, 0 skipped """ Scenario: Float with exponent parameter values with type "e" Given a file named "features/example.float_param.with_type_e.feature" with: """ Feature: Float parameter values with type "e" (float with exponents) Scenario Outline: Good cases Given a float with exponent param with "" Then the value should be matched as float number with "" Examples: | Value | Expected Value | Case | | 1.0E-10 | 1E-10 | With mantisse, negative exponent | | 1.23E+5 | 123E3 | Exponent with plus sign | | 123.0E+3 | 1.23E5 | Exponent with plus sign | | -1.23E5 | -123E3 | Negative number with mantisse and exponent | | INF | +INF | Infinity (INF) | | +INF | INF | | | -INF | -INF | | | +inf | INF | Lower-case special names | Scenario Outline: Bad cases (not matched) Given a float with exponent param with "" Then the value should be as float number Examples: | Value | Outcome | Reason | | 1E10 | not-matched | Without mantissa | | 1.E10 | not-matched | Short mantissa | | 123 | not-matched | Integer number | | Alice | not-matched | Not a number | """ When I run "behave -f plain features/example.float_param.with_type_e.feature" Then it should pass with: """ 12 scenarios passed, 0 failed, 0 skipped """ Scenario: Generic float parameter values with type "g" Given a file named "features/example.float_param.with_type_g.feature" with: """ Feature: Float parameter values with type "g" (generic float) Scenario Outline: Good cases Given a generic float param with "" Then the value should be matched as float number with "" Examples: | Value | Expected Value | Case | | 1 | 1.0 | Integer number format | | 1E10 | 1.0E10 | Float with exponent and shortened mantissa | | 1.23E5 | 1.23E5 | Float with exponent and mantissa | | 1.23e5 | 1.23E5 | Float with exponent and mantissa (lower-case) | | 1.0E-10 | 1E-10 | With mantisse, negative exponent | | 1.23E+5 | 123E3 | Exponent with plus sign | | 123.0E+3 | 1.23E5 | Exponent with plus sign | | -1.23E5 | -123E3 | Negative number with mantisse and exponent | Scenario Outline: Bad cases (not matched) Given a generic float param with "" Then the value should be as float number Examples: | Value | Outcome | Reason | | 0b101 | not-matched | Binary number | | 0o17 | not-matched | Octal number | | 0x1A | not-matched | Hexadecimal number | | 1.E10 | not-matched | Short mantissa | | Alice | not-matched | Not a number | """ When I run "behave -f plain features/example.float_param.with_type_g.feature" Then it should pass with: """ 13 scenarios passed, 0 failed, 0 skipped """ behave-1.2.6/features/step_param.builtin_types.with_integer.feature0000644000076600000240000003110413244555737025761 0ustar jensstaff00000000000000@sequential Feature: Parse integer data types in step parameters (type transformation) As a test writer I want write steps with parameter that should have integer data types So that I can just use the step parameter with the correct type Behave uses the parse module (inverse of Python string.format). Therefore, the following ``parse types`` for integer numbers are already supported: ===== =========================================== ============= ================================ Type Characters Matched (regex class) Output Type Example(s) ===== =========================================== ============= ================================ d Digits (effectively integer numbers) int 12345 0b101 0o761 0x1ABE n Numbers with thousands separators (, or .) int 12,345 b Binary numbers int 10111 0b1011 o Octal numbers int 07654 0o123 x Hexadecimal numbers (lower and upper case) int DEADBEAF 0xBEEF ===== =========================================== ============= ================================ SEE ALSO: * http://pypi.python.org/pypi/parse * string.format: http://docs.python.org/library/string.html#format-string-syntax @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/integer_param_steps.py" with: """ from behave import then, step from six import integer_types class NotMatched(object): pass @step('a integer param with "{value:d}"') def step_integer_param_with(context, value): assert isinstance(value, integer_types), "value.type=%s" % type(value) context.value = value @step('a integer param with "{value}"') def step_integer_param_otherwise(context, value): context.value = NotMatched @step('a number param with "{value:n}"') def step_number_param_with(context, value): step_integer_param_with(context, value) @step('a number param with "{value}"') def step_number_param_otherwise(context, value): step_integer_param_otherwise(context, value) @step('a binary number param with "{value:b}"') def step_binary_param_with(context, value): step_integer_param_with(context, value) @step('a binary number param with "{value}"') def step_binary_param_otherwise(context, value): step_integer_param_otherwise(context, value) @step('a octal number param with "{value:o}"') def step_hexadecimal_param_with(context, value): step_integer_param_with(context, value) @step('a octal number param with "{value}"') def step_hexadecimal_param_otherwise(context, value): step_integer_param_otherwise(context, value) @step('a hexadecimal number param with "{value:x}"') def step_hexadecimal_param_with(context, value): step_integer_param_with(context, value) @step('a hexadecimal number param with "{value}"') def step_hexadecimal_param_otherwise(context, value): step_integer_param_otherwise(context, value) @then('the value should be {outcome} as integer number') def step_value_should_be_matched_as_number(context, outcome): if outcome == "matched": assert isinstance(context.value, integer_types), \ "Unexpected type: %s" % type(context.value) else: assert context.value is NotMatched @then('the value should be {outcome} as integer number with "{expected:d}"') def step_value_should_be_matched_as_number_with_expected(context, outcome, expected): step_value_should_be_matched_as_number(context, outcome) assert context.value == expected, \ "FAILED: value(%s) == expected(%s)" % (context.value, expected) """ Scenario: Use type "d" (Digits) for integer params Given a file named "features/example.int_param.with_type_d.feature" with: """ Feature: Use type "d" (Digits) for integer params Scenario Outline: Good cases Given a integer param with "" Then the value should be matched as integer number with "" Examples: | Value | Expected Value | Case | | -1 | -1 | Negative number | | +1 | 1 | With plus sign | | 0 | 0 | | | 1 | 1 | | | 10 | 10 | | | 42 | 42 | | Scenario Outline: Bad cases (not matched) Given a integer param with "" Then the value should be as integer number Examples: | Value | Outcome | Reason | | 1.23 | not-matched | Float number | | 1E+2 | not-matched | Float number | | Alice | not-matched | Not a number | Scenario Outline: Conversion from other number system (base=2, 8, 16) Given a integer param with "" Then the value should be matched as integer number with "" Examples: | Value | Expected Value | Case | | 0b1011 | 11 | Binary number | | 0o123 | 83 | Octal number | | 0xBEEF | 48879 | Hexadecimal number | """ When I run "behave -f plain features/example.int_param.with_type_d.feature" Then it should pass with: """ 12 scenarios passed, 0 failed, 0 skipped """ Scenario: Use type "n" (Number) for integer params This data type supports numbers with thousands separators (',' or '.'). Given a file named "features/example.int_param.with_type_n.feature" with: """ Feature: Use type "n" (Number) for integer params Scenario Outline: Good cases Given a number param with "" Then the value should be matched as integer number with "" Examples: | Value | Expected Value | | -1 | -1 | | 0 | 0 | | 1 | 1 | | 10 | 10 | | 12.345 | 12345 | | 12,543 | 12543 | | 12,345.678 | 12345678 | | 12.345,678 | 12345678 | Scenario Outline: Bad cases (not matched) Given a number param with "" Then the value should be as integer number Examples: | Value | Outcome | Reason | | 123.34 | not-matched | Separator in wrong position | | 1.23 | not-matched | Float number or separator in wrong position | | 1E+2 | not-matched | Float number | | Alice | not-matched | Not a number | """ When I run "behave -f plain features/example.int_param.with_type_n.feature" Then it should pass with: """ 12 scenarios passed, 0 failed, 0 skipped """ Scenario: Use type "b" (Binary Number) for integer params Given a file named "features/example.int_param.with_type_b.feature" with: """ Feature: Use type "b" (Binary number) for integer params Scenario Outline: Good cases Given a binary number param with "" Then the value should be matched as integer number with "" Examples: | Value | Expected Value | Case | | 0 | 0 | | | 1 | 1 | | | 10 | 2 | | | 11 | 3 | | | 100 | 4 | | | 101 | 5 | | | 0111 | 7 | With padded, leading zero | | 0b1001 | 9 | With binary number prefix "0b" | | 0b1011 | 11 | With binary number prefix "0b" | | 10000000 | 128 | Larger binary number | Scenario Outline: Bad cases (not matched) Given a binary number param with "" Then the value should be as integer number Examples: | Value | Outcome | Reason | | 21 | not-matched | Invalid binary number | | 0b21 | not-matched | Invalid binary number with binary number prefix | | 1.23 | not-matched | Float number | | 1E+2 | not-matched | Float number | | Alice | not-matched | Not a number | """ When I run "behave -f plain features/example.int_param.with_type_b.feature" Then it should pass with: """ 15 scenarios passed, 0 failed, 0 skipped """ Scenario: Use type "o" (octal number) for integer params Given a file named "features/example.int_param.with_type_o.feature" with: """ Feature: Use type "o" (octal number) for integer params Scenario Outline: Good cases Given a octal number param with "" Then the value should be matched as integer number with "" Examples: | Value | Expected Value | Case | | 0 | 0 | | | 12 | 10 | | | 21 | 17 | | | 65 | 53 | | | 123 | 83 | | | 0o1 | 1 | With leading octal prefix | | 0o12 | 10 | | | 0o21 | 17 | | Scenario Outline: Bad cases (not matched) Given a octal number param with "" Then the value should be as integer number Examples: | Value | Outcome | Reason | | 8 | not-matched | Invalid octal number | | 81 | not-matched | Invalid octal number | | 0o81 | not-matched | Invalid octal number with octal number prefix | | 1.23 | not-matched | Float number | | 1E+2 | not-matched | Float number | | Alice | not-matched | Not a number | """ When I run "behave -f plain features/example.int_param.with_type_o.feature" Then it should pass with: """ 14 scenarios passed, 0 failed, 0 skipped """ Scenario: Use type "x" (hexadecimal number) for integer params Given a file named "features/example.int_param.with_type_x.feature" with: """ Feature: Use type "x" (hexadecimal number) for integer params Scenario Outline: Good cases: Given a hexadecimal number param with "" Then the value should be matched as integer number with "" Examples: | Value | Expected Value | Case | | 0 | 0 | | | 12 | 18 | | | 21 | 33 | | | 65 | 101 | | | 123 | 291 | | | beef | 48879 | With hexadecimal letters (lowercase) | | BEEF | 48879 | With hexadecimal letters (uppercase) | | deadbeef | 3735928559 | Larger hex number | | DeadBeef | 3735928559 | Larger hex number (mixed case) | | 0x01 | 1 | With hexadecimal prefix | | 0x12 | 18 | | | 0x21 | 33 | | | 0xbeef | 48879 | | | 0xBeEF | 48879 | | Scenario Outline: Bad cases (not matched) Given a hexadecimal number param with "" Then the value should be as integer number Examples: | Value | Outcome | Reason | | G | not-matched | Invalid hex digit | | G1 | not-matched | Invalid hex number | | 0x1G | not-matched | Invalid hex number with hex number prefix | | 1.23 | not-matched | Float number | | 1E+2 | not-matched | Float number | | Alice | not-matched | Not a number | """ When I run "behave -f plain features/example.int_param.with_type_x.feature" Then it should pass with: """ 20 scenarios passed, 0 failed, 0 skipped """ behave-1.2.6/features/step_param.custom_types.feature0000644000076600000240000001130113244555737023133 0ustar jensstaff00000000000000@sequential @todo Feature: Parse custom data types in step parameters (type transformation) As a test writer I want to provide own type parsers (and matcher) to simplify writing step definitions. . WORKS ONLY WITH MATCHERS: . * parse . * cfparse (parse with cardinality field extension) @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/type_converter_steps.py" with: """ from behave import then, step, register_type import parse @parse.with_pattern(r"\d+") # -- ONLY FOR: Zero and positive integers. def parse_number(text): return int(text) register_type(Number=parse_number) class NotMatched(object): pass @step('a param with "Number:{value:Number}"') def step_param_with_number_value(context, value): assert isinstance(value, int) context.value = value @step('a param with "{type:w}:{value}"') def step_param_with_unknown_type_value(context, type, value): context.value = NotMatched @then('the param should be "{outcome}"') def step_value_should_be_matched_or_not_matched(context, outcome): if outcome == "matched": assert isinstance(context.value, int), \ "Unexpected type: %s" % type(context.value) assert context.value is not NotMatched elif outcome == "not matched": assert context.value is NotMatched else: raise ValueError("INVALID: outcome=%s (use: matched, not matched)" % outcome) """ And a file named "features/steps/common_steps.py" with: """ from behave4cmd0 import passing_steps """ And a file named "behave.ini" with: """ [behave] show_skipped = false show_timings = false """ Scenario: Use own custom type for numbers Given a file named "features/example.number_type.feature" with: """ Feature: Simple Custom Type for Numbers Scenario Outline: Numbers Given a param with "Number:" Then the param should be "" Examples: Good cases for type=Number | value | outcome | Case | | 0 | matched | Zero | | 1 | matched | One | | 10 | matched | Positive number with 2 digits. | | 42 | matched | Another positive number. | Examples: Bad cases for type=Number | value | outcome | Case | | +1 | not matched | Positive number with plus sign. | | -1 | not matched | Negative number. | | 1.234 | not matched | BAD: Floating-point number. | | ABC | not matched | BAD: Not a number. | """ When I run "behave -f plain features/example.number_type.feature" Then it should pass with: """ 8 scenarios passed, 0 failed, 0 skipped """ Scenario: Type conversion fails Given a file named "features/steps/bad_type_converter_steps.py" with: """ from behave import step, register_type import parse @parse.with_pattern(r".*") # -- NOTE: Wildcard pattern, accepts anything. def parse_fails(text): raise ValueError(text) register_type(BadType=parse_fails) @step('a param with "BadType:{value:BadType}"') def step_param_with_badtype_value(context, value): assert False, "SHOULD_NEVER_COME_HERE: BadType converter raises error." """ And a file named "features/example.type_conversion_fails.feature" with: """ Feature: Type Conversion Fails Scenario: BadType raises ValueError during type conversion Given a param with "BadType:BAD_VALUE" Scenario: Ensure other scenarios are executed Then another step passes """ When I run "behave -f plain features/example.type_conversion_fails.feature" Then it should fail with: """ 1 scenario passed, 1 failed, 0 skipped 1 step passed, 1 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Scenario: BadType raises ValueError during type conversion Given a param with "BadType:BAD_VALUE" ... failed Traceback (most recent call last): """ And the command output should contain: """ File "features/steps/bad_type_converter_steps.py", line 6, in parse_fails raise ValueError(text) """ And the command output should contain "ValueError: BAD_VALUE" And the command output should contain "StepParseError: BAD_VALUE" behave-1.2.6/features/steps/0000755000076600000240000000000013244564040016013 5ustar jensstaff00000000000000behave-1.2.6/features/steps/behave_active_tags_steps.py0000644000076600000240000000650213244555737023426 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ .. code-block:: gherkin Given I setup the current values for active tags with: | category | value | | foo | xxx | Then the following active tag combinations are enabled: | tags | enabled? | | @active.with_foo=xxx | yes | | @active.with_foo=other | no | """ from behave import given, when, then, step from behave.tag_matcher import ActiveTagMatcher from behave.tag_expression import TagExpression from behave.userdata import parse_bool from hamcrest import assert_that, equal_to # ----------------------------------------------------------------------------- # UTILITY FUNCTIONS: # ----------------------------------------------------------------------------- def normalize_tags(tags): # -- STRIP: Leading '@' from tags. return [TagExpression.normalize_tag(tag) for tag in tags] # ----------------------------------------------------------------------------- # STEP DEFINITIONS: # ----------------------------------------------------------------------------- @given(u'I setup the current values for active tags with') def step_given_setup_the_current_values_for_active_tags_with(context): assert context.table, "REQUIRE: table" context.table.require_columns(["category", "value"]) active_values = getattr(context, "active_value_provider", None) if active_values is None: # -- SETUP DATA: context.active_value_provider = active_values = {} for row in context.table.rows: category = row["category"] value = row["value"] active_values[category] = value @then(u'the following active tag combinations are enabled') def step_then_following_active_tags_combinations_are_enabled(context): assert context.table, "REQUIRE: table" assert context.active_value_provider, "REQUIRE: active_value_provider" context.table.require_columns(["tags", "enabled?"]) ignore_unknown_categories = getattr(context, "active_tags_ignore_unknown_categories", ActiveTagMatcher.ignore_unknown_categories) table = context.table annotate_column_id = 0 active_tag_matcher = ActiveTagMatcher(context.active_value_provider) active_tag_matcher.ignore_unknown_categories = ignore_unknown_categories mismatched_rows = [] for row_index, row in enumerate(table.rows): tags = normalize_tags(row["tags"].split()) expected_enabled = parse_bool(row["enabled?"]) actual_enabled = active_tag_matcher.should_run_with(tags) if actual_enabled != expected_enabled: # -- ANNOTATE MISMATCH IN EXTRA COLUMN: if annotate_column_id == 0: annotate_column_id = table.ensure_column_exists("MISMATCH!") row.cells[annotate_column_id] = "= %s" % actual_enabled mismatched_rows.append(row_index) # -- FINALLY: Ensure that there are no mismatched rows. assert_that(mismatched_rows, equal_to([]), "No mismatched rows:") @step(u'unknown categories are ignored in active tags') def step_unknown_categories_are_ignored_in_active_tags(context): context.active_tags_ignore_unknown_categories = True @step(u'unknown categories are not ignored in active tags') def step_unknown_categories_are_not_ignored_in_active_tags(context): context.active_tags_ignore_unknown_categories = False behave-1.2.6/features/steps/behave_context_steps.py0000644000076600000240000000552213244555737022622 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Step definition for Context object tests. EXAMPLE Scenario: Show that Context parameter Given I set the parameter "person" to "Alice" in the behave context Then the behave context should have a parameter named "person" And the behave context object should contain: | Parameter | Value | | person | "Alice" | Scenario: Show that Context parameter are not present in next scenario Then the behave context should not have a parameter named "person" """ from __future__ import absolute_import from behave import given, then, step from hamcrest import assert_that, equal_to import six # ----------------------------------------------------------------------------- # STEPS: # ----------------------------------------------------------------------------- @step(u'I set the context parameter "{param_name}" to "{value}"') def step_set_behave_context_parameter_to(context, param_name, value): setattr(context, param_name, value) @step(u'the parameter "{param_name}" exists in the behave context') def step_behave_context_parameter_exists(context, param_name): assert hasattr(context, param_name) @step(u'the parameter "{param_name}" does not exist in the behave context') def step_behave_context_parameter_not_exists(context, param_name): assert not hasattr(context, param_name) @given(u'the behave context has a parameter "{param_name}"') def given_behave_context_has_parameter_named(context, param_name): step_behave_context_parameter_exists(context, param_name) @given(u'the behave context does not have a parameter "{param_name}"') def given_behave_context_does_not_have_parameter_named(context, param_name): step_behave_context_parameter_not_exists(context, param_name) @step(u'the behave context should have a parameter "{param_name}"') def step_behave_context_should_have_parameter_named(context, param_name): step_behave_context_parameter_exists(context, param_name) @step(u'the behave context should not have a parameter "{param_name}"') def step_behave_context_should_not_have_parameter_named(context, param_name): step_behave_context_parameter_not_exists(context, param_name) @then(u'the behave context should contain') def then_behave_context_should_contain_with_table(context): assert context.table, "ENSURE: table is provided." for row in context.table.rows: param_name = row["Parameter"] param_value = row["Value"] if param_value.startswith('"') and param_value.endswith('"'): param_value = param_value[1:-1] actual = six.text_type(getattr(context, param_name, None)) assert hasattr(context, param_name) assert_that(actual, equal_to(param_value)) @given(u'the behave context contains') def given_behave_context_contains_with_table(context): then_behave_context_should_contain_with_table(context) behave-1.2.6/features/steps/behave_model_tag_logic_steps.py0000644000076600000240000001031513244555737024242 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Provides step definitions that test tag logic for selected features, scenarios. .. code-block:: Gherkin # -- Scenario: Select scenarios with tags Given I use the behave model builder with: | statement | name | tags | Comment | | Scenario | A1 | @foo | | | Scenario | A3 | @foo @bar | | | Scenario | B3 | | Untagged | When I run the behave with tags Then the following scenarios are selected with cmdline: | cmdline | selected | Logic comment | | --tags=@foo | A1, A3, B2 | @foo | | --tags=-@foo | A1, A3, B2 | @foo | .. code-block:: Gherkin # IDEA: # -- Scenario: Select scenarios with tags Given I use the behave model builder with: | statement | name | tags | Comment | | Feature | Alice | @alice | | | Scenario | A1 | @foo | | | Scenario | A2 | @bar | | | Scenario | A3 | @foo @bar | | | Feature | Bob | @bob | | | Scenario | B1 | @bar | | | Scenario | B2 | @foo | | | Scenario | B3 | | Untagged | When I run the behave with options "--tags=@foo" Then the following scenarios are selected: | statement | name | selected | | Scenario | A1 | yes | | Scenario | A2 | no | | Scenario | A3 | yes | | Scenario | B1 | no | | Scenario | B2 | yes | | Scenario | B3 | no | """ from __future__ import absolute_import from behave import given, when, then from behave_model_util import BehaveModelBuilder, convert_comma_list from behave_model_util import \ run_model_with_cmdline, collect_selected_and_skipped_scenarios from hamcrest import assert_that, equal_to # ----------------------------------------------------------------------------- # STEP DEFINITIONS: # ----------------------------------------------------------------------------- @given('a behave model with') def step_given_a_behave_model_with_table(context): """ Build a behave feature model from a tabular description. .. code-block:: gherkin # -- Scenario: Select scenarios with tags Given I use the behave model builder with: | statement | name | tags | Comment | | Scenario | S0 | | Untagged | | Scenario | S1 | @foo | | | Scenario | S3 | @foo @bar | | """ assert context.table, "REQUIRE: context.table" context.table.require_columns(BehaveModelBuilder.REQUIRED_COLUMNS) model_builder = BehaveModelBuilder() context.behave_model = model_builder.build_model_from_table(context.table) @when('I run the behave model with "{hint}"') def step_when_run_behave_model_with_hint(context, hint): pass # -- ONLY: SYNTACTIC SUGAR @then('the following scenarios are selected with cmdline') def step_then_scenarios_are_selected_with_cmdline(context): """ .. code-block:: Gherkin Then the following scenarios are selected with cmdline: | cmdline | selected? | Logic comment | | --tags=@foo | A1, A3, B2 | @foo | """ assert context.behave_model, "REQUIRE: context attribute" assert context.table, "REQUIRE: context.table" context.table.require_columns(["cmdline", "selected?"]) model = context.behave_model for row_index, row in enumerate(context.table.rows): cmdline = row["cmdline"] expected_selected_names = convert_comma_list(row["selected?"]) # -- STEP: Run model with cmdline tags run_model_with_cmdline(model, cmdline) selected, skipped = collect_selected_and_skipped_scenarios(model) actual_selected = [scenario.name for scenario in selected] # -- CHECK: assert_that(actual_selected, equal_to(expected_selected_names), "cmdline=%s (row=%s)" % (cmdline, row_index)) behave-1.2.6/features/steps/behave_model_util.py0000644000076600000240000000721513244555737022056 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- from __future__ import absolute_import from behave.model import Feature, Scenario, reset_model from behave.model_core import Status from behave.runner import ModelRunner from behave.parser import parse_tags from behave.configuration import Configuration # ----------------------------------------------------------------------------- # TYPE CONVERTERS: # ----------------------------------------------------------------------------- def convert_comma_list(text): text = text.strip() return [part.strip() for part in text.split(",")] def convert_model_element_tags(text): return parse_tags(text.strip()) # ----------------------------------------------------------------------------- # TEST DOMAIN, FIXTURES, STEP UTILS: # ----------------------------------------------------------------------------- class Model(object): def __init__(self, features=None): self.features = features or [] class BehaveModelBuilder(object): REQUIRED_COLUMNS = ["statement", "name"] OPTIONAL_COLUMNS = ["tags"] def __init__(self): self.features = [] self.current_feature = None self.current_scenario = None def build_feature(self, name=u"", tags=None): if not name: name = u"alice" filename = u"%s.feature" % name line = 1 feature = Feature(filename, line, u"Feature", name, tags=tags) self.features.append(feature) self.current_feature = feature return feature def build_scenario(self, name="", tags=None): if not self.current_feature: self.build_feature() filename = self.current_feature.filename line = self.current_feature.line + 1 scenario = Scenario(filename, line, u"Scenario", name, tags=tags) self.current_feature.add_scenario(scenario) self.current_scenario = scenario return scenario def build_unknown(self, statement, name=u"", row_index=None): # pylint: disable=no-self-use assert False, u"UNSUPPORTED: statement=%s, name=%s (row=%s)" % \ (statement, name, row_index) def build_model_from_table(self, table): table.require_columns(self.REQUIRED_COLUMNS) for row_index, row in enumerate(table.rows): statement = row["statement"] name = row["name"] tags = row.get("tags", []) if tags: tags = convert_model_element_tags(tags) if statement == "Feature": self.build_feature(name, tags) elif statement == "Scenario": self.build_scenario(name, tags) else: self.build_unknown(statement, name, row_index=row_index) return Model(self.features) def run_model_with_cmdline(model, cmdline): reset_model(model.features) command_args = cmdline config = Configuration(command_args, load_config=False, default_format="null", stdout_capture=False, stderr_capture=False, log_capture=False) model_runner = ModelRunner(config, model.features) return model_runner.run() def collect_selected_and_skipped_scenarios(model): # pylint: disable=invalid-name selected = [] skipped = [] for feature in model.features: scenarios = feature.scenarios for scenario in scenarios: if scenario.status == Status.skipped: skipped.append(scenario) else: assert scenario.status != Status.untested selected.append(scenario) return (selected, skipped) behave-1.2.6/features/steps/behave_select_files_steps.py0000644000076600000240000000635113244555737023600 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Provides step definitions that test how the behave runner selects feature files. EXAMPLE: Given behave has the following feature fileset: ''' features/alice.feature features/bob.feature features/barbi.feature ''' When behave includes feature files with "features/a.*\.feature" And behave excludes feature files with "features/b.*\.feature" Then the following feature files are selected: ''' features/alice.feature ''' """ from __future__ import absolute_import from behave import given, when, then from behave.runner_util import FeatureListParser from hamcrest import assert_that, equal_to from copy import copy import re import six # ----------------------------------------------------------------------------- # STEP UTILS: # ----------------------------------------------------------------------------- class BasicBehaveRunner(object): def __init__(self, config=None): self.config = config self.feature_files = [] def select_files(self): """ Emulate behave runners file selection by using include/exclude patterns. :return: List of selected feature filenames. """ selected = [] for filename in self.feature_files: if not self.config.exclude(filename): selected.append(six.text_type(filename)) return selected # ----------------------------------------------------------------------------- # STEP DEFINITIONS: # ----------------------------------------------------------------------------- @given('behave has the following feature fileset') def step_given_behave_has_feature_fileset(context): assert context.text is not None, "REQUIRE: text" behave_runner = BasicBehaveRunner(config=copy(context.config)) behave_runner.feature_files = FeatureListParser.parse(context.text) context.behave_runner = behave_runner @when('behave includes all feature files') def step_when_behave_includes_all_feature_files(context): assert context.behave_runner, "REQUIRE: context.behave_runner" context.behave_runner.config.include_re = None @when('behave includes feature files with "{pattern}"') def step_when_behave_includes_feature_files_with_pattern(context, pattern): assert context.behave_runner, "REQUIRE: context.behave_runner" context.behave_runner.config.include_re = re.compile(pattern) @when('behave excludes no feature files') def step_when_behave_excludes_no_feature_files(context): assert context.behave_runner, "REQUIRE: context.behave_runner" context.behave_runner.config.exclude_re = None @when('behave excludes feature files with "{pattern}"') def step_when_behave_excludes_feature_files_with_pattern(context, pattern): assert context.behave_runner, "REQUIRE: context.behave_runner" context.behave_runner.config.exclude_re = re.compile(pattern) @then('the following feature files are selected') def step_then_feature_files_are_selected_with_text(context): assert context.text is not None, "REQUIRE: text" assert context.behave_runner, "REQUIRE: context.behave_runner" selected_files = context.text.strip().splitlines() actual_files = context.behave_runner.select_files() assert_that(actual_files, equal_to(selected_files)) behave-1.2.6/features/steps/behave_tag_expression_steps.py0000644000076600000240000001347013244555737024171 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Provides step definitions that test tag expressions (and tag logic). .. code-block:: gherkin Given the default tags "-@foo" And the tag expression "@foo" Then the tag expression selects elements with tags: | tags | selected? | | @foo | yes | | @other | no | .. code-block:: gherkin Given the named model elements with tags: | name | tags | | S1 | @foo | Then the tag expression select model elements with: | tag expression | selected? | | @foo | S1, S3 | | -@foo | S0, S2, S3 | """ from __future__ import absolute_import from behave import given, then, register_type from behave.tag_expression import TagExpression from behave_model_util import convert_comma_list, convert_model_element_tags from hamcrest import assert_that, equal_to # ----------------------------------------------------------------------------- # TEST DOMAIN, FIXTURES, STEP UTILS: # ----------------------------------------------------------------------------- class ModelElement(object): def __init__(self, name, tags=None): self.name = name self.tags = tags or [] # ----------------------------------------------------------------------------- # TYPE CONVERTERS: # ----------------------------------------------------------------------------- def convert_tag_expression(text): parts = text.strip().split() return TagExpression(parts) register_type(TagExpression=convert_tag_expression) def convert_yesno(text): text = text.strip().lower() assert text in convert_yesno.choices return text in convert_yesno.true_choices convert_yesno.choices = ("yes", "no", "true", "false") convert_yesno.true_choices = ("yes", "true") # ----------------------------------------------------------------------------- # STEP DEFINITIONS: # ----------------------------------------------------------------------------- @given('the tag expression "{tag_expression:TagExpression}"') def step_given_the_tag_expression(context, tag_expression): """ Define a tag expression that is used later-on. .. code-block:: gherkin Given the tag expression "@foo" """ context.tag_expression = tag_expression @given('the default tags "{default_tags:TagExpression}"') def step_given_the_tag_expression(context, default_tags): """ Define a tag expression that is used later-on. .. code-block:: gherkin Given the tag expression "@foo" """ context.default_tags = default_tags tag_expression = getattr(context, "tag_expression", None) if tag_expression is None: context.tag_expression = default_tags @then('the tag expression selects elements with tags') def step_then_tag_expression_selects_elements_with_tags(context): """ Checks if a tag expression selects an element with the given tags. .. code-block:: gherkin Then the tag expression selects elements with tags: | tags | selected? | | @foo | yes | | @other | no | """ assert context.tag_expression, "REQUIRE: context.tag_expression" context.table.require_columns(["tags", "selected?"]) tag_expression = context.tag_expression expected = [] actual = [] for row in context.table.rows: element_tags = convert_model_element_tags(row["tags"]) expected_element_selected = convert_yesno(row["selected?"]) actual_element_selected = tag_expression.check(element_tags) expected.append((element_tags, expected_element_selected)) actual.append((element_tags, actual_element_selected)) # -- PERFORM CHECK: assert_that(actual, equal_to(expected)) @given('the model elements with name and tags') def step_given_named_model_elements_with_tags(context): """ .. code-block:: gherkin Given the model elements with name and tags: | name | tags | | S1 | @foo | Then the tag expression select model elements with: | tag expression | selected? | | @foo | S1, S3 | | -@foo | S0, S2, S3 | """ assert context.table, "REQUIRE: context.table" context.table.require_columns(["name", "tags"]) # -- PREPARE: model_element_names = set() model_elements = [] for row in context.table.rows: name = row["name"].strip() tags = convert_model_element_tags(row["tags"]) assert name not in model_element_names, "DUPLICATED: name=%s" % name model_elements.append(ModelElement(name, tags=tags)) model_element_names.add(name) # -- SETUP: context.model_elements = model_elements @then('the tag expression selects model elements with') def step_given_named_model_elements_with_tags(context): """ .. code-block:: gherkin Then the tag expression select model elements with: | tag expression | selected? | | @foo | S1, S3 | | -@foo | S0, S2, S3 | """ assert context.model_elements, "REQUIRE: context attribute" assert context.table, "REQUIRE: context.table" context.table.require_columns(["tag expression", "selected?"]) for row_index, row in enumerate(context.table.rows): tag_expression_text = row["tag expression"] tag_expression = convert_tag_expression(tag_expression_text) expected_selected_names = convert_comma_list(row["selected?"]) actual_selected = [] for model_element in context.model_elements: if tag_expression.check(model_element.tags): actual_selected.append(model_element.name) assert_that(actual_selected, equal_to(expected_selected_names), "tag_expression=%s (row=%s)" % (tag_expression_text, row_index)) behave-1.2.6/features/steps/behave_undefined_steps.py0000644000076600000240000000656613244555737023110 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Provides step definitions for behave based on behave4cmd. REQUIRES: * behave4cmd.steplib.output steps (command output from behave). """ from __future__ import absolute_import from behave import then from behave.runner_util import make_undefined_step_snippet # ----------------------------------------------------------------------------- # UTILITY FUNCTIONS: # ----------------------------------------------------------------------------- def text_indent(text, indent_size=0): prefix = " " * indent_size return prefix.join(text.splitlines(True)) # ----------------------------------------------------------------------------- # STEPS FOR: Undefined step definitions # ----------------------------------------------------------------------------- @then(u'an undefined-step snippets section exists') def step_undefined_step_snippets_section_exists(context): """ Checks if an undefined-step snippet section is in behave command output. """ context.execute_steps(u''' Then the command output should contain: """ You can implement step definitions for undefined steps with these snippets: """ ''') @then(u'an undefined-step snippet should exist for "{step}"') def step_undefined_step_snippet_should_exist_for(context, step): """ Checks if an undefined-step snippet is provided for a step in behave command output (last command). EXAMPLE: Then an undefined-step snippet should exist for "Given an undefined step" """ undefined_step_snippet = make_undefined_step_snippet(step) context.execute_steps(u'''\ Then the command output should contain: """ {undefined_step_snippet} """ '''.format(undefined_step_snippet=text_indent(undefined_step_snippet, 4))) @then(u'an undefined-step snippet should not exist for "{step}"') def step_undefined_step_snippet_should_not_exist_for(context, step): """ Checks if an undefined-step snippet is provided for a step in behave command output (last command). """ undefined_step_snippet = make_undefined_step_snippet(step) context.execute_steps(u'''\ Then the command output should not contain: """ {undefined_step_snippet} """ '''.format(undefined_step_snippet=text_indent(undefined_step_snippet, 4))) @then(u'undefined-step snippets should exist for') def step_undefined_step_snippets_should_exist_for_table(context): """ Checks if undefined-step snippets are provided. EXAMPLE: Then undefined-step snippets should exist for: | Step | | When an undefined step is used | | Then another undefined step is used | """ assert context.table, "REQUIRES: table" for row in context.table.rows: step = row["Step"] step_undefined_step_snippet_should_exist_for(context, step) @then(u'undefined-step snippets should not exist for') def step_undefined_step_snippets_should_not_exist_for_table(context): """ Checks if undefined-step snippets are not provided. EXAMPLE: Then undefined-step snippets should not exist for: | Step | | When an known step is used | | Then another known step is used | """ assert context.table, "REQUIRES: table" for row in context.table.rows: step = row["Step"] step_undefined_step_snippet_should_not_exist_for(context, step) behave-1.2.6/features/steps/fixture_steps.py0000644000076600000240000000247013244555737021311 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- # -- FILE: features/steps/fixture_steps.py from __future__ import print_function from behave import when import os import sys # XXX # # -- VARIANT 1: # @when(u'I click on ${environment_variable:w}') # def step_impl(context, environment_variable): # env_value = os.environ.get(environment_variable, None) # if env_value is None: # raise LookupError("Environment variable '%s' is undefined" % environment_variable) # print("USE ENVIRONMENT-VAR: %s = %s (variant 1)" % (environment_variable, env_value)) # # # # -- VARIANT 2: Use type converter # from behave import register_type # import parse # # @parse.with_pattern(r"\$\w+") # -- ONLY FOR: $WORD # def parse_environment_var(text): # assert text.startswith("$") # env_name = text[1:] # env_value = os.environ.get(env_name, None) # return (env_name, env_value) # # register_type(EnvironmentVar=parse_environment_var) # # @when(u'I use the environment variable {environment_variable:EnvironmentVar}') # def step_impl(context, environment_variable): # env_name, env_value = environment_variable # if env_value is None: # raise LookupError("Environment variable '%s' is undefined" % env_name) # print("USE ENVIRONMENT-VAR: %s = %s (variant 2)" \ # % (env_name, env_value)) # behave-1.2.6/features/steps/use_steplib_behave4cmd.py0000644000076600000240000000045313244555737023004 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Use behave4cmd0 step library (predecessor of behave4cmd). """ from __future__ import absolute_import # -- REGISTER-STEPS FROM STEP-LIBRARY: import behave4cmd0.__all_steps__ import behave4cmd0.passing_steps import behave4cmd0.failing_steps import behave4cmd0.note_steps behave-1.2.6/features/summary.undefined_steps.feature0000644000076600000240000000726513244555737023134 0ustar jensstaff00000000000000Feature: Summary with Undefined Steps . SPECIFICATION: . * An undefined step should be counted as "undefined" step. . * An undefined step should cause its scenario to fail. . * If an undefined step is detected the remaining scenario steps are skipped. . . RELATED TO: . * issue #42 Multiple undefined steps in same scenario are detected. @setup Scenario: Test Setup Given a new working directory And a file named "features/steps/passing_steps.py" with: """ from behave import step @step('a step passes') def step_passes(context): pass """ Scenario: Undefined step as first step in a scenario Given a file named "features/summary_undefined_first_step.feature" with: """ Feature: Scenario: Given an undefined step is used When a step passes Then a step passes """ When I run "behave -f plain features/summary_undefined_first_step.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped 0 steps passed, 0 failed, 2 skipped, 1 undefined """ Scenario: Undefined step as last step in a scenario Given a file named "features/summary_undefined_last_step.feature" with: """ Feature: Scenario: Given a step passes When an undefined step is used """ When I run "behave -f plain features/summary_undefined_last_step.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 1 undefined """ Scenario: Undefined step as middle step in a scenario Given a file named "features/summary_undefined_middle_step.feature" with: """ Feature: Scenario: Given a step passes When an undefined step is used Then a step passes And a step passes """ When I run "behave -f plain features/summary_undefined_middle_step.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 0 failed, 2 skipped, 1 undefined """ Scenario: Two undefined steps in same scenario, all are detected (skipped) Given a file named "features/summary_undefined_step2.feature" with: """ Feature: Scenario: Given a step passes When an undefined step is used Then a step passes And another undefined step is used """ When I run "behave -f plain features/summary_undefined_step2.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 0 failed, 1 skipped, 2 undefined """ Scenario: Two undefined steps in different scenarios Given a file named "features/summary_undefined_step_and_another.feature" with: """ Feature: Scenario: Given a step passes When an undefined step is used Then a step passes Scenario: Given an undefined step is used When a step passes """ When I run "behave -f plain features/summary_undefined_step_and_another.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 2 failed, 0 skipped 1 step passed, 0 failed, 2 skipped, 2 undefined """ behave-1.2.6/features/tags.active_tags.feature0000644000076600000240000003570113244555737021503 0ustar jensstaff00000000000000Feature: Active Tags As a test writer I want that some tags are evaluated at runtime So that some features/scenarios automatically excluded from the run-set. . SPECIFICATION: Active tag . * An active tag is evaluated at runtime. . * An active tag is either enabled or disabled (decided by runtime decision logic). . * A disabled active tag causes that its feature/scenario is excluded from the run-set. . * If several active tags are used for a feature/scenario/scenario outline, . the following tag logic is used: . . SIMPLIFIED ALGORITHM: For active-tag expression . . enabled := enabled(active-tag1) and enabled(active-tag2) and ... . . where all active-tags have a different category. . . REAL ALGORITHM: . If multiple active-tags for the same catgory exist, . then these active-tags need to be merged together into a tag_group. . . enabled := enabled(tag_group1) and enabled(tag_group2) and ... . tag_group.enabled := enabled(tag1) or enabled(tag2) or ... . . where all the active-tags within a tag-group have the same category. . . ACTIVE TAG SCHEMA (dialect1): . @use.with_{category}={value} . @not.with_{category}={value} . . DEPRECATED: @only.with_{category}={value} . . ACTIVE TAG SCHEMA (dialect2): . @active.with_{category}={value} . @not_active.with_{category}={value} . . RATIONALE: . Some aspects of the runtime environment are only known . when the tests are running. Therefore, it many cases it is simpler . to use such a mechanism as "active tags" instead of moving this decision . to the build-script that runs the tests. . . This allows to automatically skip some tests (scenarios, features) . that would otherwise fail anyway. . . NOTE: . * DRY-RUN MODE: Hooks are not called in dry-run mode. @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/pass_steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass # -- REUSE: Step definitions. from behave4cmd0 import note_steps """ And a file named "features/environment.py" with: """ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values import sys # -- ACTIVE TAG SUPPORT: @use.with_{category}={value}, ... active_tag_value_provider = { "browser": "chrome", "webserver": "apache", } active_tag_matcher = ActiveTagMatcher(active_tag_value_provider) # -- BEHAVE-HOOKS: def before_all(context): setup_active_tag_values(active_tag_value_provider, context.config.userdata) def before_scenario(context, scenario): if active_tag_matcher.should_exclude_with(scenario.effective_tags): sys.stdout.write("ACTIVE-TAG DISABLED: Scenario %s\n" % scenario.name) scenario.skip(active_tag_matcher.exclude_reason) """ And a file named "behave.ini" with: """ [behave] default_format = pretty show_timings = no show_skipped = no color = no """ Scenario: Use active-tag with Scenario and one category (tag schema dialect2) Given a file named "features/e1.active_tags.feature" with: """ Feature: @use.with_browser=chrome Scenario: Alice (Run only with Web-Browser Chrome) Given a step passes When another step passes @only.with_browser=safari Scenario: Bob (Run only with Web-Browser Safari) Given some step passes """ When I run "behave -f plain features/e1.active_tags.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 1 skipped 2 steps passed, 0 failed, 1 skipped, 0 undefined """ And the command output should contain: """ ACTIVE-TAG DISABLED: Scenario Bob """ But the command output should not contain: """ ACTIVE-TAG DISABLED: Scenario Alice """ Scenario: Use tag schema dialect1 with several categories Given a file named "features/e2.active_tags.feature" with: """ Feature: @active.with_webserver=apache Scenario: Alice (Run only with Apache Web-Server) Given a step passes When another step passes @active.with_browser=safari Scenario: Bob (Run only with Safari Web-Browser) Given another step passes """ When I run "behave -f plain -D browser=firefox features/e2.active_tags.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 1 skipped 2 steps passed, 0 failed, 1 skipped, 0 undefined """ And the command output should contain: """ ACTIVE-TAG DISABLED: Scenario Bob """ But the command output should not contain: """ ACTIVE-TAG DISABLED: Scenario Alice """ Scenario Outline: Use active tags with positive and negative logic: Given a file named "features/e3.active_tags.feature" with: """ Feature: @active.with_browser=chrome Scenario: Alice Given a step passes When another step passes @not_active.with_browser=opera Scenario: Bob Given another step passes @use.with_browser=safari Scenario: Charly Given some step passes """ When I run "behave -f plain -D browser= features/e3.active_tags.feature" Then it should pass with: """ ACTIVE-TAG DISABLED: Scenario """ And the command output should not contain: """ ACTIVE-TAG DISABLED: Scenario """ And note that "" Examples: | browser | enabled scenario | disabled scenario | comment | | chrome | Alice | Charly | Bob is also enabled | | firefox | Bob | Alice | Charly is also disabled | | safari | Charly | Alice | Bob is also enabled | Scenario: Tag logic with one active tag Given I setup the current values for active tags with: | category | value | | foo | xxx | Then the following active tag combinations are enabled: | tags | enabled? | | @active.with_foo=xxx | yes | | @active.with_foo=other | no | | @not_active.with_foo=xxx | no | | @not_active.with_foo=other | yes | And the following active tag combinations are enabled: | tags | enabled? | | @use.with_foo=xxx | yes | | @use.with_foo=other | no | | @only.with_foo=xxx | yes | | @only.with_foo=other | no | | @not.with_foo=xxx | no | | @not.with_foo=other | yes | Scenario: Tag logic with two active tags Given I setup the current values for active tags with: | category | value | | foo | xxx | | bar | zzz | Then the following active tag combinations are enabled: | tags | enabled? | | @use.with_foo=xxx @use.with_bar=zzz | yes | | @use.with_foo=xxx @use.with_bar=other | no | | @use.with_foo=other @use.with_bar=zzz | no | | @use.with_foo=other @use.with_bar=other | no | | @not.with_foo=xxx @use.with_bar=zzz | no | | @not.with_foo=xxx @use.with_bar=other | no | | @not.with_foo=other @use.with_bar=zzz | yes | | @not.with_foo=other @use.with_bar=other | no | | @use.with_foo=xxx @not.with_bar=zzz | no | | @use.with_foo=xxx @not.with_bar=other | yes | | @use.with_foo=other @not.with_bar=zzz | no | | @use.with_foo=other @not.with_bar=other | no | | @not.with_foo=xxx @not.with_bar=zzz | no | | @not.with_foo=xxx @not.with_bar=other | no | | @not.with_foo=other @not.with_bar=zzz | no | | @not.with_foo=other @not.with_bar=other | yes | Scenario: Tag logic with two active tags of same category Given I setup the current values for active tags with: | category | value | | foo | xxx | Then the following active tag combinations are enabled: | tags | enabled? | Comment | | @use.with_foo=xxx @use.with_foo=other | yes | Enabled: tag1 | | @use.with_foo=xxx @not.with_foo=other | yes | Enabled: tag1, tag2| | @use.with_foo=xxx @not.with_foo=xxx | yes | Enabled: tag1 (BAD-SPEC) | | @use.with_foo=other @not.with_foo=xxx | no | Enabled: none | | @not.with_foo=other @not.with_foo=xxx | yes | Enabled: tag1 | Scenario: Tag logic with unknown categories (case: ignored) Unknown categories (where a value is missing) are ignored by default. Therefore, the active tag that uses an unknown category acts as if it is always enabled (even in the negated case). Given I setup the current values for active tags with: | category | value | | foo | xxx | When unknown categories are ignored in active tags Then the following active tag combinations are enabled: | tags | enabled? | | @use.with_unknown=xxx | yes | | @use.with_unknown=zzz | yes | | @not.with_unknown=xxx | yes | | @not.with_unknown=zzz | yes | But note that "the active tag with the unknown category acts like a passive tag" Scenario: Tag logic with unknown categories (case: not ignored) If unknown categories are not ignored, then the active tag is disabled. Given I setup the current values for active tags with: | category | value | | foo | xxx | When unknown categories are not ignored in active tags Then the following active tag combinations are enabled: | tags | enabled? | | @use.with_unknown=xxx | no | | @use.with_unknown=zzz | no | | @not.with_unknown=xxx | yes | | @not.with_unknown=zzz | yes | But note that "the active tag with the unknown category is disabled" Scenario: ScenarioOutline with enabled active-tag is executed Given a file named "features/outline1.active_tags.feature" with: """ Feature: @use.with_browser=chrome Scenario Outline: Alice -- , Given a step passes But note that " can speak " Examples: | name | language | | Anna | German | | Arabella | English | """ When I run "behave -D browser=chrome features/outline1.active_tags.feature" Then it should pass with: """ 2 scenarios passed, 0 failed, 0 skipped 4 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ @use.with_browser=chrome Scenario Outline: Alice -- Anna, German -- @1.1 # features/outline1.active_tags.feature:10 Given a step passes # features/steps/pass_steps.py:3 But note that "Anna can speak German" # ../behave4cmd0/note_steps.py:15 @use.with_browser=chrome Scenario Outline: Alice -- Arabella, English -- @1.2 # features/outline1.active_tags.feature:11 Given a step passes # features/steps/pass_steps.py:3 But note that "Arabella can speak English" # ../behave4cmd0/note_steps.py:15 """ And the command output should not contain "ACTIVE-TAG DISABLED: Scenario Alice" But note that "we check now that tags for example rows are generated correctly" And the command output should not contain "@use.with_browserchrome" But the command output should contain "@use.with_browser=chrome" Scenario: ScenarioOutline with disabled active-tag is skipped Given a file named "features/outline2.active_tags.feature" with: """ Feature: @use.with_browser=chrome Scenario Outline: Bob -- , Given some step passes But note that " can speak " Examples: | name | language | | Bernhard | French | """ When I run "behave -D browser=other features/outline2.active_tags.feature" Then it should pass with: """ 0 scenarios passed, 0 failed, 1 skipped 0 steps passed, 0 failed, 2 skipped, 0 undefined """ And the command output should contain: """ ACTIVE-TAG DISABLED: Scenario Bob -- Bernhard, French -- @1.1 """ Scenario: ScenarioOutline with generated active-tag Given a file named "features/outline3.active_tags.feature" with: """ Feature: @use.with_browser= Scenario Outline: Charly -- , , Given a step passes But note that " can speak " Examples: | name | language | browser | Comment | | Anna | German | firefox | Should be skipped when browser=chrome | | Arabella | English | chrome | Should be executed when browser=chrome | """ When I run "behave -D browser=chrome features/outline3.active_tags.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 1 skipped 2 steps passed, 0 failed, 2 skipped, 0 undefined """ And the command output should contain: """ ACTIVE-TAG DISABLED: Scenario Charly -- Anna, German, firefox -- @1.1 """ And the command output should not contain: """ ACTIVE-TAG DISABLED: Scenario Charly -- Arabella, English, chrome -- @1.2 """ And the command output should contain: """ ACTIVE-TAG DISABLED: Scenario Charly -- Anna, German, firefox -- @1.1 @use.with_browser=chrome Scenario Outline: Charly -- Arabella, English, chrome -- @1.2 # features/outline3.active_tags.feature:11 Given a step passes # features/steps/pass_steps.py:3 But note that "Arabella can speak English" # ../behave4cmd0/note_steps.py:15 """ behave-1.2.6/features/tags.default_tags.feature0000644000076600000240000000625013244555737021651 0ustar jensstaff00000000000000Feature: Default Tags As a tester I want to define a number of default tags in a configuration file So that I do not have to specify these tags each time on the command-line. . NOTES: . * default tags are normally used to exclude/disable tag(s) . * command-line tags override the default tags . * default tags can be disabled by using the following expression . on the command-line: behave --tags=-xxx ... (where xxx is an unknown tag) @setup Scenario: Test Setup Given a new working directory And a file named "features/one.feature" with: """ Feature: @not_implemented Scenario: Alice Given missing step passes @foo Scenario: Bob Given another step passes Then a step passes """ And a file named "features/steps/passing_steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass """ Scenario: Use default tags with one tag Given a file named "behave.ini" with: """ [behave] show_skipped = false default_tags = -@not_implemented """ When I run "behave -f plain features" Then it should pass with: """ 1 scenario passed, 0 failed, 1 skipped 2 steps passed, 0 failed, 1 skipped, 0 undefined """ And the command output should contain "Scenario: Bob" But the command output should not contain "Scenario: Alice" Scenario: Override default tag on command-line Given a file named "behave.ini" with: """ [behave] show_skipped = false default_tags = -@not_implemented """ When I run "behave -f plain --tags=not_implemented features" Then it should pass with: """ 1 scenario passed, 0 failed, 1 skipped 1 step passed, 0 failed, 2 skipped, 0 undefined """ And the command output should contain "Scenario: Alice" But the command output should not contain "Scenario: Bob" Scenario: Use default tags with two tags Given a file named "behave.ini" with: """ [behave] show_skipped = false default_tags = -@not_implemented -@xfail # tag logic := not @not_implemented and not @xfail """ When I run "behave -f plain features" Then it should pass with: """ 1 scenario passed, 0 failed, 1 skipped 2 steps passed, 0 failed, 1 skipped, 0 undefined """ And the command output should contain "Scenario: Bob" But the command output should not contain "Scenario: Alice" Scenario: Override default tags on command-line Given a file named "behave.ini" with: """ [behave] show_skipped = false default_tags = -@not_implemented -@xfail # tag logic := not @not_implemented and not @xfail """ When I run "behave -f plain --tags=not_implemented,foo features" Then it should pass with: """ 2 scenarios passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain "Scenario: Alice" But the command output should contain "Scenario: Bob" behave-1.2.6/features/tags.tag_expression.feature0000644000076600000240000001254413244555737022244 0ustar jensstaff00000000000000Feature: Tag Expression As a tester I want to select a subset of all features/scenarios by using tags And I want to include and/or exclude some tags So that I can focus on the "important" scenarios (and features). . SPECIFICATION: . * a tag expression is a boolean expression . * a tag expression supports the operators: and, or, not . * a tag expression is structured as: . (or_expr1) and (or_expr2) and ... . . EXAMPLES: . | Tag logic | Tag expression | Comment | . | @foo | @foo | Select elements with @foo tag | . | @foo | foo | Same, '@' is optional. | . | not @foo | -@foo | Use minus for "not". | . | not @foo | ~foo | Same, use tilde instead of minus | . | @foo or @bar | @foo,@bar | Use comma for "or". | . | @foo and @bar | @foo @bar | Use space separated terms. | . | @foo or not @bar | @foo,-@bar | | . | @foo and not @bar | @foo -@bar | | Scenario: Select @foo Given the tag expression "@foo" Then the tag expression selects elements with tags: | tags | selected? | | | no | | @foo | yes | | @other | no | | @foo @other | yes | Scenario: Tag expression with 0..1 tags Given the model elements with name and tags: | name | tags | Comment | | S0 | | Untagged | | S1 | @foo | With 1 tag | | S2 | @other | | | S3 | @foo @other | With 2 tags | And note that "are all combinations of 0..2 tags" Then the tag expression selects model elements with: | tag expression | selected? | Case comment | | | S0, S1, S2, S3 | Select all (empty tag expression) | | @foo | S1, S3 | Select @foo | | -@foo | S0, S2 | not @foo, selects untagged elements | But note that "tag expression variants are also supported" And the tag expression selects model elements with: | tag expression | selected? | Case comment | | foo | S1, S3 | @foo: '@' is optional | | -foo | S0, S2 | not @foo: '@' is optional | | ~foo | S0, S2 | not @foo: tilde as minus | | ~@foo | S0, S2 | not @foo: '~@' is supported | Scenario: Tag expression with two tags (@foo, @bar) Given the model elements with name and tags: | name | tags | Comment | | S0 | | Untagged | | S1 | @foo | With 1 tag | | S2 | @bar | | | S3 | @other | | | S4 | @foo @bar | With 2 tags | | S5 | @foo @other | | | S6 | @bar @other | | | S7 | @foo @bar @other | With 3 tags | And note that "are all combinations of 0..3 tags" Then the tag expression selects model elements with: | tag expression | selected? | Case | | | S0, S1, S2, S3, S4, S5, S6, S7 | Select all | | @foo,@bar | S1, S2, S4, S5, S6, S7 | @foo or @bar | | @foo,-@bar | S0, S1, S3, S4, S5, S7 | @foo or not @bar | | -@foo,-@bar | S0, S1, S2, S3, S5, S6 | not @foo or @not @bar | | @foo @bar | S4, S7 | @foo and @bar | | @foo -@bar | S1, S5 | @foo and not @bar | | -@foo -@bar | S0, S3 | not @foo and not @bar | Scenario: Tag expression with three tags (@foo, @bar, @zap) Given the model elements with name and tags: | name | tags | Comment | | S0 | | Untagged | | S1 | @foo | With 1 tag | | S2 | @bar | | | S3 | @zap | | | S4 | @other | | | S5 | @foo @bar | With 2 tags | | S6 | @foo @zap | | | S7 | @foo @other | | | S8 | @bar @zap | | | S9 | @bar @other | | | S10 | @zap @other | | | S11 | @foo @bar @zap | With 3 tags | | S12 | @foo @bar @other | | | S13 | @foo @zap @other | | | S14 | @bar @zap @other | | | S15 | @foo @bar @zap @other | With 4 tags | And note that "are all combinations of 0..4 tags" Then the tag expression selects model elements with: | tag expression | selected? | Case | | @foo,@bar @zap | S6, S8, S11, S13, S14, S15 | (@foo or @bar) and @zap | | @foo,@bar -@zap | S1, S2, S5, S7, S9, S12 | (@foo or @bar) and not @zap | | @foo,-@bar @zap | S3, S6, S10, S11, S13, S15 | (@foo or not @bar) and @zap | behave-1.2.6/features/userdata.feature0000644000076600000240000002531313244555737020063 0ustar jensstaff00000000000000Feature: User-specific Configuration Data (userdata) As a test writer I want to provide my own configuration data So that the test suite and/or the environment can be adapted to its needs. . MECHANISM: . * Use -D name=value (--define) option to specify user data on command-line. . * Specify user data in section "behave.userdata" of "behave.ini" . * Load/setup user data in before_all() hook (advanced cases) . . USING USER DATA: . * context.config.userdata (as dict) . . SUPPORTED DATA TYPES (from "behave.ini" and command-line): . * string . * bool-like (= "true", if definition has no value) @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/pass_steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass """ And a file named "features/steps/userdata_steps.py" with: """ from behave import step from hamcrest import assert_that, equal_to @step('the following user-data is provided') def step_userdata_is_provided_with_table(context): assert context.table, "REQUIRE: table" context.table.require_columns(["name", "value"]) userdata = context.config.userdata for row in context.table.rows: name = row["name"] expected_value = row["value"] if name in userdata: actual_value = userdata[name] assert_that(str(actual_value), equal_to(expected_value)) else: assert False, "MISSING: userdata %s" % name @step('I modify the user-data with') def step_modify_userdata_with_table(context): assert context.table, "REQUIRE: table" context.table.require_columns(["name", "value"]) userdata = context.config.userdata for row in context.table.rows: name = row["name"] value = row["value"] userdata[name] = value """ @userdata.define Scenario: Use define command-line option Given a file named "features/userdata_ex1.feature" with: """ Feature: Scenario: Given the following user-data is provided: | name | value | | person1 | Alice | | person2 | Bob | """ And an empty file named "behave.ini" When I run "behave -D person1=Alice --define person2=Bob features/userdata_ex1.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ @userdata.define Scenario: Duplicated define with other value overrides first value Given a file named "features/userdata_ex2.feature" with: """ Feature: Scenario: Given the following user-data is provided: | name | value | | person1 | Bob | """ And an empty file named "behave.ini" When I run "behave -D person1=Alice -D person1=Bob features/userdata_ex2.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ @userdata.define Scenario: Use boolean flag as command-line definition Ensure that command-line define without value part is a boolean flag. Given a file named "features/userdata_ex3.feature" with: """ Feature: Scenario: Given the following user-data is provided: | name | value | | DEBUG | true | """ And an empty file named "behave.ini" When I run "behave -D DEBUG features/userdata_ex3.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ @userdata.config Scenario: Use user-data from behave configuration file Given a file named "behave.ini" with: """ [behave.userdata] person1 = Alice person2 = Bob """ When I run "behave features/userdata_ex1.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ @userdata.config Scenario: Override user-data from behave configuration file on command-line Given a file named "features/userdata_config2.feature" with: """ Feature: Scenario: Given the following user-data is provided: | name | value | | person1 | Charly | | person2 | Bob | """ And a file named "behave.ini" with: """ [behave.userdata] person1 = Alice person2 = Bob """ When I run "behave -D person1=Charly features/userdata_config2.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ @userdata.config Scenario: Extend user-data from behave configuration file on command-line Given a file named "features/userdata_config3.feature" with: """ Feature: Scenario: Given the following user-data is provided: | name | value | | person1 | Alice | | person2 | Bob | | person3 | Charly | """ And a file named "behave.ini" with: """ [behave.userdata] person1 = Alice person2 = Bob """ When I run "behave -D person3=Charly features/userdata_config3.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ @userdata.load Scenario: Load user-data configuration in before_all hook (JSON) Given an empty file named "behave.ini" And a file named "features/environment.py" with: """ import json import os.path def before_all(context): userdata = context.config.userdata configfile = userdata.get("configfile", "userconfig.json") if os.path.exists(configfile): config = json.load(open(configfile)) userdata.update(config) """ And a file named "userconfig.json" with: """ { "person1": "Anna", "person2": "Beatrix" } """ And a file named "features/userdata_load1.feature" with: """ Feature: Scenario: Given the following user-data is provided: | name | value | | person1 | Anna | | person2 | Beatrix | """ When I run "behave features/userdata_load1.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ @userdata.load Scenario: Load user-data configuration in before_all hook (INI) Given a file named "behave.ini" with: """ [behave.userdata] configfile = userconfig.ini config_section = behave.userdata.more """ And a file named "userconfig.ini" with: """ [behave.userdata.more] person1 = Anna2 person2 = Beatrix2 """ And a file named "features/environment.py" with: """ try: import configparser except: import ConfigParser as configparser # -- PY2 def before_all(context): userdata = context.config.userdata configfile = userdata.get("configfile", "userconfig.ini") section = userdata.get("config_section", "behave.userdata") parser = configparser.SafeConfigParser() parser.read(configfile) if parser.has_section(section): userdata.update(parser.items(section)) """ And a file named "features/userdata_load2.feature" with: """ Feature: Scenario: Given the following user-data is provided: | name | value | | person1 | Anna2 | | person2 | Beatrix2 | """ When I run "behave features/userdata_load2.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ @bad_practice @userdata.modify Scenario: Modified user-data is used by the remaining features/scenarios Given a file named "features/userdata_modify1.feature" with: """ Feature: Scenario: Given the following user-data is provided: | name | value | | person1 | Alice | | person2 | Bob | When I modify the user-data with: | name | value | | person1 | MODIFIED_VALUE | Then the following user-data is provided: | name | value | | person1 | MODIFIED_VALUE | | person2 | Bob | Scenario: Next scenario has modified user-data, too Given the following user-data is provided: | name | value | | person1 | MODIFIED_VALUE | | person2 | Bob | """ And a file named "behave.ini" with: """ [behave.userdata] person1 = Alice person2 = Bob """ When I run "behave features/userdata_modify1.feature" Then it should pass with: """ 2 scenarios passed, 0 failed, 0 skipped 4 steps passed, 0 failed, 0 skipped, 0 undefined """ But note that "modifying userdata is BAD-PRACTICE, except in before_all() hook" @userdata.case_sensitive Scenario: Loaded user-data from configuration should have case-sensitive keys Given a file named "features/userdata_case_sensitive.feature" with: """ Feature: Scenario: Given the following user-data is provided: | name | value | | CamelCaseKey | 1 | | CAPS_SNAKE_KEY | 2 | | lower_snake_key | 3 | """ And a file named "behave.ini" with: """ [behave.userdata] CamelCaseKey = 1 CAPS_SNAKE_KEY = 2 lower_snake_key = 3 """ When I run "behave features/userdata_case_sensitive.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ behave-1.2.6/invoke.yaml0000644000076600000240000000103113244555737015226 0ustar jensstaff00000000000000# ===================================================== # INVOKE CONFIGURATION: # ===================================================== # -- ON WINDOWS: # run: # echo: true # pty: false # shell: C:\Windows\System32\cmd.exe # ===================================================== # MAYBE: tasks: auto_dash_names: false project: name: behave run: echo: true pty: true behave_test: scopes: - features - tools/test-features - issue.features args: features tools/test-features issue.features behave-1.2.6/issue.features/0000755000076600000240000000000013244564040016004 5ustar jensstaff00000000000000behave-1.2.6/issue.features/environment.py0000644000076600000240000000625213244555737020744 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- # FILE: features/environment.py # pylint: disable=unused-argument """ Functionality: * active tags """ from __future__ import print_function import sys import platform import os.path import six from behave.tag_matcher import ActiveTagMatcher from behave4cmd0.setup_command_shell import setup_command_shell_processors4behave # PREPARED: # from behave.tag_matcher import setup_active_tag_values def require_tool(tool_name): """Check if a tool (an executable program) is provided on this platform. :params tool_name: Name of the tool to check if it is available. :return: True, if tool is found. :return: False, if tool is not available (or not in search path). """ # print("CHECK-TOOL: %s" % tool_name) path = os.environ.get("PATH") if not path: return False for searchdir in path.split(os.pathsep): executable1 = os.path.normpath(os.path.join(searchdir, tool_name)) executables = [executable1] if sys.platform.startswith("win"): executables.append(executable1 + ".exe") for executable in executables: # print("TOOL-CHECK: %s" % os.path.abspath(executable)) if os.path.isfile(executable): # print("TOOL-FOUND: %s" % os.path.abspath(executable)) return True # -- OTHERWISE: Tool not found # print("TOOL-NOT-FOUND: %s" % tool_name) return False def as_bool_string(value): if bool(value): return "yes" else: return "no" def discover_ci_server(): # pylint: disable=invalid-name ci_server = "none" CI = os.environ.get("CI", "false").lower() == "true" APPVEYOR = os.environ.get("APPVEYOR", "false").lower() == "true" TRAVIS = os.environ.get("TRAVIS", "false").lower() == "true" if CI: if APPVEYOR: ci_server = "appveyor" elif TRAVIS: ci_server = "travis" else: ci_server = "unknown" return ci_server # -- MATCHES ANY TAGS: @use.with_{category}={value} # NOTE: active_tag_value_provider provides category values for active tags. active_tag_value_provider = { "python2": str(six.PY2).lower(), "python3": str(six.PY3).lower(), # -- python.implementation: cpython, pypy, jython, ironpython "python.implementation": platform.python_implementation().lower(), "pypy": str("__pypy__" in sys.modules).lower(), "os": sys.platform, "xmllint": as_bool_string(require_tool("xmllint")), "ci": discover_ci_server() } active_tag_matcher = ActiveTagMatcher(active_tag_value_provider) def before_all(context): # -- SETUP ACTIVE-TAG MATCHER (with userdata): # USE: behave -D browser=safari ... # NOT-NEEDED: setup_active_tag_values(active_tag_value_provider, # context.config.userdata) setup_command_shell_processors4behave() def before_feature(context, feature): if active_tag_matcher.should_exclude_with(feature.tags): feature.skip(reason=active_tag_matcher.exclude_reason) def before_scenario(context, scenario): if active_tag_matcher.should_exclude_with(scenario.effective_tags): scenario.skip(reason=active_tag_matcher.exclude_reason) behave-1.2.6/issue.features/issue0030.feature0000644000076600000240000000115613244555737021034 0ustar jensstaff00000000000000@issue Feature: Issue #30 "behave --version" runs features and shows no version Scenario: Ensure environment assumptions are correct (Sanity Check) Given a new working directory When I run "behave" Then it should fail And the command output should contain: """ No steps directory in '{__WORKDIR__}/features' """ Scenario: Ensure --version option is processed correctly Given a new working directory When I run "behave --version" Then it should pass And the command output should not contain: """ No steps directory in '{__WORKDIR__}/features' """ behave-1.2.6/issue.features/issue0031.feature0000644000076600000240000000110013244555737021022 0ustar jensstaff00000000000000@issue Feature: Issue #31 "behave --format help" raises an error Scenario: Given a new working directory When I run "behave --format=help" Then it should pass And the command output should contain: """ Available formatters: json JSON dump of test run json.pretty JSON dump of test run (human readable) null Provides formatter that does not output anything. plain Very basic formatter with maximum compatibility pretty Standard colourised pretty formatter """ behave-1.2.6/issue.features/issue0032.feature0000644000076600000240000000143113244555737021032 0ustar jensstaff00000000000000@issue Feature: Issue #32 "behave --junit-directory=xxx" fails if more than 1 level must be created Scenario: Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import given, when, then @given(u'passing') def step(context): pass """ And a file named "features/issue32_1.feature" with: """ Feature: One Scenario: One Given passing """ When I run "behave --junit --junit-directory=report/test_results" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ And the directory "report/test_results" should exist behave-1.2.6/issue.features/issue0035.feature0000644000076600000240000000465313244555737021046 0ustar jensstaff00000000000000@issue Feature: Issue #35 Plain Formatter shows wrong steps when tag-selection is used Background: Test Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import given, when, then @given(u'the ninja has a third level black-belt') def step(context): pass @when(u'attacked by {opponent}') def step(context, opponent): pass @then(u'the ninja should {reaction}') def step(context, reaction): pass """ And a file named "features/issue35_1.feature" with: """ Feature: Using Tags with Features and Scenarios @one Scenario: Weaker opponent Given the ninja has a third level black-belt When attacked by a samurai Then the ninja should engage the opponent @two Scenario: Stronger opponent Given the ninja has a third level black-belt When attacked by Chuck Norris Then the ninja should run for his life """ Scenario: Select First Scenario with Tag When I run "behave --no-timings -f plain --tags=@one features/issue35_1.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 1 skipped 3 steps passed, 0 failed, 3 skipped, 0 undefined """ And the command output should contain: """ Feature: Using Tags with Features and Scenarios Scenario: Weaker opponent Given the ninja has a third level black-belt ... passed When attacked by a samurai ... passed Then the ninja should engage the opponent ... passed Scenario: Stronger opponent """ Scenario: Select Second Scenario with Tag When I run "behave --no-timings -f plain --tags=@two features/issue35_1.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 1 skipped 3 steps passed, 0 failed, 3 skipped, 0 undefined """ And the command output should contain: """ Feature: Using Tags with Features and Scenarios Scenario: Weaker opponent Scenario: Stronger opponent Given the ninja has a third level black-belt ... passed When attacked by Chuck Norris ... passed Then the ninja should run for his life ... passed """ behave-1.2.6/issue.features/issue0040.feature0000644000076600000240000001141113244555737021030 0ustar jensstaff00000000000000@issue Feature: Issue #40 Test Summary Scenario/Step Counts are incorrect for Scenario Outline As I user I want that each passed and each failed scenario is counted And I want that each passed and failed step in a ScenarioOutline is counted Background: Test Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import given, when, then @given(u'a step {outcome} with "{name}"') def step(context, outcome, name): context.name = name assert outcome == "passes" @when(u'a step {outcome} with "{name}"') def step(context, outcome, name): assert outcome == "passes" assert context.name == name @then(u'a step {outcome} with "{name}"') def step(context, outcome, name): assert outcome == "passes" assert context.name == name """ Scenario: ScenarioOutline with Passing Steps Given a file named "features/issue40_1.feature" with: """ Feature: Verify Scenario/Step Summary Pass Count with ScenarioOutline Scenario Outline: Given a step passes with "" When a step passes with "" Then a step passes with "" Examples: |name | |Alice| |Bob | """ When I run "behave -c -f plain features/issue40_1.feature" Then it should pass with: """ 2 scenarios passed, 0 failed, 0 skipped 6 steps passed, 0 failed, 0 skipped, 0 undefined """ Scenario: ScenarioOutline with Failing Given-Steps Given a file named "features/issue40_2G.feature" with: """ Feature: Scenario/Step Summary Pass/Fail Count with ScenarioOutline Scenario Outline: Given a step fails with "" When a step passes with "" Then a step passes with "" Examples: |name | |Alice| |Bob | """ When I run "behave -c -f plain features/issue40_2G.feature" Then it should fail with: """ 0 scenarios passed, 2 failed, 0 skipped 0 steps passed, 2 failed, 4 skipped, 0 undefined """ Scenario: ScenarioOutline with Failing When-Steps Given a file named "features/issue40_2W.feature" with: """ Feature: Scenario/Step Summary Pass/Fail Count with ScenarioOutline Scenario Outline: Given a step passes with "" When a step fails with "" Then a step passes with "" Examples: |name | |Alice| |Bob | """ When I run "behave -c -f plain features/issue40_2W.feature" Then it should fail with: """ 0 scenarios passed, 2 failed, 0 skipped 2 steps passed, 2 failed, 2 skipped, 0 undefined """ Scenario: ScenarioOutline with Failing Then-Steps Given a file named "features/issue40_2T.feature" with: """ Feature: Scenario/Step Summary Pass/Fail Count with ScenarioOutline Scenario Outline: Given a step passes with "" When a step passes with "" Then a step fails with "" Examples: |name | |Alice| |Bob | """ When I run "behave -c -f plain features/issue40_2T.feature" Then it should fail with: """ 0 scenarios passed, 2 failed, 0 skipped 4 steps passed, 2 failed, 0 skipped, 0 undefined """ Scenario: ScenarioOutline with Mismatched When-Step Example Row Given a file named "features/issue40_3W.feature" with: """ Feature: Scenario/Step Summary Pass/Fail Count with ScenarioOutline Scenario Outline: Given a step passes with "" When a step passes with "Alice" Then a step passes with "" Examples: |name | |Alice| |Bob | """ When I run "behave -c -f plain features/issue40_3W.feature" Then it should fail with: """ 1 scenario passed, 1 failed, 0 skipped 4 steps passed, 1 failed, 1 skipped, 0 undefined """ Scenario: ScenarioOutline with Mismatched Then-Step Example Row Given a file named "features/issue40_3W.feature" with: """ Feature: Scenario/Step Summary Pass/Fail Count with ScenarioOutline Scenario Outline: Given a step passes with "" When a step passes with "" Then a step passes with "Alice" Examples: |name | |Alice| |Bob | """ When I run "behave -c -f plain features/issue40_3W.feature" Then it should fail with: """ 1 scenario passed, 1 failed, 0 skipped 5 steps passed, 1 failed, 0 skipped, 0 undefined """ behave-1.2.6/issue.features/issue0041.feature0000644000076600000240000001050413244555737021033 0ustar jensstaff00000000000000@issue Feature: Issue #41 Missing Steps are duplicated in a Scenario Outline As I user I want that missing steps are reported only once. Background: Test Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import given, when, then @given(u'I enter a "{name}"') def step(context, name): context.name = name @when(u'I enter a "{name}"') def step(context, name): context.name = name @then(u'the name is "{name}"') def step(context, name): assert context.name == name """ Scenario: Missing Given Step Given a file named "features/issue41_missing1.feature" with: """ Feature: Missing Given-Step in a Scenario Outline Scenario Outline: Given an unknown step When I enter a "" Then the name is "" Examples: |name | |Alice| |Bob | """ When I run "behave -c -f plain features/issue41_missing1.feature" Then it should fail with: """ 0 steps passed, 0 failed, 4 skipped, 2 undefined """ And the command output should contain: """ You can implement step definitions for undefined steps with these snippets: @given(u'an unknown step') def step_impl(context): raise NotImplementedError(u'STEP: Given an unknown step') """ But the command output should not contain: """ You can implement step definitions for undefined steps with these snippets: @given(u'an unknown step') def step_impl(context): raise NotImplementedError(u'STEP: Given an unknown step') @given(u'an unknown step') def step_impl(context): raise NotImplementedError(u'STEP: Given an unknown step') """ Scenario: Missing When Step Given a file named "features/issue41_missing2.feature" with: """ Feature: Missing When-Step in a Scenario Outline Scenario Outline: Given I enter a "" When I use an unknown step Then the name is "" Examples: |name | |Alice| |Bob | """ When I run "behave -c -f plain features/issue41_missing2.feature" Then it should fail with: """ 2 steps passed, 0 failed, 2 skipped, 2 undefined """ And the command output should contain: """ You can implement step definitions for undefined steps with these snippets: @when(u'I use an unknown step') def step_impl(context): raise NotImplementedError(u'STEP: When I use an unknown step') """ But the command output should not contain: """ You can implement step definitions for undefined steps with these snippets: @when(u'I use an unknown step') def step_impl(context): raise NotImplementedError(u'STEP: When I use an unknown step') @when(u'I use an unknown step') def step_impl(context): raise NotImplementedError(u'STEP: When I use an unknown step') """ Scenario: Missing Then Step Given a file named "features/issue41_missing3.feature" with: """ Feature: Missing Then-Step in a Scenario Outline Scenario Outline: Given I enter a "" When I enter a "" Then I use an unknown step Examples: |name | |Alice| |Bob | """ When I run "behave -c -f plain features/issue41_missing3.feature" Then it should fail with: """ 4 steps passed, 0 failed, 0 skipped, 2 undefined """ And the command output should contain: """ You can implement step definitions for undefined steps with these snippets: @then(u'I use an unknown step') def step_impl(context): raise NotImplementedError(u'STEP: Then I use an unknown step') """ But the command output should not contain: """ You can implement step definitions for undefined steps with these snippets: @then(u'I use an unknown step') def step_impl(context): raise NotImplementedError(u'STEP: Then I use an unknown step') @then(u'I use an unknown step') def step_impl(context): raise NotImplementedError(u'STEP: Then I use an unknown step') """ behave-1.2.6/issue.features/issue0042.feature0000644000076600000240000001666113244555737021046 0ustar jensstaff00000000000000@issue Feature: Issue #42 Nice to have snippets for all unimplemented steps taking into account of the tags fltering As a user I want that all undefined steps are reported, not only just the first one in a scenario. In addition, all known steps after the first undefined step in a scenario should be marked as skipped (even failing ones). Background: Test Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import given, when, then @given(u'I enter a "{name}"') def step(context, name): context.name = name @when(u'I enter a "{name}"') def step(context, name): context.name = name @then(u'the name is "{name}"') def step(context, name): assert context.name == name """ Scenario: One undefined step in a scenario Given a file named "features/issue42_missing1.feature" with: """ Feature: Missing Given-Step in a Scenario Scenario: Given an unknown step When I enter a "Alice" Then the name is "Alice" """ When I run "behave -f plain features/issue42_missing1.feature" Then it should fail with: """ 0 steps passed, 0 failed, 2 skipped, 1 undefined """ And the command output should contain: """ You can implement step definitions for undefined steps with these snippets: @given(u'an unknown step') def step_impl(context): raise NotImplementedError(u'STEP: Given an unknown step') """ Scenario: Two undefined steps in a scenario Given a file named "features/issue42_missing2.feature" with: """ Feature: Missing Given and When steps in a Scenario Scenario: Given an unknown step When another unknown step And I enter a "Alice" Then the name is "Alice" """ When I run "behave -f plain features/issue42_missing2.feature" Then it should fail with: """ 0 steps passed, 0 failed, 2 skipped, 2 undefined """ And the command output should contain: """ You can implement step definitions for undefined steps with these snippets: @given(u'an unknown step') def step_impl(context): raise NotImplementedError(u'STEP: Given an unknown step') @when(u'another unknown step') def step_impl(context): raise NotImplementedError(u'STEP: When another unknown step') """ Scenario: Two undefined steps in the middle with passing steps Given a file named "features/issue42_missing3.feature" with: """ Feature: Missing 2 When steps after passing step Scenario: When I enter a "Alice" And an unknown step And another unknown step Then the name is "Alice" """ When I run "behave -f plain features/issue42_missing3.feature" Then it should fail with: """ 1 step passed, 0 failed, 1 skipped, 2 undefined """ And the command output should contain: """ You can implement step definitions for undefined steps with these snippets: @when(u'an unknown step') def step_impl(context): raise NotImplementedError(u'STEP: When an unknown step') @when(u'another unknown step') def step_impl(context): raise NotImplementedError(u'STEP: When another unknown step') """ Scenario: Undefined steps are detected if they occur after a failing step Given a file named "features/issue42_missing4.feature" with: """ Feature: Missing 2 When steps after passing step Scenario: When I enter a "Alice" Then the name is "Bob" And an unknown step And another unknown step """ When I run "behave -f plain features/issue42_missing4.feature" Then it should fail with: """ 1 step passed, 1 failed, 0 skipped, 2 undefined """ And the command output should contain: """ You can implement step definitions for undefined steps with these snippets: @then(u'an unknown step') def step_impl(context): raise NotImplementedError(u'STEP: Then an unknown step') @then(u'another unknown step') def step_impl(context): raise NotImplementedError(u'STEP: Then another unknown step') """ Scenario: Failing step after first undefined step should be marked as skipped Given a file named "features/issue42_missing4.feature" with: """ Feature: Missing 2 When steps after passing step Scenario: When I enter a "Alice" And an unknown step Then the name is "Bob" And another unknown step """ When I run "behave -f plain features/issue42_missing4.feature" Then it should fail with: """ 1 step passed, 0 failed, 1 skipped, 2 undefined """ And the command output should contain: """ You can implement step definitions for undefined steps with these snippets: @when(u'an unknown step') def step_impl(context): raise NotImplementedError(u'STEP: When an unknown step') @then(u'another unknown step') def step_impl(context): raise NotImplementedError(u'STEP: Then another unknown step') """ Scenario: Two undefined steps in scenario outline Given a file named "features/issue42_missing5.feature" with: """ Feature: Missing Given and When Step in a Scenario Outline Scenario Outline: Given an unknown step When another unknown step And I enter a "" Then the name is "" Examples: |name | |Alice| |Bob | """ When I run "behave -f plain features/issue42_missing5.feature" Then it should fail with: """ 0 steps passed, 0 failed, 4 skipped, 4 undefined """ And the command output should contain: """ You can implement step definitions for undefined steps with these snippets: @given(u'an unknown step') def step_impl(context): raise NotImplementedError(u'STEP: Given an unknown step') @when(u'another unknown step') def step_impl(context): raise NotImplementedError(u'STEP: When another unknown step') """ Scenario: Two undefined steps and run with tags Given a file named "features/issue42_missing6.feature" with: """ Feature: Missing steps in tagged scenarios @tag1 Scenario: When I enter a "Alice" And an unknown step Then the name is "Bob" @tag1 Scenario: When I enter a "Alice" And another unknown step Then the name is "Bob" @another_tag Scenario: When I enter a "Alice" And yet another unknown step Then the name is "Bob" """ When I run "behave -f plain --tags tag1 features/issue42_missing6.feature" Then it should fail with: """ 2 steps passed, 0 failed, 5 skipped, 2 undefined """ And the command output should contain: """ You can implement step definitions for undefined steps with these snippets: @when(u'an unknown step') def step_impl(context): raise NotImplementedError(u'STEP: When an unknown step') @when(u'another unknown step') def step_impl(context): raise NotImplementedError(u'STEP: When another unknown step') """ behave-1.2.6/issue.features/issue0044.feature0000644000076600000240000000300613244555737021035 0ustar jensstaff00000000000000@issue Feature: Issue #44 Shell-like comments are removed in Multiline Args As I user I want that multiline arguments (docstrings) contents are preserved. Background: Test Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import given, when, then from hamcrest import assert_that, equal_to @given(u'a multiline text argument with') def step(context): context.expected_text = context.text @then(u'the multiline text argument should be') def step(context): assert_that(context.text, equal_to(context.expected_text)) """ Scenario: Ensure shell comment lines are not filtered out in multiline text Given a file named "features/issue44_test.feature" with: ''' Feature: Multiline text with shell comment lines Scenario: Given a multiline text argument with: """ Lorem ipsum. # THIS IS A SHELL COMMENT. Ipsum lorem. """ Then the multiline text argument should be: """ Lorem ipsum. # THIS IS A SHELL COMMENT. Ipsum lorem. """ ''' When I run "behave -c -f pretty features/issue44_test.feature" Then it should pass And the command output should contain: """ # THIS IS A SHELL COMMENT. """ But the command output should not contain: """ Lorem ipsum. Ipsum lorem. """ behave-1.2.6/issue.features/issue0046.feature0000644000076600000240000000433713244555737021047 0ustar jensstaff00000000000000@issue Feature: Issue #46 Behave returns 0 (SUCCESS) even in case of test failures As I behave user I want to detect test success or test failures By using the process return value, 0 (SUCCESS) and non-zero for failure. Background: Test Setup Given a new working directory Given a file named "features/steps/steps.py" with: """ from behave import given @given(u'passing') def step(context): pass @given(u'failing') def step(context): assert False, "failing" """ Scenario: Successful Execution Given a file named "features/passing.feature" with: """ Feature: Passing Scenario: Passing Scenario Example Given passing """ When I run "behave -c -q features/passing.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Failing Execution Given a file named "features/failing.feature" with: """ Feature: Failing Scenario: Failing Scenario Example Given failing """ When I run "behave -c -q features/failing.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped 0 steps passed, 1 failed, 0 skipped, 0 undefined """ Scenario: Passing and Failing Execution Given a file named "features/passing_and_failing.feature" with: """ Feature: Passing and Failing Scenario: Passing Scenario Example Given passing Scenario: Failing Scenario Example Given failing """ When I run "behave -c -q features/passing_and_failing.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 1 scenario passed, 1 failed, 0 skipped 1 step passed, 1 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: Passing and Failing Scenario: Passing Scenario Example Given passing Scenario: Failing Scenario Example Given failing Assertion Failed: failing """ behave-1.2.6/issue.features/issue0052.feature0000644000076600000240000000332413244555737021037 0ustar jensstaff00000000000000@issue Feature: Issue #52 Summary counts are wrong with option --tags Wrong summary counts are shown for skipped and failed scenarios when option --tags=done is used (and some scenarios are skipped). Background: Test Setup Given a new working directory Given a file named "features/steps/steps.py" with: """ from behave import given @given(u'passing') def step(context): pass @given(u'failing') def step(context): assert False, "failing" """ Scenario: Successful Execution of Tagged Scenario Given a file named "features/tagged_scenario1.feature" with: """ Feature: Passing tagged Scenario @done Scenario: P1 Given passing @unimplemented Scenario: N1 Given passing @unimplemented Scenario: N2 Given passing """ When I run "behave --junit -c --tags @done features/tagged_scenario1.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 2 skipped """ Scenario: Failing Execution of Tagged Scenario Given a file named "features/tagged_scenario2.feature" with: """ Feature: Failing tagged Scenario @done Scenario: F1 Given failing @unimplemented Scenario: N1 Given passing @unimplemented Scenario: N2 Given passing """ When I run "behave --junit -c --tags @done features/tagged_scenario2.feature" Then it should fail And the command output should contain: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 2 skipped """ behave-1.2.6/issue.features/issue0059.feature0000644000076600000240000000136013244555737021044 0ustar jensstaff00000000000000@issue Feature: Issue #59 Fatal error when using --format=json Using the JSON formatter caused a fatal error. Background: Test Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import given @given(u'passing') def step(context): pass """ And a file named "features/issue59_test.feature" with: """ Feature: Passing tagged Scenario Scenario: P1 Given passing """ Scenario: Use the JSONFormatter When I run "behave --format=json features/issue59_test.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped """ behave-1.2.6/issue.features/issue0063.feature0000644000076600000240000000642613244555737021047 0ustar jensstaff00000000000000@issue Feature: Issue #63: 'ScenarioOutline' object has no attribute 'stdout' The problem occurs when "behave --junit ..." is used And a feature contains one or more ScenarioOutline(s). Background: Test Setup Given a new working directory And a file named "features/steps/steps.py" with: """ # -- OMIT: from __future__ import print_function from behave import given import sys def generate_output(step, outcome, name): # -- OMIT: print("{0}0 {1}: {2};".format(step, outcome, name)) sys.stdout.write("{0}1 {1}: {2};\n".format(step, outcome, name)) sys.stderr.write("{0}2 {1}: {2};\n".format(step, outcome, name)) @given(u'a {outcome} step with "{name}"') def step(context, outcome, name): context.name = name generate_output("Given", outcome, name) assert outcome == "passing" @when(u'a {outcome} step with "{name}" occurs') def step(context, outcome, name): generate_output("When", outcome, name) assert outcome == "passing" @then(u'a {outcome} step with "{name}" is reached') def step(context, outcome, name): generate_output("Then", outcome, name) assert outcome == "passing" assert context.name == name """ Scenario: ScenarioOutline with Passing Steps Given a file named "features/issue63_case1.feature" with: """ Feature: ScenarioOutline with Passing Steps Scenario Outline: Given a passing step with "" When a passing step with "" occurs Then a passing step with "" is reached Examples: |name | |Alice| |Bob | """ When I run "behave -c --junit features/issue63_case1.feature" Then it should pass with: """ 2 scenarios passed, 0 failed, 0 skipped 6 steps passed, 0 failed, 0 skipped, 0 undefined """ But the command output should not contain: """ AttributeError: 'ScenarioOutline' object has no attribute 'stdout' """ Scenario: ScenarioOutline with Passing and Failing Steps Given a file named "features/issue63_case2.feature" with: """ Feature: ScenarioOutline with Passing and Failing Steps Scenario Outline: Given a passing step with "" When a failing step with "" occurs Then a passing step with "" is reached Examples: |name | |Alice| |Bob | """ When I run "behave -c --junit features/issue63_case2.feature" Then it should fail with: """ 0 scenarios passed, 2 failed, 0 skipped 2 steps passed, 2 failed, 2 skipped, 0 undefined """ But the command output should not contain: """ AttributeError: 'ScenarioOutline' object has no attribute 'stdout' """ And the command output should not contain: """ AttributeError: 'Scenario' object has no attribute 'exception' """ And the command output should contain: """ Captured stdout: Given1 passing: Alice; When1 failing: Alice; """ And the command output should contain: """ Captured stderr: Given2 passing: Alice; When2 failing: Alice; """ behave-1.2.6/issue.features/issue0064.feature0000644000076600000240000000521613244555737021044 0ustar jensstaff00000000000000@issue Feature: Issue #64 Exit status not set to 1 even there are failures in certain cases The behave exit status not always returns 1 when failure(s) occur. The problem was associated with the Feature.run() logic implementation. This problem was first discovered while verifying issue #52 (see comments). See also similar test when tags select a subset of scenarios. RELATED ISSUES: * issue #52 Background: Test Setup Given a new working directory Given a file named "features/steps/steps.py" with: """ from behave import given @given(u'passing') def step(context): pass @given(u'failing') def step(context): assert False, "failing" """ Scenario: Failing in First Scenario Given a file named "features/issue64_case1.feature" with: """ Feature: Failing in First Scenario Scenario: Given failing Scenario: Given passing """ When I run "behave --format=plain features/issue64_case1.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 1 scenario passed, 1 failed, 0 skipped """ Scenario: Failing in Middle Scenario Given a file named "features/issue64_case2.feature" with: """ Feature: Failing in Middle Scenario Scenario: Given passing Scenario: Given failing Scenario: Given passing """ When I run "behave --format=plain features/issue64_case2.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 2 scenarios passed, 1 failed, 0 skipped """ Scenario: Failing in Last Scenario Given a file named "features/issue64_case3.feature" with: """ Feature: Failing in Last Scenario Scenario: Given passing Scenario: Given passing Scenario: Given failing """ When I run "behave --format=plain features/issue64_case3.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 2 scenarios passed, 1 failed, 0 skipped """ Scenario: Failing in First and Last Scenario Given a file named "features/issue64_case4.feature" with: """ Feature: Failing in First and Last Scenario Scenario: Given failing Scenario: Given passing Scenario: Given failing """ When I run "behave --format=plain features/issue64_case4.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 1 scenario passed, 2 failed, 0 skipped """ behave-1.2.6/issue.features/issue0065.feature0000644000076600000240000000102513244555737021037 0ustar jensstaff00000000000000@issue Feature: Issue #65 Unrecognized --tag-help argument The "behave --help" output refers to the option "--tag-help" in the description of the "--tags" option. The correct option for more help on tags is "--tags-help". Scenario: Ensure environment assumptions are correct (Sanity Check) Given a new working directory When I run "behave --help" Then the command output should contain: """ --tags-help """ But the command output should not contain: """ --tag-help """ behave-1.2.6/issue.features/issue0066.feature0000644000076600000240000000476013244555737021051 0ustar jensstaff00000000000000@issue Feature: Issue #66: context.text and context.table are not cleared I noticed that context.table and context.text survive after the step is finished. Background: Test Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import given, when, then from hamcrest import assert_that, equal_to, is_not, is_, none import six @given(u'a step with multiline text') def step(context): assert context.text is not None assert context.text, "Ensure non-empty" assert isinstance(context.text, six.string_types) @given(u'a step with a table') def step(context): assert context.table is not None @when(u'I check the "context.{name}" attribute') def step(context, name): context.name = name context.value = getattr(context, name, None) @then(u'its value is None') def step(context): assert_that(context.value, is_(none())) @then(u'its value is "{value}"') def step(context, value): assert_that(context.value, equal_to(value)) @then(u'its value is not "{value}"') def step(context, value): assert_that(value, is_not(equal_to(context.value))) """ Scenario: Ensure multiline text data is cleared for next step Given a file named "features/issue66_case1.feature" with: """ Feature: Scenario: Given a step with multiline text: ''' Alice, Bob and Charly ''' When I check the "context.text" attribute Then its value is not "Alice, Bob and Charly" But its value is None """ When I run "behave -f plain features/issue66_case1.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 4 steps passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Ensure table data is cleared for next step Given a file named "features/issue66_case2.feature" with: """ Feature: Scenario: Given a step with a table: | name | gender | | Alice | female | | Bob | male | When I check the "context.table" attribute Then its value is None """ When I run "behave -f plain features/issue66_case2.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ behave-1.2.6/issue.features/issue0067.feature0000644000076600000240000000533513244555737021051 0ustar jensstaff00000000000000@issue Feature: Issue #67: JSON formatter cannot serialize tables. The JSON formatter cannot handle tables (currently): * Table as setup/intermediate/result table in steps of scenario * Examples tables in a ScenarioOutline A JSON exception occurs when such a feature file should be processed. Scenario: Scenario with Tables Given a new working directory And a file named "features/steps/steps1.py" with: """ from behave import given, when, then @given(u'I add the following employees') def step(context): pass # -- SKIP: Table processing here. @when(u'I select department "{department}"') def step(context, department): context.department = department @then(u'I get the following employees') def step(context): pass # -- SKIP: Table processing here. """ And a file named "features/issue67_case1.feature" with: """ Feature: Scenario with Tables Scenario: Given I add the following employees: | name | department | | Alice | Wonderland | | Bob | Moonwalk | When I select department "Wonderland" Then I get the following employees: | name | department | | Alice | Wonderland | """ When I run "behave -f json features/issue67_case1.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ But the command output should not contain: """ TypeError: is not JSON serializable """ Scenario: ScenarioOutline with Examples Table Given a file named "features/steps/steps2.py" with: """ from behave import given, when, then @given(u'a step with "{name}"') def step(context, name): context.name = name @when(u'a step with "{name}" occurs') def step(context, name): assert context.name == name @then(u'a step with "{name}" is reached') def step(context, name): assert context.name == name """ And a file named "features/issue67_case2.feature" with: """ Feature: ScenarioOutline with Examples Table Scenario Outline: Given a step with "" When a step with "" occurs Then a step with "" is reached Examples: |name | |Alice| |Bob | """ When I run "behave -f json features/issue67_case2.feature" Then it should pass with: """ 2 scenarios passed, 0 failed, 0 skipped 6 steps passed, 0 failed, 0 skipped, 0 undefined """ behave-1.2.6/issue.features/issue0069.feature0000644000076600000240000000377213244555737021056 0ustar jensstaff00000000000000@issue Feature: Issue #69: JUnitReporter: Fault when processing ScenarioOutlines with failing steps The problem occurs when "behave --junit ..." is used And a feature contains one or more ScenarioOutline(s) with failing steps. The JUnitReport does not process ScenarioOutline correctly (logic-error). Therefore, Scenarios of a ScenarioOutline are processes as Scenario steps. This causes problems when the step.status is "failed". RELATED: * issue #63 Background: Test Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import given @given(u'a {outcome} step with "{name}"') def step(context, outcome, name): context.name = name assert outcome == "passing" @when(u'a {outcome} step with "{name}" occurs') def step(context, outcome, name): assert outcome == "passing" assert context.name == name @then(u'a {outcome} step with "{name}" is reached') def step(context, outcome, name): assert outcome == "passing" assert context.name == name """ Scenario: ScenarioOutline with Failing Steps Given a file named "features/issue63_case2.feature" with: """ Feature: ScenarioOutline with Passing and Failing Steps Scenario Outline: Given a passing step with "" When a failing step with "" occurs Then a passing step with "" is reached Examples: |name | |Alice| |Bob | """ When I run "behave -c --junit features/issue63_case2.feature" Then it should fail with: """ 0 scenarios passed, 2 failed, 0 skipped 2 steps passed, 2 failed, 2 skipped, 0 undefined """ But the command output should not contain: """ AttributeError: 'Scenario' object has no attribute 'exception' """ And the command output should not contain: """ behave/reporter/junit.py """ behave-1.2.6/issue.features/issue0072.feature0000644000076600000240000000214113244555737021035 0ustar jensstaff00000000000000@issue Feature: Issue #72: Using 'GHERKIN_COLORS' fails with Exception > Trying: GHERKIN_COLORS=executing=white behave > It fails: > > Traceback (most recent call last): > ... > File "/.../behave/formatter/ansi_escapes.py", line 38, in > escapes[alias] = ''.join([colors[c] for c in aliases[alias].split(',')]) > TypeError: list indices must be integers, not str > > The reason is that the global variable colors is defined twice in this module. > The second variable overrides/modifies the first (without intention). Scenario: Using GHERKIN_COLORS in new working dir Given a new working directory And I set the environment variable "GHERKIN_COLORS" to "executing=white" When I run "behave" Then it should fail with: """ No steps directory in '{__WORKDIR__}/features' """ But the command output should not contain: """ Traceback (most recent call last): """ And the command output should not contain: """ TypeError: list indices must be integers, not str """ behave-1.2.6/issue.features/issue0073.feature0000644000076600000240000002066713244555737021053 0ustar jensstaff00000000000000@issue Feature: Issue #73: the current_matcher is not predictable . behave provides 2 matchers: ParseMatcher (parse) and RegexpMatcher (re). . The ParseMatcher is used per default when the test runner starts. . . A step definition file may change the matcher several times . by calling `use_step_matcher("re")` or `use_step_matcher("parse")`. . In order to make the writing of step definitions more predictable, . the matcher should be reset to the default matcher . before loading each step definition. . . A project can define its own default matcher by calling the . `use_step_matcher(...)` in the "environment.py" hook. . This matcher will be used as default before a step definition is loaded. Scenario: Verify that ParseMatcher is the default matcher Given a new working directory And a file named "features/steps/parse_steps.py" with: """ from behave import step @step(u'a step {outcome:w}') def step_passes(context, outcome): assert outcome == "passes", "FAIL: outcome={0}".format(outcome) """ And a file named "features/passing.feature" with: """ Feature: Scenario: Given a step passes When a step passes Then a step passes """ When I run "behave -f plain features/passing.feature" Then it should pass with """ 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Use only RegexMatcher in Step Definitions Given a new working directory And a file named "features/environment.py" with: """ from behave import use_step_matcher use_step_matcher("re") """ And a file named "features/steps/regexp_steps.py" with: """ from behave import step @step(u'a step (?Ppasses|fails)') def step_passes(context, outcome): assert outcome == "passes", "FAIL: outcome={0}".format(outcome) """ And a file named "features/passing.feature" with: """ Feature: Scenario: Given a step passes When a step passes Then a step passes """ When I run "behave -f plain features/passing.feature" Then it should pass with """ 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Use ParseMatcher and RegexMatcher in Step Definitions (default=re) Given a new working directory And a file named "features/environment.py" with: """ from behave import use_step_matcher use_step_matcher("re") """ And a file named "features/steps/eparse_steps.py" with: """ from behave import step, use_step_matcher use_step_matcher("parse") @step(u'an extraordinary step {outcome:w}') def step_passes(context, outcome): assert outcome == "passes", "FAIL: outcome={0}".format(outcome) """ And a file named "features/steps/regexp_steps.py" with: """ from behave import step @step(u'a step (?Ppasses|fails)') def step_passes(context, outcome): assert outcome == "passes", "FAIL: outcome={0}".format(outcome) """ And a file named "features/steps/xparse_steps.py" with: """ from behave import step, use_step_matcher use_step_matcher("parse") @step(u'another step {outcome:w}') def step_passes(context, outcome): assert outcome == "passes", "FAIL: outcome={0}".format(outcome) """ And a file named "features/passing3.feature" with: """ Feature: Scenario: Given a step passes When another step passes Then an extraordinary step passes """ When I run "behave -f plain features/passing3.feature" Then it should pass with """ 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Mix ParseMatcher and RegexMatcher in Step Definitions (default=re) Given a new working directory And a file named "features/environment.py" with: """ from behave import use_step_matcher use_step_matcher("re") """ And a file named "features/steps/given_steps.py" with: """ from behave import step, use_step_matcher use_step_matcher("parse") @given(u'a step {outcome:w}') def given_step_passes(context, outcome): assert outcome == "passes", "FAIL: outcome={0}".format(outcome) use_step_matcher("re") @given(u'another step (?Ppasses|fails)') def given_another_step_passes(context, outcome): assert outcome == "passes", "FAIL: outcome={0}".format(outcome) use_step_matcher("parse") @given(u'an extraordinary step {outcome:w}') def given_extraordinary_step_passes(context, outcome): assert outcome == "passes", "FAIL: outcome={0}".format(outcome) """ And a file named "features/steps/when_steps.py" with: """ from behave import step, use_step_matcher @when(u'a step (?Ppasses|fails)') def when_step_passes(context, outcome): assert outcome == "passes", "FAIL: outcome={0}".format(outcome) use_step_matcher("parse") @when(u'another step {outcome:w}') def when_another_step_passes(context, outcome): assert outcome == "passes", "FAIL: outcome={0}".format(outcome) use_step_matcher("re") @when(u'an extraordinary step (?Ppasses|fails)') def when_extraordinary_step_passes(context, outcome): assert outcome == "passes", "FAIL: outcome={0}".format(outcome) """ And a file named "features/steps/then_steps.py" with: """ from behave import step, use_step_matcher use_step_matcher("parse") @then(u'a step {outcome:w}') def then_step_passes(context, outcome): assert outcome == "passes", "FAIL: outcome={0}".format(outcome) use_step_matcher("re") @then(u'another step (?Ppasses|fails)') def then_another_step_passes(context, outcome): assert outcome == "passes", "FAIL: outcome={0}".format(outcome) use_step_matcher("parse") @then(u'an extraordinary step {outcome:w}') def then_extraordinary_step_passes(context, outcome): assert outcome == "passes", "FAIL: outcome={0}".format(outcome) """ And a file named "features/passing3.feature" with: """ Feature: Scenario: 1 Given a step passes When another step passes Then an extraordinary step passes Scenario: 2 Given another step passes When an extraordinary step passes Then a step passes Scenario: 3 Given an extraordinary step passes When a step passes Then another step passes """ When I run "behave -c -f pretty --no-timings features/passing3.feature" Then it should pass with: """ 3 scenarios passed, 0 failed, 0 skipped 9 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: # features/passing3.feature:1 Scenario: 1 # features/passing3.feature:2 Given a step passes # features/steps/given_steps.py:4 When another step passes # features/steps/when_steps.py:8 Then an extraordinary step passes # features/steps/then_steps.py:14 Scenario: 2 # features/passing3.feature:7 Given another step passes # features/steps/given_steps.py:9 When an extraordinary step passes # features/steps/when_steps.py:13 Then a step passes # features/steps/then_steps.py:4 Scenario: 3 # features/passing3.feature:12 Given an extraordinary step passes # features/steps/given_steps.py:14 When a step passes # features/steps/when_steps.py:3 Then another step passes # features/steps/then_steps.py:9 """ behave-1.2.6/issue.features/issue0075.feature0000644000076600000240000000123013244555737021036 0ustar jensstaff00000000000000@issue Feature: Issue #75: behave @features_from_text_file does not work . Feature of Cucumber. Reading the source code, I see it partly implemented. . . $ behave @list_of_features.txt . https://github.com/jeamland/behave/blob/master/behave/runner.py#L416:L430 . . However it fails because: . * it does not remove the @ from the path . * it does not search the steps/ directory in the parents of the feature files themselves @reuse.colocated_test Scenario: Use feature listfile Given I use the current directory as working directory When I run "behave -f plain features/runner.feature_listfile.feature" Then it should pass behave-1.2.6/issue.features/issue0077.feature0000644000076600000240000000541513244555737021051 0ustar jensstaff00000000000000@issue Feature: Issue #77: Does not capture stdout from sub-processes . My step functions are using wrapper objects to interact with SUT. . Those wrappers use this kind of thing to invoke executables: . . subprocess.check_call('myprog', ..., stderr=subprocess.STDOUT) . . However, the output from those calls does not appear in the stdout . captured by behave when a step fails. Background: Test Setup Given a new working directory Given a file named "hello.py" with: """ import sys def hello(): result = 0 args = sys.argv[1:] if args and args[0].startswith("--fail"): result = 1 args = args[1:] message = " ".join(args) sys.stdout.write("Hello {0}\n".format(message)) sys.exit(result) if __name__ == "__main__": hello() """ And a file named "features/steps/subprocess_call_steps.py" with: """ from behave import given, when, then import subprocess import os.path import sys PYTHON = sys.executable HERE = os.path.dirname(__file__) @when(u'I make a subprocess call "hello {commandline}"') def step(context, commandline): result = subprocess_call_hello(commandline.split()) assert result == 0 def subprocess_call_hello(args): command_args = [ PYTHON, "hello.py" ] + args result = subprocess.check_call(command_args, stderr=subprocess.STDOUT) return result # result = subprocess.check_output(command_args, stderr=subprocess.STDOUT) # return result """ Scenario: Subprocess call shows generated output Given a file named "features/issue77_hello_OK.feature" with: """ Feature: Scenario: When I make a subprocess call "hello world." """ When I run "behave -f plain features/issue77_hello_OK.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Hello world. """ Scenario: Subprocess call fails with captured output Given a file named "features/issue77_hello_FAIL.feature" with: """ Feature: Scenario: When I make a subprocess call "hello --fail FAIL." """ When I run "behave -f plain features/issue77_hello_FAIL.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped 0 steps passed, 1 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Hello FAIL. """ behave-1.2.6/issue.features/issue0080.feature0000644000076600000240000000276513244555737021050 0ustar jensstaff00000000000000@issue Feature: Issue #80: source file names not properly printed with python3 . $ behave -f pretty example/example.feature . Scenario: run a simple test # example/example.feature:3 . Given we have behave installed # :3 . When we implement a test # :7 . Then behave will test it for us! # :11 Background: Test Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import given, when, then @given(u'a step passes') def step(context): pass @when(u'a step passes') def step(context): pass @then(u'a step passes') def step(context): pass """ And a file named "features/basic.feature" with: """ Feature: Scenario: Given a step passes When a step passes Then a step passes """ Scenario: Show step locations When I run "behave -c -f pretty --no-timings features/basic.feature" Then it should pass And the command output should contain: """ Feature: # features/basic.feature:1 Scenario: # features/basic.feature:2 Given a step passes # features/steps/steps.py:3 When a step passes # features/steps/steps.py:7 Then a step passes # features/steps/steps.py:11 """ And the command output should not contain "# :" behave-1.2.6/issue.features/issue0081.feature0000644000076600000240000001240213244555737021036 0ustar jensstaff00000000000000@issue Feature: Issue #81: Allow defining steps in a separate library . The current design forces steps.py to be in a particular folder. . This does not allow to reuse a common library of BDD steps across . multiple software projects in a company. . It would be great if one could define a separate lib with common steps . (e.g. steps4mycompany.py) Background: Test Setup Given a new working directory And an empty file named "step_library42/__init__.py" And a file named "step_library42/alice_steps.py" with: """ # -- ALICE-STEPS: Anonymous step names. from behave import given, when, then @given(u'I use the step library "{library}"') def step(context, library): pass @when(u'I use steps from this step library') def step(context): pass @then(u'these steps are executed') def step(context): pass """ And a file named "features/use_step_library.feature" with: """ Feature: Scenario: Given I use the step library "alice" When I use steps from this step library Then these steps are executed """ Scenario: Proof of Concept Given a file named "features/steps/use_step_libs.py" with: """ from step_library42.alice_steps import * """ When I run "behave --no-timings -f plain features/use_step_library.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: Scenario: Given I use the step library "alice" ... passed When I use steps from this step library ... passed Then these steps are executed ... passed """ Scenario: With --format=pretty Given a file named "features/steps/use_step_libs.py" with: """ from step_library42.alice_steps import * """ When I run "behave -c -f pretty features/use_step_library.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: # features/use_step_library.feature:1 Scenario: # features/use_step_library.feature:2 Given I use the step library "alice" # step_library42/alice_steps.py:4 When I use steps from this step library # step_library42/alice_steps.py:8 Then these steps are executed # step_library42/alice_steps.py:12 """ Scenario: Selective step import from step library Given a file named "step_library42/bob_steps.py" with: """ # -- BOB-STEPS: Explicit step function names (otherwise same as alice). from behave import given, when, then @given(u'I use the step library "{library}"') def given_I_use_the_step_library(context, library): pass @when(u'I use steps from this step library') def when_I_use_steps_from_this_step_library(context): pass @then(u'these steps are executed') def then_these_steps_are_executed(context): pass """ And a file named "features/steps/use_step_libs.py" with: """ from step_library42.bob_steps import given_I_use_the_step_library from step_library42.bob_steps import when_I_use_steps_from_this_step_library from step_library42.bob_steps import then_these_steps_are_executed """ When I run "behave -c -f pretty features/use_step_library.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: # features/use_step_library.feature:1 Scenario: # features/use_step_library.feature:2 Given I use the step library "alice" # step_library42/bob_steps.py:4 When I use steps from this step library # step_library42/bob_steps.py:8 Then these steps are executed # step_library42/bob_steps.py:12 """ Scenario: Import step library in "environment.py" Given a file named "features/environment.py" with: """ from step_library42.alice_steps import * """ And an empty file named "features/steps/__init__.py" When I run "behave -c -f pretty features/use_step_library.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: # features/use_step_library.feature:1 Scenario: # features/use_step_library.feature:2 Given I use the step library "alice" # step_library42/alice_steps.py:4 When I use steps from this step library # step_library42/alice_steps.py:8 Then these steps are executed # step_library42/alice_steps.py:12 """ behave-1.2.6/issue.features/issue0083.feature0000644000076600000240000000407613244555737021050 0ustar jensstaff00000000000000@issue Feature: Issue #83: behave.__main__:main() Various sys.exit issues . Currently, the main function has several issues related . to sys.exit() returncode usage: . . 1. sys.exit("string") is invalid, a number must be used: . => Used in exception cases after run (ParseError, ConfigError) . . 2. On success, the main() function returns implicitly None . instead of using sys.exit(0) . => No statement at end of function after failed case. @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step(u'a step passes') def step_passes(context): pass """ Scenario: Successful test run Given a file named "features/passing.feature" with: """ Feature: Scenario: Given a step passes When a step passes Then a step passes """ When I run "behave -c features/passing.feature" Then it should pass And the command returncode is "0" Scenario: ParseError occurs Given a file named "features/invalid_with_ParseError.feature" with: """ Feature: Scenario: Invalid scenario which raises ParseError Given a step passes When2 a step passes """ When I run "behave -c features/invalid_with_ParseError.feature" Then it should fail And the command returncode is non-zero And the command output should contain: """ Failed to parse "{__WORKDIR__}/features/invalid_with_ParseError.feature" """ Scenario: ConfigError occurs Given a new working directory And a file named "features/passing2.feature" with: """ Feature: Scenario: Given a step passes """ When I run "behave -c features/passing2.feature" Then it should fail And the command returncode is non-zero And the command output should contain: """ No steps directory in '{__WORKDIR__}/features' """ behave-1.2.6/issue.features/issue0084.feature0000644000076600000240000000444113244555737021045 0ustar jensstaff00000000000000@issue Feature: Issue #84: behave.runner behave does not reliably detected failed test runs . Behave does currently not reliably detected failed test runs and . therefore returns not sys.exit(1) at end of main(). . . 1. behave.runner:Runner.run_with_paths() returns failed==True . if last feature was successful and test runner does not stop . after first failing feature. . . 2. Issue #64: Same problem in behave.model.Feature.run() with scenarios Scenario: Test Setup Given a new working directory And a file named "features/passing.feature" with: """ Feature: Passing Scenario: Given a step passes When a step passes Then a step passes """ And a file named "features/failing.feature" with: """ Feature: Failing Scenario: Given a step fails When a step fails Then a step fails """ And a file named "features/steps/steps.py" with: """ from behave import step @step(u'a step passes') def step_passes(context): pass @step(u'a step fails') def step_fails(context): assert False, "step: a step fails" """ Scenario: First feature fails, second feature passes When I run "behave -f plain features/failing.feature features/passing.feature" Then it should fail with: """ 1 feature passed, 1 failed, 0 skipped 1 scenario passed, 1 failed, 0 skipped 3 steps passed, 1 failed, 2 skipped, 0 undefined """ Scenario: First feature passes, second feature fails When I run "behave -f plain features/passing.feature features/failing.feature" Then it should fail with: """ 1 feature passed, 1 failed, 0 skipped 1 scenario passed, 1 failed, 0 skipped 3 steps passed, 1 failed, 2 skipped, 0 undefined """ Scenario: First feature passes, second fails, last passes When I run "behave -f plain features/passing.feature features/failing.feature features/passing.feature" Then it should fail with: """ 2 features passed, 1 failed, 0 skipped 2 scenarios passed, 1 failed, 0 skipped 6 steps passed, 1 failed, 2 skipped, 0 undefined """ behave-1.2.6/issue.features/issue0085.feature0000644000076600000240000001145213244555737021046 0ustar jensstaff00000000000000@issue Feature: Issue #85: AssertionError with nested regex and pretty formatter | When --format=pretty is used | an AssertationError occurs for missing optional/nested-groups. | When --format=plain is used, everything is fine Scenario: Test Setup Given a new working directory And a file named "features/steps/regexp_steps.py" with: """ from __future__ import print_function from behave import given, when, then, use_step_matcher @given(u'a {re_category} regular expression "{pattern}"') def impl(context, re_category, pattern): pass @then(u'the parameter "{name}" is "{expected_value}"') def impl(context, name, expected_value): actual_value = getattr(context, name, None) if actual_value is None: actual_value = "" assert hasattr(context, name) assert actual_value == expected_value, "MISMATCH: actual({0}) == expected({1})".format(actual_value, expected_value) @then(u'the parameter "{name}" is none') def impl(context, name): actual_value = getattr(context, name, None) assert hasattr(context, name) assert actual_value is None, "MISMATCH: actual({0}) == None)".format(actual_value) def store_in_context(context, data): for name, value in data.items(): setattr(context, name, value) use_step_matcher('re') @when(u'I try to match "(?Pfoo and more)"') def impl(context, **kwargs): kwargs["regexp_case"] = "simple" print("CASE UNNESTED: {0}".format(kwargs)) store_in_context(context, kwargs) @when(u'I try to match "(?Pfoo(?Pbar)?)"') def impl(context, **kwargs): kwargs["regexp_case"] = "nested" print("CASE NESTED: {0}".format(kwargs)) store_in_context(context, kwargs) @when(u'I try to match "(?Pfoo) (?Pbar)?"') def impl(context, **kwargs): kwargs["regexp_case"] = "optional" print("CASE OPTIONAL: {0}".format(kwargs)) store_in_context(context, kwargs) """ And a file named "features/matching.feature" with: """ Feature: Using regexp matcher with nested and optional parameters Scenario: regex, no nested groups, matching Given a simple regular expression "(?Pfoo and more)" When I try to match "foo and more" Then the parameter "regexp_case" is "simple" And the parameter "foo" is "foo and more" Scenario: Nested groups without nested match Given a nested-group regular expression "(?Pfoo(?Pbar)?)" When I try to match "foo" Then the parameter "regexp_case" is "nested" And the parameter "foo" is "foo" And the parameter "bar" is none Scenario: Nested groups with nested match Given a nested-group regular expression "(?Pfoo(?Pbar)?)" When I try to match "foobar" Then the parameter "regexp_case" is "nested" And the parameter "foo" is "foobar" And the parameter "bar" is "bar" Scenario: Optional group without match Given a optional-group regular expression "(?Pfoo) (?Pbar)?" When I try to match "foo " Then the parameter "regexp_case" is "optional" And the parameter "foo" is "foo" And the parameter "bar" is none Scenario: Optional group with match Given a optional-group regular expression "(?Pfoo) (?Pbar)?" When I try to match "foo bar" Then the parameter "regexp_case" is "optional" And the parameter "foo" is "foo" And the parameter "bar" is "bar" """ Scenario: Run regexp steps with --format=plain When I run "behave --format=plain features/matching.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 5 scenarios passed, 0 failed, 0 skipped 24 steps passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Run regexp steps with --format=pretty When I run "behave -c --format=pretty features/matching.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 5 scenarios passed, 0 failed, 0 skipped 24 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should not contain """ assert isinstance(text, unicode) """ And the command output should not contain """ AssertationError """ behave-1.2.6/issue.features/issue0092.feature0000644000076600000240000000405713244555737021047 0ustar jensstaff00000000000000@issue Feature: Issue #92: Output from --format=plain shows skipped steps in next scenario . DUPLICATED, FIXED-BY: issue #35 solution. . . Given a feature has more than one scenario . When the --format=plain option is used . and a middle step of a scenario fails . Then the skipped steps appear under the next scenario Scenario: Given a new working directory And a file named "features/issue92_syndrome.feature" with: """ Feature: Testing Plain Output Reproduces bug where output from previous scenario appears before current. Scenario: First example Given this step works When this step fails Then this step appears in the wrong place Scenario: Second example Given this step works When this step fails Then this step appears in the wrong place """ And a file named "features/steps/steps.py" with: """ from behave import step @step(u'this step works') def working(context): pass @step(u'this step fails') def failing(context): assert False, 'step failed' @step(u'this step appears in the wrong place') def missing(context): pass """ When I run "behave --no-timings --format=plain features/issue92_syndrome.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 2 failed, 0 skipped 2 steps passed, 2 failed, 2 skipped, 0 undefined """ And the command output should contain: """ Feature: Testing Plain Output Scenario: First example Given this step works ... passed When this step fails ... failed Assertion Failed: step failed Scenario: Second example Given this step works ... passed When this step fails ... failed Assertion Failed: step failed """ behave-1.2.6/issue.features/issue0096.feature0000644000076600000240000001352313244555737021051 0ustar jensstaff00000000000000@issue Feature: Issue #96: Sub-steps failed without any error info to help debug issue . I am trying to run execute_steps. One of them fails, but the error output . from behave has no details whatsoever. It is virtually impossible . to figure out why it failed. as no error output is present except the . final error message . . def before_scenario(context,scenario): . context.execute_steps(u''' . When "admin:admin" sends POST "/tasks/testStart" . Then I expect HTTP code 200 . ''') . . File ".../behave/runner.py", line 262, in execute_steps . assert False, "FAILED SUB-STEP: %s" % step . AssertionError: FAILED SUB-STEP: When "admin:admin" sends POST "/tasks/testStart" . . All we get is the "sub-step failed" but no info whatsoever . as to why it failed... Background: Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step import sys @step(u'a step passes') def step_passes(context): pass @step(u'a step fails') def step_fails(context): assert False, 'EXPECT: Step fails.' @step(u'a step fails with stdout "{message}"') def step_fails_with_stdout(context, message): sys.stdout.write("%s\n" % message) assert False, 'EXPECT: Step fails with stdout.' @step(u'a step fails with stderr "{message}"') def step_fails_with_stderr(context, message): sys.stderr.write("%s\n" % message) assert False, 'EXPECT: Step fails with stderr.' @step(u'a step raises an error "{message}"') def step_raises_exception(context, message): raise RuntimeError(message) @step(u'the following steps should pass') def step_following_steps_should_pass(context): context.execute_steps(context.text.strip()) """ Scenario: Execute steps and one fails (EXPECTATION-MISMATCH: Assert fails) Given a file named "features/issue96_case1.feature" with: ''' Feature: Scenario: When the following steps should pass: """ Given a step passes When a step fails Then a step passes """ ''' When I run "behave -c features/issue96_case1.feature" Then it should fail with: """ Assertion Failed: FAILED SUB-STEP: When a step fails Substep info: Assertion Failed: EXPECT: Step fails. """ Scenario: Execute steps and error occurs (UNEXPECTED: exception is raised) Given a file named "features/issue96_case2.feature" with: ''' Feature: Scenario: When the following steps should pass: """ Given a step passes When a step raises an error "Alice is alive" Then a step passes """ ''' When I run "behave -c features/issue96_case2.feature" Then it should fail with: """ RuntimeError: Alice is alive """ And the command output should contain: """ Assertion Failed: FAILED SUB-STEP: When a step raises an error "Alice is alive" Substep info: Traceback (most recent call last): """ Scenario: Execute steps and one fails with stdout capture Given a file named "features/issue96_case3.feature" with: ''' Feature: Scenario: When the following steps should pass: """ Given a step passes When a step fails with stdout "STDOUT: Alice is alive" Then a step passes """ ''' When I run "behave -c features/issue96_case3.feature" Then it should fail with: """ Assertion Failed: FAILED SUB-STEP: When a step fails with stdout "STDOUT: Alice is alive" Substep info: Assertion Failed: EXPECT: Step fails with stdout. """ And the command output should contain: """ Captured stdout: STDOUT: Alice is alive """ Scenario: Execute steps and one fails with stderr capture Given a file named "features/issue96_case4.feature" with: ''' Feature: Scenario: When the following steps should pass: """ Given a step passes When a step fails with stderr "STDERR: Alice is alive" Then a step passes """ ''' When I run "behave -c features/issue96_case4.feature" Then it should fail with: """ Assertion Failed: FAILED SUB-STEP: When a step fails with stderr "STDERR: Alice is alive" Substep info: Assertion Failed: EXPECT: Step fails with stderr. """ And the command output should contain: """ Captured stderr: STDERR: Alice is alive """ Scenario: Execute steps and fail in before_scenario hook Given a file named "features/issue96_case5.feature" with: """ Feature: Scenario: Given a step passes When a step passes Then a step passes """ And a file named "features/environment.py" with: """ def before_scenario(context, scenario): context.execute_steps(u''' Given a step passes When a step passes Then a step fails ''') """ When I run "behave -c features/issue96_case5.feature" Then it should fail with: """ HOOK-ERROR in before_scenario: AssertionError: FAILED SUB-STEP: Then a step fails Substep info: Assertion Failed: EXPECT: Step fails. """ behave-1.2.6/issue.features/issue0099.feature0000644000076600000240000001020513244555737021046 0ustar jensstaff00000000000000@issue Feature: Issue #99: Layout variation "a directory containing your feature files" is broken for running single features . When I use a layout as described in the 1.2.2 documentation, . I can only specify a whole directory of feature files to run. . Specifying a single feature file results in an error from behave: . . $ behave -v tests/feature/webui/features/feature_under_test.feature . ... . Supplied path: "tests/feature/webui/features/feature_under_test.feature" . Primary path is to a file so using its directory . Trying base directory: .../tests/feature/webui/features . Trying base directory: .../tests/feature/webui . ERROR: Could not find "steps" directory in your specified path '.../tests/feature/webui/features' . No steps directory in '.../tests/feature/webui/features' . . My directory layout is as follows: . . .../tests/feature/webui/ . +-- features/ . +-- steps/ . +-- environment.py . . SEE ALSO: . * http://packages.python.org/behave/gherkin.html#layout-variations Background: Given a new working directory And a file named "root/steps/steps.py" with: """ from behave import step @step(u'a step passes') def step_passes(context): pass """ And a file named "root/features/alice.feature" with: """ Feature: Alice Scenario: Given a step passes """ And a file named "root/features/bob.feature" with: """ Feature: Bob Scenario: Given a step passes """ And a file named "root/features2/charly.feature" with: """ Feature: Charly Scenario: When a step passes """ Scenario: Run features with root directory When I run "behave -f plain --no-timings root" Then it should pass with: """ 3 features passed, 0 failed, 0 skipped 3 scenarios passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: Alice Scenario: Given a step passes ... passed Feature: Bob Scenario: Given a step passes ... passed Feature: Charly Scenario: When a step passes ... passed """ Scenario: Run features with root/features directory When I run "behave -f plain --no-timings root/features" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: Alice Scenario: Given a step passes ... passed Feature: Bob Scenario: Given a step passes ... passed """ Scenario: Run features with feature files When I run "behave -f plain --no-timings root/features/alice.feature root/features2/charly.feature" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: Alice Scenario: Given a step passes ... passed Feature: Charly Scenario: When a step passes ... passed """ Scenario: Run features with feature dir and feature files (other ordering) When I run "behave -f plain --no-timings root/features2 root/features/alice.feature" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: Charly Scenario: When a step passes ... passed Feature: Alice Scenario: Given a step passes ... passed """ behave-1.2.6/issue.features/issue0109.feature0000644000076600000240000000420113244555737021035 0ustar jensstaff00000000000000@issue Feature: Issue #109: Insists that implemented tests are not implemented . STATUS: Resolved, not a behave problem. . . Following feature file marks implemented step "when I submit the following data" . as not implemented. Scenario: Given a new working directory And a file named "features/syndrome109.feature" with: """ @wip Feature: Manage accounts from the admin interface Scenario: Login successfully via login form Given I navigate to "/admin/" when I submit the following data | name | value | | username | admin@foo.bar | | password | pass | then I see the word "Welcome" Scenario: Create user via admin user creation form Given I navigate to "/admin/users/user/add/" when I submit the following data | name | value | | email | spaaaaaaaaaaaaaaaaaaam@ham.eggs | | password1 | pass | | password2 | pass | then I see the word "successfully" """ And a file named "features/steps/steps.py" with: """ from behave import given, when, then @given(u'I navigate to "{url}"') def step_navigate_to_url(context, url): pass @when(u'I submit the following data') def step_submit_data(context): pass @then(u'I see the word "{word}"') def step_see_word(context, word): pass """ When I run "behave -w features/syndrome109.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped 6 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should not contain: """ You can implement step definitions for undefined steps with these snippets: """ behave-1.2.6/issue.features/issue0111.feature0000644000076600000240000000312313244555737021030 0ustar jensstaff00000000000000@issue Feature: Issue #111: Comment following @wip tag results in scenario being ignored . If a comment is placed after the @wip tag, the following scenario . is ignored by behave: . . @wip # comment: this is work in progress . Scenario: test scenario . . results in behave -w not running the "test scenario". . After removing the comment, it runs as expected. Scenario: Test Setup Given a new working directory And a file named "features/steps/passing_steps.py" with: """ from behave import step @step(u'a step passes') def step_passes(context): pass """ And a file named "features/syndrome111.feature" with: """ Feature: @wip # Comment: blabla Scenario: S1 Given a step passes @wip @one # Comment: foobar Scenario: S2 Given a step passes """ Scenario: Scenario w/ comment on tag-line should run as normal When I run "behave --wip features/syndrome111.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Ensure 2nd scenario can be selected with other tag When I run "behave -f plain --tags=one features/syndrome111.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 1 skipped 1 step passed, 0 failed, 1 skipped, 0 undefined """ behave-1.2.6/issue.features/issue0112.feature0000644000076600000240000000416113244555737021034 0ustar jensstaff00000000000000@issue @change_request Feature: Issue #112: Improvement to AmbiguousStep error . AmbiguousStep could be more useful if it also showed the existing string . with which the new one is clashing. This is particularly useful . if using step parameters. Background: Given a new working directory And a file named "features/syndrome112.feature" with: """ Feature: Scenario: Given I buy 10 oranges """ Scenario: Good step ordering -- From specific to generic regular expression Given a file named "features/steps/good_steps.py" with: """ from behave import given, when, then # -- ORDERING-IMPORTANT: From more specific steps to less specific. @given(u'I buy {number:n} {items:w}') def step_given_I_buy2(context, number, items): pass # -- OTHERWISE: Generic step matches all other patterns. @given(u'I buy {amount} {product}') def step_given_I_buy(context, amount, product): pass """ When I run "behave -c features/syndrome112.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Bad step ordering causes AmbiguousStep Given a file named "features/steps/bad_steps.py" with: """ from behave import given, when, then # -- ORDERING-VIOLATED: Generic step comes first. @given(u'I buy {amount} {product}') def step_given_I_buy(context, amount, product): pass # -- AMBIGUOUS-STEP: Will occur here. @given(u'I buy {number:n} {items:w}') def step_given_I_buy2(context, number, items): pass """ When I run "behave -c features/syndrome112.feature" Then it should fail And the command output should contain: """ AmbiguousStep: @given('I buy {number:n} {items:w}') has already been defined in existing step @given('I buy {amount} {product}') at features/steps/bad_steps.py:4 """ behave-1.2.6/issue.features/issue0114.feature0000644000076600000240000000633113244555737021037 0ustar jensstaff00000000000000@issue @change_request Feature: Issue #114: Avoid unnecessary blank lines w/ --no-skipped option . Unnessary blank lines appear when you use (for each skipped feature): . . behave -f progress --tags=@one --no-skipped ... @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/passing_steps.py" with: """ from behave import step @step(u'a step passes') def step_passes(context): pass """ And a file named "features/e1.feature" with: """ @example Feature: E1 Scenario: S1.1 Given a step passes """ And a file named "features/e2.feature" with: """ @exclude Feature: E2 Scenario: S2.1 Given a step passes """ And a file named "features/e3.feature" with: """ @example Feature: E3 Scenario: S3.1 Given a step passes """ Scenario: Run Features with tags and --show-skipped option When I run "behave -f progress --tags=@example" Then it should pass with: """ 2 features passed, 0 failed, 1 skipped 2 scenarios passed, 0 failed, 1 skipped 2 steps passed, 0 failed, 1 skipped, 0 undefined """ And the command output should contain: """ features/e1.feature . features/e2.feature S features/e3.feature . """ Scenario: Run Features with tag and --no-skipped option (CASE 1) When I run "behave -f progress --tags=@example --no-skipped" Then it should pass with: """ 2 features passed, 0 failed, 1 skipped 2 scenarios passed, 0 failed, 1 skipped 2 steps passed, 0 failed, 1 skipped, 0 undefined """ And the command output should contain exactly: """ features/e1.feature . features/e3.feature . """ But the command output should not contain exactly: """ features/e1.feature . features/e3.feature . """ Scenario: Run Features with other tag and --no-skipped option (CASE 2) When I run "behave -f progress --tags=@exclude --no-skipped" Then it should pass with: """ 1 feature passed, 0 failed, 2 skipped 1 scenario passed, 0 failed, 2 skipped 1 step passed, 0 failed, 2 skipped, 0 undefined """ And the command output should contain exactly: """ features/e2.feature . """ Scenario: Run Features with tag, --no-skipped and plain formatter (CASE 3) When I run "behave -f plain --tags=@example --no-skipped -T" Then it should pass with: """ 2 features passed, 0 failed, 1 skipped 2 scenarios passed, 0 failed, 1 skipped 2 steps passed, 0 failed, 1 skipped, 0 undefined """ And the command output should contain exactly: """ Feature: E1 Scenario: S1.1 Given a step passes ... passed Feature: E3 """ But the command output should not contain exactly: """ Feature: E1 Scenario: S1.1 Given a step passes ... passed Feature: E3 """ behave-1.2.6/issue.features/issue0116.feature0000644000076600000240000000360213244555737021037 0ustar jensstaff00000000000000@issue @change_request Feature: Issue #116: SummaryReporter shows failed scenarios list Scenario: Test Setup Given a new working directory And a file named "features/steps/passing_failing_steps.py" with: """ from behave import step @step(u'a step passes') def step_passes(context): pass @step(u'a step fails') def step_fails(context): assert False, "FAILS" """ And a file named "features/e1.feature" with: """ Feature: E1 Scenario: E1.1 Given a step passes @xfail Scenario: E1.2 (XFAIL) Given a step fails Scenario: E1.3 Given a step passes """ And a file named "features/e2.feature" with: """ @example2 Feature: E2 @xfail Scenario: E2.1 (XFAIL) Given a step fails Scenario: E2.2 Given a step passes """ Scenario: Summary shows list of failed scenarios when at least one fails When I run "behave -f plain features/" Then it should fail And the command output should contain: """ Failing scenarios: features/e1.feature:7 E1.2 (XFAIL) features/e2.feature:5 E2.1 (XFAIL) 0 features passed, 2 failed, 0 skipped 3 scenarios passed, 2 failed, 0 skipped 3 steps passed, 2 failed, 0 skipped, 0 undefined """ Scenario: Summary hides list of failed scenarios when all scenarios pass When I run "behave -f plain --tags=~@xfail features/" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 3 scenarios passed, 0 failed, 2 skipped 3 steps passed, 0 failed, 2 skipped, 0 undefined """ But the command output should not contain: """ Failing scenarios: """ behave-1.2.6/issue.features/issue0125.feature0000644000076600000240000000305013244555737021034 0ustar jensstaff00000000000000@issue Feature: Issue #125: Duplicate "Captured stdout" if substep has failed Background: Test Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step('a step fails with stdout "{message}"') def step_fails_with_stdout(context, message): print(message) assert False, 'EXPECT: Step fails with stdout.' @step('substep fails with stdout "{message}"') def substep_fails_with_stdout(context, message): context.execute_steps(u'When a step fails with stdout "%s"' % message) """ Scenario: Subprocess call shows generated output Given a file named "features/issue125_example.feature" with: """ Feature: Scenario: When substep fails with stdout "Hello" """ When I run "behave -f plain --no-timings features/issue125_example.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped 0 steps passed, 1 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: Scenario: When substep fails with stdout "Hello" ... failed Assertion Failed: FAILED SUB-STEP: When a step fails with stdout "Hello" Substep info: Assertion Failed: EXPECT: Step fails with stdout. """ And the command output should contain 1 times: """ Captured stdout: Hello """ But note that "the captured output should not be contained multiple times" behave-1.2.6/issue.features/issue0127.feature0000644000076600000240000000427313244555737021046 0ustar jensstaff00000000000000@issue Feature: Issue #127: Strip trailing colons . Trailing colon in a step is stripped by the Gherkin parser. . Undefined step snippets should not suggest the step with a trailing colon. . . GENERAL RULE (by looking at the parser): . 1. Colon in step in feature file is OK . (parser strips this for step-with-table or step-with-multiline-text). . 2. Step definitions in Python files should not end with a colon . (used in @given/@when/@then decorators). Background: Given a new working directory And a file named "features/example127.feature" with: """ Feature: Scenario: Given the following superusers exist: | Name | User Id | | Alice | 101 | | Bob | 102 | """ Scenario: Step Definition has no trailing colon (GOOD CASE) Given a file named "features/steps/good_steps.py" with: """ from behave import given @given(u'the following superusers exist') def step_given_following_superusers_exist(context): pass """ When I run "behave -f plain features/example127.feature" Then it should pass And the command output should not contain: """ You can implement step definitions for undefined steps with these snippets: @given(u'the following superusers exist:') def step_impl(context): raise NotImplementedError(u'STEP: Given the following superusers exist:') """ Scenario: Step Definition has trailing colon (BAD CASE) Given a file named "features/steps/bad_steps.py" with: """ from behave import given @given(u'the following superusers exist:') def step_given_following_superusers_exist(context): pass """ When I run "behave -f plain features/example127.feature" Then it should fail And the command output should contain: """ You can implement step definitions for undefined steps with these snippets: @given(u'the following superusers exist') def step_impl(context): raise NotImplementedError(u'STEP: Given the following superusers exist') """ behave-1.2.6/issue.features/issue0139.feature0000644000076600000240000000365413244555737021053 0ustar jensstaff00000000000000@issue @not_reproducible Feature: Issue #139: Wrong steps seem to be executed when using --wip . RELATED-TO: issue #35 . behave --format=plain --tags @one" seems to execute right scenario w/ wrong steps . . If you have a feature file with two scenarios where the second is tagged . with @wip, running behave -w will output step names from the first scenario. . It does seem to run the correct code for the steps. Scenario: Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import given, when, then, step @step('a step passes') def step_passes(context): pass @step('a step fails') def step_fails(context): assert False, "XFAIL" @when('I run a test step') def step_impl(context): pass @when('I run some other test step') def step_impl(context): pass @then('I should not see a failure here') def step_impl(context): pass """ And a file named "features/issue0139_example.feature" with: """ Feature: Bug in wip/behave -w Scenario: This is strange Given a step passes When a step passes Then a step fails @wip Scenario: Demonstrate bug When I run a test step And I run some other test step Then I should not see a failure here """ When I run "behave -w -f plain -T features/issue0139_example.feature" Then it should pass And the command output should contain: """ Feature: Bug in wip/behave -w Scenario: This is strange Scenario: Demonstrate bug When I run a test step ... passed And I run some other test step ... passed Then I should not see a failure here ... passed """ behave-1.2.6/issue.features/issue0142.feature0000644000076600000240000000220213244555737021031 0ustar jensstaff00000000000000@issue @not_reproducible Feature: Issue #142: --junit flag fails to output with step table data: TypeError: is not JSON serializable DUPLICATES: issue #67 (already fixed). Scenario: Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import given, when, then, step @then('use table data with') def step_impl(context): pass """ And a file named "features/issue0142_example.feature" with: """ Feature: Scenario: Use a table Then use table data with: | data | value | | behave outputs junit with tables | false | """ When I run "behave --junit -f json features/issue0142_example.feature" Then it should pass But the command output should not contain: """ TypeError: is not JSON serializable """ And the command output should not contain: """ Traceback (most recent call last): """ behave-1.2.6/issue.features/issue0143.feature0000644000076600000240000000340213244555737021035 0ustar jensstaff00000000000000@issue Feature: Issue #143: Logging starts with a StreamHandler way too early . This verifies that some imported library or other item has not made a . call to logging too soon, which would add a StreamHandler. @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/steps.py" with: """ import logging from behave import given, when, then, step @step('I create {count:n} log records') def step_create_log_records(context, count): for i in range(count): logging.debug('Some debug logging') """ And a file named "features/issue0143_example.feature" with: """ Feature: Logging should not be output unless there is a failure Scenario: A passing test Given I create 4 log records """ Scenario: Ensure that no log-ouput occurs with enabled log-capture Given an empty file named "features/environment.py" When I run "behave -f plain --logcapture features/issue0143_example.feature" Then it should pass And the command output should not contain: """ DEBUG:root:Some debug logging """ Scenario: Ensure that log-ouput occurs with disabled log-capture Given a file named "features/environment.py" with: """ import logging def before_all(context): # -- basicConfig() will not set level if setup is already done. logging.basicConfig() logging.getLogger().setLevel(logging.DEBUG) """ When I run "behave -f plain --no-logcapture features/issue0143_example.feature" Then it should pass And the command output should contain: """ DEBUG:root:Some debug logging """ behave-1.2.6/issue.features/issue0145.feature0000644000076600000240000000405313244555737021042 0ustar jensstaff00000000000000@issue Feature: Issue #145: before_feature/after_feature should not be skipped Hooks before_feature(), after_feature() (and before_step()) are skipped if --tags options select feature tag and scenario tag. SEE ALSO: https://github.com/cucumber/cucumber/wiki/Tags @setup Scenario: Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step('a step passes') def step_passes(context): pass """ And a file named "features/issue0145_example.feature" with: """ @feature Feature: Feature-145 @scenario Scenario: Scenario-145 Given a step passes When a step passes Then a step passes """ And a file named "features/environment.py" with: """ from __future__ import print_function def before_feature(context, feature): print("hooks.before_feature: %s called." % feature.name) def after_feature(context, feature): print("hooks.after_feature: %s called." % feature.name) """ Scenario: Select only @scenario tag When I run "behave -f plain -T --tags=@scenario features/issue0145_example.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ And the behave hook "before_feature" was called And the behave hook "after_feature" was called Scenario: Select @feature tag and @scenario tag (logical-and, fails if not fixed) When I run "behave -f plain -T --tags=@feature --tags=@scenario features/issue0145_example.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ And the behave hook "before_feature" was called And the behave hook "after_feature" was called behave-1.2.6/issue.features/issue0148.feature0000644000076600000240000000637013244555737021051 0ustar jensstaff00000000000000@issue @already_fixed Feature: Issue #148: Substeps do not fail FIXED-BY: issue #117 context.execute_steps() should support table and multi-line text. RELATED-TO: issue #96 @setup Scenario: Setup Given a new working directory And a file named "features/steps/passing_steps.py" with: """ @step('a step passes') def step_passes(context): pass @step('a step fails') def step_fails(context): assert False, "XFAIL" """ And a file named "features/issue0148_example.feature" with: """ Feature: Sub steps @xfail Scenario: Failing test without substeps Given a step passes When a step fails Then a step passes @xfail Scenario: Failing test with substeps Given a step passes When I do something with stupid substeps Then a step passes """ Scenario: Missing Step Keywords in Substeps Given a file named "features/steps/substeps.py" with: """ @When('I do something with stupid substeps') def step(context): context.execute_steps(u''' I do something stupid there is a second stupid step ''') # Given/When/Then keywords are missing in substeps above. """ When I run "behave -f plain -T features/issue0148_example.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 2 failed, 0 skipped 2 steps passed, 2 failed, 2 skipped, 0 undefined """ And the command output should contain: """ Scenario: Failing test without substeps Given a step passes ... passed When a step fails ... failed """ And the command output should contain: """ Scenario: Failing test with substeps Given a step passes ... passed When I do something with stupid substeps ... failed """ And the command output should contain: """ ParserError: Failed to parse : Parser failure in state steps, at line 2: "I do something stupid" """ Scenario: Use Step Keywords in Substeps Given a file named "features/steps/substeps.py" with: """ @when('I do something with stupid substeps') def step(context): context.execute_steps(u''' When a step fails Then a step fails ''') """ When I run "behave -f plain -T features/issue0148_example.feature" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 2 failed, 0 skipped 2 steps passed, 2 failed, 2 skipped, 0 undefined """ And the command output should contain: """ Scenario: Failing test with substeps Given a step passes ... passed When I do something with stupid substeps ... failed Assertion Failed: FAILED SUB-STEP: When a step fails Substep info: Assertion Failed: XFAIL """ But the command output should not contain: """ ParserError: Failed to parse """ behave-1.2.6/issue.features/issue0152.feature0000644000076600000240000000355613244555737021047 0ustar jensstaff00000000000000@issue Feature: Issue #152: Fix encoding issues . I fixed two encoding issues in pretty formatter and in JUnit serialization. . Now it's possible to use accented letters in feature files and . create JUnit reports from the tests. Scenario: Ensure JUnit reports can be created from a foreign language Given a new working directory And an empty file named "features/steps/steps.py" And a file named "features/eins.feature" with: """ # language: de Funktionalität: Die Welt ist schön Szenario: Was wäre wenn die schöne, neue Welt untergeht """ When I run "behave -f plain --junit --no-timings features/eins.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 0 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Funktionalität: Die Welt ist schön Szenario: Was wäre wenn die schöne, neue Welt untergeht """ @reuse.colocated_test Scenario: Ensure JUnit reports can be created from a foreign language Given I use the current directory as working directory When I run "behave -f plain --junit --no-timings tools/test-features/french.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped 5 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Fonctionnalité: testing stuff Scénario: test stuff Etant donné I am testing stuff ... passed Quand I exercise it work ... passed Alors it will work ... passed Scénario: test more stuff Etant donné I am testing stuff ... passed Alors it will work ... passed """ behave-1.2.6/issue.features/issue0159.feature0000644000076600000240000000354013244555737021047 0ustar jensstaff00000000000000@issue Feature: Issue #159: output stream is wrapped twice in the codecs.StreamWriter @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/steps.py" with: """ # -*- coding: utf-8 -*- from behave import step @step('firstname is "{name}"') def step_impl(context, name): pass @step(u'full name is Loïc "{name}"') def step_impl(context, name): pass """ Scenario: Single feature, pass (a) Given a file named "features/issue159_stream_writer.feature" with: """ Feature: Scenario: When firstname is "Loïc" """ When I run "behave -f plain features/" Then it should pass Scenario: Single feature, pass (b) Given a file named "features/issue159_stream_writer.feature" with: """ Feature: Scenario: When full name is Loïc "Dupont" """ When I run "behave -f plain features/" Then it should pass Scenario: Two features, FAIL (a) Given a file named "features/issue159_stream_writer.feature" with: """ Feature: Scenario: When full name is Loïc "Dupont" """ And a file named "features/issue159_stream_writer_again.feature" with: """ Feature: Scenario: When full name is Loïc "Dupond" """ When I run "behave -f plain features/" Then it should pass Scenario: Two features, FAIL (b) Given a file named "features/issue159_stream_writer.feature" with: """ Feature: Scenario: When firstname is "Loïc" """ And a file named "features/issue159_stream_writer_again.feature" with: """ Feature: Scenario: When firstname is "Loïc" """ When I run "behave -f plain features/" Then it should pass behave-1.2.6/issue.features/issue0162.feature0000644000076600000240000000445113244555737021043 0ustar jensstaff00000000000000@issue Feature: Issue #162 Unnecessary ContextMaskWarnings when assert fails or exception is raised . Behave shows unnecessary ContextMaskWarnings related to: . . * tags . * capture_stdout . * capture_stderr . * log_capture . . if: . . * an assertion fails in a step-definition/step-function . * an exception is raised by a step-definition/step-function . . and an additional scenario follows. . REASON: Context "behave" mode is not restored when an exception is raised. @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step('a step passes') def step_passes(context): pass @step('a step assert fails') def step_assert_fails(context): assert False, "XFAIL-STEP" @step('an exception is raised') def step_raises_exception(context): raise RuntimeError("XFAIL-STEP") """ Scenario: Assertion fails in a step Given a file named "features/example0162_assert_fails.feature" with: """ Feature: Scenario: Given a step passes When a step assert fails Then a step passes Scenario: Given a step passes """ When I run "behave -f plain features/example0162_assert_fails.feature" Then it should fail with: """ 1 scenario passed, 1 failed, 0 skipped 2 steps passed, 1 failed, 1 skipped, 0 undefined """ But the command output should not contain: """ ContextMaskWarning: user code is masking context attribute """ Scenario: Exception is raised in a step Given a file named "features/example0162_exception_raised.feature" with: """ Feature: Scenario: Given a step passes When an exception is raised Then a step passes Scenario: Given a step passes """ When I run "behave -f plain features/example0162_exception_raised.feature" Then it should fail with: """ 1 scenario passed, 1 failed, 0 skipped 2 steps passed, 1 failed, 1 skipped, 0 undefined """ But the command output should not contain: """ ContextMaskWarning: user code is masking context attribute """ behave-1.2.6/issue.features/issue0171.feature0000644000076600000240000000111113244555737021031 0ustar jensstaff00000000000000@issue Feature: Issue #171: Importing step from other step file fails with AmbiguousStep Error . When a step module imports another step module . this should not cause AmbiguousStep errors . due to duplicated registration of the same step functions. . . NOTES: . * In general you should avoid this case (provided as example here). @reuse.colocated_test Scenario: Step module imports other step module Given I use the current directory as working directory When I run "behave -f plain features/step.import_other_step_module.feature" Then it should pass behave-1.2.6/issue.features/issue0172.feature0000644000076600000240000000326113244555737021042 0ustar jensstaff00000000000000@issue Feature: Issue #172 Junit report file name populated incorrectly when running against a feature file @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step('a step passes') def step_passes(context): pass """ And a file named "features/feature_in_root_folder.feature" with: """ Feature: Scenario: Given a step passes """ And a file named "features/subfolder/feature_in_subfolder.feature" with: """ Feature: Scenario: Given a step passes """ Scenario: Running behave for one feature in root folder When I run "behave --junit --junit-directory=test_results features/feature_in_root_folder.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped """ And a file named "test_results/TESTS-feature_in_root_folder.xml" exists Scenario: Running behave for one feature in a subfolder When I run "behave --junit --junit-directory=test_results features/subfolder/feature_in_subfolder.feature" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped """ And a file named "test_results/TESTS-subfolder.feature_in_subfolder.xml" exists Scenario: Running behave for all features When I run "behave --junit --junit-directory=test_results" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped """ And a file named "test_results/TESTS-feature_in_root_folder.xml" exists And a file named "test_results/TESTS-subfolder.feature_in_subfolder.xml" exists behave-1.2.6/issue.features/issue0175.feature0000644000076600000240000000557113244555737021053 0ustar jensstaff00000000000000@issue Feature: Issue #175: Scenario isn't marked as 'failed' when Background step fails Scenario has currently status "skipped" when a background step fails. Expected is that scenario status should be "failed". Ensure that this is the case. RELATED: features/background.feature REUSE: Scenario from there (as copy). . NOTE: . Cucumber has a slightly different behaviour. . When a background step fails the first scenario is marked as failed. . But the remaining scenarios are marked as skipped. . . This can lead to problems when you have sporadic background step failures. . For this reason, behave retries the background steps for each scenario. . . SEE ALSO: . * https://github.com/cucumber/cucumber/blob/master/features/docs/gherkin/background.feature @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/background_steps.py" with: """ from behave import step @step('{word} background step {outcome}') def step_background_step_passes_or_fails(context, word, outcome): if outcome == "fails": assert False, "XFAIL: background step" elif outcome == "passes": pass else: message = "Unexpected outcome=%s. Use: passes, fails" raise RuntimeError(message % outcome) """ And a file named "features/steps/passing_steps.py" with: """ from behave import step @step('{word} step passes') def step_passes(context, word): pass @step('{word} step fails') def step_passes(context, word): assert False, "XFAIL" """ Scenario: Failing Background Step causes all Scenarios to fail Given a file named "features/example.background_step_fails.feature" with: """ Feature: Background: B1 Given a background step passes And a background step fails And another background step passes Scenario: S1 When a step passes Scenario: S2 Then a step passes And another step passes """ When I run "behave -f plain -T features/example.background_step_fails.feature" Then it should fail with: """ 0 scenarios passed, 2 failed, 0 skipped 2 steps passed, 2 failed, 5 skipped, 0 undefined """ And the command output should contain: """ Feature: Background: B1 Scenario: S1 Given a background step passes ... passed And a background step fails ... failed Assertion Failed: XFAIL: background step Scenario: S2 Given a background step passes ... passed And a background step fails ... failed Assertion Failed: XFAIL: background step """ behave-1.2.6/issue.features/issue0177.feature0000644000076600000240000000253113244555737021046 0ustar jensstaff00000000000000@issue Feature: Issue #177: Cannot setup logging_format . DESCPRIPTION: . When the logging_format is set in the behave configuration file . or on command-line, an exception is thrown, because . the ConfigParser tries to replace the placeholders in the format string . with option values in the configuration file (which do not exist). . . SOLUTION: . The format string must be processed as raw value (by the ConfigParser). . . RELATED: . * features/logging.setup_format.feature . * features/logging.setup_level.feature @reuse.colocated_test Scenario: Setup logging_format Given I use the current directory as working directory When I run "behave -f plain features/logging.setup_format.feature" Then it should pass And the command output should not contain: """ Traceback (most recent call last): """ And the command output should not contain: """ ConfigParser.InterpolationMissingOptionError: Bad value """ @reuse.colocated_test Scenario: Setup logging_level Ensure that the problem that was also mentioned, works as expected. Note that this "problem" never existed (hint: missing user knowledge). Given I use the current directory as working directory When I run "behave -f plain features/logging.setup_level.feature" Then it should pass behave-1.2.6/issue.features/issue0181.feature0000644000076600000240000000214313244555737021040 0ustar jensstaff00000000000000@issue Feature: Issue #181: Escape apostrophes in undefined steps snippets . I have noticed that, for the following line in my features file: . . Then I'm redirected to http://www.example.com . . Behave outputs the following: . . @then(u'I'm redirected to http://www.example.com') . def step_impl(context): . assert False Scenario: Given a new working directory And an empty file named "features/steps/steps.py" And a file named "features/issue181_example.feature" with """ Feature: Scenario: Given I'm using an "undefined step" """ When I run "behave -f plain features/issue181_example.feature" Then it should fail with: """ 0 steps passed, 0 failed, 0 skipped, 1 undefined """ And the command output should contain: """ You can implement step definitions for undefined steps with these snippets: @given(u'I\'m using an "undefined step"') def step_impl(context): raise NotImplementedError(u'STEP: Given I\'m using an "undefined step"') """ behave-1.2.6/issue.features/issue0184.feature0000644000076600000240000001016613244555737021047 0ustar jensstaff00000000000000@issue Feature: Issue #184: TypeError when running behave with --include option . Running behave with option '--include' causes fail with following error: . . Traceback (most recent call last): . File "/.../bin/behave", line 8, in . load_entry_point('behave==1.2.3', 'console_scripts', 'behave')() . File "/.../lib/python2.7/site-packages/behave/__main__.py", line 111, in main . ... . File "/.../lib/python2.7/site-packages/behave/runner.py", line 490, in run_with_paths . if not self.config.exclude(filename) ] . File "/.../lib/python2.7/site-packages/behave/configuration.py", line 488, in exclude . if self.include_re and self.include_re.search(filename) is None: . TypeError: expected string or buffer . . RELATED: . * features/runner.select_files_by_regexp.feature . * features/runner.select_files_by_regexp2.feature @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/passing_steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass """ And a file named "features/alice.feature" with: """ Feature: Alice Scenario: A1 Given a step passes """ And a file named "features/bob.feature" with: """ Feature: Bob Scenario: B1 When another step passes Scenario: B2 Then another step passes """ Scenario: Use --include command-line option to select some features When I run "behave -f plain --include='features/a.*\.feature'" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped """ And the command output should contain: """ Feature: Alice """ But the command output should not contain: """ Feature: Bob """ Scenario: Use --include command-line option to select all features When I run "behave -f plain --include='.*\.feature'" Then it should pass with: """ 2 features passed, 0 failed, 0 skipped 3 scenarios passed, 0 failed, 0 skipped """ Scenario: Use --exclude command-line option When I run "behave -f plain --exclude='features/a.*\.feature'" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped """ And the command output should contain: """ Feature: Bob """ But the command output should not contain: """ Feature: Alice """ Scenario: Use --include and --exclude command-line options When I run "behave -f plain --include='.*\.feature' --exclude='features/a.*\.feature'" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 0 skipped """ And the command output should contain: """ Feature: Bob """ But the command output should not contain: """ Feature: Alice """ Scenario: Use --include command-line option with file location When I run "behave -f plain --include='features/a.*\.feature' features/alice.feature:3" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped """ And the command output should contain: """ Feature: Alice """ But the command output should not contain: """ Feature: Bob """ Scenario: Use --exclude command-line option with feature list file Given a file named "selected.txt" with: """ # -- FEATURE-LIST FILE: features/alice.feature:3 features/bob.feature:7 """ When I run "behave -f plain --no-skipped --exclude='.*/a.*\.feature' @selected.txt" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 1 skipped """ And the command output should contain: """ Feature: Bob Scenario: B2 """ But the command output should not contain: """ Feature: Alice """ behave-1.2.6/issue.features/issue0186.feature0000644000076600000240000000076013244555737021050 0ustar jensstaff00000000000000@issue Feature: Issue #186: ScenarioOutline uses wrong return value when if fails ScenarioOutline returns encountered a failure only if the last scenario failed. Failures in earlier examples return the wrong result. Ensure that ScenarioOutline run-logic behaves as expected. @reuse.colocated_test Scenario: Reuse existing test Given I use the current directory as working directory When I run "behave -f plain features/scenario_outline.basics.feature" Then it should pass behave-1.2.6/issue.features/issue0188.feature0000644000076600000240000000365213244555737021055 0ustar jensstaff00000000000000@issue Feature: Issue #188: Better diagnostics if nested step is undefined . Currently if nested step has no match, it's shown like this: . . Assertion Failed: Sub-step failed: When I do strange thign . Substep info: None . . Took some time to find that typo. . The suggestion is to fill substep error_message with at least "No match for step" . so it would become: . . Assertion Failed: Sub-step failed: When I do strange thign . Substep info: No match for step . . IMPLEMENTATION NOTE: . A slightly different output is provided: . . Assertion Failed: UNDEFINED SUB-STEP: When I do strange thign Scenario: Nested steps contain an undefined step Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass @then('a good diagnostic message is shown') def step_good_diagnostic_message_is_shown(context): pass @step('I execute nested steps with an undefined step') def step_passes(context): context.execute_steps(u''' Given another step passes When an undefined, nested step is executed Then third step passes ''') """ And a file named "features/example.execute_nested_undefined_step.feature" with: """ Feature: Scenario: Given a step passes When I execute nested steps with an undefined step Then a good diagnostic message is shown """ When I run "behave -f plain -T features/example.execute_nested_undefined_step.feature" Then it should fail with: """ Scenario: Given a step passes ... passed When I execute nested steps with an undefined step ... failed Assertion Failed: UNDEFINED SUB-STEP: When an undefined, nested step is executed """ behave-1.2.6/issue.features/issue0191.feature0000644000076600000240000001522113244555737021042 0ustar jensstaff00000000000000@issue Feature: Issue #191 Using context.execute_steps() may change context.table/.text . PROBLEM DESCRIPTION: . When you execute nested steps via "context.execute_steps()" in a . step implementation, the following context attributes of the current step . may be modified and may be longer valid: . * context.text (multi-line text) . * context.table @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/common_steps.py" with: """ from behave import given, step @step('{word:w} step passes') def step_passes(context, word): pass @given('I define the following nested steps') def step_define_nested_steps(context): assert context.text is not None, "REQUIRE: text" context.nested_steps = context.text """ And a file named "features/steps/table_steps.py" with: """ from behave import when, then, step @step('I use another table with') def step_use_another_table_with(context): assert context.table, "REQUIRE: table" context.nested_table = context.table @when('I execute the nested steps and use the table') def step_execute_nested_steps_and_use_table(context): assert context.table, "REQUIRE: table" assert context.nested_steps, "REQUIRE: context.nested_steps" context.my_table1 = context.table context.execute_steps(context.nested_steps) context.my_table2 = context.table @then('the original table is restored after the nested steps are executed') def step_table_is_restored(context): assert context.my_table1 is not None assert context.my_table2 is not None assert context.my_table1 is context.my_table2 """ And a file named "features/steps/text_steps.py" with: """ from behave import when, then, step @step('I use another step with text "{text}"') def step_use_another_text_with(context, text): assert context.text is None context.text = text # -- MODIFY: context.text (emulation) @step('I use another text with') def step_use_another_text_with(context): assert context.text is not None, "REQUIRE: text" context.nested_text = context.text @when('I execute the nested steps and use the text') def step_execute_nested_steps_and_use_text(context): assert context.text is not None, "REQUIRE: text" assert context.nested_steps, "REQUIRE: context.nested_steps" context.my_text1 = context.text context.execute_steps(context.nested_steps) context.my_text2 = context.text @then('the original text is restored after the nested steps are executed') def step_text_is_restored(context): assert context.my_text1 is not None assert context.my_text2 is not None assert context.my_text1 is context.my_text2 """ @nested_steps.with_table Scenario: After executing simple nested steps the original table is restored Given a file named "features/example.nested_simple_steps_and_table.feature" with: """ Feature: Scenario: Given I define the following nested steps: ''' Given a step passes When another step passes ''' When I execute the nested steps and use the table: | Name | Age | | Alice | 21 | | Bob | 32 | Then the original table is restored after the nested steps are executed """ When I run "behave -f plain -T features/example.nested_simple_steps_and_table.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ @nested_steps.with_table Scenario: After executing nested steps with a table the original table is restored Given a file named "features/example.nested_steps_and_table.feature" with: """ Feature: Scenario: Given I define the following nested steps: ''' Given I use another table with: | Person | Registered | | Anton | true | | Barby | false | ''' When I execute the nested steps and use the table: | Name | Age | | Charly | 41 | | Doro | 52 | Then the original table is restored after the nested steps are executed """ When I run "behave -f plain -T features/example.nested_steps_and_table.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ @nested_steps.with_text Scenario: After executing simple nested steps the original text is restored Given a file named "features/example.nested_simple_steps_and_text.feature" with: """ Feature: Scenario: Given I define the following nested steps: ''' Given a step passes When another step passes ''' When I execute the nested steps and use the text: ''' Lorem ipsum Ipsum lorem ''' Then the original text is restored after the nested steps are executed """ When I run "behave -f plain -T features/example.nested_simple_steps_and_text.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ @nested_steps.with_text Scenario: After executing nested steps with a text the original text is restored Given a file named "features/example.nested_steps_and_text.feature" with: """ Feature: Scenario: Given I define the following nested steps: ''' Given I use another step with text "Hello Alice" ''' When I execute the nested steps and use the text: ''' Lorem ipsum Ipsum lorem ''' Then the original text is restored after the nested steps are executed """ When I run "behave -f plain -T features/example.nested_steps_and_text.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ behave-1.2.6/issue.features/issue0194.feature0000644000076600000240000001703613244555737021053 0ustar jensstaff00000000000000@issue Feature: Issue #194: Nested steps prevent that original stdout/stderr is restored . When nested steps are used, . the original stdout/stderr streams are not restored after the scenario. . This is caused by starting/stopping capture again while executing nested steps. . . ENSURE THAT: . * Original streams are restored in after_scenario() hook. . * Nested steps should not replace existing capture objects. @setup Scenario: Feature Setup Given a new working directory And a file named "behave.ini" with: """ [behave] log_capture = false logging_level = INFO logging_format = LOG.%(levelname)-8s %(name)s: %(message)s """ And a file named "features/steps/use_behave4cmd_steps.py" with: """ import behave4cmd0.passing_steps import behave4cmd0.failing_steps import behave4cmd0.note_steps """ And a file named "features/steps/stdout_steps.py" with: """ from behave import given, when, then, step, matchers import parse import sys # -- USER-DEFINED DATA TYPES: @parse.with_pattern(r"stdout|stderr") def parse_stream_name(text): assert text in ("stdout", "stderr") return text matchers.register_type(StreamName=parse_stream_name) # -- UTILITY FUNCTIONS: def write_text_to(stream, text, enforce_newline=True): if enforce_newline and not text.endswith("\n"): text += "\n" stream.write(text) # -- STEP DEFINITIONS: @step('I write "{text}" to {stream_name:StreamName}') def step_write_text_to_stdxxx(context, text, stream_name): stream = getattr(sys, stream_name) write_text_to(stream, text) @step('I execute the following steps') def step_execute_steps(context): assert context.text, "REQUIRE: context.text" context.execute_steps(context.text) sys.stdout.write("STDOUT:AFTER-EXECUTE-STEPS\n") sys.stderr.write("STDERR:AFTER-EXECUTE-STEPS\n") """ And a file named "features/environment.py" with: """ import sys stdout_id = 0 stderr_id = 0 def stdout_print(text): sys.__stdout__.write(text + "\n") def inspect_stdout(context, scope, statement): global stdout_id stream_id = id(sys.stdout) stream_class = sys.stdout.__class__.__name__ expected_id = stdout_id if stream_id != expected_id: name = statement.name stdout_print("CHANGED-STDOUT %s:%s: stream.id=%s:%d (was: %d)" % \ (scope, name, stream_class, stream_id, expected_id)) stdout_id = stream_id def inspect_stderr(context, scope, statement): global stderr_id stream_id = id(sys.stderr) stream_class = sys.stderr.__class__.__name__ expected_id = stderr_id if stream_id != expected_id: name = statement.name stdout_print("CHANGED-STDERR %s:%s: stream.id=%s:%d (was: %d)" % \ (scope, name, stream_class, stream_id, expected_id)) stderr_id = stream_id def inspect_stdxxx(context, scope, statement): inspect_stdout(context, scope, statement) inspect_stderr(context, scope, statement) def before_all(context): context.config.setup_logging(filename="behave.log") def before_scenario(context, scenario): inspect_stdxxx(context, "before_scenario", scenario) def after_scenario(context, scenario): inspect_stdxxx(context, "after_scenario", scenario) # -- ENSURE: Original streams are restored. assert sys.stdout is sys.__stdout__ assert sys.stderr is sys.__stderr__ stdout_print("AFTER-SCENARIO %s: Streams are restored." % scenario.name) def before_step(context, step): inspect_stdxxx(context, "before_step", step) def after_step(context, step): inspect_stdxxx(context, "after_step", step) """ Scenario: Stdout capture works with nested steps Given a file named "features/stdout.failing_with_nested.feature" with """ Feature: Scenario: Failing with nested steps Given I write "STDOUT:Hello Alice" to stdout When I write "STDOUT:Hello Bob" to stdout Then I execute the following steps: ''' * I write "STDOUT:Hello nested.Alice" to stdout * I write "STDOUT:Hello nested.Bob" to stdout ''' And I write "STDOUT:Hello Charly" to stdout And another step fails Scenario: Another failing Given I write "STDOUT:Hello Dora" to stdout And another step fails """ When I run "behave -f plain features/stdout.failing_with_nested.feature" Then it should fail with: """ 0 scenarios passed, 2 failed, 0 skipped 5 steps passed, 2 failed, 0 skipped, 0 undefined """ And note that "the summary is only shown if hooks have no errors" And the command output should contain: """ Captured stdout: STDOUT:Hello Alice STDOUT:Hello Bob STDOUT:Hello nested.Alice STDOUT:Hello nested.Bob STDOUT:AFTER-EXECUTE-STEPS STDOUT:Hello Charly """ And the command output should contain: """ Captured stdout: STDOUT:Hello Dora """ And the command output should contain: """ AFTER-SCENARIO Failing with nested steps: Streams are restored. """ And the command output should contain: """ AFTER-SCENARIO Another failing: Streams are restored. """ Scenario: Stderr capture works with nested steps Given a file named "features/stderr.failing_with_nested.feature" with """ Feature: Scenario: Failing with nested steps Given I write "STDERR:Hello Alice" to stderr When I write "STDERR:Hello Bob" to stderr Then I execute the following steps: ''' * I write "STDERR:Hello nested.Alice" to stderr * I write "STDERR:Hello nested.Bob" to stderr ''' And I write "STDERR:Hello Charly" to stderr And another step fails Scenario: Another failing Given I write "STDERR:Hello Dora" to stderr And another step fails """ When I run "behave -f plain features/stderr.failing_with_nested.feature" Then it should fail with: """ 0 scenarios passed, 2 failed, 0 skipped 5 steps passed, 2 failed, 0 skipped, 0 undefined """ And note that "the summary is only shown if hooks have no errors" And the command output should contain: """ Captured stderr: STDERR:Hello Alice STDERR:Hello Bob STDERR:Hello nested.Alice STDERR:Hello nested.Bob STDERR:AFTER-EXECUTE-STEPS STDERR:Hello Charly """ And the command output should contain: """ Captured stderr: STDERR:Hello Dora """ And the command output should contain: """ AFTER-SCENARIO Failing with nested steps: Streams are restored. """ And the command output should contain: """ AFTER-SCENARIO Another failing: Streams are restored. """ behave-1.2.6/issue.features/issue0197.feature0000644000076600000240000000054513244555737021053 0ustar jensstaff00000000000000@issue Feature: Issue #197: Hooks processing should be more exception safe TESTS PROVIDED IN: features/runner.hook_errors.feature @reuse.colocated_test Scenario: Hook processing in case of errors Given I use the current directory as working directory When I run "behave -f plain features/runner.hook_errors.feature" Then it should pass behave-1.2.6/issue.features/issue0216.feature0000644000076600000240000001105313244555737021037 0ustar jensstaff00000000000000@issue Feature: Issue #216: ANSI escape sequences are used while using --wip option . ENSURE THAT: . * Coloring is disabled when --wip option is used. . * In addition, no colouring is used when using --show-skipped option . * Undefined step snippets are not "colored". @setup Scenario: Feature Setup Given a new working directory And a file named "behave.ini" with: """ [behave] default_format = pretty show_skipped = false show_timings = false """ And a file named "features/steps/use_behave4cmd_steps.py" with: """ import behave4cmd0.passing_steps import behave4cmd0.failing_steps import behave4cmd0.note_steps """ And a file named "features/steps/ansi_steps.py" with: """ from behave import step, then from behave4cmd0.command_steps import step_command_output_should_not_contain_text @then(u'the command output should not contain any ANSI escape sequences') def step_command_ouput_should_not_contain_ansi_sequences(context): CSI = u"\x1b[" #< ANSI CONTROL SEQUENCE INTRODUCER (CSI). step_command_output_should_not_contain_text(context, CSI) """ And a file named "features/scenario_with_undefined_steps.feature" with: """ Feature: @wip Scenario: Alice Given a step passes When a step is undefined Then a step passes @foo Scenario: Bob When another step is undefined """ Scenario: When using --wip, coloring is disabled When I run "behave --wip features" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped, 1 untested 1 step passed, 0 failed, 1 skipped, 1 undefined, 1 untested """ And the command output should contain: """ Scenario: Alice Given a step passes ... passed When a step is undefined ... undefined """ But the command output should not contain any ANSI escape sequences And note that "the plain formatter is used as default formatter" Scenario: When using --wip and --show-skipped, coloring is disabled When I run "behave --wip --show-skipped features" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped, 1 untested 1 step passed, 0 failed, 1 skipped, 1 undefined, 1 untested """ And the command output should contain: """ Scenario: Alice Given a step passes ... passed When a step is undefined ... undefined """ But the command output should not contain any ANSI escape sequences And note that "the plain formatter is used as default formatter" Scenario: When using --wip and --format, coloring is disabled When I run "behave --wip -f pretty features" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped, 1 untested 1 step passed, 0 failed, 1 skipped, 1 undefined, 1 untested """ And the command output should contain: """ Feature: # features/scenario_with_undefined_steps.feature:1 @wip Scenario: Alice # features/scenario_with_undefined_steps.feature:4 Given a step passes # ../behave4cmd0/passing_steps.py:23 When a step is undefined # None Then a step passes # None """ But the command output should not contain any ANSI escape sequences And note that "the plain formatter is overridden on command-line" And note that "the coloring mode is disabled" Scenario: When using --wip and --color, coloring is disabled When I run "behave --wip -f pretty --color features" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped, 1 untested 1 step passed, 0 failed, 1 skipped, 1 undefined, 1 untested """ And the command output should contain: """ Feature: # features/scenario_with_undefined_steps.feature:1 @wip Scenario: Alice # features/scenario_with_undefined_steps.feature:4 Given a step passes # ../behave4cmd0/passing_steps.py:23 When a step is undefined # None Then a step passes # None """ But the command output should not contain any ANSI escape sequences And note that "the coloring mode is overridden by the wip mode" behave-1.2.6/issue.features/issue0226.feature0000644000076600000240000000342413244555737021043 0ustar jensstaff00000000000000@issue @unicode Feature: UnicodeDecodeError in tracebacks (when an exception in a step implementation) . Exception with non-ASCII character is raised in a step implementation. . UnicodeDecodeError occurs with: . 'ascii' codec can't decode byte 0x82 in position 11: ordinal not in range(128) . . RELATED: . * features/i18n.unicode_problems.feature @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/steps.py" with: """ # -*- coding: UTF-8 -*- from behave import step import six if six.PY2: chr = unichr @step(u'a step raises an exception with non-ASCII character "{char_code:d}"') def step_raises_exception_with_non_ascii_text(context, char_code): assert 0 <= char_code <= 255, "RANGE-ERROR: char_code=%s" % char_code raise RuntimeError(u"FAIL:"+ chr(char_code) +";") """ Scenario Outline: Syndrome with non-ASCII char (format=) Given a file named "features/syndrome_0226_.feature" with: """ Feature: Scenario: Given a step raises an exception with non-ASCII character "" """ When I run "behave -f features/syndrome_0226_.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped 0 steps passed, 1 failed, 0 skipped, 0 undefined """ And the command output should contain: """ RuntimeError: FAIL:; """ But the command output should not contain "UnicodeDecodeError" Examples: | format | char_code | special_char | comment! | | plain | 162 | ¢ | cent | | pretty | 191 | ¿ | question-mark on-the-head | behave-1.2.6/issue.features/issue0228.feature0000644000076600000240000000240613244555737021044 0ustar jensstaff00000000000000@feature_request @issue Feature: Issue #228: Allow before_scenario to determine whether steps should be run. Allow before_scenario to call mark_skipped() and early out if the current scenario should be skipped. Scenario: Allow before_scenario to skip the current scenario Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step('a step passes') def step_passes(context): pass """ And a file named "features/environment.py" with """ def before_scenario(context, scenario): if scenario.name == 'Skip this scenario': scenario.skip() """ And a file named "features/issue228_example.feature" with """ Feature: Scenario: Skip this scenario Given I'm using an "undefined step" Scenario: Run this scenario Given a step passes """ When I run "behave -f plain features/issue228_example.feature" Then it should pass And the command output should contain: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 1 skipped 1 step passed, 0 failed, 1 skipped, 0 undefined """ behave-1.2.6/issue.features/issue0230.feature0000644000076600000240000000267213244555737021042 0ustar jensstaff00000000000000@issue @unicode Feature: Assert with non-ASCII char causes UnicodeDecodeError . Failing assert with non-ASCII character in its message . causes UnicodeDecodeError and silent exit in Python2. . . RELATED: . * features/i18n.unicode_problems.feature @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step(u'a step fails with non-ASCII character "{char_code:d}"') def step_fails_with_non_ascii_text(context, char_code): assert 0 <= char_code <= 255, "RANGE-ERROR: char_code=%s" % char_code assert False, "FAIL:"+ chr(char_code) +";" """ Scenario Outline: Syndrome with non-ASCII char (format=) Given a file named "features/syndrome_0230_.feature" with: """ Feature: Scenario: Given a step fails with non-ASCII character "" """ When I run "behave -f features/syndrome_0230_.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped 0 steps passed, 1 failed, 0 skipped, 0 undefined """ And the command output should not contain "UnicodeDecodeError" But the command output should contain: """ Assertion Failed: FAIL: """ Examples: | format | char_code | | plain | 130 | | pretty | 190 | behave-1.2.6/issue.features/issue0231.feature0000644000076600000240000000453013244555737021036 0ustar jensstaff00000000000000@issue @not_reproducible Feature: Issue #231: Display the output of the last print command . The output of the last print command in a step is not displayed . in the behave output (at least with standard pretty formatter), . unless the string to print ends with newline ('\n'). . . ANALYSIS: NOT-REPRODUCIBLE . Checked print function and stdout without newline. . Both show the expected capture stdout output when the step fails. @setup Scenario: Feature Setup Given a new working directory And a file named "features/syndrome1.feature" with: """ Feature: Scenario: Alice Given a step passes When a step passes Then I write "ALICE was HERE" without newline to stdout and fail """ And a file named "features/syndrome2.feature" with: """ Feature: Scenario: Bob Given a step passes Then I print "BOB was HERE" without newline and fail """ And a file named "features/steps/steps.py" with: """ from __future__ import print_function from behave import step import sys @step('{word:w} step passes') def step_passes(context, word): pass @step('I write "{message}" without newline to stdout and fail') def step_write_without_newline_and_fail(context, message): sys.stdout.write(message) assert False, "FAIL: "+ message @step('I print "{message}" without newline and fail') def step_print_without_newline_and_fail(context, message): print(message, end="") assert False, "FAIL: "+ message """ Scenario: Write to stdout without newline When I run "behave -f pretty -c -T features/syndrome1.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped 2 steps passed, 1 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Captured stdout: ALICE was HERE """ Scenario: Use print function without newline When I run "behave -f pretty -c -T features/syndrome2.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped 1 step passed, 1 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Captured stdout: BOB was HERE """ behave-1.2.6/issue.features/issue0238.feature0000644000076600000240000000327113244555737021046 0ustar jensstaff00000000000000@issue @feature_request Feature: Issue #238 Skip a Scenario in a Scenario Outline Scenario: Given a new working directory And a file named "features/issue238_1.feature" with: """ Feature: Testing Scenario skipping Scenario Outline: Given a set of "" When I ensure that "" != invalid Then it should pass Examples: | thing | | valid | | invalid | """ And a file named "features/steps/steps.py" with: """ @given('a set of "{thing}"') def step_check_thing_assumption(ctx, thing): if thing == "invalid": ctx.scenario.skip("ASSUMPTION-MISMATCH: INVALID-THING") @when('I ensure that "{thing}" != invalid') def step_ensure_that_thing_is_valid(ctx, thing): assert thing != "invalid" @then('it should pass') def step_passes(context): pass """ When I run "behave -f plain --show-skipped --no-timings" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 1 skipped 3 steps passed, 0 failed, 3 skipped, 0 undefined """ And the command output should contain: """ Scenario Outline: -- @1.1 Given a set of "valid" ... passed When I ensure that "valid" != invalid ... passed Then it should pass ... passed Scenario Outline: -- @1.2 Given a set of "invalid" ... skipped """ But note that "the step that skipped the scenario is also marked as skipped"behave-1.2.6/issue.features/issue0251.feature0000644000076600000240000000066013244555737021040 0ustar jensstaff00000000000000@issue @unicode Feature: UnicodeDecodeError in model.Step (when step fails) . Output of failing step contains non-ASCII characters. . . RELATED: . * features/i18n.unicode_problems.feature @reuse.colocated_test Scenario: Given I use the current directory as working directory When I run "behave -f plain --tags=@setup,@problematic.output features/i18n.unicode_problems.feature" Then it should pass behave-1.2.6/issue.features/issue0280.feature0000644000076600000240000000763513244555737021053 0ustar jensstaff00000000000000@issue Feature: Issue #280: AmbiguousStep error with similar step definitions and use_step_matcher("re") . While using the RegexMatcher with steps that have the same step prefix . an AmbiguousStep exception occurs if the shorter step is registered first. . . EXAMPLE: . Two steps with definitions that have the same step prefix: . . * I do something . * I do something more . . cause an AmbiguousStep error to be thrown: . . behave.step_registry.AmbiguousStep: @when('I do something more') has already . been defined in existing step @when('I do something') at ... . . SOLUTION: Add regex begin-/end-markers around the step text( '^'+ step + '$') . NOTE: Only RegexMatcher is affected. @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/calculator_steps1.py" with: """ from behave import given, then from hamcrest import assert_that, equal_to class SimpleCalculator(object): def __init__(self): self.result = 0 def add(self, value): self.result += value @given(u'a calculator') def step_impl(context): context.calculator = SimpleCalculator() @then(u'the calculator result is "{expected_result:d}"') def step_impl(context, expected_result): assert_that(context.calculator.result, equal_to(expected_result)) """ Scenario: Ensure RegexMatcher is not ordering sensitive Given a file named "features/syndrome_280_1.feature" with: """ Feature: Scenario: Use both steps Given I do something When I do something more """ And a file named "features/steps/simple_steps.py" with: """ from behave import step, use_step_matcher use_step_matcher("re") # -- ORDERING SENSITIVE PART: @step(u'I do something') def step_impl(context): pass @step(u'I do something more') def step_impl(context): pass """ When I run "behave -f plain features/syndrome_280_1.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped """ But the command output should not contain "AmbiguousStep:" And the command output should not contain: """ AmbiguousStep: @step('I do something more') has already been defined in existing step @step('I do something') at features/steps/simple_steps.py:5' """ Scenario: Ensure correct step implementation is selected Given a file named "features/syndrome_280_2.feature" with: """ Feature: Steps with same step prefix -- Use correct step implementation Scenario: Use shorter step Given a calculator When I add "2" to it And I add "3" to it Then the calculator result is "5" Scenario: Use longer step Given a calculator When I add "2" to it twice And I add "3" to it Then the calculator result is "7" """ And a file named "features/steps/calculator_steps2.py" with: """ from behave import when, use_step_matcher use_step_matcher("re") # -- ORDERING SENSITIVE PART: @when(u'I add "(?P\d+)" to it') def step_impl(context, value): number_value = int(value) context.calculator.add(number_value) @when(u'I add "(?P\d+)" to it twice') def step_impl(context, value): number_value = int(value) context.calculator.add(number_value) context.calculator.add(number_value) """ When I run "behave -f pretty --no-color features/syndrome_280_2.feature" Then it should pass with: """ When I add "2" to it twice # features/steps/calculator_steps2.py:10 And I add "3" to it # features/steps/calculator_steps2.py:5 """ But the command output should not contain "AmbiguousStep" behave-1.2.6/issue.features/issue0288.feature0000644000076600000240000000537313244555737021060 0ustar jensstaff00000000000000@issue Feature: Issue #288 -- Use print function instead print statement in environment/steps files . Loaded files have future flags of importing module "behave.runner". . This should be removed. . . AFFECTED FILES: . * features/environment.py . * features/steps/*.py . . AFFECTED FUTURE FLAGS: . * print_function . * absolute_import . * ... @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/reuse_steps.py" with: """ from behave4cmd0 import passing_steps """ And a file named "features/passing.feature" with: """ Feature: Scenario: Given a step passes """ @preferred Scenario: Use print function with future-statement in steps/environment (PY2, PY3) Given a file named "features/steps/my_steps.py" with: """ from __future__ import print_function print("Hello step") """ And a file named "features/environment.py" with: """ from __future__ import print_function print("Hello environment") """ When I run "behave -f plain features/passing.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ And the command output should not contain: """ Traceback (most recent call last): """ @use.with_python2=true Scenario: Use python2 print keyword in steps/environment Given a file named "features/steps/my_steps.py" with: """ print "Hello step" """ And a file named "features/environment.py" with: """ print "Hello step" """ When I run "behave -f plain features/passing.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ And the command output should not contain "SyntaxError" And the command output should not contain: """ Traceback (most recent call last): """ @use.with_python3=true Scenario: Use print function without future-statement in steps/environment (PY3) Given a file named "features/steps/my_steps.py" with: """ print("Hello step") """ And a file named "features/environment.py" with: """ print("Hello environment") """ When I run "behave -f plain features/passing.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ And the command output should not contain: """ Traceback (most recent call last): """ behave-1.2.6/issue.features/issue0300.feature0000644000076600000240000000352713244555737021040 0ustar jensstaff00000000000000@issue Feature: Issue #300 -- UnicodeDecodeError when read steps.py . My system is running on Chinese GBK character set. . But you know we make our files as utf-8 format generally, and so do I. . I set my step file api1_steps.py as utf-8, and entered some Chinese characters in. . I run "behave", but I got UnicodeDecodeError, just like this: . . File "D:\workspace\env_110\lib\site-packages\behave\runner.py", line 304, in exec_file . code = compile(f.read(), filename2, 'exec') . UnicodeDecodeError: 'gbk' codec can't decode byte 0xad in position 510: illegal multibyte sequence | OPEN ISSUES: | * Acceptable/supported Python source file encodings | * config: Add encoding option for feature files, step files or both. Scenario: Cause BAD-ASCII Syndrome in steps file Given a new working directory And a file named "features/steps/bad_ascii_steps.py" with: """ ''' BAD ASCII CASES (requires UTF-8/latin1 support): * Café * Ärgernis ist überall ''' # COMMENT: Ärgernis ist überall """ And a file named "features/steps/steps.py" with: """ from __future__ import unicode_literals from behave import step @step('{word:w} step passes') def step_passes(context, word): pass """ And a file named "features/e1.feature" with: """ Feature: Scenario: Alice Given a step passes Then another step passes """ When I run "behave -f plain features/e1.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ behave-1.2.6/issue.features/issue0302.feature0000644000076600000240000000562513244555737021043 0ustar jensstaff00000000000000@issue Feature: Issue #302: Cannot escape pipe in table field value Support escaped-pipe characters in cells of a table. @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import then, step from hamcrest import assert_that, equal_to @step(u'I use table data with') def step_use_table_data_with_table(context): assert context.table, "REQUIRE: context.table is provided" context.table.require_columns(["name", "value"]) context.table_data = {} for row in context.table.rows: name = row["name"] value = row["value"] context.table_data[name] = value @then(u'table data "{name}" is "{value}"') def step_table_data_name_is_value(context, name, value): table_data = context.table_data actual_value = table_data[name] assert_that(actual_value, equal_to(value)) """ Scenario: Escaped-pipes in table cell Given a file named "features/issue0302_example.feature" with: """ Feature: Scenario: Use a table Given I use table data with: | name | value | | alice | one\|two\|three\|four | Then table data "alice" is "one|two|three|four" """ When I run "behave -f plain features/issue0302_example.feature" Then it should pass And the command output should not contain: """ Traceback (most recent call last): """ Scenario: Leading escaped-pipe in table cell Given a file named "features/issue0302_example2.feature" with: """ Feature: Scenario: Use a table Given I use table data with: | name | value | | bob |\|one | Then table data "bob" is "|one" """ When I run "behave -f plain features/issue0302_example2.feature" Then it should pass Scenario: Trailing escaped-pipe in table cell Given a file named "features/issue0302_example3.feature" with: """ Feature: Scenario: Use a table Given I use table data with: | name | value | | charly | one\|| Then table data "charly" is "one|" """ When I run "behave -f plain features/issue0302_example3.feature" Then it should pass Scenario: Double escaped-pipe in table cell Given a file named "features/issue0302_example4.feature" with: """ Feature: Scenario: Use a table Given I use table data with: | name | value | | doro | one\\|two | Then table data "doro" is "one\|two" """ When I run "behave -f plain features/issue0302_example4.feature" Then it should pass behave-1.2.6/issue.features/issue0309.feature0000644000076600000240000000340213244555737021041 0ustar jensstaff00000000000000@issue Feature: Issue #309 -- behave --lang-list fails on Python3 . When I type "behave --lang-list", the following error occurs on Python 3.4: . . Traceback (most recent call last): . File "/usr/local/bin/behave", line 11, in . sys.exit(main()) . File "/usr/local/lib/python3.4/dist-packages/behave/__main__.py", line 65, in main . iso_codes.sort() . AttributeError: 'dict_keys' object has no attribute 'sort' . . ADDITIONAL PROBLEM: On Python2 you may get an UnicodeDecodeError . Traceback (most recent call last): . ... . Languages available: . File "/Users/jens/se/behave_main.fix/behave/__main__.py", line 70, in main . print(u'%s: %s / %s' % (iso_code, native, name)) . UnicodeEncodeError: 'ascii' codec can't encode characters in position 4-10: ordinal not in range(128) . . RELATED FEATURES: . * features/cmdline.lang_list.feature . @problematic @not.with_os=win32 Scenario: Use behave --lang-list When I run "behave --lang-list" Then it should pass with: """ Languages available: ar: العربية / Arabic bg: български / Bulgarian ca: català / Catalan cs: Česky / Czech cy-GB: Cymraeg / Welsh da: dansk / Danish de: Deutsch / German en: English / English """ And the command output should contain: """ sv: Svenska / Swedish tr: Türkçe / Turkish uk: Українська / Ukrainian uz: Узбекча / Uzbek vi: Tiếng Việt / Vietnamese zh-CN: 简体中文 / Chinese simplified zh-TW: 繁體中文 / Chinese traditional """ But the command output should not contain "Traceback" behave-1.2.6/issue.features/issue0330.feature0000644000076600000240000000775213244555737021047 0ustar jensstaff00000000000000@issue Feature: Issue #330: Skipped scenarios are included in junit reports when --no-skipped is used @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass """ And a file named "features/alice.feature" with: """ @tag1 Feature: Alice Scenario: Alice1 Given a step passes """ And a file named "features/bob.feature" with: """ Feature: Bob Scenario: Bob1 Given another step passes """ And a file named "features/charly.feature" with: """ Feature: Charly @tag1 Scenario: Charly1 Given some step passes Scenario: Charly2 When another step passes """ And a file named "alice_and_bob.featureset" with: """ features/alice.feature features/bob.feature """ And a file named "behave.ini" with: """ [behave] default_format = plain junit_directory = test_results [behave.userdata] behave.reporter.junit.show_timestamp = false behave.reporter.junit.show_hostname = false """ Scenario: Junit report for skipped feature is not created with --no-skipped When I run "behave --junit -t @tag1 --no-skipped @alice_and_bob.featureset" Then it should pass with: """ 1 feature passed, 0 failed, 1 skipped """ And a file named "test_results/TESTS-alice.xml" exists And a file named "test_results/TESTS-bob.xml" does not exist And the command output should contain: """ Feature: Alice Scenario: Alice1 Given a step passes ... passed """ But the command output should not contain "Feature: Bob" And note that "bob.feature is skipped" Scenario: Junit report for skipped feature is created with --show-skipped When I run "behave --junit -t @tag1 --show-skipped @alice_and_bob.featureset" Then it should pass with: """ 1 feature passed, 0 failed, 1 skipped """ And a file named "test_results/TESTS-alice.xml" exists And a file named "test_results/TESTS-bob.xml" exists And the file "test_results/TESTS-bob.xml" should contain: """ """ Scenario: Junit report for skipped scenario is neither shown nor counted with --no-skipped When I run "behave --junit -t @tag1 --no-skipped" Then it should pass with: """ 2 features passed, 0 failed, 1 skipped 2 scenarios passed, 0 failed, 2 skipped """ And a file named "test_results/TESTS-alice.xml" exists And a file named "test_results/TESTS-charly.xml" exists And the file "test_results/TESTS-charly.xml" should contain: """ @not.with_python.implementation= Scenario Outline: Use step file with Given a file named "features/steps/my_steps.py" and encoding="" with: """ # -*- coding: -*- ''' . ''' from behave import step @step(u'a special step') def step_impl(context): pass """ When I run "behave -f plain features/passing.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped 2 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Feature: Scenario: Given a step passes ... passed Then a special step ... passed """ And the command output should not contain: """ SyntaxError: invalid character in identifier """ Examples: | encoding | text | case | exclude_pyimpl | Comment | | UTF-8-sig | Ärgernis ist überall | UTF-8 encoding and BOM | pypy | pypy SyntaxError: UTF-8 with BOM | | latin1 | Café | Latin1 encoding | none | | | iso-8859-1 | Ärgernis ist überall | iso-8859-1 encoding | none | Alias for latin1 | | cp1252 | Ärgernis ist überall | cp1252 encoding | none | Windows: Western Europe | | cp1251 | Привет! (=hello) | cp1251 encoding (Russia)| none | Windows: Russia | | cp866 | Привет! (=hello) | cp688 encoding (Russia) | none | IBM866: Russia | | euc_jp | こんにちは (=hello) | euc_jp encoding (Japan) | none | Japanese | | gbk | 您好 (=hello) | gbk encoding (China) | none | Unified Chinese | | gb2312 | 您好 (=hello) | gb2312 encoding (China) | none | Simplified Chinese | # -- DISABLE EXAMPLE ROW: Pypy 4.x has SyntaxError with UTF-8 BOM # | UTF-8-sig | Ärgernis ist überall | UTF-8 encoding and BOM | | behave-1.2.6/issue.features/issue0383.feature0000644000076600000240000000535613244555737021055 0ustar jensstaff00000000000000@issue Feature: Issue #383 -- Handle (custom) Type parsing errors better . Custom type parsing errors that occur during step matching . lead currently to abortion of the test run. . In addition, the actual reason what occured is very low-level. . . DESIRED: . * Protect test runner/execution logic . * Improve handling of these kind of errors . * Provide better diagnostics . * Continue to run tests (if possible) . . NOTE: . This kind of problem is often caused by regular expressions that . are not specific enough for the type conversion (LAZY-REGEXP DESIGN). . Therefore, the problem occurs during type conversion/parsing phase . and not in the initial step detection/matching phase. . . RELATED: features/step_param.custom_types.feature Scenario: Type conversion fails Given a new working directory And a file named "features/steps/bad_type_converter_steps.py" with: """ from behave import step, register_type import parse @parse.with_pattern(r".*") # -- NOTE: Wildcard pattern, accepts anything. def parse_fails(text): raise ValueError(text) register_type(BadType=parse_fails) @step('a param with "BadType:{value:BadType}"') def step_param_with_badtype_value(context, value): assert False, "SHOULD_NEVER_COME_HERE: BadType converter raises error." """ And a file named "features/steps/reused_steps.py" with: """ from behave4cmd0 import passing_steps """ And a file named "behave.ini" with: """ [behave] show_timings = false """ And a file named "features/example.type_conversion_fails.feature" with: """ Feature: Type Conversion Fails Scenario: BadType raises ValueError during type conversion Given a param with "BadType:BAD_VALUE" Scenario: Ensure other scenarios are executed Then another step passes """ When I run "behave -f plain features/example.type_conversion_fails.feature" Then it should fail with: """ 1 scenario passed, 1 failed, 0 skipped 1 step passed, 1 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Scenario: BadType raises ValueError during type conversion Given a param with "BadType:BAD_VALUE" ... failed Traceback (most recent call last): """ And the command output should contain: """ File "features/steps/bad_type_converter_steps.py", line 6, in parse_fails raise ValueError(text) """ And the command output should contain "ValueError: BAD_VALUE" And the command output should contain "StepParseError: BAD_VALUE" behave-1.2.6/issue.features/issue0384.feature0000644000076600000240000000751613244555737021056 0ustar jensstaff00000000000000@issue Feature: Issue #384 -- Active Tags fail with ScenarioOutline . ScenarioOutline can currently not be used with active-tag(s). . REASON: Template mechanism transforms active-tag into invalid active-tag per example row. . . RELATED: features/tag.active_tags.feature Background: Given a new working directory And a file named "features/steps/pass_steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass # -- REUSE: Step definitions. from behave4cmd0 import note_steps """ And a file named "features/environment.py" with: """ from behave.tag_matcher import ActiveTagMatcher, setup_active_tag_values import sys # -- ACTIVE TAG SUPPORT: @use.with_{category}={value}, ... active_tag_value_provider = { "browser": "chrome", "webserver": "apache", } active_tag_matcher = ActiveTagMatcher(active_tag_value_provider) def before_all(context): setup_active_tag_values(active_tag_value_provider, context.config.userdata) def before_scenario(context, scenario): if active_tag_matcher.should_exclude_with(scenario.effective_tags): sys.stdout.write("ACTIVE-TAG DISABLED: Scenario %s\n" % scenario.name) scenario.skip(active_tag_matcher.exclude_reason) """ And a file named "behave.ini" with: """ [behave] default_format = pretty show_timings = no show_skipped = no color = no """ And a file named "features/outline.active_tags.feature" with: """ Feature: @use.with_browser=chrome Scenario Outline: Alice -- , Given a step passes But note that " can speak " Examples: | name | language | | Anna | German | | Arabella | English | """ Scenario: ScenarioOutline with enabled active-tag is executed When I run "behave -D browser=chrome features/outline.active_tags.feature" Then it should pass with: """ 2 scenarios passed, 0 failed, 0 skipped 4 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ @use.with_browser=chrome Scenario Outline: Alice -- Anna, German -- @1.1 # features/outline.active_tags.feature:10 Given a step passes # features/steps/pass_steps.py:3 But note that "Anna can speak German" # ../behave4cmd0/note_steps.py:15 @use.with_browser=chrome Scenario Outline: Alice -- Arabella, English -- @1.2 # features/outline.active_tags.feature:11 Given a step passes # features/steps/pass_steps.py:3 But note that "Arabella can speak English" # ../behave4cmd0/note_steps.py:15 """ And the command output should not contain "ACTIVE-TAG DISABLED: Scenario Alice" But note that "we check now for the specific syndrome" And the command output should not contain "@use.with_browserchrome" But the command output should contain "@use.with_browser=chrome" Scenario: ScenarioOutline with disabled active-tag is skipped When I run "behave -D browser=other features/outline.active_tags.feature" Then it should pass with: """ 0 scenarios passed, 0 failed, 2 skipped 0 steps passed, 0 failed, 4 skipped, 0 undefined """ And the command output should contain: """ ACTIVE-TAG DISABLED: Scenario Alice -- Anna, German -- @1.1 ACTIVE-TAG DISABLED: Scenario Alice -- Arabella, English -- @1.2 """ behave-1.2.6/issue.features/issue0385.feature0000644000076600000240000000630313244555737021050 0ustar jensstaff00000000000000@issue @change_request Feature: Issue #385 -- before_scenario called too late . RATIONALE: . Due to: . . * skip scenario/feature support in before_scenario (and before_feature) hooks . * active-tags . . formatters are now called to early (for before feature/scenario functionality). . Formatters should be called after the before-hooks are processed. . . NOTES: . * Test uses show_skipped=false to ensure that at least the . scenario/feature title is shown with plain formatter. @setup Scenario: Given a new working directory And a file named "features/steps/pass_steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass """ And a file named "features/environment.py" with: """ from __future__ import print_function def before_feature(context, feature): if "skip" in feature.tags: print("SKIP-FEATURE: %s" % feature.name) feature.mark_skipped() def before_scenario(context, scenario): if "skip" in scenario.tags: print("SKIP-SCENARIO: %s" % scenario.name) scenario.mark_skipped() """ And a file named "behave.ini" with: """ [behave] show_skipped = false """ Scenario: Formatter is not called with skip in before_scenario hook Given a file named "features/alice.feature" with: """ Feature: Alice @skip Scenario: Alice and Bob Given a step passes Scenario: Alice in China When another step passes """ When I run "behave -f plain features/alice.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 1 skipped """ And the command output should contain: """ SKIP-SCENARIO: Alice and Bob Scenario: Alice in China When another step passes ... passed """ But the command output should not contain: """ Scenario: Alice and Bob SKIP-FEATURE: Alice and Bob """ And the command output should not contain "Scenario: Alice and Bob" Scenario: Formatter is not called with skip in before_feature hook Given a file named "features/bob.feature" with: """ @skip Feature: Bob Scenario: Bob and Alice Given a step passes Scenario: Bob in China When another step passes """ When I run "behave -f plain features/bob.feature" Then it should pass with: """ SKIP-FEATURE: Bob 0 features passed, 0 failed, 1 skipped 0 scenarios passed, 0 failed, 2 skipped """ But the command output should not contain: """ Feature: Bob SKIP-FEATURE: Bob """ And the command output should not contain "Feature: Bob" And the command output should not contain "Scenario: Bob and Alice" And the command output should not contain "Scenario: Bob in China" And note that "all scenarios of the feature are also skipped" behave-1.2.6/issue.features/issue0424.feature0000644000076600000240000000427413244555737021047 0ustar jensstaff00000000000000@issue @unicode Feature: Issue #424 -- Unicode output problem when fails in nested steps . HINTS: . * Python step file should have encoding line (# -*- coding: ... -*-) . * Assert failure message should use unicode-string instead of byte-string Scenario: Given a new working directory And a file named "features/steps/pass_steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass """ And a file named "features/steps/steps.py" with: """ # -*- coding: UTF-8 -*- # NOTE: Python2 requires encoding to decode special chars correctly. from behave import step @step('I press the big red button') def step_press_red_button(context): assert False, u"Ungültiger Wert" # HINT: Special chars require Unicode. @step('I call the nested step with the "red button"') def step_press_red_button(context): context.execute_steps(u'When I press the big red button') """ And a file named "behave.ini" with: """ [behave] show_timings = false """ And a file named "features/alice.feature" with: """ Feature: Scenario: Use step directly When I press the big red button Scenario: Use nested step Given another step passes When I call the nested step with the "red button" """ When I run "behave -f plain features/alice.feature" Then it should fail with: """ 0 scenarios passed, 2 failed, 0 skipped 1 step passed, 2 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Scenario: Use step directly When I press the big red button ... failed Assertion Failed: Ungültiger Wert Scenario: Use nested step Given another step passes ... passed When I call the nested step with the "red button" ... failed Assertion Failed: FAILED SUB-STEP: When I press the big red button Substep info: Assertion Failed: Ungültiger Wert """ behave-1.2.6/issue.features/issue0446.feature0000644000076600000240000000772513244555737021057 0ustar jensstaff00000000000000@issue @junit Feature: Issue #446 -- Support scenario hook-errors with JUnitReporter . Currently, when a hook error occurs in: . . * before_scenario() . * after_scenario() . . a sanity check in the JUnitReporter prevents sane JUnit XML output. @setup Scenario: Skip scenario without steps Given a new working directory And a file named "features/steps/pass_steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass """ And a file named "features/before_scenario_failure.feature" with: """ Feature: Alice @hook_failure.before_scenario Scenario: A1 Given a step passes """ And a file named "features/after_scenario_failure.feature" with: """ Feature: Bob @hook_failure.after_scenario Scenario: B1 Given another step passes """ And a file named "features/environment.py" with: """ def cause_hook_failure(): raise RuntimeError("OOPS") def before_scenario(context, scenario): if "hook_failure.before_scenario" in scenario.tags: cause_hook_failure() def after_scenario(context, scenario): if "hook_failure.after_scenario" in scenario.tags: cause_hook_failure() """ And a file named "behave.ini" with: """ [behave] show_timings = false [behave.userdata] behave.reporter.junit.show_timestamp = False behave.reporter.junit.show_hostname = False """ Scenario: Hook error in before_scenario() When I run "behave -f plain --junit features/before_scenario_failure.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped """ And the command output should contain: """ HOOK-ERROR in before_scenario: RuntimeError: OOPS """ And the file "reports/TESTS-before_scenario_failure.xml" should contain: """ """ And the file "reports/TESTS-before_scenario_failure.xml" should contain: """ File "features/environment.py", line 6, in before_scenario cause_hook_failure() File "features/environment.py", line 2, in cause_hook_failure raise RuntimeError("OOPS") """ And note that "the traceback is contained in the XML element " Scenario: Hook error in after_scenario() When I run "behave -f plain --junit features/after_scenario_failure.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped """ And the command output should contain: """ Scenario: B1 Given another step passes ... passed HOOK-ERROR in after_scenario: RuntimeError: OOPS """ And the file "reports/TESTS-after_scenario_failure.xml" should contain: """ """ And the file "reports/TESTS-after_scenario_failure.xml" should contain: """ File "features/environment.py", line 10, in after_scenario cause_hook_failure() File "features/environment.py", line 2, in cause_hook_failure raise RuntimeError("OOPS") """ And note that "the traceback is contained in the XML element " behave-1.2.6/issue.features/issue0449.feature0000644000076600000240000000237213244555737021053 0ustar jensstaff00000000000000@issue @unicode Feature: Issue #449 -- Unicode output problem when fails with Russion text . Either Exception text (as summary) or traceback python line shows . special characters correctly. Scenario: Given a new working directory And a file named "features/steps/problematic_steps.py" with: """ # -*- coding: UTF-8 -*- # NOTE: Python2 requires encoding to decode special chars correctly. from behave import step from hamcrest.core import assert_that, equal_to @step("Russian text") def step_russian_text(stop): assert_that(False, equal_to(True), u"Всё очень плохо") # cyrillic """ And a file named "behave.ini" with: """ [behave] show_timings = false """ And a file named "features/syndrome.feature" with: """ Feature: Scenario: Given Russian text """ When I run "behave -f plain features/syndrome.feature" Then it should fail with: """ Scenario: Given Russian text ... failed Assertion Failed: Всё очень плохо """ But the command output should not contain: """ Assertion Failed: 'ascii' codec can't encode characters in position """ behave-1.2.6/issue.features/issue0453.feature0000644000076600000240000000260413244555737021044 0ustar jensstaff00000000000000@issue @unicode Feature: Issue #453 -- Unicode output problem when Exception is raised in step . Either Exception text (as summary) or traceback python line shows . special characters incorrectly. . . Result (of failed step): . File "features/steps/steps.py", line 8, in foo . raise Exception(u"по Ñ�Ñ�Ñ�Ñ�ки") <-- This is not . Exception: по русски <-- This is OK Scenario: Given a new working directory And a file named "features/steps/problematic_steps.py" with: """ # -*- coding: UTF-8 -*- from behave import step @step(u'an exception with special chars is raised') def step_exception_raised(context): raise Exception(u"по русски") """ And a file named "features/syndrome.feature" with: """ Feature: Scenario: Given an exception with special chars is raised """ When I run "behave -f plain features/syndrome.feature" Then it should fail with: """ Scenario: Given an exception with special chars is raised ... failed """ And the command output should contain: """ File "features/steps/problematic_steps.py", line 6, in step_exception_raised raise Exception(u"по русски") Exception: по русски """ behave-1.2.6/issue.features/issue0457.feature0000644000076600000240000000374013244555737021052 0ustar jensstaff00000000000000@not_reproducible @issue Feature: Issue #457 -- Double-quotes in error messages of JUnit XML reports STATUS: Problem is currently not reproducible. XML attributes are escaped correctly even when double-quotes are used. @setup Scenario: Feature Setup Given a new working directory And a file named "features/steps/fail_steps.py" with: """ from behave import step @step('{word:w} step fails with message') def step_fails(context, word): assert context.text assert False, "FAILED: "+ context.text @step('{word:w} step fails with error and message') def step_fails2(context, word): assert context.text raise RuntimeError(context.text) """ Scenario: Use failing assertation in a JUnit XML report Given a file named "features/fails1.feature" with: """ Feature: Scenario: Alice Given a step fails with message: ''' My name is "Alice" ''' """ When I run "behave --junit features/fails1.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped """ And the file "reports/TESTS-fails1.xml" should contain: """ I am ''' """ When I run "behave --junit features/fails2.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped """ And the file "reports/TESTS-fails2.xml" should contain: """ . load_entry_point('behave==1.2.5', 'console_scripts', 'behave')() . File "/usr/lib/python2.7/site-packages/behave/__main__.py", line 111, in main . print(u"ParseError: %s" % e) . UnicodeEncodeError: 'ascii' codec can't encode characters in position 92-103: ordinal not in range(128) . . ANALYSIS: . ParseError indicates that the problem occured while parsing the . feature file. Feature file encoding is assumed @not.with_ci=appveyor Scenario: Given a new working directory And a file named "features/steps/pass_steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass """ And a file named "features/steps/steps.py" with: """ # -*- coding: latin-1 -*- from __future__ import print_function from behave import step @step('I use {special_unicode_char:w} in text') def step_use_ZBSP_with_text(context, special_unicode_char): assert context.text print(u"TEXT: %s" % context.text) """ And a file named "behave.ini" with: """ [behave] show_timings = false """ And a file named "features/syndrome.feature" with: """ Feature: Scenario Outline: Use special unicode char () Given I use ZBSP in text: ''' HERE we use a ==><== SPECIAL UNICODE CHAR. ''' Examples: | special_unicode_char | comment | | ⌘ | MACOS command key symbol | | © | copyright sign | | € | Euro sign (currency) | | xxx XXX | special space | """ When I run "behave -f plain features/syndrome.feature" Then it should pass with: """ 4 scenarios passed, 0 failed, 0 skipped """ And the command output should contain: ''' Scenario Outline: Use special unicode char (MACOS command key symbol) -- @1.1 Given I use ZBSP in text ... passed """ HERE we use a ==>⌘<== SPECIAL UNICODE CHAR. """ Scenario Outline: Use special unicode char (copyright sign) -- @1.2 Given I use ZBSP in text ... passed """ HERE we use a ==>©<== SPECIAL UNICODE CHAR. """ Scenario Outline: Use special unicode char (Euro sign (currency)) -- @1.3 Given I use ZBSP in text ... passed """ HERE we use a ==>€<== SPECIAL UNICODE CHAR. """ Scenario Outline: Use special unicode char (special space) -- @1.4 Given I use ZBSP in text ... passed """ HERE we use a ==>xxx XXX<== SPECIAL UNICODE CHAR. """ ''' behave-1.2.6/issue.features/issue0506.feature0000644000076600000240000000503613244555737021045 0ustar jensstaff00000000000000@issue @user_error @python2.problem Feature: Issue #506 -- Behave stops on error . ANALYSIS: . Using exception.__cause__ = original_exception causes problems in Python2 . When you use the Python3 chained-exception mechanism, . you should better ensure that the "original_exception.__traceback__" attribute . exists. Otherwise, a new exception (missing attribute) is raised . when you format the traceback, Scenario: Given a new working directory And a file named "features/steps/pass_steps.py" with: """ from behave import step @step('{word:w} step passes') def step_passes(context, word): pass """ And a file named "features/steps/steps.py" with: """ from behave import when, then from behave._types import ChainedExceptionUtil import copy @when('I bad chained-exception causes my step to fail') def step_bad_usage_of_chained_exception(context): # -- BAD IMPLEMENATION: exception = ZeroDivisionError('integer division or modulo by zero') exception.__cause__ = copy.copy(exception) raise exception @when('a chained-exception causes my step to fail') def step_chained_exception_causes_failure(context): try: raise ZeroDivisionError("OOPS-1") except ZeroDivisionError as e: e2 = RuntimeError("OOPS-2") ChainedExceptionUtil.set_cause(e2, e) raise e2 @then('this step must be executed') def step_check_step(context): pass """ And a file named "features/syndrome.feature" with: """ Feature: Failing step which can lead to stop behave @failing Scenario: Run stopping behave scenario When a chained-exception causes my step to fail Then this step must be executed """ When I run "behave -f plain features/syndrome.feature" Then it should fail with: """ 0 scenarios passed, 1 failed, 0 skipped """ And the command output should contain: """ ZeroDivisionError: OOPS-1 The above exception was the direct cause of the following exception: Traceback (most recent call last): """ And the command output should contain: """ File "features/steps/steps.py", line 19, in step_chained_exception_causes_failure raise e2 RuntimeError: OOPS-2 """ behave-1.2.6/issue.features/issue0510.feature0000644000076600000240000000326113244555737021036 0ustar jensstaff00000000000000@issue @junit @wip Feature: Issue #510 -- JUnit XML output is not well-formed (in some cases) . Special control characters in JUnit stdout/stderr sections . are directly written to CDATA XML sections. . . According to the XML charset specification only the following unicode . codepoints (characters) are allowed in a CDATA section: . . Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] . /* any Unicode character, excluding the surrogate blocks, FFFE, and FFFF. */ . . [XML-charsets] "The normative reference is XML 1.0 (Fifth Edition), . section 2.2, https://www.w3.org/TR/REC-xml/#charsets @use.with_xmllint=yes @xfail Scenario: Given a new working directory And a file named "features/steps/special_char_steps.py" with: """ # -*- coding: UTF-8 -*- from __future__ import print_function from behave import step @step(u'we print ^D') def step_print_special_char_control_d(context): print(u"\004") """ And a file named "features/special_char.feature" with: """ Feature: An illegal char Scenario: Control-D When we print ^D """ When I run "behave --junit features/special_char.feature" Then it should pass with: """ 1 scenario passed, 0 failed, 0 skipped """ When I run "xmllint reports/TESTS-special_char.xml" Then it should pass And the command output should not contain "parser error" And the command output should not contain: """ reports/TESTS-special_char.xml:12: parser error : PCDATA invalid Char value 4 """ And note that "xmllint reports additional correlated errors" behave-1.2.6/issue.features/issue0547.feature0000644000076600000240000000225013244555737021045 0ustar jensstaff00000000000000@issue Feature: Issue 547 -- behave crashes when adding a step definition with optional parts . NOTE: cfparse/parse matcher conflict issue w/ CTOR init code. Scenario: Syndrome w/ cfparse Given a new working directory And a file named "features/environment.py" with: """ from behave import register_type, use_step_matcher import parse @parse.with_pattern(r"optional\s+") def parse_optional_word(text): return text.strip() use_step_matcher("cfparse") register_type(opt_=parse_optional_word) """ And a file named "features/steps/steps.py" with: """ from behave import step @step(u'some {:opt_?}word') def step_impl(context, opt_): pass """ And a file named "features/alice.feature" with: """ Feature: Alice Scenario: Bob Given some optional word """ When I run "behave -f plain features/alice.feature" Then it should pass And the command output should not contain: """ ValueError: format spec u'opt_?' not recognised """ behave-1.2.6/issue.features/issue0573.feature0000644000076600000240000000472713244555737021057 0ustar jensstaff00000000000000@issue @mistaken Feature: Issue 573 Select scenarios fails with empty Scenarios @setup Scenario: Test Setup Given a new working directory And a file named "features/steps/steps.py" with: """ from behave import step @step('a step passes') def step_passes(context): pass """ And a file named "features/syndrome.feature" with: """ Feature: Alice Scenario: Empty (no steps) Scenario: Not Empty (with steps) When a step passes """ And a file named "behave.ini" with: """ [behave] show_skipped = false show_timings = false """ Scenario: Select scenarios by name in dry-run mode When I run "behave -f plain --name="Not Empty \(with steps\)" --dry-run features/" Then the command output should contain: """ Feature: Alice Scenario: Not Empty (with steps) When a step passes ... untested """ And it should pass with: """ 0 features passed, 0 failed, 0 skipped, 1 untested 0 scenarios passed, 0 failed, 1 skipped, 1 untested 0 steps passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Select scenarios by name and run them When I run "behave -f plain --name="Not Empty \(with steps\)" features/" Then the command output should contain: """ Feature: Alice Scenario: Not Empty (with steps) When a step passes ... passed """ And it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 1 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ Scenario: Select scenarios by name with partial name and run them When I run "behave -f plain --name="Not Empty" features/" Then the command output should contain: """ Feature: Alice Scenario: Not Empty (with steps) When a step passes ... passed """ And it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 1 skipped 1 step passed, 0 failed, 0 skipped, 0 undefined """ behave-1.2.6/issue.features/issue0597.feature0000644000076600000240000000661213244555737021060 0ustar jensstaff00000000000000@issue Feature: Issue #597 -- Steps with accented letters doesn't seem to work Background: Given a new working directory And a file named "features/french.feature" with: """ # language: fr Fonctionnalité: Alice Scénario: A1 Soit allé Quand comment Alors cava """ And a file named "behave.ini" with: """ [behave] show_timings = false """ Scenario: Passing w/o Encoding-Hint in Steps File (case: py2, py3) Given a file named "features/steps/french_steps.py" with: """ # -*- coding: UTF-8 -*- from behave import given, when, then @given(u'allé') def given_step_alle(ctx): pass @when(u'comment') def when_step_comment(ctx): pass @then(u'cava') def then_step_cava(ctx): pass """ When I run "behave -f plain features/" Then it should pass with: """ Fonctionnalité: Alice Scénario: A1 Soit allé ... passed Quand comment ... passed Alors cava ... passed 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ @only.with_python2=true Scenario: Failing w/o Encoding-Hint in Steps File (case: python2) Given a file named "features/steps/french_steps.py" with: """ from behave import given, when, then @given(u'allé') def given_step_alle(ctx): pass @when(u'comment') def when_step_comment(ctx): pass @then(u'cava') def then_step_cava(ctx): pass """ When I run "behave -f plain features/" Then it should fail with: """ 0 features passed, 1 failed, 0 skipped 0 scenarios passed, 1 failed, 0 skipped 0 steps passed, 0 failed, 2 skipped, 1 undefined """ And the command output should contain: """ Scénario: A1 Soit allé ... undefined """ And the command output should contain: """ You can implement step definitions for undefined steps with these snippets: @given(u'allé') def step_impl(context): raise NotImplementedError(u'STEP: Given allé') """ But note that "python2 uses encoding=ascii" And note that "encoding-hint in steps file solves the problem" @not.with_python2=true Scenario: Passing w/o Encoding-Hint in Steps File (case: python3) Given a file named "features/steps/french_steps.py" with: """ from behave import given, when, then @given(u'allé') def given_step_alle(ctx): pass @when(u'comment') def when_step_comment(ctx): pass @then(u'cava') def then_step_cava(ctx): pass """ When I run "behave -f plain features/" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined """ And the command output should contain: """ Scénario: A1 Soit allé ... passed """ And the command output should not contain: """ @given(u'allé') def step_impl(context): raise NotImplementedError(u'STEP: Given allé') """ But note that "python3 discovers encoding (or uses encoding=UTF-8)" behave-1.2.6/issue.features/issue0606.feature0000644000076600000240000000254413244555737021047 0ustar jensstaff00000000000000@issue Feature: Issue #606 -- Name option with special unicode chars Background: Given a new working directory And a file named "features/alice.feature" with: """ Feature: Alice Scenario: Ärgernis ist überall Given a step passes Scenario: My second Ärgernis When another step passes Scenario: Unused Then some step passes """ And a file named "features/steps/passing_steps.py" with: """ from behave import step @step(u'{word:w} step passes') def step_passes(context, word): pass """ And a file named "behave.ini" with: """ [behave] show_timings = false """ Scenario: Use special unicode chars in --name options When I run "behave -f plain --name Ärgernis features/" Then it should pass with: """ 1 feature passed, 0 failed, 0 skipped 2 scenarios passed, 0 failed, 1 skipped 2 steps passed, 0 failed, 1 skipped, 0 undefined """ And the command output should contain: """ Scenario: Ärgernis ist überall Given a step passes ... passed Scenario: My second Ärgernis When another step passes ... passed """ But the command output should not contain: """ UnicodeDecodeError: 'ascii' codec can't decode byte """ behave-1.2.6/issue.features/README.txt0000644000076600000240000000067413244555737017526 0ustar jensstaff00000000000000issue.features: =============================================================================== :Status: PREPARED (fixes are being applied). :Requires: Python >= 2.6 (due to step implementations) This directory contains behave self-tests to ensure that behave related issues are fixed. PROCEDURE: * ONCE: Install python requirements ("requirements.txt") * Run the tests with behave :: bin/behave -f progress issue.features/ behave-1.2.6/issue.features/requirements.txt0000644000076600000240000000064113244555737021306 0ustar jensstaff00000000000000# ============================================================================ # PYTHON PACKAGE REQUIREMENTS: For running issue.features/ # ============================================================================ # REQUIRES: Python >= 2.5 # REQUIRES: Python >= 3.2 # DESCRIPTION: # pip install -r # # ============================================================================ PyHamcrest >= 1.6 behave-1.2.6/issue.features/steps/0000755000076600000240000000000013244564040017142 5ustar jensstaff00000000000000behave-1.2.6/issue.features/steps/ansi_steps.py0000644000076600000240000000126113244555737021701 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import from behave import then from behave4cmd0.command_steps import \ step_command_output_should_contain_text, \ step_command_output_should_not_contain_text # -- CONSTANTS: # ANSI CONTROL SEQUENCE INTRODUCER (CSI). CSI = u"\x1b[" @then(u'the command output should contain ANSI escape sequences') def step_command_ouput_should_contain_ansi_sequences(context): step_command_output_should_contain_text(context, CSI) @then(u'the command output should not contain any ANSI escape sequences') def step_command_ouput_should_not_contain_ansi_sequences(context): step_command_output_should_not_contain_text(context, CSI) behave-1.2.6/issue.features/steps/behave_hooks_steps.py0000644000076600000240000000045713244555737023412 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import from behave import then @then('the behave hook "{hook}" was called') def step_behave_hook_was_called(context, hook): substeps = u'Then the command output should contain "hooks.{0}: "'.format(hook) context.execute_steps(substeps) behave-1.2.6/issue.features/steps/use_steplib_behave4cmd.py0000644000076600000240000000051613244555737024133 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Use behave4cmd0 step library (predecessor of behave4cmd). """ from __future__ import absolute_import # -- REGISTER-STEPS FROM STEP-LIBRARY: # import behave4cmd0.__all_steps__ import behave4cmd0.command_steps import behave4cmd0.passing_steps import behave4cmd0.failing_steps import behave4cmd0.note_steps behave-1.2.6/LICENSE0000644000076600000240000000243413244555737014064 0ustar jensstaff00000000000000Copyright (c) 2012-2014 Benno Rice, Richard Jones, Jens Engel and others, except where noted. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. behave-1.2.6/MANIFEST.in0000644000076600000240000000221713244563763014612 0ustar jensstaff00000000000000include README.rst include CHANGES.rst include MANIFEST.in include LICENSE include .coveragerc include .editorconfig include .pycheckrc include .pylintrc include *.cfg include *.ini include *.py include *.rst include *.txt include *.yml include *.yaml include bin/behave* include bin/invoke* recursive-include .ci *.yml recursive-include bin *.py *.cmd *.sh *.zip *.yml *.json recursive-include behave4cmd0 *.py recursive-include etc *.xsd *.txt *.json-schema recursive-include examples *.py *.feature *.txt *.rst *.ini recursive-include docs *.rst *.txt *.css *.py *.html *.rst-* *.png Makefile recursive-include docs/_themes *.html *.conf *.css *.css_t LICENSE* recursive-include test *.py recursive-include tests *.py *.txt recursive-include tasks *.py *.zip *.txt *.rst recursive-include tools *.feature *.py *.yml *.sh recursive-include features *.feature *.py *.txt recursive-include issue.features *.feature *.py *.txt recursive-include more.features *.feature *.py *.txt recursive-include py.requirements *.txt prune .tox prune .venv* prune paver_ext exclude pavement.py behave-1.2.6/more.features/0000755000076600000240000000000013244564040015616 5ustar jensstaff00000000000000behave-1.2.6/more.features/formatter.json.validate_output.feature0000644000076600000240000000247313244555737025401 0ustar jensstaff00000000000000@slow Feature: Validate JSON Formatter Output As a tester I want that the JSON output is validated against its JSON schema So that the JSON formatter generates valid JSON output. | NOTES: | Some have the behave testruns may contain failures (@xfail). | This should lead to more realistic JSON output, too. Scenario: Validate JSON output from features/ test run Given I use the current directory as working directory When I run "behave -f json -o testrun1.json features/" When I run "bin/jsonschema_validate.py testrun1.json" Then it should pass with: """ validate: testrun1.json ... OK """ Scenario: Validate JSON output from issue.features/ test run Given I use the current directory as working directory When I run "behave -f json -o testrun2.json issue.features/" When I run "bin/jsonschema_validate.py testrun2.json" Then it should pass with: """ validate: testrun2.json ... OK """ Scenario: Validate JSON output from tools/test-features/ test run Given I use the current directory as working directory When I run "behave -f json -o testrun3.json tools/test-features/" When I run "bin/jsonschema_validate.py testrun3.json" Then it should pass with: """ validate: testrun3.json ... OK """behave-1.2.6/more.features/steps/0000755000076600000240000000000013244564040016754 5ustar jensstaff00000000000000behave-1.2.6/more.features/steps/tutorial_steps.py0000644000076600000240000000053013244555737022422 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """Step implementations for tutorial example.""" from behave import * @given('we have behave installed') def step_impl(context): pass @when('we implement a test') def step_impl(context): assert True is not False @then('behave will test it for us!') def step_impl(context): assert context.failed is False behave-1.2.6/more.features/steps/use_steplib_behave4cmd.py0000644000076600000240000000022113244555737023736 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Use behave4cmd0 step library (predecessor of behave4cmd). """ # -- REGISTER-STEPS: import behave4cmd0.__all_steps__ behave-1.2.6/more.features/tutorial.feature0000644000076600000240000000024013244555737021047 0ustar jensstaff00000000000000Feature: showing off behave Scenario: run a simple test Given we have behave installed When we implement a test Then behave will test it for us! behave-1.2.6/PKG-INFO0000644000076600000240000001433213244564040014137 0ustar jensstaff00000000000000Metadata-Version: 1.2 Name: behave Version: 1.2.6 Summary: behave is behaviour-driven development, Python style Home-page: http://github.com/behave/behave Author: Jens Engel, Benno Rice and Richard Jones Author-email: behave-users@googlegroups.com License: BSD Description-Content-Type: UNKNOWN Description: .. image:: https://img.shields.io/travis/behave/behave/master.svg :target: https://travis-ci.org/behave/behave :alt: Travis CI Build Status .. image:: https://readthedocs.org/projects/behave/badge/?version=latest :target: http://behave.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. image:: https://img.shields.io/pypi/v/behave.svg :target: https://pypi.python.org/pypi/behave :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/behave.svg :target: https://pypi.python.org/pypi/behave :alt: Downloads .. image:: https://img.shields.io/pypi/l/behave.svg :target: https://pypi.python.org/pypi/behave/ :alt: License .. image:: https://badges.gitter.im/Join%20Chat.svg :alt: Join the chat at https://gitter.im/behave/behave :target: https://gitter.im/behave/behave?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge .. |logo| image:: https://raw.github.com/behave/behave/master/docs/_static/behave_logo1.png behave is behavior-driven development, Python style. |logo| Behavior-driven development (or BDD) is an agile software development technique that encourages collaboration between developers, QA and non-technical or business participants in a software project. *behave* uses tests written in a natural language style, backed up by Python code. First, `install *behave*.`_ Now make a directory called "features/". In that directory create a file called "example.feature" containing: .. code-block:: gherkin # -- FILE: features/example.feature Feature: Showing off behave Scenario: Run a simple test Given we have behave installed When we implement 5 tests Then behave will test them for us! Make a new directory called "features/steps/". In that directory create a file called "example_steps.py" containing: .. code-block:: python # -- FILE: features/steps/example_steps.py from behave import given, when, then, step @given('we have behave installed') def step_impl(context): pass @when('we implement {number:d} tests') def step_impl(context, number): # -- NOTE: number is converted into integer assert number > 1 or number == 0 context.tests_count = number @then('behave will test them for us!') def step_impl(context): assert context.failed is False assert context.tests_count >= 0 Run behave: .. code-block:: bash $ behave Feature: Showing off behave # features/example.feature:2 Scenario: Run a simple test # features/example.feature:4 Given we have behave installed # features/steps/example_steps.py:4 When we implement 5 tests # features/steps/example_steps.py:8 Then behave will test them for us! # features/steps/example_steps.py:13 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined Now, continue reading to learn how to get the most out of *behave*. To get started, we recommend the `tutorial`_ and then the `feature testing language`_ and `api`_ references. .. _`Install *behave*.`: http://pythonhosted.org/behave/install.html .. _`tutorial`: http://pythonhosted.org/behave/tutorial.html#features .. _`feature testing language`: http://pythonhosted.org/behave/gherkin.html .. _`api`: http://pythonhosted.org/behave/api.html More Information ------------------------------------------------------------------------------- * `behave documentation`_: `latest edition`_, `stable edition`_, `PDF`_ * `behave.example`_: Behave Examples and Tutorials (docs, executable examples). * `changelog`_ (latest changes) .. _behave documentation: http://behave.readthedocs.io/ .. _changelog: https://github.com/behave/behave/blob/master/CHANGES.rst .. _behave.example: https://github.com/behave/behave.example .. _`latest edition`: http://behave.readthedocs.io/en/latest/ .. _`stable edition`: http://behave.readthedocs.io/en/stable/ .. _PDF: https://media.readthedocs.org/pdf/behave/latest/behave.pdf Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 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 :: 3.6 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: Jython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Software Development :: Testing Classifier: License :: OSI Approved :: BSD License Provides: behave Provides: setuptools_behave Requires-Python: >=2.6, !=3.0.*, !=3.1.*, !=3.2.* behave-1.2.6/PROJECT_INFO.rst0000644000076600000240000000214613244555737015532 0ustar jensstaff00000000000000Project Info =============================================================================== =============== =================================================================== Category Hyperlink =============== =================================================================== Documentation: http://behave.readthedocs.io/ for `latest`_ and `stable`_ (`PDF`_) Download: http://pypi.python.org/pypi/behave (or: `github archive`_) Development: https://github.com/behave/behave Issue Tracker: https://github.com/behave/behave/issues Changelog: `CHANGES.rst `_ Newsgroup: https://groups.google.com/forum/#!forum/behave-users =============== =================================================================== .. hint:: The PyPI version is the latest stable version. Otherwise, use the latest stable tag or the "bleeding edge" from Github. .. _latest: http://behave.readthedocs.io/en/latest/ .. _stable: http://behave.readthedocs.io/en/stable/ .. _PDF: https://media.readthedocs.org/pdf/behave/latest/behave.pdf .. _`github archive`: https://github.com/behave/behave/tags behave-1.2.6/py.requirements/0000755000076600000240000000000013244564040016211 5ustar jensstaff00000000000000behave-1.2.6/py.requirements/all.txt0000644000076600000240000000076013244555737017542 0ustar jensstaff00000000000000# ============================================================================ # BEHAVE: PYTHON PACKAGE REQUIREMENTS: All requirements # ============================================================================ # DESCRIPTION: # pip install -r # # SEE ALSO: # * http://www.pip-installer.org/ # ============================================================================ # ALREADY: -r testing.txt # ALREADY: -r docs.txt -r basic.txt -r develop.txt -r more_py26.txt -r json.txt behave-1.2.6/py.requirements/basic.txt0000644000076600000240000000140013244555737020043 0ustar jensstaff00000000000000# ============================================================================ # BEHAVE: PYTHON PACKAGE REQUIREMENTS: Normal usage/installation (minimal) # ============================================================================ # DESCRIPTION: # pip install -r # # SEE ALSO: # * http://www.pip-installer.org/ # ============================================================================ parse >= 1.8.2 parse_type >= 0.4.2 six >= 1.10 traceback2; python_version < '3.0' contextlib2 # MAYBE: python_version < '3.5' win_unicode_console >= 0.5; python_version >= '2.7' colorama >= 0.3.7 # -- FOR PYTHON 2.6: # REQUIRES: pip >= 6.0 argparse; python_version <= '2.6' ordereddict; python_version <= '2.6' importlib; python_version <= '2.6' behave-1.2.6/py.requirements/ci.travis.txt0000644000076600000240000000062113244555737020670 0ustar jensstaff00000000000000mock nose PyHamcrest >= 1.9 pytest >= 3.0 # -- NEEDED: By some tests (as proof of concept) # NOTE: path.py-10.1 is required for python2.6 path.py >= 10.1 # -- NOTE: Travis.CI tweak related w/ invalid linecache2 tests. # This problem does not exist if you use pip. linecache2 >= 1.0; python_version < '3.0' # FIX: setuptoools problem w/ Python3.7-dev setuptools >= 38.5.1; python_version > '3.6' behave-1.2.6/py.requirements/develop.txt0000644000076600000240000000136513244555737020432 0ustar jensstaff00000000000000# ============================================================================ # PYTHON PACKAGE REQUIREMENTS FOR: behave -- For development only # ============================================================================ # -- BUILD-TOOL: # PREPARE USAGE: invoke # ALREADY: six >= 1.11.0 invoke >= 0.21.0 path.py >= 10.1 pathlib; python_version <= '3.4' pycmd # -- CONFIGURATION MANAGEMENT (helpers): bumpversion >= 0.4.0 # -- DEVELOPMENT SUPPORT: # PREPARED: nose-cov >= 1.4 tox >= 1.8.1 coverage >= 4.2 pytest-cov # -- PYTHON2/3 COMPATIBILITY: pypa/modernize # python-futurize modernize >= 0.5 # -- STATIC CODE ANALYSIS: pylint # -- REQUIRES: testing, docs, invoke-task requirements -r testing.txt -r docs.txt -r ../tasks/py.requirements.txt behave-1.2.6/py.requirements/docs.txt0000644000076600000240000000045013244555737017716 0ustar jensstaff00000000000000# ============================================================================ # BEHAVE: PYTHON PACKAGE REQUIREMENTS: For documentation generation # ============================================================================ # REQUIRES: pip >= 8.0 Sphinx >= 1.6 sphinx_bootstrap_theme >= 0.6.0 behave-1.2.6/py.requirements/json.txt0000644000076600000240000000046213244555737017742 0ustar jensstaff00000000000000# ============================================================================ # PYTHON PACKAGE REQUIREMENTS FOR: behave -- For development only # ============================================================================ # -- OPTIONAL: For JSON validation # REQUIRES: python >= 2.6 jsonschema >= 1.3.0 behave-1.2.6/py.requirements/more_py26.txt0000644000076600000240000000055013244555737020611 0ustar jensstaff00000000000000# ============================================================================ # BEHAVE: PYTHON PACKAGE REQUIREMENTS: More packages for Python2.6 # ============================================================================ # DEPRECATED: Use "basic.txt" instead # REQUIRE: pip >= 6.0 ordereddict; python_version <= '2.6' importlib; python_version <= '2.6' behave-1.2.6/py.requirements/README.txt0000644000076600000240000000016613244555737017727 0ustar jensstaff00000000000000This directory contains python package requirements for behave. These requirement files are used by: * pip * tox behave-1.2.6/py.requirements/testing.txt0000644000076600000240000000074313244555737020450 0ustar jensstaff00000000000000# ============================================================================ # PYTHON PACKAGE REQUIREMENTS FOR: behave -- For testing only # ============================================================================ # -- TESTING: Unit tests and behave self-tests. # PREPARED-FUTURE: behave4cmd0, behave4cmd pytest >= 3.0 nose >= 1.3 mock >= 2.0 PyHamcrest >= 1.9 # -- NEEDED: By some tests (as proof of concept) # NOTE: path.py-10.1 is required for python2.6 path.py >= 10.1 behave-1.2.6/pytest.ini0000644000076600000240000000145713244555737015114 0ustar jensstaff00000000000000# ============================================================================ # PYTEST CONFIGURATION FILE # ============================================================================ # NOTE: # Can also be defined in in tox.ini or pytest.ini file. # # SEE ALSO: # * http://pytest.org/ # * http://pytest.org/latest/customize.html # * http://pytest.org/latest/usage.html # ============================================================================ # MORE OPTIONS: # addopts = # python_classes=*Test # python_functions=test_* # ============================================================================ [pytest] minversion = 2.8 testpaths = test tests python_files = test_*.py # -- BACKWARD COMPATIBILITY: pytest < 2.8 norecursedirs = .git .tox attic build dist py.requirements tmp* _WORKSPACE behave-1.2.6/README.rst0000644000076600000240000001005113244555737014540 0ustar jensstaff00000000000000====== Behave ====== .. image:: https://img.shields.io/travis/behave/behave/master.svg :target: https://travis-ci.org/behave/behave :alt: Travis CI Build Status .. image:: https://readthedocs.org/projects/behave/badge/?version=latest :target: http://behave.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status .. image:: https://img.shields.io/pypi/v/behave.svg :target: https://pypi.python.org/pypi/behave :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/behave.svg :target: https://pypi.python.org/pypi/behave :alt: Downloads .. image:: https://img.shields.io/pypi/l/behave.svg :target: https://pypi.python.org/pypi/behave/ :alt: License .. image:: https://badges.gitter.im/Join%20Chat.svg :alt: Join the chat at https://gitter.im/behave/behave :target: https://gitter.im/behave/behave?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge .. |logo| image:: https://raw.github.com/behave/behave/master/docs/_static/behave_logo1.png behave is behavior-driven development, Python style. |logo| Behavior-driven development (or BDD) is an agile software development technique that encourages collaboration between developers, QA and non-technical or business participants in a software project. *behave* uses tests written in a natural language style, backed up by Python code. First, `install *behave*.`_ Now make a directory called "features/". In that directory create a file called "example.feature" containing: .. code-block:: gherkin # -- FILE: features/example.feature Feature: Showing off behave Scenario: Run a simple test Given we have behave installed When we implement 5 tests Then behave will test them for us! Make a new directory called "features/steps/". In that directory create a file called "example_steps.py" containing: .. code-block:: python # -- FILE: features/steps/example_steps.py from behave import given, when, then, step @given('we have behave installed') def step_impl(context): pass @when('we implement {number:d} tests') def step_impl(context, number): # -- NOTE: number is converted into integer assert number > 1 or number == 0 context.tests_count = number @then('behave will test them for us!') def step_impl(context): assert context.failed is False assert context.tests_count >= 0 Run behave: .. code-block:: bash $ behave Feature: Showing off behave # features/example.feature:2 Scenario: Run a simple test # features/example.feature:4 Given we have behave installed # features/steps/example_steps.py:4 When we implement 5 tests # features/steps/example_steps.py:8 Then behave will test them for us! # features/steps/example_steps.py:13 1 feature passed, 0 failed, 0 skipped 1 scenario passed, 0 failed, 0 skipped 3 steps passed, 0 failed, 0 skipped, 0 undefined Now, continue reading to learn how to get the most out of *behave*. To get started, we recommend the `tutorial`_ and then the `feature testing language`_ and `api`_ references. .. _`Install *behave*.`: http://pythonhosted.org/behave/install.html .. _`tutorial`: http://pythonhosted.org/behave/tutorial.html#features .. _`feature testing language`: http://pythonhosted.org/behave/gherkin.html .. _`api`: http://pythonhosted.org/behave/api.html More Information ------------------------------------------------------------------------------- * `behave documentation`_: `latest edition`_, `stable edition`_, `PDF`_ * `behave.example`_: Behave Examples and Tutorials (docs, executable examples). * `changelog`_ (latest changes) .. _behave documentation: http://behave.readthedocs.io/ .. _changelog: https://github.com/behave/behave/blob/master/CHANGES.rst .. _behave.example: https://github.com/behave/behave.example .. _`latest edition`: http://behave.readthedocs.io/en/latest/ .. _`stable edition`: http://behave.readthedocs.io/en/stable/ .. _PDF: https://media.readthedocs.org/pdf/behave/latest/behave.pdf behave-1.2.6/setup.cfg0000644000076600000240000000062213244564040014660 0ustar jensstaff00000000000000[aliases] docs = build_sphinx test = nosetests [sdist] formats = zip, gztar [bdist_wheel] universal = true [upload_docs] upload-dir = build/docs/html [behave_test] format = progress tags = -@xfail args = features tools/test-features issue.features [build_sphinx] source-dir = docs/ build-dir = build/docs builder = html all_files = true [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 behave-1.2.6/setup.py0000644000076600000240000001033113244561735014557 0ustar jensstaff00000000000000# -*- coding: utf-8 -* """ Setup script for behave. USAGE: python setup.py install python setup.py behave_test # -- XFAIL on Windows (currently). python setup.py nosetests """ import sys import os.path HERE0 = os.path.dirname(__file__) or os.curdir os.chdir(HERE0) HERE = os.curdir sys.path.insert(0, HERE) from setuptools import find_packages, setup from setuptools_behave import behave_test # ----------------------------------------------------------------------------- # CONFIGURATION: # ----------------------------------------------------------------------------- python_version = float("%s.%s" % sys.version_info[:2]) BEHAVE = os.path.join(HERE, "behave") README = os.path.join(HERE, "README.rst") description = "".join(open(README).readlines()[4:]) # ----------------------------------------------------------------------------- # UTILITY: # ----------------------------------------------------------------------------- def find_packages_by_root_package(where): """ Better than excluding everything that is not needed, collect only what is needed. """ root_package = os.path.basename(where) packages = [ "%s.%s" % (root_package, sub_package) for sub_package in find_packages(where)] packages.insert(0, root_package) return packages # ----------------------------------------------------------------------------- # SETUP: # ----------------------------------------------------------------------------- setup( name="behave", version="1.2.6", description="behave is behaviour-driven development, Python style", long_description=description, author="Jens Engel, Benno Rice and Richard Jones", author_email="behave-users@googlegroups.com", url="http://github.com/behave/behave", provides = ["behave", "setuptools_behave"], packages = find_packages_by_root_package(BEHAVE), py_modules = ["setuptools_behave"], entry_points={ "console_scripts": [ "behave = behave.__main__:main" ], "distutils.commands": [ "behave_test = setuptools_behave:behave_test" ] }, # -- REQUIREMENTS: # SUPPORT: python2.6, python2.7, python3.3 (or higher) python_requires=">=2.6, !=3.0.*, !=3.1.*, !=3.2.*", install_requires=[ "parse >= 1.8.2", "parse_type >= 0.4.2", "six >= 1.11", "argparse; python_version < '2.7'", "importlib; python_version < '2.7'", "ordereddict; python_version < '2.7'", "traceback2; python_version < '3.0'", "enum34; python_version < '3.4'", # PREPARED: "win_unicode_console; python_version < '3.6'", # PREPARED: "colorama", ], test_suite="nose.collector", tests_require=[ "pytest >= 3.0", "nose >= 1.3", "mock >= 1.1", "PyHamcrest >= 1.8", "path.py >= 10.1" ], cmdclass = { "behave_test": behave_test, }, extras_require={ 'docs': ["sphinx >= 1.6", "sphinx_bootstrap_theme >= 0.6"], 'develop': [ "coverage", "pytest >= 3.0", "pytest-cov", "tox", "invoke >= 0.21.0", "path.py >= 8.1.2", "pycmd", "pathlib", # python_version <= '3.4' "modernize >= 0.5", "pylint", ], }, # MAYBE-DISABLE: use_2to3 use_2to3= bool(python_version >= 3.0), license="BSD", classifiers=[ "Development Status :: 4 - Beta", "Environment :: Console", "Intended Audience :: Developers", "Operating System :: OS Independent", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "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 :: 3.6", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: Jython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Testing", "License :: OSI Approved :: BSD License", ], zip_safe = True, ) behave-1.2.6/setuptools_behave.py0000644000076600000240000001134013244555737017160 0ustar jensstaff00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """ Setuptools command for behave. .. code-block:: console python setup.py behave_test python setup.py behave_test --format=progress3 python setup.py behave_test --args=features/one.feature python setup.py behave_test --tags=-xfail --args=features .. seealso:: * http://pypi.python.org/pypi/behave * http://github.com/behave/behave """ from setuptools import Command from distutils import dir_util from fnmatch import fnmatch import os.path import sys import shlex import subprocess class behave_test(Command): """ Simple test runner that runs 'behave' via a "setup.py" file. This ensures that all requirements are provided before the tests are run. """ command_name = "behave_test" description = "Run feature tests with behave" default_format = "progress" default_args = "features" local_egg_dir = ".eggs" command_consumes_arguments = False user_options = [ ("format=", "f", "Use formatter (default: %s)" % default_format), ("tags=", "t", "Use tags to select/exclude features/scenarios"), ("dry-run", "d", "Use dry-run mode"), ("args=", None, "Features to run (default: %s)" % default_args), ] boolean_options = [ "dry-run" ] def initialize_options(self): self.format = self.default_format self.tags = None self.dry_run = None self.args = self.default_args def finalize_options(self): self.ensure_string("format", self.default_format) self.ensure_string_list_with_comma_words("tags") self.ensure_string_list("args") def ensure_string_list_with_comma_words(self, option): """Ensures that a string with whitespace separated words is converted into list of strings. Note that commas are allowed in words (compared :meth:`ensure_string_list()`). """ value = getattr(self, option, None) if not value: return parts = shlex.split(value) setattr(self, option, parts) def _ensure_required_packages_are_installed(self, install_dir="."): # -- NOTE: Packages are downloaded and provided as local eggs. self.announce("ensure_required_packages_are_installed") initial_dir = os.getcwd() try: dir_util.mkpath(install_dir) # -- NO LONGER NEEDED: os.chdir(self.local_egg_dir) if self.distribution.install_requires: self.distribution.fetch_build_eggs(self.distribution.install_requires) if self.distribution.tests_require: self.distribution.fetch_build_eggs(self.distribution.tests_require) finally: os.chdir(initial_dir) def _select_paths(self, path=".", pattern="*"): selected = [ os.path.join(path, f) for f in os.listdir(path) if fnmatch(f, pattern)] return selected def _setup_env_with_local_python_path(self, egg_install_dir): PYTHONPATH = os.environ.get("PYTHONPATH", "") pathsep = os.pathsep PPATH=[x for x in PYTHONPATH.split(pathsep) if x] PPATH.insert(0, os.getcwd()) local_eggs = self._select_paths(egg_install_dir, "*.egg") if local_eggs: PPATH[1:1] = [ os.path.abspath(p) for p in local_eggs] os.environ["PYTHONPATH"] = pathsep.join(PPATH) self.announce("Use PYTHONPATH=%s" % os.environ["PYTHONPATH"], level=3) return PYTHONPATH def run(self): # -- UPDATE SEARCHPATH: Ensure that local dir and local eggs are used. egg_install_dir = self.local_egg_dir self._ensure_required_packages_are_installed(egg_install_dir) OLD_PYTHONPATH = self._setup_env_with_local_python_path(egg_install_dir) for path in self.args: returncode = self.behave(path) if returncode: self.announce("FAILED", level=4) raise SystemExit(returncode) # -- FINALLY: Restore os.environ["PYTHONPATH"] = OLD_PYTHONPATH return returncode def behave(self, path): behave = os.path.join("bin", "behave") if not os.path.exists(behave): # -- ALTERNATIVE: USE: behave script: behave = "behave" # -- USE: behave module (main) behave = "-m behave" cmd_options = "" if self.tags: cmd_options = "--tags=" + " --tags=".join(self.tags) if self.dry_run: cmd_options += " --dry-run" cmd_options += " --format=%s %s" % (self.format, path) self.announce("CMDLINE: python %s %s" % (behave, cmd_options), level=3) behave_cmd = shlex.split(behave) return subprocess.call([sys.executable] + behave_cmd + shlex.split(cmd_options)) behave-1.2.6/tasks/0000755000076600000240000000000013244564040014164 5ustar jensstaff00000000000000behave-1.2.6/tasks/__behave.py0000644000076600000240000000270713244555737016311 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Invoke build script (python based). .. seealso:: https://github.com/pyinvoke/invoke """ from __future__ import print_function import sys from invoke import task, Collection # USE_PTY = os.isatty(sys.stdout) USE_PTY = sys.stdout.isatty() # --------------------------------------------------------------------------- # TASKS # --------------------------------------------------------------------------- # MAYBE: echo=False): @task(help={ "args": "Command line args for behave", "format": "Formatter to use", }) def behave_test(ctx, args="", format=""): # pylint: disable=redefined-builtin """Run behave tests.""" format = format or ctx.behave_test.format options = ctx.behave_test.options or "" args = args or ctx.behave_test.args behave = "{python} bin/behave".format(python=sys.executable) ctx.run("{behave} -f {format} {options} {args}".format( behave=behave, format=format, options=options, args=args), pty=USE_PTY) # --------------------------------------------------------------------------- # TASK MANAGEMENT / CONFIGURATION # --------------------------------------------------------------------------- # namespace.add_task(behave_test, default=True) namespace = Collection() namespace.add_task(behave_test, default=True) namespace.configure({ "behave_test": { "args": "", "format": "progress2", "options": "", # -- NOTE: Overide in configfile "invoke.yaml" }, }) behave-1.2.6/tasks/__init__.py0000644000076600000240000000364713244555737016324 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- # pylint: disable=wrong-import-position, wrong-import-order """ Invoke build script. Show all tasks with:: invoke -l .. seealso:: * http://pyinvoke.org * https://github.com/pyinvoke/invoke """ from __future__ import absolute_import # ----------------------------------------------------------------------------- # BOOTSTRAP PATH: Use provided vendor bundle if "invoke" is not installed # ----------------------------------------------------------------------------- from . import _setup # pylint: disable=wrong-import-order INVOKE_MINVERSION = "0.14.0" _setup.setup_path() _setup.require_invoke_minversion(INVOKE_MINVERSION) # ----------------------------------------------------------------------------- # IMPORTS: # ----------------------------------------------------------------------------- import sys from invoke import Collection # -- TASK-LIBRARY: from . import clean from . import docs from . import test from . import release # ----------------------------------------------------------------------------- # TASKS: # ----------------------------------------------------------------------------- # None # ----------------------------------------------------------------------------- # TASK CONFIGURATION: # ----------------------------------------------------------------------------- namespace = Collection() namespace.add_task(clean.clean) namespace.add_task(clean.clean_all) namespace.add_collection(Collection.from_module(docs)) namespace.add_collection(Collection.from_module(test)) namespace.add_collection(Collection.from_module(release)) # -- INJECT: clean configuration into this namespace namespace.configure(clean.namespace.configuration()) if sys.platform.startswith("win"): # -- OVERRIDE SETTINGS: For platform=win32, ... (Windows) from ._compat_shutil import which run_settings = dict(echo=True, pty=False, shell=which("cmd")) namespace.configure({"run": run_settings}) behave-1.2.6/tasks/__main__.py0000644000076600000240000000355213244555737016300 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Provides "invoke" script when invoke is not installed. Note that this approach uses the "tasks/_vendor/invoke.zip" bundle package. Usage:: # -- INSTEAD OF: invoke command # Show invoke version python -m tasks --version # List all tasks python -m tasks -l .. seealso:: * http://pyinvoke.org * https://github.com/pyinvoke/invoke Examples for Invoke Scripts using the Bundle ------------------------------------------------------------------------------- For UNIX like platforms: .. code-block:: sh #!/bin/sh #!/bin/bash # RUN INVOKE: From bundled ZIP file (with Bourne shell/bash script). # FILE: invoke.sh (in directory that contains tasks/ directory) HERE=$(dirname $0) export INVOKE_TASKS_USE_VENDOR_BUNDLES="yes" python ${HERE}/tasks/_vendor/invoke.zip $* For Windows platform: .. code-block:: bat @echo off REM RUN INVOKE: From bundled ZIP file (with Windows Batchfile). REM FILE: invoke.cmd (in directory that contains tasks/ directory) setlocal set HERE=%~dp0 set INVOKE_TASKS_USE_VENDOR_BUNDLES="yes" if not defined PYTHON set PYTHON=python %PYTHON% %HERE%tasks/_vendor/invoke.zip "%*" """ from __future__ import absolute_import import os import sys # ----------------------------------------------------------------------------- # BOOTSTRAP PATH: Use provided vendor bundle if "invoke" is not installed # ----------------------------------------------------------------------------- # NOTE: tasks/__init__.py performs sys.path setup. os.environ["INVOKE_TASKS_USE_VENDOR_BUNDLES"] = "yes" # ----------------------------------------------------------------------------- # AUTO-MAIN: # ----------------------------------------------------------------------------- if __name__ == "__main__": from invoke.main import program sys.exit(program.run()) behave-1.2.6/tasks/_compat_shutil.py0000644000076600000240000000033413244555737017565 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- # pylint: disable=unused-import # PYTHON VERSION COMPATIBILITY HELPER try: from shutil import which # -- SINCE: Python 3.3 except ImportError: from backports.shutil_which import which behave-1.2.6/tasks/_dry_run.py0000644000076600000240000000220513244555737016373 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Basic support to use a --dry-run mode w/ invoke tasks. .. code-block:: from ._dry_run import DryRunContext @task def destroy_something(ctx, path, dry_run=False): if dry_run: ctx = DryRunContext(ctx) # -- DRY-RUN MODE: Only echos commands. ctx.run("rm -rf {}".format(path)) """ from __future__ import print_function class DryRunContext(object): PREFIX = "DRY-RUN: " SCHEMA = "{prefix}{command}" SCHEMA_WITH_KWARGS = "{prefix}{command} (with kwargs={kwargs})" def __init__(self, ctx=None, prefix=None, schema=None): if prefix is None: prefix = self.PREFIX if schema is None: schema = self.SCHEMA self.ctx = ctx self.prefix = prefix self.schema = schema def run(self, command, **kwargs): message = self.schema.format(command=command, prefix=self.prefix, kwargs=kwargs) print(message) def sudo(self, command, **kwargs): command2 = "sudo %s" % command self.run(command2, **kwargs) behave-1.2.6/tasks/_setup.py0000644000076600000240000001130313244555737016050 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Decides if vendor bundles are used or not. Setup python path accordingly. """ from __future__ import absolute_import, print_function import os.path import sys # ----------------------------------------------------------------------------- # DEFINES: # ----------------------------------------------------------------------------- HERE = os.path.dirname(__file__) TASKS_VENDOR_DIR = os.path.join(HERE, "_vendor") INVOKE_BUNDLE = os.path.join(TASKS_VENDOR_DIR, "invoke.zip") INVOKE_BUNDLE_VERSION = "0.13.0" # pylint: disable=invalid-name DEBUG_SYSPATH = False # ----------------------------------------------------------------------------- # EXCEPTIONS: # ----------------------------------------------------------------------------- class VersionRequirementError(SystemExit): pass # ----------------------------------------------------------------------------- # FUNCTIONS: # ----------------------------------------------------------------------------- def setup_path(invoke_minversion=None): """Setup python search and add ``TASKS_VENDOR_DIR`` (if available).""" # print("INVOKE.tasks: setup_path") if not os.path.isdir(TASKS_VENDOR_DIR): print("SKIP: TASKS_VENDOR_DIR=%s is missing" % TASKS_VENDOR_DIR) return elif os.path.abspath(TASKS_VENDOR_DIR) in sys.path: # -- SETUP ALREADY DONE: # return pass use_vendor_bundles = os.environ.get("INVOKE_TASKS_USE_VENDOR_BUNDLES", "no") if need_vendor_bundles(invoke_minversion): use_vendor_bundles = "yes" if use_vendor_bundles == "yes": syspath_insert(0, os.path.abspath(TASKS_VENDOR_DIR)) if setup_path_for_bundle(INVOKE_BUNDLE, pos=1): import invoke bundle_path = os.path.relpath(INVOKE_BUNDLE, os.getcwd()) print("USING: %s (version: %s)" % (bundle_path, invoke.__version__)) else: # -- BEST-EFFORT: May rescue something syspath_append(os.path.abspath(TASKS_VENDOR_DIR)) setup_path_for_bundle(INVOKE_BUNDLE, pos=len(sys.path)) if DEBUG_SYSPATH: for index, p in enumerate(sys.path): print(" %d. %s" % (index, p)) def require_invoke_minversion(min_version, verbose=False): """Ensures that :mod:`invoke` has at the least the :param:`min_version`. Otherwise, :param min_version: Minimal acceptable invoke version (as string). :param verbose: Indicates if invoke.version should be shown. :raises: VersionRequirementError=SystemExit if requirement fails. """ # -- REQUIRES: sys.path is setup and contains invoke try: import invoke invoke_version = invoke.__version__ except ImportError: invoke_version = "__NOT_INSTALLED" if invoke_version < min_version: message = "REQUIRE: invoke.version >= %s (but was: %s)" % \ (min_version, invoke_version) message += "\nUSE: pip install invoke>=%s" % min_version raise VersionRequirementError(message) # pylint: disable=invalid-name INVOKE_VERSION = os.environ.get("INVOKE_VERSION", None) if verbose and not INVOKE_VERSION: os.environ["INVOKE_VERSION"] = invoke_version print("USING: invoke.version=%s" % invoke_version) def need_vendor_bundles(invoke_minversion=None): invoke_minversion = invoke_minversion or "0.0.0" need_vendor_answers = [] need_vendor_answers.append(need_vendor_bundle_invoke(invoke_minversion)) # -- REQUIRE: path.py try: import path need_bundle = False except ImportError: need_bundle = True need_vendor_answers.append(need_bundle) # -- DIAG: print("INVOKE: need_bundle=%s" % need_bundle1) # return need_bundle1 or need_bundle2 return any(need_vendor_answers) def need_vendor_bundle_invoke(invoke_minversion="0.0.0"): # -- REQUIRE: invoke try: import invoke need_bundle = invoke.__version__ < invoke_minversion if need_bundle: del sys.modules["invoke"] del invoke except ImportError: need_bundle = True except Exception: # pylint: disable=broad-except need_bundle = True return need_bundle # ----------------------------------------------------------------------------- # UTILITY FUNCTIONS: # ----------------------------------------------------------------------------- def setup_path_for_bundle(bundle_path, pos=0): if os.path.exists(bundle_path): syspath_insert(pos, os.path.abspath(bundle_path)) return True return False def syspath_insert(pos, path): if path in sys.path: sys.path.remove(path) sys.path.insert(pos, path) def syspath_append(path): if path in sys.path: sys.path.remove(path) sys.path.append(path) behave-1.2.6/tasks/_tasklet_cleanup.py0000644000076600000240000002245513244555737020100 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Provides cleanup tasks for invoke build scripts (as generic invoke tasklet). Simplifies writing common, composable and extendable cleanup tasks. PYTHON PACKAGE REQUIREMENTS: * path.py >= 8.2.1 (as path-object abstraction) * pathlib (for ant-like wildcard patterns; since: python > 3.5) * pycmd (required-by: clean_python()) clean task: Add Additional Directories and Files to be removed ------------------------------------------------------------------------------- Create an invoke configuration file (YAML of JSON) with the additional configuration data: .. code-block:: yaml # -- FILE: invoke.yaml # USE: clean.directories, clean.files to override current configuration. clean: extra_directories: - **/tmp/ extra_files: - **/*.log - **/*.bak Registration of Cleanup Tasks ------------------------------ Other task modules often have an own cleanup task to recover the clean state. The :meth:`clean` task, that is provided here, supports the registration of additional cleanup tasks. Therefore, when the :meth:`clean` task is executed, all registered cleanup tasks will be executed. EXAMPLE:: # -- FILE: tasks/docs.py from __future__ import absolute_import from invoke import task, Collection from invoke_tasklet_cleanup import cleanup_tasks, cleanup_dirs @task def clean(ctx, dry_run=False): "Cleanup generated documentation artifacts." cleanup_dirs(["build/docs"]) namespace = Collection(clean) ... # -- REGISTER CLEANUP TASK: cleanup_tasks.add_task(clean, "clean_docs") cleanup_tasks.configure(namespace.configuration()) """ # NOT-NEEDED: from invoke.exceptions import Failure from __future__ import absolute_import, print_function import os.path import sys import pathlib from invoke import task, Collection from invoke.executor import Executor from path import Path # ----------------------------------------------------------------------------- # CLEANUP UTILITIES: # ----------------------------------------------------------------------------- def execute_cleanup_tasks(ctx, cleanup_tasks, dry_run=False): """Execute several cleanup tasks as part of the cleanup. REQUIRES: ``clean(ctx, dry_run=False)`` signature in cleanup tasks. :param ctx: Context object for the tasks. :param cleanup_tasks: Collection of cleanup tasks (as Collection). :param dry_run: Indicates dry-run mode (bool) """ # pylint: disable=redefined-outer-name executor = Executor(cleanup_tasks, ctx.config) for cleanup_task in cleanup_tasks.tasks: print("CLEANUP TASK: %s" % cleanup_task) executor.execute((cleanup_task, dict(dry_run=dry_run))) def cleanup_dirs(patterns, dry_run=False, workdir="."): """Remove directories (and their contents) recursively. Skips removal if directories does not exist. :param patterns: Directory name patterns, like "**/tmp*" (as list). :param dry_run: Dry-run mode indicator (as bool). :param workdir: Current work directory (default=".") """ current_dir = Path(workdir) python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath() warn2_counter = 0 for dir_pattern in patterns: for directory in path_glob(dir_pattern, current_dir): directory2 = directory.abspath() if sys.executable.startswith(directory2): # pylint: disable=line-too-long print("SKIP-SUICIDE: '%s' contains current python executable" % directory) continue elif directory2.startswith(python_basedir): # -- PROTECT CURRENTLY USED VIRTUAL ENVIRONMENT: if warn2_counter <= 4: print("SKIP-SUICIDE: '%s'" % directory) warn2_counter += 1 continue if dry_run: print("RMTREE: %s (dry-run)" % directory) else: print("RMTREE: %s" % directory) directory.rmtree_p() def cleanup_files(patterns, dry_run=False, workdir="."): """Remove files or files selected by file patterns. Skips removal if file does not exist. :param patterns: File patterns, like "**/*.pyc" (as list). :param dry_run: Dry-run mode indicator (as bool). :param workdir: Current work directory (default=".") """ current_dir = Path(workdir) python_basedir = Path(Path(sys.executable).dirname()).joinpath("..").abspath() error_message = None error_count = 0 for file_pattern in patterns: for file_ in path_glob(file_pattern, current_dir): if file_.abspath().startswith(python_basedir): # -- PROTECT CURRENTLY USED VIRTUAL ENVIRONMENT: continue if dry_run: print("REMOVE: %s (dry-run)" % file_) else: print("REMOVE: %s" % file_) try: file_.remove_p() except os.error as e: message = "%s: %s" % (e.__class__.__name__, e) print(message + " basedir: "+ python_basedir) error_count += 1 if not error_message: error_message = message if False and error_message: class CleanupError(RuntimeError): pass raise CleanupError(error_message) def path_glob(pattern, current_dir=None): """Use pathlib for ant-like patterns, like: "**/*.py" :param pattern: File/directory pattern to use (as string). :param current_dir: Current working directory (as Path, pathlib.Path, str) :return Resolved Path (as path.Path). """ if not current_dir: current_dir = pathlib.Path.cwd() elif not isinstance(current_dir, pathlib.Path): # -- CASE: string, path.Path (string-like) current_dir = pathlib.Path(str(current_dir)) for p in current_dir.glob(pattern): yield Path(str(p)) # ----------------------------------------------------------------------------- # GENERIC CLEANUP TASKS: # ----------------------------------------------------------------------------- @task def clean(ctx, dry_run=False): """Cleanup temporary dirs/files to regain a clean state.""" # -- VARIATION-POINT 1: Allow user to override in configuration-file directories = ctx.clean.directories files = ctx.clean.files # -- VARIATION-POINT 2: Allow user to add more files/dirs to be removed. extra_directories = ctx.clean.extra_directories or [] extra_files = ctx.clean.extra_files or [] if extra_directories: directories.extend(extra_directories) if extra_files: files.extend(extra_files) # -- PERFORM CLEANUP: execute_cleanup_tasks(ctx, cleanup_tasks, dry_run=dry_run) cleanup_dirs(directories, dry_run=dry_run) cleanup_files(files, dry_run=dry_run) @task(name="clean-all", aliases=("distclean",)) def clean_all(ctx, dry_run=False): """Clean up everything, even the precious stuff. NOTE: clean task is executed first. """ cleanup_dirs(ctx.clean_all.directories or [], dry_run=dry_run) cleanup_dirs(ctx.clean_all.extra_directories or [], dry_run=dry_run) cleanup_files(ctx.clean_all.files or [], dry_run=dry_run) cleanup_files(ctx.clean_all.extra_files or [], dry_run=dry_run) execute_cleanup_tasks(ctx, cleanup_all_tasks, dry_run=dry_run) clean(ctx, dry_run=dry_run) @task def clean_python(ctx, dry_run=False): """Cleanup python related files/dirs: *.pyc, *.pyo, ...""" # MAYBE NOT: "**/__pycache__" cleanup_dirs(["build", "dist", "*.egg-info", "**/__pycache__"], dry_run=dry_run) if not dry_run: ctx.run("py.cleanup") cleanup_files(["**/*.pyc", "**/*.pyo", "**/*$py.class"], dry_run=dry_run) # ----------------------------------------------------------------------------- # TASK CONFIGURATION: # ----------------------------------------------------------------------------- namespace = Collection(clean, clean_all) namespace.configure({ "clean": { "directories": [], "files": [ "*.bak", "*.log", "*.tmp", "**/.DS_Store", "**/*.~*~", # -- MACOSX ], "extra_directories": [], "extra_files": [], }, "clean_all": { "directories": [".venv*", ".tox", "downloads", "tmp"], "files": [], "extra_directories": [], "extra_files": [], }, }) # -- EXTENSION-POINT: CLEANUP TASKS (called by: clean, clean_all task) # NOTE: Can be used by other tasklets to register cleanup tasks. cleanup_tasks = Collection("cleanup_tasks") cleanup_all_tasks = Collection("cleanup_all_tasks") # -- EXTEND NORMAL CLEANUP-TASKS: # DISABLED: cleanup_tasks.add_task(clean_python) # # ----------------------------------------------------------------------------- # EXTENSION-POINT: CONFIGURATION HELPERS: Can be used from other task modules # ----------------------------------------------------------------------------- def config_add_cleanup_dirs(directories): # pylint: disable=protected-access the_cleanup_directories = namespace._configuration["clean"]["directories"] the_cleanup_directories.extend(directories) def config_add_cleanup_files(files): # pylint: disable=protected-access the_cleanup_files = namespace._configuration["clean"]["files"] the_cleanup_files.extend(files) behave-1.2.6/tasks/_vendor/0000755000076600000240000000000013244564040015620 5ustar jensstaff00000000000000behave-1.2.6/tasks/_vendor/invoke.zip0000644000076600000240000052037113244555737017664 0ustar jensstaff00000000000000PK uIinvoke/UT =WMX^XMXux PKntIbpinvoke/__init__.pyUT  VMXWMXux uSn0 }W-(R@7]=ڊLZl%=GڲmtxxxH4fGN i ҥɲdr|mEJS($z3R4Gx< |33N+p^jljKcH;xCגV$d X0[ʋEO zr^Vho& pAF8b wq۲+#veAgXj%s/PAxؠ5ׇ|x5R 9;||H)mc ?< M;&/Qz?Y, ,*\J4Gpv;>BG` Y)97<~^2"?u汿9P`y=v$c/?J9(;MZ'+ږFCXޏ(dqAs7 r=$īRqW. (UJVldBYsl#U?츬Ş@jLzӻ؎`y~PKstIC/+=Qinvoke/_version.pyUT *VMXWMXux /K-*ϋKˏWU0Q04Q0䊇˂$34r 4KttkjrPKHIIz];invoke/collection.pyUT HWWWMXux [ms۶_3+ʕtoݹۤx~d$H,4mm&Iٮ%888/ydMvjW7wƸ/m}TUyi쮮a2h[.sy[go3NF~p'^Ç/^JL,Kl]ex|uvvF_Uy0("' W뵭l^gΔ:?'/'MktgV'bٗ>_ 6y|M`3ry4ݡ.ڃ- [d+) Z+Ք (˚ \wou Im^V0RvPN;s+Vm:"T ImQ ^TfKe@LޙՍDuS(5a*՘qDQ Je]7VMYkf>}v+Hߡu;-VXAiuF[P+^Cskuo݋јektiyA~|qpq#y~~ A)P{[<ouiX嫪LV2<ef\.5%,UWqg0krOw)L _;u%x>g>zA^BGuI#ȡu+̕r/ $[2fGG\ v޹t j2O^rLa%wu.͝)gt̷_`$k05`.j_ЖbD,5{duYHK r ;ZuɽC—Ka.~ n[;c"낵oTka0y5Ƙ3I(e Cl lkȑQo;F L1o(Er0T-Q5)( ycӎ tC1$[ Lh@3Oi@+կuC ՠ>4U{"scDIAҬHz[S*4FA5Tr,`H0ԱJ :O%@QMQȂRT< (6FZ(Lv!U@@:z` )\tA͟Vѐyx?L\Ppg @))x艡S&Bbp4e@}{k;z{bPwWyli:M;/w8 r;#@`0(gx|e]t k*m9yS7٫?!=  x<\'(O9$K~?-a=Ky(zQL3-t٤0] 7[*usvQWK90dO3xv13d7"",epZm!E?mfgj Z s)Wߴ_9˱] eRk<QWcɮ} )g̋^qXyW mHyh"rpњq6E~"g?җrfZ+ßЩ%b;%T,aMTW:S3\iΎu. $MRS,u){7p=d HEW+/HOm24)W !?8%l*L#0Q 6Pvipq@کjLH+>}{3/,R)92/CX2 ]*BI "b,yA5&FDm!@*j .FYƒ *{ÿzQ7 JD9EkpB/.Q`3+ |C'eE]- / :;lp6 |P)FNLS{=„&gδ-tՀ9"Q:c^%Kg>qJֵ0&!wVJE\X n[BikM*sߞMFJ@0w;I; @I;Hqs ,dzB%Pgdv p1[*w`g` 􁁐k۾-#3eE-W?`;a }W|3>>!w|]˛?|O kByGu<>නSiM2&S buߪ^v(o lQa*]vYiqb/ eǔdD{-ì_%@',O9Ų%G !28nZ20Do.tf=ΐB:~nDMὰi:y@_έ=W-J2 AIi5*t68S ٥ӂo88J!uĤƌc:%5w5`,9ԍ# aJ+~HLmåt_y[]}s F{}-;I'R,Jh2bN$y.5*4 }CDF+>*q#J1OUbH7Yn8&ivC/6})sJhL,H_ؘB8,.ro+F"`W%_$ nt3%tA;'Ia~|{TQ,LeQHS Èd ˤKCuqX=o$q|søq_ܶ[!9W5uRq+\⛳Tդ]8"kQ/c;4 Q}PpK#4CSZ$KS ы)VK9}2s5uzѿo n{BL|Nq5!$O TV;: gϯ,0Cy mNc^= Euߜ=o @^Jvp\N2_EQNqU6V)  "#8?)MO, !y!M|RD4M)C~q#=T9Ile ʤAlU&|׸$Ҟd1) H+R_> uuAL$#bcV<< Y3_+Uw³%: ~5flީ#ᜯƿO Ӯ&pۜK7gs"f; ,uسar< Tg IS l#e[ѐ0f˞T#7kwj[c*| m;ɕ%_S)א<% $o&-uouI&pG1nF]'<Ak܄fZn^}vp0 Mm%rX!2q,뾾/U6Hm,q-uD*aZN4I1"KN]C ϧ"&'oȏT\B +y`z49x[FNMS!DGZD?AyJ{mdwVsԠ#AԤ_)縘eģXs8o"*$K,nOJ ov?͉,kvhJ5qd:7>՟0,|F?`7u cQqy7Fдj\&Ub`CQCZbąs4*gӋlYWt9u=ܤ]`Dr ÀU!ۼy|G) 9aD)%L>A/i4ת0xc)N,gS˳􇫕i5C;X 5M6?112؇kۚ2wa!}&T7~ُ_2l L%=ЙLgdu[-{A0}roTs'ORCSx77y3o FW{/UdK?`ۤyݷء*t-^M=^fg 노d DBű&k9w.wwjߓu.I`v/vzif*:i!=*j}.+%2{Jw#'JxSeiRχm|T&ojwey.M=o_}УT)Go*lKcfڒa#),,%xVPop܄ ')CɄ9v]í'ոXB E)cxˌY.=3 ?75t{>$ϗabfSRoj:-|T ݷxO\="VeM_{8(UA-']vbw !SθmpHW9ҟ +mOj聯eRjtt2'),u!PvNh&.S'Ć?i_K's"YӐw&f4Z֋PKHII({ xbinvoke/complete.pyUT HWWWMXux WK6 WҌ4IҴcfNKKX"Z{Cd'm}ъ r\R]dnPr+W{&L̫O`$9<}ϋEU%?WH& ga i_kt< qRb'$kAa 1L]z˨du[%GOZX˃sқK[Wb Kq'!d'8w|)lzri'SZ`OEi V2o 3e zb4c7gƄ'5g!7:c0T|t[w5y 0i C|N6/fL?%VtpµЏӀ. _;M|}#=GUTYTذ'g(< ,ai/=pB;ЈI?G4S XZ̽s@,oNoY|%;;[|p^PKtIg7 [invoke/config.pyUT FVMXWMXux \s6uf*)u/깹\ƙ&m禗(XS?lryw]HN&b?]jTRgk]>T~G)ۤ̊yf!.Ҷՙntm_gwgbqg1_?t^ѵoK<[G8e֛M8YKQ/UUV H>>~qr]MTR^IRQ9z Y7eQ6v9"}M=5O*g'7qUO L'2"f29ѯߝc3Yō&bƾʢm~,ڴk,-Rn!MYV?|7gggI׵z7몼;N:it~՗D"-+uSfIY4!I VdUtTtm$\e"Kպ$ u+2KՔ*GE-x5icm Ajp_wZM+U8\G1ۆdQ~F]U 3pH8Bl)ku1Orm.W_5|B2E(x(ߠuCJyO{HF=4]$Nr|y]݌W$ ;6ha8+9tLef0[ & SJhW4/+"q@ڡncRdqnh-Y5(ğYٕs 0% %&y=W3uZZqz3"|ܾ\0CzuJ[ 4 F,Tl"/@kbS=Şgy✄SVL) ҧh mnG Fѳ:;thֻ&CPն,Sօ/kHҴ*M5dD3.P̟V&{B֣* :Ru}M3sFYIg)Y;g #v0Ȇx jYS@` y#a=yi=, K o.V()7R:c1LJ yv\j{R0@9([O\KzBAE|v{L:3~L9q U(=xP`GcxF4ȶ9&%|θ ,!gQ1xnF 4>%? !/suJe"KTļJ}h!_O҇QoՂ`hOae0bSG%)6e6jLѣDFIǤϛMW7쩫H6չ^ueqS^k*5ͶQS9l-"- V)4dyL~-$\k߲"cқŪ\JvI^^_ X`kǙ(8BX7>˳#񼶽8zXe{Jg67#֘ [,L6<ٔaO *+&"' dWT_ȨaFgFERY!J*o(R7ǃ'c-.nV, jR7 <YZs)Xm5ve}(;"4\tD;Paz@/HIj)im%n5Iah'^Snћ}\ȤeLiaɄF,xW:&mEɟ+rXu}Ca^h.\S]"5N2RS ^^$6/qhM= qo ۵D :|W]H"?V룿YDT~PcT7fg?_%ιt%p0raB7v@dWbrsv#~l[è'!)͏q^~*RKD)ERےA6b=tz Sχw4gz@[vLzdOq:4{Fؐ qrMMVX U+ʔ8KirjMx@ZpKmH?l6@ *5]Skc![5`.p |LTo\[ߜI+c 6lKW^wXyR68q4^w@Gf> iZ F6Br?F" .5|J:\10*sqyH,'U!RۡH*lHL.j8 z{yIAMᘥ'm($ CNODgf"'-(7-NJ6v|JƭCRΏ u^R&F~FR,(@"ȴݒ<nXF}_L?zKh3d58;ݝ# T,^q&4vϻ@j#ƽ #ro^<ݑ:0'kmd XЍdiHϥ dPa?6)%Hy^wOW~V1RHx۷/_uH0P܄jLsu?zN̟{.Ucj eksrHZlf~鴫wz:զaB6➑ιГUǦ[/UdREݱshyg_oP'0JtPvNp~ʼnЄ4T!9sD #Dߥ{x\Mp̼A4/{tYy$\۷٦s $}%SR!S{T`^kH+#x1rrZ(W'䏵iyx" N'-+g5<^v5 S %xCEwpt1iğ"-ok=<(xؐ AMwJ:lȏ p o 9XYR9klj=vXٸUS5A˧Ƈ\uA>%`PN< 9;O|AWݧ<"=qk/Nx/YT=}zb2si)qR.TU%d y. >,p #KGa֛d,Ps@%CxNi1 b֜Pl1)Q蹦&٧ NP]Ҳ5shpHõG{ngc0Q{E؆w& Q1`|MǓOE\}^R A]ݡD^Π#[.D.r ~O`"HE6yD.WbyaVݓ@cm9' d<=pkL?+'{O]~.R J%#[˰5%&ط1HұCC:tuC7%zo@bbX74 C-Q*%xPt|s=*yeWqλr%)+oиMq̑CCvqM~%GHvySv5ZPD(F)oHm)ƫLgne{@zUvzC+rBu*hDE'ԾGK95+~^WLCIPerI-f58@`잚hh]gnK#߈Y}+7n 8wܵιٕs,#T?alh^EZ,(;`_!aQa~ܨ_d>t6wec15mYo(U̎Ng̛14OIF9 N(vH2M85}Hٷ!MkҘ܏e0[&\!Jǩw}B3)x!#}jphBd_q $DFfO8BG2hEd~v2m@3}W giV_?7&Cɇ:r:,MɶaL\_g}$7h<&!?Ⱦ]k&zt/5W>%Q  9xњ[qF?8}e r[Mb`fo:ױl(nYmn{kllʔ8:y<-|LFcMZ_Jk!6Dd];]Vf7Z3s%tMg]Qp.` ))UP~Ǐ1{-3nl }q4|g`)ZcF#~`1)_`x"dIsl(圏[mgb1z Ž|'x:3[GTE P$ٿϕ0B 7(eyxf" C~퇻'~W. JA4>Q44Ւ fM&B=PEx"yՑ^@#v傏ґqx_ H~佷w(}5ϸDH>^h|]026wqge4[]?;1x_\80:C?~fnzbS=|N 44իzEzܤ_xKt:*ѓ/;/nұJS6頲@P: o\wa#T[yyQ}ɏQ. BTu-' fVa* ƿ d&[;lۖm̀N>zfL\<ֽS}U4#P_& GY'.],{Zz(ߥW8Bt{,c4gq0 <}mkjc)`h Wa=<>bo$~|t|r|4 D=:#H./0+ \c?'F_&(*| @pa[b??}E5=zAПGjںtgOW3쓂V+[SmE29RZyS1|> 5^I0iN 4y>>:X)A LZ9G{CJ.@&+:@#vb,=*#K| ǝB\s99ű؇1lʘ^_0i&tϸ\8c_8 sUVwb0 "0R/7B):G{&WHŵf*|đ)K9G\hU PF!NS Ji`! W/OtϙXB)T%~D# pj+(E@pSβ㰀m9L*@Q Ph,+D~m2ĭ(HAln'֔FBc1ر Aqk|#Fr@Q5o'2jQINNAJdFq<Sk!;SG,rE,d"5D}?D/ȶU۬;JPJfOj*bu&3Zl_,d 4RµUggvCBR$?xܳy!o?XCgu~ï Tu OOSt,h5ɣ;*kx趮f/?nowTK/~uM Za}DDX<QQtSO CXsBNW7H8Џ@Ӄr|KwEd 6dۡ"KӃ%OYt.i@7Zl8_<uXT/+LӁ>Nm[0AL FnYd^ؑ0f0NW]L)g9q i@GLfpuVUxFQ^JUq1vh(sOgFid(N8=HQ7Ⱥܞó,:n rk L&PM}eZR00[L2%Hds &{= `^ 'gx)8hŧqlOqܢ=_-9Kr_D+'=1aRfV|9"- ܼvC˂8ڹ`i8(3H1?V$f)js|tHS"b[D~ʖVPR\@%6v0MHu| `^h*@G\N3U葐+ BFppAKHq/u1!TS5]okș%nCWua߮З`( Uڟ Q]pݎrSI\@90n45u঩E"Mʶ`rLJbϼ3Wpχ.C7ܶ Wڞim$4JkgoبAfkGS oTp~݉URfgxΎYES8S{ ,Q 8%s8)jm'嫟-%Mbª P{>:噂Ɓ w55<)@72Yǖ{CӦKd=@ R=Dh~N+{;m8Ox%.>ɯ(<ϣoy?b62G6rEWD c˩?-i!C{)翙 4WrknCڨsnɢ^2% "? ɾߩ.^d;:<5ǐ齡L9 yeF5Pz G3M8XĀ lM9to߫R|-r^c}2Pc-d= M^=:Z>\YиzHt4 >ҳ'Qj4FPXڢ\QNOreMh̰jQD"}HxO_PKHII@n invoke/env.pyUT HWWWMXux Wn8}W[E۾HöE LKtF$ !EӢ"vfp8͒Us+jk7`jJհZJ6׬1y|5l'`[Wo쎶n:Ff,lO7Դ"JV44x+;{edG*jy }[ k咼٩FY ph=;7*ACXJ[LlڳVJaAܗ%I6%:c Vz^:IwTm. S-+ H[6Ә>zh9/2;MVVxc񉝰u5J''T3΋*? id5X@_ҒHQCpX+ :O(VTqY)#؍x@YxNou׌+;ZX5v Bo޳ 7d&?~hY3< XAY;}Ľt6|L ]Y}5S[.abCbvQơK e#8쒽do^觌lX}pd n`2bn'g{=T{)P$f;M2"&΂qB DGu&QCl`aPckSdeu 9Ձ/|z _T$7C Jd.} ],ʢCNMĎ\M9#P\MK(%UJRF N'[hL-([GzeGvx/WLzEvN#Rߍ\' 7V<@=tǧ+N5RJ1E9uhԈaj:a|fy9^(H?Ѯ([!Xtq̚ע GQ3y2Dgv]hc5,v {шm"o =aTR8S~<}oE msQC La,Nm ^]v#>ظ>Fi>,>vEm8gɪC_E HŸFKϋy]` !"7N_‚˜^26 :Ӹ:;"mnߑ0u41'S]T1^ԃS 6"0;b/( ď#HT8J՝??Aryj EOQuЀӸGUWҚ)D~S\u?'7H2>I֧i(1Q" FhjxH /"?2ci+RDZAXA5%Clbb8]Vy[* &;Ձ3NJёI;xzߣ7lu22PKtIM invoke/exceptions.pyUT nVMXWMXux Yms6_3'RszФtҌĚthY߳ UvL" \, yS u/Y)S.Ln)q'IZIJ*Rʊm\z|m('-{G|r5VT9SWTx#\QS;VH-N2󂅅\RI( l@i}K2M*0ifYFHaBJ>*x+3mCkTү74@OIqɄU.~hxe S|~0^&ڊЅ_N%˸qHD,G)O=,JXPR}՛*۰_-lWYMO,2V)&S=p{U0s^d2Ìepz]z1苿%)<jqLV6H4uunҴ/=|   ؜u 4b[cY#n筡bs啭 -}T+:X)U Ai"7º:SX=~m ɏh{}/E%Y1@-Ş\'=!3HWF4eö[3N֠Xn5J}*%|ߤ)*>ҸpMa6.VZkEeo4 Wklbk29I)V V i^e=Q"26\DK CPbJH,12du|08}/IЏqqii?/9K=<d0s_~BݒVaO4UHFI{ :C[2M4'!i/]<{zz}sn%E99 XFQ,&p,@94vl}${`k(`p͹yjd|%><~LZ3~˪]}{V8 @xr6$XcGAS=-CkE!)7fZ AW4Fă$!U 0?Q!0\*r:@ O_Զ6;A3\V SXĥ$.~!dJp/y(W [0||(b$uRxUx?mkUQS~)b-b#-܊tFܹ' 梛\U$cS:r}IwM֘|gLvKJT "5jywFe͋"'X ~ku)1-QQG-(=*^ R)v5Z72wZ!u!0ظKBP HHIH(ܠϪqihk}|n&H ;Y @1YV[NJ`"J{5ȏ[sD^G[kK$nߺ҃x)2Quٔ(7-p1u uBUnS!@%`J Xw/SNidΝYFJŭ:Q䩂lC NOH$T_& AnJ.@"9Hg_$BZ҅MgDQ[׿^nY~U5ΘĻY-g?F+F9=>/i( 8*D]2SBqbn9h[lAc{y/$;8ȤdX*PW`nBGړ1*U.^b  -vOhWCuZGGKp0Ty@ևP"K b]-f;Zn,I^qJK|ˇ;Z[LZ9ks6-qD`l6YސAu q4[[(%Cض3yi`ϋ{KRJi$T!t $B/PqE992iICMZ #RB>. fDuqAWALޯD\W!٨j[޽nTё&K;{341c.^g={4zI TVU5 5gOd7[?I1q[W^Vz;-Ad@|^L(;:Y3KE(KpBu%C1yr2q45wJ ؟1~t?犺1h[q$P^+-8S 9p]ohSзMՀ.-dhHwS T3~"D4DoQ( 4`4H%no2oH*DhfB榖#ji<ԥ!W8bhгy ZeckA&\,sTeyG![VgTaDaPy$^oߠuzpDa7N9Ԛ` O]~8{){T?G3FۑLn_E\n@ޖAAG~xqk4l3药>^XꦹWi>~֓Gʳ{]Fq.E$Q:Q<4ϚGR e4ms1и7!&FLg45 Hk_EWx]:nnRÉR=PdqLؽT $ڋ0^ȳRgXS(iRPχ()mRE& M!|X$B֋oj0Jh2obDV=ԛxW_e$}3RA '@ C6mZuѕ*ɖs+~C iHY]#˩AM@pJ̨Q4_8=PKHII6lډ invoke/executor.pyUT HWWWMXux Z[o8~ϯ@cgmu;f XLhP1-6'%8ޢ;DIv~- f/Tz_Gui_yw3uʶ_eP%{rQY qG v"/sE7vfo.>C%/Ц[(6 EE ^Y3M^yVBgii(yU9T]y'NV[(Z6V; j#u̩r)KpT}FR: T,T  TT( %}cU+3}n`6OcU{S.yM (!qӅ)mIZٔ~@.z#duQTrDX]m{Ⓐ8i,WZ;SD,h`)0*Հbყر5f1d i^U)Պ`JOof}wDS_XQHnxv"nrB<><;D.!\lvFAN 1V+x :U B$uU(Iz ݶ[+Tg1u]tqQ1τj@)Ȉc{"Z\ r50^4q1 nlmu 1sE.@ c~ h6ހ˧^04U v\_ 5E.̪`& -2_!dre]#~Y#Tj)r sHO'6W\jm3(ܶ; mC(/䨰!-Sr$F' " 8"m?qEݰAoɡHj.<bH9ўMQZ|SU02 :wPȴ:yBz߾,\W7YmȲlaGphšf+ޜbYL7}ӌx ͧ5+$ "~GH;) foceV =vЉ{'.-8ΖBP:au7lIpz1r rLg* >/5!G,:_ N!v",V%]"IPTc pZ1'a'QiB^6%ldCv2\J$;,*^$ALT$ntY@ kj:HmB$.>բͬ;h *Bē:6)$Hi?E_s\ULhGGf״U5? ?$OHފ1Mx?}]wL%,:5ge oxXM'GbPAxxgofz k̽#;Ak&_𮛘c%=ja3hB/.,߬TWƥ;|`0pmM#CR]uխc$2[hזc4,|/\J<"Ɖ'ӱT(Z}wW'H٧(fjGi>VT);wtXȉ܋eA<(][nb87X-:-:Tk!s.Z0m'9D!QR%AF\s *E])BѮ4ٹ_ PPx9z}Qv;->=ЯT !;]uw8MGfWV&I;ywr"۰P)O#M[hX[ǥG^qc#zY aW]E'H0<%UqpEB>;޸2Á^m 9a'sG BC[%cPl) Zs=4?t+~jn]¿|#CeږfRʻN<LMvhb8soNA$Wr1ʚ)GCfr{iuKJZι >W/$szNSwkenzu)Aa_F(ajFkb;<?]ҍ.NDœt@Iܭp-*4w2`G8F Bj <0BpwKa F ch-/4QPЯ'X0\7:4L '!+j+[iojSל!h~3kD`~80cMc+-Ue7x]apg%RƊDӳPKHIIinvoke/main.pyUT HWWWMXux MA 0Es!( ;7܋Nk̔iZ-?1μȓfQWEQp&,vT4tJF)CXH$Bz؟c&ojifxxnf+ޥ_GPK HIIinvoke/parser/UT HWW^XMXux PKHIIKeinvoke/parser/__init__.pyUT HWWWMXux  @ V'bbv As%D|E< T4+œVQ  pQ[Oe +(-4<(JyE{"~oܳvPKHII  invoke/parser/argument.pyUT HWWWMXux VQo6~ϯaYi7.l64B%bCI1ݑ%%N7?XxG֒Y Wfr|V$I j3,PXo|lW\xՁ j@\(O(v0q9rl . -Ŭs!PB Wr(iЮ,smqT0wH,ڗ)d!2y"R q>X!)$.*?Qp"&Pb>M,thH \xL # 9'/<8&:ix뙰G=[G5eOv,ÎZr=v>M9z'*27 &pnq|1Q&J}%’dZ%NQN\㛧,)K*Pm?Xwpo|z}Ę9 t5]GWK#_җA?%?$1 ^)ep֧|◃n)M&n "(Ml9kbgncssUŊS'4C ;BHapyt1= .{qKg-x˫ xU {[8~_#wԁy ?m?BV4pY7gT׉S}n8H[ߘO9?U[ zXV|u'y9g7CJOouI笪r<%dn$m[Q@{ZkH5|:Υl=7 qߚM+-I{gt-کPΑ+Z4:ý;eЂ0.X:±\7Jc=e01|dJ8OL2kۡTDb1b2+<p^Q ME(KR+p hJƍ-!nd9' l>/xyIޒa5]T ![j5X٣vMNzGLg(ƣa"$ي$)[2bkʤ1𨽁ަE;ޚZG@-RgAbpLz}sG'kXHiFz/xfCȕ,eU'S 8HPj/In*r.Oz7yXjZ IqٗԄ ^D MAJ^{3g]dl(q̊l(: '"=[ėߵ_&w$LHfdV1҆m&Kfw?ę`OI^??];bX<^<{`!UUE2P5e@Kr>!g\oN*%3+33囐/3VQ!r=tXIb$GLA8&vI=&B·e0QJsRi5cFi6E 7uTdL轲H<8ӎglapZ- ]`r/}HqLa-5~2LHzVhs^qs/_17tDZەsB9nJY aEӣ/* S~Gult{C nf]IƜ?m^^ӏ3kY,Xjʒf~@9<ٵg5Op}Ka0O9>yT <ðV{9KJ[]^-f4k'"Rig"$ϕ&ɎKPA "^% Ctrᅪrt?$l>樂yĐycMT !cO 3{Z_afnjk$ok~{?8s 9O7h; ?k-JtiyP̓}mɦS}NSI ?sp*8o&QL֟ufj0ucgo},KΫֈj#nnkss#d6' x Y ;&=#r"+E(-Wb(wVFNESQRw*WwRaJr]Pðy;e]π`4 4e#PFAlESDHF{̆hF*{裸NZ(:+`6[!|]iz/4 QLDE}qtlmKl۲/;a) C ٩mTfUDWSpɽگK*^`l4⸹VF$0I'D,cZX:U >$\n ' (DN@LU`ɮDHd9mKZ?ChZpt1;g/s܊岩nU :r\N*6b:ݱ,~0ĪZȬDzC 77~pqo:A:?xSi/SO"U>}y Y4lmȅ t 58z9=A)yސ_E#{>'?}D) rc>!88#,$Dzb_aC{4E tFG8Ðf` fBbϼRo﬿86tw37͡^( <yD/qČ ؒW: +hG2嗢Ђ;޶{U)u'8Y& ){xeLKFԺj'_Ly@ bV]#IwU]^Ej MYH f9$I"&4{>ǿZ>p^ށX *yϘ hZ6u3 Apl/9#AcB|޾ ZqU lG  G XA G=LkyS郘)j=Rgιfx*"er8̑%ʜeǛHN3߁qPHnÌV| D//ě-ஹCq"V盪ZBbd1䀫gUzeb5aYh *~c ?,Ha܁(,IӋxaeQwbJ\v3 }3%ݔcͷ-֙*ZQ7^6w 6"Ĩc~-^K}Œ zt$R }qyY/Ƨ tWI\ª儞SZǽW 85WEw>g] #L7c! Z9$  +ILn60I (:BkC@Bm$I.ۊ"ZI1 Fw3.5,)ȴ@S]xqr KObb7zĹAt p+:Nx9n+xXIT1w^{Ê(" E8Exr$LqGu*TÌ6.s=da[TmlOFO*~4j)_Ss\9"Z$/,CXG}ۑYrl}´aHHԧt>[oV;ƒOCH#Z\"aº?pcma)8T46wE.zte J,&Il憞>JHQKF*  ~9.Wdɫj%/"e%r8B \m5,J~v/<:_YPW/TLp5nь@ zmGP$ },f̱{1`՝ :q̆+XK#!(ȹPcSci[+= "!g׸ԺxD<귁3;;BPc"D צ]N /~zI-JFxf} P͗5b9چ@@Wa5l^0ơ-CC e"k U=6s+Ҕ=;q/pN/'wj{R!֩xa8wiMu `].S^Y͎<&BV7-{Rً{?Wx) m5z^mPWܮ ȕ Z55T[X 䤊B(Y(#&V\2I RiM 791waMhk3zr1^!uGΗ }.sa^ ]QtK\>Ә{X9jx]\) F zG_ g6:]zOKC@µ[CqimS*2`֐ `Esݴl7޺К-Z]Z(&FltԎ,},r2wx,,mHPFN)͙ϰ)٬ >dQ]ó#bvUQDb39ƴ!=ًӔEgxV+=5YPD!8N"C&|ӎN"܈)_oY=9wVn7(nm뎮r@k2Qxcd/NǺċq;|+HӼwOx0?3lGx4w_ltmϵ*gK*jo5&rMw^Oܵ3cS2|Cews3YnKd+ʾG'ݵaf/XaK'ιc)w3>8p<%<8N;"PKtIv'invoke/platform.pyUT vVMXWMXux Xr}W+y󰥔k#SҚU^R:.4" Dq~O l2ӗݧyrr2-Y<յLUPRIE,5e`rMg4MEJBQ$4"1$6bKLڨHIZRRUyj'Bm(l6t486UJh7/ȗjLXfjYw[=jv%BLnmYbKugXWJ<&/s3U^Jp0f; &ӫÜ>P3N}@M^~8˥~Ncm ۹iڄR:ó(BM뻐FEDXv uHx6ϟb -Z%m5,FRlc!.+iU;8*_k-TwMRVwM4\cPeBW 3z|$Q4:i%n_5Ja 糐#Tm%.t:{)V_t`ӆ5A;7P΄.#F{k>UEXa~C(DγF!dE`X3%J 5g>40ĮgQFbCSxwPx+5c*@}69fd |]AHT< kݶUi mK#s"YZ 鈖j2*1\mTD_P+!!=EࠐN1ld6/gZADZ%BT z1N-{c]\EVe[h;>额 ! }Ūw/T]Ld ޞlq}Rںk[ql F=T=y`hgLWtWghx] u^׭!08YBƳhrW"/ZnxBK_/_hE` B'I2$#aW}'^^L< ѭnd[;=Ta=bj7`(c- d6)JiP&5dps-/A Z| &:&wf_zIgGDJYve%pQMH `]Nfޏv3+[I ll:}i.LofA1Eck?0!}uQmjn| ~妷Qw283ⶵgEc^9@eQO';|b wFF DCCt3CGO?˕1}ǻ>ؼb/Ҹ'{Z`)4O6])W)#qDz3Gt X|v'Sr"w_N()b#Kw~0~pxC*#݄&CTO0|l֝~Gؐ,}px.VhL}pK/.'@ߎbqAb› G[1VAOXiP%QD@QهFKrR.h&7Y }~hZȹ? mL% 1Z8!@ǧeHX`c1%m) F7к-AOD @zC;ig'9h&sVBg@FRx2c,7rMUn? ^ҳ7FI6cg};[H_\*UVe`73xE}lepvb-%e`i[' .<yF;P^l 0ݠhBeyf"m\z1n< ([p\ݮ׎N:Z_&H#0<_j;l>8uTZ7j Q hy G`@NƮLfӻ˫Nhl PKtI0 qTinvoke/program.pyUT VMXWMXux \ے8}W刖خݷz}qv2୐ 8H /UoĝquDD"y2Զ*bܶM[RdcY5-Me5y=*+Wl,<0>Mc?>?uT%תHNUgaSjm]sv)2My)SHrE7NGYվ[43؜'ծ=1cԭڴ7Զ䁠\T6#O1eHU5|%K~\Vj{T[sY|:})lx&odWeۋrK3fbO#sJ"jKk%]=;S?턟w$Ӫ@ jJ譣HҒ"'&t:-Ixd~#O467{CGHebiݪ J=%?Ur1ۣ;MgN"]27/^&~^-lzn! iOʆ۶˰P>򴆃)Uf$(wXPgbY'_mslb8ȒϏwRl-p84lyW} a"l".@t =Zmq(*|"CЬk&;s[iYBtd &CLGmmU5gK6Ar#Afd ÁjymOz|C iC^q!eсo|{~$;**'ojRCcfʒY7߃hvd ߒ07؉@(.yp>qD>T%dhkmlPPؤj$ ur,@o*x<<$[rYm$`P:ZYs"5{1U5BPhm u8%͊bZZgMEPMHijr=~;X*9NgX&o=iE9'iX>jgϓgzc,ٗT%t/AIpRғy[Zy#kZ}?oXY84./2'I1 -Gn:H @So2'x'Īi >qBPpͲAD憴¾:MFmZi{M~|( ł`6Aq!TI|8c?$ͮr5ëM m[c2d`N1)`gq@B3~veg ̶cO`T"b0׉ t(ZoΘ5eDi3Nir ZҬ>47DR jW7 0UCNBlx46uƬSQ̫9i;+y752oA)g"J;d2]D*!D;LJ$F :2qc ;ӮsZ%4%L <,Imu5c")ݑfC2]D=IRr6``n̴_k0td?!d 8rPKhZՠƶHL'`,ɗj((Ғ;ǞHYhWJ?q#jDmWdcXoS8ݚ< Wп_:`aC9AK=[61u~?TD qo5<Bײ "Đ> = s;$S zlvnwȈ!=9+$l?3?#( n&rWsЏN#/FIڈ[4S d̉/\pxFE.)P H""eFH掴ضliJ d_;=B5yjp2[rKLUObr6{)6yK yivpdS4MPg|Ǘ32QNք$͇:ɜl,ޔ- FT:p!9 _niLFp߿xn!?)ëىm0}WDA:ɱ9tݣ-?;S?njk?:n *` 8)nRdƶ Mǫ10&.Ǘu&<>/9aBUBn҅+dXIAۤK+cQ6͇N< HZt܃n|w.A|M 3@0%p!5y635-zއ,x*>Xo=; _̤)ƊsME`7O 짖̘.iP# f7 r KDRjjlWK_Is d&Aȗh+1rv;$9`+& 8^u[x%ZCY#8 U3DC&@ ˏb6zƴ :31:4 קfCzϦׄA=Z\Ji[e UAm 7&Ёi(7XrҭtOFFeű$ҚxdvR(Al&j3Pz aK}YMml`~ $Kهk>l s"Q^gѡm%VґWxPC,Vmɪ\^' rȮwӌwE= {τڬt1w 2i&t$un Q5A A"3vSd7"YH~Po*uUy}кsFk&J9@/MދNU$AEQ]֥җ=vvv1:@liQ$}{j\ul*V{Upwb8`)~:.bydp;<cnmezOGOQEʳ (lJ4&Y%Pʹ! v203,!ڒy&W@" UdJsY@Rj(8<|埓Hںɏ l{y#\vTc%Td4u:;T%\# &\_nMx`&R WOm_>d2^1I_`܎L:83 tmDGugG:Ba4eyu8,=+J+_M9^/gK7oCKfĄH'KTSsog77bGrKp£M!qj =#3"UP6z]Ųs/Dn\8(aIOpzFŭ l✫ٍx]̧lsK8^y{}=`N8e„s2]m4<<6*دYgafJikU>Z[0~UR"܋nX_I5j%- CPbQzW@\u~ͥ*j']Hk+Q'i}' k7}X9Õǘ|\JR0وI~p0 0pdD7H̨8RtS;Fgc"k.~6qnGي`sw?wgBїoۃ,9"鐘7}ok%ZȚ>ǺDAԤH'l~u :eQ18y?=:,l's cVi"$d3;nes v h*䠋 G21 c}<Kz fIO-~Zxӥc%zyg^7yv,. "uuAV:s˺~B|st1/M}E"aP6 6['_xg죮@>, >=)< ̩7=^GdKK{( 4, ғ(ܰNc"RD.<\)Ѻ.qXan ~l֍:Ls),cx: wC9g3j19!>FdaȚ _\^A&eBm<9y)=o,D:D3#M|ݱ&oܩ]@4rFVM{9~/_/Npgð1E4z CoHNq\v|E)j|kȄYH'_}X>}^y|17a75z*q/=NjAͪ]m~.3mAնA¨@Z"jױ }L1u3SA-6w`ocl*WyԔDcLvDt~7ԱN,LvmTl0Lhԏ:k9Iǹ! y*+Iuxi|Ԥꗫd3/<(Ygs iɨ; f0}'Woh/JZ ~o%|+o'v41r[ZnL`aߡJv]=ҴȽ2fᨋ|/jc, z2 vԈ&3JpY/[>]~p/Gxd}^AZ5D[nM yC]GxL{2X6N:oģW w~ڧ04 UZɛ>:v;9\c{c䎑 f4̏{ ~Qw!=)WVmZSa`SҨvߗ7Qn`h{р(pұ(-## Q? 39Zi=IQ7Ό3u'3Ų1jBP^ĦreeVL^U F| ўzD}ٌc_G]A%Y.i岿;.ƲDvxFbUc' 5Gu{ѓ{]Qւ҃"a=3N@#@,S$JT^Q'+ndzo!{98T1aH%='.k'JbPB@orސfm3龌 o'*&HcnَI%7&@UnLiL+:;ѳ Pz~Z&kvm]-LdjkqK޽&ǀbc=:.jc:oWU4[(Vł^W,m6wfMf-2lS-ޛeF4m3}G`v|Xm㽬몖G,{UjQ/^mM)heStQnW@[ҚE;ξɋ6슷}mŭyµC}{뷗cluS-n:qn2Y3ׯ|O}Vfk;m\)qV4yǙy\$])Um4ŇG-9_veia5;mHv}mI>' j׆0ޥ7Gmd~)65|mhYY6flkZ :?Ɣ- mΪU1mlm7٬iinq6ϋv@giwuIgfZn##ب|8kV4l6:Fm^|fOhL׳i'-ƎOB},;:brlswkQJ4abKw̩Պ5mΒFqȆfJͦjͤ : ͦlSܶ}^f$Cdڵ۪i|ctt(zd:xín " On]=~ t~eJ~UdwzGZƂT)رiv*)?¤n3 o4B&'!p:%eY^@⶚),´:/s`&;?Ŋr\3:J 4SOH$ǧ~®,!PASW :fNN_(@jA7Bp`(|rфbJc^HX&ܫft$2ʢ4ARߗ}v+6$@XafEJGM3c-mYo8e}rK:ÇڭYUDuO,MR֌ X$0f]*j*dbĉh7!@.'Hs3$p/ avSfC<1 ^؄8;ۀĪ-PV{VUxٷt$"!iUtsc$Lja4!Mr>]!__įe(Z񔨋Bv_-c7 ޵46oɼ]d&K >#[ C$LVyc,92aP6W|>IU"=ÐL#5T5ucb.!K>:Ca%Y }fKH}Qׁ#xc&Ā Č;3!kS$XzO˹i/?Ŏ8*1Uz bb!D2.rteN,)SWDMjV(Y|ӴIIS'A"mRNp1ƻQ?LSu %̘M j+ U\3.FSB1}Ҍ/h457꾫,58|$\U~TRʫ=$Lf/dڧr;Q'UfŠ1×L8܁Y-NAU4'E:uYKTHFJY45q)wM"h5q?lln-(i\#ӇM`QFzRְ#B։(8Ĝeb5Е`wݓT`%RnUt^,6I5ىih΂y=A~=2҆`# WvN[AM cHEH%6k; $5x|KtXu/l$ȼ,rUFBwEņSf8HG/Lk1ǺZ^ 'Kz$fـ  aҔ0}J $]HN-"7$&$qpD dNbCw&ou1:L DѴ ͝zLDi- C59iyTOls6fa!e >'@cih T9֓!!/jot/)EMsѺ:SN`򉞈E&1ԎQ>$5FE–g5#;VaWE˕klh 5 3bWjnm! Yzi S|It@dATDJEyLEb\HjہϋuʱuB#Mm&j9X۪i Ǐ=ZWv.Ά1:%c ;VFLsiGʛL|6 iUkBK!TLG N~"G|_` ص%N#ԧMހB3fvA5sj8K50*5U厡Yo͍xᘞ#OXq&)*UT#*}ҎުjI7ag{,hMywz( S/40 p } @E]prEŔjIg❻_.߅v 3|X 9"1ڝ3p5㽳ozWoί+}BdOM#l26 QC]N -|m1Q0Kd]7U($mʋ K ~ccyJQ֛Sq8FU(Q ~enBcW[]@ߐ)vD (cW2\ -if.2Oմ%")/I<]V ngbynl0%[JaPrhk݃z5ao#\4a'v!i$s(u<6`Τ]t:y] :bgXFdDI\VM>H[q@}d%1I9 ¡.nn'=^qby!w[$ZʖѸ-ųyDm"1Tcme-Qɤ"}YϜiDxͦB 1rr@aHm!9s$l4wuvuie=WEeԒ̲}adN聦C"nejXhinBF[˙@&=&yQ@e=T.1tqꎽ 5u_w\^z qvtU7DMơ@ 6(LmU馸 %=5xo3{aG{?>}6?=x9J=Q8%2' N<*4!O/ƮpsIp'C5t'{Է0qK^d?+gR.ʘ4l I٭lAf?t~zW0F#'|͋8~҄ ^ a~=8uˋ& N@,S?k&ϑ1G"tf k@n!Zù8Y:4;#n ~ cHf<9x3HX@6z} 74  Ec'%fpYׯ9J~i:d9~~Myʬ_wJ,]yJb#| b#K;Vx{ASZy,F?"C$JK7_<9XM3 6hl<>g1TX)Vtu|V$IHؠϳjY+BnLeNmL<@`u)ihji94E3碳VN+dBLFrO'y|2%%dXɠmմvGG}aտnowűҵMȠeq&i*1` ri1}YߘL洗p2^;wO!(q[lMLv.2 ӬwF?̷9L=qUsd:T"H챱 Lq|kMl^Nx=.Gќ[)،/ 3ݲGIIZx;qXk.{EH֖>{fc- 5AAͶ~w^Pp,ލȬ"HAGAH4nU:_wOi"C:Ȯh Kaݙ*~M™ݠSI y1'(͌a7RC ~40d(`dŖN<CRB'B{ל;AxzlD%U0!4lnxⅸpExjp:ksd?I3CN'j2X%Ve9pJh 2PI\(§p)qe ;/E>VDE8UrUG5 3>f #i} T Kum/3Y6"(1-4MEexaaiLsDR\Bzp+4&L[9yKf^AED XRXh#Hr}/j!y9꧲sNsXBs١V)ar(h lppJ8$Ԟ$LuGV#(~BÔ_~ϦU<1Y9c%C7"BmnxLjUnȲD77MZ| BC l'CPnDls^c>G_@5Q7t&en+FZ'[iBD{[e(>47hbku[R[BmЭVU=BԱjfcA,.=o8VEg'2ݵ8?LGZVo] v*ʓ3=3^ckXoa;+}-j:NG ɅF͚%q3`qaM clkeG-vR&s.PMgAuG25Qa@YTPXA^L#xOor8'ң^dݬs2zQ2UmJ_ Fpɝr1Kֶ3*TGAl53x$Xr7ǵQKv@Y^űΪe"C|*s5"0 9"q>,T]=ժ6$Ըv$J\3۸s>)Z,(zO! ;+1h( -1F!3a "'%[O [vս4k +:*iE5}DUgVC.D~iwr Cq \nzk5_Fcj2*/ITv8c41$͒u;T} ʉI߫x``2ݛz:kehgH%Gd&E 18nw݃u(@6# i>-m6Nx;Yr^=^[>#1?؞>)sA ̨4Kņs&y "X-1ީ6DaLbۯ#x3*~g"I Nڜ8HmP[i]~i)бvyaN,3$ZՍ˚0ZeՖViq<ō^*-٘jߎeWrЩ%Z̙0WF ]\GiQ /73K?;tKZ8};k:EXh&c`8[c5*…SߣnxRW./ELW/ 4Ƶ~L)3F8[˷E+}/}1,&zJ&E<ĽD: o>UXHw7 !1Οz1T Fl 9Yq_05;o.l5#r}z&C!;8t8$؉:}m|ۈآ$Si5 "+ 1g$HQ 'SbCe|au7I_$>y gDd*2{<.bu/_3z5~Z0}j7gdroCSnuS٥t;lo8oBNŽ.JɆ88-OO\NdϱFq87dubc-5|5{Xm܇ʈޗoQw^9"*$K3 ʵX/|{<L]^U3%c9uE/ꪼaRjX@95*MbmnjPA sNGC{+^TXWhI{Y\)M1ol 4&nbR kc5(s}?{Kv$UlzH$2 86${, xmYҘTQxű`8e^ E֒:A8F+ľl7med߻J8—c߿VMve`OXLiuň>@W+Vrz,Ef1]Bj) >:,P8TMuc Ɍ?nNݘC]^.5g $scfp )' =dC^w\=Zj.[u5}3"Wf4kX?}hï$̷Ԇ!No~ ߔ/xA.mZe`18o B>=m> 3-ծ큗i~Ny:G6Oj #Bʉu$ȏ`[CƾVeA 7+dAz9C)aHps}+85C]<0ni=A+L#;#]cHxd\}Pyð56TzJzZ+hooB$rϸa0ѢޟnyF%u>%ȬV7i5o]Y#>Ѯܚq)XIG#MYm9X`T :-?ZJI{a݅0 \k3soHܮ,5tӝ%sqmՅ7"{ )禰Kϧ_Iu7OA?Jn]Ö5g/[íYʍ)W;i;m.M݇zJV >nr߀U&#?.n~4 拨|փRV#<#4f*颾ȵB?pJp> r!P$7X%'^[; ZUMiw =[. D/Ì 8I{(}0Z2Z`(Z$]wy,E4kwq:4MqWgq Px=t$2( YS mߒƕ+o$YA"wî>ir\ Pȴ1YdYĭ{ |Sy_F I1qOӥ>H4l%+D Ҏa# \m$y).o$A]v~a|9YiFA*(kΙXuu.|EԗON$`HBWolxw=޳G^Phzel҉=LGARo]Q^iG_Ô/b'MUFMp16ncZ6"Nּqu!-z ՞]Ԡ(GRs'N,~1E=ξ+^?%'0 )nִȩ5r noh;Cs| v73`8G:]քz-'nicE!g2VlPT3)H׬epbcxӽLW!<ĽJo|ɟS #x`+0塪Ы W,c%k&ݨ7U2V{uuqF~ҐZ`#jL͗9wnD*<"Q݆Ҷ\A2 URA[6jЬߨ, t? ˩Rar=ŀcZs` )8 (ܿe/0fUzq̤ڊ[>]zVCDVgZCB{PFd"[#qC|4ʍKI&Ws\,ҵa9"@U1'Otn9B563Yڈ/t/`}LP,:=tt(nra$,mW5Zfy=>1ïfAZ{/.^ԵOCҳ>CH,vR=JU"Q ^@chPڳ&"8{e$ULIחjoF(IahoWM6)ǐhԯWp}ћZ5,ō&Ώd_v c?j3NHG?쫱m =~opUkl:kSǼͻ;vMmAkX%-X(!HrOV@ t" aml` z *8P,'JⒷb>CE4x}ch$HyC[◺uiKGN$|ElyJ*@Sq<$|͢}>ۆ +%]馔yMXbשeC؃/cwBܵw)w>\,dSm KjhFUNFDaxQGk|7a1^%|*e>54!K`= R쫺Pr]f6BCq37.wKj6||6KEF<[%:qP"69x8%+܅:6% $zϛ%?-7t5V'Wt%Mlħa%gN -xr݆̐اfߗټ>}о]|'-I;mEzȰ5s8-u)|"n08^^_8(4GЬ蛑34)vlZMh٤!J>>rE%g@Z]Ǿ,{ P`vꠉ@Eu}AYW_t::;:-3+>uW]OjѮA#/y,.^}}?4;`BG,T O"T,4C ^l'.j lޛo$* }sû= &r?v{pT+Y6d*WQL%;vUP@ᾁg:֙wnZ}mbseh~ˢ_ɩe,m |J;x 'u5?qO~go_/_~߯XinO&?$JC1/j\ea0߾z?'jy^5qV 'ш\ %oՅ *EkwBˀj&WՔޭ~q)IƮlHꨗ [%׃ZߩT WЭ^$ޯ^;x<^^]Hv.. \⃛ /-3d/(m_\r+b68ؔ:׍wYؘV@K6 Wӌ$ mQ<= ڗ8OO=~F#kD+Z I j]B~K`^K͆C{'Kg^!,OT~R[8hdiw#A4z%5y YBMn"('C`˝-򱻷`< !ÇZ"kt "wdkw'RӨ|2šo[ cY2|q@3Nwgv'ݧ2H۔ gg3L'jK]pյaL˚^< /p~MltLn++WTnm*.>9>hyC͎qF/OhyeQstnMQԭF4Vʸإv/ꥡ9>…zTٞeý=j=Kj# Fv{$SD-?0T=|R4OOFOo]ǡn2HHd^]ڞg=@NGnb6^cGGGÏ]lc ?|"W=ظgzA}Q0+;_7_Zufxu&kB&]|'}]KlH`AD?M]iEO| vYg]Grlkary}N$OzNbιk ;߂UNm#ŅnpF&q>$]tf vE P4pNZQ]?=yzd/ >L2Td}rǦpgx _uw,ӂQiCǡbC]HzI=Ϗ#|OܴF v8a1#9Ϗ:J z 9%1egٓOB~LV|Cinvoke/tasks.pyUT VMXWMXux ;koF+& \N~snI m&EqaHY)R吖 dD"gΜ9FŻmiծ)ʨuSw5j~֕VW<4ڨ5U5ꚋ;SxdTm6!Q}v|SfݹqolĢiN[>yܛU^uk]{LMUV䢯 Z'Unrz}iG_TM}glwa*k>+mO[M{jPwJ}uTJAWQكUMmI~ŋ_~\`dBeth"'ٳgX[SzՍ-4uFnȬ6Sz8oޙ9:J.^'}.'D+ѝZE ep)Λۓ"3@IG9WϢcƃ5$em-&(0ܚ>_КoEPN$POd"YkdE ?Ճ*d0tݓԇgOڏ@ۜNojLowgUL;ٞ=}K+F\פ  6 șjFkKpսBGвWpIcC З#rv`03J@Ȁ xV[ B|rB6֎,Э!|r];mN^Fl`&+(Mc_~r+}bfA%Xeoۦ$HnOz,8˒W j ,ĐE~hw i: G׍٦GƢSA Խ9AQ F+E0/@/S7|,=hD}=aEEjt !zyIM^0HjQ)@lX7JB<A]iq$C7n.1mLyĤLv8W7eQ{{7Uw(œQ΅.Zސ ~a,;HgHFT;h_΢a.4I,eI )\}9d)C߀{ G1/"C3]ȲvU%:Z(M "H|^C~5%FZ`D&˥ځcn cLB⋩N̮uEPD6VAfҷ%ݾ;p'Xw`ql5Jps'Y:?U?mH l (2syh KfМmcT0F֎_;tݓ%Gxx-)F%Aj}D}&ְMdG̓B%Q@cʩ;Z6!C;rw-I=_<(h<]0''-qzk>57-p J8!$[ D&x-H$qYTN.æ my${gI}n_aY1tw= `) x!1IQKԔ=>,@dgm]V?T\ &]nd-LHzvh}aC0niEu,;14gcC9C*"/lP@:=^s4acB?@d5kF 0F6(@^_oiS#zY$sd%#^ NC8DE5CA߶qs9$Zߛ'>;$o>ǪQ&_]fޏo%Grн֘'pN8IßڪAv  hбg4ayb6߰X{YvVx1FOs2/,r@@HOӊ,RMlujt 2tԨVͅѮ9u*N"[mt =HE*PFѭ8RA6|k;hHlMG10ƈLuuψA)s'g+Z{jl0>IPb9qϯ!\ fοa|~\k.$rƘz=]aT8I ^ҿ^ GL.? " k:PHZjQ\q)cZJƎ<(B4:{пyb77mc+Б8#vJ£zdrK #1/!I b/ Q9aƒ ˛5fFFL6uOv_ĕNK'#Z?TSD4geu_`=т cEMeT]\2z A>$ yFR*5e l3I':K`l@E(="C=!=BvL6ԚUI tBT<&u;M%|0hy[Xbo-EͶa}a"Q8J,OH48w`-2Up4Ad^;q! gS P93^1`/pm=٤}ڟt/]::R relCҍm000\"8^^^V삥C 4_Hܛi ;kSxp@VsKry,ΐD7gjx-s+X*~hJZrFLCZT Bxc?RZǃk])~)ax1D1N؎ܦCPrӝ2nc,Un6uKe5d F>*. J[ \SW$MKϱ0ͱC4"@\ f;Rb[,ѕmtO2.x |U HBrlυՈT4NE>-[P6m$(2sT4JB8|=)Ue.{=xl#(7M e=&.1 V [a̼NOUޤ$r|" D#c cm2VpiQDN3AMqIl϶Tfx Vtk8xu(tid:8ΣkZ.l%3?Uv̶j})jlSrz,s=ɺAOoVo~}'7{ZloFƔK=\٬ #ךVr&~ҫk6j%=xӛerc^j1kApxaG>HDUѺDEUMǏ J<( k~GZ ]>Sr[ ݇f=*\bÖVl]K-^_˒rv K{)<{/fC< eD6Ηɢ\[Kbr<ҾvY1rЈB ^ķ`!pmt[BL@n  PN3)OFy\,Zsal=[[T ]J4bM|Z[;(WC/STࣩFLMc&4 Cф2n)B8x 9 ; oE U `R{C~m/L+CAckEA=`܋R\?[5VG j2ta9婷N2n*`zzqAP <wZLKlBڶޔW:hJN\o M,|V6d|Sױho$xrJ  N}Ng ǚJQ%9r "Ul'M"F*Q!Ē˨~ >.G5El$mRHT̃ehw8Hȵm/ A@[{S|c$AtPI>x,RhUϞu8&Q;@Cȡ)[.jOMe;9Xp48R$dRA[Zx<@zВLל0Zf#w12<# H9Pl,%`L1ŻHdQڅ'z i-#vRӺ""iP/G| mnR66ׇ$BwSl-NSKrP`$ ~98UEx5ƛ*8o%D:)3Ujfݷ|G嬟DCM(( ҉)\ wW(ƊK35ayAo9-yzv2*Vf)^?Htu*b0xykI}CYjWX𐇔V s&yCm1~-č@A:ɽm)e<ECpy";QR> <`,Fg2Ȓǘ)̔D5zIʄ CPiޞt%x$YsܾF<+5&[La%}x:iƆ"\&3ԣvMBҘ.mpcI_G&אAw{}[fsD3yHT6advv kT~7W6`X=nLф޵{ggJR/,<Džο|C[ DarHzi5Ge8Ǫު1V56o~@ucɋq1b&_/W^c1}p|`=K̓G[H(,=K{'Dkx4X'NFE_$|Cmi|_U'?a LSSvO>{|(Cq^mڄE V` r|{wsM8M;am ρqȕ<ID5 )4X0x [D5621s)昋_vGF`dmJoy;z83MzO+Z9]G ~'YmҬԸ[',PK HIIinvoke/vendor/UT HWW^XMXux PK HIIinvoke/vendor/__init__.pyUT HWWWMXux PK HIIinvoke/vendor/fluidity/UT HWW^XMXux PKHIIJqi"invoke/vendor/fluidity/__init__.pyUT HWWWMXux K+UMLKU-/*Q.I,I(x: %Eyř%y:\ g^YbNfs~^ZfziQ"XL4xsKRK&e(e"PKHIIRh)invoke/vendor/fluidity/backwardscompat.pyUT HWWWMXux -/*Q(,LzeEřyyi v :V\ @ԒҢ<Ē"z|6q0tjo%sɆ88pc;;Vh|^B`Hz|&pӿcλ> L(?A9뇻h@ȥI\`4g:+6B{? BZ?v,5;Y7}DW tUo7;{[꺑g"YPp-@Pi"3}FZK^n,J,\ dLϰR,sFRoTRDV2f4%q.FkֺR@ iKY.5BUWHkYBZ uℚIpWZ5|{]wBϑ S~:^PKHII !!invoke/vendor/fluidity/machine.pyUT HWWWMXux YK6W[GInڢmaLj0(:mN֊<u{bRjTwYTju}ˊ>F[Pn7Wp[߉ ;^`_9bII.,}8'U9E(u FHkT+_¤p&T2Nzlxu;3QyYv,6HA?Zog?Ǧ&MhduÖY,4j@{^dJ((FAq 6I{"9A~vܶ=A|²C#vBzH%׸(nA}xȣLm+4 o_s?p1fv2H Ujce66rڗ?e yJ!HkSE(C%Gz @TUS"Yzzavmz$h/.gLҖ]C7b^!>)4;!n40AFT3vѳ" TؠFt9>!~&¢u j]F-7+_5ίcE [? x2( FpxVOګخW Ҙ+e}^b@E 60r[ac tgb{P삃ڌNiW \Sj  /ۨF{Sd;(3GjzF^9$8F7 (y3Ac)|߱,I?L˧l#hQZ]r? 2@Nm i$Ą/A Kk>0záN 3Fqsrn,i}Rs/?/)p$Q01ԯO|,p>atCM[tJZ,0lK_R=W/j8L`eOr\~#M|ŏ:AЎ{s },٨ōRcDW;_ k'k!X&mvLi1@ڸĒf.Q/譙40Hs 2u_%]y.S~Kבi)"瞱HMb_O#(,ðv' |\&^"-ւ 4 8x1Peۏ+%1>S6 {N #_KtI,G_4CC!Ft)K'st5GD~@SL D]y^NQ"9'lDTf>UiAsD-\'Q_͊X.&bPx%W<(7z:`C$~)&#GUB7eSGXQ@O:(%`,1*-ȦWNp Iq};7g'){v%'-ù\df^$ `Z{vLq}j-E Ia\=S1GPK HIIinvoke/vendor/lexicon/UT HWW^XMXux PKHIISJ1!invoke/vendor/lexicon/__init__.pyUT HWWWMXux un0 ~ ;) ^ ݡ^vA脍,74;LXɟGV3Gڏʒa~˻IUwt+[f2NL*Yȱ؁R䉕I)n|8>`BPXd7 i~`$B9յ\qݩ(R-<&F9Krb³O6:&tGM#3z ]|,mpdjjI*?cdqi}g&yD: eV,mG6:Hp?\%Q: C Sލ3PKHIIp C #invoke/vendor/lexicon/alias_dict.pyUT HWWWMXux VnF}WLIu<$)"/!šЊ.ui.I7̙3g.Hub̌]:[GHHu+8cUHb>9;򈥒'y6 5_ά vQajϛC{Fp2zd36Ͽ.; -mF91#{g@FM5ˆ S ;a0ϻ4 3PJ{NT#ހN*<>Z|A|*53ϤKģ ԒWadhyX[Y33w? ,r \C% j' |xKL4'eBm;|7AwHۄ,–qXXIwL6vF4". A6RpdSR7?ktZwB>ѬCbJ7RR6y6lLݘpaI giļN*띬o"n!7dhFː2N; E3l*.VইJrJXFEVďTbέr=TPh!' g}GIBa !ݑ-prx>WZdsqWqA˅p%J5f"L. V27"J/3:-ym$\qMWr RBk pUH`xiPL:r@) BCmF&4c */cf,ДkAd* uT@X:-8D6#vbe\H>/L2C%R윫#}E)B 269aj&d_%yG$Vb4Sk6Rf!hZc*O(iZ$q#1APTtMCʠ,obQ,4teRBRmh &tB1)"ДXj.Q#bYRjd@٠7+ԡh78h9TX^|I$x LxNbdioqPKtIXMZcuinvoke/vendor/six.pyUT VMXWMXux ]m6_kR챳RԍgXvN;ŢHh E*83n$T"Fw H,(!l)I\yY,Xۭ\ ‽+vlixlɛo~~dxK$[C!B [/>bɃ[d tegBzay f p@+W$|RNyB$~" ?[Xz{\q Wg?nEbidO!8 p!GQ9 סI80vI.6" jĂJ> 0G(` ш ٠bVU.&DYCTG}5ȰL(yR6!KD7Vo5~3v{6O?_Ml "\_ ul/(٧K >(%;}:Ӝ}C1Hwr:_MF1q ˧1Vbg|rs9O8N9l5/'*8:ĸ Z-!6QLdwpW##@e ?؇ ȹ6?t?6=/qcqBJE !\(&# -hЯ$FE?#8$0ؗn@ny,  =&a 0ժF{5!|UPRNsun`q*TdrAԼL =6miR +Ȃ+eOdEr?^'qiR3X6}(AkUȨpl&cWJ?u8Eðj Ǵ*gP˕ IvXeخqgBxHRSm]lNhsi-3]Զm΃0/'8J\5dp]]{ +a0=hgGUG0J(H4 Cm[0`OӐnsCc5Hhkhok>U1hĞ;}վHՄf ̘ۉD գA> i2M=MUyw<3Cġ玊EyLW@vɕAx509[!,Kro0I͚t"A;❽;DK".̷woN2D(nqn=sL 3|kQmE,,}_D ]>6QhOL'On0g 5m94bcHÞЗ4jϯ߳Ӈ /p^5U#=zeXU%Fjfk}X Nɷ ]ꅂ3e qX!mB@LSܾ3ŜYw{Rrn~m WȳӖY =R7(7U:1z͟YⓀV,$_(x` !9 V:a]c2$K}hN`(D^ xڼZjɠ'~[,Vk.J9V_iTn(Vטs%3i`:^* G0ق"zfj(hU' J؂ƛL[-M'7Sh_gmդ  (ſ6+.բ?{C9h+ }}O:58?7h?f$M74Xv+p|<}8OӶbH~ a5{* sQڃ_V8WJhcHh$^|Cʷ̰٦\|u.XXy7\/8XDzu*t\&hXIE.y^z#)w  0u32y[0''i)6/}bQ=K#W-M|텑a_g`+ APs5_ի)wI 9LziG +/K}~wWB^EMJ)/Xz=a Sgd0~xH[ތThw#m W}e2&q%G&n+Uz=|崼T,C&=PO5ɵ]*ڶ7σ^z߭,~J@hsJ?Kxcn~RH?0>զ?WGQ}6wep,>Sxl)Xfᬤ]IIGܖJ cЃz}<ب 2Jف[=)>Jj"Ұ a'g)We۱<,Wi]ksť *-Tyz9.''Ŋ{$ xHEb; A|Ћ{濃N 1\nYo=njv mdƟ ۧɋȉ@)H⨧!is!p?o_"mp 4&_B5^> l!dg\t ][ P/埽l_2=_9_F/T@r(ǣV #n^Evn_ߜeZx(|ȱKx3قZt3(#A1pc|Qnu0@}xzUa".>{=Ok18+-C҃Ϟv?ihe>ܓy5KW6\?ko[#I^|^Loԟc~'ىA>YzW9y| b(O-t|&8^k7^aK%]4vӨXq8G|f$kf0Jc^ϤDG }'ju0 R-Lk=Ύoka SuUTA?mepGoiѨGCԇŸn7ҕ642ՆWئS,]rvE_f*jBvS*G‹4. ERy.FMx646RE2.ٹ=x#0[x}k,{zaWnS B `r2^y J\voHz^6ץ 9kLGD}c۩D*I99d+5>-m߁nkaa5MqMH{&>)&P%o= GuM:Ԃ)%@ -OCOͺS:9Ymñ:VW=?Қj4M3`XR[WG}fbrBC!q o5tjPDӁVT **7G<鯸'#^+ٴ^2#?ph ۘJE,A$ ;;&8Xd [زnXd|Y O_:qՖ ԲVMeٖ;L,=<-F,QgԎ_dKẻ^ TCR5- UR_pi^3]:c>{#SN5KڈE=Ɠyr3X>n`u\4m)<ȅuVA֫'@0[>͍_)\XŎ}2I4X )X p۔L$pȝ(QaW$n)dr\j.69ԁLi(2^ȆEKiBG!O?5p.4^*[ɬE]]Uj'`VfErѱ{ |5Dv !Nf) WUr8O?(v30s~{Og8?qQI6t >E $,E o4ug;;J^ekCe}=D#P#D% DMYQ(=΄ ꊛHJ*h `CZMb9dv%?z#,#Bq6@Ky~dW׆~X4BY ֮7n+ Ivi5~\ NCRk j[7(;C$o}DpH_= *{^OLSP`m B#1!Ɯ)'Iʭ#P{#NwH` ޾ךrs_s)$_spK#sJ@jĠV%. a"\d{ɑCv;$֧”2DŽmC^WM.4xZH,%03٦f+kͨ贼Qr@TK7QGJՌ"3\c>6gOچs~ەws%QGm}ǫ.IyȡahE $dai1bY0 '4f bMEo+>8Ϫ]߯WWןCq-hǫ.oF=AFϰteR?.-[-ۣ&dSv;4,7nCCs{FWP,g0#ۺ.f8t{UuhX ^?eClTjצ'iYdL4?{6#@ul4ه8ϓSˑ+h|;`eV!)6;uqSgpw1ȵՈ˦vѽ[yE瘶 LI(I*ƬR6$Jk9Cr8wuɄֵfڲ 5VTmuwƪ.髫B$v'Yv'uelɮ~\|y5XyCJ,v)>1_r ɾF~'WFL&X3jj9gϞ52z-sWhrAT-]OQ_?_f4tg'*܍A'+TgT/WH)|5b<`;yW>v1x@Iq 1|-NT`=Jye ӛЛZH׃Kw_w$q-qG8Bk(')U/ڠ^e yaѡ#В+۝z ˫Ʈ_t5gV\bFQ|12: i`^`hS~ԁаpH˿LcyCTӸL_{}a6%HLrx3W-=\Dh?/́4IKfOy9gH|YpO8?<' Hşp'7BQ[cyu) 0Bc1HAU&{EEAOa`wmV/{* +&KŁ3jq@m br#V(jpG<1;(\x~$JȕLZ67\29 Eٿ8DG[3nz4g2-g{FD-03&ܔ[CpJ:`Z:{;Dv=y :HGN7Np*/vTCq9FoO !d-GQМw;tJd_8/9)8sſK% Q3I1 dr94p7΄㠘r5dvm&67`pʤQDkk$؆N@%E~,DGv84@ߣ2|,L]4 #jE@>7GIQEKɜĐwesX*AP[6u1]43G:3*=]9{gQĨ4iW$ ^EߐcYPKHII#<9invoke/vendor/yaml2/composer.pyUT HWWVMXux WmkF_u[i)9 u ܇HXiVvN*pl3ό78EdEnfrWgלW|v;d* ;W\ uCG`nM~Ylbދ8vhطWw hLRH,q<Ȍ풲d[K%BKd}r[ W7WuiKkg:_ |DpA{&3r _e*iJ&}yA :p gȬCf,S+~B+ (MHO"?$_t36}>ɊdZ@ZNX}Zĵ]#HV5 b-8'n$C#1*n;q^f}yrzڹ J͐kTf":~$sS3BiڮENUD+],013BhFj Ɂ)+S` ,y;[D={Pj[k瑴xeg#<0EޚoFӊ 0PY[qnmWf -W=4HOL 2F౛Y},:%YIaߖ4k0r{ N^ XF{Om= oZ .żgI|mPKHII܂y9b"invoke/vendor/yaml2/constructor.pyUT HWWVMXux k ƮASna$F$E0h O\ݱH|V>OFVptrv3K,IҢH*IUU6m]UOte7?"M_5/Vu蜷UƚL\ei|Tu^2'Q&Qk&Q۰lYMDGߥ[~g`eKQuol wHݭhsAdl%I^mVyϩDz+-ug=0M JnXTp1_ cS&-qe˷ D2CԢsw3N'~Vu(/b&+K]w]8O1X >3 tFDhUc>Zl p-Rmy=3X!\hO7S~`J܇^]@&_c#(q~> v~^+WJUNԕ<Ӡ)oz۴Ů&(d6BN`9|8DѕbۯL%C ;`5ҡW!=)>8(C=}u?4I^s%6'!d|Gz3 {ez9GcoC{ɊgS>NNf"'x%jM PšڢDžedpKeKi_u8J9᧳$z:ދ<ᓺ Hd"D_X8Y2 k i|7b^.fSށz &ܥJ5ą$9R3 ;0D*zvTYrW*_`so⟿X4p+%{0=<08}vy+a5#*^-dzSW 5.*B3) BC͂6ƙ-:(ɻhx /F̈́0A*FDaX5ɀ6ԨhFLD# z\Z?t>{f aT3)1e!(ًt>'sE KU5fA.T.^35 GH&º/24IzAڰ6yΰD:O1&y:4<."#+gup1-Ewxf7ǃ Y4Xj&5[[y7* WRXrH,rVdn{8"-0~2VCC{$>UUŏtEPEpO\h }eXg\= hA< lmh!&҅+: laf{Dsf/!dx%z$] AҴ~8:9V?q9H~[wrw+N3F!Ұ}uI-9%CKdJxLBJE¸zW5qĖ-m<.\ sB?|=* $zҷ"A_X\oY+th O04d6B+cjX蓫CcX-cU>cP)qe-IBA mvWۈ:v*ALԊd:ԫ`P+\G)\M`DߔTT=ASC+̴n#sE8~tL}d˻D@jCu=C_VjqyHVir8%G .Xc+aq 树qꭄj;YTjjKo!Є$Ƥdڏ!x|(NT-%v{ ׉ES <.O4}(b蛪^E}Q|QDAt:ukLlK'}pf)))^wKc^^=Ǎ!N׉Z(/e2)%'r9<2 O]ӂ:pfu9-j% `JehՠqM ZDb͒Lk2s|Ncc"-cƼq'6^ C%AuõQ]o]V15mG"=sOvb+!rGT;6LPKHIIVؼ"invoke/vendor/yaml2/emitter.pyUT HWWVMXux =iwF+* -JCiw6ֲGg{24h[dV>PW]]]wWX[gyZUɂ%/IU}ï,J Vv^dYXy[[geH9gGގ^m;xMr ף/z^^MbD}~-?I>Iy|y %:F l:x=%?Ehm)iY̦lO'լJ~`q>eEYg<[%HQ(qAS;,2Qbo&8-Y =Of(j&^sQZ UR2rR-I鍉7#vJX9 ŸWLT/ ܔqJ"`;jaNEljDHJ\E2h5=.X;f_Xd4"JWGEQT(&qV:IyZe&ʫgb:d--tR"`8\ l ٖ N,ٶAxSaJV^ƓY!B̚LsPm>^CȖ_# e˄]}?sʊtA@tCե_U8Lq]M)Vy\Uh1I+^ڮBk7,BoW=;2J s/{; # zv*<=ؑnO&zTZF'ydl?r x5k@Xf-@[u~2ر*"ћEr/@F|2&0p\ZcYK¡@s#]|^*sM@X12%X}ӬMʤR"=[ꁏd `@me_ѭ٨ wAn0*6\̞k@[Tn1"V>!!љ/ް([o6sqIA9Ga >VjqDx0)5AA8L5A.6rۈc>Z6"c-Sx^Hky!aQMyNM$#AI72VЯo^򿛐˄ld(GYK:8QoP3Ξc| 2;v]cv!uW|uޅk5}GM V&IcȹϡiSx.Ke뛊g`Ѡxf1;wE0U mL=DŽʆD qO]pb( GqgO>%AAW$I:Srz,ZU b 9%igL!>|=̐qȫX9 sweb H0pQ>φ16 #L/\5 X]Sݬk7 7q->bX~Ǣ)#ʱvd3QDr6A/!آُ͹L3V[e 2o>DrdL~6;'=qi~mA -m6 F {# co7'*]8Qyk{+ 3uKCrgki z'ɣu7Ce""@nV'Tr"k:@[BIb&[кN6*Rꇏcn%NnF7wXi^['u3t·F[ -t%IS9Q԰΂SY$W1 x>5'@::O^?%sz9Zy܁Vv|Ye@͆ 8Tķȱξg:L_ĐK*xnZ{qi9A{e?;GV6MXfT^ cUtX[:A {ЌɆ^XWWmR{1]v7+l"~bqE< 4HÓkA^)Vn@lH^6}v[ONOc-K_75t?p[{:o!y{/tˁj".'BELoRzVǣAGCÂ;˒;1ȩ\o+hl^=C.iꛇm7wzԪC'ΎD2F!q$"5Nu(V0d+1G 9o'=MmǜI!g3:q Zu*pPhQ7ez #Ns7R4okv`gyN.+sED}YF;N6vIC&̄(qBA{huSuYWcşkzDnFl?qz=}EU5.49SǸ(sLvGMz(òa֋1"ח:BxGMm1ܪ޽Am;9U9z:M pOqRYzhӨN8>PpLᱸٲӓ}CRHڤ/,Qa3Ƌ譄iUcTAj;Jv-NuSuS^,i$:yY?\Ma1 N0]X{.\[`p^dfa>"Z6]6`W \(&omL7gfˈ׌ЌbtTﻅqH|YVY"γ9Q #"e4֎~z0H[|C8lc:M$UcI/ZV[AYF>էTwGpϱ{#[Focй2G!H`S>- njI#3Q@2)-)y%EJ΃oL7(+E7_y`o_o`N^4 GB\Bӥ璎O}E_HV{Hb L*{$k!BI7 ]Yw0#5jl pq"O0d^~s*LɐTx=Yw_EU_ij|t(d~$D^ʮda|f+IEOG T>uS%pWn4Alǧ?ţ<~iLU⍗ҐWǔ["^[&#Eh:sA< T%dYt t܌3~7<^<;crHJ%u'|H ffdx#Pѷ"I.쓏v<~@㪢n N/mwxx"3rhIڱSkHgd a8B"I%M)ԓRLaZ>nR1 [;4L/ =9 ̎+IH^t\_k{+ V*ۀڤ ɨwO;Z)~6pSy~ ?G8ͽY8VihMPÞZs$#,\1PTQɦ`|ŕ}ߧ!4 ۓeR!g ,9K7dl [0GQ"¢KQy2W]6^>VemeR_ 4S­_t4GJ`w(oΦ*aRENg9_I Sl÷9_ޘIR:M(rzz.5,K[e83Y>0" ի/Lçbϒ0x>h >͂xL^$bvJ|c^:BhQgtzN7B.\y^wtaʹk))FGތoGo^<~wDFKQ:Ǫ-JTnɁ*ݒU{*9wK^S%vzUtb8U{ U.IJe/J%Q4:QC?h@ڰ?<9q5BfۉfezD鄂Š5$]c#e FZQuwRsPJ< QM' d 5?h R*d>:G$:FA}1;Owǭ·C-xCy42ك[ii> eZfI;7:LT$Pըye(a?u`:,FEӤ8䋒4J#.2%/G;Ix%rcxJ'9xz" n%"ꎢ-[&F^M^uAs3xmnK'gZ% #+vO0о6I(7W\I;>]|<_ m?g3K4wkv,]s:Q㯑=fBu@[wd|X3 [|ޢ RŋEQTʕ,k_L Qhs*oy[rnmc]=]goO9^9䎜gx䌵H@IUxcJb<$"N n6w;%C`xr/,H6%>#тYebIkmt+_kI`?ζ5xhaMwN¶[W|y38er+|ǻUKfYqT5M+UYXQMsTȅº5 Umt'~N\~!r yMd1o1QtC~b:90VFt\Bqc.ZCZ-Io<6X.2Y';q yfn|ϊX5Ku w>gVi㪍5[?nM^0K%^fߘQu'GPKHII+linvoke/vendor/yaml2/loader.pyUT HWWVMXux ݑM0=@x7uadi̤6!) ȿҮ7_;oIGfIB3ѫ׍U%dyMŠbL c=j(_ d@g:4(s3Bц!N7{B:)E Bu7k7(dumoۺ^vyadXӃ 4K؇}PKHII invoke/vendor/yaml2/nodes.pyUT HWWVMXux TMO0+& {0ًgј.æZ E޶|v7&&{okJ.0_p(B`X!/St;-4TTpi%de?;pyȫ_AC:%6xRϐ008Sz_؇r"<`)ŪчM4 ?QY]TYX(H SI>O90l )l;ztFeDɏ *^Iԭ*6C2Ub{Fs5!f&h#{BvSovQX0h7n/>pt?>1S03w5>`8/ e_m3@=.iū:}PKHII6Ƕcinvoke/vendor/yaml2/parser.pyUT HWWVMXux ko{ %aOWvl ,-ܶp &:vvhg^{%hlϜ9CW78N?G5z;:;EYXrtz<@a2#'0 }\exF3OqRpg~E#tyu1\^.PX4*&tZc.)'UB~$Ig=yyl|~E ~5T:`U&i6qOr 'SL`81:}>YYJ4)_%V; *Pe1EA sHe+S WuF䏗0"K ijiItdt: (  hp֩fUHy5Oɋ_O~*X[٘Hܡvl0 R9zلawbْ3:}=1طB_j㤀*ٚtah']Eɀ<_Y*j_zy~,xX]|bК ʼ&|$v{ HzV)QepǶ;S=d+u(n@NWr =bA bX<9FtWsd^woŝ譋]q5']޶rgCjfT|m]́1G3!~ĖL&aO&뽢eB/@8Ҭ~gg SiV03RAlD$COqb> g՝i9ז߽a~OP~]F$X΢D+ :`,LUDv bfl0 gR&%"Lh.dtjr^#ҪwUzy+uivϿaw "äx$9q`3|#' <4p@) FpSEXޫ75]:=P ¬E2q=t!ʢ `+,,"hMc E0u9<Q6Lo08CCN7iDʈab( R0=cI"TSghnc)ƫUDap?\56oX1-VlWk>R^= v'RFЁi|':D/H/1U3:pHMO!Jvi8tH d ]W(CU B5$ݨYsaR*S!+ Od9 >]XƸ>Nf~0$gEեM"B.peP((ؕFrD]~G>KZR,TPFW'J}hgؠ:~AZj a.iXY^Mc>y.= ZS;hS-y ).>(g |kkB*% QTf'.pp(%7 i}Za<6f5v94W!.|CGA@HHfq Or$&vke&jC]:cs1cƐ+3ۙ͒D(V#^)e:;i/ZJQSR c+YXnD~5Ҩ3V.\)Պx,֓|aw:Z˚R(*qH+DEI)Pgur E@]ά" #ꑦ^1Љ/Jv ex"CdJ7Y 43-?,@(IYj FoDKbX#n_ppp5` &ͳ1}ƠLMP}<43'=oٕ*ba sc'&ު7 %ޱ+=4hd4]Czx] GlFHlKg`{SMo8;L^N6C=2CSwޘi"n5j{sLZOQa>!!G(+.j^=CM5jلQt-ִvG)oOPFkc`zlƾD<`f+3r'8 Y) JKաg֪{0дF {Z 31r.lkxPC^{q _2FT+¸IkGޔŵZO4rWV4C Gj Y,|:_y>^«ꝬCOwYI1۶A2T`s58$hD!vC㷮ӧq1ӢAޘD`gq&ir/4GS,\qx0z7E<|h[^}nGP=K ]URH_ jcl\c@֣ע1U$=_6$Uʶ3_]3gC9>ڔWgV7&[1KAny2!JJ1i?ZY Ոc\y-K`mʅ6*헍q\ =Gu3Symhk͆ݯ( x Fy{E˗1[XHKTG؎V6W/Ž_3غ%QLTT^Ht=IC:,$I$چ@ j) 4ٰ[W1q- k-$[pjlݴ'{ztXCG0gu p]Xmǡ C[یYhSf|X?^LcƜ"4i K6c}mV ~u㑉B]Cv́NYݴIn=EYn 컃6[Y ݨgtV. zl1sZ35j ,L ))bV|G[h(1fm&Tz{po>Z|z \zh/ [BS b[f"W,%T%*-ǮX*@g9|.Jy`ϾpP x@R HHp` 8 .2Öe H2&%@"PHxrF(|w*W)0<)XTI( JK\]K4@o\Jv%.3!@WpPFNiY9R ep墶m#ktKqq@{iiK1_@sq5U@Tj/*YV<Wb+H7J%C^{U\+I!Agg.+?2T 2*HJ!xR=4* zpՂI-9{(:Z 1˲8%&A5Dp;DqPb{T"#4s4ҕ \F 9q*Cɳ]9;gU بRdI>ցR'mY3źMH6_\1O?t1EB:oo!ITt P_Zjeջ >"YÇ>2ֱ1ӒNFva7p0/F]J@`6wuM+f`vիqOL9.|ޙzV/]O˅X%z's7nO&T'-xGouCC-9r3un^7,jI̡L_qYCLcWlYбtŏqf=;%*.K5 ա㑎DrqB51<̙1d)CA+.i(;ĤGCa*Xj,anFJ@\z.HI'AP}N@xTNH#/5l '#(kE˔55rR;A #$mCfɰ})4co??.~_(XRbʸK:=Dg5=v;7mcg꾺ZEyakox75|8bɅepi+~^tO\kp;_VuyXЏj6ԵovL5Y3d H9](:zaЙy15t JS80\Dм댺Fm<$eY{ڢjǭ 6qۃNO /72ԧbDڂRtڝ+Ȯtꡒe[o,jDgm,4eVbɐo[47 }z+=~#T=㏛hZѫz{^7nDZ=߀^em6(l>;m-1|m6Ԧ\n,k{~l0m$^ˋOatAwJcO3X5eʴ/S3s2:r1G:ꝌΪR303)GPKHII-IT D"invoke/vendor/yaml2/representer.pyUT HWWVMXux \moܸ_AHltvڴȵ @s-z9k(.֝V/3CJ|I9|f8 yQUy.UM!_גɜ%+yriZNU[,0mGײ\k7E-3pqt !0뫷_ iT7/$XWt,D+e]˥="ߜY}nx@Ŗ%/aO( gͥF5A"ːa5w2zgV 8VCDrwT[Dg1 hfC# ?k1Sv ]:~mCGBγtr9?ЬD=Ѐf~~Q]pa,ڭ(YJV[M`s" z3(uڑ sLfDwgJ7Cu!!MwS: [mt=:do&"̼c { Z([ۮu?em=Ag[Y*M+ntsK6K6{6ԭ0"ۇ$61bFY)5AH.]2ѭJ$r(d*M8p::}D%v'3G{Um,\PD) bʱ2)Ϭ>˲^ZJ&xa*9[ C$;K  9a$݉4I/F;%Ҋ׺6ȓƫipi]7bh8n rGq耍y:ݔ!q''ڻN0r|8Lr@;$Yb4{vm/S` MV?_{'4`>©[*@_$W e>pZj;`gh@5DpW*`7{ECGKHoUO=QdUl!o_.˷ өAo K6Ĉ3Z & rd\U/a~__Z_'Qy̿_aRq@4 4JH54 ÃD EQj&A6%f'ýt1̣Ѐ,F63%$?b6tQP{YUgXXK9,P6O0SК"YŷUX,DƊSjyO 3aWC(!_b[4k5\`Tw I $)aa؃  gQ$yȩlˆ88K>u}Psd׺;`ҍȞ<_7my#iZB)NH, Dž8SPX^.ԿmmB-,s,CY01 KYoV9 R nh@v|nvhk|8e5x>+TJ?=̔(1S/d1ql%Gނ|8tj0fIQb5 ,B@ʶXZDG0\tL_υ:hSv)Z.ڪE,(O5j{ʶEy٦#U`ں5ؤux-B͐t$C@3m k( cYB'S2EfV'>2@Գ *{8#B䢑7}[b-Adb+8t:)4w埁ƿ W5S|(kD%j< 8AZ=aɕNnC%;#&E+wHx ө^".7>W~Ch&{*[-.׬x}+g/܋tMEˎ5e%sfWV9$:д 5*\e1ݎ!}grw3' _@ {P܈ȩ.="&y75:T9\58PmҀh2 W q]6u"-4珥k2F`.<'>>>|]-K). <t8v;0=wC i<7t:ZDz3UGS8{T9}1xa}a{{8q=0*(9!\.ǐYm,~KX=P۷a gxh\hCpoɻ2AKu&6ʁ:2 ]Y bD$H"(&Kc K@^Ord`{ipA0.*r3aIKW3 qMs Yx*<+!? /:%)_Zmfc!f!UfB ʂ,؎@i$wsѴdČW`8g !C35;|nBazwceI#4%gY=g:5wojXjt4"ChOeK|~< <nh3vuMq[2D.O0TCA}*ÉMBY4XxZ?'ֲơL|빡r g <^hG:eDKt<ӁT8E"J8Zl^aM܍$?p2Ryuitly*,rAjYi(ɮ +*W馂B`SX)˃⬚W#"?8$N0y)j]FBvam*G~vuTAVOuصp;r_zq.mC ,U yT){Eh-)jЏB;VՎQq 2|rA~QK'yuj#p=8ʼ*!MMId*T{pA^[ `Z\,9moU-JՏ x?T,_*`s'08{9uryVઢUw[]kҺKqMlʛJΕ.vꖢm$m1h3X(_v2sZ*NVї.`hUnXt} 6ă7M0n@K '"Tg m4̷\&m.u{UPId^*ZyqޡKscztKEDh8[&qwcԧ-h+_?r۫ToYV{1MUNv¼ѵuwDd0J`6w-aC%&]iSjGݖ$qbĽXϦ2 ̩QwGs]vpce.6kw*i֬[ݶvzAj^j]:|3{Hg_o]G-(_y[wՙ 25$v'['}p((]5ͪq͓mq$U[z34)(8TMTk`>XNc֛"yx˅ q}% 쀃yQzkcbXQٕsRLv(فu`Ԛrvz\. ueh{uC06{uU"E";I/YxffNzɔ_)5GT: 1$m&5 7g{ͣ8+mjQ~E_oM.y=|;KQ ~fyZt~vW>yʀ6Ig~9}..p 8fV;KbY+H"ʫA͢ Z;;/9 (|6Y!8<8:~ŗ_}ͷO,=X{CpGX frW~Xd_yz;|H,ㅱxh{(~ ; W7FD3~-"XCGꮓu=H<Δi\E>g*xr 8E1У `vVf|yjYӗ1㩡VŸR|JWq&~jv"#l$`a{SIjIj S*7h3^l$Y~z ޳}?PKHIIW$invoke/vendor/yaml2/scanner.pyUT HWWVMXux =kwF+禒ZY8NקymgokmHb{~̃r$ `0 sNp04ʃyM]Av(8˒$󠼚G-qjoΫ߽v,d Ëjz.%^?yr[Q?],ʪ@?UϏ{󧝧ݿxUCq̓0NAQ^%I0f(- N7lȃYG$*8)FPq$V'Ks/ϳ֭<ijy0M)%$eo'aQZߪ8ؼ3r8O~vK4.eASPP]tGaݢr,8>Ӹ<>Qr6xi B,ƋqNBRNd$˫"O;(^<Y>KR'ӈA &asi"?B|äȂp2)篟Gl Pax9paYTNIK&̣]?޺7 NK z9y8.T,'[oi(t]^8&2β,N9ߡE E. nmS'IuA"b@E:N@~7Qpr=NQlmN!($c%tfd+xOVa^Y ډ6=fepA42ÈUM d_!3@xja( JYCƟb- s *Nϓ,)aj*AJOFX|dV,(l&5C/[a0Q49 4s"GtKcD i&gO 'Eaҗf†%1 mǤ#L j :U"K 4h]7+#a3E"T}%旋$+YdbIE_Y b)PA!9 y"Bb#k4&;`gm辈I( l3+7{>yT.T-!*ᒩ:AqgXv( ju-w\Bnǘaa2`͛ɉ0@b%%]DBΤRMmtkN}lo\tfhS{ +ĆGqÐ? 5i- Î@˃^"{-rTҖ^ȧ<|@~(p8RGMg*D{ 0SPZHJV*E_azfB cUDKhz%ǵs6yd}9gX CgO0,<oհHa<9kaXj;tb+j>%_Koѽ^R2Vf Z)9N#ow VtQ]EnӬl@!6uYI-6[ʷ"2Gh /TB*1ʜ'{OL{i6:L! c:qF\!8_*[|ZيbIe K7MFTj~{ֺ١qwm]5#w{u[ onrحIUטXuiU"TWoC(Dž78l;8д6)xn:Rve1ό:)׭lupSXbU;` v+T58ЏlVmJAO(mhJد rD64k7JVA-Nڎe(*7e#8Lv1l`Iu-G;ɀE s8"\7UECU)>w*5&pwژUOK<#08<Ӂ `\h%g5ށ厫Cs϶iX9c%pE C2;gt L:eh֭A DZhm 렛 -%8M;p¬J4yLR Cr+ExU¢f{F M5i+Lsz\*F\IFikr$rAxSNVҕVJjše8cKފٵ ԩ0)26Gb=0aqpXbw٨MYi;]K}6;+\k60(< U<4N0­"2$L ē \&GE) ,9`LSTqiSPʸ䐠ZQ AX7,x7Ex$VeolG+cl|%0ѧXb#utq'j@ԋ19mYxy sD0 n0icѤ|k~ e+fB S8I8MZ#`-Yx5ڼȄ9vag mIA5jIݐ혋"E*7ݼT^"KCeV\qζ;tT^Ϋ[?GڸL`5pVd n=ޗ5&Gkmٶ 7,1-"Cn"P[DZ*9sѸUamT~WV6fF.t ^>ub0)J=Ӹ:!2,Pm[Cߘq(F-n}U8t37->P|Uzl-\ N=誎!.6MFXjV 2u}}wR<?ŚGhByZ{kVW50Wx};BF+O ȞL@hkȩ,h KO+De,pɷĵWGs".J+FQ4Dyv~Tfn֛`i.еӉX )MXXTφDn>6jJhI^yF[Aj9 HGj#"cc7k54'*:9Cp+n<F.Sqi5I01y"a,̀dWqϩ)d ׶Qm}ѵd?k,`N썪 Q40鶴|@w~dw]t{ l,{i4kԮό C㠗Y9tx"< <UEfE4iq֢HKR$,$ *|FU#&!2CX-BؼןT8Z2{\}T{ >1ZjTh\4xǕHG3:(fL.io^|V[uзcY՚ zT{L݅4e7;`T[@(ְ]R8ZbʩFt:,=q兹CP6!0tYř8-aJƵkl-7J}1JayB8lIp%vO%!eA ӫj4$2nT9䙂3 qPɡC}b皓;>z~,=6+= (RM*y7θ̾ǜ"9if3G#-&fI1Y,_4.cPi⽌"& BM;DF3|@iE<PI B }4Y|PBjg\ H|,=\uĠcNz׾pCI}}zraLHf!QA9b4⇕0 q*K^FxГ>*n:$jf ll 1%ҞVU@&@Gx5't0& i$8*?-߻v?ƏǼl*nVy9:߲Wm;HYnE47ZѴ1a 7^'E+f޿M= 9Pit֒e4.R?U;NlV>&LZ }d7]E^"%'Fk$R٘ i$c^77ZwaZ:4=yIiP;I,aiᛷ}׷x:Ioo;D=, ;Ũހ"Dn=xY7LqBU N4xg&R3RܵUEx38ǚp9*U& 0&kuҠLf>l6Z%¤TqZē%v(Ԝ v@&Q{3K?'90AY 'g\y[ř4tBGq"j ԝ<&btf^P`9'\ܺԏ8b04nVj-m; %|VGV@l.E8RYKpbjDx ;bdcZ" I㉳:I4&xDdZݠ&&L&m[X ۱QSM؎DvLN vڀ-9xAZGq4zǫ6AJkC4~K^T-c\Ud%[A,?ا*U-]5' aO{D(1p82<_-6;Tgc>c4DvcUCl wN,In.3 eAU}{~=Ykގxڒʪ[?"tr +Uxm-T_3Umk}̧iTc_VMjLnf§c.UPk "aշ(,0_hhv4)jt,%˛ UYr{+>!9HǨ׍ ]vuU K'W}ԡ=yE#I!,g熼qX\+IݙOUd5*Ȝ:F *kf~M6(UײqC˒ i_ 7qȊ_KFzٞVW嶏78l]E!y@(N(ky|rfOαźnp]j>$wx.V>K.B8KX0uлCG&YT$"`%L- pW.6N:ꦩ7]o oWyW]9/#$>i\df\IX3W Co*u%Kc!*vvD' EzVė|_:{\tAfϔ,w cIk 3dV1$U.%ܿ,Z[ פX|c VʾřoB%QcSJT,ޅLjUw=<"X؀פtkߞWDaE։F*>˯iۺ/u3 >- >Yww@G|v7CL*y6u->TRJ=<ǔ.tf[ {E.-TЧL<.C +]H}nɵ^H<ácx+.k[qӟwYH;z =NK7nuS C3Bt,uc{ܻlZoL-܁7E؛"u\\thgDVF_?2i2FhC>kdI1D@695g8{&gD}ف*?-tG.@SyVPNi5lÌ1K뇌~u.-_u98UcI6@( Xx,'* ( ]KzӆZRP;b}< ӤPW1$uTҼ|o|8/iUcpV}5>ՇƇH0A}Gk5|}d~|'ñc>Фe|z?)Z}08KvCZЫ?WwPde^+_U!/phIu?}yUG:o Ӻ=dJ/3ygY ؈}T\~D:G]0bXr1pƇrZ :C<^M]7[h3oMZNyD:8#͐[Nx{FpyxIx:EU;SOzuÀ]׫_9#yأw1t,LTdө|$lC?40sM=SixXM;3vp,!;ލ&\4:G5!?Х4HUjǚ)c`-|BKk lfzQF7 $5ˊJKȞ6䐼^DQPt첪۵][ ڂcecWj|x5,Mӄ1'x)rDYbTՖ*BS_7&F2>hvOMC; i,W25KOKFQUaI'(L0@_<=h\XT0ݔ7 ol|0]̓&w*ӓrq#H p=݉7z7tjFlF0F|%eIM\vV4 SLn7;F?*ܵq}Gפ{i+[OOMa-IR;B[@6ߴ#`VAnncYNKRi0GlSJ?Dcv Ja.aVh~FrXGN Ns!ג2jWSeB| 0-ί0(Ͽbb,Sޕk*ǰ7:ʕ¶~^ZZ!+JwBPW7T箲Mŷ_%^Pv;dЁo¯&J u8Ezᴈa|W|S/xC!u}|?K>Ը9`<o݀wm94d7#bdo-TnryӅ_'7|<㿙[3ݰR ;,ԸJ xٌRS2b3PMEY}}u?e\[۾L%Š[8n.F]=W\ |Ȱ\W5Wb/~|V$u^|׶#Z2aFU ۤ+ׅM&V1ׯduaͳ/"_W[xt>oޭ;۴ٟ6 f M HAsHUV~٥EK]'*˓h/Y8QK{IlnߩXNe_ILF` l("i[4PHF>~/gNKE3rVTy,.iuڪ{vhB.[a ̟$Bգ:jjZ0rU6߱Dm˝eޖ1uMHU|yhk'v:*cw`5UoҵkDž sTOx%9JaT$}^ spW E WI/+=,4T:^^anUaM53_eUuىo +6..6{;pb >oϋ3e]]4DlBR\W#e*$I۵ ,ҨI;ӓbfPZz;h,#TWa۬Fq"?֑`$-^zT IR]VAyswݳVڷr8! l%HQgdEuޑÃX-W"$^'1ԺP<8(Vy KD&QJ5xutI>I>q@<Y3[گ$ny V3yHߣ3]Gۊ=qitIY+`CVLv}NJ'ʷ@ 85 m%"[YcL%ʩ# 'h@i]gti2PKHIIA0 invoke/vendor/yaml2/tokens.pyUT HWWVMXux Tj0+%7P&3Y&7[J%% Bsνo!UE`0*# 6ARpI.A2fмàE05d iS'J HZ\ 3ST1hm)͠[ĸ2XYP6kG%*ȫ2GNY gr1!HK'(e4fOq^]u*f2ڵ7Q#%`teApJwfgoE pk*gr{ԬAhpL N;)4 X )Om˖w[\^7,;թ;R tVi1mΣwpqtuX3rЋCS:uUtJt6pכen~K:EtQ)=/'YX (w4D}-|-菹>.*<9 MZeA 1QMzS/%K{n^_eJ iGM BٸP;<*ꐝ?UY=PK HIIinvoke/vendor/yaml3/UT HWW^XMXux PKHII8aX%invoke/vendor/yaml3/__init__.pyUT HWWWMXux Zo6_Atq @mP]/CQD\(#_#EGV?ywlctɖm,w8|6N߈ʶAVTnZB؁y!L_]ueX,c+vŋ3g >^)R:ؑewm3%GL-fs.v&WSpelJ=blΫuFrސ+u-={@q˷og*٩du5LGH)eDBU HiU5@S>%oi!ٰ)U$g(#4Ɖ"b&@d#q*אڣ%7H=2{YW!MDbDo%wɵ]33,0yē[++Ve/xB !XVXmI-7-FMQZ:H^-3XpNH#.F@f_?cU6J㺽 kG'(=CJVm WI\ k%{Ȑ J#gv, ڼZL2m`9ь5l BO4Xu~&ݙ. xr a"Ȩ6عxGh$Y[ep e16-2bv1< i~+5%-Wd,l"m5ժꉖ{waT!*,m)ښ+&}#_JXvI֨[q\{}4d/RpIZkggfg<$8_}YuSVMY .YMϓ.髪ɺ*7Agme(SV'۪sj2@6 ҤaMaz̊^e,P߲d'uLLM,矿;Ή-.'<^5(_[]dH:Ț8,_ xHci\^+vUg=0u:XDpX )cۖS:krf1:y}|kV ja0O=}-&문Y_t\' Y-$+@5&149: FӤ%&xU(a'Ģف_E3"* }Ztn3PPM=fev:<ZQp.n#t"_btwcZÈF1$ GDPe[xؖ:z$k(4V9Ц0;xSXn-fn0I1ui+ExxUvY5suH4%Z[Mr׻:`zB‰d.L: T&4wrrfw;4޹ꛬ(ghCbX0.}w].!5*bNYJ2aT  b[%yR2qL]k&t{Ћ~Y+XB gq3ҧ`nn'{ݩv%Nc*{1'DejД;n-qr ]fz&[R_cV]}S)ͦ1R,b"5ujk^ ;%62{ydUd$N-#稍fOϟΩsϖuJ!Yˈ3ݖUۇC+miX4lXuL)UU,(:U兦1WtIzt ?~*tvxICd7BEFC \X׻KP:@I۶9@3Du]ӋR^"Ev*[~GR]~yK %\^I.{ϪZV/TO %tk>Ud/hyV7AMm->p]O}D1zB |<Rui5vcVGsTFup#OIOsX[eǤ0S͞â7v (3qFH~T թ\-/AaXp@5,#BoeȺvnAr bPRfW@=6 OIΗ4Ϭi,z{ ] ^OK+Q: 5y._'S&'tO}8ݓg>|Rwf-I>{&Io 7 UF#1^ YT{y7o/=O^tP tÊm"_AR;:aoIq{086?16X |dQ XuRCXM)0e(征W# ط'>+Ubp/O~Ft·s]ѮR P6,*ډa||ϒ싥]C!,k4CM^My,@^?.wU?9cjkW?DKЧK,1_g%Fbqx9 \sJ3}^ OGvD ƇMҬ%m]ݝjjE UUXׂ]Yp"ĦP25 t 3 ZtQ>wq¥C~ 73޷EcXyF[u0iF*s!ri\y6e 4 < dCSOx=&lp ARH8[9$?N=zhݿ791Wjv4cqC8 {Fduȁ|3{:WXymS o7`@;zUym.wWnVO1?%/jLlAAu{b9/ ~MjUh[gK;,*ڃUc8JY*X> =)?T c:f# @nW-gs{$AܱNGpz<$**J%DUm8]e$i̅SE)6H#IKNǒk#5h#ݻ䎲abn*L=/X皷,:Xw|0-߶뷭Ǐ5˼:vuf쐽f>xq :HQhvлbR*qot"OBIwuNP(36{"52 4 D=)+`H+m£\%jB ԖPZ뾎xzK⭏6|Mz'":9us!9#7'p<[gj(.AYt @ G1!r Q0,R뵴mM6c ֈMۊ}]h8Vgw+tJyk(vG'&x"_WQ|Zɗ3GC4 LI)XŎOERUpevѰ)HR]<|{ʯy7;X9(ⴞIh{ՆNWiN^U-2#4˲eSd/>&V55k쁈)C6xQ԰t-PoX[FVt(>(""RRSEv aHw`n# C5ba6Ιfϱ#|s]pШ|Nh   r⯥GU 5MY5A%Y$w=XO~a6kVѫ.YsXс G:CaU1PR:|^Aѝ1>D+t߹QTJVs;x1`Y!(pI-\,Ri?2 ART^W6th]TE (t.FF \z; [;؝W`!Q5}?I7)Pj3_JzjPYW;НXqgS@-v>ꎠmAݦiJ"Ch Бj8V~p|,Q7l$C#u`|{tZS /FB{X$kqߎuaux` 7 _ "AXu !A(o ͣ29PKHIIkC invoke/vendor/yaml3/cyaml.pyUT HWWWMXux VN0)zid7w4p%mqΧl-&p{2 c5_D ECՉ!k !Pe ;0DVH"  Z+-R bO$PGB+A02^"GɈR(m`%Nβ= j1TcT pӱWqCXr^.ǿ/Ep~JrGP>B8V?к[a?Qn6tLcw ׂC>j&#J, $+ӍUOILir&򍍅זђS#j`M6j/Nv}A/ps x2ȋtb5S+٧[ WwtpREm#lrT$3f(G3kfDߘwPKHIIl invoke/vendor/yaml3/dumper.pyUT HWWWMXux SN )4&{1z!N׉Zh/e2)%gzx`u(@s[J6 `Jeȝ3kP8= ZD"% Yəd4+eP 7,#TPJQFk c7Tg)l5%LH%. Eeq +Hے͓Q}}&@HW -q2K#*QbQ lUG8OiNRܡ?ulBEpb>՘`9֍PYW_.g.ֽ6mG"=sOir\;vbmX#NT&PKHIIsʧinvoke/vendor/yaml3/emitter.pyUT HWWWMXux =iwF+* -JCiw6ײGg{2 Hhж& }T8twuu]}`c iY&K|L&e~gU`,aye<&+e`p߽9~w@g e,zU ,}1`&nl0x.xt8Y\3L/THwH@gCƙK [/ez( A⒪ V(>[À0!pyUlcuMuPP1,l4PaC$` \h+F0XKS Jcţ@{Q!szӉvww!vo[FAcY0s]32b(SˁytN-rwJDvHqB9| "qZ|BOPeGo[}"Zџ?NhG6E$(_*lDqG! zf?rz6knZgY`rEȡf0Y2yڼWQV`gg'hkyЙiki`({@|н_S;tZR@zp*,QAZ-R{c_^[[to[*>#cB;9-Ӥh(Ӥ&(Dʫe]k3A驕ķI(^4ZQe 9Ҹ\: #҃k_'0iNUDȿWܐ N4ttL^Rsj^U$Hڇcm%MnF7ӓsX^SF'U5t·F-T% IS'5wuQTt#ur4nƌl'ma"rhupkzQMROjd^r"ÏJ P'+I! 1 2$HkdH (xIbܧ@n4jj{Jis/]?j|PLV<%':Mp&-=ZZΥj-?fVml2|BFVM0֯1F'Ѿ*xՏNpQfPY0` 5~ҢficԳG˱\g nn[>9wE#=;H<uI"9MNx w@M-?2bd\J=ƻvjVMĉR:񯛷5[SCԬ#uWMuV;:A zP)l^XWmB{]Ȭv7)l"~Z1E= 2(Ók=^'NsnLJ@ lH.U~ѭ YOvwKc0 \7׵t?f{xZ Ę~{[s~jĊ-'ELvRz+Vk;8`QаdqYks.w2r{5!R{jC6rLoIwv[ Fj֡fK"o8d]'U:Xq+7D껞6OcΤ3X8AoJ( Ҩ3t>!ahm]^/5rz2{@=(xuʦ܎= t"gڦIEj섃~S? h*+9E(쫍B$}a md0P/ ާ  Pq~ҨFڛ(k894f6M 4zRQ4zhTԃ $l UW'l[wz’w<@5 Iq&bZ?q5qIq̞:i<;t#|Y"^cҽ@h(V$nOQw8#?2Ԣy^!?s$2jrᰭ%=':| wQ?bO:Gb?#AםG;c<5tf;5!opr1i^{#" aT=aN߭lٽ&3{ (X 'q/}3OޘF…|\[4/_l[oFQ{r +sI'؊>"H`yX?:Ӹ񲯙!D|qC۴A1 m^xwm'p{9x"юR;p驆7 EID 4T#\cfU.yP;J R<~qhi:گR]ȄcW} 6rbZk"׮]%oW|eW$I}fH]$%+2rSqbU0QTPklO7*{J3(pNb&Tܕx_H˵>h~Yi+t9rΫ9X@0M{)jRA{U=%RJe}\^읜W[)0F7l4 ⭐ҐG-71^k4 oA JdYhh܌/(  ;p#⬜_MNs/;J8jbR8]oﱧéPȈA _&eT)X!1fq) XWQjD#UDTDSn鐌F= C;ŽQKA@{رmW1ʶlo>?^\e5J9@50K %EYcI@񥌳"],bEA1H|s2I.Yd@3E Xh SdSjP?QS_7TW ^Qb'cG ~ЖG;;\Z Bc9]kp Pj>j;'72KxwjԪŗR7_ug5?bmmJf~Tg{oyk3NW3x~e./l|C#ĠVjqg€̸a>9jKK7>|!;ԯ6\tK,#7\y9#bǃ@Tm#@pYG·#>oz ̧SHQ愬'wZGOg!ie2%,T>U -xLgbϑ0x>f,z C<[xRü lY eGe1yǀ׸EܩVH*?>NYHT٧܂SM*$7W޸W>oܝ5ၹ)nxpvw/L4Gw?_ˢ(v 3UP:`| ;USB,= * V͒Q2=.8TVg/^z,Yg>#O0#-ȕ oۉA7X{oa ECZwDsBI<̰QMx',a,lZLf4!/v? ͐Q^m3Vq#A=Ji=a!K)jN[@޶[7Q F\=F齖}ӽ֜s?HYt5bJUPC'>h?g|q.ˆ /ɋ+J?gEVo{7oqxf~#ڱM6+jGe:=l{w274қS擝4W]=q eZ5}MS'Ku zgY7{URv; 2Jv_3_טſL}foםZT+Wæ¼B_K/>|k^wad۳!XݨWWXyl\zv}.6H&|_{լNC&sKsggnn8~s^/WkluXꬡ6_ .PKHIIHV invoke/vendor/yaml3/error.pyUT HWWWMXux UQo0~W"U VƤ=mT:DӰGߝmiyHlww^u£P?B4&auѶ@m +^<ZVobŎPc+uSvmt'~N\~!r yMd1o1QtC~b:90VFt\Bqc.ZCZ-Io<6X.2Y';q yfn|ϊX5Ku w>gVi㪍5[?nM^0K%^fߘQu'GPKHII`լrinvoke/vendor/yaml3/loader.pyUT HWWWMXux ݑ0EفLօ1ڄPV_EJJbv\c(%cpwB S= ^bP!BTީ&%j3WUՋza'VZ%+$h qF!nP4)nm谟 {{w reEf Buc^N;+X^s2vo۹^OvyuNa÷dXӣ 4k؇}PKHII invoke/vendor/yaml3/nodes.pyUT HWWWMXux TMO0+& {0ًgј.æZ E޶|v7&&{okJ.0_p(B`X!/St;-4TTpi%de?;pyȫ_AC:%6xRϐ008Sz_؇r"<`)ŪчM4 ?QY]TYX(H SI>O90l )l;ztFeDɏ *^Iԭ*6C2Ub{Fs5!f&h#{BvSovQX0h7n/>pt?>1S03w5>`8/ e_m3@=.iū:}PKHIII@cinvoke/vendor/yaml3/parser.pyUT HWWWMXux ksӸ{&I ~l-n[0`cgm,{%a н3Ė%9;{y(AFg& 0CQNOO(Lf2rzv_M`8U5XճIMdb0|N_.Oe.qVD8G}4M W$ 8+tfY-hQh4RY8iݫs4:?׫c>% q7ҤZIsy2:]jec^ZGE\uWTuY{5O˃ߌO~.X;٘HH2{=*J,Q-\y[p xs&04GR!Խz+u{[j Oma:ՀF)5cf fBR-ߙL8Lz{M˄^qYzgg 4+qTiV03RQ|H$cOv7I4Ikk w^0u4.#?gQA"T bhBǪ!R7ćm1Oa6CE3f)Jݐrj&k4M J-p{:~6zsz5\S {fo"M/z33aIar %$^}aAV@zgO'}2yB$ k4u (|(㊠/R41dA v>Z`GP2)):ڟަ+#vBQĊtN?'Ka96F֭@C)f# ADwN} ,AW +F$p>ʣu>uO= \cYts=z("*rnpO @E43tlPWFo\ ,50T>4,,6Av0lF3芋XLQ\7 y^ |Hg38rf(af+a/(@P;XB7;sL1v-w-F?:W AmKRT?+*-H|J%Q~1_M4 ,p'WJ""^,$qݧ.鲦 J\<Ҋ*QQR =ԙA{=BP3HHgl4X%;Eir@<[!"g+Jh,@(IYj F?'߈*2, Gxݾ>j\r3LgcuuƠ LIP|<4'oו*ba oXokcS*YH6q8Mka9+0Ӑ^XD}m*g??fWAfhI:MI i+'S@lz}H&$O:dP-A2Zg!t@K3QZvؗլOeZX4vGa޽~*"_iQu&[(RapZ9Fхmm/j8I~MKCu؈jE7iЛ6uY sF*يv蒫QH d9k#?9ϟWTgUC5q.63f4CKq\'x-'P< 1Ruk >mD?]G >*ig`&Oy4A1#j8w[Ǐkq.zLPPKݵoIcj2B)pWH¸mM.[ruWVeG 0JPΦR\w2cLq)[|u3\ nPX0 -i+Wg;V.&K19KڮANY2J:1P ebY+#5W`y22_3Xsr( ǥer8s;ecc|$RlFE¤jϠQǥuݬTaƖjg)a3a@<`n>j;Q^qQeLz/MK{q&nEI=}Դ3S=W)R B`c-d D0$oBODmHA0@+ ~g-6 -GnuQ#|s=Xhz6ts`λc6% #4%mƍ}[{r7.QwwE3갇8)H2,(w任lqPM23HyKT oKu r:+lG7=cWi +u75O:}svLwtNySaٸgGU'P AД!&KFrmr;Ӫ3 [UI?j\KJUE 쾃`7⮿x"*f̳]ͫm~?@Z$'÷"/ecl~5~m+mc߯7,MrѝG |SB^m]Jv7q}\|.9L!ϭ/z f*=`G.8ݰ&1[_KN2;U a[g3x:qZ~&"Q(i :R{T>(wd0 bPKHII sMminvoke/vendor/yaml3/reader.pyUT HWWWMXux Xo6ߟ ˆڠ.Т1P톡Ë+2k%g~w$-Q6}7Nl>w<>< >R bSf"W,%T%*-ǶvP*@g)|.Jy`ǾrP CRHp` 8*Æom H2&%@&FPHxjFh|w*g)0<)XTI(J%%i~ ypB`4{.%ߘ\`(8x^(ct,C)mmbɼư{+uxAb +R]i\ \MP">5X'"ˊ{jWlT\^i\B[N߅on~1 WȵpɎX6<9,4 !I66rK (rsyt,j 9K~PlM\.s3E@5?y줟kҦOb̚C=NIDAVyJv< +%aZ)߀`-M5Xl{34i_t5D+ئdBONo`+ez[ vZUQ}l -bər!ZX-Z2ꏈC憶f-6BN'Qvo=+.[zV QoQvr߷RD'ŋn/EjUP zu1տN~|>n"<1#kW"4c٬>-W@UKZVLz\05pe6tMgN~xn ~qXu$ւwV7'̕d[jGm8U' F^I9t՘jljxA|j{y|#MdGeMwpѕH{b̗YQ9$tHKM'DZyJJK3eV%P&=v^.ǿu܄WUɳǺgKZ%dvC:Qpb7VNB~GOa #!*eJoRmirVP-K_J1j:ہQoXIp~G7}Ogn֮T2n%~Kbrl[OGps.oIc>ou{Q/li~c>~z>biXa~^x9,\k;_ey=яb&U/~LՄ3d"ISOoEQv=ФAcON˟`[ ]1`ρ7i>H1roF. / '57݀鴇J.:;o0壇DQ٨o[?Lգ&I} 42U{5:=)X]n_DUh⡪i@5ΊR#:VFnZǾ?# B;Ով'6F'^+v|O#ԾHo|PNpڝUw;QrX|ۦsw;]7ݕyޑC.D9:r2euTNGo7eTg?q"+^;{KF H!`cJ{{+cnC3_]U)a3g(hҳ[:-3 !:av9p#:s4ةGӠA[y"},錚s8$xI(zؼ|9}Yw[6?E؜ ''YBvfV(qV+пFs"8QMi!J*FnA11^δm(푐mѢy6vA'J 7o{ނ=fy^?u#\Ip4T:#tk[HBK)7%}*땨\^j#N!YeD0IACsY%b`S^=Y[^B(13:޼yeW ed,$sYW\niW9XY[*ժn@br EMY3?`dZPF"&!6@oB[nX@-k]=?2稃xAM>> ӎջ&hiV~h=wH8Sw8T!_G cSCoƁ#VM,~J.yL;+"@[;#yY6ݹ;i$1dѣS.*А>ُ8pC6dAq@B nG| )O*a?#m&5Yu8D-Nj$JyҰ%LDz`n:3تf_}'N6r+{-x,r.d,A=$w&\W]@Λdt8NStఆdxOd`y ⡧MC)'OLB)`IBݩVDlOm mE\JunߨX[8G SE{ E_Z  1 xi0Җ[8ENbw(몯r(Γ #aKcL+>o Ff3Lo;yn/\m"Y&/ ֑K CI<ErwI@>ޯX-KIŚH j#~گV;Za=gymF᜞рE/B#V5 v3R2 ^"  %0eV׭0eo܉U(؎xBNK Ȍ}[QC"TYiC덢> vq Z5_Q4q;;]U4ѻ55"/ ԒÁAV(ǝ!dA?@2x÷5+ٺ;Vkrf{鿱[; iz=w*xM19wo;-gJRsRXuzv|u&N]M"VاtM*trsX~֑!$W+jX8!A:w 9_hj)LxF~WU>] șX]6m޿uwϘ OͶ#ݦwJsI) ?[XbhUEi޲{2( %?xx F<Y߈dlzly΃G5,rAji( /mWɦB`ģSx)⬺E E~QIGb@ hew>8 A~JXlS 8 !]2̬Lo^xC *~RZ:4K`5k\9> v6K13?4-KU)V;=eRK?{fx-u}T %OEm","O' +NTo{0py[07,%0&U!88 l׾*ciصvp8R=䴅PZsnAӛBŲ )T[VJs\,a9;.6˫WmԵ+D<[)VQmjKZߦnp*N(ԯv*sf5NVч.ahM} ._ݽc abuF3fEC6I0[r_fG2'R=ӒMIMb\Sǹ[s~xЮB4M$o9LSW\}^휭YV{Ϋʅymk1*>tKKgMzi&w{GBpKV?40vGݭ,w9<=EIPnީdR.7Jc/Au_j;j}5]J{@/Ζ]lwTCLMIs,%2l` ش.[EKݓ5 *N]vs4 c6TO"j0Z};0t$}u]4nEf=a` 0o0;`:1p)BZؿr6=12NQ<]0 liZ%lM0 S-ksWW| Oo4yC'BW!8FyS]oF5oV*cn}؞$af<4ӷy3nAʯͻi%%?/~'oz(_wGU ϭ , &[Lx|βyO}D̬?˯; z qD|~,d VFlҮ|j "]@xT$ sㅱxp >n^C77w!^dZ@ϡ#U4L:SN{q-9Πt Kc)LSh3|[ ke֙ߞg~gPKHIIEy$invoke/vendor/yaml3/scanner.pyUT HWWWMXux =kW8+r3=iz aCXB,g@fސ ۀ7nvؙ~J]znh=9yh| y4v޾}yPU^Oiuoow_Vۯ_x+*>{5EYuUw婢]{~˝zScmq̒0NAQ^%I0Φ(- N 6lȃiG$*8)FPQ$GGf'Ks7ϳΝ< ,E&~J"̷w(ol A8O+ FEI)]QXGw$: 4.E3I<<Σ4.Aq̧)tL0K_ ԟzjov/| x neee>a+"f)D|J .=󰬸*L'vE$y1 Bb,ƋqNBN Nd$˫"qG()SzA c$@#)Ng2eD ަ12&EI|?bSG|,[Bk٤`%gQo j% m<y8.4'gh0t擪=pMhfgY[9cDݿBՋA & ]J8ͦ[:M2g눃:8 G덂S@Q}`s3X;, 4Nlҗ02EJ6+qm/**he49z==(˖l,*G]+zY" .LS&;ѧ(!,+6 O-3`b@6<8g)γyh`Y ӳ$f3as'L5D4)PZ,+b-krD*0'Y8,)ĚDgV,頑 >LR zk0F oD uL.IzJ[/,y4>?-t,ìթzakjG\nIgu@N aőmOWdF uqB#PpB>!`4rI'ޮn ,X٦D4 Q>uFr)&-$N˕bVh}}mj+=xd;j%ߎ[oõ_&i3j5 }äE*nJ"eV=I6# ݾv0ʷBl^Xd8xՆ2T& TbEh?YOWmd>YK^NMж 7fqfՁRǡq*vKFKH~I]V )JO ޡ_5.!vh9NƦ/8nTF.7[vk ;ƒ*m= B֚.lY]i*nRnݰJbUDQo[IJU}`ST8GM;\^n+P膘8+-ܷ5*a <k+@o׮(O:5ڌe4(ƏCN&*\*>_d1}݂6{a7#_)n…OqExƶy6\A|D2fhlJR?'!TƱJ CY wh~Oһi$hEFq9J߰vWmLs/\D^!h)02bjP6tL]CCY },΂4&gH k.;z&NqC"LAY *dvˉڷGVq( 4TUj^nؤ:93 qh%o!a@P̢1ȺC^^z>4.“DxB's#yɐy`:I &V yA'8p)Y~Z:ɈSfO(3b nEN"gMZ\XŪ"(1iBﱷ/ DnKxP#C6X `DhEg=yeO(ϼ;ڪ|1a@Q{t4 }khZ}U,ơժoo&s8KaQ%kZ⼨!) SA<śK e> 9lgÂ@~XMX4Fp ]TLaPNVVzil5PhK*r_QXv&~N'": ')[c.9½#'.wM0Bkr$ 5⪝ZPnUdIڄӶ%U[emsS*dӈq&KO3D.Eu~/\g7zaW tN/"LJt t@ņi. P3D{qqnEˈgWA5ƿ1ayǡ8:xC${`MwIsy+wO|UP?x6#V*g1>&0JaCpP]]]*qOq摨G-AP]کα5u ͼ.ɨtYݗ pNK/͜4LQz7.CEf1I-=>51hwQ^zgdeDZAgM 3u4Ks O+b6$+77akfc!S |N6lтLj0K b/+;ŲuJ/"bšc7K5$gដTtK.bWhÈl<In Ma"cM3CۚWS)d֖Vm}Ѵd>+,$NFDQVM'_#PowCÞ8z~v+MYV|3- o0l ,p:+WZ&0-Obj?}I?E jukO$|Jǎƴ\6ύ\ }7CVY?w{TڅTeȝ0 ]Bk.')uX2*]yN2KK\yoЦ,M%u`^\0 OAKc5&[?EE`h(DI)Fd~" :E8ʝ B<:pJ t!Π7\k_O8^i[Z$Y?W\cXV3ҤIOGU 1dM%zZ"jR5zByо3"*8c().fށnoO߹|w\6ZYm"PLDɒ|nPC7F J^1x|%Ma!ΉYW}agp[϶䊓Yylы+@dOԹL"TTQ+5haihЦFo`DloCvζ|>)1.#13fóiR'v<G3-$1gnVGoǷ. < Q> 8f>CXqďxe_kBַЅwuhO"aYc/Pj8Ns"LKsO"(DF j= J1jw#5}FޏV‹]YSj2$i'HkHli~'4 ?BtIZ ܉*lTqTę8U!\2zCa9 mڱ#$:Q`3Ȣ<U> /h+I(ڶqx]:YK,d70-ޥ#luQ2 B P3|SuꆮiZꯣ9lo JxlA-&;Tg C]YTJe멪!+.ax[24U]e+A2FQ@oo<IJ[ 4GŭĐݳrQ#gu9__!{E#STdvDe<$gTB|W(˖VIK1Ʈѻ%Ը>SQ)BjnQ MEi,o") .|zgOF0Z5$,q҇8]Y,r6y ߒEdBLxh(`+tZWVX+AǥA:5AIF 74,IРE߆9XʺlW54ugS]wwV>YJ ߦk<2 _Z=qTP)O 븍Q;hs4LiE(~[$r[Y)1R{;*\.YtHյIC­m8i޳/rVFl4_dji Xр] uF bh#! PEw )29+H+/n=[T HUA"fOg,{6 ip@!/ X_ӎsG̭OFWQ$Xɢ[ ʮE [=4O]R'{bgb܄'A uXa*t4uNHj Bzn[Pz] /wٶS)AҀOOլOu-۞g6 > Ͽ6UoRbeτjp@A$ﵝ8f<>+50|(a`eSqbNYV suVic9'YVȱޟe*Ma\4WF8^@`zyn9V$y^QiK `hqjf׽ 4-(ujήwWa='SƔ%Y;̊QXј5w]L)4.1c_XG-1Y(RgT6dF5{O*L4n2,zyKe|6ZMa\< (Cp$>gs4ޯkpCm++C}YQ~[\%yͿ(|AKQ!4~^׀bq1m3u4@E6 ]m8:ri紙pR˔d b}>[kCS#T 6Dy4נlS_]9\|+%PCvmfID;T)hJ;h闧O470ը܀bG]4ΐRO2$ dvXj$ii Zj, ^<_'tų e9ew;Lr%Y];RJbV=u'B%K ԷF6~ʽܠd`P[b3q`_"]%(H3Ұ=P])B]"t.^dbRY]:ŭp&6 it*X8=.u;loiVݿƆ&{N7^?rGKC&'zh>]hFr#ƴSV$RM)옰f"KgeSJbN0"KDF|zG2X%n(؃??ƥ8Iy^zz)}Cr$d[~j|ң\L,O_܄>8{JK5 eT^pNv;T~=ĪA`x8ORAn&vPx@|nxW⸃RD;t'eCQ6jLї*uquAR;vjG> jJ|a0 NPZ|LL-~QLA2$|RF+2YkZԙ|aB<Y3aXu oy ^o=΄V,>{g jڰWnҿ܏4O[OTl+68(cClM #_&N0/Ѐ҄Ͼ0ti2PKHIIA0 invoke/vendor/yaml3/tokens.pyUT HWWWMXux Tj0+%7P&3Y&7[J%% Bsνo!UE`0*# 6ARpI.A2fмàE05d iS'J HZ\ 3ST1hm)͠[ĸ2XYP6kG%*ȫ2GNY gr1!HK'(e4fOq^]u*f2ڵ7Q#%`teApJwfgoE pk*gr{ԬAhpL N;)4 X )Om˖w[\^7,;թ;R tVi1mΣwpqtuX3rЋCS:uUtJt6pכen~K:EtQ)=/'YX (w4D}-|-菹>.*<9 MZeA 1QMzS/%K{n^_eJ iGM BٸP;<*ꐝ?UY=PKuIJ)invoke/watchers.pyUT =WMXWMXux Xr6+PeR\&3nvr;+ 5Epвɿ-l'`Q}jiㄡ֐U{sr1z' sJV-iw^"'''U#wIWm̓ѕlg'?pfՖ+Y!+'t+,~yN vFWĻo2;['~֊!MpC)DMIJRT 9^,W+*ZPyQs}'$m a[uҊYz3"<Ͽ[hj&QICa;yK@BoDiXd:S)WϏZ ul#k{zaHUFl'J{rP޴XʑƦǭ3ȱ}C-;R 8JX@A}]%RH^5MH 0.c`lD<⑦z^Վ;w'W$(z`ّpFV7WdנQ嘜,!(*XvT <ߊo9Z0YB$Λ4U h}{KeI.;v5+2MV^8`3!(Zmm2ԯSOSYڀm{穈G[B wb">5m"sKf `13RԨ,.RtHeg|M*9cv"+쌗K@Xq._i򕺦{HL~YٟÊ u5 l:,G0RA o"; @&ejI]N Cbn]m2LE=ZAP:t)L\]rq#Ū qKAt4Jj@w<qA!X}12$Z|pVT v&蝦#c~h Ei+yBAw9%*K:-ּqz٧gÅ~Y4DDIPBX\{I xj?I9a)ÍTz9>0%X PKHII __main__.pyUT HWWHWWux 50 { a<F ʀFבmy{Bo]>:+Ĺ E >isz =l}nvR]+(75dJ"VX?"vzy<@Ie B25'RYt?''invoke/tasks.pyUTVMXux PKtIPԋa.invoke/util.pyUTVMXux PK HIIAinvoke/vendor/UTHWWux PK HIIJinvoke/vendor/__init__.pyUTHWWux PK HIIAinvoke/vendor/fluidity/UTHWWux PKHIIJqi"invoke/vendor/fluidity/__init__.pyUTHWWux PKHIIRh)invoke/vendor/fluidity/backwardscompat.pyUTHWWux PKHIIeR9~invoke/vendor/fluidity/LICENSEUTHWWux PKHII !!Winvoke/vendor/fluidity/machine.pyUTHWWux PK HIIAinvoke/vendor/lexicon/UTHWWux PKHIISJ1!invoke/vendor/lexicon/__init__.pyUTHWWux PKHIIp C #minvoke/vendor/lexicon/alias_dict.pyUTHWWux PKHIIqL'"invoke/vendor/lexicon/attribute_dict.pyUTHWWux PKHIIם"#invoke/vendor/lexicon/LICENSEUTHWWux PKtIXMZcu&invoke/vendor/six.pyUTVMXux PK HIIADinvoke/vendor/yaml2/UTHWWux PKHII^f'0&Dinvoke/vendor/yaml2/__init__.pyUTHWWux PKHII#<9PLinvoke/vendor/yaml2/composer.pyUTHWWux PKHII܂y9b"Pinvoke/vendor/yaml2/constructor.pyUTHWWux PKHIIrm =binvoke/vendor/yaml2/cyaml.pyUTHWWux PKHIIhek Vdinvoke/vendor/yaml2/dumper.pyUTHWWux PKHIIVؼ"finvoke/vendor/yaml2/emitter.pyUTHWWux PKHIIre invoke/vendor/yaml2/error.pyUTHWWux PKHII Hinvoke/vendor/yaml2/events.pyUTHWWux PKHII+linvoke/vendor/yaml2/loader.pyUTHWWux PKHII invoke/vendor/yaml2/nodes.pyUTHWWux PKHII6Ƕcinvoke/vendor/yaml2/parser.pyUTHWWux PKHII:#bZ˘invoke/vendor/yaml2/reader.pyUTHWWux PKHII-IT D"invoke/vendor/yaml2/representer.pyUTHWWux PKHII7b| #4invoke/vendor/yaml2/resolver.pyUTHWWux PKHIIW$ invoke/vendor/yaml2/scanner.pyUTHWWux PKHII^5K!invoke/vendor/yaml2/serializer.pyUTHWWux PKHIIA0  invoke/vendor/yaml2/tokens.pyUTHWWux PK HIIAinvoke/vendor/yaml3/UTHWWux PKHII8aX%invoke/vendor/yaml3/__init__.pyUTHWWux PKHIIj,\)Jinvoke/vendor/yaml3/composer.pyUTHWWux PKHIIv<c"invoke/vendor/yaml3/constructor.pyUTHWWux PKHIIkC invoke/vendor/yaml3/cyaml.pyUTHWWux PKHIIl invoke/vendor/yaml3/dumper.pyUTHWWux PKHIIsʧinvoke/vendor/yaml3/emitter.pyUTHWWux PKHIIHV invoke/vendor/yaml3/error.pyUTHWWux PKHII !invoke/vendor/yaml3/events.pyUTHWWux PKHII`լr4$invoke/vendor/yaml3/loader.pyUTHWWux PKHII h%invoke/vendor/yaml3/nodes.pyUTHWWux PKHIII@cE'invoke/vendor/yaml3/parser.pyUTHWWux PKHII sMm=6invoke/vendor/yaml3/reader.pyUTHWWux PKHIIE 4">invoke/vendor/yaml3/representer.pyUTHWWux PKHII ]t"CIinvoke/vendor/yaml3/resolver.pyUTHWWux PKHIIEy$Qinvoke/vendor/yaml3/scanner.pyUTHWWux PKHII_= (3,) PY2 = not PY3 string_types = str, text_type = str getcwdu = os.getcwd def surrogate_escape(error): """ Simulate the Python 3 ``surrogateescape`` handler, but for Python 2 only. """ chars = error.object[error.start:error.end] assert len(chars) == 1 val = ord(chars) val += 0xdc00 return __builtin__.unichr(val), error.end if PY2: import __builtin__ string_types = __builtin__.basestring, text_type = __builtin__.unicode getcwdu = os.getcwdu codecs.register_error('surrogateescape', surrogate_escape) @contextlib.contextmanager def io_error_compat(): try: yield except IOError as io_err: # On Python 2, io.open raises IOError; transform to OSError for # future compatibility. os_err = OSError(*io_err.args) os_err.filename = getattr(io_err, 'filename', None) raise os_err ############################################################################## __all__ = ['Path', 'CaseInsensitivePattern'] LINESEPS = ['\r\n', '\r', '\n'] U_LINESEPS = LINESEPS + ['\u0085', '\u2028', '\u2029'] NEWLINE = re.compile('|'.join(LINESEPS)) U_NEWLINE = re.compile('|'.join(U_LINESEPS)) NL_END = re.compile(r'(?:{0})$'.format(NEWLINE.pattern)) U_NL_END = re.compile(r'(?:{0})$'.format(U_NEWLINE.pattern)) try: import pkg_resources __version__ = pkg_resources.require('path.py')[0].version except Exception: __version__ = '8.2.1' # XXX-MODIFIED-WAS: 'unknown' class TreeWalkWarning(Warning): pass # from jaraco.functools def compose(*funcs): compose_two = lambda f1, f2: lambda *args, **kwargs: f1(f2(*args, **kwargs)) return functools.reduce(compose_two, funcs) def simple_cache(func): """ Save results for the :meth:'path.using_module' classmethod. When Python 3.2 is available, use functools.lru_cache instead. """ saved_results = {} def wrapper(cls, module): if module in saved_results: return saved_results[module] saved_results[module] = func(cls, module) return saved_results[module] return wrapper class ClassProperty(property): def __get__(self, cls, owner): return self.fget.__get__(None, owner)() class multimethod(object): """ Acts like a classmethod when invoked from the class and like an instancemethod when invoked from the instance. """ def __init__(self, func): self.func = func def __get__(self, instance, owner): return ( functools.partial(self.func, owner) if instance is None else functools.partial(self.func, owner, instance) ) class Path(text_type): """ Represents a filesystem path. For documentation on individual methods, consult their counterparts in :mod:`os.path`. Some methods are additionally included from :mod:`shutil`. The functions are linked directly into the class namespace such that they will be bound to the Path instance. For example, ``Path(src).copy(target)`` is equivalent to ``shutil.copy(src, target)``. Therefore, when referencing the docs for these methods, assume `src` references `self`, the Path instance. """ module = os.path """ The path module to use for path operations. .. seealso:: :mod:`os.path` """ def __init__(self, other=''): if other is None: raise TypeError("Invalid initial value for path: None") @classmethod @simple_cache def using_module(cls, module): subclass_name = cls.__name__ + '_' + module.__name__ if PY2: subclass_name = str(subclass_name) bases = (cls,) ns = {'module': module} return type(subclass_name, bases, ns) @ClassProperty @classmethod def _next_class(cls): """ What class should be used to construct new instances from this class """ return cls @classmethod def _always_unicode(cls, path): """ Ensure the path as retrieved from a Python API, such as :func:`os.listdir`, is a proper Unicode string. """ if PY3 or isinstance(path, text_type): return path return path.decode(sys.getfilesystemencoding(), 'surrogateescape') # --- Special Python methods. def __repr__(self): return '%s(%s)' % (type(self).__name__, super(Path, self).__repr__()) # Adding a Path and a string yields a Path. def __add__(self, more): try: return self._next_class(super(Path, self).__add__(more)) except TypeError: # Python bug return NotImplemented def __radd__(self, other): if not isinstance(other, string_types): return NotImplemented return self._next_class(other.__add__(self)) # The / operator joins Paths. def __div__(self, rel): """ fp.__div__(rel) == fp / rel == fp.joinpath(rel) Join two path components, adding a separator character if needed. .. seealso:: :func:`os.path.join` """ return self._next_class(self.module.join(self, rel)) # Make the / operator work even when true division is enabled. __truediv__ = __div__ # The / operator joins Paths the other way around def __rdiv__(self, rel): """ fp.__rdiv__(rel) == rel / fp Join two path components, adding a separator character if needed. .. seealso:: :func:`os.path.join` """ return self._next_class(self.module.join(rel, self)) # Make the / operator work even when true division is enabled. __rtruediv__ = __rdiv__ def __enter__(self): self._old_dir = self.getcwd() os.chdir(self) return self def __exit__(self, *_): os.chdir(self._old_dir) @classmethod def getcwd(cls): """ Return the current working directory as a path object. .. seealso:: :func:`os.getcwdu` """ return cls(getcwdu()) # # --- Operations on Path strings. def abspath(self): """ .. seealso:: :func:`os.path.abspath` """ return self._next_class(self.module.abspath(self)) def normcase(self): """ .. seealso:: :func:`os.path.normcase` """ return self._next_class(self.module.normcase(self)) def normpath(self): """ .. seealso:: :func:`os.path.normpath` """ return self._next_class(self.module.normpath(self)) def realpath(self): """ .. seealso:: :func:`os.path.realpath` """ return self._next_class(self.module.realpath(self)) def expanduser(self): """ .. seealso:: :func:`os.path.expanduser` """ return self._next_class(self.module.expanduser(self)) def expandvars(self): """ .. seealso:: :func:`os.path.expandvars` """ return self._next_class(self.module.expandvars(self)) def dirname(self): """ .. seealso:: :attr:`parent`, :func:`os.path.dirname` """ return self._next_class(self.module.dirname(self)) def basename(self): """ .. seealso:: :attr:`name`, :func:`os.path.basename` """ return self._next_class(self.module.basename(self)) def expand(self): """ Clean up a filename by calling :meth:`expandvars()`, :meth:`expanduser()`, and :meth:`normpath()` on it. This is commonly everything needed to clean up a filename read from a configuration file, for example. """ return self.expandvars().expanduser().normpath() @property def namebase(self): """ The same as :meth:`name`, but with one file extension stripped off. For example, ``Path('/home/guido/python.tar.gz').name == 'python.tar.gz'``, but ``Path('/home/guido/python.tar.gz').namebase == 'python.tar'``. """ base, ext = self.module.splitext(self.name) return base @property def ext(self): """ The file extension, for example ``'.py'``. """ f, ext = self.module.splitext(self) return ext @property def drive(self): """ The drive specifier, for example ``'C:'``. This is always empty on systems that don't use drive specifiers. """ drive, r = self.module.splitdrive(self) return self._next_class(drive) parent = property( dirname, None, None, """ This path's parent directory, as a new Path object. For example, ``Path('/usr/local/lib/libpython.so').parent == Path('/usr/local/lib')`` .. seealso:: :meth:`dirname`, :func:`os.path.dirname` """) name = property( basename, None, None, """ The name of this file or directory without the full path. For example, ``Path('/usr/local/lib/libpython.so').name == 'libpython.so'`` .. seealso:: :meth:`basename`, :func:`os.path.basename` """) def splitpath(self): """ p.splitpath() -> Return ``(p.parent, p.name)``. .. seealso:: :attr:`parent`, :attr:`name`, :func:`os.path.split` """ parent, child = self.module.split(self) return self._next_class(parent), child def splitdrive(self): """ p.splitdrive() -> Return ``(p.drive, )``. Split the drive specifier from this path. If there is no drive specifier, :samp:`{p.drive}` is empty, so the return value is simply ``(Path(''), p)``. This is always the case on Unix. .. seealso:: :func:`os.path.splitdrive` """ drive, rel = self.module.splitdrive(self) return self._next_class(drive), rel def splitext(self): """ p.splitext() -> Return ``(p.stripext(), p.ext)``. Split the filename extension from this path and return the two parts. Either part may be empty. The extension is everything from ``'.'`` to the end of the last path segment. This has the property that if ``(a, b) == p.splitext()``, then ``a + b == p``. .. seealso:: :func:`os.path.splitext` """ filename, ext = self.module.splitext(self) return self._next_class(filename), ext def stripext(self): """ p.stripext() -> Remove one file extension from the path. For example, ``Path('/home/guido/python.tar.gz').stripext()`` returns ``Path('/home/guido/python.tar')``. """ return self.splitext()[0] def splitunc(self): """ .. seealso:: :func:`os.path.splitunc` """ unc, rest = self.module.splitunc(self) return self._next_class(unc), rest @property def uncshare(self): """ The UNC mount point for this path. This is empty for paths on local drives. """ unc, r = self.module.splitunc(self) return self._next_class(unc) @multimethod def joinpath(cls, first, *others): """ Join first to zero or more :class:`Path` components, adding a separator character (:samp:`{first}.module.sep`) if needed. Returns a new instance of :samp:`{first}._next_class`. .. seealso:: :func:`os.path.join` """ if not isinstance(first, cls): first = cls(first) return first._next_class(first.module.join(first, *others)) def splitall(self): r""" Return a list of the path components in this path. The first item in the list will be a Path. Its value will be either :data:`os.curdir`, :data:`os.pardir`, empty, or the root directory of this path (for example, ``'/'`` or ``'C:\\'``). The other items in the list will be strings. ``path.Path.joinpath(*result)`` will yield the original path. """ parts = [] loc = self while loc != os.curdir and loc != os.pardir: prev = loc loc, child = prev.splitpath() if loc == prev: break parts.append(child) parts.append(loc) parts.reverse() return parts def relpath(self, start='.'): """ Return this path as a relative path, based from `start`, which defaults to the current working directory. """ cwd = self._next_class(start) return cwd.relpathto(self) def relpathto(self, dest): """ Return a relative path from `self` to `dest`. If there is no relative path from `self` to `dest`, for example if they reside on different drives in Windows, then this returns ``dest.abspath()``. """ origin = self.abspath() dest = self._next_class(dest).abspath() orig_list = origin.normcase().splitall() # Don't normcase dest! We want to preserve the case. dest_list = dest.splitall() if orig_list[0] != self.module.normcase(dest_list[0]): # Can't get here from there. return dest # Find the location where the two paths start to differ. i = 0 for start_seg, dest_seg in zip(orig_list, dest_list): if start_seg != self.module.normcase(dest_seg): break i += 1 # Now i is the point where the two paths diverge. # Need a certain number of "os.pardir"s to work up # from the origin to the point of divergence. segments = [os.pardir] * (len(orig_list) - i) # Need to add the diverging part of dest_list. segments += dest_list[i:] if len(segments) == 0: # If they happen to be identical, use os.curdir. relpath = os.curdir else: relpath = self.module.join(*segments) return self._next_class(relpath) # --- Listing, searching, walking, and matching def listdir(self, pattern=None): """ D.listdir() -> List of items in this directory. Use :meth:`files` or :meth:`dirs` instead if you want a listing of just files or just subdirectories. The elements of the list are Path objects. With the optional `pattern` argument, this only lists items whose names match the given pattern. .. seealso:: :meth:`files`, :meth:`dirs` """ if pattern is None: pattern = '*' return [ self / child for child in map(self._always_unicode, os.listdir(self)) if self._next_class(child).fnmatch(pattern) ] def dirs(self, pattern=None): """ D.dirs() -> List of this directory's subdirectories. The elements of the list are Path objects. This does not walk recursively into subdirectories (but see :meth:`walkdirs`). With the optional `pattern` argument, this only lists directories whose names match the given pattern. For example, ``d.dirs('build-*')``. """ return [p for p in self.listdir(pattern) if p.isdir()] def files(self, pattern=None): """ D.files() -> List of the files in this directory. The elements of the list are Path objects. This does not walk into subdirectories (see :meth:`walkfiles`). With the optional `pattern` argument, this only lists files whose names match the given pattern. For example, ``d.files('*.pyc')``. """ return [p for p in self.listdir(pattern) if p.isfile()] def walk(self, pattern=None, errors='strict'): """ D.walk() -> iterator over files and subdirs, recursively. The iterator yields Path objects naming each child item of this directory and its descendants. This requires that ``D.isdir()``. This performs a depth-first traversal of the directory tree. Each directory is returned just before all its children. The `errors=` keyword argument controls behavior when an error occurs. The default is ``'strict'``, which causes an exception. Other allowed values are ``'warn'`` (which reports the error via :func:`warnings.warn()`), and ``'ignore'``. `errors` may also be an arbitrary callable taking a msg parameter. """ class Handlers: def strict(msg): raise def warn(msg): warnings.warn(msg, TreeWalkWarning) def ignore(msg): pass if not callable(errors) and errors not in vars(Handlers): raise ValueError("invalid errors parameter") errors = vars(Handlers).get(errors, errors) try: childList = self.listdir() except Exception: exc = sys.exc_info()[1] tmpl = "Unable to list directory '%(self)s': %(exc)s" msg = tmpl % locals() errors(msg) return for child in childList: if pattern is None or child.fnmatch(pattern): yield child try: isdir = child.isdir() except Exception: exc = sys.exc_info()[1] tmpl = "Unable to access '%(child)s': %(exc)s" msg = tmpl % locals() errors(msg) isdir = False if isdir: for item in child.walk(pattern, errors): yield item def walkdirs(self, pattern=None, errors='strict'): """ D.walkdirs() -> iterator over subdirs, recursively. With the optional `pattern` argument, this yields only directories whose names match the given pattern. For example, ``mydir.walkdirs('*test')`` yields only directories with names ending in ``'test'``. The `errors=` keyword argument controls behavior when an error occurs. The default is ``'strict'``, which causes an exception. The other allowed values are ``'warn'`` (which reports the error via :func:`warnings.warn()`), and ``'ignore'``. """ if errors not in ('strict', 'warn', 'ignore'): raise ValueError("invalid errors parameter") try: dirs = self.dirs() except Exception: if errors == 'ignore': return elif errors == 'warn': warnings.warn( "Unable to list directory '%s': %s" % (self, sys.exc_info()[1]), TreeWalkWarning) return else: raise for child in dirs: if pattern is None or child.fnmatch(pattern): yield child for subsubdir in child.walkdirs(pattern, errors): yield subsubdir def walkfiles(self, pattern=None, errors='strict'): """ D.walkfiles() -> iterator over files in D, recursively. The optional argument `pattern` limits the results to files with names that match the pattern. For example, ``mydir.walkfiles('*.tmp')`` yields only files with the ``.tmp`` extension. """ if errors not in ('strict', 'warn', 'ignore'): raise ValueError("invalid errors parameter") try: childList = self.listdir() except Exception: if errors == 'ignore': return elif errors == 'warn': warnings.warn( "Unable to list directory '%s': %s" % (self, sys.exc_info()[1]), TreeWalkWarning) return else: raise for child in childList: try: isfile = child.isfile() isdir = not isfile and child.isdir() except: if errors == 'ignore': continue elif errors == 'warn': warnings.warn( "Unable to access '%s': %s" % (self, sys.exc_info()[1]), TreeWalkWarning) continue else: raise if isfile: if pattern is None or child.fnmatch(pattern): yield child elif isdir: for f in child.walkfiles(pattern, errors): yield f def fnmatch(self, pattern, normcase=None): """ Return ``True`` if `self.name` matches the given `pattern`. `pattern` - A filename pattern with wildcards, for example ``'*.py'``. If the pattern contains a `normcase` attribute, it is applied to the name and path prior to comparison. `normcase` - (optional) A function used to normalize the pattern and filename before matching. Defaults to :meth:`self.module`, which defaults to :meth:`os.path.normcase`. .. seealso:: :func:`fnmatch.fnmatch` """ default_normcase = getattr(pattern, 'normcase', self.module.normcase) normcase = normcase or default_normcase name = normcase(self.name) pattern = normcase(pattern) return fnmatch.fnmatchcase(name, pattern) def glob(self, pattern): """ Return a list of Path objects that match the pattern. `pattern` - a path relative to this directory, with wildcards. For example, ``Path('/users').glob('*/bin/*')`` returns a list of all the files users have in their :file:`bin` directories. .. seealso:: :func:`glob.glob` """ cls = self._next_class return [cls(s) for s in glob.glob(self / pattern)] # # --- Reading or writing an entire file at once. def open(self, *args, **kwargs): """ Open this file and return a corresponding :class:`file` object. Keyword arguments work as in :func:`io.open`. If the file cannot be opened, an :class:`~exceptions.OSError` is raised. """ with io_error_compat(): return io.open(self, *args, **kwargs) def bytes(self): """ Open this file, read all bytes, return them as a string. """ with self.open('rb') as f: return f.read() def chunks(self, size, *args, **kwargs): """ Returns a generator yielding chunks of the file, so it can be read piece by piece with a simple for loop. Any argument you pass after `size` will be passed to :meth:`open`. :example: >>> hash = hashlib.md5() >>> for chunk in Path("path.py").chunks(8192, mode='rb'): ... hash.update(chunk) This will read the file by chunks of 8192 bytes. """ with self.open(*args, **kwargs) as f: for chunk in iter(lambda: f.read(size) or None, None): yield chunk def write_bytes(self, bytes, append=False): """ Open this file and write the given bytes to it. Default behavior is to overwrite any existing file. Call ``p.write_bytes(bytes, append=True)`` to append instead. """ if append: mode = 'ab' else: mode = 'wb' with self.open(mode) as f: f.write(bytes) def text(self, encoding=None, errors='strict'): r""" Open this file, read it in, return the content as a string. All newline sequences are converted to ``'\n'``. Keyword arguments will be passed to :meth:`open`. .. seealso:: :meth:`lines` """ with self.open(mode='r', encoding=encoding, errors=errors) as f: return U_NEWLINE.sub('\n', f.read()) def write_text(self, text, encoding=None, errors='strict', linesep=os.linesep, append=False): r""" Write the given text to this file. The default behavior is to overwrite any existing file; to append instead, use the `append=True` keyword argument. There are two differences between :meth:`write_text` and :meth:`write_bytes`: newline handling and Unicode handling. See below. Parameters: `text` - str/unicode - The text to be written. `encoding` - str - The Unicode encoding that will be used. This is ignored if `text` isn't a Unicode string. `errors` - str - How to handle Unicode encoding errors. Default is ``'strict'``. See ``help(unicode.encode)`` for the options. This is ignored if `text` isn't a Unicode string. `linesep` - keyword argument - str/unicode - The sequence of characters to be used to mark end-of-line. The default is :data:`os.linesep`. You can also specify ``None`` to leave all newlines as they are in `text`. `append` - keyword argument - bool - Specifies what to do if the file already exists (``True``: append to the end of it; ``False``: overwrite it.) The default is ``False``. --- Newline handling. ``write_text()`` converts all standard end-of-line sequences (``'\n'``, ``'\r'``, and ``'\r\n'``) to your platform's default end-of-line sequence (see :data:`os.linesep`; on Windows, for example, the end-of-line marker is ``'\r\n'``). If you don't like your platform's default, you can override it using the `linesep=` keyword argument. If you specifically want ``write_text()`` to preserve the newlines as-is, use ``linesep=None``. This applies to Unicode text the same as to 8-bit text, except there are three additional standard Unicode end-of-line sequences: ``u'\x85'``, ``u'\r\x85'``, and ``u'\u2028'``. (This is slightly different from when you open a file for writing with ``fopen(filename, "w")`` in C or ``open(filename, 'w')`` in Python.) --- Unicode If `text` isn't Unicode, then apart from newline handling, the bytes are written verbatim to the file. The `encoding` and `errors` arguments are not used and must be omitted. If `text` is Unicode, it is first converted to :func:`bytes` using the specified `encoding` (or the default encoding if `encoding` isn't specified). The `errors` argument applies only to this conversion. """ if isinstance(text, text_type): if linesep is not None: text = U_NEWLINE.sub(linesep, text) text = text.encode(encoding or sys.getdefaultencoding(), errors) else: assert encoding is None text = NEWLINE.sub(linesep, text) self.write_bytes(text, append=append) def lines(self, encoding=None, errors='strict', retain=True): r""" Open this file, read all lines, return them in a list. Optional arguments: `encoding` - The Unicode encoding (or character set) of the file. The default is ``None``, meaning the content of the file is read as 8-bit characters and returned as a list of (non-Unicode) str objects. `errors` - How to handle Unicode errors; see help(str.decode) for the options. Default is ``'strict'``. `retain` - If ``True``, retain newline characters; but all newline character combinations (``'\r'``, ``'\n'``, ``'\r\n'``) are translated to ``'\n'``. If ``False``, newline characters are stripped off. Default is ``True``. This uses ``'U'`` mode. .. seealso:: :meth:`text` """ if encoding is None and retain: with self.open('U') as f: return f.readlines() else: return self.text(encoding, errors).splitlines(retain) def write_lines(self, lines, encoding=None, errors='strict', linesep=os.linesep, append=False): r""" Write the given lines of text to this file. By default this overwrites any existing file at this path. This puts a platform-specific newline sequence on every line. See `linesep` below. `lines` - A list of strings. `encoding` - A Unicode encoding to use. This applies only if `lines` contains any Unicode strings. `errors` - How to handle errors in Unicode encoding. This also applies only to Unicode strings. linesep - The desired line-ending. This line-ending is applied to every line. If a line already has any standard line ending (``'\r'``, ``'\n'``, ``'\r\n'``, ``u'\x85'``, ``u'\r\x85'``, ``u'\u2028'``), that will be stripped off and this will be used instead. The default is os.linesep, which is platform-dependent (``'\r\n'`` on Windows, ``'\n'`` on Unix, etc.). Specify ``None`` to write the lines as-is, like :meth:`file.writelines`. Use the keyword argument ``append=True`` to append lines to the file. The default is to overwrite the file. .. warning :: When you use this with Unicode data, if the encoding of the existing data in the file is different from the encoding you specify with the `encoding=` parameter, the result is mixed-encoding data, which can really confuse someone trying to read the file later. """ with self.open('ab' if append else 'wb') as f: for l in lines: isUnicode = isinstance(l, text_type) if linesep is not None: pattern = U_NL_END if isUnicode else NL_END l = pattern.sub('', l) + linesep if isUnicode: l = l.encode(encoding or sys.getdefaultencoding(), errors) f.write(l) def read_md5(self): """ Calculate the md5 hash for this file. This reads through the entire file. .. seealso:: :meth:`read_hash` """ return self.read_hash('md5') def _hash(self, hash_name): """ Returns a hash object for the file at the current path. `hash_name` should be a hash algo name (such as ``'md5'`` or ``'sha1'``) that's available in the :mod:`hashlib` module. """ m = hashlib.new(hash_name) for chunk in self.chunks(8192, mode="rb"): m.update(chunk) return m def read_hash(self, hash_name): """ Calculate given hash for this file. List of supported hashes can be obtained from :mod:`hashlib` package. This reads the entire file. .. seealso:: :meth:`hashlib.hash.digest` """ return self._hash(hash_name).digest() def read_hexhash(self, hash_name): """ Calculate given hash for this file, returning hexdigest. List of supported hashes can be obtained from :mod:`hashlib` package. This reads the entire file. .. seealso:: :meth:`hashlib.hash.hexdigest` """ return self._hash(hash_name).hexdigest() # --- Methods for querying the filesystem. # N.B. On some platforms, the os.path functions may be implemented in C # (e.g. isdir on Windows, Python 3.2.2), and compiled functions don't get # bound. Playing it safe and wrapping them all in method calls. def isabs(self): """ .. seealso:: :func:`os.path.isabs` """ return self.module.isabs(self) def exists(self): """ .. seealso:: :func:`os.path.exists` """ return self.module.exists(self) def isdir(self): """ .. seealso:: :func:`os.path.isdir` """ return self.module.isdir(self) def isfile(self): """ .. seealso:: :func:`os.path.isfile` """ return self.module.isfile(self) def islink(self): """ .. seealso:: :func:`os.path.islink` """ return self.module.islink(self) def ismount(self): """ .. seealso:: :func:`os.path.ismount` """ return self.module.ismount(self) def samefile(self, other): """ .. seealso:: :func:`os.path.samefile` """ if not hasattr(self.module, 'samefile'): other = Path(other).realpath().normpath().normcase() return self.realpath().normpath().normcase() == other return self.module.samefile(self, other) def getatime(self): """ .. seealso:: :attr:`atime`, :func:`os.path.getatime` """ return self.module.getatime(self) atime = property( getatime, None, None, """ Last access time of the file. .. seealso:: :meth:`getatime`, :func:`os.path.getatime` """) def getmtime(self): """ .. seealso:: :attr:`mtime`, :func:`os.path.getmtime` """ return self.module.getmtime(self) mtime = property( getmtime, None, None, """ Last-modified time of the file. .. seealso:: :meth:`getmtime`, :func:`os.path.getmtime` """) def getctime(self): """ .. seealso:: :attr:`ctime`, :func:`os.path.getctime` """ return self.module.getctime(self) ctime = property( getctime, None, None, """ Creation time of the file. .. seealso:: :meth:`getctime`, :func:`os.path.getctime` """) def getsize(self): """ .. seealso:: :attr:`size`, :func:`os.path.getsize` """ return self.module.getsize(self) size = property( getsize, None, None, """ Size of the file, in bytes. .. seealso:: :meth:`getsize`, :func:`os.path.getsize` """) if hasattr(os, 'access'): def access(self, mode): """ Return ``True`` if current user has access to this path. mode - One of the constants :data:`os.F_OK`, :data:`os.R_OK`, :data:`os.W_OK`, :data:`os.X_OK` .. seealso:: :func:`os.access` """ return os.access(self, mode) def stat(self): """ Perform a ``stat()`` system call on this path. .. seealso:: :meth:`lstat`, :func:`os.stat` """ return os.stat(self) def lstat(self): """ Like :meth:`stat`, but do not follow symbolic links. .. seealso:: :meth:`stat`, :func:`os.lstat` """ return os.lstat(self) def __get_owner_windows(self): """ Return the name of the owner of this file or directory. Follow symbolic links. Return a name of the form ``r'DOMAIN\\User Name'``; may be a group. .. seealso:: :attr:`owner` """ desc = win32security.GetFileSecurity( self, win32security.OWNER_SECURITY_INFORMATION) sid = desc.GetSecurityDescriptorOwner() account, domain, typecode = win32security.LookupAccountSid(None, sid) return domain + '\\' + account def __get_owner_unix(self): """ Return the name of the owner of this file or directory. Follow symbolic links. .. seealso:: :attr:`owner` """ st = self.stat() return pwd.getpwuid(st.st_uid).pw_name def __get_owner_not_implemented(self): raise NotImplementedError("Ownership not available on this platform.") if 'win32security' in globals(): get_owner = __get_owner_windows elif 'pwd' in globals(): get_owner = __get_owner_unix else: get_owner = __get_owner_not_implemented owner = property( get_owner, None, None, """ Name of the owner of this file or directory. .. seealso:: :meth:`get_owner`""") if hasattr(os, 'statvfs'): def statvfs(self): """ Perform a ``statvfs()`` system call on this path. .. seealso:: :func:`os.statvfs` """ return os.statvfs(self) if hasattr(os, 'pathconf'): def pathconf(self, name): """ .. seealso:: :func:`os.pathconf` """ return os.pathconf(self, name) # # --- Modifying operations on files and directories def utime(self, times): """ Set the access and modified times of this file. .. seealso:: :func:`os.utime` """ os.utime(self, times) return self def chmod(self, mode): """ Set the mode. May be the new mode (os.chmod behavior) or a `symbolic mode `_. .. seealso:: :func:`os.chmod` """ if isinstance(mode, string_types): mask = _multi_permission_mask(mode) mode = mask(self.stat().st_mode) os.chmod(self, mode) return self def chown(self, uid=-1, gid=-1): """ Change the owner and group by names rather than the uid or gid numbers. .. seealso:: :func:`os.chown` """ if hasattr(os, 'chown'): if 'pwd' in globals() and isinstance(uid, string_types): uid = pwd.getpwnam(uid).pw_uid if 'grp' in globals() and isinstance(gid, string_types): gid = grp.getgrnam(gid).gr_gid os.chown(self, uid, gid) else: raise NotImplementedError("Ownership not available on this platform.") return self def rename(self, new): """ .. seealso:: :func:`os.rename` """ os.rename(self, new) return self._next_class(new) def renames(self, new): """ .. seealso:: :func:`os.renames` """ os.renames(self, new) return self._next_class(new) # # --- Create/delete operations on directories def mkdir(self, mode=0o777): """ .. seealso:: :func:`os.mkdir` """ os.mkdir(self, mode) return self def mkdir_p(self, mode=0o777): """ Like :meth:`mkdir`, but does not raise an exception if the directory already exists. """ try: self.mkdir(mode) except OSError: _, e, _ = sys.exc_info() if e.errno != errno.EEXIST: raise return self def makedirs(self, mode=0o777): """ .. seealso:: :func:`os.makedirs` """ os.makedirs(self, mode) return self def makedirs_p(self, mode=0o777): """ Like :meth:`makedirs`, but does not raise an exception if the directory already exists. """ try: self.makedirs(mode) except OSError: _, e, _ = sys.exc_info() if e.errno != errno.EEXIST: raise return self def rmdir(self): """ .. seealso:: :func:`os.rmdir` """ os.rmdir(self) return self def rmdir_p(self): """ Like :meth:`rmdir`, but does not raise an exception if the directory is not empty or does not exist. """ try: self.rmdir() except OSError: _, e, _ = sys.exc_info() if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST: raise return self def removedirs(self): """ .. seealso:: :func:`os.removedirs` """ os.removedirs(self) return self def removedirs_p(self): """ Like :meth:`removedirs`, but does not raise an exception if the directory is not empty or does not exist. """ try: self.removedirs() except OSError: _, e, _ = sys.exc_info() if e.errno != errno.ENOTEMPTY and e.errno != errno.EEXIST: raise return self # --- Modifying operations on files def touch(self): """ Set the access/modified times of this file to the current time. Create the file if it does not exist. """ fd = os.open(self, os.O_WRONLY | os.O_CREAT, 0o666) os.close(fd) os.utime(self, None) return self def remove(self): """ .. seealso:: :func:`os.remove` """ os.remove(self) return self def remove_p(self): """ Like :meth:`remove`, but does not raise an exception if the file does not exist. """ try: self.unlink() except OSError: _, e, _ = sys.exc_info() if e.errno != errno.ENOENT: raise return self def unlink(self): """ .. seealso:: :func:`os.unlink` """ os.unlink(self) return self def unlink_p(self): """ Like :meth:`unlink`, but does not raise an exception if the file does not exist. """ self.remove_p() return self # --- Links if hasattr(os, 'link'): def link(self, newpath): """ Create a hard link at `newpath`, pointing to this file. .. seealso:: :func:`os.link` """ os.link(self, newpath) return self._next_class(newpath) if hasattr(os, 'symlink'): def symlink(self, newlink): """ Create a symbolic link at `newlink`, pointing here. .. seealso:: :func:`os.symlink` """ os.symlink(self, newlink) return self._next_class(newlink) if hasattr(os, 'readlink'): def readlink(self): """ Return the path to which this symbolic link points. The result may be an absolute or a relative path. .. seealso:: :meth:`readlinkabs`, :func:`os.readlink` """ return self._next_class(os.readlink(self)) def readlinkabs(self): """ Return the path to which this symbolic link points. The result is always an absolute path. .. seealso:: :meth:`readlink`, :func:`os.readlink` """ p = self.readlink() if p.isabs(): return p else: return (self.parent / p).abspath() # High-level functions from shutil # These functions will be bound to the instance such that # Path(name).copy(target) will invoke shutil.copy(name, target) copyfile = shutil.copyfile copymode = shutil.copymode copystat = shutil.copystat copy = shutil.copy copy2 = shutil.copy2 copytree = shutil.copytree if hasattr(shutil, 'move'): move = shutil.move rmtree = shutil.rmtree def rmtree_p(self): """ Like :meth:`rmtree`, but does not raise an exception if the directory does not exist. """ try: self.rmtree() except OSError: _, e, _ = sys.exc_info() if e.errno != errno.ENOENT: raise return self def chdir(self): """ .. seealso:: :func:`os.chdir` """ os.chdir(self) cd = chdir def merge_tree(self, dst, symlinks=False, *args, **kwargs): """ Copy entire contents of self to dst, overwriting existing contents in dst with those in self. If the additional keyword `update` is True, each `src` will only be copied if `dst` does not exist, or `src` is newer than `dst`. Note that the technique employed stages the files in a temporary directory first, so this function is not suitable for merging trees with large files, especially if the temporary directory is not capable of storing a copy of the entire source tree. """ update = kwargs.pop('update', False) with tempdir() as _temp_dir: # first copy the tree to a stage directory to support # the parameters and behavior of copytree. stage = _temp_dir / str(hash(self)) self.copytree(stage, symlinks, *args, **kwargs) # now copy everything from the stage directory using # the semantics of dir_util.copy_tree dir_util.copy_tree(stage, dst, preserve_symlinks=symlinks, update=update) # # --- Special stuff from os if hasattr(os, 'chroot'): def chroot(self): """ .. seealso:: :func:`os.chroot` """ os.chroot(self) if hasattr(os, 'startfile'): def startfile(self): """ .. seealso:: :func:`os.startfile` """ os.startfile(self) return self # in-place re-writing, courtesy of Martijn Pieters # http://www.zopatista.com/python/2013/11/26/inplace-file-rewriting/ @contextlib.contextmanager def in_place(self, mode='r', buffering=-1, encoding=None, errors=None, newline=None, backup_extension=None): """ A context in which a file may be re-written in-place with new content. Yields a tuple of :samp:`({readable}, {writable})` file objects, where `writable` replaces `readable`. If an exception occurs, the old file is restored, removing the written data. Mode *must not* use ``'w'``, ``'a'``, or ``'+'``; only read-only-modes are allowed. A :exc:`ValueError` is raised on invalid modes. For example, to add line numbers to a file:: p = Path(filename) assert p.isfile() with p.in_place() as (reader, writer): for number, line in enumerate(reader, 1): writer.write('{0:3}: '.format(number))) writer.write(line) Thereafter, the file at `filename` will have line numbers in it. """ import io if set(mode).intersection('wa+'): raise ValueError('Only read-only file modes can be used') # move existing file to backup, create new file with same permissions # borrowed extensively from the fileinput module backup_fn = self + (backup_extension or os.extsep + 'bak') try: os.unlink(backup_fn) except os.error: pass os.rename(self, backup_fn) readable = io.open(backup_fn, mode, buffering=buffering, encoding=encoding, errors=errors, newline=newline) try: perm = os.fstat(readable.fileno()).st_mode except OSError: writable = open(self, 'w' + mode.replace('r', ''), buffering=buffering, encoding=encoding, errors=errors, newline=newline) else: os_mode = os.O_CREAT | os.O_WRONLY | os.O_TRUNC if hasattr(os, 'O_BINARY'): os_mode |= os.O_BINARY fd = os.open(self, os_mode, perm) writable = io.open(fd, "w" + mode.replace('r', ''), buffering=buffering, encoding=encoding, errors=errors, newline=newline) try: if hasattr(os, 'chmod'): os.chmod(self, perm) except OSError: pass try: yield readable, writable except Exception: # move backup back readable.close() writable.close() try: os.unlink(self) except os.error: pass os.rename(backup_fn, self) raise else: readable.close() writable.close() finally: try: os.unlink(backup_fn) except os.error: pass @ClassProperty @classmethod def special(cls): """ Return a SpecialResolver object suitable referencing a suitable directory for the relevant platform for the given type of content. For example, to get a user config directory, invoke: dir = Path.special().user.config Uses the `appdirs `_ to resolve the paths in a platform-friendly way. To create a config directory for 'My App', consider: dir = Path.special("My App").user.config.makedirs_p() If the ``appdirs`` module is not installed, invocation of special will raise an ImportError. """ return functools.partial(SpecialResolver, cls) class SpecialResolver(object): class ResolverScope: def __init__(self, paths, scope): self.paths = paths self.scope = scope def __getattr__(self, class_): return self.paths.get_dir(self.scope, class_) def __init__(self, path_class, *args, **kwargs): appdirs = importlib.import_module('appdirs') # let appname default to None until # https://github.com/ActiveState/appdirs/issues/55 is solved. not args and kwargs.setdefault('appname', None) vars(self).update( path_class=path_class, wrapper=appdirs.AppDirs(*args, **kwargs), ) def __getattr__(self, scope): return self.ResolverScope(self, scope) def get_dir(self, scope, class_): """ Return the callable function from appdirs, but with the result wrapped in self.path_class """ prop_name = '{scope}_{class_}_dir'.format(**locals()) value = getattr(self.wrapper, prop_name) MultiPath = Multi.for_class(self.path_class) return MultiPath.detect(value) class Multi: """ A mix-in for a Path which may contain multiple Path separated by pathsep. """ @classmethod def for_class(cls, path_cls): name = 'Multi' + path_cls.__name__ if PY2: name = str(name) return type(name, (cls, path_cls), {}) @classmethod def detect(cls, input): if os.pathsep not in input: cls = cls._next_class return cls(input) def __iter__(self): return iter(map(self._next_class, self.split(os.pathsep))) @ClassProperty @classmethod def _next_class(cls): """ Multi-subclasses should use the parent class """ return next( class_ for class_ in cls.__mro__ if not issubclass(class_, Multi) ) class tempdir(Path): """ A temporary directory via :func:`tempfile.mkdtemp`, and constructed with the same parameters that you can use as a context manager. Example: with tempdir() as d: # do stuff with the Path object "d" # here the directory is deleted automatically .. seealso:: :func:`tempfile.mkdtemp` """ @ClassProperty @classmethod def _next_class(cls): return Path def __new__(cls, *args, **kwargs): dirname = tempfile.mkdtemp(*args, **kwargs) return super(tempdir, cls).__new__(cls, dirname) def __init__(self, *args, **kwargs): pass def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): if not exc_value: self.rmtree() def _multi_permission_mask(mode): """ Support multiple, comma-separated Unix chmod symbolic modes. >>> _multi_permission_mask('a=r,u+w')(0) == 0o644 True """ compose = lambda f, g: lambda *args, **kwargs: g(f(*args, **kwargs)) return functools.reduce(compose, map(_permission_mask, mode.split(','))) def _permission_mask(mode): """ Convert a Unix chmod symbolic mode like ``'ugo+rwx'`` to a function suitable for applying to a mask to affect that change. >>> mask = _permission_mask('ugo+rwx') >>> mask(0o554) == 0o777 True >>> _permission_mask('go-x')(0o777) == 0o766 True >>> _permission_mask('o-x')(0o445) == 0o444 True >>> _permission_mask('a+x')(0) == 0o111 True >>> _permission_mask('a=rw')(0o057) == 0o666 True >>> _permission_mask('u=x')(0o666) == 0o166 True >>> _permission_mask('g=')(0o157) == 0o107 True """ # parse the symbolic mode parsed = re.match('(?P[ugoa]+)(?P[-+=])(?P[rwx]*)$', mode) if not parsed: raise ValueError("Unrecognized symbolic mode", mode) # generate a mask representing the specified permission spec_map = dict(r=4, w=2, x=1) specs = (spec_map[perm] for perm in parsed.group('what')) spec = functools.reduce(operator.or_, specs, 0) # now apply spec to each subject in who shift_map = dict(u=6, g=3, o=0) who = parsed.group('who').replace('a', 'ugo') masks = (spec << shift_map[subj] for subj in who) mask = functools.reduce(operator.or_, masks) op = parsed.group('op') # if op is -, invert the mask if op == '-': mask ^= 0o777 # if op is =, retain extant values for unreferenced subjects if op == '=': masks = (0o7 << shift_map[subj] for subj in who) retain = functools.reduce(operator.or_, masks) ^ 0o777 op_map = { '+': operator.or_, '-': operator.and_, '=': lambda mask, target: target & retain ^ mask, } return functools.partial(op_map[op], mask) class CaseInsensitivePattern(text_type): """ A string with a ``'normcase'`` property, suitable for passing to :meth:`listdir`, :meth:`dirs`, :meth:`files`, :meth:`walk`, :meth:`walkdirs`, or :meth:`walkfiles` to match case-insensitive. For example, to get all files ending in .py, .Py, .pY, or .PY in the current directory:: from path import Path, CaseInsensitivePattern as ci Path('.').files(ci('*.py')) """ @property def normcase(self): return __import__('ntpath').normcase ######################## # Backward-compatibility class path(Path): def __new__(cls, *args, **kwargs): msg = "path is deprecated. Use Path instead." warnings.warn(msg, DeprecationWarning) return Path.__new__(cls, *args, **kwargs) __all__ += ['path'] ######################## behave-1.2.6/tasks/_vendor/pathlib.py0000644000076600000240000012101113244555737017626 0ustar jensstaff00000000000000import fnmatch import functools import io import ntpath import os import posixpath import re import sys import time from collections import Sequence from contextlib import contextmanager from errno import EINVAL, ENOENT from operator import attrgetter from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO try: from urllib import quote as urlquote, quote as urlquote_from_bytes except ImportError: from urllib.parse import quote as urlquote, quote_from_bytes as urlquote_from_bytes try: intern = intern except NameError: intern = sys.intern try: basestring = basestring except NameError: basestring = str supports_symlinks = True try: import nt except ImportError: nt = None else: if sys.getwindowsversion()[:2] >= (6, 0) and sys.version_info >= (3, 2): from nt import _getfinalpathname else: supports_symlinks = False _getfinalpathname = None __all__ = [ "PurePath", "PurePosixPath", "PureWindowsPath", "Path", "PosixPath", "WindowsPath", ] # # Internals # _py2 = sys.version_info < (3,) _py2_fs_encoding = 'ascii' def _py2_fsencode(parts): # py2 => minimal unicode support return [part.encode(_py2_fs_encoding) if isinstance(part, unicode) else part for part in parts] def _is_wildcard_pattern(pat): # Whether this pattern needs actual matching using fnmatch, or can # be looked up directly as a file. return "*" in pat or "?" in pat or "[" in pat class _Flavour(object): """A flavour implements a particular (platform-specific) set of path semantics.""" def __init__(self): self.join = self.sep.join def parse_parts(self, parts): if _py2: parts = _py2_fsencode(parts) parsed = [] sep = self.sep altsep = self.altsep drv = root = '' it = reversed(parts) for part in it: if not part: continue if altsep: part = part.replace(altsep, sep) drv, root, rel = self.splitroot(part) if sep in rel: for x in reversed(rel.split(sep)): if x and x != '.': parsed.append(intern(x)) else: if rel and rel != '.': parsed.append(intern(rel)) if drv or root: if not drv: # If no drive is present, try to find one in the previous # parts. This makes the result of parsing e.g. # ("C:", "/", "a") reasonably intuitive. for part in it: drv = self.splitroot(part)[0] if drv: break break if drv or root: parsed.append(drv + root) parsed.reverse() return drv, root, parsed def join_parsed_parts(self, drv, root, parts, drv2, root2, parts2): """ Join the two paths represented by the respective (drive, root, parts) tuples. Return a new (drive, root, parts) tuple. """ if root2: if not drv2 and drv: return drv, root2, [drv + root2] + parts2[1:] elif drv2: if drv2 == drv or self.casefold(drv2) == self.casefold(drv): # Same drive => second path is relative to the first return drv, root, parts + parts2[1:] else: # Second path is non-anchored (common case) return drv, root, parts + parts2 return drv2, root2, parts2 class _WindowsFlavour(_Flavour): # Reference for Windows paths can be found at # http://msdn.microsoft.com/en-us/library/aa365247%28v=vs.85%29.aspx sep = '\\' altsep = '/' has_drv = True pathmod = ntpath is_supported = (nt is not None) drive_letters = ( set(chr(x) for x in range(ord('a'), ord('z') + 1)) | set(chr(x) for x in range(ord('A'), ord('Z') + 1)) ) ext_namespace_prefix = '\\\\?\\' reserved_names = ( set(['CON', 'PRN', 'AUX', 'NUL']) | set(['COM%d' % i for i in range(1, 10)]) | set(['LPT%d' % i for i in range(1, 10)]) ) # Interesting findings about extended paths: # - '\\?\c:\a', '//?/c:\a' and '//?/c:/a' are all supported # but '\\?\c:/a' is not # - extended paths are always absolute; "relative" extended paths will # fail. def splitroot(self, part, sep=sep): first = part[0:1] second = part[1:2] if (second == sep and first == sep): # XXX extended paths should also disable the collapsing of "." # components (according to MSDN docs). prefix, part = self._split_extended_path(part) first = part[0:1] second = part[1:2] else: prefix = '' third = part[2:3] if (second == sep and first == sep and third != sep): # is a UNC path: # vvvvvvvvvvvvvvvvvvvvv root # \\machine\mountpoint\directory\etc\... # directory ^^^^^^^^^^^^^^ index = part.find(sep, 2) if index != -1: index2 = part.find(sep, index + 1) # a UNC path can't have two slashes in a row # (after the initial two) if index2 != index + 1: if index2 == -1: index2 = len(part) if prefix: return prefix + part[1:index2], sep, part[index2+1:] else: return part[:index2], sep, part[index2+1:] drv = root = '' if second == ':' and first in self.drive_letters: drv = part[:2] part = part[2:] first = third if first == sep: root = first part = part.lstrip(sep) return prefix + drv, root, part def casefold(self, s): return s.lower() def casefold_parts(self, parts): return [p.lower() for p in parts] def resolve(self, path): s = str(path) if not s: return os.getcwd() if _getfinalpathname is not None: return self._ext_to_normal(_getfinalpathname(s)) # Means fallback on absolute return None def _split_extended_path(self, s, ext_prefix=ext_namespace_prefix): prefix = '' if s.startswith(ext_prefix): prefix = s[:4] s = s[4:] if s.startswith('UNC\\'): prefix += s[:3] s = '\\' + s[3:] return prefix, s def _ext_to_normal(self, s): # Turn back an extended path into a normal DOS-like path return self._split_extended_path(s)[1] def is_reserved(self, parts): # NOTE: the rules for reserved names seem somewhat complicated # (e.g. r"..\NUL" is reserved but not r"foo\NUL"). # We err on the side of caution and return True for paths which are # not considered reserved by Windows. if not parts: return False if parts[0].startswith('\\\\'): # UNC paths are never reserved return False return parts[-1].partition('.')[0].upper() in self.reserved_names def make_uri(self, path): # Under Windows, file URIs use the UTF-8 encoding. drive = path.drive if len(drive) == 2 and drive[1] == ':': # It's a path on a local drive => 'file:///c:/a/b' rest = path.as_posix()[2:].lstrip('/') return 'file:///%s/%s' % ( drive, urlquote_from_bytes(rest.encode('utf-8'))) else: # It's a path on a network drive => 'file://host/share/a/b' return 'file:' + urlquote_from_bytes(path.as_posix().encode('utf-8')) class _PosixFlavour(_Flavour): sep = '/' altsep = '' has_drv = False pathmod = posixpath is_supported = (os.name != 'nt') def splitroot(self, part, sep=sep): if part and part[0] == sep: stripped_part = part.lstrip(sep) # According to POSIX path resolution: # http://pubs.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11 # "A pathname that begins with two successive slashes may be # interpreted in an implementation-defined manner, although more # than two leading slashes shall be treated as a single slash". if len(part) - len(stripped_part) == 2: return '', sep * 2, stripped_part else: return '', sep, stripped_part else: return '', '', part def casefold(self, s): return s def casefold_parts(self, parts): return parts def resolve(self, path): sep = self.sep accessor = path._accessor seen = {} def _resolve(path, rest): if rest.startswith(sep): path = '' for name in rest.split(sep): if not name or name == '.': # current dir continue if name == '..': # parent dir path, _, _ = path.rpartition(sep) continue newpath = path + sep + name if newpath in seen: # Already seen this path path = seen[newpath] if path is not None: # use cached value continue # The symlink is not resolved, so we must have a symlink loop. raise RuntimeError("Symlink loop from %r" % newpath) # Resolve the symbolic link try: target = accessor.readlink(newpath) except OSError as e: if e.errno != EINVAL: raise # Not a symlink path = newpath else: seen[newpath] = None # not resolved symlink path = _resolve(path, target) seen[newpath] = path # resolved symlink return path # NOTE: according to POSIX, getcwd() cannot contain path components # which are symlinks. base = '' if path.is_absolute() else os.getcwd() return _resolve(base, str(path)) or sep def is_reserved(self, parts): return False def make_uri(self, path): # We represent the path using the local filesystem encoding, # for portability to other applications. bpath = bytes(path) return 'file://' + urlquote_from_bytes(bpath) _windows_flavour = _WindowsFlavour() _posix_flavour = _PosixFlavour() class _Accessor: """An accessor implements a particular (system-specific or not) way of accessing paths on the filesystem.""" class _NormalAccessor(_Accessor): def _wrap_strfunc(strfunc): @functools.wraps(strfunc) def wrapped(pathobj, *args): return strfunc(str(pathobj), *args) return staticmethod(wrapped) def _wrap_binary_strfunc(strfunc): @functools.wraps(strfunc) def wrapped(pathobjA, pathobjB, *args): return strfunc(str(pathobjA), str(pathobjB), *args) return staticmethod(wrapped) stat = _wrap_strfunc(os.stat) lstat = _wrap_strfunc(os.lstat) open = _wrap_strfunc(os.open) listdir = _wrap_strfunc(os.listdir) chmod = _wrap_strfunc(os.chmod) if hasattr(os, "lchmod"): lchmod = _wrap_strfunc(os.lchmod) else: def lchmod(self, pathobj, mode): raise NotImplementedError("lchmod() not available on this system") mkdir = _wrap_strfunc(os.mkdir) unlink = _wrap_strfunc(os.unlink) rmdir = _wrap_strfunc(os.rmdir) rename = _wrap_binary_strfunc(os.rename) if sys.version_info >= (3, 3): replace = _wrap_binary_strfunc(os.replace) if nt: if supports_symlinks: symlink = _wrap_binary_strfunc(os.symlink) else: def symlink(a, b, target_is_directory): raise NotImplementedError("symlink() not available on this system") else: # Under POSIX, os.symlink() takes two args @staticmethod def symlink(a, b, target_is_directory): return os.symlink(str(a), str(b)) utime = _wrap_strfunc(os.utime) # Helper for resolve() def readlink(self, path): return os.readlink(path) _normal_accessor = _NormalAccessor() # # Globbing helpers # @contextmanager def _cached(func): try: func.__cached__ yield func except AttributeError: cache = {} def wrapper(*args): try: return cache[args] except KeyError: value = cache[args] = func(*args) return value wrapper.__cached__ = True try: yield wrapper finally: cache.clear() def _make_selector(pattern_parts): pat = pattern_parts[0] child_parts = pattern_parts[1:] if pat == '**': cls = _RecursiveWildcardSelector elif '**' in pat: raise ValueError("Invalid pattern: '**' can only be an entire path component") elif _is_wildcard_pattern(pat): cls = _WildcardSelector else: cls = _PreciseSelector return cls(pat, child_parts) if hasattr(functools, "lru_cache"): _make_selector = functools.lru_cache()(_make_selector) class _Selector: """A selector matches a specific glob pattern part against the children of a given path.""" def __init__(self, child_parts): self.child_parts = child_parts if child_parts: self.successor = _make_selector(child_parts) else: self.successor = _TerminatingSelector() def select_from(self, parent_path): """Iterate over all child paths of `parent_path` matched by this selector. This can contain parent_path itself.""" path_cls = type(parent_path) is_dir = path_cls.is_dir exists = path_cls.exists listdir = parent_path._accessor.listdir return self._select_from(parent_path, is_dir, exists, listdir) class _TerminatingSelector: def _select_from(self, parent_path, is_dir, exists, listdir): yield parent_path class _PreciseSelector(_Selector): def __init__(self, name, child_parts): self.name = name _Selector.__init__(self, child_parts) def _select_from(self, parent_path, is_dir, exists, listdir): if not is_dir(parent_path): return path = parent_path._make_child_relpath(self.name) if exists(path): for p in self.successor._select_from(path, is_dir, exists, listdir): yield p class _WildcardSelector(_Selector): def __init__(self, pat, child_parts): self.pat = re.compile(fnmatch.translate(pat)) _Selector.__init__(self, child_parts) def _select_from(self, parent_path, is_dir, exists, listdir): if not is_dir(parent_path): return cf = parent_path._flavour.casefold for name in listdir(parent_path): casefolded = cf(name) if self.pat.match(casefolded): path = parent_path._make_child_relpath(name) for p in self.successor._select_from(path, is_dir, exists, listdir): yield p class _RecursiveWildcardSelector(_Selector): def __init__(self, pat, child_parts): _Selector.__init__(self, child_parts) def _iterate_directories(self, parent_path, is_dir, listdir): yield parent_path for name in listdir(parent_path): path = parent_path._make_child_relpath(name) if is_dir(path): for p in self._iterate_directories(path, is_dir, listdir): yield p def _select_from(self, parent_path, is_dir, exists, listdir): if not is_dir(parent_path): return with _cached(listdir) as listdir: yielded = set() try: successor_select = self.successor._select_from for starting_point in self._iterate_directories(parent_path, is_dir, listdir): for p in successor_select(starting_point, is_dir, exists, listdir): if p not in yielded: yield p yielded.add(p) finally: yielded.clear() # # Public API # class _PathParents(Sequence): """This object provides sequence-like access to the logical ancestors of a path. Don't try to construct it yourself.""" __slots__ = ('_pathcls', '_drv', '_root', '_parts') def __init__(self, path): # We don't store the instance to avoid reference cycles self._pathcls = type(path) self._drv = path._drv self._root = path._root self._parts = path._parts def __len__(self): if self._drv or self._root: return len(self._parts) - 1 else: return len(self._parts) def __getitem__(self, idx): if idx < 0 or idx >= len(self): raise IndexError(idx) return self._pathcls._from_parsed_parts(self._drv, self._root, self._parts[:-idx - 1]) def __repr__(self): return "<{0}.parents>".format(self._pathcls.__name__) class PurePath(object): """PurePath represents a filesystem path and offers operations which don't imply any actual filesystem I/O. Depending on your system, instantiating a PurePath will return either a PurePosixPath or a PureWindowsPath object. You can also instantiate either of these classes directly, regardless of your system. """ __slots__ = ( '_drv', '_root', '_parts', '_str', '_hash', '_pparts', '_cached_cparts', ) def __new__(cls, *args): """Construct a PurePath from one or several strings and or existing PurePath objects. The strings and path objects are combined so as to yield a canonicalized path, which is incorporated into the new PurePath object. """ if cls is PurePath: cls = PureWindowsPath if os.name == 'nt' else PurePosixPath return cls._from_parts(args) def __reduce__(self): # Using the parts tuple helps share interned path parts # when pickling related paths. return (self.__class__, tuple(self._parts)) @classmethod def _parse_args(cls, args): # This is useful when you don't want to create an instance, just # canonicalize some constructor arguments. parts = [] for a in args: if isinstance(a, PurePath): parts += a._parts elif isinstance(a, basestring): parts.append(a) else: raise TypeError( "argument should be a path or str object, not %r" % type(a)) return cls._flavour.parse_parts(parts) @classmethod def _from_parts(cls, args, init=True): # We need to call _parse_args on the instance, so as to get the # right flavour. self = object.__new__(cls) drv, root, parts = self._parse_args(args) self._drv = drv self._root = root self._parts = parts if init: self._init() return self @classmethod def _from_parsed_parts(cls, drv, root, parts, init=True): self = object.__new__(cls) self._drv = drv self._root = root self._parts = parts if init: self._init() return self @classmethod def _format_parsed_parts(cls, drv, root, parts): if drv or root: return drv + root + cls._flavour.join(parts[1:]) else: return cls._flavour.join(parts) def _init(self): # Overriden in concrete Path pass def _make_child(self, args): drv, root, parts = self._parse_args(args) drv, root, parts = self._flavour.join_parsed_parts( self._drv, self._root, self._parts, drv, root, parts) return self._from_parsed_parts(drv, root, parts) def __str__(self): """Return the string representation of the path, suitable for passing to system calls.""" try: return self._str except AttributeError: self._str = self._format_parsed_parts(self._drv, self._root, self._parts) or '.' return self._str def as_posix(self): """Return the string representation of the path with forward (/) slashes.""" f = self._flavour return str(self).replace(f.sep, '/') def __bytes__(self): """Return the bytes representation of the path. This is only recommended to use under Unix.""" if sys.version_info < (3, 2): raise NotImplementedError("needs Python 3.2 or later") return os.fsencode(str(self)) def __repr__(self): return "{0}({1!r})".format(self.__class__.__name__, self.as_posix()) def as_uri(self): """Return the path as a 'file' URI.""" if not self.is_absolute(): raise ValueError("relative path can't be expressed as a file URI") return self._flavour.make_uri(self) @property def _cparts(self): # Cached casefolded parts, for hashing and comparison try: return self._cached_cparts except AttributeError: self._cached_cparts = self._flavour.casefold_parts(self._parts) return self._cached_cparts def __eq__(self, other): if not isinstance(other, PurePath): return NotImplemented return self._cparts == other._cparts and self._flavour is other._flavour def __ne__(self, other): return not self == other def __hash__(self): try: return self._hash except AttributeError: self._hash = hash(tuple(self._cparts)) return self._hash def __lt__(self, other): if not isinstance(other, PurePath) or self._flavour is not other._flavour: return NotImplemented return self._cparts < other._cparts def __le__(self, other): if not isinstance(other, PurePath) or self._flavour is not other._flavour: return NotImplemented return self._cparts <= other._cparts def __gt__(self, other): if not isinstance(other, PurePath) or self._flavour is not other._flavour: return NotImplemented return self._cparts > other._cparts def __ge__(self, other): if not isinstance(other, PurePath) or self._flavour is not other._flavour: return NotImplemented return self._cparts >= other._cparts drive = property(attrgetter('_drv'), doc="""The drive prefix (letter or UNC path), if any.""") root = property(attrgetter('_root'), doc="""The root of the path, if any.""") @property def anchor(self): """The concatenation of the drive and root, or ''.""" anchor = self._drv + self._root return anchor @property def name(self): """The final path component, if any.""" parts = self._parts if len(parts) == (1 if (self._drv or self._root) else 0): return '' return parts[-1] @property def suffix(self): """The final component's last suffix, if any.""" name = self.name i = name.rfind('.') if 0 < i < len(name) - 1: return name[i:] else: return '' @property def suffixes(self): """A list of the final component's suffixes, if any.""" name = self.name if name.endswith('.'): return [] name = name.lstrip('.') return ['.' + suffix for suffix in name.split('.')[1:]] @property def stem(self): """The final path component, minus its last suffix.""" name = self.name i = name.rfind('.') if 0 < i < len(name) - 1: return name[:i] else: return name def with_name(self, name): """Return a new path with the file name changed.""" if not self.name: raise ValueError("%r has an empty name" % (self,)) return self._from_parsed_parts(self._drv, self._root, self._parts[:-1] + [name]) def with_suffix(self, suffix): """Return a new path with the file suffix changed (or added, if none).""" # XXX if suffix is None, should the current suffix be removed? drv, root, parts = self._flavour.parse_parts((suffix,)) if drv or root or len(parts) != 1: raise ValueError("Invalid suffix %r" % (suffix)) suffix = parts[0] if not suffix.startswith('.'): raise ValueError("Invalid suffix %r" % (suffix)) name = self.name if not name: raise ValueError("%r has an empty name" % (self,)) old_suffix = self.suffix if not old_suffix: name = name + suffix else: name = name[:-len(old_suffix)] + suffix return self._from_parsed_parts(self._drv, self._root, self._parts[:-1] + [name]) def relative_to(self, *other): """Return the relative path to another path identified by the passed arguments. If the operation is not possible (because this is not a subpath of the other path), raise ValueError. """ # For the purpose of this method, drive and root are considered # separate parts, i.e.: # Path('c:/').relative_to('c:') gives Path('/') # Path('c:/').relative_to('/') raise ValueError if not other: raise TypeError("need at least one argument") parts = self._parts drv = self._drv root = self._root if root: abs_parts = [drv, root] + parts[1:] else: abs_parts = parts to_drv, to_root, to_parts = self._parse_args(other) if to_root: to_abs_parts = [to_drv, to_root] + to_parts[1:] else: to_abs_parts = to_parts n = len(to_abs_parts) cf = self._flavour.casefold_parts if (root or drv) if n == 0 else cf(abs_parts[:n]) != cf(to_abs_parts): formatted = self._format_parsed_parts(to_drv, to_root, to_parts) raise ValueError("{!r} does not start with {!r}" .format(str(self), str(formatted))) return self._from_parsed_parts('', root if n == 1 else '', abs_parts[n:]) @property def parts(self): """An object providing sequence-like access to the components in the filesystem path.""" # We cache the tuple to avoid building a new one each time .parts # is accessed. XXX is this necessary? try: return self._pparts except AttributeError: self._pparts = tuple(self._parts) return self._pparts def joinpath(self, *args): """Combine this path with one or several arguments, and return a new path representing either a subpath (if all arguments are relative paths) or a totally different path (if one of the arguments is anchored). """ return self._make_child(args) def __truediv__(self, key): return self._make_child((key,)) def __rtruediv__(self, key): return self._from_parts([key] + self._parts) if sys.version_info < (3,): __div__ = __truediv__ __rdiv__ = __rtruediv__ @property def parent(self): """The logical parent of the path.""" drv = self._drv root = self._root parts = self._parts if len(parts) == 1 and (drv or root): return self return self._from_parsed_parts(drv, root, parts[:-1]) @property def parents(self): """A sequence of this path's logical parents.""" return _PathParents(self) def is_absolute(self): """True if the path is absolute (has both a root and, if applicable, a drive).""" if not self._root: return False return not self._flavour.has_drv or bool(self._drv) def is_reserved(self): """Return True if the path contains one of the special names reserved by the system, if any.""" return self._flavour.is_reserved(self._parts) def match(self, path_pattern): """ Return True if this path matches the given pattern. """ cf = self._flavour.casefold path_pattern = cf(path_pattern) drv, root, pat_parts = self._flavour.parse_parts((path_pattern,)) if not pat_parts: raise ValueError("empty pattern") if drv and drv != cf(self._drv): return False if root and root != cf(self._root): return False parts = self._cparts if drv or root: if len(pat_parts) != len(parts): return False pat_parts = pat_parts[1:] elif len(pat_parts) > len(parts): return False for part, pat in zip(reversed(parts), reversed(pat_parts)): if not fnmatch.fnmatchcase(part, pat): return False return True class PurePosixPath(PurePath): _flavour = _posix_flavour __slots__ = () class PureWindowsPath(PurePath): _flavour = _windows_flavour __slots__ = () # Filesystem-accessing classes class Path(PurePath): __slots__ = ( '_accessor', ) def __new__(cls, *args, **kwargs): if cls is Path: cls = WindowsPath if os.name == 'nt' else PosixPath self = cls._from_parts(args, init=False) if not self._flavour.is_supported: raise NotImplementedError("cannot instantiate %r on your system" % (cls.__name__,)) self._init() return self def _init(self, # Private non-constructor arguments template=None, ): if template is not None: self._accessor = template._accessor else: self._accessor = _normal_accessor def _make_child_relpath(self, part): # This is an optimization used for dir walking. `part` must be # a single part relative to this path. parts = self._parts + [part] return self._from_parsed_parts(self._drv, self._root, parts) def _opener(self, name, flags, mode=0o666): # A stub for the opener argument to built-in open() return self._accessor.open(self, flags, mode) def _raw_open(self, flags, mode=0o777): """ Open the file pointed by this path and return a file descriptor, as os.open() does. """ return self._accessor.open(self, flags, mode) # Public API @classmethod def cwd(cls): """Return a new path pointing to the current working directory (as returned by os.getcwd()). """ return cls(os.getcwd()) def iterdir(self): """Iterate over the files in this directory. Does not yield any result for the special paths '.' and '..'. """ for name in self._accessor.listdir(self): if name in ('.', '..'): # Yielding a path object for these makes little sense continue yield self._make_child_relpath(name) def glob(self, pattern): """Iterate over this subtree and yield all existing files (of any kind, including directories) matching the given pattern. """ pattern = self._flavour.casefold(pattern) drv, root, pattern_parts = self._flavour.parse_parts((pattern,)) if drv or root: raise NotImplementedError("Non-relative patterns are unsupported") selector = _make_selector(tuple(pattern_parts)) for p in selector.select_from(self): yield p def rglob(self, pattern): """Recursively yield all existing files (of any kind, including directories) matching the given pattern, anywhere in this subtree. """ pattern = self._flavour.casefold(pattern) drv, root, pattern_parts = self._flavour.parse_parts((pattern,)) if drv or root: raise NotImplementedError("Non-relative patterns are unsupported") selector = _make_selector(("**",) + tuple(pattern_parts)) for p in selector.select_from(self): yield p def absolute(self): """Return an absolute version of this path. This function works even if the path doesn't point to anything. No normalization is done, i.e. all '.' and '..' will be kept along. Use resolve() to get the canonical path to a file. """ # XXX untested yet! if self.is_absolute(): return self # FIXME this must defer to the specific flavour (and, under Windows, # use nt._getfullpathname()) obj = self._from_parts([os.getcwd()] + self._parts, init=False) obj._init(template=self) return obj def resolve(self): """ Make the path absolute, resolving all symlinks on the way and also normalizing it (for example turning slashes into backslashes under Windows). """ s = self._flavour.resolve(self) if s is None: # No symlink resolution => for consistency, raise an error if # the path doesn't exist or is forbidden self.stat() s = str(self.absolute()) # Now we have no symlinks in the path, it's safe to normalize it. normed = self._flavour.pathmod.normpath(s) obj = self._from_parts((normed,), init=False) obj._init(template=self) return obj def stat(self): """ Return the result of the stat() system call on this path, like os.stat() does. """ return self._accessor.stat(self) def owner(self): """ Return the login name of the file owner. """ import pwd return pwd.getpwuid(self.stat().st_uid).pw_name def group(self): """ Return the group name of the file gid. """ import grp return grp.getgrgid(self.stat().st_gid).gr_name def open(self, mode='r', buffering=-1, encoding=None, errors=None, newline=None): """ Open the file pointed by this path and return a file object, as the built-in open() function does. """ if sys.version_info >= (3, 3): return io.open(str(self), mode, buffering, encoding, errors, newline, opener=self._opener) else: return io.open(str(self), mode, buffering, encoding, errors, newline) def touch(self, mode=0o666, exist_ok=True): """ Create this file with the given access mode, if it doesn't exist. """ if exist_ok: # First try to bump modification time # Implementation note: GNU touch uses the UTIME_NOW option of # the utimensat() / futimens() functions. t = time.time() try: self._accessor.utime(self, (t, t)) except OSError: # Avoid exception chaining pass else: return flags = os.O_CREAT | os.O_WRONLY if not exist_ok: flags |= os.O_EXCL fd = self._raw_open(flags, mode) os.close(fd) def mkdir(self, mode=0o777, parents=False): if not parents: self._accessor.mkdir(self, mode) else: try: self._accessor.mkdir(self, mode) except OSError as e: if e.errno != ENOENT: raise self.parent.mkdir(parents=True) self._accessor.mkdir(self, mode) def chmod(self, mode): """ Change the permissions of the path, like os.chmod(). """ self._accessor.chmod(self, mode) def lchmod(self, mode): """ Like chmod(), except if the path points to a symlink, the symlink's permissions are changed, rather than its target's. """ self._accessor.lchmod(self, mode) def unlink(self): """ Remove this file or link. If the path is a directory, use rmdir() instead. """ self._accessor.unlink(self) def rmdir(self): """ Remove this directory. The directory must be empty. """ self._accessor.rmdir(self) def lstat(self): """ Like stat(), except if the path points to a symlink, the symlink's status information is returned, rather than its target's. """ return self._accessor.lstat(self) def rename(self, target): """ Rename this path to the given path. """ self._accessor.rename(self, target) def replace(self, target): """ Rename this path to the given path, clobbering the existing destination if it exists. """ if sys.version_info < (3, 3): raise NotImplementedError("replace() is only available " "with Python 3.3 and later") self._accessor.replace(self, target) def symlink_to(self, target, target_is_directory=False): """ Make this path a symlink pointing to the given path. Note the order of arguments (self, target) is the reverse of os.symlink's. """ self._accessor.symlink(target, self, target_is_directory) # Convenience functions for querying the stat results def exists(self): """ Whether this path exists. """ try: self.stat() except OSError as e: if e.errno != ENOENT: raise return False return True def is_dir(self): """ Whether this path is a directory. """ try: return S_ISDIR(self.stat().st_mode) except OSError as e: if e.errno != ENOENT: raise # Path doesn't exist or is a broken symlink # (see https://bitbucket.org/pitrou/pathlib/issue/12/) return False def is_file(self): """ Whether this path is a regular file (also True for symlinks pointing to regular files). """ try: return S_ISREG(self.stat().st_mode) except OSError as e: if e.errno != ENOENT: raise # Path doesn't exist or is a broken symlink # (see https://bitbucket.org/pitrou/pathlib/issue/12/) return False def is_symlink(self): """ Whether this path is a symbolic link. """ try: return S_ISLNK(self.lstat().st_mode) except OSError as e: if e.errno != ENOENT: raise # Path doesn't exist return False def is_block_device(self): """ Whether this path is a block device. """ try: return S_ISBLK(self.stat().st_mode) except OSError as e: if e.errno != ENOENT: raise # Path doesn't exist or is a broken symlink # (see https://bitbucket.org/pitrou/pathlib/issue/12/) return False def is_char_device(self): """ Whether this path is a character device. """ try: return S_ISCHR(self.stat().st_mode) except OSError as e: if e.errno != ENOENT: raise # Path doesn't exist or is a broken symlink # (see https://bitbucket.org/pitrou/pathlib/issue/12/) return False def is_fifo(self): """ Whether this path is a FIFO. """ try: return S_ISFIFO(self.stat().st_mode) except OSError as e: if e.errno != ENOENT: raise # Path doesn't exist or is a broken symlink # (see https://bitbucket.org/pitrou/pathlib/issue/12/) return False def is_socket(self): """ Whether this path is a socket. """ try: return S_ISSOCK(self.stat().st_mode) except OSError as e: if e.errno != ENOENT: raise # Path doesn't exist or is a broken symlink # (see https://bitbucket.org/pitrou/pathlib/issue/12/) return False class PosixPath(Path, PurePosixPath): __slots__ = () class WindowsPath(Path, PureWindowsPath): __slots__ = () behave-1.2.6/tasks/_vendor/README.rst0000644000076600000240000000213513244555737017325 0ustar jensstaff00000000000000tasks/_vendor: Bundled vendor parts -- needed by tasks =============================================================================== This directory contains bundled archives that may be needed to run the tasks. Especially, it contains an executable "invoke.zip" archive. This archive can be used when invoke is not installed. To execute invoke from the bundled ZIP archive:: python -m tasks/_vendor/invoke.zip --help python -m tasks/_vendor/invoke.zip --version Example for a local "bin/invoke" script in a UNIX like platform environment:: #!/bin/bash # RUN INVOKE: From bundled ZIP file. HERE=$(dirname $0) python ${HERE}/../tasks/_vendor/invoke.zip $* Example for a local "bin/invoke.cmd" script in a Windows environment:: @echo off REM ========================================================================== REM RUN INVOKE: From bundled ZIP file. REM ========================================================================== setlocal set HERE=%~dp0 if not defined PYTHON set PYTHON=python %PYTHON% %HERE%../tasks/_vendor/invoke.zip "%*" behave-1.2.6/tasks/_vendor/six.py0000644000076600000240000007262213244555737017023 0ustar jensstaff00000000000000"""Utilities for writing code that runs on Python 2 and 3""" # Copyright (c) 2010-2015 Benjamin Peterson # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. from __future__ import absolute_import import functools import itertools import operator import sys import types __author__ = "Benjamin Peterson " __version__ = "1.10.0" # Useful for very coarse version differentiation. PY2 = sys.version_info[0] == 2 PY3 = sys.version_info[0] == 3 PY34 = sys.version_info[0:2] >= (3, 4) if PY3: string_types = str, integer_types = int, class_types = type, text_type = str binary_type = bytes MAXSIZE = sys.maxsize else: string_types = basestring, integer_types = (int, long) class_types = (type, types.ClassType) text_type = unicode binary_type = str if sys.platform.startswith("java"): # Jython always uses 32 bits. MAXSIZE = int((1 << 31) - 1) else: # It's possible to have sizeof(long) != sizeof(Py_ssize_t). class X(object): def __len__(self): return 1 << 31 try: len(X()) except OverflowError: # 32-bit MAXSIZE = int((1 << 31) - 1) else: # 64-bit MAXSIZE = int((1 << 63) - 1) del X def _add_doc(func, doc): """Add documentation to a function.""" func.__doc__ = doc def _import_module(name): """Import module, returning the module after the last dot.""" __import__(name) return sys.modules[name] class _LazyDescr(object): def __init__(self, name): self.name = name def __get__(self, obj, tp): result = self._resolve() setattr(obj, self.name, result) # Invokes __set__. try: # This is a bit ugly, but it avoids running this again by # removing this descriptor. delattr(obj.__class__, self.name) except AttributeError: pass return result class MovedModule(_LazyDescr): def __init__(self, name, old, new=None): super(MovedModule, self).__init__(name) if PY3: if new is None: new = name self.mod = new else: self.mod = old def _resolve(self): return _import_module(self.mod) def __getattr__(self, attr): _module = self._resolve() value = getattr(_module, attr) setattr(self, attr, value) return value class _LazyModule(types.ModuleType): def __init__(self, name): super(_LazyModule, self).__init__(name) self.__doc__ = self.__class__.__doc__ def __dir__(self): attrs = ["__doc__", "__name__"] attrs += [attr.name for attr in self._moved_attributes] return attrs # Subclasses should override this _moved_attributes = [] class MovedAttribute(_LazyDescr): def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): super(MovedAttribute, self).__init__(name) if PY3: if new_mod is None: new_mod = name self.mod = new_mod if new_attr is None: if old_attr is None: new_attr = name else: new_attr = old_attr self.attr = new_attr else: self.mod = old_mod if old_attr is None: old_attr = name self.attr = old_attr def _resolve(self): module = _import_module(self.mod) return getattr(module, self.attr) class _SixMetaPathImporter(object): """ A meta path importer to import six.moves and its submodules. This class implements a PEP302 finder and loader. It should be compatible with Python 2.5 and all existing versions of Python3 """ def __init__(self, six_module_name): self.name = six_module_name self.known_modules = {} def _add_module(self, mod, *fullnames): for fullname in fullnames: self.known_modules[self.name + "." + fullname] = mod def _get_module(self, fullname): return self.known_modules[self.name + "." + fullname] def find_module(self, fullname, path=None): if fullname in self.known_modules: return self return None def __get_module(self, fullname): try: return self.known_modules[fullname] except KeyError: raise ImportError("This loader does not know module " + fullname) def load_module(self, fullname): try: # in case of a reload return sys.modules[fullname] except KeyError: pass mod = self.__get_module(fullname) if isinstance(mod, MovedModule): mod = mod._resolve() else: mod.__loader__ = self sys.modules[fullname] = mod return mod def is_package(self, fullname): """ Return true, if the named module is a package. We need this method to get correct spec objects with Python 3.4 (see PEP451) """ return hasattr(self.__get_module(fullname), "__path__") def get_code(self, fullname): """Return None Required, if is_package is implemented""" self.__get_module(fullname) # eventually raises ImportError return None get_source = get_code # same as get_code _importer = _SixMetaPathImporter(__name__) class _MovedItems(_LazyModule): """Lazy loading of moved objects""" __path__ = [] # mark as package _moved_attributes = [ MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), MovedAttribute("intern", "__builtin__", "sys"), MovedAttribute("map", "itertools", "builtins", "imap", "map"), MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), MovedAttribute("reduce", "__builtin__", "functools"), MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), MovedAttribute("StringIO", "StringIO", "io"), MovedAttribute("UserDict", "UserDict", "collections"), MovedAttribute("UserList", "UserList", "collections"), MovedAttribute("UserString", "UserString", "collections"), MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), MovedModule("builtins", "__builtin__"), MovedModule("configparser", "ConfigParser"), MovedModule("copyreg", "copy_reg"), MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), MovedModule("http_cookies", "Cookie", "http.cookies"), MovedModule("html_entities", "htmlentitydefs", "html.entities"), MovedModule("html_parser", "HTMLParser", "html.parser"), MovedModule("http_client", "httplib", "http.client"), MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), MovedModule("cPickle", "cPickle", "pickle"), MovedModule("queue", "Queue"), MovedModule("reprlib", "repr"), MovedModule("socketserver", "SocketServer"), MovedModule("_thread", "thread", "_thread"), MovedModule("tkinter", "Tkinter"), MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), MovedModule("tkinter_tix", "Tix", "tkinter.tix"), MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), MovedModule("tkinter_colorchooser", "tkColorChooser", "tkinter.colorchooser"), MovedModule("tkinter_commondialog", "tkCommonDialog", "tkinter.commondialog"), MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), MovedModule("tkinter_font", "tkFont", "tkinter.font"), MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", "tkinter.simpledialog"), MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), ] # Add windows specific modules. if sys.platform == "win32": _moved_attributes += [ MovedModule("winreg", "_winreg"), ] for attr in _moved_attributes: setattr(_MovedItems, attr.name, attr) if isinstance(attr, MovedModule): _importer._add_module(attr, "moves." + attr.name) del attr _MovedItems._moved_attributes = _moved_attributes moves = _MovedItems(__name__ + ".moves") _importer._add_module(moves, "moves") class Module_six_moves_urllib_parse(_LazyModule): """Lazy loading of moved objects in six.moves.urllib_parse""" _urllib_parse_moved_attributes = [ MovedAttribute("ParseResult", "urlparse", "urllib.parse"), MovedAttribute("SplitResult", "urlparse", "urllib.parse"), MovedAttribute("parse_qs", "urlparse", "urllib.parse"), MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), MovedAttribute("urldefrag", "urlparse", "urllib.parse"), MovedAttribute("urljoin", "urlparse", "urllib.parse"), MovedAttribute("urlparse", "urlparse", "urllib.parse"), MovedAttribute("urlsplit", "urlparse", "urllib.parse"), MovedAttribute("urlunparse", "urlparse", "urllib.parse"), MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), MovedAttribute("quote", "urllib", "urllib.parse"), MovedAttribute("quote_plus", "urllib", "urllib.parse"), MovedAttribute("unquote", "urllib", "urllib.parse"), MovedAttribute("unquote_plus", "urllib", "urllib.parse"), MovedAttribute("urlencode", "urllib", "urllib.parse"), MovedAttribute("splitquery", "urllib", "urllib.parse"), MovedAttribute("splittag", "urllib", "urllib.parse"), MovedAttribute("splituser", "urllib", "urllib.parse"), MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), MovedAttribute("uses_params", "urlparse", "urllib.parse"), MovedAttribute("uses_query", "urlparse", "urllib.parse"), MovedAttribute("uses_relative", "urlparse", "urllib.parse"), ] for attr in _urllib_parse_moved_attributes: setattr(Module_six_moves_urllib_parse, attr.name, attr) del attr Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes _importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), "moves.urllib_parse", "moves.urllib.parse") class Module_six_moves_urllib_error(_LazyModule): """Lazy loading of moved objects in six.moves.urllib_error""" _urllib_error_moved_attributes = [ MovedAttribute("URLError", "urllib2", "urllib.error"), MovedAttribute("HTTPError", "urllib2", "urllib.error"), MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), ] for attr in _urllib_error_moved_attributes: setattr(Module_six_moves_urllib_error, attr.name, attr) del attr Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes _importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), "moves.urllib_error", "moves.urllib.error") class Module_six_moves_urllib_request(_LazyModule): """Lazy loading of moved objects in six.moves.urllib_request""" _urllib_request_moved_attributes = [ MovedAttribute("urlopen", "urllib2", "urllib.request"), MovedAttribute("install_opener", "urllib2", "urllib.request"), MovedAttribute("build_opener", "urllib2", "urllib.request"), MovedAttribute("pathname2url", "urllib", "urllib.request"), MovedAttribute("url2pathname", "urllib", "urllib.request"), MovedAttribute("getproxies", "urllib", "urllib.request"), MovedAttribute("Request", "urllib2", "urllib.request"), MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), MovedAttribute("BaseHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), MovedAttribute("FileHandler", "urllib2", "urllib.request"), MovedAttribute("FTPHandler", "urllib2", "urllib.request"), MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), MovedAttribute("urlretrieve", "urllib", "urllib.request"), MovedAttribute("urlcleanup", "urllib", "urllib.request"), MovedAttribute("URLopener", "urllib", "urllib.request"), MovedAttribute("FancyURLopener", "urllib", "urllib.request"), MovedAttribute("proxy_bypass", "urllib", "urllib.request"), ] for attr in _urllib_request_moved_attributes: setattr(Module_six_moves_urllib_request, attr.name, attr) del attr Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes _importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), "moves.urllib_request", "moves.urllib.request") class Module_six_moves_urllib_response(_LazyModule): """Lazy loading of moved objects in six.moves.urllib_response""" _urllib_response_moved_attributes = [ MovedAttribute("addbase", "urllib", "urllib.response"), MovedAttribute("addclosehook", "urllib", "urllib.response"), MovedAttribute("addinfo", "urllib", "urllib.response"), MovedAttribute("addinfourl", "urllib", "urllib.response"), ] for attr in _urllib_response_moved_attributes: setattr(Module_six_moves_urllib_response, attr.name, attr) del attr Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes _importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), "moves.urllib_response", "moves.urllib.response") class Module_six_moves_urllib_robotparser(_LazyModule): """Lazy loading of moved objects in six.moves.urllib_robotparser""" _urllib_robotparser_moved_attributes = [ MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), ] for attr in _urllib_robotparser_moved_attributes: setattr(Module_six_moves_urllib_robotparser, attr.name, attr) del attr Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes _importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), "moves.urllib_robotparser", "moves.urllib.robotparser") class Module_six_moves_urllib(types.ModuleType): """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" __path__ = [] # mark as package parse = _importer._get_module("moves.urllib_parse") error = _importer._get_module("moves.urllib_error") request = _importer._get_module("moves.urllib_request") response = _importer._get_module("moves.urllib_response") robotparser = _importer._get_module("moves.urllib_robotparser") def __dir__(self): return ['parse', 'error', 'request', 'response', 'robotparser'] _importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), "moves.urllib") def add_move(move): """Add an item to six.moves.""" setattr(_MovedItems, move.name, move) def remove_move(name): """Remove item from six.moves.""" try: delattr(_MovedItems, name) except AttributeError: try: del moves.__dict__[name] except KeyError: raise AttributeError("no such move, %r" % (name,)) if PY3: _meth_func = "__func__" _meth_self = "__self__" _func_closure = "__closure__" _func_code = "__code__" _func_defaults = "__defaults__" _func_globals = "__globals__" else: _meth_func = "im_func" _meth_self = "im_self" _func_closure = "func_closure" _func_code = "func_code" _func_defaults = "func_defaults" _func_globals = "func_globals" try: advance_iterator = next except NameError: def advance_iterator(it): return it.next() next = advance_iterator try: callable = callable except NameError: def callable(obj): return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) if PY3: def get_unbound_function(unbound): return unbound create_bound_method = types.MethodType def create_unbound_method(func, cls): return func Iterator = object else: def get_unbound_function(unbound): return unbound.im_func def create_bound_method(func, obj): return types.MethodType(func, obj, obj.__class__) def create_unbound_method(func, cls): return types.MethodType(func, None, cls) class Iterator(object): def next(self): return type(self).__next__(self) callable = callable _add_doc(get_unbound_function, """Get the function out of a possibly unbound function""") get_method_function = operator.attrgetter(_meth_func) get_method_self = operator.attrgetter(_meth_self) get_function_closure = operator.attrgetter(_func_closure) get_function_code = operator.attrgetter(_func_code) get_function_defaults = operator.attrgetter(_func_defaults) get_function_globals = operator.attrgetter(_func_globals) if PY3: def iterkeys(d, **kw): return iter(d.keys(**kw)) def itervalues(d, **kw): return iter(d.values(**kw)) def iteritems(d, **kw): return iter(d.items(**kw)) def iterlists(d, **kw): return iter(d.lists(**kw)) viewkeys = operator.methodcaller("keys") viewvalues = operator.methodcaller("values") viewitems = operator.methodcaller("items") else: def iterkeys(d, **kw): return d.iterkeys(**kw) def itervalues(d, **kw): return d.itervalues(**kw) def iteritems(d, **kw): return d.iteritems(**kw) def iterlists(d, **kw): return d.iterlists(**kw) viewkeys = operator.methodcaller("viewkeys") viewvalues = operator.methodcaller("viewvalues") viewitems = operator.methodcaller("viewitems") _add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") _add_doc(itervalues, "Return an iterator over the values of a dictionary.") _add_doc(iteritems, "Return an iterator over the (key, value) pairs of a dictionary.") _add_doc(iterlists, "Return an iterator over the (key, [values]) pairs of a dictionary.") if PY3: def b(s): return s.encode("latin-1") def u(s): return s unichr = chr import struct int2byte = struct.Struct(">B").pack del struct byte2int = operator.itemgetter(0) indexbytes = operator.getitem iterbytes = iter import io StringIO = io.StringIO BytesIO = io.BytesIO _assertCountEqual = "assertCountEqual" if sys.version_info[1] <= 1: _assertRaisesRegex = "assertRaisesRegexp" _assertRegex = "assertRegexpMatches" else: _assertRaisesRegex = "assertRaisesRegex" _assertRegex = "assertRegex" else: def b(s): return s # Workaround for standalone backslash def u(s): return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") unichr = unichr int2byte = chr def byte2int(bs): return ord(bs[0]) def indexbytes(buf, i): return ord(buf[i]) iterbytes = functools.partial(itertools.imap, ord) import StringIO StringIO = BytesIO = StringIO.StringIO _assertCountEqual = "assertItemsEqual" _assertRaisesRegex = "assertRaisesRegexp" _assertRegex = "assertRegexpMatches" _add_doc(b, """Byte literal""") _add_doc(u, """Text literal""") def assertCountEqual(self, *args, **kwargs): return getattr(self, _assertCountEqual)(*args, **kwargs) def assertRaisesRegex(self, *args, **kwargs): return getattr(self, _assertRaisesRegex)(*args, **kwargs) def assertRegex(self, *args, **kwargs): return getattr(self, _assertRegex)(*args, **kwargs) if PY3: exec_ = getattr(moves.builtins, "exec") def reraise(tp, value, tb=None): if value is None: value = tp() if value.__traceback__ is not tb: raise value.with_traceback(tb) raise value else: def exec_(_code_, _globs_=None, _locs_=None): """Execute code in a namespace.""" if _globs_ is None: frame = sys._getframe(1) _globs_ = frame.f_globals if _locs_ is None: _locs_ = frame.f_locals del frame elif _locs_ is None: _locs_ = _globs_ exec("""exec _code_ in _globs_, _locs_""") exec_("""def reraise(tp, value, tb=None): raise tp, value, tb """) if sys.version_info[:2] == (3, 2): exec_("""def raise_from(value, from_value): if from_value is None: raise value raise value from from_value """) elif sys.version_info[:2] > (3, 2): exec_("""def raise_from(value, from_value): raise value from from_value """) else: def raise_from(value, from_value): raise value print_ = getattr(moves.builtins, "print", None) if print_ is None: def print_(*args, **kwargs): """The new-style print function for Python 2.4 and 2.5.""" fp = kwargs.pop("file", sys.stdout) if fp is None: return def write(data): if not isinstance(data, basestring): data = str(data) # If the file has an encoding, encode unicode with it. if (isinstance(fp, file) and isinstance(data, unicode) and fp.encoding is not None): errors = getattr(fp, "errors", None) if errors is None: errors = "strict" data = data.encode(fp.encoding, errors) fp.write(data) want_unicode = False sep = kwargs.pop("sep", None) if sep is not None: if isinstance(sep, unicode): want_unicode = True elif not isinstance(sep, str): raise TypeError("sep must be None or a string") end = kwargs.pop("end", None) if end is not None: if isinstance(end, unicode): want_unicode = True elif not isinstance(end, str): raise TypeError("end must be None or a string") if kwargs: raise TypeError("invalid keyword arguments to print()") if not want_unicode: for arg in args: if isinstance(arg, unicode): want_unicode = True break if want_unicode: newline = unicode("\n") space = unicode(" ") else: newline = "\n" space = " " if sep is None: sep = space if end is None: end = newline for i, arg in enumerate(args): if i: write(sep) write(arg) write(end) if sys.version_info[:2] < (3, 3): _print = print_ def print_(*args, **kwargs): fp = kwargs.get("file", sys.stdout) flush = kwargs.pop("flush", False) _print(*args, **kwargs) if flush and fp is not None: fp.flush() _add_doc(reraise, """Reraise an exception.""") if sys.version_info[0:2] < (3, 4): def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, updated=functools.WRAPPER_UPDATES): def wrapper(f): f = functools.wraps(wrapped, assigned, updated)(f) f.__wrapped__ = wrapped return f return wrapper else: wraps = functools.wraps def with_metaclass(meta, *bases): """Create a base class with a metaclass.""" # This requires a bit of explanation: the basic idea is to make a dummy # metaclass for one level of class instantiation that replaces itself with # the actual metaclass. class metaclass(meta): def __new__(cls, name, this_bases, d): return meta(name, bases, d) return type.__new__(metaclass, 'temporary_class', (), {}) def add_metaclass(metaclass): """Class decorator for creating a class with a metaclass.""" def wrapper(cls): orig_vars = cls.__dict__.copy() slots = orig_vars.get('__slots__') if slots is not None: if isinstance(slots, str): slots = [slots] for slots_var in slots: orig_vars.pop(slots_var) orig_vars.pop('__dict__', None) orig_vars.pop('__weakref__', None) return metaclass(cls.__name__, cls.__bases__, orig_vars) return wrapper def python_2_unicode_compatible(klass): """ A decorator that defines __unicode__ and __str__ methods under Python 2. Under Python 3 it does nothing. To support Python 2 and 3 with a single code base, define a __str__ method returning text and apply this decorator to the class. """ if PY2: if '__str__' not in klass.__dict__: raise ValueError("@python_2_unicode_compatible cannot be applied " "to %s because it doesn't define __str__()." % klass.__name__) klass.__unicode__ = klass.__str__ klass.__str__ = lambda self: self.__unicode__().encode('utf-8') return klass # Complete the moves implementation. # This code is at the end of this module to speed up module loading. # Turn this module into a package. __path__ = [] # required for PEP 302 and PEP 451 __package__ = __name__ # see PEP 366 @ReservedAssignment if globals().get("__spec__") is not None: __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable # Remove other six meta path importers, since they cause problems. This can # happen if six is removed from sys.modules and then reloaded. (Setuptools does # this for some reason.) if sys.meta_path: for i, importer in enumerate(sys.meta_path): # Here's some real nastiness: Another "instance" of the six module might # be floating around. Therefore, we can't use isinstance() to check for # the six meta path importer, since the other six instance will have # inserted an importer with different class. if (type(importer).__name__ == "_SixMetaPathImporter" and importer.name == __name__): del sys.meta_path[i] break del i, importer # Finally, add the importer to the meta path import hook. sys.meta_path.append(_importer) behave-1.2.6/tasks/clean.py0000644000076600000240000000347113244555737015642 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Provides cleanup tasks for this project. Mostly reuses tasks from "invoke_tasklet_clean". """ from __future__ import absolute_import from ._tasklet_cleanup import cleanup_tasks # ----------------------------------------------------------------------------- # TASKS: # ----------------------------------------------------------------------------- # INHERITED-FROM: _invoke_tasklet_clean # pylint: disable=unused-import from ._tasklet_cleanup import clean, clean_all, clean_python, namespace # -- EXTENSTION-POINT: CLEANUP TASKS (called by: "clean" task): cleanup_tasks.add_task(clean_python) # ----------------------------------------------------------------------------- # TASK CONFIGURATION: # ----------------------------------------------------------------------------- # namespace = Collection(clean, clean_all) # namespace.configure({ # "clean": { # "directories": [], # "files": [ # "*.bak", "*.log", "*.tmp", # "**/.DS_Store", "**/*.~*~", # -- MACOSX # ], # "extra_directories": [], # "extra_files": [], # }, # "clean_all": { # "directories": [".venv*", ".tox", "downloads", "tmp"], # "files": [], # "extra_directories": [], # "extra_files": [], # }, # }) # # ----------------------------------------------------------------------------- # TASK CONFIGURATION HELPERS: Can be used from other task modules # ----------------------------------------------------------------------------- # def config_add_cleanup_dirs(directories): # cleanup_directories = namespace._configuration["clean"]["directories"] # cleanup_directories.extend(directories) # # def config_add_cleanup_files(files): # cleanup_files = namespace._configuration["clean"]["files"] # cleanup_files.extend(files) behave-1.2.6/tasks/docs.py0000644000076600000240000000675113244555737015514 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Provides tasks to build documentation with sphinx, etc. """ from __future__ import absolute_import, print_function import sys from invoke import task, Collection from invoke.util import cd from path import Path # -- TASK-LIBRARY: from ._tasklet_cleanup import cleanup_tasks, cleanup_dirs # ----------------------------------------------------------------------------- # TASKS: # ----------------------------------------------------------------------------- @task def clean(ctx, dry_run=False): """Cleanup generated document artifacts.""" basedir = ctx.sphinx.destdir or "build/docs" cleanup_dirs([basedir], dry_run=dry_run) @task(help={ "builder": "Builder to use (html, ...)", "options": "Additional options for sphinx-build", }) def build(ctx, builder="html", options=""): """Build docs with sphinx-build""" sourcedir = ctx.config.sphinx.sourcedir destdir = Path(ctx.config.sphinx.destdir or "build")/builder destdir = destdir.abspath() with cd(sourcedir): destdir_relative = Path(".").relpathto(destdir) command = "sphinx-build {opts} -b {builder} {sourcedir} {destdir}" \ .format(builder=builder, sourcedir=".", destdir=destdir_relative, opts=options) ctx.run(command) @task(help={ "builder": "Builder to use (html, ...)", "options": "Additional options for sphinx-build", }) def rebuild(ctx, builder="html", options=""): """Rebuilds the docs. Perform the steps: clean, build """ clean(ctx) build(ctx, builder=builder, options=options) @task def linkcheck(ctx): """Check if all links are corect.""" build(ctx, builder="linkcheck") @task def browse(ctx): """Open documentation in web browser.""" page_html = Path(ctx.config.sphinx.destdir)/"html"/"index.html" if not page_html.exists(): build(ctx, builder="html") assert page_html.exists() open_cmd = "open" # -- WORKS ON: MACOSX if sys.platform.startswith("win"): open_cmd = "start" ctx.run("{open} {page_html}".format(open=open_cmd, page_html=page_html)) # ctx.run('python -m webbrowser -t {page_html}'.format(page_html=page_html)) # -- DISABLED: # import webbrowser # print("Starting webbrowser with page=%s" % page_html) # webbrowser.open(str(page_html)) @task(help={"dest": "Destination directory to save docs"}) # pylint: disable=redefined-builtin def save(ctx, dest="docs.html", format="html"): """Save/update docs under destination directory.""" print("STEP: Generate docs in HTML format") build(ctx, builder=format) print("STEP: Save docs under %s/" % dest) source_dir = Path(ctx.config.sphinx.destdir)/format Path(dest).rmtree_p() source_dir.copytree(dest) # -- POST-PROCESSING: Polish up. for part in [".buildinfo", ".doctrees"]: partpath = Path(dest)/part if partpath.isdir(): partpath.rmtree_p() elif partpath.exists(): partpath.remove_p() # ----------------------------------------------------------------------------- # TASK CONFIGURATION: # ----------------------------------------------------------------------------- namespace = Collection(clean, rebuild, linkcheck, browse, save) namespace.add_task(build, default=True) namespace.configure({ "sphinx": { "sourcedir": "docs", "destdir": "build/docs" } }) # -- ADD CLEANUP TASK: cleanup_tasks.add_task(clean, "clean_docs") cleanup_tasks.configure(namespace.configuration()) behave-1.2.6/tasks/py.requirements.txt0000644000076600000240000000106213244555737020113 0ustar jensstaff00000000000000# ============================================================================ # INVOKE PYTHON PACKAGE REQUIREMENTS: For tasks # ============================================================================ # DESCRIPTION: # pip install -r # # SEE ALSO: # * http://www.pip-installer.org/ # ============================================================================ invoke >= 0.21.0 six >= 1.11.0 path.py >= 10.1 pathlib; python_version <= '3.4' backports.shutil_which; python_version <= '3.3' # For cleanup of python files: py.cleanup pycmd behave-1.2.6/tasks/release.py0000644000076600000240000001440713244555737016201 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Tasks for releasing this project. Normal steps:: python setup.py sdist bdist_wheel twine register dist/{project}-{version}.tar.gz twine upload dist/* twine upload --skip-existing dist/* python setup.py upload # -- DEPRECATED: No longer supported -> Use RTD instead # -- DEPRECATED: python setup.py upload_docs pypi repositories: * https://pypi.python.org/pypi * https://testpypi.python.org/pypi (not working anymore) * https://test.pypi.org/legacy/ (not working anymore) Configuration file for pypi repositories: .. code-block:: init # -- FILE: $HOME/.pypirc [distutils] index-servers = pypi testpypi [pypi] # DEPRECATED: repository = https://pypi.python.org/pypi username = __USERNAME_HERE__ password: [testpypi] # DEPRECATED: repository = https://test.pypi.org/legacy username = __USERNAME_HERE__ password: .. seealso:: * https://packaging.python.org/ * https://packaging.python.org/guides/ * https://packaging.python.org/tutorials/distributing-packages/ """ from __future__ import absolute_import, print_function from invoke import Collection, task from ._tasklet_cleanup import path_glob from ._dry_run import DryRunContext # ----------------------------------------------------------------------------- # TASKS: # ----------------------------------------------------------------------------- @task def checklist(ctx=None): # pylint: disable=unused-argument """Checklist for releasing this project.""" checklist_text = """PRE-RELEASE CHECKLIST: [ ] Everything is checked in [ ] All tests pass w/ tox RELEASE CHECKLIST: [{x1}] Bump version to new-version and tag repository (via bump_version) [{x2}] Build packages (sdist, bdist_wheel via prepare) [{x3}] Register and upload packages to testpypi repository (first) [{x4}] Verify release is OK and packages from testpypi are usable [{x5}] Register and upload packages to pypi repository [{x6}] Push last changes to Github repository POST-RELEASE CHECKLIST: [ ] Bump version to new-develop-version (via bump_version) [ ] Adapt CHANGES (if necessary) [ ] Commit latest changes to Github repository """ steps = dict(x1=None, x2=None, x3=None, x4=None, x5=None, x6=None) yesno_map = {True: "x", False: "_", None: " "} answers = {name: yesno_map[value] for name, value in steps.items()} print(checklist_text.format(**answers)) @task(name="bump_version") def bump_version(ctx, new_version, version_part=None, dry_run=False): """Bump version (to prepare a new release).""" version_part = version_part or "minor" if dry_run: ctx = DryRunContext(ctx) ctx.run("bumpversion --new-version={} {}".format(new_version, version_part)) @task(name="build", aliases=["build_packages"]) def build_packages(ctx, hide=False): """Build packages for this release.""" print("build_packages:") ctx.run("python setup.py sdist bdist_wheel", echo=True, hide=hide) @task def prepare(ctx, new_version=None, version_part=None, hide=True, dry_run=False): """Prepare the release: bump version, build packages, ...""" if new_version is not None: bump_version(ctx, new_version, version_part=version_part, dry_run=dry_run) build_packages(ctx, hide=hide) packages = ensure_packages_exist(ctx, check_only=True) print_packages(packages) # -- NOT-NEEDED: # @task(name="register") # def register_packages(ctx, repo=None, dry_run=False): # """Register release (packages) in artifact-store/repository.""" # original_ctx = ctx # if repo is None: # repo = ctx.project.repo or "pypi" # if dry_run: # ctx = DryRunContext(ctx) # packages = ensure_packages_exist(original_ctx) # print_packages(packages) # for artifact in packages: # ctx.run("twine register --repository={repo} {artifact}".format( # artifact=artifact, repo=repo)) @task def upload(ctx, repo=None, dry_run=False): """Upload release packages to repository (artifact-store).""" original_ctx = ctx if repo is None: repo = ctx.project.repo or "pypi" if dry_run: ctx = DryRunContext(ctx) packages = ensure_packages_exist(original_ctx) print_packages(packages) ctx.run("twine upload --repository={repo} dist/*".format(repo=repo)) # -- DEPRECATED: Use RTD instead # @task(name="upload_docs") # def upload_docs(ctx, repo=None, dry_run=False): # """Upload and publish docs. # # NOTE: Docs are built first. # """ # if repo is None: # repo = ctx.project.repo or "pypi" # if dry_run: # ctx = DryRunContext(ctx) # # ctx.run("python setup.py upload_docs") # # ----------------------------------------------------------------------------- # TASK HELPERS: # ----------------------------------------------------------------------------- def print_packages(packages): print("PACKAGES[%d]:" % len(packages)) for package in packages: package_size = package.stat().st_size package_time = package.stat().st_mtime print(" - %s (size=%s)" % (package, package_size)) def ensure_packages_exist(ctx, pattern=None, check_only=False): if pattern is None: project_name = ctx.project.name project_prefix = project_name.replace("_", "-").split("-")[0] pattern = "dist/%s*" % project_prefix packages = list(path_glob(pattern, current_dir=".")) if not packages: if check_only: message = "No artifacts found: pattern=%s" % pattern raise RuntimeError(message) else: # -- RECURSIVE-SELF-CALL: Once print("NO-PACKAGES-FOUND: Build packages first ...") build_packages(ctx, hide=True) packages = ensure_packages_exist(ctx, pattern, check_only=True) return packages # ----------------------------------------------------------------------------- # TASK CONFIGURATION: # ----------------------------------------------------------------------------- # DISABLED: register_packages namespace = Collection(bump_version, checklist, prepare, build_packages, upload) namespace.configure({ "project": { "repo": "pypi", } }) behave-1.2.6/tasks/test.py0000644000076600000240000001475613244555737015547 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Invoke test tasks. """ from __future__ import print_function import os.path import sys from invoke import task, Collection # -- TASK-LIBRARY: from ._tasklet_cleanup import cleanup_tasks, cleanup_dirs, cleanup_files # --------------------------------------------------------------------------- # TASKS # --------------------------------------------------------------------------- @task(name="all", help={ "args": "Command line args for test run.", }) def test_all(ctx, args="", options=""): """Run all tests (default).""" pytest_args = select_by_prefix(args, ctx.pytest.scopes) behave_args = select_by_prefix(args, ctx.behave_test.scopes) pytest_should_run = not args or (args and pytest_args) behave_should_run = not args or (args and behave_args) if pytest_should_run: pytest(ctx, pytest_args, options=options) if behave_should_run: behave(ctx, behave_args, options=options) @task def clean(ctx, dry_run=False): """Cleanup (temporary) test artifacts.""" directories = ctx.test.clean.directories or [] files = ctx.test.clean.files or [] cleanup_dirs(directories, dry_run=dry_run) cleanup_files(files, dry_run=dry_run) @task(name="unit") def unittest(ctx, args="", options=""): """Run unit tests.""" pytest(ctx, args, options) @task def pytest(ctx, args="", options=""): """Run unit tests.""" args = args or ctx.pytest.args options = options or ctx.pytest.options ctx.run("pytest {options} {args}".format(options=options, args=args)) @task(help={ "args": "Command line args for behave", "format": "Formatter to use (progress, pretty, ...)", }) # pylint: disable=redefined-builtin def behave(ctx, args="", format="", options=""): """Run behave tests.""" format = format or ctx.behave_test.format options = options or ctx.behave_test.options args = args or ctx.behave_test.args if os.path.exists("bin/behave"): behave_cmd = "{python} bin/behave".format(python=sys.executable) else: behave_cmd = "{python} -m behave".format(python=sys.executable) for group_args in grouped_by_prefix(args, ctx.behave_test.scopes): ctx.run("{behave} -f {format} {options} {args}".format( behave=behave_cmd, format=format, options=options, args=group_args)) @task(help={ "args": "Tests to run (empty: all)", "report": "Coverage report format to use (report, html, xml)", }) def coverage(ctx, args="", report="report", append=False): """Determine test coverage (run pytest, behave)""" append = append or ctx.coverage.append report_formats = ctx.coverage.report_formats or [] if report not in report_formats: report_formats.insert(0, report) opts = [] if append: opts.append("--append") pytest_args = select_by_prefix(args, ctx.pytest.scopes) behave_args = select_by_prefix(args, ctx.behave_test.scopes) pytest_should_run = not args or (args and pytest_args) behave_should_run = not args or (args and behave_args) if not args: behave_args = ctx.behave_test.args or "features" if isinstance(pytest_args, list): pytest_args = " ".join(pytest_args) if isinstance(behave_args, list): behave_args = " ".join(behave_args) # -- RUN TESTS WITH COVERAGE: if pytest_should_run: ctx.run("coverage run {options} -m pytest {args}".format( args=pytest_args, options=" ".join(opts))) if behave_should_run: behave_options = ctx.behave_test.coverage_options or "" os.environ["COVERAGE_PROCESS_START"] = os.path.abspath(".coveragerc") behave(ctx, args=behave_args, options=behave_options) del os.environ["COVERAGE_PROCESS_START"] # -- POST-PROCESSING: ctx.run("coverage combine") for report_format in report_formats: ctx.run("coverage {report_format}".format(report_format=report_format)) # --------------------------------------------------------------------------- # UTILITIES: # --------------------------------------------------------------------------- def select_prefix_for(arg, prefixes): for prefix in prefixes: if arg.startswith(prefix): return prefix return os.path.dirname(arg) def select_by_prefix(args, prefixes): selected = [] for arg in args.strip().split(): assert not arg.startswith("-"), "REQUIRE: arg, not options" scope = select_prefix_for(arg, prefixes) if scope: selected.append(arg) return " ".join(selected) def grouped_by_prefix(args, prefixes): """Group behave args by (directory) scope into multiple test-runs.""" group_args = [] current_scope = None for arg in args.strip().split(): assert not arg.startswith("-"), "REQUIRE: arg, not options" scope = select_prefix_for(arg, prefixes) if scope != current_scope: if group_args: # -- DETECTED GROUP-END: yield " ".join(group_args) group_args = [] current_scope = scope group_args.append(arg) if group_args: yield " ".join(group_args) # --------------------------------------------------------------------------- # TASK MANAGEMENT / CONFIGURATION # --------------------------------------------------------------------------- namespace = Collection(clean, unittest, pytest, behave, coverage) namespace.add_task(test_all, default=True) namespace.configure({ "test": { "clean": { "directories": [ ".cache", "assets", # -- TEST RUNS "__WORKDIR__", "reports", "test_results", # -- BEHAVE test ], "files": [ ".coverage", ".coverage.*", "report.html", "rerun*.txt", "rerun*.featureset", "testrun*.json", ], }, }, "pytest": { "scopes": ["test", "tests"], "args": "", "options": "", # -- NOTE: Overide in configfile "invoke.yaml" }, # "behave_test": behave.namespace._configuration["behave_test"], "behave_test": { "scopes": ["features", "issue.features"], "args": "features issue.features", "format": "progress", "options": "", # -- NOTE: Overide in configfile "invoke.yaml" "coverage_options": "", }, "coverage": { "append": False, "report_formats": ["report", "html"], }, }) # -- ADD CLEANUP TASK: cleanup_tasks.add_task(clean, "clean_test") cleanup_tasks.configure(namespace.configuration()) behave-1.2.6/test/0000755000076600000240000000000013244564040014016 5ustar jensstaff00000000000000behave-1.2.6/test/__init__.py0000644000076600000240000000000013244555737016132 0ustar jensstaff00000000000000behave-1.2.6/test/_importer_candidate.py0000644000076600000240000000005013244555737020374 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- name = "Hello" behave-1.2.6/test/reporters/0000755000076600000240000000000013244564040016043 5ustar jensstaff00000000000000behave-1.2.6/test/reporters/__init__.py0000644000076600000240000000000013244555737020157 0ustar jensstaff00000000000000behave-1.2.6/test/reporters/test_summary.py0000644000076600000240000002134213244555737021170 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- from __future__ import absolute_import, division import sys from mock import Mock, patch from nose.tools import * from behave.model import ScenarioOutline, Scenario from behave.model_core import Status from behave.reporter.summary import SummaryReporter, format_summary class TestFormatStatus(object): def test_passed_entry_contains_label(self): summary = { Status.passed.name: 1, Status.skipped.name: 0, Status.failed.name: 0, Status.undefined.name: 0, } assert format_summary('fnord', summary).startswith('1 fnord passed') def test_passed_entry_is_pluralised(self): summary = { Status.passed.name: 10, Status.skipped.name: 0, Status.failed.name: 0, Status.undefined.name: 0, } assert format_summary('fnord', summary).startswith('10 fnords passed') def test_remaining_fields_are_present(self): summary = { Status.passed.name: 10, Status.skipped.name: 1, Status.failed.name: 2, Status.undefined.name: 3, } output = format_summary('fnord', summary) assert '1 skipped' in output assert '2 failed' in output assert '3 undefined' in output def test_missing_fields_are_not_present(self): summary = { Status.passed.name: 10, Status.skipped.name: 1, Status.failed.name: 2, } output = format_summary('fnord', summary) assert '1 skipped' in output assert '2 failed' in output assert Status.undefined.name not in output class TestSummaryReporter(object): @patch('sys.stdout') def test_duration_is_totalled_up_and_outputted(self, stdout): features = [Mock(), Mock(), Mock(), Mock()] features[0].duration = 1.9 features[0].status = Status.passed features[0].__iter__ = Mock(return_value=iter([])) features[1].duration = 2.7 features[1].status = Status.passed features[1].__iter__ = Mock(return_value=iter([])) features[2].duration = 3.5 features[2].status = Status.passed features[2].__iter__ = Mock(return_value=iter([])) features[3].duration = 4.3 features[3].status = Status.passed features[3].__iter__ = Mock(return_value=iter([])) config = Mock() sys.stdout.encoding = "UTF-8" reporter = SummaryReporter(config) [reporter.feature(f) for f in features] eq_(round(reporter.duration, 3), 12.400) reporter.end() output = stdout.write.call_args_list[-1][0][0] minutes = int(reporter.duration / 60.0) seconds = reporter.duration % 60 assert '%dm' % (minutes,) in output assert '%02.1f' % (seconds,) in output @patch('sys.stdout') @patch('behave.reporter.summary.format_summary') def test_feature_status_is_collected_and_reported(self, format_summary, stdout): features = [Mock(), Mock(), Mock(), Mock(), Mock()] features[0].duration = 1.9 features[0].status = Status.passed features[0].__iter__ = Mock(return_value=iter([])) features[1].duration = 2.7 features[1].status = Status.failed features[1].__iter__ = Mock(return_value=iter([])) features[2].duration = 3.5 features[2].status = Status.skipped features[2].__iter__ = Mock(return_value=iter([])) features[3].duration = 4.3 features[3].status = Status.passed features[3].__iter__ = Mock(return_value=iter([])) features[4].duration = 5.1 features[4].status = Status.untested features[4].__iter__ = Mock(return_value=iter([])) config = Mock() sys.stdout.encoding = "UTF-8" reporter = SummaryReporter(config) [reporter.feature(f) for f in features] reporter.end() expected = { Status.passed.name: 2, Status.failed.name: 1, Status.skipped.name: 1, Status.untested.name: 1, } eq_(format_summary.call_args_list[0][0], ('feature', expected)) @patch('sys.stdout') @patch('behave.reporter.summary.format_summary') def test_scenario_status_is_collected_and_reported(self, format_summary, stdout): feature = Mock() scenarios = [Mock(), Mock(), Mock(), Mock(), Mock()] scenarios[0].status = Status.failed scenarios[0].__iter__ = Mock(return_value=iter([])) scenarios[1].status = Status.failed scenarios[1].__iter__ = Mock(return_value=iter([])) scenarios[2].status = Status.skipped scenarios[2].__iter__ = Mock(return_value=iter([])) scenarios[3].status = Status.passed scenarios[3].__iter__ = Mock(return_value=iter([])) scenarios[4].status = Status.untested scenarios[4].__iter__ = Mock(return_value=iter([])) feature.status = Status.failed feature.duration = 12.3 feature.__iter__ = Mock(return_value=iter(scenarios)) config = Mock() sys.stdout.encoding = "UTF-8" reporter = SummaryReporter(config) reporter.feature(feature) reporter.end() expected = { Status.passed.name: 1, Status.failed.name: 2, Status.skipped.name: 1, Status.untested.name: 1, } eq_(format_summary.call_args_list[1][0], ('scenario', expected)) @patch('behave.reporter.summary.format_summary') @patch('sys.stdout') def test_scenario_outline_status_is_collected_and_reported(self, stdout, format_summary): feature = Mock() scenarios = [ ScenarioOutline(u"", 0, u"scenario_outline", u"name"), Mock(), Mock(), Mock() ] subscenarios = [ Mock(), Mock(), Mock(), Mock() ] subscenarios[0].status = Status.passed subscenarios[0].__iter__ = Mock(return_value=iter([])) subscenarios[1].status = Status.failed subscenarios[1].__iter__ = Mock(return_value=iter([])) subscenarios[2].status = Status.failed subscenarios[2].__iter__ = Mock(return_value=iter([])) subscenarios[3].status = Status.skipped subscenarios[3].__iter__ = Mock(return_value=iter([])) scenarios[0]._scenarios = subscenarios scenarios[1].status = Status.failed scenarios[1].__iter__ = Mock(return_value=iter([])) scenarios[2].status = Status.skipped scenarios[2].__iter__ = Mock(return_value=iter([])) scenarios[3].status = Status.passed scenarios[3].__iter__ = Mock(return_value=iter([])) feature.status = Status.failed feature.duration = 12.4 feature.__iter__ = Mock(return_value=iter(scenarios)) config = Mock() sys.stdout.encoding = "UTF-8" reporter = SummaryReporter(config) reporter.feature(feature) reporter.end() expected = { Status.passed.name: 2, Status.failed.name: 3, Status.skipped.name: 2, Status.untested.name: 0, } eq_(format_summary.call_args_list[1][0], ('scenario', expected)) @patch('sys.stdout') @patch('behave.reporter.summary.format_summary') def test_step_status_is_collected_and_reported(self, format_summary, stdout): feature = Mock() scenario = Mock() steps = [Mock(), Mock(), Mock(), Mock(), Mock()] steps[0].status = Status.failed steps[0].__iter__ = Mock(return_value=iter([])) steps[1].status = Status.passed steps[1].__iter__ = Mock(return_value=iter([])) steps[2].status = Status.passed steps[2].__iter__ = Mock(return_value=iter([])) steps[3].status = Status.skipped steps[4].__iter__ = Mock(return_value=iter([])) steps[4].status = Status.undefined steps[4].__iter__ = Mock(return_value=iter([])) feature.status = Status.failed feature.duration = 12.3 feature.__iter__ = Mock(return_value=iter([scenario])) scenario.status = Status.failed scenario.__iter__ = Mock(return_value=iter(steps)) config = Mock() sys.stdout.encoding = "UTF-8" reporter = SummaryReporter(config) reporter.feature(feature) reporter.end() expected = { Status.passed.name: 2, Status.failed.name: 1, Status.skipped.name: 1, Status.untested.name: 0, Status.undefined.name: 1, } eq_(format_summary.call_args_list[2][0], ('step', expected)) behave-1.2.6/test/test_ansi_escapes.py0000644000076600000240000000525513244555737020110 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- # pylint: disable=C0103,R0201,W0401,W0614,W0621 # C0103 Invalid name (setUp(), ...) # R0201 Method could be a function # W0401 Wildcard import # W0614 Unused import ... from wildcard import # W0621 Redefining name ... from outer scope from __future__ import absolute_import from nose import tools from behave.formatter import ansi_escapes import unittest from six.moves import range class StripEscapesTest(unittest.TestCase): ALL_COLORS = list(ansi_escapes.colors.keys()) CURSOR_UPS = [ ansi_escapes.up(count) for count in range(10) ] TEXTS = [ u"lorem ipsum", u"Alice\nBob\nCharly\nDennis", ] @classmethod def colorize(cls, text, color): color_escape = "" if color: color_escape = ansi_escapes.colors[color] return color_escape + text + ansi_escapes.escapes["reset"] @classmethod def colorize_text(cls, text, colors=None): if not colors: colors = [] colors_size = len(colors) color_index = 0 colored_chars = [] for char in text: color = colors[color_index] colored_chars.append(cls.colorize(char, color)) color_index += 1 if color_index >= colors_size: color_index = 0 return "".join(colored_chars) def test_should_return_same_text_without_escapes(self): for text in self.TEXTS: tools.eq_(text, ansi_escapes.strip_escapes(text)) def test_should_return_empty_string_for_any_ansi_escape(self): # XXX-JE-CHECK-PY23: If list() is really needed. for text in list(ansi_escapes.colors.values()): tools.eq_("", ansi_escapes.strip_escapes(text)) for text in list(ansi_escapes.escapes.values()): tools.eq_("", ansi_escapes.strip_escapes(text)) def test_should_strip_color_escapes_from_text(self): for text in self.TEXTS: colored_text = self.colorize_text(text, self.ALL_COLORS) tools.eq_(text, ansi_escapes.strip_escapes(colored_text)) self.assertNotEqual(text, colored_text) for color in self.ALL_COLORS: colored_text = self.colorize(text, color) tools.eq_(text, ansi_escapes.strip_escapes(colored_text)) self.assertNotEqual(text, colored_text) def test_should_strip_cursor_up_escapes_from_text(self): for text in self.TEXTS: for cursor_up in self.CURSOR_UPS: colored_text = cursor_up + text + ansi_escapes.escapes["reset"] tools.eq_(text, ansi_escapes.strip_escapes(colored_text)) self.assertNotEqual(text, colored_text) behave-1.2.6/test/test_configuration.py0000644000076600000240000001436313244555737020322 0ustar jensstaff00000000000000import os.path import sys import tempfile import six from nose.tools import * from behave import configuration from behave.configuration import Configuration, UserData from unittest import TestCase # one entry of each kind handled TEST_CONFIG="""[behave] outfiles= /absolute/path1 relative/path2 paths = /absolute/path3 relative/path4 tags = @foo,~@bar @zap format=pretty tag-counter stdout_capture=no bogus=spam [behave.userdata] foo = bar answer = 42 """ ROOTDIR_PREFIX = "" if sys.platform.startswith("win"): # -- OR: ROOTDIR_PREFIX = os.path.splitdrive(sys.executable) # NOTE: python2 requires lower-case drive letter. ROOTDIR_PREFIX_DEFAULT = "C:" if six.PY2: ROOTDIR_PREFIX_DEFAULT = ROOTDIR_PREFIX_DEFAULT.lower() ROOTDIR_PREFIX = os.environ.get("BEHAVE_ROOTDIR_PREFIX", ROOTDIR_PREFIX_DEFAULT) class TestConfiguration(object): def test_read_file(self): tn = tempfile.mktemp() tndir = os.path.dirname(tn) with open(tn, "w") as f: f.write(TEST_CONFIG) # -- WINDOWS-REQUIRES: normpath d = configuration.read_configuration(tn) eq_(d["outfiles"], [ os.path.normpath(ROOTDIR_PREFIX + "/absolute/path1"), os.path.normpath(os.path.join(tndir, "relative/path2")), ]) eq_(d["paths"], [ os.path.normpath(ROOTDIR_PREFIX + "/absolute/path3"), os.path.normpath(os.path.join(tndir, "relative/path4")), ]) eq_(d["format"], ["pretty", "tag-counter"]) eq_(d["tags"], ["@foo,~@bar", "@zap"]) eq_(d["stdout_capture"], False) ok_("bogus" not in d) eq_(d["userdata"], {"foo": "bar", "answer": "42"}) def ensure_stage_environment_is_not_set(self): if "BEHAVE_STAGE" in os.environ: del os.environ["BEHAVE_STAGE"] def test_settings_without_stage(self): # -- OR: Setup with default, unnamed stage. self.ensure_stage_environment_is_not_set() assert "BEHAVE_STAGE" not in os.environ config = Configuration("") eq_("steps", config.steps_dir) eq_("environment.py", config.environment_file) def test_settings_with_stage(self): config = Configuration(["--stage=STAGE1"]) eq_("STAGE1_steps", config.steps_dir) eq_("STAGE1_environment.py", config.environment_file) def test_settings_with_stage_and_envvar(self): os.environ["BEHAVE_STAGE"] = "STAGE2" config = Configuration(["--stage=STAGE1"]) eq_("STAGE1_steps", config.steps_dir) eq_("STAGE1_environment.py", config.environment_file) del os.environ["BEHAVE_STAGE"] def test_settings_with_stage_from_envvar(self): os.environ["BEHAVE_STAGE"] = "STAGE2" config = Configuration("") eq_("STAGE2_steps", config.steps_dir) eq_("STAGE2_environment.py", config.environment_file) del os.environ["BEHAVE_STAGE"] class TestConfigurationUserData(TestCase): """Test userdata aspects in behave.configuration.Configuration class.""" def test_cmdline_defines(self): config = Configuration([ "-D", "foo=foo_value", "--define=bar=bar_value", "--define", "baz=BAZ_VALUE", ]) eq_("foo_value", config.userdata["foo"]) eq_("bar_value", config.userdata["bar"]) eq_("BAZ_VALUE", config.userdata["baz"]) def test_cmdline_defines_override_configfile(self): userdata_init = {"foo": "XXX", "bar": "ZZZ", "baz": 42} config = Configuration( "-D foo=foo_value --define bar=123", load_config=False, userdata=userdata_init) eq_("foo_value", config.userdata["foo"]) eq_("123", config.userdata["bar"]) eq_(42, config.userdata["baz"]) def test_cmdline_defines_without_value_are_true(self): config = Configuration("-D foo --define bar -Dbaz") eq_("true", config.userdata["foo"]) eq_("true", config.userdata["bar"]) eq_("true", config.userdata["baz"]) eq_(True, config.userdata.getbool("foo")) def test_cmdline_defines_with_empty_value(self): config = Configuration("-D foo=") eq_("", config.userdata["foo"]) def test_cmdline_defines_with_assign_character_as_value(self): config = Configuration("-D foo=bar=baz") eq_("bar=baz", config.userdata["foo"]) def test_cmdline_defines__with_quoted_name_value_pair(self): cmdlines = [ '-D "person=Alice and Bob"', "-D 'person=Alice and Bob'", ] for cmdline in cmdlines: config = Configuration(cmdline, load_config=False) eq_(config.userdata, dict(person="Alice and Bob")) def test_cmdline_defines__with_quoted_value(self): cmdlines = [ '-D person="Alice and Bob"', "-D person='Alice and Bob'", ] for cmdline in cmdlines: config = Configuration(cmdline, load_config=False) eq_(config.userdata, dict(person="Alice and Bob")) def test_setup_userdata(self): config = Configuration("", load_config=False) config.userdata = dict(person1="Alice", person2="Bob") config.userdata_defines = [("person2", "Charly")] config.setup_userdata() expected_data = dict(person1="Alice", person2="Charly") eq_(config.userdata, expected_data) def test_update_userdata__with_cmdline_defines(self): # -- NOTE: cmdline defines are reapplied. config = Configuration("-D person2=Bea", load_config=False) config.userdata = UserData(person1="AAA", person3="Charly") config.update_userdata(dict(person1="Alice", person2="Bob")) expected_data = dict(person1="Alice", person2="Bea", person3="Charly") eq_(config.userdata, expected_data) eq_(config.userdata_defines, [("person2", "Bea")]) def test_update_userdata__without_cmdline_defines(self): config = Configuration("", load_config=False) config.userdata = UserData(person1="AAA", person3="Charly") config.update_userdata(dict(person1="Alice", person2="Bob")) expected_data = dict(person1="Alice", person2="Bob", person3="Charly") eq_(config.userdata, expected_data) self.assertFalse(config.userdata_defines) behave-1.2.6/test/test_formatter.py0000644000076600000240000001757213244555737017463 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- from __future__ import absolute_import import struct import sys import tempfile import unittest import six from mock import Mock, patch from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import from behave.formatter._registry import make_formatters from behave.formatter import pretty from behave.formatter.base import StreamOpener from behave.model import Tag, Feature, Scenario, Step from behave.model_core import Status from behave.matchers import Match class TestGetTerminalSize(unittest.TestCase): def setUp(self): try: self.ioctl_patch = patch("fcntl.ioctl") self.ioctl = self.ioctl_patch.start() except ImportError: self.ioctl_patch = None self.ioctl = None self.zero_struct = struct.pack("HHHH", 0, 0, 0, 0) def tearDown(self): if self.ioctl_patch: self.ioctl_patch.stop() def test_windows_fallback(self): # pylint: disable=no-self-use platform = sys.platform sys.platform = "windows" eq_(pretty.get_terminal_size(), (80, 24)) sys.platform = platform def test_termios_fallback(self): # pylint: disable=no-self-use try: import termios return except ImportError: pass eq_(pretty.get_terminal_size(), (80, 24)) def test_exception_in_ioctl(self): try: import termios except ImportError: return def raiser(*args, **kwargs): # pylint: disable=unused-argument raise Exception("yeehar!") self.ioctl.side_effect = raiser eq_(pretty.get_terminal_size(), (80, 24)) self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct) def test_happy_path(self): try: import termios except ImportError: return self.ioctl.return_value = struct.pack("HHHH", 17, 23, 5, 5) eq_(pretty.get_terminal_size(), (23, 17)) self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct) def test_zero_size_fallback(self): try: import termios except ImportError: return self.ioctl.return_value = self.zero_struct eq_(pretty.get_terminal_size(), (80, 24)) self.ioctl.assert_called_with(0, termios.TIOCGWINSZ, self.zero_struct) def _tf(): """Open a temp file that looks a bunch like stdout.""" if six.PY3: # in python3 it's got an encoding and accepts new-style strings return tempfile.TemporaryFile(mode="w", encoding="UTF-8") # pre-python3 it's not got an encoding and accepts encoded data # (old-style strings) return tempfile.TemporaryFile(mode="w") class FormatterTests(unittest.TestCase): formatter_name = "plain" # SANE DEFAULT, overwritten by concrete classes def setUp(self): self.config = Mock() self.config.color = True self.config.outputs = [StreamOpener(stream=sys.stdout)] self.config.format = [self.formatter_name] _line = 0 @property def line(self): self._line += 1 return self._line def _formatter(self, file_object, config): # pylint: disable=no-self-use stream_opener = StreamOpener(stream=file_object) f = make_formatters(config, [stream_opener])[0] f.uri("") return f def _feature(self, keyword=u"k\xe9yword", name=u"name", tags=None, location=u"location", # pylint: disable=unused-argument description=None, scenarios=None, background=None): if tags is None: tags = [u"spam", u"ham"] if description is None: description = [u"description"] if scenarios is None: scenarios = [] line = self.line tags = [Tag(name, line) for name in tags] return Feature("", line, keyword, name, tags=tags, description=description, scenarios=scenarios, background=background) def _scenario(self, keyword=u"k\xe9yword", name=u"name", tags=None, steps=None): if tags is None: tags = [] if steps is None: steps = [] line = self.line tags = [Tag(name, line) for name in tags] return Scenario("", line, keyword, name, tags=tags, steps=steps) def _step(self, keyword=u"k\xe9yword", step_type="given", name=u"name", text=None, table=None): line = self.line return Step("", line, keyword, step_type, name, text=text, table=table) def _match(self, arguments=None): # pylint: disable=no-self-use def dummy(): pass return Match(dummy, arguments) def test_feature(self): # this test does not actually check the result of the formatting; it # just exists to make sure that formatting doesn't explode in the face of # unicode and stuff p = self._formatter(_tf(), self.config) f = self._feature() p.feature(f) def test_scenario(self): p = self._formatter(_tf(), self.config) f = self._feature() p.feature(f) s = self._scenario() p.scenario(s) def test_step(self): p = self._formatter(_tf(), self.config) f = self._feature() p.feature(f) scenario = self._scenario() p.scenario(scenario) s = self._step() p.step(s) p.match(self._match([])) s.status = Status.passed p.result(s) class TestPretty(FormatterTests): formatter_name = "pretty" class TestPlain(FormatterTests): formatter_name = "plain" class TestJson(FormatterTests): formatter_name = "json" class TestTagsCount(FormatterTests): formatter_name = "tags" def test_tag_counts(self): p = self._formatter(_tf(), self.config) s = self._scenario(tags=[u"ham", u"foo"]) f = self._feature(scenarios=[s]) # feature.tags= ham, spam p.feature(f) p.scenario(s) eq_(p.tag_counts, {"ham": [f, s], "spam": [f], "foo": [s]}) class MultipleFormattersTests(FormatterTests): formatters = [] def setUp(self): self.config = Mock() self.config.color = True self.config.outputs = [StreamOpener(stream=sys.stdout) for i in self.formatters] self.config.format = self.formatters def _formatters(self, file_object, config): # pylint: disable=no-self-use stream_opener = StreamOpener(stream=file_object) formatters = make_formatters(config, [stream_opener]) for f in formatters: f.uri("") return formatters def test_feature(self): # this test does not actually check the result of the formatting; it # just exists to make sure that formatting doesn't explode in the face of # unicode and stuff formatters = self._formatters(_tf(), self.config) f = self._feature() for p in formatters: p.feature(f) def test_scenario(self): formatters = self._formatters(_tf(), self.config) f = self._feature() for p in formatters: p.feature(f) s = self._scenario() p.scenario(s) def test_step(self): formatters = self._formatters(_tf(), self.config) f = self._feature() for p in formatters: p.feature(f) scenario = self._scenario() p.scenario(scenario) s = self._step() p.step(s) p.match(self._match([])) s.status = Status.passed p.result(s) class TestPrettyAndPlain(MultipleFormattersTests): formatters = ["pretty", "plain"] class TestPrettyAndJSON(MultipleFormattersTests): formatters = ["pretty", "json"] class TestJSONAndPlain(MultipleFormattersTests): formatters = ["json", "plain"] behave-1.2.6/test/test_formatter_progress.py0000644000076600000240000000227713244555737021403 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Test progress formatters: * behave.formatter.progress.ScenarioProgressFormatter * behave.formatter.progress.StepProgressFormatter """ from __future__ import absolute_import from .test_formatter import FormatterTests as FormatterTest from .test_formatter import MultipleFormattersTests as MultipleFormattersTest class TestScenarioProgressFormatter(FormatterTest): formatter_name = "progress" class TestStepProgressFormatter(FormatterTest): formatter_name = "progress2" class TestPrettyAndScenarioProgress(MultipleFormattersTest): formatters = ['pretty', 'progress'] class TestPlainAndScenarioProgress(MultipleFormattersTest): formatters = ['plain', 'progress'] class TestJSONAndScenarioProgress(MultipleFormattersTest): formatters = ['json', 'progress'] class TestPrettyAndStepProgress(MultipleFormattersTest): formatters = ['pretty', 'progress2'] class TestPlainAndStepProgress(MultipleFormattersTest): formatters = ['plain', 'progress2'] class TestJSONAndStepProgress(MultipleFormattersTest): formatters = ['json', 'progress2'] class TestScenarioProgressAndStepProgress(MultipleFormattersTest): formatters = ['progress', 'progress2'] behave-1.2.6/test/test_formatter_rerun.py0000644000076600000240000000641313244555737020666 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Test behave formatters: * behave.formatter.rerun.RerunFormatter """ from __future__ import absolute_import from behave.model_core import Status from .test_formatter import FormatterTests as FormatterTest, _tf from .test_formatter import MultipleFormattersTests as MultipleFormattersTest from nose.tools import * class TestRerunFormatter(FormatterTest): formatter_name = "rerun" def test_feature_with_two_passing_scenarios(self): p = self._formatter(_tf(), self.config) f = self._feature() scenarios = [ self._scenario(), self._scenario() ] for scenario in scenarios: f.add_scenario(scenario) # -- FORMATTER CALLBACKS: p.feature(f) for scenario in f.scenarios: p.scenario(scenario) assert scenario.status == Status.passed p.eof() eq_([], p.failed_scenarios) # -- EMIT REPORT: p.close() def test_feature_with_one_passing_one_failing_scenario(self): p = self._formatter(_tf(), self.config) f = self._feature() passing_scenario = self._scenario() failing_scenario = self._scenario() failing_scenario.steps.append(self._step()) scenarios = [ passing_scenario, failing_scenario ] for scenario in scenarios: f.add_scenario(scenario) # -- FORMATTER CALLBACKS: p.feature(f) for scenario in f.scenarios: p.scenario(scenario) failing_scenario.steps[0].status = Status.failed assert scenarios[0].status == Status.passed assert scenarios[1].status == Status.failed p.eof() eq_([ failing_scenario ], p.failed_scenarios) # -- EMIT REPORT: p.close() def test_feature_with_one_passing_two_failing_scenario(self): p = self._formatter(_tf(), self.config) f = self._feature() passing_scenario = self._scenario() failing_scenario1 = self._scenario() failing_scenario1.steps.append(self._step()) failing_scenario2 = self._scenario() failing_scenario2.steps.append(self._step()) scenarios = [ failing_scenario1, passing_scenario, failing_scenario2 ] for scenario in scenarios: f.add_scenario(scenario) # -- FORMATTER CALLBACKS: p.feature(f) for scenario in f.scenarios: p.scenario(scenario) failing_scenario1.steps[0].status = Status.failed failing_scenario2.steps[0].status = Status.failed assert scenarios[0].status == Status.failed assert scenarios[1].status == Status.passed assert scenarios[2].status == Status.failed p.eof() eq_([ failing_scenario1, failing_scenario2 ], p.failed_scenarios) # -- EMIT REPORT: p.close() class TestRerunAndPrettyFormatters(MultipleFormattersTest): formatters = ["rerun", "pretty"] class TestRerunAndPlainFormatters(MultipleFormattersTest): formatters = ["rerun", "plain"] class TestRerunAndScenarioProgressFormatters(MultipleFormattersTest): formatters = ["rerun", "progress"] class TestRerunAndStepProgressFormatters(MultipleFormattersTest): formatters = ["rerun", "progress2"] class TestRerunAndJsonFormatter(MultipleFormattersTest): formatters = ["rerun", "json"] behave-1.2.6/test/test_formatter_tags.py0000644000076600000240000000417413244555737020473 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Test formatters: * behave.formatter.tags.TagsCountFormatter * behave.formatter.tags.TagsLocationFormatter """ from __future__ import absolute_import from .test_formatter import FormatterTests as FormatterTest from .test_formatter import MultipleFormattersTests as MultipleFormattersTest # ----------------------------------------------------------------------------- # FORMATTER TESTS: With TagCountFormatter # ----------------------------------------------------------------------------- class TestTagsCountFormatter(FormatterTest): formatter_name = "tags" # ----------------------------------------------------------------------------- # FORMATTER TESTS: With TagLocationFormatter # ----------------------------------------------------------------------------- class TestTagsLocationFormatter(FormatterTest): formatter_name = "tags.location" # ----------------------------------------------------------------------------- # MULTI-FORMATTER TESTS: With TagCountFormatter # ----------------------------------------------------------------------------- class TestPrettyAndTagsCount(MultipleFormattersTest): formatters = ["pretty", "tags"] class TestPlainAndTagsCount(MultipleFormattersTest): formatters = ["plain", "tags"] class TestJSONAndTagsCount(MultipleFormattersTest): formatters = ["json", "tags"] class TestRerunAndTagsCount(MultipleFormattersTest): formatters = ["rerun", "tags"] # ----------------------------------------------------------------------------- # MULTI-FORMATTER TESTS: With TagLocationFormatter # ----------------------------------------------------------------------------- class TestPrettyAndTagsLocation(MultipleFormattersTest): formatters = ["pretty", "tags.location"] class TestPlainAndTagsLocation(MultipleFormattersTest): formatters = ["plain", "tags.location"] class TestJSONAndTagsLocation(MultipleFormattersTest): formatters = ["json", "tags.location"] class TestRerunAndTagsLocation(MultipleFormattersTest): formatters = ["rerun", "tags.location"] class TestTagsCountAndTagsLocation(MultipleFormattersTest): formatters = ["tags", "tags.location"] behave-1.2.6/test/test_importer.py0000644000076600000240000001165613244555737017316 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Tests for behave.importing. The module provides a lazy-loading/importing mechanism. """ from __future__ import absolute_import from behave.importer import LazyObject, LazyDict, load_module, parse_scoped_name from behave.formatter.base import Formatter from nose.tools import eq_, assert_raises import sys import types # import unittest class TestTheory(object): pass class ImportModuleTheory(TestTheory): """ Provides a test theory for importing modules. """ @classmethod def ensure_module_is_not_imported(cls, module_name): if module_name in sys.modules: del sys.modules[module_name] cls.assert_module_is_not_imported(module_name) @staticmethod def assert_module_is_imported(module_name): module = sys.modules.get(module_name, None) assert module_name in sys.modules assert module is not None @staticmethod def assert_module_is_not_imported(module_name): assert module_name not in sys.modules @staticmethod def assert_module_with_name(module, name): assert isinstance(module, types.ModuleType) eq_(module.__name__, name) class TestLoadModule(object): theory = ImportModuleTheory def test_load_module__should_fail_for_unknown_module(self): assert_raises(ImportError, load_module, "__unknown_module__") def test_load_module__should_succeed_for_already_imported_module(self): module_name = "behave.importer" self.theory.assert_module_is_imported(module_name) module = load_module(module_name) self.theory.assert_module_with_name(module, module_name) self.theory.assert_module_is_imported(module_name) def test_load_module__should_succeed_for_existing_module(self): module_name = "test._importer_candidate" self.theory.ensure_module_is_not_imported(module_name) module = load_module(module_name) self.theory.assert_module_with_name(module, module_name) self.theory.assert_module_is_imported(module_name) class TestLazyObject(object): def test_get__should_succeed_for_known_object(self): lazy = LazyObject("behave.importer", "LazyObject") value = lazy.get() assert value is LazyObject lazy2 = LazyObject("behave.importer:LazyObject") value2 = lazy2.get() assert value2 is LazyObject lazy3 = LazyObject("behave.formatter.steps", "StepsFormatter") value3 = lazy3.get() assert issubclass(value3, Formatter) def test_get__should_fail_for_unknown_module(self): lazy = LazyObject("__unknown_module__", "xxx") assert_raises(ImportError, lazy.get) def test_get__should_fail_for_unknown_object_in_module(self): lazy = LazyObject("test._importer_candidate", "xxx") assert_raises(ImportError, lazy.get) class LazyDictTheory(TestTheory): @staticmethod def safe_getitem(data, key): return dict.__getitem__(data, key) @classmethod def assert_item_is_lazy(cls, data, key): value = cls.safe_getitem(data, key) cls.assert_is_lazy_object(value) @classmethod def assert_item_is_not_lazy(cls, data, key): value = cls.safe_getitem(data, key) cls.assert_is_not_lazy_object(value) @staticmethod def assert_is_lazy_object(obj): assert isinstance(obj, LazyObject) @staticmethod def assert_is_not_lazy_object(obj): assert not isinstance(obj, LazyObject) class TestLazyDict(object): theory = LazyDictTheory def test_unknown_item_access__should_raise_keyerror(self): lazy_dict = LazyDict({"alice": 42}) item_access = lambda key: lazy_dict[key] assert_raises(KeyError, item_access, "unknown") def test_plain_item_access__should_succeed(self): theory = LazyDictTheory lazy_dict = LazyDict({"alice": 42}) theory.assert_item_is_not_lazy(lazy_dict, "alice") value = lazy_dict["alice"] eq_(value, 42) def test_lazy_item_access__should_load_object(self): ImportModuleTheory.ensure_module_is_not_imported("inspect") lazy_dict = LazyDict({"alice": LazyObject("inspect:ismodule")}) self.theory.assert_item_is_lazy(lazy_dict, "alice") self.theory.assert_item_is_lazy(lazy_dict, "alice") value = lazy_dict["alice"] self.theory.assert_is_not_lazy_object(value) self.theory.assert_item_is_not_lazy(lazy_dict, "alice") def test_lazy_item_access__should_fail_with_unknown_module(self): lazy_dict = LazyDict({"bob": LazyObject("__unknown_module__", "xxx")}) item_access = lambda key: lazy_dict[key] assert_raises(ImportError, item_access, "bob") def test_lazy_item_access__should_fail_with_unknown_object(self): lazy_dict = LazyDict({ "bob": LazyObject("behave.importer", "XUnknown") }) item_access = lambda key: lazy_dict[key] assert_raises(ImportError, item_access, "bob") behave-1.2.6/test/test_log_capture.py0000644000076600000240000000160213244555737017747 0ustar jensstaff00000000000000from __future__ import absolute_import, with_statement from nose.tools import * from mock import patch from behave.log_capture import LoggingCapture from six.moves import range class TestLogCapture(object): def test_get_value_returns_all_log_records(self): class FakeConfig(object): logging_filter = None logging_format = None logging_datefmt = None logging_level = None fake_records = [object() for x in range(0, 10)] handler = LoggingCapture(FakeConfig()) handler.buffer = fake_records with patch.object(handler.formatter, 'format') as format: format.return_value = 'foo' expected = '\n'.join(['foo'] * len(fake_records)) eq_(handler.getvalue(), expected) calls = [args[0][0] for args in format.call_args_list] eq_(calls, fake_records) behave-1.2.6/test/test_matchers.py0000644000076600000240000002043113244555737017252 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- from __future__ import absolute_import, with_statement from mock import Mock, patch from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import import parse from behave.matchers import Match, Matcher, ParseMatcher, RegexMatcher, \ SimplifiedRegexMatcher, CucumberRegexMatcher from behave import matchers, runner class DummyMatcher(Matcher): desired_result = None def check_match(self, step): return DummyMatcher.desired_result class TestMatcher(object): # pylint: disable=invalid-name, no-self-use def setUp(self): DummyMatcher.desired_result = None def test_returns_none_if_check_match_returns_none(self): matcher = DummyMatcher(None, None) assert matcher.match('just a random step') is None def test_returns_match_object_if_check_match_returns_arguments(self): arguments = ['some', 'random', 'objects'] func = lambda x: -x DummyMatcher.desired_result = arguments matcher = DummyMatcher(func, None) match = matcher.match('just a random step') assert isinstance(match, Match) assert match.func is func assert match.arguments == arguments class TestParseMatcher(object): # pylint: disable=invalid-name, no-self-use def setUp(self): self.recorded_args = None def record_args(self, *args, **kwargs): self.recorded_args = (args, kwargs) def test_returns_none_if_parser_does_not_match(self): # pylint: disable=redefined-outer-name # REASON: parse matcher = ParseMatcher(None, 'a string') with patch.object(matcher.parser, 'parse') as parse: parse.return_value = None assert matcher.match('just a random step') is None def test_returns_arguments_based_on_matches(self): func = lambda x: -x matcher = ParseMatcher(func, 'foo') results = parse.Result([1, 2, 3], {'foo': 'bar', 'baz': -45.3}, { 0: (13, 14), 1: (16, 17), 2: (22, 23), 'foo': (32, 35), 'baz': (39, 44), }) expected = [ (13, 14, '1', 1, None), (16, 17, '2', 2, None), (22, 23, '3', 3, None), (32, 35, 'bar', 'bar', 'foo'), (39, 44, '-45.3', -45.3, 'baz'), ] with patch.object(matcher.parser, 'parse') as p: p.return_value = results m = matcher.match('some numbers 1, 2 and 3 and the bar is -45.3') assert m.func is func args = m.arguments have = [(a.start, a.end, a.original, a.value, a.name) for a in args] eq_(have, expected) def test_named_arguments(self): text = "has a {string}, an {integer:d} and a {decimal:f}" matcher = ParseMatcher(self.record_args, text) context = runner.Context(Mock()) m = matcher.match("has a foo, an 11 and a 3.14159") m.run(context) eq_(self.recorded_args, ((context,), { 'string': 'foo', 'integer': 11, 'decimal': 3.14159 })) def test_positional_arguments(self): text = "has a {}, an {:d} and a {:f}" matcher = ParseMatcher(self.record_args, text) context = runner.Context(Mock()) m = matcher.match("has a foo, an 11 and a 3.14159") m.run(context) eq_(self.recorded_args, ((context, 'foo', 11, 3.14159), {})) class TestRegexMatcher(object): # pylint: disable=invalid-name, no-self-use MATCHER_CLASS = RegexMatcher def test_returns_none_if_regex_does_not_match(self): RegexMatcher = self.MATCHER_CLASS matcher = RegexMatcher(None, 'a string') regex = Mock() regex.match.return_value = None matcher.regex = regex assert matcher.match('just a random step') is None def test_returns_arguments_based_on_groups(self): RegexMatcher = self.MATCHER_CLASS func = lambda x: -x matcher = RegexMatcher(func, 'foo') regex = Mock() regex.groupindex = {'foo': 4, 'baz': 5} match = Mock() match.groups.return_value = ('1', '2', '3', 'bar', '-45.3') positions = { 1: (13, 14), 2: (16, 17), 3: (22, 23), 4: (32, 35), 5: (39, 44), } match.start.side_effect = lambda idx: positions[idx][0] match.end.side_effect = lambda idx: positions[idx][1] regex.match.return_value = match matcher.regex = regex expected = [ (13, 14, '1', '1', None), (16, 17, '2', '2', None), (22, 23, '3', '3', None), (32, 35, 'bar', 'bar', 'foo'), (39, 44, '-45.3', '-45.3', 'baz'), ] m = matcher.match('some numbers 1, 2 and 3 and the bar is -45.3') assert m.func is func args = m.arguments have = [(a.start, a.end, a.original, a.value, a.name) for a in args] eq_(have, expected) class TestSimplifiedRegexMatcher(TestRegexMatcher): MATCHER_CLASS = SimplifiedRegexMatcher def test_steps_with_same_prefix_are_not_ordering_sensitive(self): # -- RELATED-TO: issue #280 # pylint: disable=unused-argument def step_func1(context): pass # pylint: disable=multiple-statements def step_func2(context): pass # pylint: disable=multiple-statements # pylint: enable=unused-argument matcher1 = SimplifiedRegexMatcher(step_func1, "I do something") matcher2 = SimplifiedRegexMatcher(step_func2, "I do something more") # -- CHECK: ORDERING SENSITIVITY matched1 = matcher1.match(matcher2.pattern) matched2 = matcher2.match(matcher1.pattern) assert matched1 is None assert matched2 is None # -- CHECK: Can match itself (if step text is simple) matched1 = matcher1.match(matcher1.pattern) matched2 = matcher2.match(matcher2.pattern) assert isinstance(matched1, Match) assert isinstance(matched2, Match) @raises(AssertionError) def test_step_should_not_use_regex_begin_marker(self): SimplifiedRegexMatcher(None, "^I do something") @raises(AssertionError) def test_step_should_not_use_regex_end_marker(self): SimplifiedRegexMatcher(None, "I do something$") @raises(AssertionError) def test_step_should_not_use_regex_begin_and_end_marker(self): SimplifiedRegexMatcher(None, "^I do something$") class TestCucumberRegexMatcher(TestRegexMatcher): MATCHER_CLASS = CucumberRegexMatcher def test_steps_with_same_prefix_are_not_ordering_sensitive(self): # -- RELATED-TO: issue #280 # pylint: disable=unused-argument def step_func1(context): pass # pylint: disable=multiple-statements def step_func2(context): pass # pylint: disable=multiple-statements # pylint: enable=unused-argument matcher1 = CucumberRegexMatcher(step_func1, "^I do something$") matcher2 = CucumberRegexMatcher(step_func2, "^I do something more$") # -- CHECK: ORDERING SENSITIVITY matched1 = matcher1.match(matcher2.pattern[1:-1]) matched2 = matcher2.match(matcher1.pattern[1:-1]) assert matched1 is None assert matched2 is None # -- CHECK: Can match itself (if step text is simple) matched1 = matcher1.match(matcher1.pattern[1:-1]) matched2 = matcher2.match(matcher2.pattern[1:-1]) assert isinstance(matched1, Match) assert isinstance(matched2, Match) def test_step_should_use_regex_begin_marker(self): CucumberRegexMatcher(None, "^I do something") def test_step_should_use_regex_end_marker(self): CucumberRegexMatcher(None, "I do something$") def test_step_should_use_regex_begin_and_end_marker(self): CucumberRegexMatcher(None, "^I do something$") def test_step_matcher_current_matcher(): current_matcher = matchers.current_matcher for name, klass in list(matchers.matcher_mapping.items()): matchers.use_step_matcher(name) matcher = matchers.get_matcher(lambda x: -x, 'foo') assert isinstance(matcher, klass) matchers.current_matcher = current_matcher behave-1.2.6/test/test_model.py0000644000076600000240000010134613244555737016551 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- # pylint: disable=no-self-use, line-too-long from __future__ import absolute_import, print_function, with_statement import unittest from mock import Mock, patch from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import import six from six.moves import range # pylint: disable=redefined-builtin from six.moves import zip # pylint: disable=redefined-builtin if six.PY2: import traceback2 as traceback traceback_modname = "traceback2" else: import traceback traceback_modname = "traceback" from behave.model_core import FileLocation, Status from behave.model import Feature, Scenario, ScenarioOutline, Step from behave.model import Table, Row from behave.matchers import NoMatch from behave.runner import Context from behave.capture import CaptureController from behave.configuration import Configuration from behave.compat.collections import OrderedDict from behave import step_registry # -- CONVENIENCE-ALIAS: _text = six.text_type class TestFeatureRun(unittest.TestCase): # pylint: disable=invalid-name def setUp(self): self.runner = Mock() self.runner.aborted = False self.runner.feature.tags = [] self.config = self.runner.config = Mock() self.context = self.runner.context = Mock() self.formatters = self.runner.formatters = [Mock()] self.run_hook = self.runner.run_hook = Mock() def test_formatter_feature_called(self): feature = Feature('foo.feature', 1, u'Feature', u'foo', background=Mock()) feature.run(self.runner) self.formatters[0].feature.assert_called_with(feature) def test_formatter_background_called_when_feature_has_background(self): feature = Feature('foo.feature', 1, u'Feature', u'foo', background=Mock()) feature.run(self.runner) self.formatters[0].background.assert_called_with(feature.background) def test_formatter_background_not_called_when_feature_has_no_background(self): feature = Feature('foo.feature', 1, u'Feature', u'foo') feature.run(self.runner) assert not self.formatters[0].background.called def test_run_runs_scenarios(self): scenarios = [Mock(), Mock()] for scenario in scenarios: scenario.tags = [] scenario.run.return_value = False self.config.tags.check.return_value = True # pylint: disable=no-member self.config.name = [] feature = Feature('foo.feature', 1, u'Feature', u'foo', scenarios=scenarios) feature.run(self.runner) for scenario in scenarios: scenario.run.assert_called_with(self.runner) def test_run_runs_named_scenarios(self): scenarios = [Mock(Scenario), Mock(Scenario)] scenarios[0].name = 'first scenario' scenarios[1].name = 'second scenario' scenarios[0].tags = [] scenarios[1].tags = [] # -- FAKE-CHECK: scenarios[0].should_run_with_name_select.return_value = True scenarios[1].should_run_with_name_select.return_value = False for scenario in scenarios: scenario.run.return_value = False self.config.tags.check.return_value = True # pylint: disable=no-member self.config.name = ['first', 'third'] self.config.name_re = Configuration.build_name_re(self.config.name) feature = Feature('foo.feature', 1, u'Feature', u'foo', scenarios=scenarios) feature.run(self.runner) scenarios[0].run.assert_called_with(self.runner) assert not scenarios[1].run.called scenarios[0].should_run_with_name_select.assert_called_with(self.config) scenarios[1].should_run_with_name_select.assert_called_with(self.config) def test_run_runs_named_scenarios_with_regexp(self): scenarios = [Mock(), Mock()] scenarios[0].name = 'first scenario' scenarios[1].name = 'second scenario' scenarios[0].tags = [] scenarios[1].tags = [] # -- FAKE-CHECK: scenarios[0].should_run_with_name_select.return_value = False scenarios[1].should_run_with_name_select.return_value = True for scenario in scenarios: scenario.run.return_value = False self.config.tags.check.return_value = True # pylint: disable=no-member self.config.name = ['third .*', 'second .*'] self.config.name_re = Configuration.build_name_re(self.config.name) feature = Feature('foo.feature', 1, u'Feature', u'foo', scenarios=scenarios) feature.run(self.runner) assert not scenarios[0].run.called scenarios[1].run.assert_called_with(self.runner) scenarios[0].should_run_with_name_select.assert_called_with(self.config) scenarios[1].should_run_with_name_select.assert_called_with(self.config) # @prepared def test_run_exclude_named_scenarios_with_regexp(self): # -- NOTE: Works here only because it is run against Mocks. scenarios = [Mock(), Mock(), Mock()] scenarios[0].name = "Alice in Florida" scenarios[1].name = "Alice and Bob" scenarios[2].name = "Bob in Paris" scenarios[0].tags = [] scenarios[1].tags = [] scenarios[2].tags = [] # -- FAKE-CHECK: scenarios[0].should_run_with_name_select.return_value = False scenarios[1].should_run_with_name_select.return_value = False scenarios[2].should_run_with_name_select.return_value = True for scenario in scenarios: scenario.run.return_value = False self.config.tags.check.return_value = True # pylint: disable=no-member self.config.name = ["(?!Alice)"] # Exclude all scenarios with "Alice" self.config.name_re = Configuration.build_name_re(self.config.name) feature = Feature('foo.feature', 1, u'Feature', u'foo', scenarios=scenarios) feature.run(self.runner) assert not scenarios[0].run.called scenarios[0].should_run_with_name_select.assert_called_with(self.config) scenarios[1].should_run_with_name_select.assert_called_with(self.config) scenarios[2].should_run_with_name_select.assert_called_with(self.config) scenarios[0].run.assert_not_called() scenarios[1].run.assert_not_called() scenarios[2].run.assert_called_with(self.runner) def test_feature_hooks_not_run_if_feature_not_being_run(self): self.config.tags.check.return_value = False # pylint: disable=no-member feature = Feature('foo.feature', 1, u'Feature', u'foo') feature.run(self.runner) assert not self.run_hook.called class TestScenarioRun(unittest.TestCase): # pylint: disable=invalid-name def setUp(self): self.runner = Mock() self.runner.aborted = False self.runner.feature.tags = [] self.config = self.runner.config = Mock() self.config.dry_run = False self.context = self.runner.context = Mock() self.formatters = self.runner.formatters = [Mock()] self.run_hook = self.runner.run_hook = Mock() def test_run_invokes_formatter_scenario_and_steps_correctly(self): self.config.stdout_capture = False self.config.log_capture = False self.config.tags.check.return_value = True # pylint: disable=no-member steps = [Mock(), Mock()] scenario = Scenario('foo.feature', 17, u'Scenario', u'foo', steps=steps) scenario.run(self.runner) self.formatters[0].scenario.assert_called_with(scenario) for step in steps: step.run.assert_called_with(self.runner) if six.PY3: stringio_target = 'io.StringIO' else: stringio_target = 'StringIO.StringIO' def test_handles_stdout_and_log_capture(self): self.config.stdout_capture = True self.config.log_capture = True self.config.tags.check.return_value = True # pylint: disable=no-member steps = [Mock(), Mock()] scenario = Scenario('foo.feature', 17, u'Scenario', u'foo', steps=steps) scenario.run(self.runner) self.runner.setup_capture.assert_called_with() self.runner.teardown_capture.assert_called_with() def test_failed_step_causes_remaining_steps_to_be_skipped(self): self.config.stdout_capture = False self.config.log_capture = False self.config.tags.check.return_value = True # pylint: disable=no-member steps = [Mock(), Mock()] scenario = Scenario('foo.feature', 17, u'Scenario', u'foo', steps=steps) steps[0].run.return_value = False steps[1].step_type = "when" steps[1].name = "step1" def step1_function(context): # pylint: disable=unused-argument pass my_step_registry = step_registry.StepRegistry() my_step_registry.add_step_definition("when", "step1", step1_function) with patch("behave.step_registry.registry", my_step_registry): assert scenario.run(self.runner) eq_(steps[1].status, Status.skipped) def test_failed_step_causes_context_failure_to_be_set(self): self.config.stdout_capture = False self.config.log_capture = False self.config.tags.check.return_value = True # pylint: disable=no-member steps = [ Mock(step_type="given", name="step0"), Mock(step_type="then", name="step1"), ] scenario = Scenario('foo.feature', 17, u'Scenario', u'foo', steps=steps) steps[0].run.return_value = False assert scenario.run(self.runner) # pylint: disable=protected-access self.context._set_root_attribute.assert_called_with("failed", True) def test_undefined_step_causes_failed_scenario_status(self): self.config.stdout_capture = False self.config.log_capture = False self.config.tags.check.return_value = True # pylint: disable=no-member passed_step = Mock() undefined_step = Mock() steps = [passed_step, undefined_step] scenario = Scenario('foo.feature', 17, u'Scenario', u'foo', steps=steps) passed_step.run.return_value = True passed_step.status = Status.passed undefined_step.run.return_value = False undefined_step.status = Status.undefined assert scenario.run(self.runner) eq_(undefined_step.status, Status.undefined) eq_(scenario.status, Status.failed) # pylint: disable=protected-access self.context._set_root_attribute.assert_called_with("failed", True) def test_skipped_steps_set_step_status_and_scenario_status_if_not_set(self): self.config.stdout_capture = False self.config.log_capture = False self.config.tags.check.return_value = False # pylint: disable=no-member steps = [Mock(), Mock()] scenario = Scenario('foo.feature', 17, u'Scenario', u'foo', steps=steps) scenario.run(self.runner) assert False not in [s.status == Status.skipped for s in steps] eq_(scenario.status, Status.skipped) def test_scenario_hooks_not_run_if_scenario_not_being_run(self): self.config.tags.check.return_value = False # pylint: disable=no-member scenario = Scenario('foo.feature', 17, u'Scenario', u'foo') scenario.run(self.runner) assert not self.run_hook.called def test_should_run_with_name_select(self): scenario_name = u"first scenario" scenario = Scenario("foo.feature", 17, u"Scenario", scenario_name) self.config.name = ['first .*', 'second .*'] self.config.name_re = Configuration.build_name_re(self.config.name) assert scenario.should_run_with_name_select(self.config) class TestScenarioOutline(unittest.TestCase): # pylint: disable=invalid-name def test_run_calls_run_on_each_generated_scenario(self): # pylint: disable=protected-access outline = ScenarioOutline('foo.feature', 17, u'Scenario Outline', u'foo') outline._scenarios = [Mock(), Mock()] for scenario in outline._scenarios: scenario.run.return_value = False runner = Mock() runner.context = Mock() outline.run(runner) for s in outline._scenarios: s.run.assert_called_with(runner) def test_run_stops_on_first_failure_if_requested(self): # pylint: disable=protected-access outline = ScenarioOutline('foo.feature', 17, u'Scenario Outline', u'foo') outline._scenarios = [Mock(), Mock()] outline._scenarios[0].run.return_value = True runner = Mock() runner.context = Mock() config = runner.config = Mock() config.stop = True outline.run(runner) outline._scenarios[0].run.assert_called_with(runner) assert not outline._scenarios[1].run.called def test_run_sets_context_variable_for_outline(self): # pylint: disable=protected-access outline = ScenarioOutline('foo.feature', 17, u'Scenario Outline', u'foo') outline._scenarios = [Mock(), Mock(), Mock()] for scenario in outline._scenarios: scenario.run.return_value = False runner = Mock() context = runner.context = Mock() config = runner.config = Mock() config.stop = True outline.run(runner) eq_(context._set_root_attribute.call_args_list, [ (('active_outline', outline._scenarios[0]._row), {}), (('active_outline', outline._scenarios[1]._row), {}), (('active_outline', outline._scenarios[2]._row), {}), (('active_outline', None), {}), ]) def test_run_should_pass_when_all_examples_pass(self): # pylint: disable=protected-access outline = ScenarioOutline('foo.feature', 17, u'Scenario Outline', u'foo') outline._scenarios = [Mock(), Mock(), Mock()] for scenario in outline._scenarios: scenario.run.return_value = False runner = Mock() context = runner.context = Mock() config = runner.config = Mock() config.stop = True resultFailed = outline.run(runner) eq_(resultFailed, False) def test_run_should_fail_when_first_examples_fails(self): outline = ScenarioOutline('foo.feature', 17, u'Scenario Outline', u'foo') failed = True # pylint: disable=protected-access outline._scenarios = [Mock(), Mock()] outline._scenarios[0].run.return_value = failed outline._scenarios[1].run.return_value = not failed runner = Mock() context = runner.context = Mock() config = runner.config = Mock() config.stop = True resultFailed = outline.run(runner) eq_(resultFailed, True) def test_run_should_fail_when_last_examples_fails(self): outline = ScenarioOutline('foo.feature', 17, u'Scenario Outline', u'foo') failed = True # pylint: disable=protected-access outline._scenarios = [Mock(), Mock()] outline._scenarios[0].run.return_value = not failed outline._scenarios[1].run.return_value = failed runner = Mock() context = runner.context = Mock() config = runner.config = Mock() config.stop = True resultFailed = outline.run(runner) eq_(resultFailed, True) def test_run_should_fail_when_middle_examples_fails(self): outline = ScenarioOutline('foo.feature', 17, u'Scenario Outline', u'foo') failed = True # pylint: disable=protected-access outline._scenarios = [Mock(), Mock(), Mock()] outline._scenarios[0].run.return_value = not failed outline._scenarios[1].run.return_value = failed outline._scenarios[2].run.return_value = not failed runner = Mock() context = runner.context = Mock() config = runner.config = Mock() config.stop = True resultFailed = outline.run(runner) eq_(resultFailed, True) def raiser(exception): def func(*args, **kwargs): # pylint: disable=unused-argument raise exception return func class TestStepRun(unittest.TestCase): # pylint: disable=invalid-name def setUp(self): self.step_registry = Mock() self.runner = Mock() # self.capture_controller = self.runner.capture_controller = Mock() self.capture_controller = CaptureController(self.runner.config) self.runner.capture_controller = self.capture_controller self.runner.step_registry = self.step_registry self.config = self.runner.config = Mock() self.config.outputs = [None] self.context = self.runner.context = Mock() print('context is %s' % self.context) self.formatters = self.runner.formatters = [Mock()] self.stdout_capture = self.capture_controller.stdout_capture = Mock() self.stdout_capture.getvalue.return_value = '' self.stderr_capture = self.capture_controller.stderr_capture = Mock() self.stderr_capture.getvalue.return_value = '' self.log_capture = self.capture_controller.log_capture = Mock() self.log_capture.getvalue.return_value = '' self.run_hook = self.runner.run_hook = Mock() def test_run_appends_step_to_undefined_when_no_match_found(self): step = Step('foo.feature', 17, u'Given', 'given', u'foo') self.runner.step_registry.find_match.return_value = None self.runner.undefined_steps = [] assert not step.run(self.runner) assert step in self.runner.undefined_steps eq_(step.status, Status.undefined) def test_run_reports_undefined_step_via_formatter_when_not_quiet(self): step = Step('foo.feature', 17, u'Given', 'given', u'foo') self.runner.step_registry.find_match.return_value = None assert not step.run(self.runner) self.formatters[0].match.assert_called_with(NoMatch()) self.formatters[0].result.assert_called_with(step) def test_run_with_no_match_does_not_touch_formatter_when_quiet(self): step = Step('foo.feature', 17, u'Given', 'given', u'foo') self.runner.step_registry.find_match.return_value = None assert not step.run(self.runner, quiet=True) assert not self.formatters[0].match.called assert not self.formatters[0].result.called def test_run_when_not_quiet_reports_match_and_result(self): step = Step('foo.feature', 17, u'Given', 'given', u'foo') match = Mock() self.runner.step_registry.find_match.return_value = match side_effects = (None, raiser(AssertionError('whee')), raiser(Exception('whee'))) for side_effect in side_effects: match.run.side_effect = side_effect step.run(self.runner) self.formatters[0].match.assert_called_with(match) self.formatters[0].result.assert_called_with(step) def test_run_when_quiet_reports_nothing(self): step = Step('foo.feature', 17, u'Given', 'given', u'foo') match = Mock() self.runner.step_registry.find_match.return_value = match side_effects = (None, raiser(AssertionError('whee')), raiser(Exception('whee'))) for side_effect in side_effects: match.run.side_effect = side_effect step.run(self.runner, quiet=True) assert not self.formatters[0].match.called assert not self.formatters[0].result.called def test_run_runs_before_hook_then_match_then_after_hook(self): step = Step('foo.feature', 17, u'Given', 'given', u'foo') match = Mock() self.runner.step_registry.find_match.return_value = match side_effects = (None, AssertionError('whee'), Exception('whee')) for side_effect in side_effects: # Make match.run() and runner.run_hook() the same mock so # we can make sure things happen in the right order. self.runner.run_hook = match.run = Mock() def effect(thing): # pylint: disable=unused-argument def raiser_(*args, **kwargs): match.run.side_effect = None if thing: raise thing def nonraiser(*args, **kwargs): match.run.side_effect = raiser_ return nonraiser match.run.side_effect = effect(side_effect) step.run(self.runner) eq_(match.run.call_args_list, [ (('before_step', self.context, step), {}), ((self.context,), {}), (('after_step', self.context, step), {}), ]) def test_run_sets_table_if_present(self): step = Step('foo.feature', 17, u'Given', 'given', u'foo', table=Mock()) self.runner.step_registry.find_match.return_value = Mock() step.run(self.runner) eq_(self.context.table, step.table) def test_run_sets_text_if_present(self): step = Step('foo.feature', 17, u'Given', 'given', u'foo', text=Mock(name='text')) self.runner.step_registry.find_match.return_value = Mock() step.run(self.runner) eq_(self.context.text, step.text) def test_run_sets_status_to_passed_if_nothing_goes_wrong(self): step = Step('foo.feature', 17, u'Given', 'given', u'foo') step.error_message = None self.runner.step_registry.find_match.return_value = Mock() step.run(self.runner) eq_(step.status, Status.passed) eq_(step.error_message, None) def test_run_sets_status_to_failed_on_assertion_error(self): step = Step('foo.feature', 17, u'Given', 'given', u'foo') self.runner.context = Context(self.runner) self.runner.config.stdout_capture = True self.runner.config.log_capture = False self.runner.capture_controller = CaptureController(self.runner.config) self.runner.capture_controller.setup_capture(self.runner.context) step.error_message = None match = Mock() match.run.side_effect = raiser(AssertionError('whee')) self.runner.step_registry.find_match.return_value = match step.run(self.runner) eq_(step.status, Status.failed) assert step.error_message.startswith('Assertion Failed') @patch('%s.format_exc' % traceback_modname) def test_run_sets_status_to_failed_on_exception(self, format_exc): step = Step('foo.feature', 17, u'Given', 'given', u'foo') step.error_message = None match = Mock() match.run.side_effect = raiser(Exception('whee')) self.runner.step_registry.find_match.return_value = match format_exc.return_value = 'something to do with an exception' step.run(self.runner) eq_(step.status, Status.failed) eq_(step.error_message, format_exc.return_value) @patch('time.time') def test_run_calculates_duration(self, time_time): step = Step('foo.feature', 17, u'Given', 'given', u'foo') match = Mock() self.runner.step_registry.find_match.return_value = match def time_time_1(): def time_time_2(): return 23 time_time.side_effect = time_time_2 return 17 side_effects = (None, raiser(AssertionError('whee')), raiser(Exception('whee'))) for side_effect in side_effects: match.run.side_effect = side_effect time_time.side_effect = time_time_1 step.run(self.runner) eq_(step.duration, 23 - 17) def test_run_captures_stdout_and_logging(self): step = Step('foo.feature', 17, u'Given', 'given', u'foo') match = Mock() self.runner.step_registry.find_match.return_value = match assert step.run(self.runner) self.runner.start_capture.assert_called_with() self.runner.stop_capture.assert_called_with() def test_run_appends_any_captured_stdout_on_failure(self): step = Step('foo.feature', 17, u'Given', 'given', u'foo') match = Mock() self.runner.step_registry.find_match.return_value = match self.stdout_capture.getvalue.return_value = 'frogs' match.run.side_effect = raiser(Exception('halibut')) assert not step.run(self.runner) assert 'Captured stdout:' in step.error_message assert 'frogs' in step.error_message def test_run_appends_any_captured_logging_on_failure(self): step = Step('foo.feature', 17, u'Given', 'given', u'foo') match = Mock() self.runner.step_registry.find_match.return_value = match self.log_capture.getvalue.return_value = 'toads' match.run.side_effect = raiser(AssertionError('kipper')) assert not step.run(self.runner) assert 'Captured logging:' in step.error_message assert 'toads' in step.error_message class TestTableModel(unittest.TestCase): # pylint: disable=invalid-name HEAD = [u'type of stuff', u'awesomeness', u'ridiculousness'] DATA = [ [u'fluffy', u'large', u'frequent'], [u'lint', u'low', u'high'], [u'green', u'variable', u'awkward'], ] def setUp(self): self.table = Table(self.HEAD, 0, self.DATA) def test_equivalence(self): t1 = self.table self.setUp() eq_(t1, self.table) def test_table_iteration(self): for i, row in enumerate(self.table): for j, cell in enumerate(row): eq_(cell, self.DATA[i][j]) def test_table_row_by_index(self): for i in range(3): eq_(self.table[i], Row(self.HEAD, self.DATA[i], 0)) def test_table_row_name(self): eq_(self.table[0]['type of stuff'], 'fluffy') eq_(self.table[1]['awesomeness'], 'low') eq_(self.table[2]['ridiculousness'], 'awkward') def test_table_row_index(self): eq_(self.table[0][0], 'fluffy') eq_(self.table[1][1], 'low') eq_(self.table[2][2], 'awkward') @raises(KeyError) def test_table_row_keyerror(self): self.table[0]['spam'] # pylint: disable=pointless-statement def test_table_row_items(self): eq_(list(self.table[0].items()), list(zip(self.HEAD, self.DATA[0]))) class TestModelRow(unittest.TestCase): # pylint: disable=invalid-name, bad-whitespace HEAD = [u'name', u'sex', u'age'] DATA = [u'Alice', u'female', u'12'] def setUp(self): self.row = Row(self.HEAD, self.DATA, 0) def test_len(self): eq_(len(self.row), 3) def test_getitem_with_valid_colname(self): # pylint: disable=bad-whitespace eq_(self.row['name'], u'Alice') eq_(self.row['sex'], u'female') eq_(self.row['age'], u'12') @raises(KeyError) def test_getitem_with_unknown_colname(self): self.row['__UNKNOWN_COLUMN__'] # pylint: disable=pointless-statement def test_getitem_with_valid_index(self): eq_(self.row[0], u'Alice') eq_(self.row[1], u'female') eq_(self.row[2], u'12') @raises(IndexError) def test_getitem_with_invalid_index(self): colsize = len(self.row) eq_(colsize, 3) self.row[colsize] # pylint: disable=pointless-statement def test_get_with_valid_colname(self): # pylint: disable=bad-whitespace eq_(self.row.get('name'), u'Alice') eq_(self.row.get('sex'), u'female') eq_(self.row.get('age'), u'12') def test_getitem_with_unknown_colname_should_return_default(self): eq_(self.row.get('__UNKNOWN_COLUMN__', 'XXX'), u'XXX') def test_as_dict(self): data1 = self.row.as_dict() data2 = dict(self.row.as_dict()) assert isinstance(data1, dict) assert isinstance(data2, dict) assert isinstance(data1, OrderedDict) # -- REQUIRES: Python2.7 or ordereddict installed. # assert not isinstance(data2, OrderedDict) eq_(data1, data2) # pylint: disable=bad-whitespace eq_(data1['name'], u'Alice') eq_(data1['sex'], u'female') eq_(data1['age'], u'12') class TestFileLocation(unittest.TestCase): # pylint: disable=invalid-name ordered_locations1 = [ FileLocation("features/alice.feature", 1), FileLocation("features/alice.feature", 5), FileLocation("features/alice.feature", 10), FileLocation("features/alice.feature", 11), FileLocation("features/alice.feature", 100), ] ordered_locations2 = [ FileLocation("features/alice.feature", 1), FileLocation("features/alice.feature", 10), FileLocation("features/bob.feature", 5), FileLocation("features/charly.feature", None), FileLocation("features/charly.feature", 0), FileLocation("features/charly.feature", 100), ] same_locations = [ (FileLocation("alice.feature"), FileLocation("alice.feature", None), ), (FileLocation("alice.feature", 10), FileLocation("alice.feature", 10), ), (FileLocation("features/bob.feature", 11), FileLocation("features/bob.feature", 11), ), ] def test_compare_equal(self): for value1, value2 in self.same_locations: eq_(value1, value2) def test_compare_equal_with_string(self): for location in self.ordered_locations2: eq_(location, location.filename) eq_(location.filename, location) def test_compare_not_equal(self): for value1, value2 in self.same_locations: assert not(value1 != value2) # pylint: disable=unneeded-not, superfluous-parens for locations in [self.ordered_locations1, self.ordered_locations2]: for value1, value2 in zip(locations, locations[1:]): assert value1 != value2 def test_compare_less_than(self): for locations in [self.ordered_locations1, self.ordered_locations2]: for value1, value2 in zip(locations, locations[1:]): assert value1 < value2, "FAILED: %s < %s" % (_text(value1), _text(value2)) assert value1 != value2 def test_compare_less_than_with_string(self): locations = self.ordered_locations2 for value1, value2 in zip(locations, locations[1:]): if value1.filename == value2.filename: continue assert value1 < value2.filename, \ "FAILED: %s < %s" % (_text(value1), _text(value2.filename)) assert value1.filename < value2, \ "FAILED: %s < %s" % (_text(value1.filename), _text(value2)) def test_compare_greater_than(self): for locations in [self.ordered_locations1, self.ordered_locations2]: for value1, value2 in zip(locations, locations[1:]): assert value2 > value1, "FAILED: %s > %s" % (_text(value2), _text(value1)) assert value2 != value1 def test_compare_less_or_equal(self): for value1, value2 in self.same_locations: assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2)) assert value1 == value2 for locations in [self.ordered_locations1, self.ordered_locations2]: for value1, value2 in zip(locations, locations[1:]): assert value1 <= value2, "FAILED: %s <= %s" % (_text(value1), _text(value2)) assert value1 != value2 def test_compare_greater_or_equal(self): for value1, value2 in self.same_locations: assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1)) assert value2 == value1 for locations in [self.ordered_locations1, self.ordered_locations2]: for value1, value2 in zip(locations, locations[1:]): assert value2 >= value1, "FAILED: %s >= %s" % (_text(value2), _text(value1)) assert value2 != value1 def test_filename_should_be_same_as_self(self): for location in self.ordered_locations2: assert location == location.filename assert location.filename == location def test_string_conversion(self): for location in self.ordered_locations2: expected = u"%s:%s" % (location.filename, location.line) if location.line is None: expected = location.filename assert six.text_type(location) == expected def test_repr_conversion(self): for location in self.ordered_locations2: expected = u'' % \ (location.filename, location.line) actual = repr(location) assert actual == expected, "FAILED: %s == %s" % (actual, expected) behave-1.2.6/test/test_parser.py0000644000076600000240000014222713244555737016750 0ustar jensstaff00000000000000#-*- coding: UTF-8 -*- from __future__ import absolute_import from nose.tools import * from behave import i18n, model, parser class Common(object): def compare_steps(self, steps, expected): have = [(s.step_type, s.keyword, s.name, s.text, s.table) for s in steps] eq_(have, expected) class TestParser(Common): def test_parses_feature_name(self): feature = parser.parse_feature(u"Feature: Stuff\n") eq_(feature.name, "Stuff") def test_parses_feature_name_without_newline(self): feature = parser.parse_feature(u"Feature: Stuff") eq_(feature.name, "Stuff") def test_parses_feature_description(self): doc = u""" Feature: Stuff In order to thing As an entity I want to do stuff """.strip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") eq_(feature.description, ["In order to thing", "As an entity", "I want to do stuff"]) def test_parses_feature_with_a_tag(self): doc = u""" @foo Feature: Stuff In order to thing As an entity I want to do stuff """.strip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") eq_(feature.description, ["In order to thing", "As an entity", "I want to do stuff"]) eq_(feature.tags, [model.Tag(u'foo', 1)]) def test_parses_feature_with_more_tags(self): doc = u""" @foo @bar @baz @qux @winkle_pickers @number8 Feature: Stuff In order to thing As an entity I want to do stuff """.strip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") eq_(feature.description, ["In order to thing", "As an entity", "I want to do stuff"]) eq_(feature.tags, [model.Tag(name, 1) for name in (u'foo', u'bar', u'baz', u'qux', u'winkle_pickers', u'number8')]) def test_parses_feature_with_a_tag_and_comment(self): doc = u""" @foo # Comment: ... Feature: Stuff In order to thing As an entity I want to do stuff """.strip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") eq_(feature.description, ["In order to thing", "As an entity", "I want to do stuff"]) eq_(feature.tags, [model.Tag(u'foo', 1)]) def test_parses_feature_with_more_tags_and_comment(self): doc = u""" @foo @bar @baz @qux @winkle_pickers # Comment: @number8 Feature: Stuff In order to thing As an entity I want to do stuff """.strip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") eq_(feature.description, ["In order to thing", "As an entity", "I want to do stuff"]) eq_(feature.tags, [model.Tag(name, 1) for name in (u'foo', u'bar', u'baz', u'qux', u'winkle_pickers')]) # -- NOT A TAG: u'number8' def test_parses_feature_with_background(self): doc = u""" Feature: Stuff Background: Given there is stuff When I do stuff Then stuff happens """.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") assert(feature.background) self.compare_steps(feature.background.steps, [ ('given', 'Given', 'there is stuff', None, None), ('when', 'When', 'I do stuff', None, None), ('then', 'Then', 'stuff happens', None, None), ]) def test_parses_feature_with_description_and_background(self): doc = u""" Feature: Stuff This... is... STUFF! Background: Given there is stuff When I do stuff Then stuff happens """.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") eq_(feature.description, ["This... is... STUFF!"]) assert(feature.background) self.compare_steps(feature.background.steps, [ ('given', 'Given', 'there is stuff', None, None), ('when', 'When', 'I do stuff', None, None), ('then', 'Then', 'stuff happens', None, None), ]) def test_parses_feature_with_a_scenario(self): doc = u""" Feature: Stuff Scenario: Doing stuff Given there is stuff When I do stuff Then stuff happens """.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") assert(len(feature.scenarios) == 1) eq_(feature.scenarios[0].name, 'Doing stuff') self.compare_steps(feature.scenarios[0].steps, [ ('given', 'Given', 'there is stuff', None, None), ('when', 'When', 'I do stuff', None, None), ('then', 'Then', 'stuff happens', None, None), ]) def test_parses_lowercase_step_keywords(self): doc = u""" Feature: Stuff Scenario: Doing stuff giVeN there is stuff when I do stuff tHEn stuff happens """.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") assert(len(feature.scenarios) == 1) eq_(feature.scenarios[0].name, 'Doing stuff') self.compare_steps(feature.scenarios[0].steps, [ ('given', 'Given', 'there is stuff', None, None), ('when', 'When', 'I do stuff', None, None), ('then', 'Then', 'stuff happens', None, None), ]) def test_parses_ja_keywords(self): doc = u""" 機能: Stuff シナリオ: Doing stuff 前提there is stuff もしI do stuff ならばstuff happens """.lstrip() feature = parser.parse_feature(doc, language='ja') eq_(feature.name, "Stuff") assert(len(feature.scenarios) == 1) eq_(feature.scenarios[0].name, 'Doing stuff') self.compare_steps(feature.scenarios[0].steps, [ ('given', u'前提', 'there is stuff', None, None), ('when', u'もし', 'I do stuff', None, None), ('then', u'ならば', 'stuff happens', None, None), ]) def test_parses_feature_with_description_and_background_and_scenario(self): doc = u""" Feature: Stuff Oh my god, it's full of stuff... Background: Given I found some stuff Scenario: Doing stuff Given there is stuff When I do stuff Then stuff happens """.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") eq_(feature.description, ["Oh my god, it's full of stuff..."]) assert(feature.background) self.compare_steps(feature.background.steps, [ ('given', 'Given', 'I found some stuff', None, None), ]) assert(len(feature.scenarios) == 1) eq_(feature.scenarios[0].name, 'Doing stuff') self.compare_steps(feature.scenarios[0].steps, [ ('given', 'Given', 'there is stuff', None, None), ('when', 'When', 'I do stuff', None, None), ('then', 'Then', 'stuff happens', None, None), ]) def test_parses_feature_with_multiple_scenarios(self): doc = u""" Feature: Stuff Scenario: Doing stuff Given there is stuff When I do stuff Then stuff happens Scenario: Doing other stuff When stuff happens Then I am stuffed Scenario: Doing different stuff Given stuff Then who gives a stuff """.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") assert(len(feature.scenarios) == 3) eq_(feature.scenarios[0].name, 'Doing stuff') self.compare_steps(feature.scenarios[0].steps, [ ('given', 'Given', 'there is stuff', None, None), ('when', 'When', 'I do stuff', None, None), ('then', 'Then', 'stuff happens', None, None), ]) eq_(feature.scenarios[1].name, 'Doing other stuff') self.compare_steps(feature.scenarios[1].steps, [ ('when', 'When', 'stuff happens', None, None), ('then', 'Then', 'I am stuffed', None, None), ]) eq_(feature.scenarios[2].name, 'Doing different stuff') self.compare_steps(feature.scenarios[2].steps, [ ('given', 'Given', 'stuff', None, None), ('then', 'Then', 'who gives a stuff', None, None), ]) def test_parses_feature_with_multiple_scenarios_with_tags(self): doc = u""" Feature: Stuff Scenario: Doing stuff Given there is stuff When I do stuff Then stuff happens @one_tag Scenario: Doing other stuff When stuff happens Then I am stuffed @lots @of @tags Scenario: Doing different stuff Given stuff Then who gives a stuff """.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") assert(len(feature.scenarios) == 3) eq_(feature.scenarios[0].name, 'Doing stuff') self.compare_steps(feature.scenarios[0].steps, [ ('given', 'Given', 'there is stuff', None, None), ('when', 'When', 'I do stuff', None, None), ('then', 'Then', 'stuff happens', None, None), ]) eq_(feature.scenarios[1].name, 'Doing other stuff') eq_(feature.scenarios[1].tags, [model.Tag(u'one_tag', 1)]) self.compare_steps(feature.scenarios[1].steps, [ ('when', 'When', 'stuff happens', None, None), ('then', 'Then', 'I am stuffed', None, None), ]) eq_(feature.scenarios[2].name, 'Doing different stuff') eq_(feature.scenarios[2].tags, [model.Tag(n, 1) for n in (u'lots', u'of', u'tags')]) self.compare_steps(feature.scenarios[2].steps, [ ('given', 'Given', 'stuff', None, None), ('then', 'Then', 'who gives a stuff', None, None), ]) def test_parses_feature_with_multiple_scenarios_and_other_bits(self): doc = u""" Feature: Stuff Stuffing Background: Given you're all stuffed Scenario: Doing stuff Given there is stuff When I do stuff Then stuff happens Scenario: Doing other stuff When stuff happens Then I am stuffed Scenario: Doing different stuff Given stuff Then who gives a stuff """.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") eq_(feature.description, ["Stuffing"]) assert(feature.background) self.compare_steps(feature.background.steps, [ ('given', 'Given', "you're all stuffed", None, None) ]) assert(len(feature.scenarios) == 3) eq_(feature.scenarios[0].name, 'Doing stuff') self.compare_steps(feature.scenarios[0].steps, [ ('given', 'Given', 'there is stuff', None, None), ('when', 'When', 'I do stuff', None, None), ('then', 'Then', 'stuff happens', None, None), ]) eq_(feature.scenarios[1].name, 'Doing other stuff') self.compare_steps(feature.scenarios[1].steps, [ ('when', 'When', 'stuff happens', None, None), ('then', 'Then', 'I am stuffed', None, None), ]) eq_(feature.scenarios[2].name, 'Doing different stuff') self.compare_steps(feature.scenarios[2].steps, [ ('given', 'Given', 'stuff', None, None), ('then', 'Then', 'who gives a stuff', None, None), ]) def test_parses_feature_with_a_scenario_with_and_and_but(self): doc = u""" Feature: Stuff Scenario: Doing stuff Given there is stuff And some other stuff When I do stuff Then stuff happens But not the bad stuff """.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") assert(len(feature.scenarios) == 1) eq_(feature.scenarios[0].name, 'Doing stuff') self.compare_steps(feature.scenarios[0].steps, [ ('given', 'Given', 'there is stuff', None, None), ('given', 'And', 'some other stuff', None, None), ('when', 'When', 'I do stuff', None, None), ('then', 'Then', 'stuff happens', None, None), ('then', 'But', 'not the bad stuff', None, None), ]) def test_parses_feature_with_a_step_with_a_string_argument(self): doc = u''' Feature: Stuff Scenario: Doing stuff Given there is stuff: """ So Much Stuff """ Then stuff happens '''.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") assert(len(feature.scenarios) == 1) eq_(feature.scenarios[0].name, 'Doing stuff') self.compare_steps(feature.scenarios[0].steps, [ ('given', 'Given', 'there is stuff', "So\nMuch\nStuff", None), ('then', 'Then', 'stuff happens', None, None), ]) def test_parses_string_argument_correctly_handle_whitespace(self): doc = u''' Feature: Stuff Scenario: Doing stuff Given there is stuff: """ So Much Stuff Has Indents """ Then stuff happens '''.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") assert(len(feature.scenarios) == 1) eq_(feature.scenarios[0].name, 'Doing stuff') string = "So\n Much\n Stuff\n Has\nIndents" self.compare_steps(feature.scenarios[0].steps, [ ('given', 'Given', 'there is stuff', string, None), ('then', 'Then', 'stuff happens', None, None), ]) def test_parses_feature_with_a_step_with_a_string_with_blank_lines(self): doc = u''' Feature: Stuff Scenario: Doing stuff Given there is stuff: """ So Much Stuff """ Then stuff happens '''.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") assert(len(feature.scenarios) == 1) eq_(feature.scenarios[0].name, 'Doing stuff') self.compare_steps(feature.scenarios[0].steps, [ ('given', 'Given', 'there is stuff', "So\n\nMuch\n\n\nStuff", None), ('then', 'Then', 'stuff happens', None, None), ]) # MORE-JE-ADDED: def test_parses_string_argument_without_stripping_empty_lines(self): # -- ISSUE 44: Parser removes comments in multiline text string. doc = u''' Feature: Multiline Scenario: Multiline Text with Comments Given a multiline argument with: """ """ And a multiline argument with: """ Alpha. Omega. """ Then empty middle lines are not stripped '''.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Multiline") assert(len(feature.scenarios) == 1) eq_(feature.scenarios[0].name, "Multiline Text with Comments") text1 = "" text2 = "Alpha.\n\nOmega." self.compare_steps(feature.scenarios[0].steps, [ ('given', 'Given', 'a multiline argument with', text1, None), ('given', 'And', 'a multiline argument with', text2, None), ('then', 'Then', 'empty middle lines are not stripped', None, None), ]) def test_parses_feature_with_a_step_with_a_string_with_comments(self): doc = u''' Feature: Stuff Scenario: Doing stuff Given there is stuff: """ So Much # Derp """ Then stuff happens '''.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") assert(len(feature.scenarios) == 1) eq_(feature.scenarios[0].name, 'Doing stuff') self.compare_steps(feature.scenarios[0].steps, [ ('given', 'Given', 'there is stuff', "So\nMuch\n# Derp", None), ('then', 'Then', 'stuff happens', None, None), ]) def test_parses_feature_with_a_step_with_a_table_argument(self): doc = u''' Feature: Stuff Scenario: Doing stuff Given we classify stuff: | type of stuff | awesomeness | ridiculousness | | fluffy | large | frequent | | lint | low | high | | green | variable | awkward | Then stuff is in buckets '''.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") assert(len(feature.scenarios) == 1) eq_(feature.scenarios[0].name, 'Doing stuff') table = model.Table( [u'type of stuff', u'awesomeness', u'ridiculousness'], 0, [ [u'fluffy', u'large', u'frequent'], [u'lint', u'low', u'high'], [u'green', u'variable', u'awkward'], ] ) self.compare_steps(feature.scenarios[0].steps, [ ('given', 'Given', 'we classify stuff', None, table), ('then', 'Then', 'stuff is in buckets', None, None), ]) def test_parses_feature_with_table_and_escaped_pipe_in_cell_values(self): doc = u''' Feature: Scenario: Given we have special cell values: | name | value | | alice | one\|two | | bob |\|one | | charly | one\|| | doro | one\|two\|three\|four | '''.lstrip() feature = parser.parse_feature(doc) assert(len(feature.scenarios) == 1) table = model.Table( [u"name", u"value"], 0, [ [u"alice", u"one|two"], [u"bob", u"|one"], [u"charly", u"one|"], [u"doro", u"one|two|three|four"], ] ) self.compare_steps(feature.scenarios[0].steps, [ ('given', 'Given', 'we have special cell values', None, table), ]) def test_parses_feature_with_a_scenario_outline(self): doc = u''' Feature: Stuff Scenario Outline: Doing all sorts of stuff Given we have When we do stuff Then we have Examples: Some stuff | Stuff | Things | | wool | felt | | cotton | thread | | wood | paper | | explosives | hilarity | '''.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") assert(len(feature.scenarios) == 1) eq_(feature.scenarios[0].name, 'Doing all sorts of stuff') table = model.Table( [u'Stuff', u'Things'], 0, [ [u'wool', u'felt'], [u'cotton', u'thread'], [u'wood', u'paper'], [u'explosives', u'hilarity'], ] ) eq_(feature.scenarios[0].examples[0].name, 'Some stuff') eq_(feature.scenarios[0].examples[0].table, table) self.compare_steps(feature.scenarios[0].steps, [ ('given', 'Given', 'we have ', None, None), ('when', 'When', 'we do stuff', None, None), ('then', 'Then', 'we have ', None, None), ]) def test_parses_feature_with_a_scenario_outline_with_multiple_examples(self): doc = u''' Feature: Stuff Scenario Outline: Doing all sorts of stuff Given we have When we do stuff Then we have Examples: Some stuff | Stuff | Things | | wool | felt | | cotton | thread | Examples: Some other stuff | Stuff | Things | | wood | paper | | explosives | hilarity | '''.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") assert(len(feature.scenarios) == 1) eq_(feature.scenarios[0].name, 'Doing all sorts of stuff') self.compare_steps(feature.scenarios[0].steps, [ ('given', 'Given', 'we have ', None, None), ('when', 'When', 'we do stuff', None, None), ('then', 'Then', 'we have ', None, None), ]) table = model.Table( [u'Stuff', u'Things'], 0, [ [u'wool', u'felt'], [u'cotton', u'thread'], ] ) eq_(feature.scenarios[0].examples[0].name, 'Some stuff') eq_(feature.scenarios[0].examples[0].table, table) table = model.Table( [u'Stuff', u'Things'], 0, [ [u'wood', u'paper'], [u'explosives', u'hilarity'], ] ) eq_(feature.scenarios[0].examples[1].name, 'Some other stuff') eq_(feature.scenarios[0].examples[1].table, table) def test_parses_feature_with_a_scenario_outline_with_tags(self): doc = u''' Feature: Stuff @stuff @derp Scenario Outline: Doing all sorts of stuff Given we have When we do stuff Then we have Examples: Some stuff | Stuff | Things | | wool | felt | | cotton | thread | | wood | paper | | explosives | hilarity | '''.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") assert(len(feature.scenarios) == 1) eq_(feature.scenarios[0].name, 'Doing all sorts of stuff') eq_(feature.scenarios[0].tags, [model.Tag(u'stuff', 1), model.Tag(u'derp', 1)]) self.compare_steps(feature.scenarios[0].steps, [ ('given', 'Given', 'we have ', None, None), ('when', 'When', 'we do stuff', None, None), ('then', 'Then', 'we have ', None, None), ]) table = model.Table( [u'Stuff', u'Things'], 0, [ [u'wool', u'felt'], [u'cotton', u'thread'], [u'wood', u'paper'], [u'explosives', u'hilarity'], ] ) eq_(feature.scenarios[0].examples[0].name, 'Some stuff') eq_(feature.scenarios[0].examples[0].table, table) def test_parses_scenario_outline_with_tagged_examples1(self): # -- CASE: Examples with 1 tag-line (= 1 tag) doc = u''' Feature: Alice @foo Scenario Outline: Bob Given we have @bar Examples: Charly | Stuff | Things | | wool | felt | | cotton | thread | '''.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Alice") assert(len(feature.scenarios) == 1) scenario_outline = feature.scenarios[0] eq_(scenario_outline.name, "Bob") eq_(scenario_outline.tags, [model.Tag(u"foo", 1)]) self.compare_steps(scenario_outline.steps, [ ("given", "Given", "we have ", None, None), ]) table = model.Table( [u"Stuff", u"Things"], 0, [ [u"wool", u"felt"], [u"cotton", u"thread"], ] ) eq_(scenario_outline.examples[0].name, "Charly") eq_(scenario_outline.examples[0].table, table) eq_(scenario_outline.examples[0].tags, [model.Tag(u"bar", 1)]) # -- ScenarioOutline.scenarios: # Inherit tags from ScenarioOutline and Examples element. eq_(len(scenario_outline.scenarios), 2) expected_tags = [model.Tag(u"foo", 1), model.Tag(u"bar", 1)] eq_(set(scenario_outline.scenarios[0].tags), set(expected_tags)) eq_(set(scenario_outline.scenarios[1].tags), set(expected_tags)) def test_parses_scenario_outline_with_tagged_examples2(self): # -- CASE: Examples with multiple tag-lines (= 2 tag-lines) doc = u''' Feature: Alice @foo Scenario Outline: Bob Given we have @bar @baz Examples: Charly | Stuff | Things | | wool | felt | | cotton | thread | '''.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Alice") assert(len(feature.scenarios) == 1) scenario_outline = feature.scenarios[0] eq_(scenario_outline.name, "Bob") eq_(scenario_outline.tags, [model.Tag(u"foo", 1)]) self.compare_steps(scenario_outline.steps, [ ("given", "Given", "we have ", None, None), ]) table = model.Table( [u"Stuff", u"Things"], 0, [ [u"wool", u"felt"], [u"cotton", u"thread"], ] ) eq_(scenario_outline.examples[0].name, "Charly") eq_(scenario_outline.examples[0].table, table) expected_tags = [model.Tag(u"bar", 1), model.Tag(u"baz", 1)] eq_(scenario_outline.examples[0].tags, expected_tags) # -- ScenarioOutline.scenarios: # Inherit tags from ScenarioOutline and Examples element. eq_(len(scenario_outline.scenarios), 2) expected_tags = [ model.Tag(u"foo", 1), model.Tag(u"bar", 1), model.Tag(u"baz", 1) ] eq_(set(scenario_outline.scenarios[0].tags), set(expected_tags)) eq_(set(scenario_outline.scenarios[1].tags), set(expected_tags)) def test_parses_feature_with_the_lot(self): doc = u''' # This one's got comments too. @derp Feature: Stuff In order to test my parser As a test runner I want to run tests # A møøse once bit my sister Background: Given this is a test @fred Scenario: Testing stuff Given we are testing And this is only a test But this is an important test When we test with a multiline string: """ Yarr, my hovercraft be full of stuff. Also, I be feelin' this pirate schtick be a mite overdone, me hearties. Also: rum. """ Then we want it to work #These comments are everywhere man Scenario Outline: Gosh this is long Given this is Then we want it to be But not Examples: Initial | length | width | height | # I don't know why this one is here | 1 | 2 | 3 | | 4 | 5 | 6 | Examples: Subsequent | length | width | height | | 7 | 8 | 9 | Scenario: This one doesn't have a tag Given we don't have a tag Then we don't really mind @stuff @derp Scenario Outline: Doing all sorts of stuff Given we have When we do stuff with a table: | a | b | c | d | e | | 1 | 2 | 3 | 4 | 5 | # I can see a comment line from here | 6 | 7 | 8 | 9 | 10 | Then we have Examples: Some stuff | Stuff | Things | | wool | felt | | cotton | thread | | wood | paper | | explosives | hilarity | '''.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Stuff") eq_(feature.tags, [model.Tag(u'derp', 1)]) eq_(feature.description, ['In order to test my parser', 'As a test runner', 'I want to run tests']) assert(feature.background) self.compare_steps(feature.background.steps, [ ('given', 'Given', 'this is a test', None, None) ]) assert(len(feature.scenarios) == 4) eq_(feature.scenarios[0].name, 'Testing stuff') eq_(feature.scenarios[0].tags, [model.Tag(u'fred', 1)]) string = '\n'.join([ 'Yarr, my hovercraft be full of stuff.', "Also, I be feelin' this pirate schtick be a mite overdone, " + \ "me hearties.", ' Also: rum.' ]) self.compare_steps(feature.scenarios[0].steps, [ ('given', 'Given', 'we are testing', None, None), ('given', 'And', 'this is only a test', None, None), ('given', 'But', 'this is an important test', None, None), ('when', 'When', 'we test with a multiline string', string, None), ('then', 'Then', 'we want it to work', None, None), ]) eq_(feature.scenarios[1].name, 'Gosh this is long') eq_(feature.scenarios[1].tags, []) table = model.Table( [u'length', u'width', u'height'], 0, [ [u'1', u'2', u'3'], [u'4', u'5', u'6'], ] ) eq_(feature.scenarios[1].examples[0].name, 'Initial') eq_(feature.scenarios[1].examples[0].table, table) table = model.Table( [u'length', u'width', u'height'], 0, [ [u'7', u'8', u'9'], ] ) eq_(feature.scenarios[1].examples[1].name, 'Subsequent') eq_(feature.scenarios[1].examples[1].table, table) self.compare_steps(feature.scenarios[1].steps, [ ('given', 'Given', 'this is ', None, None), ('then', 'Then', 'we want it to be ', None, None), ('then', 'But', 'not ', None, None), ]) eq_(feature.scenarios[2].name, "This one doesn't have a tag") eq_(feature.scenarios[2].tags, []) self.compare_steps(feature.scenarios[2].steps, [ ('given', 'Given', "we don't have a tag", None, None), ('then', 'Then', "we don't really mind", None, None), ]) table = model.Table( [u'Stuff', u'Things'], 0, [ [u'wool', u'felt'], [u'cotton', u'thread'], [u'wood', u'paper'], [u'explosives', u'hilarity'], ] ) eq_(feature.scenarios[3].name, 'Doing all sorts of stuff') eq_(feature.scenarios[3].tags, [model.Tag(u'stuff', 1), model.Tag(u'derp', 1)]) eq_(feature.scenarios[3].examples[0].name, 'Some stuff') eq_(feature.scenarios[3].examples[0].table, table) table = model.Table( [u'a', u'b', u'c', u'd', u'e'], 0, [ [u'1', u'2', u'3', u'4', u'5'], [u'6', u'7', u'8', u'9', u'10'], ] ) self.compare_steps(feature.scenarios[3].steps, [ ('given', 'Given', 'we have ', None, None), ('when', 'When', 'we do stuff with a table', None, table), ('then', 'Then', 'we have ', None, None), ]) def test_fails_to_parse_when_and_is_out_of_order(self): doc = u""" Feature: Stuff Scenario: Failing at stuff And we should fail """.lstrip() assert_raises(parser.ParserError, parser.parse_feature, doc) def test_fails_to_parse_when_but_is_out_of_order(self): doc = u""" Feature: Stuff Scenario: Failing at stuff But we shall fail """.lstrip() assert_raises(parser.ParserError, parser.parse_feature, doc) def test_fails_to_parse_when_examples_is_in_the_wrong_place(self): doc = u""" Feature: Stuff Scenario: Failing at stuff But we shall fail Examples: Failure | Fail | Wheel| """.lstrip() assert_raises(parser.ParserError, parser.parse_feature, doc) class TestForeign(Common): def test_first_line_comment_sets_language(self): doc = u""" # language: fr Fonctionnalit\xe9: testing stuff Oh my god, it's full of stuff... """ feature = parser.parse_feature(doc) eq_(feature.name, "testing stuff") eq_(feature.description, ["Oh my god, it's full of stuff..."]) def test_multiple_language_comments(self): # -- LAST LANGUAGE is used. doc = u""" # language: en # language: fr Fonctionnalit\xe9: testing stuff Oh my god, it's full of stuff... """ feature = parser.parse_feature(doc) eq_(feature.name, "testing stuff") eq_(feature.description, ["Oh my god, it's full of stuff..."]) def test_language_comment_wins_over_commandline(self): doc = u""" # language: fr Fonctionnalit\xe9: testing stuff Oh my god, it's full of stuff... """ feature = parser.parse_feature(doc, language="de") eq_(feature.name, "testing stuff") eq_(feature.description, ["Oh my god, it's full of stuff..."]) def test_whitespace_before_first_line_comment_still_sets_language(self): doc = u""" # language: cs Po\u017eadavek: testing stuff Oh my god, it's full of stuff... """ feature = parser.parse_feature(doc) eq_(feature.name, "testing stuff") eq_(feature.description, ["Oh my god, it's full of stuff..."]) def test_anything_before_language_comment_makes_it_not_count(self): doc = u""" @wombles # language: cy-GB Arwedd: testing stuff Oh my god, it's full of stuff... """ assert_raises(parser.ParserError, parser.parse_feature, doc) def test_defaults_to_DEFAULT_LANGUAGE(self): feature_kwd = i18n.languages[parser.DEFAULT_LANGUAGE]['feature'][0] doc = u""" @wombles # language: cs %s: testing stuff Oh my god, it's full of stuff... """ % feature_kwd feature = parser.parse_feature(doc) eq_(feature.name, "testing stuff") eq_(feature.description, ["Oh my god, it's full of stuff..."]) def test_whitespace_in_the_language_comment_is_flexible_1(self): doc = u""" #language:da Egenskab: testing stuff Oh my god, it's full of stuff... """ feature = parser.parse_feature(doc) eq_(feature.name, "testing stuff") eq_(feature.description, ["Oh my god, it's full of stuff..."]) def test_whitespace_in_the_language_comment_is_flexible_2(self): doc = u""" # language:de Funktionalit\xe4t: testing stuff Oh my god, it's full of stuff... """ feature = parser.parse_feature(doc) eq_(feature.name, "testing stuff") eq_(feature.description, ["Oh my god, it's full of stuff..."]) def test_whitespace_in_the_language_comment_is_flexible_3(self): doc = u""" #language: en-lol OH HAI: testing stuff Oh my god, it's full of stuff... """ feature = parser.parse_feature(doc) eq_(feature.name, "testing stuff") eq_(feature.description, ["Oh my god, it's full of stuff..."]) def test_whitespace_in_the_language_comment_is_flexible_4(self): doc = u""" # language: lv F\u012b\u010da: testing stuff Oh my god, it's full of stuff... """ feature = parser.parse_feature(doc) eq_(feature.name, "testing stuff") eq_(feature.description, ["Oh my god, it's full of stuff..."]) def test_parses_french(self): doc = u""" Fonctionnalit\xe9: testing stuff Oh my god, it's full of stuff... Contexte: Soit I found some stuff Sc\xe9nario: test stuff Soit I am testing stuff Alors it should work Sc\xe9nario: test more stuff Soit I am testing stuff Alors it will work """.lstrip() feature = parser.parse_feature(doc, 'fr') eq_(feature.name, "testing stuff") eq_(feature.description, ["Oh my god, it's full of stuff..."]) assert(feature.background) self.compare_steps(feature.background.steps, [ ('given', 'Soit', 'I found some stuff', None, None), ]) assert(len(feature.scenarios) == 2) eq_(feature.scenarios[0].name, 'test stuff') self.compare_steps(feature.scenarios[0].steps, [ ('given', 'Soit', 'I am testing stuff', None, None), ('then', 'Alors', 'it should work', None, None), ]) def test_parses_french_multi_word(self): doc = u""" Fonctionnalit\xe9: testing stuff Oh my god, it's full of stuff... Sc\xe9nario: test stuff Etant donn\xe9 I am testing stuff Alors it should work """.lstrip() feature = parser.parse_feature(doc, 'fr') eq_(feature.name, "testing stuff") eq_(feature.description, ["Oh my god, it's full of stuff..."]) assert(len(feature.scenarios) == 1) eq_(feature.scenarios[0].name, 'test stuff') self.compare_steps(feature.scenarios[0].steps, [ ('given', u'Etant donn\xe9', 'I am testing stuff', None, None), ('then', 'Alors', 'it should work', None, None), ]) test_parses_french_multi_word.go = 1 def test_properly_handles_whitespace_on_keywords_that_do_not_want_it(self): doc = u""" # language: zh-TW \u529f\u80fd: I have no idea what I'm saying \u5834\u666f: No clue whatsoever \u5047\u8a2dI've got no idea \u7576I say things \u800c\u4e14People don't understand \u90a3\u9ebcPeople should laugh \u4f46\u662fI should take it well """ feature = parser.parse_feature(doc) eq_(feature.name, "I have no idea what I'm saying") eq_(len(feature.scenarios), 1) eq_(feature.scenarios[0].name, 'No clue whatsoever') self.compare_steps(feature.scenarios[0].steps, [ ('given', u'\u5047\u8a2d', "I've got no idea", None, None), ('when', u'\u7576', 'I say things', None, None), ('when', u'\u800c\u4e14', "People don't understand", None, None), ('then', u'\u90a3\u9ebc', "People should laugh", None, None), ('then', u'\u4f46\u662f', "I should take it well", None, None), ]) class TestParser4ScenarioDescription(Common): def test_parse_scenario_description(self): doc = u''' Feature: Scenario Description Scenario: With scenario description First line of scenario description. Second line of scenario description. Third line of scenario description (after an empty line). Given we have stuff When we do stuff Then we have things '''.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Scenario Description") assert(len(feature.scenarios) == 1) eq_(feature.scenarios[0].name, "With scenario description") eq_(feature.scenarios[0].tags, []) eq_(feature.scenarios[0].description, [ "First line of scenario description.", "Second line of scenario description.", "Third line of scenario description (after an empty line).", ]) self.compare_steps(feature.scenarios[0].steps, [ ('given', 'Given', 'we have stuff', None, None), ('when', 'When', 'we do stuff', None, None), ('then', 'Then', 'we have things', None, None), ]) def test_parse_scenario_with_description_but_without_steps(self): doc = u''' Feature: Scenario Description Scenario: With description but without steps First line of scenario description. Second line of scenario description. Scenario: Another one Given we have stuff When we do stuff Then we have things '''.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Scenario Description") assert(len(feature.scenarios) == 2) eq_(feature.scenarios[0].name, "With description but without steps") eq_(feature.scenarios[0].tags, []) eq_(feature.scenarios[0].description, [ "First line of scenario description.", "Second line of scenario description.", ]) eq_(feature.scenarios[0].steps, []) eq_(feature.scenarios[1].name, "Another one") eq_(feature.scenarios[1].tags, []) eq_(feature.scenarios[1].description, []) self.compare_steps(feature.scenarios[1].steps, [ ('given', 'Given', 'we have stuff', None, None), ('when', 'When', 'we do stuff', None, None), ('then', 'Then', 'we have things', None, None), ]) def test_parse_scenario_with_description_but_without_steps_followed_by_scenario_with_tags(self): doc = u''' Feature: Scenario Description Scenario: With description but without steps First line of scenario description. Second line of scenario description. @foo @bar Scenario: Another one Given we have stuff When we do stuff Then we have things '''.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Scenario Description") assert(len(feature.scenarios) == 2) eq_(feature.scenarios[0].name, "With description but without steps") eq_(feature.scenarios[0].tags, []) eq_(feature.scenarios[0].description, [ "First line of scenario description.", "Second line of scenario description.", ]) eq_(feature.scenarios[0].steps, []) eq_(feature.scenarios[1].name, "Another one") eq_(feature.scenarios[1].tags, ["foo", "bar"]) eq_(feature.scenarios[1].description, []) self.compare_steps(feature.scenarios[1].steps, [ ('given', 'Given', 'we have stuff', None, None), ('when', 'When', 'we do stuff', None, None), ('then', 'Then', 'we have things', None, None), ]) def test_parse_two_scenarios_with_description(self): doc = u''' Feature: Scenario Description Scenario: One with description but without steps First line of scenario description. Second line of scenario description. Scenario: Two with description and with steps Another line of scenario description. Given we have stuff When we do stuff Then we have things '''.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Scenario Description") assert(len(feature.scenarios) == 2) eq_(feature.scenarios[0].name, "One with description but without steps") eq_(feature.scenarios[0].tags, []) eq_(feature.scenarios[0].description, [ "First line of scenario description.", "Second line of scenario description.", ]) eq_(feature.scenarios[0].steps, []) eq_(feature.scenarios[1].name, "Two with description and with steps") eq_(feature.scenarios[1].tags, []) eq_(feature.scenarios[1].description, [ "Another line of scenario description.", ]) self.compare_steps(feature.scenarios[1].steps, [ ('given', 'Given', 'we have stuff', None, None), ('when', 'When', 'we do stuff', None, None), ('then', 'Then', 'we have things', None, None), ]) def parse_tags(line): the_parser = parser.Parser() return the_parser.parse_tags(line.strip()) class TestParser4Tags(Common): def test_parse_tags_with_one_tag(self): tags = parse_tags('@one ') eq_(len(tags), 1) eq_(tags[0], "one") def test_parse_tags_with_more_tags(self): tags = parse_tags('@one @two.three-four @xxx') eq_(len(tags), 3) eq_(tags, [model.Tag(name, 1) for name in (u'one', u'two.three-four', u'xxx' )]) def test_parse_tags_with_tag_and_comment(self): tags = parse_tags('@one # @fake-tag-in-comment xxx') eq_(len(tags), 1) eq_(tags[0], "one") def test_parse_tags_with_tags_and_comment(self): tags = parse_tags('@one @two.three-four @xxx # @fake-tag-in-comment xxx') eq_(len(tags), 3) eq_(tags, [model.Tag(name, 1) for name in (u'one', u'two.three-four', u'xxx' )]) @raises(parser.ParserError) def test_parse_tags_with_invalid_tags(self): parse_tags('@one invalid.tag boom') class TestParser4Background(Common): def test_parse_background(self): doc = u''' Feature: Background A feature description line 1. A feature description line 2. Background: One Given we init stuff When we init more stuff Scenario: One Given we have stuff When we do stuff Then we have things '''.lstrip() feature = parser.parse_feature(doc) eq_(feature.name, "Background") eq_(feature.description, [ "A feature description line 1.", "A feature description line 2.", ]) assert feature.background is not None eq_(feature.background.name, "One") self.compare_steps(feature.background.steps, [ ('given', 'Given', 'we init stuff', None, None), ('when', 'When', 'we init more stuff', None, None), ]) assert(len(feature.scenarios) == 1) eq_(feature.scenarios[0].name, "One") eq_(feature.scenarios[0].tags, []) self.compare_steps(feature.scenarios[0].steps, [ ('given', 'Given', 'we have stuff', None, None), ('when', 'When', 'we do stuff', None, None), ('then', 'Then', 'we have things', None, None), ]) def test_parse_background_with_tags_should_fail(self): doc = u''' Feature: Background with tags Expect that a ParserError occurs because Background does not support tags/tagging. @tags_are @not_supported @here Background: One Given we init stuff '''.lstrip() assert_raises(parser.ParserError, parser.parse_feature, doc) def test_parse_two_background_should_fail(self): doc = u''' Feature: Two Backgrounds Expect that a ParserError occurs because at most one Background is supported. Background: One Given we init stuff Background: Two When we init more stuff '''.lstrip() assert_raises(parser.ParserError, parser.parse_feature, doc) def test_parse_background_after_scenario_should_fail(self): doc = u''' Feature: Background after Scenario Expect that a ParserError occurs because Background is only allowed before any Scenario. Scenario: One Given we have stuff Background: Two When we init more stuff '''.lstrip() assert_raises(parser.ParserError, parser.parse_feature, doc) def test_parse_background_after_scenario_outline_should_fail(self): doc = u''' Feature: Background after ScenarioOutline Expect that a ParserError occurs because Background is only allowed before any ScenarioOuline. Scenario Outline: ... Given there is Examples: | name | | Alice | Background: Two When we init more stuff '''.lstrip() assert_raises(parser.ParserError, parser.parse_feature, doc) class TestParser4Steps(Common): """ Tests parser.parse_steps() and parser.Parser.parse_steps() functionality. """ def test_parse_steps_with_simple_steps(self): doc = u''' Given a simple step When I have another simple step And I have another simple step Then every step will be parsed without errors '''.lstrip() steps = parser.parse_steps(doc) eq_(len(steps), 4) # -- EXPECTED STEP DATA: # SCHEMA: step_type, keyword, name, text, table self.compare_steps(steps, [ ("given", "Given", "a simple step", None, None), ("when", "When", "I have another simple step", None, None), ("when", "And", "I have another simple step", None, None), ("then", "Then", "every step will be parsed without errors", None, None), ]) def test_parse_steps_with_multiline_text(self): doc = u''' Given a step with multi-line text: """ Lorem ipsum Ipsum lorem """ When I have a step with multi-line text: """ Ipsum lorem Lorem ipsum """ Then every step will be parsed without errors '''.lstrip() steps = parser.parse_steps(doc) eq_(len(steps), 3) # -- EXPECTED STEP DATA: # SCHEMA: step_type, keyword, name, text, table text1 = "Lorem ipsum\nIpsum lorem" text2 = "Ipsum lorem\nLorem ipsum" self.compare_steps(steps, [ ("given", "Given", "a step with multi-line text", text1, None), ("when", "When", "I have a step with multi-line text", text2, None), ("then", "Then", "every step will be parsed without errors", None, None), ]) def test_parse_steps_when_last_step_has_multiline_text(self): doc = u''' Given a simple step Then the last step has multi-line text: """ Lorem ipsum Ipsum lorem """ '''.lstrip() steps = parser.parse_steps(doc) eq_(len(steps), 2) # -- EXPECTED STEP DATA: # SCHEMA: step_type, keyword, name, text, table text2 = "Lorem ipsum\nIpsum lorem" self.compare_steps(steps, [ ("given", "Given", "a simple step", None, None), ("then", "Then", "the last step has multi-line text", text2, None), ]) def test_parse_steps_with_table(self): doc = u''' Given a step with a table: | Name | Age | | Alice | 12 | | Bob | 23 | When I have a step with a table: | Country | Capital | | France | Paris | | Germany | Berlin | | Spain | Madrid | | USA | Washington | Then every step will be parsed without errors '''.lstrip() steps = parser.parse_steps(doc) eq_(len(steps), 3) # -- EXPECTED STEP DATA: # SCHEMA: step_type, keyword, name, text, table table1 = model.Table([u"Name", u"Age"], 0, [ [ u"Alice", u"12" ], [ u"Bob", u"23" ], ]) table2 = model.Table([u"Country", u"Capital"], 0, [ [ u"France", u"Paris" ], [ u"Germany", u"Berlin" ], [ u"Spain", u"Madrid" ], [ u"USA", u"Washington" ], ]) self.compare_steps(steps, [ ("given", "Given", "a step with a table", None, table1), ("when", "When", "I have a step with a table", None, table2), ("then", "Then", "every step will be parsed without errors", None, None), ]) def test_parse_steps_when_last_step_has_a_table(self): doc = u''' Given a simple step Then the last step has a final table: | Name | City | | Alonso | Barcelona | | Bred | London | '''.lstrip() steps = parser.parse_steps(doc) eq_(len(steps), 2) # -- EXPECTED STEP DATA: # SCHEMA: step_type, keyword, name, text, table table2 = model.Table([u"Name", u"City"], 0, [ [ u"Alonso", u"Barcelona" ], [ u"Bred", u"London" ], ]) self.compare_steps(steps, [ ("given", "Given", "a simple step", None, None), ("then", "Then", "the last step has a final table", None, table2), ]) @raises(parser.ParserError) def test_parse_steps_with_malformed_table(self): doc = u''' Given a step with a malformed table: | Name | City | | Alonso | Barcelona | 2004 | | Bred | London | 2010 | '''.lstrip() steps = parser.parse_steps(doc) behave-1.2.6/test/test_runner.py0000644000076600000240000010633313244555737016763 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- # pylint: disable=too-many-lines, line-too-long from __future__ import absolute_import, print_function, with_statement from collections import defaultdict from platform import python_implementation import os.path import sys import warnings import tempfile import unittest import six from six import StringIO from mock import Mock, patch from nose.tools import * # pylint: disable=wildcard-import, unused-wildcard-import from behave import runner_util from behave.model import Table from behave.step_registry import StepRegistry from behave import parser, runner from behave.configuration import ConfigError from behave.formatter.base import StreamOpener # -- CONVENIENCE-ALIAS: _text = six.text_type class TestContext(unittest.TestCase): # pylint: disable=invalid-name, protected-access, no-self-use def setUp(self): r = Mock() self.config = r.config = Mock() r.config.verbose = False self.context = runner.Context(r) def test_user_mode_shall_restore_behave_mode(self): # -- CASE: No exception is raised. initial_mode = runner.Context.BEHAVE eq_(self.context._mode, initial_mode) with self.context.use_with_user_mode(): eq_(self.context._mode, runner.Context.USER) self.context.thing = "stuff" eq_(self.context._mode, initial_mode) def test_user_mode_shall_restore_behave_mode_if_assert_fails(self): initial_mode = runner.Context.BEHAVE eq_(self.context._mode, initial_mode) try: with self.context.use_with_user_mode(): eq_(self.context._mode, runner.Context.USER) assert False, "XFAIL" except AssertionError: eq_(self.context._mode, initial_mode) def test_user_mode_shall_restore_behave_mode_if_exception_is_raised(self): initial_mode = runner.Context.BEHAVE eq_(self.context._mode, initial_mode) try: with self.context.use_with_user_mode(): eq_(self.context._mode, runner.Context.USER) raise RuntimeError("XFAIL") except RuntimeError: eq_(self.context._mode, initial_mode) def test_use_with_user_mode__shall_restore_initial_mode(self): # -- CASE: No exception is raised. # pylint: disable=protected-access initial_mode = runner.Context.BEHAVE self.context._mode = initial_mode with self.context.use_with_user_mode(): eq_(self.context._mode, runner.Context.USER) self.context.thing = "stuff" eq_(self.context._mode, initial_mode) def test_use_with_user_mode__shall_restore_initial_mode_with_error(self): # -- CASE: Exception is raised. # pylint: disable=protected-access initial_mode = runner.Context.BEHAVE self.context._mode = initial_mode try: with self.context.use_with_user_mode(): eq_(self.context._mode, runner.Context.USER) raise RuntimeError("XFAIL") except RuntimeError: eq_(self.context._mode, initial_mode) def test_use_with_behave_mode__shall_restore_initial_mode(self): # -- CASE: No exception is raised. # pylint: disable=protected-access initial_mode = runner.Context.USER self.context._mode = initial_mode with self.context._use_with_behave_mode(): eq_(self.context._mode, runner.Context.BEHAVE) self.context.thing = "stuff" eq_(self.context._mode, initial_mode) def test_use_with_behave_mode__shall_restore_initial_mode_with_error(self): # -- CASE: Exception is raised. # pylint: disable=protected-access initial_mode = runner.Context.USER self.context._mode = initial_mode try: with self.context._use_with_behave_mode(): eq_(self.context._mode, runner.Context.BEHAVE) raise RuntimeError("XFAIL") except RuntimeError: eq_(self.context._mode, initial_mode) def test_context_contains(self): eq_("thing" in self.context, False) self.context.thing = "stuff" eq_("thing" in self.context, True) self.context._push() eq_("thing" in self.context, True) def test_attribute_set_at_upper_level_visible_at_lower_level(self): self.context.thing = "stuff" self.context._push() eq_(self.context.thing, "stuff") def test_attribute_set_at_lower_level_not_visible_at_upper_level(self): self.context._push() self.context.thing = "stuff" self.context._pop() assert getattr(self.context, "thing", None) is None def test_attributes_set_at_upper_level_visible_at_lower_level(self): self.context.thing = "stuff" self.context._push() eq_(self.context.thing, "stuff") self.context.other_thing = "more stuff" self.context._push() eq_(self.context.thing, "stuff") eq_(self.context.other_thing, "more stuff") self.context.third_thing = "wombats" self.context._push() eq_(self.context.thing, "stuff") eq_(self.context.other_thing, "more stuff") eq_(self.context.third_thing, "wombats") def test_attributes_set_at_lower_level_not_visible_at_upper_level(self): self.context.thing = "stuff" self.context._push() self.context.other_thing = "more stuff" self.context._push() self.context.third_thing = "wombats" eq_(self.context.thing, "stuff") eq_(self.context.other_thing, "more stuff") eq_(self.context.third_thing, "wombats") self.context._pop() eq_(self.context.thing, "stuff") eq_(self.context.other_thing, "more stuff") assert getattr(self.context, "third_thing", None) is None, "%s is not None" % self.context.third_thing self.context._pop() eq_(self.context.thing, "stuff") assert getattr(self.context, "other_thing", None) is None, "%s is not None" % self.context.other_thing assert getattr(self.context, "third_thing", None) is None, "%s is not None" % self.context.third_thing def test_masking_existing_user_attribute_when_verbose_causes_warning(self): warns = [] def catch_warning(*args, **kwargs): warns.append(args[0]) old_showwarning = warnings.showwarning warnings.showwarning = catch_warning # pylint: disable=protected-access self.config.verbose = True with self.context.use_with_user_mode(): self.context.thing = "stuff" self.context._push() self.context.thing = "other stuff" warnings.showwarning = old_showwarning print(repr(warns)) assert warns, "warns is empty!" warning = warns[0] assert isinstance(warning, runner.ContextMaskWarning), "warning is not a ContextMaskWarning" info = warning.args[0] assert info.startswith("user code"), "%r doesn't start with 'user code'" % info assert "'thing'" in info, "%r not in %r" % ("'thing'", info) assert "tutorial" in info, '"tutorial" not in %r' % (info, ) def test_masking_existing_user_attribute_when_not_verbose_causes_no_warning(self): warns = [] def catch_warning(*args, **kwargs): warns.append(args[0]) old_showwarning = warnings.showwarning warnings.showwarning = catch_warning # explicit # pylint: disable=protected-access self.config.verbose = False with self.context.use_with_user_mode(): self.context.thing = "stuff" self.context._push() self.context.thing = "other stuff" warnings.showwarning = old_showwarning assert not warns def test_behave_masking_user_attribute_causes_warning(self): warns = [] def catch_warning(*args, **kwargs): warns.append(args[0]) old_showwarning = warnings.showwarning warnings.showwarning = catch_warning with self.context.use_with_user_mode(): self.context.thing = "stuff" # pylint: disable=protected-access self.context._push() self.context.thing = "other stuff" warnings.showwarning = old_showwarning print(repr(warns)) assert warns, "OOPS: warns is empty, but expected non-empty" warning = warns[0] assert isinstance(warning, runner.ContextMaskWarning), "warning is not a ContextMaskWarning" info = warning.args[0] assert info.startswith("behave runner"), "%r doesn't start with 'behave runner'" % info assert "'thing'" in info, "%r not in %r" % ("'thing'", info) filename = __file__.rsplit(".", 1)[0] if python_implementation() == "Jython": filename = filename.replace("$py", ".py") assert filename in info, "%r not in %r" % (filename, info) def test_setting_root_attribute_that_masks_existing_causes_warning(self): # pylint: disable=protected-access warns = [] def catch_warning(*args, **kwargs): warns.append(args[0]) old_showwarning = warnings.showwarning warnings.showwarning = catch_warning with self.context.use_with_user_mode(): self.context._push() self.context.thing = "teak" self.context._set_root_attribute("thing", "oak") warnings.showwarning = old_showwarning print(repr(warns)) assert warns warning = warns[0] assert isinstance(warning, runner.ContextMaskWarning) info = warning.args[0] assert info.startswith("behave runner"), "%r doesn't start with 'behave runner'" % info assert "'thing'" in info, "%r not in %r" % ("'thing'", info) filename = __file__.rsplit(".", 1)[0] if python_implementation() == "Jython": filename = filename.replace("$py", ".py") assert filename in info, "%r not in %r" % (filename, info) def test_context_deletable(self): eq_("thing" in self.context, False) self.context.thing = "stuff" eq_("thing" in self.context, True) del self.context.thing eq_("thing" in self.context, False) @raises(AttributeError) def test_context_deletable_raises(self): # pylint: disable=protected-access eq_("thing" in self.context, False) self.context.thing = "stuff" eq_("thing" in self.context, True) self.context._push() eq_("thing" in self.context, True) del self.context.thing class ExampleSteps(object): text = None table = None @staticmethod def step_passes(context): # pylint: disable=unused-argument pass @staticmethod def step_fails(context): # pylint: disable=unused-argument assert False, "XFAIL" @classmethod def step_with_text(cls, context): assert context.text is not None, "REQUIRE: multi-line text" cls.text = context.text @classmethod def step_with_table(cls, context): assert context.table, "REQUIRE: table" cls.table = context.table @classmethod def register_steps_with(cls, step_registry): # pylint: disable=bad-whitespace step_definitions = [ ("step", "a step passes", cls.step_passes), ("step", "a step fails", cls.step_fails), ("step", "a step with text", cls.step_with_text), ("step", "a step with a table", cls.step_with_table), ] for keyword, pattern, func in step_definitions: step_registry.add_step_definition(keyword, pattern, func) class TestContext_ExecuteSteps(unittest.TestCase): """ Test the behave.runner.Context.execute_steps() functionality. """ # pylint: disable=invalid-name, no-self-use step_registry = None def setUp(self): if not self.step_registry: # -- SETUP ONCE: self.step_registry = StepRegistry() ExampleSteps.register_steps_with(self.step_registry) ExampleSteps.text = None ExampleSteps.table = None runner_ = Mock() self.config = runner_.config = Mock() runner_.config.verbose = False runner_.config.stdout_capture = False runner_.config.stderr_capture = False runner_.config.log_capture = False runner_.step_registry = self.step_registry self.context = runner.Context(runner_) runner_.context = self.context self.context.feature = Mock() self.context.feature.parser = parser.Parser() self.context.runner = runner_ # self.context.text = None # self.context.table = None def test_execute_steps_with_simple_steps(self): doc = u""" Given a step passes Then a step passes """.lstrip() with patch("behave.step_registry.registry", self.step_registry): result = self.context.execute_steps(doc) eq_(result, True) def test_execute_steps_with_failing_step(self): doc = u""" Given a step passes When a step fails Then a step passes """.lstrip() with patch("behave.step_registry.registry", self.step_registry): try: result = self.context.execute_steps(doc) except AssertionError as e: ok_("FAILED SUB-STEP: When a step fails" in _text(e)) def test_execute_steps_with_undefined_step(self): doc = u""" Given a step passes When a step is undefined Then a step passes """.lstrip() with patch("behave.step_registry.registry", self.step_registry): try: result = self.context.execute_steps(doc) except AssertionError as e: ok_("UNDEFINED SUB-STEP: When a step is undefined" in _text(e)) def test_execute_steps_with_text(self): doc = u''' Given a step passes When a step with text: """ Lorem ipsum Ipsum lorem """ Then a step passes '''.lstrip() with patch("behave.step_registry.registry", self.step_registry): result = self.context.execute_steps(doc) expected_text = "Lorem ipsum\nIpsum lorem" eq_(result, True) eq_(expected_text, ExampleSteps.text) def test_execute_steps_with_table(self): doc = u""" Given a step with a table: | Name | Age | | Alice | 12 | | Bob | 23 | Then a step passes """.lstrip() with patch("behave.step_registry.registry", self.step_registry): # pylint: disable=bad-whitespace, bad-continuation result = self.context.execute_steps(doc) expected_table = Table([u"Name", u"Age"], 0, [ [u"Alice", u"12"], [u"Bob", u"23"], ]) eq_(result, True) eq_(expected_table, ExampleSteps.table) def test_context_table_is_restored_after_execute_steps_without_table(self): doc = u""" Given a step passes Then a step passes """.lstrip() with patch("behave.step_registry.registry", self.step_registry): original_table = "" self.context.table = original_table self.context.execute_steps(doc) eq_(self.context.table, original_table) def test_context_table_is_restored_after_execute_steps_with_table(self): doc = u""" Given a step with a table: | Name | Age | | Alice | 12 | | Bob | 23 | Then a step passes """.lstrip() with patch("behave.step_registry.registry", self.step_registry): original_table = "" self.context.table = original_table self.context.execute_steps(doc) eq_(self.context.table, original_table) def test_context_text_is_restored_after_execute_steps_without_text(self): doc = u""" Given a step passes Then a step passes """.lstrip() with patch("behave.step_registry.registry", self.step_registry): original_text = "" self.context.text = original_text self.context.execute_steps(doc) eq_(self.context.text, original_text) def test_context_text_is_restored_after_execute_steps_with_text(self): doc = u''' Given a step passes When a step with text: """ Lorem ipsum Ipsum lorem """ '''.lstrip() with patch("behave.step_registry.registry", self.step_registry): original_text = "" self.context.text = original_text self.context.execute_steps(doc) eq_(self.context.text, original_text) @raises(ValueError) def test_execute_steps_should_fail_when_called_without_feature(self): doc = u""" Given a passes Then a step passes """.lstrip() with patch("behave.step_registry.registry", self.step_registry): self.context.feature = None self.context.execute_steps(doc) def create_mock_config(): config = Mock() config.steps_dir = "steps" config.environment_file = "environment.py" return config class TestRunner(object): # pylint: disable=invalid-name, no-self-use def test_load_hooks_execfiles_hook_file(self): with patch("behave.runner.exec_file") as ef: with patch("os.path.exists") as exists: exists.return_value = True base_dir = "fake/path" hooks_path = os.path.join(base_dir, "environment.py") r = runner.Runner(create_mock_config()) r.base_dir = base_dir r.load_hooks() exists.assert_called_with(hooks_path) ef.assert_called_with(hooks_path, r.hooks) def test_run_hook_runs_a_hook_that_exists(self): config = Mock() r = runner.Runner(config) # XXX r.config = Mock() r.config.stdout_capture = False r.config.stderr_capture = False r.config.dry_run = False r.hooks["before_lunch"] = hook = Mock() args = (runner.Context(Mock()), Mock(), Mock()) r.run_hook("before_lunch", *args) hook.assert_called_with(*args) def test_run_hook_does_not_runs_a_hook_that_exists_if_dry_run(self): r = runner.Runner(None) r.config = Mock() r.config.dry_run = True r.hooks["before_lunch"] = hook = Mock() args = (runner.Context(Mock()), Mock(), Mock()) r.run_hook("before_lunch", *args) assert len(hook.call_args_list) == 0 def test_setup_capture_creates_stringio_for_stdout(self): r = runner.Runner(Mock()) r.config.stdout_capture = True r.config.log_capture = False r.context = Mock() r.setup_capture() assert r.capture_controller.stdout_capture is not None assert isinstance(r.capture_controller.stdout_capture, StringIO) def test_setup_capture_does_not_create_stringio_if_not_wanted(self): r = runner.Runner(Mock()) r.config.stdout_capture = False r.config.stderr_capture = False r.config.log_capture = False r.setup_capture() assert r.capture_controller.stdout_capture is None @patch("behave.capture.LoggingCapture") def test_setup_capture_creates_memory_handler_for_logging(self, handler): r = runner.Runner(Mock()) r.config.stdout_capture = False r.config.log_capture = True r.context = Mock() r.setup_capture() assert r.capture_controller.log_capture is not None handler.assert_called_with(r.config) r.capture_controller.log_capture.inveigle.assert_called_with() def test_setup_capture_does_not_create_memory_handler_if_not_wanted(self): r = runner.Runner(Mock()) r.config.stdout_capture = False r.config.stderr_capture = False r.config.log_capture = False r.setup_capture() assert r.capture_controller.log_capture is None def test_start_stop_capture_switcheroos_sys_stdout(self): old_stdout = sys.stdout sys.stdout = new_stdout = Mock() r = runner.Runner(Mock()) r.config.stdout_capture = True r.config.log_capture = False r.context = Mock() r.setup_capture() r.start_capture() eq_(sys.stdout, r.capture_controller.stdout_capture) r.stop_capture() eq_(sys.stdout, new_stdout) sys.stdout = old_stdout def test_start_stop_capture_leaves_sys_stdout_alone_if_off(self): r = runner.Runner(Mock()) r.config.stdout_capture = False r.config.log_capture = False old_stdout = sys.stdout r.start_capture() eq_(sys.stdout, old_stdout) r.stop_capture() eq_(sys.stdout, old_stdout) def test_teardown_capture_removes_log_tap(self): r = runner.Runner(Mock()) r.config.stdout_capture = False r.config.log_capture = True r.capture_controller.log_capture = Mock() r.teardown_capture() r.capture_controller.log_capture.abandon.assert_called_with() def test_exec_file(self): fn = tempfile.mktemp() with open(fn, "w") as f: f.write("spam = __file__\n") g = {} l = {} runner_util.exec_file(fn, g, l) assert "__file__" in l # pylint: disable=too-many-format-args assert "spam" in l, '"spam" variable not set in locals (%r)' % (g, l) # pylint: enable=too-many-format-args eq_(l["spam"], fn) def test_run_returns_true_if_everything_passed(self): r = runner.Runner(Mock()) r.setup_capture = Mock() r.setup_paths = Mock() r.run_with_paths = Mock() r.run_with_paths.return_value = True assert r.run() def test_run_returns_false_if_anything_failed(self): r = runner.Runner(Mock()) r.setup_capture = Mock() r.setup_paths = Mock() r.run_with_paths = Mock() r.run_with_paths.return_value = False assert not r.run() class TestRunWithPaths(unittest.TestCase): # pylint: disable=invalid-name, no-self-use def setUp(self): self.config = Mock() self.config.reporters = [] self.config.logging_level = None self.config.logging_filter = None self.config.outputs = [Mock(), StreamOpener(stream=sys.stdout)] self.config.format = ["plain", "progress"] self.runner = runner.Runner(self.config) self.load_hooks = self.runner.load_hooks = Mock() self.load_step_definitions = self.runner.load_step_definitions = Mock() self.run_hook = self.runner.run_hook = Mock() self.run_step = self.runner.run_step = Mock() self.feature_locations = self.runner.feature_locations = Mock() self.calculate_summaries = self.runner.calculate_summaries = Mock() self.formatter_class = patch("behave.formatter.pretty.PrettyFormatter") formatter_class = self.formatter_class.start() formatter_class.return_value = self.formatter = Mock() def tearDown(self): self.formatter_class.stop() def test_loads_hooks_and_step_definitions(self): self.feature_locations.return_value = [] self.runner.run_with_paths() assert self.load_hooks.called assert self.load_step_definitions.called def test_runs_before_all_and_after_all_hooks(self): # Make runner.feature_locations() and runner.run_hook() the same mock so # we can make sure things happen in the right order. self.runner.feature_locations = self.run_hook self.runner.feature_locations.return_value = [] self.runner.context = Mock() self.runner.run_with_paths() eq_(self.run_hook.call_args_list, [ ((), {}), (("before_all", self.runner.context), {}), (("after_all", self.runner.context), {}), ]) @patch("behave.parser.parse_file") @patch("os.path.abspath") def test_parses_feature_files_and_appends_to_feature_list(self, abspath, parse_file): feature_locations = ["one", "two", "three"] feature = Mock() feature.tags = [] feature.__iter__ = Mock(return_value=iter([])) feature.run.return_value = False self.runner.feature_locations.return_value = feature_locations abspath.side_effect = lambda x: x.upper() self.config.lang = "fritz" self.config.format = ["plain"] self.config.outputs = [StreamOpener(stream=sys.stdout)] self.config.output.encoding = None self.config.exclude = lambda s: False self.config.junit = False self.config.summary = False parse_file.return_value = feature self.runner.run_with_paths() expected_parse_file_args = \ [((x.upper(),), {"language": "fritz"}) for x in feature_locations] eq_(parse_file.call_args_list, expected_parse_file_args) eq_(self.runner.features, [feature] * 3) class FsMock(object): def __init__(self, *paths): self.base = os.path.abspath(".") self.sep = os.path.sep # This bit of gymnastics is to support Windows. We feed in a bunch of # paths in places using FsMock that assume that POSIX-style paths # work. This is faster than fixing all of those but at some point we # should totally do it properly with os.path.join() and all that. def full_split(path): bits = [] while path: path, bit = os.path.split(path) bits.insert(0, bit) return bits paths = [os.path.join(self.base, *full_split(path)) for path in paths] print(repr(paths)) self.paths = paths self.files = set() self.dirs = defaultdict(list) separators = [sep for sep in (os.path.sep, os.path.altsep) if sep] for path in paths: if path[-1] in separators: self.dirs[path[:-1]] = [] d, p = os.path.split(path[:-1]) while d and p: self.dirs[d].append(p) d, p = os.path.split(d) else: self.files.add(path) d, f = os.path.split(path) self.dirs[d].append(f) self.calls = [] def listdir(self, dir): # pylint: disable=W0622 # W0622 Redefining built-in dir self.calls.append(("listdir", dir)) return self.dirs.get(dir, []) def isfile(self, path): self.calls.append(("isfile", path)) return path in self.files def isdir(self, path): self.calls.append(("isdir", path)) return path in self.dirs def exists(self, path): self.calls.append(("exists", path)) return path in self.dirs or path in self.files def walk(self, path, locations=None): if locations is None: assert path in self.dirs, "%s not in %s" % (path, self.dirs) locations = [] dirnames = [] filenames = [] for e in self.dirs[path]: if os.path.join(path, e) in self.dirs: dirnames.append(e) self.walk(os.path.join(path, e), locations) else: filenames.append(e) locations.append((path, dirnames, filenames)) return locations # utilities that we need # pylint: disable=no-self-use def dirname(self, path, orig=os.path.dirname): return orig(path) def abspath(self, path, orig=os.path.abspath): return orig(path) def join(self, x, y, orig=os.path.join): return orig(x, y) def split(self, path, orig=os.path.split): return orig(path) def splitdrive(self, path, orig=os.path.splitdrive): return orig(path) class TestFeatureDirectory(object): # pylint: disable=invalid-name, no-self-use def test_default_path_no_steps(self): config = create_mock_config() config.paths = [] config.verbose = True r = runner.Runner(config) fs = FsMock() # will look for a "features" directory and not find one with patch("os.path", fs): assert_raises(ConfigError, r.setup_paths) ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls) def test_default_path_no_features(self): config = create_mock_config() config.paths = [] config.verbose = True r = runner.Runner(config) fs = FsMock("features/steps/") with patch("os.path", fs): with patch("os.walk", fs.walk): assert_raises(ConfigError, r.setup_paths) def test_default_path(self): config = create_mock_config() config.paths = [] config.verbose = True r = runner.Runner(config) fs = FsMock("features/steps/", "features/foo.feature") with patch("os.path", fs): with patch("os.walk", fs.walk): with r.path_manager: r.setup_paths() eq_(r.base_dir, os.path.abspath("features")) def test_supplied_feature_file(self): config = create_mock_config() config.paths = ["foo.feature"] config.verbose = True r = runner.Runner(config) r.context = Mock() fs = FsMock("steps/", "foo.feature") with patch("os.path", fs): with patch("os.walk", fs.walk): with r.path_manager: r.setup_paths() ok_(("isdir", os.path.join(fs.base, "steps")) in fs.calls) ok_(("isfile", os.path.join(fs.base, "foo.feature")) in fs.calls) eq_(r.base_dir, fs.base) def test_supplied_feature_file_no_steps(self): config = create_mock_config() config.paths = ["foo.feature"] config.verbose = True r = runner.Runner(config) fs = FsMock("foo.feature") with patch("os.path", fs): with patch("os.walk", fs.walk): with r.path_manager: assert_raises(ConfigError, r.setup_paths) def test_supplied_feature_directory(self): config = create_mock_config() config.paths = ["spam"] config.verbose = True r = runner.Runner(config) fs = FsMock("spam/", "spam/steps/", "spam/foo.feature") with patch("os.path", fs): with patch("os.walk", fs.walk): with r.path_manager: r.setup_paths() ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls) eq_(r.base_dir, os.path.join(fs.base, "spam")) def test_supplied_feature_directory_no_steps(self): config = create_mock_config() config.paths = ["spam"] config.verbose = True r = runner.Runner(config) fs = FsMock("spam/", "spam/foo.feature") with patch("os.path", fs): with patch("os.walk", fs.walk): assert_raises(ConfigError, r.setup_paths) ok_(("isdir", os.path.join(fs.base, "spam", "steps")) in fs.calls) def test_supplied_feature_directory_missing(self): config = create_mock_config() config.paths = ["spam"] config.verbose = True r = runner.Runner(config) fs = FsMock() with patch("os.path", fs): with patch("os.walk", fs.walk): assert_raises(ConfigError, r.setup_paths) class TestFeatureDirectoryLayout2(object): # pylint: disable=invalid-name, no-self-use def test_default_path(self): config = create_mock_config() config.paths = [] config.verbose = True r = runner.Runner(config) fs = FsMock( "features/", "features/steps/", "features/group1/", "features/group1/foo.feature", ) with patch("os.path", fs): with patch("os.walk", fs.walk): with r.path_manager: r.setup_paths() eq_(r.base_dir, os.path.abspath("features")) def test_supplied_root_directory(self): config = create_mock_config() config.paths = ["features"] config.verbose = True r = runner.Runner(config) fs = FsMock( "features/", "features/group1/", "features/group1/foo.feature", "features/steps/", ) with patch("os.path", fs): with patch("os.walk", fs.walk): with r.path_manager: r.setup_paths() ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls) eq_(r.base_dir, os.path.join(fs.base, "features")) def test_supplied_root_directory_no_steps(self): config = create_mock_config() config.paths = ["features"] config.verbose = True r = runner.Runner(config) fs = FsMock( "features/", "features/group1/", "features/group1/foo.feature", ) with patch("os.path", fs): with patch("os.walk", fs.walk): with r.path_manager: assert_raises(ConfigError, r.setup_paths) ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls) eq_(r.base_dir, None) def test_supplied_feature_file(self): config = create_mock_config() config.paths = ["features/group1/foo.feature"] config.verbose = True r = runner.Runner(config) r.context = Mock() fs = FsMock( "features/", "features/group1/", "features/group1/foo.feature", "features/steps/", ) with patch("os.path", fs): with patch("os.walk", fs.walk): with r.path_manager: r.setup_paths() ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls) ok_(("isfile", os.path.join(fs.base, "features", "group1", "foo.feature")) in fs.calls) eq_(r.base_dir, fs.join(fs.base, "features")) def test_supplied_feature_file_no_steps(self): config = create_mock_config() config.paths = ["features/group1/foo.feature"] config.verbose = True r = runner.Runner(config) fs = FsMock( "features/", "features/group1/", "features/group1/foo.feature", ) with patch("os.path", fs): with patch("os.walk", fs.walk): with r.path_manager: assert_raises(ConfigError, r.setup_paths) def test_supplied_feature_directory(self): config = create_mock_config() config.paths = ["features/group1"] config.verbose = True r = runner.Runner(config) fs = FsMock( "features/", "features/group1/", "features/group1/foo.feature", "features/steps/", ) with patch("os.path", fs): with patch("os.walk", fs.walk): with r.path_manager: r.setup_paths() ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls) eq_(r.base_dir, os.path.join(fs.base, "features")) def test_supplied_feature_directory_no_steps(self): config = create_mock_config() config.paths = ["features/group1"] config.verbose = True r = runner.Runner(config) fs = FsMock( "features/", "features/group1/", "features/group1/foo.feature", ) with patch("os.path", fs): with patch("os.walk", fs.walk): assert_raises(ConfigError, r.setup_paths) ok_(("isdir", os.path.join(fs.base, "features", "steps")) in fs.calls) behave-1.2.6/test/test_step_registry.py0000644000076600000240000000635613244555737020361 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- # pylint: disable=unused-wildcard-import from __future__ import absolute_import, with_statement from mock import Mock, patch from nose.tools import * # pylint: disable=wildcard-import from six.moves import range # pylint: disable=redefined-builtin from behave import step_registry class TestStepRegistry(object): # pylint: disable=invalid-name, no-self-use def test_add_step_definition_adds_to_lowercased_keyword(self): registry = step_registry.StepRegistry() # -- MONKEYPATCH-PROBLEM: # with patch('behave.matchers.get_matcher') as get_matcher: with patch('behave.step_registry.get_matcher') as get_matcher: func = lambda x: -x pattern = 'just a test string' magic_object = object() get_matcher.return_value = magic_object for step_type in list(registry.steps.keys()): l = [] registry.steps[step_type] = l registry.add_step_definition(step_type.upper(), pattern, func) get_matcher.assert_called_with(func, pattern) eq_(l, [magic_object]) def test_find_match_with_specific_step_type_also_searches_generic(self): registry = step_registry.StepRegistry() given_mock = Mock() given_mock.match.return_value = None step_mock = Mock() step_mock.match.return_value = None registry.steps['given'].append(given_mock) registry.steps['step'].append(step_mock) step = Mock() step.step_type = 'given' step.name = 'just a test step' assert registry.find_match(step) is None given_mock.match.assert_called_with(step.name) step_mock.match.assert_called_with(step.name) def test_find_match_with_no_match_returns_none(self): registry = step_registry.StepRegistry() step_defs = [Mock() for x in range(0, 10)] for mock in step_defs: mock.match.return_value = None registry.steps['when'] = step_defs step = Mock() step.step_type = 'when' step.name = 'just a test step' assert registry.find_match(step) is None def test_find_match_with_a_match_returns_match(self): registry = step_registry.StepRegistry() step_defs = [Mock() for x in range(0, 10)] for mock in step_defs: mock.match.return_value = None magic_object = object() step_defs[5].match.return_value = magic_object registry.steps['then'] = step_defs step = Mock() step.step_type = 'then' step.name = 'just a test step' assert registry.find_match(step) is magic_object for mock in step_defs[6:]: eq_(mock.match.call_count, 0) # pylint: disable=line-too-long @patch.object(step_registry.registry, 'add_step_definition') def test_make_step_decorator_ends_up_adding_a_step_definition(self, add_step_definition): step_type = object() step_pattern = object() func = object() decorator = step_registry.registry.make_decorator(step_type) wrapper = decorator(step_pattern) assert wrapper(func) is func add_step_definition.assert_called_with(step_type, step_pattern, func) behave-1.2.6/test/test_tag_expression.py0000644000076600000240000004165413244555737020510 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import from behave.tag_expression import TagExpression from nose import tools import unittest # ---------------------------------------------------------------------------- # BASIC TESTS: 0..1 tags, not @tag # ---------------------------------------------------------------------------- class TestTagExpressionNoTags(unittest.TestCase): def setUp(self): self.e = TagExpression([]) def test_should_match_empty_tags(self): assert self.e.check([]) def test_should_match_foo(self): assert self.e.check(['foo']) class TestTagExpressionFoo(unittest.TestCase): def setUp(self): self.e = TagExpression(['foo']) def test_should_not_match_no_tags(self): assert not self.e.check([]) def test_should_match_foo(self): assert self.e.check(['foo']) def test_should_not_match_bar(self): assert not self.e.check(['bar']) class TestTagExpressionNotFoo(unittest.TestCase): def setUp(self): self.e = TagExpression(['-foo']) def test_should_match_no_tags(self): assert self.e.check([]) def test_should_not_match_foo(self): assert not self.e.check(['foo']) def test_should_match_bar(self): assert self.e.check(['bar']) # ---------------------------------------------------------------------------- # LOGICAL-AND TESTS: With @foo, @bar (2 tags) # ---------------------------------------------------------------------------- class TestTagExpressionFooAndBar(unittest.TestCase): # -- LOGIC: @foo and @bar def setUp(self): self.e = TagExpression(['foo', 'bar']) def test_should_not_match_no_tags(self): assert not self.e.check([]) def test_should_not_match_foo(self): assert not self.e.check(['foo']) def test_should_not_match_bar(self): assert not self.e.check(['bar']) def test_should_not_match_other(self): assert not self.e.check(['other']) def test_should_match_foo_bar(self): assert self.e.check(['foo', 'bar']) assert self.e.check(['bar', 'foo']) def test_should_not_match_foo_other(self): assert not self.e.check(['foo', 'other']) assert not self.e.check(['other', 'foo']) def test_should_not_match_bar_other(self): assert not self.e.check(['bar', 'other']) assert not self.e.check(['other', 'bar']) def test_should_not_match_zap_other(self): assert not self.e.check(['zap', 'other']) assert not self.e.check(['other', 'zap']) def test_should_match_foo_bar_other(self): assert self.e.check(['foo', 'bar', 'other']) assert self.e.check(['bar', 'other', 'foo']) assert self.e.check(['other', 'bar', 'foo']) def test_should_not_match_foo_zap_other(self): assert not self.e.check(['foo', 'zap', 'other']) assert not self.e.check(['other', 'zap', 'foo']) def test_should_not_match_bar_zap_other(self): assert not self.e.check(['bar', 'zap', 'other']) assert not self.e.check(['other', 'bar', 'zap']) def test_should_not_match_zap_baz_other(self): assert not self.e.check(['zap', 'baz', 'other']) assert not self.e.check(['baz', 'other', 'baz']) assert not self.e.check(['other', 'baz', 'zap']) class TestTagExpressionFooAndNotBar(unittest.TestCase): # -- LOGIC: @foo and not @bar def setUp(self): self.e = TagExpression(['foo', '-bar']) def test_should_not_match_no_tags(self): assert not self.e.check([]) def test_should_match_foo(self): assert self.e.check(['foo']) def test_should_not_match_bar(self): assert not self.e.check(['bar']) def test_should_not_match_other(self): assert not self.e.check(['other']) def test_should_not_match_foo_bar(self): assert not self.e.check(['foo', 'bar']) assert not self.e.check(['bar', 'foo']) def test_should_match_foo_other(self): assert self.e.check(['foo', 'other']) assert self.e.check(['other', 'foo']) def test_should_not_match_bar_other(self): assert not self.e.check(['bar', 'other']) assert not self.e.check(['other', 'bar']) def test_should_not_match_zap_other(self): assert not self.e.check(['bar', 'other']) assert not self.e.check(['other', 'bar']) def test_should_not_match_foo_bar_other(self): assert not self.e.check(['foo', 'bar', 'other']) assert not self.e.check(['bar', 'other', 'foo']) assert not self.e.check(['other', 'bar', 'foo']) def test_should_match_foo_zap_other(self): assert self.e.check(['foo', 'zap', 'other']) assert self.e.check(['other', 'zap', 'foo']) def test_should_not_match_bar_zap_other(self): assert not self.e.check(['bar', 'zap', 'other']) assert not self.e.check(['other', 'bar', 'zap']) def test_should_not_match_zap_baz_other(self): assert not self.e.check(['zap', 'baz', 'other']) assert not self.e.check(['baz', 'other', 'baz']) assert not self.e.check(['other', 'baz', 'zap']) class TestTagExpressionNotBarAndFoo(TestTagExpressionFooAndNotBar): # -- REUSE: Test suite due to symmetry in reversed expression # LOGIC: not @bar and @foo == @foo and not @bar def setUp(self): self.e = TagExpression(['-bar', 'foo']) class TestTagExpressionNotFooAndNotBar(unittest.TestCase): # -- LOGIC: not @bar and not @foo def setUp(self): self.e = TagExpression(['-foo', '-bar']) def test_should_match_no_tags(self): assert self.e.check([]) def test_should_not_match_foo(self): assert not self.e.check(['foo']) def test_should_not_match_bar(self): assert not self.e.check(['bar']) def test_should_match_other(self): assert self.e.check(['other']) def test_should_not_match_foo_bar(self): assert not self.e.check(['foo', 'bar']) assert not self.e.check(['bar', 'foo']) def test_should_not_match_foo_other(self): assert not self.e.check(['foo', 'other']) assert not self.e.check(['other', 'foo']) def test_should_not_match_bar_other(self): assert not self.e.check(['bar', 'other']) assert not self.e.check(['other', 'bar']) def test_should_match_zap_other(self): assert self.e.check(['zap', 'other']) assert self.e.check(['other', 'zap']) def test_should_not_match_foo_bar_other(self): assert not self.e.check(['foo', 'bar', 'other']) assert not self.e.check(['bar', 'other', 'foo']) assert not self.e.check(['other', 'bar', 'foo']) def test_should_not_match_foo_zap_other(self): assert not self.e.check(['foo', 'zap', 'other']) assert not self.e.check(['other', 'zap', 'foo']) def test_should_not_match_bar_zap_other(self): assert not self.e.check(['bar', 'zap', 'other']) assert not self.e.check(['other', 'bar', 'zap']) def test_should_match_zap_baz_other(self): assert self.e.check(['zap', 'baz', 'other']) assert self.e.check(['baz', 'other', 'baz']) assert self.e.check(['other', 'baz', 'zap']) class TestTagExpressionNotBarAndNotFoo(TestTagExpressionNotFooAndNotBar): # -- REUSE: Test suite due to symmetry in reversed expression # LOGIC: not @bar and not @foo == not @foo and not @bar def setUp(self): self.e = TagExpression(['-bar', '-foo']) # ---------------------------------------------------------------------------- # LOGICAL-OR TESTS: With @foo, @bar (2 tags) # ---------------------------------------------------------------------------- class TestTagExpressionFooOrBar(unittest.TestCase): def setUp(self): self.e = TagExpression(['foo,bar']) def test_should_not_match_no_tags(self): assert not self.e.check([]) def test_should_match_foo(self): assert self.e.check(['foo']) def test_should_match_bar(self): assert self.e.check(['bar']) def test_should_not_match_other(self): assert not self.e.check(['other']) def test_should_match_foo_bar(self): assert self.e.check(['foo', 'bar']) assert self.e.check(['bar', 'foo']) def test_should_match_foo_other(self): assert self.e.check(['foo', 'other']) assert self.e.check(['other', 'foo']) def test_should_match_bar_other(self): assert self.e.check(['bar', 'other']) assert self.e.check(['other', 'bar']) def test_should_not_match_zap_other(self): assert not self.e.check(['zap', 'other']) assert not self.e.check(['other', 'zap']) def test_should_match_foo_bar_other(self): assert self.e.check(['foo', 'bar', 'other']) assert self.e.check(['bar', 'other', 'foo']) assert self.e.check(['other', 'bar', 'foo']) def test_should_match_foo_zap_other(self): assert self.e.check(['foo', 'zap', 'other']) assert self.e.check(['other', 'zap', 'foo']) def test_should_match_bar_zap_other(self): assert self.e.check(['bar', 'zap', 'other']) assert self.e.check(['other', 'bar', 'zap']) def test_should_not_match_zap_baz_other(self): assert not self.e.check(['zap', 'baz', 'other']) assert not self.e.check(['baz', 'other', 'baz']) assert not self.e.check(['other', 'baz', 'zap']) class TestTagExpressionBarOrFoo(TestTagExpressionFooOrBar): # -- REUSE: Test suite due to symmetry in reversed expression # LOGIC: @bar or @foo == @foo or @bar def setUp(self): self.e = TagExpression(['bar,foo']) class TestTagExpressionFooOrNotBar(unittest.TestCase): def setUp(self): self.e = TagExpression(['foo,-bar']) def test_should_match_no_tags(self): assert self.e.check([]) def test_should_match_foo(self): assert self.e.check(['foo']) def test_should_not_match_bar(self): assert not self.e.check(['bar']) def test_should_match_other(self): assert self.e.check(['other']) def test_should_match_foo_bar(self): assert self.e.check(['foo', 'bar']) assert self.e.check(['bar', 'foo']) def test_should_match_foo_other(self): assert self.e.check(['foo', 'other']) assert self.e.check(['other', 'foo']) def test_should_not_match_bar_other(self): assert not self.e.check(['bar', 'other']) assert not self.e.check(['other', 'bar']) def test_should_match_zap_other(self): assert self.e.check(['zap', 'other']) assert self.e.check(['other', 'zap']) def test_should_match_foo_bar_other(self): assert self.e.check(['foo', 'bar', 'other']) assert self.e.check(['bar', 'other', 'foo']) assert self.e.check(['other', 'bar', 'foo']) def test_should_match_foo_zap_other(self): assert self.e.check(['foo', 'zap', 'other']) assert self.e.check(['other', 'zap', 'foo']) def test_should_not_match_bar_zap_other(self): assert not self.e.check(['bar', 'zap', 'other']) assert not self.e.check(['other', 'bar', 'zap']) def test_should_match_zap_baz_other(self): assert self.e.check(['zap', 'baz', 'other']) assert self.e.check(['baz', 'other', 'baz']) assert self.e.check(['other', 'baz', 'zap']) class TestTagExpressionNotBarOrFoo(TestTagExpressionFooOrNotBar): # -- REUSE: Test suite due to symmetry in reversed expression # LOGIC: not @bar or @foo == @foo or not @bar def setUp(self): self.e = TagExpression(['-bar,foo']) class TestTagExpressionNotFooOrNotBar(unittest.TestCase): def setUp(self): self.e = TagExpression(['-foo,-bar']) def test_should_match_no_tags(self): assert self.e.check([]) def test_should_match_foo(self): assert self.e.check(['foo']) def test_should_match_bar(self): assert self.e.check(['bar']) def test_should_match_other(self): assert self.e.check(['other']) def test_should_not_match_foo_bar(self): assert not self.e.check(['foo', 'bar']) assert not self.e.check(['bar', 'foo']) def test_should_match_foo_other(self): assert self.e.check(['foo', 'other']) assert self.e.check(['other', 'foo']) def test_should_match_bar_other(self): assert self.e.check(['bar', 'other']) assert self.e.check(['other', 'bar']) def test_should_match_zap_other(self): assert self.e.check(['zap', 'other']) assert self.e.check(['other', 'zap']) def test_should_not_match_foo_bar_other(self): assert not self.e.check(['foo', 'bar', 'other']) assert not self.e.check(['bar', 'other', 'foo']) assert not self.e.check(['other', 'bar', 'foo']) def test_should_match_foo_zap_other(self): assert self.e.check(['foo', 'zap', 'other']) assert self.e.check(['other', 'zap', 'foo']) def test_should_match_bar_zap_other(self): assert self.e.check(['bar', 'zap', 'other']) assert self.e.check(['other', 'bar', 'zap']) def test_should_match_zap_baz_other(self): assert self.e.check(['zap', 'baz', 'other']) assert self.e.check(['baz', 'other', 'baz']) assert self.e.check(['other', 'baz', 'zap']) class TestTagExpressionNotBarOrNotFoo(TestTagExpressionNotFooOrNotBar): # -- REUSE: Test suite due to symmetry in reversed expression # LOGIC: not @bar or @foo == @foo or not @bar def setUp(self): self.e = TagExpression(['-bar,-foo']) # ---------------------------------------------------------------------------- # MORE TESTS: With 3 tags # ---------------------------------------------------------------------------- class TestTagExpressionFooOrBarAndNotZap(unittest.TestCase): def setUp(self): self.e = TagExpression(['foo,bar', '-zap']) def test_should_match_foo(self): assert self.e.check(['foo']) def test_should_not_match_foo_zap(self): assert not self.e.check(['foo', 'zap']) def test_should_not_match_tags(self): assert not self.e.check([]) def test_should_match_foo(self): assert self.e.check(['foo']) def test_should_match_bar(self): assert self.e.check(['bar']) def test_should_not_match_other(self): assert not self.e.check(['other']) def test_should_match_foo_bar(self): assert self.e.check(['foo', 'bar']) assert self.e.check(['bar', 'foo']) def test_should_match_foo_other(self): assert self.e.check(['foo', 'other']) assert self.e.check(['other', 'foo']) def test_should_match_bar_other(self): assert self.e.check(['bar', 'other']) assert self.e.check(['other', 'bar']) def test_should_not_match_zap_other(self): assert not self.e.check(['zap', 'other']) assert not self.e.check(['other', 'zap']) def test_should_match_foo_bar_other(self): assert self.e.check(['foo', 'bar', 'other']) assert self.e.check(['bar', 'other', 'foo']) assert self.e.check(['other', 'bar', 'foo']) def test_should_not_match_foo_bar_zap(self): assert not self.e.check(['foo', 'bar', 'zap']) assert not self.e.check(['bar', 'zap', 'foo']) assert not self.e.check(['zap', 'bar', 'foo']) def test_should_not_match_foo_zap_other(self): assert not self.e.check(['foo', 'zap', 'other']) assert not self.e.check(['other', 'zap', 'foo']) def test_should_not_match_bar_zap_other(self): assert not self.e.check(['bar', 'zap', 'other']) assert not self.e.check(['other', 'bar', 'zap']) def test_should_not_match_zap_baz_other(self): assert not self.e.check(['zap', 'baz', 'other']) assert not self.e.check(['baz', 'other', 'baz']) assert not self.e.check(['other', 'baz', 'zap']) # ---------------------------------------------------------------------------- # TESTS WITH LIMIT # ---------------------------------------------------------------------------- class TestTagExpressionFoo3OrNotBar4AndZap5(unittest.TestCase): def setUp(self): self.e = TagExpression(['foo:3,-bar', 'zap:5']) def test_should_count_tags_for_positive_tags(self): tools.eq_(self.e.limits, {'foo': 3, 'zap': 5}) def test_should_match_foo_zap(self): assert self.e.check(['foo', 'zap']) class TestTagExpressionParsing(unittest.TestCase): def setUp(self): self.e = TagExpression([' foo:3 , -bar ', ' zap:5 ']) def test_should_have_limits(self): tools.eq_(self.e.limits, {'zap': 5, 'foo': 3}) class TestTagExpressionTagLimits(unittest.TestCase): def test_should_be_counted_for_negative_tags(self): e = TagExpression(['-todo:3']) tools.eq_(e.limits, {'todo': 3}) def test_should_be_counted_for_positive_tags(self): e = TagExpression(['todo:3']) tools.eq_(e.limits, {'todo': 3}) def test_should_raise_an_error_for_inconsistent_limits(self): tools.assert_raises(Exception, TagExpression, ['todo:3', '-todo:4']) def test_should_allow_duplicate_consistent_limits(self): e = TagExpression(['todo:3', '-todo:3']) tools.eq_(e.limits, {'todo': 3}) behave-1.2.6/test/test_tag_expression2.py0000644000076600000240000004175613244555737020575 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- """ Alternative approach to test TagExpression by testing all possible combinations. REQUIRES: Python >= 2.6, because itertools.combinations() is used. """ from __future__ import absolute_import from behave.tag_expression import TagExpression from nose import tools import itertools from six.moves import range has_combinations = hasattr(itertools, "combinations") if has_combinations: # -- REQUIRE: itertools.combinations # SINCE: Python 2.6 def all_combinations(items): variants = [] for n in range(len(items)+1): variants.extend(itertools.combinations(items, n)) return variants NO_TAGS = "__NO_TAGS__" def make_tags_line(tags): """ Convert into tags-line as in feature file. """ if tags: return "@" + " @".join(tags) return NO_TAGS TestCase = object # ---------------------------------------------------------------------------- # TEST: all_combinations() test helper # ---------------------------------------------------------------------------- class TestAllCombinations(TestCase): def test_all_combinations_with_2values(self): items = "@one @two".split() expected = [ (), ('@one',), ('@two',), ('@one', '@two'), ] actual = all_combinations(items) tools.eq_(actual, expected) tools.eq_(len(actual), 4) def test_all_combinations_with_3values(self): items = "@one @two @three".split() expected = [ (), ('@one',), ('@two',), ('@three',), ('@one', '@two'), ('@one', '@three'), ('@two', '@three'), ('@one', '@two', '@three'), ] actual = all_combinations(items) tools.eq_(actual, expected) tools.eq_(len(actual), 8) # ---------------------------------------------------------------------------- # COMPLICATED TESTS FOR: TagExpression logic # ---------------------------------------------------------------------------- class TagExpressionTestCase(TestCase): def assert_tag_expression_matches(self, tag_expression, tag_combinations, expected): matched = [ make_tags_line(c) for c in tag_combinations if tag_expression.check(c) ] tools.eq_(matched, expected) def assert_tag_expression_mismatches(self, tag_expression, tag_combinations, expected): mismatched = [ make_tags_line(c) for c in tag_combinations if not tag_expression.check(c) ] tools.eq_(mismatched, expected) class TestTagExpressionWith1Term(TagExpressionTestCase): """ ALL_COMBINATIONS[4] with: @foo @other self.NO_TAGS, "@foo", "@other", "@foo @other", """ tags = ("foo", "other") tag_combinations = all_combinations(tags) def test_matches__foo(self): tag_expression = TagExpression(["@foo"]) expected = [ # -- WITH 0 tags: None "@foo", "@foo @other", ] self.assert_tag_expression_matches(tag_expression, self.tag_combinations, expected) def test_matches__not_foo(self): tag_expression = TagExpression(["-@foo"]) expected = [ NO_TAGS, "@other", ] self.assert_tag_expression_matches(tag_expression, self.tag_combinations, expected) class TestTagExpressionWith2Terms(TagExpressionTestCase): """ ALL_COMBINATIONS[8] with: @foo @bar @other self.NO_TAGS, "@foo", "@bar", "@other", "@foo @bar", "@foo @other", "@bar @other", "@foo @bar @other", """ tags = ("foo", "bar", "other") tag_combinations = all_combinations(tags) # -- LOGICAL-OR CASES: def test_matches__foo_or_bar(self): tag_expression = TagExpression(["@foo,@bar"]) expected = [ # -- WITH 0 tags: None "@foo", "@bar", "@foo @bar", "@foo @other", "@bar @other", "@foo @bar @other", ] self.assert_tag_expression_matches(tag_expression, self.tag_combinations, expected) def test_matches__foo_or_not_bar(self): tag_expression = TagExpression(["@foo,-@bar"]) expected = [ NO_TAGS, "@foo", "@other", "@foo @bar", "@foo @other", "@foo @bar @other", ] self.assert_tag_expression_matches(tag_expression, self.tag_combinations, expected) def test_matches__not_foo_or_not_bar(self): tag_expression = TagExpression(["-@foo,-@bar"]) expected = [ NO_TAGS, "@foo", "@bar", "@other", "@foo @other", "@bar @other", ] self.assert_tag_expression_matches(tag_expression, self.tag_combinations, expected) # -- LOGICAL-AND CASES: def test_matches__foo_and_bar(self): tag_expression = TagExpression(["@foo", "@bar"]) expected = [ # -- WITH 0 tags: None # -- WITH 1 tag: None "@foo @bar", "@foo @bar @other", ] self.assert_tag_expression_matches(tag_expression, self.tag_combinations, expected) def test_matches__foo_and_not_bar(self): tag_expression = TagExpression(["@foo", "-@bar"]) expected = [ # -- WITH 0 tags: None # -- WITH 1 tag: None "@foo", "@foo @other", # -- WITH 3 tag: None ] self.assert_tag_expression_matches(tag_expression, self.tag_combinations, expected) def test_matches__not_foo_and_not_bar(self): tag_expression = TagExpression(["-@foo", "-@bar"]) expected = [ NO_TAGS, "@other", # -- WITH 2 tag: None # -- WITH 3 tag: None ] self.assert_tag_expression_matches(tag_expression, self.tag_combinations, expected) class TestTagExpressionWith3Terms(TagExpressionTestCase): """ ALL_COMBINATIONS[16] with: @foo @bar @zap @other self.NO_TAGS, "@foo", "@bar", "@zap", "@other", "@foo @bar", "@foo @zap", "@foo @other", "@bar @zap", "@bar @other", "@zap @other", "@foo @bar @zap", "@foo @bar @other", "@foo @zap @other", "@bar @zap @other", "@foo @bar @zap @other", """ tags = ("foo", "bar", "zap", "other") tag_combinations = all_combinations(tags) # -- LOGICAL-OR CASES: def test_matches__foo_or_bar_or_zap(self): tag_expression = TagExpression(["@foo,@bar,@zap"]) matched = [ # -- WITH 0 tags: None # -- WITH 1 tag: "@foo", "@bar", "@zap", # -- WITH 2 tags: "@foo @bar", "@foo @zap", "@foo @other", "@bar @zap", "@bar @other", "@zap @other", # -- WITH 3 tags: "@foo @bar @zap", "@foo @bar @other", "@foo @zap @other", "@bar @zap @other", # -- WITH 4 tags: "@foo @bar @zap @other", ] self.assert_tag_expression_matches(tag_expression, self.tag_combinations, matched) mismatched = [ # -- WITH 0 tags: NO_TAGS, # -- WITH 1 tag: "@other", # -- WITH 2 tags: None # -- WITH 3 tags: None # -- WITH 4 tags: None ] self.assert_tag_expression_mismatches(tag_expression, self.tag_combinations, mismatched) def test_matches__foo_or_not_bar_or_zap(self): tag_expression = TagExpression(["@foo,-@bar,@zap"]) matched = [ # -- WITH 0 tags: NO_TAGS, # -- WITH 1 tag: "@foo", "@zap", "@other", # -- WITH 2 tags: "@foo @bar", "@foo @zap", "@foo @other", "@bar @zap", "@zap @other", # -- WITH 3 tags: "@foo @bar @zap", "@foo @bar @other", "@foo @zap @other", "@bar @zap @other", # -- WITH 4 tags: "@foo @bar @zap @other", ] self.assert_tag_expression_matches(tag_expression, self.tag_combinations, matched) mismatched = [ # -- WITH 0 tags: None # -- WITH 1 tag: "@bar", # -- WITH 2 tags: "@bar @other", # -- WITH 3 tags: None # -- WITH 4 tags: None ] self.assert_tag_expression_mismatches(tag_expression, self.tag_combinations, mismatched) def test_matches__foo_or_not_bar_or_not_zap(self): tag_expression = TagExpression(["foo,-@bar,-@zap"]) matched = [ # -- WITH 0 tags: NO_TAGS, # -- WITH 1 tag: "@foo", "@bar", "@zap", "@other", # -- WITH 2 tags: "@foo @bar", "@foo @zap", "@foo @other", "@bar @other", "@zap @other", # -- WITH 3 tags: "@foo @bar @zap", "@foo @bar @other", "@foo @zap @other", # -- WITH 4 tags: "@foo @bar @zap @other", ] self.assert_tag_expression_matches(tag_expression, self.tag_combinations, matched) mismatched = [ # -- WITH 0 tags: None # -- WITH 1 tag: None # -- WITH 2 tags: "@bar @zap", # -- WITH 3 tags: None "@bar @zap @other", # -- WITH 4 tags: None ] self.assert_tag_expression_mismatches(tag_expression, self.tag_combinations, mismatched) def test_matches__not_foo_or_not_bar_or_not_zap(self): tag_expression = TagExpression(["-@foo,-@bar,-@zap"]) matched = [ # -- WITH 0 tags: NO_TAGS, # -- WITH 1 tag: "@foo", "@bar", "@zap", "@other", # -- WITH 2 tags: "@foo @bar", "@foo @zap", "@foo @other", "@bar @zap", "@bar @other", "@zap @other", # -- WITH 3 tags: "@foo @bar @other", "@foo @zap @other", "@bar @zap @other", # -- WITH 4 tags: None ] self.assert_tag_expression_matches(tag_expression, self.tag_combinations, matched) mismatched = [ # -- WITH 0 tags: None # -- WITH 1 tag: None # -- WITH 2 tags: # -- WITH 3 tags: "@foo @bar @zap", # -- WITH 4 tags: "@foo @bar @zap @other", ] self.assert_tag_expression_mismatches(tag_expression, self.tag_combinations, mismatched) def test_matches__foo_and_bar_or_zap(self): tag_expression = TagExpression(["@foo", "@bar,@zap"]) matched = [ # -- WITH 0 tags: # -- WITH 1 tag: # -- WITH 2 tags: "@foo @bar", "@foo @zap", # -- WITH 3 tags: "@foo @bar @zap", "@foo @bar @other", "@foo @zap @other", # -- WITH 4 tags: None "@foo @bar @zap @other", ] self.assert_tag_expression_matches(tag_expression, self.tag_combinations, matched) mismatched = [ # -- WITH 0 tags: NO_TAGS, # -- WITH 1 tag: "@foo", "@bar", "@zap", "@other", # -- WITH 2 tags: "@foo @other", "@bar @zap", "@bar @other", "@zap @other", # -- WITH 3 tags: "@bar @zap @other", # -- WITH 4 tags: None ] self.assert_tag_expression_mismatches(tag_expression, self.tag_combinations, mismatched) def test_matches__foo_and_bar_or_not_zap(self): tag_expression = TagExpression(["@foo", "@bar,-@zap"]) matched = [ # -- WITH 0 tags: # -- WITH 1 tag: "@foo", # -- WITH 2 tags: "@foo @bar", "@foo @other", # -- WITH 3 tags: "@foo @bar @zap", "@foo @bar @other", # -- WITH 4 tags: None "@foo @bar @zap @other", ] self.assert_tag_expression_matches(tag_expression, self.tag_combinations, matched) mismatched = [ # -- WITH 0 tags: NO_TAGS, # -- WITH 1 tag: "@bar", "@zap", "@other", # -- WITH 2 tags: "@foo @zap", "@bar @zap", "@bar @other", "@zap @other", # -- WITH 3 tags: "@foo @zap @other", "@bar @zap @other", # -- WITH 4 tags: None ] self.assert_tag_expression_mismatches(tag_expression, self.tag_combinations, mismatched) def test_matches__foo_and_bar_and_zap(self): tag_expression = TagExpression(["@foo", "@bar", "@zap"]) matched = [ # -- WITH 0 tags: # -- WITH 1 tag: # -- WITH 2 tags: # -- WITH 3 tags: "@foo @bar @zap", # -- WITH 4 tags: None "@foo @bar @zap @other", ] self.assert_tag_expression_matches(tag_expression, self.tag_combinations, matched) mismatched = [ # -- WITH 0 tags: NO_TAGS, # -- WITH 1 tag: "@foo", "@bar", "@zap", "@other", # -- WITH 2 tags: "@foo @bar", "@foo @zap", "@foo @other", "@bar @zap", "@bar @other", "@zap @other", # -- WITH 3 tags: "@foo @bar @other", "@foo @zap @other", "@bar @zap @other", # -- WITH 4 tags: None ] self.assert_tag_expression_mismatches(tag_expression, self.tag_combinations, mismatched) def test_matches__not_foo_and_not_bar_and_not_zap(self): tag_expression = TagExpression(["-@foo", "-@bar", "-@zap"]) matched = [ # -- WITH 0 tags: NO_TAGS, # -- WITH 1 tag: "@other", # -- WITH 2 tags: # -- WITH 3 tags: # -- WITH 4 tags: None ] self.assert_tag_expression_matches(tag_expression, self.tag_combinations, matched) mismatched = [ # -- WITH 0 tags: # -- WITH 1 tag: "@foo", "@bar", "@zap", # -- WITH 2 tags: "@foo @bar", "@foo @zap", "@foo @other", "@bar @zap", "@bar @other", "@zap @other", # -- WITH 3 tags: "@foo @bar @zap", "@foo @bar @other", "@foo @zap @other", "@bar @zap @other", # -- WITH 4 tags: None "@foo @bar @zap @other", ] self.assert_tag_expression_mismatches(tag_expression, self.tag_combinations, mismatched) behave-1.2.6/test/test_tag_matcher.py0000644000076600000240000010576313244555737017736 0ustar jensstaff00000000000000# -*- coding: utf-8 -*- from __future__ import absolute_import from behave.tag_matcher import * from mock import Mock from unittest import TestCase import warnings # -- REQUIRES: pytest # import pytest class Traits4ActiveTagMatcher(object): TagMatcher = ActiveTagMatcher value_provider = { "foo": "alice", "bar": "BOB", } category1_enabled_tag = TagMatcher.make_category_tag("foo", "alice") category1_disabled_tag = TagMatcher.make_category_tag("foo", "bob") category1_disabled_tag2 = TagMatcher.make_category_tag("foo", "charly") category1_similar_tag = TagMatcher.make_category_tag("foo", "alice2") category2_enabled_tag = TagMatcher.make_category_tag("bar", "BOB") category2_disabled_tag = TagMatcher.make_category_tag("bar", "CHARLY") category2_similar_tag = TagMatcher.make_category_tag("bar", "BOB2") unknown_category_tag = TagMatcher.make_category_tag("UNKNOWN", "one") # -- NEGATED TAGS: category1_not_enabled_tag = \ TagMatcher.make_category_tag("foo", "alice", "not_active") category1_not_enabled_tag2 = \ TagMatcher.make_category_tag("foo", "alice", "not") category1_not_disabled_tag = \ TagMatcher.make_category_tag("foo", "bob", "not_active") category1_negated_similar_tag1 = \ TagMatcher.make_category_tag("foo", "alice2", "not_active") active_tags1 = [ category1_enabled_tag, category1_disabled_tag, category1_similar_tag, category1_not_enabled_tag, category1_not_enabled_tag2, ] active_tags2 = [ category2_enabled_tag, category2_disabled_tag, category2_similar_tag, ] active_tags = active_tags1 + active_tags2 # -- REQUIRES: pytest # class TestActiveTagMatcher2(object): # TagMatcher = ActiveTagMatcher # traits = Traits4ActiveTagMatcher # # @classmethod # def make_tag_matcher(cls): # value_provider = { # "foo": "alice", # "bar": "BOB", # } # tag_matcher = cls.TagMatcher(value_provider) # return tag_matcher # # @pytest.mark.parametrize("case, expected_len, tags", [ # ("case: Two enabled tags", 2, # [traits.category1_enabled_tag, traits.category2_enabled_tag]), # ("case: Active enabled and normal tag", 1, # [traits.category1_enabled_tag, "foo"]), # ("case: Active disabled and normal tag", 1, # [traits.category1_disabled_tag, "foo"]), # ("case: Normal and active negated tag", 1, # ["foo", traits.category1_not_enabled_tag]), # ("case: Two normal tags", 0, # ["foo", "bar"]), # ]) # def test_select_active_tags__with_two_tags(self, case, expected_len, tags): # tag_matcher = self.make_tag_matcher() # selected = tag_matcher.select_active_tags(tags) # selected = list(selected) # assert len(selected) == expected_len, case # # @pytest.mark.parametrize("case, expected, tags", [ # # -- GROUP: With positive logic (non-negated tags) # ("case P00: 2 disabled tags", True, # [ traits.category1_disabled_tag, traits.category2_disabled_tag]), # ("case P01: disabled and enabled tag", True, # [ traits.category1_disabled_tag, traits.category2_enabled_tag]), # ("case P10: enabled and disabled tag", True, # [ traits.category1_enabled_tag, traits.category2_disabled_tag]), # ("case P11: 2 enabled tags", False, # -- SHOULD-RUN # [ traits.category1_enabled_tag, traits.category2_enabled_tag]), # # -- GROUP: With negated tag # ("case N00: not-enabled and disabled tag", True, # [ traits.category1_not_enabled_tag, traits.category2_disabled_tag]), # ("case N01: not-enabled and enabled tag", True, # [ traits.category1_not_enabled_tag, traits.category2_enabled_tag]), # ("case N10: not-disabled and disabled tag", True, # [ traits.category1_not_disabled_tag, traits.category2_disabled_tag]), # ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN # [ traits.category1_not_disabled_tag, traits.category2_enabled_tag]), # # -- GROUP: With unknown category # ("case U0x: disabled and unknown tag", True, # [ traits.category1_disabled_tag, traits.unknown_category_tag]), # ("case U1x: enabled and unknown tag", False, # -- SHOULD-RUN # [ traits.category1_enabled_tag, traits.unknown_category_tag]), # ]) # def test_should_exclude_with__combinations_of_2_categories(self, case, expected, tags): # tag_matcher = self.make_tag_matcher() # actual_result = tag_matcher.should_exclude_with(tags) # assert expected == actual_result, case # # @pytest.mark.parametrize("case, expected, tags", [ # # -- GROUP: With positive logic (non-negated tags) # ("case P00: 2 disabled tags", True, # [ traits.category1_disabled_tag, traits.category1_disabled_tag2]), # ("case P01: disabled and enabled tag", True, # [ traits.category1_disabled_tag, traits.category1_enabled_tag]), # ("case P10: enabled and disabled tag", True, # [ traits.category1_enabled_tag, traits.category1_disabled_tag]), # ("case P11: 2 enabled tags (same)", False, # -- SHOULD-RUN # [ traits.category1_enabled_tag, traits.category1_enabled_tag]), # # -- GROUP: With negated tag # ("case N00: not-enabled and disabled tag", True, # [ traits.category1_not_enabled_tag, traits.category1_disabled_tag]), # ("case N01: not-enabled and enabled tag", True, # [ traits.category1_not_enabled_tag, traits.category1_enabled_tag]), # ("case N10: not-disabled and disabled tag", True, # [ traits.category1_not_disabled_tag, traits.category1_disabled_tag]), # ("case N11: not-disabled and enabled tag", False, # -- SHOULD-RUN # [ traits.category1_not_disabled_tag, traits.category1_enabled_tag]), # ]) # def test_should_exclude_with__combinations_with_same_category(self, # case, expected, tags): # tag_matcher = self.make_tag_matcher() # actual_result = tag_matcher.should_exclude_with(tags) # assert expected == actual_result, case class TestActiveTagMatcher1(TestCase): TagMatcher = ActiveTagMatcher traits = Traits4ActiveTagMatcher @classmethod def make_tag_matcher(cls): tag_matcher = cls.TagMatcher(cls.traits.value_provider) return tag_matcher def setUp(self): self.tag_matcher = self.make_tag_matcher() def test_select_active_tags__basics(self): active_tag = "active.with_CATEGORY=VALUE" tags = ["foo", active_tag, "bar"] selected = list(self.tag_matcher.select_active_tags(tags)) self.assertEqual(len(selected), 1) selected_tag, selected_match = selected[0] self.assertEqual(selected_tag, active_tag) def test_select_active_tags__matches_tag_parts(self): tags = ["active.with_CATEGORY=VALUE"] selected = list(self.tag_matcher.select_active_tags(tags)) self.assertEqual(len(selected), 1) selected_tag, selected_match = selected[0] self.assertEqual(selected_match.group("prefix"), "active") self.assertEqual(selected_match.group("category"), "CATEGORY") self.assertEqual(selected_match.group("value"), "VALUE") def test_select_active_tags__finds_tag_with_any_valid_tag_prefix(self): TagMatcher = self.TagMatcher for tag_prefix in TagMatcher.tag_prefixes: tag = TagMatcher.make_category_tag("foo", "alice", tag_prefix) tags = [ tag ] selected = self.tag_matcher.select_active_tags(tags) selected = list(selected) self.assertEqual(len(selected), 1) selected_tag0 = selected[0][0] self.assertEqual(selected_tag0, tag) self.assertTrue(selected_tag0.startswith(tag_prefix)) def test_select_active_tags__ignores_invalid_active_tags(self): invalid_active_tags = [ ("foo.alice", "case: Normal tag"), ("with_foo=alice", "case: Subset of an active tag"), ("ACTIVE.with_foo.alice", "case: Wrong tag_prefix (uppercase)"), ("only.with_foo.alice", "case: Wrong value_separator"), ] for invalid_tag, case in invalid_active_tags: tags = [ invalid_tag ] selected = self.tag_matcher.select_active_tags(tags) selected = list(selected) self.assertEqual(len(selected), 0, case) def test_select_active_tags__with_two_tags(self): # XXX-JE-DUPLICATED: traits = self.traits test_patterns = [ ("case: Two enabled tags", [traits.category1_enabled_tag, traits.category2_enabled_tag]), ("case: Active enabled and normal tag", [traits.category1_enabled_tag, "foo"]), ("case: Active disabled and normal tag", [traits.category1_disabled_tag, "foo"]), ("case: Active negated and normal tag", [traits.category1_not_enabled_tag, "foo"]), ] for case, tags in test_patterns: selected = self.tag_matcher.select_active_tags(tags) selected = list(selected) self.assertTrue(len(selected) >= 1, case) def test_should_exclude_with__returns_false_with_enabled_tag(self): traits = self.traits tags1 = [ traits.category1_enabled_tag ] tags2 = [ traits.category2_enabled_tag ] self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1)) self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2)) def test_should_exclude_with__returns_false_with_disabled_tag_and_more(self): # -- NOTE: Need 1+ enabled active-tags of same category => ENABLED traits = self.traits test_patterns = [ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"), ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"), ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"), ] enabled = True # EXPECTED for tags, case in test_patterns: self.assertEqual(not enabled, self.tag_matcher.should_exclude_with(tags), "%s: tags=%s" % (case, tags)) def test_should_exclude_with__returns_true_with_other_tag(self): traits = self.traits tags = [ traits.category1_disabled_tag ] self.assertEqual(True, self.tag_matcher.should_exclude_with(tags)) def test_should_exclude_with__returns_true_with_other_tag_and_more(self): traits = self.traits test_patterns = [ ([ traits.category1_disabled_tag, "foo" ], "case: first"), ([ "foo", traits.category1_disabled_tag ], "case: last"), ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"), ] for tags, case in test_patterns: self.assertEqual(True, self.tag_matcher.should_exclude_with(tags), "%s: tags=%s" % (case, tags)) def test_should_exclude_with__returns_true_with_similar_tag(self): traits = self.traits tags = [ traits.category1_similar_tag ] self.assertEqual(True, self.tag_matcher.should_exclude_with(tags)) def test_should_exclude_with__returns_true_with_similar_and_more(self): traits = self.traits test_patterns = [ ([ traits.category1_similar_tag, "foo" ], "case: first"), ([ "foo", traits.category1_similar_tag ], "case: last"), ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"), ] for tags, case in test_patterns: self.assertEqual(True, self.tag_matcher.should_exclude_with(tags), "%s: tags=%s" % (case, tags)) def test_should_exclude_with__returns_false_without_category_tag(self): test_patterns = [ ([ ], "case: No tags"), ([ "foo" ], "case: One tag"), ([ "foo", "bar" ], "case: Two tags"), ] for tags, case in test_patterns: self.assertEqual(False, self.tag_matcher.should_exclude_with(tags), "%s: tags=%s" % (case, tags)) def test_should_exclude_with__returns_false_with_unknown_category_tag(self): """Tags from unknown categories, not supported by value_provider, should not be excluded. """ traits = self.traits tags = [ traits.unknown_category_tag ] self.assertEqual("active.with_UNKNOWN=one", traits.unknown_category_tag) self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN")) self.assertEqual(False, self.tag_matcher.should_exclude_with(tags)) def test_should_exclude_with__combinations_of_2_categories(self): # XXX-JE-DUPLICATED: traits = self.traits test_patterns = [ ("case P00: 2 disabled category tags", True, [ traits.category1_disabled_tag, traits.category2_disabled_tag]), ("case P01: disabled and enabled category tags", True, [ traits.category1_disabled_tag, traits.category2_enabled_tag]), ("case P10: enabled and disabled category tags", True, [ traits.category1_enabled_tag, traits.category2_disabled_tag]), ("case P11: 2 enabled category tags", False, # -- SHOULD-RUN [ traits.category1_enabled_tag, traits.category2_enabled_tag]), # -- SPECIAL CASE: With negated category ("case N00: not-enabled and disabled category tags", True, [ traits.category1_not_enabled_tag, traits.category2_disabled_tag]), ("case N01: not-enabled and enabled category tags", True, [ traits.category1_not_enabled_tag, traits.category2_enabled_tag]), ("case N10: not-disabled and disabled category tags", True, [ traits.category1_not_disabled_tag, traits.category2_disabled_tag]), ("case N11: not-enabled and enabled category tags", False, # -- SHOULD-RUN [ traits.category1_not_disabled_tag, traits.category2_enabled_tag]), # -- SPECIAL CASE: With unknown category ("case 0x: disabled and unknown category tags", True, [ traits.category1_disabled_tag, traits.unknown_category_tag]), ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN [ traits.category1_enabled_tag, traits.unknown_category_tag]), ] for case, expected, tags in test_patterns: actual_result = self.tag_matcher.should_exclude_with(tags) self.assertEqual(expected, actual_result, "%s: tags=%s" % (case, tags)) def test_should_run_with__negates_result_of_should_exclude_with(self): traits = self.traits test_patterns = [ ([ ], "case: No tags"), ([ "foo" ], "case: One non-category tag"), ([ "foo", "bar" ], "case: Two non-category tags"), ([ traits.category1_enabled_tag ], "case: enabled tag"), ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"), ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"), ([ traits.category1_disabled_tag ], "case: other tag"), ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"), ([ traits.category1_similar_tag ], "case: similar tag"), ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"), ] for tags, case in test_patterns: result1 = self.tag_matcher.should_run_with(tags) result2 = self.tag_matcher.should_exclude_with(tags) self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags)) self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags)) class TestPredicateTagMatcher(TestCase): def test_exclude_with__mechanics(self): predicate_function_blueprint = lambda tags: False predicate_function = Mock(predicate_function_blueprint) predicate_function.return_value = True tag_matcher = PredicateTagMatcher(predicate_function) tags = [ "foo", "bar" ] self.assertEqual(True, tag_matcher.should_exclude_with(tags)) predicate_function.assert_called_once_with(tags) self.assertEqual(True, predicate_function(tags)) def test_should_exclude_with__returns_true_when_predicate_is_true(self): predicate_always_true = lambda tags: True tag_matcher1 = PredicateTagMatcher(predicate_always_true) tags = [ "foo", "bar" ] self.assertEqual(True, tag_matcher1.should_exclude_with(tags)) self.assertEqual(True, predicate_always_true(tags)) def test_should_exclude_with__returns_true_when_predicate_is_true2(self): # -- CASE: Use predicate function instead of lambda. def predicate_contains_foo(tags): return any(x == "foo" for x in tags) tag_matcher2 = PredicateTagMatcher(predicate_contains_foo) tags = [ "foo", "bar" ] self.assertEqual(True, tag_matcher2.should_exclude_with(tags)) self.assertEqual(True, predicate_contains_foo(tags)) def test_should_exclude_with__returns_false_when_predicate_is_false(self): predicate_always_false = lambda tags: False tag_matcher1 = PredicateTagMatcher(predicate_always_false) tags = [ "foo", "bar" ] self.assertEqual(False, tag_matcher1.should_exclude_with(tags)) self.assertEqual(False, predicate_always_false(tags)) class TestPredicateTagMatcher(TestCase): def test_exclude_with__mechanics(self): predicate_function_blueprint = lambda tags: False predicate_function = Mock(predicate_function_blueprint) predicate_function.return_value = True tag_matcher = PredicateTagMatcher(predicate_function) tags = [ "foo", "bar" ] self.assertEqual(True, tag_matcher.should_exclude_with(tags)) predicate_function.assert_called_once_with(tags) self.assertEqual(True, predicate_function(tags)) def test_should_exclude_with__returns_true_when_predicate_is_true(self): predicate_always_true = lambda tags: True tag_matcher1 = PredicateTagMatcher(predicate_always_true) tags = [ "foo", "bar" ] self.assertEqual(True, tag_matcher1.should_exclude_with(tags)) self.assertEqual(True, predicate_always_true(tags)) def test_should_exclude_with__returns_true_when_predicate_is_true2(self): # -- CASE: Use predicate function instead of lambda. def predicate_contains_foo(tags): return any(x == "foo" for x in tags) tag_matcher2 = PredicateTagMatcher(predicate_contains_foo) tags = [ "foo", "bar" ] self.assertEqual(True, tag_matcher2.should_exclude_with(tags)) self.assertEqual(True, predicate_contains_foo(tags)) def test_should_exclude_with__returns_false_when_predicate_is_false(self): predicate_always_false = lambda tags: False tag_matcher1 = PredicateTagMatcher(predicate_always_false) tags = [ "foo", "bar" ] self.assertEqual(False, tag_matcher1.should_exclude_with(tags)) self.assertEqual(False, predicate_always_false(tags)) class TestCompositeTagMatcher(TestCase): @staticmethod def count_tag_matcher_with_result(tag_matchers, tags, result_value): count = 0 for tag_matcher in tag_matchers: current_result = tag_matcher.should_exclude_with(tags) if current_result == result_value: count += 1 return count def setUp(self): predicate_false = lambda tags: False predicate_contains_foo = lambda tags: any(x == "foo" for x in tags) self.tag_matcher_false = PredicateTagMatcher(predicate_false) self.tag_matcher_foo = PredicateTagMatcher(predicate_contains_foo) tag_matchers = [ self.tag_matcher_foo, self.tag_matcher_false ] self.ctag_matcher = CompositeTagMatcher(tag_matchers) def test_should_exclude_with__returns_true_when_any_tag_matcher_returns_true(self): test_patterns = [ ("case: with foo", ["foo", "bar"]), ("case: with foo2", ["foozy", "foo", "bar"]), ] for case, tags in test_patterns: actual_result = self.ctag_matcher.should_exclude_with(tags) self.assertEqual(True, actual_result, "%s: tags=%s" % (case, tags)) actual_true_count = self.count_tag_matcher_with_result( self.ctag_matcher.tag_matchers, tags, True) self.assertEqual(1, actual_true_count) def test_should_exclude_with__returns_false_when_no_tag_matcher_return_true(self): test_patterns = [ ("case: without foo", ["fool", "bar"]), ("case: without foo2", ["foozy", "bar"]), ] for case, tags in test_patterns: actual_result = self.ctag_matcher.should_exclude_with(tags) self.assertEqual(False, actual_result, "%s: tags=%s" % (case, tags)) actual_true_count = self.count_tag_matcher_with_result( self.ctag_matcher.tag_matchers, tags, True) self.assertEqual(0, actual_true_count) # ----------------------------------------------------------------------------- # PROTOTYPING CLASSES (deprecating) # ----------------------------------------------------------------------------- class TestOnlyWithCategoryTagMatcher(TestCase): TagMatcher = OnlyWithCategoryTagMatcher def setUp(self): category = "xxx" with warnings.catch_warnings(): warnings.simplefilter("ignore", DeprecationWarning) self.tag_matcher = OnlyWithCategoryTagMatcher(category, "alice") self.enabled_tag = self.TagMatcher.make_category_tag(category, "alice") self.similar_tag = self.TagMatcher.make_category_tag(category, "alice2") self.other_tag = self.TagMatcher.make_category_tag(category, "other") self.category = category def test_should_exclude_with__returns_false_with_enabled_tag(self): tags = [ self.enabled_tag ] self.assertEqual(False, self.tag_matcher.should_exclude_with(tags)) def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self): test_patterns = [ ([ self.enabled_tag, self.other_tag ], "case: first"), ([ self.other_tag, self.enabled_tag ], "case: last"), ([ "foo", self.enabled_tag, self.other_tag, "bar" ], "case: middle"), ] for tags, case in test_patterns: self.assertEqual(False, self.tag_matcher.should_exclude_with(tags), "%s: tags=%s" % (case, tags)) def test_should_exclude_with__returns_true_with_other_tag(self): tags = [ self.other_tag ] self.assertEqual(True, self.tag_matcher.should_exclude_with(tags)) def test_should_exclude_with__returns_true_with_other_tag_and_more(self): test_patterns = [ ([ self.other_tag, "foo" ], "case: first"), ([ "foo", self.other_tag ], "case: last"), ([ "foo", self.other_tag, "bar" ], "case: middle"), ] for tags, case in test_patterns: self.assertEqual(True, self.tag_matcher.should_exclude_with(tags), "%s: tags=%s" % (case, tags)) def test_should_exclude_with__returns_true_with_similar_tag(self): tags = [ self.similar_tag ] self.assertEqual(True, self.tag_matcher.should_exclude_with(tags)) def test_should_exclude_with__returns_true_with_similar_and_more(self): test_patterns = [ ([ self.similar_tag, "foo" ], "case: first"), ([ "foo", self.similar_tag ], "case: last"), ([ "foo", self.similar_tag, "bar" ], "case: middle"), ] for tags, case in test_patterns: self.assertEqual(True, self.tag_matcher.should_exclude_with(tags), "%s: tags=%s" % (case, tags)) def test_should_exclude_with__returns_false_without_category_tag(self): test_patterns = [ ([ ], "case: No tags"), ([ "foo" ], "case: One tag"), ([ "foo", "bar" ], "case: Two tags"), ] for tags, case in test_patterns: self.assertEqual(False, self.tag_matcher.should_exclude_with(tags), "%s: tags=%s" % (case, tags)) def test_should_run_with__negates_result_of_should_exclude_with(self): test_patterns = [ ([ ], "case: No tags"), ([ "foo" ], "case: One non-category tag"), ([ "foo", "bar" ], "case: Two non-category tags"), ([ self.enabled_tag ], "case: enabled tag"), ([ self.enabled_tag, self.other_tag ], "case: enabled and other tag"), ([ self.enabled_tag, "foo" ], "case: enabled and foo tag"), ([ self.other_tag ], "case: other tag"), ([ self.other_tag, "foo" ], "case: other and foo tag"), ([ self.similar_tag ], "case: similar tag"), ([ "foo", self.similar_tag ], "case: foo and similar tag"), ] for tags, case in test_patterns: result1 = self.tag_matcher.should_run_with(tags) result2 = self.tag_matcher.should_exclude_with(tags) self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags)) self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags)) def test_make_category_tag__returns_category_tag_prefix_without_value(self): category = "xxx" tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category) tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, None) tag3 = OnlyWithCategoryTagMatcher.make_category_tag(category, value=None) self.assertEqual("only.with_xxx=", tag1) self.assertEqual("only.with_xxx=", tag2) self.assertEqual("only.with_xxx=", tag3) self.assertTrue(tag1.startswith(OnlyWithCategoryTagMatcher.tag_prefix)) def test_make_category_tag__returns_category_tag_with_value(self): category = "xxx" tag1 = OnlyWithCategoryTagMatcher.make_category_tag(category, "alice") tag2 = OnlyWithCategoryTagMatcher.make_category_tag(category, "bob") self.assertEqual("only.with_xxx=alice", tag1) self.assertEqual("only.with_xxx=bob", tag2) def test_make_category_tag__returns_category_tag_with_tag_prefix(self): my_tag_prefix = "ONLY_WITH." category = "xxx" TagMatcher = OnlyWithCategoryTagMatcher tag0 = TagMatcher.make_category_tag(category, tag_prefix=my_tag_prefix) tag1 = TagMatcher.make_category_tag(category, "alice", my_tag_prefix) tag2 = TagMatcher.make_category_tag(category, "bob", tag_prefix=my_tag_prefix) self.assertEqual("ONLY_WITH.xxx=", tag0) self.assertEqual("ONLY_WITH.xxx=alice", tag1) self.assertEqual("ONLY_WITH.xxx=bob", tag2) self.assertTrue(tag1.startswith(my_tag_prefix)) def test_ctor__with_tag_prefix(self): tag_prefix = "ONLY_WITH." tag_matcher = OnlyWithCategoryTagMatcher("xxx", "alice", tag_prefix) tags = ["foo", "ONLY_WITH.xxx=foo", "only.with_xxx=bar", "bar"] actual_tags = tag_matcher.select_category_tags(tags) self.assertEqual(["ONLY_WITH.xxx=foo"], actual_tags) class Traits4OnlyWithAnyCategoryTagMatcher(object): """Test data for OnlyWithAnyCategoryTagMatcher.""" TagMatcher0 = OnlyWithCategoryTagMatcher TagMatcher = OnlyWithAnyCategoryTagMatcher category1_enabled_tag = TagMatcher0.make_category_tag("foo", "alice") category1_similar_tag = TagMatcher0.make_category_tag("foo", "alice2") category1_disabled_tag = TagMatcher0.make_category_tag("foo", "bob") category2_enabled_tag = TagMatcher0.make_category_tag("bar", "BOB") category2_similar_tag = TagMatcher0.make_category_tag("bar", "BOB2") category2_disabled_tag = TagMatcher0.make_category_tag("bar", "CHARLY") unknown_category_tag = TagMatcher0.make_category_tag("UNKNOWN", "one") class TestOnlyWithAnyCategoryTagMatcher(TestCase): TagMatcher = OnlyWithAnyCategoryTagMatcher traits = Traits4OnlyWithAnyCategoryTagMatcher def setUp(self): value_provider = { "foo": "alice", "bar": "BOB", } with warnings.catch_warnings(): warnings.simplefilter("ignore", DeprecationWarning) self.tag_matcher = self.TagMatcher(value_provider) # def test_deprecating_warning_is_issued(self): # value_provider = {"foo": "alice"} # with warnings.catch_warnings(record=True) as recorder: # warnings.simplefilter("always", DeprecationWarning) # tag_matcher = OnlyWithAnyCategoryTagMatcher(value_provider) # self.assertEqual(len(recorder), 1) # last_warning = recorder[-1] # assert issubclass(last_warning.category, DeprecationWarning) # assert "deprecated" in str(last_warning.message) def test_should_exclude_with__returns_false_with_enabled_tag(self): traits = self.traits tags1 = [ traits.category1_enabled_tag ] tags2 = [ traits.category2_enabled_tag ] self.assertEqual(False, self.tag_matcher.should_exclude_with(tags1)) self.assertEqual(False, self.tag_matcher.should_exclude_with(tags2)) def test_should_exclude_with__returns_false_with_enabled_tag_and_more(self): traits = self.traits test_patterns = [ ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: first"), ([ traits.category1_disabled_tag, traits.category1_enabled_tag ], "case: last"), ([ "foo", traits.category1_enabled_tag, traits.category1_disabled_tag, "bar" ], "case: middle"), ] for tags, case in test_patterns: self.assertEqual(False, self.tag_matcher.should_exclude_with(tags), "%s: tags=%s" % (case, tags)) def test_should_exclude_with__returns_true_with_other_tag(self): traits = self.traits tags = [ traits.category1_disabled_tag ] self.assertEqual(True, self.tag_matcher.should_exclude_with(tags)) def test_should_exclude_with__returns_true_with_other_tag_and_more(self): traits = self.traits test_patterns = [ ([ traits.category1_disabled_tag, "foo" ], "case: first"), ([ "foo", traits.category1_disabled_tag ], "case: last"), ([ "foo", traits.category1_disabled_tag, "bar" ], "case: middle"), ] for tags, case in test_patterns: self.assertEqual(True, self.tag_matcher.should_exclude_with(tags), "%s: tags=%s" % (case, tags)) def test_should_exclude_with__returns_true_with_similar_tag(self): traits = self.traits tags = [ traits.category1_similar_tag ] self.assertEqual(True, self.tag_matcher.should_exclude_with(tags)) def test_should_exclude_with__returns_true_with_similar_and_more(self): traits = self.traits test_patterns = [ ([ traits.category1_similar_tag, "foo" ], "case: first"), ([ "foo", traits.category1_similar_tag ], "case: last"), ([ "foo", traits.category1_similar_tag, "bar" ], "case: middle"), ] for tags, case in test_patterns: self.assertEqual(True, self.tag_matcher.should_exclude_with(tags), "%s: tags=%s" % (case, tags)) def test_should_exclude_with__returns_false_without_category_tag(self): test_patterns = [ ([ ], "case: No tags"), ([ "foo" ], "case: One tag"), ([ "foo", "bar" ], "case: Two tags"), ] for tags, case in test_patterns: self.assertEqual(False, self.tag_matcher.should_exclude_with(tags), "%s: tags=%s" % (case, tags)) def test_should_exclude_with__returns_false_with_unknown_category_tag(self): """Tags from unknown categories, not supported by value_provider, should not be excluded. """ traits = self.traits tags = [ traits.unknown_category_tag ] self.assertEqual("only.with_UNKNOWN=one", traits.unknown_category_tag) self.assertEqual(None, self.tag_matcher.value_provider.get("UNKNOWN")) self.assertEqual(False, self.tag_matcher.should_exclude_with(tags)) def test_should_exclude_with__combinations_of_2_categories(self): traits = self.traits test_patterns = [ ("case 00: 2 disabled category tags", True, [ traits.category1_disabled_tag, traits.category2_disabled_tag]), ("case 01: disabled and enabled category tags", True, [ traits.category1_disabled_tag, traits.category2_enabled_tag]), ("case 10: enabled and disabled category tags", True, [ traits.category1_enabled_tag, traits.category2_disabled_tag]), ("case 11: 2 enabled category tags", False, # -- SHOULD-RUN [ traits.category1_enabled_tag, traits.category2_enabled_tag]), # -- SPECIAL CASE: With unknown category ("case 0x: disabled and unknown category tags", True, [ traits.category1_disabled_tag, traits.unknown_category_tag]), ("case 1x: enabled and unknown category tags", False, # SHOULD-RUN [ traits.category1_enabled_tag, traits.unknown_category_tag]), ] for case, expected, tags in test_patterns: actual_result = self.tag_matcher.should_exclude_with(tags) self.assertEqual(expected, actual_result, "%s: tags=%s" % (case, tags)) def test_should_run_with__negates_result_of_should_exclude_with(self): traits = self.traits test_patterns = [ ([ ], "case: No tags"), ([ "foo" ], "case: One non-category tag"), ([ "foo", "bar" ], "case: Two non-category tags"), ([ traits.category1_enabled_tag ], "case: enabled tag"), ([ traits.category1_enabled_tag, traits.category1_disabled_tag ], "case: enabled and other tag"), ([ traits.category1_enabled_tag, "foo" ], "case: enabled and foo tag"), ([ traits.category1_disabled_tag ], "case: other tag"), ([ traits.category1_disabled_tag, "foo" ], "case: other and foo tag"), ([ traits.category1_similar_tag ], "case: similar tag"), ([ "foo", traits.category1_similar_tag ], "case: foo and similar tag"), ] for tags, case in test_patterns: result1 = self.tag_matcher.should_run_with(tags) result2 = self.tag_matcher.should_exclude_with(tags) self.assertEqual(result1, not result2, "%s: tags=%s" % (case, tags)) self.assertEqual(not result1, result2, "%s: tags=%s" % (case, tags)) behave-1.2.6/tests/0000755000076600000240000000000013244564040014201 5ustar jensstaff00000000000000behave-1.2.6/tests/__init__.py0000644000076600000240000000000013244555737016315 0ustar jensstaff00000000000000behave-1.2.6/tests/api/0000755000076600000240000000000013244564040014752 5ustar jensstaff00000000000000behave-1.2.6/tests/api/__init__.py0000644000076600000240000000000013244555737017066 0ustar jensstaff00000000000000behave-1.2.6/tests/api/__ONLY_PY34_or_newer.txt0000644000076600000240000000000013244555737021254 0ustar jensstaff00000000000000behave-1.2.6/tests/api/_test_async_step34.py0000644000076600000240000001260013244555737021055 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Unit tests for :mod:`behave.api.async_test`. """ # -- IMPORTS: from __future__ import absolute_import, print_function from behave.api.async_step import AsyncContext, use_or_create_async_context from behave._stepimport import use_step_import_modules from behave.runner import Context, Runner import sys from mock import Mock import pytest from .testing_support import StopWatch, SimpleStepContainer from .testing_support_async import AsyncStepTheory # ----------------------------------------------------------------------------- # ASYNC STEP EXAMPLES: # ----------------------------------------------------------------------------- # if python_version >= 3.5: # @step('an async coroutine step waits "{duration:f}" seconds') # @async_run_until_complete # async def step_async_step_waits_seconds(context, duration): # print("async_step: Should sleep for %.3f seconds" % duration) # await asyncio.sleep(duration) # # if python_version >= 3.4: # @step('a tagged-coroutine async step waits "{duration:f}" seconds') # @async_run_until_complete # @asyncio.coroutine # def step_async_step_waits_seconds2(context, duration): # print("async_step2: Should sleep for %.3f seconds" % duration) # yield from asyncio.sleep(duration) # # ----------------------------------------------------------------------------- # TEST MARKERS: # ----------------------------------------------------------------------------- python_version = float("%s.%s" % sys.version_info[:2]) # xfail = pytest.mark.xfail py34_or_newer = pytest.mark.skipif(python_version < 3.4, reason="Needs Python >= 3.4") # ----------------------------------------------------------------------------- # TESTSUITE: # ----------------------------------------------------------------------------- @py34_or_newer class TestAsyncStepDecorator34(object): def test_step_decorator_async_run_until_complete2(self): step_container = SimpleStepContainer() with use_step_import_modules(step_container): # -- STEP-DEFINITIONS EXAMPLE (as MODULE SNIPPET): # VARIANT 2: Use @asyncio.coroutine def step_impl() from behave import step from behave.api.async_step import async_run_until_complete import asyncio @step('a tagged-coroutine async step waits "{duration:f}" seconds') @async_run_until_complete @asyncio.coroutine def step_async_step_waits_seconds2(context, duration): yield from asyncio.sleep(duration) # -- USES: async def step_impl(...) as async-step (coroutine) AsyncStepTheory.validate(step_async_step_waits_seconds2) # -- RUN ASYNC-STEP: Verify that it is behaving correctly. # ENSURE: Execution of async-step matches expected duration. context = Context(runner=Runner(config={})) with StopWatch() as stop_watch: step_async_step_waits_seconds2(context, duration=0.2) assert abs(stop_watch.duration - 0.2) <= 0.05 class TestAsyncContext(object): @staticmethod def make_context(): return Context(runner=Runner(config={})) def test_use_or_create_async_context__when_missing(self): # -- CASE: AsynContext attribute is created with default_name context = self.make_context() context._push() async_context = use_or_create_async_context(context) assert isinstance(async_context, AsyncContext) assert async_context.name == "async_context" assert getattr(context, "async_context", None) is async_context context._pop() assert getattr(context, "async_context", None) is None def test_use_or_create_async_context__when_exists(self): # -- CASE: AsynContext attribute exists with default_name context = self.make_context() async_context0 = context.async_context = AsyncContext() assert context.async_context.name == "async_context" assert hasattr(context, AsyncContext.default_name) async_context = use_or_create_async_context(context) assert isinstance(async_context, AsyncContext) assert async_context.name == "async_context" assert getattr(context, "async_context", None) is async_context assert async_context is async_context0 def test_use_or_create_async_context__when_missing_with_name(self): # -- CASE: AsynContext attribute is created with own name loop0 = Mock() context = self.make_context() async_context = use_or_create_async_context(context, "acontext", loop=loop0) assert isinstance(async_context, AsyncContext) assert async_context.name == "acontext" assert getattr(context, "acontext", None) is async_context assert async_context.loop is loop0 def test_use_or_create_async_context__when_exists_with_name(self): # -- CASE: AsynContext attribute exists with own name loop0 = Mock() context = self.make_context() async_context0 = context.acontext = AsyncContext(name="acontext", loop=loop0) assert context.acontext.name == "acontext" loop1 = Mock() async_context = use_or_create_async_context(context, "acontext", loop=loop1) assert isinstance(async_context, AsyncContext) assert async_context is async_context0 assert getattr(context, "acontext", None) is async_context assert async_context.loop is loop0 behave-1.2.6/tests/api/_test_async_step35.py0000644000076600000240000000604013244555737021057 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Unit tests for :mod:`behave.api.async_test` for Python 3.5 (or newer). """ # -- IMPORTS: from __future__ import absolute_import, print_function import sys from behave._stepimport import use_step_import_modules from behave.runner import Context, Runner import pytest from .testing_support import StopWatch, SimpleStepContainer from .testing_support_async import AsyncStepTheory # ----------------------------------------------------------------------------- # SUPPORT: # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- # ASYNC STEP EXAMPLES: # ----------------------------------------------------------------------------- # if python_version >= 3.5: # @step('an async coroutine step waits "{duration:f}" seconds') # @async_run_until_complete # async def step_async_step_waits_seconds(context, duration): # print("async_step: Should sleep for %.3f seconds" % duration) # await asyncio.sleep(duration) # # if python_version >= 3.4: # @step('a tagged-coroutine async step waits "{duration:f}" seconds') # @async_run_until_complete # @asyncio.coroutine # def step_async_step_waits_seconds2(context, duration): # print("async_step2: Should sleep for %.3f seconds" % duration) # yield from asyncio.sleep(duration) # # ----------------------------------------------------------------------------- # TEST MARKERS: # ----------------------------------------------------------------------------- # xfail = pytest.mark.xfail python_version = float("%s.%s" % sys.version_info[:2]) py35_or_newer = pytest.mark.skipif(python_version < 3.5, reason="Needs Python >= 3.5") # ----------------------------------------------------------------------------- # TESTSUITE: # ----------------------------------------------------------------------------- @py35_or_newer class TestAsyncStepDecorator35(object): def test_step_decorator_async_run_until_complete1(self): step_container = SimpleStepContainer() with use_step_import_modules(step_container): # -- STEP-DEFINITIONS EXAMPLE (as MODULE SNIPPET): # VARIANT 1: Use async def step_impl() from behave import step from behave.api.async_step import async_run_until_complete import asyncio @step('an async coroutine step waits "{duration:f}" seconds') @async_run_until_complete async def step_async_step_waits_seconds(context, duration): await asyncio.sleep(duration) # -- USES: async def step_impl(...) as async-step (coroutine) AsyncStepTheory.validate(step_async_step_waits_seconds) # -- RUN ASYNC-STEP: Verify that it is behaving correctly. # ENSURE: Execution of async-step matches expected duration. context = Context(runner=Runner(config={})) with StopWatch() as stop_watch: step_async_step_waits_seconds(context, 0.2) assert abs(stop_watch.duration - 0.2) <= 0.05 behave-1.2.6/tests/api/test_async_step.py0000644000076600000240000000127713244555737020557 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Unit test facade to protect py.test runner from Python 3.4/3.5 grammar changes. """ from __future__ import absolute_import import sys python_version = sys.version_info[:2] if python_version >= (3, 5): # -- PROTECTED-IMPORT: # Older Python version have problems with grammer extensions (async/await). from ._test_async_step35 import TestAsyncStepDecorator35 from ._test_async_step34 import TestAsyncStepDecorator34, TestAsyncContext elif (3, 4) <= python_version < (3, 5): # -- PROTECTED-IMPORT: # Older Python version have problems with grammer extensions (yield-from). from ._test_async_step34 import TestAsyncStepDecorator34, TestAsyncContext behave-1.2.6/tests/api/testing_support.py0000644000076600000240000000560413244555737020617 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Test support functionality for other tests. """ # -- IMPORTS: from __future__ import absolute_import from behave.step_registry import StepRegistry from behave.matchers import ParseMatcher, CFParseMatcher, RegexMatcher import time # ----------------------------------------------------------------------------- # TEST SUPPORT: # ----------------------------------------------------------------------------- class StopWatch(object): def __init__(self): self.start_time = None self.duration = None def start(self): self.start_time = self.now() self.duration = None def stop(self): self.duration = self.now() - self.start_time @staticmethod def now(): return time.time() @property def elapsed(self): assert self.start_time is not None return self.now() - self.start_time def __enter__(self): self.start() return self def __exit__(self, exc_type, exc_val, exc_tb): self.stop() # -- NEEDED-UNTIL: stepimport functionality is completly provided. class MatcherFactory(object): matcher_mapping = { "parse": ParseMatcher, "cfparse": CFParseMatcher, "re": RegexMatcher, } default_matcher = ParseMatcher def __init__(self, matcher_mapping=None, default_matcher=None): self.matcher_mapping = matcher_mapping or self.matcher_mapping self.default_matcher = default_matcher or self.default_matcher self.current_matcher = self.default_matcher self.type_registry = {} # self.type_registry = ParseMatcher.custom_types.copy() def register_type(self, **kwargs): self.type_registry.update(**kwargs) def use_step_matcher(self, name): self.current_matcher = self.matcher_mapping[name] def use_default_step_matcher(self, name=None): if name: self.default_matcher = self.matcher_mapping[name] self.current_matcher = self.default_matcher def make_matcher(self, func, step_text, step_type=None): return self.current_matcher(func, step_text, step_type=step_type, custom_types=self.type_registry) def step_matcher(self, name): """ DEPRECATED, use :method:`~MatcherFactory.use_step_matcher()` instead. """ # -- BACKWARD-COMPATIBLE NAME: Mark as deprecated. import warnings warnings.warn("Use 'use_step_matcher()' instead", PendingDeprecationWarning, stacklevel=2) self.use_step_matcher(name) class SimpleStepContainer(object): def __init__(self, step_registry=None): if step_registry is None: step_registry = StepRegistry() matcher_factory = MatcherFactory() self.step_registry = step_registry self.step_registry.matcher_factory = matcher_factory self.matcher_factory = matcher_factory behave-1.2.6/tests/api/testing_support_async.py0000644000076600000240000000111313244555737022003 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Unit test support for :mod:`behave.api.async_test` tests. """ import inspect # ----------------------------------------------------------------------------- # TEST SUPPORT: # ----------------------------------------------------------------------------- class AsyncStepTheory(object): @staticmethod def ensure_normal_function(func): if hasattr(inspect, "isawaitable"): # -- SINCE: Python 3.5 assert not inspect.isawaitable(func) @classmethod def validate(cls, func): cls.ensure_normal_function(func) behave-1.2.6/tests/issues/0000755000076600000240000000000013244564040015514 5ustar jensstaff00000000000000behave-1.2.6/tests/issues/test_issue0336.py0000644000076600000240000000506413244555737020613 0ustar jensstaff00000000000000# -*- coding: UTF-8 """ Test issue #336: Unicode problem w/ exception traceback on Windows (python2.7) Default encoding (unicode-escape) of text() causes problems w/ exception tracebacks. STATUS: BASICS SOLVED (default encoding changed) ALTERNATIVE SOLUTIONS: * Use traceback2: Returns unicode-string when calling traceback. * Use text(traceback.format_exc(), sys.getfilesystemencoding(), "replace") where the text-conversion of a traceback is used. MAYBE traceback_to_text(traceback.format_exc()) """ from __future__ import print_function from behave.textutil import text import pytest import six # require_python2 = pytest.mark.skipif(not six.PY2, reason="REQUIRE: Python2") # require_python3 = pytest.mark.skipif(six.PY2, reason="REQUIRE: Python3 (or newer)") class TestIssue(object): # -- USE SAVED TRACEBACK: No need to require Windows platform. traceback_bytes = br"""\ Traceback (most recent call last): File "C:\Users\alice\xxx\behave\model.py", line 1456, in run match.run(runner.context) File "C:\Users\alice\xxx\behave\model.py", line 1903, in run self.func(context, args, *kwargs) File "features\steps\my_steps.py", line 210, in step_impl directories, task_names, reg_keys) AssertionError """ traceback_file_line_texts = [ # -- NOTE: Cannot use: ur'C:\Users ..." => \U is a unicode escape char. u'File "C:\\Users\\alice\\xxx\\behave\\model.py", line 1456, in run', u'File "C:\\Users\\alice\\xxx\\behave\\model.py", line 1903, in run', u'File "features\\steps\\my_steps.py", line 210, in step_impl', ] # @require_python2 def test_issue__with_default_encoding(self): """Test ensures that problem is fixed with default encoding""" text2 = text(self.traceback_bytes) assert isinstance(self.traceback_bytes, bytes) assert isinstance(text2, six.text_type) for file_line_text in self.traceback_file_line_texts: assert file_line_text in text2 # @require_python2 def test__problem_exists_with_problematic_encoding(self): """Test ensures that problem exists with encoding=unicode-escape""" # -- NOTE: Explicit use of problematic encoding problematic_encoding = "unicode-escape" text2 = text(self.traceback_bytes, problematic_encoding) print("TEXT: "+ text2) assert isinstance(self.traceback_bytes, bytes) assert isinstance(text2, six.text_type) # -- VERIFY BAD-OUTCOME: With problematic encoding file_line_text = self.traceback_file_line_texts[0] assert file_line_text not in text2 behave-1.2.6/tests/issues/test_issue0449.py0000644000076600000240000000316713244555737020622 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ SIMILAR: #453 NOTE: traceback2 (backport for Python2) solves the problem. Either Exception text (as summary) or traceback python line shows special characters correctly. .. code-block:: python # -*- coding=utf-8 -*- from behave import * from hamcrest.core import assert_that, equal_to @step("Russian text") def foo(stop): assert_that(False, equal_to(True), u"Всё очень плохо") # cyrillic And I also have UTF-8 as my console charset. Running this code leads to "Assertion Failed: 'ascii' codec can't encode characters in position 0-5: ordinal not in range(128)" error. That is becase behave.textutil.text returns six.text_type(e) where 'e' is exception (https://github.com/behave/behave/blob/master/behave/textutil.py#L83). Changing line 83 to six.text_type(value) solves this issue. """ from __future__ import print_function from behave.textutil import text from hamcrest.core import assert_that, equal_to from hamcrest.library import contains_string import six import pytest if six.PY2: import traceback2 as traceback else: import traceback def foo(): assert_that(False, equal_to(True), u"Всё очень плохо") # cyrillic @pytest.mark.parametrize("encoding", [None, "UTF-8", "unicode_escape"]) def test_issue(encoding): expected = u"Всё очень плохо" try: foo() except Exception as e: text2 = traceback.format_exc() text3 = text(text2, encoding) print(u"EXCEPTION-TEXT: %s" % text3) print(u"text2: "+ text2) assert_that(text3, contains_string(u"AssertionError: Всё очень плохо")) behave-1.2.6/tests/issues/test_issue0453.py0000644000076600000240000000404113244555737020605 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ MAYBE: DUPLICATES: #449 NOTE: traceback2 (backport for Python2) solves the problem. def foo(stop): raise Exception(u"по русски") Result: File "features/steps/steps.py", line 8, in foo raise Exception(u"по Ñ�Ñ�Ñ�Ñ�ки") <-- This is not Exception: по русски <-- This is OK It happens here (https://github.com/behave/behave/blob/master/behave/model.py#L1299) because traceback.format_exc() creates incorrect text. You then convert it using _text() and result is also bad. To fix it, you may take e.message which is correct and traceback.format_tb(sys.exc_info()[2]) which is also correct. """ from __future__ import print_function from behave.textutil import text from hamcrest.core import assert_that, equal_to from hamcrest.library import contains_string import six import pytest if six.PY2: import traceback2 as traceback else: import traceback def problematic_step_impl(context): raise Exception(u"по русски") @pytest.mark.parametrize("encoding", [None, "UTF-8", "unicode_escape"]) def test_issue(encoding): """ with encoding=UTF-8: File "/Users/jens/se/behave_main.unicode/tests/issues/test_issue0453.py", line 31, in problematic_step_impl raise Exception(u"по русски") Exception: \u043f\u043e \u0440\u0443\u0441\u0441\u043a\u0438 with encoding=unicode_escape: File "/Users/jens/se/behave_main.unicode/tests/issues/test_issue0453.py", line 31, in problematic_step_impl raise Exception(u"по русски") Exception: по русски """ context = None text2 = "" expected_text = u"по русски" try: problematic_step_impl(context) except Exception: text2 = traceback.format_exc() text3 = text(text2, encoding) print(u"EXCEPTION-TEXT: %s" % text3) assert_that(text3, contains_string(u'raise Exception(u"по русски"')) assert_that(text3, contains_string(u"Exception: по русски")) behave-1.2.6/tests/issues/test_issue0458.py0000644000076600000240000000314213244555737020613 0ustar jensstaff00000000000000# -*- coding: UTF-8 """ Test issue #458: Traceback (most recent call last): File "/usr/local/bin/behave", line 11, in sys.exit(main()) File "/Library/Python/2.7/site-packages/behave/__main__.py", line 123, in main print(u"Exception %s: %s" % (e.__class__.__name__, text)) UnicodeEncodeError: 'ascii' codec can't encode character u'\u2019' in position 69: ordinal not in range(128) try: failed = runner.run() except ParserError as e: print(u"ParseError: %s" % e) except ConfigError as e: print(u"ConfigError: %s" % e) except FileNotFoundError as e: print(u"FileNotFoundError: %s" % e) except InvalidFileLocationError as e: print(u"InvalidFileLocationError: %s" % e) except InvalidFilenameError as e: print(u"InvalidFilenameError: %s" % e) except Exception as e: # -- DIAGNOSTICS: text = _text(e) print(u"Exception %s: %s" % (e.__class__.__name__, text)) raise """ from behave.textutil import text as _text import pytest def raise_exception(exception_class, message): raise exception_class(message) @pytest.mark.parametrize("exception_class, message", [ (AssertionError, "Ärgernis"), (AssertionError, u"Ärgernis"), (RuntimeError, "Übermut"), (RuntimeError, u"Übermut"), ]) def test_issue(exception_class, message): with pytest.raises(exception_class) as e: # runner.run() raise_exception(exception_class, message) # -- SHOULD NOT RAISE EXCEPTION HERE: text = _text(e) # -- DIAGNOSTICS: print(u"text"+ text) print(u"exception: %s" % e) behave-1.2.6/tests/issues/test_issue0495.py0000644000076600000240000000470513244555737020622 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Explore issue #495. HINT: Problem can be reproduced if you remove encoding-declaration from above. :: Traceback (most recent call last): File "/usr/local/bin/behave", line 11, in sys.exit(main()) File "/usr/local/lib/python2.7/dist-packages/behave/__main__.py", line 109, in main failed = runner.run() ... File "/usr/local/lib/python2.7/dist-packages/behave/model.py", line 919, in run runner.run_hook('after_scenario', runner.context, self) File "/usr/local/lib/python2.7/dist-packages/behave/runner.py", line 405, in run_hook self.hooks[name](context, *args) File "/mnt/work/test/lib/logcapture.py", line 22, in f v = h.getvalue() File "/usr/local/lib/python2.7/dist-packages/behave/log_capture.py", line 99, in getvalue return '\n'.join(self.formatter.format(r) for r in self.buffer) UnicodeDecodeError: 'ascii' codec can't decode byte 0xc2 in position 3807: ordinal not in range(128) """ from behave.log_capture import capture from behave.configuration import Configuration import logging import pytest # ----------------------------------------------------------------------------- # TEST SUPPORT # ----------------------------------------------------------------------------- # def ensure_logging_setup(): # logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") class SimpleContext(object): pass # ----------------------------------------------------------------------------- # TESTS: # ----------------------------------------------------------------------------- @pytest.mark.parametrize("log_message", [ u"Hello Alice", # case: unproblematic (GOOD CASE) u"Ärgernis ist überall", # case: unicode-string "Ärgernis", # case: byte-string (use encoding-declaration above) ]) def test_issue(log_message): @capture(level=logging.INFO) def hook_after_scenario(context, message): logging.warn(message) raise RuntimeError() # -- PREPARE: # ensure_logging_setup() context = SimpleContext() context.config = Configuration("", load_config=False, log_capture=True, logging_format="%(levelname)s: %(message)s", logging_level=logging.INFO ) context.config.setup_logging() # -- EXECUTE: with pytest.raises(RuntimeError): hook_after_scenario(context, log_message) # EXPECT: UnicodeError is not raised behave-1.2.6/tests/README.txt0000644000076600000240000000046013244555737015714 0ustar jensstaff00000000000000This is the new directory root for: * unit tests * functional tests (that do not use feature files) The tests here use the :pypi:`pytest` test framework for testing. .. note:: Other tests in the collocated "../test/" directory will be eventually cleaned and converted in (clean) pytests ;-) behave-1.2.6/tests/unit/0000755000076600000240000000000013244564040015160 5ustar jensstaff00000000000000behave-1.2.6/tests/unit/__init__.py0000644000076600000240000000000013244555737017274 0ustar jensstaff00000000000000behave-1.2.6/tests/unit/test_behave4cmd_command_shell_proc.py0000644000076600000240000001236413244555737024526 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Regular expressions for winpath: http://regexlib.com/Search.aspx?k=file+name ^(([a-zA-Z]:|\\)\\)?(((\.)|(\.\.)|([^\\/:\*\?"\|<>\. ](([^\\/:\*\?"\|<>\. ])|([^\\/:\*\?"\|<>]*[^\\/:\*\?"\|<>\. ]))?))\\)*[^\\/:\*\?"\|<>\. ](([^\\/:\*\?"\|<>\. ])|([^\\/:\*\?"\|<>]*[^\\/:\*\?"\|<>\. ]))?$ https://github.com/kgryte/regex-filename-windows/blob/master/lib/index.js REGEX: /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+|)([\\\/]|)([\s\S]*?)((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(?:[\\\/]*)$/ Splits a Windows filename. Modified from Node.js [source]{@link https://github.com/nodejs/node/blob/1a3b295d0f46b2189bd853800b1e63ab4d106b66/lib/path.js#L65}. """ from __future__ import absolute_import from behave4cmd0.command_shell_proc import \ BehaveWinCommandOutputProcessor, \ TracebackLineNormalizer, ExceptionWithPathNormalizer import re import pytest # ----------------------------------------------------------------------------- # IMPLEMENTATION # ----------------------------------------------------------------------------- # winpath_pattern = ur"^($([A-Za-z]:(\[^\]+)*)|((\[^\]+)*)|([^\]+\[^\]+)*)$" winpath_pattern = u"^([A-Za-z]:(\\[\w\.\-]+)+)|((\\[\w\.\-]+)*)|(\s[\w\.\-]+([\w\.\-]+)*)$" winpath_re = re.compile(winpath_pattern, re.UNICODE) class PathNormalizer(object): def normalize(self, output): pattern = u'^.*$' def __call__(self, output): pass # ----------------------------------------------------------------------------- # TEST CANDIDATES: # ----------------------------------------------------------------------------- line_processor_configerrors = [ ExceptionWithPathNormalizer( u"ConfigError: No steps directory in '(?P.*)'", "ConfigError: No steps directory in"), BehaveWinCommandOutputProcessor.line_processors[1], ] line_processor_parsererrors = [ ExceptionWithPathNormalizer( u'ParserError: Failed to parse "(?P.*)"', u'ParserError: Failed to parse'), BehaveWinCommandOutputProcessor.line_processors[2], ] line_processor_ioerrors = [ ExceptionWithPathNormalizer( # ur"No such file or directory: '(?P.*)'", # u"No such file or directory:"), # IOError # ur"Error: \\[Errno 2\\] No such file or directory: '(?P.*)'", u"No such file or directory: '(?P.*)'", u"[Errno 2] No such file or directory:"), # IOError BehaveWinCommandOutputProcessor.line_processors[3], ] line_processor_traceback = [ ExceptionWithPathNormalizer( '^\s*File "(?P.*)", line \d+, in ', ' File "'), BehaveWinCommandOutputProcessor.line_processors[4], ] # ----------------------------------------------------------------------------- # TEST SUITE # ----------------------------------------------------------------------------- xfail = pytest.mark.xfail() class TestWinpathRegex(object): @pytest.mark.parametrize("winpath", [ u'C:\\foo\\bar\\alice.txt', u'C:\\foo\\bar', u'C:\\alice.txt', u'C:\\.verbose', u'.\\foo\\bar\\alice.txt', u'..\\foo\\..\\bar\\alice.txt', u'foo\\bar\\alice.txt', u'alice.txt', ]) def test_match__with_valid_winpath(self, winpath): mo = winpath_re.match(winpath) assert mo is not None @xfail @pytest.mark.parametrize("winpath", [ u'2:\\foo\\bar\\alice.txt', u'C:\\bar\\alice.txt', ]) def test_match__with_invalid_winpath(self, winpath): mo = winpath_re.match(winpath) assert mo is None class TestPathNormalizer(object): @pytest.mark.parametrize("output, expected", [ (u"ConfigError: No steps directory in 'C:\\one\\two\\three.txt'", u"ConfigError: No steps directory in 'C:/one/two/three.txt'"), ]) def test_call__with_pattern1(self, output, expected): for line_processor in line_processor_configerrors: actual = line_processor(output) assert actual == expected # ParserError: Failed to parse "(?P.*)" @pytest.mark.parametrize("output, expected", [ (u'ParserError: Failed to parse "C:\\one\\two\\three.txt"', u'ParserError: Failed to parse "C:/one/two/three.txt"'), ]) def test_call__with_pattern2(self, output, expected): for line_processor in line_processor_parsererrors: actual = line_processor(output) assert actual == expected @pytest.mark.parametrize("output, expected", [ (u"Error: [Errno 2] No such file or directory: 'C:\\one\\two\\three.txt'", u"Error: [Errno 2] No such file or directory: 'C:/one/two/three.txt'"), ]) def test_call__with_pattern3(self, output, expected): for index, line_processor in enumerate(line_processor_ioerrors): actual = line_processor(output) assert actual == expected, "line_processor %s" % index @pytest.mark.parametrize("output, expected", [ (u' File "C:\\one\\two\\three.txt", line 123, in xxx_some_method', u' File "C:/one/two/three.txt", line 123, in xxx_some_method'), ]) def test_call__with_pattern4(self, output, expected): for index, line_processor in enumerate(line_processor_traceback): actual = line_processor(output) assert actual == expected, "line_processor %s" % index behave-1.2.6/tests/unit/test_capture.py0000644000076600000240000002351213244555737020254 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Unittests for :mod:`behave.capture` module. """ from __future__ import absolute_import, print_function import sys from behave.capture import Captured, CaptureController from mock import Mock import pytest # ----------------------------------------------------------------------------- # TEST SUPPORT: # ----------------------------------------------------------------------------- def create_capture_controller(config=None): if not config: config = Mock() config.stdout_capture = True config.stderr_capture = True config.log_capture = True config.logging_filter = None config.logging_level = "INFO" return CaptureController(config) def setup_capture_controller(capture_controller, context=None): if not context: context = Mock() context.aborted = False context.config = capture_controller.config capture_controller.setup_capture(context) # ----------------------------------------------------------------------------- # FIXTURES: # ----------------------------------------------------------------------------- @pytest.fixture def capture_controller(): """Provides a capture_controller that is already setup and automatically performs a teardown afterwards. """ capture_controller = create_capture_controller() setup_capture_controller(capture_controller) yield capture_controller # -- CLEANUP: capture_controller.teardown_capture() # ----------------------------------------------------------------------------- # TEST SUITE: # ----------------------------------------------------------------------------- class TestCaptured(object): def test_default_ctor(self): captured = Captured() assert captured.stdout == u"" assert captured.stderr == u"" assert captured.log_output == u"" assert not captured def test_ctor_with_params(self): captured = Captured(u"STDOUT", u"STDERR", u"LOG_OUTPUT") assert captured.stdout == u"STDOUT" assert captured.stderr == u"STDERR" assert captured.log_output == u"LOG_OUTPUT" captured = Captured(stdout=u"STDOUT") assert captured.stdout == u"STDOUT" assert captured.stderr == u"" assert captured.log_output == u"" captured = Captured(stderr=u"STDERR") assert captured.stdout == u"" assert captured.stderr == u"STDERR" assert captured.log_output == u"" captured = Captured(log_output=u"LOG_OUTPUT") assert captured.stdout == u"" assert captured.stderr == u"" assert captured.log_output == u"LOG_OUTPUT" def test_reset(self): captured = Captured("STDOUT", "STDERR", "LOG_OUTPUT") captured.reset() assert captured.stdout == u"" assert captured.stderr == u"" assert captured.log_output == u"" assert not captured def test_bool_conversion__returns_false_without_captured_output(self): captured = Captured() assert bool(captured) == False @pytest.mark.parametrize("params", [ dict(stdout="xxx"), dict(stderr="yyy"), dict(log_output="zzz"), dict(stderr="yyy", log_output="zzz"), dict(stdout="xxx", stderr="yyy", log_output="zzz"), ]) def test_bool_conversion__returns_true_with_captured_output(self, params): captured = Captured(**params) assert bool(captured) @pytest.mark.parametrize("params, expected", [ (dict(), ""), (dict(stdout="STDOUT"), "STDOUT"), (dict(stderr="STDERR"), "STDERR"), (dict(log_output="LOG_OUTPUT"), "LOG_OUTPUT"), (dict(stdout="STDOUT", stderr="STDERR"), "STDOUT\nSTDERR"), (dict(stdout="STDOUT", log_output="LOG_OUTPUT"), "STDOUT\nLOG_OUTPUT"), (dict(stdout="STDOUT", stderr="STDERR", log_output="LOG_OUTPUT"), "STDOUT\nSTDERR\nLOG_OUTPUT"), ]) def test_output__contains_concatenated_parts(self, params, expected): captured = Captured(**params) assert captured.output == expected def test_add__without_captured_data(self): captured1 = Captured() captured2 = Captured("STDOUT", "STDERR", "LOG_OUTPUT") captured1.add(captured2) assert captured1.stdout == "STDOUT" assert captured1.stderr == "STDERR" assert captured1.log_output == "LOG_OUTPUT" def test_add__with_captured_data(self): captured1 = Captured("stdout1", "stderr1", "log_output1") captured2 = Captured("STDOUT2", "STDERR2", "LOG_OUTPUT2") captured1.add(captured2) assert captured1.stdout == "stdout1\nSTDOUT2" assert captured1.stderr == "stderr1\nSTDERR2" assert captured1.log_output == "log_output1\nLOG_OUTPUT2" def test_operator_add(self): captured1 = Captured("stdout1", "stderr1", "log_output1") captured2 = Captured("STDOUT2", "STDERR2", "LOG_OUTPUT2") captured3 = captured1 + captured2 assert captured3.stdout == "stdout1\nSTDOUT2" assert captured3.stderr == "stderr1\nSTDERR2" assert captured3.log_output == "log_output1\nLOG_OUTPUT2" # -- ENSURE: captured1 is not modified. assert captured1.stdout == "stdout1" assert captured1.stderr == "stderr1" assert captured1.log_output == "log_output1" # -- ENSURE: captured2 is not modified. assert captured2.stdout == "STDOUT2" assert captured2.stderr == "STDERR2" assert captured2.log_output == "LOG_OUTPUT2" def test_operator_iadd(self): captured1 = Captured("stdout1", "stderr1", "log_output1") captured2 = Captured("STDOUT2", "STDERR2", "LOG_OUTPUT2") captured1 += captured2 assert captured1.stdout == "stdout1\nSTDOUT2" assert captured1.stderr == "stderr1\nSTDERR2" assert captured1.log_output == "log_output1\nLOG_OUTPUT2" # -- ENSURE: captured2 is not modified. assert captured2.stdout == "STDOUT2" assert captured2.stderr == "STDERR2" assert captured2.log_output == "LOG_OUTPUT2" def test_make_report__with_all_sections(self): captured = Captured(stdout="xxx", stderr="yyy", log_output="zzz") expected = """\ Captured stdout: xxx Captured stderr: yyy Captured logging: zzz""" assert captured.make_report() == expected def test_make_report__should_only_contain_nonempty_data_sections(self): captured1 = Captured(stdout="xxx") expected = "Captured stdout:\nxxx" assert captured1.make_report() == expected captured2 = Captured(stderr="yyy") expected = "Captured stderr:\nyyy" assert captured2.make_report() == expected captured3 = Captured(log_output="zzz") expected = "Captured logging:\nzzz" assert captured3.make_report() == expected class Theory4ActiveCaptureController(object): @staticmethod def check_invariants(controller): if controller.config.capture_stdout: assert controller.stdout_capture is not None assert sys.stdout is controller.stdout_capture else: assert controller.stdout_capture is None assert sys.stdout is not controller.stdout_capture if controller.config.capture_stderr: assert controller.stderr_capture is not None assert sys.stderr is controller.stderr_capture else: assert controller.stderr_capture is None assert sys.stderr is not controller.stderr_capture if controller.config.capture_log: assert controller.log_capture is not None # assert sys.stderr is controller.stderr_capture else: assert controller.log_capture is None # assert sys.stderr is not controller.stderr_capture class TestCaptureController(object): # @pytest.no_capture def test_basics(self): capture_controller = create_capture_controller() context = Mock() context.aborted = False context.config = capture_controller.config capture_controller.setup_capture(context) # XXX AVOID: Due to pytest capture mode # Theory4ActiveCaptureController.check_invariants(capture_controller) capture_controller.start_capture() sys.stdout.write("HELLO\n") sys.stderr.write("world\n") capture_controller.stop_capture() assert capture_controller.captured.output == "HELLO\nworld\n" # -- FINALLY: capture_controller.teardown_capture() def test_capturing__retrieve_captured_several_times(self, capture_controller): capture_controller.start_capture() sys.stdout.write("HELLO\n") sys.stderr.write("Alice\n") assert capture_controller.captured.output == "HELLO\nAlice\n" print("Sam") sys.stderr.write("Bob\n") capture_controller.stop_capture() assert capture_controller.captured.output == "HELLO\nSam\nAlice\nBob\n" def test_capturing__with_several_start_stop_cycles(self, capture_controller): capture_controller.start_capture() sys.stdout.write("HELLO\n") sys.stderr.write("Alice\n") capture_controller.stop_capture() assert capture_controller.captured.output == "HELLO\nAlice\n" sys.stdout.write("__UNCAPTURED:stdout;\n") sys.stderr.write("__UNCAPTURED:stderr;\n") capture_controller.start_capture() sys.stdout.write("Sam\n") sys.stderr.write("Bob\n") capture_controller.stop_capture() assert capture_controller.captured.output == "HELLO\nSam\nAlice\nBob\n" def test_make_capture_report(self, capture_controller): capture_controller.start_capture() print("HELLO") sys.stderr.write("Alice\n") capture_controller.stop_capture() report = capture_controller.make_capture_report() expected = """\ Captured stdout: HELLO Captured stderr: Alice""" assert report == expected behave-1.2.6/tests/unit/test_context_cleanups.py0000644000076600000240000002136313244555737022171 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Test the cleanup-funcs functionality provided via :meth:`behave.runner.Context.add_cleanup()` method. OPEN ISSUES: * Should cleanup_func use context args, like: cleanup_func(context) ? * Should formatters be somehow informed about cleanup-errors ? * Should context._pop() calls be protected against raised exceptions from on_cleanup_error() implementations ? """ from __future__ import print_function from behave.runner import Context, scoped_context_layer from contextlib import contextmanager from mock import Mock, NonCallableMock import pytest # ------------------------------------------------------------------------------ # TEST SUPPORT: # ------------------------------------------------------------------------------ def cleanup_func(): pass class CleanupFunction(object): def __init__(self, name="CLEANUP-FUNC", listener=None): self.name = name self.listener = listener def __call__(self, *args, **kwargs): if self.listener: message = "called:%s" % self.name self.listener(message) class CallListener(object): def __init__(self): self.collected = [] def __call__(self, message): self.collected.append(message) # ------------------------------------------------------------------------------ # TESTS: # ------------------------------------------------------------------------------ class TestContextCleanup(object): def test_cleanup_func_is_called_when_context_frame_is_popped(self): my_cleanup = Mock(spec=cleanup_func) context = Context(runner=Mock()) with scoped_context_layer(context): # CALLS-HERE: context._push() context.add_cleanup(my_cleanup) # -- ENSURE: Not called before context._pop() my_cleanup.assert_not_called() # CALLS-HERE: context._pop() my_cleanup.assert_called_once() # MAYBE: my_cleanup2.assert_called_with(context) def test_cleanup_funcs_are_called_when_context_frame_is_popped(self): my_cleanup1 = Mock(spec=cleanup_func) my_cleanup2 = Mock(spec=cleanup_func) # -- SETUP: context = Context(runner=Mock()) with scoped_context_layer(context): context.add_cleanup(my_cleanup1) context.add_cleanup(my_cleanup2) # -- ENSURE: Not called before context._pop() my_cleanup1.assert_not_called() my_cleanup2.assert_not_called() # -- CALLS-HERE: context._pop() my_cleanup1.assert_called_once() my_cleanup2.assert_called_once() def test_cleanup_funcs_are_called_in_reversed_order(self): call_listener = CallListener() my_cleanup1A = CleanupFunction("CLEANUP1", listener=call_listener) my_cleanup2A = CleanupFunction("CLEANUP2", listener=call_listener) my_cleanup1 = Mock(side_effect=my_cleanup1A) my_cleanup2 = Mock(side_effect=my_cleanup2A) # -- SETUP: context = Context(runner=Mock()) with scoped_context_layer(context): context.add_cleanup(my_cleanup1) context.add_cleanup(my_cleanup2) my_cleanup1.assert_not_called() my_cleanup2.assert_not_called() # -- ENSURE: Reversed order of cleanup calls expected_call_order = ["called:CLEANUP2", "called:CLEANUP1"] assert call_listener.collected == expected_call_order my_cleanup1.assert_called_once() my_cleanup2.assert_called_once() def test_cleanup_funcs_on_two_context_frames(self): call_listener = CallListener() my_cleanup_A1 = CleanupFunction("CLEANUP_A1", listener=call_listener) my_cleanup_A2 = CleanupFunction("CLEANUP_A2", listener=call_listener) my_cleanup_B1 = CleanupFunction("CLEANUP_B1", listener=call_listener) my_cleanup_B2 = CleanupFunction("CLEANUP_B2", listener=call_listener) my_cleanup_B3 = CleanupFunction("CLEANUP_B3", listener=call_listener) my_cleanup_A1M = Mock(side_effect=my_cleanup_A1) my_cleanup_A2M = Mock(side_effect=my_cleanup_A2) my_cleanup_B1M = Mock(side_effect=my_cleanup_B1) my_cleanup_B2M = Mock(side_effect=my_cleanup_B2) my_cleanup_B3M = Mock(side_effect=my_cleanup_B3) # -- SETUP: context = Context(runner=Mock()) with scoped_context_layer(context): # -- LAYER A: context.add_cleanup(my_cleanup_A1M) context.add_cleanup(my_cleanup_A2M) with scoped_context_layer(context): # -- LAYER B: context.add_cleanup(my_cleanup_B1M) context.add_cleanup(my_cleanup_B2M) context.add_cleanup(my_cleanup_B3M) my_cleanup_B1M.assert_not_called() my_cleanup_B2M.assert_not_called() my_cleanup_B3M.assert_not_called() # -- context.pop(LAYER_B): Call cleanups for Bx expected_call_order = [ "called:CLEANUP_B3", "called:CLEANUP_B2", "called:CLEANUP_B1", ] assert call_listener.collected == expected_call_order my_cleanup_A1M.assert_not_called() my_cleanup_A2M.assert_not_called() my_cleanup_B1M.assert_called_once() my_cleanup_B2M.assert_called_once() my_cleanup_B3M.assert_called_once() # -- context.pop(LAYER_A): Call cleanups for Ax expected_call_order = [ "called:CLEANUP_B3", "called:CLEANUP_B2", "called:CLEANUP_B1", "called:CLEANUP_A2", "called:CLEANUP_A1", ] assert call_listener.collected == expected_call_order my_cleanup_A1M.assert_called_once() my_cleanup_A2M.assert_called_once() my_cleanup_B1M.assert_called_once() my_cleanup_B2M.assert_called_once() my_cleanup_B3M.assert_called_once() def test_add_cleanup__rejects_noncallable_cleanup_func(self): class NonCallable(object): pass non_callable = NonCallable() context = Context(runner=Mock()) with pytest.raises(AssertionError) as e: with scoped_context_layer(context): context.add_cleanup(non_callable) assert "REQUIRES: callable(cleanup_func)" in str(e) def test_on_cleanup_error__prints_error_by_default(self, capsys): def bad_cleanup_func(): raise RuntimeError("in CLEANUP call") bad_cleanup = Mock(side_effect=bad_cleanup_func) context = Context(runner=Mock()) with pytest.raises(RuntimeError): with scoped_context_layer(context): context.add_cleanup(bad_cleanup) captured_output, _ = capsys.readouterr() bad_cleanup.assert_called() assert "CLEANUP-ERROR in " in captured_output assert "RuntimeError: in CLEANUP call" in captured_output # -- FOR DIAGNOSTICS: print(captured_output) def test_on_cleanup_error__is_called_if_defined(self): def bad_cleanup(): raise RuntimeError("in CLEANUP call") def handle_cleanup_error(context, cleanup_func, exception): print("CALLED: handle_cleanup_error") context = Context(runner=Mock()) handle_cleanup_error_func = Mock(spec=handle_cleanup_error) with pytest.raises(RuntimeError): with scoped_context_layer(context): context.on_cleanup_error = handle_cleanup_error_func context.add_cleanup(bad_cleanup) handle_cleanup_error_func.assert_called_once() # expected_args = (context, handle_cleanup_error_func, # RuntimeError("in CLEANUP call")) # handle_cleanup_error_func.assert_called_with(*expected_args) def test_on_cleanup_error__may_be_called_several_times_per_cleanup(self): def bad_cleanup1(): raise RuntimeError("CLEANUP_1") def bad_cleanup2(): raise RuntimeError("CLEANUP_2") class CleanupErrorCollector(object): def __init__(self): self.collected = [] def __call__(self, context, cleanup_func, exception): self.collected.append((context, cleanup_func, exception)) context = Context(runner=Mock()) collect_cleanup_error = CleanupErrorCollector() with pytest.raises(RuntimeError): with scoped_context_layer(context): context.on_cleanup_error = collect_cleanup_error context.add_cleanup(bad_cleanup1) context.add_cleanup(bad_cleanup2) expected = [ (context, bad_cleanup2, RuntimeError("CLEANUP_2")), (context, bad_cleanup1, RuntimeError("CLEANUP_1")), ] assert len(collect_cleanup_error.collected) == 2 assert collect_cleanup_error.collected[0][:-1] == expected[0][:-1] assert collect_cleanup_error.collected[1][:-1] == expected[1][:-1] behave-1.2.6/tests/unit/test_explore_generator.py0000644000076600000240000000476113244555737022342 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Explore contextmanager/generator behaviour when: * setup step raises error (generator step) * with-block raises error """ import pytest from contextlib import contextmanager class TestContextManager(object): def test_when_setup_raises_error_then_cleanup_isnot_called(self): @contextmanager def foo(checkpoints): checkpoints.append("foo.setup.begin") raise RuntimeError("OOPS") checkpoints.append("foo.setup.done") yield checkpoints.append("foo.cleanup") checkpoints = [] with pytest.raises(RuntimeError): with foo(checkpoints): checkpoints.append("foo.with-block") assert checkpoints == ["foo.setup.begin"] def test_with_try_finally_when_setup_raises_error_then_cleanup_is_called(self): @contextmanager def foo(checkpoints): try: checkpoints.append("foo.setup.begin") raise RuntimeError("OOPS") checkpoints.append("foo.setup.done") yield finally: checkpoints.append("foo.cleanup") checkpoints = [] with pytest.raises(RuntimeError): with foo(checkpoints): checkpoints.append("foo.with-block") assert checkpoints == ["foo.setup.begin", "foo.cleanup"] class TestGenerator(object): def test_when_setup_raises_error_then_cleanup_isnot_called(self): def foo(checkpoints): checkpoints.append("foo.setup.begin") raise RuntimeError("OOPS") checkpoints.append("foo.setup.done") yield checkpoints.append("foo.cleanup") checkpoints = [] with pytest.raises(RuntimeError): for iter in foo(checkpoints): checkpoints.append("foo.with-block") assert checkpoints == ["foo.setup.begin"] def test_with_try_finally_when_setup_raises_error_then_cleanup_is_called(self): def foo(checkpoints): try: checkpoints.append("foo.setup.begin") raise RuntimeError("OOPS") checkpoints.append("foo.setup.done") yield finally: checkpoints.append("foo.cleanup") checkpoints = [] with pytest.raises(RuntimeError): for iter in foo(checkpoints): checkpoints.append("foo.with-block") assert checkpoints == ["foo.setup.begin", "foo.cleanup"] behave-1.2.6/tests/unit/test_fixture.py0000644000076600000240000011431113244555737020275 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Unit tests for :mod:`behave.fixture` module. """ from __future__ import absolute_import, print_function import sys import inspect from behave.fixture import \ fixture, use_fixture, is_context_manager, InvalidFixtureError, \ use_fixture_by_tag, use_composite_fixture_with, fixture_call_params from behave.runner import Context, CleanupError, scoped_context_layer from behave._types import Unknown import pytest from mock import Mock import six # ----------------------------------------------------------------------------- # TEST SUPPORT # ----------------------------------------------------------------------------- def make_runtime_context(runner=None): """Build a runtime/runner context for the tests here (partly faked). :return: Runtime context object (normally used by the runner). """ if runner is None: runner = Mock() runner.config = Mock() return Context(runner) class BasicFixture(object): """Helper class used in behave fixtures (test-support).""" def __init__(self, *args, **kwargs): setup_called = kwargs.pop("_setup_called", True) name = kwargs.get("name", self.__class__.__name__) self.name = name self.args = args self.kwargs = kwargs.copy() self.setup_called = setup_called self.cleanup_called = False def __del__(self): if not self.cleanup_called: # print("LATE-CLEANUP (in dtor): %s" % self.name) self.cleanup() @classmethod def setup(cls, *args, **kwargs): name = kwargs.get("name", cls.__name__) print("%s.setup: %s, kwargs=%r" % (cls.__name__, name, kwargs)) kwargs["_setup_called"] = True return cls(*args, **kwargs) def cleanup(self): self.cleanup_called = True print("%s.cleanup: %s" % (self.__class__.__name__, self.name)) def __str__(self): args_text = ", ".join([str(arg) for arg in self.args]) kwargs_parts = ["%s= %s" % (key, value) for key, value in sorted(six.iteritems(self.kwargs))] kwargs_text = ", ".join(kwargs_parts) return "%s: args=%s; kwargs=%s" % (self.name, args_text, kwargs_text) class FooFixture(BasicFixture): pass class BarFixture(BasicFixture): pass # -- FIXTURE EXCEPTIONS: class FixtureSetupError(RuntimeError): pass class FixtureCleanupError(RuntimeError): pass # ----------------------------------------------------------------------------- # CUSTOM ASSERTIONS: # ----------------------------------------------------------------------------- def assert_context_setup(context, fixture_name, fixture_class=Unknown): """Ensure that fixture object is stored in context.""" the_fixture = getattr(context, fixture_name, Unknown) assert hasattr(context, fixture_name) assert the_fixture is not Unknown if fixture_class is not Unknown: assert isinstance(the_fixture, fixture_class) def assert_context_cleanup(context, fixture_name): """Ensure that fixture object is no longer stored in context.""" assert not hasattr(context, fixture_name) def assert_fixture_setup_called(fixture_obj): """Ensure that fixture setup was performed.""" if hasattr(fixture_obj, "setup_called"): assert fixture_obj.setup_called def assert_fixture_cleanup_called(fixture_obj): if hasattr(fixture_obj, "cleanup_called"): assert fixture_obj.cleanup_called def assert_fixture_cleanup_not_called(fixture_obj): if hasattr(fixture_obj, "cleanup_called"): assert not fixture_obj.cleanup_called # ----------------------------------------------------------------------------- # TEST SUITE: # ----------------------------------------------------------------------------- class TestFixtureDecorator(object): def test_decorator(self): @fixture def foo(context, *args, **kwargs): pass assert foo.behave_fixture == True assert foo.name is None assert foo.pattern is None assert callable(foo) def test_decorator_call_without_params(self): @fixture() def bar0(context, *args, **kwargs): pass assert bar0.behave_fixture == True assert bar0.name is None assert bar0.pattern is None assert callable(bar0) def test_decorator_call_with_name(self): @fixture(name="fixture.bar2") def bar1(context, *args, **kwargs): pass assert bar1.behave_fixture == True assert bar1.name == "fixture.bar2" assert bar1.pattern is None assert callable(bar1) def test_decorated_generator_function_is_callable(self): # -- SIMILAR-TO: TestUseFixture.test_with_generator_func() @fixture def foo(context, *args, **kwargs): fixture_object = FooFixture.setup(*args, **kwargs) yield fixture_object fixture_object.cleanup() # -- ENSURE: Decorated fixture function can be called w/ params. context = None func_it = foo(context, 1, 2, 3, name="foo_1") # -- VERIFY: Expectations assert is_context_manager(foo) assert inspect.isfunction(foo) assert inspect.isgenerator(func_it) def test_decorated_function_is_callable(self): # -- SIMILAR-TO: TestUseFixture.test_with_function() @fixture(name="fixture.bar") def bar(context, *args, **kwargs): fixture_object = BarFixture.setup(*args, **kwargs) return fixture_object # -- ENSURE: Decorated fixture function can be called w/ params. context = None the_fixture = bar(context, 3, 2, 1, name="bar_1") # -- VERIFY: Expectations assert inspect.isfunction(bar) assert isinstance(the_fixture, BarFixture) def test_decorator_with_non_callable_raises_type_error(self): class NotCallable(object): pass with pytest.raises(TypeError) as exc_info: not_callable = NotCallable() bad_fixture = fixture(not_callable) # DECORATED_BY_HAND exception_text = str(exc_info.value) assert "Invalid func:" in exception_text assert "NotCallable object" in exception_text class TestUseFixture(object): def test_basic_lifecycle(self): # -- NOTE: Use explicit checks instead of assert-helper functions. @fixture def foo(context, checkpoints, *args, **kwargs): checkpoints.append("foo.setup") yield FooFixture() checkpoints.append("foo.cleanup") checkpoints = [] context = make_runtime_context() with scoped_context_layer(context): the_fixture = use_fixture(foo, context, checkpoints) # -- ENSURE: Fixture setup is performed (and as expected) assert isinstance(the_fixture, FooFixture) assert checkpoints == ["foo.setup"] checkpoints.append("scoped-block") # -- ENSURE: Fixture cleanup was performed assert checkpoints == ["foo.setup", "scoped-block", "foo.cleanup"] def test_fixture_with_args(self): """Ensures that positional args are passed to fixture function.""" @fixture def foo(context, *args, **kwargs): fixture_object = FooFixture.setup(*args, **kwargs) context.foo = fixture_object yield fixture_object fixture_object.cleanup() context = make_runtime_context() with scoped_context_layer(context): the_fixture = use_fixture(foo, context, 1, 2, 3) expected_args = (1, 2, 3) assert the_fixture.args == expected_args def test_fixture_with_kwargs(self): """Ensures that keyword args are passed to fixture function.""" @fixture def bar(context, *args, **kwargs): fixture_object = BarFixture.setup(*args, **kwargs) context.bar = fixture_object return fixture_object context = make_runtime_context() with scoped_context_layer(context): the_fixture = use_fixture(bar, context, name="bar", timeout=10) expected_kwargs = dict(name="bar", timeout=10) assert the_fixture.kwargs == expected_kwargs def test_with_generator_function(self): @fixture def foo(context, checkpoints, *args, **kwargs): checkpoints.append("foo.setup") fixture_object = FooFixture.setup(*args, **kwargs) context.foo = fixture_object yield fixture_object fixture_object.cleanup() checkpoints.append("foo.cleanup.done") checkpoints = [] context = make_runtime_context() with scoped_context_layer(context): the_fixture = use_fixture(foo, context, checkpoints) assert_context_setup(context, "foo", FooFixture) assert_fixture_setup_called(the_fixture) assert_fixture_cleanup_not_called(the_fixture) assert checkpoints == ["foo.setup"] print("Do something...") assert_context_cleanup(context, "foo") assert_fixture_cleanup_called(the_fixture) assert checkpoints == ["foo.setup", "foo.cleanup.done"] def test_with_function(self): @fixture def bar(context, checkpoints, *args, **kwargs): checkpoints.append("bar.setup") fixture_object = BarFixture.setup(*args, **kwargs) context.bar = fixture_object return fixture_object checkpoints = [] context = make_runtime_context() with scoped_context_layer(context): the_fixture = use_fixture(bar, context, checkpoints) assert_context_setup(context, "bar", BarFixture) assert_fixture_setup_called(the_fixture) assert_fixture_cleanup_not_called(the_fixture) assert checkpoints == ["bar.setup"] print("Do something...") assert_context_cleanup(context, "foo") assert_fixture_cleanup_not_called(the_fixture) # -- NOT: Normal functions have no fixture-cleanup part. def test_can_use_fixture_two_times(self): """Ensures that a fixture can be used multiple times (with different names) within a context layer. """ @fixture def foo(context, checkpoints, *args, **kwargs): fixture_object = FooFixture.setup(*args, **kwargs) setattr(context, fixture_object.name, fixture_object) checkpoints.append("foo.setup:%s" % fixture_object.name) yield fixture_object checkpoints.append("foo.cleanup:%s" % fixture_object.name) fixture_object.cleanup() checkpoints = [] context = make_runtime_context() with scoped_context_layer(context): the_fixture1 = use_fixture(foo, context, checkpoints, name="foo_1") the_fixture2 = use_fixture(foo, context, checkpoints, name="foo_2") # -- VERIFY: Fixture and context setup was performed. assert checkpoints == ["foo.setup:foo_1", "foo.setup:foo_2"] assert context.foo_1 is the_fixture1 assert context.foo_2 is the_fixture2 assert the_fixture1 is not the_fixture2 checkpoints.append("scoped-block") # -- VERIFY: Fixture and context cleanup is performed. assert_context_cleanup(context, "foo_1") assert_context_cleanup(context, "foo_2") assert checkpoints == ["foo.setup:foo_1", "foo.setup:foo_2", "scoped-block", "foo.cleanup:foo_2", "foo.cleanup:foo_1"] # -- NOTE: Cleanups occur in reverse order. def test_invalid_fixture_function(self): """Test invalid generator function with more than one yield-statement (not a valid fixture/context-manager). """ @fixture def invalid_fixture(context, checkpoints, *args, **kwargs): checkpoints.append("bad.setup") yield FooFixture(*args, **kwargs) checkpoints.append("bad.cleanup") yield None # -- SYNDROME HERE: More than one yield-statement # -- PERFORM-TEST: checkpoints = [] context = make_runtime_context() with pytest.raises(InvalidFixtureError): with scoped_context_layer(context): the_fixture = use_fixture(invalid_fixture, context, checkpoints) assert checkpoints == ["bad.setup"] checkpoints.append("scoped-block") # -- VERIFY: Ensure normal cleanup-parts were performed. assert checkpoints == ["bad.setup", "scoped-block", "bad.cleanup"] def test_bad_with_setup_error(self): # -- SAD: cleanup_fixture() part is called when setup-error occurs, # but not the cleanup-part of the generator (generator-magic). @fixture def bad_with_setup_error(context, checkpoints, *args, **kwargs): checkpoints.append("bad.setup_with_error") raise FixtureSetupError() yield FooFixture(*args, **kwargs) checkpoints.append("bad.cleanup:NOT_REACHED") # -- PERFORM-TEST: the_fixture = None checkpoints = [] context = make_runtime_context() bad_fixture = bad_with_setup_error with pytest.raises(FixtureSetupError): with scoped_context_layer(context): the_fixture = use_fixture(bad_fixture, context, checkpoints) checkpoints.append("scoped-block:NOT_REACHED") # -- VERIFY: Ensure normal cleanup-parts were performed. # * SAD: fixture-cleanup is not called due to fixture-setup error. assert the_fixture is None # -- NEVER STORED: Due to setup error. assert checkpoints == ["bad.setup_with_error"] def test_bad_with_setup_error_aborts_on_first_error(self): @fixture def foo(context, checkpoints, *args, **kwargs): fixture_name = kwargs.get("name", "") checkpoints.append("foo.setup:%s" % fixture_name) yield FooFixture(*args, **kwargs) checkpoints.append("foo.cleanup:%s" % fixture_name) @fixture def bad_with_setup_error(context, checkpoints, *args, **kwargs): checkpoints.append("bad.setup_with_error") raise FixtureSetupError() yield FooFixture(*args, **kwargs) checkpoints.append("bad.cleanup:NOT_REACHED") # -- PERFORM-TEST: the_fixture1 = None the_fixture2 = None the_fixture3 = None checkpoints = [] context = make_runtime_context() bad_fixture = bad_with_setup_error with pytest.raises(FixtureSetupError): with scoped_context_layer(context): the_fixture1 = use_fixture(foo, context, checkpoints, name="foo_1") the_fixture2 = use_fixture(bad_fixture, context, checkpoints, name="BAD") the_fixture3 = use_fixture(foo, context, checkpoints, name="NOT_REACHED") checkpoints.append("scoped-block:NOT_REACHED") # -- VERIFY: Ensure cleanup-parts were performed until failure-point. assert isinstance(the_fixture1, FooFixture) assert the_fixture2 is None # -- NEVER-STORED: Due to setup error. assert the_fixture3 is None # -- NEVER-CREATED: Due to bad_fixture. assert checkpoints == [ "foo.setup:foo_1", "bad.setup_with_error", "foo.cleanup:foo_1"] def test_bad_with_cleanup_error(self): @fixture def bad_with_cleanup_error(context, checkpoints, *args, **kwargs): checkpoints.append("bad.setup") yield FooFixture(*args, **kwargs) checkpoints.append("bad.cleanup_with_error") raise FixtureCleanupError() # -- PERFORM TEST: checkpoints = [] context = make_runtime_context() bad_fixture = bad_with_cleanup_error with pytest.raises(FixtureCleanupError): with scoped_context_layer(context): use_fixture(bad_fixture, context, checkpoints) checkpoints.append("scoped-block") # -- VERIFY: Ensure normal cleanup-parts were performed or tried. assert checkpoints == ["bad.setup", "scoped-block", "bad.cleanup_with_error"] def test_bad_with_cleanup_error_performs_all_cleanups(self): @fixture def foo(context, checkpoints, *args, **kwargs): fixture_name = kwargs.get("name", "") checkpoints.append("foo.setup:%s" % fixture_name) yield FooFixture(*args, **kwargs) checkpoints.append("foo.cleanup:%s" % fixture_name) @fixture def bad_with_cleanup_error(context, checkpoints, *args, **kwargs): checkpoints.append("bad.setup") yield FooFixture(*args, **kwargs) checkpoints.append("bad.cleanup_with_error") raise FixtureCleanupError() checkpoints.append("bad.cleanup.done:NOT_REACHED") # -- PERFORM TEST: the_fixture1 = None the_fixture2 = None the_fixture3 = None checkpoints = [] context = make_runtime_context() bad_fixture = bad_with_cleanup_error with pytest.raises(FixtureCleanupError): with scoped_context_layer(context): the_fixture1 = use_fixture(foo, context, checkpoints, name="foo_1") the_fixture2 = use_fixture(bad_fixture, context, checkpoints, name="BAD") the_fixture3 = use_fixture(foo, context, checkpoints, name="foo_3") checkpoints.append("scoped-block") # -- VERIFY: Tries to perform all cleanups even when cleanup-error(s) occur. assert checkpoints == [ "foo.setup:foo_1", "bad.setup", "foo.setup:foo_3", "scoped-block", "foo.cleanup:foo_3", "bad.cleanup_with_error", "foo.cleanup:foo_1"] def test_bad_with_setup_and_cleanup_error(self): # -- GOOD: cleanup_fixture() part is called when setup-error occurs. # BUT: FixtureSetupError is hidden by FixtureCleanupError @fixture def bad_with_setup_and_cleanup_error(context, checkpoints, *args, **kwargs): def cleanup_bad_with_error(): checkpoints.append("bad.cleanup_with_error") raise FixtureCleanupError() checkpoints.append("bad.setup_with_error") context.add_cleanup(cleanup_bad_with_error) raise FixtureSetupError() return FooFixture("NOT_REACHED") # -- PERFORM TEST: checkpoints = [] context = make_runtime_context() bad_fixture = bad_with_setup_and_cleanup_error with pytest.raises(FixtureCleanupError) as exc_info: with scoped_context_layer(context): use_fixture(bad_fixture, context, checkpoints, name="BAD2") checkpoints.append("scoped-block:NOT_REACHED") # -- VERIFY: Ensure normal cleanup-parts were performed or tried. # OOPS: FixtureCleanupError in fixture-cleanup hides FixtureSetupError assert checkpoints == ["bad.setup_with_error", "bad.cleanup_with_error"] assert isinstance(exc_info.value, FixtureCleanupError), "LAST-EXCEPTION-WINS" class TestUseFixtureByTag(object): def test_data_schema1(self): @fixture def foo(context, *args, **kwargs): # -- NOTE checkpoints: Injected from outer scope. checkpoints.append("foo.setup") yield "fixture:foo" checkpoints.append("foo.cleanup") fixture_registry = { "fixture.foo": foo, } # -- PERFORM-TEST: context = make_runtime_context() checkpoints = [] with scoped_context_layer(context): use_fixture_by_tag("fixture.foo", context, fixture_registry) checkpoints.append("scoped-block") # -- VERIFY: assert checkpoints == [ "foo.setup", "scoped-block", "foo.cleanup" ] def test_data_schema2(self): @fixture def foo(context, *args, **kwargs): # -- NOTE checkpoints: Injected from outer scope. params = "%r, %r" % (args, kwargs) checkpoints.append("foo.setup: %s" % params) yield "fixture.foo" checkpoints.append("foo.cleanup: %s" % params) fixture_registry = { "fixture.foo": fixture_call_params(foo, 1, 2, 3, name="foo_1") } # -- PERFORM-TEST: context = make_runtime_context() checkpoints = [] with scoped_context_layer(context): use_fixture_by_tag("fixture.foo", context, fixture_registry) checkpoints.append("scoped-block") # -- VERIFY: assert checkpoints == [ "foo.setup: (1, 2, 3), {'name': 'foo_1'}", "scoped-block", "foo.cleanup: (1, 2, 3), {'name': 'foo_1'}", ] def test_unknown_fixture_raises_lookup_error(self): fixture_registry = {} # -- PERFORM-TEST: context = make_runtime_context() with pytest.raises(LookupError) as exc_info: with scoped_context_layer(context): use_fixture_by_tag("UNKNOWN_FIXTURE", context, fixture_registry) # -- VERIFY: assert "Unknown fixture-tag: UNKNOWN_FIXTURE" in str(exc_info.value) def test_invalid_data_schema_raises_value_error(self): @fixture def foo(context, *args, **kwargs): pass class BadFixtureData(object): def __init__(self, fixture_func, *args, **kwargs): self.fixture_func = fixture_func self.fixture_args = args self.fixture_kwargs = kwargs fixture_registry = { "fixture.foo": BadFixtureData(foo, 1, 2, 3, name="foo_1") } # -- PERFORM-TEST: context = make_runtime_context() with pytest.raises(ValueError) as exc_info: with scoped_context_layer(context): use_fixture_by_tag("fixture.foo", context, fixture_registry) # -- VERIFY: expected = "fixture_data: Expected tuple or fixture-func, but is:" assert expected in str(exc_info.value) assert "BadFixtureData object" in str(exc_info.value) class TestCompositeFixture(object): def test_use_fixture(self): @fixture def fixture_foo(context, checkpoints, *args, **kwargs): fixture_name = kwargs.get("name", "foo") checkpoints.append("foo.setup:%s" % fixture_name) yield checkpoints.append("foo.cleanup:%s" % fixture_name) @fixture def composite2(context, checkpoints, *args, **kwargs): the_fixture1 = use_fixture(fixture_foo, context, checkpoints, name="_1") the_fixture2 = use_fixture(fixture_foo, context, checkpoints, name="_2") return (the_fixture1, the_fixture2) # -- PERFORM-TEST: context = make_runtime_context() checkpoints = [] with scoped_context_layer(context): use_fixture(composite2, context, checkpoints) checkpoints.append("scoped-block") assert checkpoints == [ "foo.setup:_1", "foo.setup:_2", "scoped-block", "foo.cleanup:_2", "foo.cleanup:_1", ] def test_use_fixture_with_setup_error(self): @fixture def fixture_foo(context, checkpoints, *args, **kwargs): fixture_name = kwargs.get("name", "foo") checkpoints.append("foo.setup:%s" % fixture_name) yield checkpoints.append("foo.cleanup:%s" % fixture_name) @fixture def bad_with_setup_error(context, checkpoints, *args, **kwargs): checkpoints.append("bad.setup_with_error") raise FixtureSetupError("OOPS") yield checkpoints.append("bad.cleanup:NOT_REACHED") @fixture def composite3(context, checkpoints, *args, **kwargs): bad_fixture = bad_with_setup_error the_fixture1 = use_fixture(fixture_foo, context, checkpoints, name="_1") the_fixture2 = use_fixture(bad_fixture, context, checkpoints, name="OOPS") the_fixture3 = use_fixture(fixture_foo, context, checkpoints, name="_3:NOT_REACHED") return (the_fixture1, the_fixture2, the_fixture3) # NOT_REACHED # -- PERFORM-TEST: context = make_runtime_context() checkpoints = [] with pytest.raises(FixtureSetupError): with scoped_context_layer(context): use_fixture(composite3, context, checkpoints) checkpoints.append("scoped-block:NOT_REACHED") # -- ENSURES: # * fixture1-cleanup is called even after fixture2-setup-error # * fixture3-setup/cleanup are not called due to fixture2-setup-error assert checkpoints == [ "foo.setup:_1", "bad.setup_with_error", "foo.cleanup:_1" ] def test_use_fixture_with_block_error(self): @fixture def fixture_foo(context, checkpoints, *args, **kwargs): fixture_name = kwargs.get("name", "foo") checkpoints.append("foo.setup:%s" % fixture_name) yield checkpoints.append("foo.cleanup:%s" % fixture_name) @fixture def composite2(context, checkpoints, *args, **kwargs): the_fixture1 = use_fixture(fixture_foo, context, checkpoints, name="_1") the_fixture2 = use_fixture(fixture_foo, context, checkpoints, name="_2") return (the_fixture1, the_fixture2) # -- PERFORM-TEST: context = make_runtime_context() checkpoints = [] with pytest.raises(RuntimeError): with scoped_context_layer(context): use_fixture(composite2, context, checkpoints) checkpoints.append("scoped-block_with_error") raise RuntimeError("OOPS") checkpoints.append("scoped-block.done:NOT_REACHED") # -- ENSURES: # * fixture1-cleanup/cleanup is called even scoped-block-error # * fixture2-cleanup/cleanup is called even scoped-block-error # * fixture-cleanup occurs in reversed setup-order assert checkpoints == [ "foo.setup:_1", "foo.setup:_2", "scoped-block_with_error", "foo.cleanup:_2", "foo.cleanup:_1" ] def test_use_composite_fixture(self): @fixture def fixture_foo(context, checkpoints, *args, **kwargs): fixture_name = kwargs.get("name", "foo") checkpoints.append("foo.setup:%s" % fixture_name) yield checkpoints.append("foo.cleanup:%s" % fixture_name) @fixture def composite2(context, checkpoints, *args, **kwargs): the_composite = use_composite_fixture_with(context, [ fixture_call_params(fixture_foo, checkpoints, name="_1"), fixture_call_params(fixture_foo, checkpoints, name="_2"), ]) return the_composite # -- PERFORM-TEST: context = make_runtime_context() checkpoints = [] with scoped_context_layer(context): use_fixture(composite2, context, checkpoints) checkpoints.append("scoped-block") assert checkpoints == [ "foo.setup:_1", "foo.setup:_2", "scoped-block", "foo.cleanup:_2", "foo.cleanup:_1", ] def test_use_composite_fixture_with_setup_error(self): @fixture def fixture_foo(context, checkpoints, *args, **kwargs): fixture_name = kwargs.get("name", "foo") checkpoints.append("foo.setup:%s" % fixture_name) yield checkpoints.append("foo.cleanup:%s" % fixture_name) @fixture def bad_with_setup_error(context, checkpoints, *args, **kwargs): checkpoints.append("bad.setup_with_error") raise FixtureSetupError("OOPS") yield checkpoints.append("bad.cleanup:NOT_REACHED") @fixture def composite3(context, checkpoints, *args, **kwargs): bad_fixture = bad_with_setup_error the_composite = use_composite_fixture_with(context, [ fixture_call_params(fixture_foo, checkpoints, name="_1"), fixture_call_params(bad_fixture, checkpoints, name="OOPS"), fixture_call_params(fixture_foo, checkpoints, name="_3:NOT_REACHED"), ]) return the_composite # -- PERFORM-TEST: context = make_runtime_context() checkpoints = [] with pytest.raises(FixtureSetupError): with scoped_context_layer(context): use_fixture(composite3, context, checkpoints) checkpoints.append("scoped-block:NOT_REACHED") # -- ENSURES: # * fixture1-cleanup is called even after fixture2-setup-error # * fixture3-setup/cleanup are not called due to fixture2-setup-error assert checkpoints == [ "foo.setup:_1", "bad.setup_with_error", "foo.cleanup:_1" ] def test_use_composite_fixture_with_block_error(self): @fixture def fixture_foo(context, checkpoints, *args, **kwargs): fixture_name = kwargs.get("name", "foo") checkpoints.append("foo.setup:%s" % fixture_name) yield checkpoints.append("foo.cleanup:%s" % fixture_name) @fixture def composite2(context, checkpoints, *args, **kwargs): the_composite = use_composite_fixture_with(context, [ fixture_call_params(fixture_foo, checkpoints, name="_1"), fixture_call_params(fixture_foo, checkpoints, name="_2"), ]) return the_composite # -- PERFORM-TEST: checkpoints = [] context = make_runtime_context() with pytest.raises(RuntimeError): with scoped_context_layer(context): use_fixture(composite2, context, checkpoints) checkpoints.append("scoped-block_with_error") raise RuntimeError("OOPS") checkpoints.append("scoped-block.done:NOT_REACHED") # -- ENSURES: # * fixture1-cleanup/cleanup is called even scoped-block-error # * fixture2-cleanup/cleanup is called even scoped-block-error # * fixture-cleanup occurs in reversed setup-order assert checkpoints == [ "foo.setup:_1", "foo.setup:_2", "scoped-block_with_error", "foo.cleanup:_2", "foo.cleanup:_1" ] # -- BAD-EXAMPLE: Ensure SIMPLISTIC-COMPOSITE works as expected def test_simplistic_composite_with_setup_error_skips_cleanup(self): def setup_bad_fixture_with_error(text): raise FixtureSetupError("OOPS") @fixture def sad_composite2(context, checkpoints, *args, **kwargs): checkpoints.append("foo.setup:_1") the_fixture1 = FooFixture.setup(*args, **kwargs) checkpoints.append("bad.setup_with_error") the_fixture2 = setup_bad_fixture_with_error(text="OOPS") checkpoints.append("bad.setup.done:NOT_REACHED") yield (the_fixture1, the_fixture2) checkpoints.append("foo.cleanup:_1:NOT_REACHED") # -- PERFORM-TEST: context = make_runtime_context() checkpoints = [] with pytest.raises(FixtureSetupError): with scoped_context_layer(context): use_fixture(sad_composite2, context, checkpoints) checkpoints.append("scoped-block:NOT_REACHED") # -- VERIFY: # * SAD: fixture1-cleanup is not called after fixture2-setup-error assert checkpoints == ["foo.setup:_1", "bad.setup_with_error"] def test_simplistic_composite_with_block_error_performs_cleanup(self): def setup_bad_fixture_with_error(text): raise FixtureSetupError("OOPS") @fixture def simplistic_composite2(context, checkpoints, *args, **kwargs): checkpoints.append("foo.setup:_1") the_fixture1 = FooFixture.setup(*args, name="_1") checkpoints.append("foo.setup:_2") the_fixture2 = FooFixture.setup(*args, name="_2") yield (the_fixture1, the_fixture2) checkpoints.append("foo.cleanup:_1") the_fixture1.cleanup() checkpoints.append("foo.cleanup:_2") the_fixture2.cleanup() # -- PERFORM-TEST: context = make_runtime_context() checkpoints = [] with pytest.raises(RuntimeError): with scoped_context_layer(context): use_fixture(simplistic_composite2, context, checkpoints) checkpoints.append("scoped-block_with_error") raise RuntimeError("OOPS") checkpoints.append("scoped-block.end:NOT_REACHED") # -- VERIFY: # * fixture1-setup/cleanup is called when block-error occurs # * fixture2-setup/cleanup is called when block-error occurs # * fixture-cleanups occurs in specified composite-cleanup order assert checkpoints == [ "foo.setup:_1", "foo.setup:_2", "scoped-block_with_error", "foo.cleanup:_1", "foo.cleanup:_2" ] class TestFixtureCleanup(object): """Check fixture-cleanup behaviour w/ different fixture implementations.""" def test_setup_eror_with_plaingen_then_cleanup_is_not_called(self): # -- CASE: Fixture is generator-function @fixture def foo(context, checkpoints): checkpoints.append("foo.setup.begin") raise FixtureSetupError("foo") checkpoints.append("foo.setup.done:NOT_REACHED") yield checkpoints.append("foo.cleanup:NOT_REACHED") checkpoints = [] context = make_runtime_context() with pytest.raises(FixtureSetupError): with scoped_context_layer(context): use_fixture(foo, context, checkpoints) checkpoints.append("scoped-block:NOT_REACHED") assert checkpoints == ["foo.setup.begin"] def test_setup_eror_with_finallygen_then_cleanup_is_called(self): # -- CASE: Fixture is generator-function @fixture def foo(context, checkpoints): try: checkpoints.append("foo.setup.begin") raise FixtureSetupError("foo") checkpoints.append("foo.setup.done:NOT_REACHED") yield checkpoints.append("foo.cleanup:NOT_REACHED") finally: checkpoints.append("foo.cleanup.finally") checkpoints = [] context = make_runtime_context() with pytest.raises(FixtureSetupError): with scoped_context_layer(context): use_fixture(foo, context, checkpoints) # -- NEVER-REACHED: checkpoints.append("scoped-block:NOT_REACHED") # -- ENSURE: fixture-cleanup (foo.cleanup) is called (EARLY-CLEANUP). assert checkpoints == ["foo.setup.begin", "foo.cleanup.finally"] def test_setup_error_with_context_cleanup1_then_cleanup_is_called(self): # -- CASE: Fixture is normal function @fixture def foo(context, checkpoints): def cleanup_foo(arg=""): checkpoints.append("cleanup_foo:%s" % arg) checkpoints.append("foo.setup_with_error:foo_1") context.add_cleanup(cleanup_foo, "foo_1") raise FixtureSetupError("foo") checkpoints.append("foo.setup.done:NOT_REACHED") checkpoints = [] context = make_runtime_context() with pytest.raises(FixtureSetupError): with scoped_context_layer(context): use_fixture(foo, context, checkpoints) checkpoints.append("scoped-block:NOT_REACHED") # -- ENSURE: cleanup_foo() is called (LATE-CLEANUP on scope-exit) assert checkpoints == ["foo.setup_with_error:foo_1", "cleanup_foo:foo_1"] def test_setup_error_with_context_cleanup2_then_cleanup_is_called(self): # -- CASE: Fixture is generator-function (contextmanager) # NOTE: Explicit use of context.add_cleanup() @fixture def foo(context, checkpoints, **kwargs): def cleanup_foo(arg=""): checkpoints.append("cleanup_foo:%s" % arg) checkpoints.append("foo.setup_with_error:foo_1") context.add_cleanup(cleanup_foo, "foo_1") raise FixtureSetupError("foo_1") checkpoints.append("foo.setup.done:NOT_REACHED") yield checkpoints.append("foo.cleanup:NOT_REACHED") checkpoints = [] context = make_runtime_context() with pytest.raises(FixtureSetupError): with scoped_context_layer(context): use_fixture(foo, context, checkpoints) checkpoints.append("scoped-block:NOT_REACHED") # -- ENSURE: cleanup_foo() is called assert checkpoints == ["foo.setup_with_error:foo_1", "cleanup_foo:foo_1"] def test_block_eror_with_plaingen_then_cleanup_is_called(self): # -- CASE: Fixture is generator-function @fixture def foo(context, checkpoints): checkpoints.append("foo.setup") yield checkpoints.append("foo.cleanup") checkpoints = [] context = make_runtime_context() with pytest.raises(RuntimeError): with scoped_context_layer(context): use_fixture(foo, context, checkpoints) checkpoints.append("scoped-block_with_error") raise RuntimeError("scoped-block") checkpoints.append("NOT_REACHED") # -- ENSURE: assert checkpoints == [ "foo.setup", "scoped-block_with_error", "foo.cleanup" ] def test_block_eror_with_context_cleanup_then_cleanup_is_called(self): # -- CASE: Fixture is normal-function @fixture def bar(context, checkpoints): def cleanup_bar(): checkpoints.append("cleanup_bar") checkpoints.append("bar.setup") context.add_cleanup(cleanup_bar) checkpoints = [] context = make_runtime_context() with pytest.raises(RuntimeError): with scoped_context_layer(context): use_fixture(bar, context, checkpoints) checkpoints.append("scoped-block_with_error") raise RuntimeError("scoped-block") checkpoints.append("NOT_REACHED") # -- ENSURE: assert checkpoints == [ "bar.setup", "scoped-block_with_error", "cleanup_bar" ] behave-1.2.6/tests/unit/test_model_core.py0000644000076600000240000000365513244555737020727 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ (Additional) Unit tests for :mod:`behave.model_core` module. """ from __future__ import print_function from behave.model_core import Status import pytest # ----------------------------------------------------------------------------- # TESTS: # ----------------------------------------------------------------------------- class TestStatus(object): """Test Status enum class. In addition, checks if it is partly backward compatibility to string-based status. EXAMPLE:: status = Status.passed assert status == "passed" assert status != "failed" assert status == Status.from_name("passed") """ @pytest.mark.parametrize("enum_value", list(Status.__members__.values())) def test_equals__with_string_value(self, enum_value): """Ensure that Status enum value can be compared with a string-status""" assert enum_value == enum_value.name @pytest.mark.parametrize("enum_value", list(Status.__members__.values())) def test_equals__with_unknown_name(self, enum_value): assert enum_value != "__UNKNOWN__" assert not (enum_value == "__UNKNOWN__") @pytest.mark.parametrize("enum_value, similar_name", [ (Status.passed, "Passed"), (Status.failed, "FAILED"), (Status.passed, "passed1"), (Status.failed, "failed2"), ]) def test_equals__with_similar_name(self, enum_value, similar_name): assert enum_value != similar_name @pytest.mark.parametrize("enum_value", list(Status.__members__.values())) def test_from_name__with_known_names(self, enum_value): assert enum_value == Status.from_name(enum_value.name) @pytest.mark.parametrize("unknown_name", [ "Passed", "Failed", "passed2", "failed1" ]) def test_from_name__with_unknown_name_raises_lookuperror(self, unknown_name): with pytest.raises(LookupError): Status.from_name(unknown_name) behave-1.2.6/tests/unit/test_textutil.py0000644000076600000240000002256213244555737020477 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- """ Unit tests for :mod:`behave.textutil`. """ from __future__ import absolute_import, print_function from behave.textutil import text, is_ascii_encoding, select_best_encoding import pytest import codecs import six # ----------------------------------------------------------------------------- # TEST SUPPORT: # ----------------------------------------------------------------------------- class ConvertableToUnicode(object): """Class that can be converted into a unicode string. If parameter is a bytes-string, it is converted into unicode. .. code-block:: python obj1 = ConvertableToUnicode(u"Ärgernis") obj2 = ConvertableToUnicode(u"Ärgernis".encode("latin-1") # -- CASE Python2: string is a bytes-string text_value21 = unicode(obj1) text_value22 = unicode(obj2) # -- CASE Python3: string is a unicode-string text_value31 = str(obj1) text_value32 = str(obj2) """ encoding = "utf-8" def __init__(self, text, encoding=None): self.text = text self.encoding = encoding or self.__class__.encoding def __str__(self): """Convert into a unicode string.""" text = self.text if isinstance(text, six.binary_type): text = codecs.decode(text, self.encoding) return text if six.PY2: __unicode__ = __str__ __str__ = lambda self: self.__unicode__().encode(self.encoding) class ConvertableToString(object): encoding = "utf-8" def __init__(self, text, encoding=None): self.text = text self.encoding = encoding or self.__class__.encoding def __str__(self): text = self.text if isinstance(text, six.binary_type): text = codecs.decode(text, self.encoding) return text if six.PY2: __unicode__ = __str__ def __str__(self): # MAYBE: self.__unicode__().encode(self.encoding) text = self.text if isinstance(text, six.text_type): text = codecs.encode(text, self.encoding) return text class ConvertableToPy2String(object): encoding = "utf-8" def __init__(self, text, encoding=None): self.text = text self.encoding = encoding or self.__class__.encoding def __str__(self): text = self.text if six.PY2: if isinstance(text, six.text_type): text = codecs.encode(text, self.encoding) else: if isinstance(text, six.bytes_type): text = codecs.decode(text, self.encoding) return text if six.PY2: __unicode__ = __str__ def __str__(self): # MAYBE: self.__unicode__().encode(self.encoding) text = self.text if isinstance(text, six.text_type): text = codecs.encode(text, self.encoding) return text # def raise_exception(exception_class, *args): # raise exception_class(*args) # # def catch_raised_exception_and_return_as_text(exception_class, *args, **kwargs): # encoding = kwargs.pop("encoding", None) # try: # raise_exception(*args) # except Exception as e: # return text(e, encoding=encoding) # ----------------------------------------------------------------------------- # TEST SUITE # ----------------------------------------------------------------------------- xfail = pytest.mark.xfail requires_python2 = pytest.mark.skipif(not six.PY2, reason="requires python2") UNICODE_TEXT_VALUES1 = [u"Alice", u"Bob"] UNICODE_TEXT_VALUES = [u"Café", u"100€ (Euro)", u"Frühaufsteher"] BYTES_TEXT_TUPLES_WITH_UTF8_ENCODING = [ (codecs.encode(_text, "utf-8"), _text) for _text in UNICODE_TEXT_VALUES ] class TestTextConversion(object): """Unit tests for the :func:`behave.textutil.text()` function.""" @pytest.mark.parametrize("value", UNICODE_TEXT_VALUES) def test_text__with_unicode_value(self, value): value_id = id(value) actual_text = text(value) assert actual_text == value assert id(actual_text) == value_id # PARANOID check w/ unicode copy. @pytest.mark.parametrize("bytes_value, expected_text", BYTES_TEXT_TUPLES_WITH_UTF8_ENCODING) def test_text__with_bytes_value(self, bytes_value, expected_text): actual_text = text(bytes_value) assert actual_text == expected_text @pytest.mark.parametrize("text_value, encoding", [ (u"Ärgernis", "UTF-8"), (u"Übermut", "UTF-8"), ]) def test_text__with_bytes_value_and_encoding(self, text_value, encoding): bytes_value = text_value.encode(encoding) assert isinstance(bytes_value, bytes) actual = text(bytes_value, encoding) assert isinstance(actual, six.text_type) assert actual == text_value def test_text__with_exception_traceback(self): pass @pytest.mark.parametrize("text_value", UNICODE_TEXT_VALUES) def test_text__with_object_convertable_to_unicode(self, text_value): obj = ConvertableToUnicode(text_value) actual_text = text(obj) assert actual_text == text_value assert isinstance(actual_text, six.text_type) # @pytest.mark.parametrize("expected, text_value, encoding", [ # (u"Ärgernis", u"Ärgernis".encode("UTF-8"), "UTF-8"), # (u"Übermut", u"Übermut".encode("latin-1"), "latin-1"), # ]) # def test_text__with_object_convertable_to_unicode2(self, expected, # text_value, encoding): # obj = ConvertableToUnicode(text_value, encoding) # actual_text = text(obj) # assert actual_text == expected # assert isinstance(actual_text, six.text_type) @pytest.mark.parametrize("text_value", UNICODE_TEXT_VALUES) def test_text__with_object_convertable_to_string(self, text_value): obj = ConvertableToString(text_value) actual_text = text(obj) assert actual_text == text_value assert isinstance(actual_text, six.text_type) @xfail @requires_python2 @pytest.mark.parametrize("text_value", UNICODE_TEXT_VALUES) def test_text__with_object_convertable_to_py2string_only(self, text_value): class ConvertableToPy2String(object): """Lacks feature: convertable-to-unicode (only: to-string)""" def __init__(self, message=""): self.message = message or "" if self.message and isinstance(self.message, six.text_type): self.message = self.message.encode("UTF-8") def __str__(self): # assert isinstance(self.message, str) return self.message obj = ConvertableToPy2String(text_value.encode("UTF-8")) actual = text(obj) print(u"actual: %s" % actual) print(u"text_value: %s" % text_value) assert actual == text_value class TestObjectToTextConversion(object): """Unit tests for the :func:`behave.textutil.text()` function. Explore case with object-to-text conversion. """ ENCODING = "UTF-8" # -- CASE: object=exception @pytest.mark.parametrize("message", [ u"Ärgernis", u"Übermütig" ]) def test_text__with_assert_failed_and_unicode_message(self, message): with pytest.raises(AssertionError) as e: assert False, message text2 = text(e) expected = u"AssertionError: %s" % message assert text2.endswith(expected) @requires_python2 @pytest.mark.parametrize("message", [ u"Ärgernis", u"Übermütig" ]) def test_text__with_assert_failed_and_bytes_message(self, message): # -- ONLY PYTHON2: Use case makes no sense for Python 3. bytes_message = message.encode(self.ENCODING) with pytest.raises(AssertionError) as e: assert False, bytes_message text2 = text(e) expected = u"AssertionError: %s" % message assert text2.endswith(expected) @pytest.mark.parametrize("exception_class, message", [ (AssertionError, u"Ärgernis"), (RuntimeError, u"Übermütig"), ]) def test_text__with_raised_exception_and_unicode_message(self, exception_class, message): with pytest.raises(exception_class) as e: raise exception_class(message) text2 = text(e) expected = u"%s: %s" % (exception_class.__name__, message) assert isinstance(text2, six.text_type) assert text2.endswith(expected) @requires_python2 @pytest.mark.parametrize("exception_class, message", [ (AssertionError, u"Ärgernis"), (RuntimeError, u"Übermütig"), ]) def test_text__with_raised_exception_and_bytes_message(self, exception_class, message): # -- ONLY PYTHON2: Use case makes no sense for Python 3. bytes_message = message.encode(self.ENCODING) with pytest.raises(exception_class) as e: raise exception_class(bytes_message) text2 = text(e) unicode_message = bytes_message.decode(self.ENCODING) expected = u"%s: %s" % (exception_class.__name__, unicode_message) assert isinstance(text2, six.text_type) assert text2.endswith(expected) # -- DIAGNOSTICS: print(u"text2: "+ text2) print(u"expected: " + expected) behave-1.2.6/tests/unit/test_userdata.py0000644000076600000240000003224113244555737020420 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- from __future__ import absolute_import from behave.userdata import parse_user_define, UserData, UserDataNamespace from behave._types import Unknown import pytest class TestParseUserDefine(object): """Test parse_user_define() function.""" def test_parse__name_value(self): parts = parse_user_define("person=Alice") assert parts == ("person", "Alice") def test_parse__name_only_for_boolean_flag(self): parts = parse_user_define("boolean_flag") assert parts == ("boolean_flag", "true") @pytest.mark.parametrize("text", [ " person=Alice", "person=Alice ", "person = Alice", ]) def test_parse__name_value_with_padded_whitespace(self, text): parts = parse_user_define(text) assert parts == ("person", "Alice") @pytest.mark.parametrize("text", [ '"person=Alice and Bob"', "'person=Alice and Bob'", ]) def test_parse__name_value_with_quoted_name_value_pair(self, text): parts = parse_user_define(text) assert parts == ("person", "Alice and Bob") @pytest.mark.parametrize("text", [ 'person="Alice and Bob"', "person='Alice and Bob'", ]) def test_parse__name_value_with_quoted_value(self, text): parts = parse_user_define(text) assert parts == ("person", "Alice and Bob") class TestUserData(object): """Test UserData class.""" def test_userdata_is_dictlike(self): userdata = UserData(name="Foo", number=42) value1 = userdata["name"] value2 = userdata.get("number") value3 = userdata.get("unknown", Unknown) assert isinstance(userdata, dict) assert value1 == "Foo" assert value2 == 42 assert value3 is Unknown def test_getas__with_known_param_and_valid_text(self): userdata = UserData(param="42") assert "param" in userdata, "ENSURE: known param" value = userdata.getas(int, "param") assert isinstance(value, int) assert value == 42 def test_getas__with_known_param_and_invalid_text_raises_ValueError(self): userdata = UserData(param="__BAD_NUMBER__") assert "param" in userdata, "ENSURE: known param" with pytest.raises(ValueError): userdata.getas(int, "param") def test_getas__with_known_param_and_preconverted_value(self): userdata = UserData(param=42) assert "param" in userdata, "ENSURE: known param" value = userdata.getas(int, "param") assert isinstance(value, int) assert value == 42 def test_getas__with_known_param_and_preconverted_value_and_valuetype(self): userdata = UserData(param=42) assert "param" in userdata, "ENSURE: known param" def parse_int(text): return int(text) value = userdata.getas(parse_int, "param", valuetype=int) assert isinstance(value, int) assert value == 42 def test_getas__with_unknown_param_without_default_returns_none(self): userdata = UserData() assert "param" not in userdata, "ENSURE: unknown param" value = userdata.getas(int, "param") assert value is None def test_getas__with_unknown_param_returns_default_value(self): userdata = UserData() assert "param" not in userdata, "ENSURE: unknown param" value = userdata.getas(int, "param", 123) assert isinstance(value, int) assert value == 123 def test_getint__with_known_param_and_valid_text(self): userdata = UserData(param="42") value = userdata.getint("param") assert isinstance(value, int) assert value == 42 def test_getint__with_known_param_and_invalid_text_raises_ValueError(self): userdata = UserData(param="__BAD_NUMBER__") with pytest.raises(ValueError): userdata.getint("param") def test_getint__with_unknown_param_without_default_returns_zero(self): userdata = UserData() value = userdata.getint("param") assert value == 0 def test_getint__with_unknown_param_returns_default_value(self): userdata = UserData() value = userdata.getint("param", 123) assert isinstance(value, int) assert value == 123 def test_getfloat__with_known_param_and_valid_text(self): for valid_text in ["1.2", "2", "-1E+3", "+2.34E-5"]: userdata = UserData(param=valid_text) value = userdata.getfloat("param") assert isinstance(value, float) assert value == float(valid_text) def test_getfloat__with_known_param_and_invalid_text_raises_ValueError(self): userdata = UserData(param="__BAD_NUMBER__") with pytest.raises(ValueError): userdata.getfloat("param") def test_getfloat__with_unknown_param_without_default_returns_zero(self): userdata = UserData() value = userdata.getfloat("param") assert value == 0.0 def test_getfloat__with_unknown_param_returns_default_value(self): userdata = UserData() value = userdata.getint("param", 1.2) assert isinstance(value, float) assert value == 1.2 @pytest.mark.parametrize("text", [ "true", "TRUE", "True", "yes", "on", "1" ]) def test_getbool__with_known_param_and_valid_true_text(self, text): true_text = text userdata = UserData(param=true_text) value = userdata.getbool("param") assert isinstance(value, bool), "text=%s" % true_text assert value is True @pytest.mark.parametrize("text", [ "false", "FALSE", "False", "no", "off", "0" ]) def test_getbool__with_known_param_and_valid_false_text(self, text): false_text = text userdata = UserData(param=false_text) value = userdata.getbool("param") assert isinstance(value, bool), "text=%s" % false_text assert value is False def test_getbool__with_known_param_and_invalid_text_raises_ValueError(self): userdata = UserData(param="__BAD_VALUE__") with pytest.raises(ValueError): userdata.getbool("param") def test_getbool__with_unknown_param_without_default_returns_false(self): userdata = UserData() value = userdata.getfloat("param") assert value == False def test_getbool__with_unknown_param_returns_default_value(self): userdata = UserData() value = userdata.getint("param", 1.2) assert isinstance(value, float) assert value == 1.2 class TestUserDataNamespace(object): def test_make_scoped(self): scoped_name = UserDataNamespace.make_scoped("my.scope", "param") assert scoped_name == "my.scope.param" def test_make_scoped__with_empty_scope(self): scoped_name = UserDataNamespace.make_scoped("", "param") assert scoped_name == "param" def test_ctor__converts_dict_into_userdata(self): userdata = {"my.scope.param1": 12} config = UserDataNamespace("my.scope", userdata) assert isinstance(config.data, UserData) assert config.data is not userdata assert config.data == userdata def test_ctor__converts_items_into_userdata(self): userdata = {"my.scope.param1": 12} config = UserDataNamespace("my.scope", userdata.items()) assert isinstance(config.data, UserData) assert config.data is not userdata assert config.data == userdata def test_ctor__can_assign_userdata_afterwards(self): userdata = UserData({"my.scope.param1": 12}) config = UserDataNamespace("my.scope") config.data = userdata assert isinstance(config.data, UserData) assert config.data is userdata def test_get__retrieves_value_when_scoped_param_exists(self): userdata = UserData({"my.scope.param1": 12}) config = UserDataNamespace("my.scope", userdata) assert config.get("param1") == 12 assert config["param1"] == 12 def test_get__returns_default_when_scoped_param_not_exists(self): config = UserDataNamespace("my.scope", UserData({})) assert config.get("UNKNOWN_PARAM", "DEFAULT1") == "DEFAULT1" assert config.get("UNKNOWN_PARAM", "DEFAULT2") == "DEFAULT2" def test_getint__retrieves_value_when_scoped_param_exists(self): userdata = UserData({"my.scope.param1": "12"}) config = UserDataNamespace("my.scope", userdata) assert config.getint("param1") == 12 def test_getint__retrieves_value_when_scoped_param_exists(self): userdata = UserData({"my.scope.param2": "12"}) config = UserDataNamespace("my.scope", userdata) assert config.getint("param2") == 12 def test_getint__returns_default_when_scoped_param_not_exists(self): userdata = UserData({}) config = UserDataNamespace("my.scope", userdata) assert config.getint("UNKNOWN_PARAM", 123) == 123 assert config.getint("UNKNOWN_PARAM", 321) == 321 def test_getbool__retrieves_value_when_scoped_param_exists(self): userdata = UserData({"my.scope.param3": "yes"}) config = UserDataNamespace("my.scope", userdata) assert config.getbool("param3") == True def test_getbool__returns_default_when_scoped_param_not_exists(self): userdata = UserData({}) config = UserDataNamespace("my.scope", userdata) assert config.getint("UNKNOWN_PARAM", True) == True assert config.getint("UNKNOWN_PARAM", False) == False def test_contains__when_scoped_param_exists(self): userdata = UserData({"my.scope.param": 12}) config = UserDataNamespace("my.scope", userdata) assert "param" in config assert not("param" not in config) def test_contains__when_scoped_param_not_exists(self): userdata = UserData({"my.scope.param": 12}) config = UserDataNamespace("my.scope", userdata) assert "UNKNOWN_PARAM" not in config assert not ("UNKNOWN_PARAM" in config) def test_getitem__returns_value_when_param_exists(self): userdata = UserData({"my.scope.param": "123"}) config = UserDataNamespace("my.scope", userdata) assert config["param"] == "123" def test_getitem__raises_error_when_param_not_exists(self): userdata = UserData({"my.scope.param": "123"}) config = UserDataNamespace("my.scope", userdata) with pytest.raises(KeyError): _ = config["UNKNOWN_PARAM"] def test_setitem__stores_value(self): userdata = UserData({"my.scope.param1": "123"}) config = UserDataNamespace("my.scope", userdata) scoped_name = "my.scope.new_param" config["new_param"] = 1234 assert "new_param" in config assert config["new_param"] == 1234 assert scoped_name in config.data assert config.data[scoped_name] == 1234 def test_length__returns_zero_without_params(self): userdata = UserData({"my.other_scope.param1": "123"}) config = UserDataNamespace("my.scope", userdata) assert len(config) == 0 def test_length__with_scoped_params(self): userdata1 = UserData({"my.scope.param1": "123"}) userdata2 = UserData({ "my.other_scope.param1": "123", "my.scope.param1": "123", "my.scope.param2": "456", }) userdata3 = UserData({ "my.other_scope.param1": "123", "my.scope.param1": "123", "my.scope.param2": "456", "my.scope.param3": "789", "my.other_scope.param2": "123", }) config = UserDataNamespace("my.scope") config.data = userdata1 assert len(config) == 1 config.data = userdata2 assert len(config) == 2 config.data = userdata3 assert len(config) == 3 def test_scoped_keys__with_scoped_params(self): userdata = UserData({ "my.other_scope.param1": "123", "my.scope.param1": "123", "my.scope.param2": "456", "my.other_scope.param2": "123", }) config = UserDataNamespace("my.scope", userdata) assert sorted(config.scoped_keys()) == ["my.scope.param1", "my.scope.param2"] def test_keys__with_scoped_params(self): userdata = UserData({ "my.other_scope.param1": "__OTHER1__", "my.scope.param1": "123", "my.scope.param2": "456", "my.other_scope.param2": "__OTHER2__", }) config = UserDataNamespace("my.scope", userdata) assert sorted(config.keys()) == ["param1", "param2"] def test_values__with_scoped_params(self): userdata = UserData({ "my.other_scope.param1": "__OTHER1__", "my.scope.param1": "123", "my.scope.param2": "456", "my.other_scope.param2": "__OTHER2__", }) config = UserDataNamespace("my.scope", userdata) assert sorted(config.values()) == ["123", "456"] def test_items__with_scoped_params(self): userdata = UserData({ "my.other_scope.param1": "__OTHER1__", "my.scope.param1": "123", "my.scope.param2": "456", "my.other_scope.param2": "__OTHER2__", }) config = UserDataNamespace("my.scope", userdata) assert sorted(config.items()) == [("param1", "123"), ("param2", "456")] behave-1.2.6/tools/0000755000076600000240000000000013244564037014205 5ustar jensstaff00000000000000behave-1.2.6/tools/test-features/0000755000076600000240000000000013244564040016772 5ustar jensstaff00000000000000behave-1.2.6/tools/test-features/background.feature0000644000076600000240000000036513244555737022507 0ustar jensstaff00000000000000Feature: features may have backgrounds Background: some background stuff to run Given I am testing stuff and some stuff is set up Scenario: the stuff should be set up Given stuff has been set up Then it will work behave-1.2.6/tools/test-features/environment.py0000644000076600000240000000026413244555737021727 0ustar jensstaff00000000000000 def before_all(context): context.testing_stuff = False context.stuff_set_up = False def before_feature(context, feature): context.is_spammy = 'spam' in feature.tags behave-1.2.6/tools/test-features/french.feature0000644000076600000240000000037313244555737021634 0ustar jensstaff00000000000000# language: fr Fonctionnalité: testing stuff Scénario: test stuff Etant donné I am testing stuff Quand I exercise it work Alors it will work Scénario: test more stuff Etant donné I am testing stuff Alors it will work behave-1.2.6/tools/test-features/outline.feature0000644000076600000240000000223313244555737022043 0ustar jensstaff00000000000000Feature: support scenario outlines Scenario Outline: run scenarios with one example table Given Some text When we add some text Then we should get the Examples: some simple examples | prefix | suffix | combination | | go | ogle | google | | onomat | opoeia | onomatopoeia | | comb | ination | combination | Scenario Outline: run scenarios with examples Given Some text When we add some text Then we should get the Examples: some simple examples | prefix | suffix | combination | | go | ogle | google | | onomat | opoeia | onomatopoeia | | comb | ination | combination | Examples: some other examples | prefix | suffix | combination | | 1 | 2 | 12 | | one | two | onetwo | @xfail Scenario Outline: scenarios that reference invalid subs Given Some text When we add try to use a reference Then it won't work Examples: some simple examples | prefix | | go | behave-1.2.6/tools/test-features/parse.feature0000644000076600000240000000035313244555737021477 0ustar jensstaff00000000000000Feature: parse stuff out of steps Scenario: basic parsing Given a string with an argument Then we get "with" parsed Scenario: custom type parsing Given a string with a custom type Then we get "WITH" parsed behave-1.2.6/tools/test-features/step-data.feature0000644000076600000240000000355113244555737022252 0ustar jensstaff00000000000000Feature: steps may have associated data Scenario: step with text Given some body of text """ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. """ Then the text is as expected Scenario: step with a table Given some initial data | name | department | | Barry | Beer Cans | | Pudey | Silly Walks | | Two-Lumps | Silly Walks | Then we will have the expected data @xfail Scenario: step with text that fails Given some body of text """ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim VENIAM, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. """ Then the text is as expected Scenario Outline: step with text and subtitution Given some body of text """ Lorem dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. """ Then the text is substituted as expected Examples: | ipsum | | spam | | ham | Scenario Outline: step with a table and substution Given some initial data | name | department | | Barry | Cans | | Pudey | Silly Walks | | Two-Lumps | Silly Walks | Then we will have the substituted data Examples: | spam | | spam | | ham | behave-1.2.6/tools/test-features/steps/0000755000076600000240000000000013244564040020130 5ustar jensstaff00000000000000behave-1.2.6/tools/test-features/steps/steps.py0000644000076600000240000000727013244555737021663 0ustar jensstaff00000000000000# -*- coding: UTF-8 -*- from __future__ import absolute_import from behave import given, when, then import logging from six.moves import zip spam_log = logging.getLogger('spam') ham_log = logging.getLogger('ham') @given("I am testing stuff") def step_impl(context): context.testing_stuff = True @given("some stuff is set up") def step_impl(context): context.stuff_set_up = True @given("stuff has been set up") def step_impl(context): assert context.testing_stuff is True assert context.stuff_set_up is True @when("I exercise it work") def step_impl(context): spam_log.error('logging!') ham_log.error('logging!') @then("it will work") def step_impl(context): pass @given("some text {prefix}") def step_impl(context, prefix): context.prefix = prefix @when('we add some text {suffix}') def step_impl(context, suffix): context.combination = context.prefix + suffix @then('we should get the {combination}') def step_impl(context, combination): assert context.combination == combination @given('some body of text') def step_impl(context): assert context.text context.saved_text = context.text TEXT = ''' Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.''' @then('the text is as expected') def step_impl(context): assert context.saved_text, 'context.saved_text is %r!!' % (context.saved_text, ) context.saved_text.assert_equals(TEXT) @given('some initial data') def step_impl(context): assert context.table context.saved_table = context.table TABLE_DATA = [ dict(name='Barry', department='Beer Cans'), dict(name='Pudey', department='Silly Walks'), dict(name='Two-Lumps', department='Silly Walks'), ] @then('we will have the expected data') def step_impl(context): assert context.saved_table, 'context.saved_table is %r!!' % (context.saved_table, ) for expected, got in zip(TABLE_DATA, iter(context.saved_table)): assert expected['name'] == got['name'] assert expected['department'] == got['department'] @then('the text is substituted as expected') def step_impl(context): assert context.saved_text, 'context.saved_text is %r!!' % (context.saved_text, ) expected = TEXT.replace('ipsum', context.active_outline['ipsum']) context.saved_text.assert_equals(expected) TABLE_DATA = [ dict(name='Barry', department='Beer Cans'), dict(name='Pudey', department='Silly Walks'), dict(name='Two-Lumps', department='Silly Walks'), ] @then('we will have the substituted data') def step_impl(context): assert context.saved_table, 'context.saved_table is %r!!' % (context.saved_table, ) value = context.active_outline['spam'] expected = value + ' Cans' assert context.saved_table[0]['department'] == expected, '%r != %r' % ( context.saved_table[0]['department'], expected) @given('the tag "{tag}" is set') def step_impl(context, tag): assert tag in context.tags, '%r NOT present in %r!' % (tag, context.tags) if tag == 'spam': assert context.is_spammy @given('the tag "{tag}" is not set') def step_impl(context, tag): assert tag not in context.tags, '%r IS present in %r!' % (tag, context.tags) @given('a string {argument} an argument') def step_impl(context, argument): context.argument = argument from behave.matchers import register_type register_type(custom=lambda s: s.upper()) @given('a string {argument:custom} a custom type') def step_impl(context, argument): context.argument = argument @then('we get "{argument}" parsed') def step_impl(context, argument): assert context.argument == argument behave-1.2.6/tools/test-features/tags.feature0000644000076600000240000000063313244555737021324 0ustar jensstaff00000000000000@spam Feature: handle tags Tags may be set at various levels in various ways. Scenario: nothing changes Given the tag "spam" is set @ham @cram Scenario: adding the ham Given the tag "spam" is set and the tag "ham" is set and the tag "cram" is set @ham Scenario: adding the ham Given the tag "spam" is set and the tag "ham" is set and the tag "cram" is not set behave-1.2.6/tox.ini0000644000076600000240000001276213244555737014377 0ustar jensstaff00000000000000# ============================================================================ # TOX CONFIGURATION: behave # ============================================================================ # DESCRIPTION: # # Use tox to run tasks (tests, ...) in a clean virtual environment. # Afterwards you can run tox in offline mode, like: # # tox -e py27 # # Tox can be configured for offline usage. # Initialize local workspace once (download packages, create PyPI index): # # tox -e init1 # tox -e init2 (alternative) # # NOTE: # You can either use "local1" or "local2" as local "tox.indexserver.default": # # * $HOME/.pip/downloads/ (local1, default) # * downloads/ (local2, alternative) # # SEE ALSO: # * http://tox.testrun.org/latest/config.html # ============================================================================ # -- ONLINE USAGE: # PIP_INDEX_URL = http://pypi.python.org/simple [tox] minversion = 2.3 envlist = py26, py27, py33, py34, py35, py36, pypy, docs skip_missing_interpreters = True sitepackages = False indexserver = default = https://pypi.python.org/simple default2 = file://{homedir}/.pip/downloads/simple local1 = file://{toxinidir}/downloads/simple local2 = file://{homedir}/.pip/downloads/simple pypi = https://pypi.python.org/simple # ----------------------------------------------------------------------------- # TOX PREPARE/BOOTSTRAP: Initialize local workspace for tox off-line usage # ----------------------------------------------------------------------------- [testenv:init1] install_command = pip install -i https://pypi.python.org/simple --find-links downloads --no-index {packages} changedir = {toxinidir} skipsdist = True commands= {toxinidir}/bin/toxcmd.py mkdir {toxinidir}/downloads pip install --download={toxinidir}/downloads -r py.requirements/all.txt {toxinidir}/bin/make_localpi.py {toxinidir}/downloads deps= [testenv:init2] install_command = pip install -i https://pypi.python.org/simple --find-links {homedir}/.pip/downloads --no-index {packages} changedir = {toxinidir} skipsdist = True commands= {toxinidir}/bin/toxcmd.py mkdir {homedir}/.pip/downloads pip install --download={homedir}/.pip/downloads -r py.requirements/all.txt {toxinidir}/bin/make_localpi.py {homedir}/.pip/downloads deps= # setenv = # PIP_INDEX_URL = https://pypi.python.org/simple # ----------------------------------------------------------------------------- # TEST ENVIRONMENTS: # ----------------------------------------------------------------------------- [testenv] install_command = pip install -U {opts} {packages} changedir = {toxinidir} commands= py.test {posargs:test tests} behave --format=progress {posargs:features} behave --format=progress {posargs:tools/test-features} behave --format=progress {posargs:issue.features} deps= pytest>=3.0 nose>=1.3 mock>=2.0 PyHamcrest>=1.9 path.py >= 10.1 setenv = PYTHONPATH = {toxinidir} [testenv:docs] basepython= python2 changedir = docs commands= sphinx-build -W -b html -d {envtmpdir}/doctrees . {envtmpdir}/html deps= -r{toxinidir}/py.requirements/docs.txt [testenv:cleanroom2] basepython = python2 changedir = {envdir} commands= behave --version {toxinidir}/bin/toxcmd.py copytree ../../behave4cmd0 . {toxinidir}/bin/toxcmd.py copytree ../../test . {toxinidir}/bin/toxcmd.py copytree ../../tests . {toxinidir}/bin/toxcmd.py copytree ../../features . {toxinidir}/bin/toxcmd.py copytree ../../tools . {toxinidir}/bin/toxcmd.py copytree ../../issue.features . {toxinidir}/bin/toxcmd.py copy ../../behave.ini . py.test {posargs:test tests} behave --format=progress {posargs:features} behave --format=progress {posargs:tools/test-features} behave --format=progress {posargs:issue.features} deps= {[testenv]deps} setenv = PYTHONPATH = .:{envdir} [testenv:cleanroom3] basepython = python3 changedir = {envdir} commands= behave --version {toxinidir}/bin/toxcmd.py copytree ../../behave4cmd0 . {toxinidir}/bin/toxcmd.py copytree ../../test . {toxinidir}/bin/toxcmd.py copytree ../../tests . {toxinidir}/bin/toxcmd.py copytree ../../features . {toxinidir}/bin/toxcmd.py copytree ../../tools . {toxinidir}/bin/toxcmd.py copytree ../../issue.features . {toxinidir}/bin/toxcmd.py copy ../../behave.ini . {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs behave4cmd0 {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs test {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs tools {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs features {toxinidir}/bin/toxcmd.py 2to3 -w -n --no-diffs issue.features py.test {posargs:test tests} behave --format=progress {posargs:features} behave --format=progress {posargs:tools/test-features} behave --format=progress {posargs:issue.features} deps= {[testenv]deps} setenv = PYTHONPATH = .:{envdir} # --------------------------------------------------------------------------- # SELDOM-USED: OPTIONAL TEST ENVIRONMENTS: # --------------------------------------------------------------------------- # -- SELDOM-USED, TESTED-WITH: jython2.7 # JYTHON INSTALL RELATED (jit): # http://sikulix-2014.readthedocs.org/en/latest/scenarios.html [testenv:jy27] basepython= jython commands= py.test {posargs:test tests} behave --format=progress {posargs:features} behave --format=progress {posargs:tools/test-features} behave --format=progress {posargs:issue.features} deps= jit {[testenv]deps} behave-1.2.6/VERSION.txt0000644000076600000240000000000613244561722014725 0ustar jensstaff000000000000001.2.6