robot frame 源码分析


robot-frame的application 流程

0、argumentparser
   ArgumentParser
   ArgLimitValidator
   ArgFileParser

1、
robot.utils.application:
 Application
   execute_cli
      options, arguments = self._parse_arguments(cli_arguments)
      rc = self._execute(arguments, options)

2、
robot.RobotFramework(Application):
    Application.init
    def main(self, datasources, **options):
        settings = RobotSettings(options)
        LOGGER.register_console_logger(**settings.console_output_config)
        LOGGER.info('Settings:\n%s' % unic(settings))
        builder = TestSuiteBuilder(settings['SuiteNames'],
                                   extension=settings.extension,
                                   rpa=settings.rpa)
        suite = builder.build(*datasources)
        settings.rpa = builder.rpa
        suite.configure(**settings.suite_config)
        if settings.pre_run_modifiers:
            suite.visit(ModelModifier(settings.pre_run_modifiers,
                                      settings.run_empty_suite, LOGGER))
                                **** testsuit: visitor.visit_suite(self)
        with pyloggingconf.robot_handler_enabled(settings.log_level):
            old_max_error_lines = text.MAX_ERROR_LINES
            text.MAX_ERROR_LINES = settings.max_error_lines
            try:
                result = suite.run(settings)
                                ****model.py TestSuite
            finally:
                text.MAX_ERROR_LINES = old_max_error_lines
            LOGGER.info("Tests execution ended. Statistics:\n%s"
                        % result.suite.stat_message)
            if settings.log or settings.report or settings.xunit:
                writer = ResultWriter(settings.output if settings.log
                                      else result)
                writer.write_results(settings.get_rebot_settings())
        return result.return_code
run_cli
run: RobotFramework().execute(*tests, **options)
if __name__ == '__main__':
    run_cli(sys.argv[1:])
3、
robot.robot:
 Rebot(RobotFramework):
    Application.__init__
    main: RebotSettings ResultWriter
 rebot_cli: Rebot().execute_cli(arguments, exit=exit)
 rebot: Rebot().execute(*outputs, **options)
if __name__ == '__main__':
    rebot_cli(sys.argv[1:])

4、
robot.running.builder:
    TestSuiteBuilder:
        __init__
            builder = StepBuilder()
            self._build_steps = builder.build_steps
            self._build_step = builder.build_step
        build
            root = TestSuite()
            _parse_and_build
                _build_suite
                    defaults = TestDefaults(data.setting_table, parent_defaults)
                    suite = TestSuite(name=data.name,
                                      source=data.source,
                                      doc=unic(data.setting_table.doc),
                                      metadata=self._get_metadata(data.setting_table))
                        self._build_setup(suite, data.setting_table.suite_setup)
                        self._build_teardown(suite, data.setting_table.suite_teardown)
                        for test_data in data.testcase_table.tests:
                            self._build_test(suite, test_data, defaults)
                                    values = defaults.get_test_values(data)
                                    template = self._get_template(values.template)
                                    test = suite.tests.create(name=data.name,
                                                              doc=unic(data.doc),
                                                              tags=values.tags.value,
                                                              template=template,
                                                              timeout=self._get_timeout(values.timeout))
                                    self._build_setup(test, values.setup)
                                    self._build_steps(test, data, template)
                                    self._build_teardown(test, values.teardown)
                        for child in data.children:
                            suite.suites.append(self._build_suite(child, defaults))
                        suite.rpa = self.rpa
                        ResourceFileBuilder().build(data, target=suite.resource)
                def _get_timeout(self, timeout):
                    return (timeout.value, timeout.message) if timeout else None

                def _get_template(self, template):
                    return unic(template) if template.is_active() else None

                def _build_setup(self, parent, data):
                    if data.is_active():
                        self._build_step(parent, data, kw_type='setup')

                def _build_teardown(self, parent, data):
                    if data.is_active():
                        self._build_step(parent, data, kw_type='teardown')
    ResourceFileBuilder:
        __init__
            builder = StepBuilder()
            self._build_steps = builder.build_steps
            self._build_step = builder.build_step
        build:
            _import_resource_if_needed
                ResourceData(path_or_data).populate(), path_or_data
            _build_imports
                target.imports.create(type=data.type,
                                  name=data.name,
                                  args=tuple(data.args),
                                  alias=data.alias)
            _build_variables
                target.variables.create(name=data.name, value=data.value)
            _build_keyword
                 target.keywords.create(name=data.name,
                                    args=tuple(data.args),
                                    doc=unic(data.doc),
                                    tags=tuple(data.tags),
                                    return_=tuple(data.return_),
                                    timeout=self._get_timeout(data.timeout))
                                                (timeout.value, timeout.message) if timeout else None
                self._build_steps(kw, data)
                if data.teardown.is_active():
                    self._build_step(kw, data.teardown, kw_type='teardown')
    StepBuilder:
        build_steps
        build_step
          _build
             _build_normal_step
                Keyword(name=data.name,
                       args=tuple(data.args),
                       assign=tuple(data.assign),
                       type=kw_type)
             _build_templated_step
                _format_template
                     for before, variable, after in iterator:
                        temp.extend([before, args.pop(0)])
                     temp.append(after)

robot.reporting.ResultWriter


from .model import ForLoop, Keyword, ResourceFile, TestSuite

ModelObject:
    copy
    deepcopy
    __setstate__

ItemList

Keyword(ModelObject):
    __slots__ = ['_name', 'doc', 'args', 'assign', 'timeout', 'type',
                 '_sort_key', '_next_child_sort_key']
    KEYWORD_TYPE = 'kw'         #: Normal keyword :attr:`type`.
    SETUP_TYPE = 'setup'        #: Setup :attr:`type`.
    TEARDOWN_TYPE = 'teardown'  #: Teardown :attr:`type`.
    FOR_LOOP_TYPE = 'for'       #: For loop :attr:`type`.
    FOR_ITEM_TYPE = 'foritem'   #: Single for loop iteration :attr:`type`.
    keyword_class = None        #: Internal usage only.
    message_class = Message     #: Internal usage only.

    def __init__(self, name='', doc='', args=(), assign=(), tags=(),
                 timeout=None, type=KEYWORD_TYPE):
        self.parent = None
        self._name = name
        self.doc = doc
        self.args = args      #: Keyword arguments as a list of strings.
        self.assign = assign  #: Assigned variables as a list of strings.
        self.tags = tags
        self.timeout = timeout
        #: Keyword type as a string. The value is either :attr:`KEYWORD_TYPE`,
        #: :attr:`SETUP_TYPE`, :attr:`TEARDOWN_TYPE`, :attr:`FOR_LOOP_TYPE` or
        #: :attr:`FOR_ITEM_TYPE` constant defined on the class level.
        self.type = type
        self.messages = None
        self.keywords = None
        self._sort_key = -1
        self._next_child_sort_key = 0
    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, name):
        self._name = name

    @setter
    def parent(self, parent):
        """Parent test suite, test case or keyword."""
        if parent and parent is not self.parent:
            self._sort_key = getattr(parent, '_child_sort_key', -1)
        return parent

    @property
    def _child_sort_key(self):
        self._next_child_sort_key += 1
        return self._next_child_sort_key

    @setter
    def tags(self, tags):
        """Keyword tags as a :class:`~.model.tags.Tags` object."""
        return Tags(tags)

    @setter
    def keywords(self, keywords):
        """Child keywords as a :class:`~.Keywords` object."""
        return Keywords(self.keyword_class or self.__class__, self, keywords)

    @setter
    def messages(self, messages):
        """Messages as a :class:`~.model.message.Messages` object."""
        return Messages(self.message_class, self, messages)

    @property
    def children(self):
        """Child :attr:`keywords` and :attr:`messages` in creation order."""
        # It would be cleaner to store keywords/messages in same `children`
        # list and turn `keywords` and `messages` to properties that pick items
        # from it. That would require bigger changes to the model, though.
        return sorted(chain(self.keywords, self.messages),
                      key=attrgetter('_sort_key'))

    @property
    def id(self):
        """Keyword id in format like ``s1-t3-k1``.

        See :attr:`TestSuite.id <robot.model.testsuite.TestSuite.id>` for
        more information.
        """
        if not self.parent:
            return 'k1'
        return '%s-k%d' % (self.parent.id, self.parent.keywords.index(self)+1)

    def visit(self, visitor):
        """:mod:`Visitor interface <robot.model.visitor>` entry-point."""
        visitor.visit_keyword(self)

Keywords(ItemList)
    __slots__ = []

    def __init__(self, keyword_class=Keyword, parent=None, keywords=None):
        ItemList.__init__(self, keyword_class, {'parent': parent}, keywords)

    @property
    def setup(self):
        """Keyword used as the setup or ``None`` if no setup.

        Can be set to a new setup keyword or ``None`` since RF 3.0.1.
        """
        return self[0] if (self and self[0].type == 'setup') else None

    @setup.setter
    def setup(self, kw):
        if kw is not None and kw.type != 'setup':
            raise TypeError("Setup keyword type must be 'setup', "
                            "got '%s'." % kw.type)
        if self.setup is not None:
            self.pop(0)
        if kw is not None:
            self.insert(0, kw)

    @property
    def teardown(self):
        """Keyword used as the teardown or ``None`` if no teardown.

        Can be set to a new teardown keyword or ``None`` since RF 3.0.1.
        """
        return self[-1] if (self and self[-1].type == 'teardown') else None

    @teardown.setter
    def teardown(self, kw):
        if kw is not None and kw.type != 'teardown':
            raise TypeError("Teardown keyword type must be 'teardown', "
                            "got '%s'." % kw.type)
        if self.teardown is not None:
            self.pop()
        if kw is not None:
            self.append(kw)

    @property
    def all(self):
        """Iterates over all keywords, including setup and teardown."""
        return self

    @property
    def normal(self):
        """Iterates over normal keywords, omitting setup and teardown."""
        kws = [kw for kw in self if kw.type not in ('setup', 'teardown')]
        return Keywords(self._item_class, self._common_attrs['parent'], kws)

    def __setitem__(self, index, item):
        old = self[index]
        ItemList.__setitem__(self, index, item)
        self[index]._sort_key = old._sort_key


TestCase:
     def tags(self, tags):
        """Test tags as a :class:`~.model.tags.Tags` object."""
        return Tags(tags)

    @setter
    def keywords(self, keywords):
        """Keywords as a :class:`~.Keywords` object.

        Contains also possible setup and teardown keywords.
        """
        return Keywords(self.keyword_class, self, keywords)

class TestCases(ItemList):
    __slots__ = []

    def __init__(self, test_class=TestCase, parent=None, tests=None):
        ItemList.__init__(self, test_class, {'parent': parent}, tests)

    def _check_type_and_set_attrs(self, *tests):
        tests = ItemList._check_type_and_set_attrs(self, *tests)
        for test in tests:
            for visitor in test.parent._visitors:
                test.visit(visitor)
        return tests

TestSuite:
    def suites(self, suites):
        """Child suites as a :class:`~.TestSuites` object."""
        return TestSuites(self.__class__, self, suites)
    @setter
    def tests(self, tests):
        """Tests as a :class:`~.TestCases` object."""
        return TestCases(self.test_class, self, tests)

    @setter
    def keywords(self, keywords):
        """Suite setup and teardown as a :class:`~.Keywords` object."""
        return Keywords(self.keyword_class, self, keywords)
TestSuites(ItemList):
    __slots__ = []

    def __init__(self, suite_class=TestSuite, parent=None, suites=None):
        ItemList.__init__(self, suite_class, {'parent': parent}, suites)

model.py

class Keyword(model.Keyword):
    """Represents a single executable keyword.

    These keywords never have child keywords or messages. The actual keyword
    that is executed depends on the context where this model is executed.

    See the base class for documentation of attributes not documented here.
    """
    __slots__ = []
    message_class = None  #: Internal usage only.

    def run(self, context):
        """Execute the keyword.

        Typically called internally by :meth:`TestSuite.run`.
        """
        return StepRunner(context).run_step(self)

class ForLoop(Keyword):
    """Represents a for loop in test data.

    Contains keywords in the loop body as child :attr:`keywords`.
    """
    __slots__ = ['flavor']
    keyword_class = Keyword  #: Internal usage only.

    def __init__(self, variables, values, flavor):
        Keyword.__init__(self, assign=variables, args=values,
                         type=Keyword.FOR_LOOP_TYPE)
        self.flavor = flavor

    @property
    def variables(self):
        return self.assign

    @property
    def values(self):
        return self.args

class TestCase(model.TestCase):
    """Represents a single executable test case.

    See the base class for documentation of attributes not documented here.
    """
    __slots__ = ['template']
    keyword_class = Keyword  #: Internal usage only.

    def __init__(self, name='', doc='', tags=None, timeout=None, template=None):
        model.TestCase.__init__(self, name, doc, tags, timeout)
        #: Name of the keyword that has been used as template
        #: when building the test. ``None`` if no is template used.
        self.template = template

    @setter
    def timeout(self, timeout):
        """Test timeout as a :class:`Timeout` instance or ``None``.

        This attribute is likely to change in the future.
        """
        return Timeout(*timeout) if timeout else None


class TestSuite(model.TestSuite):
    """Represents a single executable test suite.

    See the base class for documentation of attributes not documented here.
    """
    __slots__ = ['resource']
    test_class = TestCase    #: Internal usage only.
    keyword_class = Keyword  #: Internal usage only.

    def __init__(self,  name='', doc='', metadata=None, source=None, rpa=False):
        model.TestSuite.__init__(self, name, doc, metadata, source, rpa)
        #: :class:`ResourceFile` instance containing imports, variables and
        #: keywords the suite owns. When data is parsed from the file system,
        #: this data comes from the same test case file that creates the suite.
        self.resource = ResourceFile(source=source)

    def configure(self, randomize_suites=False, randomize_tests=False,
                  randomize_seed=None, **options):
        """A shortcut to configure a suite using one method call.

        Can only be used with the root test suite.

        :param randomize_xxx: Passed to :meth:`randomize`.
        :param options: Passed to
            :class:`~robot.model.configurer.SuiteConfigurer` that will then
            set suite attributes, call :meth:`filter`, etc. as needed.

        Example::

            suite.configure(included_tags=['smoke'],
                            doc='Smoke test results.')
        """
        model.TestSuite.configure(self, **options)
        self.randomize(randomize_suites, randomize_tests, randomize_seed)

    def randomize(self, suites=True, tests=True, seed=None):
        """Randomizes the order of suites and/or tests, recursively.

        :param suites: Boolean controlling should suites be randomized.
        :param tests: Boolean controlling should tests be randomized.
        :param seed: Random seed. Can be given if previous random order needs
            to be re-created. Seed value is always shown in logs and reports.
        """
        self.visit(Randomizer(suites, tests, seed))

    def run(self, settings=None, **options):
        """Executes the suite based based the given ``settings`` or ``options``.

        :param settings: :class:`~robot.conf.settings.RobotSettings` object
            to configure test execution.
        :param options: Used to construct new
            :class:`~robot.conf.settings.RobotSettings` object if ``settings``
            are not given.
        :return: :class:`~robot.result.executionresult.Result` object with
            information about executed suites and tests.

        If ``options`` are used, their names are the same as long command line
        options except without hyphens. Some options are ignored (see below),
        but otherwise they have the same semantics as on the command line.
        Options that can be given on the command line multiple times can be
        passed as lists like ``variable=['VAR1:value1', 'VAR2:value2']``.
        If such an option is used only once, it can be given also as a single
        string like ``variable='VAR:value'``.

        Additionally listener option allows passing object directly instead of
        listener name, e.g. ``run('tests.robot', listener=Listener())``.

        To capture stdout and/or stderr streams, pass open file objects in as
        special keyword arguments ``stdout`` and ``stderr``, respectively.

        Only options related to the actual test execution have an effect.
        For example, options related to selecting or modifying test cases or
        suites (e.g. ``--include``, ``--name``, ``--prerunmodifier``) or
        creating logs and reports are silently ignored. The output XML
        generated as part of the execution can be configured, though. This
        includes disabling it with ``output=None``.

        Example::

            stdout = StringIO()
            result = suite.run(variable='EXAMPLE:value',
                               critical='regression',
                               output='example.xml',
                               exitonfailure=True,
                               stdout=stdout)
            print result.return_code

        To save memory, the returned
        :class:`~robot.result.executionresult.Result` object does not
        have any information about the executed keywords. If that information
        is needed, the created output XML file needs to be read  using the
        :class:`~robot.result.resultbuilder.ExecutionResult` factory method.

        See the :mod:`package level <robot.running>` documentation for
        more examples, including how to construct executable test suites and
        how to create logs and reports based on the execution results.

        See the :func:`robot.run <robot.run.run>` function for a higher-level
        API for executing tests in files or directories.
        """
        from .namespace import IMPORTER
        from .signalhandler import STOP_SIGNAL_MONITOR
        from .runner import Runner

        with LOGGER:
            if not settings:
                settings = RobotSettings(options)
                LOGGER.register_console_logger(**settings.console_output_config)
            with pyloggingconf.robot_handler_enabled(settings.log_level):
                with STOP_SIGNAL_MONITOR:
                    IMPORTER.reset()
                    output = Output(settings)
                    runner = Runner(output, settings)
                               ****
                    self.visit(runner)
                output.close(runner.result)
        return runner.result


class Variable(object):

    def __init__(self, name, value, source=None):
        self.name = name
        self.value = value
        self.source = source

    def report_invalid_syntax(self, message, level='ERROR'):
        LOGGER.write("Error in file '%s': Setting variable '%s' failed: %s"
                     % (self.source or '<unknown>', self.name, message), level)


class Timeout(object):

    def __init__(self, value, message=None):
        self.value = value
        self.message = message

    def __str__(self):
        return self.value


class ResourceFile(object):

    def __init__(self, doc='', source=None):
        self.doc = doc
        self.source = source
        self.imports = []
        self.keywords = []
        self.variables = []

    @setter
    def imports(self, imports):
        return model.Imports(self.source, imports)

    @setter
    def keywords(self, keywords):
        return model.ItemList(UserKeyword, items=keywords)

    @setter
    def variables(self, variables):
        return model.ItemList(Variable, {'source': self.source}, items=variables)


class UserKeyword(object):

    def __init__(self, name, args=(), doc='', tags=(), return_=None, timeout=None):
        self.name = name
        self.args = args
        self.doc = doc
        self.tags = tags
        self.return_ = return_ or ()
        self.timeout = timeout
        self.keywords = []

    @setter
    def keywords(self, keywords):
        return model.Keywords(Keyword, self, keywords)

    @setter
    def timeout(self, timeout):
        """Keyword timeout as a :class:`Timeout` instance or ``None``."""
        return Timeout(*timeout) if timeout else None

    @setter
    def tags(self, tags):
        return model.Tags(tags)

StepRunner
       def __init__(self, context, templated=False):
        self._context = context
        self._templated = bool(templated)

    def run_steps(self, steps):
        errors = []
        for step in steps:
            try:
                self.run_step(step)
            except ExecutionPassed as exception:
                exception.set_earlier_failures(errors)
                raise exception
            except ExecutionFailed as exception:
                errors.extend(exception.get_errors())
                if not exception.can_continue(self._context.in_teardown,
                                              self._templated,
                                              self._context.dry_run):
                    break
        if errors:
            raise ExecutionFailures(errors)

    def run_step(self, step, name=None):
        context = self._context
        if step.type == step.FOR_LOOP_TYPE:
            runner = ForRunner(context, self._templated, step.flavor)
            return runner.run(step)
        runner = context.get_runner(name or step.name)
        if context.dry_run:
            return runner.dry_run(step, context)
        return runner.run(step, context)

Runner:
     def _run_setup_or_teardown(self, data):
        if not data:
            return None
        try:
            name = self._variables.replace_string(data.name)
        except DataError as err:
            if self._settings.dry_run:
                return None
            return err
        if name.upper() in ('', 'NONE'):
            return None
        try:
            StepRunner(self._context).run_step(data, name=name)
        except ExecutionStatus as err:
            return err

from .steprunner import StepRunner

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值