All Downloads are FREE. Search and download functionalities are using the official Maven repository.

Lib.robot.libraries.BuiltIn.py Maven / Gradle / Ivy

#  Copyright 2008-2015 Nokia Solutions and Networks
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

import difflib
import re
import time
import token
from tokenize import generate_tokens, untokenize

from robot.api import logger
from robot.errors import (ContinueForLoop, DataError, ExecutionFailed,
                          ExecutionFailures, ExecutionPassed, ExitForLoop,
                          PassExecution, ReturnFromKeyword)
from robot.running import Keyword, RUN_KW_REGISTER
from robot.running.context import EXECUTION_CONTEXTS
from robot.running.usererrorhandler import UserErrorHandler
from robot.utils import (DotDict, escape, format_assign_message,
                         get_error_message, get_time, is_falsy, is_integer,
                         is_string, is_truthy, is_unicode, IRONPYTHON, JYTHON,
                         Matcher, normalize, NormalizedDict, parse_time, prepr,
                         RERAISED_EXCEPTIONS, plural_or_not as s, roundup,
                         secs_to_timestr, seq2str, split_from_equals, StringIO,
                         timestr_to_secs, type_name, unic)
from robot.utils.asserts import assert_equal, assert_not_equal
from robot.variables import (is_list_var, is_var, DictVariableTableValue,
                             VariableTableValue, VariableSplitter,
                             variable_not_found)
from robot.version import get_version

if JYTHON:
    from java.lang import String, Number


# TODO: Clean-up registering run keyword variants in RF 3.1.
# https://github.com/robotframework/robotframework/issues/2190

def run_keyword_variant(resolve):
    def decorator(method):
        RUN_KW_REGISTER.register_run_keyword('BuiltIn', method.__name__,
                                             resolve, deprecation_warning=False)
        return method
    return decorator


class _BuiltInBase(object):

    @property
    def _context(self):
        return self._get_context()

    def _get_context(self, top=False):
        ctx = EXECUTION_CONTEXTS.current if not top else EXECUTION_CONTEXTS.top
        if ctx is None:
            raise RobotNotRunningError('Cannot access execution context')
        return ctx

    @property
    def _namespace(self):
        return self._get_context().namespace

    @property
    def _variables(self):
        return self._namespace.variables

    def _matches(self, string, pattern):
        # Must use this instead of fnmatch when string may contain newlines.
        matcher = Matcher(pattern, caseless=False, spaceless=False)
        return matcher.match(string)

    def _is_true(self, condition):
        if is_string(condition):
            condition = self.evaluate(condition, modules='os,sys')
        return bool(condition)

    def _log_types(self, *args):
        self._log_types_at_level('DEBUG', *args)

    def _log_types_at_level(self, level, *args):
        msg = ["Argument types are:"] + [self._get_type(a) for a in args]
        self.log('\n'.join(msg), level)

    def _get_type(self, arg):
        # In IronPython type(u'x') is str. We want to report unicode anyway.
        if is_unicode(arg):
            return ""
        return str(type(arg))


class _Converter(_BuiltInBase):

    def convert_to_integer(self, item, base=None):
        """Converts the given item to an integer number.

        If the given item is a string, it is by default expected to be an
        integer in base 10. There are two ways to convert from other bases:

        - Give base explicitly to the keyword as ``base`` argument.

        - Prefix the given string with the base so that ``0b`` means binary
          (base 2), ``0o`` means octal (base 8), and ``0x`` means hex (base 16).
          The prefix is considered only when ``base`` argument is not given and
          may itself be prefixed with a plus or minus sign.

        The syntax is case-insensitive and possible spaces are ignored.

        Examples:
        | ${result} = | Convert To Integer | 100    |    | # Result is 100   |
        | ${result} = | Convert To Integer | FF AA  | 16 | # Result is 65450 |
        | ${result} = | Convert To Integer | 100    | 8  | # Result is 64    |
        | ${result} = | Convert To Integer | -100   | 2  | # Result is -4    |
        | ${result} = | Convert To Integer | 0b100  |    | # Result is 4     |
        | ${result} = | Convert To Integer | -0x100 |    | # Result is -256  |

        See also `Convert To Number`, `Convert To Binary`, `Convert To Octal`,
        `Convert To Hex`, and `Convert To Bytes`.
        """
        self._log_types(item)
        return self._convert_to_integer(item, base)

    def _convert_to_integer(self, orig, base=None):
        try:
            item = self._handle_java_numbers(orig)
            item, base = self._get_base(item, base)
            if base:
                return int(item, self._convert_to_integer(base))
            return int(item)
        except:
            raise RuntimeError("'%s' cannot be converted to an integer: %s"
                               % (orig, get_error_message()))

    def _handle_java_numbers(self, item):
        if not JYTHON:
            return item
        if isinstance(item, String):
            return unic(item)
        if isinstance(item, Number):
            return item.doubleValue()
        return item

    def _get_base(self, item, base):
        if not is_string(item):
            return item, base
        item = normalize(item)
        if item.startswith(('-', '+')):
            sign = item[0]
            item = item[1:]
        else:
            sign = ''
        bases = {'0b': 2, '0o': 8, '0x': 16}
        if base or not item.startswith(tuple(bases)):
            return sign+item, base
        return sign+item[2:], bases[item[:2]]

    def convert_to_binary(self, item, base=None, prefix=None, length=None):
        """Converts the given item to a binary string.

        The ``item``, with an optional ``base``, is first converted to an
        integer using `Convert To Integer` internally. After that it
        is converted to a binary number (base 2) represented as a
        string such as ``1011``.

        The returned value can contain an optional ``prefix`` and can be
        required to be of minimum ``length`` (excluding the prefix and a
        possible minus sign). If the value is initially shorter than
        the required length, it is padded with zeros.

        Examples:
        | ${result} = | Convert To Binary | 10 |         |           | # Result is 1010   |
        | ${result} = | Convert To Binary | F  | base=16 | prefix=0b | # Result is 0b1111 |
        | ${result} = | Convert To Binary | -2 | prefix=B | length=4 | # Result is -B0010 |

        See also `Convert To Integer`, `Convert To Octal` and `Convert To Hex`.
        """
        return self._convert_to_bin_oct_hex(item, base, prefix, length, 'b')

    def convert_to_octal(self, item, base=None, prefix=None, length=None):
        """Converts the given item to an octal string.

        The ``item``, with an optional ``base``, is first converted to an
        integer using `Convert To Integer` internally. After that it
        is converted to an octal number (base 8) represented as a
        string such as ``775``.

        The returned value can contain an optional ``prefix`` and can be
        required to be of minimum ``length`` (excluding the prefix and a
        possible minus sign). If the value is initially shorter than
        the required length, it is padded with zeros.

        Examples:
        | ${result} = | Convert To Octal | 10 |            |          | # Result is 12      |
        | ${result} = | Convert To Octal | -F | base=16    | prefix=0 | # Result is -017    |
        | ${result} = | Convert To Octal | 16 | prefix=oct | length=4 | # Result is oct0020 |

        See also `Convert To Integer`, `Convert To Binary` and `Convert To Hex`.
        """
        return self._convert_to_bin_oct_hex(item, base, prefix, length, 'o')

    def convert_to_hex(self, item, base=None, prefix=None, length=None,
                       lowercase=False):
        """Converts the given item to a hexadecimal string.

        The ``item``, with an optional ``base``, is first converted to an
        integer using `Convert To Integer` internally. After that it
        is converted to a hexadecimal number (base 16) represented as
        a string such as ``FF0A``.

        The returned value can contain an optional ``prefix`` and can be
        required to be of minimum ``length`` (excluding the prefix and a
        possible minus sign). If the value is initially shorter than
        the required length, it is padded with zeros.

        By default the value is returned as an upper case string, but the
        ``lowercase`` argument a true value (see `Boolean arguments`) turns
        the value (but not the given prefix) to lower case.

        Examples:
        | ${result} = | Convert To Hex | 255 |           |              | # Result is FF    |
        | ${result} = | Convert To Hex | -10 | prefix=0x | length=2     | # Result is -0x0A |
        | ${result} = | Convert To Hex | 255 | prefix=X | lowercase=yes | # Result is Xff   |

        See also `Convert To Integer`, `Convert To Binary` and `Convert To Octal`.
        """
        spec = 'x' if is_truthy(lowercase) else 'X'
        return self._convert_to_bin_oct_hex(item, base, prefix, length, spec)

    def _convert_to_bin_oct_hex(self, item, base, prefix, length, format_spec):
        self._log_types(item)
        ret = format(self._convert_to_integer(item, base), format_spec)
        prefix = prefix or ''
        if ret[0] == '-':
            prefix = '-' + prefix
            ret = ret[1:]
        if length:
            ret = ret.rjust(self._convert_to_integer(length), '0')
        return prefix + ret

    def convert_to_number(self, item, precision=None):
        """Converts the given item to a floating point number.

        If the optional ``precision`` is positive or zero, the returned number
        is rounded to that number of decimal digits. Negative precision means
        that the number is rounded to the closest multiple of 10 to the power
        of the absolute precision. If a number is equally close to a certain
        precision, it is always rounded away from zero.

        Examples:
        | ${result} = | Convert To Number | 42.512 |    | # Result is 42.512 |
        | ${result} = | Convert To Number | 42.512 | 1  | # Result is 42.5   |
        | ${result} = | Convert To Number | 42.512 | 0  | # Result is 43.0   |
        | ${result} = | Convert To Number | 42.512 | -1 | # Result is 40.0   |

        Notice that machines generally cannot store floating point numbers
        accurately. This may cause surprises with these numbers in general
        and also when they are rounded. For more information see, for example,
        these resources:

        - http://docs.python.org/2/tutorial/floatingpoint.html
        - http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition

        If you need an integer number, use `Convert To Integer` instead.
        """
        self._log_types(item)
        return self._convert_to_number(item, precision)

    def _convert_to_number(self, item, precision=None):
        number = self._convert_to_number_without_precision(item)
        if precision is not None:
            number = roundup(number, self._convert_to_integer(precision),
                             return_type=float)
        return number

    def _convert_to_number_without_precision(self, item):
        try:
            if JYTHON:
                item = self._handle_java_numbers(item)
            return float(item)
        except:
            error = get_error_message()
            try:
                return float(self._convert_to_integer(item))
            except RuntimeError:
                raise RuntimeError("'%s' cannot be converted to a floating "
                                   "point number: %s" % (item, error))

    def convert_to_string(self, item):
        """Converts the given item to a Unicode string.

        Uses ``__unicode__`` or ``__str__`` method with Python objects and
        ``toString`` with Java objects.

        Use `Encode String To Bytes` and `Decode Bytes To String` keywords
        in ``String`` library if you need to convert between Unicode and byte
        strings using different encodings. Use `Convert To Bytes` if you just
        want to create byte strings.
        """
        self._log_types(item)
        return self._convert_to_string(item)

    def _convert_to_string(self, item):
        return unic(item)

    def convert_to_boolean(self, item):
        """Converts the given item to Boolean true or false.

        Handles strings ``True`` and ``False`` (case-insensitive) as expected,
        otherwise returns item's
        [http://docs.python.org/2/library/stdtypes.html#truth|truth value]
        using Python's ``bool()`` method.
        """
        self._log_types(item)
        if is_string(item):
            if item.upper() == 'TRUE':
                return True
            if item.upper() == 'FALSE':
                return False
        return bool(item)

    def convert_to_bytes(self, input, input_type='text'):
        u"""Converts the given ``input`` to bytes according to the ``input_type``.

        Valid input types are listed below:

        - ``text:`` Converts text to bytes character by character. All
          characters with ordinal below 256 can be used and are converted to
          bytes with same values. Many characters are easiest to represent
          using escapes like ``\\x00`` or ``\\xff``. Supports both Unicode
          strings and bytes.

        - ``int:`` Converts integers separated by spaces to bytes. Similarly as
          with `Convert To Integer`, it is possible to use binary, octal, or
          hex values by prefixing the values with ``0b``, ``0o``, or ``0x``,
          respectively.

        - ``hex:`` Converts hexadecimal values to bytes. Single byte is always
          two characters long (e.g. ``01`` or ``FF``). Spaces are ignored and
          can be used freely as a visual separator.

        - ``bin:`` Converts binary values to bytes. Single byte is always eight
          characters long (e.g. ``00001010``). Spaces are ignored and can be
          used freely as a visual separator.

        In addition to giving the input as a string, it is possible to use
        lists or other iterables containing individual characters or numbers.
        In that case numbers do not need to be padded to certain length and
        they cannot contain extra spaces.

        Examples (last column shows returned bytes):
        | ${bytes} = | Convert To Bytes | hyv\xe4    |     | # hyv\\xe4        |
        | ${bytes} = | Convert To Bytes | \\xff\\x07 |     | # \\xff\\x07      |
        | ${bytes} = | Convert To Bytes | 82 70      | int | # RF              |
        | ${bytes} = | Convert To Bytes | 0b10 0x10  | int | # \\x02\\x10      |
        | ${bytes} = | Convert To Bytes | ff 00 07   | hex | # \\xff\\x00\\x07 |
        | ${bytes} = | Convert To Bytes | 5246212121 | hex | # RF!!!           |
        | ${bytes} = | Convert To Bytes | 0000 1000  | bin | # \\x08           |
        | ${input} = | Create List      | 1          | 2   | 12                |
        | ${bytes} = | Convert To Bytes | ${input}   | int | # \\x01\\x02\\x0c |
        | ${bytes} = | Convert To Bytes | ${input}   | hex | # \\x01\\x02\\x12 |

        Use `Encode String To Bytes` in ``String`` library if you need to
        convert text to bytes using a certain encoding.

        New in Robot Framework 2.8.2.
        """
        try:
            try:
                ordinals = getattr(self, '_get_ordinals_from_%s' % input_type)
            except AttributeError:
                raise RuntimeError("Invalid input type '%s'." % input_type)
            return bytes(bytearray(o for o in ordinals(input)))
        except:
            raise RuntimeError("Creating bytes failed: %s" % get_error_message())

    def _get_ordinals_from_text(self, input):
        # https://github.com/IronLanguages/main/issues/1237
        if IRONPYTHON and isinstance(input, bytearray):
            input = bytes(input)
        for char in input:
            ordinal = char if is_integer(char) else ord(char)
            yield self._test_ordinal(ordinal, char, 'Character')

    def _test_ordinal(self, ordinal, original, type):
        if 0 <= ordinal <= 255:
            return ordinal
        raise RuntimeError("%s '%s' cannot be represented as a byte."
                           % (type, original))

    def _get_ordinals_from_int(self, input):
        if is_string(input):
            input = input.split()
        elif is_integer(input):
            input = [input]
        for integer in input:
            ordinal = self._convert_to_integer(integer)
            yield self._test_ordinal(ordinal, integer, 'Integer')

    def _get_ordinals_from_hex(self, input):
        for token in self._input_to_tokens(input, length=2):
            ordinal = self._convert_to_integer(token, base=16)
            yield self._test_ordinal(ordinal, token, 'Hex value')

    def _get_ordinals_from_bin(self, input):
        for token in self._input_to_tokens(input, length=8):
            ordinal = self._convert_to_integer(token, base=2)
            yield self._test_ordinal(ordinal, token, 'Binary value')

    def _input_to_tokens(self, input, length):
        if not is_string(input):
            return input
        input = ''.join(input.split())
        if len(input) % length != 0:
            raise RuntimeError('Expected input to be multiple of %d.' % length)
        return (input[i:i+length] for i in range(0, len(input), length))

    def create_list(self, *items):
        """Returns a list containing given items.

        The returned list can be assigned both to ``${scalar}`` and ``@{list}``
        variables.

        Examples:
        | @{list} =   | Create List | a    | b    | c    |
        | ${scalar} = | Create List | a    | b    | c    |
        | ${ints} =   | Create List | ${1} | ${2} | ${3} |
        """
        return list(items)

    @run_keyword_variant(resolve=0)
    def create_dictionary(self, *items):
        """Creates and returns a dictionary based on given items.

        Items are given using ``key=value`` syntax same way as ``&{dictionary}``
        variables are created in the Variable table. Both keys and values
        can contain variables, and possible equal sign in key can be escaped
        with a backslash like ``escaped\\=key=value``. It is also possible to
        get items from existing dictionaries by simply using them like
        ``&{dict}``.

        If same key is used multiple times, the last value has precedence.
        The returned dictionary is ordered, and values with strings as keys
        can also be accessed using convenient dot-access syntax like
        ``${dict.key}``.

        Examples:
        | &{dict} = | Create Dictionary | key=value | foo=bar |
        | Should Be True | ${dict} == {'key': 'value', 'foo': 'bar'} |
        | &{dict} = | Create Dictionary | ${1}=${2} | &{dict} | foo=new |
        | Should Be True | ${dict} == {1: 2, 'key': 'value', 'foo': 'new'} |
        | Should Be Equal | ${dict.key} | value |

        This keyword was changed in Robot Framework 2.9 in many ways:
        - Moved from ``Collections`` library to ``BuiltIn``.
        - Support also non-string keys in ``key=value`` syntax.
        - Deprecated old syntax to give keys and values separately.
        - Returned dictionary is ordered and dot-accessible.
        """
        separate, combined = self._split_dict_items(items)
        if separate:
            # TODO: Deprecated in 2.9. Remove support for this in 3.1.
            self.log("Giving keys and values separately to 'Create Dictionary' "
                     "keyword is deprecated. Use 'key=value' syntax instead.",
                     level='WARN')
        separate = self._format_separate_dict_items(separate)
        combined = DictVariableTableValue(combined).resolve(self._variables)
        result = DotDict(separate)
        result.update(combined)
        return result

    def _split_dict_items(self, items):
        separate = []
        for item in items:
            name, value = split_from_equals(item)
            if value is not None or VariableSplitter(item).is_dict_variable():
                break
            separate.append(item)
        return separate, items[len(separate):]

    def _format_separate_dict_items(self, separate):
        separate = self._variables.replace_list(separate)
        if len(separate) % 2 != 0:
            raise DataError('Expected even number of keys and values, got %d.'
                            % len(separate))
        return [separate[i:i+2] for i in range(0, len(separate), 2)]


class _Verify(_BuiltInBase):

    def _set_and_remove_tags(self, tags):
        set_tags = [tag for tag in tags if not tag.startswith('-')]
        remove_tags = [tag[1:] for tag in tags if tag.startswith('-')]
        if remove_tags:
            self.remove_tags(*remove_tags)
        if set_tags:
            self.set_tags(*set_tags)

    def fail(self, msg=None, *tags):
        """Fails the test with the given message and optionally alters its tags.

        The error message is specified using the ``msg`` argument.
        It is possible to use HTML in the given error message, similarly
        as with any other keyword accepting an error message, by prefixing
        the error with ``*HTML*``.

        It is possible to modify tags of the current test case by passing tags
        after the message. Tags starting with a hyphen (e.g. ``-regression``)
        are removed and others added. Tags are modified using `Set Tags` and
        `Remove Tags` internally, and the semantics setting and removing them
        are the same as with these keywords.

        Examples:
        | Fail | Test not ready   |             | | # Fails with the given message.    |
        | Fail | *HTML*Test not ready | | | # Fails using HTML in the message. |
        | Fail | Test not ready   | not-ready   | | # Fails and adds 'not-ready' tag.  |
        | Fail | OS not supported | -regression | | # Removes tag 'regression'.        |
        | Fail | My message       | tag    | -t*  | # Removes all tags starting with 't' except the newly added 'tag'. |

        See `Fatal Error` if you need to stop the whole test execution.

        Support for modifying tags was added in Robot Framework 2.7.4 and
        HTML message support in 2.8.
        """
        self._set_and_remove_tags(tags)
        raise AssertionError(msg) if msg else AssertionError()

    def fatal_error(self, msg=None):
        """Stops the whole test execution.

        The test or suite where this keyword is used fails with the provided
        message, and subsequent tests fail with a canned message.
        Possible teardowns will nevertheless be executed.

        See `Fail` if you only want to stop one test case unconditionally.
        """
        error = AssertionError(msg) if msg else AssertionError()
        error.ROBOT_EXIT_ON_FAILURE = True
        raise error

    def should_not_be_true(self, condition, msg=None):
        """Fails if the given condition is true.

        See `Should Be True` for details about how ``condition`` is evaluated
        and how ``msg`` can be used to override the default error message.
        """
        if self._is_true(condition):
            raise AssertionError(msg or "'%s' should not be true." % condition)

    def should_be_true(self, condition, msg=None):
        """Fails if the given condition is not true.

        If ``condition`` is a string (e.g. ``${rc} < 10``), it is evaluated as
        a Python expression as explained in `Evaluating expressions` and the
        keyword status is decided based on the result. If a non-string item is
        given, the status is got directly from its
        [http://docs.python.org/2/library/stdtypes.html#truth|truth value].

        The default error message (`` should be true``) is not very
        informative, but it can be overridden with the ``msg`` argument.

        Examples:
        | Should Be True | ${rc} < 10            |
        | Should Be True | '${status}' == 'PASS' | # Strings must be quoted |
        | Should Be True | ${number}   | # Passes if ${number} is not zero |
        | Should Be True | ${list}     | # Passes if ${list} is not empty  |

        Variables used like ``${variable}``, as in the examples above, are
        replaced in the expression before evaluation. Variables are also
        available in the evaluation namespace and can be accessed using special
        syntax ``$variable``. This is a new feature in Robot Framework 2.9
        and it is explained more thoroughly in `Evaluating expressions`.

        Examples:
        | Should Be True | $rc < 10          |
        | Should Be True | $status == 'PASS' | # Expected string must be quoted |

        Starting from Robot Framework 2.8, `Should Be True` automatically
        imports Python's [http://docs.python.org/2/library/os.html|os] and
        [http://docs.python.org/2/library/sys.html|sys] modules that contain
        several useful attributes:

        | Should Be True | os.linesep == '\\n'             | # Unixy   |
        | Should Be True | os.linesep == '\\r\\n'          | # Windows |
        | Should Be True | sys.platform == 'darwin'        | # OS X    |
        | Should Be True | sys.platform.startswith('java') | # Jython  |
        """
        if not self._is_true(condition):
            raise AssertionError(msg or "'%s' should be true." % condition)

    def should_be_equal(self, first, second, msg=None, values=True):
        """Fails if the given objects are unequal.

        Optional ``msg`` and ``values`` arguments specify how to construct
        the error message if this keyword fails:

        - If ``msg`` is not given, the error message is `` != ``.
        - If ``msg`` is given and ``values`` gets a true value, the error
          message is ``:  != ``.
        - If ``msg`` is given and ``values`` gets a false value, the error
          message is simply ````.

        ``values`` is true by default, but can be turned to false by using,
        for example, string ``false`` or ``no values``. See `Boolean arguments`
        section for more details.

        If both arguments are multiline strings, the comparison is done using
        `multiline string comparisons`.
        """
        self._log_types_at_info_if_different(first, second)
        self._should_be_equal(first, second, msg, values)

    def _should_be_equal(self, first, second, msg, values):
        if first == second:
            return
        include_values = self._include_values(values)
        if include_values and is_string(first) and is_string(second):
            self._raise_multi_diff(first, second)
        assert_equal(first, second, msg, include_values)

    def _log_types_at_info_if_different(self, first, second):
        level = 'DEBUG' if type(first) == type(second) else 'INFO'
        self._log_types_at_level(level, first, second)

    def _raise_multi_diff(self, first, second):
        first_lines, second_lines = first.splitlines(), second.splitlines()
        if len(first_lines) < 3 or len(second_lines) < 3:
            return
        self.log("%s\n!=\n%s" % (first, second))
        err = 'Multiline strings are different:\n'
        for line in difflib.unified_diff(first_lines, second_lines,
                                         fromfile='first', tofile='second',
                                         lineterm=''):
            err += line + '\n'
        raise AssertionError(err)

    def _include_values(self, values):
        return is_truthy(values) and str(values).upper() != 'NO VALUES'

    def should_not_be_equal(self, first, second, msg=None, values=True):
        """Fails if the given objects are equal.

        See `Should Be Equal` for an explanation on how to override the default
        error message with ``msg`` and ``values``.
        """
        self._log_types_at_info_if_different(first, second)
        self._should_not_be_equal(first, second, msg, values)

    def _should_not_be_equal(self, first, second, msg, values):
        assert_not_equal(first, second, msg, self._include_values(values))

    def should_not_be_equal_as_integers(self, first, second, msg=None,
                                        values=True, base=None):
        """Fails if objects are equal after converting them to integers.

        See `Convert To Integer` for information how to convert integers from
        other bases than 10 using ``base`` argument or ``0b/0o/0x`` prefixes.

        See `Should Be Equal` for an explanation on how to override the default
        error message with ``msg`` and ``values``.

        See `Should Be Equal As Integers` for some usage examples.
        """
        self._log_types_at_info_if_different(first, second)
        self._should_not_be_equal(self._convert_to_integer(first, base),
                                  self._convert_to_integer(second, base),
                                  msg, values)

    def should_be_equal_as_integers(self, first, second, msg=None, values=True,
                                    base=None):
        """Fails if objects are unequal after converting them to integers.

        See `Convert To Integer` for information how to convert integers from
        other bases than 10 using ``base`` argument or ``0b/0o/0x`` prefixes.

        See `Should Be Equal` for an explanation on how to override the default
        error message with ``msg`` and ``values``.

        Examples:
        | Should Be Equal As Integers | 42   | ${42} | Error message |
        | Should Be Equal As Integers | ABCD | abcd  | base=16 |
        | Should Be Equal As Integers | 0b1011 | 11  |
        """
        self._log_types_at_info_if_different(first, second)
        self._should_be_equal(self._convert_to_integer(first, base),
                              self._convert_to_integer(second, base),
                              msg, values)

    def should_not_be_equal_as_numbers(self, first, second, msg=None,
                                       values=True, precision=6):
        """Fails if objects are equal after converting them to real numbers.

        The conversion is done with `Convert To Number` keyword using the
        given ``precision``.

        See `Should Be Equal As Numbers` for examples on how to use
        ``precision`` and why it does not always work as expected. See also
        `Should Be Equal` for an explanation on how to override the default
        error message with ``msg`` and ``values``.
        """
        self._log_types_at_info_if_different(first, second)
        first = self._convert_to_number(first, precision)
        second = self._convert_to_number(second, precision)
        self._should_not_be_equal(first, second, msg, values)

    def should_be_equal_as_numbers(self, first, second, msg=None, values=True,
                                   precision=6):
        """Fails if objects are unequal after converting them to real numbers.

        The conversion is done with `Convert To Number` keyword using the
        given ``precision``.

        Examples:
        | Should Be Equal As Numbers | ${x} | 1.1 | | # Passes if ${x} is 1.1 |
        | Should Be Equal As Numbers | 1.123 | 1.1 | precision=1  | # Passes |
        | Should Be Equal As Numbers | 1.123 | 1.4 | precision=0  | # Passes |
        | Should Be Equal As Numbers | 112.3 | 75  | precision=-2 | # Passes |

        As discussed in the documentation of `Convert To Number`, machines
        generally cannot store floating point numbers accurately. Because of
        this limitation, comparing floats for equality is problematic and
        a correct approach to use depends on the context. This keyword uses
        a very naive approach of rounding the numbers before comparing them,
        which is both prone to rounding errors and does not work very well if
        numbers are really big or small. For more information about comparing
        floats, and ideas on how to implement your own context specific
        comparison algorithm, see
        http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/.

        See `Should Not Be Equal As Numbers` for a negative version of this
        keyword and `Should Be Equal` for an explanation on how to override
        the default error message with ``msg`` and ``values``.
        """
        self._log_types_at_info_if_different(first, second)
        first = self._convert_to_number(first, precision)
        second = self._convert_to_number(second, precision)
        self._should_be_equal(first, second, msg, values)

    def should_not_be_equal_as_strings(self, first, second, msg=None, values=True):
        """Fails if objects are equal after converting them to strings.

        See `Should Be Equal` for an explanation on how to override the default
        error message with ``msg`` and ``values``.
        """
        self._log_types_at_info_if_different(first, second)
        first, second = [self._convert_to_string(i) for i in (first, second)]
        self._should_not_be_equal(first, second, msg, values)

    def should_be_equal_as_strings(self, first, second, msg=None, values=True):
        """Fails if objects are unequal after converting them to strings.

        See `Should Be Equal` for an explanation on how to override the default
        error message with ``msg`` and ``values``.

        If both arguments are multiline strings, the comparison is done using
        `multiline string comparisons`.
        """
        self._log_types_at_info_if_different(first, second)
        first, second = [self._convert_to_string(i) for i in (first, second)]
        self._should_be_equal(first, second, msg, values)

    def should_not_start_with(self, str1, str2, msg=None, values=True):
        """Fails if the string ``str1`` starts with the string ``str2``.

        See `Should Be Equal` for an explanation on how to override the default
        error message with ``msg`` and ``values``.
        """
        if str1.startswith(str2):
            raise AssertionError(self._get_string_msg(str1, str2, msg, values,
                                                      'starts with'))

    def should_start_with(self, str1, str2, msg=None, values=True):
        """Fails if the string ``str1`` does not start with the string ``str2``.

        See `Should Be Equal` for an explanation on how to override the default
        error message with ``msg`` and ``values``.
        """
        if not str1.startswith(str2):
            raise AssertionError(self._get_string_msg(str1, str2, msg, values,
                                                      'does not start with'))

    def should_not_end_with(self, str1, str2, msg=None, values=True):
        """Fails if the string ``str1`` ends with the string ``str2``.

        See `Should Be Equal` for an explanation on how to override the default
        error message with ``msg`` and ``values``.
        """
        if str1.endswith(str2):
            raise AssertionError(self._get_string_msg(str1, str2, msg, values,
                                                      'ends with'))

    def should_end_with(self, str1, str2, msg=None, values=True):
        """Fails if the string ``str1`` does not end with the string ``str2``.

        See `Should Be Equal` for an explanation on how to override the default
        error message with ``msg`` and ``values``.
        """
        if not str1.endswith(str2):
            raise AssertionError(self._get_string_msg(str1, str2, msg, values,
                                                      'does not end with'))

    def should_not_contain(self, container, item, msg=None, values=True):
        """Fails if ``container`` contains ``item`` one or more times.

        Works with strings, lists, and anything that supports Python's ``in``
        operator. See `Should Be Equal` for an explanation on how to override
        the default error message with ``msg`` and ``values``.

        Examples:
        | Should Not Contain | ${output}    | FAILED |
        | Should Not Contain | ${some list} | value  |
        """
        if item in container:
            raise AssertionError(self._get_string_msg(container, item, msg,
                                                      values, 'contains'))

    def should_contain(self, container, item, msg=None, values=True):
        """Fails if ``container`` does not contain ``item`` one or more times.

        Works with strings, lists, and anything that supports Python's ``in``
        operator. See `Should Be Equal` for an explanation on how to override
        the default error message with ``msg`` and ``values``.

        Examples:
        | Should Contain | ${output}    | PASS  |
        | Should Contain | ${some list} | value |
        """
        if item not in container:
            raise AssertionError(self._get_string_msg(container, item, msg,
                                                      values, 'does not contain'))

    def should_contain_x_times(self, item1, item2, count, msg=None):
        """Fails if ``item1`` does not contain ``item2`` ``count`` times.

        Works with strings, lists and all objects that `Get Count` works
        with. The default error message can be overridden with ``msg`` and
        the actual count is always logged.

        Examples:
        | Should Contain X Times | ${output}    | hello  | 2 |
        | Should Contain X Times | ${some list} | value  | 3 |
        """
        count = self._convert_to_integer(count)
        x = self.get_count(item1, item2)
        if not msg:
            msg = "'%s' contains '%s' %d time%s, not %d time%s." \
                    % (unic(item1), unic(item2), x, s(x), count, s(count))
        self.should_be_equal_as_integers(x, count, msg, values=False)

    def get_count(self, item1, item2):
        """Returns and logs how many times ``item2`` is found from ``item1``.

        This keyword works with Python strings and lists and all objects
        that either have ``count`` method or can be converted to Python lists.

        Example:
        | ${count} = | Get Count | ${some item} | interesting value |
        | Should Be True | 5 < ${count} < 10 |
        """
        if not hasattr(item1, 'count'):
            try:
                item1 = list(item1)
            except:
                raise RuntimeError("Converting '%s' to list failed: %s"
                                   % (item1, get_error_message()))
        count = item1.count(item2)
        self.log('Item found from the first item %d time%s' % (count, s(count)))
        return count

    def should_not_match(self, string, pattern, msg=None, values=True):
        """Fails if the given ``string`` matches the given ``pattern``.

        Pattern matching is similar as matching files in a shell, and it is
        always case-sensitive. In the pattern ``*`` matches to anything and
        ``?`` matches to any single character.

        See `Should Be Equal` for an explanation on how to override the default
        error message with ``msg`` and ``values``.
        """
        if self._matches(string, pattern):
            raise AssertionError(self._get_string_msg(string, pattern, msg,
                                                      values, 'matches'))

    def should_match(self, string, pattern, msg=None, values=True):
        """Fails unless the given ``string`` matches the given ``pattern``.

        Pattern matching is similar as matching files in a shell, and it is
        always case-sensitive. In the pattern, ``*`` matches to anything and
        ``?`` matches to any single character.

        See `Should Be Equal` for an explanation on how to override the default
        error message with ``msg`` and ``values``.
        """
        if not self._matches(string, pattern):
            raise AssertionError(self._get_string_msg(string, pattern, msg,
                                                      values, 'does not match'))

    def should_match_regexp(self, string, pattern, msg=None, values=True):
        """Fails if ``string`` does not match ``pattern`` as a regular expression.

        Regular expression check is implemented using the Python
        [http://docs.python.org/2/library/re.html|re module]. Python's regular
        expression syntax is derived from Perl, and it is thus also very
        similar to the syntax used, for example, in Java, Ruby and .NET.

        Things to note about the regexp syntax in Robot Framework test data:

        1) Backslash is an escape character in the test data, and possible
        backslashes in the pattern must thus be escaped with another backslash
        (e.g. ``\\\\d\\\\w+``).

        2) Strings that may contain special characters, but should be handled
        as literal strings, can be escaped with the `Regexp Escape` keyword.

        3) The given pattern does not need to match the whole string. For
        example, the pattern ``ello`` matches the string ``Hello world!``. If
        a full match is needed, the ``^`` and ``$`` characters can be used to
        denote the beginning and end of the string, respectively. For example,
        ``^ello$`` only matches the exact string ``ello``.

        4) Possible flags altering how the expression is parsed (e.g.
        ``re.IGNORECASE``, ``re.MULTILINE``) can be set by prefixing the
        pattern with the ``(?iLmsux)`` group like ``(?im)pattern``. The
        available flags are ``i`` (case-insensitive), ``m`` (multiline mode),
        ``s`` (dotall mode), ``x`` (verbose), ``u`` (Unicode dependent) and
        ``L`` (locale dependent).

        If this keyword passes, it returns the portion of the string that
        matched the pattern. Additionally, the possible captured groups are
        returned.

        See the `Should Be Equal` keyword for an explanation on how to override
        the default error message with the ``msg`` and ``values`` arguments.

        Examples:
        | Should Match Regexp | ${output} | \\\\d{6}   | # Output contains six numbers  |
        | Should Match Regexp | ${output} | ^\\\\d{6}$ | # Six numbers and nothing more |
        | ${ret} = | Should Match Regexp | Foo: 42 | (?i)foo: \\\\d+ |
        | ${match} | ${group1} | ${group2} = |
        | ...      | Should Match Regexp | Bar: 43 | (Foo|Bar): (\\\\d+) |
        =>
        | ${ret} = 'Foo: 42'
        | ${match} = 'Bar: 43'
        | ${group1} = 'Bar'
        | ${group2} = '43'
        """
        res = re.search(pattern, string)
        if res is None:
            raise AssertionError(self._get_string_msg(string, pattern, msg,
                                                      values, 'does not match'))
        match = res.group(0)
        groups = res.groups()
        if groups:
            return [match] + list(groups)
        return match

    def should_not_match_regexp(self, string, pattern, msg=None, values=True):
        """Fails if ``string`` matches ``pattern`` as a regular expression.

        See `Should Match Regexp` for more information about arguments.
        """
        if re.search(pattern, string) is not None:
            raise AssertionError(self._get_string_msg(string, pattern, msg,
                                                      values, 'matches'))

    def get_length(self, item):
        """Returns and logs the length of the given item as an integer.

        The item can be anything that has a length, for example, a string,
        a list, or a mapping. The keyword first tries to get the length with
        the Python function ``len``, which calls the  item's ``__len__`` method
        internally. If that fails, the keyword tries to call the item's
        possible ``length`` and ``size`` methods directly. The final attempt is
        trying to get the value of the item's ``length`` attribute. If all
        these attempts are unsuccessful, the keyword fails.

        Examples:
        | ${length} = | Get Length    | Hello, world! |        |
        | Should Be Equal As Integers | ${length}     | 13     |
        | @{list} =   | Create List   | Hello,        | world! |
        | ${length} = | Get Length    | ${list}       |        |
        | Should Be Equal As Integers | ${length}     | 2      |

        See also `Length Should Be`, `Should Be Empty` and `Should Not Be
        Empty`.
        """
        length = self._get_length(item)
        self.log('Length is %d' % length)
        return length

    def _get_length(self, item):
        try:
            return len(item)
        except RERAISED_EXCEPTIONS:
            raise
        except:
            try:
                return item.length()
            except RERAISED_EXCEPTIONS:
                raise
            except:
                try:
                    return item.size()
                except RERAISED_EXCEPTIONS:
                    raise
                except:
                    try:
                        return item.length
                    except RERAISED_EXCEPTIONS:
                        raise
                    except:
                        raise RuntimeError("Could not get length of '%s'." % item)

    def length_should_be(self, item, length, msg=None):
        """Verifies that the length of the given item is correct.

        The length of the item is got using the `Get Length` keyword. The
        default error message can be overridden with the ``msg`` argument.
        """
        length = self._convert_to_integer(length)
        actual = self.get_length(item)
        if actual != length:
            raise AssertionError(msg or "Length of '%s' should be %d but is %d."
                                        % (item, length, actual))

    def should_be_empty(self, item, msg=None):
        """Verifies that the given item is empty.

        The length of the item is got using the `Get Length` keyword. The
        default error message can be overridden with the ``msg`` argument.
        """
        if self.get_length(item) > 0:
            raise AssertionError(msg or "'%s' should be empty." % item)

    def should_not_be_empty(self, item, msg=None):
        """Verifies that the given item is not empty.

        The length of the item is got using the `Get Length` keyword. The
        default error message can be overridden with the ``msg`` argument.
        """
        if self.get_length(item) == 0:
            raise AssertionError(msg or "'%s' should not be empty." % item)

    def _get_string_msg(self, str1, str2, msg, values, delim):
        default = "'%s' %s '%s'" % (unic(str1), delim, unic(str2))
        if not msg:
            msg = default
        elif self._include_values(values):
            msg = '%s: %s' % (msg, default)
        return msg


class _Variables(_BuiltInBase):

    def get_variables(self, no_decoration=False):
        """Returns a dictionary containing all variables in the current scope.

        Variables are returned as a special dictionary that allows accessing
        variables in space, case, and underscore insensitive manner similarly
        as accessing variables in the test data. This dictionary supports all
        same operations as normal Python dictionaries and, for example,
        Collections library can be used to access or modify it. Modifying the
        returned dictionary has no effect on the variables available in the
        current scope.

        By default variables are returned with ``${}``, ``@{}`` or ``&{}``
        decoration based on variable types. Giving a true value (see `Boolean
        arguments`) to the optional argument ``no_decoration`` will return
        the variables without the decoration. This option is new in Robot
        Framework 2.9.

        Example:
        | ${example_variable} =         | Set Variable | example value         |
        | ${variables} =                | Get Variables |                      |
        | Dictionary Should Contain Key | ${variables} | \\${example_variable} |
        | Dictionary Should Contain Key | ${variables} | \\${ExampleVariable}  |
        | Set To Dictionary             | ${variables} | \\${name} | value     |
        | Variable Should Not Exist     | \\${name}    |           |           |
        | ${no decoration} =            | Get Variables | no_decoration=Yes |
        | Dictionary Should Contain Key | ${no decoration} | example_variable |

        Note: Prior to Robot Framework 2.7.4 variables were returned as
        a custom object that did not support all dictionary methods.
        """
        return self._variables.as_dict(decoration=is_falsy(no_decoration))

    @run_keyword_variant(resolve=0)
    def get_variable_value(self, name, default=None):
        """Returns variable value or ``default`` if the variable does not exist.

        The name of the variable can be given either as a normal variable name
        (e.g. ``${NAME}``) or in escaped format (e.g. ``\\${NAME}``). Notice
        that the former has some limitations explained in `Set Suite Variable`.

        Examples:
        | ${x} = | Get Variable Value | ${a} | default |
        | ${y} = | Get Variable Value | ${a} | ${b}    |
        | ${z} = | Get Variable Value | ${z} |         |
        =>
        | ${x} gets value of ${a} if ${a} exists and string 'default' otherwise
        | ${y} gets value of ${a} if ${a} exists and value of ${b} otherwise
        | ${z} is set to Python None if it does not exist previously

        See `Set Variable If` for another keyword to set variables dynamically.
        """
        try:
            return self._variables[self._get_var_name(name)]
        except DataError:
            return self._variables.replace_scalar(default)

    def log_variables(self, level='INFO'):
        """Logs all variables in the current scope with given log level."""
        variables = self.get_variables()
        for name in sorted(variables, key=lambda s: s[2:-1].lower()):
            msg = format_assign_message(name, variables[name], cut_long=False)
            self.log(msg, level)

    @run_keyword_variant(resolve=0)
    def variable_should_exist(self, name, msg=None):
        """Fails unless the given variable exists within the current scope.

        The name of the variable can be given either as a normal variable name
        (e.g. ``${NAME}``) or in escaped format (e.g. ``\\${NAME}``). Notice
        that the former has some limitations explained in `Set Suite Variable`.

        The default error message can be overridden with the ``msg`` argument.

        See also `Variable Should Not Exist` and `Keyword Should Exist`.
        """
        name = self._get_var_name(name)
        msg = self._variables.replace_string(msg) if msg \
            else "Variable %s does not exist." % name
        try:
            self._variables[name]
        except DataError:
            raise AssertionError(msg)

    @run_keyword_variant(resolve=0)
    def variable_should_not_exist(self, name, msg=None):
        """Fails if the given variable exists within the current scope.

        The name of the variable can be given either as a normal variable name
        (e.g. ``${NAME}``) or in escaped format (e.g. ``\\${NAME}``). Notice
        that the former has some limitations explained in `Set Suite Variable`.

        The default error message can be overridden with the ``msg`` argument.

        See also `Variable Should Exist` and `Keyword Should Exist`.
        """
        name = self._get_var_name(name)
        msg = self._variables.replace_string(msg) if msg \
            else "Variable %s exists." % name
        try:
            self._variables[name]
        except DataError:
            pass
        else:
            raise AssertionError(msg)

    def replace_variables(self, text):
        """Replaces variables in the given text with their current values.

        If the text contains undefined variables, this keyword fails.
        If the given ``text`` contains only a single variable, its value is
        returned as-is and it can be any object. Otherwise this keyword
        always returns a string.

        Example:

        The file ``template.txt`` contains ``Hello ${NAME}!`` and variable
        ``${NAME}`` has the value ``Robot``.

        | ${template} =   | Get File          | ${CURDIR}/template.txt |
        | ${message} =    | Replace Variables | ${template}            |
        | Should Be Equal | ${message}        | Hello Robot!           |
        """
        return self._variables.replace_scalar(text)

    def set_variable(self, *values):
        """Returns the given values which can then be assigned to a variables.

        This keyword is mainly used for setting scalar variables.
        Additionally it can be used for converting a scalar variable
        containing a list to a list variable or to multiple scalar variables.
        It is recommended to use `Create List` when creating new lists.

        Examples:
        | ${hi} =   | Set Variable | Hello, world! |
        | ${hi2} =  | Set Variable | I said: ${hi} |
        | ${var1}   | ${var2} =    | Set Variable | Hello | world |
        | @{list} = | Set Variable | ${list with some items} |
        | ${item1}  | ${item2} =   | Set Variable  | ${list with 2 items} |

        Variables created with this keyword are available only in the
        scope where they are created. See `Set Global Variable`,
        `Set Test Variable` and `Set Suite Variable` for information on how to
        set variables so that they are available also in a larger scope.
        """
        if len(values) == 0:
            return ''
        elif len(values) == 1:
            return values[0]
        else:
            return list(values)

    @run_keyword_variant(resolve=0)
    def set_test_variable(self, name, *values):
        """Makes a variable available everywhere within the scope of the current test.

        Variables set with this keyword are available everywhere within the
        scope of the currently executed test case. For example, if you set a
        variable in a user keyword, it is available both in the test case level
        and also in all other user keywords used in the current test. Other
        test cases will not see variables set with this keyword.

        See `Set Suite Variable` for more information and examples.
        """
        name = self._get_var_name(name)
        value = self._get_var_value(name, values)
        self._variables.set_test(name, value)
        self._log_set_variable(name, value)

    @run_keyword_variant(resolve=0)
    def set_suite_variable(self, name, *values):
        """Makes a variable available everywhere within the scope of the current suite.

        Variables set with this keyword are available everywhere within the
        scope of the currently executed test suite. Setting variables with this
        keyword thus has the same effect as creating them using the Variable
        table in the test data file or importing them from variable files.

        Possible child test suites do not see variables set with this keyword
        by default. Starting from Robot Framework 2.9, that can be controlled
        by using ``children=




© 2015 - 2025 Weber Informatics LLC | Privacy Policy